diff options
Diffstat (limited to 'src/cryptography/hazmat')
66 files changed, 3933 insertions, 1951 deletions
diff --git a/src/cryptography/hazmat/_der.py b/src/cryptography/hazmat/_der.py new file mode 100644 index 000000000..462b911b4 --- /dev/null +++ b/src/cryptography/hazmat/_der.py @@ -0,0 +1,156 @@ +# 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 six + +from cryptography.utils import int_from_bytes, int_to_bytes + + +# This module contains a lightweight DER encoder and decoder. See X.690 for the +# specification. This module intentionally does not implement the more complex +# BER encoding, only DER. +# +# Note this implementation treats an element's constructed bit as part of the +# tag. This is fine for DER, where the bit is always computable from the type. + + +CONSTRUCTED = 0x20 +CONTEXT_SPECIFIC = 0x80 + +INTEGER = 0x02 +BIT_STRING = 0x03 +OCTET_STRING = 0x04 +NULL = 0x05 +OBJECT_IDENTIFIER = 0x06 +SEQUENCE = 0x10 | CONSTRUCTED +SET = 0x11 | CONSTRUCTED +PRINTABLE_STRING = 0x13 +UTC_TIME = 0x17 +GENERALIZED_TIME = 0x18 + + +class DERReader(object): + def __init__(self, data): + self.data = memoryview(data) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + if exc_value is None: + self.check_empty() + + def is_empty(self): + return len(self.data) == 0 + + def check_empty(self): + if not self.is_empty(): + raise ValueError("Invalid DER input: trailing data") + + def read_byte(self): + if len(self.data) < 1: + raise ValueError("Invalid DER input: insufficient data") + ret = six.indexbytes(self.data, 0) + self.data = self.data[1:] + return ret + + def read_bytes(self, n): + if len(self.data) < n: + raise ValueError("Invalid DER input: insufficient data") + ret = self.data[:n] + self.data = self.data[n:] + return ret + + def read_any_element(self): + tag = self.read_byte() + # Tag numbers 31 or higher are stored in multiple bytes. No supported + # ASN.1 types use such tags, so reject these. + if tag & 0x1F == 0x1F: + raise ValueError("Invalid DER input: unexpected high tag number") + length_byte = self.read_byte() + if length_byte & 0x80 == 0: + # If the high bit is clear, the first length byte is the length. + length = length_byte + else: + # If the high bit is set, the first length byte encodes the length + # of the length. + length_byte &= 0x7F + if length_byte == 0: + raise ValueError( + "Invalid DER input: indefinite length form is not allowed " + "in DER" + ) + length = 0 + for i in range(length_byte): + length <<= 8 + length |= self.read_byte() + if length == 0: + raise ValueError( + "Invalid DER input: length was not minimally-encoded" + ) + if length < 0x80: + # If the length could have been encoded in short form, it must + # not use long form. + raise ValueError( + "Invalid DER input: length was not minimally-encoded" + ) + body = self.read_bytes(length) + return tag, DERReader(body) + + def read_element(self, expected_tag): + tag, body = self.read_any_element() + if tag != expected_tag: + raise ValueError("Invalid DER input: unexpected tag") + return body + + def read_single_element(self, expected_tag): + with self: + return self.read_element(expected_tag) + + def read_optional_element(self, expected_tag): + if len(self.data) > 0 and six.indexbytes(self.data, 0) == expected_tag: + return self.read_element(expected_tag) + return None + + def as_integer(self): + if len(self.data) == 0: + raise ValueError("Invalid DER input: empty integer contents") + first = six.indexbytes(self.data, 0) + if first & 0x80 == 0x80: + raise ValueError("Negative DER integers are not supported") + # The first 9 bits must not all be zero or all be ones. Otherwise, the + # encoding should have been one byte shorter. + if len(self.data) > 1: + second = six.indexbytes(self.data, 1) + if first == 0 and second & 0x80 == 0: + raise ValueError( + "Invalid DER input: integer not minimally-encoded" + ) + return int_from_bytes(self.data, "big") + + +def encode_der_integer(x): + if not isinstance(x, six.integer_types): + raise ValueError("Value must be an integer") + if x < 0: + raise ValueError("Negative integers are not supported") + n = x.bit_length() // 8 + 1 + return int_to_bytes(x, n) + + +def encode_der(tag, *children): + length = 0 + for child in children: + length += len(child) + chunks = [six.int2byte(tag)] + if length < 0x80: + chunks.append(six.int2byte(length)) + else: + length_bytes = int_to_bytes(length) + chunks.append(six.int2byte(0x80 | len(length_bytes))) + chunks.append(length_bytes) + chunks.extend(children) + return b"".join(chunks) diff --git a/src/cryptography/hazmat/_oid.py b/src/cryptography/hazmat/_oid.py index cfe906cd3..de2771a73 100644 --- a/src/cryptography/hazmat/_oid.py +++ b/src/cryptography/hazmat/_oid.py @@ -19,26 +19,36 @@ class ObjectIdentifier(object): # range 0..39. All nodes must be integers. for node in nodes: try: - intnodes.append(int(node, 0)) + node_value = int(node, 10) except ValueError: raise ValueError( - "Malformed OID: %s (non-integer nodes)" % ( - self._dotted_string)) + "Malformed OID: %s (non-integer nodes)" + % (self._dotted_string) + ) + if node_value < 0: + raise ValueError( + "Malformed OID: %s (negative-integer nodes)" + % (self._dotted_string) + ) + intnodes.append(node_value) if len(nodes) < 2: raise ValueError( - "Malformed OID: %s (insufficient number of nodes)" % ( - self._dotted_string)) + "Malformed OID: %s (insufficient number of nodes)" + % (self._dotted_string) + ) if intnodes[0] > 2: raise ValueError( - "Malformed OID: %s (first node outside valid range)" % ( - self._dotted_string)) + "Malformed OID: %s (first node outside valid range)" + % (self._dotted_string) + ) if intnodes[0] < 2 and intnodes[1] >= 40: raise ValueError( - "Malformed OID: %s (second node outside valid range)" % ( - self._dotted_string)) + "Malformed OID: %s (second node outside valid range)" + % (self._dotted_string) + ) def __eq__(self, other): if not isinstance(other, ObjectIdentifier): @@ -50,9 +60,8 @@ class ObjectIdentifier(object): return not self == other def __repr__(self): - return "<ObjectIdentifier(oid={0}, name={1})>".format( - self.dotted_string, - self._name + return "<ObjectIdentifier(oid={}, name={})>".format( + self.dotted_string, self._name ) def __hash__(self): @@ -62,6 +71,7 @@ class ObjectIdentifier(object): def _name(self): # Lazy import to avoid an import cycle from cryptography.x509.oid import _OID_NAMES + return _OID_NAMES.get(self, "Unknown OID") dotted_string = utils.read_only_property("_dotted_string") diff --git a/src/cryptography/hazmat/backends/__init__.py b/src/cryptography/hazmat/backends/__init__.py index 565bde788..1563936dd 100644 --- a/src/cryptography/hazmat/backends/__init__.py +++ b/src/cryptography/hazmat/backends/__init__.py @@ -13,6 +13,14 @@ def default_backend(): if _default_backend is None: from cryptography.hazmat.backends.openssl.backend import backend + _default_backend = backend return _default_backend + + +def _get_backend(backend): + if backend is None: + return default_backend() + else: + return backend diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index 0a476b991..418980a34 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -57,7 +57,7 @@ class HMACBackend(object): @abc.abstractmethod def create_hmac_ctx(self, key, algorithm): """ - Create a MACContext for calculating a message authentication code. + Create a context for calculating a message authentication code. """ @@ -72,7 +72,7 @@ class CMACBackend(object): @abc.abstractmethod def create_cmac_ctx(self, algorithm): """ - Create a MACContext for calculating a message authentication code. + Create a context for calculating a message authentication code. """ @@ -86,8 +86,9 @@ class PBKDF2HMACBackend(object): """ @abc.abstractmethod - def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, - key_material): + def derive_pbkdf2_hmac( + self, algorithm, length, salt, iterations, key_material + ): """ Return length bytes derived from provided PBKDF2 parameters. """ diff --git a/src/cryptography/hazmat/backends/openssl/aead.py b/src/cryptography/hazmat/backends/openssl/aead.py index 73195ff3e..449491685 100644 --- a/src/cryptography/hazmat/backends/openssl/aead.py +++ b/src/cryptography/hazmat/backends/openssl/aead.py @@ -13,15 +13,18 @@ _DECRYPT = 0 def _aead_cipher_name(cipher): from cryptography.hazmat.primitives.ciphers.aead import ( - AESCCM, AESGCM, ChaCha20Poly1305 + AESCCM, + AESGCM, + ChaCha20Poly1305, ) + if isinstance(cipher, ChaCha20Poly1305): return b"chacha20-poly1305" elif isinstance(cipher, AESCCM): - return "aes-{0}-ccm".format(len(cipher._key) * 8).encode("ascii") + return "aes-{}-ccm".format(len(cipher._key) * 8).encode("ascii") else: assert isinstance(cipher, AESGCM) - return "aes-{0}-gcm".format(len(cipher._key) * 8).encode("ascii") + return "aes-{}-gcm".format(len(cipher._key) * 8).encode("ascii") def _aead_setup(backend, cipher_name, key, nonce, tag, tag_len, operation): @@ -30,18 +33,21 @@ def _aead_setup(backend, cipher_name, key, nonce, tag, tag_len, operation): ctx = backend._lib.EVP_CIPHER_CTX_new() ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free) res = backend._lib.EVP_CipherInit_ex( - ctx, evp_cipher, + ctx, + evp_cipher, backend._ffi.NULL, backend._ffi.NULL, backend._ffi.NULL, - int(operation == _ENCRYPT) + int(operation == _ENCRYPT), ) backend.openssl_assert(res != 0) res = backend._lib.EVP_CIPHER_CTX_set_key_length(ctx, len(key)) backend.openssl_assert(res != 0) res = backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, backend._lib.EVP_CTRL_AEAD_SET_IVLEN, len(nonce), - backend._ffi.NULL + ctx, + backend._lib.EVP_CTRL_AEAD_SET_IVLEN, + len(nonce), + backend._ffi.NULL, ) backend.openssl_assert(res != 0) if operation == _DECRYPT: @@ -49,10 +55,11 @@ def _aead_setup(backend, cipher_name, key, nonce, tag, tag_len, operation): ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag ) backend.openssl_assert(res != 0) - else: + elif cipher_name.endswith(b"-ccm"): res = backend._lib.EVP_CIPHER_CTX_ctrl( ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, tag_len, backend._ffi.NULL ) + backend.openssl_assert(res != 0) nonce_ptr = backend._ffi.from_buffer(nonce) key_ptr = backend._ffi.from_buffer(key) @@ -62,7 +69,7 @@ def _aead_setup(backend, cipher_name, key, nonce, tag, tag_len, operation): backend._ffi.NULL, key_ptr, nonce_ptr, - int(operation == _ENCRYPT) + int(operation == _ENCRYPT), ) backend.openssl_assert(res != 0) return ctx @@ -71,11 +78,7 @@ def _aead_setup(backend, cipher_name, key, nonce, tag, tag_len, operation): def _set_length(backend, ctx, data_len): intptr = backend._ffi.new("int *") res = backend._lib.EVP_CipherUpdate( - ctx, - backend._ffi.NULL, - intptr, - backend._ffi.NULL, - data_len + ctx, backend._ffi.NULL, intptr, backend._ffi.NULL, data_len ) backend.openssl_assert(res != 0) @@ -98,6 +101,7 @@ def _process_data(backend, ctx, data): def _encrypt(backend, cipher, nonce, data, associated_data, tag_length): from cryptography.hazmat.primitives.ciphers.aead import AESCCM + cipher_name = _aead_cipher_name(cipher) ctx = _aead_setup( backend, cipher_name, cipher._key, nonce, None, tag_length, _ENCRYPT @@ -125,6 +129,7 @@ def _encrypt(backend, cipher, nonce, data, associated_data, tag_length): def _decrypt(backend, cipher, nonce, data, associated_data, tag_length): from cryptography.hazmat.primitives.ciphers.aead import AESCCM + if len(data) < tag_length: raise InvalidTag tag = data[-tag_length:] diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 0a9bc53ae..45d4a1a1e 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -4,84 +4,166 @@ from __future__ import absolute_import, division, print_function -import base64 import collections import contextlib import itertools +import warnings from contextlib import contextmanager -import asn1crypto.core - import six from six.moves import range from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat._der import ( + INTEGER, + NULL, + SEQUENCE, + encode_der, + encode_der_integer, +) from cryptography.hazmat.backends.interfaces import ( - CMACBackend, CipherBackend, DERSerializationBackend, DHBackend, DSABackend, - EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend + CMACBackend, + CipherBackend, + DERSerializationBackend, + DHBackend, + DSABackend, + EllipticCurveBackend, + HMACBackend, + HashBackend, + PBKDF2HMACBackend, + PEMSerializationBackend, + RSABackend, + ScryptBackend, + X509Backend, ) from cryptography.hazmat.backends.openssl import aead from cryptography.hazmat.backends.openssl.ciphers import _CipherContext from cryptography.hazmat.backends.openssl.cmac import _CMACContext from cryptography.hazmat.backends.openssl.decode_asn1 import ( - _CRL_ENTRY_REASON_ENUM_TO_CODE, _Integers + _CRL_ENTRY_REASON_ENUM_TO_CODE, + _CRL_EXTENSION_HANDLERS, + _EXTENSION_HANDLERS_BASE, + _EXTENSION_HANDLERS_SCT, + _OCSP_BASICRESP_EXTENSION_HANDLERS, + _OCSP_REQ_EXTENSION_HANDLERS, + _OCSP_SINGLERESP_EXTENSION_HANDLERS_SCT, + _REVOKED_EXTENSION_HANDLERS, + _X509ExtensionParser, ) from cryptography.hazmat.backends.openssl.dh import ( - _DHParameters, _DHPrivateKey, _DHPublicKey, _dh_params_dup + _DHParameters, + _DHPrivateKey, + _DHPublicKey, + _dh_params_dup, ) from cryptography.hazmat.backends.openssl.dsa import ( - _DSAParameters, _DSAPrivateKey, _DSAPublicKey + _DSAParameters, + _DSAPrivateKey, + _DSAPublicKey, ) from cryptography.hazmat.backends.openssl.ec import ( - _EllipticCurvePrivateKey, _EllipticCurvePublicKey + _EllipticCurvePrivateKey, + _EllipticCurvePublicKey, +) +from cryptography.hazmat.backends.openssl.ed25519 import ( + _Ed25519PrivateKey, + _Ed25519PublicKey, +) +from cryptography.hazmat.backends.openssl.ed448 import ( + _ED448_KEY_SIZE, + _Ed448PrivateKey, + _Ed448PublicKey, ) from cryptography.hazmat.backends.openssl.encode_asn1 import ( _CRL_ENTRY_EXTENSION_ENCODE_HANDLERS, - _CRL_EXTENSION_ENCODE_HANDLERS, _EXTENSION_ENCODE_HANDLERS, + _CRL_EXTENSION_ENCODE_HANDLERS, + _EXTENSION_ENCODE_HANDLERS, _OCSP_BASICRESP_EXTENSION_ENCODE_HANDLERS, _OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS, - _encode_asn1_int_gc, _encode_asn1_str_gc, _encode_name_gc, _txt2obj_gc, + _encode_asn1_int_gc, + _encode_asn1_str_gc, + _encode_name_gc, + _txt2obj_gc, ) from cryptography.hazmat.backends.openssl.hashes import _HashContext from cryptography.hazmat.backends.openssl.hmac import _HMACContext from cryptography.hazmat.backends.openssl.ocsp import ( - _OCSPRequest, _OCSPResponse + _OCSPRequest, + _OCSPResponse, +) +from cryptography.hazmat.backends.openssl.poly1305 import ( + _POLY1305_KEY_SIZE, + _Poly1305Context, ) from cryptography.hazmat.backends.openssl.rsa import ( - _RSAPrivateKey, _RSAPublicKey + _RSAPrivateKey, + _RSAPublicKey, ) from cryptography.hazmat.backends.openssl.x25519 import ( - _X25519PrivateKey, _X25519PublicKey + _X25519PrivateKey, + _X25519PublicKey, ) from cryptography.hazmat.backends.openssl.x448 import ( - _X448PrivateKey, _X448PublicKey + _X448PrivateKey, + _X448PublicKey, ) from cryptography.hazmat.backends.openssl.x509 import ( - _Certificate, _CertificateRevocationList, - _CertificateSigningRequest, _RevokedCertificate + _Certificate, + _CertificateRevocationList, + _CertificateSigningRequest, + _RevokedCertificate, ) from cryptography.hazmat.bindings.openssl import binding from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa +from cryptography.hazmat.primitives.asymmetric import ( + dh, + dsa, + ec, + ed25519, + ed448, + rsa, +) from cryptography.hazmat.primitives.asymmetric.padding import ( - MGF1, OAEP, PKCS1v15, PSS + MGF1, + OAEP, + PKCS1v15, + PSS, ) from cryptography.hazmat.primitives.ciphers.algorithms import ( - AES, ARC4, Blowfish, CAST5, Camellia, ChaCha20, IDEA, SEED, TripleDES + AES, + ARC4, + Blowfish, + CAST5, + Camellia, + ChaCha20, + IDEA, + SEED, + TripleDES, ) from cryptography.hazmat.primitives.ciphers.modes import ( - CBC, CFB, CFB8, CTR, ECB, GCM, OFB, XTS + CBC, + CFB, + CFB8, + CTR, + ECB, + GCM, + OFB, + XTS, ) from cryptography.hazmat.primitives.kdf import scrypt -from cryptography.hazmat.primitives.serialization import ssh +from cryptography.hazmat.primitives.serialization import pkcs7, ssh from cryptography.x509 import ocsp _MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) +# Not actually supported, just used as a marker for some serialization tests. +class _RC2(object): + pass + + @utils.register_interface(CipherBackend) @utils.register_interface(CMACBackend) @utils.register_interface(DERSerializationBackend) @@ -101,39 +183,93 @@ class Backend(object): """ OpenSSL API binding interfaces. """ + name = "openssl" + # FIPS has opinions about acceptable algorithms and key sizes, but the + # disallowed algorithms are still present in OpenSSL. They just error if + # you try to use them. To avoid that we allowlist the algorithms in + # FIPS 140-3. This isn't ideal, but FIPS 140-3 is trash so here we are. + _fips_aead = { + b"aes-128-ccm", + b"aes-192-ccm", + b"aes-256-ccm", + b"aes-128-gcm", + b"aes-192-gcm", + b"aes-256-gcm", + } + _fips_ciphers = (AES, TripleDES) + _fips_hashes = ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA512_224, + hashes.SHA512_256, + hashes.SHA3_224, + hashes.SHA3_256, + hashes.SHA3_384, + hashes.SHA3_512, + hashes.SHAKE128, + hashes.SHAKE256, + ) + _fips_rsa_min_key_size = 2048 + _fips_rsa_min_public_exponent = 65537 + _fips_dsa_min_modulus = 1 << 2048 + _fips_dh_min_key_size = 2048 + _fips_dh_min_modulus = 1 << _fips_dh_min_key_size + def __init__(self): self._binding = binding.Binding() self._ffi = self._binding.ffi self._lib = self._binding.lib + self._fips_enabled = self._is_fips_enabled() self._cipher_registry = {} self._register_default_ciphers() - self.activate_osrandom_engine() + self._register_x509_ext_parsers() + self._register_x509_encoders() + if self._fips_enabled and self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: + warnings.warn( + "OpenSSL FIPS mode is enabled. Can't enable DRBG fork safety.", + UserWarning, + ) + else: + self.activate_osrandom_engine() self._dh_types = [self._lib.EVP_PKEY_DH] if self._lib.Cryptography_HAS_EVP_PKEY_DHX: self._dh_types.append(self._lib.EVP_PKEY_DHX) - def openssl_assert(self, ok): - return binding._openssl_assert(self._lib, ok) + def openssl_assert(self, ok, errors=None): + return binding._openssl_assert(self._lib, ok, errors=errors) + + def _is_fips_enabled(self): + fips_mode = getattr(self._lib, "FIPS_mode", lambda: 0) + mode = fips_mode() + if mode == 0: + # OpenSSL without FIPS pushes an error on the error stack + self._lib.ERR_clear_error() + return bool(mode) def activate_builtin_random(self): - # Obtain a new structural reference. - e = self._lib.ENGINE_get_default_RAND() - if e != self._ffi.NULL: - self._lib.ENGINE_unregister_RAND(e) - # Reset the RNG to use the new engine. - self._lib.RAND_cleanup() - # decrement the structural reference from get_default_RAND - res = self._lib.ENGINE_finish(e) - self.openssl_assert(res == 1) + if self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: + # Obtain a new structural reference. + e = self._lib.ENGINE_get_default_RAND() + if e != self._ffi.NULL: + self._lib.ENGINE_unregister_RAND(e) + # Reset the RNG to use the built-in. + res = self._lib.RAND_set_rand_method(self._ffi.NULL) + self.openssl_assert(res == 1) + # decrement the structural reference from get_default_RAND + res = self._lib.ENGINE_finish(e) + self.openssl_assert(res == 1) @contextlib.contextmanager def _get_osurandom_engine(self): # Fetches an engine by id and returns it. This creates a structural # reference. - e = self._lib.ENGINE_by_id(self._binding._osrandom_engine_id) + e = self._lib.ENGINE_by_id(self._lib.Cryptography_osrandom_engine_id) self.openssl_assert(e != self._ffi.NULL) # Initialize the engine for use. This adds a functional reference. res = self._lib.ENGINE_init(e) @@ -150,30 +286,32 @@ class Backend(object): self.openssl_assert(res == 1) def activate_osrandom_engine(self): - # Unregister and free the current engine. - self.activate_builtin_random() - with self._get_osurandom_engine() as e: - # Set the engine as the default RAND provider. - res = self._lib.ENGINE_set_default_RAND(e) + if self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: + # Unregister and free the current engine. + self.activate_builtin_random() + with self._get_osurandom_engine() as e: + # Set the engine as the default RAND provider. + res = self._lib.ENGINE_set_default_RAND(e) + self.openssl_assert(res == 1) + # Reset the RNG to use the engine + res = self._lib.RAND_set_rand_method(self._ffi.NULL) self.openssl_assert(res == 1) - # Reset the RNG to use the new engine. - self._lib.RAND_cleanup() def osrandom_engine_implementation(self): buf = self._ffi.new("char[]", 64) with self._get_osurandom_engine() as e: - res = self._lib.ENGINE_ctrl_cmd(e, b"get_implementation", - len(buf), buf, - self._ffi.NULL, 0) + res = self._lib.ENGINE_ctrl_cmd( + e, b"get_implementation", len(buf), buf, self._ffi.NULL, 0 + ) self.openssl_assert(res > 0) - return self._ffi.string(buf).decode('ascii') + return self._ffi.string(buf).decode("ascii") def openssl_version_text(self): """ Friendly string name of the loaded OpenSSL library. This is not necessarily the same version as it was compiled against. - Example: OpenSSL 1.0.1e 11 Feb 2013 + Example: OpenSSL 1.1.1d 10 Sep 2019 """ return self._ffi.string( self._lib.OpenSSL_version(self._lib.OPENSSL_VERSION) @@ -187,7 +325,7 @@ class Backend(object): def _evp_md_from_algorithm(self, algorithm): if algorithm.name == "blake2b" or algorithm.name == "blake2s": - alg = "{0}{1}".format( + alg = "{}{}".format( algorithm.name, algorithm.digest_size * 8 ).encode("ascii") else: @@ -202,6 +340,9 @@ class Backend(object): return evp_md def hash_supported(self, algorithm): + if self._fips_enabled and not isinstance(algorithm, self._fips_hashes): + return False + evp_md = self._evp_md_from_algorithm(algorithm) return evp_md != self._ffi.NULL @@ -212,6 +353,8 @@ class Backend(object): return _HashContext(self, algorithm) def cipher_supported(self, cipher, mode): + if self._fips_enabled and not isinstance(cipher, self._fips_ciphers): + return False try: adapter = self._cipher_registry[type(cipher), type(mode)] except KeyError: @@ -221,8 +364,10 @@ class Backend(object): def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): if (cipher_cls, mode_cls) in self._cipher_registry: - raise ValueError("Duplicate registration for: {0} {1}.".format( - cipher_cls, mode_cls) + raise ValueError( + "Duplicate registration for: {} {}.".format( + cipher_cls, mode_cls + ) ) self._cipher_registry[cipher_cls, mode_cls] = adapter @@ -231,36 +376,28 @@ class Backend(object): self.register_cipher_adapter( AES, mode_cls, - GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}"), ) for mode_cls in [CBC, CTR, ECB, OFB, CFB]: self.register_cipher_adapter( Camellia, mode_cls, - GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}"), ) for mode_cls in [CBC, CFB, CFB8, OFB]: self.register_cipher_adapter( - TripleDES, - mode_cls, - GetCipherByName("des-ede3-{mode.name}") + TripleDES, mode_cls, GetCipherByName("des-ede3-{mode.name}") ) self.register_cipher_adapter( - TripleDES, - ECB, - GetCipherByName("des-ede3") + TripleDES, ECB, GetCipherByName("des-ede3") ) for mode_cls in [CBC, CFB, OFB, ECB]: self.register_cipher_adapter( - Blowfish, - mode_cls, - GetCipherByName("bf-{mode.name}") + Blowfish, mode_cls, GetCipherByName("bf-{mode.name}") ) for mode_cls in [CBC, CFB, OFB, ECB]: self.register_cipher_adapter( - SEED, - mode_cls, - GetCipherByName("seed-{mode.name}") + SEED, mode_cls, GetCipherByName("seed-{mode.name}") ) for cipher_cls, mode_cls in itertools.product( [CAST5, IDEA], @@ -269,20 +406,84 @@ class Backend(object): self.register_cipher_adapter( cipher_cls, mode_cls, - GetCipherByName("{cipher.name}-{mode.name}") + GetCipherByName("{cipher.name}-{mode.name}"), ) + self.register_cipher_adapter(ARC4, type(None), GetCipherByName("rc4")) + # We don't actually support RC2, this is just used by some tests. + self.register_cipher_adapter(_RC2, type(None), GetCipherByName("rc2")) self.register_cipher_adapter( - ARC4, - type(None), - GetCipherByName("rc4") - ) - self.register_cipher_adapter( - ChaCha20, - type(None), - GetCipherByName("chacha20") + ChaCha20, type(None), GetCipherByName("chacha20") ) self.register_cipher_adapter(AES, XTS, _get_xts_cipher) + def _register_x509_ext_parsers(self): + ext_handlers = _EXTENSION_HANDLERS_BASE.copy() + # All revoked extensions are valid single response extensions, see: + # https://tools.ietf.org/html/rfc6960#section-4.4.5 + singleresp_handlers = _REVOKED_EXTENSION_HANDLERS.copy() + + if self._lib.Cryptography_HAS_SCT: + ext_handlers.update(_EXTENSION_HANDLERS_SCT) + singleresp_handlers.update(_OCSP_SINGLERESP_EXTENSION_HANDLERS_SCT) + + self._certificate_extension_parser = _X509ExtensionParser( + self, + ext_count=self._lib.X509_get_ext_count, + get_ext=self._lib.X509_get_ext, + handlers=ext_handlers, + ) + self._csr_extension_parser = _X509ExtensionParser( + self, + ext_count=self._lib.sk_X509_EXTENSION_num, + get_ext=self._lib.sk_X509_EXTENSION_value, + handlers=ext_handlers, + ) + self._revoked_cert_extension_parser = _X509ExtensionParser( + self, + ext_count=self._lib.X509_REVOKED_get_ext_count, + get_ext=self._lib.X509_REVOKED_get_ext, + handlers=_REVOKED_EXTENSION_HANDLERS, + ) + self._crl_extension_parser = _X509ExtensionParser( + self, + ext_count=self._lib.X509_CRL_get_ext_count, + get_ext=self._lib.X509_CRL_get_ext, + handlers=_CRL_EXTENSION_HANDLERS, + ) + self._ocsp_req_ext_parser = _X509ExtensionParser( + self, + ext_count=self._lib.OCSP_REQUEST_get_ext_count, + get_ext=self._lib.OCSP_REQUEST_get_ext, + handlers=_OCSP_REQ_EXTENSION_HANDLERS, + ) + self._ocsp_basicresp_ext_parser = _X509ExtensionParser( + self, + ext_count=self._lib.OCSP_BASICRESP_get_ext_count, + get_ext=self._lib.OCSP_BASICRESP_get_ext, + handlers=_OCSP_BASICRESP_EXTENSION_HANDLERS, + ) + self._ocsp_singleresp_ext_parser = _X509ExtensionParser( + self, + ext_count=self._lib.OCSP_SINGLERESP_get_ext_count, + get_ext=self._lib.OCSP_SINGLERESP_get_ext, + handlers=singleresp_handlers, + ) + + def _register_x509_encoders(self): + self._extension_encode_handlers = _EXTENSION_ENCODE_HANDLERS.copy() + self._crl_extension_encode_handlers = ( + _CRL_EXTENSION_ENCODE_HANDLERS.copy() + ) + self._crl_entry_extension_encode_handlers = ( + _CRL_ENTRY_EXTENSION_ENCODE_HANDLERS.copy() + ) + self._ocsp_request_extension_encode_handlers = ( + _OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS.copy() + ) + self._ocsp_basicresp_extension_encode_handlers = ( + _OCSP_BASICRESP_EXTENSION_ENCODE_HANDLERS.copy() + ) + def create_symmetric_encryption_ctx(self, cipher, mode): return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) @@ -292,8 +493,9 @@ class Backend(object): def pbkdf2_hmac_supported(self, algorithm): return self.hmac_supported(algorithm) - def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, - key_material): + def derive_pbkdf2_hmac( + self, algorithm, length, salt, iterations, key_material + ): buf = self._ffi.new("unsigned char[]", length) evp_md = self._evp_md_non_null_from_algorithm(algorithm) key_material_ptr = self._ffi.from_buffer(key_material) @@ -305,7 +507,7 @@ class Backend(object): iterations, evp_md, length, - buf + buf, ) self.openssl_assert(res == 1) return self._ffi.buffer(buf)[:] @@ -313,6 +515,9 @@ class Backend(object): def _consume_errors(self): return binding._consume_errors(self._lib) + def _consume_errors_with_text(self): + return binding._consume_errors_with_text(self._lib) + def _bn_to_int(self, bn): assert bn != self._ffi.NULL @@ -323,7 +528,10 @@ class Backend(object): 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") + val = int.from_bytes(self._ffi.buffer(bin_ptr)[:bin_len], "big") + if self._lib.BN_is_negative(bn): + val = -val + return val else: # Under Python 2 the best we can do is hex() hex_cdata = self._lib.BN_bn2hex(bn) @@ -382,8 +590,11 @@ class Backend(object): return _RSAPrivateKey(self, rsa_cdata, evp_pkey) def generate_rsa_parameters_supported(self, public_exponent, key_size): - return (public_exponent >= 3 and public_exponent & 1 != 0 and - key_size >= 512) + return ( + public_exponent >= 3 + and public_exponent & 1 != 0 + and key_size >= 512 + ) def load_rsa_private_numbers(self, numbers): rsa._check_private_key_components( @@ -394,7 +605,7 @@ class Backend(object): numbers.dmq1, numbers.iqmp, numbers.public_numbers.e, - numbers.public_numbers.n + numbers.public_numbers.n, ) rsa_cdata = self._lib.RSA_new() self.openssl_assert(rsa_cdata != self._ffi.NULL) @@ -413,8 +624,6 @@ class Backend(object): self.openssl_assert(res == 1) res = self._lib.RSA_set0_crt_params(rsa_cdata, dmp1, dmq1, iqmp) self.openssl_assert(res == 1) - res = self._lib.RSA_blinding_on(rsa_cdata, self._ffi.NULL) - self.openssl_assert(res == 1) evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) return _RSAPrivateKey(self, rsa_cdata, evp_pkey) @@ -452,9 +661,7 @@ class Backend(object): BIO is finished with. """ data_ptr = self._ffi.from_buffer(data) - bio = self._lib.BIO_new_mem_buf( - data_ptr, len(data) - ) + bio = self._lib.BIO_new_mem_buf(data_ptr, len(data)) self.openssl_assert(bio != self._ffi.NULL) return _MemoryBIO(self._ffi.gc(bio, self._lib.BIO_free), data_ptr) @@ -509,12 +716,18 @@ class Backend(object): self.openssl_assert(dh_cdata != self._ffi.NULL) dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) return _DHPrivateKey(self, dh_cdata, evp_pkey) + elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): + # EVP_PKEY_ED25519 is not present in OpenSSL < 1.1.1 + return _Ed25519PrivateKey(self, evp_pkey) elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): # EVP_PKEY_X448 is not present in OpenSSL < 1.1.1 return _X448PrivateKey(self, evp_pkey) elif key_type == getattr(self._lib, "EVP_PKEY_X25519", None): # EVP_PKEY_X25519 is not present in OpenSSL < 1.1.0 return _X25519PrivateKey(self, evp_pkey) + elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): + # EVP_PKEY_ED448 is not present in OpenSSL < 1.1.1 + return _Ed448PrivateKey(self, evp_pkey) else: raise UnsupportedAlgorithm("Unsupported key type.") @@ -546,25 +759,32 @@ class Backend(object): self.openssl_assert(dh_cdata != self._ffi.NULL) dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) return _DHPublicKey(self, dh_cdata, evp_pkey) + elif key_type == getattr(self._lib, "EVP_PKEY_ED25519", None): + # EVP_PKEY_ED25519 is not present in OpenSSL < 1.1.1 + return _Ed25519PublicKey(self, evp_pkey) elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): # EVP_PKEY_X448 is not present in OpenSSL < 1.1.1 return _X448PublicKey(self, evp_pkey) elif key_type == getattr(self._lib, "EVP_PKEY_X25519", None): # EVP_PKEY_X25519 is not present in OpenSSL < 1.1.0 return _X25519PublicKey(self, evp_pkey) + elif key_type == getattr(self._lib, "EVP_PKEY_ED448", None): + # EVP_PKEY_X25519 is not present in OpenSSL < 1.1.1 + return _Ed448PublicKey(self, evp_pkey) else: raise UnsupportedAlgorithm("Unsupported key type.") def _oaep_hash_supported(self, algorithm): if self._lib.Cryptography_HAS_RSA_OAEP_MD: return isinstance( - algorithm, ( + algorithm, + ( hashes.SHA1, hashes.SHA224, hashes.SHA256, hashes.SHA384, hashes.SHA512, - ) + ), ) else: return isinstance(algorithm, hashes.SHA1) @@ -576,27 +796,34 @@ class Backend(object): return self.hash_supported(padding._mgf._algorithm) elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): return ( - self._oaep_hash_supported(padding._mgf._algorithm) and - self._oaep_hash_supported(padding._algorithm) and - ( - (padding._label is None or len(padding._label) == 0) or - self._lib.Cryptography_HAS_RSA_OAEP_LABEL == 1 + self._oaep_hash_supported(padding._mgf._algorithm) + and self._oaep_hash_supported(padding._algorithm) + and ( + (padding._label is None or len(padding._label) == 0) + or self._lib.Cryptography_HAS_RSA_OAEP_LABEL == 1 ) ) else: return False def generate_dsa_parameters(self, key_size): - if key_size not in (1024, 2048, 3072): - raise ValueError("Key size must be 1024 or 2048 or 3072 bits.") + if key_size not in (1024, 2048, 3072, 4096): + raise ValueError( + "Key size must be 1024, 2048, 3072, or 4096 bits." + ) ctx = self._lib.DSA_new() self.openssl_assert(ctx != self._ffi.NULL) ctx = self._ffi.gc(ctx, self._lib.DSA_free) res = self._lib.DSA_generate_parameters_ex( - ctx, key_size, self._ffi.NULL, 0, - self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ctx, + key_size, + self._ffi.NULL, + 0, + self._ffi.NULL, + self._ffi.NULL, + self._ffi.NULL, ) self.openssl_assert(res == 1) @@ -692,20 +919,37 @@ class Backend(object): def create_cmac_ctx(self, algorithm): return _CMACContext(self, algorithm) - def create_x509_csr(self, builder, private_key, algorithm): - if not isinstance(algorithm, hashes.HashAlgorithm): - raise TypeError('Algorithm must be a registered hash algorithm.') - - if ( - isinstance(algorithm, hashes.MD5) and not - isinstance(private_key, rsa.RSAPrivateKey) + def _x509_check_signature_params(self, private_key, algorithm): + if isinstance( + private_key, (ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey) + ): + if algorithm is not None: + raise ValueError( + "algorithm must be None when signing via ed25519 or ed448" + ) + elif not isinstance( + private_key, + (rsa.RSAPrivateKey, dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey), + ): + raise TypeError( + "Key must be an rsa, dsa, ec, ed25519, or ed448 private key." + ) + elif not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Algorithm must be a registered hash algorithm.") + elif isinstance(algorithm, hashes.MD5) and not isinstance( + private_key, rsa.RSAPrivateKey ): raise ValueError( - "MD5 is not a supported hash algorithm for EC/DSA CSRs" + "MD5 hash algorithm is only supported with RSA keys" ) + def create_x509_csr(self, builder, private_key, algorithm): + if not isinstance(builder, x509.CertificateSigningRequestBuilder): + raise TypeError("Builder type mismatch.") + self._x509_check_signature_params(private_key, algorithm) + # Resolve the signature algorithm. - evp_md = self._evp_md_non_null_from_algorithm(algorithm) + evp_md = self._evp_md_x509_null_if_eddsa(private_key, algorithm) # Create an empty request. x509_req = self._lib.X509_REQ_new() @@ -724,9 +968,7 @@ class Backend(object): # Set subject public key. public_key = private_key.public_key() - res = self._lib.X509_REQ_set_pubkey( - x509_req, public_key._evp_pkey - ) + res = self._lib.X509_REQ_set_pubkey(x509_req, public_key._evp_pkey) self.openssl_assert(res == 1) # Add extensions. @@ -735,60 +977,55 @@ class Backend(object): sk_extension = self._ffi.gc( sk_extension, lambda x: self._lib.sk_X509_EXTENSION_pop_free( - x, self._ffi.addressof( + x, + self._ffi.addressof( self._lib._original_lib, "X509_EXTENSION_free" - ) - ) + ), + ), ) # Don't GC individual extensions because the memory is owned by # sk_extensions and will be freed along with it. self._create_x509_extensions( extensions=builder._extensions, - handlers=_EXTENSION_ENCODE_HANDLERS, + handlers=self._extension_encode_handlers, x509_obj=sk_extension, add_func=self._lib.sk_X509_EXTENSION_insert, - gc=False + 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. - res = self._lib.X509_REQ_sign( - x509_req, private_key._evp_pkey, evp_md - ) - if res == 0: - errors = self._consume_errors() - self.openssl_assert( - errors[0]._lib_reason_match( - self._lib.ERR_LIB_RSA, - self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY - ) + # Add attributes (all bytes encoded as ASN1 UTF8_STRING) + for attr_oid, attr_val in builder._attributes: + obj = _txt2obj_gc(self, attr_oid.dotted_string) + res = self._lib.X509_REQ_add1_attr_by_OBJ( + x509_req, + obj, + x509.name._ASN1Type.UTF8String.value, + attr_val, + len(attr_val), ) + self.openssl_assert(res == 1) - raise ValueError("Digest too big for RSA key") + # Sign the request using the requester's private key. + res = self._lib.X509_REQ_sign(x509_req, private_key._evp_pkey, evp_md) + if res == 0: + errors = self._consume_errors_with_text() + raise ValueError("Signing failed", errors) return _CertificateSigningRequest(self, x509_req) def create_x509_certificate(self, builder, private_key, algorithm): if not isinstance(builder, x509.CertificateBuilder): - raise TypeError('Builder type mismatch.') - if not isinstance(algorithm, hashes.HashAlgorithm): - raise TypeError('Algorithm must be a registered hash algorithm.') - - if ( - isinstance(algorithm, hashes.MD5) and not - isinstance(private_key, rsa.RSAPrivateKey) - ): - raise ValueError( - "MD5 is not a supported hash algorithm for EC/DSA certificates" - ) + raise TypeError("Builder type mismatch.") + self._x509_check_signature_params(private_key, algorithm) # Resolve the signature algorithm. - evp_md = self._evp_md_non_null_from_algorithm(algorithm) + evp_md = self._evp_md_x509_null_if_eddsa(private_key, algorithm) # Create an empty certificate. x509_cert = self._lib.X509_new() - x509_cert = self._ffi.gc(x509_cert, backend._lib.X509_free) + x509_cert = self._ffi.gc(x509_cert, self._lib.X509_free) # Set the x509 version. res = self._lib.X509_set_version(x509_cert, builder._version.value) @@ -813,21 +1050,21 @@ class Backend(object): # Set the "not before" time. self._set_asn1_time( - self._lib.X509_get_notBefore(x509_cert), builder._not_valid_before + self._lib.X509_getm_notBefore(x509_cert), builder._not_valid_before ) # Set the "not after" time. self._set_asn1_time( - self._lib.X509_get_notAfter(x509_cert), builder._not_valid_after + self._lib.X509_getm_notAfter(x509_cert), builder._not_valid_after ) # Add extensions. self._create_x509_extensions( extensions=builder._extensions, - handlers=_EXTENSION_ENCODE_HANDLERS, + handlers=self._extension_encode_handlers, x509_obj=x509_cert, add_func=self._lib.X509_add_ext, - gc=True + gc=True, ) # Set the issuer name. @@ -837,26 +1074,27 @@ class Backend(object): self.openssl_assert(res == 1) # Sign the certificate with the issuer's private key. - res = self._lib.X509_sign( - x509_cert, private_key._evp_pkey, evp_md - ) + res = self._lib.X509_sign(x509_cert, private_key._evp_pkey, evp_md) if res == 0: - errors = self._consume_errors() - self.openssl_assert( - errors[0]._lib_reason_match( - self._lib.ERR_LIB_RSA, - self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY - ) - ) - raise ValueError("Digest too big for RSA key") + errors = self._consume_errors_with_text() + raise ValueError("Signing failed", errors) return _Certificate(self, x509_cert) + def _evp_md_x509_null_if_eddsa(self, private_key, algorithm): + if isinstance( + private_key, (ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey) + ): + # OpenSSL requires us to pass NULL for EVP_MD for ed25519/ed448 + return self._ffi.NULL + else: + return self._evp_md_non_null_from_algorithm(algorithm) + def _set_asn1_time(self, asn1_time, time): if time.year >= 2050: - asn1_str = time.strftime('%Y%m%d%H%M%SZ').encode('ascii') + asn1_str = time.strftime("%Y%m%d%H%M%SZ").encode("ascii") else: - asn1_str = time.strftime('%y%m%d%H%M%SZ').encode('ascii') + asn1_str = time.strftime("%y%m%d%H%M%SZ").encode("ascii") res = self._lib.ASN1_TIME_set_string(asn1_time, asn1_str) self.openssl_assert(res == 1) @@ -869,23 +1107,14 @@ class Backend(object): 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 ( - isinstance(algorithm, hashes.MD5) and not - isinstance(private_key, rsa.RSAPrivateKey) - ): - raise ValueError( - "MD5 is not a supported hash algorithm for EC/DSA CRLs" - ) + raise TypeError("Builder type mismatch.") + self._x509_check_signature_params(private_key, algorithm) - evp_md = self._evp_md_non_null_from_algorithm(algorithm) + evp_md = self._evp_md_x509_null_if_eddsa(private_key, algorithm) # Create an empty CRL. x509_crl = self._lib.X509_CRL_new() - x509_crl = self._ffi.gc(x509_crl, backend._lib.X509_CRL_free) + x509_crl = self._ffi.gc(x509_crl, self._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) @@ -910,44 +1139,33 @@ class Backend(object): # Add extensions. self._create_x509_extensions( extensions=builder._extensions, - handlers=_CRL_EXTENSION_ENCODE_HANDLERS, + handlers=self._crl_extension_encode_handlers, x509_obj=x509_crl, add_func=self._lib.X509_CRL_add_ext, - gc=True + 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 - ) + revoked = self._lib.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 - ) + 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]._lib_reason_match( - self._lib.ERR_LIB_RSA, - self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY - ) - ) - raise ValueError("Digest too big for RSA key") + errors = self._consume_errors_with_text() + raise ValueError("Signing failed", errors) return _CertificateRevocationList(self, x509_crl) - def _create_x509_extensions(self, extensions, handlers, x509_obj, - add_func, gc): + def _create_x509_extensions( + self, extensions, handlers, x509_obj, add_func, gc + ): for i, extension in enumerate(extensions): - x509_extension = self._create_x509_extension( - handlers, extension - ) + x509_extension = self._create_x509_extension(handlers, extension) self.openssl_assert(x509_extension != self._ffi.NULL) if gc: @@ -968,33 +1186,38 @@ class Backend(object): value = _encode_asn1_str_gc(self, extension.value.value) return self._create_raw_x509_extension(extension, value) elif isinstance(extension.value, x509.TLSFeature): - asn1 = _Integers([x.value for x in extension.value]).dump() + asn1 = encode_der( + SEQUENCE, + *[ + encode_der(INTEGER, encode_der_integer(x.value)) + for x in extension.value + ] + ) value = _encode_asn1_str_gc(self, asn1) return self._create_raw_x509_extension(extension, value) elif isinstance(extension.value, x509.PrecertPoison): - asn1 = asn1crypto.core.Null().dump() - value = _encode_asn1_str_gc(self, asn1) + value = _encode_asn1_str_gc(self, encode_der(NULL)) return self._create_raw_x509_extension(extension, value) else: try: encode = handlers[extension.oid] except KeyError: raise NotImplementedError( - 'Extension not supported: {0}'.format(extension.oid) + "Extension not supported: {}".format(extension.oid) ) ext_struct = encode(self, extension.value) nid = self._lib.OBJ_txt2nid( extension.oid.dotted_string.encode("ascii") ) - backend.openssl_assert(nid != self._lib.NID_undef) + self.openssl_assert(nid != self._lib.NID_undef) return self._lib.X509V3_EXT_i2d( nid, 1 if extension.critical else 0, ext_struct ) def create_x509_revoked_certificate(self, builder): if not isinstance(builder, x509.RevokedCertificateBuilder): - raise TypeError('Builder type mismatch.') + raise TypeError("Builder type mismatch.") x509_revoked = self._lib.X509_REVOKED_new() self.openssl_assert(x509_revoked != self._ffi.NULL) @@ -1010,10 +1233,10 @@ class Backend(object): # add CRL entry extensions self._create_x509_extensions( extensions=builder._extensions, - handlers=_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS, + handlers=self._crl_entry_extension_encode_handlers, x509_obj=x509_revoked, add_func=self._lib.X509_REVOKED_add_ext, - gc=True + gc=True, ) return _RevokedCertificate(self, None, x509_revoked) @@ -1054,7 +1277,8 @@ class Backend(object): mem_bio = self._bytes_to_bio(data) # only DH is supported currently dh_cdata = self._lib.PEM_read_bio_DHparams( - mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL) + mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) if dh_cdata != self._ffi.NULL: dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) return _DHParameters(self, dh_cdata) @@ -1119,9 +1343,7 @@ class Backend(object): def load_der_parameters(self, data): mem_bio = self._bytes_to_bio(data) - dh_cdata = self._lib.d2i_DHparams_bio( - mem_bio.bio, self._ffi.NULL - ) + dh_cdata = self._lib.d2i_DHparams_bio(mem_bio.bio, self._ffi.NULL) if dh_cdata != self._ffi.NULL: dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) return _DHParameters(self, dh_cdata) @@ -1147,8 +1369,9 @@ class Backend(object): if x509 == self._ffi.NULL: self._consume_errors() raise ValueError( - "Unable to load certificate. See https://cryptography.io/en/la" - "test/faq/#why-can-t-i-import-my-pem-file for more details." + "Unable to load certificate. See https://cryptography.io/en/" + "latest/faq.html#why-can-t-i-import-my-pem-file for more" + " details." ) x509 = self._ffi.gc(x509, self._lib.X509_free) @@ -1173,7 +1396,8 @@ class Backend(object): self._consume_errors() raise ValueError( "Unable to load CRL. See https://cryptography.io/en/la" - "test/faq/#why-can-t-i-import-my-pem-file for more details." + "test/faq.html#why-can-t-i-import-my-pem-file for more" + " details." ) x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free) @@ -1197,8 +1421,9 @@ class Backend(object): if x509_req == self._ffi.NULL: self._consume_errors() raise ValueError( - "Unable to load request. See https://cryptography.io/en/la" - "test/faq/#why-can-t-i-import-my-pem-file for more details." + "Unable to load request. See https://cryptography.io/en/" + "latest/faq.html#why-can-t-i-import-my-pem-file for more" + " details." ) x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) @@ -1235,8 +1460,7 @@ class Backend(object): if evp_pkey == self._ffi.NULL: if userdata.error != 0: - errors = self._consume_errors() - self.openssl_assert(errors) + self._consume_errors() if userdata.error == -1: raise TypeError( "Password was not given but private key is encrypted" @@ -1244,7 +1468,7 @@ class Backend(object): else: assert userdata.error == -2 raise ValueError( - "Passwords longer than {0} bytes are not supported " + "Passwords longer than {} bytes are not supported " "by this backend.".format(userdata.maxsize - 1) ) else: @@ -1254,12 +1478,12 @@ class Backend(object): if password is not None and userdata.called == 0: raise TypeError( - "Password was given but private key is not encrypted.") + "Password was given but private key is not encrypted." + ) assert ( - (password is not None and userdata.called == 1) or - password is None - ) + password is not None and userdata.called == 1 + ) or password is None return convert_func(evp_pkey) @@ -1267,46 +1491,34 @@ class Backend(object): errors = self._consume_errors() if not errors: - raise ValueError("Could not deserialize key data.") - - elif ( - errors[0]._lib_reason_match( - self._lib.ERR_LIB_EVP, self._lib.EVP_R_BAD_DECRYPT - ) or errors[0]._lib_reason_match( - self._lib.ERR_LIB_PKCS12, - self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR + raise ValueError( + "Could not deserialize key data. The data may be in an " + "incorrect format or it may be encrypted with an unsupported " + "algorithm." ) + elif errors[0]._lib_reason_match( + self._lib.ERR_LIB_EVP, self._lib.EVP_R_BAD_DECRYPT + ) or errors[0]._lib_reason_match( + self._lib.ERR_LIB_PKCS12, + self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR, ): raise ValueError("Bad decrypt. Incorrect password?") - elif ( - errors[0]._lib_reason_match( - self._lib.ERR_LIB_EVP, self._lib.EVP_R_UNKNOWN_PBE_ALGORITHM - ) or errors[0]._lib_reason_match( - self._lib.ERR_LIB_PEM, self._lib.PEM_R_UNSUPPORTED_ENCRYPTION - ) - ): - raise UnsupportedAlgorithm( - "PEM data is encrypted with an unsupported cipher", - _Reasons.UNSUPPORTED_CIPHER - ) - elif any( error._lib_reason_match( self._lib.ERR_LIB_EVP, - self._lib.EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM + self._lib.EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM, ) for error in errors ): raise ValueError("Unsupported public key algorithm.") else: - assert errors[0].lib in ( - self._lib.ERR_LIB_EVP, - self._lib.ERR_LIB_PEM, - self._lib.ERR_LIB_ASN1, + raise ValueError( + "Could not deserialize key data. The data may be in an " + "incorrect format or it may be encrypted with an unsupported " + "algorithm." ) - raise ValueError("Could not deserialize key data.") def elliptic_curve_supported(self, curve): try: @@ -1317,14 +1529,7 @@ class Backend(object): group = self._lib.EC_GROUP_new_by_curve_name(curve_nid) if group == self._ffi.NULL: - errors = self._consume_errors() - self.openssl_assert( - curve_nid == self._lib.NID_undef or - errors[0]._lib_reason_match( - self._lib.ERR_LIB_EC, - self._lib.EC_R_UNKNOWN_GROUP - ) - ) + self._consume_errors() return False else: self.openssl_assert(curve_nid != self._lib.NID_undef) @@ -1356,8 +1561,8 @@ class Backend(object): return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) else: raise UnsupportedAlgorithm( - "Backend object does not support {0}.".format(curve.name), - _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + "Backend object does not support {}.".format(curve.name), + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, ) def load_elliptic_curve_private_numbers(self, numbers): @@ -1372,7 +1577,8 @@ class Backend(object): self.openssl_assert(res == 1) ec_cdata = self._ec_key_set_public_key_affine_coordinates( - ec_cdata, public.x, public.y) + ec_cdata, public.x, public.y + ) evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) @@ -1381,7 +1587,8 @@ class Backend(object): def load_elliptic_curve_public_numbers(self, numbers): ec_cdata = self._ec_key_new_by_curve(numbers.curve) ec_cdata = self._ec_key_set_public_key_affine_coordinates( - ec_cdata, numbers.x, numbers.y) + ec_cdata, numbers.x, numbers.y + ) evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) @@ -1419,8 +1626,9 @@ class Backend(object): value = self._ffi.gc(value, self._lib.BN_clear_free) with self._tmp_bn_ctx() as bn_ctx: - res = self._lib.EC_POINT_mul(group, point, value, self._ffi.NULL, - self._ffi.NULL, bn_ctx) + res = self._lib.EC_POINT_mul( + group, point, value, self._ffi.NULL, self._ffi.NULL, bn_ctx + ) self.openssl_assert(res == 1) bn_x = self._lib.BN_CTX_get(bn_ctx) @@ -1442,6 +1650,9 @@ class Backend(object): def _ec_key_new_by_curve(self, curve): curve_nid = self._elliptic_curve_to_nid(curve) + return self._ec_key_new_by_curve_nid(curve_nid) + + def _ec_key_new_by_curve_nid(self, curve_nid): ec_cdata = self._lib.EC_KEY_new_by_curve_name(curve_nid) self.openssl_assert(ec_cdata != self._ffi.NULL) return self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) @@ -1472,15 +1683,13 @@ class Backend(object): ocsp_req = self._ffi.gc(ocsp_req, self._lib.OCSP_REQUEST_free) cert, issuer, algorithm = builder._request evp_md = self._evp_md_non_null_from_algorithm(algorithm) - certid = self._lib.OCSP_cert_to_id( - evp_md, cert._x509, issuer._x509 - ) + certid = self._lib.OCSP_cert_to_id(evp_md, cert._x509, issuer._x509) self.openssl_assert(certid != self._ffi.NULL) onereq = self._lib.OCSP_request_add0_id(ocsp_req, certid) self.openssl_assert(onereq != self._ffi.NULL) self._create_x509_extensions( extensions=builder._extensions, - handlers=_OCSP_REQUEST_EXTENSION_ENCODE_HANDLERS, + handlers=self._ocsp_request_extension_encode_handlers, x509_obj=ocsp_req, add_func=self._lib.OCSP_REQUEST_add_ext, gc=True, @@ -1488,6 +1697,8 @@ class Backend(object): return _OCSPRequest(self, ocsp_req) def _create_ocsp_basic_response(self, builder, private_key, algorithm): + self._x509_check_signature_params(private_key, algorithm) + basic = self._lib.OCSP_BASICRESP_new() self.openssl_assert(basic != self._ffi.NULL) basic = self._ffi.gc(basic, self._lib.OCSP_BASICRESP_free) @@ -1495,8 +1706,9 @@ class Backend(object): builder._response._algorithm ) certid = self._lib.OCSP_cert_to_id( - evp_md, builder._response._cert._x509, - builder._response._issuer._x509 + evp_md, + builder._response._cert._x509, + builder._response._issuer._x509, ) self.openssl_assert(certid != self._ffi.NULL) certid = self._ffi.gc(certid, self._lib.OCSP_CERTID_free) @@ -1528,11 +1740,11 @@ class Backend(object): reason, rev_time, this_update, - next_update + next_update, ) self.openssl_assert(res != self._ffi.NULL) # okay, now sign the basic structure - evp_md = self._evp_md_non_null_from_algorithm(algorithm) + evp_md = self._evp_md_x509_null_if_eddsa(private_key, algorithm) responder_cert, responder_encoding = builder._responder_id flags = self._lib.OCSP_NOCERTS if responder_encoding is ocsp.OCSPResponderEncoding.HASH: @@ -1545,30 +1757,33 @@ class Backend(object): self._create_x509_extensions( extensions=builder._extensions, - handlers=_OCSP_BASICRESP_EXTENSION_ENCODE_HANDLERS, + handlers=self._ocsp_basicresp_extension_encode_handlers, x509_obj=basic, add_func=self._lib.OCSP_BASICRESP_add_ext, gc=True, ) res = self._lib.OCSP_basic_sign( - basic, responder_cert._x509, private_key._evp_pkey, - evp_md, self._ffi.NULL, flags + basic, + responder_cert._x509, + private_key._evp_pkey, + evp_md, + self._ffi.NULL, + flags, ) if res != 1: - errors = self._consume_errors() - self.openssl_assert( - errors[0]._lib_reason_match( - self._lib.ERR_LIB_X509, - self._lib.X509_R_KEY_VALUES_MISMATCH - ) + errors = self._consume_errors_with_text() + raise ValueError( + "Error while signing. responder_cert must be signed " + "by private_key", + errors, ) - raise ValueError("responder_cert must be signed by private_key") return basic - def create_ocsp_response(self, response_status, builder, private_key, - algorithm): + def create_ocsp_response( + self, response_status, builder, private_key, algorithm + ): if response_status is ocsp.OCSPResponseStatus.SUCCESSFUL: basic = self._create_ocsp_basic_response( builder, private_key, algorithm @@ -1584,9 +1799,8 @@ class Backend(object): return _OCSPResponse(self, ocsp_resp) def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): - return ( - self.elliptic_curve_supported(curve) and - isinstance(algorithm, ec.ECDH) + return self.elliptic_curve_supported(curve) and isinstance( + algorithm, ec.ECDH ) def _ec_cdata_to_evp_pkey(self, ec_cdata): @@ -1600,18 +1814,15 @@ class Backend(object): Get the NID for a curve name. """ - curve_aliases = { - "secp192r1": "prime192v1", - "secp256r1": "prime256v1" - } + curve_aliases = {"secp192r1": "prime192v1", "secp256r1": "prime256v1"} curve_name = curve_aliases.get(curve.name, curve.name) curve_nid = self._lib.OBJ_sn2nid(curve_name.encode()) if curve_nid == self._lib.NID_undef: raise UnsupportedAlgorithm( - "{0} is not a supported elliptic curve".format(curve.name), - _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + "{} is not a supported elliptic curve".format(curve.name), + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, ) return curve_nid @@ -1674,47 +1885,32 @@ class Backend(object): return ctx - def _private_key_bytes(self, encoding, format, encryption_algorithm, - evp_pkey, cdata): + def _private_key_bytes( + self, encoding, format, encryption_algorithm, key, evp_pkey, cdata + ): + # validate argument types + if not isinstance(encoding, serialization.Encoding): + raise TypeError("encoding must be an item from the Encoding enum") if not isinstance(format, serialization.PrivateFormat): raise TypeError( "format must be an item from the PrivateFormat enum" ) - - # X9.62 encoding is only valid for EC public keys - if encoding is serialization.Encoding.X962: - raise ValueError("X9.62 format is only valid for EC public keys") - - # Raw format and encoding are only valid for X25519, Ed25519, X448, and - # Ed448 keys. We capture those cases before this method is called so if - # we see those enum values here it means the caller has passed them to - # a key that doesn't support raw type - if format is serialization.PrivateFormat.Raw: - raise ValueError("raw format is invalid with this key or encoding") - - if encoding is serialization.Encoding.Raw: - raise ValueError("raw encoding is invalid with this key or format") - - if not isinstance(encryption_algorithm, - serialization.KeySerializationEncryption): + if not isinstance( + encryption_algorithm, serialization.KeySerializationEncryption + ): raise TypeError( "Encryption algorithm must be a KeySerializationEncryption " "instance" ) + # validate password if isinstance(encryption_algorithm, serialization.NoEncryption): password = b"" - passlen = 0 - evp_cipher = self._ffi.NULL - elif isinstance(encryption_algorithm, - serialization.BestAvailableEncryption): - # This is a curated value that we will update over time. - evp_cipher = self._lib.EVP_get_cipherbyname( - b"aes-256-cbc" - ) + elif isinstance( + encryption_algorithm, serialization.BestAvailableEncryption + ): password = encryption_algorithm.password - passlen = len(password) - if passlen > 1023: + if len(password) > 1023: raise ValueError( "Passwords longer than 1023 bytes are not supported by " "this backend" @@ -1722,183 +1918,156 @@ class Backend(object): else: raise ValueError("Unsupported encryption type") - key_type = self._lib.EVP_PKEY_id(evp_pkey) - if encoding is serialization.Encoding.PEM: - if format is serialization.PrivateFormat.PKCS8: + # PKCS8 + PEM/DER + if format is serialization.PrivateFormat.PKCS8: + if encoding is serialization.Encoding.PEM: write_bio = self._lib.PEM_write_bio_PKCS8PrivateKey - key = evp_pkey + elif encoding is serialization.Encoding.DER: + write_bio = self._lib.i2d_PKCS8PrivateKey_bio else: - assert format is serialization.PrivateFormat.TraditionalOpenSSL + raise ValueError("Unsupported encoding for PKCS8") + return self._private_key_bytes_via_bio( + write_bio, evp_pkey, password + ) + + # TraditionalOpenSSL + PEM/DER + if format is serialization.PrivateFormat.TraditionalOpenSSL: + if self._fips_enabled and not isinstance( + encryption_algorithm, serialization.NoEncryption + ): + raise ValueError( + "Encrypted traditional OpenSSL format is not " + "supported in FIPS mode." + ) + key_type = self._lib.EVP_PKEY_id(evp_pkey) + + if encoding is serialization.Encoding.PEM: if key_type == self._lib.EVP_PKEY_RSA: write_bio = self._lib.PEM_write_bio_RSAPrivateKey elif key_type == self._lib.EVP_PKEY_DSA: write_bio = self._lib.PEM_write_bio_DSAPrivateKey - else: - assert key_type == self._lib.EVP_PKEY_EC + elif key_type == self._lib.EVP_PKEY_EC: write_bio = self._lib.PEM_write_bio_ECPrivateKey + else: + raise ValueError( + "Unsupported key type for TraditionalOpenSSL" + ) + return self._private_key_bytes_via_bio( + write_bio, cdata, password + ) - key = cdata - elif encoding is serialization.Encoding.DER: - if format is serialization.PrivateFormat.TraditionalOpenSSL: - if not isinstance( - encryption_algorithm, serialization.NoEncryption - ): + if encoding is serialization.Encoding.DER: + if password: raise ValueError( "Encryption is not supported for DER encoded " "traditional OpenSSL keys" ) + if key_type == self._lib.EVP_PKEY_RSA: + write_bio = self._lib.i2d_RSAPrivateKey_bio + elif key_type == self._lib.EVP_PKEY_EC: + write_bio = self._lib.i2d_ECPrivateKey_bio + elif key_type == self._lib.EVP_PKEY_DSA: + write_bio = self._lib.i2d_DSAPrivateKey_bio + else: + raise ValueError( + "Unsupported key type for TraditionalOpenSSL" + ) + return self._bio_func_output(write_bio, cdata) - return self._private_key_bytes_traditional_der(key_type, cdata) - else: - assert format is serialization.PrivateFormat.PKCS8 - write_bio = self._lib.i2d_PKCS8PrivateKey_bio - key = evp_pkey + raise ValueError("Unsupported encoding for TraditionalOpenSSL") + + # OpenSSH + PEM + if format is serialization.PrivateFormat.OpenSSH: + if encoding is serialization.Encoding.PEM: + return ssh.serialize_ssh_private_key(key, password) + + raise ValueError( + "OpenSSH private key format can only be used" + " with PEM encoding" + ) + + # Anything that key-specific code was supposed to handle earlier, + # like Raw. + raise ValueError("format is invalid with this key") + + def _private_key_bytes_via_bio(self, write_bio, evp_pkey, password): + if not password: + evp_cipher = self._ffi.NULL else: - raise TypeError("encoding must be Encoding.PEM or Encoding.DER") + # This is a curated value that we will update over time. + evp_cipher = self._lib.EVP_get_cipherbyname(b"aes-256-cbc") - bio = self._create_mem_bio_gc() - res = write_bio( - bio, - key, + return self._bio_func_output( + write_bio, + evp_pkey, evp_cipher, password, - passlen, + len(password), + self._ffi.NULL, self._ffi.NULL, - self._ffi.NULL ) - self.openssl_assert(res == 1) - return self._read_mem_bio(bio) - - def _private_key_bytes_traditional_der(self, key_type, cdata): - if key_type == self._lib.EVP_PKEY_RSA: - write_bio = self._lib.i2d_RSAPrivateKey_bio - elif key_type == self._lib.EVP_PKEY_EC: - write_bio = self._lib.i2d_ECPrivateKey_bio - else: - self.openssl_assert(key_type == self._lib.EVP_PKEY_DSA) - write_bio = self._lib.i2d_DSAPrivateKey_bio + def _bio_func_output(self, write_bio, *args): bio = self._create_mem_bio_gc() - res = write_bio(bio, cdata) + res = write_bio(bio, *args) self.openssl_assert(res == 1) return self._read_mem_bio(bio) def _public_key_bytes(self, encoding, format, key, evp_pkey, cdata): if not isinstance(encoding, serialization.Encoding): raise TypeError("encoding must be an item from the Encoding enum") + if not isinstance(format, serialization.PublicFormat): + raise TypeError( + "format must be an item from the PublicFormat enum" + ) - # Compressed/UncompressedPoint are only valid for EC keys and those - # cases are handled by the ECPublicKey public_bytes method before this - # method is called - if format in (serialization.PublicFormat.UncompressedPoint, - serialization.PublicFormat.CompressedPoint): - raise ValueError("Point formats are not valid for this key type") - - # Raw format and encoding are only valid for X25519, Ed25519, X448, and - # Ed448 keys. We capture those cases before this method is called so if - # we see those enum values here it means the caller has passed them to - # a key that doesn't support raw type - if format is serialization.PublicFormat.Raw: - raise ValueError("raw format is invalid with this key or encoding") - - if encoding is serialization.Encoding.Raw: - raise ValueError("raw encoding is invalid with this key or format") - - if ( - format is serialization.PublicFormat.OpenSSH or - encoding is serialization.Encoding.OpenSSH - ): - if ( - format is not serialization.PublicFormat.OpenSSH or - encoding is not serialization.Encoding.OpenSSH - ): - raise ValueError( - "OpenSSH format must be used with OpenSSH encoding" - ) - return self._openssh_public_key_bytes(key) - elif format is serialization.PublicFormat.SubjectPublicKeyInfo: + # SubjectPublicKeyInfo + PEM/DER + if format is serialization.PublicFormat.SubjectPublicKeyInfo: if encoding is serialization.Encoding.PEM: write_bio = self._lib.PEM_write_bio_PUBKEY - else: - assert encoding is serialization.Encoding.DER + elif encoding is serialization.Encoding.DER: write_bio = self._lib.i2d_PUBKEY_bio + else: + raise ValueError( + "SubjectPublicKeyInfo works only with PEM or DER encoding" + ) + return self._bio_func_output(write_bio, evp_pkey) - key = evp_pkey - elif format is serialization.PublicFormat.PKCS1: + # PKCS1 + PEM/DER + if format is serialization.PublicFormat.PKCS1: # Only RSA is supported here. - assert self._lib.EVP_PKEY_id(evp_pkey) == self._lib.EVP_PKEY_RSA + key_type = self._lib.EVP_PKEY_id(evp_pkey) + if key_type != self._lib.EVP_PKEY_RSA: + raise ValueError("PKCS1 format is supported only for RSA keys") + if encoding is serialization.Encoding.PEM: write_bio = self._lib.PEM_write_bio_RSAPublicKey - else: - assert encoding is serialization.Encoding.DER + elif encoding is serialization.Encoding.DER: write_bio = self._lib.i2d_RSAPublicKey_bio + else: + raise ValueError("PKCS1 works only with PEM or DER encoding") + return self._bio_func_output(write_bio, cdata) - key = cdata - else: - raise TypeError( - "format must be an item from the PublicFormat enum" - ) - - bio = self._create_mem_bio_gc() - res = write_bio(bio, key) - self.openssl_assert(res == 1) - return self._read_mem_bio(bio) + # OpenSSH + OpenSSH + if format is serialization.PublicFormat.OpenSSH: + if encoding is serialization.Encoding.OpenSSH: + return ssh.serialize_ssh_public_key(key) - def _openssh_public_key_bytes(self, key): - if isinstance(key, rsa.RSAPublicKey): - public_numbers = key.public_numbers() - return b"ssh-rsa " + base64.b64encode( - ssh._ssh_write_string(b"ssh-rsa") + - ssh._ssh_write_mpint(public_numbers.e) + - ssh._ssh_write_mpint(public_numbers.n) - ) - elif isinstance(key, dsa.DSAPublicKey): - public_numbers = key.public_numbers() - parameter_numbers = public_numbers.parameter_numbers - return b"ssh-dss " + base64.b64encode( - ssh._ssh_write_string(b"ssh-dss") + - ssh._ssh_write_mpint(parameter_numbers.p) + - ssh._ssh_write_mpint(parameter_numbers.q) + - ssh._ssh_write_mpint(parameter_numbers.g) + - ssh._ssh_write_mpint(public_numbers.y) + raise ValueError( + "OpenSSH format must be used with OpenSSH encoding" ) - else: - assert isinstance(key, ec.EllipticCurvePublicKey) - public_numbers = key.public_numbers() - try: - curve_name = { - ec.SECP256R1: b"nistp256", - ec.SECP384R1: b"nistp384", - ec.SECP521R1: b"nistp521", - }[type(public_numbers.curve)] - except KeyError: - raise ValueError( - "Only SECP256R1, SECP384R1, and SECP521R1 curves are " - "supported by the SSH public key format" - ) - point = key.public_bytes( - serialization.Encoding.X962, - serialization.PublicFormat.UncompressedPoint - ) - return b"ecdsa-sha2-" + curve_name + b" " + base64.b64encode( - ssh._ssh_write_string(b"ecdsa-sha2-" + curve_name) + - ssh._ssh_write_string(curve_name) + - ssh._ssh_write_string(point) - ) + # Anything that key-specific code was supposed to handle earlier, + # like Raw, CompressedPoint, UncompressedPoint + raise ValueError("format is invalid with this key") def _parameter_bytes(self, encoding, format, cdata): if encoding is serialization.Encoding.OpenSSH: - raise TypeError( - "OpenSSH encoding is not supported" - ) + raise TypeError("OpenSSH encoding is not supported") # Only DH is supported here currently. q = self._ffi.new("BIGNUM **") - self._lib.DH_get0_pqg(cdata, - self._ffi.NULL, - q, - self._ffi.NULL) + self._lib.DH_get0_pqg(cdata, self._ffi.NULL, q, self._ffi.NULL) if encoding is serialization.Encoding.PEM: if q[0] != self._ffi.NULL: write_bio = self._lib.PEM_write_bio_DHxparams @@ -1918,8 +2087,12 @@ class Backend(object): return self._read_mem_bio(bio) def generate_dh_parameters(self, generator, key_size): - if key_size < 512: - raise ValueError("DH key_size must be at least 512 bits") + if key_size < dh._MIN_MODULUS_SIZE: + raise ValueError( + "DH key_size must be at least {} bits".format( + dh._MIN_MODULUS_SIZE + ) + ) if generator not in (2, 5): raise ValueError("DH generator must be 2 or 5") @@ -1929,10 +2102,7 @@ class Backend(object): dh_param_cdata = self._ffi.gc(dh_param_cdata, self._lib.DH_free) res = self._lib.DH_generate_parameters_ex( - dh_param_cdata, - key_size, - generator, - self._ffi.NULL + dh_param_cdata, key_size, generator, self._ffi.NULL ) self.openssl_assert(res == 1) @@ -1956,7 +2126,8 @@ class Backend(object): def generate_dh_private_key_and_parameters(self, generator, key_size): return self.generate_dh_private_key( - self.generate_dh_parameters(generator, key_size)) + self.generate_dh_parameters(generator, key_size) + ) def load_dh_private_numbers(self, numbers): parameter_numbers = numbers.public_numbers.parameter_numbers @@ -1995,12 +2166,10 @@ class Backend(object): # the key to the attacker in exchange for having the full key space # available. See: https://crypto.stackexchange.com/questions/12961 if codes[0] != 0 and not ( - parameter_numbers.g == 2 and - codes[0] ^ self._lib.DH_NOT_SUITABLE_GENERATOR == 0 + parameter_numbers.g == 2 + and codes[0] ^ self._lib.DH_NOT_SUITABLE_GENERATOR == 0 ): - raise ValueError( - "DH private numbers did not pass safety checks." - ) + raise ValueError("DH private numbers did not pass safety checks.") evp_pkey = self._dh_cdata_to_evp_pkey(dh_cdata) @@ -2095,11 +2264,11 @@ class Backend(object): evp_pkey = self._create_evp_pkey_gc() res = self._lib.EVP_PKEY_set_type(evp_pkey, self._lib.NID_X25519) - backend.openssl_assert(res == 1) + self.openssl_assert(res == 1) res = self._lib.EVP_PKEY_set1_tls_encodedpoint( evp_pkey, data, len(data) ) - backend.openssl_assert(res == 1) + self.openssl_assert(res == 1) return _X25519PublicKey(self, evp_pkey) def x25519_load_private_bytes(self, data): @@ -2127,7 +2296,7 @@ class Backend(object): ba[0:16] = pkcs8_prefix ba[16:] = data bio = self._bytes_to_bio(ba) - evp_pkey = backend._lib.d2i_PrivateKey_bio(bio.bio, self._ffi.NULL) + evp_pkey = self._lib.d2i_PrivateKey_bio(bio.bio, self._ffi.NULL) self.openssl_assert(evp_pkey != self._ffi.NULL) evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) @@ -2154,7 +2323,9 @@ class Backend(object): return _X25519PrivateKey(self, evp_pkey) def x25519_supported(self): - return self._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER + if self._fips_enabled: + return False + return not self._lib.CRYPTOGRAPHY_IS_LIBRESSL def x448_load_public_bytes(self, data): if len(data) != 56: @@ -2184,44 +2355,115 @@ class Backend(object): return _X448PrivateKey(self, evp_pkey) def x448_supported(self): + if self._fips_enabled: + return False return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 + def ed25519_supported(self): + if self._fips_enabled: + return False + return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B + + def ed25519_load_public_bytes(self, data): + utils._check_bytes("data", data) + + if len(data) != ed25519._ED25519_KEY_SIZE: + raise ValueError("An Ed25519 public key is 32 bytes long") + + evp_pkey = self._lib.EVP_PKEY_new_raw_public_key( + self._lib.NID_ED25519, self._ffi.NULL, data, len(data) + ) + self.openssl_assert(evp_pkey != self._ffi.NULL) + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + + return _Ed25519PublicKey(self, evp_pkey) + + def ed25519_load_private_bytes(self, data): + if len(data) != ed25519._ED25519_KEY_SIZE: + raise ValueError("An Ed25519 private key is 32 bytes long") + + utils._check_byteslike("data", data) + data_ptr = self._ffi.from_buffer(data) + evp_pkey = self._lib.EVP_PKEY_new_raw_private_key( + self._lib.NID_ED25519, self._ffi.NULL, data_ptr, len(data) + ) + self.openssl_assert(evp_pkey != self._ffi.NULL) + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + + return _Ed25519PrivateKey(self, evp_pkey) + + def ed25519_generate_key(self): + evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_ED25519) + return _Ed25519PrivateKey(self, evp_pkey) + + def ed448_supported(self): + if self._fips_enabled: + return False + return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B + + def ed448_load_public_bytes(self, data): + utils._check_bytes("data", data) + if len(data) != _ED448_KEY_SIZE: + raise ValueError("An Ed448 public key is 57 bytes long") + + evp_pkey = self._lib.EVP_PKEY_new_raw_public_key( + self._lib.NID_ED448, self._ffi.NULL, data, len(data) + ) + self.openssl_assert(evp_pkey != self._ffi.NULL) + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + + return _Ed448PublicKey(self, evp_pkey) + + def ed448_load_private_bytes(self, data): + utils._check_byteslike("data", data) + if len(data) != _ED448_KEY_SIZE: + raise ValueError("An Ed448 private key is 57 bytes long") + + data_ptr = self._ffi.from_buffer(data) + evp_pkey = self._lib.EVP_PKEY_new_raw_private_key( + self._lib.NID_ED448, self._ffi.NULL, data_ptr, len(data) + ) + self.openssl_assert(evp_pkey != self._ffi.NULL) + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + + return _Ed448PrivateKey(self, evp_pkey) + + def ed448_generate_key(self): + evp_pkey = self._evp_pkey_keygen_gc(self._lib.NID_ED448) + return _Ed448PrivateKey(self, evp_pkey) + def derive_scrypt(self, key_material, salt, length, n, r, p): buf = self._ffi.new("unsigned char[]", length) key_material_ptr = self._ffi.from_buffer(key_material) res = self._lib.EVP_PBE_scrypt( - key_material_ptr, len(key_material), salt, len(salt), n, r, p, - scrypt._MEM_LIMIT, buf, length + key_material_ptr, + len(key_material), + salt, + len(salt), + n, + r, + p, + scrypt._MEM_LIMIT, + buf, + length, ) if res != 1: - errors = self._consume_errors() - if not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111: - # This error is only added to the stack in 1.1.1+ - self.openssl_assert( - errors[0]._lib_reason_match( - self._lib.ERR_LIB_EVP, - self._lib.ERR_R_MALLOC_FAILURE - ) or - errors[0]._lib_reason_match( - self._lib.ERR_LIB_EVP, - self._lib.EVP_R_MEMORY_LIMIT_EXCEEDED - ) - ) - + errors = self._consume_errors_with_text() # memory required formula explained here: # https://blog.filippo.io/the-scrypt-parameters/ - min_memory = 128 * n * r // (1024**2) + min_memory = 128 * n * r // (1024 ** 2) raise MemoryError( "Not enough memory to derive key. These parameters require" - " {} MB of memory.".format(min_memory) + " {} MB of memory.".format(min_memory), + errors, ) return self._ffi.buffer(buf)[:] def aead_cipher_supported(self, cipher): cipher_name = aead._aead_cipher_name(cipher) - return ( - self._lib.EVP_get_cipherbyname(cipher_name) != self._ffi.NULL - ) + if self._fips_enabled and cipher_name not in self._fips_aead: + return False + return self._lib.EVP_get_cipherbyname(cipher_name) != self._ffi.NULL @contextlib.contextmanager def _zeroed_bytearray(self, length): @@ -2306,12 +2548,216 @@ class Backend(object): num = self._lib.sk_X509_num(sk_x509_ptr[0]) for i in range(num): x509 = self._lib.sk_X509_value(sk_x509, i) - x509 = self._ffi.gc(x509, self._lib.X509_free) self.openssl_assert(x509 != self._ffi.NULL) + x509 = self._ffi.gc(x509, self._lib.X509_free) additional_certificates.append(_Certificate(self, x509)) return (key, cert, additional_certificates) + def serialize_key_and_certificates_to_pkcs12( + self, name, key, cert, cas, encryption_algorithm + ): + password = None + if name is not None: + utils._check_bytes("name", name) + + if isinstance(encryption_algorithm, serialization.NoEncryption): + nid_cert = -1 + nid_key = -1 + pkcs12_iter = 0 + mac_iter = 0 + elif isinstance( + encryption_algorithm, serialization.BestAvailableEncryption + ): + # PKCS12 encryption is hopeless trash and can never be fixed. + # This is the least terrible option. + nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC + nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC + # At least we can set this higher than OpenSSL's default + pkcs12_iter = 20000 + # mac_iter chosen for compatibility reasons, see: + # https://www.openssl.org/docs/man1.1.1/man3/PKCS12_create.html + # Did we mention how lousy PKCS12 encryption is? + mac_iter = 1 + password = encryption_algorithm.password + else: + raise ValueError("Unsupported key encryption type") + + if cas is None or len(cas) == 0: + sk_x509 = self._ffi.NULL + else: + sk_x509 = self._lib.sk_X509_new_null() + sk_x509 = self._ffi.gc(sk_x509, self._lib.sk_X509_free) + + # reverse the list when building the stack so that they're encoded + # in the order they were originally provided. it is a mystery + for ca in reversed(cas): + res = self._lib.sk_X509_push(sk_x509, ca._x509) + backend.openssl_assert(res >= 1) + + with self._zeroed_null_terminated_buf(password) as password_buf: + with self._zeroed_null_terminated_buf(name) as name_buf: + p12 = self._lib.PKCS12_create( + password_buf, + name_buf, + key._evp_pkey if key else self._ffi.NULL, + cert._x509 if cert else self._ffi.NULL, + sk_x509, + nid_key, + nid_cert, + pkcs12_iter, + mac_iter, + 0, + ) + + self.openssl_assert(p12 != self._ffi.NULL) + p12 = self._ffi.gc(p12, self._lib.PKCS12_free) + + bio = self._create_mem_bio_gc() + res = self._lib.i2d_PKCS12_bio(bio, p12) + self.openssl_assert(res > 0) + return self._read_mem_bio(bio) + + def poly1305_supported(self): + if self._fips_enabled: + return False + return self._lib.Cryptography_HAS_POLY1305 == 1 + + def create_poly1305_ctx(self, key): + utils._check_byteslike("key", key) + if len(key) != _POLY1305_KEY_SIZE: + raise ValueError("A poly1305 key is 32 bytes long") + + return _Poly1305Context(self, key) + + def load_pem_pkcs7_certificates(self, data): + utils._check_bytes("data", data) + bio = self._bytes_to_bio(data) + p7 = self._lib.PEM_read_bio_PKCS7( + bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) + if p7 == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to parse PKCS7 data") + + p7 = self._ffi.gc(p7, self._lib.PKCS7_free) + return self._load_pkcs7_certificates(p7) + + def load_der_pkcs7_certificates(self, data): + utils._check_bytes("data", data) + bio = self._bytes_to_bio(data) + p7 = self._lib.d2i_PKCS7_bio(bio.bio, self._ffi.NULL) + if p7 == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to parse PKCS7 data") + + p7 = self._ffi.gc(p7, self._lib.PKCS7_free) + return self._load_pkcs7_certificates(p7) + + def _load_pkcs7_certificates(self, p7): + nid = self._lib.OBJ_obj2nid(p7.type) + self.openssl_assert(nid != self._lib.NID_undef) + if nid != self._lib.NID_pkcs7_signed: + raise UnsupportedAlgorithm( + "Only basic signed structures are currently supported. NID" + " for this data was {}".format(nid), + _Reasons.UNSUPPORTED_SERIALIZATION, + ) + + sk_x509 = p7.d.sign.cert + num = self._lib.sk_X509_num(sk_x509) + certs = [] + for i in range(num): + x509 = self._lib.sk_X509_value(sk_x509, i) + self.openssl_assert(x509 != self._ffi.NULL) + res = self._lib.X509_up_ref(x509) + # When OpenSSL is less than 1.1.0 up_ref returns the current + # refcount. On 1.1.0+ it returns 1 for success. + self.openssl_assert(res >= 1) + x509 = self._ffi.gc(x509, self._lib.X509_free) + certs.append(_Certificate(self, x509)) + + return certs + + def pkcs7_sign(self, builder, encoding, options): + bio = self._bytes_to_bio(builder._data) + init_flags = self._lib.PKCS7_PARTIAL + final_flags = 0 + + if len(builder._additional_certs) == 0: + certs = self._ffi.NULL + else: + certs = self._lib.sk_X509_new_null() + certs = self._ffi.gc(certs, self._lib.sk_X509_free) + for cert in builder._additional_certs: + res = self._lib.sk_X509_push(certs, cert._x509) + self.openssl_assert(res >= 1) + + if pkcs7.PKCS7Options.DetachedSignature in options: + # Don't embed the data in the PKCS7 structure + init_flags |= self._lib.PKCS7_DETACHED + final_flags |= self._lib.PKCS7_DETACHED + + # This just inits a structure for us. However, there + # are flags we need to set, joy. + p7 = self._lib.PKCS7_sign( + self._ffi.NULL, + self._ffi.NULL, + certs, + self._ffi.NULL, + init_flags, + ) + self.openssl_assert(p7 != self._ffi.NULL) + p7 = self._ffi.gc(p7, self._lib.PKCS7_free) + signer_flags = 0 + # These flags are configurable on a per-signature basis + # but we've deliberately chosen to make the API only allow + # setting it across all signatures for now. + if pkcs7.PKCS7Options.NoCapabilities in options: + signer_flags |= self._lib.PKCS7_NOSMIMECAP + elif pkcs7.PKCS7Options.NoAttributes in options: + signer_flags |= self._lib.PKCS7_NOATTR + + if pkcs7.PKCS7Options.NoCerts in options: + signer_flags |= self._lib.PKCS7_NOCERTS + + for certificate, private_key, hash_algorithm in builder._signers: + md = self._evp_md_non_null_from_algorithm(hash_algorithm) + p7signerinfo = self._lib.PKCS7_sign_add_signer( + p7, certificate._x509, private_key._evp_pkey, md, signer_flags + ) + self.openssl_assert(p7signerinfo != self._ffi.NULL) + + for option in options: + # DetachedSignature, NoCapabilities, and NoAttributes are already + # handled so we just need to check these last two options. + if option is pkcs7.PKCS7Options.Text: + final_flags |= self._lib.PKCS7_TEXT + elif option is pkcs7.PKCS7Options.Binary: + final_flags |= self._lib.PKCS7_BINARY + + bio_out = self._create_mem_bio_gc() + if encoding is serialization.Encoding.SMIME: + # This finalizes the structure + res = self._lib.SMIME_write_PKCS7( + bio_out, p7, bio.bio, final_flags + ) + elif encoding is serialization.Encoding.PEM: + res = self._lib.PKCS7_final(p7, bio.bio, final_flags) + self.openssl_assert(res == 1) + res = self._lib.PEM_write_bio_PKCS7_stream( + bio_out, p7, bio.bio, final_flags + ) + else: + assert encoding is serialization.Encoding.DER + # We need to call finalize here becauase i2d_PKCS7_bio does not + # finalize. + res = self._lib.PKCS7_final(p7, bio.bio, final_flags) + self.openssl_assert(res == 1) + res = self._lib.i2d_PKCS7_bio(bio_out, p7) + self.openssl_assert(res == 1) + return self._read_mem_bio(bio_out) + class GetCipherByName(object): def __init__(self, fmt): @@ -2323,7 +2769,7 @@ class GetCipherByName(object): def _get_xts_cipher(backend, cipher, mode): - cipher_name = "aes-{0}-xts".format(cipher.key_size // 2) + cipher_name = "aes-{}-xts".format(cipher.key_size // 2) return backend._lib.EVP_get_cipherbyname(cipher_name.encode("ascii")) diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py index 66ac5fd69..ad5dad3f7 100644 --- a/src/cryptography/hazmat/backends/openssl/ciphers.py +++ b/src/cryptography/hazmat/backends/openssl/ciphers.py @@ -17,6 +17,7 @@ from cryptography.hazmat.primitives.ciphers import modes class _CipherContext(object): _ENCRYPT = 1 _DECRYPT = 0 + _MAX_CHUNK_SIZE = 2 ** 30 - 1 def __init__(self, backend, cipher, mode, operation): self._backend = backend @@ -40,10 +41,11 @@ class _CipherContext(object): adapter = registry[type(cipher), type(mode)] except KeyError: raise UnsupportedAlgorithm( - "cipher {0} in {1} mode is not supported " + "cipher {} in {} mode is not supported " "by this backend.".format( - cipher.name, mode.name if mode else mode), - _Reasons.UNSUPPORTED_CIPHER + cipher.name, mode.name if mode else mode + ), + _Reasons.UNSUPPORTED_CIPHER, ) evp_cipher = adapter(self._backend, cipher, mode) @@ -53,7 +55,7 @@ class _CipherContext(object): msg += "in {0.name} mode ".format(mode) msg += ( "is not supported by this backend (Your version of OpenSSL " - "may be too old. Current version: {0}.)" + "may be too old. Current version: {}.)" ).format(self._backend.openssl_version_text()) raise UnsupportedAlgorithm(msg, _Reasons.UNSUPPORTED_CIPHER) @@ -70,11 +72,14 @@ class _CipherContext(object): else: iv_nonce = self._backend._ffi.NULL # begin init with cipher and operation type - res = self._backend._lib.EVP_CipherInit_ex(ctx, evp_cipher, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - self._backend._ffi.NULL, - operation) + res = self._backend._lib.EVP_CipherInit_ex( + ctx, + evp_cipher, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + operation, + ) self._backend.openssl_assert(res != 0) # set the key length to handle variable key ciphers res = self._backend._lib.EVP_CIPHER_CTX_set_key_length( @@ -83,26 +88,21 @@ class _CipherContext(object): self._backend.openssl_assert(res != 0) if isinstance(mode, modes.GCM): res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, self._backend._lib.EVP_CTRL_AEAD_SET_IVLEN, - len(iv_nonce), self._backend._ffi.NULL + ctx, + self._backend._lib.EVP_CTRL_AEAD_SET_IVLEN, + len(iv_nonce), + self._backend._ffi.NULL, ) self._backend.openssl_assert(res != 0) if mode.tag is not None: res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - ctx, self._backend._lib.EVP_CTRL_AEAD_SET_TAG, - len(mode.tag), mode.tag + ctx, + self._backend._lib.EVP_CTRL_AEAD_SET_TAG, + len(mode.tag), + mode.tag, ) self._backend.openssl_assert(res != 0) self._tag = mode.tag - elif ( - self._operation == self._DECRYPT and - self._backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 and - not self._backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - ): - raise NotImplementedError( - "delayed passing of GCM tag requires OpenSSL >= 1.0.2." - " To use this feature please update OpenSSL" - ) # pass key/iv res = self._backend._lib.EVP_CipherInit_ex( @@ -111,7 +111,7 @@ class _CipherContext(object): self._backend._ffi.NULL, self._backend._ffi.from_buffer(cipher.key), iv_nonce, - operation + operation, ) self._backend.openssl_assert(res != 0) # We purposely disable padding here as it's handled higher up in the @@ -125,36 +125,38 @@ class _CipherContext(object): return bytes(buf[:n]) def update_into(self, data, buf): - if len(buf) < (len(data) + self._block_size_bytes - 1): + total_data_len = len(data) + if len(buf) < (total_data_len + self._block_size_bytes - 1): raise ValueError( - "buffer must be at least {0} bytes for this " + "buffer must be at least {} bytes for this " "payload".format(len(data) + self._block_size_bytes - 1) ) - buf = self._backend._ffi.cast( - "unsigned char *", self._backend._ffi.from_buffer(buf) - ) + data_processed = 0 + total_out = 0 outlen = self._backend._ffi.new("int *") - res = self._backend._lib.EVP_CipherUpdate( - self._ctx, buf, outlen, - self._backend._ffi.from_buffer(data), len(data) - ) - self._backend.openssl_assert(res != 0) - return outlen[0] + baseoutbuf = self._backend._ffi.from_buffer(buf) + baseinbuf = self._backend._ffi.from_buffer(data) - def finalize(self): - # OpenSSL 1.0.1 on Ubuntu 12.04 (and possibly other distributions) - # appears to have a bug where you must make at least one call to update - # even if you are only using authenticate_additional_data or the - # GCM tag will be wrong. An (empty) call to update resolves this - # and is harmless for all other versions of OpenSSL. - if isinstance(self._mode, modes.GCM): - self.update(b"") + while data_processed != total_data_len: + outbuf = baseoutbuf + total_out + inbuf = baseinbuf + data_processed + inlen = min(self._MAX_CHUNK_SIZE, total_data_len - data_processed) + res = self._backend._lib.EVP_CipherUpdate( + self._ctx, outbuf, outlen, inbuf, inlen + ) + self._backend.openssl_assert(res != 0) + data_processed += inlen + total_out += outlen[0] + + return total_out + + def finalize(self): if ( - self._operation == self._DECRYPT and - isinstance(self._mode, modes.ModeWithAuthenticationTag) and - self.tag is None + self._operation == self._DECRYPT + and isinstance(self._mode, modes.ModeWithAuthenticationTag) + and self.tag is None ): raise ValueError( "Authentication tag must be provided when decrypting." @@ -172,47 +174,44 @@ class _CipherContext(object): self._backend.openssl_assert( errors[0]._lib_reason_match( self._backend._lib.ERR_LIB_EVP, - self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH - ) + self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, + ), + errors=errors, ) raise ValueError( "The length of the provided data is not a multiple of " "the block length." ) - if (isinstance(self._mode, modes.GCM) and - self._operation == self._ENCRYPT): + if ( + isinstance(self._mode, modes.GCM) + and self._operation == self._ENCRYPT + ): tag_buf = self._backend._ffi.new( "unsigned char[]", self._block_size_bytes ) res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - self._ctx, self._backend._lib.EVP_CTRL_AEAD_GET_TAG, - self._block_size_bytes, tag_buf + self._ctx, + self._backend._lib.EVP_CTRL_AEAD_GET_TAG, + self._block_size_bytes, + tag_buf, ) self._backend.openssl_assert(res != 0) self._tag = self._backend._ffi.buffer(tag_buf)[:] res = self._backend._lib.EVP_CIPHER_CTX_cleanup(self._ctx) self._backend.openssl_assert(res == 1) - return self._backend._ffi.buffer(buf)[:outlen[0]] + return self._backend._ffi.buffer(buf)[: outlen[0]] def finalize_with_tag(self, tag): - if ( - self._backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 and - not self._backend._lib.CRYPTOGRAPHY_IS_LIBRESSL - ): - raise NotImplementedError( - "finalize_with_tag requires OpenSSL >= 1.0.2. To use this " - "method please update OpenSSL" - ) if len(tag) < self._mode._min_tag_length: raise ValueError( - "Authentication tag must be {0} bytes or longer.".format( - self._mode._min_tag_length) + "Authentication tag must be {} bytes or longer.".format( + self._mode._min_tag_length + ) ) res = self._backend._lib.EVP_CIPHER_CTX_ctrl( - self._ctx, self._backend._lib.EVP_CTRL_AEAD_SET_TAG, - len(tag), tag + self._ctx, self._backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag ) self._backend.openssl_assert(res != 0) self._tag = tag @@ -221,8 +220,11 @@ class _CipherContext(object): def authenticate_additional_data(self, data): outlen = self._backend._ffi.new("int *") res = self._backend._lib.EVP_CipherUpdate( - self._ctx, self._backend._ffi.NULL, outlen, - self._backend._ffi.from_buffer(data), len(data) + self._ctx, + self._backend._ffi.NULL, + outlen, + self._backend._ffi.from_buffer(data), + len(data), ) self._backend.openssl_assert(res != 0) diff --git a/src/cryptography/hazmat/backends/openssl/cmac.py b/src/cryptography/hazmat/backends/openssl/cmac.py index bc88f3364..195fc230f 100644 --- a/src/cryptography/hazmat/backends/openssl/cmac.py +++ b/src/cryptography/hazmat/backends/openssl/cmac.py @@ -7,18 +7,21 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import ( - InvalidSignature, UnsupportedAlgorithm, _Reasons + InvalidSignature, + UnsupportedAlgorithm, + _Reasons, ) -from cryptography.hazmat.primitives import constant_time, mac +from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.ciphers.modes import CBC -@utils.register_interface(mac.MACContext) class _CMACContext(object): def __init__(self, backend, algorithm, ctx=None): if not backend.cmac_algorithm_supported(algorithm): - raise UnsupportedAlgorithm("This backend does not support CMAC.", - _Reasons.UNSUPPORTED_CIPHER) + raise UnsupportedAlgorithm( + "This backend does not support CMAC.", + _Reasons.UNSUPPORTED_CIPHER, + ) self._backend = backend self._key = algorithm.key @@ -38,8 +41,11 @@ class _CMACContext(object): key_ptr = self._backend._ffi.from_buffer(self._key) res = self._backend._lib.CMAC_Init( - ctx, key_ptr, len(self._key), - evp_cipher, self._backend._ffi.NULL + ctx, + key_ptr, + len(self._key), + evp_cipher, + self._backend._ffi.NULL, ) self._backend.openssl_assert(res == 1) @@ -54,9 +60,7 @@ class _CMACContext(object): def finalize(self): buf = self._backend._ffi.new("unsigned char[]", self._output_length) length = self._backend._ffi.new("size_t *", self._output_length) - res = self._backend._lib.CMAC_Final( - self._ctx, buf, length - ) + res = self._backend._lib.CMAC_Final(self._ctx, buf, length) self._backend.openssl_assert(res == 1) self._ctx = None @@ -68,13 +72,9 @@ class _CMACContext(object): copied_ctx = self._backend._ffi.gc( copied_ctx, self._backend._lib.CMAC_CTX_free ) - res = self._backend._lib.CMAC_CTX_copy( - copied_ctx, self._ctx - ) + res = self._backend._lib.CMAC_CTX_copy(copied_ctx, self._ctx) self._backend.openssl_assert(res == 1) - return _CMACContext( - self._backend, self._algorithm, ctx=copied_ctx - ) + return _CMACContext(self._backend, self._algorithm, ctx=copied_ctx) def verify(self, signature): digest = self.finalize() diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py index 007675d4e..cc9b8c0e3 100644 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -7,23 +7,20 @@ from __future__ import absolute_import, division, print_function import datetime import ipaddress -import asn1crypto.core - import six from cryptography import x509 +from cryptography.hazmat._der import DERReader, INTEGER, NULL, SEQUENCE from cryptography.x509.extensions import _TLS_FEATURE_TYPE_TO_ENUM from cryptography.x509.name import _ASN1_TYPE_TO_ENUM from cryptography.x509.oid import ( - CRLEntryExtensionOID, CertificatePoliciesOID, ExtensionOID, + CRLEntryExtensionOID, + CertificatePoliciesOID, + ExtensionOID, OCSPExtensionOID, ) -class _Integers(asn1crypto.core.SequenceOf): - _child_spec = asn1crypto.core.Integer - - def _obj2txt(backend, obj): # Set to 80 on the recommendation of # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values @@ -67,9 +64,9 @@ def _decode_x509_name(backend, x509_name): for x in range(count): entry = backend._lib.X509_NAME_get_entry(x509_name, x) attribute = _decode_x509_name_entry(backend, entry) - set_id = backend._lib.Cryptography_X509_NAME_ENTRY_set(entry) + set_id = backend._lib.X509_NAME_ENTRY_set(entry) if set_id != prev_set_id: - attributes.append(set([attribute])) + attributes.append({attribute}) else: # is in the same RDN a previous entry attributes[-1].add(attribute) @@ -124,10 +121,10 @@ def _decode_general_name(backend, gn): # netmask. To handle this we convert the netmask to integer, then # find the first 0 bit, which will be the prefix. If another 1 # bit is present after that the netmask is invalid. - base = ipaddress.ip_address(data[:data_len // 2]) - netmask = ipaddress.ip_address(data[data_len // 2:]) + base = ipaddress.ip_address(data[: data_len // 2]) + netmask = ipaddress.ip_address(data[data_len // 2 :]) bits = bin(int(netmask))[2:] - prefix = bits.find('0') + prefix = bits.find("0") # If no 0 bits are found it is a /32 or /128 if prefix == -1: prefix = len(bits) @@ -135,7 +132,7 @@ def _decode_general_name(backend, gn): if "1" in bits[prefix:]: raise ValueError("Invalid netmask") - ip = ipaddress.ip_network(base.exploded + u"/{0}".format(prefix)) + ip = ipaddress.ip_network(base.exploded + u"/{}".format(prefix)) else: ip = ipaddress.ip_address(data) @@ -160,10 +157,10 @@ def _decode_general_name(backend, gn): else: # x400Address or ediPartyName raise x509.UnsupportedGeneralNameType( - "{0} is not a supported type".format( + "{} is not a supported type".format( x509._GENERAL_NAMES.get(gn.type, gn.type) ), - gn.type + gn.type, ) @@ -184,48 +181,57 @@ def _decode_delta_crl_indicator(backend, ext): class _X509ExtensionParser(object): - def __init__(self, ext_count, get_ext, handlers): + def __init__(self, backend, ext_count, get_ext, handlers): self.ext_count = ext_count self.get_ext = get_ext self.handlers = handlers + self._backend = backend - def parse(self, backend, x509_obj): + def parse(self, x509_obj): extensions = [] seen_oids = set() - for i in range(self.ext_count(backend, x509_obj)): - ext = self.get_ext(backend, x509_obj, i) - backend.openssl_assert(ext != backend._ffi.NULL) - crit = backend._lib.X509_EXTENSION_get_critical(ext) + for i in range(self.ext_count(x509_obj)): + ext = self.get_ext(x509_obj, i) + self._backend.openssl_assert(ext != self._backend._ffi.NULL) + crit = self._backend._lib.X509_EXTENSION_get_critical(ext) critical = crit == 1 oid = x509.ObjectIdentifier( - _obj2txt(backend, backend._lib.X509_EXTENSION_get_object(ext)) + _obj2txt( + self._backend, + self._backend._lib.X509_EXTENSION_get_object(ext), + ) ) if oid in seen_oids: raise x509.DuplicateExtension( - "Duplicate {0} extension found".format(oid), oid + "Duplicate {} extension found".format(oid), oid ) # These OIDs are only supported in OpenSSL 1.1.0+ but we want # to support them in all versions of OpenSSL so we decode them # ourselves. if oid == ExtensionOID.TLS_FEATURE: - data = backend._lib.X509_EXTENSION_get_data(ext) - parsed = _Integers.load(_asn1_string_to_bytes(backend, data)) + # The extension contents are a SEQUENCE OF INTEGERs. + data = self._backend._lib.X509_EXTENSION_get_data(ext) + data_bytes = _asn1_string_to_bytes(self._backend, data) + features = DERReader(data_bytes).read_single_element(SEQUENCE) + parsed = [] + while not features.is_empty(): + parsed.append(features.read_element(INTEGER).as_integer()) + # Map the features to their enum value. value = x509.TLSFeature( - [_TLS_FEATURE_TYPE_TO_ENUM[x.native] for x in parsed] + [_TLS_FEATURE_TYPE_TO_ENUM[x] for x in parsed] ) extensions.append(x509.Extension(oid, critical, value)) seen_oids.add(oid) continue elif oid == ExtensionOID.PRECERT_POISON: - data = backend._lib.X509_EXTENSION_get_data(ext) - parsed = asn1crypto.core.Null.load( - _asn1_string_to_bytes(backend, data) + data = self._backend._lib.X509_EXTENSION_get_data(ext) + # The contents of the extension must be an ASN.1 NULL. + reader = DERReader(_asn1_string_to_bytes(self._backend, data)) + reader.read_single_element(NULL).check_empty() + extensions.append( + x509.Extension(oid, critical, x509.PrecertPoison()) ) - assert parsed == asn1crypto.core.Null() - extensions.append(x509.Extension( - oid, critical, x509.PrecertPoison() - )) seen_oids.add(oid) continue @@ -233,23 +239,21 @@ class _X509ExtensionParser(object): handler = self.handlers[oid] except KeyError: # 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)[:] + data = self._backend._lib.X509_EXTENSION_get_data(ext) + self._backend.openssl_assert(data != self._backend._ffi.NULL) + der = self._backend._ffi.buffer(data.data, data.length)[:] unrecognized = x509.UnrecognizedExtension(oid, der) - extensions.append( - x509.Extension(oid, critical, unrecognized) - ) + extensions.append(x509.Extension(oid, critical, unrecognized)) else: - ext_data = backend._lib.X509V3_EXT_d2i(ext) - if ext_data == backend._ffi.NULL: - backend._consume_errors() + ext_data = self._backend._lib.X509V3_EXT_d2i(ext) + if ext_data == self._backend._ffi.NULL: + self._backend._consume_errors() raise ValueError( - "The {0} extension is invalid and can't be " + "The {} extension is invalid and can't be " "parsed".format(oid) ) - value = handler(backend, ext_data) + value = handler(self._backend, ext_data) extensions.append(x509.Extension(oid, critical, value)) seen_oids.add(oid) @@ -271,16 +275,12 @@ def _decode_certificate_policies(backend, cp): qnum = backend._lib.sk_POLICYQUALINFO_num(pi.qualifiers) qualifiers = [] for j in range(qnum): - pqi = backend._lib.sk_POLICYQUALINFO_value( - pi.qualifiers, j - ) - pqualid = x509.ObjectIdentifier( - _obj2txt(backend, pqi.pqualid) - ) + pqi = backend._lib.sk_POLICYQUALINFO_value(pi.qualifiers, j) + pqualid = x509.ObjectIdentifier(_obj2txt(backend, pqi.pqualid)) if pqualid == CertificatePoliciesOID.CPS_QUALIFIER: cpsuri = backend._ffi.buffer( pqi.d.cpsuri.data, pqi.d.cpsuri.length - )[:].decode('ascii') + )[:].decode("ascii") qualifiers.append(cpsuri) else: assert pqualid == CertificatePoliciesOID.CPS_USER_NOTICE @@ -289,9 +289,7 @@ def _decode_certificate_policies(backend, cp): ) qualifiers.append(user_notice) - certificate_policies.append( - x509.PolicyInformation(oid, qualifiers) - ) + certificate_policies.append(x509.PolicyInformation(oid, qualifiers)) return x509.CertificatePolicies(certificate_policies) @@ -304,13 +302,9 @@ def _decode_user_notice(backend, un): explicit_text = _asn1_string_to_utf8(backend, un.exptext) if un.noticeref != backend._ffi.NULL: - organization = _asn1_string_to_utf8( - backend, un.noticeref.organization - ) + organization = _asn1_string_to_utf8(backend, un.noticeref.organization) - num = backend._lib.sk_ASN1_INTEGER_num( - un.noticeref.noticenos - ) + num = backend._lib.sk_ASN1_INTEGER_num(un.noticeref.noticenos) notice_numbers = [] for i in range(num): asn1_int = backend._lib.sk_ASN1_INTEGER_value( @@ -319,9 +313,7 @@ def _decode_user_notice(backend, un): notice_num = _asn1_integer_to_int(backend, asn1_int) notice_numbers.append(notice_num) - notice_reference = x509.NoticeReference( - organization, notice_numbers - ) + notice_reference = x509.NoticeReference(organization, notice_numbers) return x509.UserNotice(notice_reference, explicit_text) @@ -364,9 +356,7 @@ def _decode_authority_key_identifier(backend, akid): )[:] if akid.issuer != backend._ffi.NULL: - authority_cert_issuer = _decode_general_names( - backend, akid.issuer - ) + authority_cert_issuer = _decode_general_names(backend, akid.issuer) authority_cert_serial_number = _asn1_integer_to_int_or_none( backend, akid.serial @@ -377,22 +367,40 @@ def _decode_authority_key_identifier(backend, akid): ) -def _decode_authority_information_access(backend, aia): - aia = backend._ffi.cast("Cryptography_STACK_OF_ACCESS_DESCRIPTION *", aia) - aia = backend._ffi.gc(aia, backend._lib.sk_ACCESS_DESCRIPTION_free) - num = backend._lib.sk_ACCESS_DESCRIPTION_num(aia) +def _decode_information_access(backend, ia): + ia = backend._ffi.cast("Cryptography_STACK_OF_ACCESS_DESCRIPTION *", ia) + ia = backend._ffi.gc( + ia, + lambda x: backend._lib.sk_ACCESS_DESCRIPTION_pop_free( + x, + backend._ffi.addressof( + backend._lib._original_lib, "ACCESS_DESCRIPTION_free" + ), + ), + ) + num = backend._lib.sk_ACCESS_DESCRIPTION_num(ia) access_descriptions = [] for i in range(num): - ad = backend._lib.sk_ACCESS_DESCRIPTION_value(aia, i) + ad = backend._lib.sk_ACCESS_DESCRIPTION_value(ia, i) backend.openssl_assert(ad.method != backend._ffi.NULL) oid = x509.ObjectIdentifier(_obj2txt(backend, ad.method)) backend.openssl_assert(ad.location != backend._ffi.NULL) gn = _decode_general_name(backend, ad.location) access_descriptions.append(x509.AccessDescription(oid, gn)) + return access_descriptions + + +def _decode_authority_information_access(backend, aia): + access_descriptions = _decode_information_access(backend, aia) return x509.AuthorityInformationAccess(access_descriptions) +def _decode_subject_information_access(backend, aia): + access_descriptions = _decode_information_access(backend, aia) + return x509.SubjectInformationAccess(access_descriptions) + + def _decode_key_usage(backend, bit_string): bit_string = backend._ffi.cast("ASN1_BIT_STRING *", bit_string) bit_string = backend._ffi.gc(bit_string, backend._lib.ASN1_BIT_STRING_free) @@ -415,7 +423,7 @@ def _decode_key_usage(backend, bit_string): key_cert_sign, crl_sign, encipher_only, - decipher_only + decipher_only, ) @@ -483,8 +491,13 @@ def _decode_issuing_dist_point(backend, idp): only_some_reasons = None return x509.IssuingDistributionPoint( - full_name, relative_name, only_user, only_ca, only_some_reasons, - indirect_crl, only_attr + full_name, + relative_name, + only_user, + only_ca, + only_some_reasons, + indirect_crl, + only_attr, ) @@ -605,13 +618,9 @@ def _decode_distpoint(backend, distpoint): rnum = backend._lib.sk_X509_NAME_ENTRY_num(rns) attributes = set() for i in range(rnum): - rn = backend._lib.sk_X509_NAME_ENTRY_value( - rns, i - ) + rn = backend._lib.sk_X509_NAME_ENTRY_value(rns, i) backend.openssl_assert(rn != backend._ffi.NULL) - attributes.add( - _decode_x509_name_entry(backend, rn) - ) + attributes.add(_decode_x509_name_entry(backend, rn)) relative_name = x509.RelativeDistinguishedName(attributes) @@ -635,10 +644,11 @@ def _decode_inhibit_any_policy(backend, asn1_int): return x509.InhibitAnyPolicy(skip_certs) -def _decode_precert_signed_certificate_timestamps(backend, asn1_scts): +def _decode_scts(backend, asn1_scts): from cryptography.hazmat.backends.openssl.x509 import ( - _SignedCertificateTimestamp + _SignedCertificateTimestamp, ) + asn1_scts = backend._ffi.cast("Cryptography_STACK_OF_SCT *", asn1_scts) asn1_scts = backend._ffi.gc(asn1_scts, backend._lib.SCT_LIST_free) @@ -647,7 +657,17 @@ def _decode_precert_signed_certificate_timestamps(backend, asn1_scts): sct = backend._lib.sk_SCT_value(asn1_scts, i) scts.append(_SignedCertificateTimestamp(backend, asn1_scts, sct)) - return x509.PrecertificateSignedCertificateTimestamps(scts) + return scts + + +def _decode_precert_signed_certificate_timestamps(backend, asn1_scts): + return x509.PrecertificateSignedCertificateTimestamps( + _decode_scts(backend, asn1_scts) + ) + + +def _decode_signed_certificate_timestamps(backend, asn1_scts): + return x509.SignedCertificateTimestamps(_decode_scts(backend, asn1_scts)) # CRLReason ::= ENUMERATED { @@ -686,7 +706,7 @@ _CRL_ENTRY_REASON_ENUM_TO_CODE = { x509.ReasonFlags.certificate_hold: 6, x509.ReasonFlags.remove_from_crl: 8, x509.ReasonFlags.privilege_withdrawn: 9, - x509.ReasonFlags.aa_compromise: 10 + x509.ReasonFlags.aa_compromise: 10, } @@ -698,13 +718,11 @@ def _decode_crl_reason(backend, enum): try: return x509.CRLReason(_CRL_ENTRY_REASON_CODE_TO_ENUM[code]) except KeyError: - raise ValueError("Unsupported reason code: {0}".format(code)) + raise ValueError("Unsupported reason code: {}".format(code)) def _decode_invalidity_date(backend, inv_date): - generalized_time = backend._ffi.cast( - "ASN1_GENERALIZEDTIME *", inv_date - ) + generalized_time = backend._ffi.cast("ASN1_GENERALIZEDTIME *", inv_date) generalized_time = backend._ffi.gc( generalized_time, backend._lib.ASN1_GENERALIZEDTIME_free ) @@ -758,14 +776,14 @@ def _asn1_string_to_utf8(backend, asn1_string): res = backend._lib.ASN1_STRING_to_UTF8(buf, asn1_string) if res == -1: raise ValueError( - "Unsupported ASN1 string type. Type: {0}".format(asn1_string.type) + "Unsupported ASN1 string type. Type: {}".format(asn1_string.type) ) backend.openssl_assert(buf[0] != backend._ffi.NULL) buf = backend._ffi.gc( buf, lambda buffer: backend._lib.OPENSSL_free(buffer[0]) ) - return backend._ffi.buffer(buf[0], res)[:].decode('utf8') + return backend._ffi.buffer(buf[0], res)[:].decode("utf8") def _parse_asn1_time(backend, asn1_time): @@ -799,7 +817,7 @@ def _decode_nonce(backend, nonce): return x509.OCSPNonce(_asn1_string_to_bytes(backend, nonce)) -_EXTENSION_HANDLERS_NO_SCT = { +_EXTENSION_HANDLERS_BASE = { ExtensionOID.BASIC_CONSTRAINTS: _decode_basic_constraints, ExtensionOID.SUBJECT_KEY_IDENTIFIER: _decode_subject_key_identifier, ExtensionOID.KEY_USAGE: _decode_key_usage, @@ -809,6 +827,9 @@ _EXTENSION_HANDLERS_NO_SCT = { ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( _decode_authority_information_access ), + ExtensionOID.SUBJECT_INFORMATION_ACCESS: ( + _decode_subject_information_access + ), ExtensionOID.CERTIFICATE_POLICIES: _decode_certificate_policies, ExtensionOID.CRL_DISTRIBUTION_POINTS: _decode_crl_distribution_points, ExtensionOID.FRESHEST_CRL: _decode_freshest_crl, @@ -818,11 +839,11 @@ _EXTENSION_HANDLERS_NO_SCT = { ExtensionOID.NAME_CONSTRAINTS: _decode_name_constraints, ExtensionOID.POLICY_CONSTRAINTS: _decode_policy_constraints, } -_EXTENSION_HANDLERS = _EXTENSION_HANDLERS_NO_SCT.copy() -_EXTENSION_HANDLERS[ - ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS -] = _decode_precert_signed_certificate_timestamps - +_EXTENSION_HANDLERS_SCT = { + ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: ( + _decode_precert_signed_certificate_timestamps + ) +} _REVOKED_EXTENSION_HANDLERS = { CRLEntryExtensionOID.CRL_REASON: _decode_crl_reason, @@ -839,6 +860,7 @@ _CRL_EXTENSION_HANDLERS = { _decode_authority_information_access ), ExtensionOID.ISSUING_DISTRIBUTION_POINT: _decode_issuing_dist_point, + ExtensionOID.FRESHEST_CRL: _decode_freshest_crl, } _OCSP_REQ_EXTENSION_HANDLERS = { @@ -849,44 +871,8 @@ _OCSP_BASICRESP_EXTENSION_HANDLERS = { OCSPExtensionOID.NONCE: _decode_nonce, } -_CERTIFICATE_EXTENSION_PARSER_NO_SCT = _X509ExtensionParser( - ext_count=lambda backend, x: backend._lib.X509_get_ext_count(x), - get_ext=lambda backend, x, i: backend._lib.X509_get_ext(x, i), - handlers=_EXTENSION_HANDLERS_NO_SCT -) - -_CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser( - ext_count=lambda backend, x: backend._lib.X509_get_ext_count(x), - get_ext=lambda backend, x, i: backend._lib.X509_get_ext(x, i), - handlers=_EXTENSION_HANDLERS -) - -_CSR_EXTENSION_PARSER = _X509ExtensionParser( - ext_count=lambda backend, x: backend._lib.sk_X509_EXTENSION_num(x), - get_ext=lambda backend, x, i: backend._lib.sk_X509_EXTENSION_value(x, i), - handlers=_EXTENSION_HANDLERS -) - -_REVOKED_CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser( - ext_count=lambda backend, x: backend._lib.X509_REVOKED_get_ext_count(x), - get_ext=lambda backend, x, i: backend._lib.X509_REVOKED_get_ext(x, i), - handlers=_REVOKED_EXTENSION_HANDLERS, -) - -_CRL_EXTENSION_PARSER = _X509ExtensionParser( - ext_count=lambda backend, x: backend._lib.X509_CRL_get_ext_count(x), - get_ext=lambda backend, x, i: backend._lib.X509_CRL_get_ext(x, i), - handlers=_CRL_EXTENSION_HANDLERS, -) - -_OCSP_REQ_EXT_PARSER = _X509ExtensionParser( - ext_count=lambda backend, x: backend._lib.OCSP_REQUEST_get_ext_count(x), - get_ext=lambda backend, x, i: backend._lib.OCSP_REQUEST_get_ext(x, i), - handlers=_OCSP_REQ_EXTENSION_HANDLERS, -) - -_OCSP_BASICRESP_EXT_PARSER = _X509ExtensionParser( - ext_count=lambda backend, x: backend._lib.OCSP_BASICRESP_get_ext_count(x), - get_ext=lambda backend, x, i: backend._lib.OCSP_BASICRESP_get_ext(x, i), - handlers=_OCSP_BASICRESP_EXTENSION_HANDLERS, -) +_OCSP_SINGLERESP_EXTENSION_HANDLERS_SCT = { + ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS: ( + _decode_signed_certificate_timestamps + ) +} diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py index 095f06233..2862676c6 100644 --- a/src/cryptography/hazmat/backends/openssl/dh.py +++ b/src/cryptography/hazmat/backends/openssl/dh.py @@ -17,8 +17,8 @@ def _dh_params_dup(dh_cdata, backend): param_cdata = lib.DHparams_dup(dh_cdata) backend.openssl_assert(param_cdata != ffi.NULL) param_cdata = ffi.gc(param_cdata, lib.DH_free) - if lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_102: - # In OpenSSL versions < 1.0.2 or libressl DHparams_dup don't copy q + if lib.CRYPTOGRAPHY_IS_LIBRESSL: + # In libressl DHparams_dup don't copy q q = ffi.new("BIGNUM **") lib.DH_get0_pqg(dh_cdata, ffi.NULL, q, ffi.NULL) q_dup = lib.BN_dup(q[0]) @@ -53,7 +53,7 @@ class _DHParameters(object): return dh.DHParameterNumbers( p=self._backend._bn_to_int(p[0]), g=self._backend._bn_to_int(g[0]), - q=q_val + q=q_val, ) def generate_private_key(self): @@ -61,44 +61,27 @@ class _DHParameters(object): def parameter_bytes(self, encoding, format): if format is not serialization.ParameterFormat.PKCS3: - raise ValueError( - "Only PKCS3 serialization is supported" - ) + raise ValueError("Only PKCS3 serialization is supported") if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX: q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg(self._dh_cdata, - self._backend._ffi.NULL, - q, - self._backend._ffi.NULL) + self._backend._lib.DH_get0_pqg( + self._dh_cdata, + self._backend._ffi.NULL, + q, + self._backend._ffi.NULL, + ) if q[0] != self._backend._ffi.NULL: raise UnsupportedAlgorithm( "DH X9.42 serialization is not supported", - _Reasons.UNSUPPORTED_SERIALIZATION) + _Reasons.UNSUPPORTED_SERIALIZATION, + ) - return self._backend._parameter_bytes( - encoding, - format, - self._dh_cdata - ) - - -def _handle_dh_compute_key_error(errors, backend): - lib = backend._lib - - backend.openssl_assert( - errors[0]._lib_reason_match( - lib.ERR_LIB_DH, lib.DH_R_INVALID_PUBKEY - ) - ) - - raise ValueError("Public key value is invalid for this exchange.") + return self._backend._parameter_bytes(encoding, format, self._dh_cdata) def _get_dh_num_bits(backend, dh_cdata): p = backend._ffi.new("BIGNUM **") - backend._lib.DH_get0_pqg(dh_cdata, p, - backend._ffi.NULL, - backend._ffi.NULL) + backend._lib.DH_get0_pqg(dh_cdata, p, backend._ffi.NULL, backend._ffi.NULL) backend.openssl_assert(p[0] != backend._ffi.NULL) return backend._lib.BN_num_bits(p[0]) @@ -136,29 +119,32 @@ class _DHPrivateKey(object): parameter_numbers=dh.DHParameterNumbers( p=self._backend._bn_to_int(p[0]), g=self._backend._bn_to_int(g[0]), - q=q_val + q=q_val, ), - y=self._backend._bn_to_int(pub_key[0]) + y=self._backend._bn_to_int(pub_key[0]), ), - x=self._backend._bn_to_int(priv_key[0]) + x=self._backend._bn_to_int(priv_key[0]), ) def exchange(self, peer_public_key): buf = self._backend._ffi.new("unsigned char[]", self._key_size_bytes) pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key(peer_public_key._dh_cdata, pub_key, - self._backend._ffi.NULL) + self._backend._lib.DH_get0_key( + peer_public_key._dh_cdata, pub_key, self._backend._ffi.NULL + ) self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) res = self._backend._lib.DH_compute_key( - buf, - pub_key[0], - self._dh_cdata + buf, pub_key[0], self._dh_cdata ) if res == -1: - errors = self._backend._consume_errors() - return _handle_dh_compute_key_error(errors, self._backend) + errors_with_text = self._backend._consume_errors_with_text() + raise ValueError( + "Error computing shared key. Public key is likely invalid " + "for this exchange.", + errors_with_text, + ) else: self._backend.openssl_assert(res >= 1) @@ -173,15 +159,16 @@ class _DHPrivateKey(object): def public_key(self): dh_cdata = _dh_params_dup(self._dh_cdata, self._backend) pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key(self._dh_cdata, - pub_key, self._backend._ffi.NULL) + self._backend._lib.DH_get0_key( + self._dh_cdata, pub_key, self._backend._ffi.NULL + ) self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) pub_key_dup = self._backend._lib.BN_dup(pub_key[0]) self._backend.openssl_assert(pub_key_dup != self._backend._ffi.NULL) - res = self._backend._lib.DH_set0_key(dh_cdata, - pub_key_dup, - self._backend._ffi.NULL) + res = self._backend._lib.DH_set0_key( + dh_cdata, pub_key_dup, self._backend._ffi.NULL + ) self._backend.openssl_assert(res == 1) evp_pkey = self._backend._dh_cdata_to_evp_pkey(dh_cdata) return _DHPublicKey(self._backend, dh_cdata, evp_pkey) @@ -196,21 +183,25 @@ class _DHPrivateKey(object): ) if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX: q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg(self._dh_cdata, - self._backend._ffi.NULL, - q, - self._backend._ffi.NULL) + self._backend._lib.DH_get0_pqg( + self._dh_cdata, + self._backend._ffi.NULL, + q, + self._backend._ffi.NULL, + ) if q[0] != self._backend._ffi.NULL: raise UnsupportedAlgorithm( "DH X9.42 serialization is not supported", - _Reasons.UNSUPPORTED_SERIALIZATION) + _Reasons.UNSUPPORTED_SERIALIZATION, + ) return self._backend._private_key_bytes( encoding, format, encryption_algorithm, + self, self._evp_pkey, - self._dh_cdata + self._dh_cdata, ) @@ -238,16 +229,17 @@ class _DHPublicKey(object): else: q_val = self._backend._bn_to_int(q[0]) pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key(self._dh_cdata, - pub_key, self._backend._ffi.NULL) + self._backend._lib.DH_get0_key( + self._dh_cdata, pub_key, self._backend._ffi.NULL + ) self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) return dh.DHPublicNumbers( parameter_numbers=dh.DHParameterNumbers( p=self._backend._bn_to_int(p[0]), g=self._backend._bn_to_int(g[0]), - q=q_val + q=q_val, ), - y=self._backend._bn_to_int(pub_key[0]) + y=self._backend._bn_to_int(pub_key[0]), ) def parameters(self): @@ -262,19 +254,18 @@ class _DHPublicKey(object): if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX: q = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_pqg(self._dh_cdata, - self._backend._ffi.NULL, - q, - self._backend._ffi.NULL) + self._backend._lib.DH_get0_pqg( + self._dh_cdata, + self._backend._ffi.NULL, + q, + self._backend._ffi.NULL, + ) if q[0] != self._backend._ffi.NULL: raise UnsupportedAlgorithm( "DH X9.42 serialization is not supported", - _Reasons.UNSUPPORTED_SERIALIZATION) + _Reasons.UNSUPPORTED_SERIALIZATION, + ) return self._backend._public_key_bytes( - encoding, - format, - self, - self._evp_pkey, - None + encoding, format, self, self._evp_pkey, None ) diff --git a/src/cryptography/hazmat/backends/openssl/dsa.py b/src/cryptography/hazmat/backends/openssl/dsa.py index de61f0894..0c5faba18 100644 --- a/src/cryptography/hazmat/backends/openssl/dsa.py +++ b/src/cryptography/hazmat/backends/openssl/dsa.py @@ -7,12 +7,15 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends.openssl.utils import ( - _calculate_digest_and_algorithm, _check_not_prehashed, - _warn_sign_verify_deprecated + _calculate_digest_and_algorithm, + _check_not_prehashed, + _warn_sign_verify_deprecated, ) -from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ( - AsymmetricSignatureContext, AsymmetricVerificationContext, dsa + AsymmetricSignatureContext, + AsymmetricVerificationContext, + dsa, ) @@ -29,7 +32,7 @@ def _dsa_sig_sign(backend, private_key, data): backend.openssl_assert(res == 1) backend.openssl_assert(buflen[0]) - return backend._ffi.buffer(sig_buf)[:buflen[0]] + return backend._ffi.buffer(sig_buf)[: buflen[0]] def _dsa_sig_verify(backend, public_key, signature, data): @@ -98,7 +101,7 @@ class _DSAParameters(object): return dsa.DSAParameterNumbers( p=self._backend._bn_to_int(p[0]), q=self._backend._bn_to_int(q[0]), - g=self._backend._bn_to_int(g[0]) + g=self._backend._bn_to_int(g[0]), ) def generate_private_key(self): @@ -144,11 +147,11 @@ class _DSAPrivateKey(object): parameter_numbers=dsa.DSAParameterNumbers( p=self._backend._bn_to_int(p[0]), q=self._backend._bn_to_int(q[0]), - g=self._backend._bn_to_int(g[0]) + g=self._backend._bn_to_int(g[0]), ), - y=self._backend._bn_to_int(pub_key[0]) + y=self._backend._bn_to_int(pub_key[0]), ), - x=self._backend._bn_to_int(priv_key[0]) + x=self._backend._bn_to_int(priv_key[0]), ) def public_key(self): @@ -183,8 +186,9 @@ class _DSAPrivateKey(object): encoding, format, encryption_algorithm, + self, self._evp_pkey, - self._dsa_cdata + self._dsa_cdata, ) def sign(self, data, algorithm): @@ -235,9 +239,9 @@ class _DSAPublicKey(object): parameter_numbers=dsa.DSAParameterNumbers( p=self._backend._bn_to_int(p[0]), q=self._backend._bn_to_int(q[0]), - g=self._backend._bn_to_int(g[0]) + g=self._backend._bn_to_int(g[0]), ), - y=self._backend._bn_to_int(pub_key[0]) + y=self._backend._bn_to_int(pub_key[0]), ) def parameters(self): @@ -248,17 +252,8 @@ class _DSAPublicKey(object): return _DSAParameters(self._backend, dsa_cdata) def public_bytes(self, encoding, format): - if format is serialization.PublicFormat.PKCS1: - raise ValueError( - "DSA public keys do not support PKCS1 serialization" - ) - return self._backend._public_key_bytes( - encoding, - format, - self, - self._evp_pkey, - None + encoding, format, self, self._evp_pkey, None ) def verify(self, signature, data, algorithm): diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py index a8d69bdf9..05d32baba 100644 --- a/src/cryptography/hazmat/backends/openssl/ec.py +++ b/src/cryptography/hazmat/backends/openssl/ec.py @@ -6,15 +6,20 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import ( - InvalidSignature, UnsupportedAlgorithm, _Reasons + InvalidSignature, + UnsupportedAlgorithm, + _Reasons, ) from cryptography.hazmat.backends.openssl.utils import ( - _calculate_digest_and_algorithm, _check_not_prehashed, - _warn_sign_verify_deprecated + _calculate_digest_and_algorithm, + _check_not_prehashed, + _warn_sign_verify_deprecated, ) from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( - AsymmetricSignatureContext, AsymmetricVerificationContext, ec + AsymmetricSignatureContext, + AsymmetricVerificationContext, + ec, ) @@ -22,7 +27,8 @@ def _check_signature_algorithm(signature_algorithm): if not isinstance(signature_algorithm, ec.ECDSA): raise UnsupportedAlgorithm( "Unsupported elliptic curve signature algorithm.", - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) def _ec_key_curve_sn(backend, ec_key): @@ -34,14 +40,24 @@ def _ec_key_curve_sn(backend, ec_key): # an error for now. if nid == backend._lib.NID_undef: raise NotImplementedError( - "ECDSA certificates with unnamed curves are unsupported " - "at this time" + "ECDSA keys with unnamed curves are unsupported at this time" + ) + + # This is like the above check, but it also catches the case where you + # explicitly encoded a curve with the same parameters as a named curve. + # Don't do that. + if ( + not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + and backend._lib.EC_GROUP_get_asn1_flag(group) == 0 + ): + raise NotImplementedError( + "ECDSA keys with unnamed curves are unsupported at this time" ) curve_name = backend._lib.OBJ_nid2sn(nid) backend.openssl_assert(curve_name != backend._ffi.NULL) - sn = backend._ffi.string(curve_name).decode('ascii') + sn = backend._ffi.string(curve_name).decode("ascii") return sn @@ -62,8 +78,8 @@ def _sn_to_elliptic_curve(backend, sn): return ec._CURVE_TYPES[sn]() except KeyError: raise UnsupportedAlgorithm( - "{0} is not a supported elliptic curve".format(sn), - _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + "{} is not a supported elliptic curve".format(sn), + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE, ) @@ -77,7 +93,7 @@ def _ecdsa_sig_sign(backend, private_key, data): 0, data, len(data), sigbuf, siglen_ptr, private_key._ec_key ) backend.openssl_assert(res == 1) - return backend._ffi.buffer(sigbuf)[:siglen_ptr[0]] + return backend._ffi.buffer(sigbuf)[: siglen_ptr[0]] def _ecdsa_sig_verify(backend, public_key, signature, data): @@ -127,12 +143,12 @@ class _ECDSAVerificationContext(object): class _EllipticCurvePrivateKey(object): def __init__(self, backend, ec_key_cdata, evp_pkey): self._backend = backend - _mark_asn1_named_ec_curve(backend, ec_key_cdata) self._ec_key = ec_key_cdata self._evp_pkey = evp_pkey sn = _ec_key_curve_sn(backend, ec_key_cdata) self._curve = _sn_to_elliptic_curve(backend, sn) + _mark_asn1_named_ec_curve(backend, ec_key_cdata) curve = utils.read_only_property("_curve") @@ -156,7 +172,7 @@ class _EllipticCurvePrivateKey(object): ): raise UnsupportedAlgorithm( "This backend does not support the ECDH algorithm.", - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) if peer_public_key.curve.name != self.curve.name: @@ -183,12 +199,7 @@ class _EllipticCurvePrivateKey(object): self._backend.openssl_assert(group != self._backend._ffi.NULL) curve_nid = self._backend._lib.EC_GROUP_get_curve_name(group) - - public_ec_key = self._backend._lib.EC_KEY_new_by_curve_name(curve_nid) - self._backend.openssl_assert(public_ec_key != self._backend._ffi.NULL) - public_ec_key = self._backend._ffi.gc( - public_ec_key, self._backend._lib.EC_KEY_free - ) + public_ec_key = self._backend._ec_key_new_by_curve_nid(curve_nid) point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) self._backend.openssl_assert(point != self._backend._ffi.NULL) @@ -205,7 +216,7 @@ class _EllipticCurvePrivateKey(object): private_value = self._backend._bn_to_int(bn) return ec.EllipticCurvePrivateNumbers( private_value=private_value, - public_numbers=self.public_key().public_numbers() + public_numbers=self.public_key().public_numbers(), ) def private_bytes(self, encoding, format, encryption_algorithm): @@ -213,8 +224,9 @@ class _EllipticCurvePrivateKey(object): encoding, format, encryption_algorithm, + self, self._evp_pkey, - self._ec_key + self._ec_key, ) def sign(self, data, signature_algorithm): @@ -229,12 +241,12 @@ class _EllipticCurvePrivateKey(object): class _EllipticCurvePublicKey(object): def __init__(self, backend, ec_key_cdata, evp_pkey): self._backend = backend - _mark_asn1_named_ec_curve(backend, ec_key_cdata) self._ec_key = ec_key_cdata self._evp_pkey = evp_pkey sn = _ec_key_curve_sn(backend, ec_key_cdata) self._curve = _sn_to_elliptic_curve(backend, sn) + _mark_asn1_named_ec_curve(backend, ec_key_cdata) curve = utils.read_only_property("_curve") @@ -253,8 +265,8 @@ class _EllipticCurvePublicKey(object): ) def public_numbers(self): - get_func, group = ( - self._backend._ec_key_determine_group_get_func(self._ec_key) + get_func, group = self._backend._ec_key_determine_group_get_func( + self._ec_key ) point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) self._backend.openssl_assert(point != self._backend._ffi.NULL) @@ -269,11 +281,7 @@ class _EllipticCurvePublicKey(object): x = self._backend._bn_to_int(bn_x) y = self._backend._bn_to_int(bn_y) - return ec.EllipticCurvePublicNumbers( - x=x, - y=y, - curve=self._curve - ) + return ec.EllipticCurvePublicNumbers(x=x, y=y, curve=self._curve) def _encode_point(self, format): if format is serialization.PublicFormat.CompressedPoint: @@ -300,22 +308,15 @@ class _EllipticCurvePublicKey(object): return self._backend._ffi.buffer(buf)[:] def public_bytes(self, encoding, format): - if format is serialization.PublicFormat.PKCS1: - raise ValueError( - "EC public keys do not support PKCS1 serialization" - ) if ( - encoding is serialization.Encoding.X962 or - format is serialization.PublicFormat.CompressedPoint or - format is serialization.PublicFormat.UncompressedPoint + encoding is serialization.Encoding.X962 + or format is serialization.PublicFormat.CompressedPoint + or format is serialization.PublicFormat.UncompressedPoint ): - if ( - encoding is not serialization.Encoding.X962 or - format not in ( - serialization.PublicFormat.CompressedPoint, - serialization.PublicFormat.UncompressedPoint - ) + if encoding is not serialization.Encoding.X962 or format not in ( + serialization.PublicFormat.CompressedPoint, + serialization.PublicFormat.UncompressedPoint, ): raise ValueError( "X962 encoding must be used with CompressedPoint or " @@ -325,11 +326,7 @@ class _EllipticCurvePublicKey(object): return self._encode_point(format) else: return self._backend._public_key_bytes( - encoding, - format, - self, - self._evp_pkey, - None + encoding, format, self, self._evp_pkey, None ) def verify(self, signature, data, signature_algorithm): diff --git a/src/cryptography/hazmat/backends/openssl/ed25519.py b/src/cryptography/hazmat/backends/openssl/ed25519.py new file mode 100644 index 000000000..13bec3af1 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/ed25519.py @@ -0,0 +1,145 @@ +# 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 + +from cryptography import exceptions, utils +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed25519 import ( + Ed25519PrivateKey, + Ed25519PublicKey, + _ED25519_KEY_SIZE, + _ED25519_SIG_SIZE, +) + + +@utils.register_interface(Ed25519PublicKey) +class _Ed25519PublicKey(object): + def __init__(self, backend, evp_pkey): + self._backend = backend + self._evp_pkey = evp_pkey + + def public_bytes(self, encoding, format): + if ( + encoding is serialization.Encoding.Raw + or format is serialization.PublicFormat.Raw + ): + if ( + encoding is not serialization.Encoding.Raw + or format is not serialization.PublicFormat.Raw + ): + raise ValueError( + "When using Raw both encoding and format must be Raw" + ) + + return self._raw_public_bytes() + + return self._backend._public_key_bytes( + encoding, format, self, self._evp_pkey, None + ) + + def _raw_public_bytes(self): + buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE) + buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE) + res = self._backend._lib.EVP_PKEY_get_raw_public_key( + self._evp_pkey, buf, buflen + ) + self._backend.openssl_assert(res == 1) + self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE) + return self._backend._ffi.buffer(buf, _ED25519_KEY_SIZE)[:] + + def verify(self, signature, data): + evp_md_ctx = self._backend._lib.EVP_MD_CTX_new() + self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) + evp_md_ctx = self._backend._ffi.gc( + evp_md_ctx, self._backend._lib.EVP_MD_CTX_free + ) + res = self._backend._lib.EVP_DigestVerifyInit( + evp_md_ctx, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._evp_pkey, + ) + self._backend.openssl_assert(res == 1) + res = self._backend._lib.EVP_DigestVerify( + evp_md_ctx, signature, len(signature), data, len(data) + ) + if res != 1: + self._backend._consume_errors() + raise exceptions.InvalidSignature + + +@utils.register_interface(Ed25519PrivateKey) +class _Ed25519PrivateKey(object): + def __init__(self, backend, evp_pkey): + self._backend = backend + self._evp_pkey = evp_pkey + + def public_key(self): + buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE) + buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE) + res = self._backend._lib.EVP_PKEY_get_raw_public_key( + self._evp_pkey, buf, buflen + ) + self._backend.openssl_assert(res == 1) + self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE) + public_bytes = self._backend._ffi.buffer(buf)[:] + return self._backend.ed25519_load_public_bytes(public_bytes) + + def sign(self, data): + evp_md_ctx = self._backend._lib.EVP_MD_CTX_new() + self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) + evp_md_ctx = self._backend._ffi.gc( + evp_md_ctx, self._backend._lib.EVP_MD_CTX_free + ) + res = self._backend._lib.EVP_DigestSignInit( + evp_md_ctx, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._evp_pkey, + ) + self._backend.openssl_assert(res == 1) + buf = self._backend._ffi.new("unsigned char[]", _ED25519_SIG_SIZE) + buflen = self._backend._ffi.new("size_t *", len(buf)) + res = self._backend._lib.EVP_DigestSign( + evp_md_ctx, buf, buflen, data, len(data) + ) + self._backend.openssl_assert(res == 1) + self._backend.openssl_assert(buflen[0] == _ED25519_SIG_SIZE) + return self._backend._ffi.buffer(buf, buflen[0])[:] + + def private_bytes(self, encoding, format, encryption_algorithm): + if ( + encoding is serialization.Encoding.Raw + or format is serialization.PublicFormat.Raw + ): + if ( + format is not serialization.PrivateFormat.Raw + or encoding is not serialization.Encoding.Raw + or not isinstance( + encryption_algorithm, serialization.NoEncryption + ) + ): + raise ValueError( + "When using Raw both encoding and format must be Raw " + "and encryption_algorithm must be NoEncryption()" + ) + + return self._raw_private_bytes() + + return self._backend._private_key_bytes( + encoding, format, encryption_algorithm, self, self._evp_pkey, None + ) + + def _raw_private_bytes(self): + buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE) + buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE) + res = self._backend._lib.EVP_PKEY_get_raw_private_key( + self._evp_pkey, buf, buflen + ) + self._backend.openssl_assert(res == 1) + self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE) + return self._backend._ffi.buffer(buf, _ED25519_KEY_SIZE)[:] diff --git a/src/cryptography/hazmat/backends/openssl/ed448.py b/src/cryptography/hazmat/backends/openssl/ed448.py new file mode 100644 index 000000000..6512770e5 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/ed448.py @@ -0,0 +1,146 @@ +# 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 + +from cryptography import exceptions, utils +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed448 import ( + Ed448PrivateKey, + Ed448PublicKey, +) + +_ED448_KEY_SIZE = 57 +_ED448_SIG_SIZE = 114 + + +@utils.register_interface(Ed448PublicKey) +class _Ed448PublicKey(object): + def __init__(self, backend, evp_pkey): + self._backend = backend + self._evp_pkey = evp_pkey + + def public_bytes(self, encoding, format): + if ( + encoding is serialization.Encoding.Raw + or format is serialization.PublicFormat.Raw + ): + if ( + encoding is not serialization.Encoding.Raw + or format is not serialization.PublicFormat.Raw + ): + raise ValueError( + "When using Raw both encoding and format must be Raw" + ) + + return self._raw_public_bytes() + + return self._backend._public_key_bytes( + encoding, format, self, self._evp_pkey, None + ) + + def _raw_public_bytes(self): + buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE) + buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE) + res = self._backend._lib.EVP_PKEY_get_raw_public_key( + self._evp_pkey, buf, buflen + ) + self._backend.openssl_assert(res == 1) + self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE) + return self._backend._ffi.buffer(buf, _ED448_KEY_SIZE)[:] + + def verify(self, signature, data): + evp_md_ctx = self._backend._lib.EVP_MD_CTX_new() + self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) + evp_md_ctx = self._backend._ffi.gc( + evp_md_ctx, self._backend._lib.EVP_MD_CTX_free + ) + res = self._backend._lib.EVP_DigestVerifyInit( + evp_md_ctx, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._evp_pkey, + ) + self._backend.openssl_assert(res == 1) + res = self._backend._lib.EVP_DigestVerify( + evp_md_ctx, signature, len(signature), data, len(data) + ) + if res != 1: + self._backend._consume_errors() + raise exceptions.InvalidSignature + + +@utils.register_interface(Ed448PrivateKey) +class _Ed448PrivateKey(object): + def __init__(self, backend, evp_pkey): + self._backend = backend + self._evp_pkey = evp_pkey + + def public_key(self): + buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE) + buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE) + res = self._backend._lib.EVP_PKEY_get_raw_public_key( + self._evp_pkey, buf, buflen + ) + self._backend.openssl_assert(res == 1) + self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE) + public_bytes = self._backend._ffi.buffer(buf)[:] + return self._backend.ed448_load_public_bytes(public_bytes) + + def sign(self, data): + evp_md_ctx = self._backend._lib.EVP_MD_CTX_new() + self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL) + evp_md_ctx = self._backend._ffi.gc( + evp_md_ctx, self._backend._lib.EVP_MD_CTX_free + ) + res = self._backend._lib.EVP_DigestSignInit( + evp_md_ctx, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._evp_pkey, + ) + self._backend.openssl_assert(res == 1) + buf = self._backend._ffi.new("unsigned char[]", _ED448_SIG_SIZE) + buflen = self._backend._ffi.new("size_t *", len(buf)) + res = self._backend._lib.EVP_DigestSign( + evp_md_ctx, buf, buflen, data, len(data) + ) + self._backend.openssl_assert(res == 1) + self._backend.openssl_assert(buflen[0] == _ED448_SIG_SIZE) + return self._backend._ffi.buffer(buf, buflen[0])[:] + + def private_bytes(self, encoding, format, encryption_algorithm): + if ( + encoding is serialization.Encoding.Raw + or format is serialization.PublicFormat.Raw + ): + if ( + format is not serialization.PrivateFormat.Raw + or encoding is not serialization.Encoding.Raw + or not isinstance( + encryption_algorithm, serialization.NoEncryption + ) + ): + raise ValueError( + "When using Raw both encoding and format must be Raw " + "and encryption_algorithm must be NoEncryption()" + ) + + return self._raw_private_bytes() + + return self._backend._private_key_bytes( + encoding, format, encryption_algorithm, self, self._evp_pkey, None + ) + + def _raw_private_bytes(self): + buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE) + buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE) + res = self._backend._lib.EVP_PKEY_get_raw_private_key( + self._evp_pkey, buf, buflen + ) + self._backend.openssl_assert(res == 1) + self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE) + return self._backend._ffi.buffer(buf, _ED448_KEY_SIZE)[:] diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py index 355757624..0a33200bb 100644 --- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py @@ -11,12 +11,15 @@ import six from cryptography import utils, x509 from cryptography.hazmat.backends.openssl.decode_asn1 import ( - _CRL_ENTRY_REASON_ENUM_TO_CODE, _DISTPOINT_TYPE_FULLNAME, - _DISTPOINT_TYPE_RELATIVENAME + _CRL_ENTRY_REASON_ENUM_TO_CODE, + _DISTPOINT_TYPE_FULLNAME, + _DISTPOINT_TYPE_RELATIVENAME, ) from cryptography.x509.name import _ASN1Type from cryptography.x509.oid import ( - CRLEntryExtensionOID, ExtensionOID, OCSPExtensionOID, + CRLEntryExtensionOID, + ExtensionOID, + OCSPExtensionOID, ) @@ -94,7 +97,8 @@ def _encode_name(backend, name): name_entry, backend._lib.X509_NAME_ENTRY_free ) res = backend._lib.X509_NAME_add_entry( - subject, name_entry, -1, set_flag) + subject, name_entry, -1, set_flag + ) backend.openssl_assert(res == 1) set_flag = -1 return subject @@ -120,9 +124,11 @@ def _encode_sk_name_entry(backend, attributes): def _encode_name_entry(backend, attribute): if attribute._type is _ASN1Type.BMPString: - value = attribute.value.encode('utf_16_be') + value = attribute.value.encode("utf_16_be") + elif attribute._type is _ASN1Type.UniversalString: + value = attribute.value.encode("utf_32_be") else: - value = attribute.value.encode('utf8') + value = attribute.value.encode("utf8") obj = _txt2obj_gc(backend, attribute.oid.dotted_string) @@ -172,9 +178,8 @@ def _encode_crl_reason(backend, crl_reason): def _encode_invalidity_date(backend, invalidity_date): time = backend._lib.ASN1_GENERALIZEDTIME_set( - backend._ffi.NULL, calendar.timegm( - invalidity_date.invalidity_date.timetuple() - ) + 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) @@ -255,7 +260,7 @@ def _txt2obj(backend, name): Converts a Python string with an ASN.1 object ID in dotted form to a ASN1_OBJECT. """ - name = name.encode('ascii') + name = name.encode("ascii") obj = backend._lib.OBJ_txt2obj(name, 1) backend.openssl_assert(obj != backend._ffi.NULL) return obj @@ -341,20 +346,27 @@ def _encode_basic_constraints(backend, basic_constraints): return constraints -def _encode_authority_information_access(backend, authority_info_access): +def _encode_information_access(backend, info_access): aia = backend._lib.sk_ACCESS_DESCRIPTION_new_null() backend.openssl_assert(aia != backend._ffi.NULL) aia = backend._ffi.gc( - aia, backend._lib.sk_ACCESS_DESCRIPTION_free + aia, + lambda x: backend._lib.sk_ACCESS_DESCRIPTION_pop_free( + x, + backend._ffi.addressof( + backend._lib._original_lib, "ACCESS_DESCRIPTION_free" + ), + ), ) - for access_description in authority_info_access: + for access_description in info_access: ad = backend._lib.ACCESS_DESCRIPTION_new() method = _txt2obj( backend, access_description.access_method.dotted_string ) - gn = _encode_general_name(backend, access_description.access_location) + _encode_general_name_preallocated( + backend, access_description.access_location, ad.location + ) ad.method = method - ad.location = gn res = backend._lib.sk_ACCESS_DESCRIPTION_push(aia, ad) backend.openssl_assert(res >= 1) @@ -385,8 +397,13 @@ def _encode_subject_key_identifier(backend, ski): def _encode_general_name(backend, name): + gn = backend._lib.GENERAL_NAME_new() + _encode_general_name_preallocated(backend, name, gn) + return gn + + +def _encode_general_name_preallocated(backend, name, gn): if isinstance(name, x509.DNSName): - gn = backend._lib.GENERAL_NAME_new() backend.openssl_assert(gn != backend._ffi.NULL) gn.type = backend._lib.GEN_DNS @@ -400,32 +417,27 @@ def _encode_general_name(backend, name): backend.openssl_assert(res == 1) gn.d.dNSName = ia5 elif isinstance(name, x509.RegisteredID): - gn = backend._lib.GENERAL_NAME_new() backend.openssl_assert(gn != backend._ffi.NULL) gn.type = backend._lib.GEN_RID obj = backend._lib.OBJ_txt2obj( - name.value.dotted_string.encode('ascii'), 1 + name.value.dotted_string.encode("ascii"), 1 ) backend.openssl_assert(obj != backend._ffi.NULL) gn.d.registeredID = obj elif isinstance(name, x509.DirectoryName): - gn = backend._lib.GENERAL_NAME_new() backend.openssl_assert(gn != backend._ffi.NULL) dir_name = _encode_name(backend, name.value) gn.type = backend._lib.GEN_DIRNAME gn.d.directoryName = dir_name elif isinstance(name, x509.IPAddress): - gn = backend._lib.GENERAL_NAME_new() backend.openssl_assert(gn != backend._ffi.NULL) if isinstance(name.value, ipaddress.IPv4Network): - packed = ( - name.value.network_address.packed + - utils.int_to_bytes(((1 << 32) - name.value.num_addresses), 4) + packed = name.value.network_address.packed + utils.int_to_bytes( + ((1 << 32) - name.value.num_addresses), 4 ) elif isinstance(name.value, ipaddress.IPv6Network): - packed = ( - name.value.network_address.packed + - utils.int_to_bytes((1 << 128) - name.value.num_addresses, 16) + packed = name.value.network_address.packed + utils.int_to_bytes( + (1 << 128) - name.value.num_addresses, 16 ) else: packed = name.value.packed @@ -433,13 +445,12 @@ def _encode_general_name(backend, name): gn.type = backend._lib.GEN_IPADD gn.d.iPAddress = ipaddr elif isinstance(name, x509.OtherName): - gn = backend._lib.GENERAL_NAME_new() backend.openssl_assert(gn != backend._ffi.NULL) other_name = backend._lib.OTHERNAME_new() backend.openssl_assert(other_name != backend._ffi.NULL) type_id = backend._lib.OBJ_txt2obj( - name.type_id.dotted_string.encode('ascii'), 1 + name.type_id.dotted_string.encode("ascii"), 1 ) backend.openssl_assert(type_id != backend._ffi.NULL) data = backend._ffi.new("unsigned char[]", name.value) @@ -456,7 +467,6 @@ def _encode_general_name(backend, name): gn.type = backend._lib.GEN_OTHERNAME gn.d.otherName = other_name elif isinstance(name, x509.RFC822Name): - gn = backend._lib.GENERAL_NAME_new() backend.openssl_assert(gn != backend._ffi.NULL) # ia5strings are supposed to be ITU T.50 but to allow round-tripping # of broken certs that encode utf8 we'll encode utf8 here too. @@ -465,7 +475,6 @@ def _encode_general_name(backend, name): gn.type = backend._lib.GEN_EMAIL gn.d.rfc822Name = asn1_str elif isinstance(name, x509.UniformResourceIdentifier): - gn = backend._lib.GENERAL_NAME_new() backend.openssl_assert(gn != backend._ffi.NULL) # ia5strings are supposed to be ITU T.50 but to allow round-tripping # of broken certs that encode utf8 we'll encode utf8 here too. @@ -474,11 +483,7 @@ def _encode_general_name(backend, name): gn.type = backend._lib.GEN_URI gn.d.uniformResourceIdentifier = asn1_str else: - raise ValueError( - "{0} is an unknown GeneralName type".format(name) - ) - - return gn + raise ValueError("{} is an unknown GeneralName type".format(name)) def _encode_extended_key_usage(backend, extended_key_usage): @@ -599,11 +604,21 @@ def _encode_general_subtree(backend, subtrees): gs = backend._lib.GENERAL_SUBTREE_new() gs.base = _encode_general_name(backend, name) res = backend._lib.sk_GENERAL_SUBTREE_push(general_subtrees, gs) - assert res >= 1 + backend.openssl_assert(res >= 1) return general_subtrees +def _encode_precert_signed_certificate_timestamps(backend, scts): + sct_stack = backend._lib.sk_SCT_new_null() + backend.openssl_assert(sct_stack != backend._ffi.NULL) + sct_stack = backend._ffi.gc(sct_stack, backend._lib.sk_SCT_free) + for sct in scts: + res = backend._lib.sk_SCT_push(sct_stack, sct._sct) + backend.openssl_assert(res >= 1) + return sct_stack + + def _encode_nonce(backend, nonce): return _encode_asn1_str_gc(backend, nonce.nonce) @@ -617,26 +632,27 @@ _EXTENSION_ENCODE_HANDLERS = { ExtensionOID.EXTENDED_KEY_USAGE: _encode_extended_key_usage, ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, ExtensionOID.CERTIFICATE_POLICIES: _encode_certificate_policies, - ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( - _encode_authority_information_access - ), + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: _encode_information_access, + ExtensionOID.SUBJECT_INFORMATION_ACCESS: _encode_information_access, ExtensionOID.CRL_DISTRIBUTION_POINTS: _encode_cdps_freshest_crl, ExtensionOID.FRESHEST_CRL: _encode_cdps_freshest_crl, ExtensionOID.INHIBIT_ANY_POLICY: _encode_inhibit_any_policy, ExtensionOID.OCSP_NO_CHECK: _encode_ocsp_nocheck, ExtensionOID.NAME_CONSTRAINTS: _encode_name_constraints, ExtensionOID.POLICY_CONSTRAINTS: _encode_policy_constraints, + ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: ( + _encode_precert_signed_certificate_timestamps + ), } _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.AUTHORITY_INFORMATION_ACCESS: _encode_information_access, ExtensionOID.CRL_NUMBER: _encode_crl_number_delta_crl_indicator, ExtensionOID.DELTA_CRL_INDICATOR: _encode_crl_number_delta_crl_indicator, ExtensionOID.ISSUING_DISTRIBUTION_POINT: _encode_issuing_dist_point, + ExtensionOID.FRESHEST_CRL: _encode_cdps_freshest_crl, } _CRL_ENTRY_EXTENSION_ENCODE_HANDLERS = { diff --git a/src/cryptography/hazmat/backends/openssl/hashes.py b/src/cryptography/hazmat/backends/openssl/hashes.py index 549fa2bf5..764dce0ed 100644 --- a/src/cryptography/hazmat/backends/openssl/hashes.py +++ b/src/cryptography/hazmat/backends/openssl/hashes.py @@ -18,19 +18,21 @@ class _HashContext(object): self._backend = backend if ctx is None: - ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() + ctx = self._backend._lib.EVP_MD_CTX_new() ctx = self._backend._ffi.gc( - ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free + ctx, self._backend._lib.EVP_MD_CTX_free ) evp_md = self._backend._evp_md_from_algorithm(algorithm) if evp_md == self._backend._ffi.NULL: raise UnsupportedAlgorithm( - "{0} is not a supported hash on this backend.".format( - algorithm.name), - _Reasons.UNSUPPORTED_HASH + "{} is not a supported hash on this backend.".format( + algorithm.name + ), + _Reasons.UNSUPPORTED_HASH, ) - res = self._backend._lib.EVP_DigestInit_ex(ctx, evp_md, - self._backend._ffi.NULL) + res = self._backend._lib.EVP_DigestInit_ex( + ctx, evp_md, self._backend._ffi.NULL + ) self._backend.openssl_assert(res != 0) self._ctx = ctx @@ -38,9 +40,9 @@ class _HashContext(object): algorithm = utils.read_only_property("_algorithm") def copy(self): - copied_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() + copied_ctx = self._backend._lib.EVP_MD_CTX_new() copied_ctx = self._backend._ffi.gc( - copied_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free + copied_ctx, self._backend._lib.EVP_MD_CTX_free ) res = self._backend._lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx) self._backend.openssl_assert(res != 0) @@ -58,21 +60,23 @@ class _HashContext(object): # extendable output functions use a different finalize return self._finalize_xof() else: - buf = self._backend._ffi.new("unsigned char[]", - self._backend._lib.EVP_MAX_MD_SIZE) + buf = self._backend._ffi.new( + "unsigned char[]", self._backend._lib.EVP_MAX_MD_SIZE + ) outlen = self._backend._ffi.new("unsigned int *") res = self._backend._lib.EVP_DigestFinal_ex(self._ctx, buf, outlen) self._backend.openssl_assert(res != 0) self._backend.openssl_assert( outlen[0] == self.algorithm.digest_size ) - return self._backend._ffi.buffer(buf)[:outlen[0]] + return self._backend._ffi.buffer(buf)[: outlen[0]] def _finalize_xof(self): - buf = self._backend._ffi.new("unsigned char[]", - self.algorithm.digest_size) + buf = self._backend._ffi.new( + "unsigned char[]", self.algorithm.digest_size + ) res = self._backend._lib.EVP_DigestFinalXOF( self._ctx, buf, self.algorithm.digest_size ) self._backend.openssl_assert(res != 0) - return self._backend._ffi.buffer(buf)[:self.algorithm.digest_size] + return self._backend._ffi.buffer(buf)[: self.algorithm.digest_size] diff --git a/src/cryptography/hazmat/backends/openssl/hmac.py b/src/cryptography/hazmat/backends/openssl/hmac.py index b23ac6498..1cc9d99fe 100644 --- a/src/cryptography/hazmat/backends/openssl/hmac.py +++ b/src/cryptography/hazmat/backends/openssl/hmac.py @@ -7,12 +7,13 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import ( - InvalidSignature, UnsupportedAlgorithm, _Reasons + InvalidSignature, + UnsupportedAlgorithm, + _Reasons, ) -from cryptography.hazmat.primitives import constant_time, hashes, mac +from cryptography.hazmat.primitives import constant_time, hashes -@utils.register_interface(mac.MACContext) @utils.register_interface(hashes.HashContext) class _HMACContext(object): def __init__(self, backend, key, algorithm, ctx=None): @@ -20,17 +21,16 @@ class _HMACContext(object): self._backend = backend if ctx is None: - ctx = self._backend._lib.Cryptography_HMAC_CTX_new() + ctx = self._backend._lib.HMAC_CTX_new() self._backend.openssl_assert(ctx != self._backend._ffi.NULL) - ctx = self._backend._ffi.gc( - ctx, self._backend._lib.Cryptography_HMAC_CTX_free - ) + ctx = self._backend._ffi.gc(ctx, self._backend._lib.HMAC_CTX_free) evp_md = self._backend._evp_md_from_algorithm(algorithm) if evp_md == self._backend._ffi.NULL: raise UnsupportedAlgorithm( - "{0} is not a supported hash on this backend".format( - algorithm.name), - _Reasons.UNSUPPORTED_HASH + "{} is not a supported hash on this backend".format( + algorithm.name + ), + _Reasons.UNSUPPORTED_HASH, ) key_ptr = self._backend._ffi.from_buffer(key) res = self._backend._lib.HMAC_Init_ex( @@ -44,10 +44,10 @@ class _HMACContext(object): algorithm = utils.read_only_property("_algorithm") def copy(self): - copied_ctx = self._backend._lib.Cryptography_HMAC_CTX_new() + copied_ctx = self._backend._lib.HMAC_CTX_new() self._backend.openssl_assert(copied_ctx != self._backend._ffi.NULL) copied_ctx = self._backend._ffi.gc( - copied_ctx, self._backend._lib.Cryptography_HMAC_CTX_free + copied_ctx, self._backend._lib.HMAC_CTX_free ) res = self._backend._lib.HMAC_CTX_copy(copied_ctx, self._ctx) self._backend.openssl_assert(res != 0) @@ -61,13 +61,14 @@ class _HMACContext(object): self._backend.openssl_assert(res != 0) def finalize(self): - buf = self._backend._ffi.new("unsigned char[]", - self._backend._lib.EVP_MAX_MD_SIZE) + buf = self._backend._ffi.new( + "unsigned char[]", self._backend._lib.EVP_MAX_MD_SIZE + ) outlen = self._backend._ffi.new("unsigned int *") res = self._backend._lib.HMAC_Final(self._ctx, buf, outlen) self._backend.openssl_assert(res != 0) self._backend.openssl_assert(outlen[0] == self.algorithm.digest_size) - return self._backend._ffi.buffer(buf)[:outlen[0]] + return self._backend._ffi.buffer(buf)[: outlen[0]] def verify(self, signature): digest = self.finalize() diff --git a/src/cryptography/hazmat/backends/openssl/ocsp.py b/src/cryptography/hazmat/backends/openssl/ocsp.py index 16dbbc2ae..50c02e7a8 100644 --- a/src/cryptography/hazmat/backends/openssl/ocsp.py +++ b/src/cryptography/hazmat/backends/openssl/ocsp.py @@ -9,16 +9,23 @@ import functools from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends.openssl.decode_asn1 import ( - _CRL_ENTRY_REASON_CODE_TO_ENUM, _OCSP_BASICRESP_EXT_PARSER, - _OCSP_REQ_EXT_PARSER, _asn1_integer_to_int, - _asn1_string_to_bytes, _decode_x509_name, _obj2txt, + _CRL_ENTRY_REASON_CODE_TO_ENUM, + _asn1_integer_to_int, + _asn1_string_to_bytes, + _decode_x509_name, + _obj2txt, _parse_asn1_generalized_time, ) from cryptography.hazmat.backends.openssl.x509 import _Certificate from cryptography.hazmat.primitives import serialization from cryptography.x509.ocsp import ( - OCSPCertStatus, OCSPRequest, OCSPResponse, OCSPResponseStatus, - _CERT_STATUS_TO_ENUM, _OIDS_TO_HASH, _RESPONSE_STATUS_TO_ENUM, + OCSPCertStatus, + OCSPRequest, + OCSPResponse, + OCSPResponseStatus, + _CERT_STATUS_TO_ENUM, + _OIDS_TO_HASH, + _RESPONSE_STATUS_TO_ENUM, ) @@ -39,8 +46,11 @@ def _requires_successful_response(func): def _issuer_key_hash(backend, cert_id): key_hash = backend._ffi.new("ASN1_OCTET_STRING **") res = backend._lib.OCSP_id_get0_info( - backend._ffi.NULL, backend._ffi.NULL, - key_hash, backend._ffi.NULL, cert_id + backend._ffi.NULL, + backend._ffi.NULL, + key_hash, + backend._ffi.NULL, + cert_id, ) backend.openssl_assert(res == 1) backend.openssl_assert(key_hash[0] != backend._ffi.NULL) @@ -50,8 +60,11 @@ def _issuer_key_hash(backend, cert_id): def _issuer_name_hash(backend, cert_id): name_hash = backend._ffi.new("ASN1_OCTET_STRING **") res = backend._lib.OCSP_id_get0_info( - name_hash, backend._ffi.NULL, - backend._ffi.NULL, backend._ffi.NULL, cert_id + name_hash, + backend._ffi.NULL, + backend._ffi.NULL, + backend._ffi.NULL, + cert_id, ) backend.openssl_assert(res == 1) backend.openssl_assert(name_hash[0] != backend._ffi.NULL) @@ -61,8 +74,7 @@ def _issuer_name_hash(backend, cert_id): def _serial_number(backend, cert_id): num = backend._ffi.new("ASN1_INTEGER **") res = backend._lib.OCSP_id_get0_info( - backend._ffi.NULL, backend._ffi.NULL, - backend._ffi.NULL, num, cert_id + backend._ffi.NULL, backend._ffi.NULL, backend._ffi.NULL, num, cert_id ) backend.openssl_assert(res == 1) backend.openssl_assert(num[0] != backend._ffi.NULL) @@ -72,8 +84,11 @@ def _serial_number(backend, cert_id): def _hash_algorithm(backend, cert_id): asn1obj = backend._ffi.new("ASN1_OBJECT **") res = backend._lib.OCSP_id_get0_info( - backend._ffi.NULL, asn1obj, - backend._ffi.NULL, backend._ffi.NULL, cert_id + backend._ffi.NULL, + asn1obj, + backend._ffi.NULL, + backend._ffi.NULL, + cert_id, ) backend.openssl_assert(res == 1) backend.openssl_assert(asn1obj[0] != backend._ffi.NULL) @@ -82,7 +97,7 @@ def _hash_algorithm(backend, cert_id): return _OIDS_TO_HASH[oid] except KeyError: raise UnsupportedAlgorithm( - "Signature algorithm OID: {0} not recognized".format(oid) + "Signature algorithm OID: {} not recognized".format(oid) ) @@ -102,9 +117,13 @@ class _OCSPResponse(object): self._basic = self._backend._ffi.gc( basic, self._backend._lib.OCSP_BASICRESP_free ) - self._backend.openssl_assert( - self._backend._lib.OCSP_resp_count(self._basic) == 1 - ) + num_resp = self._backend._lib.OCSP_resp_count(self._basic) + if num_resp != 1: + raise ValueError( + "OCSP response contains more than one SINGLERESP structure" + ", which this library does not support. " + "{} found".format(num_resp) + ) self._single = self._backend._lib.OCSP_resp_get0(self._basic, 0) self._backend.openssl_assert( self._single != self._backend._ffi.NULL @@ -134,7 +153,7 @@ class _OCSPResponse(object): return x509._SIG_OIDS_TO_HASH[oid] except KeyError: raise UnsupportedAlgorithm( - "Signature algorithm OID:{0} not recognized".format(oid) + "Signature algorithm OID:{} not recognized".format(oid) ) @property @@ -317,13 +336,16 @@ class _OCSPResponse(object): @utils.cached_property @_requires_successful_response def extensions(self): - return _OCSP_BASICRESP_EXT_PARSER.parse(self._backend, self._basic) + return self._backend._ocsp_basicresp_ext_parser.parse(self._basic) + + @utils.cached_property + @_requires_successful_response + def single_extensions(self): + return self._backend._ocsp_singleresp_ext_parser.parse(self._single) def public_bytes(self, encoding): if encoding is not serialization.Encoding.DER: - raise ValueError( - "The only allowed encoding value is Encoding.DER" - ) + raise ValueError("The only allowed encoding value is Encoding.DER") bio = self._backend._create_mem_bio_gc() res = self._backend._lib.i2d_OCSP_RESPONSE_bio( @@ -338,7 +360,7 @@ class _OCSPRequest(object): def __init__(self, backend, ocsp_request): if backend._lib.OCSP_request_onereq_count(ocsp_request) > 1: raise NotImplementedError( - 'OCSP request contains more than one request' + "OCSP request contains more than one request" ) self._backend = backend self._ocsp_request = ocsp_request @@ -367,13 +389,11 @@ class _OCSPRequest(object): @utils.cached_property def extensions(self): - return _OCSP_REQ_EXT_PARSER.parse(self._backend, self._ocsp_request) + return self._backend._ocsp_req_ext_parser.parse(self._ocsp_request) def public_bytes(self, encoding): if encoding is not serialization.Encoding.DER: - raise ValueError( - "The only allowed encoding value is Encoding.DER" - ) + raise ValueError("The only allowed encoding value is Encoding.DER") bio = self._backend._create_mem_bio_gc() res = self._backend._lib.i2d_OCSP_REQUEST_bio(bio, self._ocsp_request) diff --git a/src/cryptography/hazmat/backends/openssl/poly1305.py b/src/cryptography/hazmat/backends/openssl/poly1305.py new file mode 100644 index 000000000..5699918b1 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/poly1305.py @@ -0,0 +1,65 @@ +# 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 + + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import constant_time + + +_POLY1305_TAG_SIZE = 16 +_POLY1305_KEY_SIZE = 32 + + +class _Poly1305Context(object): + def __init__(self, backend, key): + self._backend = backend + + key_ptr = self._backend._ffi.from_buffer(key) + # This function copies the key into OpenSSL-owned memory so we don't + # need to retain it ourselves + evp_pkey = self._backend._lib.EVP_PKEY_new_raw_private_key( + self._backend._lib.NID_poly1305, + self._backend._ffi.NULL, + key_ptr, + len(key), + ) + self._backend.openssl_assert(evp_pkey != self._backend._ffi.NULL) + self._evp_pkey = self._backend._ffi.gc( + evp_pkey, self._backend._lib.EVP_PKEY_free + ) + ctx = self._backend._lib.EVP_MD_CTX_new() + self._backend.openssl_assert(ctx != self._backend._ffi.NULL) + self._ctx = self._backend._ffi.gc( + ctx, self._backend._lib.EVP_MD_CTX_free + ) + res = self._backend._lib.EVP_DigestSignInit( + self._ctx, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._evp_pkey, + ) + self._backend.openssl_assert(res == 1) + + def update(self, data): + data_ptr = self._backend._ffi.from_buffer(data) + res = self._backend._lib.EVP_DigestSignUpdate( + self._ctx, data_ptr, len(data) + ) + self._backend.openssl_assert(res != 0) + + def finalize(self): + buf = self._backend._ffi.new("unsigned char[]", _POLY1305_TAG_SIZE) + outlen = self._backend._ffi.new("size_t *") + res = self._backend._lib.EVP_DigestSignFinal(self._ctx, buf, outlen) + self._backend.openssl_assert(res != 0) + self._backend.openssl_assert(outlen[0] == _POLY1305_TAG_SIZE) + return self._backend._ffi.buffer(buf)[: outlen[0]] + + def verify(self, tag): + mac = self.finalize() + if not constant_time.bytes_eq(mac, tag): + raise InvalidSignature("Value did not match computed tag.") diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py index 30d59bd80..82cd49c96 100644 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -4,25 +4,34 @@ from __future__ import absolute_import, division, print_function -import math - from cryptography import utils from cryptography.exceptions import ( - InvalidSignature, UnsupportedAlgorithm, _Reasons + InvalidSignature, + UnsupportedAlgorithm, + _Reasons, ) from cryptography.hazmat.backends.openssl.utils import ( - _calculate_digest_and_algorithm, _check_not_prehashed, - _warn_sign_verify_deprecated + _calculate_digest_and_algorithm, + _check_not_prehashed, + _warn_sign_verify_deprecated, ) from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ( - AsymmetricSignatureContext, AsymmetricVerificationContext, rsa + AsymmetricSignatureContext, + AsymmetricVerificationContext, + rsa, ) from cryptography.hazmat.primitives.asymmetric.padding import ( - AsymmetricPadding, MGF1, OAEP, PKCS1v15, PSS, calculate_max_pss_salt_length + AsymmetricPadding, + MGF1, + OAEP, + PKCS1v15, + PSS, + calculate_max_pss_salt_length, ) from cryptography.hazmat.primitives.asymmetric.rsa import ( - RSAPrivateKeyWithSerialization, RSAPublicKeyWithSerialization + RSAPrivateKeyWithSerialization, + RSAPublicKeyWithSerialization, ) @@ -47,22 +56,20 @@ def _enc_dec_rsa(backend, key, data, padding): if not isinstance(padding._mgf, MGF1): raise UnsupportedAlgorithm( "Only MGF1 is supported by this backend.", - _Reasons.UNSUPPORTED_MGF + _Reasons.UNSUPPORTED_MGF, ) if not backend.rsa_padding_supported(padding): raise UnsupportedAlgorithm( "This combination of padding and hash algorithm is not " "supported by this backend.", - _Reasons.UNSUPPORTED_PADDING + _Reasons.UNSUPPORTED_PADDING, ) else: raise UnsupportedAlgorithm( - "{0} is not supported by this backend.".format( - padding.name - ), - _Reasons.UNSUPPORTED_PADDING + "{} is not supported by this backend.".format(padding.name), + _Reasons.UNSUPPORTED_PADDING, ) return _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding) @@ -76,24 +83,19 @@ def _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding): init = backend._lib.EVP_PKEY_decrypt_init crypt = backend._lib.EVP_PKEY_decrypt - pkey_ctx = backend._lib.EVP_PKEY_CTX_new( - key._evp_pkey, backend._ffi.NULL - ) + pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL) backend.openssl_assert(pkey_ctx != backend._ffi.NULL) pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free) res = init(pkey_ctx) backend.openssl_assert(res == 1) - res = backend._lib.EVP_PKEY_CTX_set_rsa_padding( - pkey_ctx, padding_enum) + res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum) backend.openssl_assert(res > 0) buf_size = backend._lib.EVP_PKEY_size(key._evp_pkey) backend.openssl_assert(buf_size > 0) - if ( - isinstance(padding, OAEP) and - backend._lib.Cryptography_HAS_RSA_OAEP_MD - ): + if isinstance(padding, OAEP) and backend._lib.Cryptography_HAS_RSA_OAEP_MD: mgf1_md = backend._evp_md_non_null_from_algorithm( - padding._mgf._algorithm) + padding._mgf._algorithm + ) res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md) backend.openssl_assert(res > 0) oaep_md = backend._evp_md_non_null_from_algorithm(padding._algorithm) @@ -101,9 +103,9 @@ def _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding): backend.openssl_assert(res > 0) if ( - isinstance(padding, OAEP) and - padding._label is not None and - len(padding._label) > 0 + isinstance(padding, OAEP) + and padding._label is not None + and len(padding._label) > 0 ): # set0_rsa_oaep_label takes ownership of the char * so we need to # copy it into some new memory @@ -117,40 +119,19 @@ def _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding): outlen = backend._ffi.new("size_t *", buf_size) buf = backend._ffi.new("unsigned char[]", buf_size) + # Everything from this line onwards is written with the goal of being as + # constant-time as is practical given the constraints of Python and our + # API. See Bleichenbacher's '98 attack on RSA, and its many many variants. + # As such, you should not attempt to change this (particularly to "clean it + # up") without understanding why it was written this way (see + # Chesterton's Fence), and without measuring to verify you have not + # introduced observable time differences. res = crypt(pkey_ctx, buf, outlen, data, len(data)) + resbuf = backend._ffi.buffer(buf)[: outlen[0]] + backend._lib.ERR_clear_error() if res <= 0: - _handle_rsa_enc_dec_error(backend, key) - - return backend._ffi.buffer(buf)[:outlen[0]] - - -def _handle_rsa_enc_dec_error(backend, key): - errors = backend._consume_errors() - backend.openssl_assert(errors) - backend.openssl_assert(errors[0].lib == backend._lib.ERR_LIB_RSA) - if isinstance(key, _RSAPublicKey): - backend.openssl_assert( - errors[0].reason == backend._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE - ) - raise ValueError( - "Data too long for key size. Encrypt less data or use a " - "larger key size." - ) - else: - 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, - # Though this error looks similar to the - # RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE, this occurs on decrypts, - # rather than on encrypts - backend._lib.RSA_R_DATA_TOO_LARGE_FOR_MODULUS, - ] - if backend._lib.Cryptography_HAS_RSA_R_PKCS_DECODING_ERROR: - decoding_errors.append(backend._lib.RSA_R_PKCS_DECODING_ERROR) - - backend.openssl_assert(errors[0].reason in decoding_errors) - raise ValueError("Decryption failed.") + raise ValueError("Encryption/decryption failed.") + return resbuf def _rsa_sig_determine_padding(backend, key, padding, algorithm): @@ -161,49 +142,68 @@ def _rsa_sig_determine_padding(backend, key, padding, algorithm): backend.openssl_assert(pkey_size > 0) if isinstance(padding, PKCS1v15): + # Hash algorithm is ignored for PKCS1v15-padding, may be None. padding_enum = backend._lib.RSA_PKCS1_PADDING elif isinstance(padding, PSS): if not isinstance(padding._mgf, MGF1): raise UnsupportedAlgorithm( "Only MGF1 is supported by this backend.", - _Reasons.UNSUPPORTED_MGF + _Reasons.UNSUPPORTED_MGF, ) + # PSS padding requires a hash algorithm + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of hashes.HashAlgorithm.") + # Size of key in bytes - 2 is the maximum # PSS signature length (salt length is checked later) if pkey_size - algorithm.digest_size - 2 < 0: - raise ValueError("Digest too large for key size. Use a larger " - "key or different digest.") + raise ValueError( + "Digest too large for key size. Use a larger " + "key or different digest." + ) padding_enum = backend._lib.RSA_PKCS1_PSS_PADDING else: raise UnsupportedAlgorithm( - "{0} is not supported by this backend.".format(padding.name), - _Reasons.UNSUPPORTED_PADDING + "{} is not supported by this backend.".format(padding.name), + _Reasons.UNSUPPORTED_PADDING, ) return padding_enum -def _rsa_sig_setup(backend, padding, algorithm, key, data, init_func): +# Hash algorithm can be absent (None) to initialize the context without setting +# any message digest algorithm. This is currently only valid for the PKCS1v15 +# padding type, where it means that the signature data is encoded/decoded +# as provided, without being wrapped in a DigestInfo structure. +def _rsa_sig_setup(backend, padding, algorithm, key, init_func): padding_enum = _rsa_sig_determine_padding(backend, key, padding, algorithm) - evp_md = backend._evp_md_non_null_from_algorithm(algorithm) pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL) backend.openssl_assert(pkey_ctx != backend._ffi.NULL) pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free) res = init_func(pkey_ctx) backend.openssl_assert(res == 1) - res = backend._lib.EVP_PKEY_CTX_set_signature_md(pkey_ctx, evp_md) - if res == 0: + if algorithm is not None: + evp_md = backend._evp_md_non_null_from_algorithm(algorithm) + res = backend._lib.EVP_PKEY_CTX_set_signature_md(pkey_ctx, evp_md) + if res == 0: + backend._consume_errors() + raise UnsupportedAlgorithm( + "{} is not supported by this backend for RSA signing.".format( + algorithm.name + ), + _Reasons.UNSUPPORTED_HASH, + ) + res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum) + if res <= 0: backend._consume_errors() raise UnsupportedAlgorithm( - "{0} is not supported by this backend for RSA signing.".format( - algorithm.name + "{} is not supported for the RSA signature operation.".format( + padding.name ), - _Reasons.UNSUPPORTED_HASH + _Reasons.UNSUPPORTED_PADDING, ) - res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum) - backend.openssl_assert(res > 0) if isinstance(padding, PSS): res = backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen( pkey_ctx, _get_rsa_pss_salt_length(padding, key, algorithm) @@ -211,7 +211,8 @@ def _rsa_sig_setup(backend, padding, algorithm, key, data, init_func): backend.openssl_assert(res > 0) mgf1_md = backend._evp_md_non_null_from_algorithm( - padding._mgf._algorithm) + padding._mgf._algorithm + ) res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md) backend.openssl_assert(res > 0) @@ -220,45 +221,37 @@ def _rsa_sig_setup(backend, padding, algorithm, key, data, init_func): def _rsa_sig_sign(backend, padding, algorithm, private_key, data): pkey_ctx = _rsa_sig_setup( - backend, padding, algorithm, private_key, data, - backend._lib.EVP_PKEY_sign_init + backend, + padding, + algorithm, + private_key, + backend._lib.EVP_PKEY_sign_init, ) buflen = backend._ffi.new("size_t *") res = backend._lib.EVP_PKEY_sign( - pkey_ctx, - backend._ffi.NULL, - buflen, - data, - len(data) + pkey_ctx, backend._ffi.NULL, buflen, data, len(data) ) backend.openssl_assert(res == 1) buf = backend._ffi.new("unsigned char[]", buflen[0]) - res = backend._lib.EVP_PKEY_sign( - pkey_ctx, buf, buflen, data, len(data)) + res = backend._lib.EVP_PKEY_sign(pkey_ctx, buf, buflen, data, len(data)) if res != 1: - errors = backend._consume_errors() - backend.openssl_assert(errors[0].lib == backend._lib.ERR_LIB_RSA) - if ( - errors[0].reason == - backend._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE - ): - reason = ("Salt length too long for key size. Try using " - "MAX_LENGTH instead.") - else: - backend.openssl_assert( - errors[0].reason == - backend._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY - ) - reason = "Digest too large for key size. Use a larger key." - raise ValueError(reason) + errors = backend._consume_errors_with_text() + raise ValueError( + "Digest or salt length too long for key size. Use a larger key " + "or shorter salt length if you are specifying a PSS salt", + errors, + ) return backend._ffi.buffer(buf)[:] def _rsa_sig_verify(backend, padding, algorithm, public_key, signature, data): pkey_ctx = _rsa_sig_setup( - backend, padding, algorithm, public_key, data, - backend._lib.EVP_PKEY_verify_init + backend, + padding, + algorithm, + public_key, + backend._lib.EVP_PKEY_verify_init, ) res = backend._lib.EVP_PKEY_verify( pkey_ctx, signature, len(signature), data, len(data) @@ -272,6 +265,36 @@ def _rsa_sig_verify(backend, padding, algorithm, public_key, signature, data): raise InvalidSignature +def _rsa_sig_recover(backend, padding, algorithm, public_key, signature): + pkey_ctx = _rsa_sig_setup( + backend, + padding, + algorithm, + public_key, + backend._lib.EVP_PKEY_verify_recover_init, + ) + + # Attempt to keep the rest of the code in this function as constant/time + # as possible. See the comment in _enc_dec_rsa_pkey_ctx. Note that the + # outlen parameter is used even though its value may be undefined in the + # error case. Due to the tolerant nature of Python slicing this does not + # trigger any exceptions. + maxlen = backend._lib.EVP_PKEY_size(public_key._evp_pkey) + backend.openssl_assert(maxlen > 0) + buf = backend._ffi.new("unsigned char[]", maxlen) + buflen = backend._ffi.new("size_t *", maxlen) + res = backend._lib.EVP_PKEY_verify_recover( + pkey_ctx, buf, buflen, signature, len(signature) + ) + resbuf = backend._ffi.buffer(buf)[: buflen[0]] + backend._lib.ERR_clear_error() + # Assume that all parameter errors are handled during the setup phase and + # any error here is due to invalid signature. + if res != 1: + raise InvalidSignature + return resbuf + + @utils.register_interface(AsymmetricSignatureContext) class _RSASignatureContext(object): def __init__(self, backend, private_key, padding, algorithm): @@ -295,7 +318,7 @@ class _RSASignatureContext(object): self._padding, self._algorithm, self._private_key, - self._hash_ctx.finalize() + self._hash_ctx.finalize(), ) @@ -325,21 +348,33 @@ class _RSAVerificationContext(object): self._algorithm, self._public_key, self._signature, - self._hash_ctx.finalize() + self._hash_ctx.finalize(), ) @utils.register_interface(RSAPrivateKeyWithSerialization) class _RSAPrivateKey(object): def __init__(self, backend, rsa_cdata, evp_pkey): + res = backend._lib.RSA_check_key(rsa_cdata) + if res != 1: + errors = backend._consume_errors_with_text() + raise ValueError("Invalid private key", errors) + + # Blinding is on by default in many versions of OpenSSL, but let's + # just be conservative here. + res = backend._lib.RSA_blinding_on(rsa_cdata, backend._ffi.NULL) + backend.openssl_assert(res == 1) + self._backend = backend self._rsa_cdata = rsa_cdata self._evp_pkey = evp_pkey n = self._backend._ffi.new("BIGNUM **") self._backend._lib.RSA_get0_key( - self._rsa_cdata, n, self._backend._ffi.NULL, - self._backend._ffi.NULL + self._rsa_cdata, + n, + self._backend._ffi.NULL, + self._backend._ffi.NULL, ) self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) self._key_size = self._backend._lib.BN_num_bits(n[0]) @@ -352,7 +387,7 @@ class _RSAPrivateKey(object): return _RSASignatureContext(self._backend, self, padding, algorithm) def decrypt(self, ciphertext, padding): - key_size_bytes = int(math.ceil(self.key_size / 8.0)) + key_size_bytes = (self.key_size + 7) // 8 if key_size_bytes != len(ciphertext): raise ValueError("Ciphertext length must be equal to key size.") @@ -362,8 +397,6 @@ class _RSAPrivateKey(object): ctx = self._backend._lib.RSAPublicKey_dup(self._rsa_cdata) self._backend.openssl_assert(ctx != self._backend._ffi.NULL) ctx = self._backend._ffi.gc(ctx, self._backend._lib.RSA_free) - res = self._backend._lib.RSA_blinding_on(ctx, self._backend._ffi.NULL) - self._backend.openssl_assert(res == 1) evp_pkey = self._backend._rsa_cdata_to_evp_pkey(ctx) return _RSAPublicKey(self._backend, ctx, evp_pkey) @@ -399,7 +432,7 @@ class _RSAPrivateKey(object): public_numbers=rsa.RSAPublicNumbers( e=self._backend._bn_to_int(e[0]), n=self._backend._bn_to_int(n[0]), - ) + ), ) def private_bytes(self, encoding, format, encryption_algorithm): @@ -407,8 +440,9 @@ class _RSAPrivateKey(object): encoding, format, encryption_algorithm, + self, self._evp_pkey, - self._rsa_cdata + self._rsa_cdata, ) def sign(self, data, padding, algorithm): @@ -427,8 +461,10 @@ class _RSAPublicKey(object): n = self._backend._ffi.new("BIGNUM **") self._backend._lib.RSA_get0_key( - self._rsa_cdata, n, self._backend._ffi.NULL, - self._backend._ffi.NULL + self._rsa_cdata, + n, + self._backend._ffi.NULL, + self._backend._ffi.NULL, ) self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) self._key_size = self._backend._lib.BN_num_bits(n[0]) @@ -462,11 +498,7 @@ class _RSAPublicKey(object): def public_bytes(self, encoding, format): return self._backend._public_key_bytes( - encoding, - format, - self, - self._evp_pkey, - self._rsa_cdata + encoding, format, self, self._evp_pkey, self._rsa_cdata ) def verify(self, signature, data, padding, algorithm): @@ -476,3 +508,9 @@ class _RSAPublicKey(object): return _rsa_sig_verify( self._backend, padding, algorithm, self, signature, data ) + + def recover_data_from_signature(self, signature, padding, algorithm): + _check_not_prehashed(algorithm) + return _rsa_sig_recover( + self._backend, padding, algorithm, self, signature + ) diff --git a/src/cryptography/hazmat/backends/openssl/utils.py b/src/cryptography/hazmat/backends/openssl/utils.py index 363f3d2c2..3d697d1fb 100644 --- a/src/cryptography/hazmat/backends/openssl/utils.py +++ b/src/cryptography/hazmat/backends/openssl/utils.py @@ -17,9 +17,7 @@ def _evp_pkey_derive(backend, evp_pkey, peer_public_key): ctx = backend._ffi.gc(ctx, backend._lib.EVP_PKEY_CTX_free) res = backend._lib.EVP_PKEY_derive_init(ctx) backend.openssl_assert(res == 1) - res = backend._lib.EVP_PKEY_derive_set_peer( - ctx, peer_public_key._evp_pkey - ) + res = backend._lib.EVP_PKEY_derive_set_peer(ctx, peer_public_key._evp_pkey) backend.openssl_assert(res == 1) keylen = backend._ffi.new("size_t *") res = backend._lib.EVP_PKEY_derive(ctx, backend._ffi.NULL, keylen) @@ -28,9 +26,7 @@ def _evp_pkey_derive(backend, evp_pkey, peer_public_key): buf = backend._ffi.new("unsigned char[]", keylen[0]) res = backend._lib.EVP_PKEY_derive(ctx, buf, keylen) if res != 1: - raise ValueError( - "Null shared key derived from public/private pair." - ) + raise ValueError("Null shared key derived from public/private pair.") return backend._ffi.buffer(buf, keylen[0])[:] @@ -56,7 +52,8 @@ def _check_not_prehashed(signature_algorithm): if isinstance(signature_algorithm, Prehashed): raise TypeError( "Prehashed is only supported in the sign and verify methods. " - "It cannot be used with signer or verifier." + "It cannot be used with signer, verifier or " + "recover_data_from_signature." ) @@ -64,6 +61,6 @@ def _warn_sign_verify_deprecated(): warnings.warn( "signer and verifier have been deprecated. Please use sign " "and verify instead.", - utils.PersistentlyDeprecated, - stacklevel=3 + utils.PersistentlyDeprecated2017, + stacklevel=3, ) diff --git a/src/cryptography/hazmat/backends/openssl/x25519.py b/src/cryptography/hazmat/backends/openssl/x25519.py index 914f59413..4971c5481 100644 --- a/src/cryptography/hazmat/backends/openssl/x25519.py +++ b/src/cryptography/hazmat/backends/openssl/x25519.py @@ -4,13 +4,12 @@ from __future__ import absolute_import, division, print_function -import warnings - from cryptography import utils from cryptography.hazmat.backends.openssl.utils import _evp_pkey_derive from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.x25519 import ( - X25519PrivateKey, X25519PublicKey + X25519PrivateKey, + X25519PublicKey, ) @@ -23,26 +22,14 @@ class _X25519PublicKey(object): self._backend = backend self._evp_pkey = evp_pkey - def public_bytes(self, encoding=None, format=None): - if encoding is None or format is None: - if encoding is not None or format is not None: - raise ValueError("Both encoding and format are required") - else: - warnings.warn( - "public_bytes now requires encoding and format arguments. " - "Support for calling without arguments will be removed in " - "cryptography 2.7", - utils.DeprecatedIn25, - ) - encoding = serialization.Encoding.Raw - format = serialization.PublicFormat.Raw + def public_bytes(self, encoding, format): if ( - encoding is serialization.Encoding.Raw or - format is serialization.PublicFormat.Raw + encoding is serialization.Encoding.Raw + or format is serialization.PublicFormat.Raw ): if ( - encoding is not serialization.Encoding.Raw or - format is not serialization.PublicFormat.Raw + encoding is not serialization.Encoding.Raw + or format is not serialization.PublicFormat.Raw ): raise ValueError( "When using Raw both encoding and format must be Raw" @@ -50,15 +37,6 @@ class _X25519PublicKey(object): return self._raw_public_bytes() - if ( - encoding in serialization._PEM_DER and - format is not serialization.PublicFormat.SubjectPublicKeyInfo - ): - raise ValueError( - "format must be SubjectPublicKeyInfo when encoding is PEM or " - "DER" - ) - return self._backend._public_key_bytes( encoding, format, self, self._evp_pkey, None ) @@ -99,37 +77,29 @@ class _X25519PrivateKey(object): if not isinstance(peer_public_key, X25519PublicKey): raise TypeError("peer_public_key must be X25519PublicKey.") - return _evp_pkey_derive( - self._backend, self._evp_pkey, peer_public_key - ) + return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key) def private_bytes(self, encoding, format, encryption_algorithm): if ( - encoding is serialization.Encoding.Raw or - format is serialization.PublicFormat.Raw + encoding is serialization.Encoding.Raw + or format is serialization.PublicFormat.Raw ): if ( - format is not serialization.PrivateFormat.Raw or - encoding is not serialization.Encoding.Raw or not - isinstance(encryption_algorithm, serialization.NoEncryption) + format is not serialization.PrivateFormat.Raw + or encoding is not serialization.Encoding.Raw + or not isinstance( + encryption_algorithm, serialization.NoEncryption + ) ): raise ValueError( "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption" + "and encryption_algorithm must be NoEncryption()" ) return self._raw_private_bytes() - if ( - encoding in serialization._PEM_DER and - format is not serialization.PrivateFormat.PKCS8 - ): - raise ValueError( - "format must be PKCS8 when encoding is PEM or DER" - ) - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self._evp_pkey, None + encoding, format, encryption_algorithm, self, self._evp_pkey, None ) def _raw_private_bytes(self): @@ -139,9 +109,13 @@ class _X25519PrivateKey(object): # using the last 32 bytes, which is the key itself. bio = self._backend._create_mem_bio_gc() res = self._backend._lib.i2d_PKCS8PrivateKey_bio( - bio, self._evp_pkey, - self._backend._ffi.NULL, self._backend._ffi.NULL, - 0, self._backend._ffi.NULL, self._backend._ffi.NULL + bio, + self._evp_pkey, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + 0, + self._backend._ffi.NULL, + self._backend._ffi.NULL, ) self._backend.openssl_assert(res == 1) pkcs8 = self._backend._read_mem_bio(bio) diff --git a/src/cryptography/hazmat/backends/openssl/x448.py b/src/cryptography/hazmat/backends/openssl/x448.py index 13e4ce15e..7ebcdf84b 100644 --- a/src/cryptography/hazmat/backends/openssl/x448.py +++ b/src/cryptography/hazmat/backends/openssl/x448.py @@ -8,7 +8,8 @@ from cryptography import utils from cryptography.hazmat.backends.openssl.utils import _evp_pkey_derive from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.x448 import ( - X448PrivateKey, X448PublicKey + X448PrivateKey, + X448PublicKey, ) _X448_KEY_SIZE = 56 @@ -22,12 +23,12 @@ class _X448PublicKey(object): def public_bytes(self, encoding, format): if ( - encoding is serialization.Encoding.Raw or - format is serialization.PublicFormat.Raw + encoding is serialization.Encoding.Raw + or format is serialization.PublicFormat.Raw ): if ( - encoding is not serialization.Encoding.Raw or - format is not serialization.PublicFormat.Raw + encoding is not serialization.Encoding.Raw + or format is not serialization.PublicFormat.Raw ): raise ValueError( "When using Raw both encoding and format must be Raw" @@ -35,15 +36,6 @@ class _X448PublicKey(object): return self._raw_public_bytes() - if ( - encoding in serialization._PEM_DER and - format is not serialization.PublicFormat.SubjectPublicKeyInfo - ): - raise ValueError( - "format must be SubjectPublicKeyInfo when encoding is PEM or " - "DER" - ) - return self._backend._public_key_bytes( encoding, format, self, self._evp_pkey, None ) @@ -79,37 +71,29 @@ class _X448PrivateKey(object): if not isinstance(peer_public_key, X448PublicKey): raise TypeError("peer_public_key must be X448PublicKey.") - return _evp_pkey_derive( - self._backend, self._evp_pkey, peer_public_key - ) + return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key) def private_bytes(self, encoding, format, encryption_algorithm): if ( - encoding is serialization.Encoding.Raw or - format is serialization.PublicFormat.Raw + encoding is serialization.Encoding.Raw + or format is serialization.PublicFormat.Raw ): if ( - format is not serialization.PrivateFormat.Raw or - encoding is not serialization.Encoding.Raw or not - isinstance(encryption_algorithm, serialization.NoEncryption) + format is not serialization.PrivateFormat.Raw + or encoding is not serialization.Encoding.Raw + or not isinstance( + encryption_algorithm, serialization.NoEncryption + ) ): raise ValueError( "When using Raw both encoding and format must be Raw " - "and encryption_algorithm must be NoEncryption" + "and encryption_algorithm must be NoEncryption()" ) return self._raw_private_bytes() - if ( - encoding in serialization._PEM_DER and - format is not serialization.PrivateFormat.PKCS8 - ): - raise ValueError( - "format must be PKCS8 when encoding is PEM or DER" - ) - return self._backend._private_key_bytes( - encoding, format, encryption_algorithm, self._evp_pkey, None + encoding, format, encryption_algorithm, self, self._evp_pkey, None ) def _raw_private_bytes(self): diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index ac1838c6d..4d0dac764 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -6,31 +6,43 @@ from __future__ import absolute_import, division, print_function import datetime import operator -import warnings from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends.openssl.decode_asn1 import ( - _CERTIFICATE_EXTENSION_PARSER, _CERTIFICATE_EXTENSION_PARSER_NO_SCT, - _CRL_EXTENSION_PARSER, _CSR_EXTENSION_PARSER, - _REVOKED_CERTIFICATE_EXTENSION_PARSER, _asn1_integer_to_int, - _asn1_string_to_bytes, _decode_x509_name, _obj2txt, _parse_asn1_time + _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 + _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): + def __init__(self, backend, x509_cert): self._backend = backend - self._x509 = x509 + 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 "<Certificate(subject={0}, ...)>".format(self.subject) + return "<Certificate(subject={}, ...)>".format(self.subject) def __eq__(self, other): if not isinstance(other, x509.Certificate): @@ -45,31 +57,15 @@ class _Certificate(object): 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() - @property - def version(self): - version = self._backend._lib.X509_get_version(self._x509) - if version == 0: - return x509.Version.v1 - elif version == 2: - return x509.Version.v3 - else: - raise x509.InvalidVersion( - "{0} is not a valid X509 version".format(version), version - ) - - @property - def serial(self): - warnings.warn( - "Certificate serial is deprecated, use serial_number instead.", - utils.PersistentlyDeprecated, - stacklevel=2 - ) - return self.serial_number + version = utils.read_only_property("_version") @property def serial_number(self): @@ -90,12 +86,12 @@ class _Certificate(object): @property def not_valid_before(self): - asn1_time = self._backend._lib.X509_get_notBefore(self._x509) + 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_get_notAfter(self._x509) + asn1_time = self._backend._lib.X509_getm_notAfter(self._x509) return _parse_asn1_time(self._backend, asn1_time) @property @@ -117,7 +113,7 @@ class _Certificate(object): return x509._SIG_OIDS_TO_HASH[oid] except KeyError: raise UnsupportedAlgorithm( - "Signature algorithm OID:{0} not recognized".format(oid) + "Signature algorithm OID:{} not recognized".format(oid) ) @property @@ -132,14 +128,7 @@ class _Certificate(object): @utils.cached_property def extensions(self): - if self._backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER: - return _CERTIFICATE_EXTENSION_PARSER.parse( - self._backend, self._x509 - ) - else: - return _CERTIFICATE_EXTENSION_PARSER_NO_SCT.parse( - self._backend, self._x509 - ) + return self._backend._certificate_extension_parser.parse(self._x509) @property def signature(self): @@ -201,13 +190,13 @@ class _RevokedCertificate(object): self._backend, self._backend._lib.X509_REVOKED_get0_revocationDate( self._x509_revoked - ) + ), ) @utils.cached_property def extensions(self): - return _REVOKED_CERTIFICATE_EXTENSION_PARSER.parse( - self._backend, self._x509_revoked + return self._backend._revoked_cert_extension_parser.parse( + self._x509_revoked ) @@ -230,9 +219,7 @@ class _CertificateRevocationList(object): 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 - ) + 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) @@ -257,9 +244,7 @@ class _CertificateRevocationList(object): if res == 0: return None else: - self._backend.openssl_assert( - revoked[0] != self._backend._ffi.NULL - ) + self._backend.openssl_assert(revoked[0] != self._backend._ffi.NULL) return _RevokedCertificate( self._backend, self._sorted_crl, revoked[0] ) @@ -271,7 +256,7 @@ class _CertificateRevocationList(object): return x509._SIG_OIDS_TO_HASH[oid] except KeyError: raise UnsupportedAlgorithm( - "Signature algorithm OID:{0} not recognized".format(oid) + "Signature algorithm OID:{} not recognized".format(oid) ) @property @@ -366,13 +351,17 @@ class _CertificateRevocationList(object): @utils.cached_property def extensions(self): - return _CRL_EXTENSION_PARSER.parse(self._backend, self._x509_crl) + 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.') + 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 ) @@ -423,7 +412,7 @@ class _CertificateSigningRequest(object): return x509._SIG_OIDS_TO_HASH[oid] except KeyError: raise UnsupportedAlgorithm( - "Signature algorithm OID:{0} not recognized".format(oid) + "Signature algorithm OID:{} not recognized".format(oid) ) @property @@ -442,12 +431,13 @@ class _CertificateSigningRequest(object): x509_exts = self._backend._ffi.gc( x509_exts, lambda x: self._backend._lib.sk_X509_EXTENSION_pop_free( - x, self._backend._ffi.addressof( + x, + self._backend._ffi.addressof( self._backend._lib._original_lib, "X509_EXTENSION_free" - ) - ) + ), + ), ) - return _CSR_EXTENSION_PARSER.parse(self._backend, x509_exts) + return self._backend._csr_extension_parser.parse(x509_exts) def public_bytes(self, encoding): bio = self._backend._create_mem_bio_gc() @@ -495,6 +485,47 @@ class _CertificateSigningRequest(object): 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 @@ -523,9 +554,9 @@ class _SignedCertificateTimestamp(object): 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) + return datetime.datetime.utcfromtimestamp(timestamp // 1000).replace( + microsecond=milliseconds * 1000 + ) @property def entry_type(self): diff --git a/src/cryptography/hazmat/bindings/_constant_time.so b/src/cryptography/hazmat/bindings/_constant_time.so Binary files differdeleted file mode 100644 index 6a3435955..000000000 --- a/src/cryptography/hazmat/bindings/_constant_time.so +++ /dev/null diff --git a/src/cryptography/hazmat/bindings/_openssl.so b/src/cryptography/hazmat/bindings/_openssl.so Binary files differindex d896a793b..6551c6a15 100644..100755 --- a/src/cryptography/hazmat/bindings/_openssl.so +++ b/src/cryptography/hazmat/bindings/_openssl.so diff --git a/src/cryptography/hazmat/bindings/_padding.so b/src/cryptography/hazmat/bindings/_padding.so Binary files differindex a27d09e58..5cdc438f0 100644..100755 --- a/src/cryptography/hazmat/bindings/_padding.so +++ b/src/cryptography/hazmat/bindings/_padding.so diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index c0238dcc2..ca50fed13 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -13,24 +13,6 @@ def cryptography_has_ec2m(): ] -def cryptography_has_ec_1_0_2(): - return [ - "EC_curve_nid2nist", - ] - - -def cryptography_has_set_ecdh_auto(): - return [ - "SSL_CTX_set_ecdh_auto", - ] - - -def cryptography_has_rsa_r_pkcs_decoding_error(): - return [ - "RSA_R_PKCS_DECODING_ERROR" - ] - - def cryptography_has_rsa_oaep_md(): return [ "EVP_PKEY_CTX_set_rsa_oaep_md", @@ -51,78 +33,22 @@ def cryptography_has_ssl3_method(): ] -def cryptography_has_alpn(): - return [ - "SSL_CTX_set_alpn_protos", - "SSL_set_alpn_protos", - "SSL_CTX_set_alpn_select_cb", - "SSL_get0_alpn_selected", - ] - - -def cryptography_has_compression(): - return [ - "SSL_get_current_compression", - "SSL_get_current_expansion", - "SSL_COMP_get_name", - ] - - -def cryptography_has_get_server_tmp_key(): - return [ - "SSL_get_server_tmp_key", - ] - - -def cryptography_has_102_verification_error_codes(): - return [ - 'X509_V_ERR_SUITE_B_INVALID_VERSION', - 'X509_V_ERR_SUITE_B_INVALID_ALGORITHM', - 'X509_V_ERR_SUITE_B_INVALID_CURVE', - 'X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM', - 'X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED', - 'X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256', - 'X509_V_ERR_HOSTNAME_MISMATCH', - 'X509_V_ERR_EMAIL_MISMATCH', - 'X509_V_ERR_IP_ADDRESS_MISMATCH' - ] - - -def cryptography_has_102_verification_params(): +def cryptography_has_102_verification(): return [ + "X509_V_ERR_SUITE_B_INVALID_VERSION", + "X509_V_ERR_SUITE_B_INVALID_ALGORITHM", + "X509_V_ERR_SUITE_B_INVALID_CURVE", + "X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM", + "X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED", + "X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256", "X509_V_FLAG_SUITEB_128_LOS_ONLY", "X509_V_FLAG_SUITEB_192_LOS", "X509_V_FLAG_SUITEB_128_LOS", - "X509_VERIFY_PARAM_set1_host", - "X509_VERIFY_PARAM_set1_email", - "X509_VERIFY_PARAM_set1_ip", - "X509_VERIFY_PARAM_set1_ip_asc", - "X509_VERIFY_PARAM_set_hostflags", - "SSL_get0_param", - "X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT", - "X509_CHECK_FLAG_NO_WILDCARDS", - "X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS", - "X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS", - "X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS" ] def cryptography_has_110_verification_params(): - return [ - "X509_CHECK_FLAG_NEVER_CHECK_SUBJECT" - ] - - -def cryptography_has_x509_v_flag_trusted_first(): - return [ - "X509_V_FLAG_TRUSTED_FIRST", - ] - - -def cryptography_has_x509_v_flag_partial_chain(): - return [ - "X509_V_FLAG_PARTIAL_CHAIN", - ] + return ["X509_CHECK_FLAG_NEVER_CHECK_SUBJECT"] def cryptography_has_set_cert_cb(): @@ -148,35 +74,12 @@ def cryptography_has_tls_st(): ] -def cryptography_has_locking_callbacks(): - return [ - "CRYPTO_LOCK", - "CRYPTO_UNLOCK", - "CRYPTO_READ", - "CRYPTO_LOCK_SSL", - "CRYPTO_lock", - "Cryptography_setup_ssl_threads", - ] - - def cryptography_has_scrypt(): return [ "EVP_PBE_scrypt", ] -def cryptography_has_generic_dtls_method(): - return [ - "DTLS_method", - "DTLS_server_method", - "DTLS_client_method", - "SSL_OP_NO_DTLSv1", - "SSL_OP_NO_DTLSv1_2", - "DTLS_set_link_mtu", - "DTLS_get_link_min_mtu", - ] - - def cryptography_has_evp_pkey_dhx(): return [ "EVP_PKEY_DHX", @@ -197,11 +100,12 @@ def cryptography_has_sct(): "SCT_get0_signature", "SCT_get_timestamp", "SCT_set_source", + "sk_SCT_new_null", + "sk_SCT_free", "sk_SCT_num", "sk_SCT_value", - "SCT_LIST_free", "sk_SCT_push", - "sk_SCT_new_null", + "SCT_LIST_free", "SCT_new", "SCT_set1_log_id", "SCT_set_timestamp", @@ -217,20 +121,6 @@ def cryptography_has_x509_store_ctx_get_issuer(): ] -def cryptography_has_x25519(): - return [ - "EVP_PKEY_X25519", - "NID_X25519", - ] - - -def cryptography_has_x448(): - return [ - "EVP_PKEY_X448", - "NID_X448", - ] - - def cryptography_has_ed448(): return [ "EVP_PKEY_ED448", @@ -245,6 +135,13 @@ def cryptography_has_ed25519(): ] +def cryptography_has_poly1305(): + return [ + "NID_poly1305", + "EVP_PKEY_POLY1305", + ] + + def cryptography_has_oneshot_evp_digest_sign_verify(): return [ "EVP_DigestSign", @@ -267,7 +164,7 @@ def cryptography_has_evp_pkey_get_set_tls_encodedpoint(): def cryptography_has_fips(): return [ - "FIPS_set_mode", + "FIPS_mode_set", "FIPS_mode", ] @@ -301,16 +198,6 @@ def cryptography_has_openssl_cleanup(): ] -def cryptography_has_cipher_details(): - return [ - "SSL_CIPHER_is_aead", - "SSL_CIPHER_get_cipher_nid", - "SSL_CIPHER_get_digest_nid", - "SSL_CIPHER_get_kx_nid", - "SSL_CIPHER_get_auth_nid", - ] - - def cryptography_has_tlsv13(): return [ "SSL_OP_NO_TLSv1_3", @@ -326,6 +213,13 @@ def cryptography_has_tlsv13(): ] +def cryptography_has_keylog(): + return [ + "SSL_CTX_set_keylog_callback", + "SSL_CTX_get_keylog_callback", + ] + + def cryptography_has_raw_key(): return [ "EVP_PKEY_new_raw_private_key", @@ -335,9 +229,45 @@ def cryptography_has_raw_key(): ] -def cryptography_has_evp_r_memory_limit_exceeded(): +def cryptography_has_engine(): + return [ + "ENGINE_by_id", + "ENGINE_init", + "ENGINE_finish", + "ENGINE_get_default_RAND", + "ENGINE_set_default_RAND", + "ENGINE_unregister_RAND", + "ENGINE_ctrl_cmd", + "ENGINE_free", + "ENGINE_get_name", + "Cryptography_add_osrandom_engine", + "ENGINE_ctrl_cmd_string", + "ENGINE_load_builtin_engines", + "ENGINE_load_private_key", + "ENGINE_load_public_key", + ] + + +def cryptography_has_verified_chain(): + return [ + "SSL_get0_verified_chain", + ] + + +def cryptography_has_srtp(): return [ - "EVP_R_MEMORY_LIMIT_EXCEEDED", + "SSL_CTX_set_tlsext_use_srtp", + "SSL_set_tlsext_use_srtp", + "SSL_get_selected_srtp_profile", + ] + + +def cryptography_has_get_proto_version(): + return [ + "SSL_CTX_get_min_proto_version", + "SSL_CTX_get_max_proto_version", + "SSL_get_min_proto_version", + "SSL_get_max_proto_version", ] @@ -348,50 +278,26 @@ def cryptography_has_evp_r_memory_limit_exceeded(): # lists so we can use coverage to measure which are used. CONDITIONAL_NAMES = { "Cryptography_HAS_EC2M": cryptography_has_ec2m, - "Cryptography_HAS_EC_1_0_2": cryptography_has_ec_1_0_2, - "Cryptography_HAS_SET_ECDH_AUTO": cryptography_has_set_ecdh_auto, - "Cryptography_HAS_RSA_R_PKCS_DECODING_ERROR": ( - cryptography_has_rsa_r_pkcs_decoding_error - ), "Cryptography_HAS_RSA_OAEP_MD": cryptography_has_rsa_oaep_md, "Cryptography_HAS_RSA_OAEP_LABEL": cryptography_has_rsa_oaep_label, "Cryptography_HAS_SSL3_METHOD": cryptography_has_ssl3_method, - "Cryptography_HAS_ALPN": cryptography_has_alpn, - "Cryptography_HAS_COMPRESSION": cryptography_has_compression, - "Cryptography_HAS_GET_SERVER_TMP_KEY": cryptography_has_get_server_tmp_key, - "Cryptography_HAS_102_VERIFICATION_ERROR_CODES": ( - cryptography_has_102_verification_error_codes - ), - "Cryptography_HAS_102_VERIFICATION_PARAMS": ( - cryptography_has_102_verification_params - ), + "Cryptography_HAS_102_VERIFICATION": cryptography_has_102_verification, "Cryptography_HAS_110_VERIFICATION_PARAMS": ( cryptography_has_110_verification_params ), - "Cryptography_HAS_X509_V_FLAG_TRUSTED_FIRST": ( - cryptography_has_x509_v_flag_trusted_first - ), - "Cryptography_HAS_X509_V_FLAG_PARTIAL_CHAIN": ( - cryptography_has_x509_v_flag_partial_chain - ), "Cryptography_HAS_SET_CERT_CB": cryptography_has_set_cert_cb, "Cryptography_HAS_SSL_ST": cryptography_has_ssl_st, "Cryptography_HAS_TLS_ST": cryptography_has_tls_st, - "Cryptography_HAS_LOCKING_CALLBACKS": cryptography_has_locking_callbacks, "Cryptography_HAS_SCRYPT": cryptography_has_scrypt, - "Cryptography_HAS_GENERIC_DTLS_METHOD": ( - cryptography_has_generic_dtls_method - ), "Cryptography_HAS_EVP_PKEY_DHX": cryptography_has_evp_pkey_dhx, "Cryptography_HAS_MEM_FUNCTIONS": cryptography_has_mem_functions, "Cryptography_HAS_SCT": cryptography_has_sct, "Cryptography_HAS_X509_STORE_CTX_GET_ISSUER": ( cryptography_has_x509_store_ctx_get_issuer ), - "Cryptography_HAS_X25519": cryptography_has_x25519, - "Cryptography_HAS_X448": cryptography_has_x448, "Cryptography_HAS_ED448": cryptography_has_ed448, "Cryptography_HAS_ED25519": cryptography_has_ed25519, + "Cryptography_HAS_POLY1305": cryptography_has_poly1305, "Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY": ( cryptography_has_oneshot_evp_digest_sign_verify ), @@ -403,13 +309,14 @@ CONDITIONAL_NAMES = { "Cryptography_HAS_PSK": cryptography_has_psk, "Cryptography_HAS_CUSTOM_EXT": cryptography_has_custom_ext, "Cryptography_HAS_OPENSSL_CLEANUP": cryptography_has_openssl_cleanup, - "Cryptography_HAS_CIPHER_DETAILS": cryptography_has_cipher_details, "Cryptography_HAS_TLSv1_3": cryptography_has_tlsv13, + "Cryptography_HAS_KEYLOG": cryptography_has_keylog, "Cryptography_HAS_RAW_KEY": cryptography_has_raw_key, "Cryptography_HAS_EVP_DIGESTFINAL_XOF": ( cryptography_has_evp_digestfinal_xof ), - "Cryptography_HAS_EVP_R_MEMORY_LIMIT_EXCEEDED": ( - cryptography_has_evp_r_memory_limit_exceeded - ), + "Cryptography_HAS_ENGINE": cryptography_has_engine, + "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, + "Cryptography_HAS_SRTP": cryptography_has_srtp, + "Cryptography_HAS_GET_PROTO_VERSION": cryptography_has_get_proto_version, } diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index 0824ea88c..7a84a340e 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -7,8 +7,8 @@ from __future__ import absolute_import, division, print_function import collections import threading import types -import warnings +import cryptography from cryptography import utils from cryptography.exceptions import InternalError from cryptography.hazmat.bindings._openssl import ffi, lib @@ -51,20 +51,31 @@ def _consume_errors(lib): return errors -def _openssl_assert(lib, ok): - if not ok: - errors = _consume_errors(lib) - errors_with_text = [] - for err in errors: - buf = ffi.new("char[]", 256) - lib.ERR_error_string_n(err.code, buf, len(buf)) - err_text_reason = ffi.string(buf) - - errors_with_text.append( - _OpenSSLErrorWithText( - err.code, err.lib, err.func, err.reason, err_text_reason - ) +def _errors_with_text(errors): + errors_with_text = [] + for err in errors: + buf = ffi.new("char[]", 256) + lib.ERR_error_string_n(err.code, buf, len(buf)) + err_text_reason = ffi.string(buf) + + errors_with_text.append( + _OpenSSLErrorWithText( + err.code, err.lib, err.func, err.reason, err_text_reason ) + ) + + return errors_with_text + + +def _consume_errors_with_text(lib): + return _errors_with_text(_consume_errors(lib)) + + +def _openssl_assert(lib, ok, errors=None): + if not ok: + if errors is None: + errors = _consume_errors(lib) + errors_with_text = _errors_with_text(errors) raise InternalError( "Unknown OpenSSL error. This error is commonly encountered when " @@ -74,7 +85,7 @@ def _openssl_assert(lib, ok): "please file an issue at https://github.com/pyca/cryptography/" "issues with information on how to reproduce " "this. ({0!r})".format(errors_with_text), - errors_with_text + errors_with_text, ) @@ -97,11 +108,11 @@ class Binding(object): """ OpenSSL API wrapper. """ + lib = None ffi = ffi _lib_loaded = False _init_lock = threading.Lock() - _lock_init_lock = threading.Lock() def __init__(self): self._ensure_ffi_initialized() @@ -114,10 +125,9 @@ class Binding(object): # reliably clear the error queue. Once we clear it here we will # error on any subsequent unexpected item in the stack. cls.lib.ERR_clear_error() - cls._osrandom_engine_id = cls.lib.Cryptography_osrandom_engine_id - cls._osrandom_engine_name = cls.lib.Cryptography_osrandom_engine_name - result = cls.lib.Cryptography_add_osrandom_engine() - _openssl_assert(cls.lib, result in (1, 2)) + if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: + result = cls.lib.Cryptography_add_osrandom_engine() + _openssl_assert(cls.lib, result in (1, 2)) @classmethod def _ensure_ffi_initialized(cls): @@ -129,46 +139,34 @@ class Binding(object): cls.lib.SSL_library_init() # adds all ciphers/digests for EVP cls.lib.OpenSSL_add_all_algorithms() - # loads error strings for libcrypto and libssl functions - cls.lib.SSL_load_error_strings() cls._register_osrandom_engine() @classmethod def init_static_locks(cls): - with cls._lock_init_lock: - cls._ensure_ffi_initialized() - # Use Python's implementation if available, importing _ssl triggers - # the setup for this. - __import__("_ssl") - - if (not cls.lib.Cryptography_HAS_LOCKING_CALLBACKS or - cls.lib.CRYPTO_get_locking_callback() != cls.ffi.NULL): - return - - # If nothing else has setup a locking callback already, we set up - # our own - res = lib.Cryptography_setup_ssl_threads() - _openssl_assert(cls.lib, res == 1) - - -def _verify_openssl_version(lib): - if ( - lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 and - not lib.CRYPTOGRAPHY_IS_LIBRESSL - ): - warnings.warn( - "OpenSSL version 1.0.1 is no longer supported by the OpenSSL " - "project, please upgrade. A future version of cryptography will " - "drop support for it.", - utils.CryptographyDeprecationWarning + cls._ensure_ffi_initialized() + + +def _verify_package_version(version): + # Occasionally we run into situations where the version of the Python + # package does not match the version of the shared object that is loaded. + # This may occur in environments where multiple versions of cryptography + # are installed and available in the python path. To avoid errors cropping + # up later this code checks that the currently imported package and the + # shared object that were loaded have the same version and raise an + # ImportError if they do not + so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION) + if version.encode("ascii") != so_package_version: + raise ImportError( + "The version of cryptography does not match the loaded " + "shared object. This can happen if you have multiple copies of " + "cryptography installed in your Python path. Please try creating " + "a new virtual environment to resolve this issue. " + "Loaded python version: {}, shared object version: {}".format( + version, so_package_version + ) ) -# OpenSSL is not thread safe until the locks are initialized. We call this -# method in module scope so that it executes with the import lock. On -# Pythons < 3.4 this import lock is a global lock, which can prevent a race -# 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() +_verify_package_version(cryptography.__version__) -_verify_openssl_version(Binding.lib) +Binding.init_static_locks() diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py index 4fc995245..74a311d50 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dh.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py @@ -9,9 +9,14 @@ import abc import six from cryptography import utils +from cryptography.hazmat.backends import _get_backend -def generate_parameters(generator, key_size, backend): +_MIN_MODULUS_SIZE = 512 + + +def generate_parameters(generator, key_size, backend=None): + backend = _get_backend(backend) return backend.generate_dh_parameters(generator, key_size) @@ -21,8 +26,9 @@ class DHPrivateNumbers(object): raise TypeError("x must be an integer.") if not isinstance(public_numbers, DHPublicNumbers): - raise TypeError("public_numbers must be an instance of " - "DHPublicNumbers.") + raise TypeError( + "public_numbers must be an instance of " "DHPublicNumbers." + ) self._x = x self._public_numbers = public_numbers @@ -32,14 +38,15 @@ class DHPrivateNumbers(object): return NotImplemented return ( - self._x == other._x and - self._public_numbers == other._public_numbers + self._x == other._x + and self._public_numbers == other._public_numbers ) def __ne__(self, other): return not self == other - def private_key(self, backend): + def private_key(self, backend=None): + backend = _get_backend(backend) return backend.load_dh_private_numbers(self) public_numbers = utils.read_only_property("_public_numbers") @@ -53,7 +60,8 @@ class DHPublicNumbers(object): if not isinstance(parameter_numbers, DHParameterNumbers): raise TypeError( - "parameters must be an instance of DHParameterNumbers.") + "parameters must be an instance of DHParameterNumbers." + ) self._y = y self._parameter_numbers = parameter_numbers @@ -63,14 +71,15 @@ class DHPublicNumbers(object): return NotImplemented return ( - self._y == other._y and - self._parameter_numbers == other._parameter_numbers + self._y == other._y + and self._parameter_numbers == other._parameter_numbers ) def __ne__(self, other): return not self == other - def public_key(self, backend): + def public_key(self, backend=None): + backend = _get_backend(backend) return backend.load_dh_public_numbers(self) y = utils.read_only_property("_y") @@ -79,9 +88,8 @@ class DHPublicNumbers(object): class DHParameterNumbers(object): def __init__(self, p, g, q=None): - if ( - not isinstance(p, six.integer_types) or - not isinstance(g, six.integer_types) + if not isinstance(p, six.integer_types) or not isinstance( + g, six.integer_types ): raise TypeError("p and g must be integers") if q is not None and not isinstance(q, six.integer_types): @@ -90,6 +98,11 @@ class DHParameterNumbers(object): if g < 2: raise ValueError("DH generator must be 2 or greater") + if p.bit_length() < _MIN_MODULUS_SIZE: + raise ValueError( + "p (modulus) must be at least {}-bit".format(_MIN_MODULUS_SIZE) + ) + self._p = p self._g = g self._q = q @@ -99,15 +112,14 @@ class DHParameterNumbers(object): return NotImplemented return ( - self._p == other._p and - self._g == other._g and - self._q == other._q + self._p == other._p and self._g == other._g and self._q == other._q ) def __ne__(self, other): return not self == other - def parameters(self, backend): + def parameters(self, backend=None): + backend = _get_backend(backend) return backend.load_dh_parameter_numbers(self) p = utils.read_only_property("_p") diff --git a/src/cryptography/hazmat/primitives/asymmetric/dsa.py b/src/cryptography/hazmat/primitives/asymmetric/dsa.py index e380a441f..8ccc66665 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -9,6 +9,7 @@ import abc import six from cryptography import utils +from cryptography.hazmat.backends import _get_backend @six.add_metaclass(abc.ABCMeta) @@ -119,17 +120,21 @@ class DSAPublicKey(object): DSAPublicKeyWithSerialization = DSAPublicKey -def generate_parameters(key_size, backend): +def generate_parameters(key_size, backend=None): + backend = _get_backend(backend) return backend.generate_dsa_parameters(key_size) -def generate_private_key(key_size, backend): +def generate_private_key(key_size, backend=None): + backend = _get_backend(backend) return backend.generate_dsa_private_key_and_parameters(key_size) def _check_dsa_parameters(parameters): - if parameters.p.bit_length() not in [1024, 2048, 3072]: - raise ValueError("p must be exactly 1024, 2048, or 3072 bits long") + if parameters.p.bit_length() not in [1024, 2048, 3072, 4096]: + raise ValueError( + "p must be exactly 1024, 2048, 3072, or 4096 bits long" + ) if parameters.q.bit_length() not in [160, 224, 256]: raise ValueError("q must be exactly 160, 224, or 256 bits long") @@ -150,9 +155,9 @@ def _check_dsa_private_numbers(numbers): class DSAParameterNumbers(object): def __init__(self, p, q, g): if ( - not isinstance(p, six.integer_types) or - not isinstance(q, six.integer_types) or - not isinstance(g, six.integer_types) + not isinstance(p, six.integer_types) + or not isinstance(q, six.integer_types) + or not isinstance(g, six.integer_types) ): raise TypeError( "DSAParameterNumbers p, q, and g arguments must be integers." @@ -166,7 +171,8 @@ class DSAParameterNumbers(object): q = utils.read_only_property("_q") g = utils.read_only_property("_g") - def parameters(self, backend): + def parameters(self, backend=None): + backend = _get_backend(backend) return backend.load_dsa_parameter_numbers(self) def __eq__(self, other): @@ -180,9 +186,8 @@ class DSAParameterNumbers(object): def __repr__(self): return ( - "<DSAParameterNumbers(p={self.p}, q={self.q}, g={self.g})>".format( - self=self - ) + "<DSAParameterNumbers(p={self.p}, q={self.q}, " + "g={self.g})>".format(self=self) ) @@ -202,7 +207,8 @@ class DSAPublicNumbers(object): y = utils.read_only_property("_y") parameter_numbers = utils.read_only_property("_parameter_numbers") - def public_key(self, backend): + def public_key(self, backend=None): + backend = _get_backend(backend) return backend.load_dsa_public_numbers(self) def __eq__(self, other): @@ -210,8 +216,8 @@ class DSAPublicNumbers(object): return NotImplemented return ( - self.y == other.y and - self.parameter_numbers == other.parameter_numbers + self.y == other.y + and self.parameter_numbers == other.parameter_numbers ) def __ne__(self, other): @@ -239,7 +245,8 @@ class DSAPrivateNumbers(object): x = utils.read_only_property("_x") public_numbers = utils.read_only_property("_public_numbers") - def private_key(self, backend): + def private_key(self, backend=None): + backend = _get_backend(backend) return backend.load_dsa_private_numbers(self) def __eq__(self, other): diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index 1de0976a6..c7e694fc5 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -11,6 +11,7 @@ import six from cryptography import utils from cryptography.hazmat._oid import ObjectIdentifier +from cryptography.hazmat.backends import _get_backend class EllipticCurveOID(object): @@ -166,6 +167,7 @@ class EllipticCurvePublicKey(object): raise ValueError("Unsupported elliptic curve point type") from cryptography.hazmat.backends.openssl.backend import backend + return backend.load_elliptic_curve_public_bytes(curve, data) @@ -289,26 +291,22 @@ class BrainpoolP512R1(object): _CURVE_TYPES = { "prime192v1": SECP192R1, "prime256v1": SECP256R1, - "secp192r1": SECP192R1, "secp224r1": SECP224R1, "secp256r1": SECP256R1, "secp384r1": SECP384R1, "secp521r1": SECP521R1, "secp256k1": SECP256K1, - "sect163k1": SECT163K1, "sect233k1": SECT233K1, "sect283k1": SECT283K1, "sect409k1": SECT409K1, "sect571k1": SECT571K1, - "sect163r2": SECT163R2, "sect233r1": SECT233R1, "sect283r1": SECT283R1, "sect409r1": SECT409R1, "sect571r1": SECT571R1, - "brainpoolP256r1": BrainpoolP256R1, "brainpoolP384r1": BrainpoolP384R1, "brainpoolP512r1": BrainpoolP512R1, @@ -323,11 +321,13 @@ class ECDSA(object): algorithm = utils.read_only_property("_algorithm") -def generate_private_key(curve, backend): +def generate_private_key(curve, backend=None): + backend = _get_backend(backend) return backend.generate_elliptic_curve_private_key(curve) -def derive_private_key(private_value, curve, backend): +def derive_private_key(private_value, curve, backend=None): + backend = _get_backend(backend) if not isinstance(private_value, six.integer_types): raise TypeError("private_value must be an integer type.") @@ -342,9 +342,8 @@ def derive_private_key(private_value, curve, backend): class EllipticCurvePublicNumbers(object): def __init__(self, x, y, curve): - if ( - not isinstance(x, six.integer_types) or - not isinstance(y, six.integer_types) + if not isinstance(x, six.integer_types) or not isinstance( + y, six.integer_types ): raise TypeError("x and y must be integers.") @@ -355,7 +354,8 @@ class EllipticCurvePublicNumbers(object): self._x = x self._curve = curve - def public_key(self, backend): + def public_key(self, backend=None): + backend = _get_backend(backend) return backend.load_elliptic_curve_public_numbers(self) def encode_point(self): @@ -364,14 +364,15 @@ class EllipticCurvePublicNumbers(object): " and will be removed in a future version. Please use " "EllipticCurvePublicKey.public_bytes to obtain both " "compressed and uncompressed point encoding.", - utils.DeprecatedIn25, + utils.PersistentlyDeprecated2019, stacklevel=2, ) # key_size is in bits. Convert to bytes and round up byte_length = (self.curve.key_size + 7) // 8 return ( - b'\x04' + utils.int_to_bytes(self.x, byte_length) + - utils.int_to_bytes(self.y, byte_length) + b"\x04" + + utils.int_to_bytes(self.x, byte_length) + + utils.int_to_bytes(self.y, byte_length) ) @classmethod @@ -383,21 +384,21 @@ class EllipticCurvePublicNumbers(object): "Support for unsafe construction of public numbers from " "encoded data will be removed in a future version. " "Please use EllipticCurvePublicKey.from_encoded_point", - utils.DeprecatedIn25, + utils.PersistentlyDeprecated2019, stacklevel=2, ) - if data.startswith(b'\x04'): + if data.startswith(b"\x04"): # key_size is in bits. Convert to bytes and round up byte_length = (curve.key_size + 7) // 8 if len(data) == 2 * byte_length + 1: - x = utils.int_from_bytes(data[1:byte_length + 1], 'big') - y = utils.int_from_bytes(data[byte_length + 1:], 'big') + x = utils.int_from_bytes(data[1 : byte_length + 1], "big") + y = utils.int_from_bytes(data[byte_length + 1 :], "big") return cls(x, y, curve) else: - raise ValueError('Invalid elliptic curve point data length') + raise ValueError("Invalid elliptic curve point data length") else: - raise ValueError('Unsupported elliptic curve point type') + raise ValueError("Unsupported elliptic curve point type") curve = utils.read_only_property("_curve") x = utils.read_only_property("_x") @@ -408,10 +409,10 @@ class EllipticCurvePublicNumbers(object): return NotImplemented return ( - self.x == other.x and - self.y == other.y and - self.curve.name == other.curve.name and - self.curve.key_size == other.curve.key_size + self.x == other.x + and self.y == other.y + and self.curve.name == other.curve.name + and self.curve.key_size == other.curve.key_size ) def __ne__(self, other): @@ -441,7 +442,8 @@ class EllipticCurvePrivateNumbers(object): self._private_value = private_value self._public_numbers = public_numbers - def private_key(self, backend): + def private_key(self, backend=None): + backend = _get_backend(backend) return backend.load_elliptic_curve_private_numbers(self) private_value = utils.read_only_property("_private_value") @@ -452,8 +454,8 @@ class EllipticCurvePrivateNumbers(object): return NotImplemented return ( - self.private_value == other.private_value and - self.public_numbers == other.public_numbers + self.private_value == other.private_value + and self.public_numbers == other.public_numbers ) def __ne__(self, other): @@ -465,3 +467,36 @@ class EllipticCurvePrivateNumbers(object): class ECDH(object): pass + + +_OID_TO_CURVE = { + EllipticCurveOID.SECP192R1: SECP192R1, + EllipticCurveOID.SECP224R1: SECP224R1, + EllipticCurveOID.SECP256K1: SECP256K1, + EllipticCurveOID.SECP256R1: SECP256R1, + EllipticCurveOID.SECP384R1: SECP384R1, + EllipticCurveOID.SECP521R1: SECP521R1, + EllipticCurveOID.BRAINPOOLP256R1: BrainpoolP256R1, + EllipticCurveOID.BRAINPOOLP384R1: BrainpoolP384R1, + EllipticCurveOID.BRAINPOOLP512R1: BrainpoolP512R1, + EllipticCurveOID.SECT163K1: SECT163K1, + EllipticCurveOID.SECT163R2: SECT163R2, + EllipticCurveOID.SECT233K1: SECT233K1, + EllipticCurveOID.SECT233R1: SECT233R1, + EllipticCurveOID.SECT283K1: SECT283K1, + EllipticCurveOID.SECT283R1: SECT283R1, + EllipticCurveOID.SECT409K1: SECT409K1, + EllipticCurveOID.SECT409R1: SECT409R1, + EllipticCurveOID.SECT571K1: SECT571K1, + EllipticCurveOID.SECT571R1: SECT571R1, +} + + +def get_curve_for_oid(oid): + try: + return _OID_TO_CURVE[oid] + except KeyError: + raise LookupError( + "The provided object identifier has no matching elliptic " + "curve class" + ) diff --git a/src/cryptography/hazmat/primitives/asymmetric/ed25519.py b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py new file mode 100644 index 000000000..2d07a029b --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/ed25519.py @@ -0,0 +1,87 @@ +# 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 abc + +import six + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons + + +_ED25519_KEY_SIZE = 32 +_ED25519_SIG_SIZE = 64 + + +@six.add_metaclass(abc.ABCMeta) +class Ed25519PublicKey(object): + @classmethod + def from_public_bytes(cls, data): + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed25519_supported(): + raise UnsupportedAlgorithm( + "ed25519 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return backend.ed25519_load_public_bytes(data) + + @abc.abstractmethod + def public_bytes(self, encoding, format): + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def verify(self, signature, data): + """ + Verify the signature. + """ + + +@six.add_metaclass(abc.ABCMeta) +class Ed25519PrivateKey(object): + @classmethod + def generate(cls): + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed25519_supported(): + raise UnsupportedAlgorithm( + "ed25519 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return backend.ed25519_generate_key() + + @classmethod + def from_private_bytes(cls, data): + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed25519_supported(): + raise UnsupportedAlgorithm( + "ed25519 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return backend.ed25519_load_private_bytes(data) + + @abc.abstractmethod + def public_key(self): + """ + The Ed25519PublicKey derived from the private key. + """ + + @abc.abstractmethod + def private_bytes(self, encoding, format, encryption_algorithm): + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def sign(self, data): + """ + Signs the data. + """ diff --git a/src/cryptography/hazmat/primitives/asymmetric/ed448.py b/src/cryptography/hazmat/primitives/asymmetric/ed448.py new file mode 100644 index 000000000..520ffcbcb --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/ed448.py @@ -0,0 +1,82 @@ +# 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 abc + +import six + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons + + +@six.add_metaclass(abc.ABCMeta) +class Ed448PublicKey(object): + @classmethod + def from_public_bytes(cls, data): + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed448_supported(): + raise UnsupportedAlgorithm( + "ed448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return backend.ed448_load_public_bytes(data) + + @abc.abstractmethod + def public_bytes(self, encoding, format): + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def verify(self, signature, data): + """ + Verify the signature. + """ + + +@six.add_metaclass(abc.ABCMeta) +class Ed448PrivateKey(object): + @classmethod + def generate(cls): + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed448_supported(): + raise UnsupportedAlgorithm( + "ed448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + return backend.ed448_generate_key() + + @classmethod + def from_private_bytes(cls, data): + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed448_supported(): + raise UnsupportedAlgorithm( + "ed448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return backend.ed448_load_private_bytes(data) + + @abc.abstractmethod + def public_key(self): + """ + The Ed448PublicKey derived from the private key. + """ + + @abc.abstractmethod + def sign(self, data): + """ + Signs the data. + """ + + @abc.abstractmethod + def private_bytes(self, encoding, format, encryption_algorithm): + """ + The serialized bytes of the private key. + """ diff --git a/src/cryptography/hazmat/primitives/asymmetric/padding.py b/src/cryptography/hazmat/primitives/asymmetric/padding.py index a37c3f90c..fc8f6e26a 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/src/cryptography/hazmat/primitives/asymmetric/padding.py @@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function import abc -import math import six @@ -36,8 +35,10 @@ class PSS(object): def __init__(self, mgf, salt_length): self._mgf = mgf - if (not isinstance(salt_length, six.integer_types) and - salt_length is not self.MAX_LENGTH): + if ( + not isinstance(salt_length, six.integer_types) + and salt_length is not self.MAX_LENGTH + ): raise TypeError("salt_length must be an integer.") if salt_length is not self.MAX_LENGTH and salt_length < 0: @@ -73,7 +74,7 @@ def calculate_max_pss_salt_length(key, hash_algorithm): if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): raise TypeError("key must be an RSA public or private key") # bit length - 1 per RFC 3447 - emlen = int(math.ceil((key.key_size - 1) / 8.0)) + emlen = (key.key_size + 6) // 8 salt_length = emlen - hash_algorithm.digest_size - 2 assert salt_length >= 0 return salt_length diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index 27db671af..ea16bbf66 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function import abc + try: # Only available in math in 3.5+ from math import gcd @@ -15,6 +16,7 @@ import six from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import RSABackend @@ -104,15 +106,22 @@ class RSAPublicKey(object): Verifies the signature of the data. """ + @abc.abstractmethod + def recover_data_from_signature(self, signature, padding, algorithm): + """ + Recovers the original data from the signature. + """ + RSAPublicKeyWithSerialization = RSAPublicKey -def generate_private_key(public_exponent, key_size, backend): +def generate_private_key(public_exponent, key_size, backend=None): + backend = _get_backend(backend) if not isinstance(backend, RSABackend): raise UnsupportedAlgorithm( "Backend object does not implement RSABackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) _verify_rsa_parameters(public_exponent, key_size) @@ -120,18 +129,19 @@ def generate_private_key(public_exponent, key_size, backend): def _verify_rsa_parameters(public_exponent, key_size): - if public_exponent < 3: - raise ValueError("public_exponent must be >= 3.") - - if public_exponent & 1 == 0: - raise ValueError("public_exponent must be odd.") + if public_exponent not in (3, 65537): + raise ValueError( + "public_exponent must be either 3 (for legacy compatibility) or " + "65537. Almost everyone should choose 65537 here!" + ) if key_size < 512: raise ValueError("key_size must be at least 512-bits.") -def _check_private_key_components(p, q, private_exponent, dmp1, dmq1, iqmp, - public_exponent, modulus): +def _check_private_key_components( + p, q, private_exponent, dmp1, dmq1, iqmp, public_exponent, modulus +): if modulus < 3: raise ValueError("modulus must be >= 3.") @@ -184,12 +194,12 @@ def _modinv(e, m): """ Modular Multiplicative Inverse. Returns x such that: (x*e) mod m == 1 """ - x1, y1, x2, y2 = 1, 0, 0, 1 + x1, x2 = 1, 0 a, b = e, m while b > 0: q, r = divmod(a, b) - xn, yn = x1 - q * x2, y1 - q * y2 - a, b, x1, y1, x2, y2 = b, r, x2, y2, xn, yn + xn = x1 - q * x2 + a, b, x1, x2 = b, r, x2, xn return x1 % m @@ -266,15 +276,14 @@ def rsa_recover_prime_factors(n, e, d): class RSAPrivateNumbers(object): - def __init__(self, p, q, d, dmp1, dmq1, iqmp, - public_numbers): + def __init__(self, p, q, d, dmp1, dmq1, iqmp, public_numbers): if ( - not isinstance(p, six.integer_types) or - not isinstance(q, six.integer_types) or - not isinstance(d, six.integer_types) or - not isinstance(dmp1, six.integer_types) or - not isinstance(dmq1, six.integer_types) or - not isinstance(iqmp, six.integer_types) + not isinstance(p, six.integer_types) + or not isinstance(q, six.integer_types) + or not isinstance(d, six.integer_types) + or not isinstance(dmp1, six.integer_types) + or not isinstance(dmq1, six.integer_types) + or not isinstance(iqmp, six.integer_types) ): raise TypeError( "RSAPrivateNumbers p, q, d, dmp1, dmq1, iqmp arguments must" @@ -303,7 +312,8 @@ class RSAPrivateNumbers(object): iqmp = utils.read_only_property("_iqmp") public_numbers = utils.read_only_property("_public_numbers") - def private_key(self, backend): + def private_key(self, backend=None): + backend = _get_backend(backend) return backend.load_rsa_private_numbers(self) def __eq__(self, other): @@ -311,35 +321,36 @@ class RSAPrivateNumbers(object): return NotImplemented return ( - self.p == other.p and - self.q == other.q and - self.d == other.d and - self.dmp1 == other.dmp1 and - self.dmq1 == other.dmq1 and - self.iqmp == other.iqmp and - self.public_numbers == other.public_numbers + self.p == other.p + and self.q == other.q + and self.d == other.d + and self.dmp1 == other.dmp1 + and self.dmq1 == other.dmq1 + and self.iqmp == other.iqmp + and self.public_numbers == other.public_numbers ) def __ne__(self, other): return not self == other def __hash__(self): - return hash(( - self.p, - self.q, - self.d, - self.dmp1, - self.dmq1, - self.iqmp, - self.public_numbers, - )) + return hash( + ( + self.p, + self.q, + self.d, + self.dmp1, + self.dmq1, + self.iqmp, + self.public_numbers, + ) + ) class RSAPublicNumbers(object): def __init__(self, e, n): - if ( - not isinstance(e, six.integer_types) or - not isinstance(n, six.integer_types) + if not isinstance(e, six.integer_types) or not isinstance( + n, six.integer_types ): raise TypeError("RSAPublicNumbers arguments must be integers.") @@ -349,7 +360,8 @@ class RSAPublicNumbers(object): e = utils.read_only_property("_e") n = utils.read_only_property("_n") - def public_key(self, backend): + def public_key(self, backend=None): + backend = _get_backend(backend) return backend.load_rsa_public_numbers(self) def __repr__(self): diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py index ef1e7eb92..5f9b67786 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/utils.py +++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py @@ -4,49 +4,30 @@ from __future__ import absolute_import, division, print_function -import warnings - -from asn1crypto.algos import DSASignature - -import six - from cryptography import utils +from cryptography.hazmat._der import ( + DERReader, + INTEGER, + SEQUENCE, + encode_der, + encode_der_integer, +) from cryptography.hazmat.primitives import hashes -def decode_rfc6979_signature(signature): - warnings.warn( - "decode_rfc6979_signature is deprecated and will " - "be removed in a future version, use decode_dss_signature instead.", - utils.PersistentlyDeprecated, - stacklevel=2 - ) - return decode_dss_signature(signature) - - def decode_dss_signature(signature): - data = DSASignature.load(signature, strict=True).native - return data['r'], data['s'] - - -def encode_rfc6979_signature(r, s): - warnings.warn( - "encode_rfc6979_signature is deprecated and will " - "be removed in a future version, use encode_dss_signature instead.", - utils.PersistentlyDeprecated, - stacklevel=2 - ) - return encode_dss_signature(r, s) + with DERReader(signature).read_single_element(SEQUENCE) as seq: + r = seq.read_element(INTEGER).as_integer() + s = seq.read_element(INTEGER).as_integer() + return r, s def encode_dss_signature(r, s): - if ( - not isinstance(r, six.integer_types) or - not isinstance(s, six.integer_types) - ): - raise ValueError("Both r and s must be integers") - - return DSASignature({'r': r, 's': s}).dump() + return encode_der( + SEQUENCE, + encode_der(INTEGER, encode_der_integer(r)), + encode_der(INTEGER, encode_der_integer(s)), + ) class Prehashed(object): diff --git a/src/cryptography/hazmat/primitives/asymmetric/x25519.py b/src/cryptography/hazmat/primitives/asymmetric/x25519.py index 4e8badf43..fc6369153 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x25519.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x25519.py @@ -16,16 +16,17 @@ class X25519PublicKey(object): @classmethod def from_public_bytes(cls, data): from cryptography.hazmat.backends.openssl.backend import backend + if not backend.x25519_supported(): raise UnsupportedAlgorithm( "X25519 is not supported by this version of OpenSSL.", - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return backend.x25519_load_public_bytes(data) @abc.abstractmethod - def public_bytes(self, encoding=None, format=None): + def public_bytes(self, encoding, format): """ The serialized bytes of the public key. """ @@ -36,20 +37,22 @@ class X25519PrivateKey(object): @classmethod def generate(cls): from cryptography.hazmat.backends.openssl.backend import backend + if not backend.x25519_supported(): raise UnsupportedAlgorithm( "X25519 is not supported by this version of OpenSSL.", - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return backend.x25519_generate_key() @classmethod def from_private_bytes(cls, data): from cryptography.hazmat.backends.openssl.backend import backend + if not backend.x25519_supported(): raise UnsupportedAlgorithm( "X25519 is not supported by this version of OpenSSL.", - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return backend.x25519_load_private_bytes(data) diff --git a/src/cryptography/hazmat/primitives/asymmetric/x448.py b/src/cryptography/hazmat/primitives/asymmetric/x448.py index 475e678ff..3ac067bfd 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x448.py @@ -16,10 +16,11 @@ class X448PublicKey(object): @classmethod def from_public_bytes(cls, data): from cryptography.hazmat.backends.openssl.backend import backend + if not backend.x448_supported(): raise UnsupportedAlgorithm( "X448 is not supported by this version of OpenSSL.", - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return backend.x448_load_public_bytes(data) @@ -36,20 +37,22 @@ class X448PrivateKey(object): @classmethod def generate(cls): from cryptography.hazmat.backends.openssl.backend import backend + if not backend.x448_supported(): raise UnsupportedAlgorithm( "X448 is not supported by this version of OpenSSL.", - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return backend.x448_generate_key() @classmethod def from_private_bytes(cls, data): from cryptography.hazmat.backends.openssl.backend import backend + if not backend.x448_supported(): raise UnsupportedAlgorithm( "X448 is not supported by this version of OpenSSL.", - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, ) return backend.x448_load_private_bytes(data) diff --git a/src/cryptography/hazmat/primitives/ciphers/__init__.py b/src/cryptography/hazmat/primitives/ciphers/__init__.py index 171b1c693..4380f72b2 100644 --- a/src/cryptography/hazmat/primitives/ciphers/__init__.py +++ b/src/cryptography/hazmat/primitives/ciphers/__init__.py @@ -5,8 +5,13 @@ from __future__ import absolute_import, division, print_function from cryptography.hazmat.primitives.ciphers.base import ( - AEADCipherContext, AEADDecryptionContext, AEADEncryptionContext, - BlockCipherAlgorithm, Cipher, CipherAlgorithm, CipherContext + AEADCipherContext, + AEADDecryptionContext, + AEADEncryptionContext, + BlockCipherAlgorithm, + Cipher, + CipherAlgorithm, + CipherContext, ) diff --git a/src/cryptography/hazmat/primitives/ciphers/aead.py b/src/cryptography/hazmat/primitives/ciphers/aead.py index 42e19adb1..c8c93955c 100644 --- a/src/cryptography/hazmat/primitives/ciphers/aead.py +++ b/src/cryptography/hazmat/primitives/ciphers/aead.py @@ -18,7 +18,7 @@ class ChaCha20Poly1305(object): if not backend.aead_cipher_supported(self): raise exceptions.UnsupportedAlgorithm( "ChaCha20Poly1305 is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER + exceptions._Reasons.UNSUPPORTED_CIPHER, ) utils._check_byteslike("key", key) @@ -42,18 +42,14 @@ class ChaCha20Poly1305(object): ) self._check_params(nonce, data, associated_data) - return aead._encrypt( - backend, self, nonce, data, associated_data, 16 - ) + return aead._encrypt(backend, self, nonce, data, associated_data, 16) def decrypt(self, nonce, data, associated_data): if associated_data is None: associated_data = b"" self._check_params(nonce, data, associated_data) - return aead._decrypt( - backend, self, nonce, data, associated_data, 16 - ) + return aead._decrypt(backend, self, nonce, data, associated_data, 16) def _check_params(self, nonce, data, associated_data): utils._check_byteslike("nonce", nonce) @@ -80,12 +76,6 @@ class AESCCM(object): self._tag_length = tag_length - if not backend.aead_cipher_supported(self): - raise exceptions.UnsupportedAlgorithm( - "AESCCM is not supported by this version of OpenSSL", - exceptions._Reasons.UNSUPPORTED_CIPHER - ) - @classmethod def generate_key(cls, bit_length): if not isinstance(bit_length, int): @@ -126,7 +116,7 @@ class AESCCM(object): # https://tools.ietf.org/html/rfc3610#section-2.1 l_val = 15 - len(nonce) if 2 ** (8 * l_val) < data_len: - raise ValueError("Nonce too long for data") + raise ValueError("Data too long for nonce") def _check_params(self, nonce, data, associated_data): utils._check_byteslike("nonce", nonce) @@ -167,22 +157,18 @@ class AESGCM(object): ) self._check_params(nonce, data, associated_data) - return aead._encrypt( - backend, self, nonce, data, associated_data, 16 - ) + return aead._encrypt(backend, self, nonce, data, associated_data, 16) def decrypt(self, nonce, data, associated_data): if associated_data is None: associated_data = b"" self._check_params(nonce, data, associated_data) - return aead._decrypt( - backend, self, nonce, data, associated_data, 16 - ) + return aead._decrypt(backend, self, nonce, data, associated_data, 16) def _check_params(self, nonce, data, associated_data): utils._check_byteslike("nonce", nonce) utils._check_bytes("data", data) utils._check_bytes("associated_data", associated_data) - if len(nonce) == 0: - raise ValueError("Nonce must be at least 1 byte") + if len(nonce) < 8 or len(nonce) > 128: + raise ValueError("Nonce must be between 8 and 128 bytes") diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py index 1f49fd9de..8072cedd1 100644 --- a/src/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -6,7 +6,8 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.hazmat.primitives.ciphers import ( - BlockCipherAlgorithm, CipherAlgorithm + BlockCipherAlgorithm, + CipherAlgorithm, ) from cryptography.hazmat.primitives.ciphers.modes import ModeWithNonce @@ -17,9 +18,11 @@ def _verify_key_size(algorithm, key): # Verify that the key size matches the expected key size if len(key) * 8 not in algorithm.key_sizes: - raise ValueError("Invalid key size ({0}) for {1}.".format( - len(key) * 8, algorithm.name - )) + raise ValueError( + "Invalid key size ({}) for {}.".format( + len(key) * 8, algorithm.name + ) + ) return key diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py index f85704142..dae425a29 100644 --- a/src/cryptography/hazmat/primitives/ciphers/base.py +++ b/src/cryptography/hazmat/primitives/ciphers/base.py @@ -10,9 +10,13 @@ import six from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, AlreadyUpdated, NotYetFinalized, UnsupportedAlgorithm, - _Reasons + AlreadyFinalized, + AlreadyUpdated, + NotYetFinalized, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import modes @@ -94,11 +98,12 @@ class AEADEncryptionContext(object): class Cipher(object): - def __init__(self, algorithm, mode, backend): + def __init__(self, algorithm, mode, backend=None): + backend = _get_backend(backend) if not isinstance(backend, CipherBackend): raise UnsupportedAlgorithm( "Backend object does not implement CipherBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) if not isinstance(algorithm, CipherAlgorithm): @@ -179,7 +184,7 @@ class _AEADCipherContext(object): self._bytes_processed += data_size if self._bytes_processed > self._ctx._mode._MAX_ENCRYPTED_BYTES: raise ValueError( - "{0} has a maximum encrypted byte limit of {1}".format( + "{} has a maximum encrypted byte limit of {}".format( self._ctx._mode.name, self._ctx._mode._MAX_ENCRYPTED_BYTES ) ) @@ -217,7 +222,7 @@ class _AEADCipherContext(object): self._aad_bytes_processed += len(data) if self._aad_bytes_processed > self._ctx._mode._MAX_AAD_BYTES: raise ValueError( - "{0} has a maximum AAD byte limit of {1}".format( + "{} has a maximum AAD byte limit of {}".format( self._ctx._mode.name, self._ctx._mode._MAX_AAD_BYTES ) ) @@ -230,6 +235,7 @@ class _AEADEncryptionContext(_AEADCipherContext): @property def tag(self): if self._ctx is not None: - raise NotYetFinalized("You must finalize encryption before " - "getting the tag.") + raise NotYetFinalized( + "You must finalize encryption before " "getting the tag." + ) return self._tag diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py index ad91a6e14..0ba0f2b5a 100644 --- a/src/cryptography/hazmat/primitives/ciphers/modes.py +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -72,9 +72,11 @@ def _check_aes_key_length(self, algorithm): def _check_iv_length(self, algorithm): if len(self.initialization_vector) * 8 != algorithm.block_size: - raise ValueError("Invalid IV size ({0}) for {1}.".format( - len(self.initialization_vector), self.name - )) + raise ValueError( + "Invalid IV size ({}) for {}.".format( + len(self.initialization_vector), self.name + ) + ) def _check_iv_and_key_length(self, algorithm): @@ -178,9 +180,11 @@ class CTR(object): def validate_for_algorithm(self, algorithm): _check_aes_key_length(self, algorithm) if len(self.nonce) * 8 != algorithm.block_size: - raise ValueError("Invalid nonce size ({0}) for {1}.".format( - len(self.nonce), self.name - )) + raise ValueError( + "Invalid nonce size ({}) for {}.".format( + len(self.nonce), self.name + ) + ) @utils.register_interface(Mode) @@ -192,12 +196,14 @@ class GCM(object): _MAX_AAD_BYTES = (2 ** 64) // 8 def __init__(self, initialization_vector, tag=None, min_tag_length=16): - # len(initialization_vector) must in [1, 2 ** 64), but it's impossible - # to actually construct a bytes object that large, so we don't check - # for it + # OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive + # This is a sane limit anyway so we'll enforce it here. utils._check_byteslike("initialization_vector", initialization_vector) - if len(initialization_vector) == 0: - raise ValueError("initialization_vector must be at least 1 byte") + if len(initialization_vector) < 8 or len(initialization_vector) > 128: + raise ValueError( + "initialization_vector must be between 8 and 128 bytes (64 " + "and 1024 bits)." + ) self._initialization_vector = initialization_vector if tag is not None: utils._check_bytes("tag", tag) @@ -205,8 +211,9 @@ class GCM(object): raise ValueError("min_tag_length must be >= 4") if len(tag) < min_tag_length: raise ValueError( - "Authentication tag must be {0} bytes or longer.".format( - min_tag_length) + "Authentication tag must be {} bytes or longer.".format( + min_tag_length + ) ) self._tag = tag self._min_tag_length = min_tag_length diff --git a/src/cryptography/hazmat/primitives/cmac.py b/src/cryptography/hazmat/primitives/cmac.py index 1404eac3d..bf962c906 100644 --- a/src/cryptography/hazmat/primitives/cmac.py +++ b/src/cryptography/hazmat/primitives/cmac.py @@ -6,25 +6,26 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import CMACBackend -from cryptography.hazmat.primitives import ciphers, mac +from cryptography.hazmat.primitives import ciphers -@utils.register_interface(mac.MACContext) class CMAC(object): - def __init__(self, algorithm, backend, ctx=None): + def __init__(self, algorithm, backend=None, ctx=None): + backend = _get_backend(backend) if not isinstance(backend, CMACBackend): raise UnsupportedAlgorithm( "Backend object does not implement CMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) if not isinstance(algorithm, ciphers.BlockCipherAlgorithm): - raise TypeError( - "Expected instance of BlockCipherAlgorithm." - ) + raise TypeError("Expected instance of BlockCipherAlgorithm.") self._algorithm = algorithm self._backend = backend @@ -59,7 +60,5 @@ class CMAC(object): if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") return CMAC( - self._algorithm, - backend=self._backend, - ctx=self._ctx.copy() + self._algorithm, backend=self._backend, ctx=self._ctx.copy() ) diff --git a/src/cryptography/hazmat/primitives/constant_time.py b/src/cryptography/hazmat/primitives/constant_time.py index 0e987ea75..7f41b9efa 100644 --- a/src/cryptography/hazmat/primitives/constant_time.py +++ b/src/cryptography/hazmat/primitives/constant_time.py @@ -5,31 +5,10 @@ from __future__ import absolute_import, division, print_function import hmac -import warnings -from cryptography import utils -from cryptography.hazmat.bindings._constant_time import lib +def bytes_eq(a, b): + if not isinstance(a, bytes) or not isinstance(b, bytes): + raise TypeError("a and b must be bytes.") -if hasattr(hmac, "compare_digest"): - def bytes_eq(a, b): - if not isinstance(a, bytes) or not isinstance(b, bytes): - raise TypeError("a and b must be bytes.") - - return hmac.compare_digest(a, b) - -else: - warnings.warn( - "Support for your Python version is deprecated. The next version of " - "cryptography will remove support. Please upgrade to a 2.7.x " - "release that supports hmac.compare_digest as soon as possible.", - utils.DeprecatedIn23, - ) - - def bytes_eq(a, b): - if not isinstance(a, bytes) or not isinstance(b, bytes): - raise TypeError("a and b must be bytes.") - - return lib.Cryptography_constant_time_bytes_eq( - a, len(a), b, len(b) - ) == 1 + return hmac.compare_digest(a, b) diff --git a/src/cryptography/hazmat/primitives/hashes.py b/src/cryptography/hazmat/primitives/hashes.py index 9be2b6009..18e2bab36 100644 --- a/src/cryptography/hazmat/primitives/hashes.py +++ b/src/cryptography/hazmat/primitives/hashes.py @@ -10,8 +10,11 @@ import six from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import HashBackend @@ -66,11 +69,12 @@ class ExtendableOutputFunction(object): @utils.register_interface(HashContext) class Hash(object): - def __init__(self, algorithm, backend, ctx=None): + def __init__(self, algorithm, backend=None, ctx=None): + backend = _get_backend(backend) if not isinstance(backend, HashBackend): raise UnsupportedAlgorithm( "Backend object does not implement HashBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) if not isinstance(algorithm, HashAlgorithm): diff --git a/src/cryptography/hazmat/primitives/hmac.py b/src/cryptography/hazmat/primitives/hmac.py index f7f401d2b..8c421dc68 100644 --- a/src/cryptography/hazmat/primitives/hmac.py +++ b/src/cryptography/hazmat/primitives/hmac.py @@ -6,20 +6,23 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import HMACBackend -from cryptography.hazmat.primitives import hashes, mac +from cryptography.hazmat.primitives import hashes -@utils.register_interface(mac.MACContext) @utils.register_interface(hashes.HashContext) class HMAC(object): - def __init__(self, key, algorithm, backend, ctx=None): + def __init__(self, key, algorithm, backend=None, ctx=None): + backend = _get_backend(backend) if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) if not isinstance(algorithm, hashes.HashAlgorithm): @@ -48,7 +51,7 @@ class HMAC(object): self._key, self.algorithm, backend=self._backend, - ctx=self._ctx.copy() + ctx=self._ctx.copy(), ) def finalize(self): diff --git a/src/cryptography/hazmat/primitives/kdf/concatkdf.py b/src/cryptography/hazmat/primitives/kdf/concatkdf.py index 65b25cdc2..7cc0324fc 100644 --- a/src/cryptography/hazmat/primitives/kdf/concatkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/concatkdf.py @@ -8,8 +8,12 @@ import struct from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, + InvalidKey, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.backends.interfaces import HashBackend from cryptography.hazmat.primitives import constant_time, hashes, hmac @@ -17,16 +21,15 @@ from cryptography.hazmat.primitives.kdf import KeyDerivationFunction def _int_to_u32be(n): - return struct.pack('>I', n) + return struct.pack(">I", n) def _common_args_checks(algorithm, length, otherinfo): max_length = algorithm.digest_size * (2 ** 32 - 1) if length > max_length: raise ValueError( - "Can not derive keys larger than {0} bits.".format( - max_length - )) + "Can not derive keys larger than {} bits.".format(max_length) + ) if otherinfo is not None: utils._check_bytes("otherinfo", otherinfo) @@ -37,7 +40,7 @@ def _concatkdf_derive(key_material, length, auxfn, otherinfo): outlen = 0 counter = 1 - while (length > outlen): + while length > outlen: h = auxfn() h.update(_int_to_u32be(counter)) h.update(key_material) @@ -51,7 +54,8 @@ def _concatkdf_derive(key_material, length, auxfn, otherinfo): @utils.register_interface(KeyDerivationFunction) class ConcatKDFHash(object): - def __init__(self, algorithm, length, otherinfo, backend): + def __init__(self, algorithm, length, otherinfo, backend=None): + backend = _get_backend(backend) _common_args_checks(algorithm, length, otherinfo) self._algorithm = algorithm @@ -63,7 +67,7 @@ class ConcatKDFHash(object): if not isinstance(backend, HashBackend): raise UnsupportedAlgorithm( "Backend object does not implement HashBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) self._backend = backend self._used = False @@ -75,8 +79,9 @@ class ConcatKDFHash(object): if self._used: raise AlreadyFinalized self._used = True - return _concatkdf_derive(key_material, self._length, - self._hash, self._otherinfo) + return _concatkdf_derive( + key_material, self._length, self._hash, self._otherinfo + ) def verify(self, key_material, expected_key): if not constant_time.bytes_eq(self.derive(key_material), expected_key): @@ -85,7 +90,8 @@ class ConcatKDFHash(object): @utils.register_interface(KeyDerivationFunction) class ConcatKDFHMAC(object): - def __init__(self, algorithm, length, salt, otherinfo, backend): + def __init__(self, algorithm, length, salt, otherinfo, backend=None): + backend = _get_backend(backend) _common_args_checks(algorithm, length, otherinfo) self._algorithm = algorithm @@ -104,7 +110,7 @@ class ConcatKDFHMAC(object): if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) self._backend = backend self._used = False @@ -116,8 +122,9 @@ class ConcatKDFHMAC(object): if self._used: raise AlreadyFinalized self._used = True - return _concatkdf_derive(key_material, self._length, - self._hmac, self._otherinfo) + return _concatkdf_derive( + key_material, self._length, self._hmac, self._otherinfo + ) def verify(self, key_material, expected_key): if not constant_time.bytes_eq(self.derive(key_material), expected_key): diff --git a/src/cryptography/hazmat/primitives/kdf/hkdf.py b/src/cryptography/hazmat/primitives/kdf/hkdf.py index 307f91cca..9bb6bc213 100644 --- a/src/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/hkdf.py @@ -8,8 +8,12 @@ import six from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, + InvalidKey, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import constant_time, hmac from cryptography.hazmat.primitives.kdf import KeyDerivationFunction @@ -17,11 +21,12 @@ from cryptography.hazmat.primitives.kdf import KeyDerivationFunction @utils.register_interface(KeyDerivationFunction) class HKDF(object): - def __init__(self, algorithm, length, salt, info, backend): + def __init__(self, algorithm, length, salt, info, backend=None): + backend = _get_backend(backend) if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) self._algorithm = algorithm @@ -53,11 +58,12 @@ class HKDF(object): @utils.register_interface(KeyDerivationFunction) class HKDFExpand(object): - def __init__(self, algorithm, length, info, backend): + def __init__(self, algorithm, length, info, backend=None): + backend = _get_backend(backend) if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) self._algorithm = algorithm @@ -68,9 +74,8 @@ class HKDFExpand(object): if length > max_length: raise ValueError( - "Can not derive keys larger than {0} octets.".format( - max_length - )) + "Can not derive keys larger than {} octets.".format(max_length) + ) self._length = length @@ -95,7 +100,7 @@ class HKDFExpand(object): output.append(h.finalize()) counter += 1 - return b"".join(output)[:self._length] + return b"".join(output)[: self._length] def derive(self, key_material): utils._check_byteslike("key_material", key_material) diff --git a/src/cryptography/hazmat/primitives/kdf/kbkdf.py b/src/cryptography/hazmat/primitives/kdf/kbkdf.py index 56783a85c..864337001 100644 --- a/src/cryptography/hazmat/primitives/kdf/kbkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -10,8 +10,12 @@ from six.moves import range from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, + InvalidKey, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import constant_time, hashes, hmac from cryptography.hazmat.primitives.kdf import KeyDerivationFunction @@ -28,24 +32,36 @@ class CounterLocation(Enum): @utils.register_interface(KeyDerivationFunction) class KBKDFHMAC(object): - def __init__(self, algorithm, mode, length, rlen, llen, - location, label, context, fixed, backend): + def __init__( + self, + algorithm, + mode, + length, + rlen, + llen, + location, + label, + context, + fixed, + backend=None, + ): + backend = _get_backend(backend) if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) if not isinstance(algorithm, hashes.HashAlgorithm): raise UnsupportedAlgorithm( "Algorithm supplied is not a supported hash algorithm.", - _Reasons.UNSUPPORTED_HASH + _Reasons.UNSUPPORTED_HASH, ) if not backend.hmac_supported(algorithm): raise UnsupportedAlgorithm( "Algorithm supplied is not a supported hmac algorithm.", - _Reasons.UNSUPPORTED_HASH + _Reasons.UNSUPPORTED_HASH, ) if not isinstance(mode, Mode): @@ -55,8 +71,9 @@ class KBKDFHMAC(object): raise TypeError("location must be of type CounterLocation") if (label or context) and fixed: - raise ValueError("When supplying fixed data, " - "label and context are ignored.") + raise ValueError( + "When supplying fixed data, " "label and context are ignored." + ) if rlen is None or not self._valid_byte_length(rlen): raise ValueError("rlen must be between 1 and 4") @@ -68,10 +85,10 @@ class KBKDFHMAC(object): raise TypeError("llen must be an integer") if label is None: - label = b'' + label = b"" if context is None: - context = b'' + context = b"" utils._check_bytes("label", label) utils._check_bytes("context", context) @@ -89,7 +106,7 @@ class KBKDFHMAC(object): def _valid_byte_length(self, value): if not isinstance(value, int): - raise TypeError('value must be of type int') + raise TypeError("value must be of type int") value_bin = utils.int_to_bytes(1, value) if not 1 <= len(value_bin) <= 4: @@ -106,7 +123,7 @@ class KBKDFHMAC(object): # inverse floor division (equivalent to ceiling) rounds = -(-self._length // self._algorithm.digest_size) - output = [b''] + output = [b""] # For counter mode, the number of iterations shall not be # larger than 2^r-1, where r <= 32 is the binary length of the counter @@ -114,7 +131,7 @@ class KBKDFHMAC(object): # PRF will not repeat during a particular call to the KDF function. r_bin = utils.int_to_bytes(1, self._rlen) if rounds > pow(2, len(r_bin) * 8) - 1: - raise ValueError('There are too many iterations.') + raise ValueError("There are too many iterations.") for i in range(1, rounds + 1): h = hmac.HMAC(key_material, self._algorithm, backend=self._backend) @@ -130,7 +147,7 @@ class KBKDFHMAC(object): output.append(h.finalize()) - return b''.join(output)[:self._length] + return b"".join(output)[: self._length] def _generate_fixed_input(self): if self._fixed_data and isinstance(self._fixed_data, bytes): diff --git a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py index a47b7bbcb..5b67d48bb 100644 --- a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -6,8 +6,12 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, + InvalidKey, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import PBKDF2HMACBackend from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.kdf import KeyDerivationFunction @@ -15,18 +19,20 @@ from cryptography.hazmat.primitives.kdf import KeyDerivationFunction @utils.register_interface(KeyDerivationFunction) class PBKDF2HMAC(object): - def __init__(self, algorithm, length, salt, iterations, backend): + def __init__(self, algorithm, length, salt, iterations, backend=None): + backend = _get_backend(backend) if not isinstance(backend, PBKDF2HMACBackend): raise UnsupportedAlgorithm( "Backend object does not implement PBKDF2HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) if not backend.pbkdf2_hmac_supported(algorithm): raise UnsupportedAlgorithm( - "{0} is not supported for PBKDF2 by this backend.".format( - algorithm.name), - _Reasons.UNSUPPORTED_HASH + "{} is not supported for PBKDF2 by this backend.".format( + algorithm.name + ), + _Reasons.UNSUPPORTED_HASH, ) self._used = False self._algorithm = algorithm @@ -47,7 +53,7 @@ class PBKDF2HMAC(object): self._length, self._salt, self._iterations, - key_material + key_material, ) def verify(self, key_material, expected_key): diff --git a/src/cryptography/hazmat/primitives/kdf/scrypt.py b/src/cryptography/hazmat/primitives/kdf/scrypt.py index df9745e68..f028646aa 100644 --- a/src/cryptography/hazmat/primitives/kdf/scrypt.py +++ b/src/cryptography/hazmat/primitives/kdf/scrypt.py @@ -8,8 +8,12 @@ import sys from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, + InvalidKey, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import ScryptBackend from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.kdf import KeyDerivationFunction @@ -22,11 +26,12 @@ _MEM_LIMIT = sys.maxsize // 2 @utils.register_interface(KeyDerivationFunction) class Scrypt(object): - def __init__(self, salt, length, n, r, p, backend): + def __init__(self, salt, length, n, r, p, backend=None): + backend = _get_backend(backend) if not isinstance(backend, ScryptBackend): raise UnsupportedAlgorithm( "Backend object does not implement ScryptBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) self._length = length diff --git a/src/cryptography/hazmat/primitives/kdf/x963kdf.py b/src/cryptography/hazmat/primitives/kdf/x963kdf.py index fd9d125e7..1898d526a 100644 --- a/src/cryptography/hazmat/primitives/kdf/x963kdf.py +++ b/src/cryptography/hazmat/primitives/kdf/x963kdf.py @@ -8,25 +8,31 @@ import struct from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, + InvalidKey, + UnsupportedAlgorithm, + _Reasons, ) +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import HashBackend from cryptography.hazmat.primitives import constant_time, hashes from cryptography.hazmat.primitives.kdf import KeyDerivationFunction def _int_to_u32be(n): - return struct.pack('>I', n) + return struct.pack(">I", n) @utils.register_interface(KeyDerivationFunction) class X963KDF(object): - def __init__(self, algorithm, length, sharedinfo, backend): + def __init__(self, algorithm, length, sharedinfo, backend=None): + backend = _get_backend(backend) max_len = algorithm.digest_size * (2 ** 32 - 1) if length > max_len: raise ValueError( - "Can not derive keys larger than {0} bits.".format(max_len)) + "Can not derive keys larger than {} bits.".format(max_len) + ) if sharedinfo is not None: utils._check_bytes("sharedinfo", sharedinfo) @@ -37,7 +43,7 @@ class X963KDF(object): if not isinstance(backend, HashBackend): raise UnsupportedAlgorithm( "Backend object does not implement HashBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) self._backend = backend self._used = False @@ -61,7 +67,7 @@ class X963KDF(object): outlen += len(output[-1]) counter += 1 - return b"".join(output)[:self._length] + return b"".join(output)[: self._length] def verify(self, key_material, expected_key): if not constant_time.bytes_eq(self.derive(key_material), expected_key): diff --git a/src/cryptography/hazmat/primitives/keywrap.py b/src/cryptography/hazmat/primitives/keywrap.py index f55c519cf..2439cafe6 100644 --- a/src/cryptography/hazmat/primitives/keywrap.py +++ b/src/cryptography/hazmat/primitives/keywrap.py @@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function import struct +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import ECB @@ -33,7 +34,8 @@ def _wrap_core(wrapping_key, a, r, backend): return a + b"".join(r) -def aes_key_wrap(wrapping_key, key_to_wrap, backend): +def aes_key_wrap(wrapping_key, key_to_wrap, backend=None): + backend = _get_backend(backend) if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") @@ -44,7 +46,7 @@ def aes_key_wrap(wrapping_key, key_to_wrap, backend): raise ValueError("The key to wrap must be a multiple of 8 bytes") a = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" - r = [key_to_wrap[i:i + 8] for i in range(0, len(key_to_wrap), 8)] + r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)] return _wrap_core(wrapping_key, a, r, backend) @@ -55,9 +57,12 @@ def _unwrap_core(wrapping_key, a, r, backend): for j in reversed(range(6)): for i in reversed(range(n)): # pack/unpack are safe as these are always 64-bit chunks - atr = struct.pack( - ">Q", struct.unpack(">Q", a)[0] ^ ((n * j) + i + 1) - ) + r[i] + atr = ( + struct.pack( + ">Q", struct.unpack(">Q", a)[0] ^ ((n * j) + i + 1) + ) + + r[i] + ) # every decryption operation is a discrete 16 byte chunk so # it is safe to reuse the decryptor for the entire operation b = decryptor.update(atr) @@ -68,7 +73,8 @@ def _unwrap_core(wrapping_key, a, r, backend): return a, r -def aes_key_wrap_with_padding(wrapping_key, key_to_wrap, backend): +def aes_key_wrap_with_padding(wrapping_key, key_to_wrap, backend=None): + backend = _get_backend(backend) if len(wrapping_key) not in [16, 24, 32]: raise ValueError("The wrapping key must be a valid AES key length") @@ -83,11 +89,12 @@ def aes_key_wrap_with_padding(wrapping_key, key_to_wrap, backend): assert encryptor.finalize() == b"" return b else: - r = [key_to_wrap[i:i + 8] for i in range(0, len(key_to_wrap), 8)] + r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)] return _wrap_core(wrapping_key, aiv, r, backend) -def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend): +def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend=None): + backend = _get_backend(backend) if len(wrapped_key) < 16: raise InvalidUnwrap("Must be at least 16 bytes") @@ -103,7 +110,7 @@ def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend): data = b[8:] n = 1 else: - r = [wrapped_key[i:i + 8] for i in range(0, len(wrapped_key), 8)] + r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)] encrypted_aiv = r.pop(0) n = len(r) a, r = _unwrap_core(wrapping_key, encrypted_aiv, r, backend) @@ -117,10 +124,9 @@ def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend): (mli,) = struct.unpack(">I", a[4:]) b = (8 * n) - mli if ( - not bytes_eq(a[:4], b"\xa6\x59\x59\xa6") or not - 8 * (n - 1) < mli <= 8 * n or ( - b != 0 and not bytes_eq(data[-b:], b"\x00" * b) - ) + not bytes_eq(a[:4], b"\xa6\x59\x59\xa6") + or not 8 * (n - 1) < mli <= 8 * n + or (b != 0 and not bytes_eq(data[-b:], b"\x00" * b)) ): raise InvalidUnwrap() @@ -130,7 +136,8 @@ def aes_key_unwrap_with_padding(wrapping_key, wrapped_key, backend): return data[:-b] -def aes_key_unwrap(wrapping_key, wrapped_key, backend): +def aes_key_unwrap(wrapping_key, wrapped_key, backend=None): + backend = _get_backend(backend) if len(wrapped_key) < 24: raise InvalidUnwrap("Must be at least 24 bytes") @@ -141,7 +148,7 @@ def aes_key_unwrap(wrapping_key, wrapped_key, backend): raise ValueError("The wrapping key must be a valid AES key length") aiv = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" - r = [wrapped_key[i:i + 8] for i in range(0, len(wrapped_key), 8)] + r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)] a = r.pop(0) a, r = _unwrap_core(wrapping_key, a, r, backend) if not bytes_eq(a, aiv): diff --git a/src/cryptography/hazmat/primitives/mac.py b/src/cryptography/hazmat/primitives/mac.py deleted file mode 100644 index 4c95190ba..000000000 --- a/src/cryptography/hazmat/primitives/mac.py +++ /dev/null @@ -1,37 +0,0 @@ -# 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 abc - -import six - - -@six.add_metaclass(abc.ABCMeta) -class MACContext(object): - @abc.abstractmethod - def update(self, data): - """ - Processes the provided bytes. - """ - - @abc.abstractmethod - def finalize(self): - """ - Returns the message authentication code as bytes. - """ - - @abc.abstractmethod - def copy(self): - """ - Return a MACContext that is a copy of the current context. - """ - - @abc.abstractmethod - def verify(self, signature): - """ - Checks if the generated message authentication code matches the - signature. - """ diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py index 170c80218..98abffbc0 100644 --- a/src/cryptography/hazmat/primitives/padding.py +++ b/src/cryptography/hazmat/primitives/padding.py @@ -40,14 +40,17 @@ def _byte_padding_update(buffer_, data, block_size): if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") - utils._check_bytes("data", data) + utils._check_byteslike("data", data) - buffer_ += data + # six.PY2: Only coerce non-bytes objects to avoid triggering bad behavior + # of future's newbytes type. Unconditionally call bytes() after Python 2 + # support is gone. + buffer_ += data if isinstance(data, bytes) else bytes(data) finished_blocks = len(buffer_) // (block_size // 8) - result = buffer_[:finished_blocks * (block_size // 8)] - buffer_ = buffer_[finished_blocks * (block_size // 8):] + result = buffer_[: finished_blocks * (block_size // 8)] + buffer_ = buffer_[finished_blocks * (block_size // 8) :] return buffer_, result @@ -64,14 +67,17 @@ def _byte_unpadding_update(buffer_, data, block_size): if buffer_ is None: raise AlreadyFinalized("Context was already finalized.") - utils._check_bytes("data", data) + utils._check_byteslike("data", data) - buffer_ += data + # six.PY2: Only coerce non-bytes objects to avoid triggering bad behavior + # of future's newbytes type. Unconditionally call bytes() after Python 2 + # support is gone. + buffer_ += data if isinstance(data, bytes) else bytes(data) finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0) - result = buffer_[:finished_blocks * (block_size // 8)] - buffer_ = buffer_[finished_blocks * (block_size // 8):] + result = buffer_[: finished_blocks * (block_size // 8)] + buffer_ = buffer_[finished_blocks * (block_size // 8) :] return buffer_, result @@ -113,7 +119,8 @@ class _PKCS7PaddingContext(object): def update(self, data): self._buffer, result = _byte_padding_update( - self._buffer, data, self.block_size) + self._buffer, data, self.block_size + ) return result def _padding(self, size): @@ -121,7 +128,8 @@ class _PKCS7PaddingContext(object): def finalize(self): result = _byte_padding_pad( - self._buffer, self.block_size, self._padding) + self._buffer, self.block_size, self._padding + ) self._buffer = None return result @@ -135,13 +143,14 @@ class _PKCS7UnpaddingContext(object): def update(self, data): self._buffer, result = _byte_unpadding_update( - self._buffer, data, self.block_size) + self._buffer, data, self.block_size + ) return result def finalize(self): result = _byte_unpadding_check( - self._buffer, self.block_size, - lib.Cryptography_check_pkcs7_padding) + self._buffer, self.block_size, lib.Cryptography_check_pkcs7_padding + ) self._buffer = None return result @@ -167,7 +176,8 @@ class _ANSIX923PaddingContext(object): def update(self, data): self._buffer, result = _byte_padding_update( - self._buffer, data, self.block_size) + self._buffer, data, self.block_size + ) return result def _padding(self, size): @@ -175,7 +185,8 @@ class _ANSIX923PaddingContext(object): def finalize(self): result = _byte_padding_pad( - self._buffer, self.block_size, self._padding) + self._buffer, self.block_size, self._padding + ) self._buffer = None return result @@ -189,12 +200,15 @@ class _ANSIX923UnpaddingContext(object): def update(self, data): self._buffer, result = _byte_unpadding_update( - self._buffer, data, self.block_size) + self._buffer, data, self.block_size + ) return result def finalize(self): result = _byte_unpadding_check( - self._buffer, self.block_size, - lib.Cryptography_check_ansix923_padding) + self._buffer, + self.block_size, + lib.Cryptography_check_ansix923_padding, + ) self._buffer = None return result diff --git a/src/cryptography/hazmat/primitives/poly1305.py b/src/cryptography/hazmat/primitives/poly1305.py new file mode 100644 index 000000000..643968620 --- /dev/null +++ b/src/cryptography/hazmat/primitives/poly1305.py @@ -0,0 +1,58 @@ +# 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 + + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, + UnsupportedAlgorithm, + _Reasons, +) + + +class Poly1305(object): + def __init__(self, key): + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.poly1305_supported(): + raise UnsupportedAlgorithm( + "poly1305 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_MAC, + ) + self._ctx = backend.create_poly1305_ctx(key) + + def update(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + utils._check_byteslike("data", data) + self._ctx.update(data) + + def finalize(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + mac = self._ctx.finalize() + self._ctx = None + return mac + + def verify(self, tag): + utils._check_bytes("tag", tag) + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + + ctx, self._ctx = self._ctx, None + ctx.verify(tag) + + @classmethod + def generate_tag(cls, key, data): + p = Poly1305(key) + p.update(data) + return p.finalize() + + @classmethod + def verify_tag(cls, key, data, tag): + p = Poly1305(key) + p.update(data) + p.verify(tag) diff --git a/src/cryptography/hazmat/primitives/serialization/__init__.py b/src/cryptography/hazmat/primitives/serialization/__init__.py index f6d4ce994..c2f9b014a 100644 --- a/src/cryptography/hazmat/primitives/serialization/__init__.py +++ b/src/cryptography/hazmat/primitives/serialization/__init__.py @@ -5,22 +5,40 @@ from __future__ import absolute_import, division, print_function from cryptography.hazmat.primitives.serialization.base import ( - BestAvailableEncryption, Encoding, KeySerializationEncryption, - NoEncryption, ParameterFormat, PrivateFormat, PublicFormat, - load_der_parameters, load_der_private_key, load_der_public_key, - load_pem_parameters, load_pem_private_key, load_pem_public_key, + BestAvailableEncryption, + Encoding, + KeySerializationEncryption, + NoEncryption, + ParameterFormat, + PrivateFormat, + PublicFormat, + load_der_parameters, + load_der_private_key, + load_der_public_key, + load_pem_parameters, + load_pem_private_key, + load_pem_public_key, ) from cryptography.hazmat.primitives.serialization.ssh import ( - load_ssh_public_key + load_ssh_private_key, + load_ssh_public_key, ) -_PEM_DER = (Encoding.PEM, Encoding.DER) - __all__ = [ - "load_der_parameters", "load_der_private_key", "load_der_public_key", - "load_pem_parameters", "load_pem_private_key", "load_pem_public_key", - "load_ssh_public_key", "Encoding", "PrivateFormat", "PublicFormat", - "ParameterFormat", "KeySerializationEncryption", "BestAvailableEncryption", + "load_der_parameters", + "load_der_private_key", + "load_der_public_key", + "load_pem_parameters", + "load_pem_private_key", + "load_pem_public_key", + "load_ssh_private_key", + "load_ssh_public_key", + "Encoding", + "PrivateFormat", + "PublicFormat", + "ParameterFormat", + "KeySerializationEncryption", + "BestAvailableEncryption", "NoEncryption", ] diff --git a/src/cryptography/hazmat/primitives/serialization/base.py b/src/cryptography/hazmat/primitives/serialization/base.py index 4218ea824..fc27235c5 100644 --- a/src/cryptography/hazmat/primitives/serialization/base.py +++ b/src/cryptography/hazmat/primitives/serialization/base.py @@ -10,29 +10,36 @@ from enum import Enum import six from cryptography import utils +from cryptography.hazmat.backends import _get_backend -def load_pem_private_key(data, password, backend): +def load_pem_private_key(data, password, backend=None): + backend = _get_backend(backend) return backend.load_pem_private_key(data, password) -def load_pem_public_key(data, backend): +def load_pem_public_key(data, backend=None): + backend = _get_backend(backend) return backend.load_pem_public_key(data) -def load_pem_parameters(data, backend): +def load_pem_parameters(data, backend=None): + backend = _get_backend(backend) return backend.load_pem_parameters(data) -def load_der_private_key(data, password, backend): +def load_der_private_key(data, password, backend=None): + backend = _get_backend(backend) return backend.load_der_private_key(data, password) -def load_der_public_key(data, backend): +def load_der_public_key(data, backend=None): + backend = _get_backend(backend) return backend.load_der_public_key(data) -def load_der_parameters(data, backend): +def load_der_parameters(data, backend=None): + backend = _get_backend(backend) return backend.load_der_parameters(data) @@ -42,12 +49,14 @@ class Encoding(Enum): OpenSSH = "OpenSSH" Raw = "Raw" X962 = "ANSI X9.62" + SMIME = "S/MIME" class PrivateFormat(Enum): PKCS8 = "PKCS8" TraditionalOpenSSL = "TraditionalOpenSSL" Raw = "Raw" + OpenSSH = "OpenSSH" class PublicFormat(Enum): diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs12.py b/src/cryptography/hazmat/primitives/serialization/pkcs12.py index 98161d57a..201f32941 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs12.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -4,6 +4,47 @@ from __future__ import absolute_import, division, print_function +from cryptography import x509 +from cryptography.hazmat.backends import _get_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa -def load_key_and_certificates(data, password, backend): + +def load_key_and_certificates(data, password, backend=None): + backend = _get_backend(backend) return backend.load_key_and_certificates_from_pkcs12(data, password) + + +def serialize_key_and_certificates(name, key, cert, cas, encryption_algorithm): + if key is not None and not isinstance( + key, + ( + rsa.RSAPrivateKeyWithSerialization, + dsa.DSAPrivateKeyWithSerialization, + ec.EllipticCurvePrivateKeyWithSerialization, + ), + ): + raise TypeError("Key must be RSA, DSA, or EllipticCurve private key.") + if cert is not None and not isinstance(cert, x509.Certificate): + raise TypeError("cert must be a certificate") + + if cas is not None: + cas = list(cas) + if not all(isinstance(val, x509.Certificate) for val in cas): + raise TypeError("all values in cas must be certificates") + + if not isinstance( + encryption_algorithm, serialization.KeySerializationEncryption + ): + raise TypeError( + "Key encryption algorithm must be a " + "KeySerializationEncryption instance" + ) + + if key is None and cert is None and not cas: + raise ValueError("You must supply at least one of key, cert, or cas") + + backend = _get_backend(None) + return backend.serialize_key_and_certificates_to_pkcs12( + name, key, cert, cas, encryption_algorithm + ) diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs7.py b/src/cryptography/hazmat/primitives/serialization/pkcs7.py new file mode 100644 index 000000000..1e11e28ef --- /dev/null +++ b/src/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -0,0 +1,132 @@ +# 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 + +from enum import Enum + +from cryptography import x509 +from cryptography.hazmat.backends import _get_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.utils import _check_byteslike + + +def load_pem_pkcs7_certificates(data): + backend = _get_backend(None) + return backend.load_pem_pkcs7_certificates(data) + + +def load_der_pkcs7_certificates(data): + backend = _get_backend(None) + return backend.load_der_pkcs7_certificates(data) + + +class PKCS7SignatureBuilder(object): + def __init__(self, data=None, signers=[], additional_certs=[]): + self._data = data + self._signers = signers + self._additional_certs = additional_certs + + def set_data(self, data): + _check_byteslike("data", data) + if self._data is not None: + raise ValueError("data may only be set once") + + return PKCS7SignatureBuilder(data, self._signers) + + def add_signer(self, certificate, private_key, hash_algorithm): + if not isinstance( + hash_algorithm, + ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + ), + ): + raise TypeError( + "hash_algorithm must be one of hashes.SHA1, SHA224, " + "SHA256, SHA384, or SHA512" + ) + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + if not isinstance( + private_key, (rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey) + ): + raise TypeError("Only RSA & EC keys are supported at this time.") + + return PKCS7SignatureBuilder( + self._data, + self._signers + [(certificate, private_key, hash_algorithm)], + ) + + def add_certificate(self, certificate): + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + return PKCS7SignatureBuilder( + self._data, self._signers, self._additional_certs + [certificate] + ) + + def sign(self, encoding, options, backend=None): + if len(self._signers) == 0: + raise ValueError("Must have at least one signer") + if self._data is None: + raise ValueError("You must add data to sign") + options = list(options) + if not all(isinstance(x, PKCS7Options) for x in options): + raise ValueError("options must be from the PKCS7Options enum") + if encoding not in ( + serialization.Encoding.PEM, + serialization.Encoding.DER, + serialization.Encoding.SMIME, + ): + raise ValueError( + "Must be PEM, DER, or SMIME from the Encoding enum" + ) + + # Text is a meaningless option unless it is accompanied by + # DetachedSignature + if ( + PKCS7Options.Text in options + and PKCS7Options.DetachedSignature not in options + ): + raise ValueError( + "When passing the Text option you must also pass " + "DetachedSignature" + ) + + if PKCS7Options.Text in options and encoding in ( + serialization.Encoding.DER, + serialization.Encoding.PEM, + ): + raise ValueError( + "The Text option is only available for SMIME serialization" + ) + + # No attributes implies no capabilities so we'll error if you try to + # pass both. + if ( + PKCS7Options.NoAttributes in options + and PKCS7Options.NoCapabilities in options + ): + raise ValueError( + "NoAttributes is a superset of NoCapabilities. Do not pass " + "both values." + ) + + backend = _get_backend(backend) + return backend.pkcs7_sign(self, encoding, options) + + +class PKCS7Options(Enum): + Text = "Add text/plain MIME type" + Binary = "Don't translate input data into canonical MIME format" + DetachedSignature = "Don't embed data in the PKCS7 structure" + NoCapabilities = "Don't embed SMIME capabilities" + NoAttributes = "Don't embed authenticatedAttributes" + NoCerts = "Don't embed signer certificate" diff --git a/src/cryptography/hazmat/primitives/serialization/ssh.py b/src/cryptography/hazmat/primitives/serialization/ssh.py index cb838927d..5ecae59f8 100644 --- a/src/cryptography/hazmat/primitives/serialization/ssh.py +++ b/src/cryptography/hazmat/primitives/serialization/ssh.py @@ -4,139 +4,680 @@ from __future__ import absolute_import, division, print_function -import base64 +import binascii +import os +import re import struct import six from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa - - -def load_ssh_public_key(data, backend): - key_parts = data.split(b' ', 2) - - if len(key_parts) < 2: - raise ValueError( - 'Key is not in the proper format or contains extra data.') - - key_type = key_parts[0] - - if key_type == b'ssh-rsa': - loader = _load_ssh_rsa_public_key - elif key_type == b'ssh-dss': - loader = _load_ssh_dss_public_key - elif key_type in [ - b'ecdsa-sha2-nistp256', b'ecdsa-sha2-nistp384', b'ecdsa-sha2-nistp521', - ]: - loader = _load_ssh_ecdsa_public_key - else: - raise UnsupportedAlgorithm('Key type is not supported.') - - key_body = key_parts[1] - - try: - decoded_data = base64.b64decode(key_body) - except TypeError: - raise ValueError('Key is not in the proper format.') - - inner_key_type, rest = _ssh_read_next_string(decoded_data) - - if inner_key_type != key_type: +from cryptography.hazmat.backends import _get_backend +from cryptography.hazmat.primitives.asymmetric import dsa, ec, ed25519, rsa +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.serialization import ( + Encoding, + NoEncryption, + PrivateFormat, + PublicFormat, +) + +try: + from bcrypt import kdf as _bcrypt_kdf + + _bcrypt_supported = True +except ImportError: + _bcrypt_supported = False + + def _bcrypt_kdf(*args, **kwargs): + raise UnsupportedAlgorithm("Need bcrypt module") + + +try: + from base64 import encodebytes as _base64_encode +except ImportError: + from base64 import encodestring as _base64_encode + +_SSH_ED25519 = b"ssh-ed25519" +_SSH_RSA = b"ssh-rsa" +_SSH_DSA = b"ssh-dss" +_ECDSA_NISTP256 = b"ecdsa-sha2-nistp256" +_ECDSA_NISTP384 = b"ecdsa-sha2-nistp384" +_ECDSA_NISTP521 = b"ecdsa-sha2-nistp521" +_CERT_SUFFIX = b"-cert-v01@openssh.com" + +_SSH_PUBKEY_RC = re.compile(br"\A(\S+)[ \t]+(\S+)") +_SK_MAGIC = b"openssh-key-v1\0" +_SK_START = b"-----BEGIN OPENSSH PRIVATE KEY-----" +_SK_END = b"-----END OPENSSH PRIVATE KEY-----" +_BCRYPT = b"bcrypt" +_NONE = b"none" +_DEFAULT_CIPHER = b"aes256-ctr" +_DEFAULT_ROUNDS = 16 +_MAX_PASSWORD = 72 + +# re is only way to work on bytes-like data +_PEM_RC = re.compile(_SK_START + b"(.*?)" + _SK_END, re.DOTALL) + +# padding for max blocksize +_PADDING = memoryview(bytearray(range(1, 1 + 16))) + +# ciphers that are actually used in key wrapping +_SSH_CIPHERS = { + b"aes256-ctr": (algorithms.AES, 32, modes.CTR, 16), + b"aes256-cbc": (algorithms.AES, 32, modes.CBC, 16), +} + +# map local curve name to key type +_ECDSA_KEY_TYPE = { + "secp256r1": _ECDSA_NISTP256, + "secp384r1": _ECDSA_NISTP384, + "secp521r1": _ECDSA_NISTP521, +} + +_U32 = struct.Struct(b">I") +_U64 = struct.Struct(b">Q") + + +def _ecdsa_key_type(public_key): + """Return SSH key_type and curve_name for private key.""" + curve = public_key.curve + if curve.name not in _ECDSA_KEY_TYPE: raise ValueError( - 'Key header and key body contain different key type values.' + "Unsupported curve for ssh private key: %r" % curve.name ) - - return loader(key_type, rest, backend) + return _ECDSA_KEY_TYPE[curve.name] -def _load_ssh_rsa_public_key(key_type, decoded_data, backend): - e, rest = _ssh_read_next_mpint(decoded_data) - n, rest = _ssh_read_next_mpint(rest) +def _ssh_pem_encode(data, prefix=_SK_START + b"\n", suffix=_SK_END + b"\n"): + return b"".join([prefix, _base64_encode(data), suffix]) - if rest: - raise ValueError('Key body contains extra bytes.') - return rsa.RSAPublicNumbers(e, n).public_key(backend) +def _check_block_size(data, block_len): + """Require data to be full blocks""" + if not data or len(data) % block_len != 0: + raise ValueError("Corrupt data: missing padding") -def _load_ssh_dss_public_key(key_type, decoded_data, backend): - p, rest = _ssh_read_next_mpint(decoded_data) - q, rest = _ssh_read_next_mpint(rest) - g, rest = _ssh_read_next_mpint(rest) - y, rest = _ssh_read_next_mpint(rest) +def _check_empty(data): + """All data should have been parsed.""" + if data: + raise ValueError("Corrupt data: unparsed data") - if rest: - raise ValueError('Key body contains extra bytes.') - parameter_numbers = dsa.DSAParameterNumbers(p, q, g) - public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) +def _init_cipher(ciphername, password, salt, rounds, backend): + """Generate key + iv and return cipher.""" + if not password: + raise ValueError("Key is password-protected.") - return public_numbers.public_key(backend) + algo, key_len, mode, iv_len = _SSH_CIPHERS[ciphername] + seed = _bcrypt_kdf(password, salt, key_len + iv_len, rounds, True) + return Cipher(algo(seed[:key_len]), mode(seed[key_len:]), backend) -def _load_ssh_ecdsa_public_key(expected_key_type, decoded_data, backend): - curve_name, rest = _ssh_read_next_string(decoded_data) - data, rest = _ssh_read_next_string(rest) +def _get_u32(data): + """Uint32""" + if len(data) < 4: + raise ValueError("Invalid data") + return _U32.unpack(data[:4])[0], data[4:] + + +def _get_u64(data): + """Uint64""" + if len(data) < 8: + raise ValueError("Invalid data") + return _U64.unpack(data[:8])[0], data[8:] + + +def _get_sshstr(data): + """Bytes with u32 length prefix""" + n, data = _get_u32(data) + if n > len(data): + raise ValueError("Invalid data") + return data[:n], data[n:] + + +def _get_mpint(data): + """Big integer.""" + val, data = _get_sshstr(data) + if val and six.indexbytes(val, 0) > 0x7F: + raise ValueError("Invalid data") + return utils.int_from_bytes(val, "big"), data + + +def _to_mpint(val): + """Storage format for signed bigint.""" + if val < 0: + raise ValueError("negative mpint not allowed") + if not val: + return b"" + nbytes = (val.bit_length() + 8) // 8 + return utils.int_to_bytes(val, nbytes) + + +class _FragList(object): + """Build recursive structure without data copy.""" + + def __init__(self, init=None): + self.flist = [] + if init: + self.flist.extend(init) + + def put_raw(self, val): + """Add plain bytes""" + self.flist.append(val) + + def put_u32(self, val): + """Big-endian uint32""" + self.flist.append(_U32.pack(val)) + + def put_sshstr(self, val): + """Bytes prefixed with u32 length""" + if isinstance(val, (bytes, memoryview, bytearray)): + self.put_u32(len(val)) + self.flist.append(val) + else: + self.put_u32(val.size()) + self.flist.extend(val.flist) + + def put_mpint(self, val): + """Big-endian bigint prefixed with u32 length""" + self.put_sshstr(_to_mpint(val)) + + def size(self): + """Current number of bytes""" + return sum(map(len, self.flist)) + + def render(self, dstbuf, pos=0): + """Write into bytearray""" + for frag in self.flist: + flen = len(frag) + start, pos = pos, pos + flen + dstbuf[start:pos] = frag + return pos + + def tobytes(self): + """Return as bytes""" + buf = memoryview(bytearray(self.size())) + self.render(buf) + return buf.tobytes() + + +class _SSHFormatRSA(object): + """Format for RSA keys. + + Public: + mpint e, n + Private: + mpint n, e, d, iqmp, p, q + """ - if expected_key_type != b"ecdsa-sha2-" + curve_name: - raise ValueError( - 'Key header and key body contain different key type values.' + def get_public(self, data): + """RSA public fields""" + e, data = _get_mpint(data) + n, data = _get_mpint(data) + return (e, n), data + + def load_public(self, key_type, data, backend): + """Make RSA public key from data.""" + (e, n), data = self.get_public(data) + public_numbers = rsa.RSAPublicNumbers(e, n) + public_key = public_numbers.public_key(backend) + return public_key, data + + def load_private(self, data, pubfields, backend): + """Make RSA private key from data.""" + n, data = _get_mpint(data) + e, data = _get_mpint(data) + d, data = _get_mpint(data) + iqmp, data = _get_mpint(data) + p, data = _get_mpint(data) + q, data = _get_mpint(data) + + if (e, n) != pubfields: + raise ValueError("Corrupt data: rsa field mismatch") + dmp1 = rsa.rsa_crt_dmp1(d, p) + dmq1 = rsa.rsa_crt_dmq1(d, q) + public_numbers = rsa.RSAPublicNumbers(e, n) + private_numbers = rsa.RSAPrivateNumbers( + p, q, d, dmp1, dmq1, iqmp, public_numbers ) + private_key = private_numbers.private_key(backend) + return private_key, data - if rest: - raise ValueError('Key body contains extra bytes.') + def encode_public(self, public_key, f_pub): + """Write RSA public key""" + pubn = public_key.public_numbers() + f_pub.put_mpint(pubn.e) + f_pub.put_mpint(pubn.n) - curve = { - b"nistp256": ec.SECP256R1, - b"nistp384": ec.SECP384R1, - b"nistp521": ec.SECP521R1, - }[curve_name]() + def encode_private(self, private_key, f_priv): + """Write RSA private key""" + private_numbers = private_key.private_numbers() + public_numbers = private_numbers.public_numbers - if six.indexbytes(data, 0) != 4: - raise NotImplementedError( - "Compressed elliptic curve points are not supported" - ) + f_priv.put_mpint(public_numbers.n) + f_priv.put_mpint(public_numbers.e) + + f_priv.put_mpint(private_numbers.d) + f_priv.put_mpint(private_numbers.iqmp) + f_priv.put_mpint(private_numbers.p) + f_priv.put_mpint(private_numbers.q) - return ec.EllipticCurvePublicKey.from_encoded_point(curve, data) +class _SSHFormatDSA(object): + """Format for DSA keys. -def _ssh_read_next_string(data): + Public: + mpint p, q, g, y + Private: + mpint p, q, g, y, x """ - Retrieves the next RFC 4251 string value from the data. - While the RFC calls these strings, in Python they are bytes objects. + def get_public(self, data): + """DSA public fields""" + p, data = _get_mpint(data) + q, data = _get_mpint(data) + g, data = _get_mpint(data) + y, data = _get_mpint(data) + return (p, q, g, y), data + + def load_public(self, key_type, data, backend): + """Make DSA public key from data.""" + (p, q, g, y), data = self.get_public(data) + parameter_numbers = dsa.DSAParameterNumbers(p, q, g) + public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) + self._validate(public_numbers) + public_key = public_numbers.public_key(backend) + return public_key, data + + def load_private(self, data, pubfields, backend): + """Make DSA private key from data.""" + (p, q, g, y), data = self.get_public(data) + x, data = _get_mpint(data) + + if (p, q, g, y) != pubfields: + raise ValueError("Corrupt data: dsa field mismatch") + parameter_numbers = dsa.DSAParameterNumbers(p, q, g) + public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) + self._validate(public_numbers) + private_numbers = dsa.DSAPrivateNumbers(x, public_numbers) + private_key = private_numbers.private_key(backend) + return private_key, data + + def encode_public(self, public_key, f_pub): + """Write DSA public key""" + public_numbers = public_key.public_numbers() + parameter_numbers = public_numbers.parameter_numbers + self._validate(public_numbers) + + f_pub.put_mpint(parameter_numbers.p) + f_pub.put_mpint(parameter_numbers.q) + f_pub.put_mpint(parameter_numbers.g) + f_pub.put_mpint(public_numbers.y) + + def encode_private(self, private_key, f_priv): + """Write DSA private key""" + self.encode_public(private_key.public_key(), f_priv) + f_priv.put_mpint(private_key.private_numbers().x) + + def _validate(self, public_numbers): + parameter_numbers = public_numbers.parameter_numbers + if parameter_numbers.p.bit_length() != 1024: + raise ValueError("SSH supports only 1024 bit DSA keys") + + +class _SSHFormatECDSA(object): + """Format for ECDSA keys. + + Public: + str curve + bytes point + Private: + str curve + bytes point + mpint secret """ - if len(data) < 4: - raise ValueError("Key is not in the proper format") - str_len, = struct.unpack('>I', data[:4]) - if len(data) < str_len + 4: - raise ValueError("Key is not in the proper format") + def __init__(self, ssh_curve_name, curve): + self.ssh_curve_name = ssh_curve_name + self.curve = curve + + def get_public(self, data): + """ECDSA public fields""" + curve, data = _get_sshstr(data) + point, data = _get_sshstr(data) + if curve != self.ssh_curve_name: + raise ValueError("Curve name mismatch") + if six.indexbytes(point, 0) != 4: + raise NotImplementedError("Need uncompressed point") + return (curve, point), data + + def load_public(self, key_type, data, backend): + """Make ECDSA public key from data.""" + (curve_name, point), data = self.get_public(data) + public_key = ec.EllipticCurvePublicKey.from_encoded_point( + self.curve, point.tobytes() + ) + return public_key, data + + def load_private(self, data, pubfields, backend): + """Make ECDSA private key from data.""" + (curve_name, point), data = self.get_public(data) + secret, data = _get_mpint(data) + + if (curve_name, point) != pubfields: + raise ValueError("Corrupt data: ecdsa field mismatch") + private_key = ec.derive_private_key(secret, self.curve, backend) + return private_key, data + + def encode_public(self, public_key, f_pub): + """Write ECDSA public key""" + point = public_key.public_bytes( + Encoding.X962, PublicFormat.UncompressedPoint + ) + f_pub.put_sshstr(self.ssh_curve_name) + f_pub.put_sshstr(point) - return data[4:4 + str_len], data[4 + str_len:] + def encode_private(self, private_key, f_priv): + """Write ECDSA private key""" + public_key = private_key.public_key() + private_numbers = private_key.private_numbers() + self.encode_public(public_key, f_priv) + f_priv.put_mpint(private_numbers.private_value) -def _ssh_read_next_mpint(data): - """ - Reads the next mpint from the data. - Currently, all mpints are interpreted as unsigned. +class _SSHFormatEd25519(object): + """Format for Ed25519 keys. + + Public: + bytes point + Private: + bytes point + bytes secret_and_point """ - mpint_data, rest = _ssh_read_next_string(data) - return ( - utils.int_from_bytes(mpint_data, byteorder='big', signed=False), rest - ) + def get_public(self, data): + """Ed25519 public fields""" + point, data = _get_sshstr(data) + return (point,), data + def load_public(self, key_type, data, backend): + """Make Ed25519 public key from data.""" + (point,), data = self.get_public(data) + public_key = ed25519.Ed25519PublicKey.from_public_bytes( + point.tobytes() + ) + return public_key, data + + def load_private(self, data, pubfields, backend): + """Make Ed25519 private key from data.""" + (point,), data = self.get_public(data) + keypair, data = _get_sshstr(data) + + secret = keypair[:32] + point2 = keypair[32:] + if point != point2 or (point,) != pubfields: + raise ValueError("Corrupt data: ed25519 field mismatch") + private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret) + return private_key, data + + def encode_public(self, public_key, f_pub): + """Write Ed25519 public key""" + raw_public_key = public_key.public_bytes( + Encoding.Raw, PublicFormat.Raw + ) + f_pub.put_sshstr(raw_public_key) -def _ssh_write_string(data): - return struct.pack(">I", len(data)) + data + def encode_private(self, private_key, f_priv): + """Write Ed25519 private key""" + public_key = private_key.public_key() + raw_private_key = private_key.private_bytes( + Encoding.Raw, PrivateFormat.Raw, NoEncryption() + ) + raw_public_key = public_key.public_bytes( + Encoding.Raw, PublicFormat.Raw + ) + f_keypair = _FragList([raw_private_key, raw_public_key]) + + self.encode_public(public_key, f_priv) + f_priv.put_sshstr(f_keypair) + + +_KEY_FORMATS = { + _SSH_RSA: _SSHFormatRSA(), + _SSH_DSA: _SSHFormatDSA(), + _SSH_ED25519: _SSHFormatEd25519(), + _ECDSA_NISTP256: _SSHFormatECDSA(b"nistp256", ec.SECP256R1()), + _ECDSA_NISTP384: _SSHFormatECDSA(b"nistp384", ec.SECP384R1()), + _ECDSA_NISTP521: _SSHFormatECDSA(b"nistp521", ec.SECP521R1()), +} + + +def _lookup_kformat(key_type): + """Return valid format or throw error""" + if not isinstance(key_type, bytes): + key_type = memoryview(key_type).tobytes() + if key_type in _KEY_FORMATS: + return _KEY_FORMATS[key_type] + raise UnsupportedAlgorithm("Unsupported key type: %r" % key_type) + + +def load_ssh_private_key(data, password, backend=None): + """Load private key from OpenSSH custom encoding.""" + utils._check_byteslike("data", data) + backend = _get_backend(backend) + if password is not None: + utils._check_bytes("password", password) + + m = _PEM_RC.search(data) + if not m: + raise ValueError("Not OpenSSH private key format") + p1 = m.start(1) + p2 = m.end(1) + data = binascii.a2b_base64(memoryview(data)[p1:p2]) + if not data.startswith(_SK_MAGIC): + raise ValueError("Not OpenSSH private key format") + data = memoryview(data)[len(_SK_MAGIC) :] + + # parse header + ciphername, data = _get_sshstr(data) + kdfname, data = _get_sshstr(data) + kdfoptions, data = _get_sshstr(data) + nkeys, data = _get_u32(data) + if nkeys != 1: + raise ValueError("Only one key supported") + + # load public key data + pubdata, data = _get_sshstr(data) + pub_key_type, pubdata = _get_sshstr(pubdata) + kformat = _lookup_kformat(pub_key_type) + pubfields, pubdata = kformat.get_public(pubdata) + _check_empty(pubdata) + + # load secret data + edata, data = _get_sshstr(data) + _check_empty(data) + + if (ciphername, kdfname) != (_NONE, _NONE): + ciphername = ciphername.tobytes() + if ciphername not in _SSH_CIPHERS: + raise UnsupportedAlgorithm("Unsupported cipher: %r" % ciphername) + if kdfname != _BCRYPT: + raise UnsupportedAlgorithm("Unsupported KDF: %r" % kdfname) + blklen = _SSH_CIPHERS[ciphername][3] + _check_block_size(edata, blklen) + salt, kbuf = _get_sshstr(kdfoptions) + rounds, kbuf = _get_u32(kbuf) + _check_empty(kbuf) + ciph = _init_cipher( + ciphername, password, salt.tobytes(), rounds, backend + ) + edata = memoryview(ciph.decryptor().update(edata)) + else: + blklen = 8 + _check_block_size(edata, blklen) + ck1, edata = _get_u32(edata) + ck2, edata = _get_u32(edata) + if ck1 != ck2: + raise ValueError("Corrupt data: broken checksum") + + # load per-key struct + key_type, edata = _get_sshstr(edata) + if key_type != pub_key_type: + raise ValueError("Corrupt data: key type mismatch") + private_key, edata = kformat.load_private(edata, pubfields, backend) + comment, edata = _get_sshstr(edata) + + # yes, SSH does padding check *after* all other parsing is done. + # need to follow as it writes zero-byte padding too. + if edata != _PADDING[: len(edata)]: + raise ValueError("Corrupt data: invalid padding") + + return private_key + + +def serialize_ssh_private_key(private_key, password=None): + """Serialize private key with OpenSSH custom encoding.""" + if password is not None: + utils._check_bytes("password", password) + if password and len(password) > _MAX_PASSWORD: + raise ValueError( + "Passwords longer than 72 bytes are not supported by " + "OpenSSH private key format" + ) + + if isinstance(private_key, ec.EllipticCurvePrivateKey): + key_type = _ecdsa_key_type(private_key.public_key()) + elif isinstance(private_key, rsa.RSAPrivateKey): + key_type = _SSH_RSA + elif isinstance(private_key, dsa.DSAPrivateKey): + key_type = _SSH_DSA + elif isinstance(private_key, ed25519.Ed25519PrivateKey): + key_type = _SSH_ED25519 + else: + raise ValueError("Unsupported key type") + kformat = _lookup_kformat(key_type) + + # setup parameters + f_kdfoptions = _FragList() + if password: + ciphername = _DEFAULT_CIPHER + blklen = _SSH_CIPHERS[ciphername][3] + kdfname = _BCRYPT + rounds = _DEFAULT_ROUNDS + salt = os.urandom(16) + f_kdfoptions.put_sshstr(salt) + f_kdfoptions.put_u32(rounds) + backend = _get_backend(None) + ciph = _init_cipher(ciphername, password, salt, rounds, backend) + else: + ciphername = kdfname = _NONE + blklen = 8 + ciph = None + nkeys = 1 + checkval = os.urandom(4) + comment = b"" + + # encode public and private parts together + f_public_key = _FragList() + f_public_key.put_sshstr(key_type) + kformat.encode_public(private_key.public_key(), f_public_key) + + f_secrets = _FragList([checkval, checkval]) + f_secrets.put_sshstr(key_type) + kformat.encode_private(private_key, f_secrets) + f_secrets.put_sshstr(comment) + f_secrets.put_raw(_PADDING[: blklen - (f_secrets.size() % blklen)]) + + # top-level structure + f_main = _FragList() + f_main.put_raw(_SK_MAGIC) + f_main.put_sshstr(ciphername) + f_main.put_sshstr(kdfname) + f_main.put_sshstr(f_kdfoptions) + f_main.put_u32(nkeys) + f_main.put_sshstr(f_public_key) + f_main.put_sshstr(f_secrets) + + # copy result info bytearray + slen = f_secrets.size() + mlen = f_main.size() + buf = memoryview(bytearray(mlen + blklen)) + f_main.render(buf) + ofs = mlen - slen + + # encrypt in-place + if ciph is not None: + ciph.encryptor().update_into(buf[ofs:mlen], buf[ofs:]) + + txt = _ssh_pem_encode(buf[:mlen]) + buf[ofs:mlen] = bytearray(slen) + return txt + + +def load_ssh_public_key(data, backend=None): + """Load public key from OpenSSH one-line format.""" + backend = _get_backend(backend) + utils._check_byteslike("data", data) + + m = _SSH_PUBKEY_RC.match(data) + if not m: + raise ValueError("Invalid line format") + key_type = orig_key_type = m.group(1) + key_body = m.group(2) + with_cert = False + if _CERT_SUFFIX == key_type[-len(_CERT_SUFFIX) :]: + with_cert = True + key_type = key_type[: -len(_CERT_SUFFIX)] + kformat = _lookup_kformat(key_type) + + try: + data = memoryview(binascii.a2b_base64(key_body)) + except (TypeError, binascii.Error): + raise ValueError("Invalid key format") + + inner_key_type, data = _get_sshstr(data) + if inner_key_type != orig_key_type: + raise ValueError("Invalid key format") + if with_cert: + nonce, data = _get_sshstr(data) + public_key, data = kformat.load_public(key_type, data, backend) + if with_cert: + serial, data = _get_u64(data) + cctype, data = _get_u32(data) + key_id, data = _get_sshstr(data) + principals, data = _get_sshstr(data) + valid_after, data = _get_u64(data) + valid_before, data = _get_u64(data) + crit_options, data = _get_sshstr(data) + extensions, data = _get_sshstr(data) + reserved, data = _get_sshstr(data) + sig_key, data = _get_sshstr(data) + signature, data = _get_sshstr(data) + _check_empty(data) + return public_key + + +def serialize_ssh_public_key(public_key): + """One-line public key format for OpenSSH""" + if isinstance(public_key, ec.EllipticCurvePublicKey): + key_type = _ecdsa_key_type(public_key) + elif isinstance(public_key, rsa.RSAPublicKey): + key_type = _SSH_RSA + elif isinstance(public_key, dsa.DSAPublicKey): + key_type = _SSH_DSA + elif isinstance(public_key, ed25519.Ed25519PublicKey): + key_type = _SSH_ED25519 + else: + raise ValueError("Unsupported key type") + kformat = _lookup_kformat(key_type) + f_pub = _FragList() + f_pub.put_sshstr(key_type) + kformat.encode_public(public_key, f_pub) -def _ssh_write_mpint(value): - data = utils.int_to_bytes(value) - if six.indexbytes(data, 0) & 0x80: - data = b"\x00" + data - return _ssh_write_string(data) + pub = binascii.b2a_base64(f_pub.tobytes()).strip() + return b"".join([key_type, b" ", pub]) diff --git a/src/cryptography/hazmat/primitives/twofactor/hotp.py b/src/cryptography/hazmat/primitives/twofactor/hotp.py index 4ad1bdc2f..c00eec0e5 100644 --- a/src/cryptography/hazmat/primitives/twofactor/hotp.py +++ b/src/cryptography/hazmat/primitives/twofactor/hotp.py @@ -8,9 +8,8 @@ import struct import six -from cryptography.exceptions import ( - UnsupportedAlgorithm, _Reasons -) +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import constant_time, hmac from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512 @@ -19,12 +18,14 @@ from cryptography.hazmat.primitives.twofactor.utils import _generate_uri class HOTP(object): - def __init__(self, key, length, algorithm, backend, - enforce_key_length=True): + def __init__( + self, key, length, algorithm, backend=None, enforce_key_length=True + ): + backend = _get_backend(backend) if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) if len(key) < 16 and enforce_key_length is True: @@ -59,10 +60,10 @@ class HOTP(object): hmac_value = ctx.finalize() offset = six.indexbytes(hmac_value, len(hmac_value) - 1) & 0b1111 - p = hmac_value[offset:offset + 4] - return struct.unpack(">I", p)[0] & 0x7fffffff + p = hmac_value[offset : offset + 4] + return struct.unpack(">I", p)[0] & 0x7FFFFFFF def get_provisioning_uri(self, account_name, counter, issuer): - return _generate_uri(self, "hotp", account_name, issuer, [ - ("counter", int(counter)), - ]) + return _generate_uri( + self, "hotp", account_name, issuer, [("counter", int(counter))] + ) diff --git a/src/cryptography/hazmat/primitives/twofactor/totp.py b/src/cryptography/hazmat/primitives/twofactor/totp.py index 499f2824a..d59539b3f 100644 --- a/src/cryptography/hazmat/primitives/twofactor/totp.py +++ b/src/cryptography/hazmat/primitives/twofactor/totp.py @@ -4,9 +4,8 @@ from __future__ import absolute_import, division, print_function -from cryptography.exceptions import ( - UnsupportedAlgorithm, _Reasons -) +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.backends.interfaces import HMACBackend from cryptography.hazmat.primitives import constant_time from cryptography.hazmat.primitives.twofactor import InvalidToken @@ -15,12 +14,20 @@ from cryptography.hazmat.primitives.twofactor.utils import _generate_uri class TOTP(object): - def __init__(self, key, length, algorithm, time_step, backend, - enforce_key_length=True): + def __init__( + self, + key, + length, + algorithm, + time_step, + backend=None, + enforce_key_length=True, + ): + backend = _get_backend(backend) if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( "Backend object does not implement HMACBackend.", - _Reasons.BACKEND_MISSING_INTERFACE + _Reasons.BACKEND_MISSING_INTERFACE, ) self._time_step = time_step @@ -35,6 +42,10 @@ class TOTP(object): raise InvalidToken("Supplied TOTP value does not match.") def get_provisioning_uri(self, account_name, issuer): - return _generate_uri(self._hotp, "totp", account_name, issuer, [ - ("period", int(self._time_step)), - ]) + return _generate_uri( + self._hotp, + "totp", + account_name, + issuer, + [("period", int(self._time_step))], + ) diff --git a/src/cryptography/hazmat/primitives/twofactor/utils.py b/src/cryptography/hazmat/primitives/twofactor/utils.py index 0ed8c4c89..0afa1ccc0 100644 --- a/src/cryptography/hazmat/primitives/twofactor/utils.py +++ b/src/cryptography/hazmat/primitives/twofactor/utils.py @@ -23,8 +23,11 @@ def _generate_uri(hotp, type_name, account_name, issuer, extra_parameters): uriparts = { "type": type_name, - "label": ("%s:%s" % (quote(issuer), quote(account_name)) if issuer - else quote(account_name)), + "label": ( + "%s:%s" % (quote(issuer), quote(account_name)) + if issuer + else quote(account_name) + ), "parameters": urlencode(parameters), } return "otpauth://{type}/{label}?{parameters}".format(**uriparts) |