diff options
26 files changed, 379 insertions, 246 deletions
diff --git a/.travis.yml b/.travis.yml index 76decf62..8ad514fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -120,7 +120,7 @@ script: - ./.travis/run.sh after_success: - - source ~/.venv/bin/activate && coveralls + - source ~/.venv/bin/activate && bash <(curl -s https://codecov.io/bash) -e TRAVIS_OS_NAME,TOXENV,OPENSSL notifications: irc: diff --git a/.travis/install.sh b/.travis/install.sh index a046a5d8..17aee435 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -65,4 +65,4 @@ fi python -m virtualenv ~/.venv source ~/.venv/bin/activate -pip install tox coveralls +pip install tox codecov @@ -12,8 +12,8 @@ Cryptography .. image:: https://travis-ci.org/pyca/cryptography.svg?branch=master :target: https://travis-ci.org/pyca/cryptography -.. image:: https://img.shields.io/coveralls/pyca/cryptography/master.svg - :target: https://coveralls.io/r/pyca/cryptography?branch=master +.. image:: https://codecov.io/github/pyca/cryptography/coverage.svg?branch=master + :target: https://codecov.io/github/pyca/cryptography?branch=master ``cryptography`` is a package which provides cryptographic recipes and diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index ac667bb7..fe64fe1a 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -199,14 +199,25 @@ Custom X.509 Vectors set. * ``nc_permitted_excluded.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with both permitted and excluded - elements. + elements. Contains ``IPv4`` and ``IPv6`` addresses with network mask as well + as ``dNSName`` with a leading period. +* ``nc_permitted_excluded_2.pem`` - An RSA 2048 bit self-signed certificate + containing a name constraints extension with both permitted and excluded + elements. Unlike ``nc_permitted_excluded.pem``, the general names do not + contain any name constraints specific values. * ``nc_permitted.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with permitted elements. +* ``nc_permitted_2.pem`` - An RSA 2048 bit self-signed certificate containing a + name constraints extension with permitted elements that do not contain any + name constraints specific values. * ``nc_excluded.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with excluded elements. * ``nc_invalid_ip_netmask.pem`` - An RSA 2048 bit self-signed certificate containing a name constraints extension with a permitted element that has an ``IPv6`` IP and an invalid network mask. +* ``nc_single_ip_netmask.pem`` - An RSA 2048 bit self-signed certificate + containing a name constraints extension with a permitted element that has two + IPs with ``/32`` and ``/128`` network masks. * ``cp_user_notice_with_notice_reference.pem`` - An RSA 2048 bit self-signed certificate containing a certificate policies extension with a notice reference in the user notice. diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py index 1ebadccb..dac3e4d8 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -78,7 +78,6 @@ ffi = build_ffi_for_binding( "nid", "objects", "opensslv", - "osrandom_engine", "pem", "pkcs7", "pkcs12", diff --git a/src/_cffi_src/openssl/engine.py b/src/_cffi_src/openssl/engine.py index 3ebfa6c1..5079fd69 100644 --- a/src/_cffi_src/openssl/engine.py +++ b/src/_cffi_src/openssl/engine.py @@ -17,9 +17,16 @@ typedef ... DSA_METHOD; typedef ... ECDH_METHOD; typedef ... ECDSA_METHOD; typedef ... DH_METHOD; -typedef ... RAND_METHOD; +typedef struct { + void (*seed)(const void *, int); + int (*bytes)(unsigned char *, int); + void (*cleanup)(); + void (*add)(const void *, int, double); + int (*pseudorand)(unsigned char *, int); + int (*status)(); +} RAND_METHOD; typedef ... STORE_METHOD; -typedef ... *ENGINE_GEN_INT_FUNC_PTR; +typedef int (*ENGINE_GEN_INT_FUNC_PTR)(ENGINE *); typedef ... *ENGINE_CTRL_FUNC_PTR; typedef ... *ENGINE_LOAD_KEY_PTR; typedef ... *ENGINE_CIPHERS_PTR; diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py index 0ee19c9e..eebf19ba 100644 --- a/src/_cffi_src/openssl/err.py +++ b/src/_cffi_src/openssl/err.py @@ -251,6 +251,7 @@ 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 *); +void ERR_clear_error(void); unsigned long ERR_peek_error_line_data(const char **, int *, const char **, int *); unsigned long ERR_peek_last_error_line_data(const char **, diff --git a/src/_cffi_src/openssl/osrandom_engine.py b/src/_cffi_src/openssl/osrandom_engine.py deleted file mode 100644 index a8479b07..00000000 --- a/src/_cffi_src/openssl/osrandom_engine.py +++ /dev/null @@ -1,31 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - -from __future__ import absolute_import, division, print_function - -import os - -with open(os.path.join( - os.path.dirname(__file__), "src/osrandom_engine.h" -)) as f: - INCLUDES = f.read() - -TYPES = """ -static const char *const Cryptography_osrandom_engine_name; -static const char *const Cryptography_osrandom_engine_id; -""" - -FUNCTIONS = """ -int Cryptography_add_osrandom_engine(void); -""" - -MACROS = """ -""" - -with open(os.path.join( - os.path.dirname(__file__), "src/osrandom_engine.c" -)) as f: - CUSTOMIZATIONS = f.read() - -CONDITIONAL_NAMES = {} diff --git a/src/_cffi_src/openssl/src/osrandom_engine.c b/src/_cffi_src/openssl/src/osrandom_engine.c deleted file mode 100644 index 27894712..00000000 --- a/src/_cffi_src/openssl/src/osrandom_engine.c +++ /dev/null @@ -1,167 +0,0 @@ -static const char *Cryptography_osrandom_engine_id = "osrandom"; -static const char *Cryptography_osrandom_engine_name = "osrandom_engine"; - -#if defined(_WIN32) -static HCRYPTPROV hCryptProv = 0; - -static int osrandom_init(ENGINE *e) { - if (hCryptProv > 0) { - return 1; - } - if (CryptAcquireContext(&hCryptProv, NULL, NULL, - PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - return 1; - } else { - return 0; - } -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - if (hCryptProv == 0) { - return 0; - } - - if (!CryptGenRandom(hCryptProv, (DWORD)size, buffer)) { - ERR_put_error( - ERR_LIB_RAND, 0, ERR_R_RAND_LIB, "osrandom_engine.py", 0 - ); - return 0; - } - return 1; -} - -static int osrandom_finish(ENGINE *e) { - if (CryptReleaseContext(hCryptProv, 0)) { - hCryptProv = 0; - return 1; - } else { - return 0; - } -} - -static int osrandom_rand_status(void) { - if (hCryptProv == 0) { - return 0; - } else { - return 1; - } -} -#else -static int urandom_fd = -1; - -static int osrandom_finish(ENGINE *e); - -static int osrandom_init(ENGINE *e) { - if (urandom_fd > -1) { - return 1; - } - urandom_fd = open("/dev/urandom", O_RDONLY); - if (urandom_fd > -1) { - int flags = fcntl(urandom_fd, F_GETFD); - if (flags == -1) { - osrandom_finish(e); - return 0; - } else if (fcntl(urandom_fd, F_SETFD, flags | FD_CLOEXEC) == -1) { - osrandom_finish(e); - return 0; - } - return 1; - } else { - return 0; - } -} - -static int osrandom_rand_bytes(unsigned char *buffer, int size) { - ssize_t n; - while (size > 0) { - do { - n = read(urandom_fd, buffer, (size_t)size); - } while (n < 0 && errno == EINTR); - if (n <= 0) { - ERR_put_error( - ERR_LIB_RAND, 0, ERR_R_RAND_LIB, "osrandom_engine.py", 0 - ); - return 0; - } - buffer += n; - size -= n; - } - return 1; -} - -static int osrandom_finish(ENGINE *e) { - int n; - do { - n = close(urandom_fd); - } while (n < 0 && errno == EINTR); - urandom_fd = -1; - if (n < 0) { - return 0; - } else { - return 1; - } -} - -static int osrandom_rand_status(void) { - if (urandom_fd == -1) { - return 0; - } else { - return 1; - } -} -#endif - -/* This replicates the behavior of the OpenSSL FIPS RNG, which returns a - -1 in the event that there is an error when calling RAND_pseudo_bytes. */ -static int osrandom_pseudo_rand_bytes(unsigned char *buffer, int size) { - int res = osrandom_rand_bytes(buffer, size); - if (res == 0) { - return -1; - } else { - return res; - } -} - -static RAND_METHOD osrandom_rand = { - NULL, - osrandom_rand_bytes, - NULL, - NULL, - osrandom_pseudo_rand_bytes, - osrandom_rand_status, -}; - -/* Returns 1 if successfully added, 2 if engine has previously been added, - and 0 for error. */ -int Cryptography_add_osrandom_engine(void) { - ENGINE *e; - e = ENGINE_by_id(Cryptography_osrandom_engine_id); - if (e != NULL) { - ENGINE_free(e); - return 2; - } else { - ERR_clear_error(); - } - - e = ENGINE_new(); - if (e == NULL) { - return 0; - } - if(!ENGINE_set_id(e, Cryptography_osrandom_engine_id) || - !ENGINE_set_name(e, Cryptography_osrandom_engine_name) || - !ENGINE_set_RAND(e, &osrandom_rand) || - !ENGINE_set_init_function(e, osrandom_init) || - !ENGINE_set_finish_function(e, osrandom_finish)) { - ENGINE_free(e); - return 0; - } - if (!ENGINE_add(e)) { - ENGINE_free(e); - return 0; - } - if (!ENGINE_free(e)) { - return 0; - } - - return 1; -} diff --git a/src/_cffi_src/openssl/src/osrandom_engine.h b/src/_cffi_src/openssl/src/osrandom_engine.h deleted file mode 100644 index 11a3159e..00000000 --- a/src/_cffi_src/openssl/src/osrandom_engine.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifdef _WIN32 -#include <Wincrypt.h> -#else -#include <fcntl.h> -#include <unistd.h> -#endif diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 51f68899..f05b0515 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -217,7 +217,7 @@ class Backend(object): self.activate_builtin_random() # Fetches an engine by id and returns it. This creates a structural # reference. - e = self._lib.ENGINE_by_id(self._lib.Cryptography_osrandom_engine_id) + e = self._lib.ENGINE_by_id(self._binding._osrandom_engine_id) assert e != self._ffi.NULL # Initialize the engine for use. This adds a functional reference. res = self._lib.ENGINE_init(e) @@ -872,25 +872,20 @@ class Backend(object): ) for extension in builder._extensions: if isinstance(extension.value, x509.BasicConstraints): - pp, r = _encode_basic_constraints( - self, extension.value, - ) + pp, r = _encode_basic_constraints(self, extension.value) elif isinstance(extension.value, x509.SubjectAlternativeName): - pp, r = _encode_subject_alt_name( - self, extension.value, - ) + pp, r = _encode_subject_alt_name(self, extension.value) else: raise NotImplementedError('Extension not yet supported.') obj = _txt2obj(self, extension.oid.dotted_string) - extension = backend._lib.X509_EXTENSION_create_by_OBJ( - backend._ffi.NULL, + extension = self._lib.X509_EXTENSION_create_by_OBJ( + self._ffi.NULL, obj, 1 if extension.critical else 0, - _encode_asn1_str(backend, pp[0], r) + _encode_asn1_str(self, pp[0], r), ) - assert extension != backend._ffi.NULL - + assert extension != self._ffi.NULL res = self._lib.sk_X509_EXTENSION_push(extensions, extension) assert res == 1 res = self._lib.X509_REQ_add_extensions(x509_req, extensions) diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 804bce66..80e5f2b1 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -86,13 +86,17 @@ def _decode_general_name(backend, gn): # This is a wildcard name. We need to remove the leading wildcard, # IDNA decode, then re-add the wildcard. Wildcard characters should # always be left-most (RFC 2595 section 2.4). - data = u"*." + idna.decode(data[2:]) + decoded = u"*." + idna.decode(data[2:]) else: # Not a wildcard, decode away. If the string has a * in it anywhere # invalid this will raise an InvalidCodePoint - data = idna.decode(data) + decoded = idna.decode(data) + if data.startswith(b"."): + # idna strips leading periods. Name constraints can have that + # so we need to re-add it. Sigh. + decoded = u"." + decoded - return x509.DNSName(data) + return x509.DNSName(decoded) elif gn.type == backend._lib.GEN_URI: data = backend._ffi.buffer( gn.d.uniformResourceIdentifier.data, @@ -537,6 +541,35 @@ def _decode_issuer_alt_name(backend, ext): ) +def _decode_name_constraints(backend, ext): + nc = backend._ffi.cast( + "NAME_CONSTRAINTS *", backend._lib.X509V3_EXT_d2i(ext) + ) + assert nc != backend._ffi.NULL + nc = backend._ffi.gc(nc, backend._lib.NAME_CONSTRAINTS_free) + permitted = _decode_general_subtrees(backend, nc.permittedSubtrees) + excluded = _decode_general_subtrees(backend, nc.excludedSubtrees) + return x509.NameConstraints( + permitted_subtrees=permitted, excluded_subtrees=excluded + ) + + +def _decode_general_subtrees(backend, stack_subtrees): + if stack_subtrees == backend._ffi.NULL: + return None + + num = backend._lib.sk_GENERAL_SUBTREE_num(stack_subtrees) + subtrees = [] + + for i in range(num): + obj = backend._lib.sk_GENERAL_SUBTREE_value(stack_subtrees, i) + assert obj != backend._ffi.NULL + name = _decode_general_name(backend, obj.base) + subtrees.append(name) + + return subtrees + + def _decode_extended_key_usage(backend, ext): sk = backend._ffi.cast( "Cryptography_STACK_OF_ASN1_OBJECT *", @@ -728,6 +761,7 @@ _CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser( x509.OID_OCSP_NO_CHECK: _decode_ocsp_no_check, x509.OID_INHIBIT_ANY_POLICY: _decode_inhibit_any_policy, x509.OID_ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name, + x509.OID_NAME_CONSTRAINTS: _decode_name_constraints, } ) diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index e0a83972..b7178bb2 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -4,11 +4,25 @@ from __future__ import absolute_import, division, print_function +import os import threading from cryptography.hazmat.bindings._openssl import ffi, lib +@ffi.callback("int (*)(unsigned char *, int)", error=-1) +def _osrandom_rand_bytes(buf, size): + signed = ffi.cast("char *", buf) + result = os.urandom(size) + signed[0:size] = result + return 1 + + +@ffi.callback("int (*)(void)") +def _osrandom_rand_status(): + return 1 + + class Binding(object): """ OpenSSL API wrapper. @@ -21,10 +35,42 @@ class Binding(object): _init_lock = threading.Lock() _lock_init_lock = threading.Lock() + _osrandom_engine_id = ffi.new("const char[]", b"osrandom") + _osrandom_engine_name = ffi.new("const char[]", b"osrandom_engine") + _osrandom_method = ffi.new( + "RAND_METHOD *", + dict(bytes=_osrandom_rand_bytes, pseudorand=_osrandom_rand_bytes, + status=_osrandom_rand_status) + ) + def __init__(self): self._ensure_ffi_initialized() @classmethod + def _register_osrandom_engine(cls): + assert cls.lib.ERR_peek_error() == 0 + looked_up_engine = cls.lib.ENGINE_by_id(cls._osrandom_engine_id) + if looked_up_engine != ffi.NULL: + raise RuntimeError("osrandom engine already registered") + + cls.lib.ERR_clear_error() + + engine = cls.lib.ENGINE_new() + assert engine != cls.ffi.NULL + try: + result = cls.lib.ENGINE_set_id(engine, cls._osrandom_engine_id) + assert result == 1 + result = cls.lib.ENGINE_set_name(engine, cls._osrandom_engine_name) + assert result == 1 + result = cls.lib.ENGINE_set_RAND(engine, cls._osrandom_method) + assert result == 1 + result = cls.lib.ENGINE_add(engine) + assert result == 1 + finally: + result = cls.lib.ENGINE_free(engine) + assert result == 1 + + @classmethod def _ensure_ffi_initialized(cls): if cls._lib_loaded: return @@ -32,8 +78,7 @@ class Binding(object): with cls._init_lock: if not cls._lib_loaded: cls._lib_loaded = True - res = cls.lib.Cryptography_add_osrandom_engine() - assert res != 0 + cls._register_osrandom_engine() @classmethod def init_static_locks(cls): diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py index 8f3028fc..dae93655 100644 --- a/src/cryptography/hazmat/primitives/ciphers/base.py +++ b/src/cryptography/hazmat/primitives/ciphers/base.py @@ -149,6 +149,8 @@ class _CipherContext(object): class _AEADCipherContext(object): def __init__(self, ctx): self._ctx = ctx + self._bytes_processed = 0 + self._aad_bytes_processed = 0 self._tag = None self._updated = False @@ -156,6 +158,14 @@ class _AEADCipherContext(object): if self._ctx is None: raise AlreadyFinalized("Context was already finalized.") self._updated = True + self._bytes_processed += len(data) + if self._bytes_processed > self._ctx._mode._MAX_ENCRYPTED_BYTES: + raise ValueError( + "{0} has a maximum encrypted byte limit of {1}".format( + self._ctx._mode.name, self._ctx._mode._MAX_ENCRYPTED_BYTES + ) + ) + return self._ctx.update(data) def finalize(self): @@ -171,6 +181,15 @@ class _AEADCipherContext(object): raise AlreadyFinalized("Context was already finalized.") if self._updated: raise AlreadyUpdated("Update has been called on this context.") + + self._aad_bytes_processed += len(data) + if self._aad_bytes_processed > self._ctx._mode._MAX_AAD_BYTES: + raise ValueError( + "{0} has a maximum AAD byte limit of {0}".format( + self._ctx._mode.name, self._ctx._mode._MAX_AAD_BYTES + ) + ) + self._ctx.authenticate_additional_data(data) diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py index e31c9060..4284042d 100644 --- a/src/cryptography/hazmat/primitives/ciphers/modes.py +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -139,6 +139,8 @@ class CTR(object): @utils.register_interface(ModeWithAuthenticationTag) class GCM(object): name = "GCM" + _MAX_ENCRYPTED_BYTES = (2 ** 39 - 256) // 8 + _MAX_AAD_BYTES = (2 ** 64) // 8 def __init__(self, initialization_vector, tag=None, min_tag_length=16): # len(initialization_vector) must in [1, 2 ** 64), but it's impossible diff --git a/tests/conftest.py b/tests/conftest.py index c4d6b9c1..6599a643 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,11 +18,13 @@ def pytest_generate_tests(metafunc): if "backend" in metafunc.fixturenames: filtered_backends = [] required = metafunc.function.requires_backend_interface - required_interfaces = tuple( + required_interfaces = [ mark.kwargs["interface"] for mark in required - ) + ] for backend in selected_backends: - if isinstance(backend, required_interfaces): + if all( + isinstance(backend, iface) for iface in required_interfaces + ): filtered_backends.append(backend) # If you pass an empty list to parametrize Bad Things(tm) happen diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 34fff277..6a2e8a77 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -224,7 +224,7 @@ class TestOpenSSLRandomEngine(object): backend.activate_osrandom_engine() current_default = backend._lib.ENGINE_get_default_RAND() name = backend._lib.ENGINE_get_name(current_default) - assert name == backend._lib.Cryptography_osrandom_engine_name + assert name == backend._binding._osrandom_engine_name def test_osrandom_engine_is_default(self, tmpdir): engine_printer = textwrap.dedent( @@ -258,7 +258,7 @@ class TestOpenSSLRandomEngine(object): ) osrandom_engine_name = backend._ffi.string( - backend._lib.Cryptography_osrandom_engine_name + backend._binding._osrandom_engine_name ) assert engine_name.read().encode('ascii') == osrandom_engine_name @@ -277,7 +277,7 @@ class TestOpenSSLRandomEngine(object): backend.activate_osrandom_engine() e = backend._lib.ENGINE_get_default_RAND() name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name + assert name == backend._binding._osrandom_engine_name res = backend._lib.ENGINE_free(e) assert res == 1 @@ -285,7 +285,7 @@ class TestOpenSSLRandomEngine(object): e = backend._lib.ENGINE_get_default_RAND() assert e != backend._ffi.NULL name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name + assert name == backend._binding._osrandom_engine_name res = backend._lib.ENGINE_free(e) assert res == 1 backend.activate_builtin_random() @@ -303,13 +303,13 @@ class TestOpenSSLRandomEngine(object): def test_activate_osrandom_already_default(self): e = backend._lib.ENGINE_get_default_RAND() name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name + assert name == backend._binding._osrandom_engine_name res = backend._lib.ENGINE_free(e) assert res == 1 backend.activate_osrandom_engine() e = backend._lib.ENGINE_get_default_RAND() name = backend._lib.ENGINE_get_name(e) - assert name == backend._lib.Cryptography_osrandom_engine_name + assert name == backend._binding._osrandom_engine_name res = backend._lib.ENGINE_free(e) assert res == 1 diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index e6d6fc45..f3f2eaf4 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -4,11 +4,27 @@ from __future__ import absolute_import, division, print_function +import os + import pytest from cryptography.hazmat.bindings.openssl.binding import Binding +def skip_if_libre_ssl(openssl_version): + if b'LibreSSL' in openssl_version: + pytest.skip("LibreSSL hard-codes RAND_bytes to use arc4random.") + + +class TestLibreSkip(object): + def test_skip_no(self): + assert skip_if_libre_ssl(b"OpenSSL 0.9.8zf 19 Mar 2015") is None + + def test_skip_yes(self): + with pytest.raises(pytest.skip.Exception): + skip_if_libre_ssl(b"LibreSSL 2.1.6") + + class TestOpenSSL(object): def test_binding_loads(self): binding = Binding() @@ -89,8 +105,22 @@ class TestOpenSSL(object): def test_add_engine_more_than_once(self): b = Binding() - res = b.lib.Cryptography_add_osrandom_engine() - assert res == 2 + with pytest.raises(RuntimeError): + b._register_osrandom_engine() + + def test_actual_osrandom_bytes(self, monkeypatch): + b = Binding() + skip_if_libre_ssl(b.ffi.string(b.lib.OPENSSL_VERSION_TEXT)) + sample_data = (b"\x01\x02\x03\x04" * 4) + length = len(sample_data) + + def notrandom(size): + assert size == length + return sample_data + monkeypatch.setattr(os, "urandom", notrandom) + buf = b.ffi.new("char[]", length) + b.lib.RAND_bytes(buf, length) + assert b.ffi.buffer(buf)[0:length] == sample_data def test_ssl_ctx_options(self): # Test that we're properly handling 32-bit unsigned on all platforms. diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 4d48e8ad..2c3e5f90 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -253,3 +253,53 @@ class TestAESModeGCM(object): computed_ct = encryptor.update(pt) + encryptor.finalize() assert computed_ct == ct assert encryptor.tag == tag + + def test_gcm_ciphertext_limit(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor._bytes_processed = modes.GCM._MAX_ENCRYPTED_BYTES - 16 + encryptor.update(b"0" * 16) + assert ( + encryptor._bytes_processed == modes.GCM._MAX_ENCRYPTED_BYTES + ) + with pytest.raises(ValueError): + encryptor.update(b"0") + + def test_gcm_aad_limit(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor._aad_bytes_processed = modes.GCM._MAX_AAD_BYTES - 16 + encryptor.authenticate_additional_data(b"0" * 16) + assert encryptor._aad_bytes_processed == modes.GCM._MAX_AAD_BYTES + with pytest.raises(ValueError): + encryptor.authenticate_additional_data(b"0") + + def test_gcm_ciphertext_increments(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor.update(b"0" * 8) + assert encryptor._bytes_processed == 8 + encryptor.update(b"0" * 7) + assert encryptor._bytes_processed == 15 + encryptor.update(b"0" * 18) + assert encryptor._bytes_processed == 33 + + def test_gcm_aad_increments(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(b"0" * 8) + assert encryptor._aad_bytes_processed == 8 + encryptor.authenticate_additional_data(b"0" * 18) + assert encryptor._aad_bytes_processed == 26 diff --git a/tests/test_interfaces.py b/tests/test_interfaces.py index 4d571ea6..bdb4a94d 100644 --- a/tests/test_interfaces.py +++ b/tests/test_interfaces.py @@ -36,6 +36,8 @@ class TestVerifyInterface(object): def method(self): """Method with no arguments""" + # Invoke this to ensure the line is covered + NonImplementer().method() with pytest.raises(InterfaceNotImplemented): verify_interface(SimpleInterface, NonImplementer) @@ -51,4 +53,6 @@ class TestVerifyInterface(object): def property(self): """A concrete property""" + # Invoke this to ensure the line is covered + NonImplementer().property verify_interface(SimpleInterface, NonImplementer) diff --git a/tests/test_utils.py b/tests/test_utils.py index 8601d11d..f71264ea 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -85,7 +85,7 @@ def test_check_backend_support_skip(): supported = pretend.stub( kwargs={"only_if": lambda backend: False, "skip_message": "Nope"} ) - item = pretend.stub(keywords={"supported": supported}, + item = pretend.stub(keywords={"supported": [supported]}, funcargs={"backend": True}) with pytest.raises(pytest.skip.Exception) as exc_info: check_backend_support(item) @@ -96,7 +96,7 @@ def test_check_backend_support_no_skip(): supported = pretend.stub( kwargs={"only_if": lambda backend: True, "skip_message": "Nope"} ) - item = pretend.stub(keywords={"supported": supported}, + item = pretend.stub(keywords={"supported": [supported]}, funcargs={"backend": True}) assert check_backend_support(item) is None diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py index 6d91ba41..0ef84e79 100644 --- a/tests/test_x509_ext.py +++ b/tests/test_x509_ext.py @@ -2033,6 +2033,88 @@ class TestNameConstraints(object): assert nc != object() +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestNameConstraintsExtension(object): + def test_permitted_excluded(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted_excluded_2.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + x509.OID_NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.DNSName(u"zombo.local"), + ], + excluded_subtrees=[ + x509.DirectoryName(x509.Name([ + x509.NameAttribute(x509.OID_COMMON_NAME, u"zombo") + ])) + ] + ) + + def test_permitted(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted_2.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + x509.OID_NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.DNSName(u"zombo.local"), + ], + excluded_subtrees=None + ) + + def test_permitted_with_leading_period(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + x509.OID_NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.DNSName(u".cryptography.io"), + x509.UniformResourceIdentifier(u"ftp://cryptography.test") + ], + excluded_subtrees=None + ) + + def test_excluded_with_leading_period(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_excluded.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + x509.OID_NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[ + x509.DNSName(u".cryptography.io"), + x509.UniformResourceIdentifier(u"gopher://cryptography.test") + ] + ) + + class TestDistributionPoint(object): def test_distribution_point_full_name_not_general_names(self): with pytest.raises(TypeError): diff --git a/tests/utils.py b/tests/utils.py index 8be5c1fa..5083d48c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -53,10 +53,11 @@ def skip_if_empty(backend_list, required_interfaces): def check_backend_support(item): supported = item.keywords.get("supported") if supported and "backend" in item.funcargs: - if not supported.kwargs["only_if"](item.funcargs["backend"]): - pytest.skip("{0} ({1})".format( - supported.kwargs["skip_message"], item.funcargs["backend"] - )) + for mark in supported: + if not mark.kwargs["only_if"](item.funcargs["backend"]): + pytest.skip("{0} ({1})".format( + mark.kwargs["skip_message"], item.funcargs["backend"] + )) elif supported: raise ValueError("This mark is only available on methods that take a " "backend") diff --git a/vectors/cryptography_vectors/x509/custom/nc_permitted_2.pem b/vectors/cryptography_vectors/x509/custom/nc_permitted_2.pem new file mode 100644 index 00000000..05a904f5 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/nc_permitted_2.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC3DCCAcSgAwIBAgITBm/6kAXHpw8f3guDBcRXNLfl5DANBgkqhkiG9w0BAQsF +ADAXMRUwEwYDVQQDDAxjcnlwdG9ncmFwaHkwHhcNMTUwNjI5MDkzMjQ5WhcNMTYw +NjI4MDkzMjQ5WjAXMRUwEwYDVQQDDAxjcnlwdG9ncmFwaHkwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCylTa0WkLvIXB4sWoPdv5iL3idlVHKR+ncODKL +nwQ2Jtd990MfakOFRLrJFF1tfPL4qyRbbyMyrgCOoKBCAuIdBZfBDH3JWFjxGy8J +Yls8yVeAVKreV18HmLvAsBL3bnr7Gk3vpznrfoG5rn5T/fL0cqqTXFV8zQhjHiEo +zftSaoq0LOxsSgFdxXS8e8K6RMvLCZPcMpI4fo1Kq2QBT2J1x1/Hq/VnK132cs0g +TOyiTyyJfvRmlqdXowh7Jf8LQB4mM6gc023fEdQ+HH6JYX1vDQVxaiTM6KMYJNv/ +l4gchP3jknOfZffwGGdXQrtUMhQmltnSqV5nY/G2OGm/Z0pdAgMBAAGjITAfMB0G +A1UdHgEB/wQTMBGgDzANggt6b21iby5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEA +WcDkqLUsvIBfTzKncOSiy08lcwu+kq/8rybh4HoHEimcy47M+XLPnXqwA7jedz8M +Znog64O9wZ1olWs+GGrGcJAth2BqdNtRvb6/o2Hq29vNbCQeYRlOTdNzGnb5v6fB +HEPvuB7UNKyaJ2tF50oxqhg8ojgauX3fuanCtR9Obx/2U8e8zfBIauX13XfpoCyt +efeL97kYz+XIQwG8TvXpNdHO0QjmA/ToR7E5BbSo2e4cicKEomtLhKI7EXa+Ofwg +HoyVC8wl97nm7mwI7iFYK5f8YoqwILxKEP6O9+pZEOveqdKfx4+WAgeGyDvBwAjf +Ej8vkawtdgV/96ajsIqzDQ== +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/nc_permitted_excluded_2.pem b/vectors/cryptography_vectors/x509/custom/nc_permitted_excluded_2.pem new file mode 100644 index 00000000..2cdf1d68 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/nc_permitted_excluded_2.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC9DCCAdygAwIBAgITBm/3q66sET2C+Ko/TLr1EHnvdTANBgkqhkiG9w0BAQsF +ADAXMRUwEwYDVQQDDAxjcnlwdG9ncmFwaHkwHhcNMTUwNjI5MDY0ODQ4WhcNMTYw +NjI4MDY0ODQ4WjAXMRUwEwYDVQQDDAxjcnlwdG9ncmFwaHkwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCylTa0WkLvIXB4sWoPdv5iL3idlVHKR+ncODKL +nwQ2Jtd990MfakOFRLrJFF1tfPL4qyRbbyMyrgCOoKBCAuIdBZfBDH3JWFjxGy8J +Yls8yVeAVKreV18HmLvAsBL3bnr7Gk3vpznrfoG5rn5T/fL0cqqTXFV8zQhjHiEo +zftSaoq0LOxsSgFdxXS8e8K6RMvLCZPcMpI4fo1Kq2QBT2J1x1/Hq/VnK132cs0g +TOyiTyyJfvRmlqdXowh7Jf8LQB4mM6gc023fEdQ+HH6JYX1vDQVxaiTM6KMYJNv/ +l4gchP3jknOfZffwGGdXQrtUMhQmltnSqV5nY/G2OGm/Z0pdAgMBAAGjOTA3MDUG +A1UdHgEB/wQrMCmgDzANggt6b21iby5sb2NhbKEWMBSkEjAQMQ4wDAYDVQQDDAV6 +b21ibzANBgkqhkiG9w0BAQsFAAOCAQEAaSyuJlNVZkkwHn4V9EglOTm6DC/lzrLm +1y/qcXsY2NXgCRfpZal0lx25M7Dl2G1IOBG+Ub1/ua0NASlpd6BeZ4prmcD4OBib +oAhMJxt8QNNwkcMG5PnI6reQz5MiRwGOCEAZeX1opIijn/tO49RliEnEQCKbsvdr +d+0ieNhLdoxazW/k3UCu+Vdd1b3TOLERrhm/xGj2W9AhWAv7GIovhBGGfuD6BFmC +uHjxoG0So//NiHTfZ9eukgW3rNSbjQjtnC8BsRzUdhX/YBvw+SKkeVL2oz7+lRgD +fhba3FtwUfCIX3y/UAc0E0+x9bLFDyQXYNHAXq+q72sOkLXgAH8bfQ== +-----END CERTIFICATE----- diff --git a/vectors/cryptography_vectors/x509/custom/nc_single_ip_netmask.pem b/vectors/cryptography_vectors/x509/custom/nc_single_ip_netmask.pem new file mode 100644 index 00000000..2931b6b9 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/nc_single_ip_netmask.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIC/TCCAeWgAwIBAgITBnA4pkis5m3OGusBaihd9qH0hzANBgkqhkiG9w0BAQsF +ADAXMRUwEwYDVQQDDAxjcnlwdG9ncmFwaHkwHhcNMTUwNzAxMjAxNDAwWhcNMTYw +NjMwMjAxNDAwWjAXMRUwEwYDVQQDDAxjcnlwdG9ncmFwaHkwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCYyaGtu90vcm+jN+SoQHXxWMQyplY1neL9KjfE ++TsKKcy8TKJEqlT8qZr6bIL3KVbTIiYO8bCW9fHSMgHWrmtr37LlFoQ3emcLfDbM +kybmOolAxA78im0L2BIW1wT2iSHh1p/ZO5QLdt+e8zP5AkZAnXCZk912RcJYyGUW +7JQzzRfEANSLE9Gmh78NsxWNI1Ipc3dhyuk3+YHwePGCzLCeXCiF4FHGNMg8Drtr +rENNHZjHJCbMLfK9irHV5Xh1FHTK8xlqEq+YecpqboUyqgWVOOvpxUxiKagfp//Z ++iFDC1+GgpuupzFUiHPSVCZGMnE3bHvIBOkoHkNu7kNK7VX3AgMBAAGjQjBAMD4G +A1UdHgEB/wQ0MDKgMDAihyAA/wAAAAAAAAAAAAAAAAAA//////////////////// +/zAKhwjAqAAB/////zANBgkqhkiG9w0BAQsFAAOCAQEAXSDmonnBpivsW/NKE85c +1ho449K98+1cFUD51VeK42oPUd0GRQCU3ETYJ5YyK7OMoQqe4LTtNDx6ZCF+6z/r +tZctfdpwRmqh2ebGn3qDs1FAckkwwSCRtkJTdgznmtO680Ls5GveNFrgYJkYfFjj +OWpzCypse/3j3uVgSakmjBRS4BOsyX4o7trN+k1MmQOrMpWEtLlmrZJpM66sgP0j +WpI95l4paIMpkFarwCCQfJCNBpl7Uol+BD4vJvf/J7f7ZwxQMEWCBPnYk3EKMnKa +aCsmqRMV1W7SzxL07dHMzWnsC/I5oJNj4HdthGcIJf1Jut9A9KFVodOJAxKOziz2 +aQ== +-----END CERTIFICATE----- |