From 704eb06454b37d2e9f74b1f07c7f833c2a8d7a0b Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 7 Nov 2026 16:58:03 -0700 Subject: [PATCH] `PrivateKey`: add integration tests for new inherent methods Adds tests for the new encryption/signing inherent methods added in #638 --- tests/private_key.rs | 288 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 tests/private_key.rs diff --git a/tests/private_key.rs b/tests/private_key.rs new file mode 100644 index 00000000..ec666680 --- /dev/null +++ b/tests/private_key.rs @@ -0,0 +1,288 @@ +//! Integration tests for `rsa::PrivateKey`. + +use rand::{SeedableRng, rngs::ChaCha8Rng}; +use rsa::{Error, Oaep, PrivateKey, Pss, traits::PublicKeyParts}; +#[cfg(feature = "pkcs1v15")] +use rsa::{Pkcs1v15Encrypt, Pkcs1v15Sign}; +use sha2::{Digest, Sha256}; +use std::sync::OnceLock; + +fn example_private_key() -> &'static PrivateKey { + static KEY: OnceLock = OnceLock::new(); + + // Exercise non-byte-aligned moduli without depending on the encoding feature. + KEY.get_or_init(|| PrivateKey::new(&mut example_rng(), 1025).unwrap()) +} + +fn example_rng() -> ChaCha8Rng { + ChaCha8Rng::from_seed([42; 32]) +} + +#[test] +fn encryption_interoperability() { + let key = example_private_key(); + let public = key.to_public_key(); + + // Empty messages and the exact maximum message lengths are supported. + #[cfg(feature = "pkcs1v15")] + for len in [0, public.size() - 11] { + let msg = vec![42; len]; + let ct = public + .pkcs1v15_encrypt_with_rng(&mut example_rng(), &msg) + .unwrap(); + assert_eq!(ct.len(), public.size()); + assert_eq!( + ct, + public + .encrypt(&mut example_rng(), Pkcs1v15Encrypt, &msg) + .unwrap() + ); + assert_eq!(key.decrypt(Pkcs1v15Encrypt, &ct).unwrap(), msg); + assert_eq!(key.pkcs1v15_decrypt(&ct).unwrap(), msg); + assert_eq!( + key.pkcs1v15_decrypt_with_rng(&mut example_rng(), &ct) + .unwrap(), + msg + ); + } + + for len in [0, public.size() - 2 * 32 - 2] { + let msg = vec![42; len]; + let ct = public + .oaep_encrypt_with_rng::(&mut example_rng(), &msg) + .unwrap(); + + assert_eq!(ct.len(), public.size()); + assert_eq!( + ct, + public + .encrypt(&mut example_rng(), Oaep::::new(), &msg) + .unwrap() + ); + assert_eq!(key.decrypt(Oaep::::new(), &ct).unwrap(), msg); + assert_eq!(key.oaep_decrypt::(&ct).unwrap(), msg); + assert_eq!( + key.oaep_decrypt_with_rng::(&mut example_rng(), &ct) + .unwrap(), + msg + ); + } +} + +#[test] +fn signing_interoperability() { + let key = example_private_key(); + let public = key.to_public_key(); + + for msg in [b"".as_slice(), b"inherent signing methods"] { + let hash = Sha256::digest(msg); + let pss = key + .pss_sign_with_rng::(&mut example_rng(), msg) + .unwrap(); + assert_eq!(pss.len(), public.size()); + assert_eq!( + pss, + key.sign_with_rng(&mut example_rng(), Pss::::new(), &hash) + .unwrap() + ); + assert_eq!( + pss, + key.pss_sign_prehash_with_rng::(&mut example_rng(), &hash) + .unwrap() + ); + public.verify(Pss::::new(), &hash, &pss).unwrap(); + public.pss_verify::(msg, &pss).unwrap(); + public.pss_verify_prehash::(&hash, &pss).unwrap(); + assert!(public.pss_verify::(b"wrong message", &pss).is_err()); + + #[cfg(feature = "pkcs1v15")] + let pkcs = { + let pkcs = key + .pkcs1v15_sign_with_rng::(&mut example_rng(), msg) + .unwrap(); + + assert_eq!(pkcs.len(), public.size()); + assert_eq!( + pkcs, + key.sign(Pkcs1v15Sign::new::(), &hash).unwrap() + ); + assert_eq!(pkcs, key.pkcs1v15_sign::(msg).unwrap()); + assert_eq!(pkcs, key.pkcs1v15_sign_prehash::(&hash).unwrap()); + assert_eq!( + pkcs, + key.pkcs1v15_sign_prehash_with_rng::(&mut example_rng(), &hash) + .unwrap() + ); + + public + .verify(Pkcs1v15Sign::new::(), &hash, &pkcs) + .unwrap(); + public.pkcs1v15_verify::(msg, &pkcs).unwrap(); + public + .pkcs1v15_verify_prehash::(&hash, &pkcs) + .unwrap(); + + assert!( + public + .pkcs1v15_verify::(b"wrong message", &pkcs) + .is_err() + ); + + pkcs + }; + + for sig in [ + #[cfg(feature = "pkcs1v15")] + &pkcs, + &pss, + ] { + let mut padded = vec![0]; + padded.extend_from_slice(sig); + for invalid in [&sig[..sig.len() - 1], padded.as_slice(), &[]] { + assert_eq!( + public.pss_verify::(msg, invalid), + Err(Error::Verification) + ); + + #[cfg(feature = "pkcs1v15")] + assert_eq!( + public.pkcs1v15_verify::(msg, invalid), + Err(Error::Verification) + ); + } + } + } +} + +#[test] +fn pss_requires_digest_sized_salt() { + let key = example_private_key(); + let public = key.to_public_key(); + let msg = b"salt length policy"; + let hash = Sha256::digest(msg); + for salt_len in [0, 20, 32] { + let sig = key + .sign_with_rng( + &mut example_rng(), + Pss::::new_with_salt(salt_len), + &hash, + ) + .unwrap(); + assert_eq!( + public.pss_verify::(msg, &sig).is_ok(), + salt_len == 32 + ); + assert_eq!( + public.pss_verify_prehash::(&hash, &sig).is_ok(), + salt_len == 32 + ); + } +} + +#[test] +fn invalid_inputs() { + let key = example_private_key(); + let public = key.to_public_key(); + + assert_eq!( + public.oaep_encrypt_with_rng::(&mut example_rng(), &vec![0; public.size() - 65]), + Err(Error::MessageTooLong) + ); + #[cfg(feature = "pkcs1v15")] + assert_eq!( + public.pkcs1v15_encrypt_with_rng(&mut example_rng(), &vec![0; public.size() - 10]), + Err(Error::MessageTooLong) + ); + + let outside_modulus = public.n().to_be_bytes(); + let outside_modulus = &outside_modulus[outside_modulus.len() - public.size()..]; + for ct in [&[][..], outside_modulus] { + #[cfg(feature = "pkcs1v15")] + { + assert!(key.pkcs1v15_decrypt(ct).is_err()); + assert!( + key.pkcs1v15_decrypt_with_rng(&mut example_rng(), ct) + .is_err() + ); + assert!(public.pkcs1v15_verify::(b"message", ct).is_err()); + } + assert!(key.oaep_decrypt::(ct).is_err()); + assert!( + key.oaep_decrypt_with_rng::(&mut example_rng(), ct) + .is_err() + ); + assert!(public.pss_verify::(b"message", ct).is_err()); + } + + // An all-zero ciphertext has invalid padding, but is a valid representative. + let ct = vec![0; public.size()]; + + #[cfg(feature = "pkcs1v15")] + { + let synthetic = key.decrypt(Pkcs1v15Encrypt, &ct).unwrap(); + assert_eq!(key.pkcs1v15_decrypt(&ct).unwrap(), synthetic); + assert_eq!( + key.pkcs1v15_decrypt_with_rng(&mut example_rng(), &ct) + .unwrap(), + synthetic + ); + } + + assert_eq!(key.oaep_decrypt::(&ct), Err(Error::Decryption)); + assert_eq!( + key.oaep_decrypt_with_rng::(&mut example_rng(), &ct), + Err(Error::Decryption) + ); + + let sig = key + .pss_sign_with_rng::(&mut example_rng(), b"message") + .unwrap(); + + for len in [0, 31, 33] { + let hash = vec![0; len]; + assert_eq!( + public.pss_verify_prehash::(&hash, &sig), + Err(Error::Verification) + ); + + #[cfg(feature = "pkcs1v15")] + { + assert_eq!( + key.pkcs1v15_sign_prehash::(&hash), + Err(Error::InputNotHashed) + ); + assert_eq!( + public.pkcs1v15_verify_prehash::(&hash, &sig), + Err(Error::InputNotHashed) + ); + } + } +} + +#[test] +#[cfg(feature = "getrandom")] +fn system_rng_methods() { + let key = example_private_key(); + let public = key.to_public_key(); + let msg = b"system randomness"; + + #[cfg(feature = "pkcs1v15")] + { + let pkcs = public.pkcs1v15_encrypt(msg).unwrap(); + assert_eq!(key.pkcs1v15_decrypt(&pkcs).unwrap(), msg); + } + + let oaep = public.oaep_encrypt::(msg).unwrap(); + assert_eq!(key.oaep_decrypt::(&oaep).unwrap(), msg); + + let pss = key.pss_sign::(msg).unwrap(); + public.pss_verify::(msg, &pss).unwrap(); + + let hash = Sha256::digest(msg); + let pss = key.pss_sign_prehash::(&hash).unwrap(); + public.pss_verify::(msg, &pss).unwrap(); + assert_eq!( + key.pss_sign_prehash::(&hash[..31]), + Err(Error::InputNotHashed) + ); +}