first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
# 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).