Skip to main content

arcana/rsa/
pss.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4//! RSASSA-PSS signatures (RFC 8017 / PKCS#1 v2.2 §8.1).
5//!
6//! PSS is the modern RSA signature padding; it supersedes PKCS#1 v1.5
7//! signatures in every protocol that cares (TLS 1.3, JWS `PS*`,
8//! X.509 `id-RSASSA-PSS`, CMS, ...). For new deployments this should
9//! be the default.
10//!
11//! # Side-channel posture
12//!
13//! PSS itself is structurally CT (no secret-dependent branches in
14//! the EMSA-PSS encode / decode), but it relies on the underlying
15//! [`super::rsa::rsa_decrypt_raw`] for the signing direction, which
16//! is currently **not protected against the Bellcore single-fault
17//! attack** (roadmap item `T1-C` — see
18//! `arcana/doc/sca/countermeasures/rsa.rst`). Coron-Mandal (Asiacrypt
19//! 2009) showed PSS is **provably secure against random faults** in
20//! a separate fault model, but practical Bellcore-class faults still
21//! recover the key — the proof assumes the underlying RSA primitive
22//! itself is fault-resistant.
23//!
24//! # Algorithm (EMSA-PSS)
25//!
26//! For a modulus of `modBits` bits and hash `H` with output `hLen`:
27//!
28//! 1. `mHash = H(message)`
29//! 2. Generate a random `salt` of `sLen` bytes (typically `sLen == hLen`)
30//! 3. `M' = (0x00)^8 || mHash || salt`
31//! 4. `h = H(M')`
32//! 5. `DB = PS || 0x01 || salt`  where `PS` is zero-padding
33//! 6. `maskedDB = DB XOR MGF1_H(h, len(DB))`
34//! 7. Clear the top `8*emLen - emBits` bits of `maskedDB[0]`
35//!    (`emBits = modBits - 1`)
36//! 8. `EM = maskedDB || h || 0xbc`
37//! 9. `sig = EM^d mod n`  (RSASP1)
38//!
39//! Verification reverses the process and re-checks `h = H(M')`.
40//!
41//! # API
42//!
43//! ```rust,ignore
44//! use arcana::rsa::pss::{pss_sign_msg, pss_verify_msg};
45//! use arcana::hash::sha256::Sha256;
46//!
47//! let sig = pss_sign_msg::<Sha256>(&sk, msg, 32, &mut rng).unwrap();
48//! assert!(pss_verify_msg::<Sha256>(&pk, msg, 32, &sig));
49//! ```
50//!
51//! Precomputed-digest variants ([`pss_sign`] / [`pss_verify`]) are also
52//! exposed, matching the ECDSA API convention -- the common case in
53//! protocol implementations is to receive an already-hashed digest
54//! from an upstream layer (X.509, CMS, ...).
55
56use super::bigint::BigInt;
57use super::rsa::{RsaPublicKey, RsaSecretKey, representative_in_range, rsa_decrypt_raw, rsa_encrypt_raw};
58use crate::Hasher;
59
60// ============================================================================
61// MGF1 (RFC 8017 Appendix B.2.1)
62// ============================================================================
63
64/// MGF1 mask generation function using hash `H`.
65///
66/// MGF1 produces a pseudo-random byte stream of length `len` by iterating
67/// `H(seed || counter_be_u32)` for counter = 0, 1, 2, ...
68///
69/// Generic over `H` so PSS can be instantiated with SHA-256, SHA-384,
70/// SHA-512, or any future `Hasher`.
71fn mgf1<H: Hasher>(seed: &[u8], len: usize) -> Vec<u8> {
72    let h_len = H::OUTPUT_LEN;
73    let mut out = Vec::with_capacity(len);
74    let mut counter: u32 = 0;
75    while out.len() < len {
76        let mut hasher = H::new();
77        hasher.update(seed);
78        hasher.update(&counter.to_be_bytes());
79        let block = hasher.finalize();
80        let take = (len - out.len()).min(h_len);
81        out.extend_from_slice(&block[..take]);
82        counter += 1;
83    }
84    out.truncate(len);
85    out
86}
87
88// ============================================================================
89// EMSA-PSS encode / verify (RFC 8017 §9.1)
90// ============================================================================
91
92/// EMSA-PSS-Encode (RFC 8017 §9.1.1).
93///
94/// Produces the encoded message `EM` of length `emLen = ceil(em_bits / 8)`
95/// from a precomputed message digest `m_hash` and a caller-supplied `salt`.
96///
97/// Returns `None` if:
98/// - `m_hash.len() != H::OUTPUT_LEN`
99/// - `emLen < hLen + sLen + 2` (the modulus is too short for this (H, sLen))
100fn emsa_pss_encode<H: Hasher>(m_hash: &[u8], em_bits: usize, salt: &[u8]) -> Option<Vec<u8>> {
101    let h_len = H::OUTPUT_LEN;
102    let s_len = salt.len();
103    let em_len = em_bits.div_ceil(8);
104
105    // Step 1-3: length checks
106    if m_hash.len() != h_len {
107        return None;
108    }
109    if em_len < h_len + s_len + 2 {
110        return None;
111    }
112
113    // Step 4-6: M' = (0x00)^8 || mHash || salt ; h = H(M')
114    let mut m_prime = Vec::with_capacity(8 + h_len + s_len);
115    m_prime.extend_from_slice(&[0u8; 8]);
116    m_prime.extend_from_slice(m_hash);
117    m_prime.extend_from_slice(salt);
118    let h = H::hash(&m_prime);
119
120    // Step 7-8: DB = PS || 0x01 || salt
121    //   PS has length db_len - s_len - 1, all zeros.
122    let db_len = em_len - h_len - 1;
123    let mut db = vec![0u8; db_len];
124    let ps_len = db_len - s_len - 1;
125    db[ps_len] = 0x01;
126    db[ps_len + 1..].copy_from_slice(salt);
127
128    // Step 9-10: maskedDB = DB XOR MGF(h, db_len)
129    let db_mask = mgf1::<H>(&h, db_len);
130    for i in 0..db_len {
131        db[i] ^= db_mask[i];
132    }
133
134    // Step 11: clear the top (8*em_len - em_bits) bits of maskedDB[0].
135    // This ensures EM, interpreted as an integer, is strictly less
136    // than 2^em_bits < n, so the RSA exponentiation is safe.
137    let clear_bits = 8 * em_len - em_bits;
138    if clear_bits > 0 {
139        db[0] &= 0xff_u8 >> clear_bits;
140    }
141
142    // Step 12: EM = maskedDB || h || 0xbc
143    let mut em = Vec::with_capacity(em_len);
144    em.extend_from_slice(&db);
145    em.extend_from_slice(&h);
146    em.push(0xbc);
147
148    Some(em)
149}
150
151/// EMSA-PSS-Verify (RFC 8017 §9.1.2).
152///
153/// Checks that `em` is a valid encoding of `m_hash` for the given
154/// `em_bits` and `s_len`. Returns `true` iff the encoding is consistent.
155fn emsa_pss_verify<H: Hasher>(m_hash: &[u8], em: &[u8], em_bits: usize, s_len: usize) -> bool {
156    let h_len = H::OUTPUT_LEN;
157    let em_len = em_bits.div_ceil(8);
158
159    // Step 1-3: length checks
160    if m_hash.len() != h_len {
161        return false;
162    }
163    if em.len() != em_len {
164        return false;
165    }
166    if em_len < h_len + s_len + 2 {
167        return false;
168    }
169
170    // Step 4: last byte must be 0xbc
171    if em[em_len - 1] != 0xbc {
172        return false;
173    }
174
175    // Step 5-6: split EM into maskedDB and H
176    let db_len = em_len - h_len - 1;
177    let masked_db = &em[..db_len];
178    let h = &em[db_len..db_len + h_len];
179
180    // Step 7: top (8*em_len - em_bits) bits of maskedDB[0] must be zero.
181    let clear_bits = 8 * em_len - em_bits;
182    if clear_bits > 0 && (masked_db[0] >> (8 - clear_bits)) != 0 {
183        return false;
184    }
185
186    // Step 8-9: DB = maskedDB XOR MGF(h, db_len), then clear the top
187    // (8*em_len - em_bits) bits of DB[0].
188    let db_mask = mgf1::<H>(h, db_len);
189    let mut db = vec![0u8; db_len];
190    for i in 0..db_len {
191        db[i] = masked_db[i] ^ db_mask[i];
192    }
193    if clear_bits > 0 {
194        db[0] &= 0xff_u8 >> clear_bits;
195    }
196
197    // Step 10: PS = (0x00)^(db_len - s_len - 1), then 0x01.
198    let ps_len = db_len - s_len - 1;
199    for byte in &db[..ps_len] {
200        if *byte != 0 {
201            return false;
202        }
203    }
204    if db[ps_len] != 0x01 {
205        return false;
206    }
207
208    // Step 11: salt = last s_len bytes of DB
209    let salt = &db[ps_len + 1..];
210
211    // Step 12-13: M' = (0x00)^8 || m_hash || salt ; h' = H(M')
212    let mut m_prime = Vec::with_capacity(8 + h_len + s_len);
213    m_prime.extend_from_slice(&[0u8; 8]);
214    m_prime.extend_from_slice(m_hash);
215    m_prime.extend_from_slice(salt);
216    let h_prime = H::hash(&m_prime);
217
218    // Step 14: h == h'  (constant-time compare, just good hygiene
219    //                    even though `h` is not secret here)
220    let mut diff = 0u8;
221    for (a, b) in h.iter().zip(h_prime.iter()) {
222        diff |= a ^ b;
223    }
224    diff == 0
225}
226
227// ============================================================================
228// Public API -- RSASSA-PSS sign / verify
229// ============================================================================
230
231/// RSASSA-PSS sign with a **caller-supplied salt** (RFC 8017 §8.1.1).
232///
233/// Primarily useful for tests (pinning against external vectors that
234/// report a specific salt) and for reproducible signatures in
235/// deterministic / audit contexts. Production callers should use
236/// [`pss_sign`] with a fresh random salt.
237///
238/// `m_hash` is the precomputed `H(message)` digest. `H` is the hash
239/// function used for both the message digest and the internal
240/// MGF1 / `H(M')` -- PSS requires the same hash throughout.
241pub fn pss_sign_with_salt<H: Hasher>(sk: &RsaSecretKey, m_hash: &[u8], salt: &[u8]) -> Option<Vec<u8>> {
242    let k = sk.modulus_byte_len();
243    let mod_bits = sk.n.bit_len();
244    let em_bits = mod_bits - 1;
245    let em = emsa_pss_encode::<H>(m_hash, em_bits, salt)?;
246
247    // RSASP1: sign by exponentiating EM with the secret exponent.
248    let m = BigInt::from_be_bytes(&em);
249    let s = rsa_decrypt_raw(sk, &m);
250    Some(s.to_be_bytes(k))
251}
252
253/// RSASSA-PSS sign of a precomputed digest (RFC 8017 §8.1.1).
254///
255/// Draws a fresh `s_len`-byte random salt from `rng` and calls
256/// [`pss_sign_with_salt`]. Each invocation produces a different
257/// signature even for the same `(sk, m_hash)`.
258///
259/// Recommended salt length: `s_len = H::OUTPUT_LEN` (the same length
260/// as the hash). Setting `s_len = 0` produces a deterministic
261/// signature ("no salt") -- the same `(sk, m_hash, H)` will always
262/// yield the same bytes; this is allowed by the spec but gives up
263/// the randomized-signature security property.
264pub fn pss_sign<H: Hasher>(
265    sk: &RsaSecretKey,
266    m_hash: &[u8],
267    s_len: usize,
268    rng: &mut dyn FnMut(&mut [u8]),
269) -> Option<Vec<u8>> {
270    let mut salt = vec![0u8; s_len];
271    if s_len > 0 {
272        rng(&mut salt);
273    }
274    pss_sign_with_salt::<H>(sk, m_hash, &salt)
275}
276
277/// Convenience: hash `msg` with `H`, then sign with a random salt.
278pub fn pss_sign_msg<H: Hasher>(
279    sk: &RsaSecretKey,
280    msg: &[u8],
281    s_len: usize,
282    rng: &mut dyn FnMut(&mut [u8]),
283) -> Option<Vec<u8>> {
284    let digest = H::hash(msg);
285    pss_sign::<H>(sk, &digest, s_len, rng)
286}
287
288/// RSASSA-PSS verify of a precomputed digest (RFC 8017 §8.1.2).
289///
290/// Returns `true` iff `sig` is a valid PSS signature of `m_hash` under
291/// `pk` with the given `s_len`. The caller must supply the same hash
292/// `H` and the same salt length the signer used (both are protocol
293/// parameters and are typically negotiated out-of-band or fixed by
294/// the signature algorithm OID).
295pub fn pss_verify<H: Hasher>(pk: &RsaPublicKey, m_hash: &[u8], s_len: usize, sig: &[u8]) -> bool {
296    let k = pk.modulus_byte_len();
297    if sig.len() != k {
298        return false;
299    }
300    let mod_bits = pk.n.bit_len();
301    if mod_bits == 0 {
302        return false;
303    }
304    let em_bits = mod_bits - 1;
305    let em_len = em_bits.div_ceil(8);
306
307    // RSAVP1: verify by exponentiating sig with the public exponent.
308    let s = BigInt::from_be_bytes(sig);
309    // RSAVP1 step 1 (RFC 8017 §5.2.2): reject s not in [0, n-1]. s is
310    // public attacker input, so this is a public-data branch.
311    if !representative_in_range(&s, &pk.n) {
312        return false;
313    }
314    let m = rsa_encrypt_raw(pk, &s);
315    let em = m.to_be_bytes(em_len);
316    if em.len() != em_len {
317        // m was too large to fit in em_len bytes. For well-formed
318        // signatures this cannot happen because m < n and
319        // em_bits = modBits - 1 guarantees m fits in em_len.
320        return false;
321    }
322
323    emsa_pss_verify::<H>(m_hash, &em, em_bits, s_len)
324}
325
326/// Convenience: hash `msg` with `H`, then verify.
327pub fn pss_verify_msg<H: Hasher>(pk: &RsaPublicKey, msg: &[u8], s_len: usize, sig: &[u8]) -> bool {
328    let digest = H::hash(msg);
329    pss_verify::<H>(pk, &digest, s_len, sig)
330}
331
332// ============================================================================
333// Tests
334// ============================================================================
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    use crate::hash::sha256::Sha256;
340    use crate::hash::sha384::Sha384;
341    use crate::hash::sha512::Sha512;
342
343    /// Deterministic PRNG for reproducible tests (NOT cryptographic).
344    fn test_rng() -> impl FnMut(&mut [u8]) {
345        let mut state: u64 = 0xdeadbeefcafebabe;
346        move |buf: &mut [u8]| {
347            for b in buf.iter_mut() {
348                state = state
349                    .wrapping_mul(6364136223846793005)
350                    .wrapping_add(1442695040888963407);
351                *b = (state >> 33) as u8;
352            }
353        }
354    }
355
356    // ----------------------------------------------------------------------
357    // EMSA-PSS self-consistency (no RSA involved)
358    //
359    // Encode with a known salt, then verify the resulting EM. This
360    // exercises the padding layer independently of the RSA primitive
361    // so any failure points straight at the padding code.
362    // ----------------------------------------------------------------------
363
364    #[test]
365    fn test_emsa_pss_encode_verify_roundtrip_sha256() {
366        let m_hash = Sha256::hash(b"hello PSS");
367        let salt = [0x5a; 32];
368        let em_bits = 2047; // emulates a 2048-bit modulus
369        let em = emsa_pss_encode::<Sha256>(&m_hash, em_bits, &salt).expect("encode");
370        assert_eq!(em.len(), em_bits.div_ceil(8));
371        assert!(emsa_pss_verify::<Sha256>(&m_hash, &em, em_bits, salt.len()));
372    }
373
374    #[test]
375    fn test_emsa_pss_encode_verify_roundtrip_sha384() {
376        let m_hash = Sha384::hash(b"hello PSS sha384");
377        let salt = [0x17; 48];
378        let em_bits = 3071; // 3072-bit modulus
379        let em = emsa_pss_encode::<Sha384>(&m_hash, em_bits, &salt).unwrap();
380        assert_eq!(em.len(), 384);
381        assert!(emsa_pss_verify::<Sha384>(&m_hash, &em, em_bits, salt.len()));
382    }
383
384    #[test]
385    fn test_emsa_pss_encode_verify_roundtrip_sha512() {
386        let m_hash = Sha512::hash(b"hello PSS sha512");
387        let salt = [0x00; 64];
388        let em_bits = 4095; // 4096-bit modulus
389        let em = emsa_pss_encode::<Sha512>(&m_hash, em_bits, &salt).unwrap();
390        assert_eq!(em.len(), 512);
391        assert!(emsa_pss_verify::<Sha512>(&m_hash, &em, em_bits, salt.len()));
392    }
393
394    /// Salt length must match between encode and verify. Changing it
395    /// on verify must reject the encoding.
396    #[test]
397    fn test_emsa_pss_verify_wrong_salt_length_rejects() {
398        let m_hash = Sha256::hash(b"msg");
399        let salt = [0xAB; 32];
400        let em = emsa_pss_encode::<Sha256>(&m_hash, 2047, &salt).unwrap();
401        // Ask verify to expect a DIFFERENT salt length.
402        assert!(!emsa_pss_verify::<Sha256>(&m_hash, &em, 2047, 16));
403    }
404
405    /// Tampering with the encoded message in the masked-DB region
406    /// must be detected.
407    #[test]
408    fn test_emsa_pss_verify_tampered_rejects() {
409        let m_hash = Sha256::hash(b"msg");
410        let salt = [0x12; 32];
411        let mut em = emsa_pss_encode::<Sha256>(&m_hash, 2047, &salt).unwrap();
412        em[0] ^= 0x01;
413        assert!(!emsa_pss_verify::<Sha256>(&m_hash, &em, 2047, salt.len()));
414    }
415
416    /// The trailing 0xbc byte is mandatory.
417    #[test]
418    fn test_emsa_pss_verify_missing_bc_byte_rejects() {
419        let m_hash = Sha256::hash(b"msg");
420        let salt = [0x12; 32];
421        let mut em = emsa_pss_encode::<Sha256>(&m_hash, 2047, &salt).unwrap();
422        let last = em.len() - 1;
423        em[last] = 0xbd;
424        assert!(!emsa_pss_verify::<Sha256>(&m_hash, &em, 2047, salt.len()));
425    }
426
427    // ----------------------------------------------------------------------
428    // Full RSA + PSS roundtrips
429    // ----------------------------------------------------------------------
430
431    /// Happy path: sign and verify a message with a random salt.
432    #[test]
433    fn test_pss_sign_verify_roundtrip_sha256() {
434        let mut rng = test_rng();
435        let (pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
436        let msg = b"PSS end-to-end with SHA-256";
437        let sig = pss_sign_msg::<Sha256>(&sk, msg, 32, &mut rng).expect("sign");
438        assert_eq!(sig.len(), pk.modulus_byte_len());
439        assert!(pss_verify_msg::<Sha256>(&pk, msg, 32, &sig));
440    }
441
442    /// Deterministic PSS (s_len = 0) must produce byte-identical
443    /// signatures across calls. This is a much sharper self-test of
444    /// the whole pipeline than the randomized roundtrip because it
445    /// proves the sign path is pure-functional once the salt is fixed.
446    #[test]
447    fn test_pss_deterministic_signatures_agree() {
448        let mut rng = test_rng();
449        let (pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
450        let m_hash = Sha256::hash(b"determinism");
451        let sig1 = pss_sign_with_salt::<Sha256>(&sk, &m_hash, &[]).expect("sign 1");
452        let sig2 = pss_sign_with_salt::<Sha256>(&sk, &m_hash, &[]).expect("sign 2");
453        assert_eq!(sig1, sig2);
454        assert!(pss_verify::<Sha256>(&pk, &m_hash, 0, &sig1));
455    }
456
457    /// Two random-salt signatures of the same message must differ
458    /// (the salt must actually be consumed).
459    #[test]
460    fn test_pss_random_signatures_differ() {
461        let mut rng = test_rng();
462        let (_pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
463        let msg = b"randomisation";
464        let sig1 = pss_sign_msg::<Sha256>(&sk, msg, 32, &mut rng).unwrap();
465        let sig2 = pss_sign_msg::<Sha256>(&sk, msg, 32, &mut rng).unwrap();
466        assert_ne!(sig1, sig2);
467    }
468
469    /// Verify must reject a signature on a different message.
470    #[test]
471    fn test_pss_verify_rejects_wrong_message() {
472        let mut rng = test_rng();
473        let (pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
474        let sig = pss_sign_msg::<Sha256>(&sk, b"original", 32, &mut rng).unwrap();
475        assert!(!pss_verify_msg::<Sha256>(&pk, b"tampered", 32, &sig));
476    }
477
478    /// Verify must reject a tampered signature byte.
479    #[test]
480    fn test_pss_verify_rejects_tampered_signature() {
481        let mut rng = test_rng();
482        let (pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
483        let msg = b"msg";
484        let mut sig = pss_sign_msg::<Sha256>(&sk, msg, 32, &mut rng).unwrap();
485        sig[0] ^= 0x01;
486        assert!(!pss_verify_msg::<Sha256>(&pk, msg, 32, &sig));
487    }
488
489    /// Verify must reject a signature under a different public key.
490    #[test]
491    fn test_pss_verify_rejects_wrong_key() {
492        let mut rng = test_rng();
493        let (_pk_a, sk_a) = super::super::rsa::rsa_keygen(1024, &mut rng);
494        let (pk_b, _sk_b) = super::super::rsa::rsa_keygen(1024, &mut rng);
495        let sig = pss_sign_msg::<Sha256>(&sk_a, b"msg", 32, &mut rng).unwrap();
496        assert!(!pss_verify_msg::<Sha256>(&pk_b, b"msg", 32, &sig));
497    }
498
499    /// Verify must reject when the declared salt length doesn't match
500    /// what the signer used.
501    #[test]
502    fn test_pss_verify_rejects_wrong_salt_length() {
503        let mut rng = test_rng();
504        let (pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
505        let sig = pss_sign_msg::<Sha256>(&sk, b"msg", 32, &mut rng).unwrap();
506        assert!(!pss_verify_msg::<Sha256>(&pk, b"msg", 16, &sig));
507    }
508
509    /// Signing with a modulus too small to fit `H(M) || salt || 2`
510    /// bytes must return None.
511    #[test]
512    fn test_pss_sign_rejects_too_small_modulus_for_salt() {
513        let mut rng = test_rng();
514        // 512-bit modulus = 64 bytes. SHA-256 hLen=32, sLen=32
515        // requires emLen >= 32+32+2 = 66 bytes. 64 < 66 -> reject.
516        let (_pk, sk) = super::super::rsa::rsa_keygen(512, &mut rng);
517        let m_hash = Sha256::hash(b"msg");
518        let result = pss_sign::<Sha256>(&sk, &m_hash, 32, &mut rng);
519        assert!(result.is_none());
520    }
521
522    /// But the same key with a shorter salt works.
523    #[test]
524    fn test_pss_sign_accepts_short_salt_on_small_modulus() {
525        let mut rng = test_rng();
526        let (pk, sk) = super::super::rsa::rsa_keygen(512, &mut rng);
527        // emLen = 64, hLen = 32 -> max sLen = 64 - 32 - 2 = 30
528        let msg = b"small-modulus PSS";
529        let sig = pss_sign_msg::<Sha256>(&sk, msg, 16, &mut rng).expect("sign");
530        assert!(pss_verify_msg::<Sha256>(&pk, msg, 16, &sig));
531    }
532
533    /// Cross-hash: signing and verifying with different hashes must
534    /// fail even if both are otherwise valid.
535    #[test]
536    fn test_pss_hash_mismatch_rejected() {
537        let mut rng = test_rng();
538        let (pk, sk) = super::super::rsa::rsa_keygen(2048, &mut rng);
539        let msg = b"hash flexibility matters";
540        let sig = pss_sign_msg::<Sha256>(&sk, msg, 32, &mut rng).unwrap();
541        // Same key, same message, but verifying as if it were SHA-384.
542        assert!(!pss_verify_msg::<Sha384>(&pk, msg, 32, &sig));
543    }
544
545    /// PSS with SHA-384 on a 3072-bit key (NIST recommended pairing).
546    #[test]
547    fn test_pss_sha384_3072_roundtrip() {
548        let mut rng = test_rng();
549        let (pk, sk) = super::super::rsa::rsa_keygen(3072, &mut rng);
550        let msg = b"PSS SHA-384 / RSA-3072";
551        let sig = pss_sign_msg::<Sha384>(&sk, msg, 48, &mut rng).unwrap();
552        assert!(pss_verify_msg::<Sha384>(&pk, msg, 48, &sig));
553    }
554}