From b22146535ee98e27c0316c1ae55924610a66a051 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 8 Jul 2021 22:25:23 -0400 Subject: [PATCH 01/16] Silence overly zealous flake8 warning (#6163) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 93f4b253ab8d..3cab162d8889 100644 --- a/tox.ini +++ b/tox.ini @@ -63,7 +63,7 @@ commands = cargo clippy -- -D warnings [flake8] -ignore = E203,E211,W503,W504 +ignore = E203,E211,W503,W504,N818 exclude = .tox,*.egg,.git,_build,.hypothesis select = E,W,F,N,I application-import-names = cryptography,cryptography_vectors,tests From a8a38d94ae0b62e2f70ef7b75265da164b1b45b3 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 8 Aug 2021 11:47:49 -0400 Subject: [PATCH 02/16] remove twisted from downstream testing for now (#6193) spurious failures and no resolution so far --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd967a3a084c..53bd7a98f26a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -301,7 +301,6 @@ jobs: DOWNSTREAM: - paramiko - pyopenssl - - twisted - aws-encryption-sdk - dynamodb-encryption-sdk - certbot From 5fa85d25ef51d54aad7b24a3a69e489a89274192 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 11 Jun 2021 16:12:29 -0500 Subject: [PATCH 03/16] fix certbot downstream (#6110) --- .github/downstream.d/certbot.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/downstream.d/certbot.sh b/.github/downstream.d/certbot.sh index e2890a3a100c..2479d0c25b86 100755 --- a/.github/downstream.d/certbot.sh +++ b/.github/downstream.d/certbot.sh @@ -5,8 +5,8 @@ case "${1}" in git clone --depth=1 https://github.com/certbot/certbot cd certbot git rev-parse HEAD - tools/pip_install_editable.py ./acme[dev] - tools/pip_install_editable.py ./certbot[dev] + tools/pip_install_editable.py ./acme[test] + tools/pip_install_editable.py ./certbot[test] ;; run) cd certbot From 188d0dc0254678319f829c978b51b25b58f9cc33 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 8 Jun 2021 20:57:02 -0400 Subject: [PATCH 04/16] fix mypy build for latest release (#6099) --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 3cab162d8889..e5e0955af55a 100644 --- a/tox.ini +++ b/tox.ini @@ -46,6 +46,7 @@ extras = ssh deps = mypy + types-pytz check-manifest commands = flake8 . From 0ec5170b4a30e8408b793ad92f4475d9ae8a2cc7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 26 Jun 2021 05:46:35 -0400 Subject: [PATCH 05/16] moar linkcheck ignores (#6137) * moar linkcheck ignores * new alpine new python --- .github/workflows/ci.yml | 2 +- docs/conf.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53bd7a98f26a..567e5e7c920f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,7 +110,7 @@ jobs: - {IMAGE: "ubuntu-rolling", TOXENV: "py38"} - {IMAGE: "ubuntu-rolling", TOXENV: "py38-randomorder"} - {IMAGE: "fedora", TOXENV: "py39"} - - {IMAGE: "alpine", TOXENV: "py38"} + - {IMAGE: "alpine", TOXENV: "py39"} name: "${{ matrix.IMAGE.TOXENV }} on ${{ matrix.IMAGE.IMAGE }}" timeout-minutes: 30 steps: diff --git a/docs/conf.py b/docs/conf.py index 0db9dd8b842d..f79db277abfa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -198,6 +198,10 @@ r"https://info.isl.ntt.co.jp/crypt/eng/camellia/", # Inconsistent small DH params they seem incapable of fixing r"https://www.secg.org/sec1-v2.pdf", + # Incomplete cert chain + r"https://e-trust.gosuslugi.ru", + # Expired cert (1 week at time of writing) + r"https://www.cosic.esat.kuleuven.be", ] autosectionlabel_prefix_document = True From 9ad29595c12ad9c426d6fdcf5c873135ff3b4fc9 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Sat, 3 Jul 2021 18:26:36 -0600 Subject: [PATCH 06/16] Disable fail-fast in tests. (#6155) This makes it easier to isolate regressions by running all tests even if one fails. --- .github/workflows/ci.yml | 6 ++++++ .github/workflows/wheel-builder.yml | 3 +++ 2 files changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 567e5e7c920f..818fe42d0cf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ jobs: linux: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: PYTHON: - {VERSION: "3.9", TOXENV: "flake,rust,docs", COVERAGE: "false"} @@ -98,6 +99,7 @@ jobs: runs-on: ubuntu-latest container: ghcr.io/pyca/cryptography-runner-${{ matrix.IMAGE.IMAGE }} strategy: + fail-fast: false matrix: IMAGE: - {IMAGE: "centos8", TOXENV: "py36"} @@ -139,6 +141,7 @@ jobs: linux-rust: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: PYTHON: - {VERSION: "3.9", TOXENV: "py39"} @@ -184,6 +187,7 @@ jobs: macos: runs-on: macos-latest strategy: + fail-fast: false matrix: PYTHON: - {VERSION: "3.6", TOXENV: "py36", EXTRA_CFLAGS: ""} @@ -239,6 +243,7 @@ jobs: windows: runs-on: windows-latest strategy: + fail-fast: false matrix: WINDOWS: - {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} @@ -297,6 +302,7 @@ jobs: linux-downstream: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: DOWNSTREAM: - paramiko diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index 528c07fd4ac0..08c358925499 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -10,6 +10,7 @@ jobs: runs-on: ubuntu-latest container: ghcr.io/pyca/${{ matrix.MANYLINUX.CONTAINER }} strategy: + fail-fast: false matrix: PYTHON: ["cp36-cp36m"] MANYLINUX: @@ -55,6 +56,7 @@ jobs: macos: runs-on: macos-latest strategy: + fail-fast: false matrix: PYTHON: - VERSION: '3.8' @@ -106,6 +108,7 @@ jobs: windows: runs-on: windows-latest strategy: + fail-fast: false matrix: WINDOWS: - {ARCH: 'x86', WINDOWS: 'win32', RUST_TRIPLE: 'i686-pc-windows-msvc'} From 52f39e409d22121259c4247daf34e9eb704dc66f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sun, 8 Aug 2021 16:17:31 +0200 Subject: [PATCH 07/16] Test with 3.0.0-beta2 (#6192) - OpenSSL 3.0.0-beta2 now uses lib64 on X86_64 - fail on implicit function definition --- .github/workflows/ci.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 818fe42d0cf2..b44c19852212 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,9 +19,10 @@ jobs: - {VERSION: "3.9", TOXENV: "flake,rust,docs", COVERAGE: "false"} - {VERSION: "pypy3", TOXENV: "pypy3"} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.0l"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1i"}} - - {VERSION: "3.9", TOXENV: "py39-ssh", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1i"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1i", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct"}} + - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1k"}} + - {VERSION: "3.9", TOXENV: "py39-ssh", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1k"}} + - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1k", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct"}} + - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "3.0.0-beta2"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "2.9.2"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.0.2"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.1.5"}} @@ -82,8 +83,8 @@ jobs: if: matrix.PYTHON.OPENSSL && steps.ossl-cache.outputs.cache-hit != 'true' - name: Set CFLAGS/LDFLAGS run: | - echo "CFLAGS=${CFLAGS} -I${OSSL_PATH}/include" >> $GITHUB_ENV - echo "LDFLAGS=${LDFLAGS} -L${OSSL_PATH}/lib -Wl,-rpath=${OSSL_PATH}/lib" >> $GITHUB_ENV + echo "CFLAGS=${CFLAGS} -Werror=implicit-function-declaration -I${OSSL_PATH}/include" >> $GITHUB_ENV + echo "LDFLAGS=${LDFLAGS} -L${OSSL_PATH}/lib -L${OSSL_PATH}/lib64 -Wl,-rpath=${OSSL_PATH}/lib -Wl,-rpath=${OSSL_PATH}/lib64" >> $GITHUB_ENV if: matrix.PYTHON.OPENSSL - name: Tests run: | From e33fd51c85ad97d312aac722453d35d514ea473d Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 28 Feb 2021 16:06:11 -0600 Subject: [PATCH 08/16] fix pkcs12 parse ordering. fixes #5872 (#5879) * fix pkcs12 parse ordering. fixes #5872 * remove an unneeded print * simplify the test a bit more * index * black * Update tests/hazmat/primitives/test_pkcs12.py Co-authored-by: Alex Gaynor Co-authored-by: Alex Gaynor --- .../hazmat/backends/openssl/backend.py | 5 +- tests/hazmat/primitives/test_pkcs12.py | 58 ++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 271873d92ad2..a96d08d8a70d 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -6,6 +6,7 @@ import collections import contextlib import itertools +import typing import warnings from contextlib import contextmanager @@ -2562,9 +2563,7 @@ def serialize_key_and_certificates_to_pkcs12( sk_x509 = self._lib.sk_X509_new_null() sk_x509 = self._ffi.gc(sk_x509, self._lib.sk_X509_free) - # reverse the list when building the stack so that they're encoded - # in the order they were originally provided. it is a mystery - for ca in reversed(cas): + for ca in cas: res = self._lib.sk_X509_push(sk_x509, ca._x509) backend.openssl_assert(res >= 1) diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index b5de09f95ca4..b1759a1bc8ab 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -4,13 +4,15 @@ import os +from datetime import datetime import pytest from cryptography import x509 from cryptography.hazmat.backends.interfaces import DERSerializationBackend from cryptography.hazmat.backends.openssl.backend import _RC2 -from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.hazmat.primitives.serialization.pkcs12 import ( load_key_and_certificates, @@ -273,3 +275,57 @@ def test_generate_unsupported_encryption_type(self, backend): DummyKeySerializationEncryption(), ) assert str(exc.value) == "Unsupported key encryption type" + + +def test_pkcs12_ordering(): + """ + In OpenSSL < 3.0.0 PKCS12 parsing reverses the order. However, we + accidentally thought it was **encoding** that did it, leading to bug + https://github.com/pyca/cryptography/issues/5872 + This test ensures our ordering is correct going forward. + """ + + def make_cert(name): + key = ec.generate_private_key(ec.SECP256R1()) + subject = x509.Name( + [ + x509.NameAttribute(x509.NameOID.COMMON_NAME, name), + ] + ) + now = datetime.utcnow() + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(subject) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now) + .not_valid_after(now) + .sign(key, hashes.SHA256()) + ) + return (key, cert) + + # Make some certificates with distinct names. + a_name = "A" * 20 + b_name = "B" * 20 + c_name = "C" * 20 + a_key, a_cert = make_cert(a_name) + _, b_cert = make_cert(b_name) + _, c_cert = make_cert(c_name) + + # Bundle them in a PKCS#12 file in order A, B, C. + p12 = serialize_key_and_certificates( + b"p12", a_key, a_cert, [b_cert, c_cert], serialization.NoEncryption() + ) + + # Parse them out. The API should report them in the same order. + (key, cert, certs) = load_key_and_certificates(p12, None) + assert cert == a_cert + assert certs == [b_cert, c_cert] + + # The ordering in the PKCS#12 file itself should also match. + a_idx = p12.index(a_name.encode("utf-8")) + b_idx = p12.index(b_name.encode("utf-8")) + c_idx = p12.index(c_name.encode("utf-8")) + + assert a_idx < b_idx < c_idx From 3b1caebbbeb773bffccc5f79a7d3cd172e574d2d Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 22 Apr 2021 19:16:38 -0500 Subject: [PATCH 09/16] [WIP] 3.0.0 support (#5250) * 3.0.0 support * almost...there... * make mypy happy --- src/_cffi_src/build_openssl.py | 1 + src/_cffi_src/openssl/cryptography.py | 3 ++ src/_cffi_src/openssl/err.py | 6 +++ src/_cffi_src/openssl/fips.py | 2 +- src/_cffi_src/openssl/provider.py | 40 ++++++++++++++++++ .../hazmat/backends/openssl/backend.py | 42 ++++++++++++++++--- .../hazmat/backends/openssl/ciphers.py | 15 ++++++- .../hazmat/bindings/openssl/_conditional.py | 11 +++++ .../hazmat/bindings/openssl/binding.py | 20 +++++++++ tests/hazmat/backends/test_openssl_memleak.py | 6 ++- tests/hazmat/bindings/test_openssl.py | 4 +- tests/hazmat/primitives/test_dh.py | 24 ++++++++++- 12 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 src/_cffi_src/openssl/provider.py diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py index 08499d66f629..557296ed5354 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -104,6 +104,7 @@ def _extra_compile_args(platform): "osrandom_engine", "pem", "pkcs12", + "provider", "rand", "rsa", "ssl", diff --git a/src/_cffi_src/openssl/cryptography.py b/src/_cffi_src/openssl/cryptography.py index e2b5a13235ae..06d1e7787ec4 100644 --- a/src/_cffi_src/openssl/cryptography.py +++ b/src/_cffi_src/openssl/cryptography.py @@ -34,6 +34,8 @@ #define CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER \ (OPENSSL_VERSION_NUMBER >= 0x1010006f && !CRYPTOGRAPHY_IS_LIBRESSL) +#define CRYPTOGRAPHY_OPENSSL_300_OR_GREATER \ + (OPENSSL_VERSION_NUMBER >= 0x30000000 && !CRYPTOGRAPHY_IS_LIBRESSL) #define CRYPTOGRAPHY_OPENSSL_LESS_THAN_110J \ (OPENSSL_VERSION_NUMBER < 0x101000af || CRYPTOGRAPHY_IS_LIBRESSL) @@ -53,6 +55,7 @@ TYPES = """ static const int CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER; +static const int CRYPTOGRAPHY_OPENSSL_300_OR_GREATER; static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111; static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B; diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py index 0634b656c0f4..8cfeaf5ba38a 100644 --- a/src/_cffi_src/openssl/err.py +++ b/src/_cffi_src/openssl/err.py @@ -18,6 +18,7 @@ static const int ERR_LIB_EVP; static const int ERR_LIB_PEM; +static const int ERR_LIB_PROV; static const int ERR_LIB_ASN1; static const int ERR_LIB_PKCS12; @@ -45,4 +46,9 @@ """ CUSTOMIZATIONS = """ +/* This define is tied to provider support and is conditionally + removed if Cryptography_HAS_PROVIDERS is false */ +#ifndef ERR_LIB_PROV +#define ERR_LIB_PROV 0 +#endif """ diff --git a/src/_cffi_src/openssl/fips.py b/src/_cffi_src/openssl/fips.py index b9d0d64d84fb..23c10af92209 100644 --- a/src/_cffi_src/openssl/fips.py +++ b/src/_cffi_src/openssl/fips.py @@ -17,7 +17,7 @@ """ CUSTOMIZATIONS = """ -#if CRYPTOGRAPHY_IS_LIBRESSL +#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_OPENSSL_300_OR_GREATER static const long Cryptography_HAS_FIPS = 0; int (*FIPS_mode_set)(int) = NULL; int (*FIPS_mode)(void) = NULL; diff --git a/src/_cffi_src/openssl/provider.py b/src/_cffi_src/openssl/provider.py new file mode 100644 index 000000000000..d7d659ea5ef4 --- /dev/null +++ b/src/_cffi_src/openssl/provider.py @@ -0,0 +1,40 @@ +# 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. + + +INCLUDES = """ +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +#include +#include +#endif +""" + +TYPES = """ +static const long Cryptography_HAS_PROVIDERS; + +typedef ... OSSL_PROVIDER; +typedef ... OSSL_LIB_CTX; + +static const long PROV_R_BAD_DECRYPT; +static const long PROV_R_WRONG_FINAL_BLOCK_LENGTH; +""" + +FUNCTIONS = """ +OSSL_PROVIDER *OSSL_PROVIDER_load(OSSL_LIB_CTX *, const char *); +int OSSL_PROVIDER_unload(OSSL_PROVIDER *prov); +""" + +CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +static const long Cryptography_HAS_PROVIDERS = 1; +#else +static const long Cryptography_HAS_PROVIDERS = 0; +typedef void OSSL_PROVIDER; +typedef void OSSL_LIB_CTX; +static const long PROV_R_BAD_DECRYPT = 0; +static const long PROV_R_WRONG_FINAL_BLOCK_LENGTH = 0; +OSSL_PROVIDER *(*OSSL_PROVIDER_load)(OSSL_LIB_CTX *, const char *) = NULL; +int (*OSSL_PROVIDER_unload)(OSSL_PROVIDER *) = NULL; +#endif +""" diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index a96d08d8a70d..86e8f0a88138 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -1281,6 +1281,11 @@ def load_der_private_key(self, data, password): def _evp_pkey_from_der_traditional_key(self, bio_data, password): key = self._lib.d2i_PrivateKey_bio(bio_data.bio, self._ffi.NULL) if key != self._ffi.NULL: + # In OpenSSL 3.0.0-alpha15 there exist scenarios where the key will + # successfully load but errors are still put on the stack. Tracked + # as https://github.com/openssl/openssl/issues/14996 + self._consume_errors() + key = self._ffi.gc(key, self._lib.EVP_PKEY_free) if password is not None: raise TypeError( @@ -1448,6 +1453,11 @@ def _load_key(self, openssl_read_func, convert_func, data, password): else: self._handle_key_loading_error() + # In OpenSSL 3.0.0-alpha15 there exist scenarios where the key will + # successfully load but errors are still put on the stack. Tracked + # as https://github.com/openssl/openssl/issues/14996 + self._consume_errors() + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) if password is not None and userdata.called == 0: @@ -1470,11 +1480,22 @@ def _handle_key_loading_error(self): "incorrect format or it may be encrypted with an unsupported " "algorithm." ) - elif errors[0]._lib_reason_match( - self._lib.ERR_LIB_EVP, self._lib.EVP_R_BAD_DECRYPT - ) or errors[0]._lib_reason_match( - self._lib.ERR_LIB_PKCS12, - self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR, + + elif ( + errors[0]._lib_reason_match( + self._lib.ERR_LIB_EVP, self._lib.EVP_R_BAD_DECRYPT + ) + or errors[0]._lib_reason_match( + self._lib.ERR_LIB_PKCS12, + self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR, + ) + or ( + self._lib.Cryptography_HAS_PROVIDERS + and errors[0]._lib_reason_match( + self._lib.ERR_LIB_PROV, + self._lib.PROV_R_BAD_DECRYPT, + ) + ) ): raise ValueError("Bad decrypt. Incorrect password?") @@ -2520,7 +2541,16 @@ def load_key_and_certificates_from_pkcs12(self, data, password): if sk_x509_ptr[0] != self._ffi.NULL: sk_x509 = self._ffi.gc(sk_x509_ptr[0], self._lib.sk_X509_free) num = self._lib.sk_X509_num(sk_x509_ptr[0]) - for i in range(num): + + # In OpenSSL < 3.0.0 PKCS12 parsing reverses the order of the + # certificates. + indices: typing.Iterable[int] + if self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + indices = range(num) + else: + indices = reversed(range(num)) + + for i in indices: x509 = self._lib.sk_X509_value(sk_x509, i) self.openssl_assert(x509 != self._ffi.NULL) x509 = self._ffi.gc(x509, self._lib.X509_free) diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py index 0f96795fdc73..a2dd68944fd7 100644 --- a/src/cryptography/hazmat/backends/openssl/ciphers.py +++ b/src/cryptography/hazmat/backends/openssl/ciphers.py @@ -145,7 +145,13 @@ def update_into(self, data: bytes, buf) -> int: res = self._backend._lib.EVP_CipherUpdate( self._ctx, outbuf, outlen, inbuf, inlen ) - self._backend.openssl_assert(res != 0) + if res == 0 and isinstance(self._mode, modes.XTS): + raise ValueError( + "In XTS mode you must supply at least a full block in the " + "first update call. For AES this is 16 bytes." + ) + else: + self._backend.openssl_assert(res != 0) data_processed += inlen total_out += outlen[0] @@ -174,6 +180,13 @@ def finalize(self) -> bytes: errors[0]._lib_reason_match( self._backend._lib.ERR_LIB_EVP, self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, + ) + or ( + self._backend._lib.Cryptography_HAS_PROVIDERS + and errors[0]._lib_reason_match( + self._backend._lib.ERR_LIB_PROV, + self._backend._lib.PROV_R_WRONG_FINAL_BLOCK_LENGTH, + ) ), errors=errors, ) diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index 8654835796b6..1f42c7bee71a 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -270,6 +270,16 @@ def cryptography_has_get_proto_version(): ] +def cryptography_has_providers(): + return [ + "OSSL_PROVIDER_load", + "OSSL_PROVIDER_unload", + "ERR_LIB_PROV", + "PROV_R_WRONG_FINAL_BLOCK_LENGTH", + "PROV_R_BAD_DECRYPT", + ] + + # This is a mapping of # {condition: function-returning-names-dependent-on-that-condition} so we can # loop over them and delete unsupported names at runtime. It will be removed @@ -318,4 +328,5 @@ def cryptography_has_get_proto_version(): "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, "Cryptography_HAS_SRTP": cryptography_has_srtp, "Cryptography_HAS_GET_PROTO_VERSION": cryptography_has_get_proto_version, + "Cryptography_HAS_PROVIDERS": cryptography_has_providers, } diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index a2bc36a83a71..6dcec26ab8a3 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -113,6 +113,8 @@ class Binding(object): ffi = ffi _lib_loaded = False _init_lock = threading.Lock() + _legacy_provider: typing.Any = None + _default_provider: typing.Any = None def __init__(self): self._ensure_ffi_initialized() @@ -140,6 +142,24 @@ def _ensure_ffi_initialized(cls): # adds all ciphers/digests for EVP cls.lib.OpenSSL_add_all_algorithms() cls._register_osrandom_engine() + # As of OpenSSL 3.0.0 we must register a legacy cipher provider + # to get RC2 (needed for junk asymmetric private key + # serialization), RC4, Blowfish, IDEA, SEED, etc. These things + # are ugly legacy, but we aren't going to get rid of them + # any time soon. + if cls.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + cls._legacy_provider = cls.lib.OSSL_PROVIDER_load( + cls.ffi.NULL, b"legacy" + ) + _openssl_assert( + cls.lib, cls._legacy_provider != cls.ffi.NULL + ) + cls._default_provider = cls.lib.OSSL_PROVIDER_load( + cls.ffi.NULL, b"default" + ) + _openssl_assert( + cls.lib, cls._default_provider != cls.ffi.NULL + ) @classmethod def init_static_locks(cls): diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py index 0c96516fa19f..0316b5d9602e 100644 --- a/tests/hazmat/backends/test_openssl_memleak.py +++ b/tests/hazmat/backends/test_openssl_memleak.py @@ -82,7 +82,7 @@ def free(ptr, path, line): assert result == 1 # Trigger a bunch of initialization stuff. - import cryptography.hazmat.backends.openssl + from cryptography.hazmat.backends.openssl.backend import backend start_heap = set(heap) @@ -91,6 +91,10 @@ def free(ptr, path, line): gc.collect() gc.collect() + if lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + lib.OSSL_PROVIDER_unload(backend._binding._legacy_provider) + lib.OSSL_PROVIDER_unload(backend._binding._default_provider) + if lib.Cryptography_HAS_OPENSSL_CLEANUP: lib.OPENSSL_cleanup() diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index fb9a1e363742..4d1e3b5566d5 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -91,7 +91,9 @@ def test_openssl_assert_error_on_stack(self): _openssl_assert(b.lib, False) error = exc_info.value.err_code[0] - assert error.code == 101183626 + # As of 3.0.0 OpenSSL sets func codes to 0, so the combined + # code is a different value + assert error.code in (101183626, 50331786) assert error.lib == b.lib.ERR_LIB_EVP assert error.func == b.lib.EVP_F_EVP_ENCRYPTFINAL_EX assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index 131807fc0860..bb29919f0d52 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -180,7 +180,23 @@ def test_dh_parameters_allows_rfc3526_groups(self, backend, vector): params = dh.DHParameterNumbers(p, int(vector["g"])) param = params.parameters(backend) key = param.generate_private_key() - assert key.private_numbers().public_numbers.parameter_numbers == params + # In OpenSSL 3.0.0 OpenSSL maps to known groups. This results in + # a scenario where loading a known group with p and g returns a + # re-serialized form that has q as well (the Sophie Germain prime of + # that group). This makes a naive comparison of the parameter numbers + # objects fail, so we have to be a bit smarter + serialized_params = ( + key.private_numbers().public_numbers.parameter_numbers + ) + if serialized_params.q is None: + # This is the path OpenSSL < 3.0 takes + assert serialized_params == params + else: + assert serialized_params.p == params.p + assert serialized_params.g == params.g + # p = 2q + 1 since it is a Sophie Germain prime, so we can compute + # what we expect OpenSSL to have done here. + assert serialized_params.q == (params.p - 1) // 2 @pytest.mark.skip_fips(reason="non-FIPS parameters") @pytest.mark.parametrize( @@ -382,6 +398,12 @@ def test_bad_exchange(self, backend, vector): assert symkey1 != symkey2 @pytest.mark.skip_fips(reason="key_size too small for FIPS") + @pytest.mark.supported( + only_if=lambda backend: ( + not backend._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER + ), + skip_message="256-bit DH keys are not supported in OpenSSL 3.0.0+", + ) def test_load_256bit_key_from_pkcs8(self, backend): data = load_vectors_from_file( os.path.join("asymmetric", "DH", "dh_key_256.pem"), From c0bf3d0051cc7babcd97c293b34266186491e5c4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 14 Apr 2021 13:15:57 -0500 Subject: [PATCH 10/16] switch to using EVP_PKEY_derive instead of DH_compute_key in DH (#5972) * switch to using EVP_PKEY_derive instead of DH_compute_key in DH Where checks are occurring is changing in OpenSSL 3.0 and this makes it easier to be consistent (and is the API we should be using anyway). The tests change because EVP_PKEY_derive now verifies that we have shared parameters, which the test previously only verified by asserting that the derived keys didn't match * review feedback * type ignores required for typeerror tests. some day i will remember this --- src/_cffi_src/openssl/dh.py | 1 - .../hazmat/backends/openssl/dh.py | 57 ++++++++++++------- tests/hazmat/primitives/test_dh.py | 19 ++++--- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/_cffi_src/openssl/dh.py b/src/_cffi_src/openssl/dh.py index 979dafa94253..50989e45343a 100644 --- a/src/_cffi_src/openssl/dh.py +++ b/src/_cffi_src/openssl/dh.py @@ -18,7 +18,6 @@ void DH_free(DH *); int DH_size(const DH *); int DH_generate_key(DH *); -int DH_compute_key(unsigned char *, const BIGNUM *, DH *); DH *DHparams_dup(DH *); /* added in 1.1.0 when the DH struct was opaqued */ diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py index 65ddaeec5fe2..b928f024f56f 100644 --- a/src/cryptography/hazmat/backends/openssl/dh.py +++ b/src/cryptography/hazmat/backends/openssl/dh.py @@ -127,35 +127,48 @@ def private_numbers(self) -> dh.DHPrivateNumbers: ) def exchange(self, peer_public_key: dh.DHPublicKey) -> bytes: - buf = self._backend._ffi.new("unsigned char[]", self._key_size_bytes) - pub_key = self._backend._ffi.new("BIGNUM **") - self._backend._lib.DH_get0_key( - peer_public_key._dh_cdata, # type: ignore[attr-defined] - pub_key, - self._backend._ffi.NULL, + if not isinstance(peer_public_key, _DHPublicKey): + raise TypeError("peer_public_key must be a DHPublicKey") + + ctx = self._backend._lib.EVP_PKEY_CTX_new( + self._evp_pkey, self._backend._ffi.NULL ) - self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) - res = self._backend._lib.DH_compute_key( - buf, pub_key[0], self._dh_cdata + self._backend.openssl_assert(ctx != self._backend._ffi.NULL) + ctx = self._backend._ffi.gc(ctx, self._backend._lib.EVP_PKEY_CTX_free) + res = self._backend._lib.EVP_PKEY_derive_init(ctx) + self._backend.openssl_assert(res == 1) + res = self._backend._lib.EVP_PKEY_derive_set_peer( + ctx, peer_public_key._evp_pkey + ) + # Invalid kex errors here in OpenSSL 3.0 because checks were moved + # to EVP_PKEY_derive_set_peer + self._exchange_assert(res == 1) + keylen = self._backend._ffi.new("size_t *") + res = self._backend._lib.EVP_PKEY_derive( + ctx, self._backend._ffi.NULL, keylen ) + # Invalid kex errors here in OpenSSL < 3 + self._exchange_assert(res == 1) + self._backend.openssl_assert(keylen[0] > 0) + buf = self._backend._ffi.new("unsigned char[]", keylen[0]) + res = self._backend._lib.EVP_PKEY_derive(ctx, buf, keylen) + self._backend.openssl_assert(res == 1) - if res == -1: + key = self._backend._ffi.buffer(buf, keylen[0])[:] + pad = self._key_size_bytes - len(key) + + if pad > 0: + key = (b"\x00" * pad) + key + + return key + + def _exchange_assert(self, ok): + if not ok: errors_with_text = self._backend._consume_errors_with_text() raise ValueError( - "Error computing shared key. Public key is likely invalid " - "for this exchange.", + "Error computing shared key.", errors_with_text, ) - else: - self._backend.openssl_assert(res >= 1) - - key = self._backend._ffi.buffer(buf)[:res] - pad = self._key_size_bytes - len(key) - - if pad > 0: - key = (b"\x00" * pad) + key - - return key def public_key(self) -> dh.DHPublicKey: dh_cdata = _dh_params_dup(self._dh_cdata, self._backend) diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index bb29919f0d52..2914f7e776f0 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -296,6 +296,12 @@ def test_generate_dh(self, backend, with_q): assert isinstance(key.private_numbers(), dh.DHPrivateNumbers) assert isinstance(key.parameters(), dh.DHParameters) + def test_exchange_wrong_type(self, backend): + parameters = FFDH3072_P.parameters(backend) + key1 = parameters.generate_private_key() + with pytest.raises(TypeError): + key1.exchange(b"invalidtype") # type: ignore[arg-type] + def test_exchange(self, backend): parameters = FFDH3072_P.parameters(backend) assert isinstance(parameters, dh.DHParameters) @@ -386,16 +392,11 @@ def test_bad_exchange(self, backend, vector): key2 = private2.private_key(backend) pub_key2 = key2.public_key() - if pub_key2.public_numbers().y >= parameters1.p: - with pytest.raises(ValueError): - key1.exchange(pub_key2) - else: - symkey1 = key1.exchange(pub_key2) - assert symkey1 - - symkey2 = key2.exchange(pub_key1) + with pytest.raises(ValueError): + key1.exchange(pub_key2) - assert symkey1 != symkey2 + with pytest.raises(ValueError): + key2.exchange(pub_key1) @pytest.mark.skip_fips(reason="key_size too small for FIPS") @pytest.mark.supported( From c49c6a18b8f1b1c7a958983a65ba723e14dd4f35 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 10 May 2021 13:27:54 +0200 Subject: [PATCH 11/16] Use well-defined enum representation Python 3.10 changed enum's object and string representation. PyCA cryptography now uses a custom subclass of enum.Enum() will well-defined __repr__ and __str__ from Python 3.9. Related: https://bugs.python.org/issue40066 Fixes: https://github.com/pyca/cryptography/issues/5995 Signed-off-by: Christian Heimes --- .github/workflows/ci.yml | 5 +++-- src/cryptography/exceptions.py | 4 ++-- src/cryptography/hazmat/primitives/_serialization.py | 11 ++++++----- src/cryptography/hazmat/primitives/kdf/kbkdf.py | 5 ++--- .../hazmat/primitives/serialization/pkcs7.py | 4 ++-- src/cryptography/utils.py | 11 +++++++++++ src/cryptography/x509/base.py | 4 ++-- src/cryptography/x509/certificate_transparency.py | 7 ++++--- src/cryptography/x509/extensions.py | 5 ++--- src/cryptography/x509/name.py | 3 +-- src/cryptography/x509/ocsp.py | 8 ++++---- tests/test_cryptography_utils.py | 11 +++++++++++ 12 files changed, 50 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b44c19852212..5986ca96ab78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,7 @@ jobs: - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.1.5"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.2.3"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.3.1"}} + - {VERSION: "3.10-dev", TOXENV: "py310"} RUST: - stable name: "${{ matrix.PYTHON.TOXENV }} ${{ matrix.PYTHON.OPENSSL.TYPE }} ${{ matrix.PYTHON.OPENSSL.VERSION }} ${{ matrix.PYTHON.OPENSSL.CONFIG_FLAGS }}" @@ -110,8 +111,8 @@ jobs: - {IMAGE: "sid", TOXENV: "py39"} - {IMAGE: "ubuntu-bionic", TOXENV: "py36"} - {IMAGE: "ubuntu-focal", TOXENV: "py38"} - - {IMAGE: "ubuntu-rolling", TOXENV: "py38"} - - {IMAGE: "ubuntu-rolling", TOXENV: "py38-randomorder"} + - {IMAGE: "ubuntu-rolling", TOXENV: "py39"} + - {IMAGE: "ubuntu-rolling", TOXENV: "py39-randomorder"} - {IMAGE: "fedora", TOXENV: "py39"} - {IMAGE: "alpine", TOXENV: "py39"} name: "${{ matrix.IMAGE.TOXENV }} on ${{ matrix.IMAGE.IMAGE }}" diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py index f5860590571b..3bd98d8252ad 100644 --- a/src/cryptography/exceptions.py +++ b/src/cryptography/exceptions.py @@ -3,10 +3,10 @@ # for complete details. -from enum import Enum +from cryptography import utils -class _Reasons(Enum): +class _Reasons(utils.Enum): BACKEND_MISSING_INTERFACE = 0 UNSUPPORTED_HASH = 1 UNSUPPORTED_CIPHER = 2 diff --git a/src/cryptography/hazmat/primitives/_serialization.py b/src/cryptography/hazmat/primitives/_serialization.py index 96a5ed9b75d9..160a6b89c089 100644 --- a/src/cryptography/hazmat/primitives/_serialization.py +++ b/src/cryptography/hazmat/primitives/_serialization.py @@ -3,13 +3,14 @@ # for complete details. import abc -from enum import Enum + +from cryptography import utils # This exists to break an import cycle. These classes are normally accessible # from the serialization module. -class Encoding(Enum): +class Encoding(utils.Enum): PEM = "PEM" DER = "DER" OpenSSH = "OpenSSH" @@ -18,14 +19,14 @@ class Encoding(Enum): SMIME = "S/MIME" -class PrivateFormat(Enum): +class PrivateFormat(utils.Enum): PKCS8 = "PKCS8" TraditionalOpenSSL = "TraditionalOpenSSL" Raw = "Raw" OpenSSH = "OpenSSH" -class PublicFormat(Enum): +class PublicFormat(utils.Enum): SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1" PKCS1 = "Raw PKCS#1" OpenSSH = "OpenSSH" @@ -34,7 +35,7 @@ class PublicFormat(Enum): UncompressedPoint = "X9.62 Uncompressed Point" -class ParameterFormat(Enum): +class ParameterFormat(utils.Enum): PKCS3 = "PKCS3" diff --git a/src/cryptography/hazmat/primitives/kdf/kbkdf.py b/src/cryptography/hazmat/primitives/kdf/kbkdf.py index ac36474fd7ae..75fe7d518910 100644 --- a/src/cryptography/hazmat/primitives/kdf/kbkdf.py +++ b/src/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -4,7 +4,6 @@ import typing -from enum import Enum from cryptography import utils from cryptography.exceptions import ( @@ -19,11 +18,11 @@ from cryptography.hazmat.primitives.kdf import KeyDerivationFunction -class Mode(Enum): +class Mode(utils.Enum): CounterMode = "ctr" -class CounterLocation(Enum): +class CounterLocation(utils.Enum): BeforeFixed = "before_fixed" AfterFixed = "after_fixed" diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs7.py b/src/cryptography/hazmat/primitives/serialization/pkcs7.py index bcd9e330d58d..57aac7e34645 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs7.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -3,8 +3,8 @@ # for complete details. import typing -from enum import Enum +from cryptography import utils from cryptography import x509 from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.primitives import hashes, serialization @@ -35,7 +35,7 @@ def load_der_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]: ] -class PKCS7Options(Enum): +class PKCS7Options(utils.Enum): Text = "Add text/plain MIME type" Binary = "Don't translate input data into canonical MIME format" DetachedSignature = "Don't embed data in the PKCS7 structure" diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index ef0fc44332d0..9e571cfdd153 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -4,6 +4,7 @@ import abc +import enum import inspect import sys import typing @@ -162,3 +163,13 @@ def inner(instance): "int_from_bytes is deprecated, use int.from_bytes instead", DeprecatedIn34, ) + + +# Python 3.10 changed representation of enums. We use well-defined object +# representation and string representation from Python 3.9. +class Enum(enum.Enum): + def __repr__(self): + return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>" + + def __str__(self): + return f"{self.__class__.__name__}.{self._name_}" diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index 5505fa3b6d5e..26ec43d56493 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -7,8 +7,8 @@ import datetime import os import typing -from enum import Enum +from cryptography import utils from cryptography.hazmat._types import _PRIVATE_KEY_TYPES, _PUBLIC_KEY_TYPES from cryptography.hazmat.backends import _get_backend from cryptography.hazmat.primitives import hashes, serialization @@ -66,7 +66,7 @@ def _convert_to_naive_utc_time(time: datetime.datetime) -> datetime.datetime: return time -class Version(Enum): +class Version(utils.Enum): v1 = 0 v3 = 2 diff --git a/src/cryptography/x509/certificate_transparency.py b/src/cryptography/x509/certificate_transparency.py index d51bee92effc..d80f051a68ae 100644 --- a/src/cryptography/x509/certificate_transparency.py +++ b/src/cryptography/x509/certificate_transparency.py @@ -5,15 +5,16 @@ import abc import datetime -from enum import Enum +from cryptography import utils -class LogEntryType(Enum): + +class LogEntryType(utils.Enum): X509_CERTIFICATE = 0 PRE_CERTIFICATE = 1 -class Version(Enum): +class Version(utils.Enum): v1 = 0 diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index 6cae016a1c60..742f1fa29319 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -8,7 +8,6 @@ import hashlib import ipaddress import typing -from enum import Enum from cryptography import utils from cryptography.hazmat._der import ( @@ -634,7 +633,7 @@ def __hash__(self): crl_issuer = utils.read_only_property("_crl_issuer") -class ReasonFlags(Enum): +class ReasonFlags(utils.Enum): unspecified = "unspecified" key_compromise = "keyCompromise" ca_compromise = "cACompromise" @@ -978,7 +977,7 @@ def __hash__(self): return hash(tuple(self._features)) -class TLSFeatureType(Enum): +class TLSFeatureType(utils.Enum): # status_request is defined in RFC 6066 and is used for what is commonly # called OCSP Must-Staple when present in the TLS Feature extension in an # X.509 certificate. diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py index a579aa219638..9069a9f4b71c 100644 --- a/src/cryptography/x509/name.py +++ b/src/cryptography/x509/name.py @@ -3,14 +3,13 @@ # for complete details. import typing -from enum import Enum from cryptography import utils from cryptography.hazmat.backends import _get_backend from cryptography.x509.oid import NameOID, ObjectIdentifier -class _ASN1Type(Enum): +class _ASN1Type(utils.Enum): UTF8String = 12 NumericString = 18 PrintableString = 19 diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py index 1c5de73e45b1..bcf210c1eb31 100644 --- a/src/cryptography/x509/ocsp.py +++ b/src/cryptography/x509/ocsp.py @@ -6,8 +6,8 @@ import abc import datetime import typing -from enum import Enum +from cryptography import utils from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.x509.base import ( @@ -27,12 +27,12 @@ } -class OCSPResponderEncoding(Enum): +class OCSPResponderEncoding(utils.Enum): HASH = "By Hash" NAME = "By Name" -class OCSPResponseStatus(Enum): +class OCSPResponseStatus(utils.Enum): SUCCESSFUL = 0 MALFORMED_REQUEST = 1 INTERNAL_ERROR = 2 @@ -58,7 +58,7 @@ def _verify_algorithm(algorithm): ) -class OCSPCertStatus(Enum): +class OCSPCertStatus(utils.Enum): GOOD = 0 REVOKED = 1 UNKNOWN = 2 diff --git a/tests/test_cryptography_utils.py b/tests/test_cryptography_utils.py index 6b795e0c683a..803997ac06ea 100644 --- a/tests/test_cryptography_utils.py +++ b/tests/test_cryptography_utils.py @@ -2,6 +2,7 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +import enum import typing import pytest @@ -51,3 +52,13 @@ def t(self): assert len(accesses) == 1 assert t.t == 14 assert len(accesses) == 1 + + +def test_enum(): + class TestEnum(utils.Enum): + value = "something" + + assert issubclass(TestEnum, enum.Enum) + assert isinstance(TestEnum.value, enum.Enum) + assert repr(TestEnum.value) == "" + assert str(TestEnum.value) == "TestEnum.value" From 82b3dd14c87c81323fc8b09b1a5dddff0628afff Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 30 Jun 2021 06:14:42 -0500 Subject: [PATCH 12/16] 3.0.0 deprecated func and it isn't useful to us in general (#6148) remove it everywhere and assert on the code/lib/reason --- src/cryptography/hazmat/bindings/openssl/binding.py | 11 ++++------- tests/hazmat/bindings/test_openssl.py | 5 ++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index 6dcec26ab8a3..f651ab672383 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -15,15 +15,14 @@ from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES _OpenSSLErrorWithText = collections.namedtuple( - "_OpenSSLErrorWithText", ["code", "lib", "func", "reason", "reason_text"] + "_OpenSSLErrorWithText", ["code", "lib", "reason", "reason_text"] ) class _OpenSSLError(object): - def __init__(self, code, lib, func, reason): + def __init__(self, code, lib, reason): self._code = code self._lib = lib - self._func = func self._reason = reason def _lib_reason_match(self, lib, reason): @@ -31,7 +30,6 @@ def _lib_reason_match(self, lib, reason): code = utils.read_only_property("_code") lib = utils.read_only_property("_lib") - func = utils.read_only_property("_func") reason = utils.read_only_property("_reason") @@ -43,10 +41,9 @@ def _consume_errors(lib): break err_lib = lib.ERR_GET_LIB(code) - err_func = lib.ERR_GET_FUNC(code) err_reason = lib.ERR_GET_REASON(code) - errors.append(_OpenSSLError(code, err_lib, err_func, err_reason)) + errors.append(_OpenSSLError(code, err_lib, err_reason)) return errors @@ -60,7 +57,7 @@ def _errors_with_text(errors): errors_with_text.append( _OpenSSLErrorWithText( - err.code, err.lib, err.func, err.reason, err_text_reason + err.code, err.lib, err.reason, err_text_reason ) ) diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index 4d1e3b5566d5..1d9b87bad52f 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -91,11 +91,10 @@ def test_openssl_assert_error_on_stack(self): _openssl_assert(b.lib, False) error = exc_info.value.err_code[0] - # As of 3.0.0 OpenSSL sets func codes to 0, so the combined - # code is a different value + # As of 3.0.0 OpenSSL no longer sets func codes (which we now also + # ignore), so the combined code is a different value assert error.code in (101183626, 50331786) assert error.lib == b.lib.ERR_LIB_EVP - assert error.func == b.lib.EVP_F_EVP_ENCRYPTFINAL_EX assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH assert b"data not multiple of block length" in error.reason_text From 18091c8a8c5bc3a8d2e34d2530ce7d072d896b90 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 30 Jun 2021 21:12:46 -0500 Subject: [PATCH 13/16] remove unneeded binding (#6150) --- src/_cffi_src/openssl/err.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py index 8cfeaf5ba38a..8d838d4fcac9 100644 --- a/src/_cffi_src/openssl/err.py +++ b/src/_cffi_src/openssl/err.py @@ -40,7 +40,6 @@ void ERR_put_error(int, int, int, const char *, int); int ERR_GET_LIB(unsigned long); -int ERR_GET_FUNC(unsigned long); int ERR_GET_REASON(unsigned long); """ From b894019949f77c13ec7fe7e4381c834db3a6a7a9 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 11 Oct 2021 16:44:34 -0400 Subject: [PATCH 14/16] Specify the out length when obtaining the tag for poly1305 (#6403) --- src/cryptography/hazmat/backends/openssl/poly1305.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptography/hazmat/backends/openssl/poly1305.py b/src/cryptography/hazmat/backends/openssl/poly1305.py index 35f6819ce87e..2ddae9847a43 100644 --- a/src/cryptography/hazmat/backends/openssl/poly1305.py +++ b/src/cryptography/hazmat/backends/openssl/poly1305.py @@ -51,7 +51,7 @@ def update(self, data): def finalize(self): buf = self._backend._ffi.new("unsigned char[]", _POLY1305_TAG_SIZE) - outlen = self._backend._ffi.new("size_t *") + outlen = self._backend._ffi.new("size_t *", _POLY1305_TAG_SIZE) res = self._backend._lib.EVP_DigestSignFinal(self._ctx, buf, outlen) self._backend.openssl_assert(res != 0) self._backend.openssl_assert(outlen[0] == _POLY1305_TAG_SIZE) From e9df1f9fec98c931c33125811486d1ea352d3606 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 29 Aug 2021 10:05:32 -0400 Subject: [PATCH 15/16] FIPS 3.0.0 support (#6012) * FIPS 3.0.0 support * comments * remove unneeded error clear * review comments * small refactor * black * flake8 too * review feedback * oops * fix --- .github/workflows/build_openssl.sh | 12 ++++ .github/workflows/ci.yml | 9 +-- src/_cffi_src/build_openssl.py | 3 +- src/_cffi_src/openssl/evp.py | 12 ++++ .../hazmat/backends/openssl/backend.py | 68 +++++++++++++++++-- .../hazmat/bindings/openssl/_conditional.py | 8 +++ .../hazmat/bindings/openssl/binding.py | 16 +++++ tests/conftest.py | 6 ++ tests/hazmat/primitives/test_dh.py | 2 + tests/hazmat/primitives/test_pkcs12.py | 10 ++- tests/hazmat/primitives/test_pkcs7.py | 10 ++- tests/hazmat/primitives/test_serialization.py | 18 ++++- tests/wycheproof/test_hmac.py | 2 +- tests/wycheproof/test_rsa.py | 7 ++ 14 files changed, 166 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build_openssl.sh b/.github/workflows/build_openssl.sh index 99c3f4d33805..440cdcecc69f 100755 --- a/.github/workflows/build_openssl.sh +++ b/.github/workflows/build_openssl.sh @@ -22,6 +22,18 @@ if [[ "${TYPE}" == "openssl" ]]; then # avoid installing the docs (for performance) # https://github.com/openssl/openssl/issues/6685#issuecomment-403838728 make install_sw install_ssldirs + # For OpenSSL 3.0.0 set up the FIPS config. This does not activate it by + # default, but allows programmatic activation at runtime + if [[ "${VERSION}" =~ 3.0.0 && "${CONFIG_FLAGS}" =~ enable-fips ]]; then + # As of alpha16 we have to install it separately and enable it in the config flags + make -j"$(nproc)" install_fips + pushd "${OSSL_PATH}" + # include the conf file generated as part of install_fips + sed -i "s:# .include fipsmodule.cnf:.include $(pwd)/ssl/fipsmodule.cnf:" ssl/openssl.cnf + # uncomment the FIPS section + sed -i 's:# fips = fips_sect:fips = fips_sect:' ssl/openssl.cnf + popd + fi popd elif [[ "${TYPE}" == "libressl" ]]; then curl -O "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${VERSION}.tar.gz" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5986ca96ab78..ef0791b3f6c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: - {VERSION: "3.9", TOXENV: "py39-ssh", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1k"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1k", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "3.0.0-beta2"}} + - {VERSION: "3.9", TOXENV: "py39", TOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.0.0-beta2"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "2.9.2"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.0.2"}} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.1.5"}} @@ -31,8 +32,8 @@ jobs: - {VERSION: "3.10-dev", TOXENV: "py310"} RUST: - stable - name: "${{ matrix.PYTHON.TOXENV }} ${{ matrix.PYTHON.OPENSSL.TYPE }} ${{ matrix.PYTHON.OPENSSL.VERSION }} ${{ matrix.PYTHON.OPENSSL.CONFIG_FLAGS }}" - timeout-minutes: 30 + name: "${{ matrix.PYTHON.TOXENV }} ${{ matrix.PYTHON.OPENSSL.TYPE }} ${{ matrix.PYTHON.OPENSSL.VERSION }} ${{ matrix.PYTHON.TOXARGS }} ${{ matrix.PYTHON.OPENSSL.CONFIG_FLAGS }}" + timeout-minutes: 20 steps: - uses: actions/checkout@v2 - uses: actions/cache@v2 @@ -74,7 +75,7 @@ jobs: path: ${{ github.workspace }}/osslcache # When altering the openssl build process you may need to increment the value on the end of this cache key # so that you can prevent it from fetching the cache and skipping the build step. - key: ${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${{ env.CONFIG_HASH }}-1 + key: ${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${{ env.CONFIG_HASH }}-2 if: matrix.PYTHON.OPENSSL - name: Build custom OpenSSL/LibreSSL run: .github/workflows/build_openssl.sh @@ -89,7 +90,7 @@ jobs: if: matrix.PYTHON.OPENSSL - name: Tests run: | - tox -r -- --color=yes --wycheproof-root=wycheproof + tox -r -- --color=yes --wycheproof-root=wycheproof ${{ matrix.PYTHON.TOXARGS }} env: TOXENV: ${{ matrix.PYTHON.TOXENV }} - uses: ./.github/actions/upload-coverage diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py index 557296ed5354..723f82e13dbf 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -79,6 +79,8 @@ def _extra_compile_args(platform): modules=[ # This goes first so we can define some cryptography-wide symbols. "cryptography", + # Provider comes early as well so we define OSSL_LIB_CTX + "provider", "aes", "asn1", "bignum", @@ -104,7 +106,6 @@ def _extra_compile_args(platform): "osrandom_engine", "pem", "pkcs12", - "provider", "rand", "rsa", "ssl", diff --git a/src/_cffi_src/openssl/evp.py b/src/_cffi_src/openssl/evp.py index 2b2f995e389f..735b8c37cfa2 100644 --- a/src/_cffi_src/openssl/evp.py +++ b/src/_cffi_src/openssl/evp.py @@ -36,6 +36,7 @@ static const int Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY; static const long Cryptography_HAS_RAW_KEY; static const long Cryptography_HAS_EVP_DIGESTFINAL_XOF; +static const long Cryptography_HAS_300_FIPS; """ FUNCTIONS = """ @@ -165,6 +166,9 @@ size_t); int EVP_PKEY_get_raw_private_key(const EVP_PKEY *, unsigned char *, size_t *); int EVP_PKEY_get_raw_public_key(const EVP_PKEY *, unsigned char *, size_t *); + +int EVP_default_properties_is_fips_enabled(OSSL_LIB_CTX *); +int EVP_default_properties_enable_fips(OSSL_LIB_CTX *, int); """ CUSTOMIZATIONS = """ @@ -269,4 +273,12 @@ #ifndef EVP_PKEY_POLY1305 #define EVP_PKEY_POLY1305 NID_poly1305 #endif + +#if CRYPTOGRAPHY_OPENSSL_300_OR_GREATER +static const long Cryptography_HAS_300_FIPS = 1; +#else +static const long Cryptography_HAS_300_FIPS = 0; +int (*EVP_default_properties_is_fips_enabled)(OSSL_LIB_CTX *) = NULL; +int (*EVP_default_properties_enable_fips)(OSSL_LIB_CTX *, int) = NULL; +#endif """ diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 86e8f0a88138..2363a6069edb 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -196,8 +196,9 @@ class Backend(object): b"aes-256-gcm", } _fips_ciphers = (AES, TripleDES) + # Sometimes SHA1 is still permissible. That logic is contained + # within the various *_supported methods. _fips_hashes = ( - hashes.SHA1, hashes.SHA224, hashes.SHA256, hashes.SHA384, @@ -211,6 +212,12 @@ class Backend(object): hashes.SHAKE128, hashes.SHAKE256, ) + _fips_ecdh_curves = ( + ec.SECP224R1, + ec.SECP256R1, + ec.SECP384R1, + ec.SECP521R1, + ) _fips_rsa_min_key_size = 2048 _fips_rsa_min_public_exponent = 65537 _fips_dsa_min_modulus = 1 << 2048 @@ -238,17 +245,34 @@ def __init__(self): if self._lib.Cryptography_HAS_EVP_PKEY_DHX: self._dh_types.append(self._lib.EVP_PKEY_DHX) + def __repr__(self): + return "".format( + self.openssl_version_text(), self._fips_enabled + ) + def openssl_assert(self, ok, errors=None): return binding._openssl_assert(self._lib, ok, errors=errors) def _is_fips_enabled(self): - fips_mode = getattr(self._lib, "FIPS_mode", lambda: 0) - mode = fips_mode() + if self._lib.Cryptography_HAS_300_FIPS: + mode = self._lib.EVP_default_properties_is_fips_enabled( + self._ffi.NULL + ) + else: + mode = getattr(self._lib, "FIPS_mode", lambda: 0)() + if mode == 0: # OpenSSL without FIPS pushes an error on the error stack self._lib.ERR_clear_error() return bool(mode) + def _enable_fips(self): + # This function enables FIPS mode for OpenSSL 3.0.0 on installs that + # have the FIPS provider installed properly. + self._binding._enable_fips() + assert self._is_fips_enabled() + self._fips_enabled = self._is_fips_enabled() + def activate_builtin_random(self): if self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: # Obtain a new structural reference. @@ -343,15 +367,32 @@ def hash_supported(self, algorithm): evp_md = self._evp_md_from_algorithm(algorithm) return evp_md != self._ffi.NULL + def scrypt_supported(self): + if self._fips_enabled: + return False + else: + return self._lib.Cryptography_HAS_SCRYPT == 1 + def hmac_supported(self, algorithm): + # FIPS mode still allows SHA1 for HMAC + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return True + return self.hash_supported(algorithm) def create_hash_ctx(self, algorithm): return _HashContext(self, algorithm) def cipher_supported(self, cipher, mode): - if self._fips_enabled and not isinstance(cipher, self._fips_ciphers): - return False + if self._fips_enabled: + # FIPS mode requires AES or TripleDES, but only CBC/ECB allowed + # in TripleDES mode. + if not isinstance(cipher, self._fips_ciphers) or ( + isinstance(cipher, TripleDES) + and not isinstance(mode, (CBC, ECB)) + ): + return False + try: adapter = self._cipher_registry[type(cipher), type(mode)] except KeyError: @@ -767,7 +808,13 @@ def rsa_padding_supported(self, padding): if isinstance(padding, PKCS1v15): return True elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): - return self.hash_supported(padding._mgf._algorithm) + # SHA1 is permissible in MGF1 in FIPS + if self._fips_enabled and isinstance( + padding._mgf._algorithm, hashes.SHA1 + ): + return True + else: + return self.hash_supported(padding._mgf._algorithm) elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): return ( self._oaep_hash_supported(padding._mgf._algorithm) @@ -1509,10 +1556,12 @@ def _handle_key_loading_error(self): raise ValueError("Unsupported public key algorithm.") else: + errors = binding._errors_with_text(errors) raise ValueError( "Could not deserialize key data. The data may be in an " "incorrect format or it may be encrypted with an unsupported " - "algorithm." + "algorithm.", + errors, ) def elliptic_curve_supported(self, curve): @@ -1794,6 +1843,11 @@ def create_ocsp_response( return _OCSPResponse(self, ocsp_resp) def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): + if self._fips_enabled and not isinstance( + curve, self._fips_ecdh_curves + ): + return False + return self.elliptic_curve_supported(curve) and isinstance( algorithm, ec.ECDH ) diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py index 1f42c7bee71a..dfddafb4acb6 100644 --- a/src/cryptography/hazmat/bindings/openssl/_conditional.py +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -280,6 +280,13 @@ def cryptography_has_providers(): ] +def cryptography_has_300_fips(): + return [ + "EVP_default_properties_is_fips_enabled", + "EVP_default_properties_enable_fips", + ] + + # This is a mapping of # {condition: function-returning-names-dependent-on-that-condition} so we can # loop over them and delete unsupported names at runtime. It will be removed @@ -329,4 +336,5 @@ def cryptography_has_providers(): "Cryptography_HAS_SRTP": cryptography_has_srtp, "Cryptography_HAS_GET_PROTO_VERSION": cryptography_has_get_proto_version, "Cryptography_HAS_PROVIDERS": cryptography_has_providers, + "Cryptography_HAS_300_FIPS": cryptography_has_300_fips, } diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index f651ab672383..92d5b2448a48 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -116,6 +116,22 @@ class Binding(object): def __init__(self): self._ensure_ffi_initialized() + def _enable_fips(self): + # This function enables FIPS mode for OpenSSL 3.0.0 on installs that + # have the FIPS provider installed properly. + _openssl_assert(self.lib, self.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER) + self._base_provider = self.lib.OSSL_PROVIDER_load( + self.ffi.NULL, b"base" + ) + _openssl_assert(self.lib, self._base_provider != self.ffi.NULL) + self.lib._fips_provider = self.lib.OSSL_PROVIDER_load( + self.ffi.NULL, b"fips" + ) + _openssl_assert(self.lib, self.lib._fips_provider != self.ffi.NULL) + + res = self.lib.EVP_default_properties_enable_fips(self.ffi.NULL, 1) + _openssl_assert(self.lib, res == 1) + @classmethod def _register_osrandom_engine(cls): # Clear any errors extant in the queue before we start. In many diff --git a/tests/conftest.py b/tests/conftest.py index 43debdd61a85..32a4f0360b6a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,11 @@ from .utils import check_backend_support +def pytest_configure(config): + if config.getoption("--enable-fips"): + openssl_backend._enable_fips() + + def pytest_report_header(config): return "\n".join( [ @@ -21,6 +26,7 @@ def pytest_report_header(config): def pytest_addoption(parser): parser.addoption("--wycheproof-root", default=None) + parser.addoption("--enable-fips", default=False) def pytest_runtest_setup(item): diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index 2914f7e776f0..f6bf8fea3c20 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -675,6 +675,7 @@ def test_public_bytes(self, backend, encoding, loader_func): pub_num = key.public_numbers() assert loaded_pub_num == pub_num + @pytest.mark.skip_fips(reason="non-FIPS parameters") @pytest.mark.parametrize( ("key_path", "loader_func", "encoding", "is_dhx"), [ @@ -718,6 +719,7 @@ def test_public_bytes_match( ) assert serialized == key_bytes + @pytest.mark.skip_fips(reason="non-FIPS parameters") @pytest.mark.parametrize( ("key_path", "loader_func", "vec_path", "is_dhx"), [ diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index b1759a1bc8ab..65f5b283018a 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -24,6 +24,9 @@ @pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +@pytest.mark.skip_fips( + reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." +) class TestPKCS12Loading(object): def _test_load_pkcs12_ec_keys(self, filename, password, backend): cert = load_vectors_from_file( @@ -72,7 +75,6 @@ def test_load_pkcs12_ec_keys(self, filename, password, backend): only_if=lambda backend: backend.cipher_supported(_RC2(), None), skip_message="Does not support RC2", ) - @pytest.mark.skip_fips(reason="Unsupported algorithm in FIPS mode") def test_load_pkcs12_ec_keys_rc2(self, filename, password, backend): self._test_load_pkcs12_ec_keys(filename, password, backend) @@ -169,6 +171,9 @@ def _load_ca(backend): return cert, key +@pytest.mark.skip_fips( + reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." +) class TestPKCS12Creation(object): @pytest.mark.parametrize("name", [None, b"name"]) @pytest.mark.parametrize( @@ -277,6 +282,9 @@ def test_generate_unsupported_encryption_type(self, backend): assert str(exc.value) == "Unsupported key encryption type" +@pytest.mark.skip_fips( + reason="PKCS12 unsupported in FIPS mode. So much bad crypto in it." +) def test_pkcs12_ordering(): """ In OpenSSL < 3.0.0 PKCS12 parsing reverses the order. However, we diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py index 6bc65eef3104..2f1ecb3bb469 100644 --- a/tests/hazmat/primitives/test_pkcs7.py +++ b/tests/hazmat/primitives/test_pkcs7.py @@ -331,6 +331,9 @@ def test_sign_pem(self, backend): def test_sign_alternate_digests_der( self, hash_alg, expected_value, backend ): + if isinstance(hash_alg, hashes.SHA1) and backend._fips_enabled: + pytest.skip("SHA1 not supported in FIPS mode") + data = b"hello world" cert, key = _load_cert_key() builder = ( @@ -354,7 +357,12 @@ def test_sign_alternate_digests_der( (hashes.SHA512(), b"sha-512"), ], ) - def test_sign_alternate_digests_detached(self, hash_alg, expected_value): + def test_sign_alternate_digests_detached( + self, hash_alg, expected_value, backend + ): + if isinstance(hash_alg, hashes.SHA1) and backend._fips_enabled: + pytest.skip("SHA1 not supported in FIPS mode") + data = b"hello world" cert, key = _load_cert_key() builder = ( diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index ca969e031cfc..44bb55acd4d5 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -59,8 +59,19 @@ def _skip_fips_format(key_path, password, backend): if backend._fips_enabled: if key_path[0] == "Traditional_OpenSSL_Serialization": pytest.skip("Traditional OpenSSL format blocked in FIPS mode") - if key_path[0] == "PEM_Serialization" and password is not None: - pytest.skip("Encrypted PEM_Serialization blocked in FIPS mode") + if ( + key_path[0] in ("PEM_Serialization", "PKCS8") + and password is not None + ): + pytest.skip( + "The encrypted PEM vectors currently have encryption " + "that is not FIPS approved in the 3.0 provider" + ) + if key_path[0] == "DER_Serialization" and password is not None: + pytest.skip( + "The encrypted PKCS8 DER vectors currently have encryption " + "that is not FIPS approved in the 3.0 provider" + ) class TestBufferProtocolSerialization(object): @@ -75,6 +86,7 @@ class TestBufferProtocolSerialization(object): ], ) def test_load_der_rsa_private_key(self, key_path, password, backend): + _skip_fips_format(key_path, password, backend) data = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda derfile: derfile.read(), @@ -128,6 +140,7 @@ class TestDERSerialization(object): ], ) def test_load_der_rsa_private_key(self, key_path, password, backend): + _skip_fips_format(key_path, password, backend) key = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda derfile: load_der_private_key( @@ -837,6 +850,7 @@ def test_pks8_encrypted_corrupt_format(self, backend): with pytest.raises(ValueError): load_pem_private_key(key_data, password, backend) + @pytest.mark.skip_fips(reason="non-FIPS parameters") def test_rsa_pkcs8_encrypted_values(self, backend): pkey = load_vectors_from_file( os.path.join("asymmetric", "PKCS8", "enc-rsa-pkcs8.pem"), diff --git a/tests/wycheproof/test_hmac.py b/tests/wycheproof/test_hmac.py index bfc690795122..84b0c19a0539 100644 --- a/tests/wycheproof/test_hmac.py +++ b/tests/wycheproof/test_hmac.py @@ -41,7 +41,7 @@ def test_hmac(backend, wycheproof): hash_algo = _HMAC_ALGORITHMS[wycheproof.testfiledata["algorithm"]] if wycheproof.testgroup["tagSize"] // 8 != hash_algo.digest_size: pytest.skip("Truncated HMAC not supported") - if not backend.hash_supported(hash_algo): + if not backend.hmac_supported(hash_algo): pytest.skip("Hash {} not supported".format(hash_algo.name)) h = hmac.HMAC( diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py index 0b0983da3590..8694b61ce3ff 100644 --- a/tests/wycheproof/test_rsa.py +++ b/tests/wycheproof/test_rsa.py @@ -104,6 +104,13 @@ def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): assert isinstance(key, rsa.RSAPrivateKey) digest = _DIGESTS[wycheproof.testgroup["sha"]] assert digest is not None + if backend._fips_enabled: + if key.key_size < 2048 or isinstance(digest, hashes.SHA1): + pytest.skip( + "Invalid params for FIPS. key: {} bits, digest: {}".format( + key.key_size, digest.name + ) + ) sig = key.sign( binascii.unhexlify(wycheproof.testcase["msg"]), From ee0d7365ace7006e0c4e0ed1115938b19c87d7b7 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 17 Feb 2022 11:55:36 +0100 Subject: [PATCH 16/16] Test with latest OpenSSL, no LibreSSL --- .github/workflows/ci.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef0791b3f6c7..493571eed857 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,17 +19,12 @@ jobs: - {VERSION: "3.9", TOXENV: "flake,rust,docs", COVERAGE: "false"} - {VERSION: "pypy3", TOXENV: "pypy3"} - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.0l"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1k"}} - - {VERSION: "3.9", TOXENV: "py39-ssh", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1k"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1k", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "3.0.0-beta2"}} - - {VERSION: "3.9", TOXENV: "py39", TOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.0.0-beta2"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "2.9.2"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.0.2"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.1.5"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.2.3"}} - - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "libressl", VERSION: "3.3.1"}} - - {VERSION: "3.10-dev", TOXENV: "py310"} + - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1m"}} + - {VERSION: "3.9", TOXENV: "py39-ssh", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1m"}} + - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "1.1.1m", CONFIG_FLAGS: "no-engine no-rc2 no-srtp no-ct"}} + - {VERSION: "3.9", TOXENV: "py39", OPENSSL: {TYPE: "openssl", VERSION: "3.0.1"}} + - {VERSION: "3.9", TOXENV: "py39", TOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.0.1"}} + - {VERSION: "3.10", TOXENV: "py310"} RUST: - stable name: "${{ matrix.PYTHON.TOXENV }} ${{ matrix.PYTHON.OPENSSL.TYPE }} ${{ matrix.PYTHON.OPENSSL.VERSION }} ${{ matrix.PYTHON.TOXARGS }} ${{ matrix.PYTHON.OPENSSL.CONFIG_FLAGS }}"