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,88 @@
# `decrypt/pkcs1v15`
## Purpose
RSAES-PKCS1-v1_5 decryption per RFC 8017 §7.2.2. Counterpart to
`encrypt/pkcs1v15`. Provides legacy interop for protocols that
predate OAEP (TLS 1.0–1.2 RSA key transport, JWE `RSA1_5` legacy
mode). Modern callers MUST prefer `decrypt/oaep`.
The defining concern of this module is the **Bleichenbacher
defence**: every padding-format failure surfaces the SAME
`CRYPTO_RSA.DECRYPT_FAILED` error so a remote attacker observing
the typed-error code (or the OTel error counter) cannot mount a
million-message chosen-ciphertext attack.
## Features
- Combined error path — wrong leading byte, wrong block-type
marker, missing `0x00` separator, `PS` shorter than 8 bytes,
ciphertext length ≠ `k`, post-decrypt integer ≥ `n`: every
failure throws the single `DECRYPT_FAILED` code.
- RSA blinding wired in by default — `{ blinding: false }` opt-out
emits the `crypto_rsa.blinding_disabled_total{operation="decrypt"}`
counter so production dashboards can flag any caller that
disables the timing-side-channel defence.
- CRT-accelerated private operation (`rsaPrivateOperationCrt`).
- Modulus-length policy gate runs first.
- `crypto_rsa.decrypt_duration_ms{scheme="pkcs1v15"}` histogram.
## Usage: Basic Example
```ts
import { rsaDecryptPkcs1v15 } from "@suaveplan/crypto-rsa/decrypt/pkcs1v15";
import type { RsaPrivateKey } from "@suaveplan/crypto-rsa";
declare const privateKey: RsaPrivateKey;
declare const ciphertext: Uint8Array;
const plaintext = rsaDecryptPkcs1v15(privateKey, ciphertext, {
allowLegacy: false,
});
```
## Usage: Advanced Example
Disable blinding (NOT recommended outside benchmarks) — the
counter increment surfaces the choice in production telemetry:
```ts
import { rsaDecryptPkcs1v15 } from "@suaveplan/crypto-rsa/decrypt/pkcs1v15";
const plaintext = rsaDecryptPkcs1v15(privateKey, ciphertext, {
blinding: false, // emits crypto_rsa.blinding_disabled_total{operation="decrypt"}
allowLegacy: true,
});
```
## API Reference
### `rsaDecryptPkcs1v15(key, ciphertext, options?): Uint8Array`
- `key: RsaPrivateKey` — modulus must satisfy the policy gate.
- `ciphertext: Uint8Array` — must be exactly `k` bytes; any
other length throws `DECRYPT_FAILED`.
- `options.rng?: Rng` — randomness source for blinding.
- `options.blinding?: boolean` — defaults to `true`. Set to
`false` only for benchmark comparisons.
- `options.allowLegacy?: boolean` — accept 1024-bit moduli.
- Returns the recovered plaintext (`em.length - separator - 1`
bytes).
- Throws `CRYPTO_RSA.DECRYPT_FAILED` on every padding failure.
## Implementation Notes
- The combined error path is enforced by routing every failure
through the private `decryptFailed()` helper — a single
callsite for `new CryptoRsaError(DECRYPT_FAILED, ...)` makes
it impossible for a future edit to accidentally introduce a
differentiated error.
- True constant-time decryption is not achievable in JavaScript
without WebCrypto (V8/JSC bigint ops are not constant-time).
We mitigate with blinding and with eager-throw bucketing all
padding failures under one error class. For FIPS-grade
side-channel resistance, route through `crypto-browser`
(WebCrypto) or `crypto-sodium`.
- The `c >= key.n` check is RFC 8017 §5.1.2's "ciphertext
representative out of range" condition — also bucketed under
`DECRYPT_FAILED` so callers cannot distinguish "padding bad"
from "ciphertext outside `Z*_n`".