diff --git a/.github/actions/fetch-vectors/action.yml b/.github/actions/fetch-vectors/action.yml index 2b012316dc27..d40c99e07f86 100644 --- a/.github/actions/fetch-vectors/action.yml +++ b/.github/actions/fetch-vectors/action.yml @@ -9,8 +9,8 @@ runs: with: repository: "C2SP/wycheproof" path: "wycheproof" - # Latest commit on the wycheproof master branch, as of Apr 06, 2025. - ref: "3bfb67fca7c7a2ef436e263da53cdabe0fa1dd36" # wycheproof-ref + # Latest commit on the wycheproof master branch, as of May 02, 2025. + ref: "df4e933efef449fc88af0c06e028d425d84a9495" # wycheproof-ref - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7612386e5104..b05a508a31f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,7 @@ jobs: - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.4", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "0"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.2.4", CONFIG_FLAGS: "no-legacy", NO_LEGACY: "1"}} - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.2.4"}} + - {VERSION: "3.12", NOXSESSION: "tests", NOXARGS: "--enable-fips=1", OPENSSL: {TYPE: "openssl", CONFIG_FLAGS: "enable-fips", VERSION: "3.5.0"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.4.1"}} - {VERSION: "3.12", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.5.0"}} - {VERSION: "3.12", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "3.9.2"}} diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index dbfbcac648ff..361011a86c44 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -181,14 +181,17 @@ def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool: if isinstance(padding, PKCS1v15): return True elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): - # SHA1 is permissible in MGF1 in FIPS even when SHA1 is blocked - # as signature algorithm. - if self._fips_enabled and isinstance( - padding._mgf._algorithm, hashes.SHA1 + # FIPS 186-4 only allows salt length == digest length for PSS + # It is technically acceptable to set an explicit salt length + # equal to the digest length and this will incorrectly fail, but + # since we don't do that in the tests and this method is + # private, we'll ignore that until we need to do otherwise. + if ( + self._fips_enabled + and padding._salt_length != PSS.DIGEST_LENGTH ): - return True - else: - return self.hash_supported(padding._mgf._algorithm) + return False + return self.hash_supported(padding._mgf._algorithm) elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): return self._oaep_hash_supported( padding._mgf._algorithm diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index c0fa216a28d1..a48dc653f033 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -129,7 +129,10 @@ def test_rsa_padding_supported_pkcs1v15(self): def test_rsa_padding_supported_pss(self): assert ( backend.rsa_padding_supported( - padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.DIGEST_LENGTH, + ) ) is True ) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 820b9aee503f..17c8c7c1f543 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -503,11 +503,20 @@ def test_pss_signing(self, subtests, backend): hashes.SHA1(), ) + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH, + ) + ), + skip_message="Does not support PSS with these parameters.", + ) @pytest.mark.parametrize( "hash_alg", [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()], ) - def test_pss_signing_sha2(self, rsa_key_2048, hash_alg, backend): + def test_pss_sha2_max_length(self, rsa_key_2048, hash_alg, backend): _skip_pss_hash_algorithm_unsupported(backend, hash_alg) private_key = rsa_key_2048 public_key = private_key.public_key() @@ -1040,7 +1049,7 @@ def test_pss_verification(self, subtests, backend): salt_length=padding.PSS.AUTO, ) ), - skip_message="Does not support PSS.", + skip_message="Does not support PSS with these parameters.", ) def test_pss_verify_auto_salt_length( self, rsa_key_2048: rsa.RSAPrivateKey, backend @@ -1180,7 +1189,7 @@ def test_invalid_pss_signature_recover( public_key = private_key.public_key() pss_padding = padding.PSS( mgf=padding.MGF1(algorithm=hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH, + salt_length=padding.PSS.DIGEST_LENGTH, ) signature = private_key.sign(b"sign me", pss_padding, hashes.SHA256()) diff --git a/tests/wycheproof/test_rsa.py b/tests/wycheproof/test_rsa.py index d3b26a2ab3ba..5bee2f9a9ee0 100644 --- a/tests/wycheproof/test_rsa.py +++ b/tests/wycheproof/test_rsa.py @@ -138,18 +138,7 @@ def test_rsa_pkcs1v15_signature_generation(backend, wycheproof): ) 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 = wycheproof.cache_value_to_group( - "cached_key", - lambda: serialization.load_der_public_key( - binascii.unhexlify(wycheproof.testgroup["keyDer"]), - ), - ) - assert isinstance(key, rsa.RSAPublicKey) mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] - if digest is None or mgf_digest is None: pytest.skip( "PSS with digest={} and MGF digest={} not supported".format( @@ -157,6 +146,23 @@ def test_rsa_pss_signature(backend, wycheproof): wycheproof.testgroup["mgfSha"], ) ) + if backend._fips_enabled and ( + isinstance(digest, hashes.SHA1) + or isinstance(mgf_digest, hashes.SHA1) + # FIPS 186-4 only allows salt length == digest length for PSS + or wycheproof.testgroup["sLen"] != mgf_digest.digest_size + # inner MGF1 hash must match outer hash + or wycheproof.testgroup["sha"] != wycheproof.testgroup["mgfSha"] + ): + pytest.skip("Invalid params for FIPS") + + key = wycheproof.cache_value_to_group( + "cached_key", + lambda: serialization.load_der_public_key( + binascii.unhexlify(wycheproof.testgroup["keyDer"]), + ), + ) + assert isinstance(key, rsa.RSAPublicKey) if wycheproof.valid or wycheproof.acceptable: key.verify( @@ -202,6 +208,11 @@ def test_rsa_pss_signature(backend, wycheproof): "rsa_oaep_misc_test.json", ) def test_rsa_oaep_encryption(backend, wycheproof): + if backend._fips_enabled and wycheproof.has_flag("SmallIntegerCiphertext"): + pytest.skip( + "Small integer ciphertexts are rejected in OpenSSL 3.5 FIPS" + ) + digest = _DIGESTS[wycheproof.testgroup["sha"]] mgf_digest = _DIGESTS[wycheproof.testgroup["mgfSha"]] assert digest is not None diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 8e00d2e15f99..f36373271d36 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -852,7 +852,7 @@ def test_load_cert_pub_key(self, backend): assert isinstance(pss, padding.PSS) assert isinstance(pss._mgf, padding.MGF1) assert isinstance(pss._mgf._algorithm, hashes.SHA256) - assert pss._salt_length == 222 + assert pss._salt_length == 32 assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) pub_key.verify( cert.signature, @@ -2855,6 +2855,11 @@ def test_sign_pss_length_options( computed_len, backend, ): + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len + ) + if not backend.rsa_padding_supported(pss): + pytest.skip("PSS padding with these parameters not supported") builder = ( x509.CertificateBuilder() .subject_name( @@ -2868,9 +2873,6 @@ def test_sign_pss_length_options( .not_valid_before(datetime.datetime(2020, 1, 1)) .not_valid_after(datetime.datetime(2038, 1, 1)) ) - pss = padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len - ) cert = builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss) assert isinstance(cert.signature_algorithm_parameters, padding.PSS) assert cert.signature_algorithm_parameters._salt_length == computed_len @@ -5290,6 +5292,12 @@ def test_sign_pss_length_options( computed_len, backend, ): + pss = padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len + ) + if not backend.rsa_padding_supported(pss): + pytest.skip("PSS padding with these parameters not supported") + builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) ) diff --git a/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem index e0509174c823..906bf43147fb 100644 --- a/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem +++ b/vectors/cryptography_vectors/x509/custom/rsa_pss_cert.pem @@ -1,21 +1,21 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE-----