diff options
Diffstat (limited to 'src')
23 files changed, 797 insertions, 176 deletions
diff --git a/src/_cffi_src/build_commoncrypto.py b/src/_cffi_src/build_commoncrypto.py index 1c2692a7..4e69b6d1 100644 --- a/src/_cffi_src/build_commoncrypto.py +++ b/src/_cffi_src/build_commoncrypto.py @@ -22,6 +22,7 @@ ffi = build_ffi_for_binding( "seckey", "seckeychain", "sectransform", + "sectrust", ], extra_link_args=[ "-framework", "Security", "-framework", "CoreFoundation" diff --git a/src/_cffi_src/build_constant_time.py b/src/_cffi_src/build_constant_time.py index 6d9a8f54..7a11f7b5 100644 --- a/src/_cffi_src/build_constant_time.py +++ b/src/_cffi_src/build_constant_time.py @@ -5,9 +5,8 @@ from __future__ import absolute_import, division, print_function import os -import sys -from _cffi_src.utils import build_ffi, extra_link_args +from _cffi_src.utils import build_ffi, compiler_type, extra_link_args with open(os.path.join( @@ -24,5 +23,5 @@ ffi = build_ffi( module_name="_constant_time", cdef_source=types, verify_source=functions, - extra_link_args=extra_link_args(sys.platform), + extra_link_args=extra_link_args(compiler_type()), ) diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py index c856e3d9..c47b3082 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -7,7 +7,9 @@ from __future__ import absolute_import, division, print_function import os import sys -from _cffi_src.utils import build_ffi_for_binding, extra_link_args +from _cffi_src.utils import ( + build_ffi_for_binding, compiler_type, extra_link_args +) def _get_openssl_libraries(platform): @@ -92,5 +94,5 @@ ffi = build_ffi_for_binding( pre_include=_OSX_PRE_INCLUDE, post_include=_OSX_POST_INCLUDE, libraries=_get_openssl_libraries(sys.platform), - extra_link_args=extra_link_args(sys.platform), + extra_link_args=extra_link_args(compiler_type()), ) diff --git a/src/_cffi_src/build_padding.py b/src/_cffi_src/build_padding.py index 5df93d80..4c5096a1 100644 --- a/src/_cffi_src/build_padding.py +++ b/src/_cffi_src/build_padding.py @@ -5,9 +5,8 @@ from __future__ import absolute_import, division, print_function import os -import sys -from _cffi_src.utils import build_ffi, extra_link_args +from _cffi_src.utils import build_ffi, compiler_type, extra_link_args with open(os.path.join( @@ -24,5 +23,5 @@ ffi = build_ffi( module_name="_padding", cdef_source=types, verify_source=functions, - extra_link_args=extra_link_args(sys.platform), + extra_link_args=extra_link_args(compiler_type()), ) diff --git a/src/_cffi_src/commoncrypto/sectrust.py b/src/_cffi_src/commoncrypto/sectrust.py new file mode 100644 index 00000000..b787afad --- /dev/null +++ b/src/_cffi_src/commoncrypto/sectrust.py @@ -0,0 +1,22 @@ +# 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 + +INCLUDES = """ +#include <Security/SecTrust.h> +""" + +TYPES = """ +""" + +FUNCTIONS = """ +OSStatus SecTrustCopyAnchorCertificates(CFArrayRef *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py index ddf4b9c5..30bd2451 100644 --- a/src/_cffi_src/openssl/asn1.py +++ b/src/_cffi_src/openssl/asn1.py @@ -95,13 +95,16 @@ ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *, time_t); /* ASN1 GENERALIZEDTIME */ int ASN1_GENERALIZEDTIME_set_string(ASN1_GENERALIZEDTIME *, const char *); +ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_set(ASN1_GENERALIZEDTIME *, time_t); void ASN1_GENERALIZEDTIME_free(ASN1_GENERALIZEDTIME *); +int i2d_ASN1_GENERALIZEDTIME(ASN1_GENERALIZEDTIME *, unsigned char **); /* ASN1 ENUMERATED */ ASN1_ENUMERATED *ASN1_ENUMERATED_new(void); void ASN1_ENUMERATED_free(ASN1_ENUMERATED *); int ASN1_ENUMERATED_set(ASN1_ENUMERATED *, long); long ASN1_ENUMERATED_get(ASN1_ENUMERATED *); +int i2d_ASN1_ENUMERATED(ASN1_ENUMERATED *, unsigned char **); ASN1_VALUE *ASN1_item_d2i(ASN1_VALUE **, const unsigned char **, long, const ASN1_ITEM *); diff --git a/src/_cffi_src/openssl/bignum.py b/src/_cffi_src/openssl/bignum.py index ae035007..455afdc1 100644 --- a/src/_cffi_src/openssl/bignum.py +++ b/src/_cffi_src/openssl/bignum.py @@ -71,6 +71,8 @@ int BN_mask_bits(BIGNUM *, int); """ MACROS = """ +int BN_num_bytes(const BIGNUM *); + int BN_zero(BIGNUM *); int BN_one(BIGNUM *); int BN_mod(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py index 6ec13775..9d97be16 100644 --- a/src/_cffi_src/openssl/err.py +++ b/src/_cffi_src/openssl/err.py @@ -230,6 +230,7 @@ static const int RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY; static const int RSA_R_BLOCK_TYPE_IS_NOT_01; static const int RSA_R_BLOCK_TYPE_IS_NOT_02; static const int RSA_R_PKCS_DECODING_ERROR; +static const int RSA_R_OAEP_DECODING_ERROR; static const int RSA_F_RSA_SIGN; """ diff --git a/src/_cffi_src/openssl/pem.py b/src/_cffi_src/openssl/pem.py index 846e64e3..4eb6bb45 100644 --- a/src/_cffi_src/openssl/pem.py +++ b/src/_cffi_src/openssl/pem.py @@ -79,6 +79,7 @@ MACROS = """ int PEM_write_bio_ECPrivateKey(BIO *, EC_KEY *, const EVP_CIPHER *, unsigned char *, int, pem_password_cb *, void *); +int PEM_write_bio_DHparams(BIO *, DH *); """ CUSTOMIZATIONS = """ diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py index 0fc49ac5..c5eb600a 100644 --- a/src/_cffi_src/openssl/x509.py +++ b/src/_cffi_src/openssl/x509.py @@ -193,6 +193,8 @@ X509_EXTENSION *X509_REVOKED_get_ext(X509_REVOKED *, int); int X509_REVOKED_add_ext(X509_REVOKED *, X509_EXTENSION*, int); int X509_REVOKED_add1_ext_i2d(X509_REVOKED *, int, void *, int, unsigned long); +int X509_REVOKED_set_revocationDate(X509_REVOKED *, ASN1_TIME *); + X509_CRL *X509_CRL_new(void); X509_CRL *d2i_X509_CRL_bio(BIO *, X509_CRL **); X509_EXTENSION *X509_CRL_get_ext(X509_CRL *, int); @@ -268,6 +270,8 @@ void PKCS8_PRIV_KEY_INFO_free(PKCS8_PRIV_KEY_INFO *); """ MACROS = """ +X509_REVOKED *Cryptography_X509_REVOKED_dup(X509_REVOKED *); + int i2d_X509_CINF(X509_CINF *, unsigned char **); int i2d_X509_CRL_INFO(X509_CRL_INFO *, unsigned char **); int i2d_X509_REQ_INFO(X509_REQ_INFO *, unsigned char **); @@ -290,6 +294,7 @@ X509_EXTENSIONS *sk_X509_EXTENSION_new_null(void); int sk_X509_EXTENSION_num(X509_EXTENSIONS *); X509_EXTENSION *sk_X509_EXTENSION_value(X509_EXTENSIONS *, int); int sk_X509_EXTENSION_push(X509_EXTENSIONS *, X509_EXTENSION *); +int sk_X509_EXTENSION_insert(X509_EXTENSIONS *, X509_EXTENSION *, int); X509_EXTENSION *sk_X509_EXTENSION_delete(X509_EXTENSIONS *, int); void sk_X509_EXTENSION_free(X509_EXTENSIONS *); @@ -362,4 +367,12 @@ int (*i2d_ECPrivateKey_bio)(BIO *, EC_KEY *) = NULL; EC_KEY *(*o2i_ECPublicKey)(EC_KEY **, const unsigned char **, long) = NULL; int (*i2o_ECPublicKey)(EC_KEY *, unsigned char **) = NULL; #endif + +/* X509_REVOKED_dup only exists on 1.0.2+. It is implemented using + IMPLEMENT_ASN1_DUP_FUNCTION. The below is the equivalent so we have + it available on all OpenSSLs. */ +X509_REVOKED *Cryptography_X509_REVOKED_dup(X509_REVOKED *rev) { + return ASN1_item_dup(ASN1_ITEM_rptr(X509_REVOKED), rev); +} + """ diff --git a/src/_cffi_src/utils.py b/src/_cffi_src/utils.py index 0b00353e..bdce2f3b 100644 --- a/src/_cffi_src/utils.py +++ b/src/_cffi_src/utils.py @@ -5,6 +5,8 @@ from __future__ import absolute_import, division, print_function import sys +from distutils.ccompiler import new_compiler +from distutils.dist import Distribution from cffi import FFI @@ -79,10 +81,23 @@ def build_ffi(module_name, cdef_source, verify_source, libraries=[], return ffi -def extra_link_args(platform): - if platform != "win32": - return [] +def extra_link_args(compiler_type): + if compiler_type == 'msvc': + # Enable NX and ASLR for Windows builds on MSVC. These are enabled by + # default on Python 3.3+ but not on 2.x. + return ['/NXCOMPAT', '/DYNAMICBASE'] else: - # Enable NX and ASLR for Windows builds. These are enabled by default - # on Python 3.3+ but not on 2.x. - return ["/NXCOMPAT", "/DYNAMICBASE"] + return [] + + +def compiler_type(): + """ + Gets the compiler type from distutils. On Windows with MSVC it will be + "msvc". On OS X and linux it is "unix". + """ + dist = Distribution() + dist.parse_config_files() + cmd = dist.get_command_obj('build') + cmd.ensure_finalized() + compiler = new_compiler(compiler=cmd.compiler) + return compiler.compiler_type diff --git a/src/cryptography/__about__.py b/src/cryptography/__about__.py index fbc1e735..f25cbc89 100644 --- a/src/cryptography/__about__.py +++ b/src/cryptography/__about__.py @@ -20,4 +20,4 @@ __author__ = "The cryptography developers" __email__ = "cryptography-dev@python.org" __license__ = "BSD or Apache License, Version 2.0" -__copyright__ = "Copyright 2013-2015 {0}".format(__author__) +__copyright__ = "Copyright 2013-2016 {0}".format(__author__) diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index 92d9653a..5b9e6f38 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -292,6 +292,20 @@ class X509Backend(object): Create and sign an X.509 certificate from a CertificateBuilder object. """ + @abc.abstractmethod + def create_x509_crl(self, builder, private_key, algorithm): + """ + Create and sign an X.509 CertificateRevocationList from a + CertificateRevocationListBuilder object. + """ + + @abc.abstractmethod + def create_x509_revoked_certificate(self, builder): + """ + Create a RevokedCertificate object from a RevokedCertificateBuilder + object. + """ + @six.add_metaclass(abc.ABCMeta) class DHBackend(object): diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index bbaaf424..65f18531 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -384,3 +384,21 @@ class MultiBackend(object): "This backend does not support X.509.", _Reasons.UNSUPPORTED_X509 ) + + def create_x509_crl(self, builder, private_key, algorithm): + for b in self._filtered_backends(X509Backend): + return b.create_x509_crl(builder, private_key, algorithm) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def create_x509_revoked_certificate(self, builder): + for b in self._filtered_backends(X509Backend): + return b.create_x509_revoked_certificate(builder) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index e69554f9..e8b0322e 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -37,8 +37,9 @@ from cryptography.hazmat.backends.openssl.rsa import ( _RSAPrivateKey, _RSAPublicKey ) from cryptography.hazmat.backends.openssl.x509 import ( - _Certificate, _CertificateRevocationList, _CertificateSigningRequest, - _DISTPOINT_TYPE_FULLNAME, _DISTPOINT_TYPE_RELATIVENAME + _CRL_ENTRY_REASON_ENUM_TO_CODE, _Certificate, _CertificateRevocationList, + _CertificateSigningRequest, _DISTPOINT_TYPE_FULLNAME, + _DISTPOINT_TYPE_RELATIVENAME, _RevokedCertificate ) from cryptography.hazmat.bindings._openssl import ffi as _ffi from cryptography.hazmat.bindings.openssl import binding @@ -53,7 +54,7 @@ from cryptography.hazmat.primitives.ciphers.algorithms import ( from cryptography.hazmat.primitives.ciphers.modes import ( CBC, CFB, CFB8, CTR, ECB, GCM, OFB ) -from cryptography.x509.oid import ExtensionOID, NameOID +from cryptography.x509.oid import CRLEntryExtensionOID, ExtensionOID, NameOID _MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) @@ -115,10 +116,9 @@ def _encode_asn1_str_gc(backend, data, length): return s -def _encode_inhibit_any_policy(backend, inhibit_any_policy): - asn1int = _encode_asn1_int_gc(backend, inhibit_any_policy.skip_certs) - pp = backend._ffi.new('unsigned char **') - r = backend._lib.i2d_ASN1_INTEGER(asn1int, pp) +def _encode_extension_to_der(backend, i2d_func, value): + pp = backend._ffi.new("unsigned char **") + r = i2d_func(value, pp) backend.openssl_assert(r > 0) pp = backend._ffi.gc( pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) @@ -126,6 +126,13 @@ def _encode_inhibit_any_policy(backend, inhibit_any_policy): return pp, r +def _encode_inhibit_any_policy(backend, inhibit_any_policy): + asn1int = _encode_asn1_int_gc(backend, inhibit_any_policy.skip_certs) + return _encode_extension_to_der( + backend, backend._lib.i2d_ASN1_INTEGER, asn1int + ) + + def _encode_name(backend, attributes): """ The X509_NAME created will not be gc'd. Use _encode_name_gc if needed. @@ -153,6 +160,41 @@ def _encode_name_gc(backend, attributes): return subject +def _encode_crl_number(backend, crl_number): + asn1int = _encode_asn1_int_gc(backend, crl_number.crl_number) + return _encode_extension_to_der( + backend, backend._lib.i2d_ASN1_INTEGER, asn1int + ) + + +def _encode_crl_reason(backend, crl_reason): + asn1enum = backend._lib.ASN1_ENUMERATED_new() + backend.openssl_assert(asn1enum != backend._ffi.NULL) + asn1enum = backend._ffi.gc(asn1enum, backend._lib.ASN1_ENUMERATED_free) + res = backend._lib.ASN1_ENUMERATED_set( + asn1enum, _CRL_ENTRY_REASON_ENUM_TO_CODE[crl_reason.reason] + ) + backend.openssl_assert(res == 1) + + return _encode_extension_to_der( + backend, backend._lib.i2d_ASN1_ENUMERATED, asn1enum + ) + + +def _encode_invalidity_date(backend, invalidity_date): + time = backend._lib.ASN1_GENERALIZEDTIME_set( + backend._ffi.NULL, calendar.timegm( + invalidity_date.invalidity_date.timetuple() + ) + ) + backend.openssl_assert(time != backend._ffi.NULL) + time = backend._ffi.gc(time, backend._lib.ASN1_GENERALIZEDTIME_free) + + return _encode_extension_to_der( + backend, backend._lib.i2d_ASN1_GENERALIZEDTIME, time + ) + + def _encode_certificate_policies(backend, certificate_policies): cp = backend._lib.sk_POLICYINFO_new_null() backend.openssl_assert(cp != backend._ffi.NULL) @@ -200,13 +242,9 @@ def _encode_certificate_policies(backend, certificate_policies): pi.qualifiers = pqis - pp = backend._ffi.new('unsigned char **') - r = backend._lib.i2d_CERTIFICATEPOLICIES(cp, pp) - backend.openssl_assert(r > 0) - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + return _encode_extension_to_der( + backend, backend._lib.i2d_CERTIFICATEPOLICIES, cp ) - return pp, r def _encode_notice_reference(backend, notice): @@ -282,13 +320,9 @@ def _encode_key_usage(backend, key_usage): res = set_bit(ku, 8, 0) backend.openssl_assert(res == 1) - pp = backend._ffi.new('unsigned char **') - r = backend._lib.i2d_ASN1_BIT_STRING(ku, pp) - backend.openssl_assert(r > 0) - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + return _encode_extension_to_der( + backend, backend._lib.i2d_ASN1_BIT_STRING, ku ) - return pp, r def _encode_authority_key_identifier(backend, authority_keyid): @@ -312,13 +346,9 @@ def _encode_authority_key_identifier(backend, authority_keyid): backend, authority_keyid.authority_cert_serial_number ) - pp = backend._ffi.new('unsigned char **') - r = backend._lib.i2d_AUTHORITY_KEYID(akid, pp) - backend.openssl_assert(r > 0) - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + return _encode_extension_to_der( + backend, backend._lib.i2d_AUTHORITY_KEYID, akid ) - return pp, r def _encode_basic_constraints(backend, basic_constraints): @@ -332,14 +362,9 @@ def _encode_basic_constraints(backend, basic_constraints): backend, basic_constraints.path_length ) - # Fetch the encoded payload. - pp = backend._ffi.new('unsigned char **') - r = backend._lib.i2d_BASIC_CONSTRAINTS(constraints, pp) - backend.openssl_assert(r > 0) - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + return _encode_extension_to_der( + backend, backend._lib.i2d_BASIC_CONSTRAINTS, constraints ) - return pp, r def _encode_authority_information_access(backend, authority_info_access): @@ -359,13 +384,9 @@ def _encode_authority_information_access(backend, authority_info_access): res = backend._lib.sk_ACCESS_DESCRIPTION_push(aia, ad) backend.openssl_assert(res >= 1) - pp = backend._ffi.new('unsigned char **') - r = backend._lib.i2d_AUTHORITY_INFO_ACCESS(aia, pp) - backend.openssl_assert(r > 0) - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + return _encode_extension_to_der( + backend, backend._lib.i2d_AUTHORITY_INFO_ACCESS, aia ) - return pp, r def _encode_general_names(backend, names): @@ -384,24 +405,16 @@ def _encode_alt_name(backend, san): general_names = backend._ffi.gc( general_names, backend._lib.GENERAL_NAMES_free ) - pp = backend._ffi.new("unsigned char **") - r = backend._lib.i2d_GENERAL_NAMES(general_names, pp) - backend.openssl_assert(r > 0) - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + return _encode_extension_to_der( + backend, backend._lib.i2d_GENERAL_NAMES, general_names ) - return pp, r def _encode_subject_key_identifier(backend, ski): asn1_str = _encode_asn1_str_gc(backend, ski.digest, len(ski.digest)) - pp = backend._ffi.new("unsigned char **") - r = backend._lib.i2d_ASN1_OCTET_STRING(asn1_str, pp) - backend.openssl_assert(r > 0) - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + return _encode_extension_to_der( + backend, backend._lib.i2d_ASN1_OCTET_STRING, asn1_str ) - return pp, r def _encode_general_name(backend, name): @@ -499,15 +512,10 @@ def _encode_extended_key_usage(backend, extended_key_usage): res = backend._lib.sk_ASN1_OBJECT_push(eku, obj) backend.openssl_assert(res >= 1) - pp = backend._ffi.new('unsigned char **') - r = backend._lib.i2d_EXTENDED_KEY_USAGE( - backend._ffi.cast("EXTENDED_KEY_USAGE *", eku), pp - ) - backend.openssl_assert(r > 0) - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + eku_ptr = backend._ffi.cast("EXTENDED_KEY_USAGE *", eku) + return _encode_extension_to_der( + backend, backend._lib.i2d_EXTENDED_KEY_USAGE, eku_ptr ) - return pp, r _CRLREASONFLAGS = { @@ -562,13 +570,9 @@ def _encode_crl_distribution_points(backend, crl_distribution_points): res = backend._lib.sk_DIST_POINT_push(cdp, dp) backend.openssl_assert(res >= 1) - pp = backend._ffi.new('unsigned char **') - r = backend._lib.i2d_CRL_DIST_POINTS(cdp, pp) - backend.openssl_assert(r > 0) - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + return _encode_extension_to_der( + backend, backend._lib.i2d_CRL_DIST_POINTS, cdp ) - return pp, r def _encode_name_constraints(backend, name_constraints): @@ -584,13 +588,9 @@ def _encode_name_constraints(backend, name_constraints): ) nc.excludedSubtrees = excluded - pp = backend._ffi.new('unsigned char **') - r = backend._lib.Cryptography_i2d_NAME_CONSTRAINTS(nc, pp) - assert r > 0 - pp = backend._ffi.gc( - pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + return _encode_extension_to_der( + backend, backend._lib.Cryptography_i2d_NAME_CONSTRAINTS, nc ) - return pp, r def _encode_general_subtree(backend, subtrees): @@ -625,6 +625,21 @@ _EXTENSION_ENCODE_HANDLERS = { ExtensionOID.NAME_CONSTRAINTS: _encode_name_constraints, } +_CRL_EXTENSION_ENCODE_HANDLERS = { + ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name, + ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( + _encode_authority_information_access + ), + ExtensionOID.CRL_NUMBER: _encode_crl_number, +} + +_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS = { + CRLEntryExtensionOID.CERTIFICATE_ISSUER: _encode_alt_name, + CRLEntryExtensionOID.CRL_REASON: _encode_crl_reason, + CRLEntryExtensionOID.INVALIDITY_DATE: _encode_invalidity_date, +} + class _PasswordUserdata(object): def __init__(self, password): @@ -899,17 +914,14 @@ class Backend(object): assert bn != self._ffi.NULL if six.PY3: # Python 3 has constant time from_bytes, so use that. - - bn_num_bytes = (self._lib.BN_num_bits(bn) + 7) // 8 + bn_num_bytes = self._lib.BN_num_bytes(bn) bin_ptr = self._ffi.new("unsigned char[]", bn_num_bytes) bin_len = self._lib.BN_bn2bin(bn, bin_ptr) # A zero length means the BN has value 0 self.openssl_assert(bin_len >= 0) return int.from_bytes(self._ffi.buffer(bin_ptr)[:bin_len], "big") - else: # Under Python 2 the best we can do is hex() - hex_cdata = self._lib.BN_bn2hex(bn) self.openssl_assert(hex_cdata != self._ffi.NULL) hex_str = self._ffi.string(hex_cdata) @@ -1312,30 +1324,21 @@ class Backend(object): self.openssl_assert(res == 1) # Add extensions. - extensions = self._lib.sk_X509_EXTENSION_new_null() - self.openssl_assert(extensions != self._ffi.NULL) - extensions = self._ffi.gc( - extensions, - self._lib.sk_X509_EXTENSION_free, + sk_extension = self._lib.sk_X509_EXTENSION_new_null() + self.openssl_assert(sk_extension != self._ffi.NULL) + sk_extension = self._ffi.gc( + sk_extension, self._lib.sk_X509_EXTENSION_free ) - for extension in builder._extensions: - try: - encode = _EXTENSION_ENCODE_HANDLERS[extension.oid] - except KeyError: - raise NotImplementedError('Extension not yet supported.') - - pp, r = encode(self, extension.value) - obj = _txt2obj_gc(self, extension.oid.dotted_string) - extension = self._lib.X509_EXTENSION_create_by_OBJ( - self._ffi.NULL, - obj, - 1 if extension.critical else 0, - _encode_asn1_str_gc(self, pp[0], r), - ) - self.openssl_assert(extension != self._ffi.NULL) - res = self._lib.sk_X509_EXTENSION_push(extensions, extension) - self.openssl_assert(res >= 1) - res = self._lib.X509_REQ_add_extensions(x509_req, extensions) + # gc is not necessary for CSRs, as sk_X509_EXTENSION_free + # will release all the X509_EXTENSIONs. + self._create_x509_extensions( + extensions=builder._extensions, + handlers=_EXTENSION_ENCODE_HANDLERS, + x509_obj=sk_extension, + add_func=self._lib.sk_X509_EXTENSION_insert, + gc=False + ) + res = self._lib.X509_REQ_add_extensions(x509_req, sk_extension) self.openssl_assert(res == 1) # Sign the request using the requester's private key. @@ -1416,24 +1419,13 @@ class Backend(object): self.openssl_assert(res != self._ffi.NULL) # Add extensions. - for i, extension in enumerate(builder._extensions): - try: - encode = _EXTENSION_ENCODE_HANDLERS[extension.oid] - except KeyError: - raise NotImplementedError('Extension not yet supported.') - - pp, r = encode(self, extension.value) - obj = _txt2obj_gc(self, extension.oid.dotted_string) - extension = self._lib.X509_EXTENSION_create_by_OBJ( - self._ffi.NULL, - obj, - 1 if extension.critical else 0, - _encode_asn1_str_gc(self, pp[0], r) - ) - self.openssl_assert(extension != self._ffi.NULL) - extension = self._ffi.gc(extension, self._lib.X509_EXTENSION_free) - res = self._lib.X509_add_ext(x509_cert, extension, i) - self.openssl_assert(res == 1) + self._create_x509_extensions( + extensions=builder._extensions, + handlers=_EXTENSION_ENCODE_HANDLERS, + x509_obj=x509_cert, + add_func=self._lib.X509_add_ext, + gc=True + ) # Set the issuer name. res = self._lib.X509_set_issuer_name( @@ -1455,6 +1447,147 @@ class Backend(object): return _Certificate(self, x509_cert) + def create_x509_crl(self, builder, private_key, algorithm): + if not isinstance(builder, x509.CertificateRevocationListBuilder): + raise TypeError('Builder type mismatch.') + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError('Algorithm must be a registered hash algorithm.') + + if self._lib.OPENSSL_VERSION_NUMBER <= 0x10001000: + if isinstance(private_key, _DSAPrivateKey): + raise NotImplementedError( + "CRL signatures aren't implemented for DSA" + " keys on OpenSSL versions less than 1.0.1." + ) + if isinstance(private_key, _EllipticCurvePrivateKey): + raise NotImplementedError( + "CRL signatures aren't implemented for EC" + " keys on OpenSSL versions less than 1.0.1." + ) + + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode('ascii') + ) + self.openssl_assert(evp_md != self._ffi.NULL) + + # Create an empty CRL. + x509_crl = self._lib.X509_CRL_new() + x509_crl = self._ffi.gc(x509_crl, backend._lib.X509_CRL_free) + + # Set the x509 CRL version. We only support v2 (integer value 1). + res = self._lib.X509_CRL_set_version(x509_crl, 1) + self.openssl_assert(res == 1) + + # Set the issuer name. + res = self._lib.X509_CRL_set_issuer_name( + x509_crl, _encode_name_gc(self, list(builder._issuer_name)) + ) + self.openssl_assert(res == 1) + + # Set the last update time. + last_update = self._lib.ASN1_TIME_set( + self._ffi.NULL, calendar.timegm(builder._last_update.timetuple()) + ) + self.openssl_assert(last_update != self._ffi.NULL) + last_update = self._ffi.gc(last_update, self._lib.ASN1_TIME_free) + res = self._lib.X509_CRL_set_lastUpdate(x509_crl, last_update) + self.openssl_assert(res == 1) + + # Set the next update time. + next_update = self._lib.ASN1_TIME_set( + self._ffi.NULL, calendar.timegm(builder._next_update.timetuple()) + ) + self.openssl_assert(next_update != self._ffi.NULL) + next_update = self._ffi.gc(next_update, self._lib.ASN1_TIME_free) + res = self._lib.X509_CRL_set_nextUpdate(x509_crl, next_update) + self.openssl_assert(res == 1) + + # Add extensions. + self._create_x509_extensions( + extensions=builder._extensions, + handlers=_CRL_EXTENSION_ENCODE_HANDLERS, + x509_obj=x509_crl, + add_func=self._lib.X509_CRL_add_ext, + gc=True + ) + + # add revoked certificates + for revoked_cert in builder._revoked_certificates: + # Duplicating because the X509_CRL takes ownership and will free + # this memory when X509_CRL_free is called. + revoked = self._lib.Cryptography_X509_REVOKED_dup( + revoked_cert._x509_revoked + ) + self.openssl_assert(revoked != self._ffi.NULL) + res = self._lib.X509_CRL_add0_revoked(x509_crl, revoked) + self.openssl_assert(res == 1) + + res = self._lib.X509_CRL_sign( + x509_crl, private_key._evp_pkey, evp_md + ) + if res == 0: + errors = self._consume_errors() + self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_RSA) + self.openssl_assert( + errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY + ) + raise ValueError("Digest too big for RSA key") + + return _CertificateRevocationList(self, x509_crl) + + def _create_x509_extensions(self, extensions, handlers, x509_obj, + add_func, gc): + for i, extension in enumerate(extensions): + try: + encode = handlers[extension.oid] + except KeyError: + raise NotImplementedError( + 'Extension not supported: {0}'.format(extension.oid) + ) + + pp, r = encode(self, extension.value) + obj = _txt2obj_gc(self, extension.oid.dotted_string) + x509_extension = self._lib.X509_EXTENSION_create_by_OBJ( + self._ffi.NULL, + obj, + 1 if extension.critical else 0, + _encode_asn1_str_gc(self, pp[0], r) + ) + self.openssl_assert(x509_extension != self._ffi.NULL) + if gc: + x509_extension = self._ffi.gc( + x509_extension, self._lib.X509_EXTENSION_free + ) + res = add_func(x509_obj, x509_extension, i) + self.openssl_assert(res >= 1) + + def create_x509_revoked_certificate(self, builder): + if not isinstance(builder, x509.RevokedCertificateBuilder): + raise TypeError('Builder type mismatch.') + + x509_revoked = self._lib.X509_REVOKED_new() + self.openssl_assert(x509_revoked != self._ffi.NULL) + x509_revoked = self._ffi.gc(x509_revoked, self._lib.X509_REVOKED_free) + serial_number = _encode_asn1_int_gc(self, builder._serial_number) + res = self._lib.X509_REVOKED_set_serialNumber( + x509_revoked, serial_number + ) + self.openssl_assert(res == 1) + res = self._lib.ASN1_TIME_set( + x509_revoked.revocationDate, + calendar.timegm(builder._revocation_date.timetuple()) + ) + self.openssl_assert(res != self._ffi.NULL) + # add CRL entry extensions + self._create_x509_extensions( + extensions=builder._extensions, + handlers=_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS, + x509_obj=x509_revoked, + add_func=self._lib.X509_REVOKED_add_ext, + gc=True + ) + return _RevokedCertificate(self, None, x509_revoked) + def load_pem_private_key(self, data, password): return self._load_key( self._lib.PEM_read_bio_PrivateKey, @@ -2136,6 +2269,9 @@ class Backend(object): generalized_time = self._ffi.gc( generalized_time, self._lib.ASN1_GENERALIZEDTIME_free ) + return self._parse_asn1_generalized_time(generalized_time) + + def _parse_asn1_generalized_time(self, generalized_time): time = self._asn1_string_to_ascii( self._ffi.cast("ASN1_STRING *", generalized_time) ) diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py index 664f6d35..033cd3b1 100644 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -138,6 +138,7 @@ def _handle_rsa_enc_dec_error(backend, key): decoding_errors = [ backend._lib.RSA_R_BLOCK_TYPE_IS_NOT_01, backend._lib.RSA_R_BLOCK_TYPE_IS_NOT_02, + backend._lib.RSA_R_OAEP_DECODING_ERROR, ] if backend._lib.Cryptography_HAS_RSA_R_PKCS_DECODING_ERROR: decoding_errors.append(backend._lib.RSA_R_PKCS_DECODING_ERROR) diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 7e89ac67..b8614e0b 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -4,8 +4,8 @@ from __future__ import absolute_import, division, print_function -import datetime import ipaddress +import operator from email.utils import parseaddr @@ -19,7 +19,7 @@ from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes, serialization from cryptography.x509.oid import ( - CRLExtensionOID, CertificatePoliciesOID, ExtensionOID + CRLEntryExtensionOID, CertificatePoliciesOID, ExtensionOID ) @@ -213,6 +213,15 @@ class _X509ExtensionParser(object): "Critical extension {0} is not currently supported" .format(oid), oid ) + else: + # Dump the DER payload into an UnrecognizedExtension object + data = backend._lib.X509_EXTENSION_get_data(ext) + backend.openssl_assert(data != backend._ffi.NULL) + der = backend._ffi.buffer(data.data, data.length)[:] + unrecognized = x509.UnrecognizedExtension(oid, der) + extensions.append( + x509.Extension(oid, critical, unrecognized) + ) else: # For extensions which are not supported by OpenSSL we pass the # extension object directly to the parsing routine so it can @@ -679,7 +688,19 @@ def _decode_inhibit_any_policy(backend, asn1_int): return x509.InhibitAnyPolicy(skip_certs) -_CRL_REASON_CODE_TO_ENUM = { +# CRLReason ::= ENUMERATED { +# unspecified (0), +# keyCompromise (1), +# cACompromise (2), +# affiliationChanged (3), +# superseded (4), +# cessationOfOperation (5), +# certificateHold (6), +# -- value 7 is not used +# removeFromCRL (8), +# privilegeWithdrawn (9), +# aACompromise (10) } +_CRL_ENTRY_REASON_CODE_TO_ENUM = { 0: x509.ReasonFlags.unspecified, 1: x509.ReasonFlags.key_compromise, 2: x509.ReasonFlags.ca_compromise, @@ -693,13 +714,27 @@ _CRL_REASON_CODE_TO_ENUM = { } +_CRL_ENTRY_REASON_ENUM_TO_CODE = { + x509.ReasonFlags.unspecified: 0, + x509.ReasonFlags.key_compromise: 1, + x509.ReasonFlags.ca_compromise: 2, + x509.ReasonFlags.affiliation_changed: 3, + x509.ReasonFlags.superseded: 4, + x509.ReasonFlags.cessation_of_operation: 5, + x509.ReasonFlags.certificate_hold: 6, + x509.ReasonFlags.remove_from_crl: 8, + x509.ReasonFlags.privilege_withdrawn: 9, + x509.ReasonFlags.aa_compromise: 10 +} + + def _decode_crl_reason(backend, enum): enum = backend._ffi.cast("ASN1_ENUMERATED *", enum) enum = backend._ffi.gc(enum, backend._lib.ASN1_ENUMERATED_free) code = backend._lib.ASN1_ENUMERATED_get(enum) try: - return _CRL_REASON_CODE_TO_ENUM[code] + return x509.CRLReason(_CRL_ENTRY_REASON_CODE_TO_ENUM[code]) except KeyError: raise ValueError("Unsupported reason code: {0}".format(code)) @@ -711,12 +746,9 @@ def _decode_invalidity_date(backend, inv_date): generalized_time = backend._ffi.gc( generalized_time, backend._lib.ASN1_GENERALIZEDTIME_free ) - time = backend._ffi.string( - backend._lib.ASN1_STRING_data( - backend._ffi.cast("ASN1_STRING *", generalized_time) - ) - ).decode("ascii") - return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ") + return x509.InvalidityDate( + backend._parse_asn1_generalized_time(generalized_time) + ) def _decode_cert_issuer(backend, ext): @@ -739,16 +771,24 @@ def _decode_cert_issuer(backend, ext): backend._consume_errors() raise ValueError( "The {0} extension is corrupted and can't be parsed".format( - CRLExtensionOID.CERTIFICATE_ISSUER)) + CRLEntryExtensionOID.CERTIFICATE_ISSUER)) gns = backend._ffi.gc(gns, backend._lib.GENERAL_NAMES_free) - return x509.GeneralNames(_decode_general_names(backend, gns)) + return x509.CertificateIssuer(_decode_general_names(backend, gns)) @utils.register_interface(x509.RevokedCertificate) class _RevokedCertificate(object): - def __init__(self, backend, x509_revoked): + 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 @@ -853,26 +893,34 @@ class _CertificateRevocationList(object): self._backend.openssl_assert(res == 1) return self._backend._read_mem_bio(bio) - def _revoked_certificates(self): + def _revoked_cert(self, idx): revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) - revoked_list = [] - if revoked != self._backend._ffi.NULL: - num = self._backend._lib.sk_X509_REVOKED_num(revoked) - for i in range(num): - r = self._backend._lib.sk_X509_REVOKED_value(revoked, i) - self._backend.openssl_assert(r != self._backend._ffi.NULL) - revoked_list.append(_RevokedCertificate(self._backend, r)) - - return revoked_list + 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): - return iter(self._revoked_certificates()) + for i in range(len(self)): + yield self._revoked_cert(i) def __getitem__(self, idx): - return self._revoked_certificates()[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): - return len(self._revoked_certificates()) + 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) @property def extensions(self): @@ -975,13 +1023,13 @@ _EXTENSION_HANDLERS = { } _REVOKED_EXTENSION_HANDLERS = { - CRLExtensionOID.CRL_REASON: _decode_crl_reason, - CRLExtensionOID.INVALIDITY_DATE: _decode_invalidity_date, - CRLExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer, + CRLEntryExtensionOID.CRL_REASON: _decode_crl_reason, + CRLEntryExtensionOID.INVALIDITY_DATE: _decode_invalidity_date, + CRLEntryExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer, } _REVOKED_UNSUPPORTED_EXTENSIONS = set([ - CRLExtensionOID.CERTIFICATE_ISSUER, + CRLEntryExtensionOID.CERTIFICATE_ISSUER, ]) _CRL_EXTENSION_HANDLERS = { diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index 07b6b9ac..8e419439 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -8,6 +8,7 @@ import collections import os import threading import types +import warnings from cryptography.exceptions import InternalError from cryptography.hazmat.bindings._openssl import ffi, lib @@ -180,3 +181,11 @@ class Binding(object): # condition registering the OpenSSL locks. On Python 3.4+ the import lock # is per module so this approach will not work. Binding.init_static_locks() + +if Binding.lib.SSLeay() < 0x10001000: + warnings.warn( + "OpenSSL versions less than 1.0.1 are no longer supported by the " + "OpenSSL project, please upgrade. A future version of cryptography " + "will drop support for these versions.", + DeprecationWarning + ) diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 4449e85a..b85d50d3 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -12,7 +12,10 @@ import sys import warnings +# the functions deprecated in 1.0 are on an arbitrarily extended deprecation +# cycle and should not be removed until we agree on when that cycle ends. DeprecatedIn10 = DeprecationWarning +DeprecatedIn12 = PendingDeprecationWarning def read_only_property(name): diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index c4434fd1..a1deb7f4 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -6,20 +6,22 @@ from __future__ import absolute_import, division, print_function from cryptography.x509.base import ( Certificate, CertificateBuilder, CertificateRevocationList, + CertificateRevocationListBuilder, CertificateSigningRequest, CertificateSigningRequestBuilder, - InvalidVersion, RevokedCertificate, + InvalidVersion, RevokedCertificate, RevokedCertificateBuilder, Version, load_der_x509_certificate, load_der_x509_crl, load_der_x509_csr, load_pem_x509_certificate, load_pem_x509_crl, load_pem_x509_csr, ) from cryptography.x509.extensions import ( AccessDescription, AuthorityInformationAccess, AuthorityKeyIdentifier, BasicConstraints, CRLDistributionPoints, - CRLNumber, CertificatePolicies, DistributionPoint, DuplicateExtension, - ExtendedKeyUsage, Extension, ExtensionNotFound, ExtensionType, Extensions, - GeneralNames, InhibitAnyPolicy, IssuerAlternativeName, KeyUsage, + CRLNumber, CRLReason, CertificateIssuer, CertificatePolicies, + DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension, + ExtensionNotFound, ExtensionType, Extensions, GeneralNames, + InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName, KeyUsage, NameConstraints, NoticeReference, OCSPNoCheck, PolicyInformation, ReasonFlags, SubjectAlternativeName, SubjectKeyIdentifier, - UnsupportedExtension, UserNotice + UnrecognizedExtension, UnsupportedExtension, UserNotice ) from cryptography.x509.general_name import ( DNSName, DirectoryName, GeneralName, IPAddress, OtherName, RFC822Name, @@ -28,9 +30,9 @@ from cryptography.x509.general_name import ( ) from cryptography.x509.name import Name, NameAttribute from cryptography.x509.oid import ( - AuthorityInformationAccessOID, CRLExtensionOID, CertificatePoliciesOID, - ExtendedKeyUsageOID, ExtensionOID, NameOID, ObjectIdentifier, - SignatureAlgorithmOID, _SIG_OIDS_TO_HASH + AuthorityInformationAccessOID, CRLEntryExtensionOID, CRLExtensionOID, + CertificatePoliciesOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, + ObjectIdentifier, SignatureAlgorithmOID, _SIG_OIDS_TO_HASH ) @@ -95,9 +97,9 @@ OID_ANY_POLICY = CertificatePoliciesOID.ANY_POLICY OID_CPS_QUALIFIER = CertificatePoliciesOID.CPS_QUALIFIER OID_CPS_USER_NOTICE = CertificatePoliciesOID.CPS_USER_NOTICE -OID_CERTIFICATE_ISSUER = CRLExtensionOID.CERTIFICATE_ISSUER -OID_CRL_REASON = CRLExtensionOID.CRL_REASON -OID_INVALIDITY_DATE = CRLExtensionOID.INVALIDITY_DATE +OID_CERTIFICATE_ISSUER = CRLEntryExtensionOID.CERTIFICATE_ISSUER +OID_CRL_REASON = CRLEntryExtensionOID.CRL_REASON +OID_INVALIDITY_DATE = CRLEntryExtensionOID.INVALIDITY_DATE OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS OID_OCSP = AuthorityInformationAccessOID.OCSP @@ -152,8 +154,10 @@ __all__ = [ "OtherName", "Certificate", "CertificateRevocationList", + "CertificateRevocationListBuilder", "CertificateSigningRequest", "RevokedCertificate", + "RevokedCertificateBuilder", "CertificateSigningRequestBuilder", "CertificateBuilder", "Version", @@ -161,4 +165,9 @@ __all__ = [ "OID_CA_ISSUERS", "OID_OCSP", "_GENERAL_NAMES", + "CRLExtensionOID", + "CertificateIssuer", + "CRLReason", + "InvalidityDate", + "UnrecognizedExtension", ] diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index 057d0e9b..55e965f7 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -518,3 +518,159 @@ class CertificateBuilder(object): raise ValueError("A certificate must have a public key") return backend.create_x509_certificate(self, private_key, algorithm) + + +class CertificateRevocationListBuilder(object): + def __init__(self, issuer_name=None, last_update=None, next_update=None, + extensions=[], revoked_certificates=[]): + 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): + 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): + 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.') + if last_update <= _UNIX_EPOCH: + raise ValueError('The last update date must be after the unix' + ' epoch (1970 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): + 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.') + if next_update <= _UNIX_EPOCH: + raise ValueError('The last update date must be after the unix' + ' epoch (1970 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, extension, critical): + """ + Adds an X.509 extension to the certificate revocation list. + """ + if not isinstance(extension, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extension.oid, critical, extension) + + # TODO: This is quadratic in the number of extensions + for e in self._extensions: + if e.oid == extension.oid: + raise ValueError('This extension has already been set.') + return CertificateRevocationListBuilder( + self._issuer_name, self._last_update, self._next_update, + self._extensions + [extension], self._revoked_certificates + ) + + def add_revoked_certificate(self, revoked_certificate): + """ + 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, algorithm, backend): + 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") + + return backend.create_x509_crl(self, private_key, algorithm) + + +class RevokedCertificateBuilder(object): + def __init__(self, serial_number=None, revocation_date=None, + extensions=[]): + self._serial_number = serial_number + self._revocation_date = revocation_date + self._extensions = extensions + + def serial_number(self, number): + if not isinstance(number, six.integer_types): + 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 non-negative.') + if utils.bit_length(number) > 160: # As defined in RFC 5280 + raise ValueError('The serial number should not be more than 160 ' + 'bits.') + return RevokedCertificateBuilder( + number, self._revocation_date, self._extensions + ) + + def revocation_date(self, time): + 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.') + if time <= _UNIX_EPOCH: + raise ValueError('The revocation date must be after the unix' + ' epoch (1970 January 1).') + return RevokedCertificateBuilder( + self._serial_number, time, self._extensions + ) + + def add_extension(self, extension, critical): + if not isinstance(extension, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extension.oid, critical, extension) + + # TODO: This is quadratic in the number of extensions + for e in self._extensions: + if e.oid == extension.oid: + raise ValueError('This extension has already been set.') + return RevokedCertificateBuilder( + self._serial_number, self._revocation_date, + self._extensions + [extension] + ) + + def build(self, backend): + 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 backend.create_x509_revoked_certificate(self) diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index 15feb717..f7b5d7f5 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import abc +import datetime import hashlib import ipaddress from enum import Enum @@ -18,7 +19,9 @@ from cryptography import utils from cryptography.hazmat.primitives import constant_time, serialization from cryptography.x509.general_name import GeneralName, IPAddress, OtherName from cryptography.x509.name import Name -from cryptography.x509.oid import ExtensionOID, ObjectIdentifier +from cryptography.x509.oid import ( + CRLEntryExtensionOID, ExtensionOID, ObjectIdentifier +) class _SubjectPublicKeyInfo(univ.Sequence): @@ -88,6 +91,13 @@ class Extensions(object): raise ExtensionNotFound("No {0} extension was found".format(oid), oid) def get_extension_for_class(self, extclass): + if extclass is UnrecognizedExtension: + raise TypeError( + "UnrecognizedExtension can't be used with " + "get_extension_for_class because more than one instance of the" + " class may be present." + ) + for ext in self: if isinstance(ext.value, extclass): return ext @@ -102,6 +112,9 @@ class Extensions(object): def __len__(self): return len(self._extensions) + def __getitem__(self, idx): + return self._extensions[idx] + def __repr__(self): return ( "<Extensions({0})>".format(self._extensions) @@ -127,6 +140,9 @@ class CRLNumber(object): def __ne__(self, other): return not self == other + def __hash__(self): + return hash(self.crl_number) + def __repr__(self): return "<CRLNumber({0})>".format(self.crl_number) @@ -226,6 +242,9 @@ class SubjectKeyIdentifier(object): def __ne__(self, other): return not self == other + def __hash__(self): + return hash(self.digest) + @utils.register_interface(ExtensionType) class AuthorityInformationAccess(object): @@ -258,6 +277,9 @@ class AuthorityInformationAccess(object): def __ne__(self, other): return not self == other + def __getitem__(self, idx): + return self._descriptions[idx] + class AccessDescription(object): def __init__(self, access_method, access_location): @@ -330,6 +352,9 @@ class BasicConstraints(object): def __ne__(self, other): return not self == other + def __hash__(self): + return hash((self.ca, self.path_length)) + @utils.register_interface(ExtensionType) class CRLDistributionPoints(object): @@ -364,6 +389,9 @@ class CRLDistributionPoints(object): def __ne__(self, other): return not self == other + def __getitem__(self, idx): + return self._distribution_points[idx] + class DistributionPoint(object): def __init__(self, full_name, relative_name, reasons, crl_issuer): @@ -486,6 +514,9 @@ class CertificatePolicies(object): def __ne__(self, other): return not self == other + def __getitem__(self, idx): + return self._policies[idx] + class PolicyInformation(object): def __init__(self, policy_identifier, policy_qualifiers): @@ -885,6 +916,9 @@ class GeneralNames(object): def __ne__(self, other): return not self == other + def __getitem__(self, idx): + return self._general_names[idx] + @utils.register_interface(ExtensionType) class SubjectAlternativeName(object): @@ -911,6 +945,9 @@ class SubjectAlternativeName(object): return self._general_names == other._general_names + def __getitem__(self, idx): + return self._general_names[idx] + def __ne__(self, other): return not self == other @@ -942,3 +979,127 @@ class IssuerAlternativeName(object): def __ne__(self, other): return not self == other + + def __getitem__(self, idx): + return self._general_names[idx] + + +@utils.register_interface(ExtensionType) +class CertificateIssuer(object): + oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER + + def __init__(self, general_names): + self._general_names = GeneralNames(general_names) + + def __iter__(self): + return iter(self._general_names) + + def __len__(self): + return len(self._general_names) + + def get_values_for_type(self, type): + return self._general_names.get_values_for_type(type) + + def __repr__(self): + return "<CertificateIssuer({0})>".format(self._general_names) + + def __eq__(self, other): + if not isinstance(other, CertificateIssuer): + return NotImplemented + + return self._general_names == other._general_names + + def __ne__(self, other): + return not self == other + + def __getitem__(self, idx): + return self._general_names[idx] + + +@utils.register_interface(ExtensionType) +class CRLReason(object): + oid = CRLEntryExtensionOID.CRL_REASON + + def __init__(self, reason): + if not isinstance(reason, ReasonFlags): + raise TypeError("reason must be an element from ReasonFlags") + + self._reason = reason + + def __repr__(self): + return "<CRLReason(reason={0})>".format(self._reason) + + def __eq__(self, other): + if not isinstance(other, CRLReason): + return NotImplemented + + return self.reason == other.reason + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.reason) + + reason = utils.read_only_property("_reason") + + +@utils.register_interface(ExtensionType) +class InvalidityDate(object): + oid = CRLEntryExtensionOID.INVALIDITY_DATE + + def __init__(self, invalidity_date): + if not isinstance(invalidity_date, datetime.datetime): + raise TypeError("invalidity_date must be a datetime.datetime") + + self._invalidity_date = invalidity_date + + def __repr__(self): + return "<InvalidityDate(invalidity_date={0})>".format( + self._invalidity_date + ) + + def __eq__(self, other): + if not isinstance(other, InvalidityDate): + return NotImplemented + + return self.invalidity_date == other.invalidity_date + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.invalidity_date) + + invalidity_date = utils.read_only_property("_invalidity_date") + + +@utils.register_interface(ExtensionType) +class UnrecognizedExtension(object): + def __init__(self, oid, value): + if not isinstance(oid, ObjectIdentifier): + raise TypeError("oid must be an ObjectIdentifier") + self._oid = oid + self._value = value + + oid = utils.read_only_property("_oid") + value = utils.read_only_property("_value") + + def __repr__(self): + return ( + "<UnrecognizedExtension(oid={0.oid}, value={0.value!r})>".format( + self + ) + ) + + def __eq__(self, other): + if not isinstance(other, UnrecognizedExtension): + return NotImplemented + + return self.oid == other.oid and self.value == other.value + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.oid, self.value)) diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py index 94295d66..ced15103 100644 --- a/src/cryptography/x509/oid.py +++ b/src/cryptography/x509/oid.py @@ -88,12 +88,20 @@ class ExtensionOID(object): CRL_NUMBER = ObjectIdentifier("2.5.29.20") -class CRLExtensionOID(object): +class CRLEntryExtensionOID(object): CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29") CRL_REASON = ObjectIdentifier("2.5.29.21") INVALIDITY_DATE = ObjectIdentifier("2.5.29.24") +CRLExtensionOID = utils.deprecated( + CRLEntryExtensionOID, + __name__, + "CRLExtensionOID has been renamed to CRLEntryExtensionOID", + utils.DeprecatedIn12 +) + + class NameOID(object): COMMON_NAME = ObjectIdentifier("2.5.4.3") COUNTRY_NAME = ObjectIdentifier("2.5.4.6") @@ -220,9 +228,9 @@ _OID_NAMES = { ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", - CRLExtensionOID.CRL_REASON: "cRLReason", - CRLExtensionOID.INVALIDITY_DATE: "invalidityDate", - CRLExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", + CRLEntryExtensionOID.CRL_REASON: "cRLReason", + CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", + CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", ExtensionOID.NAME_CONSTRAINTS: "nameConstraints", ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints", ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies", |