diff options
95 files changed, 4472 insertions, 593 deletions
diff --git a/.travis.yml b/.travis.yml index b6fd6bd1..cacd1e27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,9 +39,6 @@ notifications: use_notice: true skip_join: true -# When building an exclude matrix on Travis you must supply the exact variable -# combinations you want to exclude from your build matrix. There is no -# (current) way to make this less verbose. matrix: exclude: - os: osx @@ -52,6 +49,7 @@ matrix: - os: osx env: TOX_ENV=py3pep8 compiler: clang + - os: linux env: TOX_ENV=docs compiler: clang diff --git a/.travis/install.sh b/.travis/install.sh index 79790050..e028033e 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -32,18 +32,18 @@ if [[ "$(uname -s)" == "Darwin" ]]; then if which pyenv > /dev/null; then eval "$(pyenv init -)"; fi case "${TOX_ENV}" in py26) - curl -O https://raw.githubusercontent.com/pypa/pip/master/contrib/get-pip.py + curl -O https://bootstrap.pypa.io/get-pip.py sudo python get-pip.py sudo pip install virtualenv ;; py27) - curl -O https://raw.githubusercontent.com/pypa/pip/master/contrib/get-pip.py + curl -O https://bootstrap.pypa.io/get-pip.py sudo python get-pip.py sudo pip install virtualenv ;; pypy) - pyenv install pypy-2.2.1 - pyenv global pypy-2.2.1 + pyenv install pypy-2.3 + pyenv global pypy-2.3 pip install virtualenv ;; py32) @@ -62,7 +62,7 @@ if [[ "$(uname -s)" == "Darwin" ]]; then pip install virtualenv ;; docs) - curl -O https://raw.githubusercontent.com/pypa/pip/master/contrib/get-pip.py + curl -O https://bootstrap.pypa.io/get-pip.py sudo python get-pip.py sudo pip install virtualenv ;; diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e09fa5d6..13c62de5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,19 +1,42 @@ Changelog ========= -0.4 - `master`_ +0.5 - `master`_ ~~~~~~~~~~~~~~~ .. note:: This version is not yet released and is under active development. +* Added :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDFExpand`. +* Added :class:`~cryptography.hazmat.primitives.ciphers.modes.CFB8` support + for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` and + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on + :doc:`/hazmat/backends/commoncrypto` and :doc:`/hazmat/backends/openssl`. +* Added ``AES`` :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR` + support to the OpenSSL backend when linked against 0.9.8. +* Added + :class:`~cryptography.hazmat.backends.interfaces.PKCS8SerializationBackend` + and + :class:`~cryptography.hazmat.backends.interfaces.TraditionalOpenSSLSerializationBackend` + support to the :doc:`/hazmat/backends/openssl`. + + +0.4 - 2014-05-03 +~~~~~~~~~~~~~~~~ + * Deprecated ``salt_length`` on :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF1` and added it to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. It will be removed from ``MGF1`` in two releases per our :doc:`/api-stability` policy. * Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SEED` support. * Added :class:`~cryptography.hazmat.primitives.cmac.CMAC`. -* Added decryption support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` - and encryption support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. +* Added decryption support to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + and encryption support to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. +* Added signature support to + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` + and verification support to + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. 0.3 - 2014-03-27 ~~~~~~~~~~~~~~~~ @@ -21,8 +44,10 @@ Changelog * Added :class:`~cryptography.hazmat.primitives.twofactor.hotp.HOTP`. * Added :class:`~cryptography.hazmat.primitives.twofactor.totp.TOTP`. * Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.IDEA` support. -* Added signature support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` - and verification support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. +* Added signature support to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + and verification support to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. * Moved test vectors to the new ``cryptography_vectors`` package. 0.2.2 - 2014-03-03 @@ -33,7 +58,8 @@ Changelog 0.2.1 - 2014-02-22 ~~~~~~~~~~~~~~~~~~ -* Fix a bug where importing cryptography from multiple paths could cause initialization to fail. +* Fix a bug where importing cryptography from multiple paths could cause + initialization to fail. 0.2 - 2014-02-20 ~~~~~~~~~~~~~~~~ @@ -49,8 +75,10 @@ Changelog * Added :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`. * Added :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. * Added :doc:`/hazmat/backends/multibackend`. -* Set default random for the :doc:`/hazmat/backends/openssl` to the OS random engine. -* Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.CAST5` (CAST-128) support. +* Set default random for the :doc:`/hazmat/backends/openssl` to the OS + random engine. +* Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.CAST5` + (CAST-128) support. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ @@ -1,6 +1,10 @@ Cryptography ============ +.. image:: https://pypip.in/version/cryptography/badge.svg + :target: https://pypi.python.org/pypi/cryptography/ + :alt: Latest Version + .. image:: https://travis-ci.org/pyca/cryptography.svg?branch=master :target: https://travis-ci.org/pyca/cryptography diff --git a/cryptography/__about__.py b/cryptography/__about__.py index ce0f32fc..ee53902b 100644 --- a/cryptography/__about__.py +++ b/cryptography/__about__.py @@ -22,7 +22,7 @@ __summary__ = ("cryptography is a package which provides cryptographic recipes" " and primitives to Python developers.") __uri__ = "https://github.com/pyca/cryptography" -__version__ = "0.4.dev1" +__version__ = "0.5.dev1" __author__ = "The cryptography developers" __email__ = "cryptography-dev@python.org" diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 674ce8ae..cdb9bdca 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -43,7 +43,7 @@ class Fernet(object): key = base64.urlsafe_b64decode(key) if len(key) != 32: raise ValueError( - "Fernet key must be 32 url-safe base64-encoded bytes" + "Fernet key must be 32 url-safe base64-encoded bytes." ) self._signing_key = key[:16] @@ -60,10 +60,8 @@ class Fernet(object): return self._encrypt_from_parts(data, current_time, iv) def _encrypt_from_parts(self, data, current_time, iv): - if isinstance(data, six.text_type): - raise TypeError( - "Unicode-objects must be encoded before encryption" - ) + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() @@ -82,10 +80,8 @@ class Fernet(object): return base64.urlsafe_b64encode(basic_parts + hmac) def decrypt(self, token, ttl=None): - if isinstance(token, six.text_type): - raise TypeError( - "Unicode-objects must be encoded before decryption" - ) + if not isinstance(token, bytes): + raise TypeError("token must be bytes.") current_time = int(time.time()) diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py index 4faca73e..213cbd8c 100644 --- a/cryptography/hazmat/backends/commoncrypto/backend.py +++ b/cryptography/hazmat/backends/commoncrypto/backend.py @@ -28,7 +28,7 @@ from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, Blowfish, CAST5, TripleDES ) from cryptography.hazmat.primitives.ciphers.modes import ( - CBC, CFB, CTR, ECB, GCM, OFB + CBC, CFB, CFB8, CTR, ECB, GCM, OFB ) @@ -154,7 +154,7 @@ class Backend(object): def _register_cipher_adapter(self, cipher_cls, cipher_const, mode_cls, mode_const): if (cipher_cls, mode_cls) in self._cipher_registry: - raise ValueError("Duplicate registration for: {0} {1}".format( + raise ValueError("Duplicate registration for: {0} {1}.".format( cipher_cls, mode_cls) ) self._cipher_registry[cipher_cls, mode_cls] = (cipher_const, @@ -165,6 +165,7 @@ class Backend(object): (CBC, self._lib.kCCModeCBC), (ECB, self._lib.kCCModeECB), (CFB, self._lib.kCCModeCFB), + (CFB8, self._lib.kCCModeCFB8), (OFB, self._lib.kCCModeOFB), (CTR, self._lib.kCCModeCTR), (GCM, self._lib.kCCModeGCM), @@ -178,6 +179,7 @@ class Backend(object): for mode_cls, mode_const in [ (CBC, self._lib.kCCModeCBC), (CFB, self._lib.kCCModeCFB), + (CFB8, self._lib.kCCModeCFB8), (OFB, self._lib.kCCModeOFB), ]: self._register_cipher_adapter( @@ -226,7 +228,7 @@ class Backend(object): # rdar://15589470 raise ValueError( "The length of the provided data is not a multiple of " - "the block length" + "the block length." ) else: raise InternalError( @@ -264,7 +266,7 @@ class _CipherContext(object): # This bug has been filed as rdar://15589470 self._bytes_processed = 0 if (isinstance(cipher, interfaces.BlockCipherAlgorithm) and not - isinstance(mode, (OFB, CFB, CTR))): + isinstance(mode, (OFB, CFB, CFB8, CTR))): self._byte_block_size = cipher.block_size // 8 else: self._byte_block_size = 1 @@ -275,7 +277,7 @@ class _CipherContext(object): except KeyError: raise UnsupportedAlgorithm( "cipher {0} in {1} mode is not supported " - "by this backend".format( + "by this backend.".format( cipher.name, mode.name if mode else mode), _Reasons.UNSUPPORTED_CIPHER ) @@ -322,7 +324,7 @@ class _CipherContext(object): if self._bytes_processed % self._byte_block_size: raise ValueError( "The length of the provided data is not a multiple of " - "the block length" + "the block length." ) buf = self._backend._ffi.new("unsigned char[]", self._byte_block_size) outlen = self._backend._ffi.new("size_t *") @@ -349,7 +351,7 @@ class _GCMCipherContext(object): except KeyError: raise UnsupportedAlgorithm( "cipher {0} in {1} mode is not supported " - "by this backend".format( + "by this backend.".format( cipher.name, mode.name if mode else mode), _Reasons.UNSUPPORTED_CIPHER ) @@ -423,7 +425,7 @@ class _HashContext(object): methods = self._backend._hash_mapping[self.algorithm.name] except KeyError: raise UnsupportedAlgorithm( - "{0} is not a supported hash on this backend".format( + "{0} is not a supported hash on this backend.".format( algorithm.name), _Reasons.UNSUPPORTED_HASH ) @@ -467,7 +469,7 @@ class _HMACContext(object): alg = self._backend._supported_hmac_algorithms[algorithm.name] except KeyError: raise UnsupportedAlgorithm( - "{0} is not a supported HMAC hash on this backend".format( + "{0} is not a supported HMAC hash on this backend.".format( algorithm.name), _Reasons.UNSUPPORTED_HASH ) diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index aaaca5e2..ba02bbd2 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -129,6 +129,19 @@ class RSABackend(object): Returns encrypted bytes. """ + @abc.abstractmethod + def rsa_padding_supported(self, padding): + """ + Returns True if the backend supports the given padding options. + """ + + @abc.abstractmethod + def generate_rsa_parameters_supported(self, public_exponent, key_size): + """ + Returns True if the backend supports the given parameters for key + generation. + """ + @six.add_metaclass(abc.ABCMeta) class DSABackend(object): @@ -145,6 +158,32 @@ class DSABackend(object): a DSAParameters object. """ + @abc.abstractmethod + def create_dsa_signature_ctx(self, private_key, algorithm): + """ + Returns an object conforming to the AsymmetricSignatureContext + interface. + """ + + @abc.abstractmethod + def create_dsa_verification_ctx(self, public_key, signature, algorithm): + """ + Returns an object conforming to the AsymmetricVerificationContext + interface. + """ + + @abc.abstractmethod + def dsa_hash_supported(self, algorithm): + """ + Return True if the hash algorithm is supported by the backend for DSA. + """ + + @abc.abstractmethod + def dsa_parameters_supported(self, p, q, g): + """ + Return True if the parameters are supported by the backend for DSA. + """ + @six.add_metaclass(abc.ABCMeta) class TraditionalOpenSSLSerializationBackend(object): @@ -157,6 +196,16 @@ class TraditionalOpenSSLSerializationBackend(object): @six.add_metaclass(abc.ABCMeta) +class PKCS8SerializationBackend(object): + @abc.abstractmethod + def load_pkcs8_pem_private_key(self, data, password): + """ + Load a private key from PEM encoded data, using password if the data + is encrypted. + """ + + +@six.add_metaclass(abc.ABCMeta) class CMACBackend(object): @abc.abstractmethod def cmac_algorithm_supported(self, algorithm): @@ -169,3 +218,39 @@ class CMACBackend(object): """ Create a CMACContext for calculating a message authentication code. """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurveBackend(object): + @abc.abstractmethod + def elliptic_curve_signature_algorithm_supported( + self, signature_algorithm, curve + ): + """ + Returns True if the backend supports the named elliptic curve with the + specified signature algorithm. + """ + + @abc.abstractmethod + def elliptic_curve_supported(self, curve): + """ + Returns True if the backend supports the named elliptic curve. + """ + + @abc.abstractmethod + def generate_elliptic_curve_private_key(self, curve): + """ + Return an object conforming to the EllipticCurvePrivateKey interface. + """ + + @abc.abstractmethod + def elliptic_curve_public_key_from_numbers(self, numbers): + """ + Return an EllipticCurvePublicKey provider using the given numbers. + """ + + @abc.abstractmethod + def elliptic_curve_private_key_from_numbers(self, numbers): + """ + Return an EllipticCurvePublicKey provider using the given numbers. + """ diff --git a/cryptography/hazmat/backends/multibackend.py b/cryptography/hazmat/backends/multibackend.py index 981a60bd..b4cb6889 100644 --- a/cryptography/hazmat/backends/multibackend.py +++ b/cryptography/hazmat/backends/multibackend.py @@ -52,7 +52,7 @@ class MultiBackend(object): except UnsupportedAlgorithm: pass raise UnsupportedAlgorithm( - "cipher {0} in {1} mode is not supported by this backend".format( + "cipher {0} in {1} mode is not supported by this backend.".format( algorithm.name, mode.name if mode else mode), _Reasons.UNSUPPORTED_CIPHER ) @@ -64,7 +64,7 @@ class MultiBackend(object): except UnsupportedAlgorithm: pass raise UnsupportedAlgorithm( - "cipher {0} in {1} mode is not supported by this backend".format( + "cipher {0} in {1} mode is not supported by this backend.".format( algorithm.name, mode.name if mode else mode), _Reasons.UNSUPPORTED_CIPHER ) @@ -82,7 +82,7 @@ class MultiBackend(object): except UnsupportedAlgorithm: pass raise UnsupportedAlgorithm( - "{0} is not a supported hash on this backend".format( + "{0} is not a supported hash on this backend.".format( algorithm.name), _Reasons.UNSUPPORTED_HASH ) @@ -100,7 +100,7 @@ class MultiBackend(object): except UnsupportedAlgorithm: pass raise UnsupportedAlgorithm( - "{0} is not a supported hash on this backend".format( + "{0} is not a supported hash on this backend.".format( algorithm.name), _Reasons.UNSUPPORTED_HASH ) @@ -121,7 +121,7 @@ class MultiBackend(object): except UnsupportedAlgorithm: pass raise UnsupportedAlgorithm( - "{0} is not a supported hash on this backend".format( + "{0} is not a supported hash on this backend.".format( algorithm.name), _Reasons.UNSUPPORTED_HASH ) @@ -129,13 +129,21 @@ class MultiBackend(object): def generate_rsa_private_key(self, public_exponent, key_size): for b in self._filtered_backends(RSABackend): return b.generate_rsa_private_key(public_exponent, key_size) - raise UnsupportedAlgorithm("RSA is not supported by the backend", + raise UnsupportedAlgorithm("RSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def generate_rsa_parameters_supported(self, public_exponent, key_size): + for b in self._filtered_backends(RSABackend): + return b.generate_rsa_parameters_supported( + public_exponent, key_size + ) + raise UnsupportedAlgorithm("RSA is not supported by the backend.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) def create_rsa_signature_ctx(self, private_key, padding, algorithm): for b in self._filtered_backends(RSABackend): return b.create_rsa_signature_ctx(private_key, padding, algorithm) - raise UnsupportedAlgorithm("RSA is not supported by the backend", + raise UnsupportedAlgorithm("RSA is not supported by the backend.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) def create_rsa_verification_ctx(self, public_key, signature, padding, @@ -143,19 +151,68 @@ class MultiBackend(object): for b in self._filtered_backends(RSABackend): return b.create_rsa_verification_ctx(public_key, signature, padding, algorithm) - raise UnsupportedAlgorithm("RSA is not supported by the backend", + raise UnsupportedAlgorithm("RSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def mgf1_hash_supported(self, algorithm): + for b in self._filtered_backends(RSABackend): + return b.mgf1_hash_supported(algorithm) + raise UnsupportedAlgorithm("RSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def decrypt_rsa(self, private_key, ciphertext, padding): + for b in self._filtered_backends(RSABackend): + return b.decrypt_rsa(private_key, ciphertext, padding) + raise UnsupportedAlgorithm("RSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def encrypt_rsa(self, public_key, plaintext, padding): + for b in self._filtered_backends(RSABackend): + return b.encrypt_rsa(public_key, plaintext, padding) + raise UnsupportedAlgorithm("RSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def rsa_padding_supported(self, padding): + for b in self._filtered_backends(RSABackend): + return b.rsa_padding_supported(padding) + raise UnsupportedAlgorithm("RSA is not supported by the backend.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) def generate_dsa_parameters(self, key_size): for b in self._filtered_backends(DSABackend): return b.generate_dsa_parameters(key_size) - raise UnsupportedAlgorithm("DSA is not supported by the backend", + raise UnsupportedAlgorithm("DSA is not supported by the backend.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) def generate_dsa_private_key(self, parameters): for b in self._filtered_backends(DSABackend): return b.generate_dsa_private_key(parameters) - raise UnsupportedAlgorithm("DSA is not supported by the backend", + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def create_dsa_verification_ctx(self, public_key, signature, algorithm): + for b in self._filtered_backends(DSABackend): + return b.create_dsa_verification_ctx(public_key, signature, + algorithm) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def create_dsa_signature_ctx(self, private_key, algorithm): + for b in self._filtered_backends(DSABackend): + return b.create_dsa_signature_ctx(private_key, algorithm) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def dsa_hash_supported(self, algorithm): + for b in self._filtered_backends(DSABackend): + return b.dsa_hash_supported(algorithm) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def dsa_parameters_supported(self, p, q, g): + for b in self._filtered_backends(DSABackend): + return b.dsa_parameters_supported(p, q, g) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) def cmac_algorithm_supported(self, algorithm): @@ -170,5 +227,5 @@ class MultiBackend(object): return b.create_cmac_ctx(algorithm) except UnsupportedAlgorithm: pass - raise UnsupportedAlgorithm("This backend does not support CMAC", + raise UnsupportedAlgorithm("This backend does not support CMAC.", _Reasons.UNSUPPORTED_CIPHER) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index f9154f3b..4112f0e5 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -26,7 +26,8 @@ from cryptography.exceptions import ( ) from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend, - PBKDF2HMACBackend, RSABackend + PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend, + TraditionalOpenSSLSerializationBackend ) from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import hashes, interfaces @@ -38,10 +39,11 @@ from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES ) from cryptography.hazmat.primitives.ciphers.modes import ( - CBC, CFB, CTR, ECB, GCM, OFB + CBC, CFB, CFB8, CTR, ECB, GCM, OFB ) +_MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) _OpenSSLError = collections.namedtuple("_OpenSSLError", ["code", "lib", "func", "reason"]) @@ -53,6 +55,8 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError", @utils.register_interface(HMACBackend) @utils.register_interface(PBKDF2HMACBackend) @utils.register_interface(RSABackend) +@utils.register_interface(TraditionalOpenSSLSerializationBackend) +@utils.register_interface(PKCS8SerializationBackend) class Backend(object): """ OpenSSL API binding interfaces. @@ -112,11 +116,14 @@ class Backend(object): def openssl_version_text(self): """ - Friendly string name of linked OpenSSL. + Friendly string name of the loaded OpenSSL library. This is not + necessarily the same version as it was compiled against. Example: OpenSSL 1.0.1e 11 Feb 2013 """ - return self._ffi.string(self._lib.OPENSSL_VERSION_TEXT).decode("ascii") + return self._ffi.string( + self._lib.SSLeay_version(self._lib.SSLEAY_VERSION) + ).decode("ascii") def create_hmac_ctx(self, key, algorithm): return _HMACContext(self, key, algorithm) @@ -132,6 +139,14 @@ class Backend(object): return _HashContext(self, algorithm) def cipher_supported(self, cipher, mode): + if self._evp_cipher_supported(cipher, mode): + return True + elif isinstance(mode, CTR) and isinstance(cipher, AES): + return True + else: + return False + + def _evp_cipher_supported(self, cipher, mode): try: adapter = self._cipher_registry[type(cipher), type(mode)] except KeyError: @@ -141,22 +156,25 @@ class Backend(object): def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): if (cipher_cls, mode_cls) in self._cipher_registry: - raise ValueError("Duplicate registration for: {0} {1}".format( + raise ValueError("Duplicate registration for: {0} {1}.".format( cipher_cls, mode_cls) ) self._cipher_registry[cipher_cls, mode_cls] = adapter def _register_default_ciphers(self): - for cipher_cls, mode_cls in itertools.product( - [AES, Camellia], - [CBC, CTR, ECB, OFB, CFB], - ): + for mode_cls in [CBC, CTR, ECB, OFB, CFB, CFB8]: self.register_cipher_adapter( - cipher_cls, + AES, mode_cls, GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") ) - for mode_cls in [CBC, CFB, OFB]: + for mode_cls in [CBC, CTR, ECB, OFB, CFB]: + self.register_cipher_adapter( + Camellia, + mode_cls, + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + ) + for mode_cls in [CBC, CFB, CFB8, OFB]: self.register_cipher_adapter( TripleDES, mode_cls, @@ -195,10 +213,24 @@ class Backend(object): ) def create_symmetric_encryption_ctx(self, cipher, mode): - return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) + if (isinstance(mode, CTR) and isinstance(cipher, AES) + and not self._evp_cipher_supported(cipher, mode)): + # This is needed to provide support for AES CTR mode in OpenSSL + # 0.9.8. It can be removed when we drop 0.9.8 support (RHEL 5 + # extended life ends 2020). + return _AESCTRCipherContext(self, cipher, mode) + else: + return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) def create_symmetric_decryption_ctx(self, cipher, mode): - return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + if (isinstance(mode, CTR) and isinstance(cipher, AES) + and not self._evp_cipher_supported(cipher, mode)): + # This is needed to provide support for AES CTR mode in OpenSSL + # 0.9.8. It can be removed when we drop 0.9.8 support (RHEL 5 + # extended life ends 2020). + return _AESCTRCipherContext(self, cipher, mode) + else: + return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) def pbkdf2_hmac_supported(self, algorithm): if self._lib.Cryptography_HAS_PBKDF2_HMAC: @@ -231,7 +263,7 @@ class Backend(object): if not isinstance(algorithm, hashes.SHA1): raise UnsupportedAlgorithm( "This version of OpenSSL only supports PBKDF2HMAC with " - "SHA1", + "SHA1.", _Reasons.UNSUPPORTED_HASH ) res = self._lib.PKCS5_PBKDF2_HMAC_SHA1( @@ -269,7 +301,7 @@ class Backend(object): def _unknown_error(self, error): return InternalError( "Unknown error code {0} from OpenSSL, " - "you should probably file a bug. {1}".format( + "you should probably file a bug. {1}.".format( error.code, self._err_string(error.code) ) ) @@ -325,14 +357,7 @@ class Backend(object): return bn_ptr[0] def generate_rsa_private_key(self, 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 key_size < 512: - raise ValueError("key_size must be at least 512-bits") + rsa._verify_rsa_parameters(public_exponent, key_size) ctx = self._lib.RSA_new() assert ctx != self._ffi.NULL @@ -348,6 +373,10 @@ class Backend(object): return self._rsa_cdata_to_private_key(ctx) + def generate_rsa_parameters_supported(self, public_exponent, key_size): + return (public_exponent >= 3 and public_exponent & 1 != 0 and + key_size >= 512) + def _new_evp_pkey(self): evp_pkey = self._lib.EVP_PKEY_new() assert evp_pkey != self._ffi.NULL @@ -371,6 +400,51 @@ class Backend(object): return evp_pkey + def _bytes_to_bio(self, data): + """ + Return a _MemoryBIO namedtuple of (BIO, char*). + + The char* is the storage for the BIO and it must stay alive until the + BIO is finished with. + """ + data_char_p = backend._ffi.new("char[]", data) + bio = backend._lib.BIO_new_mem_buf( + data_char_p, len(data) + ) + assert bio != self._ffi.NULL + + return _MemoryBIO(self._ffi.gc(bio, self._lib.BIO_free), data_char_p) + + def _evp_pkey_to_private_key(self, evp_pkey): + """ + Return the appropriate type of PrivateKey given an evp_pkey cdata + pointer. + """ + + type = evp_pkey.type + + if type == self._lib.EVP_PKEY_RSA: + rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) + assert rsa_cdata != self._ffi.NULL + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + return self._rsa_cdata_to_private_key(rsa_cdata) + elif type == self._lib.EVP_PKEY_DSA: + dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) + assert dsa_cdata != self._ffi.NULL + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + return self._dsa_cdata_to_private_key(dsa_cdata) + else: + raise UnsupportedAlgorithm("Unsupported key type.") + + def _dsa_cdata_to_private_key(self, cdata): + return dsa.DSAPrivateKey( + modulus=self._bn_to_int(cdata.p), + subgroup_order=self._bn_to_int(cdata.q), + generator=self._bn_to_int(cdata.g), + x=self._bn_to_int(cdata.priv_key), + y=self._bn_to_int(cdata.pub_key) + ) + def _rsa_cdata_to_private_key(self, cdata): return rsa.RSAPrivateKey( p=self._bn_to_int(cdata.p), @@ -383,6 +457,37 @@ class Backend(object): modulus=self._bn_to_int(cdata.n), ) + def _pem_password_cb(self, password): + """ + Generate a pem_password_cb function pointer that copied the password to + OpenSSL as required and returns the number of bytes copied. + + typedef int pem_password_cb(char *buf, int size, + int rwflag, void *userdata); + + Useful for decrypting PKCS8 files and so on. + + Returns a tuple of (cdata function pointer, callback function). + """ + + def pem_password_cb(buf, size, writing, userdata): + pem_password_cb.called += 1 + + if not password or len(password) >= size: + return 0 + else: + pw_buf = self._ffi.buffer(buf, size) + pw_buf[:len(password)] = password + return len(password) + + pem_password_cb.called = 0 + + return ( + self._ffi.callback("int (char *, int, int, void *)", + pem_password_cb), + pem_password_cb + ) + def _rsa_cdata_from_private_key(self, private_key): # Does not GC the RSA cdata. You *must* make sure it's freed # correctly yourself! @@ -428,16 +533,26 @@ class Backend(object): else: return isinstance(algorithm, hashes.SHA1) + def rsa_padding_supported(self, padding): + if isinstance(padding, PKCS1v15): + return True + elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): + return self.mgf1_hash_supported(padding._mgf._algorithm) + elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): + return isinstance(padding._mgf._algorithm, hashes.SHA1) + else: + return False + def generate_dsa_parameters(self, key_size): if key_size not in (1024, 2048, 3072): raise ValueError( - "Key size must be 1024 or 2048 or 3072 bits") + "Key size must be 1024 or 2048 or 3072 bits.") if (self._lib.OPENSSL_VERSION_NUMBER < 0x1000000f and key_size > 1024): raise ValueError( "Key size must be 1024 because OpenSSL < 1.0.0 doesn't " - "support larger key sizes") + "support larger key sizes.") ctx = self._lib.DSA_new() assert ctx != self._ffi.NULL @@ -474,6 +589,51 @@ class Backend(object): y=self._bn_to_int(ctx.pub_key) ) + def create_dsa_signature_ctx(self, private_key, algorithm): + return _DSASignatureContext(self, private_key, algorithm) + + def create_dsa_verification_ctx(self, public_key, signature, + algorithm): + return _DSAVerificationContext(self, public_key, signature, + algorithm) + + def _dsa_cdata_from_public_key(self, public_key): + # Does not GC the DSA cdata. You *must* make sure it's freed + # correctly yourself! + ctx = self._lib.DSA_new() + assert ctx != self._ffi.NULL + parameters = public_key.parameters() + ctx.p = self._int_to_bn(parameters.p) + ctx.q = self._int_to_bn(parameters.q) + ctx.g = self._int_to_bn(parameters.g) + ctx.pub_key = self._int_to_bn(public_key.y) + return ctx + + def _dsa_cdata_from_private_key(self, private_key): + # Does not GC the DSA cdata. You *must* make sure it's freed + # correctly yourself! + ctx = self._lib.DSA_new() + assert ctx != self._ffi.NULL + parameters = private_key.parameters() + ctx.p = self._int_to_bn(parameters.p) + ctx.q = self._int_to_bn(parameters.q) + ctx.g = self._int_to_bn(parameters.g) + ctx.priv_key = self._int_to_bn(private_key.x) + ctx.pub_key = self._int_to_bn(private_key.y) + return ctx + + def dsa_hash_supported(self, algorithm): + if self._lib.OPENSSL_VERSION_NUMBER < 0x1000000f: + return isinstance(algorithm, hashes.SHA1) + else: + return self.hash_supported(algorithm) + + def dsa_parameters_supported(self, p, q, g): + if self._lib.OPENSSL_VERSION_NUMBER < 0x1000000f: + return (utils.bit_length(p) <= 1024 and utils.bit_length(q) <= 160) + else: + return True + def decrypt_rsa(self, private_key, ciphertext, padding): key_size_bytes = int(math.ceil(private_key.key_size / 8.0)) if key_size_bytes != len(ciphertext): @@ -491,28 +651,28 @@ class Backend(object): padding_enum = self._lib.RSA_PKCS1_OAEP_PADDING if not isinstance(padding._mgf, MGF1): raise UnsupportedAlgorithm( - "Only MGF1 is supported by this backend", + "Only MGF1 is supported by this backend.", _Reasons.UNSUPPORTED_MGF ) if not isinstance(padding._mgf._algorithm, hashes.SHA1): raise UnsupportedAlgorithm( "This backend supports only SHA1 inside MGF1 when " - "using OAEP", + "using OAEP.", _Reasons.UNSUPPORTED_HASH ) if padding._label is not None and padding._label != b"": - raise ValueError("This backend does not support OAEP labels") + raise ValueError("This backend does not support OAEP labels.") if not isinstance(padding._algorithm, hashes.SHA1): raise UnsupportedAlgorithm( - "This backend only supports SHA1 when using OAEP", + "This backend only supports SHA1 when using OAEP.", _Reasons.UNSUPPORTED_HASH ) else: raise UnsupportedAlgorithm( - "{0} is not supported by this backend".format( + "{0} is not supported by this backend.".format( padding.name ), _Reasons.UNSUPPORTED_PADDING @@ -592,14 +752,14 @@ class Backend(object): self._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE) raise ValueError( "Data too long for key size. Encrypt less data or use a " - "larger key size" + "larger key size." ) else: assert ( errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02 ) - raise ValueError("Decryption failed") + raise ValueError("Decryption failed.") def cmac_algorithm_supported(self, algorithm): return ( @@ -611,6 +771,107 @@ class Backend(object): def create_cmac_ctx(self, algorithm): return _CMACContext(self, algorithm) + def load_traditional_openssl_pem_private_key(self, data, password): + # OpenSSLs API for loading PKCS#8 certs can also load the traditional + # format so we just use that for both of them. + + return self.load_pkcs8_pem_private_key(data, password) + + def load_pkcs8_pem_private_key(self, data, password): + mem_bio = self._bytes_to_bio(data) + + password_callback, password_func = self._pem_password_cb(password) + + evp_pkey = self._lib.PEM_read_bio_PrivateKey( + mem_bio.bio, + self._ffi.NULL, + password_callback, + self._ffi.NULL + ) + + if evp_pkey == self._ffi.NULL: + errors = self._consume_errors() + if not errors: + raise ValueError("Could not unserialize key data.") + + if ( + errors[0][1:] == ( + self._lib.ERR_LIB_PEM, + self._lib.PEM_F_PEM_DO_HEADER, + self._lib.PEM_R_BAD_PASSWORD_READ + ) + ) or ( + errors[0][1:] == ( + self._lib.ERR_LIB_PEM, + self._lib.PEM_F_PEM_READ_BIO_PRIVATEKEY, + self._lib.PEM_R_BAD_PASSWORD_READ + ) + ): + assert not password + raise TypeError( + "Password was not given but private key is encrypted.") + + elif errors[0][1:] == ( + self._lib.ERR_LIB_EVP, + self._lib.EVP_F_EVP_DECRYPTFINAL_EX, + self._lib.EVP_R_BAD_DECRYPT + ): + raise ValueError( + "Bad decrypt. Incorrect password?" + ) + + elif errors[0][1:] in ( + ( + self._lib.ERR_LIB_PEM, + self._lib.PEM_F_PEM_GET_EVP_CIPHER_INFO, + self._lib.PEM_R_UNSUPPORTED_ENCRYPTION + ), + + ( + self._lib.ERR_LIB_EVP, + self._lib.EVP_F_EVP_PBE_CIPHERINIT, + self._lib.EVP_R_UNKNOWN_PBE_ALGORITHM + ) + ): + raise UnsupportedAlgorithm( + "PEM data is encrypted with an unsupported cipher", + _Reasons.UNSUPPORTED_CIPHER + ) + + elif any( + error[1:] == ( + self._lib.ERR_LIB_EVP, + self._lib.EVP_F_EVP_PKCS82PKEY, + self._lib.EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM + ) + for error in errors + ): + raise UnsupportedAlgorithm( + "Unsupported public key algorithm.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ) + + else: + assert errors[0][1] in ( + self._lib.ERR_LIB_EVP, + self._lib.ERR_LIB_PEM, + self._lib.ERR_LIB_ASN1, + ) + raise ValueError("Could not unserialize key data.") + + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + + if password is not None and password_func.called == 0: + raise TypeError( + "Password was given but private key is not encrypted.") + + assert ( + (password is not None and password_func.called == 1) or + password is None + ) + + return self._evp_pkey_to_private_key(evp_pkey) + class GetCipherByName(object): def __init__(self, fmt): @@ -651,7 +912,7 @@ class _CipherContext(object): except KeyError: raise UnsupportedAlgorithm( "cipher {0} in {1} mode is not supported " - "by this backend".format( + "by this backend.".format( cipher.name, mode.name if mode else mode), _Reasons.UNSUPPORTED_CIPHER ) @@ -660,7 +921,7 @@ class _CipherContext(object): if evp_cipher == self._backend._ffi.NULL: raise UnsupportedAlgorithm( "cipher {0} in {1} mode is not supported " - "by this backend".format( + "by this backend.".format( cipher.name, mode.name if mode else mode), _Reasons.UNSUPPORTED_CIPHER ) @@ -786,6 +1047,41 @@ class _CipherContext(object): return self._tag +@utils.register_interface(interfaces.CipherContext) +class _AESCTRCipherContext(object): + """ + This is needed to provide support for AES CTR mode in OpenSSL 0.9.8. It can + be removed when we drop 0.9.8 support (RHEL5 extended life ends 2020). + """ + def __init__(self, backend, cipher, mode): + self._backend = backend + + self._key = self._backend._ffi.new("AES_KEY *") + assert self._key != self._backend._ffi.NULL + res = self._backend._lib.AES_set_encrypt_key( + cipher.key, len(cipher.key) * 8, self._key + ) + assert res == 0 + self._ecount = self._backend._ffi.new("char[]", 16) + self._nonce = self._backend._ffi.new("char[16]", mode.nonce) + self._num = self._backend._ffi.new("unsigned int *", 0) + + def update(self, data): + buf = self._backend._ffi.new("unsigned char[]", len(data)) + self._backend._lib.AES_ctr128_encrypt( + data, buf, len(data), self._key, self._nonce, + self._ecount, self._num + ) + return self._backend._ffi.buffer(buf)[:] + + def finalize(self): + self._key = None + self._ecount = None + self._nonce = None + self._num = None + return b"" + + @utils.register_interface(interfaces.HashContext) class _HashContext(object): def __init__(self, backend, algorithm, ctx=None): @@ -801,7 +1097,7 @@ class _HashContext(object): algorithm.name.encode("ascii")) if evp_md == self._backend._ffi.NULL: raise UnsupportedAlgorithm( - "{0} is not a supported hash on this backend".format( + "{0} is not a supported hash on this backend.".format( algorithm.name), _Reasons.UNSUPPORTED_HASH ) @@ -852,7 +1148,7 @@ class _HMACContext(object): algorithm.name.encode('ascii')) if evp_md == self._backend._ffi.NULL: raise UnsupportedAlgorithm( - "{0} is not a supported hash on this backend".format( + "{0} is not a supported hash on this backend.".format( algorithm.name), _Reasons.UNSUPPORTED_HASH ) @@ -921,7 +1217,7 @@ class _RSASignatureContext(object): if not isinstance(padding, interfaces.AsymmetricPadding): raise TypeError( - "Expected provider of interfaces.AsymmetricPadding") + "Expected provider of interfaces.AsymmetricPadding.") if isinstance(padding, PKCS1v15): if self._backend._lib.Cryptography_HAS_PKEY_CTX: @@ -932,7 +1228,7 @@ class _RSASignatureContext(object): elif isinstance(padding, PSS): if not isinstance(padding._mgf, MGF1): raise UnsupportedAlgorithm( - "Only MGF1 is supported by this backend", + "Only MGF1 is supported by this backend.", _Reasons.UNSUPPORTED_MGF ) @@ -957,24 +1253,18 @@ class _RSASignatureContext(object): self._finalize_method = self._finalize_pss else: raise UnsupportedAlgorithm( - "{0} is not supported by this backend".format(padding.name), + "{0} is not supported by this backend.".format(padding.name), _Reasons.UNSUPPORTED_PADDING ) self._padding = padding self._algorithm = algorithm - self._hash_ctx = _HashContext(backend, self._algorithm) + self._hash_ctx = hashes.Hash(self._algorithm, self._backend) def update(self, data): - if self._hash_ctx is None: - raise AlreadyFinalized("Context has already been finalized") - self._hash_ctx.update(data) def finalize(self): - if self._hash_ctx is None: - raise AlreadyFinalized("Context has already been finalized") - evp_pkey = self._backend._rsa_private_key_to_evp_pkey( self._private_key) @@ -1023,7 +1313,6 @@ class _RSASignatureContext(object): ) assert res > 0 data_to_sign = self._hash_ctx.finalize() - self._hash_ctx = None buflen = self._backend._ffi.new("size_t *") res = self._backend._lib.EVP_PKEY_sign( pkey_ctx, @@ -1053,16 +1342,18 @@ class _RSASignatureContext(object): return self._backend._ffi.buffer(buf)[:] def _finalize_pkcs1(self, evp_pkey, pkey_size, evp_md): + if self._hash_ctx._ctx is None: + raise AlreadyFinalized("Context has already been finalized.") + sig_buf = self._backend._ffi.new("char[]", pkey_size) sig_len = self._backend._ffi.new("unsigned int *") res = self._backend._lib.EVP_SignFinal( - self._hash_ctx._ctx, + self._hash_ctx._ctx._ctx, sig_buf, sig_len, evp_pkey ) self._hash_ctx.finalize() - self._hash_ctx = None if res == 0: errors = self._backend._consume_errors() assert errors[0].lib == self._backend._lib.ERR_LIB_RSA @@ -1075,7 +1366,6 @@ class _RSASignatureContext(object): def _finalize_pss(self, evp_pkey, pkey_size, evp_md): data_to_sign = self._hash_ctx.finalize() - self._hash_ctx = None padded = self._backend._ffi.new("unsigned char[]", pkey_size) rsa_cdata = self._backend._lib.EVP_PKEY_get1_RSA(evp_pkey) assert rsa_cdata != self._backend._ffi.NULL @@ -1121,7 +1411,7 @@ class _RSAVerificationContext(object): if not isinstance(padding, interfaces.AsymmetricPadding): raise TypeError( - "Expected provider of interfaces.AsymmetricPadding") + "Expected provider of interfaces.AsymmetricPadding.") if isinstance(padding, PKCS1v15): if self._backend._lib.Cryptography_HAS_PKEY_CTX: @@ -1132,7 +1422,7 @@ class _RSAVerificationContext(object): elif isinstance(padding, PSS): if not isinstance(padding._mgf, MGF1): raise UnsupportedAlgorithm( - "Only MGF1 is supported by this backend", + "Only MGF1 is supported by this backend.", _Reasons.UNSUPPORTED_MGF ) @@ -1159,24 +1449,18 @@ class _RSAVerificationContext(object): self._verify_method = self._verify_pss else: raise UnsupportedAlgorithm( - "{0} is not supported by this backend".format(padding.name), + "{0} is not supported by this backend.".format(padding.name), _Reasons.UNSUPPORTED_PADDING ) self._padding = padding self._algorithm = algorithm - self._hash_ctx = _HashContext(backend, self._algorithm) + self._hash_ctx = hashes.Hash(self._algorithm, self._backend) def update(self, data): - if self._hash_ctx is None: - raise AlreadyFinalized("Context has already been finalized") - self._hash_ctx.update(data) def verify(self): - if self._hash_ctx is None: - raise AlreadyFinalized("Context has already been finalized") - evp_pkey = self._backend._rsa_public_key_to_evp_pkey( self._public_key) @@ -1223,7 +1507,6 @@ class _RSAVerificationContext(object): assert res > 0 data_to_verify = self._hash_ctx.finalize() - self._hash_ctx = None res = self._backend._lib.EVP_PKEY_verify( pkey_ctx, self._signature, @@ -1241,14 +1524,16 @@ class _RSAVerificationContext(object): raise InvalidSignature def _verify_pkcs1(self, evp_pkey, evp_md): + if self._hash_ctx._ctx is None: + raise AlreadyFinalized("Context has already been finalized.") + res = self._backend._lib.EVP_VerifyFinal( - self._hash_ctx._ctx, + self._hash_ctx._ctx._ctx, self._signature, len(self._signature), evp_pkey ) self._hash_ctx.finalize() - self._hash_ctx = None # The previous call can return negative numbers in the event of an # error. This is not a signature failure but we need to fail if it # occurs. @@ -1279,7 +1564,6 @@ class _RSAVerificationContext(object): raise InvalidSignature data_to_verify = self._hash_ctx.finalize() - self._hash_ctx = None res = self._backend._lib.RSA_verify_PKCS1_PSS( rsa_cdata, data_to_verify, @@ -1297,11 +1581,79 @@ class _RSAVerificationContext(object): raise InvalidSignature +@utils.register_interface(interfaces.AsymmetricVerificationContext) +class _DSAVerificationContext(object): + def __init__(self, backend, public_key, signature, algorithm): + self._backend = backend + self._public_key = public_key + self._signature = signature + self._algorithm = algorithm + + self._hash_ctx = hashes.Hash(self._algorithm, self._backend) + + def update(self, data): + self._hash_ctx.update(data) + + def verify(self): + self._dsa_cdata = self._backend._dsa_cdata_from_public_key( + self._public_key) + self._dsa_cdata = self._backend._ffi.gc(self._dsa_cdata, + self._backend._lib.DSA_free) + + data_to_verify = self._hash_ctx.finalize() + + # The first parameter passed to DSA_verify is unused by OpenSSL but + # must be an integer. + res = self._backend._lib.DSA_verify( + 0, data_to_verify, len(data_to_verify), self._signature, + len(self._signature), self._dsa_cdata) + + if res != 1: + errors = self._backend._consume_errors() + assert errors + if res == -1: + assert errors[0].lib == self._backend._lib.ERR_LIB_ASN1 + + raise InvalidSignature + + +@utils.register_interface(interfaces.AsymmetricSignatureContext) +class _DSASignatureContext(object): + def __init__(self, backend, private_key, algorithm): + self._backend = backend + self._private_key = private_key + self._algorithm = algorithm + self._hash_ctx = hashes.Hash(self._algorithm, self._backend) + self._dsa_cdata = self._backend._dsa_cdata_from_private_key( + self._private_key) + self._dsa_cdata = self._backend._ffi.gc(self._dsa_cdata, + self._backend._lib.DSA_free) + + def update(self, data): + self._hash_ctx.update(data) + + def finalize(self): + data_to_sign = self._hash_ctx.finalize() + sig_buf_len = self._backend._lib.DSA_size(self._dsa_cdata) + sig_buf = self._backend._ffi.new("unsigned char[]", sig_buf_len) + buflen = self._backend._ffi.new("unsigned int *") + + # The first parameter passed to DSA_sign is unused by OpenSSL but + # must be an integer. + res = self._backend._lib.DSA_sign( + 0, data_to_sign, len(data_to_sign), sig_buf, + buflen, self._dsa_cdata) + assert res == 1 + assert buflen[0] + + return self._backend._ffi.buffer(sig_buf)[:buflen[0]] + + @utils.register_interface(interfaces.CMACContext) class _CMACContext(object): def __init__(self, backend, algorithm, ctx=None): if not backend.cmac_algorithm_supported(algorithm): - raise UnsupportedAlgorithm("This backend does not support CMAC", + raise UnsupportedAlgorithm("This backend does not support CMAC.", _Reasons.UNSUPPORTED_CIPHER) self._backend = backend diff --git a/cryptography/hazmat/bindings/commoncrypto/binding.py b/cryptography/hazmat/bindings/commoncrypto/binding.py index 144bb099..ee7378ad 100644 --- a/cryptography/hazmat/bindings/commoncrypto/binding.py +++ b/cryptography/hazmat/bindings/commoncrypto/binding.py @@ -25,10 +25,16 @@ class Binding(object): """ _module_prefix = "cryptography.hazmat.bindings.commoncrypto." _modules = [ + "cf", "common_digest", "common_hmac", "common_key_derivation", "common_cryptor", + "secimport", + "secitem", + "seckey", + "seckeychain", + "sectransform", ] ffi = None @@ -45,6 +51,7 @@ class Binding(object): cls.ffi, cls.lib = build_ffi( module_prefix=cls._module_prefix, modules=cls._modules, + extra_link_args=["-framework", "Security"] ) @classmethod diff --git a/cryptography/hazmat/bindings/commoncrypto/cf.py b/cryptography/hazmat/bindings/commoncrypto/cf.py new file mode 100644 index 00000000..671963a3 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/cf.py @@ -0,0 +1,114 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <CoreFoundation/CoreFoundation.h> +""" + +TYPES = """ +typedef bool Boolean; +typedef signed long OSStatus; +typedef unsigned char UInt8; +typedef uint32_t UInt32; + +typedef const void * CFAllocatorRef; +const CFAllocatorRef kCFAllocatorDefault; +typedef const void * CFDataRef; +typedef signed long long CFIndex; +typedef ... *CFStringRef; +typedef ... *CFArrayRef; +typedef ... *CFBooleanRef; +typedef ... *CFErrorRef; +typedef ... *CFNumberRef; +typedef ... *CFTypeRef; +typedef ... *CFDictionaryRef; +typedef ... *CFMutableDictionaryRef; +typedef struct { + ...; +} CFDictionaryKeyCallBacks; +typedef struct { + ...; +} CFDictionaryValueCallBacks; +typedef struct { + ...; +} CFRange; + +typedef UInt32 CFStringEncoding; +enum { + kCFStringEncodingASCII = 0x0600 +}; + +enum { + kCFNumberSInt8Type = 1, + kCFNumberSInt16Type = 2, + kCFNumberSInt32Type = 3, + kCFNumberSInt64Type = 4, + kCFNumberFloat32Type = 5, + kCFNumberFloat64Type = 6, + kCFNumberCharType = 7, + kCFNumberShortType = 8, + kCFNumberIntType = 9, + kCFNumberLongType = 10, + kCFNumberLongLongType = 11, + kCFNumberFloatType = 12, + kCFNumberDoubleType = 13, + kCFNumberCFIndexType = 14, + kCFNumberNSIntegerType = 15, + kCFNumberCGFloatType = 16, + kCFNumberMaxType = 16 +}; +typedef int CFNumberType; + +const CFDictionaryKeyCallBacks kCFTypeDictionaryKeyCallBacks; +const CFDictionaryValueCallBacks kCFTypeDictionaryValueCallBacks; + +const CFBooleanRef kCFBooleanTrue; +const CFBooleanRef kCFBooleanFalse; +""" + +FUNCTIONS = """ +CFDataRef CFDataCreate(CFAllocatorRef, const UInt8 *, CFIndex); +CFStringRef CFStringCreateWithCString(CFAllocatorRef, const char *, + CFStringEncoding); +CFDictionaryRef CFDictionaryCreate(CFAllocatorRef, const void **, + const void **, CFIndex, + const CFDictionaryKeyCallBacks *, + const CFDictionaryValueCallBacks *); +CFMutableDictionaryRef CFDictionaryCreateMutable( + CFAllocatorRef, + CFIndex, + const CFDictionaryKeyCallBacks *, + const CFDictionaryValueCallBacks * +); +void CFDictionarySetValue(CFMutableDictionaryRef, const void *, const void *); +CFIndex CFArrayGetCount(CFArrayRef); +const void *CFArrayGetValueAtIndex(CFArrayRef, CFIndex); +CFIndex CFDataGetLength(CFDataRef); +void CFDataGetBytes(CFDataRef, CFRange, UInt8 *); +CFRange CFRangeMake(CFIndex, CFIndex); +void CFShow(CFTypeRef); +Boolean CFBooleanGetValue(CFBooleanRef); +CFNumberRef CFNumberCreate(CFAllocatorRef, CFNumberType, const void *); +void CFRelease(CFTypeRef); +CFTypeRef CFRetain(CFTypeRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/secimport.py b/cryptography/hazmat/bindings/commoncrypto/secimport.py new file mode 100644 index 00000000..add62c79 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/secimport.py @@ -0,0 +1,95 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecImportExport.h> +""" + +TYPES = """ +typedef ... *SecAccessRef; + +CFStringRef kSecImportExportPassphrase; +CFStringRef kSecImportExportKeychain; +CFStringRef kSecImportExportAccess; + +typedef uint32_t SecExternalItemType; +enum { + kSecItemTypeUnknown, + kSecItemTypePrivateKey, + kSecItemTypePublicKey, + kSecItemTypeSessionKey, + kSecItemTypeCertificate, + kSecItemTypeAggregate +}; + + +typedef uint32_t SecExternalFormat; +enum { + kSecFormatUnknown = 0, + kSecFormatOpenSSL, + kSecFormatSSH, + kSecFormatBSAFE, + kSecFormatRawKey, + kSecFormatWrappedPKCS8, + kSecFormatWrappedOpenSSL, + kSecFormatWrappedSSH, + kSecFormatWrappedLSH, + kSecFormatX509Cert, + kSecFormatPEMSequence, + kSecFormatPKCS7, + kSecFormatPKCS12, + kSecFormatNetscapeCertSequence, + kSecFormatSSHv2 +}; + +typedef uint32_t SecItemImportExportFlags; +enum { + kSecKeyImportOnlyOne = 0x00000001, + kSecKeySecurePassphrase = 0x00000002, + kSecKeyNoAccessControl = 0x00000004 +}; +typedef uint32_t SecKeyImportExportFlags; + +typedef struct { + /* for import and export */ + uint32_t version; + SecKeyImportExportFlags flags; + CFTypeRef passphrase; + CFStringRef alertTitle; + CFStringRef alertPrompt; + + /* for import only */ + SecAccessRef accessRef; + CFArrayRef keyUsage; + + CFArrayRef keyAttributes; +} SecItemImportExportKeyParameters; +""" + +FUNCTIONS = """ +OSStatus SecItemImport(CFDataRef, CFStringRef, SecExternalFormat *, + SecExternalItemType *, SecItemImportExportFlags, + const SecItemImportExportKeyParameters *, + SecKeychainRef, CFArrayRef *); +OSStatus SecPKCS12Import(CFDataRef, CFDictionaryRef, CFArrayRef *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/secitem.py b/cryptography/hazmat/bindings/commoncrypto/secitem.py new file mode 100644 index 00000000..4d7710bd --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/secitem.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecItem.h> +""" + +TYPES = """ +const CFTypeRef kSecAttrKeyType; +const CFTypeRef kSecAttrKeySizeInBits; +const CFTypeRef kSecAttrIsPermanent; +const CFTypeRef kSecAttrKeyTypeRSA; +const CFTypeRef kSecAttrKeyTypeDSA; +const CFTypeRef kSecAttrKeyTypeEC; +const CFTypeRef kSecAttrKeyTypeEC; +const CFTypeRef kSecUseKeychain; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/seckey.py b/cryptography/hazmat/bindings/commoncrypto/seckey.py new file mode 100644 index 00000000..38aaece8 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/seckey.py @@ -0,0 +1,34 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecKey.h> +""" + +TYPES = """ +typedef ... *SecKeyRef; +""" + +FUNCTIONS = """ +OSStatus SecKeyGeneratePair(CFDictionaryRef, SecKeyRef *, SecKeyRef *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/seckeychain.py b/cryptography/hazmat/bindings/commoncrypto/seckeychain.py new file mode 100644 index 00000000..c045c347 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/seckeychain.py @@ -0,0 +1,36 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecKeychain.h> +""" + +TYPES = """ +typedef ... *SecKeychainRef; +""" + +FUNCTIONS = """ +OSStatus SecKeychainCreate(const char *, UInt32, const void *, Boolean, + SecAccessRef, SecKeychainRef *); +OSStatus SecKeychainDelete(SecKeychainRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/sectransform.py b/cryptography/hazmat/bindings/commoncrypto/sectransform.py new file mode 100644 index 00000000..d6dbc5f6 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/sectransform.py @@ -0,0 +1,79 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecDigestTransform.h> +#include <Security/SecSignVerifyTransform.h> +#include <Security/SecEncryptTransform.h> +""" + +TYPES = """ +typedef ... *SecTransformRef; + +CFStringRef kSecImportExportPassphrase; +CFStringRef kSecImportExportKeychain; +CFStringRef kSecImportExportAccess; + +CFStringRef kSecEncryptionMode; +CFStringRef kSecEncryptKey; +CFStringRef kSecIVKey; +CFStringRef kSecModeCBCKey; +CFStringRef kSecModeCFBKey; +CFStringRef kSecModeECBKey; +CFStringRef kSecModeNoneKey; +CFStringRef kSecModeOFBKey; +CFStringRef kSecOAEPEncodingParametersAttributeName; +CFStringRef kSecPaddingKey; +CFStringRef kSecPaddingNoneKey; +CFStringRef kSecPaddingOAEPKey; +CFStringRef kSecPaddingPKCS1Key; +CFStringRef kSecPaddingPKCS5Key; +CFStringRef kSecPaddingPKCS7Key; + +const CFStringRef kSecTransformInputAttributeName; +const CFStringRef kSecTransformOutputAttributeName; +const CFStringRef kSecTransformDebugAttributeName; +const CFStringRef kSecTransformTransformName; +const CFStringRef kSecTransformAbortAttributeName; + +CFStringRef kSecInputIsAttributeName; +CFStringRef kSecInputIsPlainText; +CFStringRef kSecInputIsDigest; +CFStringRef kSecInputIsRaw; + +const CFStringRef kSecDigestTypeAttribute; +const CFStringRef kSecDigestLengthAttribute; +const CFStringRef kSecDigestMD5; +const CFStringRef kSecDigestSHA1; +const CFStringRef kSecDigestSHA2; +""" + +FUNCTIONS = """ +Boolean SecTransformSetAttribute(SecTransformRef, CFStringRef, CFTypeRef, + CFErrorRef *); +SecTransformRef SecDecryptTransformCreate(SecKeyRef, CFErrorRef *); +SecTransformRef SecEncryptTransformCreate(SecKeyRef, CFErrorRef *); +SecTransformRef SecVerifyTransformCreate(SecKeyRef, CFDataRef, CFErrorRef *); +SecTransformRef SecSignTransformCreate(SecKeyRef, CFErrorRef *) ; +CFTypeRef SecTransformExecute(SecTransformRef, CFErrorRef *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/openssl/aes.py b/cryptography/hazmat/bindings/openssl/aes.py index 17c154cf..b0e00721 100644 --- a/cryptography/hazmat/bindings/openssl/aes.py +++ b/cryptography/hazmat/bindings/openssl/aes.py @@ -29,6 +29,12 @@ typedef struct aes_key_st AES_KEY; FUNCTIONS = """ int AES_set_encrypt_key(const unsigned char *, const int, AES_KEY *); int AES_set_decrypt_key(const unsigned char *, const int, AES_KEY *); +/* The ctr128_encrypt function is only useful in 0.9.8. You should use EVP for + this in 1.0.0+. */ +void AES_ctr128_encrypt(const unsigned char *, unsigned char *, + const unsigned long, const AES_KEY *, + unsigned char[], unsigned char[], unsigned int *); + """ MACROS = """ diff --git a/cryptography/hazmat/bindings/openssl/asn1.py b/cryptography/hazmat/bindings/openssl/asn1.py index dfdf1bf5..2edfd2d8 100644 --- a/cryptography/hazmat/bindings/openssl/asn1.py +++ b/cryptography/hazmat/bindings/openssl/asn1.py @@ -141,6 +141,9 @@ ASN1_INTEGER *BN_to_ASN1_INTEGER(BIGNUM *, ASN1_INTEGER *); /* These isn't a macro the arg is const on openssl 1.0.2+ */ int ASN1_GENERALIZEDTIME_check(ASN1_GENERALIZEDTIME *); + +/* Not a macro, const on openssl 1.0 */ +int ASN1_STRING_set_default_mask_asc(char *); """ CUSTOMIZATIONS = """ diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index f0ff3275..464081b0 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -55,6 +55,7 @@ class Binding(object): "dh", "dsa", "ec", + "ecdh", "ecdsa", "engine", "err", @@ -148,7 +149,7 @@ class Binding(object): lock.release() else: raise RuntimeError( - "Unknown lock mode {0}: lock={1}, file={2}, line={3}".format( + "Unknown lock mode {0}: lock={1}, file={2}, line={3}.".format( mode, n, file, line ) ) diff --git a/cryptography/hazmat/bindings/openssl/bio.py b/cryptography/hazmat/bindings/openssl/bio.py index 0c521b4d..cfe6034f 100644 --- a/cryptography/hazmat/bindings/openssl/bio.py +++ b/cryptography/hazmat/bindings/openssl/bio.py @@ -123,10 +123,10 @@ long BIO_callback_ctrl( int, void (*)(struct bio_st *, int, const char *, int, long, long) ); -char* BIO_ptr_ctrl(BIO *bp, int cmd, long larg); -long BIO_int_ctrl(BIO *bp, int cmd, long larg, int iarg); -size_t BIO_ctrl_pending(BIO *b); -size_t BIO_ctrl_wpending(BIO *b); +char *BIO_ptr_ctrl(BIO *, int, long); +long BIO_int_ctrl(BIO *, int, long, int); +size_t BIO_ctrl_pending(BIO *); +size_t BIO_ctrl_wpending(BIO *); int BIO_read(BIO *, void *, int); int BIO_gets(BIO *, char *, int); int BIO_write(BIO *, const void *, int); diff --git a/cryptography/hazmat/bindings/openssl/dh.py b/cryptography/hazmat/bindings/openssl/dh.py index 1791a670..a0f99479 100644 --- a/cryptography/hazmat/bindings/openssl/dh.py +++ b/cryptography/hazmat/bindings/openssl/dh.py @@ -34,9 +34,21 @@ typedef struct dh_st { FUNCTIONS = """ DH *DH_new(void); void DH_free(DH *); +int DH_size(const DH *); +DH *DH_generate_parameters(int, int, void (*)(int, int, void *), void *); +int DH_check(const DH *, int *); +int DH_generate_key(DH *); +int DH_compute_key(unsigned char *, const BIGNUM *, DH *); +int DH_set_ex_data(DH *, int, void *); +void *DH_get_ex_data(DH *, int); +DH *d2i_DHparams(DH **, const unsigned char **, long); +int i2d_DHparams(const DH *, unsigned char **); +int DHparams_print_fp(FILE *, const DH *); +int DHparams_print(BIO *, const DH *); """ MACROS = """ +int DH_generate_parameters_ex(DH *, int, int, BN_GENCB *); """ CUSTOMIZATIONS = """ diff --git a/cryptography/hazmat/bindings/openssl/ec.py b/cryptography/hazmat/bindings/openssl/ec.py index 45c17c2e..26fc8ff0 100644 --- a/cryptography/hazmat/bindings/openssl/ec.py +++ b/cryptography/hazmat/bindings/openssl/ec.py @@ -27,6 +27,8 @@ static const int Cryptography_HAS_EC_1_0_1; static const int Cryptography_HAS_EC_NISTP_64_GCC_128; static const int Cryptography_HAS_EC2M; +static const int OPENSSL_EC_NAMED_CURVE; + typedef ... EC_KEY; typedef ... EC_GROUP; typedef ... EC_POINT; @@ -61,6 +63,8 @@ int EC_GROUP_set_curve_GF2m( int EC_GROUP_get_curve_GF2m( const EC_GROUP *, BIGNUM *, BIGNUM *, BIGNUM *, BN_CTX *); +int EC_GROUP_get_degree(const EC_GROUP *); + const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *); const EC_POINT *EC_GROUP_get0_generator(const EC_GROUP *); int EC_GROUP_get_curve_name(const EC_GROUP *); @@ -198,6 +202,7 @@ int EC_METHOD_get_field_type(const EC_METHOD *); CUSTOMIZATIONS = """ #ifdef OPENSSL_NO_EC static const long Cryptography_HAS_EC = 0; + typedef void EC_KEY; typedef void EC_GROUP; typedef void EC_POINT; @@ -208,6 +213,8 @@ typedef struct { } EC_builtin_curve; typedef long point_conversion_form_t; +static const int OPENSSL_EC_NAMED_CURVE = 0; + void (*EC_KEY_free)(EC_KEY *) = NULL; size_t (*EC_get_builtin_curves)(EC_builtin_curve *, size_t) = NULL; EC_KEY *(*EC_KEY_new_by_curve_name)(int) = NULL; @@ -250,6 +257,8 @@ int (*EC_GROUP_set_curve_GFp)( int (*EC_GROUP_get_curve_GFp)( const EC_GROUP *, BIGNUM *, BIGNUM *, BIGNUM *, BN_CTX *); +int (*EC_GROUP_get_degree)(const EC_GROUP *) = NULL; + const EC_METHOD *(*EC_GROUP_method_of)(const EC_GROUP *) = NULL; const EC_POINT *(*EC_GROUP_get0_generator)(const EC_GROUP *) = NULL; int (*EC_GROUP_get_curve_name)(const EC_GROUP *) = NULL; @@ -389,6 +398,7 @@ static const long Cryptography_HAS_EC2M = 1; CONDITIONAL_NAMES = { "Cryptography_HAS_EC": [ + "OPENSSL_EC_NAMED_CURVE", "EC_GROUP_new", "EC_GROUP_free", "EC_GROUP_clear_free", @@ -399,6 +409,7 @@ CONDITIONAL_NAMES = { "EC_GROUP_method_of", "EC_GROUP_get0_generator", "EC_GROUP_get_curve_name", + "EC_GROUP_get_degree", "EC_KEY_free", "EC_get_builtin_curves", "EC_KEY_new_by_curve_name", diff --git a/cryptography/hazmat/bindings/openssl/ecdh.py b/cryptography/hazmat/bindings/openssl/ecdh.py new file mode 100644 index 00000000..960d46fb --- /dev/null +++ b/cryptography/hazmat/bindings/openssl/ecdh.py @@ -0,0 +1,68 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#ifndef OPENSSL_NO_ECDH +#include <openssl/ecdh.h> +#endif +""" + +TYPES = """ +static const int Cryptography_HAS_ECDH; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +int ECDH_compute_key(void *, size_t, const EC_POINT *, EC_KEY *, + void *(*)(const void *, size_t, void *, size_t *)); + +int ECDH_get_ex_new_index(long, void *, CRYPTO_EX_new *, CRYPTO_EX_dup *, + CRYPTO_EX_free *); + +int ECDH_set_ex_data(EC_KEY *, int, void *); + +void *ECDH_get_ex_data(EC_KEY *, int); +""" + +CUSTOMIZATIONS = """ +#ifdef OPENSSL_NO_ECDH +static const long Cryptography_HAS_ECDH = 0; + +int (*ECDH_compute_key)(void *, size_t, const EC_POINT *, EC_KEY *, + void *(*)(const void *, size_t, void *, + size_t *)) = NULL; + +int (*ECDH_get_ex_new_index)(long, void *, CRYPTO_EX_new *, CRYPTO_EX_dup *, + CRYPTO_EX_free *) = NULL; + +int (*ECDH_set_ex_data)(EC_KEY *, int, void *) = NULL; + +void *(*ECDH_get_ex_data)(EC_KEY *, int) = NULL; + +#else +static const long Cryptography_HAS_ECDH = 1; +#endif +""" + +CONDITIONAL_NAMES = { + "Cryptography_HAS_ECDH": [ + "ECDH_compute_key", + "ECDH_get_ex_new_index", + "ECDH_set_ex_data", + "ECDH_get_ex_data", + ], +} diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py index f6456d66..f685e494 100644 --- a/cryptography/hazmat/bindings/openssl/err.py +++ b/cryptography/hazmat/bindings/openssl/err.py @@ -135,6 +135,7 @@ static const int EVP_F_PKCS5_V2_PBE_KEYIVGEN; static const int EVP_F_PKCS8_SET_BROKEN; static const int EVP_F_RC2_MAGIC_TO_METH; static const int EVP_F_RC5_CTRL; + static const int EVP_R_AES_KEY_SETUP_FAILED; static const int EVP_R_ASN1_LIB; static const int EVP_R_BAD_BLOCK_LENGTH; @@ -168,6 +169,7 @@ static const int EVP_R_UNSUPPORTED_CIPHER; static const int EVP_R_UNSUPPORTED_KEY_DERIVATION_FUNCTION; static const int EVP_R_UNSUPPORTED_KEYLENGTH; static const int EVP_R_UNSUPPORTED_SALT_TYPE; +static const int EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM; static const int EVP_R_WRONG_FINAL_BLOCK_LENGTH; static const int EVP_R_WRONG_PUBLIC_KEY_TYPE; diff --git a/cryptography/hazmat/bindings/openssl/opensslv.py b/cryptography/hazmat/bindings/openssl/opensslv.py index e4aa6212..ef6e057b 100644 --- a/cryptography/hazmat/bindings/openssl/opensslv.py +++ b/cryptography/hazmat/bindings/openssl/opensslv.py @@ -18,6 +18,8 @@ INCLUDES = """ """ TYPES = """ +/* Note that these will be resolved when cryptography is compiled and are NOT + guaranteed to be the version that it actually loads. */ static const int OPENSSL_VERSION_NUMBER; static const char *const OPENSSL_VERSION_TEXT; """ diff --git a/cryptography/hazmat/bindings/openssl/ssl.py b/cryptography/hazmat/bindings/openssl/ssl.py index 7ed42f9f..94b96d98 100644 --- a/cryptography/hazmat/bindings/openssl/ssl.py +++ b/cryptography/hazmat/bindings/openssl/ssl.py @@ -15,6 +15,8 @@ from __future__ import absolute_import, division, print_function INCLUDES = """ #include <openssl/ssl.h> + +typedef STACK_OF(SSL_CIPHER) Cryptography_STACK_OF_SSL_CIPHER; """ TYPES = """ @@ -24,6 +26,7 @@ TYPES = """ static const long Cryptography_HAS_SSL2; static const long Cryptography_HAS_TLSv1_1; static const long Cryptography_HAS_TLSv1_2; +static const long Cryptography_HAS_SECURE_RENEGOTIATION; /* Internally invented symbol to tell us if SNI is supported */ static const long Cryptography_HAS_TLSEXT_HOSTNAME; @@ -84,6 +87,8 @@ static const long SSL_OP_COOKIE_EXCHANGE; static const long SSL_OP_NO_TICKET; static const long SSL_OP_ALL; static const long SSL_OP_SINGLE_ECDH_USE; +static const long SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; +static const long SSL_OP_LEGACY_SERVER_CONNECT; static const long SSL_VERIFY_PEER; static const long SSL_VERIFY_FAIL_IF_NO_PEER_CERT; static const long SSL_VERIFY_CLIENT_ONCE; @@ -153,6 +158,8 @@ typedef struct { static const long TLSEXT_NAMETYPE_host_name; typedef ... SSL_CIPHER; +typedef ... Cryptography_STACK_OF_SSL_CIPHER; +typedef ... COMP_METHOD; """ FUNCTIONS = """ @@ -160,6 +167,7 @@ void SSL_load_error_strings(void); int SSL_library_init(void); /* SSL */ +const char *SSL_state_string_long(const SSL *); SSL_SESSION *SSL_get1_session(SSL *); int SSL_set_session(SSL *, SSL_SESSION *); int SSL_get_verify_mode(const SSL *); @@ -189,6 +197,11 @@ int SSL_get_error(const SSL *, int); int SSL_do_handshake(SSL *); int SSL_shutdown(SSL *); const char *SSL_get_cipher_list(const SSL *, int); +Cryptography_STACK_OF_SSL_CIPHER *SSL_get_ciphers(const SSL *); + +const COMP_METHOD *SSL_get_current_compression(SSL *); +const COMP_METHOD *SSL_get_current_expansion(SSL *); +const char *SSL_COMP_get_name(const COMP_METHOD *); /* context */ void SSL_CTX_free(SSL_CTX *); @@ -247,6 +260,7 @@ int SSL_want_read(const SSL *); int SSL_want_write(const SSL *); long SSL_total_renegotiations(SSL *); +long SSL_get_secure_renegotiation_support(SSL *); /* Defined as unsigned long because SSL_OP_ALL is greater than signed 32-bit and Windows defines long as 32-bit. */ @@ -350,9 +364,23 @@ int SSL_select_next_proto(unsigned char **, unsigned char *, const unsigned char *, unsigned int); void SSL_get0_next_proto_negotiated(const SSL *, const unsigned char **, unsigned *); + +int sk_SSL_CIPHER_num(Cryptography_STACK_OF_SSL_CIPHER *); +SSL_CIPHER *sk_SSL_CIPHER_value(Cryptography_STACK_OF_SSL_CIPHER *, int); """ CUSTOMIZATIONS = """ +/** Secure renegotiation is supported in OpenSSL >= 0.9.8m + * But some Linux distributions have back ported some features. + */ +#ifndef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION +static const long Cryptography_HAS_SECURE_RENEGOTIATION = 0; +long (*SSL_get_secure_renegotiation_support)(SSL *) = NULL; +const long SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION = 0; +const long SSL_OP_LEGACY_SERVER_CONNECT = 0; +#else +static const long Cryptography_HAS_SECURE_RENEGOTIATION = 1; +#endif #ifdef OPENSSL_NO_SSL2 static const long Cryptography_HAS_SSL2 = 0; SSL_METHOD* (*SSLv2_method)(void) = NULL; @@ -550,5 +578,11 @@ CONDITIONAL_NAMES = { "SSL_CTX_set_next_proto_select_cb", "SSL_select_next_proto", "SSL_get0_next_proto_negotiated", - ] + ], + + "Cryptography_HAS_SECURE_RENEGOTIATION": [ + "SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION", + "SSL_OP_LEGACY_SERVER_CONNECT", + "SSL_get_secure_renegotiation_support", + ], } diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py index 4c2de36a..a9ae9ecb 100644 --- a/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -27,7 +27,7 @@ def _check_dsa_parameters(modulus, subgroup_order, generator): not isinstance(subgroup_order, six.integer_types) or not isinstance(generator, six.integer_types) ): - raise TypeError("DSA parameters must be integers") + raise TypeError("DSA parameters must be integers.") if (utils.bit_length(modulus), utils.bit_length(subgroup_order)) not in ( @@ -36,10 +36,10 @@ def _check_dsa_parameters(modulus, subgroup_order, generator): (3072, 256)): raise ValueError("modulus and subgroup_order lengths must be " "one of these pairs (1024, 160) or (2048, 256) " - "or (3072, 256)") + "or (3072, 256).") if generator <= 1 or generator >= modulus: - raise ValueError("generator must be > 1 and < modulus") + raise ValueError("generator must be > 1 and < modulus.") @utils.register_interface(interfaces.DSAParameters) @@ -55,7 +55,7 @@ class DSAParameters(object): def generate(cls, key_size, backend): if not isinstance(backend, DSABackend): raise UnsupportedAlgorithm( - "Backend object does not implement DSABackend", + "Backend object does not implement DSABackend.", _Reasons.BACKEND_MISSING_INTERFACE ) @@ -94,13 +94,13 @@ class DSAPrivateKey(object): not isinstance(x, six.integer_types) or not isinstance(y, six.integer_types) ): - raise TypeError("DSAPrivateKey arguments must be integers") + raise TypeError("DSAPrivateKey arguments must be integers.") if x <= 0 or x >= subgroup_order: - raise ValueError("x must be > 0 and < subgroup_order") + raise ValueError("x must be > 0 and < subgroup_order.") if y != pow(generator, x, modulus): - raise ValueError("y must be equal to (generator ** x % modulus)") + raise ValueError("y must be equal to (generator ** x % modulus).") self._modulus = modulus self._subgroup_order = subgroup_order @@ -112,12 +112,21 @@ class DSAPrivateKey(object): def generate(cls, parameters, backend): if not isinstance(backend, DSABackend): raise UnsupportedAlgorithm( - "Backend object does not implement DSABackend", + "Backend object does not implement DSABackend.", _Reasons.BACKEND_MISSING_INTERFACE ) return backend.generate_dsa_private_key(parameters) + def signer(self, algorithm, backend): + if not isinstance(backend, DSABackend): + raise UnsupportedAlgorithm( + "Backend object does not implement DSABackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + return backend.create_dsa_signature_ctx(self, algorithm) + @property def key_size(self): return utils.bit_length(self._modulus) @@ -144,13 +153,23 @@ class DSAPublicKey(object): def __init__(self, modulus, subgroup_order, generator, y): _check_dsa_parameters(modulus, subgroup_order, generator) if not isinstance(y, six.integer_types): - raise TypeError("y must be an integer") + raise TypeError("y must be an integer.") self._modulus = modulus self._subgroup_order = subgroup_order self._generator = generator self._y = y + def verifier(self, signature, algorithm, backend): + if not isinstance(backend, DSABackend): + raise UnsupportedAlgorithm( + "Backend object does not implement DSABackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + return backend.create_dsa_verification_ctx(self, signature, + algorithm) + @property def key_size(self): return utils.bit_length(self._modulus) diff --git a/cryptography/hazmat/primitives/asymmetric/ec.py b/cryptography/hazmat/primitives/asymmetric/ec.py new file mode 100644 index 00000000..1e49ad7b --- /dev/null +++ b/cryptography/hazmat/primitives/asymmetric/ec.py @@ -0,0 +1,69 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import six + +from cryptography.hazmat.primitives import interfaces + + +class EllipticCurvePublicNumbers(object): + def __init__(self, x, y, curve): + if ( + not isinstance(x, six.integer_types) or + not isinstance(y, six.integer_types) + ): + raise TypeError("x and y must be integers.") + + if not isinstance(curve, interfaces.EllipticCurve): + raise TypeError("curve must provide the EllipticCurve interface.") + + self._y = y + self._x = x + self._curve = curve + + @property + def curve(self): + return self._curve + + @property + def x(self): + return self._x + + @property + def y(self): + return self._y + + +class EllipticCurvePrivateNumbers(object): + def __init__(self, private_value, public_numbers): + if not isinstance(private_value, six.integer_types): + raise TypeError("private_value must be an integer.") + + if not isinstance(public_numbers, EllipticCurvePublicNumbers): + raise TypeError( + "public_numbers must be an EllipticCurvePublicNumbers " + "instance." + ) + + self._private_value = private_value + self._public_numbers = public_numbers + + @property + def private_value(self): + return self._private_value + + @property + def public_numbers(self): + return self._public_numbers diff --git a/cryptography/hazmat/primitives/asymmetric/padding.py b/cryptography/hazmat/primitives/asymmetric/padding.py index dcc6fe06..d44bbda5 100644 --- a/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/cryptography/hazmat/primitives/asymmetric/padding.py @@ -38,18 +38,19 @@ class PSS(object): warnings.warn( "salt_length is deprecated on MGF1 and should be added via the" " PSS constructor.", - utils.DeprecatedIn04 + utils.DeprecatedIn04, + stacklevel=2 ) else: if (not isinstance(salt_length, six.integer_types) and salt_length is not self.MAX_LENGTH): - raise TypeError("salt_length must be an integer") + raise TypeError("salt_length must be an integer.") if salt_length is not self.MAX_LENGTH and salt_length < 0: - raise ValueError("salt_length must be zero or greater") + raise ValueError("salt_length must be zero or greater.") if salt_length is None and self._mgf._salt_length is None: - raise ValueError("You must supply salt_length") + raise ValueError("You must supply salt_length.") self._salt_length = salt_length @@ -80,13 +81,14 @@ class MGF1(object): warnings.warn( "salt_length is deprecated on MGF1 and should be passed to " "the PSS constructor instead.", - utils.DeprecatedIn04 + utils.DeprecatedIn04, + stacklevel=2 ) if (not isinstance(salt_length, six.integer_types) and salt_length is not self.MAX_LENGTH): - raise TypeError("salt_length must be an integer") + raise TypeError("salt_length must be an integer.") if salt_length is not self.MAX_LENGTH and salt_length < 0: - raise ValueError("salt_length must be zero or greater") + raise ValueError("salt_length must be zero or greater.") self._salt_length = salt_length diff --git a/cryptography/hazmat/primitives/asymmetric/rsa.py b/cryptography/hazmat/primitives/asymmetric/rsa.py index d23f8046..481797fe 100644 --- a/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -21,6 +21,17 @@ from cryptography.hazmat.backends.interfaces import RSABackend from cryptography.hazmat.primitives import interfaces +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 key_size < 512: + raise ValueError("key_size must be at least 512-bits.") + + @utils.register_interface(interfaces.RSAPublicKey) class RSAPublicKey(object): def __init__(self, public_exponent, modulus): @@ -28,16 +39,16 @@ class RSAPublicKey(object): not isinstance(public_exponent, six.integer_types) or not isinstance(modulus, six.integer_types) ): - raise TypeError("RSAPublicKey arguments must be integers") + raise TypeError("RSAPublicKey arguments must be integers.") if modulus < 3: - raise ValueError("modulus must be >= 3") + raise ValueError("modulus must be >= 3.") if public_exponent < 3 or public_exponent >= modulus: - raise ValueError("public_exponent must be >= 3 and < modulus") + raise ValueError("public_exponent must be >= 3 and < modulus.") if public_exponent & 1 == 0: - raise ValueError("public_exponent must be odd") + raise ValueError("public_exponent must be odd.") self._public_exponent = public_exponent self._modulus = modulus @@ -45,7 +56,7 @@ class RSAPublicKey(object): def verifier(self, signature, padding, algorithm, backend): if not isinstance(backend, RSABackend): raise UnsupportedAlgorithm( - "Backend object does not implement RSABackend", + "Backend object does not implement RSABackend.", _Reasons.BACKEND_MISSING_INTERFACE ) @@ -55,7 +66,7 @@ class RSAPublicKey(object): def encrypt(self, plaintext, padding, backend): if not isinstance(backend, RSABackend): raise UnsupportedAlgorithm( - "Backend object does not implement RSABackend", + "Backend object does not implement RSABackend.", _Reasons.BACKEND_MISSING_INTERFACE ) @@ -132,43 +143,43 @@ class RSAPrivateKey(object): not isinstance(public_exponent, six.integer_types) or not isinstance(modulus, six.integer_types) ): - raise TypeError("RSAPrivateKey arguments must be integers") + raise TypeError("RSAPrivateKey arguments must be integers.") if modulus < 3: - raise ValueError("modulus must be >= 3") + raise ValueError("modulus must be >= 3.") if p >= modulus: - raise ValueError("p must be < modulus") + raise ValueError("p must be < modulus.") if q >= modulus: - raise ValueError("q must be < modulus") + raise ValueError("q must be < modulus.") if dmp1 >= modulus: - raise ValueError("dmp1 must be < modulus") + raise ValueError("dmp1 must be < modulus.") if dmq1 >= modulus: - raise ValueError("dmq1 must be < modulus") + raise ValueError("dmq1 must be < modulus.") if iqmp >= modulus: - raise ValueError("iqmp must be < modulus") + raise ValueError("iqmp must be < modulus.") if private_exponent >= modulus: - raise ValueError("private_exponent must be < modulus") + raise ValueError("private_exponent must be < modulus.") if public_exponent < 3 or public_exponent >= modulus: - raise ValueError("public_exponent must be >= 3 and < modulus") + raise ValueError("public_exponent must be >= 3 and < modulus.") if public_exponent & 1 == 0: - raise ValueError("public_exponent must be odd") + raise ValueError("public_exponent must be odd.") if dmp1 & 1 == 0: - raise ValueError("dmp1 must be odd") + raise ValueError("dmp1 must be odd.") if dmq1 & 1 == 0: - raise ValueError("dmq1 must be odd") + raise ValueError("dmq1 must be odd.") if p * q != modulus: - raise ValueError("p*q must equal modulus") + raise ValueError("p*q must equal modulus.") self._p = p self._q = q @@ -183,16 +194,17 @@ class RSAPrivateKey(object): def generate(cls, public_exponent, key_size, backend): if not isinstance(backend, RSABackend): raise UnsupportedAlgorithm( - "Backend object does not implement RSABackend", + "Backend object does not implement RSABackend.", _Reasons.BACKEND_MISSING_INTERFACE ) + _verify_rsa_parameters(public_exponent, key_size) return backend.generate_rsa_private_key(public_exponent, key_size) def signer(self, padding, algorithm, backend): if not isinstance(backend, RSABackend): raise UnsupportedAlgorithm( - "Backend object does not implement RSABackend", + "Backend object does not implement RSABackend.", _Reasons.BACKEND_MISSING_INTERFACE ) @@ -201,7 +213,7 @@ class RSAPrivateKey(object): def decrypt(self, ciphertext, padding, backend): if not isinstance(backend, RSABackend): raise UnsupportedAlgorithm( - "Backend object does not implement RSABackend", + "Backend object does not implement RSABackend.", _Reasons.BACKEND_MISSING_INTERFACE ) diff --git a/cryptography/hazmat/primitives/ciphers/algorithms.py b/cryptography/hazmat/primitives/ciphers/algorithms.py index 52daf178..bd8437c2 100644 --- a/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -20,7 +20,7 @@ from cryptography.hazmat.primitives import interfaces 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( + raise ValueError("Invalid key size ({0}) for {1}.".format( len(key) * 8, algorithm.name )) return key diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 2274e945..e3fe5adc 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -26,12 +26,14 @@ class Cipher(object): def __init__(self, algorithm, mode, backend): if not isinstance(backend, CipherBackend): raise UnsupportedAlgorithm( - "Backend object does not implement CipherBackend", + "Backend object does not implement CipherBackend.", _Reasons.BACKEND_MISSING_INTERFACE ) if not isinstance(algorithm, interfaces.CipherAlgorithm): - raise TypeError("Expected interface of interfaces.CipherAlgorithm") + raise TypeError( + "Expected interface of interfaces.CipherAlgorithm." + ) if mode is not None: mode.validate_for_algorithm(algorithm) @@ -44,7 +46,7 @@ class Cipher(object): if isinstance(self.mode, interfaces.ModeWithAuthenticationTag): if self.mode.tag is not None: raise ValueError( - "Authentication tag must be None when encrypting" + "Authentication tag must be None when encrypting." ) ctx = self._backend.create_symmetric_encryption_ctx( self.algorithm, self.mode @@ -55,7 +57,7 @@ class Cipher(object): if isinstance(self.mode, interfaces.ModeWithAuthenticationTag): if self.mode.tag is None: raise ValueError( - "Authentication tag must be provided when decrypting" + "Authentication tag must be provided when decrypting." ) ctx = self._backend.create_symmetric_decryption_ctx( self.algorithm, self.mode @@ -79,12 +81,12 @@ class _CipherContext(object): def update(self, data): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") return self._ctx.update(data) def finalize(self): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") data = self._ctx.finalize() self._ctx = None return data @@ -100,13 +102,13 @@ class _AEADCipherContext(object): def update(self, data): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") self._updated = True return self._ctx.update(data) def finalize(self): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") data = self._ctx.finalize() self._tag = self._ctx.tag self._ctx = None @@ -114,9 +116,9 @@ class _AEADCipherContext(object): def authenticate_additional_data(self, data): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") if self._updated: - raise AlreadyUpdated("Update has been called on this context") + raise AlreadyUpdated("Update has been called on this context.") self._ctx.authenticate_additional_data(data) @@ -126,5 +128,5 @@ class _AEADEncryptionContext(_AEADCipherContext): def tag(self): if self._ctx is not None: raise NotYetFinalized("You must finalize encryption before " - "getting the tag") + "getting the tag.") return self._tag diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 739f23dd..e70a9db5 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -17,6 +17,13 @@ from cryptography import utils from cryptography.hazmat.primitives import interfaces +def _check_iv_length(mode, algorithm): + if len(mode.initialization_vector) * 8 != algorithm.block_size: + raise ValueError("Invalid IV size ({0}) for {1}.".format( + len(mode.initialization_vector), mode.name + )) + + @utils.register_interface(interfaces.Mode) @utils.register_interface(interfaces.ModeWithInitializationVector) class CBC(object): @@ -25,11 +32,7 @@ class CBC(object): def __init__(self, initialization_vector): self.initialization_vector = initialization_vector - def validate_for_algorithm(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 - )) + validate_for_algorithm = _check_iv_length @utils.register_interface(interfaces.Mode) @@ -48,11 +51,7 @@ class OFB(object): def __init__(self, initialization_vector): self.initialization_vector = initialization_vector - def validate_for_algorithm(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 - )) + validate_for_algorithm = _check_iv_length @utils.register_interface(interfaces.Mode) @@ -63,11 +62,18 @@ class CFB(object): def __init__(self, initialization_vector): self.initialization_vector = initialization_vector - def validate_for_algorithm(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 - )) + validate_for_algorithm = _check_iv_length + + +@utils.register_interface(interfaces.Mode) +@utils.register_interface(interfaces.ModeWithInitializationVector) +class CFB8(object): + name = "CFB8" + + def __init__(self, initialization_vector): + self.initialization_vector = initialization_vector + + validate_for_algorithm = _check_iv_length @utils.register_interface(interfaces.Mode) @@ -80,7 +86,7 @@ class CTR(object): def validate_for_algorithm(self, algorithm): if len(self.nonce) * 8 != algorithm.block_size: - raise ValueError("Invalid nonce size ({0}) for {1}".format( + raise ValueError("Invalid nonce size ({0}) for {1}.".format( len(self.nonce), self.name )) @@ -97,7 +103,7 @@ class GCM(object): # for it if tag is not None and len(tag) < 4: raise ValueError( - "Authentication tag must be 4 bytes or longer" + "Authentication tag must be 4 bytes or longer." ) self.initialization_vector = initialization_vector diff --git a/cryptography/hazmat/primitives/cmac.py b/cryptography/hazmat/primitives/cmac.py index 7e7f65ab..fa463ae0 100644 --- a/cryptography/hazmat/primitives/cmac.py +++ b/cryptography/hazmat/primitives/cmac.py @@ -13,8 +13,6 @@ from __future__ import absolute_import, division, print_function -import six - from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, UnsupportedAlgorithm, _Reasons @@ -28,13 +26,13 @@ class CMAC(object): def __init__(self, algorithm, backend, ctx=None): if not isinstance(backend, CMACBackend): raise UnsupportedAlgorithm( - "Backend object does not implement CMACBackend", + "Backend object does not implement CMACBackend.", _Reasons.BACKEND_MISSING_INTERFACE ) if not isinstance(algorithm, interfaces.BlockCipherAlgorithm): raise TypeError( - "Expected instance of interfaces.BlockCipherAlgorithm" + "Expected instance of interfaces.BlockCipherAlgorithm." ) self._algorithm = algorithm @@ -46,28 +44,28 @@ class CMAC(object): def update(self, data): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") - if isinstance(data, six.text_type): - raise TypeError("Unicode-objects must be encoded before hashing") + raise AlreadyFinalized("Context was already finalized.") + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") self._ctx.update(data) def finalize(self): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") digest = self._ctx.finalize() self._ctx = None return digest def verify(self, signature): - if isinstance(signature, six.text_type): - raise TypeError("Unicode-objects must be encoded before verifying") + if not isinstance(signature, bytes): + raise TypeError("signature must be bytes.") digest = self.finalize() if not constant_time.bytes_eq(digest, signature): raise InvalidSignature("Signature did not match digest.") def copy(self): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") return CMAC( self._algorithm, backend=self._backend, diff --git a/cryptography/hazmat/primitives/constant_time.py b/cryptography/hazmat/primitives/constant_time.py index e0e9aa37..4547da13 100644 --- a/cryptography/hazmat/primitives/constant_time.py +++ b/cryptography/hazmat/primitives/constant_time.py @@ -17,8 +17,6 @@ import sys import cffi -import six - from cryptography.hazmat.bindings.utils import _create_modulename TYPES = """ @@ -57,7 +55,7 @@ _lib = _ffi.verify( def bytes_eq(a, b): - if isinstance(a, six.text_type) or isinstance(b, six.text_type): - raise TypeError("Unicode-objects must be encoded before comparing") + 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 diff --git a/cryptography/hazmat/primitives/hashes.py b/cryptography/hazmat/primitives/hashes.py index 35b677b0..04f7620a 100644 --- a/cryptography/hazmat/primitives/hashes.py +++ b/cryptography/hazmat/primitives/hashes.py @@ -13,8 +13,6 @@ from __future__ import absolute_import, division, print_function -import six - from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, UnsupportedAlgorithm, _Reasons @@ -28,7 +26,7 @@ class Hash(object): def __init__(self, algorithm, backend, ctx=None): if not isinstance(backend, HashBackend): raise UnsupportedAlgorithm( - "Backend object does not implement HashBackend", + "Backend object does not implement HashBackend.", _Reasons.BACKEND_MISSING_INTERFACE ) @@ -45,21 +43,21 @@ class Hash(object): def update(self, data): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") - if isinstance(data, six.text_type): - raise TypeError("Unicode-objects must be encoded before hashing") + raise AlreadyFinalized("Context was already finalized.") + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") self._ctx.update(data) def copy(self): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") return Hash( self.algorithm, backend=self._backend, ctx=self._ctx.copy() ) def finalize(self): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") digest = self._ctx.finalize() self._ctx = None return digest diff --git a/cryptography/hazmat/primitives/hmac.py b/cryptography/hazmat/primitives/hmac.py index afbb2f75..026ad3b3 100644 --- a/cryptography/hazmat/primitives/hmac.py +++ b/cryptography/hazmat/primitives/hmac.py @@ -13,8 +13,6 @@ from __future__ import absolute_import, division, print_function -import six - from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, UnsupportedAlgorithm, _Reasons @@ -28,7 +26,7 @@ class HMAC(object): def __init__(self, key, algorithm, backend, ctx=None): if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend", + "Backend object does not implement HMACBackend.", _Reasons.BACKEND_MISSING_INTERFACE ) @@ -45,14 +43,14 @@ class HMAC(object): def update(self, msg): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") - if isinstance(msg, six.text_type): - raise TypeError("Unicode-objects must be encoded before hashing") + raise AlreadyFinalized("Context was already finalized.") + if not isinstance(msg, bytes): + raise TypeError("msg must be bytes.") self._ctx.update(msg) def copy(self): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") return HMAC( self._key, self.algorithm, @@ -62,14 +60,14 @@ class HMAC(object): def finalize(self): if self._ctx is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") digest = self._ctx.finalize() self._ctx = None return digest def verify(self, signature): - if isinstance(signature, six.text_type): - raise TypeError("Unicode-objects must be encoded before verifying") + if not isinstance(signature, bytes): + raise TypeError("signature must be bytes.") digest = self.finalize() if not constant_time.bytes_eq(digest, signature): raise InvalidSignature("Signature did not match digest.") diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 810a67a4..0dd1d01a 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -489,3 +489,63 @@ class CMACContext(object): """ Return a CMACContext that is a copy of the current context. """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurve(object): + @abc.abstractproperty + def name(self): + """ + The name of the curve. e.g. secp256r1. + """ + + @abc.abstractproperty + def key_size(self): + """ + The bit length of the base point of the curve. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurveSignatureAlgorithm(object): + @abc.abstractproperty + def algorithm(self): + """ + The digest algorithm used with this signature. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurvePrivateKey(object): + @abc.abstractmethod + def signer(self, signature_algorithm): + """ + Returns an AsymmetricSignatureContext used for signing data. + """ + + @abc.abstractmethod + def public_key(self): + """ + The EllipticCurvePublicKey for this private key. + """ + + @abc.abstractproperty + def curve(self): + """ + The EllipticCurve that this key is on. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurvePublicKey(object): + @abc.abstractmethod + def verifier(self, signature, signature_algorithm): + """ + Returns an AsymmetricVerificationContext used for signing data. + """ + + @abc.abstractproperty + def curve(self): + """ + The EllipticCurve that this key is on. + """ diff --git a/cryptography/hazmat/primitives/kdf/hkdf.py b/cryptography/hazmat/primitives/kdf/hkdf.py index 03500aaa..04d02b26 100644 --- a/cryptography/hazmat/primitives/kdf/hkdf.py +++ b/cryptography/hazmat/primitives/kdf/hkdf.py @@ -28,12 +28,53 @@ class HKDF(object): def __init__(self, algorithm, length, salt, info, backend): if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend", + "Backend object does not implement HMACBackend.", _Reasons.BACKEND_MISSING_INTERFACE ) self._algorithm = algorithm + if not isinstance(salt, bytes) and salt is not None: + raise TypeError("salt must be bytes.") + + if salt is None: + salt = b"\x00" * (self._algorithm.digest_size // 8) + + self._salt = salt + + self._backend = backend + + self._hkdf_expand = HKDFExpand(self._algorithm, length, info, backend) + + def _extract(self, key_material): + h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend) + h.update(key_material) + return h.finalize() + + def derive(self, key_material): + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") + + return self._hkdf_expand.derive(self._extract(key_material)) + + def verify(self, key_material, expected_key): + if not constant_time.bytes_eq(self.derive(key_material), expected_key): + raise InvalidKey + + +@utils.register_interface(interfaces.KeyDerivationFunction) +class HKDFExpand(object): + def __init__(self, algorithm, length, info, backend): + if not isinstance(backend, HMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + self._algorithm = algorithm + + self._backend = backend + max_length = 255 * (algorithm.digest_size // 8) if length > max_length: @@ -44,32 +85,16 @@ class HKDF(object): self._length = length - if isinstance(salt, six.text_type): - raise TypeError( - "Unicode-objects must be encoded before using them as a salt.") - - if salt is None: - salt = b"\x00" * (self._algorithm.digest_size // 8) - - self._salt = salt - - if isinstance(info, six.text_type): - raise TypeError( - "Unicode-objects must be encoded before using them as info.") + if not isinstance(info, bytes) and info is not None: + raise TypeError("info must be bytes.") if info is None: info = b"" self._info = info - self._backend = backend self._used = False - def _extract(self, key_material): - h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend) - h.update(key_material) - return h.finalize() - def _expand(self, key_material): output = [b""] counter = 1 @@ -85,17 +110,14 @@ class HKDF(object): return b"".join(output)[:self._length] def derive(self, key_material): - if isinstance(key_material, six.text_type): - raise TypeError( - "Unicode-objects must be encoded before using them as key " - "material." - ) + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") if self._used: raise AlreadyFinalized self._used = True - return self._expand(self._extract(key_material)) + return self._expand(key_material) def verify(self, key_material, expected_key): if not constant_time.bytes_eq(self.derive(key_material), expected_key): diff --git a/cryptography/hazmat/primitives/kdf/pbkdf2.py b/cryptography/hazmat/primitives/kdf/pbkdf2.py index bec35bb2..97b6408c 100644 --- a/cryptography/hazmat/primitives/kdf/pbkdf2.py +++ b/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -13,8 +13,6 @@ from __future__ import absolute_import, division, print_function -import six - from cryptography import utils from cryptography.exceptions import ( AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons @@ -28,38 +26,32 @@ class PBKDF2HMAC(object): def __init__(self, algorithm, length, salt, iterations, backend): if not isinstance(backend, PBKDF2HMACBackend): raise UnsupportedAlgorithm( - "Backend object does not implement PBKDF2HMACBackend", + "Backend object does not implement PBKDF2HMACBackend.", _Reasons.BACKEND_MISSING_INTERFACE ) if not backend.pbkdf2_hmac_supported(algorithm): raise UnsupportedAlgorithm( - "{0} is not supported for PBKDF2 by this backend".format( + "{0} is not supported for PBKDF2 by this backend.".format( algorithm.name), _Reasons.UNSUPPORTED_HASH ) self._used = False self._algorithm = algorithm self._length = length - if isinstance(salt, six.text_type): - raise TypeError( - "Unicode-objects must be encoded before using them as key " - "material." - ) + if not isinstance(salt, bytes): + raise TypeError("salt must be bytes.") self._salt = salt self._iterations = iterations self._backend = backend def derive(self, key_material): if self._used: - raise AlreadyFinalized("PBKDF2 instances can only be used once") + raise AlreadyFinalized("PBKDF2 instances can only be used once.") self._used = True - if isinstance(key_material, six.text_type): - raise TypeError( - "Unicode-objects must be encoded before using them as key " - "material." - ) + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") return self._backend.derive_pbkdf2_hmac( self._algorithm, self._length, diff --git a/cryptography/hazmat/primitives/padding.py b/cryptography/hazmat/primitives/padding.py index c1a763b5..74f1ef2e 100644 --- a/cryptography/hazmat/primitives/padding.py +++ b/cryptography/hazmat/primitives/padding.py @@ -79,10 +79,10 @@ _lib = _ffi.verify( class PKCS7(object): def __init__(self, block_size): if not (0 <= block_size < 256): - raise ValueError("block_size must be in range(0, 256)") + raise ValueError("block_size must be in range(0, 256).") if block_size % 8 != 0: - raise ValueError("block_size must be a multiple of 8") + raise ValueError("block_size must be a multiple of 8.") self.block_size = block_size @@ -102,10 +102,10 @@ class _PKCS7PaddingContext(object): def update(self, data): if self._buffer is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") - if isinstance(data, six.text_type): - raise TypeError("Unicode-objects must be encoded before padding") + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") self._buffer += data @@ -118,7 +118,7 @@ class _PKCS7PaddingContext(object): def finalize(self): if self._buffer is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") pad_size = self.block_size // 8 - len(self._buffer) result = self._buffer + six.int2byte(pad_size) * pad_size @@ -135,10 +135,10 @@ class _PKCS7UnpaddingContext(object): def update(self, data): if self._buffer is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") - if isinstance(data, six.text_type): - raise TypeError("Unicode-objects must be encoded before unpadding") + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") self._buffer += data @@ -154,17 +154,17 @@ class _PKCS7UnpaddingContext(object): def finalize(self): if self._buffer is None: - raise AlreadyFinalized("Context was already finalized") + raise AlreadyFinalized("Context was already finalized.") if len(self._buffer) != self.block_size // 8: - raise ValueError("Invalid padding bytes") + raise ValueError("Invalid padding bytes.") valid = _lib.Cryptography_check_pkcs7_padding( self._buffer, self.block_size // 8 ) if not valid: - raise ValueError("Invalid padding bytes") + raise ValueError("Invalid padding bytes.") pad_size = six.indexbytes(self._buffer, -1) res = self._buffer[:-pad_size] diff --git a/cryptography/hazmat/primitives/serialization.py b/cryptography/hazmat/primitives/serialization.py new file mode 100644 index 00000000..ed73c4c4 --- /dev/null +++ b/cryptography/hazmat/primitives/serialization.py @@ -0,0 +1,26 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + + +def load_pem_traditional_openssl_private_key(data, password, backend): + return backend.load_traditional_openssl_pem_private_key( + data, password + ) + + +def load_pem_pkcs8_private_key(data, password, backend): + return backend.load_pkcs8_pem_private_key( + data, password + ) diff --git a/cryptography/hazmat/primitives/twofactor/hotp.py b/cryptography/hazmat/primitives/twofactor/hotp.py index 41c467c8..d0b476a7 100644 --- a/cryptography/hazmat/primitives/twofactor/hotp.py +++ b/cryptography/hazmat/primitives/twofactor/hotp.py @@ -29,7 +29,7 @@ class HOTP(object): def __init__(self, key, length, algorithm, backend): if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend", + "Backend object does not implement HMACBackend.", _Reasons.BACKEND_MISSING_INTERFACE ) @@ -37,13 +37,13 @@ class HOTP(object): raise ValueError("Key length has to be at least 128 bits.") if not isinstance(length, six.integer_types): - raise TypeError("Length parameter must be an integer type") + raise TypeError("Length parameter must be an integer type.") if length < 6 or length > 8: raise ValueError("Length of HOTP has to be between 6 to 8.") if not isinstance(algorithm, (SHA1, SHA256, SHA512)): - raise TypeError("Algorithm must be SHA1, SHA256 or SHA512") + raise TypeError("Algorithm must be SHA1, SHA256 or SHA512.") self._key = key self._length = length @@ -57,15 +57,13 @@ class HOTP(object): def verify(self, hotp, counter): if not constant_time.bytes_eq(self.generate(counter), hotp): - raise InvalidToken("Supplied HOTP value does not match") + raise InvalidToken("Supplied HOTP value does not match.") def _dynamic_truncate(self, counter): ctx = hmac.HMAC(self._key, self._algorithm, self._backend) ctx.update(struct.pack(">Q", counter)) hmac_value = ctx.finalize() - offset_bits = six.indexbytes(hmac_value, len(hmac_value) - 1) & 0b1111 - - offset = int(offset_bits) + offset = six.indexbytes(hmac_value, len(hmac_value) - 1) & 0b1111 p = hmac_value[offset:offset + 4] return struct.unpack(">I", p)[0] & 0x7fffffff diff --git a/cryptography/hazmat/primitives/twofactor/totp.py b/cryptography/hazmat/primitives/twofactor/totp.py index e55ba00d..854c5163 100644 --- a/cryptography/hazmat/primitives/twofactor/totp.py +++ b/cryptography/hazmat/primitives/twofactor/totp.py @@ -25,7 +25,7 @@ class TOTP(object): def __init__(self, key, length, algorithm, time_step, backend): if not isinstance(backend, HMACBackend): raise UnsupportedAlgorithm( - "Backend object does not implement HMACBackend", + "Backend object does not implement HMACBackend.", _Reasons.BACKEND_MISSING_INTERFACE ) @@ -38,4 +38,4 @@ class TOTP(object): def verify(self, totp, time): if not constant_time.bytes_eq(self.generate(time), totp): - raise InvalidToken("Supplied TOTP value does not match") + raise InvalidToken("Supplied TOTP value does not match.") diff --git a/cryptography/utils.py b/cryptography/utils.py index 5566d123..484eec90 100644 --- a/cryptography/utils.py +++ b/cryptography/utils.py @@ -16,7 +16,7 @@ from __future__ import absolute_import, division, print_function import sys -DeprecatedIn04 = PendingDeprecationWarning +DeprecatedIn04 = DeprecationWarning def register_interface(iface): diff --git a/dev-requirements.txt b/dev-requirements.txt index 092b9914..4fff76b5 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -5,6 +5,7 @@ invoke iso8601 pep8-naming pretend +pyasn1 pytest requests sphinx diff --git a/docs/development/custom-vectors/cast5.rst b/docs/development/custom-vectors/cast5.rst index f045ec1b..97de9016 100644 --- a/docs/development/custom-vectors/cast5.rst +++ b/docs/development/custom-vectors/cast5.rst @@ -15,7 +15,8 @@ the following Python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/cast5/generate_cast5.py -Download link: :download:`generate_cast5.py </development/custom-vectors/cast5/generate_cast5.py>` +Download link: :download:`generate_cast5.py +</development/custom-vectors/cast5/generate_cast5.py>` Verification @@ -26,4 +27,5 @@ The following Go code was used to verify the vectors. .. literalinclude:: /development/custom-vectors/cast5/verify_cast5.go :language: go -Download link: :download:`verify_cast5.go </development/custom-vectors/cast5/verify_cast5.go>` +Download link: :download:`verify_cast5.go +</development/custom-vectors/cast5/verify_cast5.go>` diff --git a/docs/development/custom-vectors/idea.rst b/docs/development/custom-vectors/idea.rst index c2268634..336cdf01 100644 --- a/docs/development/custom-vectors/idea.rst +++ b/docs/development/custom-vectors/idea.rst @@ -14,7 +14,8 @@ the following python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/idea/generate_idea.py -Download link: :download:`generate_idea.py </development/custom-vectors/idea/generate_idea.py>` +Download link: :download:`generate_idea.py +</development/custom-vectors/idea/generate_idea.py>` Verification @@ -25,6 +26,7 @@ project's Python bindings. .. literalinclude:: /development/custom-vectors/idea/verify_idea.py -Download link: :download:`verify_idea.py </development/custom-vectors/idea/verify_idea.py>` +Download link: :download:`verify_idea.py +</development/custom-vectors/idea/verify_idea.py>` .. _`Botan`: http://botan.randombit.net diff --git a/docs/development/custom-vectors/seed.rst b/docs/development/custom-vectors/seed.rst index 5ea4295b..290fb77a 100644 --- a/docs/development/custom-vectors/seed.rst +++ b/docs/development/custom-vectors/seed.rst @@ -14,7 +14,8 @@ the following python script was run to generate the vector files. .. literalinclude:: /development/custom-vectors/seed/generate_seed.py -Download link: :download:`generate_seed.py </development/custom-vectors/seed/generate_seed.py>` +Download link: :download:`generate_seed.py +</development/custom-vectors/seed/generate_seed.py>` Verification @@ -25,6 +26,7 @@ project's Python bindings. .. literalinclude:: /development/custom-vectors/seed/verify_seed.py -Download link: :download:`verify_seed.py </development/custom-vectors/seed/verify_seed.py>` +Download link: :download:`verify_seed.py +</development/custom-vectors/seed/verify_seed.py>` .. _`Botan`: http://botan.randombit.net diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index ad3b4791..dd62c794 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -52,3 +52,6 @@ Post-release tasks * Check for any outstanding code undergoing a deprecation cycle by looking in ``cryptography.utils`` for ``DeprecatedIn**`` definitions. If any exist open a ticket to increment them for the next release. +* Send an email to the `mailing list`_ announcing the release. + +.. _`mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev diff --git a/docs/faq.rst b/docs/faq.rst index 0b7bdce4..4e8efc1d 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -14,5 +14,34 @@ to NaCl. If you prefer NaCl's design, we highly recommend `PyNaCl`_. +When I try to use ``cryptography`` on Windows I get a ``cffi.ffiplatform.VerificationError`` +-------------------------------------------------------------------------------------------- + +This error looks something like: + +.. code-block:: console + + cffi.ffiplatform.VerificationError: importing '<some_path>.pyd': DLL load failed: + +It typically occurs on Windows when you have not installed OpenSSL. Download +a `pre-compiled binary`_ to resolve the issue. To select the right architecture +(32-bit or 64-bit) open a command prompt and start your Python interpreter. + +If it is 32-bit it will say ``32 bit`` as well as ``Intel`` in the output: + +.. code-block:: console + + Python 2.7.6 (default, Nov 10 2013, 19:24:18) [MSC v.1500 32 bit (Intel)] on win32 + +If it is 64-bit you will see ``64 bit`` as well as ``AMD64``: + +.. code-block:: console + + Python 2.7.6 (default, Nov 10 2013, 19:24:24) [MSC v.1500 64 bit (AMD64)] on win32 + +Note that for both 32-bit and 64-bit it will say ``win32``, but other data +in the string may vary based on your version of Python. + .. _`NaCl`: http://nacl.cr.yp.to/ .. _`PyNaCl`: https://pynacl.readthedocs.org +.. _`pre-compiled binary`: https://www.openssl.org/related/binaries.html diff --git a/docs/fernet.rst b/docs/fernet.rst index f55a2d60..1c4918ad 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -34,12 +34,13 @@ symmetric (also known as "secret key") authenticated cryptography. they'll also be able forge arbitrary messages that will be authenticated and decrypted. - .. method:: encrypt(plaintext) + .. method:: encrypt(data) - :param bytes plaintext: The message you would like to encrypt. + :param bytes data: The message you would like to encrypt. :returns bytes: A secure message that cannot be read or altered without the key. It is URL-safe base64-encoded. This is referred to as a "Fernet token". + :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. note:: @@ -66,6 +67,7 @@ symmetric (also known as "secret key") authenticated cryptography. ``ttl``, it is malformed, or it does not have a valid signature. + :raises TypeError: This exception is raised if ``token`` is not ``bytes``. .. class:: InvalidToken diff --git a/docs/hazmat/backends/commoncrypto.rst b/docs/hazmat/backends/commoncrypto.rst index 77d6612c..ddaf97e5 100644 --- a/docs/hazmat/backends/commoncrypto.rst +++ b/docs/hazmat/backends/commoncrypto.rst @@ -3,8 +3,8 @@ CommonCrypto backend ==================== -The `CommonCrypto`_ C library provided by Apple on OS X and iOS. The CommonCrypto -backend is only supported on OS X versions 10.8 and above. +The `CommonCrypto`_ C library provided by Apple on OS X and iOS. The +CommonCrypto backend is only supported on OS X versions 10.8 and above. .. currentmodule:: cryptography.hazmat.backends.commoncrypto.backend diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 2f63f3e0..c1ce621a 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -6,8 +6,8 @@ Backend interfaces .. currentmodule:: cryptography.hazmat.backends.interfaces -Backend implementations may provide a number of interfaces to support operations -such as :doc:`/hazmat/primitives/symmetric-encryption`, +Backend implementations may provide a number of interfaces to support +operations such as :doc:`/hazmat/primitives/symmetric-encryption`, :doc:`/hazmat/primitives/cryptographic-hashes`, and :doc:`/hazmat/primitives/mac/hmac`. @@ -263,6 +263,26 @@ A specific ``backend`` may provide one or more of these interfaces. :returns: ``True`` if the specified ``algorithm`` is supported by this backend, otherwise ``False``. + .. method:: rsa_padding_supported(padding) + + Check if the specified ``padding`` is supported by the backend. + + :param padding: An instance of an + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` + provider. + + :returns: ``True`` if the specified ``padding`` is supported by this + backend, otherwise ``False``. + + .. method:: generate_rsa_parameters_supported(public_exponent, key_size) + + Check if the specified parameters are supported for key generation by + the backend. + + :param int public_exponent: The public exponent. + + :param int key_size: The bit length of the generated modulus. + .. method:: decrypt_rsa(private_key, ciphertext, padding) :param private_key: An instance of an @@ -275,6 +295,14 @@ A specific ``backend`` may provide one or more of these interfaces. :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` provider. + :return bytes: The decrypted data. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If an unsupported + MGF, hash function, or padding is chosen. + + :raises ValueError: When decryption fails or key size does not match + ciphertext length. + .. method:: encrypt_rsa(public_key, plaintext, padding) :param public_key: An instance of an @@ -287,6 +315,12 @@ A specific ``backend`` may provide one or more of these interfaces. :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` provider. + :return bytes: The encrypted data. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If an unsupported + MGF, hash function, or padding is chosen. + + :raises ValueError: When plaintext is too long for the key size. .. class:: TraditionalOpenSSLSerializationBackend @@ -345,6 +379,55 @@ A specific ``backend`` may provide one or more of these interfaces. 1.0.0 and the key size is larger than 1024; older OpenSSL versions do not support keys larger than 1024 bits. + .. method:: create_dsa_signature_ctx(private_key, algorithm) + + :param private_key: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.DSAPrivateKey` + provider. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext` + + .. method:: create_dsa_verification_ctx(public_key, signature, algorithm) + + :param public_key: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.DSAPublicKey` + provider. + + :param bytes signature: The signature to verify. DER encoded as + specified in :rfc:`6979`. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricVerificationContext` + + .. method:: dsa_hash_supported(algorithm): + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :returns: ``True`` if the specified ``algorithm`` is supported by this + backend, otherwise ``False``. + + .. method:: dsa_parameters_supported(p, q, g): + + :param int p: The p value of a DSA key. + + :param int q: The q value of a DSA key. + + :param int g: The g value of a DSA key. + + :returns: ``True`` if the given values of ``p``, ``q``, and ``g`` are + supported by this backend, otherwise ``False``. + .. class:: CMACBackend @@ -371,3 +454,76 @@ A specific ``backend`` may provide one or more of these interfaces. :returns: :class:`~cryptography.hazmat.primitives.interfaces.CMACContext` + + +.. class:: PKCS8SerializationBackend + + .. versionadded:: 0.5 + + A backend with methods for working with PKCS #8 key serialization. + + .. method:: load_pkcs8_pem_private_key(data, password) + + :param bytes data: PEM data to deserialize. + + :param bytes password: The password to use if this data is encrypted. + Should be None if the data is not encrypted. + + :return: A new instance of the appropriate private key or public key + that the serialized data contains. + + :raises ValueError: If the data could not be deserialized correctly. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the data is + encrypted with an unsupported algorithm. + + +.. class:: EllipticCurveBackend + + .. versionadded:: 0.5 + + .. method:: elliptic_curve_supported(curve) + + :param curve: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + provider. + + :returns: True if the elliptic curve is supported by this backend. + + .. method:: elliptic_curve_signature_algorithm_supported(signature_algorithm, curve) + + :param signature_algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurveSignatureAlgorithm` + provider. + + :param curve: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + provider. + + :returns: True if the signature algorithm and curve are supported by this backend. + + .. method:: generate_elliptic_curve_private_key(curve) + + :param curve: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + provider. + + .. method:: elliptic_curve_private_key_from_numbers(numbers) + + :param numbers: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateNumbers` + provider. + + :returns: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateKey` + provider. + + .. method:: elliptic_curve_public_key_from_numbers(numbers) + + :param numbers: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePublicNumbers` + provider. + + :returns: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePublicKey` + provider. diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index 43e5d8f3..e829798a 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -14,10 +14,14 @@ Red Hat Enterprise Linux 5) and greater. Earlier versions may work but are It implements the following interfaces: * :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + * :class:`~cryptography.hazmat.backends.interfaces.CMACBackend` + * :class:`~cryptography.hazmat.backends.interfaces.DSABackend` * :class:`~cryptography.hazmat.backends.interfaces.HashBackend` * :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` * :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` + * :class:`~cryptography.hazmat.backends.interfaces.PKCS8SerializationBackend` * :class:`~cryptography.hazmat.backends.interfaces.RSABackend` + * :class:`~cryptography.hazmat.backends.interfaces.TraditionalOpenSSLSerializationBackend` It also exposes the following: @@ -44,9 +48,9 @@ where two different processes can return similar or identical keys and compromise the security of the system. The approach this project has chosen to mitigate this vulnerability is to -include an engine that replaces the OpenSSL default CSPRNG with one that sources -its entropy from ``/dev/urandom`` on UNIX-like operating systems and uses -``CryptGenRandom`` on Windows. This method of pulling from the system pool +include an engine that replaces the OpenSSL default CSPRNG with one that +sources its entropy from ``/dev/urandom`` on UNIX-like operating systems and +uses ``CryptGenRandom`` on Windows. This method of pulling from the system pool allows us to avoid potential issues with `initializing the RNG`_ as well as protecting us from the ``fork()`` weakness. @@ -68,8 +72,8 @@ On Windows the implementation of ``CryptGenRandom`` depends on which version of the operation system you are using. See the `Microsoft documentation`_ for more details. -Linux uses its own PRNG design. ``/dev/urandom`` is a non-blocking source seeded -from the same pool as ``/dev/random``. +Linux uses its own PRNG design. ``/dev/urandom`` is a non-blocking source +seeded from the same pool as ``/dev/random``. .. _`OpenSSL`: https://www.openssl.org/ diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 2819bbdb..6848d84c 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -38,11 +38,11 @@ DSA Generate a new ``DSAParameters`` instance using ``backend``. :param int key_size: The length of the modulus in bits. It should be - either "1024, 2048 or 3072". For keys generated in 2014 this should - be `at least 2048`_ (See page 41). - Note that some applications (such as SSH) have not yet gained support - for larger key sizes specified in FIPS 186-3 and are still restricted - to only the 1024-bit keys specified in FIPS 186-2. + either 1024, 2048 or 3072. For keys generated in 2014 this should + be `at least 2048`_ (See page 41). Note that some applications + (such as SSH) have not yet gained support for larger key sizes + specified in FIPS 186-3 and are still restricted to only the + 1024-bit keys specified in FIPS 186-2. :return: A new instance of ``DSAParameters`` @@ -97,6 +97,48 @@ DSA or if the OpenSSL version is older than 1.0.0 and the key size is larger than 1024 because older OpenSSL versions don't support a key size larger than 1024. + .. method:: signer(algorithm, backend) + + .. versionadded:: 0.4 + + Sign data which can be verified later by others using the public key. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import dsa + >>> parameters = dsa.DSAParameters.generate( + ... key_size=1024, + ... backend=default_backend() + ... ) + >>> private_key = dsa.DSAPrivateKey.generate( + ... parameters=parameters, + ... backend=default_backend() + ... ) + >>> signer = private_key.signer( + ... hashes.SHA256(), + ... default_backend() + ... ) + >>> data = b"this is some data I'd like to sign" + >>> signer.update(data) + >>> signature = signer.finalize() + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.RSABackend` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext` + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if + the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + .. class:: DSAPublicKey(modulus, subgroup_order, generator, y) @@ -118,6 +160,55 @@ DSA ``subgroup_order``, ``generator``, or ``y`` do not match the bounds specified in `FIPS 186-4`_. + .. method:: verifier(signature, algorithm, backend) + + .. versionadded:: 0.4 + + Verify data was signed by the private key associated with this public + key. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import dsa + >>> parameters = dsa.DSAParameters.generate( + ... key_size=1024, + ... backend=default_backend() + ... ) + >>> private_key = dsa.DSAPrivateKey.generate( + ... parameters=parameters, + ... backend=default_backend() + ... ) + >>> signer = private_key.signer( + ... hashes.SHA256(), + ... default_backend() + ... ) + >>> data = b"this is some data I'd like to sign" + >>> signer.update(data) + >>> signature = signer.finalize() + >>> public_key = private_key.public_key() + >>> verifier = public_key.verifier( + ... signature, + ... hashes.SHA256(), + ... default_backend() + ... ) + >>> verifier.update(data) + >>> verifier.verify() + + :param bytes signature: The signature to verify. DER encoded as + specified in :rfc:`6979`. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricVerificationContext` .. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst new file mode 100644 index 00000000..f88b965a --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -0,0 +1,51 @@ +.. hazmat:: + +Elliptic Curve +============== + +.. currentmodule:: cryptography.hazmat.primitives.asymmetric.ec + + +.. class:: EllipticCurvePrivateNumbers(private_value, public_numbers) + + .. versionadded:: 0.5 + + The collection of integers that make up an EC private key. + + .. attribute:: public_numbers + + :type: :class:`~cryptography.hazmat.primitives.ec.EllipticCurvePublicNumbers` + + The :class:`EllipticCurvePublicNumbers` which makes up the EC public + key associated with this EC private key. + + .. attribute:: private_value + + :type: int + + The private value. + + +.. class:: EllipticCurvePublicNumbers(x, y, curve) + + .. versionadded:: 0.5 + + The collection of integers that make up an EC public key. + + .. attribute:: curve + + :type: :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + + The elliptic curve for this key. + + .. attribute:: x + + :type: int + + The affine x component of the public point used for verifying. + + .. attribute:: y + + :type: int + + The affine y component of the public point used for verifying. diff --git a/docs/hazmat/primitives/asymmetric/index.rst b/docs/hazmat/primitives/asymmetric/index.rst index ca048d11..6a5228ba 100644 --- a/docs/hazmat/primitives/asymmetric/index.rst +++ b/docs/hazmat/primitives/asymmetric/index.rst @@ -7,5 +7,7 @@ Asymmetric algorithms :maxdepth: 1 dsa + ec rsa padding + serialization diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index 8c34497e..54839119 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -85,7 +85,10 @@ RSA :param padding: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` - provider. + provider. Valid values are + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` and + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` + (``PSS`` is recommended for all new applications). :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` @@ -154,21 +157,39 @@ RSA :class:`~cryptography.hazmat.primitives.asymmetric.padding.OAEP` it may also be raised for invalid label values. - .. code-block:: python + .. doctest:: - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import padding + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import padding - plaintext = private_key.decrypt( - ciphertext, - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), - label=None - ), - default_backend() - ) + >>> # Generate a key + >>> private_key = rsa.RSAPrivateKey.generate( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> public_key = private_key.public_key() + >>> # encrypt some data + >>> ciphertext = public_key.encrypt( + ... b"encrypted data", + ... padding.OAEP( + ... mgf=padding.MGF1(algorithm=hashes.SHA1()), + ... algorithm=hashes.SHA1(), + ... label=None + ... ), + ... default_backend() + ... ) + >>> # Now do the actual decryption + >>> plaintext = private_key.decrypt( + ... ciphertext, + ... padding.OAEP( + ... mgf=padding.MGF1(algorithm=hashes.SHA1()), + ... algorithm=hashes.SHA1(), + ... label=None + ... ), + ... default_backend() + ... ) .. class:: RSAPublicKey(public_exponent, modulus) @@ -216,7 +237,7 @@ RSA ... hashes.SHA256(), ... default_backend() ... ) - >>> data= b"this is some data I'd like to sign" + >>> data = b"this is some data I'd like to sign" >>> signer.update(data) >>> signature = signer.finalize() >>> public_key = private_key.public_key() @@ -236,7 +257,10 @@ RSA :param padding: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` - provider. + provider. Valid values are + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` and + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` + (``PSS`` is recommended for all new applications). :param algorithm: An instance of a :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` @@ -306,27 +330,29 @@ RSA :class:`~cryptography.hazmat.primitives.asymmetric.padding.OAEP` it may also be raised for invalid label values. - .. code-block:: python - - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import padding, rsa - - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=2048, - backend=default_backend() - ) - public_key = private_key.public_key() - ciphertext = public_key.encrypt( - plaintext, - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), - label=None - ), - default_backend() - ) + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import padding + + >>> # Generate a key + >>> private_key = rsa.RSAPrivateKey.generate( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> public_key = private_key.public_key() + >>> # encrypt some data + >>> ciphertext = public_key.encrypt( + ... b"encrypted data", + ... padding.OAEP( + ... mgf=padding.MGF1(algorithm=hashes.SHA1()), + ... algorithm=hashes.SHA1(), + ... label=None + ... ), + ... default_backend() + ... ) .. class:: RSAPublicNumbers(e, n) diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst new file mode 100644 index 00000000..2b3eb511 --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -0,0 +1,100 @@ +.. hazmat:: + +Key Serialization +================= + +.. currentmodule:: cryptography.hazmat.primitives.serialization + +There are several common schemes for serializing asymmetric private and public +keys to bytes. They generally support encryption of private keys and additional +key metadata. + +Many serialization formats support multiple different types of asymmetric keys +and will return an an instance of the appropriate type. You should check that +the returned key matches the type your application expects when using these +methods. + + .. code-block:: pycon + + >>> key = load_pkcs8_private_key(pem_data, None, backend) + >>> if isinstance(key, rsa.RSAPrivateKey): + >>> signature = sign_with_rsa_key(key, message) + >>> elif isinstance(key, dsa.DSAPrivateKey): + >>> signature = sign_with_dsa_key(key, message) + >>> else: + >>> raise TypeError + + +PKCS #8 Format +~~~~~~~~~~~~~~ + +PKCS #8 is a serialization format originally standardized by RSA and +currently maintained by the IETF in :rfc:`5208`. It supports password based +encryption and additional key metadata attributes. + + +.. function:: load_pkcs8_private_key(data, password, backend) + + .. versionadded:: 0.5 + + Deserialize a private key from PEM encoded data to one of the supported + asymmetric private key types. + + :param bytes data: The PEM encoded key data. + + :param bytes password: The password to use to decrypt the data. Should + be ``None`` if the private key is not encrypted. + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.PKCS8SerializationBackend` + provider. + + :returns: A new instance of a private key. + + :raises ValueError: If the PEM data could not be decrypted or if its + structure could not be decoded successfully. + + :raises TypeError: If a ``password`` was given and the private key was + not encrypted. Or if the key was encrypted but no + password was supplied. + + :raises UnsupportedAlgorithm: If the serialized key is of a type that + is not supported by the backend or if the key is encrypted with a + symmetric cipher that is not supported by the backend. + + +Traditional OpenSSL Format +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The "traditional" PKCS #1 based serialization format used by OpenSSL. +It supports password based symmetric key encryption. Commonly found in +OpenSSL based TLS applications. It is usually found in PEM format with a +header that mentions the type of the serialized key. e.g. +``-----BEGIN RSA PRIVATE KEY-----``. + +.. function:: load_pem_traditional_openssl_private_key(data, password, backend) + + .. versionadded:: 0.5 + + Deserialize a private key from PEM encoded data to one of the supported + asymmetric private key types. + + :param bytes data: The PEM encoded key data. + + :param bytes password: The password to use to decrypt the data. Should + be ``None`` if the private key is not encrypted. + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.TraditionalOpenSSLSerializationBackend` + provider. + + :returns: A new instance of a private key. + + :raises ValueError: If the PEM data could not be decrypted or if its + structure could not be decoded successfully. + + :raises TypeError: If a ``password`` was given and the private key was + not encrypted. Or if the key was encrypted but no + password was supplied. + + :raises UnsupportedAlgorithm: If the serialized key is of a type that + is not supported by the backend or if the key is encrypted with a + symmetric cipher that is not supported by the backend. diff --git a/docs/hazmat/primitives/constant-time.rst b/docs/hazmat/primitives/constant-time.rst index c6fcb3a3..1394b6b3 100644 --- a/docs/hazmat/primitives/constant-time.rst +++ b/docs/hazmat/primitives/constant-time.rst @@ -36,6 +36,8 @@ about the timing attacks on KeyCzar and Java's ``MessageDigest.isEqual()``. :param bytes b: The right-hand side. :returns bool: ``True`` if ``a`` has the same bytes as ``b``, otherwise ``False``. + :raises TypeError: This exception is raised if ``a`` or ``b`` is not + ``bytes``. .. _`Coda Hale's blog post`: http://codahale.com/a-lesson-in-timing-attacks/ diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst index 773d97f6..7e5295c4 100644 --- a/docs/hazmat/primitives/cryptographic-hashes.rst +++ b/docs/hazmat/primitives/cryptographic-hashes.rst @@ -54,6 +54,7 @@ Message digests :param bytes data: The bytes to be hashed. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`. + :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. method:: copy() diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index c76582c0..b2857f58 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -381,6 +381,23 @@ Asymmetric interfaces The DSAParameters object associated with this private key. + .. method:: signer(algorithm, backend) + + .. versionadded:: 0.4 + + Sign data which can be verified later by others using the public key. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext` + .. attribute:: key_size :type: int @@ -412,17 +429,129 @@ Asymmetric interfaces The bit length of the modulus. + .. attribute:: y + + :type: int + + The public key. + .. method:: parameters() :return: :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters` The DSAParameters object associated with this public key. - .. attribute:: y + .. method:: verifier(signature, algorithm, backend) + + .. versionadded:: 0.4 + + Verify data was signed by the private key associated with this public + key. + + :param bytes signature: The signature to verify. DER encoded as + specified in :rfc:`6979`. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricVerificationContext` + + +.. class:: EllipticCurve + + .. versionadded:: 0.5 + + A named elliptic curve. + + .. attribute:: name + + :type: string + + The name of the curve. Usually the name used for the ASN.1 OID such as + ``secp256k1``. + + .. attribute:: key_size :type: int - The public key. + The bit length of the curve's base point. + + +.. class:: EllipticCurveSignatureAlgorithm + + .. versionadded:: 0.5 + + A signature algorithm for use with elliptic curve keys. + + .. attribute:: algorithm + + :type: :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + + The digest algorithm to be used with the signature scheme. + + +.. class:: EllipticCurvePrivateKey + + .. versionadded:: 0.5 + + An elliptic curve private key for use with an algorithm such as `ECDSA`_ or + `EdDSA`_. + + .. classmethod:: signer(signature_algorithm) + + Sign data which can be verified later by others using the public key. + + :param signature_algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurveSignatureAlgorithm` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext` + + .. attribute:: curve + + :type: :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + + The elliptic curve for this key. + + .. method:: public_key() + + :return: :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePublicKey` + + The EllipticCurvePublicKey object for this private key. + + +.. class:: EllipticCurvePublicKey + + .. versionadded:: 0.5 + + An elliptic curve public key. + + .. classmethod:: verifier(signer, signature_algorithm) + + Verify data was signed by the private key associated with this public + key. + + :param bytes signature: The signature to verify. + + :param signature_algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurveSignatureAlgorithm` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext` + + .. attribute:: curve + + :type: :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + + The elliptic curve for this key. .. class:: AsymmetricSignatureContext @@ -574,3 +703,5 @@ Key derivation functions .. _`Chinese remainder theorem`: https://en.wikipedia.org/wiki/Chinese_remainder_theorem .. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm .. _`CMAC`: https://en.wikipedia.org/wiki/CMAC +.. _`ECDSA`: http://en.wikipedia.org/wiki/ECDSA +.. _`EdDSA`: http://en.wikipedia.org/wiki/EdDSA diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst index 269f949d..f68b12c1 100644 --- a/docs/hazmat/primitives/key-derivation-functions.rst +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -88,6 +88,8 @@ Different KDFs are suitable for different tasks such as: provided ``backend`` does not implement :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` + :raises TypeError: This exception is raised if ``salt`` is not ``bytes``. + .. method:: derive(key_material) :param bytes key_material: The input key material. For PBKDF2 this @@ -99,6 +101,9 @@ Different KDFs are suitable for different tasks such as: called more than once. + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. + This generates and returns a new key from the supplied password. .. method:: verify(key_material, expected_key) @@ -191,10 +196,108 @@ Different KDFs are suitable for different tasks such as: provided ``backend`` does not implement :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + :raises TypeError: This exception is raised if ``salt`` or ``info`` is not + ``bytes``. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :return bytes: The derived key. + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. + + Derives a new key from the input key material by performing both the + extract and expand operations. + + .. method:: verify(key_material, expected_key) + + :param key_material bytes: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param expected_key bytes: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. + + +.. class:: HKDFExpand(algorithm, length, info, backend) + + .. versionadded:: 0.5 + + HKDF consists of two stages, extract and expand. This class exposes an + expand only version of HKDF that is suitable when the key material is + already cryptographically strong. + + .. warning:: + + HKDFExpand should only be used if the key material is + cryptographically strong. You should use + :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF` if + you are unsure. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> info = b"hkdf-example" + >>> key_material = os.urandom(16) + >>> hkdf = HKDFExpand( + ... algorithm=hashes.SHA256(), + ... length=32, + ... info=info, + ... backend=backend + ... ) + >>> key = hkdf.derive(key_material) + >>> hkdf = HKDFExpand( + ... algorithm=hashes.SHA256(), + ... length=32, + ... info=info, + ... backend=backend + ... ) + >>> hkdf.verify(key_material, key) + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param int length: The desired length of the derived key. Maximum is + ``255 * (algorithm.digest_size // 8)``. + + :param bytes info: Application specific context information. If ``None`` + is explicitly passed an empty byte string will be used. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + provider. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + :raises TypeError: This is raised if the provided ``info`` is a unicode object + :raises TypeError: This exception is raised if ``info`` is not ``bytes``. + .. method:: derive(key_material) :param bytes key_material: The input key material. - :retunr bytes: The derived key. + :return bytes: The derived key. + + :raises TypeError: This is raised if the provided ``key_material`` is + a unicode object + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. Derives a new key from the input key material by performing both the extract and expand operations. @@ -214,6 +317,8 @@ Different KDFs are suitable for different tasks such as: :meth:`verify` is called more than once. + :raises TypeError: This is raised if the provided ``key_material`` is + a unicode object This checks whether deriving a new key from the supplied ``key_material`` generates the same key as the ``expected_key``, and diff --git a/docs/hazmat/primitives/mac/cmac.rst b/docs/hazmat/primitives/mac/cmac.rst index a6b048b5..498b8b1e 100644 --- a/docs/hazmat/primitives/mac/cmac.rst +++ b/docs/hazmat/primitives/mac/cmac.rst @@ -10,8 +10,8 @@ Cipher-based message authentication code import binascii key = binascii.unhexlify(b"0" * 32) -`Cipher-based message authentication codes`_ (or CMACs) are a tool for calculating -message authentication codes using a block cipher coupled with a +`Cipher-based message authentication codes`_ (or CMACs) are a tool for +calculating message authentication codes using a block cipher coupled with a secret key. You can use an CMAC to verify both the integrity and authenticity of a message. @@ -38,9 +38,9 @@ A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`. :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be raised. - If the `algorithm`` isn't a - :class:`~cryptography.primitives.interfaces.BlockCipherAlgorithm` provider, - ``TypeError`` will be raised. + If ``algorithm`` isn't a + :class:`~cryptography.hazmat.primitives.interfaces.BlockCipherAlgorithm` + provider then ``TypeError`` will be raised. To check that a given signature is correct use the :meth:`verify` method. You will receive an exception if the signature is wrong: @@ -68,6 +68,7 @@ A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`. :param bytes data: The bytes to hash and authenticate. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. method:: copy() @@ -89,6 +90,8 @@ A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` :raises cryptography.exceptions.InvalidSignature: If signature does not match digest + :raises TypeError: This exception is raised if ``signature`` is not + ``bytes``. .. method:: finalize() diff --git a/docs/hazmat/primitives/mac/hmac.rst b/docs/hazmat/primitives/mac/hmac.rst index 11b10735..d56927b9 100644 --- a/docs/hazmat/primitives/mac/hmac.rst +++ b/docs/hazmat/primitives/mac/hmac.rst @@ -38,6 +38,10 @@ of a message. :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be raised. + If ``algorithm`` isn't a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` provider + then ``TypeError`` will be raised. + To check that a given signature is correct use the :meth:`verify` method. You will receive an exception if the signature is wrong: @@ -65,6 +69,7 @@ of a message. :param bytes msg: The bytes to hash and authenticate. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + :raises TypeError: This exception is raised if ``msg`` is not ``bytes``. .. method:: copy() @@ -86,6 +91,8 @@ of a message. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` :raises cryptography.exceptions.InvalidSignature: If signature does not match digest + :raises TypeError: This exception is raised if ``signature`` is not + ``bytes``. .. method:: finalize() diff --git a/docs/hazmat/primitives/mac/index.rst b/docs/hazmat/primitives/mac/index.rst index 59fb8da2..acfe9bed 100644 --- a/docs/hazmat/primitives/mac/index.rst +++ b/docs/hazmat/primitives/mac/index.rst @@ -3,6 +3,14 @@ Message Authentication Codes ============================ +While cryptography supports both the CMAC and HMAC algorithms, we strongly +recommend that HMAC should be used unless you have a good reason otherwise. + +For more information on why HMAC is preferred, see `Use cases for CMAC vs. +HMAC?`_ + +.. _`Use cases for CMAC vs. HMAC?`: http://crypto.stackexchange.com/questions/15721/use-cases-for-cmac-vs-hmac + .. toctree:: :maxdepth: 1 diff --git a/docs/hazmat/primitives/padding.rst b/docs/hazmat/primitives/padding.rst index 4092ac00..0322f9d2 100644 --- a/docs/hazmat/primitives/padding.rst +++ b/docs/hazmat/primitives/padding.rst @@ -70,6 +70,7 @@ multiple of the block size. :return bytes: Returns the data that was padded or unpadded. :raises TypeError: Raised if data is not bytes. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`. + :raises TypeError: This exception is raised if ``data`` is not ``bytes``. .. method:: finalize() diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index c98b3814..bca78354 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -20,9 +20,9 @@ provides secrecy but not authenticity. That means an attacker can't see the message but an attacker can create bogus messages and force the application to decrypt them. -For this reason it is *strongly* recommended to combine encryption with a -message authentication code, such as :doc:`HMAC </hazmat/primitives/mac/hmac>`, in -an "encrypt-then-MAC" formulation as `described by Colin Percival`_. +For this reason it is **strongly** recommended to combine encryption with a +message authentication code, such as :doc:`HMAC </hazmat/primitives/mac/hmac>`, +in an "encrypt-then-MAC" formulation as `described by Colin Percival`_. .. class:: Cipher(algorithm, mode, backend) @@ -134,8 +134,8 @@ Algorithms .. versionadded:: 0.4 - SEED is a block cipher developed by the Korea Information Security Agency ( - KISA). It is defined in :rfc:`4269` and is used broadly throughout South + SEED is a block cipher developed by the Korea Information Security Agency + (KISA). It is defined in :rfc:`4269` and is used broadly throughout South Korean industry, but rarely found elsewhere. :param bytes key: The secret key. This must be kept secret. ``128`` bits in @@ -275,6 +275,19 @@ Modes Must be the same number of bytes as the ``block_size`` of the cipher. Do not reuse an ``initialization_vector`` with a given ``key``. +.. class:: CFB8(initialization_vector) + + CFB (Cipher Feedback) is a mode of operation for block ciphers. It + transforms a block cipher into a stream cipher. The CFB8 variant uses an + 8-bit shift register. + + **This mode does not require padding.** + + :param bytes initialization_vector: Must be random bytes. They do not need + to be kept secret and they can be included in a transmitted message. + Must be the same number of bytes as the ``block_size`` of the cipher. + Do not reuse an ``initialization_vector`` with a given ``key``. + .. class:: GCM(initialization_vector, tag=None) .. danger:: diff --git a/docs/installation.rst b/docs/installation.rst index 3ebbecfd..8fbbcb30 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -24,6 +24,7 @@ We test compiling with ``clang`` as well as ``gcc`` and use the following OpenSSL releases: * ``OpenSSL 0.9.8e-fips-rhel5`` (``RHEL/CentOS 5``) +* ``OpenSSL 0.9.8k`` * ``OpenSSL 0.9.8y`` * ``OpenSSL 1.0.0-fips`` (``RHEL/CentOS 6.4``) * ``OpenSSL 1.0.1`` @@ -73,6 +74,7 @@ You should now be able to build and install cryptography with the usual $ pip install cryptography + Using your own OpenSSL on Linux ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -106,7 +108,8 @@ Using your own OpenSSL on OS X ------------------------------ To link cryptography against a custom version of OpenSSL you'll need to set -``ARCHFLAGS``, ``LDFLAGS``, and ``CFLAGS``. OpenSSL can be installed via `Homebrew`_ or `MacPorts`_: +``ARCHFLAGS``, ``LDFLAGS``, and ``CFLAGS``. OpenSSL can be installed via +`Homebrew`_ or `MacPorts`_: `Homebrew`_ @@ -122,6 +125,31 @@ or `MacPorts`_: $ sudo port install openssl $ env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/opt/local/lib" CFLAGS="-I/opt/local/include" pip install cryptography +Building cryptography with conda +-------------------------------- + +Because of a `bug in conda`_, attempting to install cryptography out of the box +will result in an error. This can be resolved by setting the library path +environment variable for your platform. + +On OS X: + +.. code-block:: console + + $ env DYLD_LIBRARY_PATH="$HOME/anaconda/lib" pip install cryptography + +and on Linux: + +.. code-block:: console + + $ env LD_LIBRARY_PATH="$HOME/anaconda/lib" pip install cryptography + +You will need to set this variable every time you start Python. For more +information, consult `Greg Wilson's blog post`_ on the subject. + + .. _`Homebrew`: http://brew.sh .. _`MacPorts`: http://www.macports.org .. _`pre-compiled binaries`: https://www.openssl.org/related/binaries.html +.. _`bug in conda`: https://github.com/conda/conda-recipes/issues/110 +.. _`Greg Wilson's blog post`: http://software-carpentry.org/blog/2014/04/mr-biczo-was-right.html diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index aae52ae7..9baf0822 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,3 +1,4 @@ +affine backend backends Backends @@ -8,6 +9,7 @@ Changelog ciphertext committer committers +conda crypto cryptographic cryptographically @@ -9,4 +9,5 @@ markers = pbkdf2hmac: this test requires a backend providing PBKDF2HMACBackend rsa: this test requires a backend providing RSABackend traditional_openssl_serialization: this test requires a backend providing TraditionalOpenSSLSerializationBackend + pkcs8_serialization: this test requires a backend providing PKCS8SerializationBackend supported: parametrized test requiring only_if and skip_message @@ -44,6 +44,7 @@ requirements = [ # If you add a new dep here you probably need to add it in the tox.ini as well test_requirements = [ "pytest", + "pyasn1", "pretend", "iso8601", ] @@ -62,7 +62,7 @@ def download_artifacts(): response.raise_for_status() for artifact in response.json()["artifacts"]: response = requests.get( - "{0}artifacts/{1}".format(run["url"], artifact["relativePath"]) + "{0}artifact/{1}".format(run["url"], artifact["relativePath"]) ) out_path = os.path.join( os.path.dirname(__file__), diff --git a/tests/conftest.py b/tests/conftest.py index 86d5a03b..b1326dc8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,8 @@ import pytest from cryptography.hazmat.backends import _available_backends from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend, - PBKDF2HMACBackend, RSABackend, TraditionalOpenSSLSerializationBackend + PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend, + TraditionalOpenSSLSerializationBackend ) from .utils import check_backend_support, check_for_iface, select_backends @@ -45,6 +46,11 @@ def pytest_runtest_setup(item): TraditionalOpenSSLSerializationBackend, item ) + check_for_iface( + "pkcs8_serialization", + PKCS8SerializationBackend, + item + ) check_backend_support(item) diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index d8c09bd7..3fa364e2 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -98,6 +98,21 @@ class DummyRSABackend(object): algorithm): pass + def mgf1_hash_supported(self, algorithm): + pass + + def rsa_padding_supported(self, padding): + pass + + def generate_rsa_parameters_supported(self, public_exponent, key_size): + pass + + def decrypt_rsa(self, private_key, ciphertext, padding): + pass + + def encrypt_rsa(self, public_key, plaintext, padding): + pass + @utils.register_interface(DSABackend) class DummyDSABackend(object): @@ -107,6 +122,18 @@ class DummyDSABackend(object): def generate_dsa_private_key(self, parameters): pass + def create_dsa_signature_ctx(self, private_key, algorithm): + pass + + def create_dsa_verification_ctx(self, public_key, signature, algorithm): + pass + + def dsa_hash_supported(self, algorithm): + pass + + def dsa_parameters_supported(self, p, q, g): + pass + @utils.register_interface(CMACBackend) class DummyCMACBackend(object): @@ -199,6 +226,16 @@ class TestMultiBackend(object): backend.create_rsa_verification_ctx("public_key", "sig", padding.PKCS1v15(), hashes.MD5()) + backend.mgf1_hash_supported(hashes.MD5()) + + backend.rsa_padding_supported(padding.PKCS1v15()) + + backend.generate_rsa_parameters_supported(65537, 1024) + + backend.encrypt_rsa("public_key", "encryptme", padding.PKCS1v15()) + + backend.decrypt_rsa("private_key", "encrypted", padding.PKCS1v15()) + backend = MultiBackend([]) with raises_unsupported_algorithm( _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM @@ -217,6 +254,31 @@ class TestMultiBackend(object): backend.create_rsa_verification_ctx( "public_key", "sig", padding.PKCS1v15(), hashes.MD5()) + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.mgf1_hash_supported(hashes.MD5()) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.rsa_padding_supported(padding.PKCS1v15()) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.generate_rsa_parameters_supported(65537, 1024) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.encrypt_rsa("public_key", "encryptme", padding.PKCS1v15()) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.decrypt_rsa("private_key", "encrypted", padding.PKCS1v15()) + def test_dsa(self): backend = MultiBackend([ DummyDSABackend() @@ -227,6 +289,11 @@ class TestMultiBackend(object): parameters = object() backend.generate_dsa_private_key(parameters) + backend.create_dsa_verification_ctx("public_key", "sig", hashes.SHA1()) + backend.create_dsa_signature_ctx("private_key", hashes.SHA1()) + backend.dsa_hash_supported(hashes.SHA1()) + backend.dsa_parameters_supported(1, 2, 3) + backend = MultiBackend([]) with raises_unsupported_algorithm( _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM @@ -238,6 +305,28 @@ class TestMultiBackend(object): ): backend.generate_dsa_private_key(parameters) + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.create_dsa_signature_ctx("private_key", hashes.SHA1()) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.create_dsa_verification_ctx( + "public_key", b"sig", hashes.SHA1() + ) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.dsa_hash_supported(hashes.SHA1()) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.dsa_parameters_supported('p', 'q', 'g') + def test_cmac(self): backend = MultiBackend([ DummyCMACBackend([algorithms.AES]) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index bba7d758..bfcdf14a 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -13,6 +13,8 @@ from __future__ import absolute_import, division, print_function +import pretend + import pytest from cryptography import utils @@ -22,7 +24,7 @@ from cryptography.hazmat.primitives import hashes, interfaces from cryptography.hazmat.primitives.asymmetric import dsa, padding, rsa from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES -from cryptography.hazmat.primitives.ciphers.modes import CBC +from cryptography.hazmat.primitives.ciphers.modes import CBC, CTR from cryptography.hazmat.primitives.interfaces import BlockCipherAlgorithm from ...utils import raises_unsupported_algorithm @@ -41,11 +43,20 @@ class DummyCipher(object): name = "dummy-cipher" +@utils.register_interface(interfaces.AsymmetricPadding) +class DummyPadding(object): + name = "dummy-cipher" + + @utils.register_interface(interfaces.HashAlgorithm) class DummyHash(object): name = "dummy-hash" +class DummyMGF(object): + _salt_length = 0 + + class TestOpenSSL(object): def test_backend_exists(self): assert backend @@ -64,6 +75,11 @@ class TestOpenSSL(object): def test_supports_cipher(self): assert backend.cipher_supported(None, None) is False + def test_aes_ctr_always_available(self): + # AES CTR should always be available in both 0.9.8 and 1.0.0+ + assert backend.cipher_supported(AES(b"\x00" * 16), + CTR(b"\x00" * 16)) is True + def test_register_duplicate_cipher_adapter(self): with pytest.raises(ValueError): backend.register_cipher_adapter(AES, CBC, None) @@ -144,15 +160,6 @@ class TestOpenSSL(object): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): backend.derive_pbkdf2_hmac(hashes.SHA256(), 10, b"", 1000, b"") - # This test is not in the TestOpenSSLRandomEngine class because to check - # if it's really default we don't want to run the setup_method before it - def test_osrandom_engine_is_default(self): - e = backend._lib.ENGINE_get_default_RAND() - name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name - res = backend._lib.ENGINE_free(e) - assert res == 1 - @pytest.mark.skipif( backend._lib.OPENSSL_VERSION_NUMBER >= 0x1000000f, reason="Requires an older OpenSSL. Must be < 1.0.0" @@ -174,6 +181,25 @@ class TestOpenSSL(object): parameters = dsa.DSAParameters.generate(3072, backend) assert utils.bit_length(parameters.p) == 3072 + def test_int_to_bn(self): + value = (2 ** 4242) - 4242 + bn = backend._int_to_bn(value) + assert bn != backend._ffi.NULL + bn = backend._ffi.gc(bn, backend._lib.BN_free) + + assert bn + assert backend._bn_to_int(bn) == value + + def test_int_to_bn_inplace(self): + value = (2 ** 4242) - 4242 + bn_ptr = backend._lib.BN_new() + assert bn_ptr != backend._ffi.NULL + bn_ptr = backend._ffi.gc(bn_ptr, backend._lib.BN_free) + bn = backend._int_to_bn(value, bn_ptr) + + assert bn == bn_ptr + assert backend._bn_to_int(bn_ptr) == value + class TestOpenSSLRandomEngine(object): def teardown_method(self, method): @@ -184,6 +210,15 @@ class TestOpenSSLRandomEngine(object): name = backend._lib.ENGINE_get_name(current_default) assert name == backend._lib.Cryptography_osrandom_engine_name + # This must be the first test in the class so that the teardown method + # has not (potentially) altered the default engine. + def test_osrandom_engine_is_default(self): + e = backend._lib.ENGINE_get_default_RAND() + name = backend._lib.ENGINE_get_name(e) + assert name == backend._lib.Cryptography_osrandom_engine_name + res = backend._lib.ENGINE_free(e) + assert res == 1 + def test_osrandom_sanity_check(self): # This test serves as a check against catastrophic failure. buf = backend._ffi.new("char[]", 500) @@ -234,27 +269,30 @@ class TestOpenSSLRandomEngine(object): e = backend._lib.ENGINE_get_default_RAND() assert e == backend._ffi.NULL - def test_int_to_bn(self): - value = (2 ** 4242) - 4242 - bn = backend._int_to_bn(value) - assert bn != backend._ffi.NULL - bn = backend._ffi.gc(bn, backend._lib.BN_free) - assert bn - assert backend._bn_to_int(bn) == value +class TestOpenSSLRSA(object): + def test_generate_rsa_parameters_supported(self): + assert backend.generate_rsa_parameters_supported(1, 1024) is False + assert backend.generate_rsa_parameters_supported(4, 1024) is False + assert backend.generate_rsa_parameters_supported(3, 1024) is True + assert backend.generate_rsa_parameters_supported(3, 511) is False - def test_int_to_bn_inplace(self): - value = (2 ** 4242) - 4242 - bn_ptr = backend._lib.BN_new() - assert bn_ptr != backend._ffi.NULL - bn_ptr = backend._ffi.gc(bn_ptr, backend._lib.BN_free) - bn = backend._int_to_bn(value, bn_ptr) + def test_generate_bad_public_exponent(self): + with pytest.raises(ValueError): + backend.generate_rsa_private_key(public_exponent=1, key_size=2048) - assert bn == bn_ptr - assert backend._bn_to_int(bn_ptr) == value + with pytest.raises(ValueError): + backend.generate_rsa_private_key(public_exponent=4, key_size=2048) + def test_cant_generate_insecure_tiny_key(self): + with pytest.raises(ValueError): + backend.generate_rsa_private_key(public_exponent=65537, + key_size=511) + + with pytest.raises(ValueError): + backend.generate_rsa_private_key(public_exponent=65537, + key_size=256) -class TestOpenSSLRSA(object): @pytest.mark.skipif( backend._lib.OPENSSL_VERSION_NUMBER >= 0x1000100f, reason="Requires an older OpenSSL. Must be < 1.0.1" @@ -270,8 +308,8 @@ class TestOpenSSLRSA(object): padding.PSS( mgf=padding.MGF1( algorithm=hashes.SHA256(), - salt_length=padding.MGF1.MAX_LENGTH - ) + ), + salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA1(), backend @@ -283,8 +321,8 @@ class TestOpenSSLRSA(object): padding.PSS( mgf=padding.MGF1( algorithm=hashes.SHA256(), - salt_length=padding.MGF1.MAX_LENGTH - ) + ), + salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA1(), backend @@ -293,6 +331,44 @@ class TestOpenSSLRSA(object): def test_unsupported_mgf1_hash_algorithm(self): assert backend.mgf1_hash_supported(DummyHash()) is False + def test_rsa_padding_unsupported_pss_mgf1_hash(self): + assert backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(DummyHash()), salt_length=0) + ) is False + + def test_rsa_padding_unsupported(self): + assert backend.rsa_padding_supported(DummyPadding()) is False + + def test_rsa_padding_supported_pkcs1v15(self): + assert backend.rsa_padding_supported(padding.PKCS1v15()) is True + + def test_rsa_padding_supported_pss(self): + assert backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ) is True + + def test_rsa_padding_supported_oaep(self): + assert backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ), + ) is True + + def test_rsa_padding_unsupported_mgf(self): + assert backend.rsa_padding_supported( + padding.OAEP( + mgf=DummyMGF(), + algorithm=hashes.SHA1(), + label=None + ), + ) is False + + assert backend.rsa_padding_supported( + padding.PSS(mgf=DummyMGF(), salt_length=0) + ) is False + def test_unsupported_mgf1_hash_algorithm_decrypt(self): private_key = rsa.RSAPrivateKey.generate( public_exponent=65537, @@ -358,3 +434,14 @@ class TestOpenSSLCMAC(object): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): backend.create_cmac_ctx(FakeAlgorithm()) + + +class TestOpenSSLSerialisationWithOpenSSL(object): + def test_password_too_long(self): + ffi_cb, cb = backend._pem_password_cb(b"aa") + assert cb(None, 1, False, None) == 0 + + def test_unsupported_evp_pkey_type(self): + key = pretend.stub(type="unsupported") + with raises_unsupported_algorithm(None): + backend._evp_pkey_to_private_key(key) diff --git a/tests/hazmat/primitives/fixtures_rsa.py b/tests/hazmat/primitives/fixtures_rsa.py new file mode 100644 index 00000000..494b9942 --- /dev/null +++ b/tests/hazmat/primitives/fixtures_rsa.py @@ -0,0 +1,493 @@ +# icensed under the Apache icense, Version 2.0 (the "icense"); +# you may not use this file except in compliance with the icense. +# You may obtain a copy of the icense at +# +# http://www.apache.org/licenses/ICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the icense is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the icense for the specific language governing permissions and +# limitations under the icense. + + +from __future__ import absolute_import, division, print_function + + +RSA_KEY_512 = { + "p": int( + "d57846898d5c0de249c08467586cb458fa9bc417cdf297f73cfc52281b787cd9", 16 + ), + "q": int( + "d10f71229e87e010eb363db6a85fd07df72d985b73c42786191f2ce9134afb2d", 16 + ), + "private_exponent": int( + "272869352cacf9c866c4e107acc95d4c608ca91460a93d28588d51cfccc07f449" + "18bbe7660f9f16adc2b4ed36ca310ef3d63b79bd447456e3505736a45a6ed21", 16 + ), + "dmp1": int( + "addff2ec7564c6b64bc670d250b6f24b0b8db6b2810099813b7e7658cecf5c39", 16 + ), + "dmq1": int( + "463ae9c6b77aedcac1397781e50e4afc060d4b216dc2778494ebe42a6850c81", 16 + ), + "iqmp": int( + "54deef8548f65cad1d411527a32dcb8e712d3e128e4e0ff118663fae82a758f4", 16 + ), + "public_exponent": 65537, + "modulus": int( + "ae5411f963c50e3267fafcf76381c8b1e5f7b741fdb2a544bcf48bd607b10c991" + "90caeb8011dc22cf83d921da55ec32bd05cac3ee02ca5e1dbef93952850b525", 16 + ), +} + +RSA_KEY_512_ALT = { + "p": int( + "febe19c29a0b50fefa4f7b1832f84df1caf9be8242da25c9d689e18226e67ce5", + 16), + "q": int( + "eb616c639dd999feda26517e1c77b6878f363fe828c4e6670ec1787f28b1e731", + 16), + "private_exponent": int( + "80edecfde704a806445a4cc782b85d3f36f17558f385654ea767f006470fdfcbda5e2" + "206839289d3f419b4e4fb8e1acee1b4fb9c591f69b64ec83937f5829241", 16), + "dmp1": int( + "7f4fa06e2a3077a54691cc5216bf13ad40a4b9fa3dd0ea4bca259487484baea5", + 16), + "dmq1": int( + "35eaa70d5a8711c352ed1c15ab27b0e3f46614d575214535ae279b166597fac1", + 16), + "iqmp": int( + "cc1f272de6846851ec80cb89a02dbac78f44b47bc08f53b67b4651a3acde8b19", + 16), + "public_exponent": 65537, + "modulus": int( + "ea397388b999ef0f7e7416fa000367efd9a0ba0deddd3f8160d1c36d62267f210fbd9" + "c97abeb6654450ff03e7601b8caa6c6f4cba18f0b52c179d17e8f258ad5", 16), +} + +RSA_KEY_522 = { + "p": int( + "1a8aab9a069f92b52fdf05824f2846223dc27adfc806716a247a77d4c36885e4bf", + 16), + "q": int( + "19e8d620d177ec54cdb733bb1915e72ef644b1202b889ceb524613efa49c07eb4f", + 16), + "private_exponent": int( + "10b8a7c0a92c1ae2d678097d69db3bfa966b541fb857468291d48d1b52397ea2bac0d" + "4370c159015c7219e3806a01bbafaffdd46f86e3da1e2d1fe80a0369ccd745", 16), + "dmp1": int( + "3eb6277f66e6e2dcf89f1b8529431f730839dbd9a3e49555159bc8470eee886e5", + 16), + "dmq1": int( + "184b4d74aa54c361e51eb23fee4eae5e4786b37b11b6e0447af9c0b9c4e4953c5b", + 16), + "iqmp": int( + "f80e9ab4fa7b35d0d232ef51c4736d1f2dcf2c7b1dd8716211b1bf1337e74f8ae", + 16), + "public_exponent": 65537, + "modulus": int( + "2afaea0e0bb6fca037da7d190b5270a6c665bc18e7a456f7e69beaac4433db748ba99" + "acdd14697e453bca596eb35b47f2d48f1f85ef08ce5109dad557a9cf85ebf1", 16), +} + +RSA_KEY_599 = { + "p": int( + "cf95d20be0c7af69f4b3d909f65d858c26d1a7ef34da8e3977f4fa230580e58814b54" + "24be99", 16), + "q": int( + "6052be4b28debd4265fe12ace5aa4a0c4eb8d63ff8853c66824b35622161eb48a3bc8" + "c3ada5", 16), + "private_exponent": int( + "69d9adc465e61585d3142d7cc8dd30605e8d1cbbf31009bc2cd5538dc40528d5d68ee" + "fe6a42d23674b6ec76e192351bf368c8968f0392110bf1c2825dbcff071270b80adcc" + "fa1d19d00a1", 16), + "dmp1": int( + "a86d10edde456687fba968b1f298d2e07226adb1221b2a466a93f3d83280f0bb46c20" + "2b6811", 16), + "dmq1": int( + "40d570e08611e6b1da94b95d46f8e7fe80be48f7a5ff8838375b08039514a399b11c2" + "80735", 16), + "iqmp": int( + "cd051cb0ea68b88765c041262ace2ec4db11dab14afd192742e34d5da3328637fabdf" + "bae26e", 16), + "public_exponent": 65537, + "modulus": int( + "4e1b470fe00642426f3808e74c959632dd67855a4c503c5b7876ccf4dc7f6a1a49107" + "b90d26daf0a7879a6858218345fbc6e59f01cd095ca5647c27c25265e6c474fea8953" + "7191c7073d9d", 16), +} + +RSA_KEY_745 = { + "p": int( + "1c5a0cfe9a86debd19eca33ba961f15bc598aa7983a545ce775b933afc89eb51bcf90" + "836257fdd060d4b383240241d", 16 + ), + "q": int( + "fb2634f657f82ee6b70553382c4e2ed26b947c97ce2f0016f1b282cf2998184ad0527" + "a9eead826dd95fe06b57a025", 16 + ), + "private_exponent": int( + "402f30f976bc07d15ff0779abff127b20a8b6b1d0024cc2ad8b6762d38f174f81e792" + "3b49d80bdbdd80d9675cbc7b2793ec199a0430eb5c84604dacfdb29259ae6a1a44676" + "22f0b23d4cb0f5cb1db4b8173c8d9d3e57a74dbd200d2141", 16), + "dmp1": int( + "e5e95b7751a6649f199be21bef7a51c9e49821d945b6fc5f538b4a670d8762c375b00" + "8e70f31d52b3ea2bd14c3101", 16), + "dmq1": int( + "12b85d5843645f72990fcf8d2f58408b34b3a3b9d9078dd527fceb5d2fb7839008092" + "dd4aca2a1fb00542801dcef5", 16), + "iqmp": int( + "5672740d947f621fc7969e3a44ec26736f3f819863d330e63e9409e139d20753551ac" + "c16544dd2bdadb9dee917440", 16), + "public_exponent": 65537, + "modulus": int( + "1bd085f92237774d34013b477ceebbb2f2feca71118db9b7429341477947e7b1d04e8" + "c43ede3c52bb25781af58d4ff81289f301eac62dc3bcd7dafd7a4d5304e9f308e7669" + "52fbf2b62373e66611fa53189987dbef9f7243dcbbeb25831", 16), +} + +RSA_KEY_768 = { + "p": int( + "f80c0061b607f93206b68e208906498d68c6e396faf457150cf975c8f849848465869" + "7ecd402313397088044c4c2071b", 16), + "q": int( + "e5b5dbecc93c6d306fc14e6aa9737f9be2728bc1a326a8713d2849b34c1cb54c63468" + "3a68abb1d345dbf15a3c492cf55", 16), + "private_exponent": int( + "d44601442255ffa331212c60385b5e898555c75c0272632ff42d57c4b16ca97dbca9f" + "d6d99cd2c9fd298df155ed5141b4be06c651934076133331d4564d73faed7ce98e283" + "2f7ce3949bc183be7e7ca34f6dd04a9098b6c73649394b0a76c541", 16), + "dmp1": int( + "a5763406fa0b65929661ce7b2b8c73220e43a5ebbfe99ff15ddf464fd238105ad4f2a" + "c83818518d70627d8908703bb03", 16), + "dmq1": int( + "cb467a9ef899a39a685aecd4d0ad27b0bfdc53b68075363c373d8eb2bed8eccaf3533" + "42f4db735a9e087b7539c21ba9d", 16), + "iqmp": int( + "5fe86bd3aee0c4d09ef11e0530a78a4534c9b833422813b5c934a450c8e564d8097a0" + "6fd74f1ebe2d5573782093f587a", 16), + "public_exponent": 65537, + "modulus": int( + "de92f1eb5f4abf426b6cac9dd1e9bf57132a4988b4ed3f8aecc15e251028bd6df46eb" + "97c711624af7db15e6430894d1b640c13929329241ee094f5a4fe1a20bc9b75232320" + "a72bc567207ec54d6b48dccb19737cf63acc1021abb337f19130f7", 16), +} + +RSA_KEY_1024 = { + "p": int( + "ea4d9d9a1a068be44b9a5f8f6de0512b2c5ba1fb804a4655babba688e6e890b347c1a" + "7426685a929337f513ae4256f0b7e5022d642237f960c5b24b96bee8e51", 16), + "q": int( + "cffb33e400d6f08b410d69deb18a85cf0ed88fcca9f32d6f2f66c62143d49aff92c11" + "4de937d4f1f62d4635ee89af99ce86d38a2b05310f3857c7b5d586ac8f9", 16), + "private_exponent": int( + "3d12d46d04ce942fb99be7bf30587b8cd3e21d75a2720e7bda1b867f1d418d91d8b9f" + "e1c00181fdde94f2faf33b4e6f800a1b3ae3b972ccb6d5079dcb6c794070ac8306d59" + "c00b58b7a9a81122a6b055832de7c72334a07494d8e7c9fbeed2cc37e011d9e6bfc6e" + "9bcddbef7f0f5771d9cf82cd4b268c97ec684575c24b6c881", 16), + "dmp1": int( + "470f2b11257b7ec9ca34136f487f939e6861920ad8a9ae132a02e74af5dceaa5b4c98" + "2949ccb44b67e2bcad2f58674db237fe250e0d62b47b28fa1dfaa603b41", 16), + "dmq1": int( + "c616e8317d6b3ae8272973709b80e8397256697ff14ea03389de454f619f99915a617" + "45319fefbe154ec1d49441a772c2f63f7d15c478199afc60469bfd0d561", 16), + "iqmp": int( + "d15e7c9ad357dfcd5dbdc8427680daf1006761bcfba93a7f86589ad88832a8d564b1c" + "d4291a658c96fbaea7ca588795820902d85caebd49c2d731e3fe0243130", 16), + "public_exponent": 65537, + "modulus": int( + "be5aac07456d990133ebce69c06b48845b972ab1ad9f134bc5683c6b5489b5119ede0" + "7be3bed0e355d48e0dfab1e4fb5187adf42d7d3fb0401c082acb8481bf17f0e871f88" + "77be04c3a1197d40aa260e2e0c48ed3fd2b93dc3fc0867591f67f3cd60a77adee1d68" + "a8c3730a5702485f6ac9ede7f0fd2918e037ee4cc1fc1b4c9", 16), +} + +RSA_KEY_1025 = { + "p": int( + "18e9bfb7071725da04d31c103fa3563648c69def43a204989214eb57b0c8b299f9ef3" + "5dda79a62d8d67fd2a9b69fbd8d0490aa2edc1e111a2b8eb7c737bb691a5", 16), + "q": int( + "d8eccaeeb95815f3079d13685f3f72ca2bf2550b349518049421375df88ca9bbb4ba8" + "cb0e3502203c9eeae174112509153445d251313e4711a102818c66fcbb7", 16), + "private_exponent": int( + "fe9ac54910b8b1bc948a03511c54cab206a1d36d50d591124109a48abb7480977ccb0" + "47b4d4f1ce7b0805df2d4fa3fe425f49b78535a11f4b87a4eba0638b3340c23d4e6b2" + "1ecebe9d5364ea6ead2d47b27836019e6ecb407000a50dc95a8614c9d0031a6e3a524" + "d2345cfb76e15c1f69d5ba35bdfb6ec63bcb115a757ef79d9", 16), + "dmp1": int( + "18537e81006a68ea76d590cc88e73bd26bc38d09c977959748e5265c0ce21c0b5fd26" + "53d975f97ef759b809f791487a8fff1264bf561627fb4527a3f0bbb72c85", 16), + "dmq1": int( + "c807eac5a1f1e1239f04b04dd16eff9a00565127a91046fa89e1eb5d6301cace85447" + "4d1f47b0332bd35b4214b66e9166953241538f761f30d969272ee214f17", 16), + "iqmp": int( + "133aa74dd41fe70fa244f07d0c4091a22f8c8f0134fe6aea9ec8b55383b758fefe358" + "2beec36eca91715eee7d21931f24fa9e97e8e3a50f9cd0f731574a5eafcc", 16), + "public_exponent": 65537, + "modulus": int( + "151c44fed756370fb2d4a0e6ec7dcac84068ca459b6aaf22daf902dca72c77563bf27" + "6fe3523f38f5ddaf3ea9aa88486a9d8760ff732489075862bee0e599de5c5f509b451" + "9f4f446521bad15cd279a498fe1e89107ce0d237e3103d7c5eb80166642e2924b152a" + "ebff97b71fdd2d68ebb45034cc784e2e822ff6d1edf98af3f3", 16), +} + +RSA_KEY_1026 = { + "p": int( + "1fcbfb8719c5bdb5fe3eb0937c76bb096e750b9442dfe31d6a877a13aed2a6a4e9f79" + "40f815f1c307dd6bc2b4b207bb6fe5be3a15bd2875a957492ce197cdedb1", 16), + "q": int( + "1f704a0f6b8966dd52582fdc08227dd3dbaeaa781918b41144b692711091b4ca4eb62" + "985c3513853828ce8739001dfba9a9a7f1a23cbcaf74280be925e2e7b50d", 16), + "private_exponent": int( + "c67975e35a1d0d0b3ebfca736262cf91990cb31cf4ac473c0c816f3bc2720bcba2475" + "e8d0de8535d257816c0fc53afc1b597eada8b229069d6ef2792fc23f59ffb4dc6c3d9" + "0a3c462082025a4cba7561296dd3d8870c4440d779406f00879afe2c681e7f5ee055e" + "ff829e6e55883ec20830c72300762e6e3a333d94b4dbe4501", 16), + "dmp1": int( + "314730ca7066c55d086a9fbdf3670ef7cef816b9efea8b514b882ae9d647217cf41d7" + "e9989269dc9893d02e315cb81f058c49043c2cac47adea58bdf5e20e841", 16), + "dmq1": int( + "1da28a9d687ff7cfeebc2439240de7505a8796376968c8ec723a2b669af8ce53d9c88" + "af18540bd78b2da429014923fa435f22697ac60812d7ca9c17a557f394cd", 16), + "iqmp": int( + "727947b57b8a36acd85180522f1b381bce5fdbd962743b3b14af98a36771a80f58ddd" + "62675d72a5935190da9ddc6fd6d6d5e9e9f805a2e92ab8d56b820493cdf", 16), + "public_exponent": 65537, + "modulus": int( + "3e7a5e6483e55eb8b723f9c46732d21b0af9e06a4a1099962d67a35ee3f62e3129cfa" + "e6ab0446da18e26f33e1d753bc1cc03585c100cf0ab5ef056695706fc8b0c9c710cd7" + "3fe6e5beda70f515a96fabd3cc5ac49efcb2594b220ff3b603fcd927f6a0838ef04bf" + "52f3ed9eab801f09e5aed1613ddeb946ed0fbb02060b3a36fd", 16), +} + +RSA_KEY_1027 = { + "p": int( + "30135e54cfb072c3d3eaf2000f3ed92ceafc85efc867b9d4bf5612f2978c432040093" + "4829f741c0f002b54af2a4433ff872b6321ef00ff1e72cba4e0ced937c7d", 16), + "q": int( + "1d01a8aead6f86b78c875f18edd74214e06535d65da054aeb8e1851d6f3319b4fb6d8" + "6b01e07d19f8261a1ded7dc08116345509ab9790e3f13e65c037e5bb7e27", 16), + "private_exponent": int( + "21cf4477df79561c7818731da9b9c88cd793f1b4b8e175bd0bfb9c0941a4dc648ecf1" + "6d96b35166c9ea116f4c2eb33ce1c231e641a37c25e54c17027bdec08ddafcb83642e" + "795a0dd133155ccc5eed03b6e745930d9ac7cfe91f9045149f33295af03a2198c660f" + "08d8150d13ce0e2eb02f21ac75d63b55822f77bd5be8d07619", 16), + "dmp1": int( + "173fb695931e845179511c18b546b265cb79b517c135902377281bdf9f34205e1f399" + "4603ad63e9f6e7885ea73a929f03fa0d6bed943051ce76cddde2d89d434d", 16), + "dmq1": int( + "10956b387b2621327da0c3c8ffea2af8be967ee25163222746c28115a406e632a7f12" + "5a9397224f1fa5c116cd3a313e5c508d31db2deb83b6e082d213e33f7fcf", 16), + "iqmp": int( + "234f833949f2c0d797bc6a0e906331e17394fa8fbc8449395766d3a8d222cf6167c48" + "8e7fe1fe9721d3e3b699a595c8e6f063d92bd840dbc84d763b2b37002109", 16), + "public_exponent": 65537, + "modulus": int( + "57281707d7f9b1369c117911758980e32c05b133ac52c225bcf68b79157ff47ea0a5a" + "e9f579ef1fd7e42937f921eb3123c4a045cc47a2159fbbf904783e654954c42294c30" + "a95c15db7c7b91f136244e548f62474b137087346c5522e54f226f49d6c93bc58cb39" + "972e41bde452bb3ae9d60eb93e5e1ce91d222138d9890c7d0b", 16), +} + +RSA_KEY_1028 = { + "p": int( + "359d17378fae8e9160097daee78a206bd52efe1b757c12a6da8026cc4fc4bb2620f12" + "b8254f4db6aed8228be8ee3e5a27ec7d31048602f01edb00befd209e8c75", 16), + "q": int( + "33a2e70b93d397c46e63b273dcd3dcfa64291342a6ce896e1ec8f1c0edc44106550f3" + "c06e7d3ca6ea29eccf3f6ab5ac6235c265313d6ea8e8767e6a343f616581", 16), + "private_exponent": int( + "880640088d331aa5c0f4cf2887809a420a2bc086e671e6ffe4e47a8c80792c038a314" + "9a8e45ef9a72816ab45b36e3af6800351067a6b2751843d4232413146bb575491463a" + "8addd06ce3d1bcf7028ec6c5d938c545a20f0a40214b5c574ca7e840062b2b5f8ed49" + "4b144bb2113677c4b10519177fee1d4f5fb8a1c159b0b47c01", 16), + "dmp1": int( + "75f8c52dad2c1cea26b8bba63236ee4059489e3d2db766136098bcc6b67fde8f77cd3" + "640035107bfb1ffc6480983cfb84fe0c3be008424ebc968a7db7e01f005", 16), + "dmq1": int( + "3893c59469e4ede5cd0e6ff9837ca023ba9b46ff40c60ccf1bec10f7d38db5b1ba817" + "6c41a3f750ec4203b711455aca06d1e0adffc5cffa42bb92c7cb77a6c01", 16), + "iqmp": int( + "ad32aafae3c962ac25459856dc8ef1f733c3df697eced29773677f435d186cf759d1a" + "5563dd421ec47b4d7e7f12f29647c615166d9c43fc49001b29089344f65", 16), + "public_exponent": 65537, + "modulus": int( + "ad0696bef71597eb3a88e135d83c596930cac73868fbd7e6b2d64f34eea5c28cce351" + "0c68073954d3ba4deb38643e7a820a4cf06e75f7f82eca545d412bd63781945c28d40" + "6e95a6cced5ae924a8bfa4f3def3e0250d91246c269ec40c89c93a85acd3770ba4d2e" + "774732f43abe94394de43fb57f93ca25f7a59d75d400a3eff5", 16), +} + +RSA_KEY_1029 = { + "p": int( + "66f33e513c0b6b6adbf041d037d9b1f0ebf8de52812a3ac397a963d3f71ba64b3ad04" + "e4d4b5e377e6fa22febcac292c907dc8dcfe64c807fd9a7e3a698850d983", 16), + "q": int( + "3b47a89a19022461dcc2d3c05b501ee76955e8ce3cf821beb4afa85a21a26fd7203db" + "deb8941f1c60ada39fd6799f6c07eb8554113f1020460ec40e93cd5f6b21", 16), + "private_exponent": int( + "280c42af8b1c719821f2f6e2bf5f3dd53c81b1f3e1e7cc4fce6e2f830132da0665bde" + "bc1e307106b112b52ad5754867dddd028116cf4471bc14a58696b99524b1ad8f05b31" + "cf47256e54ab4399b6a073b2c0452441438dfddf47f3334c13c5ec86ece4d33409056" + "139328fafa992fb5f5156f25f9b21d3e1c37f156d963d97e41", 16), + "dmp1": int( + "198c7402a4ec10944c50ab8488d7b5991c767e75eb2817bd427dff10335ae141fa2e8" + "7c016dc22d975cac229b9ffdf7d943ddfd3a04b8bf82e83c3b32c5698b11", 16), + "dmq1": int( + "15fd30c7687b68ef7c2a30cdeb913ec56c4757c218cf9a04d995470797ee5f3a17558" + "fbb6d00af245d2631d893b382da48a72bc8a613024289895952ab245b0c1", 16), + "iqmp": int( + "4f8fde17e84557a3f4e242d889e898545ab55a1a8e075c9bb0220173ccffe84659abe" + "a235104f82e32750309389d4a52af57dbb6e48d831917b6efeb190176570", 16), + "public_exponent": 65537, + "modulus": int( + "17d6e0a09aa5b2d003e51f43b9c37ffde74688f5e3b709fd02ef375cb6b8d15e299a9" + "f74981c3eeaaf947d5c2d64a1a80f5c5108a49a715c3f7be95a016b8d3300965ead4a" + "4df76e642d761526803e9434d4ec61b10cb50526d4dcaef02593085ded8c331c1b27b" + "200a45628403065efcb2c0a0ca1f75d648d40a007fbfbf2cae3", 16), +} + +RSA_KEY_1030 = { + "p": int( + "6f4ac8a8172ef1154cf7f80b5e91de723c35a4c512860bfdbafcc3b994a2384bf7796" + "3a2dd0480c7e04d5d418629651a0de8979add6f47b23da14c27a682b69c9", 16), + "q": int( + "65a9f83e07dea5b633e036a9dccfb32c46bf53c81040a19c574c3680838fc6d28bde9" + "55c0ff18b30481d4ab52a9f5e9f835459b1348bbb563ad90b15a682fadb3", 16), + "private_exponent": int( + "290db707b3e1a96445ae8ea93af55a9f211a54ebe52995c2eb28085d1e3f09c986e73" + "a00010c8e4785786eaaa5c85b98444bd93b585d0c24363ccc22c482e150a3fd900176" + "86968e4fa20423ae72823b0049defceccb39bb34aa4ef64e6b14463b76d6a871c859e" + "37285455b94b8e1527d1525b1682ac6f7c8fd79d576c55318c1", 16), + "dmp1": int( + "23f7fa84010225dea98297032dac5d45745a2e07976605681acfe87e0920a8ab3caf5" + "9d9602f3d63dc0584f75161fd8fff20c626c21c5e02a85282276a74628a9", 16), + "dmq1": int( + "18ebb657765464a8aa44bf019a882b72a2110a77934c54915f70e6375088b10331982" + "962bce1c7edd8ef9d3d95aa2566d2a99da6ebab890b95375919408d00f33", 16), + "iqmp": int( + "3d59d208743c74054151002d77dcdfc55af3d41357e89af88d7eef2767be54c290255" + "9258d85cf2a1083c035a33e65a1ca46dc8b706847c1c6434cef7b71a9dae", 16), + "public_exponent": 65537, + "modulus": int( + "2c326574320818a6a8cb6b3328e2d6c1ba2a3f09b6eb2bc543c03ab18eb5efdaa8fcd" + "bb6b4e12168304f587999f9d96a421fc80cb933a490df85d25883e6a88750d6bd8b3d" + "4117251eee8f45e70e6daac7dbbd92a9103c623a09355cf00e3f16168e38b9c4cb5b3" + "68deabbed8df466bc6835eaba959bc1c2f4ec32a09840becc8b", 16), +} + +RSA_KEY_1031 = { + "p": int( + "c0958c08e50137db989fb7cc93abf1984543e2f955d4f43fb2967f40105e79274c852" + "293fa06ce63ca8436155e475ed6d1f73fea4c8e2516cc79153e3dc83e897", 16), + "q": int( + "78cae354ea5d6862e5d71d20273b7cddb8cdfab25478fe865180676b04250685c4d03" + "30c216574f7876a7b12dfe69f1661d3b0cea6c2c0dcfb84050f817afc28d", 16), + "private_exponent": int( + "1d55cc02b17a5d25bfb39f2bc58389004d0d7255051507f75ef347cdf5519d1a00f4b" + "d235ce4171bfab7bdb7a6dcfae1cf41433fb7da5923cc84f15a675c0b83492c95dd99" + "a9fc157aea352ffdcbb5d59dbc3662171d5838d69f130678ee27841a79ef64f679ce9" + "3821fa69c03f502244c04b737edad8967def8022a144feaab29", 16), + "dmp1": int( + "5b1c2504ec3a984f86b4414342b5bcf59a0754f13adf25b2a0edbc43f5ba8c3cc061d" + "80b03e5866d059968f0d10a98deaeb4f7830436d76b22cf41f2914e13eff", 16), + "dmq1": int( + "6c361e1819691ab5d67fb2a8f65c958d301cdf24d90617c68ec7005edfb4a7b638cde" + "79d4b61cfba5c86e8c0ccf296bc7f611cb8d4ae0e072a0f68552ec2d5995", 16), + "iqmp": int( + "b7d61945fdc8b92e075b15554bab507fa8a18edd0a18da373ec6c766c71eece61136a" + "84b90b6d01741d40458bfad17a9bee9d4a8ed2f6e270782dc3bf5d58b56e", 16), + "public_exponent": 65537, + "modulus": int( + "5adebaa926ea11fb635879487fdd53dcfbb391a11ac7279bb3b4877c9b811370a9f73" + "da0690581691626d8a7cf5d972cced9c2091ccf999024b23b4e6dc6d99f80a454737d" + "ec0caffaebe4a3fac250ed02079267c8f39620b5ae3e125ca35338522dc9353ecac19" + "cb2fe3b9e3a9291619dbb1ea3a7c388e9ee6469fbf5fb22892b", 16), +} + +RSA_KEY_1536 = { + "p": int( + "f1a65fa4e2aa6e7e2b560251e8a4cd65b625ad9f04f6571785782d1c213d91c961637" + "0c572f2783caf2899f7fb690cf99a0184257fbd4b071b212c88fb348279a5387e61f1" + "17e9c62980c45ea863fa9292087c0f66ecdcde6443d5a37268bf71", 16), + "q": int( + "e54c2cbc3839b1da6ae6fea45038d986d6f523a3ae76051ba20583aab711ea5965cf5" + "3cf54128cc9573f7460bba0fd6758a57aaf240c391790fb38ab473d83ef735510c53d" + "1d10c31782e8fd7da42615e33565745c30a5e6ceb2a3ae0666cc35", 16), + "private_exponent": int( + "7bcad87e23da2cb2a8c328883fabce06e1f8e9b776c8bf253ad9884e6200e3bd9bd3b" + "a2cbe87d3854527bf005ba5d878c5b0fa20cfb0a2a42884ae95ca12bf7304285e9214" + "5e992f7006c7c0ae839ad550da495b143bec0f4806c7f44caed45f3ccc6dc44cfaf30" + "7abdb757e3d28e41c2d21366835c0a41e50a95af490ac03af061d2feb36ac0afb87be" + "a13fb0f0c5a410727ebedb286c77f9469473fae27ef2c836da6071ef7efc1647f1233" + "4009a89eecb09a8287abc8c2afd1ddd9a1b0641", 16), + "dmp1": int( + "a845366cd6f9df1f34861bef7594ed025aa83a12759e245f58adaa9bdff9c3befb760" + "75d3701e90038e888eec9bf092df63400152cb25fc07effc6c74c45f0654ccbde15cd" + "90dd5504298a946fa5cf22a956072da27a6602e6c6e5c97f2db9c1", 16), + "dmq1": int( + "28b0c1e78cdac03310717992d321a3888830ec6829978c048156152d805b4f8919c61" + "70b5dd204e5ddf3c6c53bc6aff15d0bd09faff7f351b94abb9db980b31f150a6d7573" + "08eb66938f89a5225cb4dd817a824c89e7a0293b58fc2eefb7e259", 16), + "iqmp": int( + "6c1536c0e16e42a094b6caaf50231ba81916871497d73dcbbbd4bdeb9e60cae0413b3" + "8143b5d680275b29ed7769fe5577e4f9b3647ddb064941120914526d64d80016d2eb7" + "dc362da7c569623157f3d7cff8347f11494bf5c048d77e28d3f515", 16), + "public_exponent": 65537, + "modulus": int( + "d871bb2d27672e54fc62c4680148cbdf848438da804e2c48b5a9c9f9daf6cc6e8ea7d" + "2296f25064537a9a542aef3dd449ea75774238d4da02c353d1bee70013dccc248ceef" + "4050160705c188043c8559bf6dbfb6c4bb382eda4e9547575a8227d5b3c0a70883913" + "64cf9f018d8bea053b226ec65e8cdbeaf48a071d0074860a734b1cb7d2146d43014b2" + "0776dea42f7853a54690e6cbbf3331a9f43763cfe2a51c3293bea3b2eebec0d8e43eb" + "317a443afe541107d886e5243c096091543ae65", 16), +} + +RSA_KEY_2048 = { + "p": int( + "e14202e58c5f7446648d75e5dc465781f661f6b73000c080368afcfb21377f4ef19da" + "845d4ef9bc6b151f6d9f34629103f2e57615f9ba0a3a2fbb035069e1d63b4bb0e78ad" + "dad1ec3c6f87e25c877a1c4c1972098e09158ef7b9bc163852a18d44a70b7b31a03dc" + "2614fd9ab7bf002cba79054544af3bfbdb6aed06c7b24e6ab", 16), + "q": int( + "dbe2bea1ff92599bd19f9d045d6ce62250c05cfeac5117f3cf3e626cb696e3d886379" + "557d5a57b7476f9cf886accfd40508a805fe3b45a78e1a8a125e516cda91640ee6398" + "ec5a39d3e6b177ef12ab00d07907a17640e4ca454fd8487da3c4ffa0d5c2a5edb1221" + "1c8e33c7ee9fa6753771fd111ec04b8317f86693eb2928c89", 16), + "private_exponent": int( + "aef17f80f2653bc30539f26dd4c82ed6abc1d1b53bc0abcdbee47e9a8ab433abde865" + "9fcfae1244d22de6ad333c95aee7d47f30b6815065ac3322744d3ea75058002cd1b29" + "3141ee2a6dc682342432707080071bd2131d6262cab07871c28aa5238b87173fb78c3" + "7f9c7bcd18c12e8971bb77fd9fa3e0792fec18d8d9bed0b03ba02b263606f24dbace1" + "c8263ce2802a769a090e993fd49abc50c3d3c78c29bee2de0c98055d2f102f1c5684b" + "8dddee611d5205392d8e8dd61a15bf44680972a87f040a611a149271eeb2573f8bf6f" + "627dfa70e77def2ee6584914fa0290e041349ea0999cdff3e493365885b906cbcf195" + "843345809a85098cca90fea014a21", 16), + "dmp1": int( + "9ba56522ffcfa5244eae805c87cc0303461f82be29691b9a7c15a5a050df6c143c575" + "7c288d3d7ab7f32c782e9d9fcddc10a604e6425c0e5d0e46069035d95a923646d276d" + "d9d95b8696fa29ab0de18e53f6f119310f8dd9efca62f0679291166fed8cbd5f18fe1" + "3a5f1ead1d71d8c90f40382818c18c8d069be793dbc094f69", 16), + "dmq1": int( + "a8d4a0aaa2212ccc875796a81353da1fdf00d46676c88d2b96a4bfcdd924622d8e607" + "f3ac1c01dda7ebfb0a97dd7875c2a7b2db6728fb827b89c519f5716fb3228f4121647" + "04b30253c17de2289e9cce3343baa82eb404f789e094a094577a9b0c5314f1725fdf5" + "8e87611ad20da331bd30b8aebc7dc97d0e9a9ba8579772c9", 16), + "iqmp": int( + "17bd5ef638c49440d1853acb3fa63a5aca28cb7f94ed350db7001c8445da8943866a7" + "0936e1ee2716c98b484e357cc054d82fbbd98d42f880695d38a1dd4eb096f629b9417" + "aca47e6de5da9f34e60e8a0ffd7e35be74deeef67298d94b3e0db73fc4b7a4cb360c8" + "9d2117a0bfd9434d37dc7c027d6b01e5295c875015510917d", 16), + "public_exponent": 65537, + "modulus": int( + "c17afc7e77474caa5aa83036158a3ffbf7b5216851ba2230e5d6abfcc1c6cfef59e92" + "3ea1330bc593b73802ab608a6e4a3306523a3116ba5aa3966145174e13b6c49e9b780" + "62e449d72efb10fd49e91fa08b96d051e782e9f5abc5b5a6f7984827adb8e73da00f2" + "2b2efdcdb76eab46edad98ed65662743fdc6c0e336a5d0cdbaa7dc29e53635e24c87a" + "5b2c4215968063cdeb68a972babbc1e3cff00fb9a80e372a4d0c2c920d1e8cee333ce" + "470dc2e8145adb05bf29aee1d24f141e8cc784989c587fc6fbacd979f3f2163c1d729" + "9b365bc72ffe2848e967aed1e48dcc515b3a50ed4de04fd053846ca10a223b10cc841" + "cc80fdebee44f3114c13e886af583", 16), +} diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py index a4d696c9..b9354f0e 100644 --- a/tests/hazmat/primitives/test_3des.py +++ b/tests/hazmat/primitives/test_3des.py @@ -137,3 +137,40 @@ class TestTripleDESModeCFB(object): ), lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.TripleDES("\x00" * 8), modes.CFB8("\x00" * 8) + ), + skip_message="Does not support TripleDES CFB8", +) +@pytest.mark.cipher +class TestTripleDESModeCFB8(object): + test_KAT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CFB"), + [ + "TCFB8invperm.rsp", + "TCFB8permop.rsp", + "TCFB8subtab.rsp", + "TCFB8varkey.rsp", + "TCFB8vartext.rsp", + ], + lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), + lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), + ) + + test_MMT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CFB"), + [ + "TCFB8MMT1.rsp", + "TCFB8MMT2.rsp", + "TCFB8MMT3.rsp", + ], + lambda key1, key2, key3, **kwargs: algorithms.TripleDES( + binascii.unhexlify(key1 + key2 + key3) + ), + lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), + ) diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 03be268d..173075d6 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -158,6 +158,39 @@ class TestAESModeCFB(object): @pytest.mark.supported( only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 16), modes.CFB8("\x00" * 16) + ), + skip_message="Does not support AES CFB8", +) +@pytest.mark.cipher +class TestAESModeCFB8(object): + test_CFB8 = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "CFB"), + [ + "CFB8GFSbox128.rsp", + "CFB8GFSbox192.rsp", + "CFB8GFSbox256.rsp", + "CFB8KeySbox128.rsp", + "CFB8KeySbox192.rsp", + "CFB8KeySbox256.rsp", + "CFB8VarKey128.rsp", + "CFB8VarKey192.rsp", + "CFB8VarKey256.rsp", + "CFB8VarTxt128.rsp", + "CFB8VarTxt192.rsp", + "CFB8VarTxt256.rsp", + "CFB8MMT128.rsp", + "CFB8MMT192.rsp", + "CFB8MMT256.rsp", + ], + lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.AES("\x00" * 16), modes.CTR("\x00" * 16) ), skip_message="Does not support AES CTR", diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index acfd947c..022e3af7 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -184,6 +184,14 @@ class TestModeValidation(object): backend, ) + def test_cfb8(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CFB8(b"abc"), + backend, + ) + def test_ctr(self, backend): with pytest.raises(ValueError): Cipher( diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index bc3b1db6..cbe10e9c 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -18,12 +18,15 @@ import os import pytest -from cryptography.exceptions import _Reasons +from cryptography.exceptions import ( + AlreadyFinalized, InvalidSignature, _Reasons) +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.utils import bit_length from ...utils import ( - load_fips_dsa_key_pair_vectors, load_vectors_from_file, + der_encode_dsa_signature, load_fips_dsa_key_pair_vectors, + load_fips_dsa_sig_vectors, load_vectors_from_file, raises_unsupported_algorithm ) @@ -720,12 +723,152 @@ class TestDSA(object): ) +@pytest.mark.dsa +class TestDSAVerification(object): + _algorithms_dict = { + 'SHA1': hashes.SHA1, + 'SHA224': hashes.SHA224, + 'SHA256': hashes.SHA256, + 'SHA384': hashes.SHA384, + 'SHA512': hashes.SHA512 + } + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "DSA", "FIPS_186-3", "SigVer.rsp"), + load_fips_dsa_sig_vectors + ) + ) + def test_dsa_verification(self, vector, backend): + digest_algorithm = vector['digest_algorithm'].replace("-", "") + algorithm = self._algorithms_dict[digest_algorithm] + if ( + not backend.dsa_parameters_supported( + vector['p'], vector['q'], vector['g'] + ) or not backend.dsa_hash_supported(algorithm) + ): + pytest.skip( + "{0} does not support the provided parameters".format(backend) + ) + + public_key = dsa.DSAPublicKey( + vector['p'], vector['q'], vector['g'], vector['y'] + ) + sig = der_encode_dsa_signature(vector['r'], vector['s']) + verifier = public_key.verifier(sig, algorithm(), backend) + verifier.update(vector['msg']) + if vector['result'] == "F": + with pytest.raises(InvalidSignature): + verifier.verify() + else: + verifier.verify() + + def test_dsa_verify_invalid_asn1(self, backend): + parameters = dsa.DSAParameters.generate(1024, backend) + private_key = dsa.DSAPrivateKey.generate(parameters, backend) + public_key = private_key.public_key() + verifier = public_key.verifier(b'fakesig', hashes.SHA1(), backend) + verifier.update(b'fakesig') + with pytest.raises(InvalidSignature): + verifier.verify() + + def test_use_after_finalize(self, backend): + parameters = dsa.DSAParameters.generate(1024, backend) + private_key = dsa.DSAPrivateKey.generate(parameters, backend) + public_key = private_key.public_key() + verifier = public_key.verifier(b'fakesig', hashes.SHA1(), backend) + verifier.update(b'irrelevant') + with pytest.raises(InvalidSignature): + verifier.verify() + with pytest.raises(AlreadyFinalized): + verifier.verify() + with pytest.raises(AlreadyFinalized): + verifier.update(b"more data") + + def test_dsa_verifier_invalid_backend(self, backend): + pretend_backend = object() + params = dsa.DSAParameters.generate(1024, backend) + private_key = dsa.DSAPrivateKey.generate(params, backend) + public_key = private_key.public_key() + + with raises_unsupported_algorithm( + _Reasons.BACKEND_MISSING_INTERFACE): + public_key.verifier(b"sig", hashes.SHA1(), pretend_backend) + + +@pytest.mark.dsa +class TestDSASignature(object): + _algorithms_dict = { + 'SHA1': hashes.SHA1, + 'SHA224': hashes.SHA224, + 'SHA256': hashes.SHA256, + 'SHA384': hashes.SHA384, + 'SHA512': hashes.SHA512} + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "DSA", "FIPS_186-3", "SigGen.txt"), + load_fips_dsa_sig_vectors + ) + ) + def test_dsa_signing(self, vector, backend): + digest_algorithm = vector['digest_algorithm'].replace("-", "") + algorithm = self._algorithms_dict[digest_algorithm] + if ( + not backend.dsa_parameters_supported( + vector['p'], vector['q'], vector['g'] + ) or not backend.dsa_hash_supported(algorithm) + ): + pytest.skip( + "{0} does not support the provided parameters".format(backend) + ) + + private_key = dsa.DSAPrivateKey( + vector['p'], vector['q'], vector['g'], vector['x'], vector['y'] + ) + signer = private_key.signer(algorithm(), backend) + signer.update(vector['msg']) + signature = signer.finalize() + assert signature + + public_key = private_key.public_key() + verifier = public_key.verifier(signature, algorithm(), backend) + verifier.update(vector['msg']) + verifier.verify() + + def test_use_after_finalize(self, backend): + parameters = dsa.DSAParameters.generate(1024, backend) + private_key = dsa.DSAPrivateKey.generate(parameters, backend) + signer = private_key.signer(hashes.SHA1(), backend) + signer.update(b"data") + signer.finalize() + with pytest.raises(AlreadyFinalized): + signer.finalize() + with pytest.raises(AlreadyFinalized): + signer.update(b"more data") + + def test_dsa_signer_invalid_backend(self, backend): + pretend_backend = object() + params = dsa.DSAParameters.generate(1024, backend) + private_key = dsa.DSAPrivateKey.generate(params, backend) + + with raises_unsupported_algorithm( + _Reasons.BACKEND_MISSING_INTERFACE): + private_key.signer(hashes.SHA1(), pretend_backend) + + def test_dsa_generate_invalid_backend(): pretend_backend = object() - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + with raises_unsupported_algorithm( + _Reasons.BACKEND_MISSING_INTERFACE): dsa.DSAParameters.generate(1024, pretend_backend) pretend_parameters = object() - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + with raises_unsupported_algorithm( + _Reasons.BACKEND_MISSING_INTERFACE): dsa.DSAPrivateKey.generate(pretend_parameters, pretend_backend) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py new file mode 100644 index 00000000..53985fe2 --- /dev/null +++ b/tests/hazmat/primitives/test_ec.py @@ -0,0 +1,79 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography import utils +from cryptography.hazmat.primitives import interfaces +from cryptography.hazmat.primitives.asymmetric import ec + + +@utils.register_interface(interfaces.EllipticCurve) +class DummyCurve(object): + name = "dummy-curve" + + +class TestECC(object): + def test_ec_numbers(self): + numbers = ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, 3, DummyCurve() + ) + ) + + assert numbers.private_value == 1 + assert numbers.public_numbers.x == 2 + assert numbers.public_numbers.y == 3 + assert isinstance(numbers.public_numbers.curve, DummyCurve) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + None, + ec.EllipticCurvePublicNumbers( + 2, 3, DummyCurve() + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + None, 3, DummyCurve() + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, None, DummyCurve() + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, 3, None + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + None + ) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py index 2e3c0c3d..598f09f0 100644 --- a/tests/hazmat/primitives/test_hkdf.py +++ b/tests/hazmat/primitives/test_hkdf.py @@ -13,6 +13,8 @@ from __future__ import absolute_import, division, print_function +import binascii + import pytest import six @@ -21,7 +23,7 @@ from cryptography.exceptions import ( AlreadyFinalized, InvalidKey, _Reasons ) from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.hkdf import HKDF +from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand from ...utils import raises_unsupported_algorithm @@ -151,8 +153,67 @@ class TestHKDF(object): hkdf.verify(b"foo", six.u("bar")) +@pytest.mark.hmac +class TestHKDFExpand(object): + def test_derive(self, backend): + prk = binascii.unhexlify( + b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" + ) + + okm = (b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" + b"5bf34007208d5b887185865") + + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + assert binascii.hexlify(hkdf.derive(prk)) == okm + + def test_verify(self, backend): + prk = binascii.unhexlify( + b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" + ) + + okm = (b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" + b"5bf34007208d5b887185865") + + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + assert hkdf.verify(prk, binascii.unhexlify(okm)) is None + + def test_invalid_verify(self, backend): + prk = binascii.unhexlify( + b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" + ) + + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + with pytest.raises(InvalidKey): + hkdf.verify(prk, b"wrong key") + + def test_already_finalized(self, backend): + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + hkdf.derive(b"first") + + with pytest.raises(AlreadyFinalized): + hkdf.derive(b"second") + + def test_unicode_error(self, backend): + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + with pytest.raises(TypeError): + hkdf.derive(six.u("first")) + + def test_invalid_backend(): pretend_backend = object() with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): HKDF(hashes.SHA256(), 16, None, None, pretend_backend) + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + HKDFExpand(hashes.SHA256(), 16, None, pretend_backend) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 8953db5b..ba668bff 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -28,6 +28,12 @@ from cryptography.exceptions import ( from cryptography.hazmat.primitives import hashes, interfaces from cryptography.hazmat.primitives.asymmetric import padding, rsa +from .fixtures_rsa import ( + RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028, + RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, RSA_KEY_1536, RSA_KEY_2048, + RSA_KEY_512, RSA_KEY_512_ALT, RSA_KEY_522, RSA_KEY_599, RSA_KEY_745, + RSA_KEY_768, +) from .utils import ( _check_rsa_private_key, generate_rsa_verification_test ) @@ -93,7 +99,7 @@ class TestRSA(object): assert skey.key_size == key_size assert skey.public_exponent == public_exponent - def test_generate_bad_rsa_key(self, backend): + def test_generate_bad_public_exponent(self, backend): with pytest.raises(ValueError): rsa.RSAPrivateKey.generate(public_exponent=1, key_size=2048, @@ -376,6 +382,12 @@ def test_rsa_generate_invalid_backend(): @pytest.mark.rsa class TestRSASignature(object): + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) @pytest.mark.parametrize( "pkcs1_example", _flatten_pkcs1_examples(load_vectors_from_file( @@ -401,6 +413,15 @@ class TestRSASignature(object): signature = signer.finalize() assert binascii.hexlify(signature) == example["signature"] + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) @pytest.mark.parametrize( "pkcs1_example", _flatten_pkcs1_examples(load_vectors_from_file( @@ -451,15 +472,22 @@ class TestRSASignature(object): verifier.update(binascii.unhexlify(example["message"])) verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) def test_deprecated_pss_mgf1_salt_length(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) signer = private_key.signer( - padding.PSS( - mgf=padding.MGF1( + pytest.deprecated_call( + padding.PSS, + mgf=pytest.deprecated_call( + padding.MGF1, algorithm=hashes.SHA1(), salt_length=padding.MGF1.MAX_LENGTH ) @@ -472,8 +500,10 @@ class TestRSASignature(object): assert len(signature) == math.ceil(private_key.key_size / 8.0) verifier = private_key.public_key().verifier( signature, - padding.PSS( - mgf=padding.MGF1( + pytest.deprecated_call( + padding.PSS, + mgf=pytest.deprecated_call( + padding.MGF1, algorithm=hashes.SHA1(), salt_length=padding.MGF1.MAX_LENGTH ) @@ -489,15 +519,16 @@ class TestRSASignature(object): [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()] ) def test_pss_signing_sha2(self, hash_alg, backend): - if not backend.mgf1_hash_supported(hash_alg): + if not backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hash_alg), + salt_length=padding.PSS.MAX_LENGTH + ) + ): pytest.skip( - "Does not support {0} with MGF1.".format(hash_alg.name) + "Does not support {0} in MGF1 using PSS.".format(hash_alg.name) ) - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=768, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_768) public_key = private_key.public_key() pss = padding.PSS( mgf=padding.MGF1(hash_alg), @@ -520,15 +551,19 @@ class TestRSASignature(object): verifier.verify() @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA512()), + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA512()) and + backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ) + ), skip_message="Does not support SHA512." ) def test_pss_minimum_key_size_for_digest(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=522, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_522) signer = private_key.signer( padding.PSS( mgf=padding.MGF1(hashes.SHA1()), @@ -541,15 +576,20 @@ class TestRSASignature(object): signer.finalize() @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512." ) def test_pss_signing_digest_too_large_for_key_size(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) with pytest.raises(ValueError): private_key.signer( padding.PSS( @@ -560,12 +600,17 @@ class TestRSASignature(object): backend ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) def test_pss_signing_salt_length_too_long(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) signer = private_key.signer( padding.PSS( mgf=padding.MGF1(hashes.SHA1()), @@ -578,12 +623,14 @@ class TestRSASignature(object): with pytest.raises(ValueError): signer.finalize() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) def test_use_after_finalize(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1(), backend) signer.update(b"sign me") signer.finalize() @@ -593,47 +640,43 @@ class TestRSASignature(object): signer.update(b"more data") def test_unsupported_padding(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): private_key.signer(DummyPadding(), hashes.SHA1(), backend) def test_padding_incorrect_type(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) with pytest.raises(TypeError): private_key.signer("notpadding", hashes.SHA1(), backend) def test_rsa_signer_invalid_backend(self, backend): pretend_backend = object() - private_key = rsa.RSAPrivateKey.generate(65537, 2048, backend) + private_key = rsa.RSAPrivateKey(**RSA_KEY_2048) with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): private_key.signer( padding.PKCS1v15(), hashes.SHA256, pretend_backend) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) def test_unsupported_pss_mgf(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): private_key.signer(padding.PSS(mgf=DummyMGF()), hashes.SHA1(), backend) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) def test_pkcs1_digest_too_large_for_key_size(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=599, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_599) signer = private_key.signer( padding.PKCS1v15(), hashes.SHA512(), @@ -643,12 +686,14 @@ class TestRSASignature(object): with pytest.raises(ValueError): signer.finalize() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) def test_pkcs1_minimum_key_size(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=745, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_745) signer = private_key.signer( padding.PKCS1v15(), hashes.SHA512(), @@ -660,6 +705,12 @@ class TestRSASignature(object): @pytest.mark.rsa class TestRSAVerification(object): + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) @pytest.mark.parametrize( "pkcs1_example", _flatten_pkcs1_examples(load_vectors_from_file( @@ -683,12 +734,14 @@ class TestRSAVerification(object): verifier.update(binascii.unhexlify(example["message"])) verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) def test_invalid_pkcs1v15_signature_wrong_data(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) public_key = private_key.public_key() signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1(), backend) signer.update(b"sign me") @@ -703,17 +756,15 @@ class TestRSAVerification(object): with pytest.raises(InvalidSignature): verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) def test_invalid_pkcs1v15_signature_wrong_key(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) - private_key2 = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) + private_key2 = rsa.RSAPrivateKey(**RSA_KEY_512_ALT) public_key = private_key2.public_key() signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1(), backend) signer.update(b"sign me") @@ -728,6 +779,15 @@ class TestRSAVerification(object): with pytest.raises(InvalidSignature): verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=20 + ) + ), + skip_message="Does not support PSS." + ) @pytest.mark.parametrize( "pkcs1_example", _flatten_pkcs1_examples(load_vectors_from_file( @@ -754,6 +814,15 @@ class TestRSAVerification(object): verifier.update(binascii.unhexlify(example["message"])) verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) def test_invalid_pss_signature_wrong_data(self, backend): public_key = rsa.RSAPublicKey( modulus=int( @@ -780,6 +849,15 @@ class TestRSAVerification(object): with pytest.raises(InvalidSignature): verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) def test_invalid_pss_signature_wrong_key(self, backend): signature = binascii.unhexlify( b"3a1880165014ba6eb53cc1449d13e5132ebcc0cfd9ade6d7a2494a0503bd0826" @@ -808,6 +886,15 @@ class TestRSAVerification(object): with pytest.raises(InvalidSignature): verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): signature = binascii.unhexlify( b"cb43bde4f7ab89eb4a79c6e8dd67e0d1af60715da64429d90c716a490b799c29" @@ -836,12 +923,14 @@ class TestRSAVerification(object): with pytest.raises(InvalidSignature): verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) def test_use_after_finalize(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) public_key = private_key.public_key() signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1(), backend) signer.update(b"sign me") @@ -861,21 +950,13 @@ class TestRSAVerification(object): verifier.update(b"more data") def test_unsupported_padding(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): public_key.verifier(b"sig", DummyPadding(), hashes.SHA1(), backend) def test_padding_incorrect_type(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) public_key = private_key.public_key() with pytest.raises(TypeError): public_key.verifier(b"sig", "notpadding", hashes.SHA1(), backend) @@ -889,27 +970,34 @@ class TestRSAVerification(object): public_key.verifier( b"foo", padding.PKCS1v15(), hashes.SHA256(), pretend_backend) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) def test_unsupported_pss_mgf(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): public_key.verifier(b"sig", padding.PSS(mgf=DummyMGF()), hashes.SHA1(), backend) @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512." ) def test_pss_verify_digest_too_large_for_key_size(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) signature = binascii.unhexlify( b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" b"534c050ef6b19b1bdc6eb4da422e89161106a6f5b5cc16135b11eb6439b646bd" @@ -926,6 +1014,15 @@ class TestRSAVerification(object): backend ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) def test_pss_verify_salt_length_too_long(self, backend): signature = binascii.unhexlify( b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" @@ -944,8 +1041,8 @@ class TestRSAVerification(object): padding.PSS( mgf=padding.MGF1( algorithm=hashes.SHA1(), - salt_length=1000000 - ) + ), + salt_length=1000000 ), hashes.SHA1(), backend @@ -958,8 +1055,13 @@ class TestRSAVerification(object): @pytest.mark.rsa class TestRSAPSSMGF1Verification(object): test_rsa_pss_mgf1_sha1 = pytest.mark.supported( - only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA1()), - skip_message="Does not support SHA1 with MGF1." + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA1." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -972,14 +1074,19 @@ class TestRSAPSSMGF1Verification(object): lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, - salt_length=params["salt_length"] - ) + ), + salt_length=params["salt_length"] ) )) test_rsa_pss_mgf1_sha224 = pytest.mark.supported( - only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA224()), - skip_message="Does not support SHA224 with MGF1." + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA224()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA224." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -992,14 +1099,19 @@ class TestRSAPSSMGF1Verification(object): lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, - salt_length=params["salt_length"] - ) + ), + salt_length=params["salt_length"] ) )) test_rsa_pss_mgf1_sha256 = pytest.mark.supported( - only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA256()), - skip_message="Does not support SHA256 with MGF1." + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA256." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -1012,14 +1124,19 @@ class TestRSAPSSMGF1Verification(object): lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, - salt_length=params["salt_length"] - ) + ), + salt_length=params["salt_length"] ) )) test_rsa_pss_mgf1_sha384 = pytest.mark.supported( - only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA384()), - skip_message="Does not support SHA384 with MGF1." + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA384()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA384." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -1032,14 +1149,19 @@ class TestRSAPSSMGF1Verification(object): lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, - salt_length=params["salt_length"] - ) + ), + salt_length=params["salt_length"] ) )) test_rsa_pss_mgf1_sha512 = pytest.mark.supported( - only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA512()), - skip_message="Does not support SHA512 with MGF1." + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA512()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA512." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -1052,8 +1174,8 @@ class TestRSAPSSMGF1Verification(object): lambda params, hash_alg: padding.PSS( mgf=padding.MGF1( algorithm=hash_alg, - salt_length=params["salt_length"] - ) + ), + salt_length=params["salt_length"] ) )) @@ -1061,8 +1183,11 @@ class TestRSAPSSMGF1Verification(object): @pytest.mark.rsa class TestRSAPKCS1Verification(object): test_rsa_pkcs1v15_verify_sha1 = pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA1()), - skip_message="Does not support SHA1." + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA1()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA1 and PKCS1v1.5." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -1076,8 +1201,11 @@ class TestRSAPKCS1Verification(object): )) test_rsa_pkcs1v15_verify_sha224 = pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA224()), - skip_message="Does not support SHA224." + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA224()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA224 and PKCS1v1.5." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -1091,8 +1219,11 @@ class TestRSAPKCS1Verification(object): )) test_rsa_pkcs1v15_verify_sha256 = pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA256()), - skip_message="Does not support SHA256." + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA256()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA256 and PKCS1v1.5." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -1106,8 +1237,11 @@ class TestRSAPKCS1Verification(object): )) test_rsa_pkcs1v15_verify_sha384 = pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA384()), - skip_message="Does not support SHA384." + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA384()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA384 and PKCS1v1.5." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -1121,8 +1255,11 @@ class TestRSAPKCS1Verification(object): )) test_rsa_pkcs1v15_verify_sha512 = pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA512()), - skip_message="Does not support SHA512." + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA512()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA512 and PKCS1v1.5." )(generate_rsa_verification_test( load_rsa_nist_vectors, os.path.join("asymmetric", "RSA", "FIPS_186-2"), @@ -1226,6 +1363,12 @@ class TestOAEP(object): @pytest.mark.rsa class TestRSADecryption(object): + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) @pytest.mark.parametrize( "vector", _flatten_pkcs1_examples(load_vectors_from_file( @@ -1256,20 +1399,18 @@ class TestRSADecryption(object): assert message == binascii.unhexlify(example["message"]) def test_unsupported_padding(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): private_key.decrypt(b"0" * 64, DummyPadding(), backend) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) def test_decrypt_invalid_decrypt(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) with pytest.raises(ValueError): private_key.decrypt( b"\x00" * 64, @@ -1277,12 +1418,14 @@ class TestRSADecryption(object): backend ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) def test_decrypt_ciphertext_too_large(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) with pytest.raises(ValueError): private_key.decrypt( b"\x00" * 65, @@ -1290,12 +1433,14 @@ class TestRSADecryption(object): backend ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) def test_decrypt_ciphertext_too_small(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) ct = binascii.unhexlify( b"50b4c14136bd198c2f3c3ed243fce036e168d56517984a263cd66492b80804f1" b"69d210f2b9bdfb48b12f9ea05009c77da257cc600ccefe3a6283789d8ea0" @@ -1318,6 +1463,16 @@ class TestRSADecryption(object): pretend_backend ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) @pytest.mark.parametrize( "vector", _flatten_pkcs1_examples(load_vectors_from_file( @@ -1350,11 +1505,7 @@ class TestRSADecryption(object): assert message == binascii.unhexlify(example["message"]) def test_unsupported_oaep_mgf(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): private_key.decrypt( b"0" * 64, @@ -1369,26 +1520,66 @@ class TestRSADecryption(object): @pytest.mark.rsa class TestRSAEncryption(object): + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) @pytest.mark.parametrize( - ("key_size", "pad"), + ("key_data", "pad"), itertools.product( - (1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1536, 2048), - ( + (RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, + RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, + RSA_KEY_1536, RSA_KEY_2048), + [ padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None - ), - padding.PKCS1v15() - ) + ) + ] ) ) - def test_rsa_encrypt(self, key_size, pad, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=key_size, - backend=backend + def test_rsa_encrypt_oaep(self, key_data, pad, backend): + private_key = rsa.RSAPrivateKey(**key_data) + pt = b"encrypt me!" + public_key = private_key.public_key() + ct = public_key.encrypt( + pt, + pad, + backend ) + assert ct != pt + assert len(ct) == math.ceil(public_key.key_size / 8.0) + recovered_pt = private_key.decrypt( + ct, + pad, + backend + ) + assert recovered_pt == pt + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + @pytest.mark.parametrize( + ("key_data", "pad"), + itertools.product( + (RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, + RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, + RSA_KEY_1536, RSA_KEY_2048), + [padding.PKCS1v15()] + ) + ) + def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): + private_key = rsa.RSAPrivateKey(**key_data) pt = b"encrypt me!" public_key = private_key.public_key() ct = public_key.encrypt( @@ -1406,9 +1597,11 @@ class TestRSAEncryption(object): assert recovered_pt == pt @pytest.mark.parametrize( - ("key_size", "pad"), + ("key_data", "pad"), itertools.product( - (1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1536, 2048), + (RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, + RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, + RSA_KEY_1536, RSA_KEY_2048), ( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), @@ -1419,17 +1612,13 @@ class TestRSAEncryption(object): ) ) ) - def test_rsa_encrypt_key_too_small(self, key_size, pad, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=key_size, - backend=backend - ) + def test_rsa_encrypt_key_too_small(self, key_data, pad, backend): + private_key = rsa.RSAPrivateKey(**key_data) public_key = private_key.public_key() # Slightly smaller than the key size but not enough for padding. with pytest.raises(ValueError): public_key.encrypt( - b"\x00" * (key_size // 8 - 1), + b"\x00" * (private_key.key_size // 8 - 1), pad, backend ) @@ -1437,7 +1626,7 @@ class TestRSAEncryption(object): # Larger than the key size. with pytest.raises(ValueError): public_key.encrypt( - b"\x00" * (key_size // 8 + 5), + b"\x00" * (private_key.key_size // 8 + 5), pad, backend ) @@ -1455,22 +1644,14 @@ class TestRSAEncryption(object): ) def test_unsupported_padding(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): public_key.encrypt(b"somedata", DummyPadding(), backend) def test_unsupported_oaep_mgf(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) + private_key = rsa.RSAPrivateKey(**RSA_KEY_512) public_key = private_key.public_key() with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py new file mode 100644 index 00000000..b19990e0 --- /dev/null +++ b/tests/hazmat/primitives/test_serialization.py @@ -0,0 +1,546 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import absolute_import, division, print_function + +import os +import textwrap + +import pytest + +from cryptography.exceptions import _Reasons +from cryptography.hazmat.primitives.asymmetric import dsa, rsa +from cryptography.hazmat.primitives.serialization import ( + load_pem_pkcs8_private_key, + load_pem_traditional_openssl_private_key +) + +from .utils import _check_rsa_private_key, load_vectors_from_file +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.traditional_openssl_serialization +class TestTraditionalOpenSSLSerialisation(object): + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("key1.pem", b"123456"), + ("key2.pem", b"a123456"), + ("testrsa.pem", None), + ("testrsa-encrypted.pem", b"password"), + ] + ) + def test_load_pem_rsa_private_key(self, key_file, password, backend): + key = load_vectors_from_file( + os.path.join( + "asymmetric", "Traditional_OpenSSL_Serialization", key_file), + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) + + assert key + assert isinstance(key, rsa.RSAPrivateKey) + _check_rsa_private_key(key) + + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("dsa.1024.pem", None), + ("dsa.2048.pem", None), + ("dsa.3072.pem", None), + ] + ) + def test_load_pem_dsa_private_key(self, key_file, password, backend): + key = load_vectors_from_file( + os.path.join( + "asymmetric", "Traditional_OpenSSL_Serialization", key_file), + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) + + assert key + assert isinstance(key, dsa.DSAPrivateKey) + + def test_key1_pem_encrypted_values(self, backend): + pkey = load_vectors_from_file( + os.path.join( + "asymmetric", "Traditional_OpenSSL_Serialization", "key1.pem"), + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), b"123456", backend + ) + ) + assert pkey + + assert pkey.p == int( + "fb7d316fc51531b36d93adaefaf52db6ad5beb793d37c4cf9dfc1ddd17cfbafb", + 16 + ) + assert pkey.q == int( + "df98264e646de9a0fbeab094e31caad5bc7adceaaae3c800ca0275dd4bb307f5", + 16 + ) + assert pkey.private_exponent == int( + "db4848c36f478dd5d38f35ae519643b6b810d404bcb76c00e44015e56ca1cab0" + "7bb7ae91f6b4b43fcfc82a47d7ed55b8c575152116994c2ce5325ec24313b911", + 16 + ) + assert pkey.dmp1 == int( + "ce997f967192c2bcc3853186f1559fd355c190c58ddc15cbf5de9b6df954c727", + 16 + ) + assert pkey.dmq1 == int( + "b018a57ab20ffaa3862435445d863369b852cf70a67c55058213e3fe10e3848d", + 16 + ) + assert pkey.iqmp == int( + "6a8d830616924f5cf2d1bc1973f97fde6b63e052222ac7be06aa2532d10bac76", + 16 + ) + assert pkey.public_exponent == 65537 + assert pkey.modulus == int( + "dba786074f2f0350ce1d99f5aed5b520cfe0deb5429ec8f2a88563763f566e77" + "9814b7c310e5326edae31198eed439b845dd2db99eaa60f5c16a43f4be6bcf37", + 16 + ) + + def test_unused_password(self, backend): + key_file = os.path.join( + "asymmetric", "Traditional_OpenSSL_Serialization", "testrsa.pem") + password = b"this password will not be used" + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) + + def test_wrong_password(self, backend): + key_file = os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "testrsa-encrypted.pem" + ) + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) + + @pytest.mark.parametrize("password", [None, b""]) + def test_missing_password(self, backend, password): + key_file = os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "testrsa-encrypted.pem" + ) + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) + + def test_wrong_format(self, backend): + key_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_pem_traditional_openssl_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_traditional_openssl_private_key( + key_data, b"this password will not be used", backend + ) + + def test_corrupt_format(self, backend): + # privkey.pem with a bunch of data missing. + key_data = textwrap.dedent("""\ + -----BEGIN RSA PRIVATE KEY----- + MIIBPAIBAAJBAKrbeqkuRk8VcRmWFmtP+LviMB3+6dizWW3DwaffznyHGAFwUJ/I + Tv0XtbsCyl3QoyKGhrOAy3RvPK5M38iuXT0CAwEAAQJAZ3cnzaHXM/bxGaR5CR1R + rD1qFBAVfoQFiOH9uPJgMaoAuoQEisPHVcZDKcOv4wEg6/TInAIXBnEigtqvRzuy + mvcpHZwQJdmdHHkGKAs37Dfxi67HbkUCIQCeZGliHXFa071Fp06ZeWlR2ADonTZz + rJBhdTe0v5pCeQIhAIZfkiGgGBX4cIuuckzEm43g9WMUjxP/0GlK39vIyihxAiEA + mymehFRT0MvqW5xAKAx7Pgkt8HVKwVhc2LwGKHE0DZM= + -----END RSA PRIVATE KEY----- + """).encode() + + with pytest.raises(ValueError): + load_pem_traditional_openssl_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_traditional_openssl_private_key( + key_data, b"this password will not be used", backend + ) + + def test_encrypted_corrupt_format(self, backend): + # privkey.pem with a single bit flipped + key_data = textwrap.dedent("""\ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: <,ENCRYPTED + DEK-Info: AES-128-CBC,5E22A2BD85A653FB7A3ED20DE84F54CD + + hAqtb5ZkTMGcs4BBDQ1SKZzdQThWRDzEDxM3qBfjvYa35KxZ54aic013mW/lwj2I + v5bbpOjrHYHNAiZYZ7RNb+ztbF6F/g5PA5g7mFwEq+LFBY0InIplYBSv9QtE+lot + Dy4AlZa/+NzJwgdKDb+JVfk5SddyD4ywnyeORnMPy4xXKvjXwmW+iLibZVKsjIgw + H8hSxcD+FhWyJm9h9uLtmpuqhQo0jTUYpnTezZx2xeVPB53Ev7YCxR9Nsgj5GsVf + 9Z/hqLB7IFgM3pa0z3PQeUIZF/cEf72fISWIOBwwkzVrPUkXWfbuWeJXQXSs3amE + 5A295jD9BQp9CY0nNFSsy+qiXWToq2xT3y5zVNEStmN0SCGNaIlUnJzL9IHW+oMI + kPmXZMnAYBWeeCF1gf3J3aE5lZInegHNfEI0+J0LazC2aNU5Dg/BNqrmRqKWEIo/ + -----END RSA PRIVATE KEY----- + """).encode() + + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_pem_traditional_openssl_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_traditional_openssl_private_key( + key_data, password, backend + ) + + def test_unsupported_key_encryption(self, backend): + key_data = textwrap.dedent("""\ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: FAKE-123,5E22A2BD85A653FB7A3ED20DE84F54CD + + hAqtb5ZkTMGcs4BBDQ1SKZzdQThWRDzEDxM3qBfjvYa35KxZ54aic013mW/lwj2I + v5bbpOjrHYHNAiZYZ7RNb+ztbF6F/g5PA5g7mFwEq+LFBY0InIplYBSv9QtE+lot + Dy4AlZa/+NzJwgdKDb+JVfk5SddyD4ywnyeORnMPy4xXKvjXwmW+iLibZVKsjIgw + H8hSxcD+FhWyJm9h9uLtmpuqhQo0jTUYpnTezZx2xeVPB53Ev7YCxR9Nsgj5GsVf + 9Z/hqLB7IFgM3pa0z3PQeUIZF/cEf72fISWIOBwwkzVrPUkXWfbuWeJXQXSs3amE + 5A295jD9BQp9CY0nNFSsy+qiXWToq2xT3y5zVNEStmN0SCGNaIlUnJzL9IHW+oMI + kPmXZMnAYBWeeCF1gf3J3aE5lZInegHNfEI0+J0LazC2aNU5Dg/BNqrmRqKWEIo/ + -----END RSA PRIVATE KEY----- + """).encode() + + password = b"password" + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + load_pem_traditional_openssl_private_key( + key_data, password, backend + ) + + +@pytest.mark.pkcs8_serialization +class TestPKCS8Serialisation(object): + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("unencpkcs8.pem", None), + ("encpkcs8.pem", b"foobar"), + ("enc2pkcs8.pem", b"baz"), + ("pkcs12_s2k_pem-X_9607.pem", b"123456"), + ("pkcs12_s2k_pem-X_9671.pem", b"123456"), + ("pkcs12_s2k_pem-X_9925.pem", b"123456"), + ("pkcs12_s2k_pem-X_9926.pem", b"123456"), + ("pkcs12_s2k_pem-X_9927.pem", b"123456"), + ("pkcs12_s2k_pem-X_9928.pem", b"123456"), + ("pkcs12_s2k_pem-X_9929.pem", b"123456"), + ("pkcs12_s2k_pem-X_9930.pem", b"123456"), + ("pkcs12_s2k_pem-X_9931.pem", b"123456"), + ("pkcs12_s2k_pem-X_9932.pem", b"123456"), + ] + ) + def test_load_pem_rsa_private_key(self, key_file, password, backend): + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), password, backend + ) + ) + + assert key + assert isinstance(key, rsa.RSAPrivateKey) + _check_rsa_private_key(key) + + def test_unused_password(self, backend): + key_file = os.path.join( + "asymmetric", "PKCS8", "unencpkcs8.pem") + password = b"this password will not be used" + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), password, backend + ) + ) + + def test_wrong_password(self, backend): + key_file = os.path.join( + "asymmetric", "PKCS8", "encpkcs8.pem") + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), password, backend + ) + ) + + @pytest.mark.parametrize("password", [None, b""]) + def test_missing_password(self, backend, password): + key_file = os.path.join( + "asymmetric", + "PKCS8", + "encpkcs8.pem" + ) + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), password, backend + ) + ) + + def test_wrong_format(self, backend): + key_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, b"this password will not be used", backend + ) + + def test_corrupt_format(self, backend): + # unencpkcs8.pem with a bunch of data missing. + key_data = textwrap.dedent("""\ + -----BEGIN PRIVATE KEY----- + MIICdQIBADALBgkqhkiG9w0BAQEEggJhMIICXQIBAAKBgQC7JHoJfg6yNzLMOWet + 8Z49a4KD0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkk + FPZk/7x0xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAv + FiRC0Cgz+frQPFQEBsAV9RuasyQxqzxrR0Ow0qncBeGBWbYE6WZhqtcLAI895b+i + +F4lbB4iD7T9QeIDMU/aIMXA81UO4cns1z4qDAHKeyLLrPQrJ/B4X7XC+egUWm5+ + hr1qmyAMusyXIBECQQDJWZ8piluf4yrYfsJAn6hF5T4RjTztbqvO0GVG2McHY7Uj + NPSffhzHx/ll0fQEQji+OgydCCX8o3HZrgw5YfSJAkEA7e+rqdU5nO5ZG//PSEQb + tjLnRiTzBH/elQhtdZ5nF7pcpNTi4k13zutmKcWW4GK75azcRGJUhu1kDM7QYAOd + SQJAVNkYcifkvna7GmooL5VYEsQsqLbM4v0NF2TIGNfG3z1MGp75KrC5LhL97MNR + we2p/bd2k0HYyCKUGnf2nMPDiQJBAI75pwittSoE240EobUGIDTSz8CJsXIxuDmL + z+KOpdpPRR5TQmbEMEspjsFpFymMiuYPgmihQbO2cJl1qScY5OkCQQCJ6m5tcN8l + Xxg/SNpjEIv+qAyUD96XVlOJlOIeLHQ8kYE0C6ZA+MsqYIzgAreJk88Yn0lU/X0/ + mu/UpE/BRZmR + -----END PRIVATE KEY----- + """).encode() + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, b"this password will not be used", backend + ) + + def test_encrypted_corrupt_format(self, backend): + # encpkcs8.pem with some bits flipped. + key_data = textwrap.dedent("""\ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIICojAcBgoqhkiG9w0BDAEDMA4ECHK0M0+QuEL9AgIBIcSCAoDRq+KRY+0XP0tO + lwBTzViiXSXoyNnKAZKt5r5K/fGNntv22g/1s/ZNCetrqsJDC5eMUPPacz06jFq/ + Ipsep4/OgjQ9UAOzXNrWEoNyrHnWDo7usgD3CW0mKyqER4+wG0adVMbt3N+CJHGB + 85jzRmQTfkdx1rSWeSx+XyswHn8ER4+hQ+omKWMVm7AFkjjmP/KnhUnLT98J8rhU + ArQoFPHz/6HVkypFccNaPPNg6IA4aS2A+TU9vJYOaXSVfFB2yf99hfYYzC+ukmuU + 5Lun0cysK5s/5uSwDueUmDQKspnaNyiaMGDxvw8hilJc7vg0fGObfnbIpizhxJwq + gKBfR7Zt0Hv8OYi1He4MehfMGdbHskztF+yQ40LplBGXQrvAqpU4zShga1BoQ98T + 0ekbBmqj7hg47VFsppXR7DKhx7G7rpMmdKbFhAZVCjae7rRGpUtD52cpFdPhMyAX + huhMkoczwUW8B/rM4272lkHo6Br0yk/TQfTEGkvryflNVu6lniPTV151WV5U1M3o + 3G3a44eDyt7Ln+WSOpWtbPQMTrpKhur6WXgJvrpa/m02oOGdvOlDsoOCgavgQMWg + 7xKKL7620pHl7p7f/8tlE8q6vLXVvyNtAOgt/JAr2rgvrHaZSzDE0DwgCjBXEm+7 + cVMVNkHod7bLQefVanVtWqPzbmr8f7gKeuGwWSG9oew/lN2hxcLEPJHAQlnLgx3P + 0GdGjK9NvwA0EP2gYIeE4+UtSder7xQ7bVh25VB20R4TTIIs4aXXCVOoQPagnzaT + 6JLgl8FrvdfjHwIvmSOO1YMNmILBq000Q8WDqyErBDs4hsvtO6VQ4LeqJj6gClX3 + qeJNaJFu + -----END ENCRYPTED PRIVATE KEY----- + """).encode() + + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, password, backend + ) + + def test_key1_pem_encrypted_values(self, backend): + pkey = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "encpkcs8.pem"), + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), b"foobar", backend + ) + ) + assert pkey + + assert pkey.modulus == int( + "00beec64d6db5760ac2fd4c971145641b9bd7f5c56558ece608795c79807" + "376a7fe5b19f95b35ca358ea5c8abd7ae051d49cd2f1e45969a1ae945460" + "3c14b278664a0e414ebc8913acb6203626985525e17a600611b028542dd0" + "562aad787fb4f1650aa318cdcff751e1b187cbf6785fbe164e9809491b95" + "dd68480567c99b1a57", 16 + ) + + assert pkey.public_exponent == 65537 + + assert pkey.private_exponent == int( + "0cfe316e9dc6b8817f4fcfd5ae38a0886f68f773b8a6db4c9e6d8703c599" + "f3d9785c3a2c09e4c8090909fb3721e19a3009ec21221523a729265707a5" + "8f13063671c42a4096cad378ef2510cb59e23071489d8893ac4934dd149f" + "34f2d094bea57f1c8027c3a77248ac9b91218737d0c3c3dfa7d7829e6977" + "cf7d995688c86c81", 16 + ) + + assert pkey.p == int( + "00db122ac857b2c0437d7616daa98e597bb75ca9ad3a47a70bec10c10036" + "03328794b225c8e3eee6ffd3fd6d2253d28e071fe27d629ab072faa14377" + "ce6118cb67", 16 + ) + + assert pkey.q == int( + "00df1b8aa8506fcbbbb9d00257f2975e38b33d2698fd0f37e82d7ef38c56" + "f21b6ced63c825383782a7115cfcc093300987dbd2853b518d1c8f26382a" + "2d2586d391", 16 + ) + + assert pkey.dmp1 == int( + "00be18aca13e60712fdf5daa85421eb10d86d654b269e1255656194fb0c4" + "2dd01a1070ea12c19f5c39e09587af02f7b1a1030d016a9ffabf3b36d699" + "ceaf38d9bf", 16 + ) + + assert pkey.dmq1 == int( + "71aa8978f90a0c050744b77cf1263725b203ac9f730606d8ae1d289dce4a" + "28b8d534e9ea347aeb808c73107e583eb80c546d2bddadcdb3c82693a4c1" + "3d863451", 16 + ) + + assert pkey.iqmp == int( + "136b7b1afac6e6279f71b24217b7083485a5e827d156024609dae39d48a6" + "bdb55af2f062cc4a3b077434e6fffad5faa29a2b5dba2bed3e4621e478c0" + "97ccfe7f", 16 + ) + + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("unenc-dsa-pkcs8.pem", None), + ] + ) + def test_load_pem_dsa_private_key(self, key_file, password, backend): + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) + assert key + assert isinstance(key, dsa.DSAPrivateKey) + + params = key.parameters() + assert isinstance(params, dsa.DSAParameters) + + assert key.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", 16) + assert key.y == int( + "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" + "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" + "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" + "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" + "ab6bcce04bfdf5b6", + 16 + ) + + assert params.p == int( + "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" + "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" + "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" + "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" + "940cabe5d2de49a167", + 16 + ) + + assert params.q == int("00adc0e869b36f0ac013a681fdf4d4899d69820451", + 16) + + assert params.g == int( + "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" + "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" + "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" + "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" + "68ce5cfff241cd3246", + 16 + ) + + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("bad-oid-dsa-key.pem", None), + ] + ) + def test_load_bad_oid_key(self, key_file, password, backend): + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) + + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("bad-encryption-oid.pem", b"password"), + ] + ) + def test_load_bad_encryption_oid_key(self, key_file, password, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 6c3f4c95..a496459b 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -26,7 +26,7 @@ from cryptography.exceptions import ( from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.ciphers import Cipher -from cryptography.hazmat.primitives.kdf.hkdf import HKDF +from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from ...utils import load_vectors_from_file @@ -347,15 +347,14 @@ def hkdf_extract_test(backend, algorithm, params): def hkdf_expand_test(backend, algorithm, params): - hkdf = HKDF( + hkdf = HKDFExpand( algorithm, int(params["l"]), - salt=binascii.unhexlify(params["salt"]) or None, info=binascii.unhexlify(params["info"]) or None, backend=backend ) - okm = hkdf._expand(binascii.unhexlify(params["prk"])) + okm = hkdf.derive(binascii.unhexlify(params["prk"])) assert okm == binascii.unhexlify(params["okm"]) diff --git a/tests/test_utils.py b/tests/test_utils.py index 7a0b9e74..4673b49e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -27,11 +27,11 @@ from cryptography.exceptions import UnsupportedAlgorithm, _Reasons import cryptography_vectors from .utils import ( - check_backend_support, check_for_iface, load_cryptrec_vectors, - load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, - load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, - load_hash_vectors, load_nist_vectors, load_pkcs1_vectors, - load_rsa_nist_vectors, load_vectors_from_file, + check_backend_support, check_for_iface, der_encode_dsa_signature, + load_cryptrec_vectors, load_fips_dsa_key_pair_vectors, + load_fips_dsa_sig_vectors, load_fips_ecdsa_key_pair_vectors, + load_fips_ecdsa_signing_vectors, load_hash_vectors, load_nist_vectors, + load_pkcs1_vectors, load_rsa_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm, select_backends ) @@ -125,6 +125,26 @@ def test_check_backend_support_no_backend(): check_backend_support(item) +def test_der_encode_dsa_signature_values(): + sig = der_encode_dsa_signature(1, 1) + assert sig == b"0\x06\x02\x01\x01\x02\x01\x01" + + sig2 = der_encode_dsa_signature( + 1037234182290683143945502320610861668562885151617, + 559776156650501990899426031439030258256861634312 + ) + assert sig2 == ( + b'0-\x02\x15\x00\xb5\xaf0xg\xfb\x8bT9\x00\x13\xccg\x02\r\xdf\x1f,\x0b' + b'\x81\x02\x14b\r;"\xabP1D\x0c>5\xea\xb6\xf4\x81)\x8f\x9e\x9f\x08' + ) + + sig3 = der_encode_dsa_signature(0, 0) + assert sig3 == b"0\x06\x02\x01\x00\x02\x01\x00" + + sig4 = der_encode_dsa_signature(-1, 0) + assert sig4 == b"0\x06\x02\x01\xFF\x02\x01\x00" + + def test_load_nist_vectors(): vector_data = textwrap.dedent(""" # CAVS 11.1 diff --git a/tests/utils.py b/tests/utils.py index 60b6f5a2..5c0e2343 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -18,6 +18,9 @@ import collections import re from contextlib import contextmanager +from pyasn1.codec.der import encoder +from pyasn1.type import namedtype, univ + import pytest import six @@ -80,6 +83,20 @@ def raises_unsupported_algorithm(reason): assert exc_info.value._reason is reason +class _DSSSigValue(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('r', univ.Integer()), + namedtype.NamedType('s', univ.Integer()) + ) + + +def der_encode_dsa_signature(r, s): + sig = _DSSSigValue() + sig.setComponentByName('r', r) + sig.setComponentByName('s', s) + return encoder.encode(sig) + + def load_vectors_from_file(filename, loader): with cryptography_vectors.open_vector_file(filename) as vector_file: return loader(vector_file) @@ -7,6 +7,7 @@ deps = coverage iso8601 pretend + pyasn1 pytest ./vectors commands = @@ -15,6 +16,7 @@ commands = [testenv:docs] deps = + doc8 pyenchant sphinx sphinx_rtd_theme @@ -25,6 +27,7 @@ commands = sphinx-build -W -b latex -d {envtmpdir}/doctrees docs docs/_build/latex sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html sphinx-build -W -b spelling docs docs/_build/html + doc8 --allow-long-titles README.rst docs/ [testenv:docs-linkcheck] deps = @@ -44,7 +47,8 @@ deps = flake8 flake8-import-order pep8-naming -commands = flake8 . +commands = + flake8 . [testenv:py3pep8] basepython = python3.3 @@ -52,7 +56,8 @@ deps = flake8 flake8-import-order pep8-naming -commands = flake8 . +commands = + flake8 . [flake8] exclude = .tox,*.egg diff --git a/vectors/cryptography_vectors/__about__.py b/vectors/cryptography_vectors/__about__.py index ad61c1bc..bbacbf5d 100644 --- a/vectors/cryptography_vectors/__about__.py +++ b/vectors/cryptography_vectors/__about__.py @@ -22,7 +22,7 @@ __summary__ = "Test vectors for the cryptography package." __uri__ = "https://github.com/pyca/cryptography" -__version__ = "0.4.dev1" +__version__ = "0.5.dev1" __author__ = "The cryptography developers" __email__ = "cryptography-dev@python.org" diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/bad-encryption-oid.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/bad-encryption-oid.pem new file mode 100644 index 00000000..4ebcc12c --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/bad-encryption-oid.pem @@ -0,0 +1,17 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIICojAcBgoYYYYYYYYYYYYYMA4ECHK0M0+QuEL9AgIBIgSCAoDRq+KRY+0XP0tO +lwBTzViiXSXoyNnKAZKt5r5K/fGNntv22g/1s/ZNCetrqsJDC5eMUPPacz06jFq/ +Ipsep4/OgjQ9UAOzXNrWEoNyrHnWDo7usgD3CW0mKyqER4+wG0ZdVMbt3N+CJHGB +85jzRmQTfkdx1rSWeSx+XyswHn8ER4+hQ+omKWMVm7AFkjjmP/KmhUnLT98J8rhU +ArQoFPHz/6HVkypFccNaPPNg6IA4aS2A+TU9vJYOaXSVfFB2yf99hfYYzC+ukmuU +5Lun0cysK5s/5uSwDueUmDQKspnaNyiaMGDxvw8hilJc7vg0fGObfnbIpizhxJwq +gKBfR7Zt0Hv8OYi1He4MehfMGdbHskztF+yQ40LplBGXQrvAqpU4zShga1BoQ98T +0ekbBmqj7hg47VFsppXR7DKhx7G7rpMmdKbFhAZVCjae7rRGpUtD52cpFdPhMyAX +huhMkoczwUW8B/rM4272lkHo6Br0yk/TQfTEGkvryflNVu6lniPTV151WV5U1M3o +3G3a44eDyt7Ln+WSOpWtbPQMTrpKhur6WXgJvrpa/m02oOGdvOlDsoOCgavgQMWg +7xKKL7620pHl7p7f/8tlE8q6vLXVvyNtAOgt/JAr2rgvrHaZSzDE0DwgCjBXEm+7 +cVMVNkHod7bLQefVanVtWqPzbmr8f7gKeuGwWSG9oew/lN2hxcLEPJHAQlnLgx3P +0GdGjK9NvwA0EP2gYIeE4+UtSder7xQ7bVh25VB20R4TTIIs4aXXCVOoQPagnzaT +6JLgl8FrvdfjHwIvmSOO1YMNmILBq000Q8WDqyErBDs4hsvtO6VQ4LeqJj6gClX3 +qeJNaJFu +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/bad-oid-dsa-key.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/bad-oid-dsa-key.pem new file mode 100644 index 00000000..50d045be --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/bad-oid-dsa-key.pem @@ -0,0 +1,9 @@ +-----BEGIN PRIVATE KEY----- +MIIBTAIBADCCASwGByXXXXXXXXEwggEfAoGBAKoJMMwUWCUiHK/6KKwolBlqJ4M9 +5ewhJweRaJQgd3Si57I4sNNvGySZosJYUIPrAUMpJEGNhn+qIS3RBx1NzrJ4J5St +OTzAik1K2n9o1ug5pfzTS05ALYLLioy0D+wxkRv5vTYLA0yqy0xelHmSVzyekAmc +Gw8FlAyr5dLeSaFnAhUArcDoabNvCsATpoH99NSJnWmCBFECgYEAjGtFia+lOk0Q +SL/DRtHzhsp1UhzPct2qJRKGiA7hMgH/SIkLv8M9ebrK7HHnp3hQe9XxpmQi45QV +vgPnEUG6Mk9bkxMZKRgsiKn6QGKDYGbOvnS1xmkMfRARBsJAq369VOTjMB/Qhs5q +2ski+ycTorCIfLoTubxozlz/8kHNMkYEFwIVAKU1qOHQ2Rvq/IvuHZsqOo3jMRID +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/unenc-dsa-pkcs8.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/unenc-dsa-pkcs8.pem new file mode 100644 index 00000000..7b2099d3 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/unenc-dsa-pkcs8.pem @@ -0,0 +1,9 @@ +-----BEGIN PRIVATE KEY----- +MIIBTAIBADCCASwGByqGSM44BAEwggEfAoGBAKoJMMwUWCUiHK/6KKwolBlqJ4M9 +5ewhJweRaJQgd3Si57I4sNNvGySZosJYUIPrAUMpJEGNhn+qIS3RBx1NzrJ4J5St +OTzAik1K2n9o1ug5pfzTS05ALYLLioy0D+wxkRv5vTYLA0yqy0xelHmSVzyekAmc +Gw8FlAyr5dLeSaFnAhUArcDoabNvCsATpoH99NSJnWmCBFECgYEAjGtFia+lOk0Q +SL/DRtHzhsp1UhzPct2qJRKGiA7hMgH/SIkLv8M9ebrK7HHnp3hQe9XxpmQi45QV +vgPnEUG6Mk9bkxMZKRgsiKn6QGKDYGbOvnS1xmkMfRARBsJAq369VOTjMB/Qhs5q +2ski+ycTorCIfLoTubxozlz/8kHNMkYEFwIVAKU1qOHQ2Rvq/IvuHZsqOo3jMRID +-----END PRIVATE KEY----- |