"""passbook crypto models""" from binascii import hexlify from hashlib import md5 from typing import Optional from uuid import uuid4 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.x509 import Certificate, load_pem_x509_certificate from django.db import models from django.utils.translation import gettext_lazy as _ from passbook.lib.models import CreatedUpdatedModel class CertificateKeyPair(CreatedUpdatedModel): """CertificateKeyPair that can be used for signing or encrypting if `key_data` is set, otherwise it can be used to verify remote data.""" kp_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) name = models.TextField() certificate_data = models.TextField(help_text=_("PEM-encoded Certificate data")) key_data = models.TextField( help_text=_( "Optional Private Key. If this is set, you can use this keypair for encryption." ), blank=True, default="", ) _cert: Optional[Certificate] = None _private_key: Optional[RSAPrivateKey] = None _public_key: Optional[RSAPublicKey] = None @property def certificate(self) -> Certificate: """Get python cryptography Certificate instance""" if not self._cert: self._cert = load_pem_x509_certificate( self.certificate_data.encode("utf-8"), default_backend() ) return self._cert @property def public_key(self) -> Optional[RSAPublicKey]: """Get public key of the private key""" if not self._public_key: self._public_key = self.private_key.public_key() return self._public_key @property def private_key(self) -> Optional[RSAPrivateKey]: """Get python cryptography PrivateKey instance""" if not self._private_key: self._private_key = load_pem_private_key( str.encode("\n".join([x.strip() for x in self.key_data.split("\n")])), password=None, backend=default_backend(), ) return self._private_key @property def fingerprint(self) -> str: """Get SHA256 Fingerprint of certificate_data""" return hexlify(self.certificate.fingerprint(hashes.SHA256()), ":").decode( "utf-8" ) @property def kid(self): """Get Key ID used for JWKS""" return "{0}".format( md5(self.key_data.encode("utf-8")).hexdigest() # nosec if self.key_data else "" ) def __str__(self) -> str: return f"Certificate-Key Pair {self.name} {self.fingerprint}" class Meta: verbose_name = _("Certificate-Key Pair") verbose_name_plural = _("Certificate-Key Pairs")