Repository: denoland/deno
Pull request: fix(ext/node): use max salt length as default for RSA-PSS signing

## Summary
- When no `saltLength` is specified for RSA-PSS signing, Node.js defaults to `RSA_PSS_SALTLEN_MAX_SIGN` (max possible salt = `key_bytes - hash_len - 2`)
- Deno was defaulting to the digest length instead (via `rsa::pss::Pss::new::<D>()`), causing verification failures when the verifier expected max salt length
- Pass key size to `new_pss_scheme` for the sign path so it can compute max salt as the default; verify path keeps digest-length default

## Test plan
- [x] `cargo test --test node_compat -- test-crypto-rsa-pss-default-salt-length` passes
- Enabled `test-crypto-rsa-pss-default-salt-length.js` in node_compat config

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changed files:
filename: ext/node_crypto/sign.rs
patch: @@ -8,6 +8,7 @@ use elliptic_curve::generic_array::ArrayLength;
 use rand::rngs::OsRng;
 use rsa::signature::hazmat::PrehashSigner as _;
 use rsa::signature::hazmat::PrehashVerifier as _;
+use rsa::traits::PublicKeyParts as _;
 use rsa::traits::SignatureScheme as _;
 use spki::der::Decode;
 
@@ -91,16 +92,29 @@ pub enum KeyObjectHandlePrehashedSignAndVerifyError {
 
 /// Constructs a PSS scheme for the given digest type and optional salt length.
 /// Used by both sign and verify operations on RSA keys with PSS padding.
+///
+/// When `key_size_bits` is provided and `pss_salt_length` is `None`,
+/// the default salt length is max (key_bytes - hash_len - 2), matching
+/// Node.js's documented default of `RSA_PSS_SALTLEN_MAX_SIGN`.
+/// When `key_size_bits` is `None` (verify path), the default salt length
+/// is the digest length.
 fn new_pss_scheme(
   digest_type: &str,
   pss_salt_length: Option<u32>,
+  key_size_bits: Option<usize>,
 ) -> Result<rsa::pss::Pss, KeyObjectHandlePrehashedSignAndVerifyError> {
   let pss = match_fixed_digest_with_oid!(
     digest_type,
     fn <D>(algorithm: Option<RsaPssHashAlgorithm>) {
       let _: Option<RsaPssHashAlgorithm> = algorithm;
       if let Some(salt_length) = pss_salt_length {
         rsa::pss::Pss::new_with_salt::<D>(salt_length as usize)
+      } else if let Some(key_bits) = key_size_bits {
+        // Default to max salt length for signing (RSA_PSS_SALTLEN_MAX_SIGN)
+        let key_bytes = key_bits / 8;
+        let hash_len = <D as digest::Digest>::output_size();
+        let max_salt = key_bytes.saturating_sub(hash_len + 2);
+        rsa::pss::Pss::new_with_salt::<D>(max_salt)
       } else {
         rsa::pss::Pss::new::<D>()
       }
@@ -128,7 +142,11 @@ impl KeyObjectHandle {
     match private_key {
       AsymmetricPrivateKey::Rsa(key) => {
         if padding == Some(RSA_PKCS1_PSS_PADDING) {
-          let pss = new_pss_scheme(digest_type, pss_salt_length)?;
+          let pss = new_pss_scheme(
+            digest_type,
+            pss_salt_length,
+            Some(key.n().bits()),
+          )?;
           let signature = pss
             .sign(Some(&mut OsRng), key, digest)
             .map_err(|_| KeyObjectHandlePrehashedSignAndVerifyError::FailedToSignDigestWithRsaPss)?;
@@ -276,7 +294,7 @@ impl KeyObjectHandle {
     match &*public_key {
       AsymmetricPublicKey::Rsa(key) => {
         if padding == Some(RSA_PKCS1_PSS_PADDING) {
-          let pss = new_pss_scheme(digest_type, pss_salt_length)?;
+          let pss = new_pss_scheme(digest_type, pss_salt_length, None)?;
           return Ok(pss.verify(key, digest, signature).is_ok());
         }
 

filename: tests/node_compat/config.jsonc
patch: @@ -304,6 +304,7 @@
     "parallel/test-crypto-psychic-signatures.js": {},
     "parallel/test-crypto-randomfillsync-regression.js": {},
     "parallel/test-crypto-randomuuid.js": {},
+    "parallel/test-crypto-rsa-pss-default-salt-length.js": {},
     "parallel/test-crypto-sec-level.js": {},
     "parallel/test-crypto-secret-keygen.js": {},
     "parallel/test-crypto-secure-heap.js": {