diff options
53 files changed, 2014 insertions, 685 deletions
@@ -2,6 +2,7 @@ __pycache__/ _build/ .tox/ +.cache/ *.egg-info/ .coverage cffi-*.egg/ diff --git a/.travis/install.sh b/.travis/install.sh index fdd71907..4aa39799 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -5,24 +5,8 @@ set -x if [[ "${OPENSSL}" == "0.9.8" ]]; then sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ lucid main" -fi - -if [[ "${TOX_ENV}" == "pypy" ]]; then - sudo add-apt-repository -y ppa:pypy/ppa -fi - -sudo apt-get -y update - -if [[ "${OPENSSL}" == "0.9.8" ]]; then + sudo apt-get -y update sudo apt-get install -y --force-yes libssl-dev/lucid fi -if [[ "${TOX_ENV}" == "pypy" ]]; then - sudo apt-get install -y pypy - - # This is required because we need to get rid of the Travis installed PyPy - # or it'll take precedence over the PPA installed one. - sudo rm -rf /usr/local/pypy/bin -fi - pip install tox coveralls diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 9f63250f..b47f77e5 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -13,3 +13,11 @@ Extensive contribution guidelines are available in the repository at ``docs/contributing.rst``, or online at: https://cryptography.io/en/latest/contributing/ + +Security issues +--------------- + +To report a security issue, please follow the special `security reporting +guidelines`_, do not report them in the public issue tracker. + +.. _`security reporting guidelines`: https://cryptography.io/en/latest/security/ diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py index 391bed82..e9d88199 100644 --- a/cryptography/exceptions.py +++ b/cryptography/exceptions.py @@ -14,3 +14,19 @@ class UnsupportedAlgorithm(Exception): pass + + +class AlreadyFinalized(Exception): + pass + + +class AlreadyUpdated(Exception): + pass + + +class NotYetFinalized(Exception): + pass + + +class InvalidTag(Exception): + pass diff --git a/cryptography/hazmat/bindings/__init__.py b/cryptography/hazmat/bindings/__init__.py index eb828999..bd158198 100644 --- a/cryptography/hazmat/bindings/__init__.py +++ b/cryptography/hazmat/bindings/__init__.py @@ -14,7 +14,10 @@ from cryptography.hazmat.bindings import openssl -_default_backend = openssl.backend _ALL_BACKENDS = [ openssl.backend ] + + +def default_backend(): + return openssl.backend diff --git a/cryptography/hazmat/bindings/interfaces.py b/cryptography/hazmat/bindings/interfaces.py new file mode 100644 index 00000000..912476bb --- /dev/null +++ b/cryptography/hazmat/bindings/interfaces.py @@ -0,0 +1,66 @@ +# 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 abc + +import six + + +class CipherBackend(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def cipher_supported(self, cipher, mode): + """ + Return True if the given cipher and mode are supported. + """ + + @abc.abstractmethod + def register_cipher_adapter(self, cipher, mode, adapter): + """ + Register an adapter for a cipher and mode to a backend specific object. + """ + + @abc.abstractmethod + def create_symmetric_encryption_ctx(self, cipher, mode): + """ + Get a CipherContext that can be used for encryption. + """ + + @abc.abstractmethod + def create_symmetric_decryption_ctx(self, cipher, mode): + """ + Get a CipherContext that can be used for decryption. + """ + + +class HashBackend(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def hash_supported(self, algorithm): + """ + Return True if the hash algorithm is supported by this backend. + """ + + @abc.abstractmethod + def create_hash_ctx(self, algorithm): + """ + Create a HashContext for calculating a message digest. + """ + + +class HMACBackend(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def create_hmac_ctx(self, key, algorithm): + """ + Create a HashContext for calculating a message authentication code. + """ diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py index 0c3d22d5..6ab4dc26 100644 --- a/cryptography/hazmat/bindings/openssl/backend.py +++ b/cryptography/hazmat/bindings/openssl/backend.py @@ -18,16 +18,23 @@ import sys import cffi -from cryptography.exceptions import UnsupportedAlgorithm +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, InvalidTag +from cryptography.hazmat.bindings.interfaces import ( + CipherBackend, HashBackend, HMACBackend +) from cryptography.hazmat.primitives import interfaces from cryptography.hazmat.primitives.ciphers.algorithms import ( - AES, Blowfish, Camellia, CAST5, TripleDES, + AES, Blowfish, Camellia, CAST5, TripleDES, ARC4, ) from cryptography.hazmat.primitives.ciphers.modes import ( - CBC, CTR, ECB, OFB, CFB + CBC, CTR, ECB, OFB, CFB, GCM, ) +@utils.register_interface(CipherBackend) +@utils.register_interface(HashBackend) +@utils.register_interface(HMACBackend) class Backend(object): """ OpenSSL API wrapper. @@ -63,9 +70,8 @@ class Backend(object): def __init__(self): self._ensure_ffi_initialized() - self.ciphers = Ciphers(self) - self.hashes = Hashes(self) - self.hmacs = HMACs(self) + self._cipher_registry = {} + self._register_default_ciphers() @classmethod def _ensure_ffi_initialized(cls): @@ -123,6 +129,104 @@ class Backend(object): """ return self.ffi.string(self.lib.OPENSSL_VERSION_TEXT).decode("ascii") + def create_hmac_ctx(self, key, algorithm): + return _HMACContext(self, key, algorithm) + + def hash_supported(self, algorithm): + digest = self.lib.EVP_get_digestbyname(algorithm.name.encode("ascii")) + return digest != self.ffi.NULL + + def create_hash_ctx(self, algorithm): + return _HashContext(self, algorithm) + + def cipher_supported(self, cipher, mode): + try: + adapter = self._cipher_registry[type(cipher), type(mode)] + except KeyError: + return False + evp_cipher = adapter(self, cipher, mode) + return self.ffi.NULL != evp_cipher + + def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): + if (cipher_cls, mode_cls) in self._cipher_registry: + raise ValueError("Duplicate registration for: {0} {1}".format( + cipher_cls, mode_cls) + ) + 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], + ): + self.register_cipher_adapter( + cipher_cls, + mode_cls, + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + ) + for mode_cls in [CBC, CFB, OFB]: + self.register_cipher_adapter( + TripleDES, + mode_cls, + GetCipherByName("des-ede3-{mode.name}") + ) + for mode_cls in [CBC, CFB, OFB, ECB]: + self.register_cipher_adapter( + Blowfish, + mode_cls, + GetCipherByName("bf-{mode.name}") + ) + self.register_cipher_adapter( + CAST5, + ECB, + GetCipherByName("cast5-ecb") + ) + self.register_cipher_adapter( + ARC4, + type(None), + GetCipherByName("rc4") + ) + self.register_cipher_adapter( + AES, + GCM, + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + ) + + def create_symmetric_encryption_ctx(self, cipher, mode): + return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) + + def create_symmetric_decryption_ctx(self, cipher, mode): + return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + + def _handle_error(self, mode): + code = self.lib.ERR_get_error() + if not code and isinstance(mode, GCM): + raise InvalidTag + assert code != 0 + lib = self.lib.ERR_GET_LIB(code) + func = self.lib.ERR_GET_FUNC(code) + reason = self.lib.ERR_GET_REASON(code) + return self._handle_error_code(lib, func, reason) + + def _handle_error_code(self, lib, func, reason): + if lib == self.lib.ERR_LIB_EVP: + if func == self.lib.EVP_F_EVP_ENCRYPTFINAL_EX: + if reason == self.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH: + raise ValueError( + "The length of the provided data is not a multiple of " + "the block length" + ) + elif func == self.lib.EVP_F_EVP_DECRYPTFINAL_EX: + if reason == self.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH: + raise ValueError( + "The length of the provided data is not a multiple of " + "the block length" + ) + + raise SystemError( + "Unknown error code from OpenSSL, you should probably file a bug." + ) + class GetCipherByName(object): def __init__(self, fmt): @@ -133,18 +237,24 @@ class GetCipherByName(object): return backend.lib.EVP_get_cipherbyname(cipher_name.encode("ascii")) -@interfaces.register(interfaces.CipherContext) +@utils.register_interface(interfaces.CipherContext) +@utils.register_interface(interfaces.AEADCipherContext) +@utils.register_interface(interfaces.AEADEncryptionContext) class _CipherContext(object): _ENCRYPT = 1 _DECRYPT = 0 def __init__(self, backend, cipher, mode, operation): self._backend = backend + self._cipher = cipher + self._mode = mode + self._operation = operation + self._tag = None ctx = self._backend.lib.EVP_CIPHER_CTX_new() ctx = self._backend.ffi.gc(ctx, self._backend.lib.EVP_CIPHER_CTX_free) - registry = self._backend.ciphers._cipher_registry + registry = self._backend._cipher_registry try: adapter = registry[type(cipher), type(mode)] except KeyError: @@ -172,6 +282,26 @@ class _CipherContext(object): ctx, len(cipher.key) ) assert res != 0 + if isinstance(mode, GCM): + res = self._backend.lib.EVP_CIPHER_CTX_ctrl( + ctx, self._backend.lib.Cryptography_EVP_CTRL_GCM_SET_IVLEN, + len(iv_nonce), self._backend.ffi.NULL + ) + assert res != 0 + if operation == self._DECRYPT: + if not mode.tag: + raise ValueError("Authentication tag must be supplied " + "when decrypting") + res = self._backend.lib.EVP_CIPHER_CTX_ctrl( + ctx, self._backend.lib.Cryptography_EVP_CTRL_GCM_SET_TAG, + len(mode.tag), mode.tag + ) + assert res != 0 + else: + if mode.tag: + raise ValueError("Authentication tag must be None when " + "encrypting") + # pass key/iv res = self._backend.lib.EVP_CipherInit_ex(ctx, self._backend.ffi.NULL, self._backend.ffi.NULL, @@ -185,9 +315,8 @@ class _CipherContext(object): self._ctx = ctx def update(self, data): - block_size = self._backend.lib.EVP_CIPHER_CTX_block_size(self._ctx) buf = self._backend.ffi.new("unsigned char[]", - len(data) + block_size - 1) + len(data) + self._cipher.block_size - 1) outlen = self._backend.ffi.new("int *") res = self._backend.lib.EVP_CipherUpdate(self._ctx, buf, outlen, data, len(data)) @@ -195,156 +324,132 @@ class _CipherContext(object): return self._backend.ffi.buffer(buf)[:outlen[0]] def finalize(self): - block_size = self._backend.lib.EVP_CIPHER_CTX_block_size(self._ctx) - buf = self._backend.ffi.new("unsigned char[]", block_size) + buf = self._backend.ffi.new("unsigned char[]", self._cipher.block_size) outlen = self._backend.ffi.new("int *") res = self._backend.lib.EVP_CipherFinal_ex(self._ctx, buf, outlen) - assert res != 0 + if res == 0: + self._backend._handle_error(self._mode) + + if (isinstance(self._mode, GCM) and + self._operation == self._ENCRYPT): + block_byte_size = self._cipher.block_size // 8 + tag_buf = self._backend.ffi.new("unsigned char[]", block_byte_size) + res = self._backend.lib.EVP_CIPHER_CTX_ctrl( + self._ctx, self._backend.lib.Cryptography_EVP_CTRL_GCM_GET_TAG, + block_byte_size, tag_buf + ) + assert res != 0 + self._tag = self._backend.ffi.buffer(tag_buf)[:] + res = self._backend.lib.EVP_CIPHER_CTX_cleanup(self._ctx) assert res == 1 return self._backend.ffi.buffer(buf)[:outlen[0]] - -class Ciphers(object): - def __init__(self, backend): - super(Ciphers, self).__init__() - self._backend = backend - self._cipher_registry = {} - self._register_default_ciphers() - - def supported(self, cipher, mode): - try: - adapter = self._cipher_registry[type(cipher), type(mode)] - except KeyError: - return False - evp_cipher = adapter(self._backend, cipher, mode) - return self._backend.ffi.NULL != evp_cipher - - def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): - if (cipher_cls, mode_cls) in self._cipher_registry: - raise ValueError("Duplicate registration for: {0} {1}".format( - cipher_cls, mode_cls) - ) - 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], - ): - self.register_cipher_adapter( - cipher_cls, - mode_cls, - GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") - ) - for mode_cls in [CBC, CFB, OFB]: - self.register_cipher_adapter( - TripleDES, - mode_cls, - GetCipherByName("des-ede3-{mode.name}") - ) - for mode_cls in [CBC, CFB, OFB, ECB]: - self.register_cipher_adapter( - Blowfish, - mode_cls, - GetCipherByName("bf-{mode.name}") - ) - self.register_cipher_adapter( - CAST5, - ECB, - GetCipherByName("cast5-ecb") + def authenticate_additional_data(self, data): + outlen = self._backend.ffi.new("int *") + res = self._backend.lib.EVP_CipherUpdate( + self._ctx, self._backend.ffi.NULL, outlen, data, len(data) ) + assert res != 0 - def create_encrypt_ctx(self, cipher, mode): - return _CipherContext(self._backend, cipher, mode, - _CipherContext._ENCRYPT) + @property + def tag(self): + return self._tag - def create_decrypt_ctx(self, cipher, mode): - return _CipherContext(self._backend, cipher, mode, - _CipherContext._DECRYPT) +@utils.register_interface(interfaces.HashContext) +class _HashContext(object): + def __init__(self, backend, algorithm, ctx=None): + self.algorithm = algorithm -class Hashes(object): - def __init__(self, backend): - super(Hashes, self).__init__() self._backend = backend - def supported(self, hash_cls): - return (self._backend.ffi.NULL != - self._backend.lib.EVP_get_digestbyname( - hash_cls.name.encode("ascii"))) - - def create_ctx(self, hashobject): - ctx = self._backend.lib.EVP_MD_CTX_create() - ctx = self._backend.ffi.gc(ctx, self._backend.lib.EVP_MD_CTX_destroy) - evp_md = self._backend.lib.EVP_get_digestbyname( - hashobject.name.encode("ascii")) - assert evp_md != self._backend.ffi.NULL - res = self._backend.lib.EVP_DigestInit_ex(ctx, evp_md, - self._backend.ffi.NULL) + if ctx is None: + ctx = self._backend.lib.EVP_MD_CTX_create() + ctx = self._backend.ffi.gc(ctx, + self._backend.lib.EVP_MD_CTX_destroy) + evp_md = self._backend.lib.EVP_get_digestbyname( + algorithm.name.encode("ascii")) + assert evp_md != self._backend.ffi.NULL + res = self._backend.lib.EVP_DigestInit_ex(ctx, evp_md, + self._backend.ffi.NULL) + assert res != 0 + + self._ctx = ctx + + def copy(self): + copied_ctx = self._backend.lib.EVP_MD_CTX_create() + copied_ctx = self._backend.ffi.gc(copied_ctx, + self._backend.lib.EVP_MD_CTX_destroy) + res = self._backend.lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx) assert res != 0 - return ctx + return _HashContext(self._backend, self.algorithm, ctx=copied_ctx) - def update_ctx(self, ctx, data): - res = self._backend.lib.EVP_DigestUpdate(ctx, data, len(data)) + def update(self, data): + res = self._backend.lib.EVP_DigestUpdate(self._ctx, data, len(data)) assert res != 0 - def finalize_ctx(self, ctx, digest_size): - buf = self._backend.ffi.new("unsigned char[]", digest_size) - res = self._backend.lib.EVP_DigestFinal_ex(ctx, buf, + def finalize(self): + buf = self._backend.ffi.new("unsigned char[]", + self.algorithm.digest_size) + res = self._backend.lib.EVP_DigestFinal_ex(self._ctx, buf, self._backend.ffi.NULL) assert res != 0 - res = self._backend.lib.EVP_MD_CTX_cleanup(ctx) + res = self._backend.lib.EVP_MD_CTX_cleanup(self._ctx) assert res == 1 - return self._backend.ffi.buffer(buf)[:digest_size] - - def copy_ctx(self, ctx): - copied_ctx = self._backend.lib.EVP_MD_CTX_create() - copied_ctx = self._backend.ffi.gc(copied_ctx, - self._backend.lib.EVP_MD_CTX_destroy) - res = self._backend.lib.EVP_MD_CTX_copy_ex(copied_ctx, ctx) - assert res != 0 - return copied_ctx + return self._backend.ffi.buffer(buf)[:] -class HMACs(object): - def __init__(self, backend): - super(HMACs, self).__init__() +@utils.register_interface(interfaces.HashContext) +class _HMACContext(object): + def __init__(self, backend, key, algorithm, ctx=None): + self.algorithm = algorithm self._backend = backend - def create_ctx(self, key, hash_cls): - ctx = self._backend.ffi.new("HMAC_CTX *") - self._backend.lib.HMAC_CTX_init(ctx) - ctx = self._backend.ffi.gc(ctx, self._backend.lib.HMAC_CTX_cleanup) - evp_md = self._backend.lib.EVP_get_digestbyname( - hash_cls.name.encode('ascii')) - assert evp_md != self._backend.ffi.NULL - res = self._backend.lib.Cryptography_HMAC_Init_ex( - ctx, key, len(key), evp_md, self._backend.ffi.NULL - ) - assert res != 0 - return ctx + if ctx is None: + ctx = self._backend.ffi.new("HMAC_CTX *") + self._backend.lib.HMAC_CTX_init(ctx) + ctx = self._backend.ffi.gc(ctx, self._backend.lib.HMAC_CTX_cleanup) + evp_md = self._backend.lib.EVP_get_digestbyname( + algorithm.name.encode('ascii')) + assert evp_md != self._backend.ffi.NULL + res = self._backend.lib.Cryptography_HMAC_Init_ex( + ctx, key, len(key), evp_md, self._backend.ffi.NULL + ) + assert res != 0 + + self._ctx = ctx + self._key = key - def update_ctx(self, ctx, data): - res = self._backend.lib.Cryptography_HMAC_Update(ctx, data, len(data)) + def copy(self): + copied_ctx = self._backend.ffi.new("HMAC_CTX *") + self._backend.lib.HMAC_CTX_init(copied_ctx) + copied_ctx = self._backend.ffi.gc( + copied_ctx, self._backend.lib.HMAC_CTX_cleanup + ) + res = self._backend.lib.Cryptography_HMAC_CTX_copy( + copied_ctx, self._ctx + ) assert res != 0 + return _HMACContext( + self._backend, self._key, self.algorithm, ctx=copied_ctx + ) - def finalize_ctx(self, ctx, digest_size): - buf = self._backend.ffi.new("unsigned char[]", digest_size) - buflen = self._backend.ffi.new("unsigned int *", digest_size) - res = self._backend.lib.Cryptography_HMAC_Final(ctx, buf, buflen) + def update(self, data): + res = self._backend.lib.Cryptography_HMAC_Update( + self._ctx, data, len(data) + ) assert res != 0 - self._backend.lib.HMAC_CTX_cleanup(ctx) - return self._backend.ffi.buffer(buf)[:digest_size] - def copy_ctx(self, ctx): - copied_ctx = self._backend.ffi.new("HMAC_CTX *") - self._backend.lib.HMAC_CTX_init(copied_ctx) - copied_ctx = self._backend.ffi.gc(copied_ctx, - self._backend.lib.HMAC_CTX_cleanup) - res = self._backend.lib.Cryptography_HMAC_CTX_copy(copied_ctx, ctx) + def finalize(self): + buf = self._backend.ffi.new("unsigned char[]", + self.algorithm.digest_size) + buflen = self._backend.ffi.new("unsigned int *", + self.algorithm.digest_size) + res = self._backend.lib.Cryptography_HMAC_Final(self._ctx, buf, buflen) assert res != 0 - return copied_ctx + self._backend.lib.HMAC_CTX_cleanup(self._ctx) + return self._backend.ffi.buffer(buf)[:] backend = Backend() diff --git a/cryptography/hazmat/bindings/openssl/bignum.py b/cryptography/hazmat/bindings/openssl/bignum.py index fcfadff1..1b0fe5ab 100644 --- a/cryptography/hazmat/bindings/openssl/bignum.py +++ b/cryptography/hazmat/bindings/openssl/bignum.py @@ -28,6 +28,9 @@ int BN_set_word(BIGNUM *, BN_ULONG); char *BN_bn2hex(const BIGNUM *); int BN_hex2bn(BIGNUM **, const char *); +int BN_dec2bn(BIGNUM **, const char *); + +int BN_num_bits(const BIGNUM *); """ MACROS = """ diff --git a/cryptography/hazmat/bindings/openssl/engine.py b/cryptography/hazmat/bindings/openssl/engine.py index b76befce..1f377665 100644 --- a/cryptography/hazmat/bindings/openssl/engine.py +++ b/cryptography/hazmat/bindings/openssl/engine.py @@ -36,6 +36,16 @@ void ENGINE_load_builtin_engines(); int ENGINE_ctrl_cmd_string(ENGINE *, const char *, const char *, int); int ENGINE_set_default(ENGINE *, unsigned int); int ENGINE_register_complete(ENGINE *); + +int ENGINE_set_default_RSA(ENGINE *); +int ENGINE_set_default_string(ENGINE *, const char *); +int ENGINE_set_default_DSA(ENGINE *); +int ENGINE_set_default_ECDH(ENGINE *); +int ENGINE_set_default_ECDSA(ENGINE *); +int ENGINE_set_default_DH(ENGINE *); +int ENGINE_set_default_RAND(ENGINE *); +int ENGINE_set_default_ciphers(ENGINE *); +int ENGINE_set_default_digests(ENGINE *); """ MACROS = """ diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py index 6a36dee0..f31c2405 100644 --- a/cryptography/hazmat/bindings/openssl/err.py +++ b/cryptography/hazmat/bindings/openssl/err.py @@ -21,6 +21,20 @@ struct ERR_string_data_st { const char *string; }; typedef struct ERR_string_data_st ERR_STRING_DATA; + +static const int ERR_LIB_EVP; +static const int ERR_LIB_PEM; + +static const int EVP_F_EVP_ENCRYPTFINAL_EX; +static const int EVP_F_EVP_DECRYPTFINAL_EX; + +static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH; + +static const int PEM_F_PEM_READ_BIO_PRIVATEKEY; +static const int PEM_F_D2I_PKCS8PRIVATEKEY_BIO; + +static const int PEM_R_BAD_PASSWORD_READ; +static const int ASN1_R_BAD_PASSWORD_READ; """ FUNCTIONS = """ diff --git a/cryptography/hazmat/bindings/openssl/evp.py b/cryptography/hazmat/bindings/openssl/evp.py index da54f89d..8cb44610 100644 --- a/cryptography/hazmat/bindings/openssl/evp.py +++ b/cryptography/hazmat/bindings/openssl/evp.py @@ -16,10 +16,13 @@ INCLUDES = """ """ TYPES = """ +typedef ... EVP_CIPHER; typedef struct { + const EVP_CIPHER *cipher; + ENGINE *engine; + int encrypt; ...; } EVP_CIPHER_CTX; -typedef ... EVP_CIPHER; typedef ... EVP_MD; typedef struct env_md_ctx_st EVP_MD_CTX; diff --git a/cryptography/hazmat/bindings/openssl/pem.py b/cryptography/hazmat/bindings/openssl/pem.py index 00f0dc36..cef7839f 100644 --- a/cryptography/hazmat/bindings/openssl/pem.py +++ b/cryptography/hazmat/bindings/openssl/pem.py @@ -29,6 +29,15 @@ int PEM_write_bio_PrivateKey(BIO *, EVP_PKEY *, const EVP_CIPHER *, EVP_PKEY *PEM_read_bio_PrivateKey(BIO *, EVP_PKEY **, pem_password_cb *, void *); +int PEM_write_bio_PKCS8PrivateKey(BIO *, EVP_PKEY *, const EVP_CIPHER *, + char *, int, pem_password_cb *, void *); + +int i2d_PKCS8PrivateKey_bio(BIO *, EVP_PKEY *, const EVP_CIPHER *, + char *, int, pem_password_cb *, void *); + +EVP_PKEY *d2i_PKCS8PrivateKey_bio(BIO *, EVP_PKEY **, pem_password_cb *, + void *); + int PEM_write_bio_X509_REQ(BIO *, X509_REQ *); X509_REQ *PEM_read_bio_X509_REQ(BIO *, X509_REQ **, pem_password_cb *, void *); diff --git a/cryptography/hazmat/bindings/openssl/rsa.py b/cryptography/hazmat/bindings/openssl/rsa.py index 21ed5d67..ad0d37b4 100644 --- a/cryptography/hazmat/bindings/openssl/rsa.py +++ b/cryptography/hazmat/bindings/openssl/rsa.py @@ -16,15 +16,40 @@ INCLUDES = """ """ TYPES = """ -typedef ... RSA; +typedef struct rsa_st { + BIGNUM *n; + BIGNUM *e; + BIGNUM *d; + BIGNUM *p; + BIGNUM *q; + BIGNUM *dmp1; + BIGNUM *dmq1; + BIGNUM *iqmp; + ...; +} RSA; typedef ... BN_GENCB; +static const int RSA_PKCS1_PADDING; +static const int RSA_SSLV23_PADDING; +static const int RSA_NO_PADDING; +static const int RSA_PKCS1_OAEP_PADDING; +static const int RSA_X931_PADDING; """ FUNCTIONS = """ RSA *RSA_new(); void RSA_free(RSA *); +int RSA_size(const RSA *); int RSA_generate_key_ex(RSA *, int, BIGNUM *, BN_GENCB *); int RSA_check_key(const RSA *); +RSA *RSAPublicKey_dup(RSA *); +int RSA_public_encrypt(int, const unsigned char *, unsigned char *, + RSA *, int); +int RSA_private_encrypt(int, const unsigned char *, unsigned char *, + RSA *, int); +int RSA_public_decrypt(int, const unsigned char *, unsigned char *, + RSA *, int); +int RSA_private_decrypt(int, const unsigned char *, unsigned char *, + RSA *, int); """ MACROS = """ diff --git a/cryptography/hazmat/bindings/openssl/ssl.py b/cryptography/hazmat/bindings/openssl/ssl.py index 58a64f0b..04611309 100644 --- a/cryptography/hazmat/bindings/openssl/ssl.py +++ b/cryptography/hazmat/bindings/openssl/ssl.py @@ -16,13 +16,200 @@ INCLUDES = """ """ TYPES = """ +static const int SSL_FILETYPE_PEM; +static const int SSL_FILETYPE_ASN1; +static const int SSL_ERROR_NONE; +static const int SSL_ERROR_ZERO_RETURN; +static const int SSL_ERROR_WANT_READ; +static const int SSL_ERROR_WANT_WRITE; +static const int SSL_ERROR_WANT_X509_LOOKUP; +static const int SSL_ERROR_SYSCALL; +static const int SSL_ERROR_SSL; +static const int SSL_SENT_SHUTDOWN; +static const int SSL_RECEIVED_SHUTDOWN; +static const int SSL_OP_NO_SSLv2; +static const int SSL_OP_NO_SSLv3; +static const int SSL_OP_NO_TLSv1; +static const int SSL_OP_SINGLE_DH_USE; +static const int SSL_OP_EPHEMERAL_RSA; +static const int SSL_OP_MICROSOFT_SESS_ID_BUG; +static const int SSL_OP_NETSCAPE_CHALLENGE_BUG; +static const int SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; +static const int SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG; +static const int SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER; +static const int SSL_OP_MSIE_SSLV2_RSA_PADDING; +static const int SSL_OP_SSLEAY_080_CLIENT_DH_BUG; +static const int SSL_OP_TLS_D5_BUG; +static const int SSL_OP_TLS_BLOCK_PADDING_BUG; +static const int SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; +static const int SSL_OP_CIPHER_SERVER_PREFERENCE; +static const int SSL_OP_TLS_ROLLBACK_BUG; +static const int SSL_OP_PKCS1_CHECK_1; +static const int SSL_OP_PKCS1_CHECK_2; +static const int SSL_OP_NETSCAPE_CA_DN_BUG; +static const int SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG; +static const int SSL_OP_NO_QUERY_MTU; +static const int SSL_OP_COOKIE_EXCHANGE; +static const int SSL_OP_NO_TICKET; +static const int SSL_OP_ALL; +static const int SSL_VERIFY_PEER; +static const int SSL_VERIFY_FAIL_IF_NO_PEER_CERT; +static const int SSL_VERIFY_CLIENT_ONCE; +static const int SSL_VERIFY_NONE; +static const int SSL_SESS_CACHE_OFF; +static const int SSL_SESS_CACHE_CLIENT; +static const int SSL_SESS_CACHE_SERVER; +static const int SSL_SESS_CACHE_BOTH; +static const int SSL_SESS_CACHE_NO_AUTO_CLEAR; +static const int SSL_SESS_CACHE_NO_INTERNAL_LOOKUP; +static const int SSL_SESS_CACHE_NO_INTERNAL_STORE; +static const int SSL_SESS_CACHE_NO_INTERNAL; +static const int SSL_ST_CONNECT; +static const int SSL_ST_ACCEPT; +static const int SSL_ST_MASK; +static const int SSL_ST_INIT; +static const int SSL_ST_BEFORE; +static const int SSL_ST_OK; +static const int SSL_ST_RENEGOTIATE; +static const int SSL_CB_LOOP; +static const int SSL_CB_EXIT; +static const int SSL_CB_READ; +static const int SSL_CB_WRITE; +static const int SSL_CB_ALERT; +static const int SSL_CB_READ_ALERT; +static const int SSL_CB_WRITE_ALERT; +static const int SSL_CB_ACCEPT_LOOP; +static const int SSL_CB_ACCEPT_EXIT; +static const int SSL_CB_CONNECT_LOOP; +static const int SSL_CB_CONNECT_EXIT; +static const int SSL_CB_HANDSHAKE_START; +static const int SSL_CB_HANDSHAKE_DONE; +static const int SSL_MODE_ENABLE_PARTIAL_WRITE; +static const int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; +static const int SSL_MODE_AUTO_RETRY; +static const int SSL3_RANDOM_SIZE; +typedef ... X509_STORE_CTX; +static const int X509_V_OK; +typedef ... SSL_METHOD; +typedef ... SSL_CTX; + +typedef struct { + int master_key_length; + unsigned char master_key[...]; + ...; +} SSL_SESSION; + +typedef struct { + unsigned char server_random[...]; + unsigned char client_random[...]; + ...; +} SSL3_STATE; + +typedef struct { + SSL3_STATE *s3; + SSL_SESSION *session; + ...; +} SSL; + +static const int TLSEXT_NAMETYPE_host_name; """ FUNCTIONS = """ void SSL_load_error_strings(); + +int SSL_library_init(); + +/* SSL */ +SSL_CTX *SSL_set_SSL_CTX(SSL *, SSL_CTX *); +SSL_SESSION *SSL_get1_session(SSL *); +int SSL_set_session(SSL *, SSL_SESSION *); +int SSL_get_verify_mode(const SSL *); +void SSL_set_verify_depth(SSL *, int); +int SSL_get_verify_depth(const SSL *); +SSL *SSL_new(SSL_CTX *); +void SSL_free(SSL *); +int SSL_set_fd(SSL *, int); +void SSL_set_bio(SSL *, BIO *, BIO *); +void SSL_set_connect_state(SSL *); +void SSL_set_accept_state(SSL *); +void SSL_set_shutdown(SSL *, int); +int SSL_get_shutdown(const SSL *); +int SSL_pending(const SSL *); +int SSL_write(SSL *, const void *, int); +int SSL_read(SSL *, void *, int); +X509 *SSL_get_peer_certificate(const SSL *); +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); + +/* context */ +void SSL_CTX_free(SSL_CTX *); +long SSL_CTX_set_timeout(SSL_CTX *, long); +int SSL_CTX_set_default_verify_paths(SSL_CTX *); +void SSL_CTX_set_verify_depth(SSL_CTX *, int); +int SSL_CTX_get_verify_mode(const SSL_CTX *); +int SSL_CTX_get_verify_depth(const SSL_CTX *); +int SSL_CTX_set_cipher_list(SSL_CTX *, const char *); +int SSL_CTX_load_verify_locations(SSL_CTX *, const char *, const char *); +void SSL_CTX_set_default_passwd_cb(SSL_CTX *, pem_password_cb *); +void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *, void *); +int SSL_CTX_use_certificate(SSL_CTX *, X509 *); +int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int); +int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *); +int SSL_CTX_use_PrivateKey(SSL_CTX *, EVP_PKEY *); +int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int); +void SSL_CTX_set_cert_store(SSL_CTX *, X509_STORE *); +X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *); +int SSL_CTX_add_client_CA(SSL_CTX *, X509 *); + +/* X509_STORE_CTX */ +int X509_STORE_CTX_get_error(X509_STORE_CTX *); +void X509_STORE_CTX_set_error(X509_STORE_CTX *, int); +int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *); +X509 *X509_STORE_CTX_get_current_cert(X509_STORE_CTX *); + +/* SSL_SESSION */ +void SSL_SESSION_free(SSL_SESSION *); """ -MACROS = """ +MACROS = MACROS = """ +long SSL_set_mode(SSL *, long); +long SSL_get_mode(SSL *); + +long SSL_set_options(SSL *, long); +long SSL_get_options(SSL *); + +int SSL_want_read(const SSL *); +int SSL_want_write(const SSL *); + +int SSL_total_renegotiations(const SSL *); + +long SSL_CTX_set_options(SSL_CTX *, long); +long SSL_CTX_get_options(SSL_CTX *); +long SSL_CTX_set_mode(SSL_CTX *, long); +long SSL_CTX_get_mode(SSL_CTX *); +long SSL_CTX_set_session_cache_mode(SSL_CTX *, long); +long SSL_CTX_get_session_cache_mode(SSL_CTX *); +long SSL_CTX_set_tmp_dh(SSL_CTX *, DH *); +long SSL_CTX_add_extra_chain_cert(SSL_CTX *, X509 *); + +/*- These aren't macros these functions are all const X on openssl > 1.0.x -*/ + +/* methods */ +const SSL_METHOD *SSLv3_method(); +const SSL_METHOD *SSLv3_server_method(); +const SSL_METHOD *SSLv3_client_method(); +const SSL_METHOD *TLSv1_method(); +const SSL_METHOD *TLSv1_server_method(); +const SSL_METHOD *TLSv1_client_method(); +const SSL_METHOD *SSLv23_method(); +const SSL_METHOD *SSLv23_server_method(); +const SSL_METHOD *SSLv23_client_method(); + +/*- These aren't macros these arguments are all const X on openssl > 1.0.x -*/ +SSL_CTX *SSL_CTX_new(const SSL_METHOD *); +long SSL_CTX_get_timeout(const SSL_CTX *); """ CUSTOMIZATIONS = """ diff --git a/cryptography/hazmat/primitives/ciphers/algorithms.py b/cryptography/hazmat/primitives/ciphers/algorithms.py index 8046bd26..a206b273 100644 --- a/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -13,105 +13,101 @@ from __future__ import absolute_import, division, print_function +from cryptography import utils +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( + len(key) * 8, algorithm.name + )) + return key + + +@utils.register_interface(interfaces.CipherAlgorithm) class AES(object): name = "AES" block_size = 128 key_sizes = frozenset([128, 192, 256]) def __init__(self, key): - super(AES, self).__init__() - self.key = key - - # Verify that the key size matches the expected key size - if self.key_size not in self.key_sizes: - raise ValueError("Invalid key size ({0}) for {1}".format( - self.key_size, self.name - )) + self.key = _verify_key_size(self, key) @property def key_size(self): return len(self.key) * 8 +@utils.register_interface(interfaces.CipherAlgorithm) class Camellia(object): name = "camellia" block_size = 128 key_sizes = frozenset([128, 192, 256]) def __init__(self, key): - super(Camellia, self).__init__() - self.key = key - - # Verify that the key size matches the expected key size - if self.key_size not in self.key_sizes: - raise ValueError("Invalid key size ({0}) for {1}".format( - self.key_size, self.name - )) + self.key = _verify_key_size(self, key) @property def key_size(self): return len(self.key) * 8 +@utils.register_interface(interfaces.CipherAlgorithm) class TripleDES(object): name = "3DES" block_size = 64 key_sizes = frozenset([64, 128, 192]) def __init__(self, key): - super(TripleDES, self).__init__() if len(key) == 8: key += key + key elif len(key) == 16: key += key[:8] - self.key = key - - # Verify that the key size matches the expected key size - if self.key_size not in self.key_sizes: - raise ValueError("Invalid key size ({0}) for {1}".format( - self.key_size, self.name - )) + self.key = _verify_key_size(self, key) @property def key_size(self): return len(self.key) * 8 +@utils.register_interface(interfaces.CipherAlgorithm) class Blowfish(object): name = "Blowfish" block_size = 64 key_sizes = frozenset(range(32, 449, 8)) def __init__(self, key): - super(Blowfish, self).__init__() - self.key = key - - # Verify that the key size matches the expected key size - if self.key_size not in self.key_sizes: - raise ValueError("Invalid key size ({0}) for {1}".format( - self.key_size, self.name - )) + self.key = _verify_key_size(self, key) @property def key_size(self): return len(self.key) * 8 +@utils.register_interface(interfaces.CipherAlgorithm) class CAST5(object): name = "CAST5" block_size = 64 - key_sizes = frozenset([40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128]) + key_sizes = frozenset(range(40, 129, 8)) + + def __init__(self, key): + self.key = _verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 + + +@utils.register_interface(interfaces.CipherAlgorithm) +class ARC4(object): + name = "RC4" + block_size = 1 + key_sizes = frozenset([40, 56, 64, 80, 128, 192, 256]) def __init__(self, key): - super(CAST5, self).__init__() - self.key = key - - # Verify that the key size matches the expected key size - if self.key_size not in self.key_sizes: - raise ValueError("Invalid key size ({0}) for {1}".format( - self.key_size, self.name - )) + self.key = _verify_key_size(self, key) @property def key_size(self): diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 1599308c..b8615cb9 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -13,46 +13,97 @@ from __future__ import absolute_import, division, print_function +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, NotYetFinalized, AlreadyUpdated, +) from cryptography.hazmat.primitives import interfaces class Cipher(object): - def __init__(self, algorithm, mode, backend=None): - super(Cipher, self).__init__() - - if backend is None: - from cryptography.hazmat.bindings import ( - _default_backend as backend, - ) + def __init__(self, algorithm, mode, backend): + if not isinstance(algorithm, interfaces.CipherAlgorithm): + raise TypeError("Expected interface of interfaces.CipherAlgorithm") self.algorithm = algorithm self.mode = mode self._backend = backend def encryptor(self): - return _CipherContext( - self._backend.ciphers.create_encrypt_ctx(self.algorithm, - self.mode)) + ctx = self._backend.create_symmetric_encryption_ctx( + self.algorithm, self.mode + ) + return self._wrap_ctx(ctx, True) def decryptor(self): - return _CipherContext( - self._backend.ciphers.create_decrypt_ctx(self.algorithm, - self.mode)) + ctx = self._backend.create_symmetric_decryption_ctx( + self.algorithm, self.mode + ) + return self._wrap_ctx(ctx, False) + + def _wrap_ctx(self, ctx, encrypt): + if isinstance(self.mode, interfaces.ModeWithAuthenticationTag): + if encrypt: + return _AEADEncryptionContext(ctx) + else: + return _AEADCipherContext(ctx) + else: + return _CipherContext(ctx) -@interfaces.register(interfaces.CipherContext) +@utils.register_interface(interfaces.CipherContext) class _CipherContext(object): def __init__(self, ctx): self._ctx = ctx def update(self, data): if self._ctx is None: - raise ValueError("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") + data = self._ctx.finalize() + self._ctx = None + return data + + +@utils.register_interface(interfaces.AEADCipherContext) +@utils.register_interface(interfaces.CipherContext) +class _AEADCipherContext(object): + def __init__(self, ctx): + self._ctx = ctx + self._tag = None + self._updated = False + + def update(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized") + self._updated = True return self._ctx.update(data) def finalize(self): if self._ctx is None: - raise ValueError("Context was already finalized") + raise AlreadyFinalized("Context was already finalized") data = self._ctx.finalize() + self._tag = self._ctx.tag self._ctx = None return data + + def authenticate_additional_data(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized") + if self._updated: + raise AlreadyUpdated("Update has been called on this context") + self._ctx.authenticate_additional_data(data) + + +@utils.register_interface(interfaces.AEADEncryptionContext) +class _AEADEncryptionContext(_AEADCipherContext): + @property + def tag(self): + if self._ctx is not None: + raise NotYetFinalized("You must finalize encryption before " + "getting the tag") + return self._tag diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index e54872a6..e1c70185 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -13,49 +13,57 @@ from __future__ import absolute_import, division, print_function +from cryptography import utils from cryptography.hazmat.primitives import interfaces -@interfaces.register(interfaces.Mode) -@interfaces.register(interfaces.ModeWithInitializationVector) +@utils.register_interface(interfaces.Mode) +@utils.register_interface(interfaces.ModeWithInitializationVector) class CBC(object): name = "CBC" def __init__(self, initialization_vector): - super(CBC, self).__init__() self.initialization_vector = initialization_vector -@interfaces.register(interfaces.Mode) +@utils.register_interface(interfaces.Mode) class ECB(object): name = "ECB" -@interfaces.register(interfaces.Mode) -@interfaces.register(interfaces.ModeWithInitializationVector) +@utils.register_interface(interfaces.Mode) +@utils.register_interface(interfaces.ModeWithInitializationVector) class OFB(object): name = "OFB" def __init__(self, initialization_vector): - super(OFB, self).__init__() self.initialization_vector = initialization_vector -@interfaces.register(interfaces.Mode) -@interfaces.register(interfaces.ModeWithInitializationVector) +@utils.register_interface(interfaces.Mode) +@utils.register_interface(interfaces.ModeWithInitializationVector) class CFB(object): name = "CFB" def __init__(self, initialization_vector): - super(CFB, self).__init__() self.initialization_vector = initialization_vector -@interfaces.register(interfaces.Mode) -@interfaces.register(interfaces.ModeWithNonce) +@utils.register_interface(interfaces.Mode) +@utils.register_interface(interfaces.ModeWithNonce) class CTR(object): name = "CTR" def __init__(self, nonce): - super(CTR, self).__init__() self.nonce = nonce + + +@utils.register_interface(interfaces.Mode) +@utils.register_interface(interfaces.ModeWithInitializationVector) +@utils.register_interface(interfaces.ModeWithAuthenticationTag) +class GCM(object): + name = "GCM" + + def __init__(self, initialization_vector, tag=None): + self.initialization_vector = initialization_vector + self.tag = tag diff --git a/cryptography/hazmat/primitives/hashes.py b/cryptography/hazmat/primitives/hashes.py index bdad5e16..bee188b3 100644 --- a/cryptography/hazmat/primitives/hashes.py +++ b/cryptography/hazmat/primitives/hashes.py @@ -15,91 +15,97 @@ from __future__ import absolute_import, division, print_function import six +from cryptography import utils +from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import interfaces -@interfaces.register(interfaces.HashContext) +@utils.register_interface(interfaces.HashContext) class Hash(object): - def __init__(self, algorithm, backend=None, ctx=None): + def __init__(self, algorithm, backend, ctx=None): if not isinstance(algorithm, interfaces.HashAlgorithm): raise TypeError("Expected instance of interfaces.HashAlgorithm.") self.algorithm = algorithm - if backend is None: - from cryptography.hazmat.bindings import _default_backend - backend = _default_backend - self._backend = backend if ctx is None: - self._ctx = self._backend.hashes.create_ctx(self.algorithm) + self._ctx = self._backend.create_hash_ctx(self.algorithm) else: - self._ctx = None + self._ctx = ctx 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") - self._backend.hashes.update_ctx(self._ctx, data) + self._ctx.update(data) def copy(self): - return self.__class__(self.algorithm, backend=self._backend, - ctx=self._backend.hashes.copy_ctx(self._ctx)) + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized") + return Hash( + self.algorithm, backend=self._backend, ctx=self._ctx.copy() + ) def finalize(self): - return self._backend.hashes.finalize_ctx(self._ctx, - self.algorithm.digest_size) + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized") + digest = self._ctx.finalize() + self._ctx = None + return digest -@interfaces.register(interfaces.HashAlgorithm) +@utils.register_interface(interfaces.HashAlgorithm) class SHA1(object): name = "sha1" digest_size = 20 block_size = 64 -@interfaces.register(interfaces.HashAlgorithm) +@utils.register_interface(interfaces.HashAlgorithm) class SHA224(object): name = "sha224" digest_size = 28 block_size = 64 -@interfaces.register(interfaces.HashAlgorithm) +@utils.register_interface(interfaces.HashAlgorithm) class SHA256(object): name = "sha256" digest_size = 32 block_size = 64 -@interfaces.register(interfaces.HashAlgorithm) +@utils.register_interface(interfaces.HashAlgorithm) class SHA384(object): name = "sha384" digest_size = 48 block_size = 128 -@interfaces.register(interfaces.HashAlgorithm) +@utils.register_interface(interfaces.HashAlgorithm) class SHA512(object): name = "sha512" digest_size = 64 block_size = 128 -@interfaces.register(interfaces.HashAlgorithm) +@utils.register_interface(interfaces.HashAlgorithm) class RIPEMD160(object): name = "ripemd160" digest_size = 20 block_size = 64 -@interfaces.register(interfaces.HashAlgorithm) +@utils.register_interface(interfaces.HashAlgorithm) class Whirlpool(object): name = "whirlpool" digest_size = 64 block_size = 64 -@interfaces.register(interfaces.HashAlgorithm) +@utils.register_interface(interfaces.HashAlgorithm) class MD5(object): name = "md5" digest_size = 16 diff --git a/cryptography/hazmat/primitives/hmac.py b/cryptography/hazmat/primitives/hmac.py index 1457ed78..618bccc5 100644 --- a/cryptography/hazmat/primitives/hmac.py +++ b/cryptography/hazmat/primitives/hmac.py @@ -15,37 +15,45 @@ from __future__ import absolute_import, division, print_function import six +from cryptography import utils +from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import interfaces -@interfaces.register(interfaces.HashContext) +@utils.register_interface(interfaces.HashContext) class HMAC(object): - def __init__(self, key, algorithm, ctx=None, backend=None): - super(HMAC, self).__init__() + def __init__(self, key, algorithm, backend, ctx=None): if not isinstance(algorithm, interfaces.HashAlgorithm): raise TypeError("Expected instance of interfaces.HashAlgorithm.") self.algorithm = algorithm - if backend is None: - from cryptography.hazmat.bindings import _default_backend - backend = _default_backend - self._backend = backend self._key = key if ctx is None: - self._ctx = self._backend.hmacs.create_ctx(key, self.algorithm) + self._ctx = self._backend.create_hmac_ctx(key, self.algorithm) else: self._ctx = ctx 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") - self._backend.hmacs.update_ctx(self._ctx, msg) + self._ctx.update(msg) def copy(self): - return self.__class__(self._key, self.algorithm, backend=self._backend, - ctx=self._backend.hmacs.copy_ctx(self._ctx)) + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized") + return HMAC( + self._key, + self.algorithm, + backend=self._backend, + ctx=self._ctx.copy() + ) def finalize(self): - return self._backend.hmacs.finalize_ctx(self._ctx, - self.algorithm.digest_size) + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized") + digest = self._ctx.finalize() + self._ctx = None + return digest diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 67dbe6fa..e3f4f586 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -18,11 +18,18 @@ import abc import six -def register(iface): - def register_decorator(klass): - iface.register(klass) - return klass - return register_decorator +class CipherAlgorithm(six.with_metaclass(abc.ABCMeta)): + @abc.abstractproperty + def name(self): + """ + A string naming this mode. (e.g. AES, Camellia) + """ + + @abc.abstractproperty + def key_size(self): + """ + The size of the key being used as an integer in bits. (e.g. 128, 256) + """ class Mode(six.with_metaclass(abc.ABCMeta)): @@ -49,6 +56,14 @@ class ModeWithNonce(six.with_metaclass(abc.ABCMeta)): """ +class ModeWithAuthenticationTag(six.with_metaclass(abc.ABCMeta)): + @abc.abstractproperty + def tag(self): + """ + The value of the tag supplied to the constructor of this mode. + """ + + class CipherContext(six.with_metaclass(abc.ABCMeta)): @abc.abstractmethod def update(self, data): @@ -63,6 +78,22 @@ class CipherContext(six.with_metaclass(abc.ABCMeta)): """ +class AEADCipherContext(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def authenticate_additional_data(self, data): + """ + authenticate_additional_data takes bytes and returns nothing. + """ + + +class AEADEncryptionContext(six.with_metaclass(abc.ABCMeta)): + @abc.abstractproperty + def tag(self): + """ + Returns tag bytes after finalizing encryption. + """ + + class PaddingContext(six.with_metaclass(abc.ABCMeta)): @abc.abstractmethod def update(self, data): diff --git a/cryptography/hazmat/primitives/padding.py b/cryptography/hazmat/primitives/padding.py index 6849d149..cfa90db9 100644 --- a/cryptography/hazmat/primitives/padding.py +++ b/cryptography/hazmat/primitives/padding.py @@ -15,6 +15,7 @@ import cffi import six +from cryptography import utils from cryptography.hazmat.primitives import interfaces @@ -64,7 +65,6 @@ bool Cryptography_check_pkcs7_padding(const uint8_t *data, uint8_t block_len) { class PKCS7(object): def __init__(self, block_size): - super(PKCS7, self).__init__() if not (0 <= block_size < 256): raise ValueError("block_size must be in range(0, 256)") @@ -80,10 +80,9 @@ class PKCS7(object): return _PKCS7UnpaddingContext(self.block_size) -@interfaces.register(interfaces.PaddingContext) +@utils.register_interface(interfaces.PaddingContext) class _PKCS7PaddingContext(object): def __init__(self, block_size): - super(_PKCS7PaddingContext, self).__init__() self.block_size = block_size # TODO: O(n ** 2) complexity for repeated concatentation, we should use # zero-buffer (#193) @@ -115,10 +114,9 @@ class _PKCS7PaddingContext(object): return result -@interfaces.register(interfaces.PaddingContext) +@utils.register_interface(interfaces.PaddingContext) class _PKCS7UnpaddingContext(object): def __init__(self, block_size): - super(_PKCS7UnpaddingContext, self).__init__() self.block_size = block_size # TODO: O(n ** 2) complexity for repeated concatentation, we should use # zero-buffer (#193) diff --git a/cryptography/utils.py b/cryptography/utils.py new file mode 100644 index 00000000..e697d515 --- /dev/null +++ b/cryptography/utils.py @@ -0,0 +1,21 @@ +# 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 register_interface(iface): + def register_decorator(klass): + iface.register(klass) + return klass + return register_decorator diff --git a/docs/architecture.rst b/docs/architecture.rst index 4cf639c2..5ca2c252 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -1,11 +1,6 @@ Architecture ============ -.. warning:: - - Because ``cryptography`` is so young, much of this document is - aspirational, rather than documentation. - ``cryptography`` has three different layers: * ``cryptography``: This package contains higher level recipes, for example diff --git a/docs/contributing.rst b/docs/contributing.rst index 3b301842..4647818a 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -122,6 +122,18 @@ So, specifically: * No blank line at the end. * Use Sphinx parameter/attribute documentation `syntax`_. +Because of the inherent challenges in implementing correct cryptographic +systems, we want to make our documentation point people in the right directions +as much as possible. To that end: + +* When documenting a generic interface, use a strong algorithm in examples. + (e.g. when showing a hashing example, don't use + :class:`cryptography.hazmat.primitives.hashes.MD5`) +* When giving prescriptive advice, always provide references and supporting + material. +* When there is real disagreement between cryptographic experts, represent both + sides of the argument and describe the trade-offs clearly. + When documenting a new module in the ``hazmat`` package, its documentation should begin with the "Hazardous Materials" warning: @@ -129,6 +141,9 @@ should begin with the "Hazardous Materials" warning: .. hazmat:: +When referring to a hypothetical individual (such as "a person receiving an +encrypted message") use gender neutral pronouns (they/them/their). + Development Environment ----------------------- diff --git a/docs/cryptography-docs.py b/docs/cryptography-docs.py index 4ed5526a..ea7e8eef 100644 --- a/docs/cryptography-docs.py +++ b/docs/cryptography-docs.py @@ -31,10 +31,14 @@ class Hazmat(nodes.Admonition, nodes.Element): pass -def visit_hazmat_node(self, node): +def html_visit_hazmat_node(self, node): return self.visit_admonition(node, "danger") +def latex_visit_hazmat_node(self, node): + return self.visit_admonition(node) + + def depart_hazmat_node(self, node): return self.depart_admonition(node) @@ -42,6 +46,7 @@ def depart_hazmat_node(self, node): def setup(app): app.add_node( Hazmat, - html=(visit_hazmat_node, depart_hazmat_node) + html=(html_visit_hazmat_node, depart_hazmat_node), + latex=(latex_visit_hazmat_node, depart_hazmat_node), ) app.add_directive("hazmat", HazmatDirective) diff --git a/docs/exceptions.rst b/docs/exceptions.rst index 6ac11b3c..087066b8 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -3,6 +3,23 @@ Exceptions .. currentmodule:: cryptography.exceptions +.. class:: AlreadyFinalized + + This is raised when a context is used after being finalized. + + +.. class:: NotYetFinalized + + This is raised when the AEAD tag property is accessed on a context + before it is finalized. + + +.. class:: AlreadyUpdated + + This is raised when additional data is added to a context after update + has already been called. + + .. class:: UnsupportedAlgorithm This is raised when a backend doesn't support the requested algorithm (or diff --git a/docs/glossary.rst b/docs/glossary.rst index e4fc8283..63e0a6ce 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -27,4 +27,15 @@ Glossary asymmetric cryptography Cryptographic operations where encryption and decryption use different - keys. There are seperate encryption and decryption keys. + keys. There are separate encryption and decryption keys. + + authentication + The process of verifying that a message was created by a specific + individual (or program). Like encryption, authentication can be either + symmetric or asymmetric. Authentication is necessary for effective + encryption. + + Ciphertext indistinguishability + This is a property of encryption systems whereby two encrypted messages + aren't distinguishable without knowing the encryption key. This is + considered a basic, necessary property for a working encryption system. diff --git a/docs/hazmat/bindings/index.rst b/docs/hazmat/bindings/index.rst index 19e03999..746f4596 100644 --- a/docs/hazmat/bindings/index.rst +++ b/docs/hazmat/bindings/index.rst @@ -7,3 +7,28 @@ Bindings :maxdepth: 1 openssl + interfaces + + +Getting a Backend Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. currentmodule:: cryptography.hazmat.bindings + +``cryptography`` aims to support multiple backends to ensure it can provide +the widest number of supported cryptographic algorithms as well as supporting +platform specific implementations. + +You can get the default backend by calling +:func:`~default_backend`. + +The default backend will change over time as we implement new backends and +the libraries we use in those backends changes. + + +.. function:: default_backend() + + :returns: An object that provides at least + :class:`~interfaces.CipherBackend`, :class:`~interfaces.HashBackend`, and + :class:`~interfaces.HMACBackend`. + diff --git a/docs/hazmat/bindings/interfaces.rst b/docs/hazmat/bindings/interfaces.rst new file mode 100644 index 00000000..711c82c2 --- /dev/null +++ b/docs/hazmat/bindings/interfaces.rst @@ -0,0 +1,141 @@ +.. hazmat:: + +Backend Interfaces +================== + +.. currentmodule:: cryptography.hazmat.bindings.interfaces + + +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/hmac`. + +A specific ``backend`` may provide one or more of these interfaces. + + +.. class:: CipherBackend + + A backend which provides methods for using ciphers for encryption + and decryption. + + .. method:: cipher_supported(cipher, mode) + + Check if a ``cipher`` and ``mode`` combination is supported by + this backend. + + :param cipher: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm` + provider. + :param mode: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.Mode` provider. + + :returns: ``True`` if the specified ``cipher`` and ``mode`` combination + is supported by this backend, otherwise ``False`` + + .. method:: register_cipher_adapter(cipher_cls, mode_cls, adapter) + + Register an adapter which can be used to create a backend specific + object from instances of the + :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm` and + the :class:`~cryptography.hazmat.primitives.interfaces.Mode` primitives. + + :param cipher_cls: A class whose instances provide + :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm` + :param mode_cls: A class whose instances provide: + :class:`~cryptography.hazmat.primitives.interfaces.Mode` + :param adapter: A ``function`` that takes 3 arguments, ``backend`` (a + :class:`CipherBackend` provider), ``cipher`` (a + :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm` + provider ), and ``mode`` (a + :class:`~cryptography.hazmat.primitives.interfaces.Mode` provider). + It returns a backend specific object which may be used to construct + a :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext`. + + + .. method:: create_symmetric_encryption_ctx(cipher, mode) + + Create a + :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext` that + can be used for encrypting data with the symmetric ``cipher`` using + the given ``mode``. + + :param cipher: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm` + provider. + :param mode: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.Mode` provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.CipherContext` + + :raises ValueError: When tag is not None in an AEAD mode + + + .. method:: create_symmetric_decryption_ctx(cipher, mode) + + Create a + :class:`~cryptogrpahy.hazmat.primitives.interfaces.CipherContext` that + can be used for decrypting data with the symmetric ``cipher`` using + the given ``mode``. + + :param cipher: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm` + provider. + :param mode: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.Mode` provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.CipherContext` + + :raises ValueError: When tag is None in an AEAD mode + + +.. class:: HashBackend + + A backend with methods for using cryptographic hash functions. + + .. method:: hash_supported(algorithm) + + Check if the specified ``algorithm`` is supported by this backend. + + :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:: create_hash_ctx(algorithm) + + Create a + :class:`~cryptogrpahy.hazmat.primitives.interfaces.HashContext` that + uses the specified ``algorithm`` to calculate a message digest. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.HashContext` + + +.. class:: HMACBackend + + A backend with methods for using cryptographic hash functions as message + authentication codes. + + .. method:: create_hmac_ctx(algorithm) + + Create a + :class:`~cryptogrpahy.hazmat.primitives.interfaces.HashContext` that + uses the specified ``algorithm`` to calculate a hash-based message + authentication code. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.HashContext` diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst index 20fa23cf..312d7e69 100644 --- a/docs/hazmat/primitives/cryptographic-hashes.rst +++ b/docs/hazmat/primitives/cryptographic-hashes.rst @@ -5,7 +5,7 @@ Message Digests .. currentmodule:: cryptography.hazmat.primitives.hashes -.. class:: Hash(algorithm) +.. class:: Hash(algorithm, backend) A cryptographic hash function takes an arbitrary block of data and calculates a fixed-size bit string (a digest), such that different data @@ -20,30 +20,56 @@ Message Digests .. doctest:: + >>> from cryptography.hazmat.bindings import default_backend >>> from cryptography.hazmat.primitives import hashes - >>> digest = hashes.Hash(hashes.SHA256()) + >>> digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) >>> digest.update(b"abc") >>> digest.update(b"123") >>> digest.finalize() 'l\xa1=R\xcap\xc8\x83\xe0\xf0\xbb\x10\x1eBZ\x89\xe8bM\xe5\x1d\xb2\xd29%\x93\xafj\x84\x11\x80\x90' + Keep in mind that attacks against cryptographic hashes only get stronger + with time, and that often algorithms that were once thought to be strong, + become broken. Because of this it's important to include a plan for + upgrading the hash algorithm you use over time. For more information, see + `Lifetimes of cryptographic hash functions`_. + + :param algorithm: A + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider such as those described in + :ref:`below <cryptographic-hash-algorithms>`. + :param backend: A + :class:`~cryptography.hazmat.bindings.interfaces.HashBackend` + provider. + .. method:: update(data) :param bytes data: The bytes you wish to hash. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` .. method:: copy() - :return: a new instance of this object with a copied internal state. + Copy this :class:`Hash` instance, usually so that we may call + :meth:`finalize` and get an intermediate digest value while we continue + to call :meth:`update` on the original. + + :return: A new instance of :class:`Hash` which can be updated + and finalized independently of the original instance. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` .. method:: finalize() Finalize the current context and return the message digest as bytes. - Once ``finalize`` is called this object can no longer be used. + Once ``finalize`` is called this object can no longer be used and + :meth:`update`, :meth:`copy`, and :meth:`finalize` will raise + :class:`~cryptography.exceptions.AlreadyFinalized`. :return bytes: The message digest as bytes. +.. _cryptographic-hash-algorithms: + SHA-1 ~~~~~ @@ -109,3 +135,6 @@ MD5 MD5 is a deprecated cryptographic hash function. It has a 128-bit message digest and has practical known collision attacks. + + +.. _`Lifetimes of cryptographic hash functions`: http://valerieaurora.org/hash.html diff --git a/docs/hazmat/primitives/hmac.rst b/docs/hazmat/primitives/hmac.rst index bd1a4934..db5e98d3 100644 --- a/docs/hazmat/primitives/hmac.rst +++ b/docs/hazmat/primitives/hmac.rst @@ -15,7 +15,7 @@ message authentication codes using a cryptographic hash function coupled with a secret key. You can use an HMAC to verify integrity as well as authenticate a message. -.. class:: HMAC(key, algorithm) +.. class:: HMAC(key, algorithm, backend) HMAC objects take a ``key`` and a provider of :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`. @@ -27,24 +27,45 @@ message. .. doctest:: + >>> from cryptography.hazmat.bindings import default_backend >>> from cryptography.hazmat.primitives import hashes, hmac - >>> h = hmac.HMAC(key, hashes.SHA256()) + >>> h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend()) >>> h.update(b"message to hash") >>> h.finalize() '#F\xdaI\x8b"e\xc4\xf1\xbb\x9a\x8fc\xff\xf5\xdex.\xbc\xcd/+\x8a\x86\x1d\x84\'\xc3\xa6\x1d\xd8J' + + :param key: Secret key as ``bytes``. + :param algorithm: A + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider such as those described in + :ref:`Cryptographic Hashes <cryptographic-hash-algorithms>`. + :param backend: A + :class:`~cryptography.hazmat.bindings.interfaces.HMACBackend` + provider. + .. method:: update(msg) :param bytes msg: The bytes to hash and authenticate. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` .. method:: copy() - :return: a new instance of this object with a copied internal state. + Copy this :class:`HMAC` instance, usually so that we may call + :meth:`finalize` and get an intermediate digest value while we continue + to call :meth:`update` on the original. + + :return: A new instance of :class:`HMAC` which can be updated + and finalized independently of the original instance. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` .. method:: finalize() Finalize the current context and return the message digest as bytes. - Once ``finalize`` is called this object can no longer be used. + Once ``finalize`` is called this object can no longer be used and + :meth:`update`, :meth:`copy`, and :meth:`finalize` will raise + :class:`~cryptography.exceptions.AlreadyFinalized`. :return bytes: The message digest as bytes. + :raises cryptography.exceptions.AlreadyFinalized: diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 7068316e..11cff51a 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -12,11 +12,33 @@ to document argument and return types. .. _`Abstract Base Classes`: http://docs.python.org/3.2/library/abc.html -Cipher Modes -~~~~~~~~~~~~ +Symmetric Ciphers +~~~~~~~~~~~~~~~~~ .. currentmodule:: cryptography.hazmat.primitives.interfaces + +.. class:: CipherAlgorithm + + A named symmetric encryption algorithm. + + .. attribute:: name + + :type: str + + The standard name for the mode, for example, "AES", "Camellia", or + "Blowfish". + + .. attribute:: key_size + + :type: int + + The number of bits in the key being used. + + +Cipher Modes +------------ + Interfaces used by the symmetric cipher modes described in :ref:`Symmetric Encryption Modes <symmetric-encryption-modes>`. diff --git a/docs/hazmat/primitives/padding.rst b/docs/hazmat/primitives/padding.rst index aebb4d4d..4d79ac8f 100644 --- a/docs/hazmat/primitives/padding.rst +++ b/docs/hazmat/primitives/padding.rst @@ -25,8 +25,14 @@ multiple of the block size. >>> padder = padding.PKCS7(128).padder() >>> padder.update(b"1111111111") '' - >>> padder.finalize() + >>> padded_data = padder.finalize() + >>> padded_data '1111111111\x06\x06\x06\x06\x06\x06' + >>> unpadder = padding.PKCS7(128).unpadder() + >>> unpadder.update(padded_data) + '' + >>> unpadder.finalize() + '1111111111' :param block_size: The size of the block in bits that the data is being padded to. diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 5542e832..2f390175 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -12,17 +12,20 @@ Symmetric Encryption key = binascii.unhexlify(b"0" * 32) iv = binascii.unhexlify(b"0" * 32) + from cryptography.hazmat.bindings import default_backend + backend = default_backend() + Symmetric encryption is a way to encrypt (hide the plaintext value) material -where the encrypter and decrypter both use the same key. Note that symmetric +where the sender and receiver both use the same key. Note that symmetric encryption is **not** sufficient for most applications, because it only provides secrecy (an attacker can't see the message) but not authenticity (an attacker can create bogus messages and force the application to decrypt them). -For this reason it is *strongly* reccomended to combine encryption with a +For this reason it is *strongly* recommended to combine encryption with a message authentication code, such as :doc:`HMAC </hazmat/primitives/hmac>`, in an "encrypt-then-MAC" formulation as `described by Colin Percival`_. -.. class:: Cipher(algorithm, mode) +.. class:: Cipher(algorithm, mode, backend) Cipher objects combine an algorithm (such as :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`) with a @@ -34,15 +37,23 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. .. doctest:: >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - >>> cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) + >>> cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend) >>> encryptor = cipher.encryptor() >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() >>> decryptor = cipher.decryptor() >>> decryptor.update(ct) + decryptor.finalize() 'a secret message' - :param algorithms: One of the algorithms described below. - :param mode: One of the modes described below. + :param algorithms: A + :class:`~cryptography.hazmat.primitives.interfaces.CipherAlgorithm` + provider such as those described + :ref:`below <symmetric-encryption-algorithms>`. + :param mode: A :class:`~cryptography.hazmat.primitives.interfaces.Mode` + provider such as those described + :ref:`below <symmetric-encryption-modes>`. + :param backend: A + :class:`~cryptography.hazmat.bindings.interfaces.CipherBackend` + provider. .. method:: encryptor() @@ -75,10 +86,20 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. everything into the context. Once that is done call ``finalize()`` to finish the operation and obtain the remainder of the data. + Block ciphers require that plaintext or ciphertext always be a multiple of + their block size, because of that **padding** is often required to make a + message the correct size. ``CipherContext`` will not automatically apply + any padding; you'll need to add your own. For block ciphers the recommended + padding is :class:`cryptography.hazmat.primitives.padding.PKCS7`. If you + are using a stream cipher mode (such as + :class:`cryptography.hazmat.primitives.modes.CTR`) you don't have to worry + about this. + .. method:: update(data) :param bytes data: The data you wish to pass into the context. :return bytes: Returns the data that was encrypted or decrypted. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` When the ``Cipher`` was constructed in a mode that turns it into a stream cipher (e.g. @@ -89,6 +110,45 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. .. method:: finalize() :return bytes: Returns the remainder of the data. + :raises ValueError: This is raised when the data provided isn't + correctly padded to be a multiple of the + algorithm's block size. + + Once ``finalize`` is called this object can no longer be used and + :meth:`update` and :meth:`finalize` will raise + :class:`~cryptography.exceptions.AlreadyFinalized`. + +.. class:: AEADCipherContext + + When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object + with an AEAD mode you will receive a return object conforming to the + ``AEADCipherContext`` interface (in addition to the ``CipherContext`` + interface). If it is an encryption context it will additionally be an + ``AEADEncryptionContext`` interface. ``AEADCipherContext`` contains an + additional method ``authenticate_additional_data`` for adding additional + authenticated but unencrypted data. You should call this before calls to + ``update``. When you are done call ``finalize()`` to finish the operation. + + .. method:: authenticate_additional_data(data) + + :param bytes data: The data you wish to authenticate but not encrypt. + :raises: :class:`~cryptography.exceptions.AlreadyFinalized` + +.. class:: AEADEncryptionContext + + When creating an encryption context using ``encryptor()`` on a ``Cipher`` + object with an AEAD mode you will receive a return object conforming to the + ``AEADEncryptionContext`` interface (as well as ``AEADCipherContext``). + This interface provides one additional attribute ``tag``. ``tag`` can only + be obtained after ``finalize()``. + + .. attribute:: tag + + :return bytes: Returns the tag value as bytes. + :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called + before the context is finalized. + +.. _symmetric-encryption-algorithms: Algorithms ~~~~~~~~~~ @@ -116,10 +176,10 @@ Algorithms .. class:: TripleDES(key) - Triple DES (Data Encryption Standard), sometimes refered to as 3DES, is a - block cipher standardized by NIST. Triple DES has known cryptoanalytic + Triple DES (Data Encryption Standard), sometimes referred to as 3DES, is a + block cipher standardized by NIST. Triple DES has known crypto-analytic flaws, however none of them currently enable a practical attack. - Nonetheless, Triples DES is not reccomended for new applications because it + Nonetheless, Triples DES is not recommended for new applications because it is incredibly slow; old applications should consider moving away from it. :param bytes key: The secret key, either ``64``, ``128``, or ``192`` bits @@ -157,6 +217,27 @@ Weak Ciphers :param bytes key: The secret key, 32-448 bits in length (in increments of 8). This must be kept secret. +.. class:: ARC4(key) + + ARC4 (Alleged RC4) is a stream cipher with serious weaknesses in its + initial stream output. Its use is strongly discouraged. ARC4 does not use + mode constructions. + + :param bytes key: The secret key, ``40``, ``56``, ``64``, ``80``, ``128``, + ``192``, or ``256`` bits in length. This must be kept + secret. + + .. doctest:: + + >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + >>> algorithm = algorithms.ARC4(key) + >>> cipher = Cipher(algorithm, mode=None, backend=backend) + >>> encryptor = cipher.encryptor() + >>> ct = encryptor.update(b"a secret message") + >>> decryptor = cipher.decryptor() + >>> decryptor.update(ct) + 'a secret message' + .. _symmetric-encryption-modes: @@ -244,6 +325,48 @@ Modes reuse an ``initialization_vector`` with a given ``key``. +.. class:: GCM(initialization_vector, tag=None) + + .. danger:: + + When using this mode you MUST not use the decrypted data until + :meth:`~cryptography.hazmat.primitives.interfaces.CipherContext.finalize` + has been called. GCM provides NO guarantees of ciphertext integrity + until decryption is complete. + + GCM (Galois Counter Mode) is a mode of operation for block ciphers. An + AEAD (authenticated encryption with additional data) mode is a type of + block cipher mode that encrypts the message as well as authenticating it + (and optionally additional data that is not encrypted) simultaneously. + Additional means of verifying integrity (like + :doc:`HMAC </hazmat/primitives/hmac>`) are not necessary. + + :param bytes initialization_vector: Must be random bytes. They do not need + to be kept secret (they can be included + in a transmitted message). NIST + `recommends 96-bit IV length`_ for + performance critical situations, but it + can be up to 2\ :sup:`64` - 1 bits. + Do not reuse an ``initialization_vector`` + with a given ``key``. + + :param bytes tag: The tag bytes to verify during decryption. When encrypting + this must be None. + + .. doctest:: + + >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend) + >>> encryptor = cipher.encryptor() + >>> encryptor.authenticate_additional_data(b"authenticated but not encrypted payload") + >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() + >>> tag = encryptor.tag + >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag), backend) + >>> decryptor = cipher.decryptor() + >>> decryptor.authenticate_additional_data(b"authenticated but not encrypted payload") + >>> decryptor.update(ct) + decryptor.finalize() + 'a secret message' + Insecure Modes -------------- @@ -263,3 +386,4 @@ Insecure Modes .. _`described by Colin Percival`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html +.. _`recommends 96-bit IV length`: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf diff --git a/docs/security.rst b/docs/security.rst index 36c8e0f7..88959709 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -5,7 +5,7 @@ We take the security of ``cryptography`` seriously. If you believe you've identified a security issue in it, please report it to ``alex.gaynor@gmail.com``. Message may be encrypted with PGP using key fingerprint ``E27D 4AA0 1651 72CB C5D2 AF2B 125F 5C67 DFE9 4084`` (this public -key is available from most commonly-used keyservers). +key is available from most commonly-used key servers). Once you’ve submitted an issue via email, you should receive an acknowledgment within 48 hours, and depending on the action to be taken, you may receive diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index f1493e8d..1eb6f200 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -13,18 +13,22 @@ import pytest +from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.bindings import default_backend from cryptography.hazmat.bindings.openssl.backend import backend, Backend +from cryptography.hazmat.primitives import interfaces from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import CBC -class FakeMode(object): +class DummyMode(object): pass -class FakeCipher(object): +@utils.register_interface(interfaces.CipherAlgorithm) +class DummyCipher(object): pass @@ -32,6 +36,9 @@ class TestOpenSSL(object): def test_backend_exists(self): assert backend + def test_is_default(self): + assert backend == default_backend() + def test_openssl_version_text(self): """ This test checks the value of OPENSSL_VERSION_TEXT. @@ -44,11 +51,11 @@ class TestOpenSSL(object): assert backend.openssl_version_text().startswith("OpenSSL") def test_supports_cipher(self): - assert backend.ciphers.supported(None, None) is False + assert backend.cipher_supported(None, None) is False def test_register_duplicate_cipher_adapter(self): with pytest.raises(ValueError): - backend.ciphers.register_cipher_adapter(AES, CBC, None) + backend.register_cipher_adapter(AES, CBC, None) def test_instances_share_ffi(self): b = Backend() @@ -57,13 +64,34 @@ class TestOpenSSL(object): def test_nonexistent_cipher(self): b = Backend() - b.ciphers.register_cipher_adapter( - FakeCipher, - FakeMode, + b.register_cipher_adapter( + DummyCipher, + DummyMode, lambda backend, cipher, mode: backend.ffi.NULL ) cipher = Cipher( - FakeCipher(), FakeMode(), backend=b, + DummyCipher(), DummyMode(), backend=b, ) with pytest.raises(UnsupportedAlgorithm): cipher.encryptor() + + def test_handle_unknown_error(self): + with pytest.raises(SystemError): + backend._handle_error_code(0, 0, 0) + + with pytest.raises(SystemError): + backend._handle_error_code(backend.lib.ERR_LIB_EVP, 0, 0) + + with pytest.raises(SystemError): + backend._handle_error_code( + backend.lib.ERR_LIB_EVP, + backend.lib.EVP_F_EVP_ENCRYPTFINAL_EX, + 0 + ) + + with pytest.raises(SystemError): + backend._handle_error_code( + backend.lib.ERR_LIB_EVP, + backend.lib.EVP_F_EVP_DECRYPTFINAL_EX, + 0 + ) diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py index af6bdc04..69ec9c9a 100644 --- a/tests/hazmat/primitives/test_3des.py +++ b/tests/hazmat/primitives/test_3des.py @@ -23,12 +23,12 @@ import os from cryptography.hazmat.primitives.ciphers import algorithms, modes from .utils import generate_encrypt_test -from ...utils import load_nist_vectors_from_file +from ...utils import load_nist_vectors class TestTripleDES_CBC(object): test_KAT = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "3DES", "CBC"), [ "TCBCinvperm.rsp", @@ -42,7 +42,7 @@ class TestTripleDES_CBC(object): ) test_MMT = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "3DES", "CBC"), [ "TCBCMMT1.rsp", @@ -58,7 +58,7 @@ class TestTripleDES_CBC(object): class TestTripleDES_OFB(object): test_KAT = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "3DES", "OFB"), [ "TOFBpermop.rsp", @@ -72,7 +72,7 @@ class TestTripleDES_OFB(object): ) test_MMT = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "3DES", "OFB"), [ "TOFBMMT1.rsp", @@ -88,7 +88,7 @@ class TestTripleDES_OFB(object): class TestTripleDES_CFB(object): test_KAT = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ "TCFB64invperm.rsp", @@ -102,7 +102,7 @@ class TestTripleDES_CFB(object): ) test_MMT = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "3DES", "CFB"), [ "TCFB64MMT1.rsp", diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 66471fac..f7b0b9a0 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -18,15 +18,15 @@ import os from cryptography.hazmat.primitives.ciphers import algorithms, modes -from .utils import generate_encrypt_test +from .utils import generate_encrypt_test, generate_aead_test from ...utils import ( - load_nist_vectors_from_file, load_openssl_vectors_from_file + load_nist_vectors, load_openssl_vectors, ) class TestAES(object): test_CBC = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "AES", "CBC"), [ "CBCGFSbox128.rsp", @@ -50,7 +50,7 @@ class TestAES(object): ) test_ECB = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "AES", "ECB"), [ "ECBGFSbox128.rsp", @@ -74,7 +74,7 @@ class TestAES(object): ) test_OFB = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "AES", "OFB"), [ "OFBGFSbox128.rsp", @@ -98,7 +98,7 @@ class TestAES(object): ) test_CFB = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "AES", "CFB"), [ "CFB128GFSbox128.rsp", @@ -122,13 +122,32 @@ class TestAES(object): ) test_CTR = generate_encrypt_test( - load_openssl_vectors_from_file, + load_openssl_vectors, os.path.join("ciphers", "AES", "CTR"), ["aes-128-ctr.txt", "aes-192-ctr.txt", "aes-256-ctr.txt"], lambda key, iv: algorithms.AES(binascii.unhexlify(key)), lambda key, iv: modes.CTR(binascii.unhexlify(iv)), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.AES("\x00" * 16), modes.CTR("\x00" * 16) ), skip_message="Does not support AES CTR", ) + + test_GCM = generate_aead_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "GCM"), + [ + "gcmDecrypt128.rsp", + "gcmDecrypt192.rsp", + "gcmDecrypt256.rsp", + "gcmEncryptExtIV128.rsp", + "gcmEncryptExtIV192.rsp", + "gcmEncryptExtIV256.rsp", + ], + lambda key: algorithms.AES(key), + lambda iv, tag: modes.GCM(iv, tag), + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12) + ), + skip_message="Does not support AES GCM", + ) diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/test_arc4.py new file mode 100644 index 00000000..d233bec2 --- /dev/null +++ b/tests/hazmat/primitives/test_arc4.py @@ -0,0 +1,43 @@ +# 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 binascii +import os + +from cryptography.hazmat.primitives.ciphers import algorithms + +from .utils import generate_stream_encryption_test +from ...utils import load_nist_vectors + + +class TestARC4(object): + test_rfc = generate_stream_encryption_test( + load_nist_vectors, + os.path.join("ciphers", "ARC4"), + [ + "rfc-6229-40.txt", + "rfc-6229-56.txt", + "rfc-6229-64.txt", + "rfc-6229-80.txt", + "rfc-6229-128.txt", + "rfc-6229-192.txt", + "rfc-6229-256.txt", + ], + lambda key: algorithms.ARC4(binascii.unhexlify((key))), + only_if=lambda backend: backend.cipher_supported( + algorithms.ARC4("\x00" * 16), None + ), + skip_message="Does not support ARC4", + ) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 28f34478..02de3861 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -17,34 +17,47 @@ import binascii import pytest -from cryptography.exceptions import UnsupportedAlgorithm +from cryptography import utils +from cryptography.exceptions import ( + UnsupportedAlgorithm, AlreadyFinalized, +) from cryptography.hazmat.primitives import interfaces from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, modes ) +from .utils import ( + generate_aead_exception_test, generate_aead_tag_exception_test +) -class TestCipher(object): - def test_instantiate_without_backend(self): - Cipher( - algorithms.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)) - ) - def test_creates_encryptor(self): +@utils.register_interface(interfaces.CipherAlgorithm) +class DummyCipher(object): + pass + + +class TestCipher(object): + def test_creates_encryptor(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)) + modes.CBC(binascii.unhexlify(b"0" * 32)), + backend ) assert isinstance(cipher.encryptor(), interfaces.CipherContext) - def test_creates_decryptor(self): + def test_creates_decryptor(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)) + modes.CBC(binascii.unhexlify(b"0" * 32)), + backend ) assert isinstance(cipher.decryptor(), interfaces.CipherContext) + def test_instantiate_with_non_algorithm(self, backend): + algorithm = object() + with pytest.raises(TypeError): + Cipher(algorithm, mode=None, backend=backend) + class TestCipherContext(object): def test_use_after_finalize(self, backend): @@ -56,16 +69,16 @@ class TestCipherContext(object): encryptor = cipher.encryptor() encryptor.update(b"a" * 16) encryptor.finalize() - with pytest.raises(ValueError): + with pytest.raises(AlreadyFinalized): encryptor.update(b"b" * 16) - with pytest.raises(ValueError): + with pytest.raises(AlreadyFinalized): encryptor.finalize() decryptor = cipher.decryptor() decryptor.update(b"a" * 16) decryptor.finalize() - with pytest.raises(ValueError): + with pytest.raises(AlreadyFinalized): decryptor.update(b"b" * 16) - with pytest.raises(ValueError): + with pytest.raises(AlreadyFinalized): decryptor.finalize() def test_unaligned_block_encryption(self, backend): @@ -90,10 +103,45 @@ class TestCipherContext(object): def test_nonexistent_cipher(self, backend): cipher = Cipher( - object(), object(), backend + DummyCipher(), object(), backend ) with pytest.raises(UnsupportedAlgorithm): cipher.encryptor() with pytest.raises(UnsupportedAlgorithm): cipher.decryptor() + + def test_incorrectly_padded(self, backend): + cipher = Cipher( + algorithms.AES(b"\x00" * 16), + modes.CBC(b"\x00" * 16), + backend + ) + encryptor = cipher.encryptor() + encryptor.update(b"1") + with pytest.raises(ValueError): + encryptor.finalize() + + decryptor = cipher.decryptor() + decryptor.update(b"1") + with pytest.raises(ValueError): + decryptor.finalize() + + +class TestAEADCipherContext(object): + test_aead_exceptions = generate_aead_exception_test( + algorithms.AES, + modes.GCM, + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12) + ), + skip_message="Does not support AES GCM", + ) + test_aead_tag_exceptions = generate_aead_tag_exception_test( + algorithms.AES, + modes.GCM, + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 16), modes.GCM("\x00" * 12) + ), + skip_message="Does not support AES GCM", + ) diff --git a/tests/hazmat/primitives/test_blowfish.py b/tests/hazmat/primitives/test_blowfish.py index a7f13823..d5fbed6f 100644 --- a/tests/hazmat/primitives/test_blowfish.py +++ b/tests/hazmat/primitives/test_blowfish.py @@ -19,53 +19,53 @@ import os from cryptography.hazmat.primitives.ciphers import algorithms, modes from .utils import generate_encrypt_test -from ...utils import load_nist_vectors_from_file +from ...utils import load_nist_vectors class TestBlowfish(object): test_ECB = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ecb.txt"], lambda key: algorithms.Blowfish(binascii.unhexlify(key)), lambda key: modes.ECB(), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.Blowfish("\x00" * 56), modes.ECB() ), skip_message="Does not support Blowfish ECB", ) test_CBC = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cbc.txt"], lambda key, iv: algorithms.Blowfish(binascii.unhexlify(key)), lambda key, iv: modes.CBC(binascii.unhexlify(iv)), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.Blowfish("\x00" * 56), modes.CBC("\x00" * 8) ), skip_message="Does not support Blowfish CBC", ) test_OFB = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-ofb.txt"], lambda key, iv: algorithms.Blowfish(binascii.unhexlify(key)), lambda key, iv: modes.OFB(binascii.unhexlify(iv)), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.Blowfish("\x00" * 56), modes.OFB("\x00" * 8) ), skip_message="Does not support Blowfish OFB", ) test_CFB = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "Blowfish"), ["bf-cfb.txt"], lambda key, iv: algorithms.Blowfish(binascii.unhexlify(key)), lambda key, iv: modes.CFB(binascii.unhexlify(iv)), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.Blowfish("\x00" * 56), modes.CFB("\x00" * 8) ), skip_message="Does not support Blowfish CFB", diff --git a/tests/hazmat/primitives/test_camellia.py b/tests/hazmat/primitives/test_camellia.py index e1be5d1d..a2c935d9 100644 --- a/tests/hazmat/primitives/test_camellia.py +++ b/tests/hazmat/primitives/test_camellia.py @@ -20,13 +20,13 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes from .utils import generate_encrypt_test from ...utils import ( - load_cryptrec_vectors_from_file, load_openssl_vectors_from_file + load_cryptrec_vectors, load_openssl_vectors ) class TestCamellia(object): test_ECB = generate_encrypt_test( - load_cryptrec_vectors_from_file, + load_cryptrec_vectors, os.path.join("ciphers", "Camellia"), [ "camellia-128-ecb.txt", @@ -35,43 +35,43 @@ class TestCamellia(object): ], lambda key: algorithms.Camellia(binascii.unhexlify((key))), lambda key: modes.ECB(), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.Camellia("\x00" * 16), modes.ECB() ), skip_message="Does not support Camellia ECB", ) test_CBC = generate_encrypt_test( - load_openssl_vectors_from_file, + load_openssl_vectors, os.path.join("ciphers", "Camellia"), ["camellia-cbc.txt"], lambda key, iv: algorithms.Camellia(binascii.unhexlify(key)), lambda key, iv: modes.CBC(binascii.unhexlify(iv)), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.Camellia("\x00" * 16), modes.CBC("\x00" * 16) ), skip_message="Does not support Camellia CBC", ) test_OFB = generate_encrypt_test( - load_openssl_vectors_from_file, + load_openssl_vectors, os.path.join("ciphers", "Camellia"), ["camellia-ofb.txt"], lambda key, iv: algorithms.Camellia(binascii.unhexlify(key)), lambda key, iv: modes.OFB(binascii.unhexlify(iv)), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.Camellia("\x00" * 16), modes.OFB("\x00" * 16) ), skip_message="Does not support Camellia OFB", ) test_CFB = generate_encrypt_test( - load_openssl_vectors_from_file, + load_openssl_vectors, os.path.join("ciphers", "Camellia"), ["camellia-cfb.txt"], lambda key, iv: algorithms.Camellia(binascii.unhexlify(key)), lambda key, iv: modes.CFB(binascii.unhexlify(iv)), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.Camellia("\x00" * 16), modes.CFB("\x00" * 16) ), skip_message="Does not support Camellia CFB", diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py index b2988437..a283dafc 100644 --- a/tests/hazmat/primitives/test_cast5.py +++ b/tests/hazmat/primitives/test_cast5.py @@ -19,19 +19,17 @@ import os from cryptography.hazmat.primitives.ciphers import algorithms, modes from .utils import generate_encrypt_test -from ...utils import load_nist_vectors_from_file +from ...utils import load_nist_vectors class TestCAST5(object): test_ECB = generate_encrypt_test( - lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + load_nist_vectors, os.path.join("ciphers", "CAST5"), - [ - "cast5-ecb.txt", - ], + ["cast5-ecb.txt"], lambda key: algorithms.CAST5(binascii.unhexlify((key))), lambda key: modes.ECB(), - only_if=lambda backend: backend.ciphers.supported( + only_if=lambda backend: backend.cipher_supported( algorithms.CAST5("\x00" * 16), modes.ECB() ), skip_message="Does not support CAST5 ECB", diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py index dfafab3f..653f7ce6 100644 --- a/tests/hazmat/primitives/test_ciphers.py +++ b/tests/hazmat/primitives/test_ciphers.py @@ -18,7 +18,7 @@ import binascii import pytest from cryptography.hazmat.primitives.ciphers.algorithms import ( - AES, Camellia, TripleDES, Blowfish, CAST5 + AES, Camellia, TripleDES, Blowfish, CAST5, ARC4 ) @@ -91,3 +91,22 @@ class TestCAST5(object): def test_invalid_key_size(self): with pytest.raises(ValueError): CAST5(binascii.unhexlify(b"0" * 34)) + + +class TestARC4(object): + @pytest.mark.parametrize(("key", "keysize"), [ + (b"0" * 10, 40), + (b"0" * 14, 56), + (b"0" * 16, 64), + (b"0" * 20, 80), + (b"0" * 32, 128), + (b"0" * 48, 192), + (b"0" * 64, 256), + ]) + def test_key_size(self, key, keysize): + cipher = ARC4(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + ARC4(binascii.unhexlify(b"0" * 34)) diff --git a/tests/hazmat/primitives/test_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py index fca839c7..a8655812 100644 --- a/tests/hazmat/primitives/test_hash_vectors.py +++ b/tests/hazmat/primitives/test_hash_vectors.py @@ -18,108 +18,108 @@ import os from cryptography.hazmat.primitives import hashes from .utils import generate_hash_test, generate_long_string_hash_test -from ...utils import load_hash_vectors_from_file +from ...utils import load_hash_vectors class TestSHA1(object): test_SHA1 = generate_hash_test( - load_hash_vectors_from_file, + load_hash_vectors, os.path.join("hashes", "SHA1"), [ "SHA1LongMsg.rsp", "SHA1ShortMsg.rsp", ], hashes.SHA1(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA1), + only_if=lambda backend: backend.hash_supported(hashes.SHA1), skip_message="Does not support SHA1", ) class TestSHA224(object): test_SHA224 = generate_hash_test( - load_hash_vectors_from_file, + load_hash_vectors, os.path.join("hashes", "SHA2"), [ "SHA224LongMsg.rsp", "SHA224ShortMsg.rsp", ], hashes.SHA224(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA224), + only_if=lambda backend: backend.hash_supported(hashes.SHA224), skip_message="Does not support SHA224", ) class TestSHA256(object): test_SHA256 = generate_hash_test( - load_hash_vectors_from_file, + load_hash_vectors, os.path.join("hashes", "SHA2"), [ "SHA256LongMsg.rsp", "SHA256ShortMsg.rsp", ], hashes.SHA256(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA256), + only_if=lambda backend: backend.hash_supported(hashes.SHA256), skip_message="Does not support SHA256", ) class TestSHA384(object): test_SHA384 = generate_hash_test( - load_hash_vectors_from_file, + load_hash_vectors, os.path.join("hashes", "SHA2"), [ "SHA384LongMsg.rsp", "SHA384ShortMsg.rsp", ], hashes.SHA384(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA384), + only_if=lambda backend: backend.hash_supported(hashes.SHA384), skip_message="Does not support SHA384", ) class TestSHA512(object): test_SHA512 = generate_hash_test( - load_hash_vectors_from_file, + load_hash_vectors, os.path.join("hashes", "SHA2"), [ "SHA512LongMsg.rsp", "SHA512ShortMsg.rsp", ], hashes.SHA512(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA512), + only_if=lambda backend: backend.hash_supported(hashes.SHA512), skip_message="Does not support SHA512", ) class TestRIPEMD160(object): test_RIPEMD160 = generate_hash_test( - load_hash_vectors_from_file, + load_hash_vectors, os.path.join("hashes", "ripemd160"), [ "ripevectors.txt", ], hashes.RIPEMD160(), - only_if=lambda backend: backend.hashes.supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160), skip_message="Does not support RIPEMD160", ) test_RIPEMD160_long_string = generate_long_string_hash_test( hashes.RIPEMD160(), "52783243c1697bdbe16d37f97f68f08325dc1528", - only_if=lambda backend: backend.hashes.supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160), skip_message="Does not support RIPEMD160", ) class TestWhirlpool(object): test_whirlpool = generate_hash_test( - load_hash_vectors_from_file, + load_hash_vectors, os.path.join("hashes", "whirlpool"), [ "iso-test-vectors.txt", ], hashes.Whirlpool(), - only_if=lambda backend: backend.hashes.supported(hashes.Whirlpool), + only_if=lambda backend: backend.hash_supported(hashes.Whirlpool), skip_message="Does not support Whirlpool", ) @@ -128,19 +128,19 @@ class TestWhirlpool(object): ("0c99005beb57eff50a7cf005560ddf5d29057fd86b2" "0bfd62deca0f1ccea4af51fc15490eddc47af32bb2b" "66c34ff9ad8c6008ad677f77126953b226e4ed8b01"), - only_if=lambda backend: backend.hashes.supported(hashes.Whirlpool), + only_if=lambda backend: backend.hash_supported(hashes.Whirlpool), skip_message="Does not support Whirlpool", ) class TestMD5(object): test_md5 = generate_hash_test( - load_hash_vectors_from_file, + load_hash_vectors, os.path.join("hashes", "MD5"), [ "rfc-1321.txt", ], hashes.MD5(), - only_if=lambda backend: backend.hashes.supported(hashes.MD5), + only_if=lambda backend: backend.hash_supported(hashes.MD5), skip_message="Does not support MD5", ) diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py index 07ab2489..ff42e8f4 100644 --- a/tests/hazmat/primitives/test_hashes.py +++ b/tests/hazmat/primitives/test_hashes.py @@ -19,7 +19,7 @@ import pytest import six -from cryptography.hazmat.bindings import _default_backend +from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import hashes from .utils import generate_base_hash_test @@ -32,24 +32,30 @@ class TestHashContext(object): m.update(six.u("\u00FC")) def test_copy_backend_object(self): - pretend_hashes = pretend.stub(copy_ctx=lambda a: "copiedctx") - pretend_backend = pretend.stub(hashes=pretend_hashes) - pretend_ctx = pretend.stub() + pretend_backend = pretend.stub() + copied_ctx = pretend.stub() + pretend_ctx = pretend.stub(copy=lambda: copied_ctx) h = hashes.Hash(hashes.SHA1(), backend=pretend_backend, ctx=pretend_ctx) assert h._backend is pretend_backend assert h.copy()._backend is h._backend - def test_default_backend_creation(self): - """ - This test assumes the presence of SHA1 in the default backend. - """ - h = hashes.Hash(hashes.SHA1()) - assert h._backend is _default_backend - - def test_hash_algorithm_instance(self): + def test_hash_algorithm_instance(self, backend): with pytest.raises(TypeError): - hashes.Hash(hashes.SHA1) + hashes.Hash(hashes.SHA1, backend=backend) + + def test_raises_after_finalize(self, backend): + h = hashes.Hash(hashes.SHA1(), backend=backend) + h.finalize() + + with pytest.raises(AlreadyFinalized): + h.update(b"foo") + + with pytest.raises(AlreadyFinalized): + h.copy() + + with pytest.raises(AlreadyFinalized): + h.finalize() class TestSHA1(object): @@ -57,7 +63,7 @@ class TestSHA1(object): hashes.SHA1(), digest_size=20, block_size=64, - only_if=lambda backend: backend.hashes.supported(hashes.SHA1), + only_if=lambda backend: backend.hash_supported(hashes.SHA1), skip_message="Does not support SHA1", ) @@ -67,7 +73,7 @@ class TestSHA224(object): hashes.SHA224(), digest_size=28, block_size=64, - only_if=lambda backend: backend.hashes.supported(hashes.SHA224), + only_if=lambda backend: backend.hash_supported(hashes.SHA224), skip_message="Does not support SHA224", ) @@ -77,7 +83,7 @@ class TestSHA256(object): hashes.SHA256(), digest_size=32, block_size=64, - only_if=lambda backend: backend.hashes.supported(hashes.SHA256), + only_if=lambda backend: backend.hash_supported(hashes.SHA256), skip_message="Does not support SHA256", ) @@ -87,7 +93,7 @@ class TestSHA384(object): hashes.SHA384(), digest_size=48, block_size=128, - only_if=lambda backend: backend.hashes.supported(hashes.SHA384), + only_if=lambda backend: backend.hash_supported(hashes.SHA384), skip_message="Does not support SHA384", ) @@ -97,7 +103,7 @@ class TestSHA512(object): hashes.SHA512(), digest_size=64, block_size=128, - only_if=lambda backend: backend.hashes.supported(hashes.SHA512), + only_if=lambda backend: backend.hash_supported(hashes.SHA512), skip_message="Does not support SHA512", ) @@ -107,7 +113,7 @@ class TestRIPEMD160(object): hashes.RIPEMD160(), digest_size=20, block_size=64, - only_if=lambda backend: backend.hashes.supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160), skip_message="Does not support RIPEMD160", ) @@ -117,7 +123,7 @@ class TestWhirlpool(object): hashes.Whirlpool(), digest_size=64, block_size=64, - only_if=lambda backend: backend.hashes.supported(hashes.Whirlpool), + only_if=lambda backend: backend.hash_supported(hashes.Whirlpool), skip_message="Does not support Whirlpool", ) @@ -127,6 +133,6 @@ class TestMD5(object): hashes.MD5(), digest_size=16, block_size=64, - only_if=lambda backend: backend.hashes.supported(hashes.MD5), + only_if=lambda backend: backend.hash_supported(hashes.MD5), skip_message="Does not support MD5", ) diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py index a44838cf..992bcb1a 100644 --- a/tests/hazmat/primitives/test_hmac.py +++ b/tests/hazmat/primitives/test_hmac.py @@ -19,6 +19,7 @@ import pytest import six +from cryptography.exceptions import AlreadyFinalized from cryptography.hazmat.primitives import hashes, hmac from .utils import generate_base_hmac_test @@ -27,7 +28,7 @@ from .utils import generate_base_hmac_test class TestHMAC(object): test_copy = generate_base_hmac_test( hashes.MD5(), - only_if=lambda backend: backend.hashes.supported(hashes.MD5), + only_if=lambda backend: backend.hash_supported(hashes.MD5), skip_message="Does not support MD5", ) @@ -37,14 +38,28 @@ class TestHMAC(object): h.update(six.u("\u00FC")) def test_copy_backend_object(self): - pretend_hmac = pretend.stub(copy_ctx=lambda a: True) + pretend_hmac = pretend.stub() pretend_backend = pretend.stub(hmacs=pretend_hmac) - pretend_ctx = pretend.stub() + copied_ctx = pretend.stub() + pretend_ctx = pretend.stub(copy=lambda: copied_ctx) h = hmac.HMAC(b"key", hashes.SHA1(), backend=pretend_backend, ctx=pretend_ctx) assert h._backend is pretend_backend assert h.copy()._backend is pretend_backend - def test_hmac_algorithm_instance(self): + def test_hmac_algorithm_instance(self, backend): with pytest.raises(TypeError): - hmac.HMAC(b"key", hashes.SHA1) + hmac.HMAC(b"key", hashes.SHA1, backend=backend) + + def test_raises_after_finalize(self, backend): + h = hmac.HMAC(b"key", hashes.SHA1(), backend=backend) + h.finalize() + + with pytest.raises(AlreadyFinalized): + h.update(b"foo") + + with pytest.raises(AlreadyFinalized): + h.copy() + + with pytest.raises(AlreadyFinalized): + h.finalize() diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py index 52d592b6..7d0f156a 100644 --- a/tests/hazmat/primitives/test_hmac_vectors.py +++ b/tests/hazmat/primitives/test_hmac_vectors.py @@ -16,95 +16,95 @@ from __future__ import absolute_import, division, print_function from cryptography.hazmat.primitives import hashes from .utils import generate_hmac_test -from ...utils import load_hash_vectors_from_file +from ...utils import load_hash_vectors class TestHMAC_MD5(object): test_hmac_md5 = generate_hmac_test( - load_hash_vectors_from_file, + load_hash_vectors, "HMAC", [ "rfc-2202-md5.txt", ], hashes.MD5(), - only_if=lambda backend: backend.hashes.supported(hashes.MD5), + only_if=lambda backend: backend.hash_supported(hashes.MD5), skip_message="Does not support MD5", ) class TestHMAC_SHA1(object): test_hmac_sha1 = generate_hmac_test( - load_hash_vectors_from_file, + load_hash_vectors, "HMAC", [ "rfc-2202-sha1.txt", ], hashes.SHA1(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA1), + only_if=lambda backend: backend.hash_supported(hashes.SHA1), skip_message="Does not support SHA1", ) class TestHMAC_SHA224(object): test_hmac_sha224 = generate_hmac_test( - load_hash_vectors_from_file, + load_hash_vectors, "HMAC", [ "rfc-4231-sha224.txt", ], hashes.SHA224(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA224), + only_if=lambda backend: backend.hash_supported(hashes.SHA224), skip_message="Does not support SHA224", ) class TestHMAC_SHA256(object): test_hmac_sha256 = generate_hmac_test( - load_hash_vectors_from_file, + load_hash_vectors, "HMAC", [ "rfc-4231-sha256.txt", ], hashes.SHA256(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA256), + only_if=lambda backend: backend.hash_supported(hashes.SHA256), skip_message="Does not support SHA256", ) class TestHMAC_SHA384(object): test_hmac_sha384 = generate_hmac_test( - load_hash_vectors_from_file, + load_hash_vectors, "HMAC", [ "rfc-4231-sha384.txt", ], hashes.SHA384(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA384), + only_if=lambda backend: backend.hash_supported(hashes.SHA384), skip_message="Does not support SHA384", ) class TestHMAC_SHA512(object): test_hmac_sha512 = generate_hmac_test( - load_hash_vectors_from_file, + load_hash_vectors, "HMAC", [ "rfc-4231-sha512.txt", ], hashes.SHA512(), - only_if=lambda backend: backend.hashes.supported(hashes.SHA512), + only_if=lambda backend: backend.hash_supported(hashes.SHA512), skip_message="Does not support SHA512", ) class TestHMAC_RIPEMD160(object): test_hmac_ripemd160 = generate_hmac_test( - load_hash_vectors_from_file, + load_hash_vectors, "HMAC", [ "rfc-2286-ripemd160.txt", ], hashes.RIPEMD160(), - only_if=lambda backend: backend.hashes.supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160), skip_message="Does not support RIPEMD160", ) diff --git a/tests/hazmat/primitives/test_utils.py b/tests/hazmat/primitives/test_utils.py index d7247e67..c39364c7 100644 --- a/tests/hazmat/primitives/test_utils.py +++ b/tests/hazmat/primitives/test_utils.py @@ -2,7 +2,8 @@ import pytest from .utils import ( base_hash_test, encrypt_test, hash_test, long_string_hash_test, - base_hmac_test, hmac_test + base_hmac_test, hmac_test, stream_encryption_test, aead_test, + aead_exception_test, aead_tag_exception_test, ) @@ -17,6 +18,39 @@ class TestEncryptTest(object): assert exc_info.value.args[0] == "message!" +class TestAEADTest(object): + def test_skips_if_only_if_returns_false(self): + with pytest.raises(pytest.skip.Exception) as exc_info: + aead_test( + None, None, None, None, + only_if=lambda backend: False, + skip_message="message!" + ) + assert exc_info.value.args[0] == "message!" + + +class TestAEADExceptionTest(object): + def test_skips_if_only_if_returns_false(self): + with pytest.raises(pytest.skip.Exception) as exc_info: + aead_exception_test( + None, None, None, + only_if=lambda backend: False, + skip_message="message!" + ) + assert exc_info.value.args[0] == "message!" + + +class TestAEADTagExceptionTest(object): + def test_skips_if_only_if_returns_false(self): + with pytest.raises(pytest.skip.Exception) as exc_info: + aead_tag_exception_test( + None, None, None, + only_if=lambda backend: False, + skip_message="message!" + ) + assert exc_info.value.args[0] == "message!" + + class TestHashTest(object): def test_skips_if_only_if_returns_false(self): with pytest.raises(pytest.skip.Exception) as exc_info: @@ -70,3 +104,14 @@ class TestBaseHMACTest(object): skip_message="message!" ) assert exc_info.value.args[0] == "message!" + + +class TestStreamEncryptionTest(object): + def test_skips_if_only_if_returns_false(self): + with pytest.raises(pytest.skip.Exception) as exc_info: + stream_encryption_test( + None, None, None, + only_if=lambda backend: False, + skip_message="message!" + ) + assert exc_info.value.args[0] == "message!" diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index e6e97d1d..705983a0 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -4,9 +4,13 @@ import os import pytest from cryptography.hazmat.bindings import _ALL_BACKENDS -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives import hmac +from cryptography.hazmat.primitives import hashes, hmac from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.exceptions import ( + AlreadyFinalized, NotYetFinalized, AlreadyUpdated, InvalidTag, +) + +from ...utils import load_vectors_from_file def generate_encrypt_test(param_loader, path, file_names, cipher_factory, @@ -15,7 +19,10 @@ def generate_encrypt_test(param_loader, path, file_names, cipher_factory, def test_encryption(self): for backend in _ALL_BACKENDS: for file_name in file_names: - for params in param_loader(os.path.join(path, file_name)): + for params in load_vectors_from_file( + os.path.join(path, file_name), + param_loader + ): yield ( encrypt_test, backend, @@ -37,7 +44,7 @@ def encrypt_test(backend, cipher_factory, mode_factory, params, only_if, cipher = Cipher( cipher_factory(**params), mode_factory(**params), - backend + backend=backend ) encryptor = cipher.encryptor() actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) @@ -49,12 +56,123 @@ def encrypt_test(backend, cipher_factory, mode_factory, params, only_if, assert actual_plaintext == binascii.unhexlify(plaintext) +def generate_aead_test(param_loader, path, file_names, cipher_factory, + mode_factory, only_if, skip_message): + def test_aead(self): + for backend in _ALL_BACKENDS: + for file_name in file_names: + for params in load_vectors_from_file( + os.path.join(path, file_name), + param_loader + ): + yield ( + aead_test, + backend, + cipher_factory, + mode_factory, + params, + only_if, + skip_message + ) + return test_aead + + +def aead_test(backend, cipher_factory, mode_factory, params, only_if, + skip_message): + if not only_if(backend): + pytest.skip(skip_message) + if params.get("pt") is not None: + plaintext = params.pop("pt") + ciphertext = params.pop("ct") + aad = params.pop("aad") + if params.get("fail") is True: + cipher = Cipher( + cipher_factory(binascii.unhexlify(params["key"])), + mode_factory(binascii.unhexlify(params["iv"]), + binascii.unhexlify(params["tag"])), + backend + ) + decryptor = cipher.decryptor() + decryptor.authenticate_additional_data(binascii.unhexlify(aad)) + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + with pytest.raises(InvalidTag): + decryptor.finalize() + else: + cipher = Cipher( + cipher_factory(binascii.unhexlify(params["key"])), + mode_factory(binascii.unhexlify(params["iv"]), None), + backend + ) + encryptor = cipher.encryptor() + encryptor.authenticate_additional_data(binascii.unhexlify(aad)) + actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) + actual_ciphertext += encryptor.finalize() + tag_len = len(params["tag"]) + assert binascii.hexlify(encryptor.tag)[:tag_len] == params["tag"] + cipher = Cipher( + cipher_factory(binascii.unhexlify(params["key"])), + mode_factory(binascii.unhexlify(params["iv"]), + binascii.unhexlify(params["tag"])), + backend + ) + decryptor = cipher.decryptor() + decryptor.authenticate_additional_data(binascii.unhexlify(aad)) + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + actual_plaintext += decryptor.finalize() + assert actual_plaintext == binascii.unhexlify(plaintext) + + +def generate_stream_encryption_test(param_loader, path, file_names, + cipher_factory, only_if=None, + skip_message=None): + def test_stream_encryption(self): + for backend in _ALL_BACKENDS: + for file_name in file_names: + for params in load_vectors_from_file( + os.path.join(path, file_name), + param_loader + ): + yield ( + stream_encryption_test, + backend, + cipher_factory, + params, + only_if, + skip_message + ) + return test_stream_encryption + + +def stream_encryption_test(backend, cipher_factory, params, only_if, + skip_message): + if not only_if(backend): + pytest.skip(skip_message) + plaintext = params.pop("plaintext") + ciphertext = params.pop("ciphertext") + offset = params.pop("offset") + cipher = Cipher(cipher_factory(**params), None, backend=backend) + encryptor = cipher.encryptor() + # throw away offset bytes + encryptor.update(b"\x00" * int(offset)) + actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) + actual_ciphertext += encryptor.finalize() + assert actual_ciphertext == binascii.unhexlify(ciphertext) + decryptor = cipher.decryptor() + decryptor.update(b"\x00" * int(offset)) + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + actual_plaintext += decryptor.finalize() + assert actual_plaintext == binascii.unhexlify(plaintext) + + def generate_hash_test(param_loader, path, file_names, hash_cls, only_if=None, skip_message=None): def test_hash(self): for backend in _ALL_BACKENDS: for file_name in file_names: - for params in param_loader(os.path.join(path, file_name)): + for params in load_vectors_from_file( + os.path.join(path, file_name), + param_loader + ): yield ( hash_test, backend, @@ -105,6 +223,12 @@ def base_hash_test(backend, algorithm, digest_size, block_size, only_if, assert m != m_copy assert m._ctx != m_copy._ctx + m.update(b"abc") + copy = m.copy() + copy.update(b"123") + m.update(b"123") + assert copy.finalize() == m.finalize() + def generate_long_string_hash_test(hash_factory, md, only_if=None, skip_message=None): @@ -134,7 +258,10 @@ def generate_hmac_test(param_loader, path, file_names, algorithm, def test_hmac(self): for backend in _ALL_BACKENDS: for file_name in file_names: - for params in param_loader(os.path.join(path, file_name)): + for params in load_vectors_from_file( + os.path.join(path, file_name), + param_loader + ): yield ( hmac_test, backend, @@ -152,7 +279,7 @@ def hmac_test(backend, algorithm, params, only_if, skip_message): msg = params[0] md = params[1] key = params[2] - h = hmac.HMAC(binascii.unhexlify(key), algorithm) + h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend) h.update(binascii.unhexlify(msg)) assert h.finalize() == binascii.unhexlify(md.encode("ascii")) @@ -174,7 +301,90 @@ def base_hmac_test(backend, algorithm, only_if, skip_message): if only_if is not None and not only_if(backend): pytest.skip(skip_message) key = b"ab" - h = hmac.HMAC(binascii.unhexlify(key), algorithm) + h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend) h_copy = h.copy() assert h != h_copy assert h._ctx != h_copy._ctx + + +def generate_aead_exception_test(cipher_factory, mode_factory, + only_if, skip_message): + def test_aead_exception(self): + for backend in _ALL_BACKENDS: + yield ( + aead_exception_test, + backend, + cipher_factory, + mode_factory, + only_if, + skip_message + ) + return test_aead_exception + + +def aead_exception_test(backend, cipher_factory, mode_factory, + only_if, skip_message): + if not only_if(backend): + pytest.skip(skip_message) + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24)), + backend + ) + encryptor = cipher.encryptor() + encryptor.update(b"a" * 16) + with pytest.raises(NotYetFinalized): + encryptor.tag + with pytest.raises(AlreadyUpdated): + encryptor.authenticate_additional_data(b"b" * 16) + encryptor.finalize() + with pytest.raises(AlreadyFinalized): + encryptor.authenticate_additional_data(b"b" * 16) + with pytest.raises(AlreadyFinalized): + encryptor.update(b"b" * 16) + with pytest.raises(AlreadyFinalized): + encryptor.finalize() + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16), + backend + ) + decryptor = cipher.decryptor() + decryptor.update(b"a" * 16) + with pytest.raises(AttributeError): + decryptor.tag + + +def generate_aead_tag_exception_test(cipher_factory, mode_factory, + only_if, skip_message): + def test_aead_tag_exception(self): + for backend in _ALL_BACKENDS: + yield ( + aead_tag_exception_test, + backend, + cipher_factory, + mode_factory, + only_if, + skip_message + ) + return test_aead_tag_exception + + +def aead_tag_exception_test(backend, cipher_factory, mode_factory, + only_if, skip_message): + if not only_if(backend): + pytest.skip(skip_message) + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24)), + backend + ) + with pytest.raises(ValueError): + cipher.decryptor() + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16), + backend + ) + with pytest.raises(ValueError): + cipher.encryptor() diff --git a/tests/test_utils.py b/tests/test_utils.py index 0692c8d1..5c58fd76 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -17,14 +17,12 @@ import textwrap import pytest from .utils import ( - load_nist_vectors, load_nist_vectors_from_file, load_cryptrec_vectors, - load_cryptrec_vectors_from_file, load_openssl_vectors, - load_openssl_vectors_from_file, load_hash_vectors, - load_hash_vectors_from_file + load_nist_vectors, load_vectors_from_file, load_cryptrec_vectors, + load_openssl_vectors, load_hash_vectors, ) -def test_load_nist_vectors_encrypt(): +def test_load_nist_vectors(): vector_data = textwrap.dedent(""" # CAVS 11.1 # Config info for aes_values @@ -62,7 +60,7 @@ def test_load_nist_vectors_encrypt(): PLAINTEXT = 9798c4640bad75c7c3227db910174e72 """).splitlines() - assert load_nist_vectors(vector_data, "ENCRYPT") == [ + assert load_nist_vectors(vector_data) == [ { "key": b"00000000000000000000000000000000", "iv": b"00000000000000000000000000000000", @@ -75,118 +73,6 @@ def test_load_nist_vectors_encrypt(): "plaintext": b"9798c4640bad75c7c3227db910174e72", "ciphertext": b"a9a1631bf4996954ebc093957b234589", }, - ] - - -def test_load_nist_vectors_decrypt(): - vector_data = textwrap.dedent(""" - # CAVS 11.1 - # Config info for aes_values - # AESVS GFSbox test data for CBC - # State : Encrypt and Decrypt - # Key Length : 128 - # Generated on Fri Apr 22 15:11:33 2011 - - [ENCRYPT] - - COUNT = 0 - KEY = 00000000000000000000000000000000 - IV = 00000000000000000000000000000000 - PLAINTEXT = f34481ec3cc627bacd5dc3fb08f273e6 - CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e - - COUNT = 1 - KEY = 00000000000000000000000000000000 - IV = 00000000000000000000000000000000 - PLAINTEXT = 9798c4640bad75c7c3227db910174e72 - CIPHERTEXT = a9a1631bf4996954ebc093957b234589 - - [DECRYPT] - - COUNT = 0 - KEY = 00000000000000000000000000000000 - IV = 00000000000000000000000000000000 - CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e - PLAINTEXT = f34481ec3cc627bacd5dc3fb08f273e6 - - COUNT = 1 - KEY = 00000000000000000000000000000000 - IV = 00000000000000000000000000000000 - CIPHERTEXT = a9a1631bf4996954ebc093957b234589 - PLAINTEXT = 9798c4640bad75c7c3227db910174e72 - """).splitlines() - - assert load_nist_vectors(vector_data, "DECRYPT") == [ - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"f34481ec3cc627bacd5dc3fb08f273e6", - "ciphertext": b"0336763e966d92595a567cc9ce537f5e", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"9798c4640bad75c7c3227db910174e72", - "ciphertext": b"a9a1631bf4996954ebc093957b234589", - }, - ] - - -def test_load_nist_vectors_from_file_encrypt(): - assert load_nist_vectors_from_file( - os.path.join("ciphers", "AES", "CBC", "CBCGFSbox128.rsp"), - "ENCRYPT" - ) == [ - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"f34481ec3cc627bacd5dc3fb08f273e6", - "ciphertext": b"0336763e966d92595a567cc9ce537f5e", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"9798c4640bad75c7c3227db910174e72", - "ciphertext": b"a9a1631bf4996954ebc093957b234589", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"96ab5c2ff612d9dfaae8c31f30c42168", - "ciphertext": b"ff4f8391a6a40ca5b25d23bedd44a597", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"6a118a874519e64e9963798a503f1d35", - "ciphertext": b"dc43be40be0e53712f7e2bf5ca707209", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"cb9fceec81286ca3e989bd979b0cb284", - "ciphertext": b"92beedab1895a94faa69b632e5cc47ce", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"b26aeb1874e47ca8358ff22378f09144", - "ciphertext": b"459264f4798f6a78bacb89c15ed3d601", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"58c8e00b2631686d54eab84b91f0aca1", - "ciphertext": b"08a4e2efec8a8e3312ca7460b9040bbf", - }, - ] - - -def test_load_nist_vectors_from_file_decrypt(): - assert load_nist_vectors_from_file( - os.path.join("ciphers", "AES", "CBC", "CBCGFSbox128.rsp"), - "DECRYPT", - ) == [ { "key": b"00000000000000000000000000000000", "iv": b"00000000000000000000000000000000", @@ -199,36 +85,6 @@ def test_load_nist_vectors_from_file_decrypt(): "plaintext": b"9798c4640bad75c7c3227db910174e72", "ciphertext": b"a9a1631bf4996954ebc093957b234589", }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"96ab5c2ff612d9dfaae8c31f30c42168", - "ciphertext": b"ff4f8391a6a40ca5b25d23bedd44a597", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"6a118a874519e64e9963798a503f1d35", - "ciphertext": b"dc43be40be0e53712f7e2bf5ca707209", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"cb9fceec81286ca3e989bd979b0cb284", - "ciphertext": b"92beedab1895a94faa69b632e5cc47ce", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"b26aeb1874e47ca8358ff22378f09144", - "ciphertext": b"459264f4798f6a78bacb89c15ed3d601", - }, - { - "key": b"00000000000000000000000000000000", - "iv": b"00000000000000000000000000000000", - "plaintext": b"58c8e00b2631686d54eab84b91f0aca1", - "ciphertext": b"08a4e2efec8a8e3312ca7460b9040bbf", - }, ] @@ -286,20 +142,6 @@ def test_load_cryptrec_vectors_invalid(): load_cryptrec_vectors(vector_data) -def test_load_cryptrec_vectors_from_file_encrypt(): - test_set = load_cryptrec_vectors_from_file( - os.path.join("ciphers", "Camellia", "camellia-128-ecb.txt"), - ) - assert test_set[0] == ( - { - "key": b"00000000000000000000000000000000", - "plaintext": b"80000000000000000000000000000000", - "ciphertext": b"07923A39EB0A817D1C4D87BDB82D1F1C", - } - ) - assert len(test_set) == 1280 - - def test_load_openssl_vectors(): vector_data = textwrap.dedent( """ @@ -351,39 +193,6 @@ def test_load_openssl_vectors(): ] -def test_load_openssl_vectors_from_file(): - test_list = load_openssl_vectors_from_file( - os.path.join("ciphers", "Camellia", "camellia-ofb.txt") - ) - assert len(test_list) == 24 - assert test_list[:4] == [ - { - "key": b"2B7E151628AED2A6ABF7158809CF4F3C", - "iv": b"000102030405060708090A0B0C0D0E0F", - "plaintext": b"6BC1BEE22E409F96E93D7E117393172A", - "ciphertext": b"14F7646187817EB586599146B82BD719", - }, - { - "key": b"2B7E151628AED2A6ABF7158809CF4F3C", - "iv": b"50FE67CC996D32B6DA0937E99BAFEC60", - "plaintext": b"AE2D8A571E03AC9C9EB76FAC45AF8E51", - "ciphertext": b"25623DB569CA51E01482649977E28D84", - }, - { - "key": b"2B7E151628AED2A6ABF7158809CF4F3C", - "iv": b"D9A4DADA0892239F6B8B3D7680E15674", - "plaintext": b"30C81C46A35CE411E5FBC1191A0A52EF", - "ciphertext": b"C776634A60729DC657D12B9FCA801E98", - }, - { - "key": b"2B7E151628AED2A6ABF7158809CF4F3C", - "iv": b"A78819583F0308E7A6BF36B1386ABF23", - "plaintext": b"F69F2445DF4F9B17AD2B417BE66C3710", - "ciphertext": b"D776379BE0E50825E681DA1A4C980E8E", - }, - ] - - def test_load_hash_vectors(): vector_data = textwrap.dedent(""" @@ -442,14 +251,170 @@ def test_load_hash_vectors_bad_data(): load_hash_vectors(vector_data) -def test_load_hash_vectors_from_file(): - test_list = load_hash_vectors_from_file( - os.path.join("hashes", "MD5", "rfc-1321.txt") +def test_load_vectors_from_file(): + vectors = load_vectors_from_file( + os.path.join("ciphers", "Blowfish", "bf-cfb.txt"), + load_nist_vectors, ) - assert len(test_list) == 7 - assert test_list[:4] == [ - (b"", "d41d8cd98f00b204e9800998ecf8427e"), - (b"61", "0cc175b9c0f1b6a831c399e269772661"), - (b"616263", "900150983cd24fb0d6963f7d28e17f72"), - (b"6d65737361676520646967657374", "f96b697d7cb7938d525a2f31aaf161d0"), + assert vectors == [ + { + "key": b"0123456789ABCDEFF0E1D2C3B4A59687", + "iv": b"FEDCBA9876543210", + "plaintext": ( + b"37363534333231204E6F77206973207468652074696D6520666F722000" + ), + "ciphertext": ( + b"E73214A2822139CAF26ECF6D2EB9E76E3DA3DE04D1517200519D57A6C3" + ), + } + ] + + +def test_load_nist_gcm_vectors(): + vector_data = textwrap.dedent(""" + [Keylen = 128] + [IVlen = 96] + [PTlen = 0] + [AADlen = 0] + [Taglen = 128] + + Count = 0 + Key = 11754cd72aec309bf52f7687212e8957 + IV = 3c819d9a9bed087615030b65 + PT = + AAD = + CT = + Tag = 250327c674aaf477aef2675748cf6971 + + Count = 1 + Key = 272f16edb81a7abbea887357a58c1917 + IV = 794ec588176c703d3d2a7a07 + PT = + AAD = + CT = + Tag = b6e6f197168f5049aeda32dafbdaeb + + Count = 2 + Key = a49a5e26a2f8cb63d05546c2a62f5343 + IV = 907763b19b9b4ab6bd4f0281 + CT = + AAD = + Tag = a2be08210d8c470a8df6e8fbd79ec5cf + FAIL + + Count = 3 + Key = 5c1155084cc0ede76b3bc22e9f7574ef + IV = 9549e4ba69a61cad7856efc1 + PT = d1448fa852b84408e2dad8381f363de7 + AAD = e98e9d9c618e46fef32660976f854ee3 + CT = f78b60ca125218493bea1c50a2e12ef4 + Tag = d72da7f5c6cf0bca7242c71835809449 + + [Keylen = 128] + [IVlen = 96] + [PTlen = 0] + [AADlen = 0] + [Taglen = 120] + + Count = 0 + Key = eac258e99c55e6ae8ef1da26640613d7 + IV = 4e8df20faaf2c8eebe922902 + CT = + AAD = + Tag = e39aeaebe86aa309a4d062d6274339 + PT = + + Count = 1 + Key = 3726cf02fcc6b8639a5497652c94350d + IV = 55fef82cde693ce76efcc193 + CT = + AAD = + Tag = 3d68111a81ed22d2ef5bccac4fc27f + FAIL + + Count = 2 + Key = f202299d5fd74f03b12d2119a6c4c038 + IV = eec51e7958c3f20a1bb71815 + CT = + AAD = + Tag = a81886b3fb26e51fca87b267e1e157 + FAIL + + Count = 3 + Key = fd52925f39546b4c55ffb6b20c59898c + IV = f5cf3227444afd905a5f6dba + CT = + AAD = + Tag = 1665b0f1a0b456e1664cfd3de08ccd + PT = + + [Keylen = 128] + [IVlen = 8] + [PTlen = 104] + [AADlen = 0] + [Taglen = 128] + + Count = 0 + Key = 58fab7632bcf10d2bcee58520bf37414 + IV = 3c + CT = 15c4db4cbb451211179d57017f + AAD = + Tag = eae841d4355feeb3f786bc86625f1e5b + FAIL + """).splitlines() + assert load_nist_vectors(vector_data) == [ + {'aad': b'', + 'pt': b'', + 'iv': b'3c819d9a9bed087615030b65', + 'tag': b'250327c674aaf477aef2675748cf6971', + 'key': b'11754cd72aec309bf52f7687212e8957', + 'ct': b''}, + {'aad': b'', + 'pt': b'', + 'iv': b'794ec588176c703d3d2a7a07', + 'tag': b'b6e6f197168f5049aeda32dafbdaeb', + 'key': b'272f16edb81a7abbea887357a58c1917', + 'ct': b''}, + {'aad': b'', + 'iv': b'907763b19b9b4ab6bd4f0281', + 'tag': b'a2be08210d8c470a8df6e8fbd79ec5cf', + 'key': b'a49a5e26a2f8cb63d05546c2a62f5343', + 'ct': b'', + 'fail': True}, + {'aad': b'e98e9d9c618e46fef32660976f854ee3', + 'pt': b'd1448fa852b84408e2dad8381f363de7', + 'iv': b'9549e4ba69a61cad7856efc1', + 'tag': b'd72da7f5c6cf0bca7242c71835809449', + 'key': b'5c1155084cc0ede76b3bc22e9f7574ef', + 'ct': b'f78b60ca125218493bea1c50a2e12ef4'}, + {'aad': b'', + 'pt': b'', + 'iv': b'4e8df20faaf2c8eebe922902', + 'tag': b'e39aeaebe86aa309a4d062d6274339', + 'key': b'eac258e99c55e6ae8ef1da26640613d7', + 'ct': b''}, + {'aad': b'', + 'iv': b'55fef82cde693ce76efcc193', + 'tag': b'3d68111a81ed22d2ef5bccac4fc27f', + 'key': b'3726cf02fcc6b8639a5497652c94350d', + 'ct': b'', + 'fail': True}, + {'aad': b'', + 'iv': b'eec51e7958c3f20a1bb71815', + 'tag': b'a81886b3fb26e51fca87b267e1e157', + 'key': b'f202299d5fd74f03b12d2119a6c4c038', + 'ct': b'', + 'fail': True}, + {'aad': b'', + 'pt': b'', + 'iv': b'f5cf3227444afd905a5f6dba', + 'tag': b'1665b0f1a0b456e1664cfd3de08ccd', + 'key': b'fd52925f39546b4c55ffb6b20c59898c', + 'ct': b''}, + {'aad': b'', + 'iv': b'3c', + 'tag': b'eae841d4355feeb3f786bc86625f1e5b', + 'key': b'58fab7632bcf10d2bcee58520bf37414', + 'ct': b'15c4db4cbb451211179d57017f', + 'fail': True}, ] diff --git a/tests/utils.py b/tests/utils.py index 99ba2e2f..94f97d59 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -14,58 +14,44 @@ import os.path -def load_nist_vectors(vector_data, op): - section, count, data = None, None, {} +def load_vectors_from_file(filename, loader): + base = os.path.join( + os.path.dirname(__file__), "hazmat", "primitives", "vectors", + ) + with open(os.path.join(base, filename), "r") as vector_file: + return loader(vector_file) + + +def load_nist_vectors(vector_data): + test_data = None + data = [] for line in vector_data: line = line.strip() - # Blank lines are ignored - if not line: - continue - - # Lines starting with # are comments - if line.startswith("#"): + # Blank lines, comments, and section headers are ignored + if not line or line.startswith("#") or (line.startswith("[") + and line.endswith("]")): continue - # Look for section headers - if line.startswith("[") and line.endswith("]"): - section = line[1:-1] - data[section] = {} + if line.strip() == "FAIL": + test_data["fail"] = True continue # Build our data using a simple Key = Value format - name, value = line.split(" = ") + name, value = [c.strip() for c in line.split("=")] # COUNT is a special token that indicates a new block of data if name.upper() == "COUNT": - count = value - data[section][count] = {} + test_data = {} + data.append(test_data) + continue # For all other tokens we simply want the name, value stored in # the dictionary else: - data[section][count][name.lower()] = value.encode("ascii") - - # We want to test only for a particular operation, we sort them for the - # benefit of the tests of this function. - return [v for k, v in sorted(data[op].items(), key=lambda kv: kv[0])] - - -def load_nist_vectors_from_file(filename, op): - base = os.path.join( - os.path.dirname(__file__), "hazmat", "primitives", "vectors", - ) - with open(os.path.join(base, filename), "r") as vector_file: - return load_nist_vectors(vector_file, op) - + test_data[name.lower()] = value.encode("ascii") -def load_cryptrec_vectors_from_file(filename): - base = os.path.join( - os.path.dirname(__file__), - "hazmat", "primitives", "vectors", - ) - with open(os.path.join(base, filename), "r") as vector_file: - return load_cryptrec_vectors(vector_file) + return data def load_cryptrec_vectors(vector_data): @@ -96,15 +82,6 @@ def load_cryptrec_vectors(vector_data): return cryptrec_list -def load_openssl_vectors_from_file(filename): - base = os.path.join( - os.path.dirname(__file__), - "hazmat", "primitives", "vectors", - ) - with open(os.path.join(base, filename), "r") as vector_file: - return load_openssl_vectors(vector_file) - - def load_openssl_vectors(vector_data): vectors = [] @@ -166,11 +143,3 @@ def load_hash_vectors(vector_data): else: raise ValueError("Unknown line in hash vector") return vectors - - -def load_hash_vectors_from_file(filename): - base = os.path.join( - os.path.dirname(__file__), "hazmat", "primitives", "vectors" - ) - with open(os.path.join(base, filename), "r") as vector_file: - return load_hash_vectors(vector_file) @@ -7,7 +7,7 @@ deps = coverage pretend commands = - coverage run --source=cryptography/,tests/ -m pytest + coverage run --source=cryptography/,tests/ -m pytest --strict coverage report -m [testenv:docs] |