diff options
Diffstat (limited to 'src/cryptography/hazmat/primitives')
37 files changed, 1556 insertions, 497 deletions
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) |