//! RSA PKCS#1 v1.5 signature verification (RFC 8017). //! //! Supports RSA key sizes of 2048, 3072, and 4096 bits. Parses public keys //! from DER-encoded PKCS#1 RSAPublicKey and PKCS#8 SubjectPublicKeyInfo //! formats. Verifies RSASSA-PKCS1-v1_5 signatures with SHA-256 and SHA-384. use crate::asn1::{ self, Asn1Error, OID_RSA_ENCRYPTION, OID_SHA256, OID_SHA384, OID_SHA512, TAG_NULL, TAG_SEQUENCE, }; use crate::bigint::BigUint; use crate::sha2::{sha256, sha384, sha512}; use core::fmt; // --------------------------------------------------------------------------- // Error type // --------------------------------------------------------------------------- #[derive(Debug, Clone, PartialEq, Eq)] pub enum RsaError { /// ASN.1 parsing error. Asn1(Asn1Error), /// RSA modulus is too small (< 2048 bits). ModulusTooSmall, /// RSA modulus is too large (> 4096 bits). ModulusTooLarge, /// Public exponent is invalid (not odd, or too small). InvalidExponent, /// Signature length doesn't match modulus length. InvalidSignatureLength, /// Signature value is >= modulus. SignatureOutOfRange, /// EMSA-PKCS1-v1_5 encoding verification failed. InvalidPadding, /// DigestInfo doesn't match the expected hash. DigestMismatch, /// Unsupported hash algorithm. UnsupportedAlgorithm, /// Invalid key format. InvalidKeyFormat, } impl fmt::Display for RsaError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Asn1(e) => write!(f, "ASN.1 error: {e}"), Self::ModulusTooSmall => write!(f, "RSA modulus too small (< 2048 bits)"), Self::ModulusTooLarge => write!(f, "RSA modulus too large (> 4096 bits)"), Self::InvalidExponent => write!(f, "invalid RSA public exponent"), Self::InvalidSignatureLength => write!(f, "signature length doesn't match modulus"), Self::SignatureOutOfRange => write!(f, "signature value >= modulus"), Self::InvalidPadding => write!(f, "invalid PKCS#1 v1.5 padding"), Self::DigestMismatch => write!(f, "digest mismatch"), Self::UnsupportedAlgorithm => write!(f, "unsupported hash algorithm"), Self::InvalidKeyFormat => write!(f, "invalid RSA key format"), } } } impl From for RsaError { fn from(e: Asn1Error) -> Self { Self::Asn1(e) } } pub type Result = core::result::Result; // --------------------------------------------------------------------------- // Hash algorithm identifiers // --------------------------------------------------------------------------- /// Hash algorithm used with RSA signature verification. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HashAlgorithm { Sha256, Sha384, Sha512, } impl HashAlgorithm { /// DER-encoded DigestInfo prefix (AlgorithmIdentifier + digest wrapper) /// per RFC 8017 Section 9.2 Notes. fn digest_info_prefix(&self) -> &'static [u8] { match self { // DigestInfo for SHA-256: // SEQUENCE { SEQUENCE { OID sha256, NULL }, OCTET STRING (32 bytes) } Self::Sha256 => &[ 0x30, 0x31, // SEQUENCE, 49 bytes 0x30, 0x0d, // SEQUENCE, 13 bytes 0x06, 0x09, // OID, 9 bytes 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, // sha256 0x05, 0x00, // NULL 0x04, 0x20, // OCTET STRING, 32 bytes ], // DigestInfo for SHA-384: Self::Sha384 => &[ 0x30, 0x41, // SEQUENCE, 65 bytes 0x30, 0x0d, // SEQUENCE, 13 bytes 0x06, 0x09, // OID, 9 bytes 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, // sha384 0x05, 0x00, // NULL 0x04, 0x30, // OCTET STRING, 48 bytes ], // DigestInfo for SHA-512: Self::Sha512 => &[ 0x30, 0x51, // SEQUENCE, 81 bytes 0x30, 0x0d, // SEQUENCE, 13 bytes 0x06, 0x09, // OID, 9 bytes 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, // sha512 0x05, 0x00, // NULL 0x04, 0x40, // OCTET STRING, 64 bytes ], } } /// Compute the hash of the given message. fn hash(&self, msg: &[u8]) -> Vec { match self { Self::Sha256 => sha256(msg).to_vec(), Self::Sha384 => sha384(msg).to_vec(), Self::Sha512 => sha512(msg).to_vec(), } } /// Length of the hash output in bytes. fn hash_len(&self) -> usize { match self { Self::Sha256 => 32, Self::Sha384 => 48, Self::Sha512 => 64, } } } // --------------------------------------------------------------------------- // RSA public key // --------------------------------------------------------------------------- /// An RSA public key. #[derive(Debug, Clone)] pub struct RsaPublicKey { /// The modulus n. pub n: BigUint, /// The public exponent e. pub e: BigUint, /// Key size in bytes (octet length of modulus). pub key_len: usize, } impl RsaPublicKey { /// Parse an RSA public key from DER-encoded PKCS#1 RSAPublicKey format. /// /// ```asn1 /// RSAPublicKey ::= SEQUENCE { /// modulus INTEGER, /// publicExponent INTEGER /// } /// ``` pub fn from_pkcs1_der(data: &[u8]) -> Result { let seq = asn1::parse_one(data)?; if seq.tag != TAG_SEQUENCE { return Err(RsaError::InvalidKeyFormat); } let items = seq.sequence_items()?; if items.len() != 2 { return Err(RsaError::InvalidKeyFormat); } let n_bytes = items[0].as_positive_integer()?; let e_bytes = items[1].as_positive_integer()?; Self::from_components(n_bytes, e_bytes) } /// Parse an RSA public key from DER-encoded PKCS#8 SubjectPublicKeyInfo format. /// /// ```asn1 /// SubjectPublicKeyInfo ::= SEQUENCE { /// algorithm AlgorithmIdentifier, /// subjectPublicKey BIT STRING /// } /// AlgorithmIdentifier ::= SEQUENCE { /// algorithm OBJECT IDENTIFIER, /// parameters ANY OPTIONAL /// } /// ``` pub fn from_pkcs8_der(data: &[u8]) -> Result { let seq = asn1::parse_one(data)?; if seq.tag != TAG_SEQUENCE { return Err(RsaError::InvalidKeyFormat); } let items = seq.sequence_items()?; if items.len() != 2 { return Err(RsaError::InvalidKeyFormat); } // Parse AlgorithmIdentifier. let alg_id = &items[0]; if alg_id.tag != TAG_SEQUENCE { return Err(RsaError::InvalidKeyFormat); } let alg_items = alg_id.sequence_items()?; if alg_items.is_empty() { return Err(RsaError::InvalidKeyFormat); } // Verify the algorithm OID is rsaEncryption. let oid = alg_items[0].as_oid()?; if !oid.matches(OID_RSA_ENCRYPTION) { return Err(RsaError::InvalidKeyFormat); } // The parameters should be NULL (or absent). if alg_items.len() > 1 && alg_items[1].tag != TAG_NULL { return Err(RsaError::InvalidKeyFormat); } // Parse the BIT STRING containing the RSAPublicKey. let (unused_bits, key_data) = items[1].as_bit_string()?; if unused_bits != 0 { return Err(RsaError::InvalidKeyFormat); } // The BIT STRING content is a DER-encoded RSAPublicKey. Self::from_pkcs1_der(key_data) } /// Try to parse from either PKCS#1 or PKCS#8 format. pub fn from_der(data: &[u8]) -> Result { // Try PKCS#8 first (more common in certificates). if let Ok(key) = Self::from_pkcs8_der(data) { return Ok(key); } // Fall back to PKCS#1. Self::from_pkcs1_der(data) } /// Create from raw modulus and exponent bytes (big-endian, no leading zeros). fn from_components(n_bytes: &[u8], e_bytes: &[u8]) -> Result { let n = BigUint::from_be_bytes(n_bytes); let e = BigUint::from_be_bytes(e_bytes); let bit_len = n.bit_len(); if bit_len < 2048 { return Err(RsaError::ModulusTooSmall); } if bit_len > 4096 { return Err(RsaError::ModulusTooLarge); } // Public exponent must be odd and >= 3. if e_bytes.is_empty() || !e.bit(0) { return Err(RsaError::InvalidExponent); } let key_len = bit_len.div_ceil(8); Ok(Self { n, e, key_len }) } /// Verify an RSASSA-PKCS1-v1_5 signature. /// /// `hash_alg` specifies the hash algorithm used. /// `message` is the original message that was signed. /// `signature` is the signature bytes. pub fn verify_pkcs1v15( &self, hash_alg: HashAlgorithm, message: &[u8], signature: &[u8], ) -> Result<()> { // Step 1: Length check. if signature.len() != self.key_len { return Err(RsaError::InvalidSignatureLength); } // Step 2: RSA verification primitive (RSAVP1). let s = BigUint::from_be_bytes(signature); if s.cmp(&self.n) != core::cmp::Ordering::Less { return Err(RsaError::SignatureOutOfRange); } // m = s^e mod n let m = s.modpow(&self.e, &self.n); let em = m.to_be_bytes_padded(self.key_len); // Step 3: EMSA-PKCS1-v1_5 encoding. let hash = hash_alg.hash(message); let expected_em = emsa_pkcs1_v15_encode(&hash, hash_alg, self.key_len)?; // Step 4: Compare. if !constant_time_eq(&em, &expected_em) { return Err(RsaError::InvalidPadding); } Ok(()) } /// Verify a signature where the hash has already been computed. /// /// `hash_alg` specifies which hash algorithm was used. /// `hash` is the pre-computed hash of the message. /// `signature` is the signature bytes. pub fn verify_pkcs1v15_prehashed( &self, hash_alg: HashAlgorithm, hash: &[u8], signature: &[u8], ) -> Result<()> { if hash.len() != hash_alg.hash_len() { return Err(RsaError::DigestMismatch); } // Step 1: Length check. if signature.len() != self.key_len { return Err(RsaError::InvalidSignatureLength); } // Step 2: RSAVP1. let s = BigUint::from_be_bytes(signature); if s.cmp(&self.n) != core::cmp::Ordering::Less { return Err(RsaError::SignatureOutOfRange); } let m = s.modpow(&self.e, &self.n); let em = m.to_be_bytes_padded(self.key_len); // Step 3: EMSA-PKCS1-v1_5 encoding. let expected_em = emsa_pkcs1_v15_encode(hash, hash_alg, self.key_len)?; // Step 4: Compare. if !constant_time_eq(&em, &expected_em) { return Err(RsaError::InvalidPadding); } Ok(()) } } // --------------------------------------------------------------------------- // EMSA-PKCS1-v1_5 encoding (RFC 8017 Section 9.2) // --------------------------------------------------------------------------- /// Construct EMSA-PKCS1-v1_5 encoded message: /// EM = 0x00 || 0x01 || PS || 0x00 || DigestInfo /// /// where PS is padding of 0xFF bytes, and DigestInfo = prefix || hash. fn emsa_pkcs1_v15_encode(hash: &[u8], hash_alg: HashAlgorithm, em_len: usize) -> Result> { let prefix = hash_alg.digest_info_prefix(); let t_len = prefix.len() + hash.len(); // em_len must be >= t_len + 11 (0x00 + 0x01 + at least 8 bytes of PS + 0x00 + T) if em_len < t_len + 11 { return Err(RsaError::InvalidPadding); } let ps_len = em_len - t_len - 3; let mut em = Vec::with_capacity(em_len); em.push(0x00); em.push(0x01); em.extend(std::iter::repeat_n(0xFF, ps_len)); em.push(0x00); em.extend_from_slice(prefix); em.extend_from_slice(hash); debug_assert_eq!(em.len(), em_len); Ok(em) } // --------------------------------------------------------------------------- // Utility: parse algorithm OID from a signature's DigestInfo // --------------------------------------------------------------------------- /// Identify the hash algorithm from a DigestInfo DER encoding. pub fn parse_digest_info_algorithm(digest_info: &[u8]) -> Result { let seq = asn1::parse_one(digest_info)?; if seq.tag != TAG_SEQUENCE { return Err(RsaError::InvalidKeyFormat); } let items = seq.sequence_items()?; if items.len() != 2 { return Err(RsaError::InvalidKeyFormat); } // AlgorithmIdentifier let alg_id = &items[0]; if alg_id.tag != TAG_SEQUENCE { return Err(RsaError::InvalidKeyFormat); } let alg_items = alg_id.sequence_items()?; if alg_items.is_empty() { return Err(RsaError::InvalidKeyFormat); } let oid = alg_items[0].as_oid()?; if oid.matches(OID_SHA256) { Ok(HashAlgorithm::Sha256) } else if oid.matches(OID_SHA384) { Ok(HashAlgorithm::Sha384) } else if oid.matches(OID_SHA512) { Ok(HashAlgorithm::Sha512) } else { Err(RsaError::UnsupportedAlgorithm) } } // --------------------------------------------------------------------------- // Constant-time comparison // --------------------------------------------------------------------------- /// Constant-time byte slice comparison to prevent timing attacks. fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { if a.len() != b.len() { return false; } let mut diff = 0u8; for (x, y) in a.iter().zip(b.iter()) { diff |= x ^ y; } diff == 0 } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; // ----------------------------------------------------------------------- // Test EMSA-PKCS1-v1_5 encoding // ----------------------------------------------------------------------- #[test] fn emsa_encoding_sha256() { let hash = sha256(b"test"); let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); // Check structure: 0x00 0x01 [0xFF padding] 0x00 [DigestInfo] assert_eq!(em[0], 0x00); assert_eq!(em[1], 0x01); let prefix = HashAlgorithm::Sha256.digest_info_prefix(); let t_len = prefix.len() + 32; // 19 + 32 = 51 let ps_len = 256 - t_len - 3; // 256 - 51 - 3 = 202 for i in 2..2 + ps_len { assert_eq!(em[i], 0xFF, "byte {i} should be 0xFF"); } assert_eq!(em[2 + ps_len], 0x00); assert_eq!(&em[3 + ps_len..3 + ps_len + prefix.len()], prefix); assert_eq!(&em[3 + ps_len + prefix.len()..], &hash[..]); } #[test] fn emsa_encoding_too_short() { let hash = sha256(b"test"); // em_len too small. let result = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 50); assert!(result.is_err()); } // ----------------------------------------------------------------------- // DER construction helpers // ----------------------------------------------------------------------- /// Encode a DER length. fn der_length(len: usize) -> Vec { if len < 0x80 { vec![len as u8] } else if len < 0x100 { vec![0x81, len as u8] } else { vec![0x82, (len >> 8) as u8, len as u8] } } /// Build a DER TLV (tag + length + value). fn der_tlv(tag: u8, value: &[u8]) -> Vec { let mut out = vec![tag]; out.extend_from_slice(&der_length(value.len())); out.extend_from_slice(value); out } /// Build a DER INTEGER from unsigned big-endian bytes (adds leading 0 if needed). fn der_integer(bytes: &[u8]) -> Vec { let needs_pad = !bytes.is_empty() && bytes[0] & 0x80 != 0; let mut value = Vec::new(); if needs_pad { value.push(0x00); } value.extend_from_slice(bytes); der_tlv(0x02, &value) } /// Build a PKCS#1 RSAPublicKey DER from modulus and exponent bytes. fn build_pkcs1_key(modulus: &[u8], exponent: &[u8]) -> Vec { let n_int = der_integer(modulus); let e_int = der_integer(exponent); let mut content = Vec::new(); content.extend_from_slice(&n_int); content.extend_from_slice(&e_int); der_tlv(0x30, &content) } /// Build a PKCS#8 SubjectPublicKeyInfo DER for RSA. fn build_pkcs8_key(modulus: &[u8], exponent: &[u8]) -> Vec { // AlgorithmIdentifier: SEQUENCE { OID rsaEncryption, NULL } let oid = der_tlv(0x06, OID_RSA_ENCRYPTION); let null = vec![0x05, 0x00]; let mut alg_content = Vec::new(); alg_content.extend_from_slice(&oid); alg_content.extend_from_slice(&null); let alg_id = der_tlv(0x30, &alg_content); // BIT STRING containing RSAPublicKey let rsa_pub_key = build_pkcs1_key(modulus, exponent); let mut bit_string_value = vec![0x00]; // unused bits = 0 bit_string_value.extend_from_slice(&rsa_pub_key); let bit_string = der_tlv(0x03, &bit_string_value); let mut content = Vec::new(); content.extend_from_slice(&alg_id); content.extend_from_slice(&bit_string); der_tlv(0x30, &content) } // ----------------------------------------------------------------------- // Test RSA public key parsing // ----------------------------------------------------------------------- #[test] fn parse_pkcs1_key() { // 2048-bit modulus (256 bytes, top bit set). let mut modulus = vec![0x80; 256]; modulus[255] = 0x01; // make it odd let exponent = vec![0x01, 0x00, 0x01]; // 65537 let key_der = build_pkcs1_key(&modulus, &exponent); let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); assert!(key.n.bit_len() >= 2048); assert_eq!(key.e, BigUint::from_u64(65537)); } #[test] fn parse_pkcs8_key() { let mut modulus = vec![0x80; 256]; modulus[255] = 0x01; let exponent = vec![0x01, 0x00, 0x01]; let key_der = build_pkcs8_key(&modulus, &exponent); let key = RsaPublicKey::from_pkcs8_der(&key_der).unwrap(); assert!(key.n.bit_len() >= 2048); assert_eq!(key.e, BigUint::from_u64(65537)); } #[test] fn parse_from_der_auto_detect() { let mut modulus = vec![0x80; 256]; modulus[255] = 0x01; let exponent = vec![0x01, 0x00, 0x01]; // PKCS#8 auto-detect. let pkcs8 = build_pkcs8_key(&modulus, &exponent); let key = RsaPublicKey::from_der(&pkcs8).unwrap(); assert_eq!(key.e, BigUint::from_u64(65537)); // PKCS#1 auto-detect. let pkcs1 = build_pkcs1_key(&modulus, &exponent); let key = RsaPublicKey::from_der(&pkcs1).unwrap(); assert_eq!(key.e, BigUint::from_u64(65537)); } #[test] fn reject_small_modulus() { let modulus = vec![0xFF; 128]; // 1024-bit let exponent = vec![0x01, 0x00, 0x01]; let key_der = build_pkcs1_key(&modulus, &exponent); let result = RsaPublicKey::from_pkcs1_der(&key_der); assert_eq!(result.unwrap_err(), RsaError::ModulusTooSmall); } #[test] fn reject_even_exponent() { let mut modulus = vec![0xFF; 256]; modulus[255] = 0xFB; let exponent = vec![0x02]; // even let key_der = build_pkcs1_key(&modulus, &exponent); let result = RsaPublicKey::from_pkcs1_der(&key_der); assert_eq!(result.unwrap_err(), RsaError::InvalidExponent); } // ----------------------------------------------------------------------- // End-to-end signature verification test // ----------------------------------------------------------------------- #[test] fn verify_pkcs1v15_sha256_constructed() { // Construct a test case using known values. // We create a valid EMSA-PKCS1-v1_5 encoded message and verify the // mathematical relationship holds: sig^e mod n == EM. // // Use small-ish but valid RSA parameters for a fast test. // For a real test we'd use a 2048-bit key, but we test the encoding logic // with a constructed example. let message = b"hello world"; let hash = sha256(message); // Verify EMSA encoding is deterministic. let em1 = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); let em2 = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); assert_eq!(em1, em2); } #[test] fn verify_signature_wrong_length() { let mut modulus = vec![0x80; 256]; modulus[255] = 0x01; let exponent = vec![0x01, 0x00, 0x01]; let key_der = build_pkcs1_key(&modulus, &exponent); let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); // Wrong signature length. let sig = vec![0u8; 128]; // should be 256 let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test", &sig); assert_eq!(result.unwrap_err(), RsaError::InvalidSignatureLength); } #[test] fn verify_signature_out_of_range() { let mut modulus = vec![0x80; 256]; modulus[255] = 0x01; let exponent = vec![0x01, 0x00, 0x01]; let key_der = build_pkcs1_key(&modulus, &exponent); let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); // Signature >= modulus. let sig = vec![0xFF; 256]; let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test", &sig); assert_eq!(result.unwrap_err(), RsaError::SignatureOutOfRange); } #[test] fn constant_time_eq_works() { assert!(constant_time_eq(b"hello", b"hello")); assert!(!constant_time_eq(b"hello", b"world")); assert!(!constant_time_eq(b"hello", b"hell")); assert!(constant_time_eq(b"", b"")); } #[test] fn emsa_encoding_sha384() { let hash = sha384(b"test"); let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha384, 256).unwrap(); assert_eq!(em[0], 0x00); assert_eq!(em[1], 0x01); let prefix = HashAlgorithm::Sha384.digest_info_prefix(); let t_len = prefix.len() + 48; let ps_len = 256 - t_len - 3; for i in 2..2 + ps_len { assert_eq!(em[i], 0xFF); } assert_eq!(em[2 + ps_len], 0x00); assert_eq!(&em[3 + ps_len..3 + ps_len + prefix.len()], prefix); assert_eq!(&em[3 + ps_len + prefix.len()..], &hash[..]); } #[test] fn emsa_encoding_sha512() { let hash = sha512(b"test"); let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha512, 256).unwrap(); assert_eq!(em[0], 0x00); assert_eq!(em[1], 0x01); let prefix = HashAlgorithm::Sha512.digest_info_prefix(); let t_len = prefix.len() + 64; let ps_len = 256 - t_len - 3; for i in 2..2 + ps_len { assert_eq!(em[i], 0xFF); } assert_eq!(em[2 + ps_len], 0x00); } #[test] fn parse_digest_info_sha256() { // Build a DigestInfo for SHA-256. let hash = [0u8; 32]; let mut digest_info = Vec::new(); let prefix = HashAlgorithm::Sha256.digest_info_prefix(); // The prefix includes the outer SEQUENCE header; build the full DigestInfo. digest_info.extend_from_slice(prefix); digest_info.extend_from_slice(&hash); // Actually, the prefix already has the outer SEQUENCE, so we need to // parse just the prefix part up to (but not including) the OCTET STRING value. // For parse_digest_info_algorithm we need the full DigestInfo as a SEQUENCE. // Let's build it properly: let mut di = Vec::new(); di.push(0x30); // SEQUENCE di.push(0x31); // length = 49 di.push(0x30); // SEQUENCE (AlgorithmIdentifier) di.push(0x0d); // length = 13 di.push(0x06); // OID di.push(0x09); // length = 9 di.extend_from_slice(OID_SHA256); di.push(0x05); // NULL di.push(0x00); di.push(0x04); // OCTET STRING di.push(0x20); // length = 32 di.extend_from_slice(&hash); let alg = parse_digest_info_algorithm(&di).unwrap(); assert_eq!(alg, HashAlgorithm::Sha256); } #[test] fn parse_digest_info_sha384() { let hash = [0u8; 48]; let mut di = Vec::new(); di.push(0x30); // SEQUENCE di.push(0x41); // length = 65 di.push(0x30); // SEQUENCE (AlgorithmIdentifier) di.push(0x0d); // length = 13 di.push(0x06); // OID di.push(0x09); // length = 9 di.extend_from_slice(OID_SHA384); di.push(0x05); // NULL di.push(0x00); di.push(0x04); // OCTET STRING di.push(0x30); // length = 48 di.extend_from_slice(&hash); let alg = parse_digest_info_algorithm(&di).unwrap(); assert_eq!(alg, HashAlgorithm::Sha384); } // ----------------------------------------------------------------------- // Signature rejection tests with PKCS#8 key // ----------------------------------------------------------------------- #[test] fn verify_invalid_signature_rejected() { let mut modulus = vec![0x80; 256]; modulus[255] = 0x01; let exponent = vec![0x01, 0x00, 0x01]; let key_der = build_pkcs8_key(&modulus, &exponent); let key = RsaPublicKey::from_pkcs8_der(&key_der).unwrap(); // Random signature should fail (invalid padding after modpow). let mut sig = vec![0x42; 256]; sig[0] = 0x01; // Ensure < modulus let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test message", &sig); assert!(result.is_err()); } #[test] fn verify_tampered_signature_rejected() { let mut modulus = vec![0x80; 256]; modulus[255] = 0x01; let exponent = vec![0x01, 0x00, 0x01]; let key_der = build_pkcs8_key(&modulus, &exponent); let key = RsaPublicKey::from_pkcs8_der(&key_der).unwrap(); // All-zero signature should fail. let sig = vec![0u8; 256]; let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test", &sig); assert!(result.is_err()); // Signature of all 0x01 should fail. let sig = vec![0x01; 256]; let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test", &sig); assert!(result.is_err()); } // ----------------------------------------------------------------------- // Mathematical verification test // ----------------------------------------------------------------------- // // We construct a scenario where we know sig^e mod n == EM, by computing // EM first and setting sig = EM (with e=1, which is degenerate but tests // the EMSA encoding path). #[test] fn verify_with_exponent_one() { // With e=1, sig^1 mod n = sig (if sig < n). // So if sig == EMSA(msg), verification passes. let mut mod_bytes = vec![0xFF; 256]; mod_bytes[255] = 0xFD; // make it odd let exponent = vec![0x01]; // e=1 let key_der = build_pkcs1_key(&mod_bytes, &exponent); let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); // Construct the expected EM for "hello" with SHA-256. let hash = sha256(b"hello"); let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); // With e=1, sig = EM should verify. let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"hello", &em); assert!(result.is_ok(), "verification should pass: {result:?}"); // Wrong message should fail. let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"world", &em); assert!(result.is_err()); } #[test] fn verify_prehashed() { let mut mod_bytes = vec![0xFF; 256]; mod_bytes[255] = 0xFD; let exponent = vec![0x01]; // e=1 let key_der = build_pkcs1_key(&mod_bytes, &exponent); let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); let hash = sha256(b"hello"); let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); let result = key.verify_pkcs1v15_prehashed(HashAlgorithm::Sha256, &hash, &em); assert!(result.is_ok()); // Wrong hash length should fail. let result = key.verify_pkcs1v15_prehashed(HashAlgorithm::Sha256, &hash[..16], &em); assert_eq!(result.unwrap_err(), RsaError::DigestMismatch); } }