Files
skills/docs/security-stack/security/jws/src/algorithms/rsa-pss.md
2026-05-29 08:20:56 +01:00

2.8 KiB

RSASSA-PSS algorithms (PS256 / PS384 / PS512)

Purpose

RFC 7518 §3.5 RSASSA-PSS signing and verification. The signing input is hashed, EMSA-PSS-encoded with a random salt, and signed via @suaveplan/crypto-rsa.rsaSignPss. The salt length is fixed at the hash output length per RFC 7518 §3.5 ("Salt Length shall be equal to the size of the hash output") — externally-produced signatures with non-matching salt lengths simply fail verify rather than being silently accepted, which catches producers that defaulted to a different PSS profile.

Features

  • PS256 → RSASSA-PSS + SHA-256 + 32-byte salt.
  • PS384 → RSASSA-PSS + SHA-384 + 48-byte salt.
  • PS512 → RSASSA-PSS + SHA-512 + 64-byte salt.
  • MGF1 hash matches the message hash (RFC 7518 §3.5 default).
  • Delegates to @suaveplan/crypto-rsa.rsaSignPss / rsaVerifyPss.

Usage: Basic Example

import { generateRsaKeyPair } from "@suaveplan/crypto-rsa";
import { compactSign, compactVerify } from "@suaveplan/jws";

const { publicKey, privateKey } = await generateRsaKeyPair({ modulusLength: 2048 });
const token = await compactSign("hello", privateKey, "PS256");
await compactVerify(token, publicKey, { algorithms: ["PS256"] });

Usage: Advanced Example

import { ps256 } from "@suaveplan/jws";

// Signature produced with a 16-byte salt instead of 32:
const externalSig = produceFromBadProducer(message, priv, /*saltLen*/ 16);
const ok = await ps256.verify(new TextEncoder().encode("hdr.payload"), externalSig, pub);
// ok === false — salt length mismatch caught by EMSA-PSS-Verify.

// Algorithm-confusion defence: passing an HMAC byte-key throws.
import { JwsError } from "@suaveplan/jws";
try {
    await ps256.verify(input, sig, new Uint8Array(32));
} catch (err) {
    if (err instanceof JwsError && err.code === "JWS.KEY_ALG_MISMATCH") {
        // PSS verify expects an RsaPublicKey, not a raw byte buffer.
    }
}

API Reference

  • ps256: AlgorithmImpl<RsaPublicKey | RsaPrivateKey>
  • ps384: AlgorithmImpl<RsaPublicKey | RsaPrivateKey>
  • ps512: AlgorithmImpl<RsaPublicKey | RsaPrivateKey>

verify returns Promise<boolean>; sign returns Promise<Uint8Array> whose length matches the modulus (256 bytes for 2048-bit keys).

Implementation Notes

PSS salt is drawn by @suaveplan/crypto-rsa.rsaSignPss via either the caller's optional Rng injection or the default RNG (crypto.getRandomValues). This means PSS signatures are probabilistic — the same payload signed twice produces two different tokens; tests that need determinism inject a deterministic Rng. Verifying with a fixed salt length and rejecting mismatched-salt signatures is a deliberate departure from libraries that auto-detect salt length from the signature length: that auto-detection is the source of many JWT-PSS interop bugs because the auto-detect heuristic disagrees on edge-case modulus sizes.