"""This module contains the AOKI endpoints (views)."""from__future__importannotationsimportbase64fromtypingimportTYPE_CHECKING,Anyfromcryptographyimportx509fromcryptography.hazmat.primitivesimporthashesfromcryptography.hazmat.primitives.asymmetricimportec,padding,rsafromdjango.httpimportJsonResponsefromdjango.viewsimportViewfrompki.models.credentialimportCredentialModel,IDevIDReferenceModelfrompki.models.truststoreimportActiveTrustpointTlsServerCredentialModelfrompki.util.idevidimportIDevIDAuthenticationError,IDevIDAuthenticatorfrompki.util.x509importApacheTLSClientCertExtractor,ClientCertificateAuthenticationErrorfromtrustpoint_core.oidimportAlgorithmIdentifierfromtrustpoint.loggerimportLoggerMixinfromtrustpoint.views.baseimportLoggedHttpResponseifTYPE_CHECKING:fromdjango.httpimportHttpRequest
[docs]classAokiServiceMixin:"""Mixin for AOKI functionality."""@staticmethod
[docs]defget_idevid_owner_san_uri(idevid_cert:x509.Certificate)->str:"""Get the Owner ID SAN URI corresponding to a IDevID certificate. Formatted as 'dev-owner:<IDevID_Subj_SN>.<IDevID_x509_SN>.<IDevID_SHA256_Fingerpr>' """try:sn_b=idevid_cert.subject.get_attributes_for_oid(x509.NameOID.SERIAL_NUMBER)[0].valueidevid_subj_sn=sn_b.decode()ifisinstance(sn_b,bytes)elsesn_bexcept(ValueError,IndexError):idevid_subj_sn='_'idevid_x509_sn=hex(idevid_cert.serial_number)[2:].zfill(16)idevid_sha256_fingerprint=idevid_cert.fingerprint(hashes.SHA256()).hex()returnf'dev-owner:{idevid_subj_sn}.{idevid_x509_sn}.{idevid_sha256_fingerprint}'
@staticmethod
[docs]defget_owner_credential(idevid_cert:x509.Certificate)->CredentialModel|None:"""Get the Device Owner ID credential corresponding to a IDevID cert, or None if it does not exist in the DB. This does not perform any authentication or validation of the IDevID certificate! Use IDevIDAuthenticator first. """idevid_san_uri=AokiServiceMixin.get_idevid_owner_san_uri(idevid_cert)owner_cred_ref=IDevIDReferenceModel.objects.filter(idevid_ref=idevid_san_uri).first()ifnotowner_cred_ref:returnNoneowner_cred=owner_cred_ref.dev_owner_idreturnowner_cred.credential
[docs]classAokiInitializationRequestView(AokiServiceMixin,LoggerMixin,View):"""View for handling AOKI initialization requests."""
[docs]defget(self,request:HttpRequest,*args:Any,**kwargs:Any)->LoggedHttpResponse|JsonResponse:"""Handle GET requests for AOKI initialization."""delargs,kwargs# Unusedtls_cert=ActiveTrustpointTlsServerCredentialModel.objects.first()ifnottls_cert:returnLoggedHttpResponse('No TLS server certificate available. Are you on the development server?',status=500)try:client_cert,intermediary_cas=ApacheTLSClientCertExtractor.get_client_cert_as_x509(request)exceptClientCertificateAuthenticationError:returnLoggedHttpResponse('No valid TLS client certificate provided.',status=401)try:domain,_idevid_subj_sn=IDevIDAuthenticator.authenticate_idevid_from_x509_no_device(client_cert,intermediary_cas,domain=None)exceptIDevIDAuthenticationErrorase:returnLoggedHttpResponse(f'IDevID authentication failed: {e}',status=403)owner_cred=self.get_owner_credential(client_cert)ifnotowner_cred:returnLoggedHttpResponse('No DevOwnerID present for this IDevID.',status=422)owner_pk=owner_cred.get_private_key()owner_id_cert=owner_cred.certificateaoki_init_response={'aoki-init':{'version':'1.0','enrollment-info':{'protocols':[{'protocol':'EST','url':f'/.well-known/est/{domain.unique_name}/domaincredential/'}]},'owner-id-cert':owner_id_cert.get_certificate_serializer().as_pem().decode(),'tls-truststore':tls_cert.credential.certificate.get_certificate_serializer().as_pem().decode()},}resp=JsonResponse(aoki_init_response)ifisinstance(owner_pk,rsa.RSAPrivateKey):owner_signature=owner_pk.sign(data=resp.content,padding=padding.PKCS1v15(),algorithm=hashes.SHA256(),)signature_algo=AlgorithmIdentifier.RSA_SHA256.dotted_stringelifisinstance(owner_pk,ec.EllipticCurvePrivateKey):owner_signature=owner_pk.sign(data=resp.content,signature_algorithm=ec.ECDSA(hashes.SHA256()),)signature_algo=AlgorithmIdentifier.ECDSA_SHA256.dotted_stringelse:exc_msg=f'Unsupported private key type: {type(owner_pk)} for AOKI owner signing.'raiseTypeError(exc_msg)resp.headers['AOKI-Signature']=base64.b64encode(owner_signature).decode()resp.headers['AOKI-Signature-Algorithm']=signature_algoself.logger.info(resp.content)returnresp