Skip to content

Commit

Permalink
add support for centos9-fips (#8216)
Browse files Browse the repository at this point in the history
* add support for centos9-fips

Requires a variety of new FIPS constraints on our tests, including the
addition of rsa_encryption_supported

* review comments
  • Loading branch information
reaperhulk committed Feb 6, 2023
1 parent 8e3d3d5 commit 50df392
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 57 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Expand Up @@ -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]}
Expand Down
9 changes: 9 additions & 0 deletions src/cryptography/hazmat/backends/openssl/backend.py
Expand Up @@ -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,
(
Expand Down Expand Up @@ -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(
Expand Down
10 changes: 8 additions & 2 deletions tests/hazmat/backends/test_openssl.py
Expand Up @@ -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,
),
)
Expand All @@ -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(
Expand Down
77 changes: 36 additions & 41 deletions tests/hazmat/primitives/test_rsa.py
Expand Up @@ -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()
Expand Down Expand Up @@ -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.",
Expand Down Expand Up @@ -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.",
Expand All @@ -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.",
Expand All @@ -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.",
Expand All @@ -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(),
Expand All @@ -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(
Expand Down Expand Up @@ -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"],
Expand All @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(
Expand All @@ -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()
Expand All @@ -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.",
Expand Down Expand Up @@ -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(),
Expand All @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions tests/hazmat/primitives/test_ssh.py
Expand Up @@ -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(
Expand All @@ -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(),
Expand All @@ -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()

Expand Down
36 changes: 26 additions & 10 deletions tests/wycheproof/test_rsa.py
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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(
Expand All @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions tests/x509/test_x509.py
Expand Up @@ -4728,15 +4728,15 @@ 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(
issuer_private_key, subject_private_key
)
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(
Expand Down

0 comments on commit 50df392

Please sign in to comment.