first commit
This commit is contained in:
57
docs/security-stack/security/jws/src/algorithms/rsa-pss.md
Normal file
57
docs/security-stack/security/jws/src/algorithms/rsa-pss.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user