diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cryptography/exceptions.py | 14 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/x509.py | 79 | ||||
-rw-r--r-- | src/cryptography/hazmat/bindings/openssl/asn1.py | 1 | ||||
-rw-r--r-- | src/cryptography/hazmat/bindings/openssl/ssl.py | 2 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/asymmetric/utils.py | 7 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/twofactor/__init__.py | 4 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/twofactor/hotp.py | 3 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/twofactor/totp.py | 3 | ||||
-rw-r--r-- | src/cryptography/utils.py | 1 | ||||
-rw-r--r-- | src/cryptography/x509.py | 236 |
10 files changed, 334 insertions, 16 deletions
diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py index 102165c7..a4292eb8 100644 --- a/src/cryptography/exceptions.py +++ b/src/cryptography/exceptions.py @@ -6,6 +6,9 @@ from __future__ import absolute_import, division, print_function from enum import Enum +from cryptography import utils +from cryptography.hazmat.primitives import twofactor + class _Reasons(Enum): BACKEND_MISSING_INTERFACE = 0 @@ -53,5 +56,12 @@ class InvalidKey(Exception): pass -class InvalidToken(Exception): - pass +InvalidToken = utils.deprecated( + twofactor.InvalidToken, + __name__, + ( + "The InvalidToken exception has moved to the " + "cryptography.hazmat.primitives.twofactor module" + ), + utils.DeprecatedIn09 +) diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 5d47c5ea..c21aeeb1 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -14,7 +14,8 @@ from __future__ import absolute_import, division, print_function import datetime -import warnings + +import idna from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm @@ -58,6 +59,23 @@ def _build_x509_name(backend, x509_name): return x509.Name(attributes) +def _build_general_name(backend, gn): + if gn.type == backend._lib.GEN_DNS: + data = backend._ffi.buffer(gn.d.dNSName.data, gn.d.dNSName.length)[:] + return x509.DNSName(idna.decode(data)) + elif gn.type == backend._lib.GEN_RID: + oid = _obj2txt(backend, gn.d.registeredID) + return x509.RegisteredID(x509.ObjectIdentifier(oid)) + else: + # otherName, x400Address or ediPartyName + raise x509.UnsupportedGeneralNameType( + "{0} is not a supported type".format( + x509._GENERAL_NAMES.get(gn.type, gn.type) + ), + gn.type + ) + + @utils.register_interface(x509.Certificate) class _Certificate(object): def __init__(self, backend, x509): @@ -172,14 +190,10 @@ class _Certificate(object): value = self._build_basic_constraints(ext) elif oid == x509.OID_SUBJECT_KEY_IDENTIFIER: value = self._build_subject_key_identifier(ext) - elif oid == x509.OID_KEY_USAGE and critical: - # TODO: remove this obviously. - warnings.warn( - "Extension support is not fully implemented. A key usage " - "extension with the critical flag was seen and IGNORED." - ) - seen_oids.add(oid) - continue + elif oid == x509.OID_KEY_USAGE: + value = self._build_key_usage(ext) + elif oid == x509.OID_SUBJECT_ALTERNATIVE_NAME: + value = self._build_subject_alt_name(ext) elif critical: raise x509.UnsupportedExtension( "{0} is not currently supported".format(oid), oid @@ -232,6 +246,53 @@ class _Certificate(object): self._backend._ffi.buffer(asn1_string.data, asn1_string.length)[:] ) + def _build_key_usage(self, ext): + bit_string = self._backend._lib.X509V3_EXT_d2i(ext) + assert bit_string != self._backend._ffi.NULL + bit_string = self._backend._ffi.cast("ASN1_BIT_STRING *", bit_string) + bit_string = self._backend._ffi.gc( + bit_string, self._backend._lib.ASN1_BIT_STRING_free + ) + get_bit = self._backend._lib.ASN1_BIT_STRING_get_bit + digital_signature = get_bit(bit_string, 0) == 1 + content_commitment = get_bit(bit_string, 1) == 1 + key_encipherment = get_bit(bit_string, 2) == 1 + data_encipherment = get_bit(bit_string, 3) == 1 + key_agreement = get_bit(bit_string, 4) == 1 + key_cert_sign = get_bit(bit_string, 5) == 1 + crl_sign = get_bit(bit_string, 6) == 1 + encipher_only = get_bit(bit_string, 7) == 1 + decipher_only = get_bit(bit_string, 8) == 1 + return x509.KeyUsage( + digital_signature, + content_commitment, + key_encipherment, + data_encipherment, + key_agreement, + key_cert_sign, + crl_sign, + encipher_only, + decipher_only + ) + + def _build_subject_alt_name(self, ext): + gns = self._backend._ffi.cast( + "GENERAL_NAMES *", self._backend._lib.X509V3_EXT_d2i(ext) + ) + assert gns != self._backend._ffi.NULL + gns = self._backend._ffi.gc(gns, self._backend._lib.GENERAL_NAMES_free) + num = self._backend._lib.sk_GENERAL_NAME_num(gns) + general_names = [] + + for i in range(num): + gn = self._backend._lib.sk_GENERAL_NAME_value(gns, i) + assert gn != self._backend._ffi.NULL + value = _build_general_name(self._backend, gn) + + general_names.append(value) + + return x509.SubjectAlternativeName(general_names) + @utils.register_interface(x509.CertificateSigningRequest) class _CertificateSigningRequest(object): diff --git a/src/cryptography/hazmat/bindings/openssl/asn1.py b/src/cryptography/hazmat/bindings/openssl/asn1.py index 45dfe758..475bd052 100644 --- a/src/cryptography/hazmat/bindings/openssl/asn1.py +++ b/src/cryptography/hazmat/bindings/openssl/asn1.py @@ -120,6 +120,7 @@ int ASN1_BIT_STRING_set_bit(ASN1_BIT_STRING *, int, int); """ MACROS = """ +void ASN1_BIT_STRING_free(ASN1_BIT_STRING *); /* This is not a macro, but is const on some versions of OpenSSL */ int ASN1_BIT_STRING_get_bit(ASN1_BIT_STRING *, int); ASN1_TIME *M_ASN1_TIME_dup(void *); diff --git a/src/cryptography/hazmat/bindings/openssl/ssl.py b/src/cryptography/hazmat/bindings/openssl/ssl.py index d680c3a5..6161a9d1 100644 --- a/src/cryptography/hazmat/bindings/openssl/ssl.py +++ b/src/cryptography/hazmat/bindings/openssl/ssl.py @@ -526,7 +526,7 @@ static const long Cryptography_HAS_NEXTPROTONEG = 1; #endif /* ALPN was added in OpenSSL 1.0.2. */ -#if OPENSSL_VERSION_NUMBER < 0x10002001L +#if OPENSSL_VERSION_NUMBER < 0x10002001L && !defined(LIBRESSL_VERSION_NUMBER) int (*SSL_CTX_set_alpn_protos)(SSL_CTX *, const unsigned char *, unsigned) = NULL; diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py index 71f4ff8e..29390e40 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/utils.py +++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function +from pyasn1.codec.ber import eoo from pyasn1.codec.der import decoder, encoder from pyasn1.error import PyAsn1Error from pyasn1.type import namedtype, univ @@ -28,6 +29,12 @@ def decode_rfc6979_signature(signature): raise ValueError( "The signature contains bytes after the end of the ASN.1 sequence." ) + # pyasn1 can erroneously return this from top-level DER decoding. + # It's intended as a sentinel in recursive BER decoding, so it's + # returned even though an asn1Spec is provided. + if eoo.endOfOctets.isSameTypeWith(data) and data == eoo.endOfOctets: + raise ValueError("Invalid signature data. Unable to decode ASN.1") + r = int(data.getComponentByName('r')) s = int(data.getComponentByName('s')) return (r, s) diff --git a/src/cryptography/hazmat/primitives/twofactor/__init__.py b/src/cryptography/hazmat/primitives/twofactor/__init__.py index 4b540884..e71f9e67 100644 --- a/src/cryptography/hazmat/primitives/twofactor/__init__.py +++ b/src/cryptography/hazmat/primitives/twofactor/__init__.py @@ -3,3 +3,7 @@ # for complete details. from __future__ import absolute_import, division, print_function + + +class InvalidToken(Exception): + pass diff --git a/src/cryptography/hazmat/primitives/twofactor/hotp.py b/src/cryptography/hazmat/primitives/twofactor/hotp.py index 1dac920f..ba228b40 100644 --- a/src/cryptography/hazmat/primitives/twofactor/hotp.py +++ b/src/cryptography/hazmat/primitives/twofactor/hotp.py @@ -9,11 +9,12 @@ import struct import six from cryptography.exceptions import ( - InvalidToken, UnsupportedAlgorithm, _Reasons + UnsupportedAlgorithm, _Reasons ) from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import constant_time, hmac from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512 +from cryptography.hazmat.primitives.twofactor import InvalidToken class HOTP(object): diff --git a/src/cryptography/hazmat/primitives/twofactor/totp.py b/src/cryptography/hazmat/primitives/twofactor/totp.py index 0b04a131..03df9292 100644 --- a/src/cryptography/hazmat/primitives/twofactor/totp.py +++ b/src/cryptography/hazmat/primitives/twofactor/totp.py @@ -5,10 +5,11 @@ from __future__ import absolute_import, division, print_function from cryptography.exceptions import ( - InvalidToken, UnsupportedAlgorithm, _Reasons + UnsupportedAlgorithm, _Reasons ) from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives.twofactor import InvalidToken from cryptography.hazmat.primitives.twofactor.hotp import HOTP diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 0f8cbb27..445554ec 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -11,6 +11,7 @@ import warnings DeprecatedIn08 = DeprecationWarning +DeprecatedIn09 = PendingDeprecationWarning def read_only_property(name): diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index a9b6f8bd..dd6ea926 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import abc +import ipaddress from enum import Enum import six @@ -69,6 +70,19 @@ _OID_NAMES = { } +_GENERAL_NAMES = { + 0: "otherName", + 1: "rfc822Name", + 2: "dNSName", + 3: "x400Address", + 4: "directoryName", + 5: "ediPartyName", + 6: "uniformResourceIdentifier", + 7: "iPAddress", + 8: "registeredID", +} + + class Version(Enum): v1 = 0 v3 = 2 @@ -114,6 +128,12 @@ class ExtensionNotFound(Exception): self.oid = oid +class UnsupportedGeneralNameType(Exception): + def __init__(self, msg, type): + super(UnsupportedGeneralNameType, self).__init__(msg) + self.type = type + + class NameAttribute(object): def __init__(self, oid, value): if not isinstance(oid, ObjectIdentifier): @@ -353,8 +373,8 @@ class KeyUsage(object): encipher_only = self.encipher_only decipher_only = self.decipher_only except ValueError: - encipher_only = "N/A" - decipher_only = "N/A" + encipher_only = None + decipher_only = None return ("<KeyUsage(digital_signature={0.digital_signature}, " "content_commitment={0.content_commitment}, " @@ -387,6 +407,218 @@ class SubjectKeyIdentifier(object): return not self == other +@six.add_metaclass(abc.ABCMeta) +class GeneralName(object): + @abc.abstractproperty + def value(self): + """ + Return the value of the object + """ + + +@utils.register_interface(GeneralName) +class RFC822Name(object): + def __init__(self, value): + if not isinstance(value, six.text_type): + raise TypeError("value must be a unicode string") + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "<RFC822Name(value={0})>".format(self.value) + + def __eq__(self, other): + if not isinstance(other, RFC822Name): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(GeneralName) +class DNSName(object): + def __init__(self, value): + if not isinstance(value, six.text_type): + raise TypeError("value must be a unicode string") + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "<DNSName(value={0})>".format(self.value) + + def __eq__(self, other): + if not isinstance(other, DNSName): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(GeneralName) +class UniformResourceIdentifier(object): + def __init__(self, value): + if not isinstance(value, six.text_type): + raise TypeError("value must be a unicode string") + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "<UniformResourceIdentifier(value={0})>".format(self.value) + + def __eq__(self, other): + if not isinstance(other, UniformResourceIdentifier): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(GeneralName) +class DirectoryName(object): + def __init__(self, value): + if not isinstance(value, Name): + raise TypeError("value must be a Name") + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "<DirectoryName(value={0})>".format(self.value) + + def __eq__(self, other): + if not isinstance(other, DirectoryName): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(GeneralName) +class RegisteredID(object): + def __init__(self, value): + if not isinstance(value, ObjectIdentifier): + raise TypeError("value must be an ObjectIdentifier") + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "<RegisteredID(value={0})>".format(self.value) + + def __eq__(self, other): + if not isinstance(other, RegisteredID): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(GeneralName) +class IPAddress(object): + def __init__(self, value): + if not isinstance( + value, (ipaddress.IPv4Address, ipaddress.IPv6Address) + ): + raise TypeError( + "value must be an instance of ipaddress.IPv4Address or " + "ipaddress.IPv6Address" + ) + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "<IPAddress(value={0})>".format(self.value) + + def __eq__(self, other): + if not isinstance(other, IPAddress): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +class SubjectAlternativeName(object): + def __init__(self, general_names): + if not all(isinstance(x, GeneralName) for x in general_names): + raise TypeError( + "Every item in the general_names list must be an " + "object conforming to the GeneralName interface" + ) + + self._general_names = 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 [i.value for i in self if isinstance(i, type)] + + def __repr__(self): + return "<SubjectAlternativeName({0})>".format(self._general_names) + + +class AuthorityKeyIdentifier(object): + def __init__(self, key_identifier, authority_cert_issuer, + authority_cert_serial_number): + if authority_cert_issuer or authority_cert_serial_number: + if not authority_cert_issuer or not authority_cert_serial_number: + raise ValueError( + "authority_cert_issuer and authority_cert_serial_number " + "must both be present or both None" + ) + + if not isinstance(authority_cert_issuer, Name): + raise TypeError("authority_cert_issuer must be a Name") + + if not isinstance(authority_cert_serial_number, six.integer_types): + raise TypeError( + "authority_cert_serial_number must be an integer" + ) + + self._key_identifier = key_identifier + self._authority_cert_issuer = authority_cert_issuer + self._authority_cert_serial_number = authority_cert_serial_number + + def __repr__(self): + return ( + "<AuthorityKeyIdentifier(key_identifier={0.key_identifier!r}, " + "authority_cert_issuer={0.authority_cert_issuer}, " + "authority_cert_serial_number={0.authority_cert_serial_number}" + ")>".format(self) + ) + + key_identifier = utils.read_only_property("_key_identifier") + authority_cert_issuer = utils.read_only_property("_authority_cert_issuer") + authority_cert_serial_number = utils.read_only_property( + "_authority_cert_serial_number" + ) + + OID_COMMON_NAME = ObjectIdentifier("2.5.4.3") OID_COUNTRY_NAME = ObjectIdentifier("2.5.4.6") OID_LOCALITY_NAME = ObjectIdentifier("2.5.4.7") |