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}