diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d093eb9e428..1f7b246a149f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,6 +147,7 @@ jobs: - {IMAGE: "fedora", TOXENV: "py311", RUNNER: "ubuntu-latest"} - {IMAGE: "alpine", TOXENV: "py310", RUNNER: "ubuntu-latest"} - {IMAGE: "centos-stream9", TOXENV: "py39", RUNNER: "ubuntu-latest"} + - {IMAGE: "centos-stream9-fips", TOXENV: "py39", RUNNER: "ubuntu-latest", FIPS: true} - {IMAGE: "ubuntu-jammy:aarch64", TOXENV: "py310", RUNNER: [self-hosted, Linux, ARM64]} - {IMAGE: "alpine:aarch64", TOXENV: "py310", RUNNER: [self-hosted, Linux, ARM64]} diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index db2dace37582..6f6fb9021304 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -781,6 +781,9 @@ def _evp_pkey_to_public_key(self, evp_pkey) -> PUBLIC_KEY_TYPES: raise UnsupportedAlgorithm("Unsupported key type.") def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return False + return isinstance( algorithm, ( @@ -811,6 +814,12 @@ def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool: else: return False + def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool: + if self._fips_enabled and isinstance(padding, PKCS1v15): + return False + else: + return self.rsa_padding_supported(padding) + def generate_dsa_parameters(self, key_size: int) -> dsa.DSAParameters: if key_size not in (1024, 2048, 3072, 4096): raise ValueError( diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 8a0b46c9b044..2638add8d0fe 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -380,8 +380,8 @@ def test_rsa_padding_supported_oaep(self): assert ( backend.rsa_padding_supported( padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ), ) @@ -397,6 +397,12 @@ def test_rsa_padding_supported_oaep_sha2_combinations(self): hashes.SHA512(), ] for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs): + if backend._fips_enabled and ( + isinstance(mgf1alg, hashes.SHA1) + or isinstance(oaepalg, hashes.SHA1) + ): + continue + assert ( backend.rsa_padding_supported( padding.OAEP( diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index a3fb50302082..02d16a54a519 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -438,6 +438,12 @@ def test_oaep_wrong_label(self, enclabel, declabel, backend): ), ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_encryption_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5.", + ) def test_lazy_blinding(self, backend): private_key = RSA_KEY_2048.private_key(backend) public_key = private_key.public_key() @@ -1668,7 +1674,7 @@ def test_invalid_algorithm(self): class TestRSADecryption: @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", @@ -1705,7 +1711,7 @@ def test_unsupported_padding(self, backend): @pytest.mark.supported( only_if=lambda backend: ( - backend.rsa_padding_supported(padding.PKCS1v15()) + backend.rsa_encryption_supported(padding.PKCS1v15()) and not backend._lib.Cryptography_HAS_IMPLICIT_RSA_REJECTION ), skip_message="Does not support PKCS1v1.5.", @@ -1716,7 +1722,7 @@ def test_decrypt_invalid_decrypt(self, backend): private_key.decrypt(b"\x00" * 256, padding.PKCS1v15()) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", @@ -1727,7 +1733,7 @@ def test_decrypt_ciphertext_too_large(self, backend): private_key.decrypt(b"\x00" * 257, padding.PKCS1v15()) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", @@ -1742,7 +1748,7 @@ def test_decrypt_ciphertext_too_small(self, backend): private_key.decrypt(ct, padding.PKCS1v15()) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1751,7 +1757,7 @@ def test_decrypt_ciphertext_too_small(self, backend): ), skip_message="Does not support OAEP.", ) - def test_decrypt_oaep_vectors(self, subtests, backend): + def test_decrypt_oaep_sha1_vectors(self, subtests, backend): for private, public, example in _flatten_pkcs1_examples( load_vectors_from_file( os.path.join( @@ -1782,22 +1788,20 @@ def test_decrypt_oaep_vectors(self, subtests, backend): ) assert message == binascii.unhexlify(example["message"]) - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA224()), - algorithm=hashes.SHA224(), - label=None, - ) - ), - skip_message=( - "Does not support OAEP using SHA224 MGF1 and SHA224 hash." - ), - ) def test_decrypt_oaep_sha2_vectors(self, backend, subtests): vectors = _build_oaep_sha2_vectors() for private, public, example, mgf1_alg, hash_alg in vectors: with subtests.test(): + pad = padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1_alg), + algorithm=hash_alg, + label=None, + ) + if not backend.rsa_encryption_supported(pad): + pytest.skip( + f"Does not support OAEP using {mgf1_alg.name} MGF1 " + f"or {hash_alg.name} hash." + ) skey = rsa.RSAPrivateNumbers( p=private["p"], q=private["q"], @@ -1811,16 +1815,12 @@ def test_decrypt_oaep_sha2_vectors(self, backend, subtests): ).private_key(backend, unsafe_skip_rsa_key_validation=True) message = skey.decrypt( binascii.unhexlify(example["encryption"]), - padding.OAEP( - mgf=padding.MGF1(algorithm=mgf1_alg), - algorithm=hash_alg, - label=None, - ), + pad, ) assert message == binascii.unhexlify(example["message"]) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1857,7 +1857,7 @@ def test_invalid_oaep_decryption(self, backend): ) @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1909,7 +1909,7 @@ def test_unsupported_oaep_mgf(self, backend): class TestRSAEncryption: @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), @@ -1953,18 +1953,6 @@ def test_rsa_encrypt_oaep(self, key_data, pad, backend): recovered_pt = private_key.decrypt(ct, pad) assert recovered_pt == pt - @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA256()), - algorithm=hashes.SHA512(), - label=None, - ) - ), - skip_message=( - "Does not support OAEP using SHA256 MGF1 and SHA512 hash." - ), - ) @pytest.mark.parametrize( ("mgf1hash", "oaephash"), itertools.product( @@ -1990,6 +1978,11 @@ def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): algorithm=oaephash, label=None, ) + if not backend.rsa_encryption_supported(pad): + pytest.skip( + f"Does not support OAEP using {mgf1hash.name} MGF1 " + f"or {oaephash.name} hash." + ) private_key = RSA_KEY_2048.private_key(backend) pt = b"encrypt me using sha2 hashes!" public_key = private_key.public_key() @@ -2000,7 +1993,7 @@ def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): assert recovered_pt == pt @pytest.mark.supported( - only_if=lambda backend: backend.rsa_padding_supported( + only_if=lambda backend: backend.rsa_encryption_supported( padding.PKCS1v15() ), skip_message="Does not support PKCS1v1.5.", @@ -2051,8 +2044,8 @@ def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): ), ( padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), label=None, ), padding.PKCS1v15(), @@ -2061,6 +2054,8 @@ def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): ) def test_rsa_encrypt_key_too_small(self, key_data, pad, backend): private_key = key_data.private_key(backend) + if not backend.rsa_encryption_supported(pad): + pytest.skip("PKCS1v15 padding not allowed in FIPS") _check_fips_key_length(backend, private_key) public_key = private_key.public_key() # Slightly smaller than the key size but not enough for padding. diff --git a/tests/hazmat/primitives/test_ssh.py b/tests/hazmat/primitives/test_ssh.py index 8403b9f88059..672e08e08141 100644 --- a/tests/hazmat/primitives/test_ssh.py +++ b/tests/hazmat/primitives/test_ssh.py @@ -1121,14 +1121,17 @@ def test_loads_ssh_cert(self, backend): "p256-rsa-sha512.pub", ], ) - def test_verify_cert_signature(self, filename): + def test_verify_cert_signature(self, filename, backend): data = load_vectors_from_file( os.path.join("asymmetric", "OpenSSH", "certs", filename), lambda f: f.read(), mode="rb", ) cert = load_ssh_public_identity(data) + # we have no public API for getting the hash alg of the sig assert isinstance(cert, SSHCertificate) + if backend._fips_enabled and bytes(cert._inner_sig_type) == b"ssh-rsa": + pytest.skip("FIPS does not support RSA SHA1") cert.verify_cert_signature() @pytest.mark.parametrize( @@ -1142,7 +1145,7 @@ def test_verify_cert_signature(self, filename): "p256-rsa-sha512.pub", ], ) - def test_invalid_signature(self, filename): + def test_invalid_signature(self, filename, backend): data = load_vectors_from_file( os.path.join("asymmetric", "OpenSSH", "certs", filename), lambda f: f.read(), @@ -1153,6 +1156,9 @@ def test_invalid_signature(self, filename): data[-10] = 71 cert = load_ssh_public_identity(data) assert isinstance(cert, SSHCertificate) + # we have no public API for getting the hash alg of the sig + if backend._fips_enabled and bytes(cert._inner_sig_type) == b"ssh-rsa": + pytest.skip("FIPS does not support RSA SHA1") with pytest.raises(InvalidSignature): cert.verify_cert_signature() diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py index 56ec21bc073b..8ce1f8cbd854 100644 --- a/tests/wycheproof/test_rsa.py +++ b/tests/wycheproof/test_rsa.py @@ -103,7 +103,9 @@ def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): digest = _DIGESTS[wycheproof.testgroup["sha"]] assert digest is not None if backend._fips_enabled: - if key.key_size < 2048 or isinstance(digest, hashes.SHA1): + if key.key_size < backend._fips_rsa_min_key_size or isinstance( + digest, hashes.SHA1 + ): pytest.skip( "Invalid params for FIPS. key: {} bits, digest: {}".format( key.key_size, digest.name @@ -130,11 +132,13 @@ def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): "rsa_pss_misc_test.json", ) def test_rsa_pss_signature(backend, wycheproof): + digest = _DIGESTS[wycheproof.testgroup["sha"]] + if backend._fips_enabled and isinstance(digest, hashes.SHA1): + pytest.skip("Invalid params for FIPS. SHA1 is disallowed") key = serialization.load_der_public_key( binascii.unhexlify(wycheproof.testgroup["keyDer"]), backend ) assert isinstance(key, rsa.RSAPublicKey) - digest = _DIGESTS[wycheproof.testgroup["sha"]] mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] if digest is None or mgf_digest is None: @@ -189,23 +193,29 @@ def test_rsa_pss_signature(backend, wycheproof): "rsa_oaep_misc_test.json", ) def test_rsa_oaep_encryption(backend, wycheproof): - key = serialization.load_pem_private_key( - wycheproof.testgroup["privateKeyPem"].encode("ascii"), - password=None, - backend=backend, - unsafe_skip_rsa_key_validation=True, - ) - assert isinstance(key, rsa.RSAPrivateKey) digest = _DIGESTS[wycheproof.testgroup["sha"]] mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] assert digest is not None assert mgf_digest is not None - padding_algo = padding.OAEP( mgf=padding.MGF1(algorithm=mgf_digest), algorithm=digest, label=binascii.unhexlify(wycheproof.testcase["label"]), ) + if not backend.rsa_encryption_supported(padding_algo): + pytest.skip( + f"Does not support OAEP using {mgf_digest.name} MGF1 " + f"or {digest.name} hash." + ) + key = serialization.load_pem_private_key( + wycheproof.testgroup["privateKeyPem"].encode("ascii"), + password=None, + backend=backend, + unsafe_skip_rsa_key_validation=True, + ) + assert isinstance(key, rsa.RSAPrivateKey) + if backend._fips_enabled and key.key_size < backend._fips_rsa_min_key_size: + pytest.skip("Invalid params for FIPS. <2048 bit keys are disallowed") if wycheproof.valid or wycheproof.acceptable: pt = key.decrypt( @@ -219,6 +229,12 @@ def test_rsa_oaep_encryption(backend, wycheproof): ) +@pytest.mark.supported( + only_if=lambda backend: backend.rsa_encryption_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5 for encryption.", +) @wycheproof_tests( "rsa_pkcs1_2048_test.json", "rsa_pkcs1_3072_test.json", diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index ac73ad11c247..821f1fe87e80 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -4728,7 +4728,7 @@ def test_tbs_certificate_bytes(self, backend): cert.signature_hash_algorithm, ) - def test_verify_directly_issued_by_dsa(self): + def test_verify_directly_issued_by_dsa(self, backend): issuer_private_key = DSA_KEY_3072.private_key() subject_private_key = DSA_KEY_2048.private_key() ca, cert = _generate_ca_and_leaf( @@ -4736,7 +4736,7 @@ def test_verify_directly_issued_by_dsa(self): ) cert.verify_directly_issued_by(ca) - def test_verify_directly_issued_by_dsa_bad_sig(self): + def test_verify_directly_issued_by_dsa_bad_sig(self, backend): issuer_private_key = DSA_KEY_3072.private_key() subject_private_key = DSA_KEY_2048.private_key() ca, cert = _generate_ca_and_leaf(