From 4a245a657a071dff959d30eff1d214c7ff88eada Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 20 Jul 2020 11:10:29 -0500 Subject: [PATCH] test FIPS mode on centos8 (#5323) * test FIPS mode on centos8 * remove branch we don't take * simpler * better comment * rename * revert some things that don't matter * small cleanups --- .travis.yml | 3 + .travis/run.sh | 1 + .travis/upload_coverage.sh | 4 +- src/_cffi_src/openssl/err.py | 1 + .../hazmat/backends/openssl/backend.py | 73 ++++++++++++++++++- tests/conftest.py | 11 ++- tests/hazmat/backends/test_openssl.py | 1 + tests/hazmat/backends/test_openssl_memleak.py | 2 + tests/hazmat/primitives/test_aead.py | 10 ++- tests/hazmat/primitives/test_dh.py | 13 ++++ tests/hazmat/primitives/test_dsa.py | 14 +++- tests/hazmat/primitives/test_ec.py | 7 +- tests/hazmat/primitives/test_pkcs12.py | 1 + tests/hazmat/primitives/test_rsa.py | 14 +++- tests/hazmat/primitives/test_serialization.py | 15 ++++ tests/hazmat/primitives/utils.py | 22 +++++- tests/wycheproof/test_aes.py | 8 ++ tox.ini | 3 +- 18 files changed, 191 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index a7dec16c9747..9c203101ae4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,6 +64,9 @@ matrix: - python: 3.6 services: docker env: TOXENV=py36 DOCKER=pyca/cryptography-runner-centos8 + - python: 3.6 + services: docker + env: TOXENV=py36 OPENSSL_FORCE_FIPS_MODE=1 DOCKER=pyca/cryptography-runner-centos8-fips - python: 2.7 services: docker env: TOXENV=py27 DOCKER=pyca/cryptography-runner-stretch diff --git a/.travis/run.sh b/.travis/run.sh index ab12ac3c8eea..53065609b54a 100755 --- a/.travis/run.sh +++ b/.travis/run.sh @@ -32,6 +32,7 @@ if [ -n "${DOCKER}" ]; then -v "${TRAVIS_BUILD_DIR}":"${TRAVIS_BUILD_DIR}" \ -v "${HOME}/wycheproof":/wycheproof \ -w "${TRAVIS_BUILD_DIR}" \ + -e OPENSSL_FORCE_FIPS_MODE \ -e TOXENV "${DOCKER}" \ /bin/sh -c "tox -- --wycheproof-root='/wycheproof'" elif [ -n "${TOXENV}" ]; then diff --git a/.travis/upload_coverage.sh b/.travis/upload_coverage.sh index 7be892e31f31..2999bb7e6b25 100755 --- a/.travis/upload_coverage.sh +++ b/.travis/upload_coverage.sh @@ -14,8 +14,8 @@ if [ -n "${TOXENV}" ]; then source ~/.venv/bin/activate curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash - bash codecov.sh -Z -e TRAVIS_OS_NAME,TOXENV,OPENSSL,DOCKER || \ - bash codecov.sh -Z -e TRAVIS_OS_NAME,TOXENV,OPENSSL,DOCKER + bash codecov.sh -Z -e TRAVIS_OS_NAME,TOXENV,OPENSSL,DOCKER,OPENSSL_FORCE_FIPS_MODE || \ + bash codecov.sh -Z -e TRAVIS_OS_NAME,TOXENV,OPENSSL,DOCKER,OPENSSL_FORCE_FIPS_MODE ;; esac fi diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py index ecdc6e3dea39..c0697f5d469b 100644 --- a/src/_cffi_src/openssl/err.py +++ b/src/_cffi_src/openssl/err.py @@ -81,6 +81,7 @@ static const int EVP_R_CAMELLIA_KEY_SETUP_FAILED; static const int EC_R_UNKNOWN_GROUP; +static const int EC_R_NOT_A_NIST_PRIME; static const int PEM_R_BAD_BASE64_DECODE; static const int PEM_R_BAD_DECRYPT; diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index dc9c1557ada9..0daea2d27468 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -7,6 +7,7 @@ import collections import contextlib import itertools +import warnings from contextlib import contextmanager import six @@ -119,14 +120,44 @@ class Backend(object): """ name = "openssl" + # FIPS has opinions about acceptable algorithms and key sizes, but the + # disallowed algorithms are still present in OpenSSL. They just error if + # you try to use them. To avoid that we allowlist the algorithms in + # FIPS 140-3. This isn't ideal, but FIPS 140-3 is trash so here we are. + _fips_aead = { + b'aes-128-ccm', b'aes-192-ccm', b'aes-256-ccm', + b'aes-128-gcm', b'aes-192-gcm', b'aes-256-gcm', + } + _fips_ciphers = ( + AES, TripleDES + ) + _fips_hashes = ( + hashes.SHA1, hashes.SHA224, hashes.SHA256, hashes.SHA384, + hashes.SHA512, hashes.SHA512_224, hashes.SHA512_256, hashes.SHA3_224, + hashes.SHA3_256, hashes.SHA3_384, hashes.SHA3_512, hashes.SHAKE128, + hashes.SHAKE256, + ) + _fips_rsa_min_key_size = 2048 + _fips_rsa_min_public_exponent = 65537 + _fips_dsa_min_modulus = 1 << 2048 + _fips_dh_min_key_size = 2048 + _fips_dh_min_modulus = 1 << _fips_dh_min_key_size + def __init__(self): self._binding = binding.Binding() self._ffi = self._binding.ffi self._lib = self._binding.lib + self._fips_enabled = self._is_fips_enabled() self._cipher_registry = {} self._register_default_ciphers() - self.activate_osrandom_engine() + if self._fips_enabled and self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: + warnings.warn( + "OpenSSL FIPS mode is enabled. Can't enable DRBG fork safety.", + UserWarning + ) + else: + self.activate_osrandom_engine() self._dh_types = [self._lib.EVP_PKEY_DH] if self._lib.Cryptography_HAS_EVP_PKEY_DHX: self._dh_types.append(self._lib.EVP_PKEY_DHX) @@ -134,6 +165,14 @@ def __init__(self): def openssl_assert(self, ok): return binding._openssl_assert(self._lib, ok) + def _is_fips_enabled(self): + fips_mode = getattr(self._lib, "FIPS_mode", lambda: 0) + mode = fips_mode() + if mode == 0: + # OpenSSL without FIPS pushes an error on the error stack + self._lib.ERR_clear_error() + return bool(mode) + def activate_builtin_random(self): if self._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: # Obtain a new structural reference. @@ -222,6 +261,9 @@ def _evp_md_non_null_from_algorithm(self, algorithm): return evp_md def hash_supported(self, algorithm): + if self._fips_enabled and not isinstance(algorithm, self._fips_hashes): + return False + evp_md = self._evp_md_from_algorithm(algorithm) return evp_md != self._ffi.NULL @@ -232,6 +274,8 @@ 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 try: adapter = self._cipher_registry[type(cipher), type(mode)] except KeyError: @@ -1380,6 +1424,11 @@ def elliptic_curve_supported(self, curve): errors[0]._lib_reason_match( self._lib.ERR_LIB_EC, self._lib.EC_R_UNKNOWN_GROUP + ) or + # This occurs in FIPS mode for unsupported curves on RHEL + errors[0]._lib_reason_match( + self._lib.ERR_LIB_EC, + self._lib.EC_R_NOT_A_NIST_PRIME ) ) return False @@ -1777,6 +1826,16 @@ def _private_key_bytes(self, encoding, format, encryption_algorithm, # TraditionalOpenSSL + PEM/DER if format is serialization.PrivateFormat.TraditionalOpenSSL: + if ( + self._fips_enabled and + not isinstance( + encryption_algorithm, serialization.NoEncryption + ) + ): + raise ValueError( + "Encrypted traditional OpenSSL format is not " + "supported in FIPS mode." + ) key_type = self._lib.EVP_PKEY_id(evp_pkey) if encoding is serialization.Encoding.PEM: @@ -2170,6 +2229,8 @@ def x25519_generate_key(self): return _X25519PrivateKey(self, evp_pkey) def x25519_supported(self): + if self._fips_enabled: + return False return self._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER def x448_load_public_bytes(self, data): @@ -2200,9 +2261,13 @@ def x448_generate_key(self): return _X448PrivateKey(self, evp_pkey) def x448_supported(self): + if self._fips_enabled: + return False return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 def ed25519_supported(self): + if self._fips_enabled: + return False return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B def ed25519_load_public_bytes(self, data): @@ -2238,6 +2303,8 @@ def ed25519_generate_key(self): return _Ed25519PrivateKey(self, evp_pkey) def ed448_supported(self): + if self._fips_enabled: + return False return not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B def ed448_load_public_bytes(self, data): @@ -2304,6 +2371,8 @@ def derive_scrypt(self, key_material, salt, length, n, r, p): def aead_cipher_supported(self, cipher): cipher_name = aead._aead_cipher_name(cipher) + if self._fips_enabled and cipher_name not in self._fips_aead: + return False return ( self._lib.EVP_get_cipherbyname(cipher_name) != self._ffi.NULL ) @@ -2453,6 +2522,8 @@ def serialize_key_and_certificates_to_pkcs12(self, name, key, cert, cas, return self._read_mem_bio(bio) def poly1305_supported(self): + if self._fips_enabled: + return False return self._lib.Cryptography_HAS_POLY1305 == 1 def create_poly1305_ctx(self, key): diff --git a/tests/conftest.py b/tests/conftest.py index fd690ce70db9..d159affc8cc1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,10 @@ def pytest_report_header(config): - return "OpenSSL: {}".format(openssl_backend.openssl_version_text()) + return "\n".join([ + "OpenSSL: {}".format(openssl_backend.openssl_version_text()), + "FIPS Enabled: {}".format(openssl_backend._fips_enabled), + ]) def pytest_addoption(parser): @@ -33,6 +36,12 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("wycheproof", testcases) +def pytest_runtest_setup(item): + if openssl_backend._fips_enabled: + for marker in item.iter_markers(name="skip_fips"): + pytest.skip(marker.kwargs["reason"]) + + @pytest.fixture() def backend(request): required_interfaces = [ diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 14b4fb9e6e96..9d5d6fb4c327 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -171,6 +171,7 @@ def test_bn_to_int(self): @pytest.mark.skipif( not backend._lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE, reason="Requires OpenSSL with ENGINE support and OpenSSL < 1.1.1d") +@pytest.mark.skip_fips(reason="osrandom engine disabled for FIPS") class TestOpenSSLRandomEngine(object): def setup(self): # The default RAND engine is global and shared between diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py index 2b21a89ff02c..b188fe5babaf 100644 --- a/tests/hazmat/backends/test_openssl_memleak.py +++ b/tests/hazmat/backends/test_openssl_memleak.py @@ -160,6 +160,7 @@ def skip_if_memtesting_not_supported(): ) +@pytest.mark.skip_fips(reason="FIPS self-test sets allow_customize = 0") @skip_if_memtesting_not_supported() class TestAssertNoMemoryLeaks(object): def test_no_leak_no_malloc(self): @@ -205,6 +206,7 @@ def func(): """)) +@pytest.mark.skip_fips(reason="FIPS self-test sets allow_customize = 0") @skip_if_memtesting_not_supported() class TestOpenSSLMemoryLeaks(object): @pytest.mark.parametrize("path", [ diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py index 4f6bc7f4e81d..f8cf648ed45c 100644 --- a/tests/hazmat/primitives/test_aead.py +++ b/tests/hazmat/primitives/test_aead.py @@ -364,9 +364,15 @@ def test_data_too_large(self): aesgcm.encrypt(nonce, b"", FakeData()) @pytest.mark.parametrize("vector", _load_gcm_vectors()) - def test_vectors(self, vector): - key = binascii.unhexlify(vector["key"]) + def test_vectors(self, backend, vector): nonce = binascii.unhexlify(vector["iv"]) + + if backend._fips_enabled and len(nonce) != 12: + # Red Hat disables non-96-bit IV support as part of its FIPS + # patches. + pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") + + key = binascii.unhexlify(vector["key"]) aad = binascii.unhexlify(vector["aad"]) ct = binascii.unhexlify(vector["ct"]) pt = binascii.unhexlify(vector.get("pt", b"")) diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index 569989e0eccc..98d1b5337f94 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -196,6 +196,7 @@ def test_dh_parameters_supported_with_q(self, backend, vector): int(vector["g"], 16), int(vector["q"], 16)) + @pytest.mark.skip_fips(reason="modulus too small for FIPS") @pytest.mark.parametrize("with_q", [False, True]) def test_convert_to_numbers(self, backend, with_q): if with_q: @@ -242,6 +243,7 @@ def test_numbers_unsupported_parameters(self, backend): with pytest.raises(ValueError): private.private_key(backend) + @pytest.mark.skip_fips(reason="FIPS requires key size >= 2048") @pytest.mark.parametrize("with_q", [False, True]) def test_generate_dh(self, backend, with_q): if with_q: @@ -309,6 +311,7 @@ def test_exchange_algorithm(self, backend): assert symkey == symkey_manual + @pytest.mark.skip_fips(reason="key_size too small for FIPS") def test_symmetric_key_padding(self, backend): """ This test has specific parameters that produce a symmetric key @@ -339,6 +342,11 @@ def test_symmetric_key_padding(self, backend): os.path.join("asymmetric", "DH", "bad_exchange.txt"), load_nist_vectors)) def test_bad_exchange(self, backend, vector): + if ( + backend._fips_enabled and + int(vector["p1"]) < backend._fips_dh_min_modulus + ): + pytest.skip("modulus too small for FIPS mode") parameters1 = dh.DHParameterNumbers(int(vector["p1"]), int(vector["g"])) public1 = dh.DHPublicNumbers(int(vector["y1"]), parameters1) @@ -370,6 +378,11 @@ def test_bad_exchange(self, backend, vector): os.path.join("asymmetric", "DH", "vec.txt"), load_nist_vectors)) def test_dh_vectors(self, backend, vector): + if ( + backend._fips_enabled and + int(vector["p"]) < backend._fips_dh_min_modulus + ): + pytest.skip("modulus too small for FIPS mode") parameters = dh.DHParameterNumbers(int(vector["p"]), int(vector["g"])) public = dh.DHPublicNumbers(int(vector["y"]), parameters) diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index 4287ad2a0620..fd1aa6ea5f30 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -23,6 +23,7 @@ from .fixtures_dsa import ( DSA_KEY_1024, DSA_KEY_2048, DSA_KEY_3072 ) +from .utils import skip_fips_traditional_openssl from ...doubles import DummyHashAlgorithm, DummyKeySerializationEncryption from ...utils import ( load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, @@ -49,7 +50,7 @@ def test_skip_if_dsa_not_supported(backend): @pytest.mark.requires_backend_interface(interface=DSABackend) class TestDSA(object): def test_generate_dsa_parameters(self, backend): - parameters = dsa.generate_parameters(1024, backend) + parameters = dsa.generate_parameters(2048, backend) assert isinstance(parameters, dsa.DSAParameters) def test_generate_invalid_dsa_parameters(self, backend): @@ -65,6 +66,11 @@ def test_generate_invalid_dsa_parameters(self, backend): ) ) def test_generate_dsa_keys(self, vector, backend): + if ( + backend._fips_enabled and + vector['p'] < backend._fips_dsa_min_modulus + ): + pytest.skip("Small modulus blocked in FIPS mode") parameters = dsa.DSAParameterNumbers( p=vector['p'], q=vector['q'], @@ -91,7 +97,7 @@ def test_generate_dsa_keys(self, vector, backend): ) def test_generate_dsa_private_key_and_parameters(self, backend): - skey = dsa.generate_private_key(1024, backend) + skey = dsa.generate_private_key(2048, backend) assert skey numbers = skey.private_numbers() skey_parameters = numbers.public_numbers.parameter_numbers @@ -718,6 +724,7 @@ class TestDSASerialization(object): ) ) def test_private_bytes_encrypted_pem(self, backend, fmt, password): + skip_fips_traditional_openssl(backend, fmt) key_bytes = load_vectors_from_file( os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), lambda pemfile: pemfile.read().encode() @@ -812,6 +819,9 @@ def test_private_bytes_unencrypted(self, backend, encoding, fmt, priv_num = key.private_numbers() assert loaded_priv_num == priv_num + @pytest.mark.skip_fips( + reason="Traditional OpenSSL key format is not supported in FIPS mode." + ) @pytest.mark.parametrize( ("key_path", "encoding", "loader_func"), [ diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 987c0ff02cac..5cfd3820e1e5 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -23,6 +23,7 @@ from cryptography.utils import CryptographyDeprecationWarning from .fixtures_ec import EC_KEY_SECP384R1 +from .utils import skip_fips_traditional_openssl from ...doubles import DummyKeySerializationEncryption from ...utils import ( load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, @@ -45,7 +46,7 @@ def _skip_ecdsa_vector(backend, curve_type, hash_type): curve_type() ): pytest.skip( - "ECDSA not supported with this hash {} and curve {}".format( + "ECDSA not supported with this hash {} and curve {}.".format( hash_type().name, curve_type().name ) ) @@ -693,6 +694,7 @@ class TestECSerialization(object): ) ) def test_private_bytes_encrypted_pem(self, backend, fmt, password): + skip_fips_traditional_openssl(backend, fmt) _skip_curve_unsupported(backend, ec.SECP256R1()) key_bytes = load_vectors_from_file( os.path.join( @@ -798,6 +800,9 @@ def test_private_bytes_unencrypted(self, backend, encoding, fmt, priv_num = key.private_numbers() assert loaded_priv_num == priv_num + @pytest.mark.skip_fips( + reason="Traditional OpenSSL key format is not supported in FIPS mode." + ) @pytest.mark.parametrize( ("key_path", "encoding", "loader_func"), [ diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index d7c8b92abded..61c5342394f9 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -67,6 +67,7 @@ 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) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 0e7bb6446561..bde8b2095b7d 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -32,7 +32,8 @@ RSA_KEY_745, RSA_KEY_768, ) from .utils import ( - _check_rsa_private_numbers, generate_rsa_verification_test + _check_rsa_private_numbers, generate_rsa_verification_test, + skip_fips_traditional_openssl ) from ...doubles import ( DummyAsymmetricPadding, DummyHashAlgorithm, DummyKeySerializationEncryption @@ -152,6 +153,13 @@ class TestRSA(object): ) ) def test_generate_rsa_keys(self, backend, public_exponent, key_size): + if backend._fips_enabled: + if key_size < backend._fips_rsa_min_key_size: + pytest.skip("Key size not FIPS compliant: {}".format(key_size)) + if public_exponent < backend._fips_rsa_min_public_exponent: + pytest.skip("Exponent not FIPS compliant: {}".format( + public_exponent) + ) skey = rsa.generate_private_key(public_exponent, key_size, backend) assert skey.key_size == key_size @@ -2052,6 +2060,7 @@ class TestRSAPrivateKeySerialization(object): ) ) def test_private_bytes_encrypted_pem(self, backend, fmt, password): + skip_fips_traditional_openssl(backend, fmt) key = RSA_KEY_2048.private_key(backend) serialized = key.private_bytes( serialization.Encoding.PEM, @@ -2138,6 +2147,9 @@ def test_private_bytes_unencrypted(self, backend, encoding, fmt, priv_num = key.private_numbers() assert loaded_priv_num == priv_num + @pytest.mark.skip_fips( + reason="Traditional OpenSSL key format is not supported in FIPS mode." + ) @pytest.mark.parametrize( ("key_path", "encoding", "loader_func"), [ diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 77f791abe324..44f5cec6a507 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -40,6 +40,14 @@ from ...utils import raises_unsupported_algorithm +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") + + class TestBufferProtocolSerialization(object): @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.parametrize( @@ -79,6 +87,7 @@ def test_load_der_rsa_private_key(self, key_path, password, backend): ] ) def test_load_pem_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 pemfile: pemfile.read(), mode="rb" @@ -404,6 +413,7 @@ class TestPEMSerialization(object): ] ) def test_load_pem_rsa_private_key(self, key_file, password, backend): + _skip_fips_format(key_file, password, backend) key = load_vectors_from_file( os.path.join("asymmetric", *key_file), lambda pemfile: load_pem_private_key( @@ -426,6 +436,7 @@ def test_load_pem_rsa_private_key(self, key_file, password, backend): ] ) def test_load_dsa_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 pemfile: load_pem_private_key( @@ -448,6 +459,7 @@ def test_load_dsa_private_key(self, key_path, password, backend): @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) def test_load_pem_ec_private_key(self, key_path, password, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) + _skip_fips_format(key_path, password, backend) key = load_vectors_from_file( os.path.join("asymmetric", *key_path), lambda pemfile: load_pem_private_key( @@ -516,6 +528,9 @@ def test_load_ec_public_key(self, backend): assert key.curve.name == "secp256r1" assert key.curve.key_size == 256 + @pytest.mark.skip_fips( + reason="Traditional OpenSSL format blocked in FIPS mode" + ) def test_rsa_traditional_encrypted_values(self, backend): pkey = load_vectors_from_file( os.path.join( diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 4aa5ce71ddf2..7e5fc0eefdec 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -14,9 +14,10 @@ AlreadyFinalized, AlreadyUpdated, InvalidSignature, InvalidTag, NotYetFinalized ) -from cryptography.hazmat.primitives import hashes, hmac +from cryptography.hazmat.primitives import hashes, hmac, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers.modes import GCM from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand from cryptography.hazmat.primitives.kdf.kbkdf import ( CounterLocation, KBKDFHMAC, Mode @@ -80,6 +81,15 @@ def test_aead(self, backend, params): def aead_test(backend, cipher_factory, mode_factory, params): + if ( + mode_factory is GCM and backend._fips_enabled and + len(params["iv"]) != 24 + ): + # Red Hat disables non-96-bit IV support as part of its FIPS + # patches. The check is for a byte length of 24 because the value is + # hex encoded. + pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") + if params.get("pt") is not None: plaintext = params["pt"] ciphertext = params["ct"] @@ -470,3 +480,13 @@ def _check_dsa_private_numbers(skey): pkey = skey.public_numbers params = pkey.parameter_numbers assert pow(params.g, skey.x, params.p) == pkey.y + + +def skip_fips_traditional_openssl(backend, fmt): + if ( + fmt is serialization.PrivateFormat.TraditionalOpenSSL and + backend._fips_enabled + ): + pytest.skip( + "Traditional OpenSSL key format is not supported in FIPS mode." + ) diff --git a/tests/wycheproof/test_aes.py b/tests/wycheproof/test_aes.py index 55e454546c44..2b1ed77d019b 100644 --- a/tests/wycheproof/test_aes.py +++ b/tests/wycheproof/test_aes.py @@ -55,6 +55,10 @@ def test_aes_gcm(backend, wycheproof): msg = binascii.unhexlify(wycheproof.testcase["msg"]) ct = binascii.unhexlify(wycheproof.testcase["ct"]) tag = binascii.unhexlify(wycheproof.testcase["tag"]) + if backend._fips_enabled and len(iv) != 12: + # Red Hat disables non-96-bit IV support as part of its FIPS + # patches. + pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") if wycheproof.valid or wycheproof.acceptable: enc = Cipher(algorithms.AES(key), modes.GCM(iv), backend).encryptor() enc.authenticate_additional_data(aad) @@ -94,6 +98,10 @@ def test_aes_gcm_aead_api(backend, wycheproof): msg = binascii.unhexlify(wycheproof.testcase["msg"]) ct = binascii.unhexlify(wycheproof.testcase["ct"]) tag = binascii.unhexlify(wycheproof.testcase["tag"]) + if backend._fips_enabled and len(iv) != 12: + # Red Hat disables non-96-bit IV support as part of its FIPS + # patches. + pytest.skip("Non-96-bit IVs unsupported in FIPS mode.") aesgcm = AESGCM(key) if wycheproof.valid or wycheproof.acceptable: computed_ct = aesgcm.encrypt(iv, msg, aad) diff --git a/tox.ini b/tox.ini index ca3579e7a378..989adf6ccc3b 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ deps = coverage ./vectors randomorder: pytest-randomly -passenv = ARCHFLAGS LDFLAGS CFLAGS INCLUDE LIB LD_LIBRARY_PATH USERNAME PYTHONIOENCODING +passenv = ARCHFLAGS LDFLAGS CFLAGS INCLUDE LIB LD_LIBRARY_PATH USERNAME PYTHONIOENCODING OPENSSL_FORCE_FIPS_MODE commands = pip list # We use parallel mode and then combine here so that coverage.py will take @@ -86,5 +86,6 @@ extensions = rst addopts = -r s markers = requires_backend_interface: this test requires a specific backend interface + skip_fips: this test is not executed in FIPS mode supported: parametrized test requiring only_if and skip_message wycheproof_tests: this test runs a wycheproof fixture