forked from STI/Astroport.ONE
1222 lines
36 KiB
Python
1222 lines
36 KiB
Python
|
# This file is dual licensed under the terms of the Apache License, Version
|
||
|
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||
|
# for complete details.
|
||
|
|
||
|
from __future__ import annotations
|
||
|
|
||
|
import abc
|
||
|
import datetime
|
||
|
import os
|
||
|
import typing
|
||
|
import warnings
|
||
|
|
||
|
from cryptography import utils
|
||
|
from cryptography.hazmat.bindings._rust import x509 as rust_x509
|
||
|
from cryptography.hazmat.primitives import hashes, serialization
|
||
|
from cryptography.hazmat.primitives.asymmetric import (
|
||
|
dsa,
|
||
|
ec,
|
||
|
ed448,
|
||
|
ed25519,
|
||
|
padding,
|
||
|
rsa,
|
||
|
x448,
|
||
|
x25519,
|
||
|
)
|
||
|
from cryptography.hazmat.primitives.asymmetric.types import (
|
||
|
CertificateIssuerPrivateKeyTypes,
|
||
|
CertificateIssuerPublicKeyTypes,
|
||
|
CertificatePublicKeyTypes,
|
||
|
)
|
||
|
from cryptography.x509.extensions import (
|
||
|
Extension,
|
||
|
Extensions,
|
||
|
ExtensionType,
|
||
|
_make_sequence_methods,
|
||
|
)
|
||
|
from cryptography.x509.name import Name, _ASN1Type
|
||
|
from cryptography.x509.oid import ObjectIdentifier
|
||
|
|
||
|
_EARLIEST_UTC_TIME = datetime.datetime(1950, 1, 1)
|
||
|
|
||
|
# This must be kept in sync with sign.rs's list of allowable types in
|
||
|
# identify_hash_type
|
||
|
_AllowedHashTypes = typing.Union[
|
||
|
hashes.SHA224,
|
||
|
hashes.SHA256,
|
||
|
hashes.SHA384,
|
||
|
hashes.SHA512,
|
||
|
hashes.SHA3_224,
|
||
|
hashes.SHA3_256,
|
||
|
hashes.SHA3_384,
|
||
|
hashes.SHA3_512,
|
||
|
]
|
||
|
|
||
|
|
||
|
class AttributeNotFound(Exception):
|
||
|
def __init__(self, msg: str, oid: ObjectIdentifier) -> None:
|
||
|
super().__init__(msg)
|
||
|
self.oid = oid
|
||
|
|
||
|
|
||
|
def _reject_duplicate_extension(
|
||
|
extension: Extension[ExtensionType],
|
||
|
extensions: list[Extension[ExtensionType]],
|
||
|
) -> None:
|
||
|
# This is quadratic in the number of extensions
|
||
|
for e in extensions:
|
||
|
if e.oid == extension.oid:
|
||
|
raise ValueError("This extension has already been set.")
|
||
|
|
||
|
|
||
|
def _reject_duplicate_attribute(
|
||
|
oid: ObjectIdentifier,
|
||
|
attributes: list[tuple[ObjectIdentifier, bytes, int | None]],
|
||
|
) -> None:
|
||
|
# This is quadratic in the number of attributes
|
||
|
for attr_oid, _, _ in attributes:
|
||
|
if attr_oid == oid:
|
||
|
raise ValueError("This attribute has already been set.")
|
||
|
|
||
|
|
||
|
def _convert_to_naive_utc_time(time: datetime.datetime) -> datetime.datetime:
|
||
|
"""Normalizes a datetime to a naive datetime in UTC.
|
||
|
|
||
|
time -- datetime to normalize. Assumed to be in UTC if not timezone
|
||
|
aware.
|
||
|
"""
|
||
|
if time.tzinfo is not None:
|
||
|
offset = time.utcoffset()
|
||
|
offset = offset if offset else datetime.timedelta()
|
||
|
return time.replace(tzinfo=None) - offset
|
||
|
else:
|
||
|
return time
|
||
|
|
||
|
|
||
|
class Attribute:
|
||
|
def __init__(
|
||
|
self,
|
||
|
oid: ObjectIdentifier,
|
||
|
value: bytes,
|
||
|
_type: int = _ASN1Type.UTF8String.value,
|
||
|
) -> None:
|
||
|
self._oid = oid
|
||
|
self._value = value
|
||
|
self._type = _type
|
||
|
|
||
|
@property
|
||
|
def oid(self) -> ObjectIdentifier:
|
||
|
return self._oid
|
||
|
|
||
|
@property
|
||
|
def value(self) -> bytes:
|
||
|
return self._value
|
||
|
|
||
|
def __repr__(self) -> str:
|
||
|
return f"<Attribute(oid={self.oid}, value={self.value!r})>"
|
||
|
|
||
|
def __eq__(self, other: object) -> bool:
|
||
|
if not isinstance(other, Attribute):
|
||
|
return NotImplemented
|
||
|
|
||
|
return (
|
||
|
self.oid == other.oid
|
||
|
and self.value == other.value
|
||
|
and self._type == other._type
|
||
|
)
|
||
|
|
||
|
def __hash__(self) -> int:
|
||
|
return hash((self.oid, self.value, self._type))
|
||
|
|
||
|
|
||
|
class Attributes:
|
||
|
def __init__(
|
||
|
self,
|
||
|
attributes: typing.Iterable[Attribute],
|
||
|
) -> None:
|
||
|
self._attributes = list(attributes)
|
||
|
|
||
|
__len__, __iter__, __getitem__ = _make_sequence_methods("_attributes")
|
||
|
|
||
|
def __repr__(self) -> str:
|
||
|
return f"<Attributes({self._attributes})>"
|
||
|
|
||
|
def get_attribute_for_oid(self, oid: ObjectIdentifier) -> Attribute:
|
||
|
for attr in self:
|
||
|
if attr.oid == oid:
|
||
|
return attr
|
||
|
|
||
|
raise AttributeNotFound(f"No {oid} attribute was found", oid)
|
||
|
|
||
|
|
||
|
class Version(utils.Enum):
|
||
|
v1 = 0
|
||
|
v3 = 2
|
||
|
|
||
|
|
||
|
class InvalidVersion(Exception):
|
||
|
def __init__(self, msg: str, parsed_version: int) -> None:
|
||
|
super().__init__(msg)
|
||
|
self.parsed_version = parsed_version
|
||
|
|
||
|
|
||
|
class Certificate(metaclass=abc.ABCMeta):
|
||
|
@abc.abstractmethod
|
||
|
def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes:
|
||
|
"""
|
||
|
Returns bytes using digest passed.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def serial_number(self) -> int:
|
||
|
"""
|
||
|
Returns certificate serial number
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def version(self) -> Version:
|
||
|
"""
|
||
|
Returns the certificate version
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def public_key(self) -> CertificatePublicKeyTypes:
|
||
|
"""
|
||
|
Returns the public key
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def not_valid_before(self) -> datetime.datetime:
|
||
|
"""
|
||
|
Not before time (represented as UTC datetime)
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def not_valid_before_utc(self) -> datetime.datetime:
|
||
|
"""
|
||
|
Not before time (represented as a non-naive UTC datetime)
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def not_valid_after(self) -> datetime.datetime:
|
||
|
"""
|
||
|
Not after time (represented as UTC datetime)
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def not_valid_after_utc(self) -> datetime.datetime:
|
||
|
"""
|
||
|
Not after time (represented as a non-naive UTC datetime)
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def issuer(self) -> Name:
|
||
|
"""
|
||
|
Returns the issuer name object.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def subject(self) -> Name:
|
||
|
"""
|
||
|
Returns the subject name object.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature_hash_algorithm(
|
||
|
self,
|
||
|
) -> hashes.HashAlgorithm | None:
|
||
|
"""
|
||
|
Returns a HashAlgorithm corresponding to the type of the digest signed
|
||
|
in the certificate.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature_algorithm_oid(self) -> ObjectIdentifier:
|
||
|
"""
|
||
|
Returns the ObjectIdentifier of the signature algorithm.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature_algorithm_parameters(
|
||
|
self,
|
||
|
) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA:
|
||
|
"""
|
||
|
Returns the signature algorithm parameters.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def extensions(self) -> Extensions:
|
||
|
"""
|
||
|
Returns an Extensions object.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature(self) -> bytes:
|
||
|
"""
|
||
|
Returns the signature bytes.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def tbs_certificate_bytes(self) -> bytes:
|
||
|
"""
|
||
|
Returns the tbsCertificate payload bytes as defined in RFC 5280.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def tbs_precertificate_bytes(self) -> bytes:
|
||
|
"""
|
||
|
Returns the tbsCertificate payload bytes with the SCT list extension
|
||
|
stripped.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def __eq__(self, other: object) -> bool:
|
||
|
"""
|
||
|
Checks equality.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def __hash__(self) -> int:
|
||
|
"""
|
||
|
Computes a hash.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def public_bytes(self, encoding: serialization.Encoding) -> bytes:
|
||
|
"""
|
||
|
Serializes the certificate to PEM or DER format.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def verify_directly_issued_by(self, issuer: Certificate) -> None:
|
||
|
"""
|
||
|
This method verifies that certificate issuer name matches the
|
||
|
issuer subject name and that the certificate is signed by the
|
||
|
issuer's private key. No other validation is performed.
|
||
|
"""
|
||
|
|
||
|
|
||
|
# Runtime isinstance checks need this since the rust class is not a subclass.
|
||
|
Certificate.register(rust_x509.Certificate)
|
||
|
|
||
|
|
||
|
class RevokedCertificate(metaclass=abc.ABCMeta):
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def serial_number(self) -> int:
|
||
|
"""
|
||
|
Returns the serial number of the revoked certificate.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def revocation_date(self) -> datetime.datetime:
|
||
|
"""
|
||
|
Returns the date of when this certificate was revoked.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def revocation_date_utc(self) -> datetime.datetime:
|
||
|
"""
|
||
|
Returns the date of when this certificate was revoked as a non-naive
|
||
|
UTC datetime.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def extensions(self) -> Extensions:
|
||
|
"""
|
||
|
Returns an Extensions object containing a list of Revoked extensions.
|
||
|
"""
|
||
|
|
||
|
|
||
|
# Runtime isinstance checks need this since the rust class is not a subclass.
|
||
|
RevokedCertificate.register(rust_x509.RevokedCertificate)
|
||
|
|
||
|
|
||
|
class _RawRevokedCertificate(RevokedCertificate):
|
||
|
def __init__(
|
||
|
self,
|
||
|
serial_number: int,
|
||
|
revocation_date: datetime.datetime,
|
||
|
extensions: Extensions,
|
||
|
):
|
||
|
self._serial_number = serial_number
|
||
|
self._revocation_date = revocation_date
|
||
|
self._extensions = extensions
|
||
|
|
||
|
@property
|
||
|
def serial_number(self) -> int:
|
||
|
return self._serial_number
|
||
|
|
||
|
@property
|
||
|
def revocation_date(self) -> datetime.datetime:
|
||
|
warnings.warn(
|
||
|
"Properties that return a naïve datetime object have been "
|
||
|
"deprecated. Please switch to revocation_date_utc.",
|
||
|
utils.DeprecatedIn42,
|
||
|
stacklevel=2,
|
||
|
)
|
||
|
return self._revocation_date
|
||
|
|
||
|
@property
|
||
|
def revocation_date_utc(self) -> datetime.datetime:
|
||
|
return self._revocation_date.replace(tzinfo=datetime.timezone.utc)
|
||
|
|
||
|
@property
|
||
|
def extensions(self) -> Extensions:
|
||
|
return self._extensions
|
||
|
|
||
|
|
||
|
class CertificateRevocationList(metaclass=abc.ABCMeta):
|
||
|
@abc.abstractmethod
|
||
|
def public_bytes(self, encoding: serialization.Encoding) -> bytes:
|
||
|
"""
|
||
|
Serializes the CRL to PEM or DER format.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes:
|
||
|
"""
|
||
|
Returns bytes using digest passed.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def get_revoked_certificate_by_serial_number(
|
||
|
self, serial_number: int
|
||
|
) -> RevokedCertificate | None:
|
||
|
"""
|
||
|
Returns an instance of RevokedCertificate or None if the serial_number
|
||
|
is not in the CRL.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature_hash_algorithm(
|
||
|
self,
|
||
|
) -> hashes.HashAlgorithm | None:
|
||
|
"""
|
||
|
Returns a HashAlgorithm corresponding to the type of the digest signed
|
||
|
in the certificate.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature_algorithm_oid(self) -> ObjectIdentifier:
|
||
|
"""
|
||
|
Returns the ObjectIdentifier of the signature algorithm.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature_algorithm_parameters(
|
||
|
self,
|
||
|
) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA:
|
||
|
"""
|
||
|
Returns the signature algorithm parameters.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def issuer(self) -> Name:
|
||
|
"""
|
||
|
Returns the X509Name with the issuer of this CRL.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def next_update(self) -> datetime.datetime | None:
|
||
|
"""
|
||
|
Returns the date of next update for this CRL.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def next_update_utc(self) -> datetime.datetime | None:
|
||
|
"""
|
||
|
Returns the date of next update for this CRL as a non-naive UTC
|
||
|
datetime.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def last_update(self) -> datetime.datetime:
|
||
|
"""
|
||
|
Returns the date of last update for this CRL.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def last_update_utc(self) -> datetime.datetime:
|
||
|
"""
|
||
|
Returns the date of last update for this CRL as a non-naive UTC
|
||
|
datetime.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def extensions(self) -> Extensions:
|
||
|
"""
|
||
|
Returns an Extensions object containing a list of CRL extensions.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature(self) -> bytes:
|
||
|
"""
|
||
|
Returns the signature bytes.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def tbs_certlist_bytes(self) -> bytes:
|
||
|
"""
|
||
|
Returns the tbsCertList payload bytes as defined in RFC 5280.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def __eq__(self, other: object) -> bool:
|
||
|
"""
|
||
|
Checks equality.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def __len__(self) -> int:
|
||
|
"""
|
||
|
Number of revoked certificates in the CRL.
|
||
|
"""
|
||
|
|
||
|
@typing.overload
|
||
|
def __getitem__(self, idx: int) -> RevokedCertificate:
|
||
|
...
|
||
|
|
||
|
@typing.overload
|
||
|
def __getitem__(self, idx: slice) -> list[RevokedCertificate]:
|
||
|
...
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def __getitem__(
|
||
|
self, idx: int | slice
|
||
|
) -> RevokedCertificate | list[RevokedCertificate]:
|
||
|
"""
|
||
|
Returns a revoked certificate (or slice of revoked certificates).
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def __iter__(self) -> typing.Iterator[RevokedCertificate]:
|
||
|
"""
|
||
|
Iterator over the revoked certificates
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def is_signature_valid(
|
||
|
self, public_key: CertificateIssuerPublicKeyTypes
|
||
|
) -> bool:
|
||
|
"""
|
||
|
Verifies signature of revocation list against given public key.
|
||
|
"""
|
||
|
|
||
|
|
||
|
CertificateRevocationList.register(rust_x509.CertificateRevocationList)
|
||
|
|
||
|
|
||
|
class CertificateSigningRequest(metaclass=abc.ABCMeta):
|
||
|
@abc.abstractmethod
|
||
|
def __eq__(self, other: object) -> bool:
|
||
|
"""
|
||
|
Checks equality.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def __hash__(self) -> int:
|
||
|
"""
|
||
|
Computes a hash.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def public_key(self) -> CertificatePublicKeyTypes:
|
||
|
"""
|
||
|
Returns the public key
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def subject(self) -> Name:
|
||
|
"""
|
||
|
Returns the subject name object.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature_hash_algorithm(
|
||
|
self,
|
||
|
) -> hashes.HashAlgorithm | None:
|
||
|
"""
|
||
|
Returns a HashAlgorithm corresponding to the type of the digest signed
|
||
|
in the certificate.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature_algorithm_oid(self) -> ObjectIdentifier:
|
||
|
"""
|
||
|
Returns the ObjectIdentifier of the signature algorithm.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature_algorithm_parameters(
|
||
|
self,
|
||
|
) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA:
|
||
|
"""
|
||
|
Returns the signature algorithm parameters.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def extensions(self) -> Extensions:
|
||
|
"""
|
||
|
Returns the extensions in the signing request.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def attributes(self) -> Attributes:
|
||
|
"""
|
||
|
Returns an Attributes object.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def public_bytes(self, encoding: serialization.Encoding) -> bytes:
|
||
|
"""
|
||
|
Encodes the request to PEM or DER format.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def signature(self) -> bytes:
|
||
|
"""
|
||
|
Returns the signature bytes.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def tbs_certrequest_bytes(self) -> bytes:
|
||
|
"""
|
||
|
Returns the PKCS#10 CertificationRequestInfo bytes as defined in RFC
|
||
|
2986.
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
@abc.abstractmethod
|
||
|
def is_signature_valid(self) -> bool:
|
||
|
"""
|
||
|
Verifies signature of signing request.
|
||
|
"""
|
||
|
|
||
|
@abc.abstractmethod
|
||
|
def get_attribute_for_oid(self, oid: ObjectIdentifier) -> bytes:
|
||
|
"""
|
||
|
Get the attribute value for a given OID.
|
||
|
"""
|
||
|
|
||
|
|
||
|
# Runtime isinstance checks need this since the rust class is not a subclass.
|
||
|
CertificateSigningRequest.register(rust_x509.CertificateSigningRequest)
|
||
|
|
||
|
|
||
|
load_pem_x509_certificate = rust_x509.load_pem_x509_certificate
|
||
|
load_der_x509_certificate = rust_x509.load_der_x509_certificate
|
||
|
|
||
|
load_pem_x509_certificates = rust_x509.load_pem_x509_certificates
|
||
|
|
||
|
load_pem_x509_csr = rust_x509.load_pem_x509_csr
|
||
|
load_der_x509_csr = rust_x509.load_der_x509_csr
|
||
|
|
||
|
load_pem_x509_crl = rust_x509.load_pem_x509_crl
|
||
|
load_der_x509_crl = rust_x509.load_der_x509_crl
|
||
|
|
||
|
|
||
|
class CertificateSigningRequestBuilder:
|
||
|
def __init__(
|
||
|
self,
|
||
|
subject_name: Name | None = None,
|
||
|
extensions: list[Extension[ExtensionType]] = [],
|
||
|
attributes: list[tuple[ObjectIdentifier, bytes, int | None]] = [],
|
||
|
):
|
||
|
"""
|
||
|
Creates an empty X.509 certificate request (v1).
|
||
|
"""
|
||
|
self._subject_name = subject_name
|
||
|
self._extensions = extensions
|
||
|
self._attributes = attributes
|
||
|
|
||
|
def subject_name(self, name: Name) -> CertificateSigningRequestBuilder:
|
||
|
"""
|
||
|
Sets the certificate requestor's distinguished name.
|
||
|
"""
|
||
|
if not isinstance(name, Name):
|
||
|
raise TypeError("Expecting x509.Name object.")
|
||
|
if self._subject_name is not None:
|
||
|
raise ValueError("The subject name may only be set once.")
|
||
|
return CertificateSigningRequestBuilder(
|
||
|
name, self._extensions, self._attributes
|
||
|
)
|
||
|
|
||
|
def add_extension(
|
||
|
self, extval: ExtensionType, critical: bool
|
||
|
) -> CertificateSigningRequestBuilder:
|
||
|
"""
|
||
|
Adds an X.509 extension to the certificate request.
|
||
|
"""
|
||
|
if not isinstance(extval, ExtensionType):
|
||
|
raise TypeError("extension must be an ExtensionType")
|
||
|
|
||
|
extension = Extension(extval.oid, critical, extval)
|
||
|
_reject_duplicate_extension(extension, self._extensions)
|
||
|
|
||
|
return CertificateSigningRequestBuilder(
|
||
|
self._subject_name,
|
||
|
[*self._extensions, extension],
|
||
|
self._attributes,
|
||
|
)
|
||
|
|
||
|
def add_attribute(
|
||
|
self,
|
||
|
oid: ObjectIdentifier,
|
||
|
value: bytes,
|
||
|
*,
|
||
|
_tag: _ASN1Type | None = None,
|
||
|
) -> CertificateSigningRequestBuilder:
|
||
|
"""
|
||
|
Adds an X.509 attribute with an OID and associated value.
|
||
|
"""
|
||
|
if not isinstance(oid, ObjectIdentifier):
|
||
|
raise TypeError("oid must be an ObjectIdentifier")
|
||
|
|
||
|
if not isinstance(value, bytes):
|
||
|
raise TypeError("value must be bytes")
|
||
|
|
||
|
if _tag is not None and not isinstance(_tag, _ASN1Type):
|
||
|
raise TypeError("tag must be _ASN1Type")
|
||
|
|
||
|
_reject_duplicate_attribute(oid, self._attributes)
|
||
|
|
||
|
if _tag is not None:
|
||
|
tag = _tag.value
|
||
|
else:
|
||
|
tag = None
|
||
|
|
||
|
return CertificateSigningRequestBuilder(
|
||
|
self._subject_name,
|
||
|
self._extensions,
|
||
|
[*self._attributes, (oid, value, tag)],
|
||
|
)
|
||
|
|
||
|
def sign(
|
||
|
self,
|
||
|
private_key: CertificateIssuerPrivateKeyTypes,
|
||
|
algorithm: _AllowedHashTypes | None,
|
||
|
backend: typing.Any = None,
|
||
|
*,
|
||
|
rsa_padding: padding.PSS | padding.PKCS1v15 | None = None,
|
||
|
) -> CertificateSigningRequest:
|
||
|
"""
|
||
|
Signs the request using the requestor's private key.
|
||
|
"""
|
||
|
if self._subject_name is None:
|
||
|
raise ValueError("A CertificateSigningRequest must have a subject")
|
||
|
|
||
|
if rsa_padding is not None:
|
||
|
if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)):
|
||
|
raise TypeError("Padding must be PSS or PKCS1v15")
|
||
|
if not isinstance(private_key, rsa.RSAPrivateKey):
|
||
|
raise TypeError("Padding is only supported for RSA keys")
|
||
|
|
||
|
return rust_x509.create_x509_csr(
|
||
|
self, private_key, algorithm, rsa_padding
|
||
|
)
|
||
|
|
||
|
|
||
|
class CertificateBuilder:
|
||
|
_extensions: list[Extension[ExtensionType]]
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
issuer_name: Name | None = None,
|
||
|
subject_name: Name | None = None,
|
||
|
public_key: CertificatePublicKeyTypes | None = None,
|
||
|
serial_number: int | None = None,
|
||
|
not_valid_before: datetime.datetime | None = None,
|
||
|
not_valid_after: datetime.datetime | None = None,
|
||
|
extensions: list[Extension[ExtensionType]] = [],
|
||
|
) -> None:
|
||
|
self._version = Version.v3
|
||
|
self._issuer_name = issuer_name
|
||
|
self._subject_name = subject_name
|
||
|
self._public_key = public_key
|
||
|
self._serial_number = serial_number
|
||
|
self._not_valid_before = not_valid_before
|
||
|
self._not_valid_after = not_valid_after
|
||
|
self._extensions = extensions
|
||
|
|
||
|
def issuer_name(self, name: Name) -> CertificateBuilder:
|
||
|
"""
|
||
|
Sets the CA's distinguished name.
|
||
|
"""
|
||
|
if not isinstance(name, Name):
|
||
|
raise TypeError("Expecting x509.Name object.")
|
||
|
if self._issuer_name is not None:
|
||
|
raise ValueError("The issuer name may only be set once.")
|
||
|
return CertificateBuilder(
|
||
|
name,
|
||
|
self._subject_name,
|
||
|
self._public_key,
|
||
|
self._serial_number,
|
||
|
self._not_valid_before,
|
||
|
self._not_valid_after,
|
||
|
self._extensions,
|
||
|
)
|
||
|
|
||
|
def subject_name(self, name: Name) -> CertificateBuilder:
|
||
|
"""
|
||
|
Sets the requestor's distinguished name.
|
||
|
"""
|
||
|
if not isinstance(name, Name):
|
||
|
raise TypeError("Expecting x509.Name object.")
|
||
|
if self._subject_name is not None:
|
||
|
raise ValueError("The subject name may only be set once.")
|
||
|
return CertificateBuilder(
|
||
|
self._issuer_name,
|
||
|
name,
|
||
|
self._public_key,
|
||
|
self._serial_number,
|
||
|
self._not_valid_before,
|
||
|
self._not_valid_after,
|
||
|
self._extensions,
|
||
|
)
|
||
|
|
||
|
def public_key(
|
||
|
self,
|
||
|
key: CertificatePublicKeyTypes,
|
||
|
) -> CertificateBuilder:
|
||
|
"""
|
||
|
Sets the requestor's public key (as found in the signing request).
|
||
|
"""
|
||
|
if not isinstance(
|
||
|
key,
|
||
|
(
|
||
|
dsa.DSAPublicKey,
|
||
|
rsa.RSAPublicKey,
|
||
|
ec.EllipticCurvePublicKey,
|
||
|
ed25519.Ed25519PublicKey,
|
||
|
ed448.Ed448PublicKey,
|
||
|
x25519.X25519PublicKey,
|
||
|
x448.X448PublicKey,
|
||
|
),
|
||
|
):
|
||
|
raise TypeError(
|
||
|
"Expecting one of DSAPublicKey, RSAPublicKey,"
|
||
|
" EllipticCurvePublicKey, Ed25519PublicKey,"
|
||
|
" Ed448PublicKey, X25519PublicKey, or "
|
||
|
"X448PublicKey."
|
||
|
)
|
||
|
if self._public_key is not None:
|
||
|
raise ValueError("The public key may only be set once.")
|
||
|
return CertificateBuilder(
|
||
|
self._issuer_name,
|
||
|
self._subject_name,
|
||
|
key,
|
||
|
self._serial_number,
|
||
|
self._not_valid_before,
|
||
|
self._not_valid_after,
|
||
|
self._extensions,
|
||
|
)
|
||
|
|
||
|
def serial_number(self, number: int) -> CertificateBuilder:
|
||
|
"""
|
||
|
Sets the certificate serial number.
|
||
|
"""
|
||
|
if not isinstance(number, int):
|
||
|
raise TypeError("Serial number must be of integral type.")
|
||
|
if self._serial_number is not None:
|
||
|
raise ValueError("The serial number may only be set once.")
|
||
|
if number <= 0:
|
||
|
raise ValueError("The serial number should be positive.")
|
||
|
|
||
|
# ASN.1 integers are always signed, so most significant bit must be
|
||
|
# zero.
|
||
|
if number.bit_length() >= 160: # As defined in RFC 5280
|
||
|
raise ValueError(
|
||
|
"The serial number should not be more than 159 " "bits."
|
||
|
)
|
||
|
return CertificateBuilder(
|
||
|
self._issuer_name,
|
||
|
self._subject_name,
|
||
|
self._public_key,
|
||
|
number,
|
||
|
self._not_valid_before,
|
||
|
self._not_valid_after,
|
||
|
self._extensions,
|
||
|
)
|
||
|
|
||
|
def not_valid_before(self, time: datetime.datetime) -> CertificateBuilder:
|
||
|
"""
|
||
|
Sets the certificate activation time.
|
||
|
"""
|
||
|
if not isinstance(time, datetime.datetime):
|
||
|
raise TypeError("Expecting datetime object.")
|
||
|
if self._not_valid_before is not None:
|
||
|
raise ValueError("The not valid before may only be set once.")
|
||
|
time = _convert_to_naive_utc_time(time)
|
||
|
if time < _EARLIEST_UTC_TIME:
|
||
|
raise ValueError(
|
||
|
"The not valid before date must be on or after"
|
||
|
" 1950 January 1)."
|
||
|
)
|
||
|
if self._not_valid_after is not None and time > self._not_valid_after:
|
||
|
raise ValueError(
|
||
|
"The not valid before date must be before the not valid after "
|
||
|
"date."
|
||
|
)
|
||
|
return CertificateBuilder(
|
||
|
self._issuer_name,
|
||
|
self._subject_name,
|
||
|
self._public_key,
|
||
|
self._serial_number,
|
||
|
time,
|
||
|
self._not_valid_after,
|
||
|
self._extensions,
|
||
|
)
|
||
|
|
||
|
def not_valid_after(self, time: datetime.datetime) -> CertificateBuilder:
|
||
|
"""
|
||
|
Sets the certificate expiration time.
|
||
|
"""
|
||
|
if not isinstance(time, datetime.datetime):
|
||
|
raise TypeError("Expecting datetime object.")
|
||
|
if self._not_valid_after is not None:
|
||
|
raise ValueError("The not valid after may only be set once.")
|
||
|
time = _convert_to_naive_utc_time(time)
|
||
|
if time < _EARLIEST_UTC_TIME:
|
||
|
raise ValueError(
|
||
|
"The not valid after date must be on or after"
|
||
|
" 1950 January 1."
|
||
|
)
|
||
|
if (
|
||
|
self._not_valid_before is not None
|
||
|
and time < self._not_valid_before
|
||
|
):
|
||
|
raise ValueError(
|
||
|
"The not valid after date must be after the not valid before "
|
||
|
"date."
|
||
|
)
|
||
|
return CertificateBuilder(
|
||
|
self._issuer_name,
|
||
|
self._subject_name,
|
||
|
self._public_key,
|
||
|
self._serial_number,
|
||
|
self._not_valid_before,
|
||
|
time,
|
||
|
self._extensions,
|
||
|
)
|
||
|
|
||
|
def add_extension(
|
||
|
self, extval: ExtensionType, critical: bool
|
||
|
) -> CertificateBuilder:
|
||
|
"""
|
||
|
Adds an X.509 extension to the certificate.
|
||
|
"""
|
||
|
if not isinstance(extval, ExtensionType):
|
||
|
raise TypeError("extension must be an ExtensionType")
|
||
|
|
||
|
extension = Extension(extval.oid, critical, extval)
|
||
|
_reject_duplicate_extension(extension, self._extensions)
|
||
|
|
||
|
return CertificateBuilder(
|
||
|
self._issuer_name,
|
||
|
self._subject_name,
|
||
|
self._public_key,
|
||
|
self._serial_number,
|
||
|
self._not_valid_before,
|
||
|
self._not_valid_after,
|
||
|
[*self._extensions, extension],
|
||
|
)
|
||
|
|
||
|
def sign(
|
||
|
self,
|
||
|
private_key: CertificateIssuerPrivateKeyTypes,
|
||
|
algorithm: _AllowedHashTypes | None,
|
||
|
backend: typing.Any = None,
|
||
|
*,
|
||
|
rsa_padding: padding.PSS | padding.PKCS1v15 | None = None,
|
||
|
) -> Certificate:
|
||
|
"""
|
||
|
Signs the certificate using the CA's private key.
|
||
|
"""
|
||
|
if self._subject_name is None:
|
||
|
raise ValueError("A certificate must have a subject name")
|
||
|
|
||
|
if self._issuer_name is None:
|
||
|
raise ValueError("A certificate must have an issuer name")
|
||
|
|
||
|
if self._serial_number is None:
|
||
|
raise ValueError("A certificate must have a serial number")
|
||
|
|
||
|
if self._not_valid_before is None:
|
||
|
raise ValueError("A certificate must have a not valid before time")
|
||
|
|
||
|
if self._not_valid_after is None:
|
||
|
raise ValueError("A certificate must have a not valid after time")
|
||
|
|
||
|
if self._public_key is None:
|
||
|
raise ValueError("A certificate must have a public key")
|
||
|
|
||
|
if rsa_padding is not None:
|
||
|
if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)):
|
||
|
raise TypeError("Padding must be PSS or PKCS1v15")
|
||
|
if not isinstance(private_key, rsa.RSAPrivateKey):
|
||
|
raise TypeError("Padding is only supported for RSA keys")
|
||
|
|
||
|
return rust_x509.create_x509_certificate(
|
||
|
self, private_key, algorithm, rsa_padding
|
||
|
)
|
||
|
|
||
|
|
||
|
class CertificateRevocationListBuilder:
|
||
|
_extensions: list[Extension[ExtensionType]]
|
||
|
_revoked_certificates: list[RevokedCertificate]
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
issuer_name: Name | None = None,
|
||
|
last_update: datetime.datetime | None = None,
|
||
|
next_update: datetime.datetime | None = None,
|
||
|
extensions: list[Extension[ExtensionType]] = [],
|
||
|
revoked_certificates: list[RevokedCertificate] = [],
|
||
|
):
|
||
|
self._issuer_name = issuer_name
|
||
|
self._last_update = last_update
|
||
|
self._next_update = next_update
|
||
|
self._extensions = extensions
|
||
|
self._revoked_certificates = revoked_certificates
|
||
|
|
||
|
def issuer_name(
|
||
|
self, issuer_name: Name
|
||
|
) -> CertificateRevocationListBuilder:
|
||
|
if not isinstance(issuer_name, Name):
|
||
|
raise TypeError("Expecting x509.Name object.")
|
||
|
if self._issuer_name is not None:
|
||
|
raise ValueError("The issuer name may only be set once.")
|
||
|
return CertificateRevocationListBuilder(
|
||
|
issuer_name,
|
||
|
self._last_update,
|
||
|
self._next_update,
|
||
|
self._extensions,
|
||
|
self._revoked_certificates,
|
||
|
)
|
||
|
|
||
|
def last_update(
|
||
|
self, last_update: datetime.datetime
|
||
|
) -> CertificateRevocationListBuilder:
|
||
|
if not isinstance(last_update, datetime.datetime):
|
||
|
raise TypeError("Expecting datetime object.")
|
||
|
if self._last_update is not None:
|
||
|
raise ValueError("Last update may only be set once.")
|
||
|
last_update = _convert_to_naive_utc_time(last_update)
|
||
|
if last_update < _EARLIEST_UTC_TIME:
|
||
|
raise ValueError(
|
||
|
"The last update date must be on or after" " 1950 January 1."
|
||
|
)
|
||
|
if self._next_update is not None and last_update > self._next_update:
|
||
|
raise ValueError(
|
||
|
"The last update date must be before the next update date."
|
||
|
)
|
||
|
return CertificateRevocationListBuilder(
|
||
|
self._issuer_name,
|
||
|
last_update,
|
||
|
self._next_update,
|
||
|
self._extensions,
|
||
|
self._revoked_certificates,
|
||
|
)
|
||
|
|
||
|
def next_update(
|
||
|
self, next_update: datetime.datetime
|
||
|
) -> CertificateRevocationListBuilder:
|
||
|
if not isinstance(next_update, datetime.datetime):
|
||
|
raise TypeError("Expecting datetime object.")
|
||
|
if self._next_update is not None:
|
||
|
raise ValueError("Last update may only be set once.")
|
||
|
next_update = _convert_to_naive_utc_time(next_update)
|
||
|
if next_update < _EARLIEST_UTC_TIME:
|
||
|
raise ValueError(
|
||
|
"The last update date must be on or after" " 1950 January 1."
|
||
|
)
|
||
|
if self._last_update is not None and next_update < self._last_update:
|
||
|
raise ValueError(
|
||
|
"The next update date must be after the last update date."
|
||
|
)
|
||
|
return CertificateRevocationListBuilder(
|
||
|
self._issuer_name,
|
||
|
self._last_update,
|
||
|
next_update,
|
||
|
self._extensions,
|
||
|
self._revoked_certificates,
|
||
|
)
|
||
|
|
||
|
def add_extension(
|
||
|
self, extval: ExtensionType, critical: bool
|
||
|
) -> CertificateRevocationListBuilder:
|
||
|
"""
|
||
|
Adds an X.509 extension to the certificate revocation list.
|
||
|
"""
|
||
|
if not isinstance(extval, ExtensionType):
|
||
|
raise TypeError("extension must be an ExtensionType")
|
||
|
|
||
|
extension = Extension(extval.oid, critical, extval)
|
||
|
_reject_duplicate_extension(extension, self._extensions)
|
||
|
return CertificateRevocationListBuilder(
|
||
|
self._issuer_name,
|
||
|
self._last_update,
|
||
|
self._next_update,
|
||
|
[*self._extensions, extension],
|
||
|
self._revoked_certificates,
|
||
|
)
|
||
|
|
||
|
def add_revoked_certificate(
|
||
|
self, revoked_certificate: RevokedCertificate
|
||
|
) -> CertificateRevocationListBuilder:
|
||
|
"""
|
||
|
Adds a revoked certificate to the CRL.
|
||
|
"""
|
||
|
if not isinstance(revoked_certificate, RevokedCertificate):
|
||
|
raise TypeError("Must be an instance of RevokedCertificate")
|
||
|
|
||
|
return CertificateRevocationListBuilder(
|
||
|
self._issuer_name,
|
||
|
self._last_update,
|
||
|
self._next_update,
|
||
|
self._extensions,
|
||
|
[*self._revoked_certificates, revoked_certificate],
|
||
|
)
|
||
|
|
||
|
def sign(
|
||
|
self,
|
||
|
private_key: CertificateIssuerPrivateKeyTypes,
|
||
|
algorithm: _AllowedHashTypes | None,
|
||
|
backend: typing.Any = None,
|
||
|
*,
|
||
|
rsa_padding: padding.PSS | padding.PKCS1v15 | None = None,
|
||
|
) -> CertificateRevocationList:
|
||
|
if self._issuer_name is None:
|
||
|
raise ValueError("A CRL must have an issuer name")
|
||
|
|
||
|
if self._last_update is None:
|
||
|
raise ValueError("A CRL must have a last update time")
|
||
|
|
||
|
if self._next_update is None:
|
||
|
raise ValueError("A CRL must have a next update time")
|
||
|
|
||
|
if rsa_padding is not None:
|
||
|
if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)):
|
||
|
raise TypeError("Padding must be PSS or PKCS1v15")
|
||
|
if not isinstance(private_key, rsa.RSAPrivateKey):
|
||
|
raise TypeError("Padding is only supported for RSA keys")
|
||
|
|
||
|
return rust_x509.create_x509_crl(
|
||
|
self, private_key, algorithm, rsa_padding
|
||
|
)
|
||
|
|
||
|
|
||
|
class RevokedCertificateBuilder:
|
||
|
def __init__(
|
||
|
self,
|
||
|
serial_number: int | None = None,
|
||
|
revocation_date: datetime.datetime | None = None,
|
||
|
extensions: list[Extension[ExtensionType]] = [],
|
||
|
):
|
||
|
self._serial_number = serial_number
|
||
|
self._revocation_date = revocation_date
|
||
|
self._extensions = extensions
|
||
|
|
||
|
def serial_number(self, number: int) -> RevokedCertificateBuilder:
|
||
|
if not isinstance(number, int):
|
||
|
raise TypeError("Serial number must be of integral type.")
|
||
|
if self._serial_number is not None:
|
||
|
raise ValueError("The serial number may only be set once.")
|
||
|
if number <= 0:
|
||
|
raise ValueError("The serial number should be positive")
|
||
|
|
||
|
# ASN.1 integers are always signed, so most significant bit must be
|
||
|
# zero.
|
||
|
if number.bit_length() >= 160: # As defined in RFC 5280
|
||
|
raise ValueError(
|
||
|
"The serial number should not be more than 159 " "bits."
|
||
|
)
|
||
|
return RevokedCertificateBuilder(
|
||
|
number, self._revocation_date, self._extensions
|
||
|
)
|
||
|
|
||
|
def revocation_date(
|
||
|
self, time: datetime.datetime
|
||
|
) -> RevokedCertificateBuilder:
|
||
|
if not isinstance(time, datetime.datetime):
|
||
|
raise TypeError("Expecting datetime object.")
|
||
|
if self._revocation_date is not None:
|
||
|
raise ValueError("The revocation date may only be set once.")
|
||
|
time = _convert_to_naive_utc_time(time)
|
||
|
if time < _EARLIEST_UTC_TIME:
|
||
|
raise ValueError(
|
||
|
"The revocation date must be on or after" " 1950 January 1."
|
||
|
)
|
||
|
return RevokedCertificateBuilder(
|
||
|
self._serial_number, time, self._extensions
|
||
|
)
|
||
|
|
||
|
def add_extension(
|
||
|
self, extval: ExtensionType, critical: bool
|
||
|
) -> RevokedCertificateBuilder:
|
||
|
if not isinstance(extval, ExtensionType):
|
||
|
raise TypeError("extension must be an ExtensionType")
|
||
|
|
||
|
extension = Extension(extval.oid, critical, extval)
|
||
|
_reject_duplicate_extension(extension, self._extensions)
|
||
|
return RevokedCertificateBuilder(
|
||
|
self._serial_number,
|
||
|
self._revocation_date,
|
||
|
[*self._extensions, extension],
|
||
|
)
|
||
|
|
||
|
def build(self, backend: typing.Any = None) -> RevokedCertificate:
|
||
|
if self._serial_number is None:
|
||
|
raise ValueError("A revoked certificate must have a serial number")
|
||
|
if self._revocation_date is None:
|
||
|
raise ValueError(
|
||
|
"A revoked certificate must have a revocation date"
|
||
|
)
|
||
|
return _RawRevokedCertificate(
|
||
|
self._serial_number,
|
||
|
self._revocation_date,
|
||
|
Extensions(self._extensions),
|
||
|
)
|
||
|
|
||
|
|
||
|
def random_serial_number() -> int:
|
||
|
return int.from_bytes(os.urandom(20), "big") >> 1
|