Skip to main content

arcana/rsa/
pkcs1.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4// Index-based loops in this module deliberately mirror the reference
5// algorithm (parallel-array access `a[i]`/`b[i]`, or a position-indexed
6// packing/reduction state) and keep the constant-time data layout explicit;
7// the `iter().enumerate()` rewrite would obscure it. Documented, single-lint
8// allow per CLAUDE.md §6 (scoped to this module, not a blanket suppression).
9#![allow(clippy::needless_range_loop)]
10
11//! PKCS#1 v1.5 padding for encryption and signatures (RFC 8017).
12//!
13//! Encryption padding: `0x00 || 0x02 || PS || 0x00 || M`
14//! Signature padding: `0x00 || 0x01 || PS || 0x00 || DigestInfo || H`
15//!
16//! # ⚠ Use OAEP / PSS for new deployments
17//!
18//! PKCS#1 v1.5 is the **legacy** padding scheme. New code should
19//! prefer:
20//! - [`super::oaep`] for encryption (RFC 8017 §7.1).
21//! - [`super::pss`] for signatures (RFC 8017 §8.1).
22//!
23//! v1.5 is preserved here because it is still the only padding
24//! supported by some embedded TLS stacks and HSMs. Where the
25//! choice exists, do not select it.
26//!
27//! # Side-channel posture — Bleichenbacher
28//!
29//! The PKCS#1 v1.5 **encryption** scheme is the historical
30//! Bleichenbacher target (CRYPTO 1998): a padding oracle on the
31//! `02` prefix recovers the plaintext in ~2²⁰ – 2²² queries. RFC
32//! 8017 §7.2.2 defines a constant-time decrypt that **always**
33//! returns the same number of bytes whether or not padding parses
34//! correctly, with the data being an internally-derived
35//! deterministic dummy in the failure path. Roadmap item `T2-J`
36//! tracks the audit + tightening of `pkcs1::decrypt` against this
37//! recipe.
38//!
39//! The PKCS#1 v1.5 **signature** scheme has no analogous oracle,
40//! but inherits the underlying [`super::rsa::rsa_decrypt_raw`]
41//! Bellcore exposure (roadmap item `T1-C`).
42
43use super::bigint::BigInt;
44use super::rsa::{RsaPublicKey, RsaSecretKey, representative_in_range, rsa_decrypt_raw, rsa_encrypt_raw};
45use crate::Hasher;
46use crate::hash::ripemd160::Ripemd160;
47use crate::hash::sha1::Sha1;
48use crate::hash::sha3::{Sha3_256, Sha3_384, Sha3_512};
49use crate::hash::sha224::Sha224;
50use crate::hash::sha256::Sha256;
51use crate::hash::sha384::Sha384;
52use crate::hash::sha512::Sha512;
53
54/// Hash algorithm identifiers for PKCS#1 v1.5 signatures.
55///
56/// Each variant carries the DER-encoded `DigestInfo` prefix that
57/// gets prepended to the hash output `H` to form `T = prefix || H`.
58/// Per RFC 8017 §9.2 Note 1 the prefix encodes
59///
60/// ```text
61/// DigestInfo ::= SEQUENCE {
62///     digestAlgorithm  SEQUENCE { OID, NULL },
63///     digest           OCTET STRING
64/// }
65/// ```
66///
67/// SHA-3 prefixes use the OIDs assigned by NIST CSOR
68/// (`2.16.840.1.101.3.4.2.{8,9,10}`) which were standardised after
69/// RFC 8017 was published, but follow the same template as the
70/// SHA-2 prefixes.
71///
72/// **Note on RIPEMD-160**: included for backwards compatibility
73/// with legacy systems (Bitcoin signatures, some X.509 CAs from
74/// the 2000s). Not recommended for new designs.
75#[derive(Clone, Copy, Debug, PartialEq)]
76pub enum HashAlg {
77    /// SHA-1 (FIPS 180-4). 20-byte output. **Legacy / collision-broken**;
78    /// included only for compatibility with old certificates and HMAC-SHA-1.
79    Sha1,
80    /// SHA-224 (FIPS 180-4). 28-byte output.
81    Sha224,
82    /// SHA-256 (FIPS 180-4). 32-byte output.
83    Sha256,
84    /// SHA-384 (FIPS 180-4). 48-byte output.
85    Sha384,
86    /// SHA-512 (FIPS 180-4). 64-byte output.
87    Sha512,
88    /// SHA3-256 (FIPS 202). 32-byte output.
89    Sha3_256,
90    /// SHA3-384 (FIPS 202). 48-byte output.
91    Sha3_384,
92    /// SHA3-512 (FIPS 202). 64-byte output.
93    Sha3_512,
94    /// RIPEMD-160 (ISO/IEC 10118-3). 20-byte output. **Legacy**;
95    /// included for Bitcoin and 2000s European X.509 CAs.
96    Ripemd160,
97}
98
99impl HashAlg {
100    /// DER-encoded DigestInfo prefix for this hash algorithm
101    /// (RFC 8017 §9.2 Note 1, plus the SHA-3 family OIDs from
102    /// NIST CSOR `2.16.840.1.101.3.4.2.{8,9,10}`, and the
103    /// TeleTrusT OID `1.3.36.3.2.1` for RIPEMD-160).
104    fn digest_info_prefix(&self) -> &'static [u8] {
105        match self {
106            // SHA-1: OID 1.3.14.3.2.26 (5-byte OID)
107            // -> prefix 30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14
108            HashAlg::Sha1 => &[
109                0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14,
110            ],
111            // SHA-224: OID 2.16.840.1.101.3.4.2.4
112            // RFC 8017 §9.2 Note 1:
113            //   30 2d 30 0d 06 09 60 86 48 01 65 03 04 02 04 05 00 04 1c
114            HashAlg::Sha224 => &[
115                0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00,
116                0x04, 0x1c,
117            ],
118            // SHA-256: OID 2.16.840.1.101.3.4.2.1
119            HashAlg::Sha256 => &[
120                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00,
121                0x04, 0x20,
122            ],
123            // SHA-384: OID 2.16.840.1.101.3.4.2.2
124            HashAlg::Sha384 => &[
125                0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00,
126                0x04, 0x30,
127            ],
128            // SHA-512: OID 2.16.840.1.101.3.4.2.3
129            HashAlg::Sha512 => &[
130                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00,
131                0x04, 0x40,
132            ],
133            // SHA3-256: OID 2.16.840.1.101.3.4.2.8
134            HashAlg::Sha3_256 => &[
135                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05, 0x00,
136                0x04, 0x20,
137            ],
138            // SHA3-384: OID 2.16.840.1.101.3.4.2.9
139            HashAlg::Sha3_384 => &[
140                0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09, 0x05, 0x00,
141                0x04, 0x30,
142            ],
143            // SHA3-512: OID 2.16.840.1.101.3.4.2.10
144            HashAlg::Sha3_512 => &[
145                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a, 0x05, 0x00,
146                0x04, 0x40,
147            ],
148            // RIPEMD-160: OID 1.3.36.3.2.1 (5-byte OID, same shape as SHA-1)
149            // -> prefix 30 21 30 09 06 05 2b 24 03 02 01 05 00 04 14
150            HashAlg::Ripemd160 => &[
151                0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14,
152            ],
153        }
154    }
155
156    /// Output length of the hash in bytes.
157    fn hash_len(&self) -> usize {
158        match self {
159            HashAlg::Sha1 | HashAlg::Ripemd160 => 20,
160            HashAlg::Sha224 => 28,
161            HashAlg::Sha256 | HashAlg::Sha3_256 => 32,
162            HashAlg::Sha384 | HashAlg::Sha3_384 => 48,
163            HashAlg::Sha512 | HashAlg::Sha3_512 => 64,
164        }
165    }
166}
167
168/// PKCS#1 v1.5 encryption.
169///
170/// Pads `msg` and encrypts with `pk`. `rng` fills buffers with random bytes.
171/// Returns the ciphertext as a byte vector of length equal to the modulus.
172pub fn pkcs1v15_encrypt(pk: &RsaPublicKey, msg: &[u8], rng: &mut dyn FnMut(&mut [u8])) -> Vec<u8> {
173    let k = pk.modulus_byte_len();
174    assert!(
175        msg.len() <= k - 11,
176        "PKCS1v15 encrypt: message too long (max {} bytes, got {})",
177        k - 11,
178        msg.len()
179    );
180
181    // EM = 0x00 || 0x02 || PS || 0x00 || M
182    let ps_len = k - msg.len() - 3;
183    let mut em = vec![0u8; k];
184    em[0] = 0x00;
185    em[1] = 0x02;
186
187    // PS: random non-zero bytes.
188    rng(&mut em[2..2 + ps_len]);
189    for b in em[2..2 + ps_len].iter_mut() {
190        while *b == 0 {
191            let mut tmp = [0u8; 1];
192            rng(&mut tmp);
193            *b = tmp[0];
194        }
195    }
196
197    em[2 + ps_len] = 0x00;
198    em[3 + ps_len..].copy_from_slice(msg);
199
200    let m = BigInt::from_be_bytes(&em);
201    let c = rsa_encrypt_raw(pk, &m);
202    c.to_be_bytes(k)
203}
204
205/// PKCS#1 v1.5 decryption.
206///
207/// Decrypts `ct` with `sk` and removes padding.
208/// Returns `None` if padding is invalid.
209pub fn pkcs1v15_decrypt(sk: &RsaSecretKey, ct: &[u8]) -> Option<Vec<u8>> {
210    let k = sk.modulus_byte_len();
211    if ct.len() != k || k < 11 {
212        return None;
213    }
214
215    let c = BigInt::from_be_bytes(ct);
216    // RSADP step 1 (RFC 8017 §5.1.2): reject a ciphertext representative
217    // c not in [0, n-1] → "decryption error". c and n are public, so this
218    // branch is on public data only; it happens BEFORE the private-key
219    // operation and is independent of the plaintext, so it does not add a
220    // padding-validity oracle (the §7.2.2 constant-time recipe below is
221    // untouched for in-range ciphertexts).
222    if !representative_in_range(&c, &sk.n) {
223        return None;
224    }
225    let m = rsa_decrypt_raw(sk, &c);
226    let em = m.to_be_bytes(k);
227
228    // Check format: 0x00 || 0x02 || PS || 0x00 || M
229    if em[0] != 0x00 || em[1] != 0x02 {
230        return None;
231    }
232
233    // Find the 0x00 separator after PS (PS must be at least 8 bytes).
234    let mut sep = None;
235    for i in 2..em.len() {
236        if em[i] == 0x00 {
237            if i < 10 {
238                // PS too short
239                return None;
240            }
241            sep = Some(i);
242            break;
243        }
244    }
245
246    let sep = sep?;
247    Some(em[sep + 1..].to_vec())
248}
249
250/// PKCS#1 v1.5 signature generation.
251///
252/// Signs the pre-computed `hash` (of `hash_alg` type) with `sk`.
253pub fn pkcs1v15_sign(sk: &RsaSecretKey, hash: &[u8], hash_alg: HashAlg) -> Vec<u8> {
254    let k = sk.modulus_byte_len();
255    let prefix = hash_alg.digest_info_prefix();
256    let t_len = prefix.len() + hash_alg.hash_len();
257
258    assert!(hash.len() == hash_alg.hash_len(), "Hash length mismatch");
259    assert!(k >= t_len + 11, "Modulus too short for PKCS1v15 signature");
260
261    // EM = 0x00 || 0x01 || PS || 0x00 || T
262    let ps_len = k - t_len - 3;
263    let mut em = vec![0u8; k];
264    em[0] = 0x00;
265    em[1] = 0x01;
266    for i in 0..ps_len {
267        em[2 + i] = 0xFF;
268    }
269    em[2 + ps_len] = 0x00;
270    em[3 + ps_len..3 + ps_len + prefix.len()].copy_from_slice(prefix);
271    em[3 + ps_len + prefix.len()..].copy_from_slice(hash);
272
273    let m = BigInt::from_be_bytes(&em);
274    let s = rsa_decrypt_raw(sk, &m); // signing = decryption with private key
275    s.to_be_bytes(k)
276}
277
278/// PKCS#1 v1.5 signature verification.
279///
280/// Verifies that `sig` is a valid signature of `hash` under `pk`.
281pub fn pkcs1v15_verify(pk: &RsaPublicKey, hash: &[u8], hash_alg: HashAlg, sig: &[u8]) -> bool {
282    let k = pk.modulus_byte_len();
283    if sig.len() != k {
284        return false;
285    }
286    if hash.len() != hash_alg.hash_len() {
287        return false;
288    }
289
290    let s = BigInt::from_be_bytes(sig);
291    // RSAVP1 step 1 (RFC 8017 §5.2.2): reject a signature representative
292    // s not in [0, n-1]. s is public attacker input, so this is a check
293    // on public data (no secret-dependent branch).
294    if !representative_in_range(&s, &pk.n) {
295        return false;
296    }
297    let m = rsa_encrypt_raw(pk, &s); // verification = encryption with public key
298    let em = m.to_be_bytes(k);
299
300    // Reconstruct expected EM.
301    let prefix = hash_alg.digest_info_prefix();
302    let t_len = prefix.len() + hash_alg.hash_len();
303    if k < t_len + 11 {
304        return false;
305    }
306
307    let ps_len = k - t_len - 3;
308    let mut expected = vec![0u8; k];
309    expected[0] = 0x00;
310    expected[1] = 0x01;
311    for i in 0..ps_len {
312        expected[2 + i] = 0xFF;
313    }
314    expected[2 + ps_len] = 0x00;
315    expected[3 + ps_len..3 + ps_len + prefix.len()].copy_from_slice(prefix);
316    expected[3 + ps_len + prefix.len()..].copy_from_slice(hash);
317
318    // Constant-time comparison.
319    let mut diff = 0u8;
320    for (a, b) in em.iter().zip(expected.iter()) {
321        diff |= a ^ b;
322    }
323    diff == 0 && em.len() == expected.len()
324}
325
326// ============================================================================
327// Convenience helpers: hash a message with the chosen hash, then sign / verify.
328// ============================================================================
329//
330// One pair per supported (hash, HashAlg variant). The macro factors the
331// trivial 2-line body so adding a new hash in the future is a single line.
332// SHA-256 keeps its standalone definition (and not the macro) to preserve
333// the byte-for-byte stability of the public symbol that pre-existed this
334// commit -- this avoids any chance of accidentally changing its inlining
335// behaviour.
336
337/// Convenience: hash a message with SHA-256, then sign.
338pub fn pkcs1v15_sign_sha256(sk: &RsaSecretKey, message: &[u8]) -> Vec<u8> {
339    let hash = Sha256::hash(message);
340    pkcs1v15_sign(sk, &hash, HashAlg::Sha256)
341}
342
343/// Convenience: hash a message with SHA-256, then verify.
344pub fn pkcs1v15_verify_sha256(pk: &RsaPublicKey, message: &[u8], sig: &[u8]) -> bool {
345    let hash = Sha256::hash(message);
346    pkcs1v15_verify(pk, &hash, HashAlg::Sha256, sig)
347}
348
349macro_rules! convenience_pair {
350    ($sign_fn:ident, $verify_fn:ident, $hasher:ty, $alg:expr, $doc:literal) => {
351        #[doc = $doc]
352        pub fn $sign_fn(sk: &RsaSecretKey, message: &[u8]) -> Vec<u8> {
353            let hash = <$hasher as Hasher>::hash(message);
354            pkcs1v15_sign(sk, &hash, $alg)
355        }
356
357        #[doc = $doc]
358        pub fn $verify_fn(pk: &RsaPublicKey, message: &[u8], sig: &[u8]) -> bool {
359            let hash = <$hasher as Hasher>::hash(message);
360            pkcs1v15_verify(pk, &hash, $alg, sig)
361        }
362    };
363}
364
365convenience_pair!(
366    pkcs1v15_sign_sha1,
367    pkcs1v15_verify_sha1,
368    Sha1,
369    HashAlg::Sha1,
370    "Convenience: hash a message with SHA-1, then sign / verify. \
371     **Legacy**: do not use for new designs; SHA-1 is collision-broken."
372);
373
374convenience_pair!(
375    pkcs1v15_sign_sha224,
376    pkcs1v15_verify_sha224,
377    Sha224,
378    HashAlg::Sha224,
379    "Convenience: hash a message with SHA-224, then sign / verify."
380);
381
382convenience_pair!(
383    pkcs1v15_sign_sha384,
384    pkcs1v15_verify_sha384,
385    Sha384,
386    HashAlg::Sha384,
387    "Convenience: hash a message with SHA-384, then sign / verify."
388);
389
390convenience_pair!(
391    pkcs1v15_sign_sha512,
392    pkcs1v15_verify_sha512,
393    Sha512,
394    HashAlg::Sha512,
395    "Convenience: hash a message with SHA-512, then sign / verify."
396);
397
398convenience_pair!(
399    pkcs1v15_sign_sha3_256,
400    pkcs1v15_verify_sha3_256,
401    Sha3_256,
402    HashAlg::Sha3_256,
403    "Convenience: hash a message with SHA3-256, then sign / verify."
404);
405
406convenience_pair!(
407    pkcs1v15_sign_sha3_384,
408    pkcs1v15_verify_sha3_384,
409    Sha3_384,
410    HashAlg::Sha3_384,
411    "Convenience: hash a message with SHA3-384, then sign / verify."
412);
413
414convenience_pair!(
415    pkcs1v15_sign_sha3_512,
416    pkcs1v15_verify_sha3_512,
417    Sha3_512,
418    HashAlg::Sha3_512,
419    "Convenience: hash a message with SHA3-512, then sign / verify."
420);
421
422convenience_pair!(
423    pkcs1v15_sign_ripemd160,
424    pkcs1v15_verify_ripemd160,
425    Ripemd160,
426    HashAlg::Ripemd160,
427    "Convenience: hash a message with RIPEMD-160, then sign / verify. \
428     **Legacy**: included for compatibility with older systems \
429     (Bitcoin, some 2000s X.509 CAs); not recommended for new designs."
430);
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    fn test_rng() -> impl FnMut(&mut [u8]) {
437        let mut state: u64 = 0xdeadbeefcafebabe;
438        move |buf: &mut [u8]| {
439            for b in buf.iter_mut() {
440                state = state
441                    .wrapping_mul(6364136223846793005)
442                    .wrapping_add(1442695040888963407);
443                *b = (state >> 33) as u8;
444            }
445        }
446    }
447
448    #[test]
449    fn test_pkcs1v15_encrypt_decrypt_roundtrip() {
450        let mut rng = test_rng();
451        let (pk, sk) = super::super::rsa::rsa_keygen(512, &mut rng);
452        let msg = b"Hello, RSA!";
453        let ct = pkcs1v15_encrypt(&pk, msg, &mut rng);
454        let pt = pkcs1v15_decrypt(&sk, &ct).expect("decryption failed");
455        assert_eq!(&pt, msg);
456    }
457
458    #[test]
459    fn test_pkcs1v15_sign_verify_roundtrip() {
460        let mut rng = test_rng();
461        let (pk, sk) = super::super::rsa::rsa_keygen(512, &mut rng);
462        let message = b"Sign me!";
463        let sig = pkcs1v15_sign_sha256(&sk, message);
464        assert!(pkcs1v15_verify_sha256(&pk, message, &sig));
465        // Tamper with signature => should fail.
466        let mut bad_sig = sig.clone();
467        bad_sig[0] ^= 0xFF;
468        assert!(!pkcs1v15_verify_sha256(&pk, message, &bad_sig));
469    }
470
471    /// Round-trip every supported hash through the convenience helpers.
472    /// Uses a single 1024-bit key (regenerating per hash would multiply
473    /// the test runtime by 7 with no extra coverage).
474    ///
475    /// 1024 bits is the smallest modulus that fits all 7 EMSA-PKCS1-v1_5
476    /// padded values: SHA-512 / SHA3-512 need `t_len = 19 + 64 = 83`
477    /// bytes plus 11 bytes of mandatory padding overhead = 94 bytes
478    /// minimum, so a 768-bit (96-byte) modulus would just barely work
479    /// and 1024 bits (128 bytes) is the next standard size.
480    #[test]
481    fn pkcs1v15_all_supported_hashes_roundtrip() {
482        let mut rng = test_rng();
483        let (pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
484        let msg = b"hash flexibility test";
485
486        // SHA-1
487        let sig = pkcs1v15_sign_sha1(&sk, msg);
488        assert!(pkcs1v15_verify_sha1(&pk, msg, &sig));
489        // SHA-224
490        let sig = pkcs1v15_sign_sha224(&sk, msg);
491        assert!(pkcs1v15_verify_sha224(&pk, msg, &sig));
492        // SHA-256 (already covered by the older test, but include here too)
493        let sig = pkcs1v15_sign_sha256(&sk, msg);
494        assert!(pkcs1v15_verify_sha256(&pk, msg, &sig));
495        // SHA-384
496        let sig = pkcs1v15_sign_sha384(&sk, msg);
497        assert!(pkcs1v15_verify_sha384(&pk, msg, &sig));
498        // SHA-512
499        let sig = pkcs1v15_sign_sha512(&sk, msg);
500        assert!(pkcs1v15_verify_sha512(&pk, msg, &sig));
501        // SHA3-256
502        let sig = pkcs1v15_sign_sha3_256(&sk, msg);
503        assert!(pkcs1v15_verify_sha3_256(&pk, msg, &sig));
504        // SHA3-384
505        let sig = pkcs1v15_sign_sha3_384(&sk, msg);
506        assert!(pkcs1v15_verify_sha3_384(&pk, msg, &sig));
507        // SHA3-512
508        let sig = pkcs1v15_sign_sha3_512(&sk, msg);
509        assert!(pkcs1v15_verify_sha3_512(&pk, msg, &sig));
510        // RIPEMD-160
511        let sig = pkcs1v15_sign_ripemd160(&sk, msg);
512        assert!(pkcs1v15_verify_ripemd160(&pk, msg, &sig));
513    }
514
515    /// Verify must reject when the **declared hash algorithm** does
516    /// not match what the signer used. This is the property that
517    /// makes the DigestInfo prefix actually meaningful: a SHA-256
518    /// signature must NOT verify as if it were a SHA-512 signature
519    /// even with the same RSA key and the same message bytes.
520    ///
521    /// Catches a class of attacks where a downgrade in the declared
522    /// hash would let an attacker substitute a precomputed-collision
523    /// payload under a weaker hash.
524    #[test]
525    fn pkcs1v15_hash_mismatch_rejected() {
526        let mut rng = test_rng();
527        let (pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
528        let msg = b"hash mismatch test";
529
530        // Sign with SHA-256, verify as SHA-256 -> OK.
531        let sig256 = pkcs1v15_sign_sha256(&sk, msg);
532        assert!(pkcs1v15_verify_sha256(&pk, msg, &sig256));
533
534        // Same signature, verify as SHA-512 -> must reject (different
535        // DigestInfo prefix, different hash length, completely
536        // different reconstructed EM).
537        assert!(!pkcs1v15_verify_sha512(&pk, msg, &sig256));
538
539        // Same signature, verify as SHA3-256 -> must reject (same
540        // hash length but different OID byte in the DigestInfo
541        // prefix, so the reconstructed EM differs by exactly one
542        // byte). This is the strictest possible mismatch test.
543        assert!(!pkcs1v15_verify_sha3_256(&pk, msg, &sig256));
544
545        // Sign with SHA-512, verify as SHA-256 -> must reject.
546        let sig512 = pkcs1v15_sign_sha512(&sk, msg);
547        assert!(pkcs1v15_verify_sha512(&pk, msg, &sig512));
548        assert!(!pkcs1v15_verify_sha256(&pk, msg, &sig512));
549
550        // Sign with SHA3-256, verify as SHA-256 -> must reject (the
551        // critical hash-family mixup case).
552        let sig3_256 = pkcs1v15_sign_sha3_256(&sk, msg);
553        assert!(pkcs1v15_verify_sha3_256(&pk, msg, &sig3_256));
554        assert!(!pkcs1v15_verify_sha256(&pk, msg, &sig3_256));
555    }
556
557    /// `HashAlg::hash_len()` must agree with the actual `OUTPUT_LEN`
558    /// of the underlying `Hasher` implementation. If they ever
559    /// drifted (e.g. someone changed SHA-3 truncation), the sign
560    /// path would assert-fail at runtime; this test catches that
561    /// at compile-then-run time.
562    #[test]
563    fn hashalg_lengths_agree_with_hasher_output_len() {
564        assert_eq!(HashAlg::Sha1.hash_len(), <Sha1 as Hasher>::OUTPUT_LEN);
565        assert_eq!(HashAlg::Sha224.hash_len(), <Sha224 as Hasher>::OUTPUT_LEN);
566        assert_eq!(HashAlg::Sha256.hash_len(), <Sha256 as Hasher>::OUTPUT_LEN);
567        assert_eq!(HashAlg::Sha384.hash_len(), <Sha384 as Hasher>::OUTPUT_LEN);
568        assert_eq!(HashAlg::Sha512.hash_len(), <Sha512 as Hasher>::OUTPUT_LEN);
569        assert_eq!(HashAlg::Sha3_256.hash_len(), <Sha3_256 as Hasher>::OUTPUT_LEN);
570        assert_eq!(HashAlg::Sha3_384.hash_len(), <Sha3_384 as Hasher>::OUTPUT_LEN);
571        assert_eq!(HashAlg::Sha3_512.hash_len(), <Sha3_512 as Hasher>::OUTPUT_LEN);
572        assert_eq!(HashAlg::Ripemd160.hash_len(), <Ripemd160 as Hasher>::OUTPUT_LEN);
573    }
574}