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.