# verify/pss ## Purpose RSASSA-PSS signature verification per RFC 8017 §8.1.2. Recovers the encoded message via `s^e mod n`, then runs the EMSA-PSS verifier against the digest of the message under the given `(hash, mgfHash, saltLength)` triple. ## Features - RFC 8017 §8.1.2 conformant - Uses `emsaPssVerify` for the structural check (which uses `constantTimeEqual` for the salt+hash compare) - Returns `false` on rejection; throws only on signature length mismatch - `saltLength` defaults to `hashOutputLength(hash)` - `mgfHash` defaults to `hash` - Modulus-length policy gates run on every call - Records `crypto_rsa.verify_duration_ms{scheme="pss",hash,engine}` and outcome counter on every call ## Usage: Basic Example ```ts import { rsaVerifyPss } from "@suaveplan/crypto-rsa/verify/pss"; import { rsaSignPss } from "@suaveplan/crypto-rsa/sign/pss"; const message = new TextEncoder().encode("hello"); const sig = rsaSignPss(privateKey, message, { hash: "SHA-256" }); console.log(rsaVerifyPss(publicKey, message, sig, { hash: "SHA-256" })); // true ``` ## Usage: Advanced Example ```ts import { rsaVerifyPss } from "@suaveplan/crypto-rsa/verify/pss"; import { rsaSignPss } from "@suaveplan/crypto-rsa/sign/pss"; // Custom saltLength: must match the value used by the signer. const sig = rsaSignPss(privateKey, message, { hash: "SHA-256", saltLength: 16 }); console.log(rsaVerifyPss(publicKey, message, sig, { hash: "SHA-256", saltLength: 16 })); // true console.log(rsaVerifyPss(publicKey, message, sig, { hash: "SHA-256", saltLength: 32 })); // false // mgfHash distinct from hash. const xSig = rsaSignPss(privateKey, message, { hash: "SHA-256", mgfHash: "SHA-1", }); console.log( rsaVerifyPss(publicKey, message, xSig, { hash: "SHA-256", mgfHash: "SHA-1", }) ); // true ``` ## API Reference ### `rsaVerifyPss(key, message, signature, options): boolean` - `key: RsaPublicKey | RsaPrivateKey` - `message: Uint8Array` - `signature: Uint8Array` — must be exactly `byteLength(n)` long - `options.hash: RsaHashAlgorithm` - `options.saltLength?: number` — defaults to `hashOutputLength(hash)` - `options.mgfHash?: RsaHashAlgorithm` — defaults to `options.hash` - `options.allowLegacy?: boolean` - `options.allowOversize?: boolean` - **returns** `true` on valid, `false` on verify-level rejection - **throws** `CryptoRsaError(CRYPTO_RSA.INVALID_SIGNATURE)` on signature length mismatch - **throws** `CryptoRsaError(CRYPTO_RSA.MODULUS_TOO_SHORT)` / `MODULUS_TOO_LARGE` / `UNSUPPORTED_HASH` ## Implementation Notes - `s = os2ip(signature)` is checked against `s < n` before the modular exponentiation; signatures with `s >= n` are rejected with `false`. - The encoded message produced by `i2osp(s^e mod n, emLen)` has length `emLen = ceil((modulusBits - 1) / 8)`. This is one byte shorter than the signature itself when `modulusBits` is a multiple of 8 — the high-order zero pad is stripped here and the leftmost-bit clearance rule in `emsaPssVerify` enforces that the original encoding had the expected zero in that position. - The constant-time guarantee for the final salt+hash compare is delivered by `emsaPssVerify`'s use of `constantTimeEqual`. The earlier structural checks (length mismatch, `s >= n`) are guarded by fast-fail paths that do NOT leak the secret-derived signal — they test only public inputs. - The `verify_duration_ms` histogram is recorded on every code path (success, rejection, structural error).