diff options
45 files changed, 1253 insertions, 367 deletions
diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..398ff08a --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +branch = True @@ -8,3 +8,4 @@ cffi-*.egg/ pycparser-*.egg/ pytest-*.egg/ dist/ +htmlcov/ diff --git a/AUTHORS.rst b/AUTHORS.rst index 1aa37e48..b3b7f35d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,7 +1,9 @@ AUTHORS ======= -* Alex Gaynor <alex.gaynor@gmail.com> +PGP key fingerprints are enclosed in parentheses. + +* Alex Gaynor <alex.gaynor@gmail.com> (E27D 4AA0 1651 72CB C5D2 AF2B 125F 5C67 DFE9 4084) * Hynek Schlawack <hs@ox.cx> * Donald Stufft <donald@stufft.io> * Laurens Van Houtven <_@lvh.io> @@ -1,11 +1,11 @@ Cryptography ============ -.. image:: https://travis-ci.org/alex/cryptography.png?branch=master - :target: https://travis-ci.org/alex/cryptography +.. image:: https://travis-ci.org/pyca/cryptography.png?branch=master + :target: https://travis-ci.org/pyca/cryptography -.. image:: https://coveralls.io/repos/alex/cryptography/badge.png?branch=master - :target: https://coveralls.io/r/alex/cryptography?branch=master +.. image:: https://coveralls.io/repos/pyca/cryptography/badge.png?branch=master + :target: https://coveralls.io/r/pyca/cryptography?branch=master ``cryptography`` is a package designed to expose cryptographic primitives and recipes to Python developers. @@ -16,3 +16,12 @@ yet. It targets Python 2.6-2.7, Python 3.2+, as well as PyPy. You can find more documentation at `Read The Docs`_. .. _`Read The Docs`: https://cryptography.readthedocs.org/ + +Discussion +~~~~~~~~~~ + +We maintain a `cryptography-dev`_ mailing list for development discussion. + +You can also join #cryptography-dev on Freenode to ask questions or get involved. + +.. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev diff --git a/cryptography/__about__.py b/cryptography/__about__.py index 6499ff2b..e5eca6c6 100644 --- a/cryptography/__about__.py +++ b/cryptography/__about__.py @@ -20,7 +20,7 @@ __all__ = [ __title__ = "cryptography" __summary__ = ("cryptography is a package designed to expose cryptographic " "primitives and recipes to Python developers.") -__uri__ = "https://github.com/alex/cryptography" +__uri__ = "https://github.com/pyca/cryptography" __version__ = "0.1.dev1" diff --git a/cryptography/bindings/__init__.py b/cryptography/bindings/__init__.py index e69de29b..215f17c7 100644 --- a/cryptography/bindings/__init__.py +++ b/cryptography/bindings/__init__.py @@ -0,0 +1,20 @@ +# 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 cryptography.bindings import openssl + + +_default_api = openssl.api +_ALL_APIS = [ + openssl.api +] diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py index 28437576..79ec5eea 100644 --- a/cryptography/bindings/openssl/api.py +++ b/cryptography/bindings/openssl/api.py @@ -25,26 +25,58 @@ class API(object): OpenSSL API wrapper. """ _modules = [ + "bignum", + "bio", + "conf", + "crypto", + "dh", + "dsa", + "engine", + "err", "evp", "opensslv", + "rand", + "rsa", + "ssl", ] def __init__(self): self.ffi = cffi.FFI() includes = [] + functions = [] + macros = [] for name in self._modules: __import__("cryptography.bindings.openssl." + name) module = sys.modules["cryptography.bindings.openssl." + name] self.ffi.cdef(module.TYPES) - self.ffi.cdef(module.FUNCTIONS) + + macros.append(module.MACROS) + functions.append(module.FUNCTIONS) includes.append(module.INCLUDES) + # loop over the functions & macros after declaring all the types + # so we can set interdependent types in different files and still + # have them all defined before we parse the funcs & macros + for func in functions: + self.ffi.cdef(func) + for macro in macros: + self.ffi.cdef(macro) + + # We include functions here so that if we got any of their definitions + # wrong, the underlying C compiler will explode. In C you are allowed + # to re-declare a function if it has the same signature. That is: + # int foo(int); + # int foo(int); + # is legal, but the following will fail to compile: + # int foo(int); + # int foo(short); self.lib = self.ffi.verify( - source="\n".join(includes), - libraries=["crypto"] + source="\n".join(includes + functions), + libraries=["crypto", "ssl"], ) self.lib.OpenSSL_add_all_algorithms() + self.lib.SSL_load_error_strings() def openssl_version_text(self): """ @@ -54,6 +86,10 @@ class API(object): """ return self.ffi.string(self.lib.OPENSSL_VERSION_TEXT).decode("ascii") + def supports_cipher(self, ciphername): + return (self.ffi.NULL != + self.lib.EVP_get_cipherbyname(ciphername.encode("ascii"))) + def create_block_cipher_context(self, cipher, mode): ctx = self.ffi.new("EVP_CIPHER_CTX *") res = self.lib.EVP_CIPHER_CTX_init(ctx) @@ -62,11 +98,13 @@ class API(object): # TODO: compute name using a better algorithm ciphername = "{0}-{1}-{2}".format( cipher.name, cipher.key_size, mode.name - ) + ).lower() evp_cipher = self.lib.EVP_get_cipherbyname(ciphername.encode("ascii")) assert evp_cipher != self.ffi.NULL if isinstance(mode, interfaces.ModeWithInitializationVector): iv_nonce = mode.initialization_vector + elif isinstance(mode, interfaces.ModeWithNonce): + iv_nonce = mode.nonce else: iv_nonce = self.ffi.NULL diff --git a/cryptography/bindings/openssl/bignum.py b/cryptography/bindings/openssl/bignum.py new file mode 100644 index 00000000..72d467c3 --- /dev/null +++ b/cryptography/bindings/openssl/bignum.py @@ -0,0 +1,34 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDES = """ +#include <openssl/bn.h> +""" + +TYPES = """ +typedef ... BIGNUM; +typedef ... BN_ULONG; +""" + +FUNCTIONS = """ +BIGNUM *BN_new(); +void BN_free(BIGNUM *); + +int BN_set_word(BIGNUM *, BN_ULONG); + +char *BN_bn2hex(const BIGNUM *); +int BN_hex2bn(BIGNUM **, const char *); +""" + +MACROS = """ +""" diff --git a/cryptography/bindings/openssl/bio.py b/cryptography/bindings/openssl/bio.py new file mode 100644 index 00000000..88be788f --- /dev/null +++ b/cryptography/bindings/openssl/bio.py @@ -0,0 +1,170 @@ +# 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. + +INCLUDES = """ +#include <openssl/bio.h> +""" + +TYPES = """ +typedef struct bio_st BIO; +typedef void bio_info_cb(BIO *, int, const char *, int, long, long); +struct bio_method_st { + int type; + const char *name; + int (*bwrite)(BIO *, const char *, int); + int (*bread)(BIO *, char *, int); + int (*bputs)(BIO *, const char *); + int (*bgets)(BIO *, char*, int); + long (*ctrl)(BIO *, int, long, void *); + int (*create)(BIO *); + int (*destroy)(BIO *); + long (*callback_ctrl)(BIO *, int, bio_info_cb *); + ...; +}; +typedef struct bio_method_st BIO_METHOD; +struct bio_st { + BIO_METHOD *method; + long (*callback)(struct bio_st*, int, const char*, int, long, long); + char *cb_arg; + int init; + int shutdown; + int flags; + int retry_reason; + int num; + void *ptr; + struct bio_st *next_bio; + struct bio_st *prev_bio; + int references; + unsigned long num_read; + unsigned long num_write; + ...; +}; +typedef ... BUF_MEM; +""" + +FUNCTIONS = """ +BIO* BIO_new(BIO_METHOD *); +int BIO_set(BIO *, BIO_METHOD *); +int BIO_free(BIO *); +void BIO_vfree(BIO *); +void BIO_free_all(BIO *); +BIO *BIO_push(BIO *, BIO *); +BIO *BIO_pop(BIO *); +BIO *BIO_next(BIO *); +BIO *BIO_find_type(BIO *, int); +int BIO_method_type(const BIO *); +BIO_METHOD *BIO_s_mem(); +BIO *BIO_new_mem_buf(void *, int); +BIO_METHOD *BIO_s_file(); +BIO *BIO_new_file(const char *, const char *); +BIO *BIO_new_fp(FILE *, int); +BIO_METHOD *BIO_s_fd(); +BIO *BIO_new_fd(int, int); +BIO_METHOD *BIO_s_socket(); +BIO *BIO_new_socket(int, int); +BIO_METHOD *BIO_s_null(); +long BIO_ctrl(BIO *, int, long, void *); +long BIO_callback_ctrl( + BIO *, + int, + void (*)(struct bio_st *, int, const char *, int, long, long) +); +char* BIO_ptr_ctrl(BIO *bp, int cmd, long larg); +long BIO_int_ctrl(BIO *bp, int cmd, long larg, int iarg); +size_t BIO_ctrl_pending(BIO *b); +size_t BIO_ctrl_wpending(BIO *b); +int BIO_read(BIO *, void *, int); +int BIO_gets(BIO *, char *, int); +int BIO_write(BIO *, const void *, int); +int BIO_puts(BIO *, const char *); +BIO_METHOD *BIO_f_null(); +BIO_METHOD *BIO_f_buffer(); +""" + +MACROS = """ +long BIO_set_fd(BIO *, long, int); +long BIO_get_fd(BIO *, char *); +long BIO_set_mem_eof_return(BIO *, int); +long BIO_get_mem_data(BIO *, char **); +long BIO_set_mem_buf(BIO *, BUF_MEM *, int); +long BIO_get_mem_ptr(BIO *, BUF_MEM **); +long BIO_set_fp(BIO *, FILE *, int); +long BIO_get_fp(BIO *, FILE **); +int BIO_read_filename(BIO *, char *); +int BIO_write_filename(BIO *, char *); +int BIO_append_filename(BIO *, char *); +int BIO_rw_filename(BIO *, char *); +int BIO_should_read(BIO *); +int BIO_should_write(BIO *); +int BIO_should_io_special(BIO *); +int BIO_retry_type(BIO *); +int BIO_should_retry(BIO *); +int BIO_reset(BIO *); +int BIO_seek(BIO *, int); +int BIO_tell(BIO *); +int BIO_flush(BIO *); +int BIO_eof(BIO *); +int BIO_set_close(BIO *,long); +int BIO_get_close(BIO *); +int BIO_pending(BIO *); +int BIO_wpending(BIO *); +int BIO_get_info_callback(BIO *, bio_info_cb **); +int BIO_set_info_callback(BIO *, bio_info_cb *); +long BIO_get_buffer_num_lines(BIO *); +long BIO_set_read_buffer_size(BIO *, long); +long BIO_set_write_buffer_size(BIO *, long); +long BIO_set_buffer_size(BIO *, long); +long BIO_set_buffer_read_data(BIO *, void *, long); +#define BIO_TYPE_MEM ... +#define BIO_TYPE_FILE ... +#define BIO_TYPE_FD ... +#define BIO_TYPE_SOCKET ... +#define BIO_TYPE_CONNECT ... +#define BIO_TYPE_ACCEPT ... +#define BIO_TYPE_NULL ... +#define BIO_CLOSE ... +#define BIO_NOCLOSE ... +#define BIO_TYPE_SOURCE_SINK ... +#define BIO_CTRL_RESET ... +#define BIO_CTRL_EOF ... +#define BIO_CTRL_SET ... +#define BIO_CTRL_SET_CLOSE ... +#define BIO_CTRL_FLUSH ... +#define BIO_CTRL_DUP ... +#define BIO_CTRL_GET_CLOSE ... +#define BIO_CTRL_INFO ... +#define BIO_CTRL_GET ... +#define BIO_CTRL_PENDING ... +#define BIO_CTRL_WPENDING ... +#define BIO_C_FILE_SEEK ... +#define BIO_C_FILE_TELL ... +#define BIO_TYPE_NONE ... +#define BIO_TYPE_PROXY_CLIENT ... +#define BIO_TYPE_PROXY_SERVER ... +#define BIO_TYPE_NBIO_TEST ... +#define BIO_TYPE_BER ... +#define BIO_TYPE_BIO ... +#define BIO_TYPE_DESCRIPTOR ... +#define BIO_FLAGS_READ ... +#define BIO_FLAGS_WRITE ... +#define BIO_FLAGS_IO_SPECIAL ... +#define BIO_FLAGS_RWS ... +#define BIO_FLAGS_SHOULD_RETRY ... +#define BIO_TYPE_NULL_FILTER ... +#define BIO_TYPE_SSL ... +#define BIO_TYPE_MD ... +#define BIO_TYPE_BUFFER ... +#define BIO_TYPE_CIPHER ... +#define BIO_TYPE_BASE64 ... +#define BIO_TYPE_FILTER ... +""" diff --git a/cryptography/bindings/openssl/conf.py b/cryptography/bindings/openssl/conf.py new file mode 100644 index 00000000..85c7a210 --- /dev/null +++ b/cryptography/bindings/openssl/conf.py @@ -0,0 +1,26 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDES = """ +#include <openssl/conf.h> +""" + +TYPES = """ +typedef ... CONF; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +""" diff --git a/cryptography/bindings/openssl/crypto.py b/cryptography/bindings/openssl/crypto.py new file mode 100644 index 00000000..501fb5a1 --- /dev/null +++ b/cryptography/bindings/openssl/crypto.py @@ -0,0 +1,37 @@ +# 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. + +INCLUDES = """ +#include <openssl/crypto.h> +""" + +TYPES = """ +""" + +FUNCTIONS = """ +void CRYPTO_free(void *); +int CRYPTO_mem_ctrl(int); +int CRYPTO_is_mem_check_on(); +void CRYPTO_mem_leaks(struct bio_st *); +void CRYPTO_cleanup_all_ex_data(); +""" + +MACROS = """ +void CRYPTO_add(int *, int, int); +void CRYPTO_malloc_init(); +void CRYPTO_malloc_debug_init(); +#define CRYPTO_MEM_CHECK_ON ... +#define CRYPTO_MEM_CHECK_OFF ... +#define CRYPTO_MEM_CHECK_ENABLE ... +#define CRYPTO_MEM_CHECK_DISABLE ... +""" diff --git a/cryptography/bindings/openssl/dh.py b/cryptography/bindings/openssl/dh.py new file mode 100644 index 00000000..ac130054 --- /dev/null +++ b/cryptography/bindings/openssl/dh.py @@ -0,0 +1,28 @@ +# 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. + +INCLUDES = """ +#include <openssl/dh.h> +""" + +TYPES = """ +typedef ... DH; +""" + +FUNCTIONS = """ +DH *DH_new(); +void DH_free(DH *); +""" + +MACROS = """ +""" diff --git a/cryptography/bindings/openssl/dsa.py b/cryptography/bindings/openssl/dsa.py new file mode 100644 index 00000000..2fa67b87 --- /dev/null +++ b/cryptography/bindings/openssl/dsa.py @@ -0,0 +1,30 @@ +# 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. + +INCLUDES = """ +#include <openssl/dsa.h> +""" + +TYPES = """ +typedef ... DSA; +""" + +FUNCTIONS = """ +DSA *DSA_generate_parameters(int, unsigned char *, int, int *, unsigned long *, + void (*)(int, int, void *), void *); +int DSA_generate_key(DSA *); +void DSA_free(DSA *); +""" + +MACROS = """ +""" diff --git a/cryptography/bindings/openssl/engine.py b/cryptography/bindings/openssl/engine.py new file mode 100644 index 00000000..b3ec3125 --- /dev/null +++ b/cryptography/bindings/openssl/engine.py @@ -0,0 +1,52 @@ +# 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. + +INCLUDES = """ +#include <openssl/engine.h> +""" + +TYPES = """ +typedef ... ENGINE; +""" + +FUNCTIONS = """ +ENGINE *ENGINE_get_first(); +ENGINE *ENGINE_get_last(); +ENGINE *ENGINE_get_next(ENGINE *); +ENGINE *ENGINE_get_prev(ENGINE *); +int ENGINE_add(ENGINE *); +int ENGINE_remove(ENGINE *); +ENGINE *ENGINE_by_id(const char *); +int ENGINE_init(ENGINE *); +int ENGINE_finish(ENGINE *); +int ENGINE_free(ENGINE *); +void ENGINE_cleanup(); +void ENGINE_load_dynamic(); +void ENGINE_load_builtin_engines(); +int ENGINE_ctrl_cmd_string(ENGINE *, const char *, const char *, int); +int ENGINE_set_default(ENGINE *, unsigned int); +int ENGINE_register_complete(ENGINE *); +""" + +MACROS = """ +#define ENGINE_METHOD_RSA ... +#define ENGINE_METHOD_DSA ... +#define ENGINE_METHOD_RAND ... +#define ENGINE_METHOD_ECDH ... +#define ENGINE_METHOD_ECDSA ... +#define ENGINE_METHOD_CIPHERS ... +#define ENGINE_METHOD_DIGESTS ... +#define ENGINE_METHOD_STORE ... +#define ENGINE_METHOD_ALL ... +#define ENGINE_METHOD_NONE ... +""" diff --git a/cryptography/bindings/openssl/err.py b/cryptography/bindings/openssl/err.py new file mode 100644 index 00000000..76c34a03 --- /dev/null +++ b/cryptography/bindings/openssl/err.py @@ -0,0 +1,54 @@ +# 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. + +INCLUDES = """ +#include <openssl/err.h> +""" + +TYPES = """ +struct ERR_string_data_st { + unsigned long error; + const char *string; +}; +typedef struct ERR_string_data_st ERR_STRING_DATA; +""" + +FUNCTIONS = """ +void ERR_load_crypto_strings(); +void ERR_free_strings(); +char* ERR_error_string(unsigned long, char *); +void ERR_error_string_n(unsigned long, char *, size_t); +const char* ERR_lib_error_string(unsigned long); +const char* ERR_func_error_string(unsigned long); +const char* ERR_reason_error_string(unsigned long); +void ERR_print_errors(BIO *); +void ERR_print_errors_fp(FILE *); +unsigned long ERR_get_error(); +unsigned long ERR_peek_error(); +unsigned long ERR_peek_last_error(); +unsigned long ERR_get_error_line(const char **, int *); +unsigned long ERR_peek_error_line(const char **, int *); +unsigned long ERR_peek_last_error_line(const char **, int *); +unsigned long ERR_get_error_line_data(const char **, int *, + const char **, int *); +unsigned long ERR_peek_error_line_data(const char **, + int *, const char **, int *); +unsigned long ERR_peek_last_error_line_data(const char **, + int *, const char **, int *); +void ERR_put_error(int, int, int, const char *, int); +void ERR_add_error_data(int, ...); +int ERR_get_next_error_library(); +""" + +MACROS = """ +""" diff --git a/cryptography/bindings/openssl/evp.py b/cryptography/bindings/openssl/evp.py index 0bc5cffc..63364374 100644 --- a/cryptography/bindings/openssl/evp.py +++ b/cryptography/bindings/openssl/evp.py @@ -20,20 +20,22 @@ typedef struct { ...; } EVP_CIPHER_CTX; typedef ... EVP_CIPHER; -typedef ... ENGINE; """ FUNCTIONS = """ void OpenSSL_add_all_algorithms(); const EVP_CIPHER *EVP_get_cipherbyname(const char *); int EVP_EncryptInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *, - unsigned char *, unsigned char *); + const unsigned char *, const unsigned char *); int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int); int EVP_EncryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, - unsigned char *, int); + const unsigned char *, int); int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *); const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *); int EVP_CIPHER_block_size(const EVP_CIPHER *); void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *); """ + +MACROS = """ +""" diff --git a/cryptography/bindings/openssl/opensslv.py b/cryptography/bindings/openssl/opensslv.py index 9b2db270..d1a1b3e6 100644 --- a/cryptography/bindings/openssl/opensslv.py +++ b/cryptography/bindings/openssl/opensslv.py @@ -21,3 +21,6 @@ static char *const OPENSSL_VERSION_TEXT; FUNCTIONS = """ """ + +MACROS = """ +""" diff --git a/cryptography/bindings/openssl/rand.py b/cryptography/bindings/openssl/rand.py new file mode 100644 index 00000000..e4f6be23 --- /dev/null +++ b/cryptography/bindings/openssl/rand.py @@ -0,0 +1,37 @@ +# 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. + +INCLUDES = """ +#include <openssl/rand.h> +""" + +TYPES = """ +""" + +FUNCTIONS = """ +void RAND_seed(const void *, int); +void RAND_add(const void *, int, double); +int RAND_status(); +int RAND_egd(const char *); +int RAND_egd_bytes(const char *, int); +int RAND_query_egd_bytes(const char *, unsigned char *, int); +const char *RAND_file_name(char *, size_t); +int RAND_load_file(const char *, long); +int RAND_write_file(const char *); +void RAND_cleanup(); +int RAND_bytes(unsigned char *, int); +int RAND_pseudo_bytes(unsigned char *, int); +""" + +MACROS = """ +""" diff --git a/cryptography/bindings/openssl/rsa.py b/cryptography/bindings/openssl/rsa.py new file mode 100644 index 00000000..c8bf1cc0 --- /dev/null +++ b/cryptography/bindings/openssl/rsa.py @@ -0,0 +1,31 @@ +# 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. + +INCLUDES = """ +#include <openssl/rsa.h> +""" + +TYPES = """ +typedef ... RSA; +typedef ... BN_GENCB; +""" + +FUNCTIONS = """ +RSA *RSA_new(); +void RSA_free(RSA *); +int RSA_generate_key_ex(RSA *, int, BIGNUM *, BN_GENCB *); +int RSA_check_key(const RSA *); +""" + +MACROS = """ +""" diff --git a/cryptography/bindings/openssl/ssl.py b/cryptography/bindings/openssl/ssl.py new file mode 100644 index 00000000..8aca86e4 --- /dev/null +++ b/cryptography/bindings/openssl/ssl.py @@ -0,0 +1,26 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDES = """ +#include <openssl/ssl.h> +""" + +TYPES = """ +""" + +FUNCTIONS = """ +void SSL_load_error_strings(); +""" + +MACROS = """ +""" diff --git a/cryptography/primitives/block/base.py b/cryptography/primitives/block/base.py index b4137fd4..50e9e9e5 100644 --- a/cryptography/primitives/block/base.py +++ b/cryptography/primitives/block/base.py @@ -15,8 +15,7 @@ from __future__ import absolute_import, division, print_function from enum import Enum -# TODO: which binding is used should be an option somewhere -from cryptography.bindings.openssl import api +from cryptography.bindings import _default_api class _Operation(Enum): @@ -25,19 +24,18 @@ class _Operation(Enum): class BlockCipher(object): - def __init__(self, cipher, mode): + def __init__(self, cipher, mode, api=None): super(BlockCipher, self).__init__() + + if api is None: + api = _default_api + self.cipher = cipher self.mode = mode + self._api = api self._ctx = api.create_block_cipher_context(cipher, mode) self._operation = None - @property - def name(self): - return "{0}-{1}-{2}".format( - self.cipher.name, self.cipher.key_size, self.mode.name, - ) - def encrypt(self, plaintext): if self._ctx is None: raise ValueError("BlockCipher was already finalized") @@ -48,14 +46,14 @@ class BlockCipher(object): raise ValueError("BlockCipher cannot encrypt when the operation is" " set to %s" % self._operation.name) - return api.update_encrypt_context(self._ctx, plaintext) + return self._api.update_encrypt_context(self._ctx, plaintext) def finalize(self): if self._ctx is None: raise ValueError("BlockCipher was already finalized") if self._operation is _Operation.encrypt: - result = api.finalize_encrypt_context(self._ctx) + result = self._api.finalize_encrypt_context(self._ctx) else: raise ValueError("BlockCipher cannot finalize the unknown " "operation %s" % self._operation.name) diff --git a/cryptography/primitives/block/ciphers.py b/cryptography/primitives/block/ciphers.py index cf54aa35..4ac150a4 100644 --- a/cryptography/primitives/block/ciphers.py +++ b/cryptography/primitives/block/ciphers.py @@ -17,7 +17,7 @@ from __future__ import absolute_import, division, print_function class AES(object): name = "AES" block_size = 128 - key_sizes = set([128, 192, 256]) + key_sizes = frozenset([128, 192, 256]) def __init__(self, key): super(AES, self).__init__() @@ -32,3 +32,23 @@ class AES(object): @property def key_size(self): return len(self.key) * 8 + + +class Camellia(object): + name = "camellia" + block_size = 128 + key_sizes = frozenset([128, 192, 256]) + + def __init__(self, key): + super(Camellia, self).__init__() + self.key = key + + # Verify that the key size matches the expected key size + if self.key_size not in self.key_sizes: + raise ValueError("Invalid key size ({0}) for {1}".format( + self.key_size, self.name + )) + + @property + def key_size(self): + return len(self.key) * 8 diff --git a/cryptography/primitives/block/modes.py b/cryptography/primitives/block/modes.py index 9cfbca64..43631801 100644 --- a/cryptography/primitives/block/modes.py +++ b/cryptography/primitives/block/modes.py @@ -16,6 +16,14 @@ from __future__ import absolute_import, division, print_function from cryptography.primitives import interfaces +def register(iface): + def register_decorator(klass): + iface.register(klass) + return klass + return register_decorator + + +@register(interfaces.ModeWithInitializationVector) class CBC(object): name = "CBC" @@ -28,6 +36,7 @@ class ECB(object): name = "ECB" +@register(interfaces.ModeWithInitializationVector) class OFB(object): name = "OFB" @@ -36,6 +45,7 @@ class OFB(object): self.initialization_vector = initialization_vector +@register(interfaces.ModeWithInitializationVector) class CFB(object): name = "CFB" @@ -44,6 +54,10 @@ class CFB(object): self.initialization_vector = initialization_vector -interfaces.ModeWithInitializationVector.register(CBC) -interfaces.ModeWithInitializationVector.register(OFB) -interfaces.ModeWithInitializationVector.register(CFB) +@register(interfaces.ModeWithNonce) +class CTR(object): + name = "CTR" + + def __init__(self, nonce): + super(CTR, self).__init__() + self.nonce = nonce diff --git a/cryptography/primitives/interfaces.py b/cryptography/primitives/interfaces.py index 6f74ccf7..c1fc9910 100644 --- a/cryptography/primitives/interfaces.py +++ b/cryptography/primitives/interfaces.py @@ -20,3 +20,7 @@ import six class ModeWithInitializationVector(six.with_metaclass(abc.ABCMeta)): pass + + +class ModeWithNonce(six.with_metaclass(abc.ABCMeta)): + pass diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 00000000..01030e87 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,5 @@ +flake8 +pretend +pytest-cov +sphinx +tox diff --git a/docs/bindings/openssl.rst b/docs/bindings/openssl.rst index 79468cb4..241cc4d6 100644 --- a/docs/bindings/openssl.rst +++ b/docs/bindings/openssl.rst @@ -4,7 +4,7 @@ OpenSSL .. warning:: The OpenSSL API is not easy to use, small mistakes can lead to significant - security vulnerabilities. We strongly reccomend not using this directly, + security vulnerabilities. We strongly recommend not using this directly, and instead using one of the higher level APIs exposed by ``cryptography``. diff --git a/docs/community.rst b/docs/community.rst index 809ffd12..86ba5055 100644 --- a/docs/community.rst +++ b/docs/community.rst @@ -10,6 +10,6 @@ You can find ``cryptography`` all over the web: * IRC: ``#cryptography-dev`` on ``irc.freenode.net`` .. _`Mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev -.. _`Source code`: https://github.com/alex/cryptography -.. _`Issue tracker`: https://github.com/alex/cryptography/issues +.. _`Source code`: https://github.com/pyca/cryptography +.. _`Issue tracker`: https://github.com/pyca/cryptography/issues .. _`Documentation`: https://cryptography.readthedocs.org/ diff --git a/docs/contributing.rst b/docs/contributing.rst index b4c72ba4..2d8fceeb 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -73,8 +73,81 @@ So, specifically: - No blank line at the end. - Use Sphinx parameter/attribute documentation `syntax`_. +Development Environment +----------------------- -.. _`GitHub`: https://github.com/alex/cryptography +Working on ``cryptography`` requires the installation of a small number of +development dependencies. These are listed in ``dev-requirements.txt`` and they +can be installed in a `virtualenv`_ using `pip`_. Once you've installed the +dependencies, install ``cryptography`` in ``editable`` mode. For example: + +.. code-block:: console + + $ # Create a virtualenv and activate it + $ pip install --requirement dev-requirements.txt + $ pip install --editable . + +You are now ready to run the tests and build the documentation. + +Running Tests +------------- + +``cryptography`` unit tests are found in the ``tests/`` directory and are +designed to be run using `pytest`_. `pytest`_ will discover the tests +automatically, so all you have to do is: + +.. code-block:: console + + $ py.test + ... + 4294 passed in 15.24 seconds + +This runs the tests with the default Python interpreter. + +You can also verify that the tests pass on other supported Python interpreters. +For this we use `tox`_, which will automatically create a `virtualenv`_ for +each supported Python version and run the tests. For example: + +.. code-block:: console + + $ tox + ... + ERROR: py26: InterpreterNotFound: python2.6 + py27: commands succeeded + ERROR: pypy: InterpreterNotFound: pypy + ERROR: py32: InterpreterNotFound: python3.2 + py33: commands succeeded + docs: commands succeeded + pep8: commands succeeded + +You may not have all the required Python versions installed, in which case you +will see one or more ``InterpreterNotFound`` errors. + +Building Documentation +---------------------- + +``cryptography`` documentation is stored in the ``docs/`` directory. It is +written in `reStructured Text`_ and rendered using `Sphinx`_. + +Use `tox`_ to build the documentation. For example: + +.. code-block:: console + + $ tox -e docs + ... + docs: commands succeeded + congratulations :) + +The HTML documentation index can now be found at ``docs/_build/html/index.html`` + + +.. _`GitHub`: https://github.com/pyca/cryptography .. _`our mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev .. _`PEP 8`: http://www.peps.io/8/ .. _`syntax`: http://sphinx-doc.org/domains.html#info-field-lists +.. _`pytest`: https://pypi.python.org/pypi/pytest +.. _`tox`: https://pypi.python.org/pypi/tox +.. _`virtualenv`: https://pypi.python.org/pypi/virtualenv +.. _`pip`: https://pypi.python.org/pypi/pip +.. _`sphinx`: https://pypi.python.org/pypi/sphinx +.. _`reStructured Text`: http://docutils.sourceforge.net/rst.html diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index 46d7c07c..7899e67d 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -51,6 +51,15 @@ Ciphers :param bytes key: The secret key, either ``128``, ``192``, or ``256`` bits. This must be kept secret. +.. class:: cryptography.primitives.block.ciphers.Camellia(key) + + Camellia is a block cipher approved for use by CRYPTREC and ISO/IEC. + It is considered to have comparable security and performance to AES, but + is not as widely studied or deployed. + + :param bytes key: The secret key, either ``128``, ``192``, or ``256`` bits. + This must be kept secret. + Modes ~~~~~ @@ -68,6 +77,25 @@ Modes reuse an ``initialization_vector`` with a given ``key``. + +.. class:: cryptography.primitives.block.modes.CTR(nonce) + + .. warning:: + + Counter mode is not recommended for use with block ciphers that have a + block size of less than 128-bits. + + CTR (Counter) is a mode of operation for block ciphers. It is considered + cryptographically strong. + + :param bytes nonce: Should be random bytes. It is critical to never reuse a + ``nonce`` with a given key. Any reuse of a nonce + with the same key compromises the security of every + message encrypted with that key. Must be the same + number of bytes as the ``block_size`` of the cipher + with a given key. The nonce does not need to be kept + secret and may be included alongside the ciphertext. + .. class:: cryptography.primitives.block.modes.OFB(initialization_vector) OFB (Output Feedback) is a mode of operation for block ciphers. It diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..723735ac --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = -r s diff --git a/tests/bindings/test_openssl.py b/tests/bindings/test_openssl.py index b23c4ccc..e5b78d18 100644 --- a/tests/bindings/test_openssl.py +++ b/tests/bindings/test_openssl.py @@ -28,3 +28,6 @@ class TestOpenSSL(object): for every OpenSSL. """ assert api.openssl_version_text().startswith("OpenSSL") + + def test_supports_cipher(self): + assert api.supports_cipher("not-a-real-cipher") is False diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..b526f2bf --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,5 @@ +def pytest_generate_tests(metafunc): + from cryptography.bindings import _ALL_APIS + + if "api" in metafunc.fixturenames: + metafunc.parametrize("api", _ALL_APIS) diff --git a/tests/primitives/test_block.py b/tests/primitives/test_block.py index 774409ca..f4d3f467 100644 --- a/tests/primitives/test_block.py +++ b/tests/primitives/test_block.py @@ -23,17 +23,17 @@ from cryptography.primitives.block.base import _Operation class TestBlockCipher(object): - def test_cipher_name(self): - cipher = BlockCipher( + def test_instantiate_without_api(self): + BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), modes.CBC(binascii.unhexlify(b"0" * 32)) ) - assert cipher.name == "AES-128-CBC" - def test_use_after_finalize(self): + def test_use_after_finalize(self, api): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)) + modes.CBC(binascii.unhexlify(b"0" * 32)), + api ) cipher.encrypt(b"a" * 16) cipher.finalize() @@ -42,20 +42,22 @@ class TestBlockCipher(object): with pytest.raises(ValueError): cipher.finalize() - def test_encrypt_with_invalid_operation(self): + def test_encrypt_with_invalid_operation(self, api): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)) + modes.CBC(binascii.unhexlify(b"0" * 32)), + api ) cipher._operation = _Operation.decrypt with pytest.raises(ValueError): cipher.encrypt(b"b" * 16) - def test_finalize_with_invalid_operation(self): + def test_finalize_with_invalid_operation(self, api): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)) + modes.CBC(binascii.unhexlify(b"0" * 32)), + api ) cipher._operation = pretend.stub(name="wat") diff --git a/tests/primitives/test_ciphers.py b/tests/primitives/test_ciphers.py index 5ee9f223..27d35850 100644 --- a/tests/primitives/test_ciphers.py +++ b/tests/primitives/test_ciphers.py @@ -17,7 +17,7 @@ import binascii import pytest -from cryptography.primitives.block.ciphers import AES +from cryptography.primitives.block.ciphers import AES, Camellia class TestAES(object): @@ -33,3 +33,18 @@ class TestAES(object): def test_invalid_key_size(self): with pytest.raises(ValueError): AES(binascii.unhexlify(b"0" * 12)) + + +class TestCamellia(object): + @pytest.mark.parametrize(("key", "keysize"), [ + (b"0" * 32, 128), + (b"0" * 48, 192), + (b"0" * 64, 256), + ]) + def test_key_size(self, key, keysize): + cipher = Camellia(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + Camellia(binascii.unhexlify(b"0" * 12)) diff --git a/tests/primitives/test_cryptrec.py b/tests/primitives/test_cryptrec.py new file mode 100644 index 00000000..edf97652 --- /dev/null +++ b/tests/primitives/test_cryptrec.py @@ -0,0 +1,42 @@ +# 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. + +""" +Tests using the CRYPTREC (Camellia) Test Vectors +""" + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +from cryptography.primitives.block import ciphers, modes + +from .utils import generate_encrypt_test +from ..utils import load_cryptrec_vectors_from_file + + +class TestCamelliaECB(object): + test_NTT = generate_encrypt_test( + load_cryptrec_vectors_from_file, + os.path.join("Camellia", "NTT"), + [ + "camellia-128-ecb.txt", + "camellia-192-ecb.txt", + "camellia-256-ecb.txt" + ], + lambda key: ciphers.Camellia(binascii.unhexlify((key))), + lambda key: modes.ECB(), + only_if=lambda api: api.supports_cipher("camellia-128-ecb"), + skip_message="Does not support Camellia ECB", + ) diff --git a/tests/primitives/test_nist.py b/tests/primitives/test_nist.py index 1e5d2396..d97b207b 100644 --- a/tests/primitives/test_nist.py +++ b/tests/primitives/test_nist.py @@ -18,33 +18,18 @@ Test using the NIST Test Vectors from __future__ import absolute_import, division, print_function import binascii -import itertools import os -import pytest - -from cryptography.primitives.block import BlockCipher, ciphers, modes +from cryptography.primitives.block import ciphers, modes +from .utils import generate_encrypt_test from ..utils import load_nist_vectors_from_file -def parameterize_encrypt_test(cipher, vector_type, params, fnames): - return pytest.mark.parametrize(params, - list(itertools.chain.from_iterable( - load_nist_vectors_from_file( - os.path.join(cipher, vector_type, fname), - "ENCRYPT", - params - ) - for fname in fnames - )) - ) - - class TestAES_CBC(object): - @parameterize_encrypt_test( - "AES", "KAT", - ("key", "iv", "plaintext", "ciphertext"), + test_KAT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("AES", "KAT"), [ "CBCGFSbox128.rsp", "CBCGFSbox192.rsp", @@ -58,40 +43,28 @@ class TestAES_CBC(object): "CBCVarTxt128.rsp", "CBCVarTxt192.rsp", "CBCVarTxt256.rsp", - ] + ], + lambda key, iv: ciphers.AES(binascii.unhexlify(key)), + lambda key, iv: modes.CBC(binascii.unhexlify(iv)), ) - def test_KAT(self, key, iv, plaintext, ciphertext): - cipher = BlockCipher( - ciphers.AES(binascii.unhexlify(key)), - modes.CBC(binascii.unhexlify(iv)), - ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() - assert binascii.hexlify(actual_ciphertext) == ciphertext - - @parameterize_encrypt_test( - "AES", "MMT", - ("key", "iv", "plaintext", "ciphertext"), + + test_MMT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("AES", "MMT"), [ "CBCMMT128.rsp", "CBCMMT192.rsp", "CBCMMT256.rsp", - ] + ], + lambda key, iv: ciphers.AES(binascii.unhexlify(key)), + lambda key, iv: modes.CBC(binascii.unhexlify(iv)), ) - def test_MMT(self, key, iv, plaintext, ciphertext): - cipher = BlockCipher( - ciphers.AES(binascii.unhexlify(key)), - modes.CBC(binascii.unhexlify(iv)), - ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() - assert binascii.hexlify(actual_ciphertext) == ciphertext class TestAES_ECB(object): - @parameterize_encrypt_test( - "AES", "KAT", - ("key", "plaintext", "ciphertext"), + test_KAT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("AES", "KAT"), [ "ECBGFSbox128.rsp", "ECBGFSbox192.rsp", @@ -105,40 +78,28 @@ class TestAES_ECB(object): "ECBVarTxt128.rsp", "ECBVarTxt192.rsp", "ECBVarTxt256.rsp", - ] + ], + lambda key: ciphers.AES(binascii.unhexlify(key)), + lambda key: modes.ECB(), ) - def test_KAT(self, key, plaintext, ciphertext): - cipher = BlockCipher( - ciphers.AES(binascii.unhexlify(key)), - modes.ECB() - ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() - assert binascii.hexlify(actual_ciphertext) == ciphertext - - @parameterize_encrypt_test( - "AES", "MMT", - ("key", "plaintext", "ciphertext"), + + test_MMT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("AES", "MMT"), [ "ECBMMT128.rsp", "ECBMMT192.rsp", "ECBMMT256.rsp", - ] + ], + lambda key: ciphers.AES(binascii.unhexlify(key)), + lambda key: modes.ECB(), ) - def test_MMT(self, key, plaintext, ciphertext): - cipher = BlockCipher( - ciphers.AES(binascii.unhexlify(key)), - modes.ECB() - ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() - assert binascii.hexlify(actual_ciphertext) == ciphertext class TestAES_OFB(object): - @parameterize_encrypt_test( - "AES", "KAT", - ("key", "iv", "plaintext", "ciphertext"), + test_KAT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("AES", "KAT"), [ "OFBGFSbox128.rsp", "OFBGFSbox192.rsp", @@ -152,40 +113,28 @@ class TestAES_OFB(object): "OFBVarTxt128.rsp", "OFBVarTxt192.rsp", "OFBVarTxt256.rsp", - ] + ], + lambda key, iv: ciphers.AES(binascii.unhexlify(key)), + lambda key, iv: modes.OFB(binascii.unhexlify(iv)), ) - def test_KAT(self, key, iv, plaintext, ciphertext): - cipher = BlockCipher( - ciphers.AES(binascii.unhexlify(key)), - modes.OFB(binascii.unhexlify(iv)) - ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() - assert binascii.hexlify(actual_ciphertext) == ciphertext - - @parameterize_encrypt_test( - "AES", "MMT", - ("key", "iv", "plaintext", "ciphertext"), + + test_MMT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("AES", "MMT"), [ "OFBMMT128.rsp", "OFBMMT192.rsp", "OFBMMT256.rsp", - ] + ], + lambda key, iv: ciphers.AES(binascii.unhexlify(key)), + lambda key, iv: modes.OFB(binascii.unhexlify(iv)), ) - def test_MMT(self, key, iv, plaintext, ciphertext): - cipher = BlockCipher( - ciphers.AES(binascii.unhexlify(key)), - modes.OFB(binascii.unhexlify(iv)) - ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() - assert binascii.hexlify(actual_ciphertext) == ciphertext class TestAES_CFB(object): - @parameterize_encrypt_test( - "AES", "KAT", - ("key", "iv", "plaintext", "ciphertext"), + test_KAT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("AES", "KAT"), [ "CFB128GFSbox128.rsp", "CFB128GFSbox192.rsp", @@ -199,31 +148,19 @@ class TestAES_CFB(object): "CFB128VarTxt128.rsp", "CFB128VarTxt192.rsp", "CFB128VarTxt256.rsp", - ] + ], + lambda key, iv: ciphers.AES(binascii.unhexlify(key)), + lambda key, iv: modes.CFB(binascii.unhexlify(iv)), ) - def test_KAT(self, key, iv, plaintext, ciphertext): - cipher = BlockCipher( - ciphers.AES(binascii.unhexlify(key)), - modes.CFB(binascii.unhexlify(iv)) - ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() - assert binascii.hexlify(actual_ciphertext) == ciphertext - - @parameterize_encrypt_test( - "AES", "MMT", - ("key", "iv", "plaintext", "ciphertext"), + + test_MMT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("AES", "MMT"), [ "CFB128MMT128.rsp", "CFB128MMT192.rsp", "CFB128MMT256.rsp", - ] + ], + lambda key, iv: ciphers.AES(binascii.unhexlify(key)), + lambda key, iv: modes.CFB(binascii.unhexlify(iv)), ) - def test_MMT(self, key, iv, plaintext, ciphertext): - cipher = BlockCipher( - ciphers.AES(binascii.unhexlify(key)), - modes.CFB(binascii.unhexlify(iv)) - ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() - assert binascii.hexlify(actual_ciphertext) == ciphertext diff --git a/tests/primitives/test_openssl_vectors.py b/tests/primitives/test_openssl_vectors.py new file mode 100644 index 00000000..5b2be784 --- /dev/null +++ b/tests/primitives/test_openssl_vectors.py @@ -0,0 +1,73 @@ +# 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. + +""" +Test using the OpenSSL Test Vectors +""" + +from __future__ import absolute_import, division, print_function + +import binascii + +from cryptography.primitives.block import ciphers, modes + +from .utils import generate_encrypt_test +from ..utils import load_openssl_vectors_from_file + + +class TestCamelliaCBC(object): + test_OpenSSL = generate_encrypt_test( + load_openssl_vectors_from_file, + "Camellia", + ["camellia-cbc.txt"], + lambda key, iv: ciphers.Camellia(binascii.unhexlify(key)), + lambda key, iv: modes.CBC(binascii.unhexlify(iv)), + only_if=lambda api: api.supports_cipher("camellia-128-cbc"), + skip_message="Does not support Camellia CBC", + ) + + +class TestCamelliaOFB(object): + test_OpenSSL = generate_encrypt_test( + load_openssl_vectors_from_file, + "Camellia", + ["camellia-ofb.txt"], + lambda key, iv: ciphers.Camellia(binascii.unhexlify(key)), + lambda key, iv: modes.OFB(binascii.unhexlify(iv)), + only_if=lambda api: api.supports_cipher("camellia-128-ofb"), + skip_message="Does not support Camellia OFB", + ) + + +class TestCamelliaCFB(object): + test_OpenSSL = generate_encrypt_test( + load_openssl_vectors_from_file, + "Camellia", + ["camellia-cfb.txt"], + lambda key, iv: ciphers.Camellia(binascii.unhexlify(key)), + lambda key, iv: modes.CFB(binascii.unhexlify(iv)), + only_if=lambda api: api.supports_cipher("camellia-128-cfb"), + skip_message="Does not support Camellia CFB", + ) + + +class TestAESCTR(object): + test_OpenSSL = generate_encrypt_test( + load_openssl_vectors_from_file, + "AES", + ["aes-128-ctr.txt", "aes-192-ctr.txt", "aes-256-ctr.txt"], + lambda key, iv: ciphers.AES(binascii.unhexlify(key)), + lambda key, iv: modes.CTR(binascii.unhexlify(iv)), + only_if=lambda api: api.supports_cipher("aes-128-ctr"), + skip_message="Does not support AES CTR", + ) diff --git a/tests/primitives/test_utils.py b/tests/primitives/test_utils.py new file mode 100644 index 00000000..4666ece7 --- /dev/null +++ b/tests/primitives/test_utils.py @@ -0,0 +1,14 @@ +import pytest + +from .utils import encrypt_test + + +class TestEncryptTest(object): + def test_skips_if_only_if_returns_false(self): + with pytest.raises(pytest.skip.Exception) as exc_info: + encrypt_test( + None, None, None, None, + only_if=lambda api: False, + skip_message="message!" + ) + assert exc_info.value.args[0] == "message!" diff --git a/tests/primitives/utils.py b/tests/primitives/utils.py new file mode 100644 index 00000000..3cf08c28 --- /dev/null +++ b/tests/primitives/utils.py @@ -0,0 +1,42 @@ +import binascii +import os + +import pytest + +from cryptography.bindings import _ALL_APIS +from cryptography.primitives.block import BlockCipher + + +def generate_encrypt_test(param_loader, path, file_names, cipher_factory, + mode_factory, only_if=lambda api: True, + skip_message=None): + def test_encryption(self): + for api in _ALL_APIS: + for file_name in file_names: + for params in param_loader(os.path.join(path, file_name)): + yield ( + encrypt_test, + api, + cipher_factory, + mode_factory, + params, + only_if, + skip_message + ) + return test_encryption + + +def encrypt_test(api, cipher_factory, mode_factory, params, only_if, + skip_message): + if not only_if(api): + pytest.skip(skip_message) + plaintext = params.pop("plaintext") + ciphertext = params.pop("ciphertext") + cipher = BlockCipher( + cipher_factory(**params), + mode_factory(**params), + api + ) + actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) + actual_ciphertext += cipher.finalize() + assert actual_ciphertext == binascii.unhexlify(ciphertext) diff --git a/tests/primitives/vectors/OpenSSL/AES/aes-128-ctr.txt b/tests/primitives/vectors/OpenSSL/AES/aes-128-ctr.txt new file mode 100644 index 00000000..f4ce15eb --- /dev/null +++ b/tests/primitives/vectors/OpenSSL/AES/aes-128-ctr.txt @@ -0,0 +1,4 @@ +# AES Counter test vectors from RFC3686 +aes-128-ctr:AE6852F8121067CC4BF7A5765577F39E:00000030000000000000000000000001:53696E676C6520626C6F636B206D7367:E4095D4FB7A7B3792D6175A3261311B8:1 +aes-128-ctr:7E24067817FAE0D743D6CE1F32539163:006CB6DBC0543B59DA48D90B00000001:000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:5104A106168A72D9790D41EE8EDAD388EB2E1EFC46DA57C8FCE630DF9141BE28:1 +aes-128-ctr:7691BE035E5020A8AC6E618529F9A0DC:00E0017B27777F3F4A1786F000000001:000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223:C1CF48A89F2FFDD9CF4652E9EFDB72D74540A42BDE6D7836D59A5CEAAEF3105325B2072F:1 diff --git a/tests/primitives/vectors/OpenSSL/AES/aes-192-ctr.txt b/tests/primitives/vectors/OpenSSL/AES/aes-192-ctr.txt new file mode 100644 index 00000000..9e166fc4 --- /dev/null +++ b/tests/primitives/vectors/OpenSSL/AES/aes-192-ctr.txt @@ -0,0 +1,4 @@ +# AES Counter test vectors from RFC3686 +aes-192-ctr:16AF5B145FC9F579C175F93E3BFB0EED863D06CCFDB78515:0000004836733C147D6D93CB00000001:53696E676C6520626C6F636B206D7367:4B55384FE259C9C84E7935A003CBE928:1 +aes-192-ctr:7C5CB2401B3DC33C19E7340819E0F69C678C3DB8E6F6A91A:0096B03B020C6EADC2CB500D00000001:000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:453243FC609B23327EDFAAFA7131CD9F8490701C5AD4A79CFC1FE0FF42F4FB00:1 +aes-192-ctr:02BF391EE8ECB159B959617B0965279BF59B60A786D3E0FE:0007BDFD5CBD60278DCC091200000001:000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223:96893FC55E5C722F540B7DD1DDF7E758D288BC95C69165884536C811662F2188ABEE0935:1 diff --git a/tests/primitives/vectors/OpenSSL/AES/aes-256-ctr.txt b/tests/primitives/vectors/OpenSSL/AES/aes-256-ctr.txt new file mode 100644 index 00000000..d4998750 --- /dev/null +++ b/tests/primitives/vectors/OpenSSL/AES/aes-256-ctr.txt @@ -0,0 +1,5 @@ +# AES Counter test vectors from RFC3686 +aes-256-ctr:776BEFF2851DB06F4C8A0542C8696F6C6A81AF1EEC96B4D37FC1D689E6C1C104:00000060DB5672C97AA8F0B200000001:53696E676C6520626C6F636B206D7367:145AD01DBF824EC7560863DC71E3E0C0:1 +aes-256-ctr:F6D66D6BD52D59BB0796365879EFF886C66DD51A5B6A99744B50590C87A23884:00FAAC24C1585EF15A43D87500000001:000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:F05E231B3894612C49EE000B804EB2A9B8306B508F839D6A5530831D9344AF1C:1 +aes-256-ctr:FF7A617CE69148E4F1726E2F43581DE2AA62D9F805532EDFF1EED687FB54153D:001CC5B751A51D70A1C1114800000001:000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223:EB6C52821D0BBBF7CE7594462ACA4FAAB407DF866569FD07F48CC0B583D6071F1EC0E6B8:1 + diff --git a/tests/test_utils.py b/tests/test_utils.py index 73394a51..28e7407b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -56,21 +56,19 @@ def test_load_nist_vectors_encrypt(): PLAINTEXT = 9798c4640bad75c7c3227db910174e72 """).splitlines() - assert load_nist_vectors(vector_data, "ENCRYPT", - ["key", "iv", "plaintext", "ciphertext"], - ) == [ - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"f34481ec3cc627bacd5dc3fb08f273e6", - b"0336763e966d92595a567cc9ce537f5e", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"9798c4640bad75c7c3227db910174e72", - b"a9a1631bf4996954ebc093957b234589", - ), + assert load_nist_vectors(vector_data, "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", + }, ] @@ -112,72 +110,69 @@ def test_load_nist_vectors_decrypt(): PLAINTEXT = 9798c4640bad75c7c3227db910174e72 """).splitlines() - assert load_nist_vectors(vector_data, "DECRYPT", - ["key", "iv", "ciphertext", "plaintext"], - ) == [ - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"0336763e966d92595a567cc9ce537f5e", - b"f34481ec3cc627bacd5dc3fb08f273e6", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"a9a1631bf4996954ebc093957b234589", - b"9798c4640bad75c7c3227db910174e72", - ), + 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( "AES/KAT/CBCGFSbox128.rsp", - "ENCRYPT", - ["key", "iv", "plaintext", "ciphertext"], + "ENCRYPT" ) == [ - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"f34481ec3cc627bacd5dc3fb08f273e6", - b"0336763e966d92595a567cc9ce537f5e", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"9798c4640bad75c7c3227db910174e72", - b"a9a1631bf4996954ebc093957b234589", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"96ab5c2ff612d9dfaae8c31f30c42168", - b"ff4f8391a6a40ca5b25d23bedd44a597", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"6a118a874519e64e9963798a503f1d35", - b"dc43be40be0e53712f7e2bf5ca707209", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"cb9fceec81286ca3e989bd979b0cb284", - b"92beedab1895a94faa69b632e5cc47ce", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"b26aeb1874e47ca8358ff22378f09144", - b"459264f4798f6a78bacb89c15ed3d601", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"58c8e00b2631686d54eab84b91f0aca1", - b"08a4e2efec8a8e3312ca7460b9040bbf", - ), + { + "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", + }, ] @@ -185,50 +180,49 @@ def test_load_nist_vectors_from_file_decrypt(): assert load_nist_vectors_from_file( "AES/KAT/CBCGFSbox128.rsp", "DECRYPT", - ["key", "iv", "ciphertext", "plaintext"], ) == [ - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"0336763e966d92595a567cc9ce537f5e", - b"f34481ec3cc627bacd5dc3fb08f273e6", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"a9a1631bf4996954ebc093957b234589", - b"9798c4640bad75c7c3227db910174e72", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"ff4f8391a6a40ca5b25d23bedd44a597", - b"96ab5c2ff612d9dfaae8c31f30c42168", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"dc43be40be0e53712f7e2bf5ca707209", - b"6a118a874519e64e9963798a503f1d35", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"92beedab1895a94faa69b632e5cc47ce", - b"cb9fceec81286ca3e989bd979b0cb284", - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"459264f4798f6a78bacb89c15ed3d601", - b"b26aeb1874e47ca8358ff22378f09144" - ), - ( - b"00000000000000000000000000000000", - b"00000000000000000000000000000000", - b"08a4e2efec8a8e3312ca7460b9040bbf", - b"58c8e00b2631686d54eab84b91f0aca1" - ), + { + "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", + }, ] @@ -254,21 +248,21 @@ def test_load_cryptrec_vectors(): """).splitlines() assert load_cryptrec_vectors(vector_data) == [ - ( - b"00000000000000000000000000000000", - b"80000000000000000000000000000000", - b"07923A39EB0A817D1C4D87BDB82D1F1C", - ), - ( - b"00000000000000000000000000000000", - b"40000000000000000000000000000000", - b"48CD6419809672D2349260D89A08D3D3", - ), - ( - b"10000000000000000000000000000000", - b"80000000000000000000000000000000", - b"07923A39EB0A817D1C4D87BDB82D1F1C", - ), + { + "key": b"00000000000000000000000000000000", + "plaintext": b"80000000000000000000000000000000", + "ciphertext": b"07923A39EB0A817D1C4D87BDB82D1F1C", + }, + { + "key": b"00000000000000000000000000000000", + "plaintext": b"40000000000000000000000000000000", + "ciphertext": b"48CD6419809672D2349260D89A08D3D3", + }, + { + "key": b"10000000000000000000000000000000", + "plaintext": b"80000000000000000000000000000000", + "ciphertext": b"07923A39EB0A817D1C4D87BDB82D1F1C", + }, ] @@ -277,11 +271,11 @@ def test_load_cryptrec_vectors_from_file_encrypt(): "Camellia/NTT/camellia-128-ecb.txt" ) assert test_set[0] == ( - ( - b"00000000000000000000000000000000", - b"80000000000000000000000000000000", - b"07923A39EB0A817D1C4D87BDB82D1F1C", - ) + { + "key": b"00000000000000000000000000000000", + "plaintext": b"80000000000000000000000000000000", + "ciphertext": b"07923A39EB0A817D1C4D87BDB82D1F1C", + } ) assert len(test_set) == 1280 @@ -310,30 +304,30 @@ def test_load_openssl_vectors(): ).splitlines() assert load_openssl_vectors(vector_data) == [ - ( - b"2B7E151628AED2A6ABF7158809CF4F3C", - b"000102030405060708090A0B0C0D0E0F", - b"6BC1BEE22E409F96E93D7E117393172A", - b"14F7646187817EB586599146B82BD719", - ), - ( - b"2B7E151628AED2A6ABF7158809CF4F3C", - b"14F7646187817EB586599146B82BD719", - b"AE2D8A571E03AC9C9EB76FAC45AF8E51", - b"A53D28BB82DF741103EA4F921A44880B", - ), - ( - b"2B7E151628AED2A6ABF7158809CF4F3C", - b"000102030405060708090A0B0C0D0E0F", - b"6BC1BEE22E409F96E93D7E117393172A", - b"14F7646187817EB586599146B82BD719", - ), - ( - b"2B7E151628AED2A6ABF7158809CF4F3C", - b"14F7646187817EB586599146B82BD719", - b"AE2D8A571E03AC9C9EB76FAC45AF8E51", - b"A53D28BB82DF741103EA4F921A44880B", - ), + { + "key": b"2B7E151628AED2A6ABF7158809CF4F3C", + "iv": b"000102030405060708090A0B0C0D0E0F", + "plaintext": b"6BC1BEE22E409F96E93D7E117393172A", + "ciphertext": b"14F7646187817EB586599146B82BD719", + }, + { + "key": b"2B7E151628AED2A6ABF7158809CF4F3C", + "iv": b"14F7646187817EB586599146B82BD719", + "plaintext": b"AE2D8A571E03AC9C9EB76FAC45AF8E51", + "ciphertext": b"A53D28BB82DF741103EA4F921A44880B", + }, + { + "key": b"2B7E151628AED2A6ABF7158809CF4F3C", + "iv": b"000102030405060708090A0B0C0D0E0F", + "plaintext": b"6BC1BEE22E409F96E93D7E117393172A", + "ciphertext": b"14F7646187817EB586599146B82BD719", + }, + { + "key": b"2B7E151628AED2A6ABF7158809CF4F3C", + "iv": b"14F7646187817EB586599146B82BD719", + "plaintext": b"AE2D8A571E03AC9C9EB76FAC45AF8E51", + "ciphertext": b"A53D28BB82DF741103EA4F921A44880B", + }, ] @@ -341,28 +335,28 @@ def test_load_openssl_vectors_from_file(): test_list = load_openssl_vectors_from_file("Camellia/camellia-ofb.txt") assert len(test_list) == 24 assert test_list[:4] == [ - ( - b"2B7E151628AED2A6ABF7158809CF4F3C", - b"000102030405060708090A0B0C0D0E0F", - b"6BC1BEE22E409F96E93D7E117393172A", - b"14F7646187817EB586599146B82BD719", - ), - ( - b"2B7E151628AED2A6ABF7158809CF4F3C", - b"50FE67CC996D32B6DA0937E99BAFEC60", - b"AE2D8A571E03AC9C9EB76FAC45AF8E51", - b"25623DB569CA51E01482649977E28D84", - ), - ( - b"2B7E151628AED2A6ABF7158809CF4F3C", - b"D9A4DADA0892239F6B8B3D7680E15674", - b"30C81C46A35CE411E5FBC1191A0A52EF", - b"C776634A60729DC657D12B9FCA801E98", - ), - ( - b"2B7E151628AED2A6ABF7158809CF4F3C", - b"A78819583F0308E7A6BF36B1386ABF23", - b"F69F2445DF4F9B17AD2B417BE66C3710", - b"D776379BE0E50825E681DA1A4C980E8E", - ), + { + "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", + }, ] diff --git a/tests/utils.py b/tests/utils.py index d06c9e3b..6b1cfd79 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -14,7 +14,7 @@ import os.path -def load_nist_vectors(vector_data, op, fields): +def load_nist_vectors(vector_data, op): section, count, data = None, None, {} for line in vector_data: @@ -44,21 +44,19 @@ def load_nist_vectors(vector_data, op, fields): # For all other tokens we simply want the name, value stored in # the dictionary else: - data[section][count][name.lower()] = value + data[section][count][name.lower()] = value.encode("ascii") - # We want to test only for a particular operation - return [ - tuple(vector[1][f].encode("ascii") for f in fields) - for vector in sorted(data[op].items(), key=lambda v: v[0]) - ] + # 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, fields): +def load_nist_vectors_from_file(filename, op): base = os.path.join( os.path.dirname(__file__), "primitives", "vectors", "NIST", ) with open(os.path.join(base, filename), "r") as vector_file: - return load_nist_vectors(vector_file, op, fields) + return load_nist_vectors(vector_file, op) def load_cryptrec_vectors_from_file(filename): @@ -87,7 +85,11 @@ def load_cryptrec_vectors(vector_data): ct = line.split(" : ")[1].replace(" ", "").encode("ascii") # after a C is found the K+P+C tuple is complete # there are many P+C pairs for each K - cryptrec_list.append((key, pt, ct)) + cryptrec_list.append({ + "key": key, + "plaintext": pt, + "ciphertext": ct + }) return cryptrec_list @@ -110,15 +112,10 @@ def load_openssl_vectors(vector_data): continue vector = line.split(":") - params = ( - # key - vector[1].encode("ascii"), - # iv - vector[2].encode("ascii"), - # plaintext - vector[3].encode("ascii"), - # ciphertext - vector[4].encode("ascii") - ) - vectors.append(params) + vectors.append({ + "key": vector[1].encode("ascii"), + "iv": vector[2].encode("ascii"), + "plaintext": vector[3].encode("ascii"), + "ciphertext": vector[4].encode("ascii"), + }) return vectors @@ -3,9 +3,12 @@ envlist = py26,py27,pypy,py32,py33,docs,pep8 [testenv] deps = - pytest-cov + pytest + coverage pretend -commands = py.test --cov=cryptography/ --cov=tests/ +commands = + coverage run --source=cryptography/,tests/ -m pytest + coverage report -m [testenv:docs] deps = sphinx |