Dedicated check for signature hash algorithms (#6931)

* Dedicated check for signature hash algorithms

Move the check for FIPS mode and blocked SHA1 signature algorithm
into the backend code. Some distros will block SHA1 for RSA signatures
in the near future. The new ``signature_hash_supported()`` method will
allow us to flip the switch in one place.

Note: The ban of SHA1 signatures does not affect MGF1's inner hash algorithm.

Signed-off-by: Christian Heimes <christian@python.org>

* Address flake issues

* Update src/cryptography/hazmat/backends/openssl/backend.py
This commit is contained in:
Christian Heimes 2022-03-18 06:47:33 +02:00 committed by GitHub
parent b0df70cd2d
commit decec8795a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 22 deletions

View file

@ -326,6 +326,15 @@ class Backend:
evp_md = self._evp_md_from_algorithm(algorithm)
return evp_md != self._ffi.NULL
def signature_hash_supported(
self, algorithm: hashes.HashAlgorithm
) -> bool:
# Dedicated check for hashing algorithm use in message digest for
# signatures, e.g. RSA PKCS#1 v1.5 SHA1 (sha1WithRSAEncryption).
if self._fips_enabled and isinstance(algorithm, hashes.SHA1):
return False
return self.hash_supported(algorithm)
def scrypt_supported(self) -> bool:
if self._fips_enabled:
return False
@ -723,7 +732,8 @@ class Backend:
if isinstance(padding, PKCS1v15):
return True
elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1):
# SHA1 is permissible in MGF1 in FIPS
# 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
):
@ -854,7 +864,7 @@ class Backend:
def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
if not self.dsa_supported():
return False
return self.hash_supported(algorithm)
return self.signature_hash_supported(algorithm)
def cmac_algorithm_supported(self, algorithm) -> bool:
return self.cipher_supported(

View file

@ -14,6 +14,7 @@ from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ed25519, rsa
from cryptography.hazmat.primitives.serialization import pkcs7
from .utils import skip_signature_hash
from ...utils import load_vectors_from_file, raises_unsupported_algorithm
@ -346,8 +347,7 @@ class TestPKCS7Builder:
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")
skip_signature_hash(backend, hash_alg)
data = b"hello world"
cert, key = _load_cert_key()
@ -375,8 +375,7 @@ class TestPKCS7Builder:
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")
skip_signature_hash(backend, hash_alg)
data = b"hello world"
cert, key = _load_cert_key()

View file

@ -369,7 +369,12 @@ class TestRSASignature:
),
skip_message="Does not support PKCS1v1.5.",
)
@pytest.mark.skip_fips(reason="SHA1 signing not supported in FIPS mode.")
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
def test_pkcs1v15_signing(self, backend, disable_rsa_checks, subtests):
vectors = _flatten_pkcs1_examples(
load_vectors_from_file(
@ -406,7 +411,12 @@ class TestRSASignature:
),
skip_message="Does not support PSS.",
)
@pytest.mark.skip_fips(reason="SHA1 signing not supported in FIPS mode.")
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
def test_pss_signing(self, subtests, backend):
for private, public, example in _flatten_pkcs1_examples(
load_vectors_from_file(
@ -549,7 +559,7 @@ class TestRSASignature:
private_key.sign(
b"msg",
"notpadding", # type: ignore[arg-type]
hashes.SHA1(),
hashes.SHA256(),
)
@pytest.mark.supported(
@ -686,6 +696,12 @@ class TestRSAVerification:
),
skip_message="Does not support PKCS1v1.5.",
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
def test_pkcs1v15_verification(self, backend, subtests):
vectors = _flatten_pkcs1_examples(
load_vectors_from_file(
@ -811,6 +827,12 @@ class TestRSAVerification:
),
skip_message="Does not support PSS.",
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
def test_pss_verification(self, subtests, backend):
for private, public, example in _flatten_pkcs1_examples(
load_vectors_from_file(
@ -843,6 +865,12 @@ class TestRSAVerification:
),
skip_message="Does not support PSS.",
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
@pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.")
def test_invalid_pss_signature_wrong_data(self, backend):
public_key = rsa.RSAPublicNumbers(
@ -878,6 +906,12 @@ class TestRSAVerification:
),
skip_message="Does not support PSS.",
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
@pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.")
def test_invalid_pss_signature_wrong_key(self, backend):
signature = binascii.unhexlify(
@ -915,6 +949,12 @@ class TestRSAVerification:
),
skip_message="Does not support PSS.",
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
@pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.")
def test_invalid_pss_signature_data_too_large_for_modulus(self, backend):
# 2048 bit PSS signature
@ -941,6 +981,12 @@ class TestRSAVerification:
hashes.SHA1(),
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
def test_invalid_pss_signature_recover(self, backend):
private_key = RSA_KEY_2048.private_key(backend)
public_key = private_key.public_key()
@ -967,7 +1013,7 @@ class TestRSAVerification:
public_key = private_key.public_key()
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING):
public_key.verify(
b"sig", b"msg", DummyAsymmetricPadding(), hashes.SHA1()
b"sig", b"msg", DummyAsymmetricPadding(), hashes.SHA256()
)
def test_padding_incorrect_type(self, backend):
@ -978,7 +1024,7 @@ class TestRSAVerification:
b"sig",
b"msg",
"notpadding", # type: ignore[arg-type]
hashes.SHA1(),
hashes.SHA256(),
)
@pytest.mark.supported(
@ -997,7 +1043,7 @@ class TestRSAVerification:
padding.PSS(
mgf=DummyMGF(), salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA1(),
hashes.SHA256(),
)
@pytest.mark.supported(
@ -1041,6 +1087,12 @@ class TestRSAVerification:
),
skip_message="Does not support PSS.",
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
@pytest.mark.skip_fips(reason="Unsupported key size in FIPS mode.")
def test_pss_verify_salt_length_too_long(self, backend):
signature = binascii.unhexlify(
@ -1109,8 +1161,11 @@ class TestRSAPSSMGF1Verification:
mgf=padding.MGF1(hashes.SHA1()),
salt_length=padding.PSS.MAX_LENGTH,
)
)
and backend.signature_hash_supported(hashes.SHA1()),
skip_message=(
"Does not support PSS using MGF1 with SHA1 or SHA1 signature."
),
skip_message="Does not support PSS using MGF1 with SHA1.",
)(
generate_rsa_verification_test(
load_rsa_nist_vectors,
@ -1242,7 +1297,7 @@ class TestRSAPSSMGF1Verification:
class TestRSAPKCS1Verification:
test_rsa_pkcs1v15_verify_sha1 = pytest.mark.supported(
only_if=lambda backend: (
backend.hash_supported(hashes.SHA1())
backend.signature_hash_supported(hashes.SHA1())
and backend.rsa_padding_supported(padding.PKCS1v15())
),
skip_message="Does not support SHA1 and PKCS1v1.5.",
@ -1262,7 +1317,7 @@ class TestRSAPKCS1Verification:
test_rsa_pkcs1v15_verify_sha224 = pytest.mark.supported(
only_if=lambda backend: (
backend.hash_supported(hashes.SHA224())
backend.signature_hash_supported(hashes.SHA224())
and backend.rsa_padding_supported(padding.PKCS1v15())
),
skip_message="Does not support SHA224 and PKCS1v1.5.",
@ -1282,7 +1337,7 @@ class TestRSAPKCS1Verification:
test_rsa_pkcs1v15_verify_sha256 = pytest.mark.supported(
only_if=lambda backend: (
backend.hash_supported(hashes.SHA256())
backend.signature_hash_supported(hashes.SHA256())
and backend.rsa_padding_supported(padding.PKCS1v15())
),
skip_message="Does not support SHA256 and PKCS1v1.5.",
@ -1302,7 +1357,7 @@ class TestRSAPKCS1Verification:
test_rsa_pkcs1v15_verify_sha384 = pytest.mark.supported(
only_if=lambda backend: (
backend.hash_supported(hashes.SHA384())
backend.signature_hash_supported(hashes.SHA384())
and backend.rsa_padding_supported(padding.PKCS1v15())
),
skip_message="Does not support SHA384 and PKCS1v1.5.",
@ -1322,7 +1377,7 @@ class TestRSAPKCS1Verification:
test_rsa_pkcs1v15_verify_sha512 = pytest.mark.supported(
only_if=lambda backend: (
backend.hash_supported(hashes.SHA512())
backend.signature_hash_supported(hashes.SHA512())
and backend.rsa_padding_supported(padding.PKCS1v15())
),
skip_message="Does not support SHA512 and PKCS1v1.5.",

View file

@ -579,3 +579,8 @@ def skip_fips_traditional_openssl(backend, fmt):
pytest.skip(
"Traditional OpenSSL key format is not supported in FIPS mode."
)
def skip_signature_hash(backend, hash_alg: hashes.HashAlgorithm):
if not backend.signature_hash_supported(hash_alg):
pytest.skip(f"{hash_alg} is not a supported signature hash algorithm.")

View file

@ -830,6 +830,12 @@ class TestRSACertificate:
assert isinstance(public_key, rsa.RSAPublicKey)
assert len(cert.signature) == public_key.key_size // 8
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA-1 signature.",
)
def test_tbs_certificate_bytes(self, backend):
cert = _load_cert(
os.path.join("x509", "custom", "post2000utctime.pem"),
@ -1616,6 +1622,12 @@ class TestRSACertificateRequest:
b"e36181e8c4c270c354b7f52c128db1b70639823324c7ea24791b7bc3d7005f3b"
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA-1 signature.",
)
def test_tbs_certrequest_bytes(self, backend):
request = _load_cert(
os.path.join("x509", "requests", "rsa_sha1.pem"),
@ -1776,8 +1788,8 @@ class TestRSACertificateRequest:
],
)
def test_build_cert(self, hashalg, hashalg_oid, backend):
if not backend.hash_supported(hashalg()):
pytest.skip(f"{hashalg} not supported in FIPS mode")
if not backend.signature_hash_supported(hashalg()):
pytest.skip(f"{hashalg} signature not supported")
issuer_private_key = RSA_KEY_2048.private_key(backend)
subject_private_key = RSA_KEY_2048.private_key(backend)
@ -2714,8 +2726,8 @@ class TestCertificateBuilder:
self, hashalg, hashalg_oid, backend
):
_skip_curve_unsupported(backend, ec.SECP256R1())
if not backend.hash_supported(hashalg()):
pytest.skip(f"{hashalg} not supported in FIPS mode")
if not backend.signature_hash_supported(hashalg()):
pytest.skip(f"{hashalg} signature not supported")
issuer_private_key = ec.generate_private_key(ec.SECP256R1(), backend)
subject_private_key = ec.generate_private_key(ec.SECP256R1(), backend)
@ -4389,6 +4401,12 @@ class TestCertificateSigningRequestBuilder:
skip_message="Does not support DSA.",
)
class TestDSACertificate:
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA-1 signature.",
)
def test_load_dsa_cert(self, backend):
cert = _load_cert(
os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"),
@ -4517,6 +4535,10 @@ class TestDSACertificate:
only_if=lambda backend: backend.dsa_supported(),
skip_message="Does not support DSA.",
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(hashes.SHA1()),
skip_message="Does not support SHA-1 signature.",
)
class TestDSACertificateRequest:
@pytest.mark.parametrize(
("path", "loader_func"),