2020-03-03 22:35:25 +00:00
|
|
|
"""passbook crypto models"""
|
|
|
|
from binascii import hexlify
|
2020-08-19 08:32:44 +00:00
|
|
|
from hashlib import md5
|
2020-03-03 22:35:25 +00:00
|
|
|
from typing import Optional
|
2020-05-20 07:17:06 +00:00
|
|
|
from uuid import uuid4
|
2020-03-03 22:35:25 +00:00
|
|
|
|
|
|
|
from cryptography.hazmat.backends import default_backend
|
|
|
|
from cryptography.hazmat.primitives import hashes
|
2020-08-19 08:32:44 +00:00
|
|
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
|
2020-03-04 18:43:18 +00:00
|
|
|
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
2020-03-03 22:35:25 +00:00
|
|
|
from cryptography.x509 import Certificate, load_pem_x509_certificate
|
|
|
|
from django.db import models
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
2020-05-20 07:17:06 +00:00
|
|
|
from passbook.lib.models import CreatedUpdatedModel
|
2020-03-03 22:35:25 +00:00
|
|
|
|
|
|
|
|
2020-05-20 07:17:06 +00:00
|
|
|
class CertificateKeyPair(CreatedUpdatedModel):
|
2020-03-03 22:35:25 +00:00
|
|
|
"""CertificateKeyPair that can be used for signing or encrypting if `key_data`
|
|
|
|
is set, otherwise it can be used to verify remote data."""
|
|
|
|
|
2020-05-20 07:17:06 +00:00
|
|
|
kp_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
|
|
|
|
2020-03-03 22:35:25 +00:00
|
|
|
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
|
2020-08-19 08:32:44 +00:00
|
|
|
_private_key: Optional[RSAPrivateKey] = None
|
|
|
|
_public_key: Optional[RSAPublicKey] = None
|
2020-03-03 22:35:25 +00:00
|
|
|
|
|
|
|
@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
|
|
|
|
|
2020-08-19 08:32:44 +00:00
|
|
|
@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
|
|
|
|
|
2020-03-04 18:43:18 +00:00
|
|
|
@property
|
|
|
|
def private_key(self) -> Optional[RSAPrivateKey]:
|
|
|
|
"""Get python cryptography PrivateKey instance"""
|
2020-08-19 08:32:44 +00:00
|
|
|
if not self._private_key:
|
|
|
|
self._private_key = load_pem_private_key(
|
2020-03-04 18:43:18 +00:00
|
|
|
str.encode("\n".join([x.strip() for x in self.key_data.split("\n")])),
|
|
|
|
password=None,
|
|
|
|
backend=default_backend(),
|
|
|
|
)
|
2020-08-19 08:32:44 +00:00
|
|
|
return self._private_key
|
2020-03-04 18:43:18 +00:00
|
|
|
|
2020-03-03 22:35:25 +00:00
|
|
|
@property
|
|
|
|
def fingerprint(self) -> str:
|
|
|
|
"""Get SHA256 Fingerprint of certificate_data"""
|
2020-07-07 15:05:31 +00:00
|
|
|
return hexlify(self.certificate.fingerprint(hashes.SHA256()), ":").decode(
|
|
|
|
"utf-8"
|
|
|
|
)
|
2020-03-03 22:35:25 +00:00
|
|
|
|
2020-08-19 08:32:44 +00:00
|
|
|
@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 ""
|
|
|
|
)
|
|
|
|
|
2020-03-03 22:35:25 +00:00
|
|
|
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")
|