Skip to main content

arcana/rsa/
pss.rs

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