first commit
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# Compact serialization
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
RFC 7515 §3.1 + §7.1 compact-form JWS — the three-segment `<protected>.<payload>.<signature>` string format that is the default representation for JWS-as-JWT and the only form most downstream consumers will ever encounter. This module owns `compactDecode` (parse), `compactVerify` (RFC §5.2 verify), and `compactSign` (RFC §5.1 sign).
|
||||
|
||||
## Features
|
||||
|
||||
- Strict structural parser: exactly two `.` separators, URL-alphabet bytes only.
|
||||
- Empty middle segment accepted (RFC 7515 §A.5 detached form).
|
||||
- Mandatory `algorithms` allowlist at verify time — algorithm-confusion-CVE defence.
|
||||
- `compactSign` returns the joined string; consumers can split on `.` to inspect segments.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { compactDecode, compactSign, compactVerify } from "@suaveplan/jws";
|
||||
|
||||
const token = await compactSign("hello", secret, "HS256");
|
||||
const decoded = compactDecode(token);
|
||||
// decoded.protected / decoded.payload / decoded.signature
|
||||
|
||||
const result = await compactVerify(token, secret, { algorithms: ["HS256"] });
|
||||
// result.verified === true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { compactVerify, type KeyResolver, JwsError } from "@suaveplan/jws";
|
||||
|
||||
const keyResolver: KeyResolver = async (kid) => keyStore.get(kid);
|
||||
|
||||
const verify = async (token: string) => {
|
||||
try {
|
||||
return await compactVerify(token, undefined, {
|
||||
algorithms: ["RS256", "ES256", "EdDSA"],
|
||||
allowEmbeddedJwk: false,
|
||||
allowX5c: false,
|
||||
keyResolver,
|
||||
acceptableCriticalHeaders: ["exp", "nbf"],
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof JwsError) {
|
||||
if (err.code === "JWS.ALG_NOT_ALLOWED") return null;
|
||||
if (err.code === "JWS.SIG_INVALID") return null;
|
||||
if (err.code === "JWS.KEY_NOT_FOUND") return null;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `compactDecode(token: string): CompactJws` — throws `JWS.MALFORMED`.
|
||||
- `compactVerify(token: string, key: unknown, opts: VerifyOptions): Promise<VerifyResult>`
|
||||
- `compactSign(payload: string | Uint8Array, key: unknown, alg: string, opts?: SignOptions): Promise<string>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Verification enforces the algorithm allowlist BEFORE any cryptographic work — `opts.algorithms.includes(header.alg)` is checked immediately after parsing the header, so a token presenting `alg: "HS256"` against an RSA public key cannot even reach the HMAC impl, neutralising the entire alg-confusion class. The pipeline also enforces `crit` allowlist before alg dispatch, so a token whose `crit` parameter is unknown to the verifier never runs cryptography either; this matters because cryptographic verification side-channels can leak information about the verifier's state (cache state, branch prediction) even when the verifier rejects the token.
|
||||
Reference in New Issue
Block a user