aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--AUTHORS.rst1
-rw-r--r--CONTRIBUTING.rst8
-rw-r--r--cryptography/exceptions.py4
-rw-r--r--cryptography/hazmat/bindings/openssl/backend.py274
-rw-r--r--cryptography/hazmat/bindings/openssl/evp.py5
-rw-r--r--cryptography/hazmat/bindings/openssl/ssl.py189
-rw-r--r--cryptography/hazmat/primitives/ciphers/algorithms.py33
-rw-r--r--cryptography/hazmat/primitives/ciphers/base.py25
-rw-r--r--cryptography/hazmat/primitives/ciphers/modes.py23
-rw-r--r--cryptography/hazmat/primitives/hashes.py42
-rw-r--r--cryptography/hazmat/primitives/hmac.py28
-rw-r--r--cryptography/hazmat/primitives/interfaces.py17
-rw-r--r--cryptography/hazmat/primitives/padding.py12
-rw-r--r--cryptography/utils.py21
-rw-r--r--dev-requirements.txt2
-rw-r--r--docs/architecture.rst5
-rw-r--r--docs/conf.py16
-rw-r--r--docs/contributing.rst12
-rw-r--r--docs/exceptions.rst6
-rw-r--r--docs/glossary.rst2
-rw-r--r--docs/hazmat/primitives/cryptographic-hashes.rst29
-rw-r--r--docs/hazmat/primitives/hmac.rst14
-rw-r--r--docs/hazmat/primitives/interfaces.rst26
-rw-r--r--docs/hazmat/primitives/symmetric-encryption.rst55
-rw-r--r--docs/security.rst2
-rw-r--r--tests/hazmat/bindings/test_openssl.py19
-rw-r--r--tests/hazmat/primitives/test_3des.py14
-rw-r--r--tests/hazmat/primitives/test_aes.py14
-rw-r--r--tests/hazmat/primitives/test_arc4.py43
-rw-r--r--tests/hazmat/primitives/test_block.py23
-rw-r--r--tests/hazmat/primitives/test_blowfish.py18
-rw-r--r--tests/hazmat/primitives/test_camellia.py18
-rw-r--r--tests/hazmat/primitives/test_cast5.py10
-rw-r--r--tests/hazmat/primitives/test_ciphers.py21
-rw-r--r--tests/hazmat/primitives/test_hash_vectors.py38
-rw-r--r--tests/hazmat/primitives/test_hashes.py36
-rw-r--r--tests/hazmat/primitives/test_hmac.py21
-rw-r--r--tests/hazmat/primitives/test_hmac_vectors.py30
-rw-r--r--tests/hazmat/primitives/test_padding.py2
-rw-r--r--tests/hazmat/primitives/test_utils.py13
-rw-r--r--tests/hazmat/primitives/utils.py65
-rw-r--r--tests/test_utils.py373
-rw-r--r--tests/utils.py75
-rw-r--r--tox.ini6
45 files changed, 1101 insertions, 590 deletions
diff --git a/.gitignore b/.gitignore
index 5d187cfe..9b8c49ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
__pycache__/
_build/
.tox/
+.cache/
*.egg-info/
.coverage
cffi-*.egg/
diff --git a/AUTHORS.rst b/AUTHORS.rst
index b3b7f35d..0ef9958d 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -10,4 +10,3 @@ PGP key fingerprints are enclosed in parentheses.
* Christian Heimes <christian@python.org>
* Paul Kehrer <paul.l.kehrer@gmail.com>
* Jarret Raim <jarito@gmail.com>
-
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..c2e71493 100644
--- a/cryptography/exceptions.py
+++ b/cryptography/exceptions.py
@@ -14,3 +14,7 @@
class UnsupportedAlgorithm(Exception):
pass
+
+
+class AlreadyFinalized(Exception):
+ pass
diff --git a/cryptography/hazmat/bindings/openssl/backend.py b/cryptography/hazmat/bindings/openssl/backend.py
index 0c3d22d5..92cd3868 100644
--- a/cryptography/hazmat/bindings/openssl/backend.py
+++ b/cryptography/hazmat/bindings/openssl/backend.py
@@ -18,10 +18,11 @@ import sys
import cffi
+from cryptography import utils
from cryptography.exceptions import UnsupportedAlgorithm
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
@@ -63,9 +64,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 +123,70 @@ 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")
+ )
+
+ 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)
+
class GetCipherByName(object):
def __init__(self, fmt):
@@ -133,18 +197,19 @@ class GetCipherByName(object):
return backend.lib.EVP_get_cipherbyname(cipher_name.encode("ascii"))
-@interfaces.register(interfaces.CipherContext)
+@utils.register_interface(interfaces.CipherContext)
class _CipherContext(object):
_ENCRYPT = 1
_DECRYPT = 0
def __init__(self, backend, cipher, mode, operation):
self._backend = backend
+ self._cipher = cipher
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:
@@ -185,9 +250,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,8 +259,7 @@ 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
@@ -205,146 +268,99 @@ class _CipherContext(object):
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()
+@utils.register_interface(interfaces.HashContext)
+class _HashContext(object):
+ def __init__(self, backend, algorithm, ctx=None):
+ self.algorithm = algorithm
- 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 create_encrypt_ctx(self, cipher, mode):
- return _CipherContext(self._backend, cipher, mode,
- _CipherContext._ENCRYPT)
-
- def create_decrypt_ctx(self, cipher, mode):
- return _CipherContext(self._backend, cipher, mode,
- _CipherContext._DECRYPT)
+ self._backend = backend
+ 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
-class Hashes(object):
- def __init__(self, backend):
- super(Hashes, self).__init__()
- self._backend = backend
+ self._ctx = ctx
- 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)
+ 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/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/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..75a87265 100644
--- a/cryptography/hazmat/primitives/ciphers/algorithms.py
+++ b/cryptography/hazmat/primitives/ciphers/algorithms.py
@@ -13,14 +13,17 @@
from __future__ import absolute_import, division, print_function
+from cryptography import utils
+from cryptography.hazmat.primitives import interfaces
+
+@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
@@ -34,13 +37,13 @@ class AES(object):
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
@@ -54,13 +57,13 @@ class Camellia(object):
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:
@@ -78,13 +81,13 @@ class TripleDES(object):
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
@@ -98,13 +101,33 @@ class Blowfish(object):
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])
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
+ ))
+
+ @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):
self.key = key
# Verify that the key size matches the expected key size
diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py
index 1599308c..3d733afc 100644
--- a/cryptography/hazmat/primitives/ciphers/base.py
+++ b/cryptography/hazmat/primitives/ciphers/base.py
@@ -13,46 +13,49 @@
from __future__ import absolute_import, division, print_function
+from cryptography import utils
+from cryptography.exceptions import AlreadyFinalized
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,
)
+ 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))
+ return _CipherContext(self._backend.create_symmetric_encryption_ctx(
+ self.algorithm, self.mode
+ ))
def decryptor(self):
- return _CipherContext(
- self._backend.ciphers.create_decrypt_ctx(self.algorithm,
- self.mode))
+ return _CipherContext(self._backend.create_symmetric_decryption_ctx(
+ self.algorithm, self.mode
+ ))
-@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 ValueError("Context was already finalized")
+ raise AlreadyFinalized("Context was already finalized")
data = self._ctx.finalize()
self._ctx = None
return data
diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py
index e54872a6..1d0de689 100644
--- a/cryptography/hazmat/primitives/ciphers/modes.py
+++ b/cryptography/hazmat/primitives/ciphers/modes.py
@@ -13,49 +13,46 @@
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
diff --git a/cryptography/hazmat/primitives/hashes.py b/cryptography/hazmat/primitives/hashes.py
index bdad5e16..93fc8c42 100644
--- a/cryptography/hazmat/primitives/hashes.py
+++ b/cryptography/hazmat/primitives/hashes.py
@@ -15,10 +15,12 @@ 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):
if not isinstance(algorithm, interfaces.HashAlgorithm):
@@ -32,74 +34,82 @@ class Hash(object):
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..08dfae01 100644
--- a/cryptography/hazmat/primitives/hmac.py
+++ b/cryptography/hazmat/primitives/hmac.py
@@ -15,13 +15,14 @@ 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__()
if not isinstance(algorithm, interfaces.HashAlgorithm):
raise TypeError("Expected instance of interfaces.HashAlgorithm.")
self.algorithm = algorithm
@@ -33,19 +34,30 @@ class HMAC(object):
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..8cc9d42c 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)):
diff --git a/cryptography/hazmat/primitives/padding.py b/cryptography/hazmat/primitives/padding.py
index ddcadd89..2dbac752 100644
--- a/cryptography/hazmat/primitives/padding.py
+++ b/cryptography/hazmat/primitives/padding.py
@@ -13,12 +13,12 @@
import six
+from cryptography import utils
from cryptography.hazmat.primitives import interfaces
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)")
@@ -34,10 +34,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)
@@ -69,10 +68,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)
@@ -101,12 +99,12 @@ class _PKCS7UnpaddingContext(object):
if self._buffer is None:
raise ValueError("Context was already finalized")
- if not self._buffer:
+ if len(self._buffer) != self.block_size // 8:
raise ValueError("Invalid padding bytes")
pad_size = six.indexbytes(self._buffer, -1)
- if pad_size > self.block_size // 8:
+ if not (0 < pad_size <= self.block_size // 8):
raise ValueError("Invalid padding bytes")
mismatch = 0
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/dev-requirements.txt b/dev-requirements.txt
index 530ada91..1ec04223 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -5,3 +5,5 @@ pretend
pytest
sphinx
tox
+sphinx_rtd_theme
+-e .
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/conf.py b/docs/conf.py
index 69be32e9..77050e72 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -14,6 +14,12 @@
import os
import sys
+try:
+ import sphinx_rtd_theme
+except ImportError:
+ sphinx_rtd_theme = None
+
+
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -98,16 +104,18 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+
+if sphinx_rtd_theme:
+ html_theme = "sphinx_rtd_theme"
+ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+else:
+ html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 3b301842..97f31e0b 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:
diff --git a/docs/exceptions.rst b/docs/exceptions.rst
index 6ac11b3c..ab1b28fe 100644
--- a/docs/exceptions.rst
+++ b/docs/exceptions.rst
@@ -3,7 +3,13 @@ Exceptions
.. currentmodule:: cryptography.exceptions
+.. class:: AlreadyFinalized
+
+ This is raised when a context is used after being finalized.
+
+
.. class:: UnsupportedAlgorithm
This is raised when a backend doesn't support the requested algorithm (or
combination of algorithms).
+
diff --git a/docs/glossary.rst b/docs/glossary.rst
index e4fc8283..b6f2d06f 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -27,4 +27,4 @@ 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.
diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst
index 76ca20c0..52e87702 100644
--- a/docs/hazmat/primitives/cryptographic-hashes.rst
+++ b/docs/hazmat/primitives/cryptographic-hashes.rst
@@ -12,9 +12,9 @@ Message Digests
results (with a high probability) in different digests.
This is an implementation of
- :class:`cryptography.hazmat.primitives.interfaces.HashContext` meant to
+ :class:`~cryptography.hazmat.primitives.interfaces.HashContext` meant to
be used with
- :class:`cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+ :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
implementations to provide an incremental interface to calculating
various message digests.
@@ -27,19 +27,34 @@ Message Digests
>>> 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`_.
+
.. 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.
@@ -102,9 +117,13 @@ MD5
.. warning::
MD5 is a deprecated hash algorithm that has practical known collision
- attacks. You are strongly discouraged from using it.
+ attacks. You are strongly discouraged from using it. Existing applications
+ should strongly consider moving away.
.. class:: 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..cff2dbf1 100644
--- a/docs/hazmat/primitives/hmac.rst
+++ b/docs/hazmat/primitives/hmac.rst
@@ -36,15 +36,25 @@ message.
.. 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/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst
index 5f1a64a1..eef359d6 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -14,13 +14,22 @@ Symmetric Encryption
Symmetric encryption is a way to encrypt (hide the plaintext value) material
-where the encrypter and decrypter both use the same key.
+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* 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)
- Cipher objects combine an algorithm (such as AES) with a mode (such as
- CBC, CTR, or GCM). A simple example of encrypting (and then decrypting)
- content with AES is:
+ Cipher objects combine an algorithm (such as
+ :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`) with a
+ mode (such as
+ :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` or
+ :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR`). A simple
+ example of encrypting (and then decrypting) content with AES is:
.. doctest::
@@ -70,6 +79,7 @@ where the encrypter and decrypter both use the same key.
: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.
@@ -81,6 +91,10 @@ where the encrypter and decrypter both use the same key.
:return bytes: Returns the remainder of the data.
+ Once ``finalize`` is called this object can no longer be used and
+ :meth:`update` and :meth:`finalize` will raise
+ :class:`~cryptography.exceptions.AlreadyFinalized`.
+
Algorithms
~~~~~~~~~~
@@ -107,10 +121,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
@@ -143,12 +157,32 @@ Weak Ciphers
Blowfish is a block cipher developed by Bruce Schneier. It is known to be
susceptible to attacks when using weak keys. The author has recommended
- that users of Blowfish move to newer algorithms like
- :class:`AES`.
+ that users of Blowfish move to newer algorithms, such as :class:`AES`.
: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)
+ >>> encryptor = cipher.encryptor()
+ >>> ct = encryptor.update(b"a secret message")
+ >>> decryptor = cipher.decryptor()
+ >>> decryptor.update(ct)
+ 'a secret message'
+
.. _symmetric-encryption-modes:
@@ -252,3 +286,6 @@ Insecure Modes
ciphers. Each block of data is encrypted in the same way. This means
identical plaintext blocks will always result in identical ciphertext
blocks, and thus result in information leakage
+
+
+.. _`described by Colin Percival`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html
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..9f27aab7 100644
--- a/tests/hazmat/bindings/test_openssl.py
+++ b/tests/hazmat/bindings/test_openssl.py
@@ -13,18 +13,21 @@
import pytest
+from cryptography import utils
from cryptography.exceptions import UnsupportedAlgorithm
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
@@ -44,11 +47,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 +60,13 @@ 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()
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..d178da7b 100644
--- a/tests/hazmat/primitives/test_aes.py
+++ b/tests/hazmat/primitives/test_aes.py
@@ -20,13 +20,13 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes
from .utils import generate_encrypt_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,12 +122,12 @@ 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",
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..9460c53d 100644
--- a/tests/hazmat/primitives/test_block.py
+++ b/tests/hazmat/primitives/test_block.py
@@ -17,13 +17,19 @@ 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
)
+@utils.register_interface(interfaces.CipherAlgorithm)
+class DummyCipher(object):
+ pass
+
+
class TestCipher(object):
def test_instantiate_without_backend(self):
Cipher(
@@ -45,6 +51,11 @@ class TestCipher(object):
)
assert isinstance(cipher.decryptor(), interfaces.CipherContext)
+ def test_instantiate_with_non_algorithm(self):
+ algorithm = object()
+ with pytest.raises(TypeError):
+ Cipher(algorithm, mode=None)
+
class TestCipherContext(object):
def test_use_after_finalize(self, backend):
@@ -56,16 +67,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,7 +101,7 @@ class TestCipherContext(object):
def test_nonexistent_cipher(self, backend):
cipher = Cipher(
- object(), object(), backend
+ DummyCipher(), object(), backend
)
with pytest.raises(UnsupportedAlgorithm):
cipher.encryptor()
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..367e764f 100644
--- a/tests/hazmat/primitives/test_hashes.py
+++ b/tests/hazmat/primitives/test_hashes.py
@@ -19,6 +19,7 @@ import pytest
import six
+from cryptography.exceptions import AlreadyFinalized
from cryptography.hazmat.bindings import _default_backend
from cryptography.hazmat.primitives import hashes
@@ -32,9 +33,9 @@ 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
@@ -51,13 +52,26 @@ class TestHashContext(object):
with pytest.raises(TypeError):
hashes.Hash(hashes.SHA1)
+ def test_raises_after_finalize(self):
+ h = hashes.Hash(hashes.SHA1())
+ 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):
test_SHA1 = generate_base_hash_test(
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 +81,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 +91,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 +101,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 +111,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 +121,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 +131,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 +141,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..d17049e3 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,9 +38,10 @@ 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
@@ -48,3 +50,16 @@ class TestHMAC(object):
def test_hmac_algorithm_instance(self):
with pytest.raises(TypeError):
hmac.HMAC(b"key", hashes.SHA1)
+
+ def test_raises_after_finalize(self):
+ h = hmac.HMAC(b"key", hashes.SHA1())
+ 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_padding.py b/tests/hazmat/primitives/test_padding.py
index 3cefafaf..6a2b6243 100644
--- a/tests/hazmat/primitives/test_padding.py
+++ b/tests/hazmat/primitives/test_padding.py
@@ -29,6 +29,8 @@ class TestPKCS7(object):
(128, b"1111111111111111"),
(128, b"111111111111111\x06"),
(128, b""),
+ (128, b"\x06" * 6),
+ (128, b"\x00" * 16),
])
def test_invalid_padding(self, size, padded):
unpadder = padding.PKCS7(size).unpadder()
diff --git a/tests/hazmat/primitives/test_utils.py b/tests/hazmat/primitives/test_utils.py
index d7247e67..cee0b20e 100644
--- a/tests/hazmat/primitives/test_utils.py
+++ b/tests/hazmat/primitives/test_utils.py
@@ -2,7 +2,7 @@ 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
)
@@ -70,3 +70,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..9327b0eb 100644
--- a/tests/hazmat/primitives/utils.py
+++ b/tests/hazmat/primitives/utils.py
@@ -8,6 +8,8 @@ from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import hmac
from cryptography.hazmat.primitives.ciphers import Cipher
+from ...utils import load_vectors_from_file
+
def generate_encrypt_test(param_loader, path, file_names, cipher_factory,
mode_factory, only_if=lambda backend: True,
@@ -15,7 +17,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,
@@ -49,12 +54,57 @@ def encrypt_test(backend, cipher_factory, mode_factory, params, only_if,
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)
+ 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 +155,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 +190,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,
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)
diff --git a/tox.ini b/tox.ini
index b807b878..eda76cd9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,11 +8,13 @@ deps =
pretend
pytest
commands =
- coverage run --source=cryptography/,tests/ -m pytest
+ coverage run --source=cryptography/,tests/ -m pytest --strict
coverage report -m
[testenv:docs]
-deps = sphinx
+deps =
+ sphinx
+ sphinx_rtd_theme
basepython = python2.7
commands =
sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html