Initial Commit
This commit is contained in:
59
packages/security-stack/security/jws/src/algorithms/none.md
Normal file
59
packages/security-stack/security/jws/src/algorithms/none.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# `none` algorithm
|
||||
|
||||
## Purpose
|
||||
|
||||
RFC 7518 §3.6 `"none"` algorithm. The `none` value declares "no integrity protection," which is virtually never what a verifier wants but is occasionally needed for non-security-critical contexts (signed-by-policy local development, deliberately-unsigned debugging payloads). This implementation exists only so the registry is complete; the verify pipeline rejects `none` by default and requires BOTH `algorithms: ["none"]` AND `acceptNone: true` to opt in, neutralising the classic "alg: none" downgrade attack.
|
||||
|
||||
## Features
|
||||
|
||||
- `verify` returns `true` if-and-only-if the signature segment is empty (zero bytes after base64url-decode).
|
||||
- `sign` always emits empty signature bytes.
|
||||
- Default rejection: pipeline throws `JWS.ALG_NONE_DISABLED` unless both flags are set.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { compactSign, compactVerify } from "@suaveplan/jws";
|
||||
|
||||
const token = await compactSign("hello", undefined, "none");
|
||||
// token has the form "<header>.<payload>." — empty signature segment.
|
||||
|
||||
await compactVerify(token, undefined, {
|
||||
algorithms: ["none"],
|
||||
acceptNone: true,
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { compactVerify, JwsError } from "@suaveplan/jws";
|
||||
|
||||
const noneToken = "eyJhbGciOiJub25lIn0.eyJzdWIiOjF9.";
|
||||
|
||||
// Default: rejected.
|
||||
try {
|
||||
await compactVerify(noneToken, undefined, { algorithms: ["HS256"] });
|
||||
} catch (err) {
|
||||
if (err instanceof JwsError && err.code === "JWS.ALG_NONE_DISABLED") {
|
||||
// CVE-class defence in depth.
|
||||
}
|
||||
}
|
||||
|
||||
// Allowlisted but no acceptNone — also rejected.
|
||||
try {
|
||||
await compactVerify(noneToken, undefined, { algorithms: ["none"] });
|
||||
} catch (err) {
|
||||
if (err instanceof JwsError && err.code === "JWS.ALG_NONE_DISABLED") {
|
||||
// Two flags required.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `none: AlgorithmImpl<undefined>` — verify `(input, sig)` returns `Promise<boolean>` (`true` iff `sig.length === 0`); sign `(input, _)` returns `Promise<Uint8Array>` of length 0.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The two-flag opt-in design is a deliberate defence against the historic JWT-CVE class where a caller passes `algorithms: ["HS256"]` AND a public key, then an attacker mints a token with `alg: "none"` and the library accepts it because the allowlist is parsed permissively. Here, even a deliberate `algorithms: ["none"]` requires the second flag `acceptNone: true` — the second flag is intentionally separate so a single typo or merged configuration cannot accidentally accept unsigned tokens. The implementation also requires the signature segment to be empty, not "not present" — a token like `"<hdr>.<payload>.deadbeef"` with `alg: "none"` is rejected because the signature bytes are non-zero.
|
||||
Reference in New Issue
Block a user