# 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 absolute_import, division, print_function import datetime import operator from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends.openssl.decode_asn1 import ( _asn1_integer_to_int, _asn1_string_to_bytes, _decode_x509_name, _obj2txt, _parse_asn1_time, ) from cryptography.hazmat.backends.openssl.encode_asn1 import ( _encode_asn1_int_gc, _txt2obj_gc, ) from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa from cryptography.x509.name import _ASN1Type @utils.register_interface(x509.Certificate) class _Certificate(object): def __init__(self, backend, x509_cert): self._backend = backend self._x509 = x509_cert version = self._backend._lib.X509_get_version(self._x509) if version == 0: self._version = x509.Version.v1 elif version == 2: self._version = x509.Version.v3 else: raise x509.InvalidVersion( "{} is not a valid X509 version".format(version), version ) def __repr__(self): return "".format(self.subject) def __eq__(self, other): if not isinstance(other, x509.Certificate): return NotImplemented res = self._backend._lib.X509_cmp(self._x509, other._x509) return res == 0 def __ne__(self, other): return not self == other def __hash__(self): return hash(self.public_bytes(serialization.Encoding.DER)) def __deepcopy__(self, memo): return self def fingerprint(self, algorithm): h = hashes.Hash(algorithm, self._backend) h.update(self.public_bytes(serialization.Encoding.DER)) return h.finalize() version = utils.read_only_property("_version") @property def serial_number(self): asn1_int = self._backend._lib.X509_get_serialNumber(self._x509) self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) return _asn1_integer_to_int(self._backend, asn1_int) def public_key(self): pkey = self._backend._lib.X509_get_pubkey(self._x509) if pkey == self._backend._ffi.NULL: # Remove errors from the stack. self._backend._consume_errors() raise ValueError("Certificate public key is of an unknown type") pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) return self._backend._evp_pkey_to_public_key(pkey) @property def not_valid_before(self): asn1_time = self._backend._lib.X509_getm_notBefore(self._x509) return _parse_asn1_time(self._backend, asn1_time) @property def not_valid_after(self): asn1_time = self._backend._lib.X509_getm_notAfter(self._x509) return _parse_asn1_time(self._backend, asn1_time) @property def issuer(self): issuer = self._backend._lib.X509_get_issuer_name(self._x509) self._backend.openssl_assert(issuer != self._backend._ffi.NULL) return _decode_x509_name(self._backend, issuer) @property def subject(self): subject = self._backend._lib.X509_get_subject_name(self._x509) self._backend.openssl_assert(subject != self._backend._ffi.NULL) return _decode_x509_name(self._backend, subject) @property def signature_hash_algorithm(self): oid = self.signature_algorithm_oid try: return x509._SIG_OIDS_TO_HASH[oid] except KeyError: raise UnsupportedAlgorithm( "Signature algorithm OID:{} not recognized".format(oid) ) @property def signature_algorithm_oid(self): alg = self._backend._ffi.new("X509_ALGOR **") self._backend._lib.X509_get0_signature( self._backend._ffi.NULL, alg, self._x509 ) self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL) oid = _obj2txt(self._backend, alg[0].algorithm) return x509.ObjectIdentifier(oid) @utils.cached_property def extensions(self): return self._backend._certificate_extension_parser.parse(self._x509) @property def signature(self): sig = self._backend._ffi.new("ASN1_BIT_STRING **") self._backend._lib.X509_get0_signature( sig, self._backend._ffi.NULL, self._x509 ) self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL) return _asn1_string_to_bytes(self._backend, sig[0]) @property def tbs_certificate_bytes(self): pp = self._backend._ffi.new("unsigned char **") res = self._backend._lib.i2d_re_X509_tbs(self._x509, pp) self._backend.openssl_assert(res > 0) pp = self._backend._ffi.gc( pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) ) return self._backend._ffi.buffer(pp[0], res)[:] def public_bytes(self, encoding): bio = self._backend._create_mem_bio_gc() if encoding is serialization.Encoding.PEM: res = self._backend._lib.PEM_write_bio_X509(bio, self._x509) elif encoding is serialization.Encoding.DER: res = self._backend._lib.i2d_X509_bio(bio, self._x509) else: raise TypeError("encoding must be an item from the Encoding enum") self._backend.openssl_assert(res == 1) return self._backend._read_mem_bio(bio) @utils.register_interface(x509.RevokedCertificate) class _RevokedCertificate(object): def __init__(self, backend, crl, x509_revoked): self._backend = backend # The X509_REVOKED_value is a X509_REVOKED * that has # no reference counting. This means when X509_CRL_free is # called then the CRL and all X509_REVOKED * are freed. Since # you can retain a reference to a single revoked certificate # and let the CRL fall out of scope we need to retain a # private reference to the CRL inside the RevokedCertificate # object to prevent the gc from being called inappropriately. self._crl = crl self._x509_revoked = x509_revoked @property def serial_number(self): asn1_int = self._backend._lib.X509_REVOKED_get0_serialNumber( self._x509_revoked ) self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) return _asn1_integer_to_int(self._backend, asn1_int) @property def revocation_date(self): return _parse_asn1_time( self._backend, self._backend._lib.X509_REVOKED_get0_revocationDate( self._x509_revoked ), ) @utils.cached_property def extensions(self): return self._backend._revoked_cert_extension_parser.parse( self._x509_revoked ) @utils.register_interface(x509.CertificateRevocationList) class _CertificateRevocationList(object): def __init__(self, backend, x509_crl): self._backend = backend self._x509_crl = x509_crl def __eq__(self, other): if not isinstance(other, x509.CertificateRevocationList): return NotImplemented res = self._backend._lib.X509_CRL_cmp(self._x509_crl, other._x509_crl) return res == 0 def __ne__(self, other): return not self == other def fingerprint(self, algorithm): h = hashes.Hash(algorithm, self._backend) bio = self._backend._create_mem_bio_gc() res = self._backend._lib.i2d_X509_CRL_bio(bio, self._x509_crl) self._backend.openssl_assert(res == 1) der = self._backend._read_mem_bio(bio) h.update(der) return h.finalize() @utils.cached_property def _sorted_crl(self): # X509_CRL_get0_by_serial sorts in place, which breaks a variety of # things we don't want to break (like iteration and the signature). # Let's dupe it and sort that instead. dup = self._backend._lib.X509_CRL_dup(self._x509_crl) self._backend.openssl_assert(dup != self._backend._ffi.NULL) dup = self._backend._ffi.gc(dup, self._backend._lib.X509_CRL_free) return dup def get_revoked_certificate_by_serial_number(self, serial_number): revoked = self._backend._ffi.new("X509_REVOKED **") asn1_int = _encode_asn1_int_gc(self._backend, serial_number) res = self._backend._lib.X509_CRL_get0_by_serial( self._sorted_crl, revoked, asn1_int ) if res == 0: return None else: self._backend.openssl_assert(revoked[0] != self._backend._ffi.NULL) return _RevokedCertificate( self._backend, self._sorted_crl, revoked[0] ) @property def signature_hash_algorithm(self): oid = self.signature_algorithm_oid try: return x509._SIG_OIDS_TO_HASH[oid] except KeyError: raise UnsupportedAlgorithm( "Signature algorithm OID:{} not recognized".format(oid) ) @property def signature_algorithm_oid(self): alg = self._backend._ffi.new("X509_ALGOR **") self._backend._lib.X509_CRL_get0_signature( self._x509_crl, self._backend._ffi.NULL, alg ) self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL) oid = _obj2txt(self._backend, alg[0].algorithm) return x509.ObjectIdentifier(oid) @property def issuer(self): issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl) self._backend.openssl_assert(issuer != self._backend._ffi.NULL) return _decode_x509_name(self._backend, issuer) @property def next_update(self): nu = self._backend._lib.X509_CRL_get_nextUpdate(self._x509_crl) self._backend.openssl_assert(nu != self._backend._ffi.NULL) return _parse_asn1_time(self._backend, nu) @property def last_update(self): lu = self._backend._lib.X509_CRL_get_lastUpdate(self._x509_crl) self._backend.openssl_assert(lu != self._backend._ffi.NULL) return _parse_asn1_time(self._backend, lu) @property def signature(self): sig = self._backend._ffi.new("ASN1_BIT_STRING **") self._backend._lib.X509_CRL_get0_signature( self._x509_crl, sig, self._backend._ffi.NULL ) self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL) return _asn1_string_to_bytes(self._backend, sig[0]) @property def tbs_certlist_bytes(self): pp = self._backend._ffi.new("unsigned char **") res = self._backend._lib.i2d_re_X509_CRL_tbs(self._x509_crl, pp) self._backend.openssl_assert(res > 0) pp = self._backend._ffi.gc( pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) ) return self._backend._ffi.buffer(pp[0], res)[:] def public_bytes(self, encoding): bio = self._backend._create_mem_bio_gc() if encoding is serialization.Encoding.PEM: res = self._backend._lib.PEM_write_bio_X509_CRL( bio, self._x509_crl ) elif encoding is serialization.Encoding.DER: res = self._backend._lib.i2d_X509_CRL_bio(bio, self._x509_crl) else: raise TypeError("encoding must be an item from the Encoding enum") self._backend.openssl_assert(res == 1) return self._backend._read_mem_bio(bio) def _revoked_cert(self, idx): revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) r = self._backend._lib.sk_X509_REVOKED_value(revoked, idx) self._backend.openssl_assert(r != self._backend._ffi.NULL) return _RevokedCertificate(self._backend, self, r) def __iter__(self): for i in range(len(self)): yield self._revoked_cert(i) def __getitem__(self, idx): if isinstance(idx, slice): start, stop, step = idx.indices(len(self)) return [self._revoked_cert(i) for i in range(start, stop, step)] else: idx = operator.index(idx) if idx < 0: idx += len(self) if not 0 <= idx < len(self): raise IndexError return self._revoked_cert(idx) def __len__(self): revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) if revoked == self._backend._ffi.NULL: return 0 else: return self._backend._lib.sk_X509_REVOKED_num(revoked) @utils.cached_property def extensions(self): return self._backend._crl_extension_parser.parse(self._x509_crl) def is_signature_valid(self, public_key): if not isinstance( public_key, (dsa.DSAPublicKey, rsa.RSAPublicKey, ec.EllipticCurvePublicKey), ): raise TypeError( "Expecting one of DSAPublicKey, RSAPublicKey," " or EllipticCurvePublicKey." ) res = self._backend._lib.X509_CRL_verify( self._x509_crl, public_key._evp_pkey ) if res != 1: self._backend._consume_errors() return False return True @utils.register_interface(x509.CertificateSigningRequest) class _CertificateSigningRequest(object): def __init__(self, backend, x509_req): self._backend = backend self._x509_req = x509_req def __eq__(self, other): if not isinstance(other, _CertificateSigningRequest): return NotImplemented self_bytes = self.public_bytes(serialization.Encoding.DER) other_bytes = other.public_bytes(serialization.Encoding.DER) return self_bytes == other_bytes def __ne__(self, other): return not self == other def __hash__(self): return hash(self.public_bytes(serialization.Encoding.DER)) def public_key(self): pkey = self._backend._lib.X509_REQ_get_pubkey(self._x509_req) self._backend.openssl_assert(pkey != self._backend._ffi.NULL) pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) return self._backend._evp_pkey_to_public_key(pkey) @property def subject(self): subject = self._backend._lib.X509_REQ_get_subject_name(self._x509_req) self._backend.openssl_assert(subject != self._backend._ffi.NULL) return _decode_x509_name(self._backend, subject) @property def signature_hash_algorithm(self): oid = self.signature_algorithm_oid try: return x509._SIG_OIDS_TO_HASH[oid] except KeyError: raise UnsupportedAlgorithm( "Signature algorithm OID:{} not recognized".format(oid) ) @property def signature_algorithm_oid(self): alg = self._backend._ffi.new("X509_ALGOR **") self._backend._lib.X509_REQ_get0_signature( self._x509_req, self._backend._ffi.NULL, alg ) self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL) oid = _obj2txt(self._backend, alg[0].algorithm) return x509.ObjectIdentifier(oid) @utils.cached_property def extensions(self): x509_exts = self._backend._lib.X509_REQ_get_extensions(self._x509_req) x509_exts = self._backend._ffi.gc( x509_exts, lambda x: self._backend._lib.sk_X509_EXTENSION_pop_free( x, self._backend._ffi.addressof( self._backend._lib._original_lib, "X509_EXTENSION_free" ), ), ) return self._backend._csr_extension_parser.parse(x509_exts) def public_bytes(self, encoding): bio = self._backend._create_mem_bio_gc() if encoding is serialization.Encoding.PEM: res = self._backend._lib.PEM_write_bio_X509_REQ( bio, self._x509_req ) elif encoding is serialization.Encoding.DER: res = self._backend._lib.i2d_X509_REQ_bio(bio, self._x509_req) else: raise TypeError("encoding must be an item from the Encoding enum") self._backend.openssl_assert(res == 1) return self._backend._read_mem_bio(bio) @property def tbs_certrequest_bytes(self): pp = self._backend._ffi.new("unsigned char **") res = self._backend._lib.i2d_re_X509_REQ_tbs(self._x509_req, pp) self._backend.openssl_assert(res > 0) pp = self._backend._ffi.gc( pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) ) return self._backend._ffi.buffer(pp[0], res)[:] @property def signature(self): sig = self._backend._ffi.new("ASN1_BIT_STRING **") self._backend._lib.X509_REQ_get0_signature( self._x509_req, sig, self._backend._ffi.NULL ) self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL) return _asn1_string_to_bytes(self._backend, sig[0]) @property def is_signature_valid(self): pkey = self._backend._lib.X509_REQ_get_pubkey(self._x509_req) self._backend.openssl_assert(pkey != self._backend._ffi.NULL) pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) res = self._backend._lib.X509_REQ_verify(self._x509_req, pkey) if res != 1: self._backend._consume_errors() return False return True def get_attribute_for_oid(self, oid): obj = _txt2obj_gc(self._backend, oid.dotted_string) pos = self._backend._lib.X509_REQ_get_attr_by_OBJ( self._x509_req, obj, -1 ) if pos == -1: raise x509.AttributeNotFound( "No {} attribute was found".format(oid), oid ) attr = self._backend._lib.X509_REQ_get_attr(self._x509_req, pos) self._backend.openssl_assert(attr != self._backend._ffi.NULL) # We don't support multiple valued attributes for now. self._backend.openssl_assert( self._backend._lib.X509_ATTRIBUTE_count(attr) == 1 ) asn1_type = self._backend._lib.X509_ATTRIBUTE_get0_type(attr, 0) self._backend.openssl_assert(asn1_type != self._backend._ffi.NULL) # We need this to ensure that our C type cast is safe. # Also this should always be a sane string type, but we'll see if # that is true in the real world... if asn1_type.type not in ( _ASN1Type.UTF8String.value, _ASN1Type.PrintableString.value, _ASN1Type.IA5String.value, ): raise ValueError( "OID {} has a disallowed ASN.1 type: {}".format( oid, asn1_type.type ) ) data = self._backend._lib.X509_ATTRIBUTE_get0_data( attr, 0, asn1_type.type, self._backend._ffi.NULL ) self._backend.openssl_assert(data != self._backend._ffi.NULL) # This cast is safe iff we assert on the type above to ensure # that it is always a type of ASN1_STRING data = self._backend._ffi.cast("ASN1_STRING *", data) return _asn1_string_to_bytes(self._backend, data) @utils.register_interface( x509.certificate_transparency.SignedCertificateTimestamp ) class _SignedCertificateTimestamp(object): def __init__(self, backend, sct_list, sct): self._backend = backend # Keep the SCT_LIST that this SCT came from alive. self._sct_list = sct_list self._sct = sct @property def version(self): version = self._backend._lib.SCT_get_version(self._sct) assert version == self._backend._lib.SCT_VERSION_V1 return x509.certificate_transparency.Version.v1 @property def log_id(self): out = self._backend._ffi.new("unsigned char **") log_id_length = self._backend._lib.SCT_get0_log_id(self._sct, out) assert log_id_length >= 0 return self._backend._ffi.buffer(out[0], log_id_length)[:] @property def timestamp(self): timestamp = self._backend._lib.SCT_get_timestamp(self._sct) milliseconds = timestamp % 1000 return datetime.datetime.utcfromtimestamp(timestamp // 1000).replace( microsecond=milliseconds * 1000 ) @property def entry_type(self): entry_type = self._backend._lib.SCT_get_log_entry_type(self._sct) # We currently only support loading SCTs from the X.509 extension, so # we only have precerts. assert entry_type == self._backend._lib.CT_LOG_ENTRY_TYPE_PRECERT return x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE @property def _signature(self): ptrptr = self._backend._ffi.new("unsigned char **") res = self._backend._lib.SCT_get0_signature(self._sct, ptrptr) self._backend.openssl_assert(res > 0) self._backend.openssl_assert(ptrptr[0] != self._backend._ffi.NULL) return self._backend._ffi.buffer(ptrptr[0], res)[:] def __hash__(self): return hash(self._signature) def __eq__(self, other): if not isinstance(other, _SignedCertificateTimestamp): return NotImplemented return self._signature == other._signature def __ne__(self, other): return not self == other