# verify/pkcs1v15 ## Purpose RSASSA-PKCS1-v1\_5 signature verification per RFC 8017 §8.2.2. Recovers the encoded message from `signature` via `s^e mod n`, reconstructs the expected EMSA-PKCS1-v1\_5 encoding of `message` with the same hash, and compares the two byte strings constant-time. ## Features - RFC 8017 §8.2.2 conformant - Constant-time encoded-message compare via `constantTimeEqual` from `@suaveplan/crypto-utils` - Returns `false` on rejection (no exception); throws only on structural malformation (signature length mismatch) - Accepts either an `RsaPublicKey` or an `RsaPrivateKey` (both carry `n` and `e`) - Modulus-length policy gates run on every call - Records `crypto_rsa.verify_duration_ms` and `crypto_rsa.outcome_total{operation,outcome}` on every call ## Usage: Basic Example ```ts import { rsaVerifyPkcs1v15 } from "@suaveplan/crypto-rsa/verify/pkcs1v15"; import { rsaSignPkcs1v15 } from "@suaveplan/crypto-rsa/sign/pkcs1v15"; const message = new TextEncoder().encode("hello, signed world"); const signature = rsaSignPkcs1v15(privateKey, message, { hash: "SHA-256" }); const ok = rsaVerifyPkcs1v15(publicKey, message, signature, { hash: "SHA-256", }); console.log(ok); // true ``` ## Usage: Advanced Example A bit-flipped signature, a tampered message, or a hash mismatch all return `false` without revealing which one tripped the gate. ```ts import { rsaVerifyPkcs1v15 } from "@suaveplan/crypto-rsa/verify/pkcs1v15"; const tampered = new Uint8Array(signature); tampered[0] = tampered[0] ^ 0x01; console.log( rsaVerifyPkcs1v15(publicKey, message, tampered, { hash: "SHA-256" }) ); // false console.log( rsaVerifyPkcs1v15(publicKey, message, signature, { hash: "SHA-512" }) ); // false (hash mismatch) ``` ## API Reference ### `rsaVerifyPkcs1v15(key, message, signature, options): boolean` - `key: RsaPublicKey | RsaPrivateKey` - `message: Uint8Array` — the bytes that were signed - `signature: Uint8Array` — must be exactly `byteLength(n)` long - `options.hash: RsaHashAlgorithm` — must match the hash used at sign time - `options.allowLegacy?: boolean` - `options.allowOversize?: boolean` - **returns** `true` when the signature is valid, `false` on any 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 - The signature integer `s = os2ip(signature)` is checked against `s < n` before the modular exponentiation; signatures with `s >= n` are rejected with `false` rather than allowed to wrap around. - Comparison uses `constantTimeEqual(recovered, expected)` — never `===` and never byte-by-byte short-circuit. This is the verify-side defence against the timing-oracle attacks described in Bleichenbacher 2006 (the SHA-1 padding-oracle exploit) and follow-ups. - The encoded message is reconstructed every call rather than carried alongside the signature. This is the RFC 8017 §8.2.2 design — the verifier MUST regenerate `EM` from the public inputs to detect signature substitution. - The `verify_duration_ms` histogram is recorded on every code path (success, rejection, structural error) so service-side dashboards see the full latency distribution including the failure modes.