diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index f94812e186f5..9a6baf8a44a2 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -40,7 +40,9 @@ def sign( self, data: bytes, padding: AsymmetricPadding, - algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + algorithm: asym_utils.Prehashed + | hashes.HashAlgorithm + | asym_utils.NoDigestInfo, ) -> bytes: """ Signs the data. @@ -121,7 +123,7 @@ def recover_data_from_signature( self, signature: bytes, padding: AsymmetricPadding, - algorithm: hashes.HashAlgorithm | None, + algorithm: hashes.HashAlgorithm | asym_utils.NoDigestInfo | None, ) -> bytes: """ Recovers the original data from the signature. diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py index 826b9567b47b..c01c34274be5 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/utils.py +++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py @@ -11,6 +11,10 @@ encode_dss_signature = asn1.encode_dss_signature +class NoDigestInfo: + pass + + class Prehashed: def __init__(self, algorithm: hashes.HashAlgorithm): if not isinstance(algorithm, hashes.HashAlgorithm): diff --git a/src/rust/src/backend/rsa.rs b/src/rust/src/backend/rsa.rs index a8627e78ff75..4cc77116c44f 100644 --- a/src/rust/src/backend/rsa.rs +++ b/src/rust/src/backend/rsa.rs @@ -290,8 +290,16 @@ impl RsaPrivateKey { padding: &pyo3::Bound<'p, pyo3::PyAny>, algorithm: &pyo3::Bound<'p, pyo3::PyAny>, ) -> CryptographyResult> { - let (data, algorithm) = - utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?; + let (data, algorithm) = { + if algorithm.is_instance(&types::NO_DIGEST_INFO.get(py)?)? { + ( + utils::BytesOrPyBytes::Bytes(data.as_bytes()), + pyo3::types::PyNone::get(py).to_owned().into_any(), + ) + } else { + utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)? + } + }; let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; ctx.sign_init().map_err(|_| { @@ -434,8 +442,16 @@ impl RsaPublicKey { padding: &pyo3::Bound<'_, pyo3::PyAny>, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult<()> { - let (data, algorithm) = - utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?; + let (data, algorithm) = { + if algorithm.is_instance(&types::NO_DIGEST_INFO.get(py)?)? { + ( + utils::BytesOrPyBytes::Bytes(data.as_bytes()), + pyo3::types::PyNone::get(py).to_owned().into_any(), + ) + } else { + utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)? + } + }; let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; ctx.verify_init()?; @@ -481,6 +497,11 @@ impl RsaPublicKey { padding: &pyo3::Bound<'_, pyo3::PyAny>, algorithm: &pyo3::Bound<'_, pyo3::PyAny>, ) -> CryptographyResult> { + let algorithm = if algorithm.is_instance(&types::NO_DIGEST_INFO.get(py)?)? { + &pyo3::types::PyNone::get(py).to_owned().into_any() + } else { + algorithm + }; if algorithm.is_instance(&types::PREHASHED.get(py)?)? { return Err(CryptographyError::from( pyo3::exceptions::PyTypeError::new_err( diff --git a/src/rust/src/types.rs b/src/rust/src/types.rs index a1330da6baa2..fbec079c68f1 100644 --- a/src/rust/src/types.rs +++ b/src/rust/src/types.rs @@ -393,6 +393,10 @@ pub static SHA1: LazyPyImport = pub static SHA256: LazyPyImport = LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA256"]); +pub static NO_DIGEST_INFO: LazyPyImport = LazyPyImport::new( + "cryptography.hazmat.primitives.asymmetric.utils", + &["NoDigestInfo"], +); pub static PREHASHED: LazyPyImport = LazyPyImport::new( "cryptography.hazmat.primitives.asymmetric.utils", &["Prehashed"], diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 2c595e7395c1..b06fa5600754 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -51,7 +51,9 @@ ) from .utils import ( _check_rsa_private_numbers, + compute_rsa_hash_digest, generate_rsa_verification_test, + generate_rsa_verification_without_digest_test, skip_fips_traditional_openssl, ) @@ -442,6 +444,49 @@ def test_pkcs1v15_signing(self, backend, subtests): ) assert binascii.hexlify(signature) == example["signature"] + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + 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_signing_without_digest(self, backend, subtests): + vectors = _flatten_pkcs1_examples( + load_vectors_from_file( + os.path.join("asymmetric", "RSA", "pkcs1v15sign-vectors.txt"), + load_pkcs1_vectors, + ) + ) + for private, public, example in vectors: + with subtests.test(): + private_key = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], n=private["modulus"] + ), + ).private_key(backend, unsafe_skip_rsa_key_validation=True) + signature = private_key.sign( + binascii.unhexlify( + compute_rsa_hash_digest( + backend, hashes.SHA1(), example["message"] + ) + ), + padding.PKCS1v15(), + asym_utils.NoDigestInfo(), + ) + assert binascii.hexlify(signature) == example["signature"] + @pytest.mark.supported( only_if=lambda backend: backend.rsa_padding_supported( padding.PSS( @@ -910,7 +955,7 @@ def test_pkcs1v15_verification(self, backend, subtests): # Test recovery of all data (full DigestInfo) with hash alg. as # None rec_sig_data = public_key.recover_data_from_signature( - signature, padding.PKCS1v15(), None + signature, padding.PKCS1v15(), asym_utils.NoDigestInfo() ) assert len(rec_sig_data) > len(msg_digest) assert msg_digest == rec_sig_data[-len(msg_digest) :] @@ -1522,6 +1567,26 @@ class TestRSAPKCS1Verification: ) ) + test_rsa_pkcs1v15_verify_sha1_without_digest = pytest.mark.supported( + only_if=lambda backend: ( + backend.signature_hash_supported(hashes.SHA1()) + and backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA1 and PKCS1v1.5.", + )( + generate_rsa_verification_without_digest_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGen15_186-2.rsp", + "SigGen15_186-3.rsp", + "SigVer15_186-3.rsp", + ], + hashes.SHA1(), + lambda params, hash_alg: padding.PKCS1v15(), + ) + ) + test_rsa_pkcs1v15_verify_sha224 = pytest.mark.supported( only_if=lambda backend: ( backend.signature_hash_supported(hashes.SHA224()) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 0557694c96e9..2ce8f70e48b6 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -21,6 +21,7 @@ ) from cryptography.hazmat.primitives import hashes, hmac, serialization from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils from cryptography.hazmat.primitives.ciphers import ( BlockCipherAlgorithm, Cipher, @@ -37,6 +38,14 @@ from ...utils import load_vectors_from_file +_hash_alg_oids = { + "sha1": binascii.unhexlify(b"3021300906052b0e03021a05000414"), + "sha224": binascii.unhexlify(b"302d300d06096086480165030402040500041c"), + "sha256": binascii.unhexlify(b"3031300d060960864801650304020105000420"), + "sha384": binascii.unhexlify(b"3041300d060960864801650304020205000430"), + "sha512": binascii.unhexlify(b"3051300d060960864801650304020305000440"), +} + def _load_all_params(path, file_names, param_loader): all_params = [] @@ -47,6 +56,13 @@ def _load_all_params(path, file_names, param_loader): return all_params +def compute_rsa_hash_digest(backend, hash_alg, msg): + oid = _hash_alg_oids[hash_alg.name] + h = hashes.Hash(hash_alg, backend=backend) + h.update(binascii.unhexlify(msg)) + return binascii.hexlify(oid) + binascii.hexlify(h.finalize()) + + def generate_encrypt_test( param_loader, path, file_names, cipher_factory, mode_factory ): @@ -497,6 +513,26 @@ def test_rsa_verification(self, backend, subtests): return test_rsa_verification +def generate_rsa_verification_without_digest_test( + param_loader, path, file_names, hash_alg, pad_factory +): + def test_rsa_verification(self, backend, subtests): + all_params = _load_all_params(path, file_names, param_loader) + all_params = [ + i for i in all_params if i["algorithm"] == hash_alg.name.upper() + ] + for params in all_params: + with subtests.test(): + params["msg"] = compute_rsa_hash_digest( + backend, hash_alg, params["msg"] + ) + rsa_verification_test( + backend, params, asym_utils.NoDigestInfo(), pad_factory + ) + + return test_rsa_verification + + def rsa_verification_test(backend, params, hash_alg, pad_factory): public_numbers = rsa.RSAPublicNumbers( e=params["public_exponent"], n=params["modulus"]