@dwk/http-signatures 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +141 -0
- package/dist/algorithms.d.ts +31 -0
- package/dist/algorithms.d.ts.map +1 -0
- package/dist/algorithms.js +106 -0
- package/dist/algorithms.js.map +1 -0
- package/dist/base64.d.ts +8 -0
- package/dist/base64.d.ts.map +1 -0
- package/dist/base64.js +23 -0
- package/dist/base64.js.map +1 -0
- package/dist/cavage.d.ts +38 -0
- package/dist/cavage.d.ts.map +1 -0
- package/dist/cavage.js +249 -0
- package/dist/cavage.js.map +1 -0
- package/dist/components.d.ts +26 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +65 -0
- package/dist/components.js.map +1 -0
- package/dist/digest.d.ts +45 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +128 -0
- package/dist/digest.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/rfc9421.d.ts +49 -0
- package/dist/rfc9421.d.ts.map +1 -0
- package/dist/rfc9421.js +216 -0
- package/dist/rfc9421.js.map +1 -0
- package/dist/sf.d.ts +49 -0
- package/dist/sf.d.ts.map +1 -0
- package/dist/sf.js +234 -0
- package/dist/sf.js.map +1 -0
- package/dist/sign.d.ts +46 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +39 -0
- package/dist/sign.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +37 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +60 -0
- package/dist/verify.js.map +1 -0
- package/package.json +44 -0
- package/src/algorithms.ts +168 -0
- package/src/base64.ts +25 -0
- package/src/cavage.ts +292 -0
- package/src/components.ts +79 -0
- package/src/digest.ts +156 -0
- package/src/index.ts +45 -0
- package/src/rfc9421.ts +280 -0
- package/src/sf.ts +246 -0
- package/src/sign.ts +79 -0
- package/src/types.ts +94 -0
- package/src/verify.ts +102 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David W. Keith
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# `@dwk/http-signatures`
|
|
2
|
+
|
|
3
|
+
> HTTP Message Signatures (RFC 9421) sign/verify, with the legacy
|
|
4
|
+
> `draft-cavage-http-signatures` profile for fediverse interop. Cross-standard
|
|
5
|
+
> reusable.
|
|
6
|
+
|
|
7
|
+
Part of the [`@dwk` IndieWeb + Solid cohort](../../README.md). See the
|
|
8
|
+
[package specification](../../spec/packages/http-signatures.md) for the full
|
|
9
|
+
requirements.
|
|
10
|
+
|
|
11
|
+
This package is **cross-standard reusable**: it takes plain-data inputs only
|
|
12
|
+
(method, URL, headers) plus a resolved `CryptoKey`, has no Workers-runtime
|
|
13
|
+
dependency (only Web Crypto), and unit-tests in isolation (Node, no `workerd`).
|
|
14
|
+
It is **protocol-agnostic** — it knows nothing about ActivityPub, IndieWeb, or
|
|
15
|
+
Solid. Key resolution (fetching an actor's public key, caching it) is the
|
|
16
|
+
caller's responsibility, supplied as a `KeyResolver`.
|
|
17
|
+
|
|
18
|
+
It mirrors the [`@dwk/dpop`](../dpop/README.md) hardening posture: only
|
|
19
|
+
asymmetric algorithms from an explicit allow-list are accepted — never `none`
|
|
20
|
+
or HMAC — and a resolved key is validated against the claimed algorithm (RSA
|
|
21
|
+
modulus floor of 2048 bits, EC curve) before any signature is checked.
|
|
22
|
+
|
|
23
|
+
## API
|
|
24
|
+
|
|
25
|
+
### Signing (RFC 9421)
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { signMessage, createContentDigest } from "@dwk/http-signatures";
|
|
29
|
+
|
|
30
|
+
const body = JSON.stringify(activity);
|
|
31
|
+
|
|
32
|
+
// Cover the body by digesting it and listing the digest among the components.
|
|
33
|
+
const headers = {
|
|
34
|
+
host: "remote.example",
|
|
35
|
+
date: new Date().toUTCString(),
|
|
36
|
+
"content-type": "application/activity+json",
|
|
37
|
+
"content-digest": await createContentDigest(body),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const sig = await signMessage(
|
|
41
|
+
{ method: "POST", url: "https://remote.example/inbox", headers },
|
|
42
|
+
{
|
|
43
|
+
key: privateKey, // a private CryptoKey you imported for `alg`
|
|
44
|
+
keyId: "https://me.example/actor#main-key",
|
|
45
|
+
alg: "ed25519",
|
|
46
|
+
components: ["@method", "@target-uri", "@authority", "date", "content-digest"],
|
|
47
|
+
// created defaults to now; expires, nonce, tag, label are optional
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Merge sig (Signature-Input + Signature) into the outbound request headers.
|
|
52
|
+
await fetch("https://remote.example/inbox", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { ...headers, ...sig },
|
|
55
|
+
body,
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Verifying
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { verifyMessage } from "@dwk/http-signatures";
|
|
63
|
+
|
|
64
|
+
const result = await verifyMessage(
|
|
65
|
+
{ method: request.method, url: request.url, headers },
|
|
66
|
+
{
|
|
67
|
+
// Resolve the public key for the claimed keyid (fetch the actor, cache it).
|
|
68
|
+
resolveKey: async ({ keyId, alg }) => fetchActorKey(keyId, alg),
|
|
69
|
+
requiredComponents: ["@method", "@target-uri", "date", "content-digest"],
|
|
70
|
+
body, // optional: also recompute & check any covered content-digest/digest
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!result.valid) {
|
|
75
|
+
// result.reason is a stable code, e.g. "signature_invalid", "key_unresolved"
|
|
76
|
+
return new Response("bad signature", { status: 401 });
|
|
77
|
+
}
|
|
78
|
+
// result.keyId is the verified principal; result.coveredComponents the audit trail.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The profile is auto-detected from the headers (a `Signature-Input` header means
|
|
82
|
+
RFC 9421, otherwise the legacy single `Signature` header is parsed as
|
|
83
|
+
`draft-cavage`); pass `profile` to force one.
|
|
84
|
+
|
|
85
|
+
### `draft-cavage` (fediverse)
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { signMessage, createDigest } from "@dwk/http-signatures";
|
|
89
|
+
|
|
90
|
+
const headers = {
|
|
91
|
+
host: "remote.example",
|
|
92
|
+
date: new Date().toUTCString(),
|
|
93
|
+
digest: await createDigest(body), // legacy "SHA-256=<base64>" header
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const sig = await signMessage(
|
|
97
|
+
{ method: "POST", url: "https://remote.example/inbox", headers },
|
|
98
|
+
{
|
|
99
|
+
profile: "cavage",
|
|
100
|
+
key: privateKey,
|
|
101
|
+
keyId: "https://me.example/actor#main-key",
|
|
102
|
+
alg: "rsa-v1_5-sha256", // serialized as algorithm="rsa-sha256"
|
|
103
|
+
components: ["(request-target)", "host", "date", "digest"],
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## What is verified
|
|
109
|
+
|
|
110
|
+
- **Profile** — `Signature-Input` / `Signature` (RFC 9421) or the single
|
|
111
|
+
`Signature` header (`draft-cavage`), auto-detected unless forced.
|
|
112
|
+
- **Algorithm** — from the allow-list (`rsa-pss-sha512`, `rsa-v1_5-sha256`,
|
|
113
|
+
`ecdsa-p256-sha256`, `ecdsa-p384-sha384`, `ed25519`); never `none` or HMAC.
|
|
114
|
+
- **Key** — the resolved `CryptoKey`'s algorithm and EC curve must match the
|
|
115
|
+
claimed algorithm, and RSA moduli must be at least 2048 bits.
|
|
116
|
+
- **Signature** — over the reconstructed signature base / signing string.
|
|
117
|
+
- **Window** — `created` not in the future and `expires` not in the past,
|
|
118
|
+
within `toleranceSeconds` (default 300).
|
|
119
|
+
- **Coverage** — every component named in the signature is reconstructed from
|
|
120
|
+
the message; `requiredComponents` lets the caller insist specific components
|
|
121
|
+
were covered.
|
|
122
|
+
- **Body integrity** (optional) — when a `body` is supplied and a
|
|
123
|
+
`content-digest` (RFC 9530) or legacy `Digest` component is covered, the
|
|
124
|
+
header is recomputed over the body and must match.
|
|
125
|
+
|
|
126
|
+
`verifyMessage` never throws — failures return `{ valid: false, reason }` with a
|
|
127
|
+
stable `SignatureFailureReason` code. On success it returns
|
|
128
|
+
`{ valid: true, profile, keyId, coveredComponents, created?, expires? }`.
|
|
129
|
+
|
|
130
|
+
## Out of scope
|
|
131
|
+
|
|
132
|
+
- **Key resolution and caching** — the caller supplies a `KeyResolver` and owns
|
|
133
|
+
fetching/importing the public key (PEM/JWK → `CryptoKey`) and any caching.
|
|
134
|
+
- **Replay/nonce storage** — the caller owns nonce bookkeeping.
|
|
135
|
+
- **Component parameters** — `;sf`, `;key`, `;bs`, `@query-param`, and response
|
|
136
|
+
components (`@status`) are not derived; a signature covering them is reported
|
|
137
|
+
as `components_malformed` rather than guessed at.
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
[ISC](../../LICENSE)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Algorithm allow-list and the Web Crypto primitives that back it.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the `@dwk/dpop` hardening posture: asymmetric algorithms only, an
|
|
5
|
+
* explicit allow-list (no `none`, no HMAC), and the resolved key is validated
|
|
6
|
+
* against the claimed algorithm — RSA moduli below 2048 bits and curve
|
|
7
|
+
* mismatches are rejected before any signature is checked.
|
|
8
|
+
*/
|
|
9
|
+
import type { SignatureAlgorithm } from "./types";
|
|
10
|
+
/** Whether `alg` is in the allow-list. */
|
|
11
|
+
export declare function isSupportedAlgorithm(alg: string): alg is SignatureAlgorithm;
|
|
12
|
+
/** The reasons {@link validateKey} can reject a key, or `null` when it is sound. */
|
|
13
|
+
export type KeyRejection = "key_alg_mismatch" | "key_too_small";
|
|
14
|
+
/**
|
|
15
|
+
* Validate that a resolved {@link CryptoKey} matches the claimed algorithm:
|
|
16
|
+
* the key's algorithm name and EC curve must line up, and RSA moduli must meet
|
|
17
|
+
* the {@link MIN_RSA_KEY_BITS} floor. Returns `null` when the key is acceptable.
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateKey(key: CryptoKey, alg: SignatureAlgorithm): KeyRejection | null;
|
|
20
|
+
/**
|
|
21
|
+
* Derive the signature algorithm implied by a resolved key's type. Used when a
|
|
22
|
+
* signature omits its algorithm (RFC 9421 `alg` is OPTIONAL; the legacy
|
|
23
|
+
* `hs2019` token is intentionally key-derived). Returns `null` for a key type
|
|
24
|
+
* outside the allow-list.
|
|
25
|
+
*/
|
|
26
|
+
export declare function deriveAlgFromKey(key: CryptoKey): SignatureAlgorithm | null;
|
|
27
|
+
/** Sign `data` with `key` under `alg`. */
|
|
28
|
+
export declare function signBytes(key: CryptoKey, alg: SignatureAlgorithm, data: Uint8Array): Promise<ArrayBuffer>;
|
|
29
|
+
/** Verify `signature` over `data` with `key` under `alg`. Never throws. */
|
|
30
|
+
export declare function verifyBytes(key: CryptoKey, alg: SignatureAlgorithm, signature: Uint8Array, data: Uint8Array): Promise<boolean>;
|
|
31
|
+
//# sourceMappingURL=algorithms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"algorithms.d.ts","sourceRoot":"","sources":["../src/algorithms.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAiElD,0CAA0C;AAC1C,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,kBAAkB,CAE3E;AAED,oFAAoF;AACpF,MAAM,MAAM,YAAY,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAUhE;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,SAAS,EACd,GAAG,EAAE,kBAAkB,GACtB,YAAY,GAAG,IAAI,CAiBrB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,kBAAkB,GAAG,IAAI,CAgB1E;AAED,0CAA0C;AAC1C,wBAAgB,SAAS,CACvB,GAAG,EAAE,SAAS,EACd,GAAG,EAAE,kBAAkB,EACvB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,WAAW,CAAC,CAEtB;AAED,2EAA2E;AAC3E,wBAAsB,WAAW,CAC/B,GAAG,EAAE,SAAS,EACd,GAAG,EAAE,kBAAkB,EACvB,SAAS,EAAE,UAAU,EACrB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,OAAO,CAAC,CAWlB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Algorithm allow-list and the Web Crypto primitives that back it.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the `@dwk/dpop` hardening posture: asymmetric algorithms only, an
|
|
5
|
+
* explicit allow-list (no `none`, no HMAC), and the resolved key is validated
|
|
6
|
+
* against the claimed algorithm — RSA moduli below 2048 bits and curve
|
|
7
|
+
* mismatches are rejected before any signature is checked.
|
|
8
|
+
*/
|
|
9
|
+
const ALGS = {
|
|
10
|
+
"rsa-pss-sha512": {
|
|
11
|
+
keyName: "RSA-PSS",
|
|
12
|
+
rsa: true,
|
|
13
|
+
importParams: { name: "RSA-PSS", hash: "SHA-512" },
|
|
14
|
+
signParams: { name: "RSA-PSS", saltLength: 64 },
|
|
15
|
+
},
|
|
16
|
+
"rsa-v1_5-sha256": {
|
|
17
|
+
keyName: "RSASSA-PKCS1-v1_5",
|
|
18
|
+
rsa: true,
|
|
19
|
+
importParams: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
20
|
+
signParams: { name: "RSASSA-PKCS1-v1_5" },
|
|
21
|
+
},
|
|
22
|
+
"ecdsa-p256-sha256": {
|
|
23
|
+
keyName: "ECDSA",
|
|
24
|
+
expectedCurve: "P-256",
|
|
25
|
+
rsa: false,
|
|
26
|
+
importParams: { name: "ECDSA", namedCurve: "P-256" },
|
|
27
|
+
signParams: { name: "ECDSA", hash: "SHA-256" },
|
|
28
|
+
},
|
|
29
|
+
"ecdsa-p384-sha384": {
|
|
30
|
+
keyName: "ECDSA",
|
|
31
|
+
expectedCurve: "P-384",
|
|
32
|
+
rsa: false,
|
|
33
|
+
importParams: { name: "ECDSA", namedCurve: "P-384" },
|
|
34
|
+
signParams: { name: "ECDSA", hash: "SHA-384" },
|
|
35
|
+
},
|
|
36
|
+
ed25519: {
|
|
37
|
+
keyName: "Ed25519",
|
|
38
|
+
rsa: false,
|
|
39
|
+
importParams: { name: "Ed25519" },
|
|
40
|
+
signParams: { name: "Ed25519" },
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
/** Minimum accepted RSA modulus size, in bits. */
|
|
44
|
+
const MIN_RSA_KEY_BITS = 2048;
|
|
45
|
+
/** Whether `alg` is in the allow-list. */
|
|
46
|
+
export function isSupportedAlgorithm(alg) {
|
|
47
|
+
return Object.prototype.hasOwnProperty.call(ALGS, alg);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validate that a resolved {@link CryptoKey} matches the claimed algorithm:
|
|
51
|
+
* the key's algorithm name and EC curve must line up, and RSA moduli must meet
|
|
52
|
+
* the {@link MIN_RSA_KEY_BITS} floor. Returns `null` when the key is acceptable.
|
|
53
|
+
*/
|
|
54
|
+
export function validateKey(key, alg) {
|
|
55
|
+
const spec = ALGS[alg];
|
|
56
|
+
const keyAlg = key.algorithm;
|
|
57
|
+
if (keyAlg.name !== spec.keyName) {
|
|
58
|
+
return "key_alg_mismatch";
|
|
59
|
+
}
|
|
60
|
+
if (spec.expectedCurve && keyAlg.namedCurve !== spec.expectedCurve) {
|
|
61
|
+
return "key_alg_mismatch";
|
|
62
|
+
}
|
|
63
|
+
if (spec.rsa &&
|
|
64
|
+
typeof keyAlg.modulusLength === "number" &&
|
|
65
|
+
keyAlg.modulusLength < MIN_RSA_KEY_BITS) {
|
|
66
|
+
return "key_too_small";
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Derive the signature algorithm implied by a resolved key's type. Used when a
|
|
72
|
+
* signature omits its algorithm (RFC 9421 `alg` is OPTIONAL; the legacy
|
|
73
|
+
* `hs2019` token is intentionally key-derived). Returns `null` for a key type
|
|
74
|
+
* outside the allow-list.
|
|
75
|
+
*/
|
|
76
|
+
export function deriveAlgFromKey(key) {
|
|
77
|
+
const a = key.algorithm;
|
|
78
|
+
switch (a.name) {
|
|
79
|
+
case "RSA-PSS":
|
|
80
|
+
return "rsa-pss-sha512";
|
|
81
|
+
case "RSASSA-PKCS1-v1_5":
|
|
82
|
+
return "rsa-v1_5-sha256";
|
|
83
|
+
case "ECDSA":
|
|
84
|
+
return a.namedCurve === "P-384"
|
|
85
|
+
? "ecdsa-p384-sha384"
|
|
86
|
+
: "ecdsa-p256-sha256";
|
|
87
|
+
case "Ed25519":
|
|
88
|
+
return "ed25519";
|
|
89
|
+
default:
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Sign `data` with `key` under `alg`. */
|
|
94
|
+
export function signBytes(key, alg, data) {
|
|
95
|
+
return crypto.subtle.sign(ALGS[alg].signParams, key, data);
|
|
96
|
+
}
|
|
97
|
+
/** Verify `signature` over `data` with `key` under `alg`. Never throws. */
|
|
98
|
+
export async function verifyBytes(key, alg, signature, data) {
|
|
99
|
+
try {
|
|
100
|
+
return await crypto.subtle.verify(ALGS[alg].signParams, key, signature, data);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=algorithms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"algorithms.js","sourceRoot":"","sources":["../src/algorithms.ts"],"names":[],"mappings":"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"}
|
package/dist/base64.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Base64 (standard, padded) <-> bytes, plus base64url helpers. */
|
|
2
|
+
/** Decode standard padded base64 to bytes. Throws on invalid input. */
|
|
3
|
+
export declare function base64ToBytes(b64: string): Uint8Array;
|
|
4
|
+
/** Encode bytes as standard padded base64. */
|
|
5
|
+
export declare function bytesToBase64(bytes: Uint8Array): string;
|
|
6
|
+
/** Encode a UTF-8 string to bytes. */
|
|
7
|
+
export declare function utf8(input: string): Uint8Array;
|
|
8
|
+
//# sourceMappingURL=base64.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../src/base64.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAOrD;AAED,8CAA8C;AAC9C,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAMvD;AAED,sCAAsC;AACtC,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAE9C"}
|
package/dist/base64.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Base64 (standard, padded) <-> bytes, plus base64url helpers. */
|
|
2
|
+
/** Decode standard padded base64 to bytes. Throws on invalid input. */
|
|
3
|
+
export function base64ToBytes(b64) {
|
|
4
|
+
const binary = atob(b64);
|
|
5
|
+
const bytes = new Uint8Array(binary.length);
|
|
6
|
+
for (let i = 0; i < binary.length; i++) {
|
|
7
|
+
bytes[i] = binary.charCodeAt(i);
|
|
8
|
+
}
|
|
9
|
+
return bytes;
|
|
10
|
+
}
|
|
11
|
+
/** Encode bytes as standard padded base64. */
|
|
12
|
+
export function bytesToBase64(bytes) {
|
|
13
|
+
let binary = "";
|
|
14
|
+
for (const byte of bytes) {
|
|
15
|
+
binary += String.fromCharCode(byte);
|
|
16
|
+
}
|
|
17
|
+
return btoa(binary);
|
|
18
|
+
}
|
|
19
|
+
/** Encode a UTF-8 string to bytes. */
|
|
20
|
+
export function utf8(input) {
|
|
21
|
+
return new TextEncoder().encode(input);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=base64.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64.js","sourceRoot":"","sources":["../src/base64.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,IAAI,CAAC,KAAa;IAChC,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC"}
|
package/dist/cavage.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The legacy `draft-cavage-http-signatures` "Signature" profile that much of
|
|
3
|
+
* the fediverse still emits. A single `Signature` header carries the `keyId`,
|
|
4
|
+
* `algorithm`, the space-separated `headers` list, and the base64 `signature`;
|
|
5
|
+
* the signing string is the `headers` components joined by newlines, where
|
|
6
|
+
* `(request-target)`, `(created)`, and `(expires)` are pseudo-headers.
|
|
7
|
+
*/
|
|
8
|
+
import type { KeyResolver } from "./rfc9421";
|
|
9
|
+
import type { HttpMessage, SignatureAlgorithm, VerifyResult } from "./types";
|
|
10
|
+
/** Resolved signing inputs for the `draft-cavage` profile. */
|
|
11
|
+
export interface CavageSignParams {
|
|
12
|
+
key: CryptoKey;
|
|
13
|
+
keyId: string;
|
|
14
|
+
alg: SignatureAlgorithm;
|
|
15
|
+
/** Cavage-style component names, e.g. `["(request-target)", "host", "date", "digest"]`. */
|
|
16
|
+
components: string[];
|
|
17
|
+
created?: number;
|
|
18
|
+
expires?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sign `message` under the `draft-cavage` profile. Returns the single
|
|
22
|
+
* `Signature` header to merge into the request. Throws if a covered component
|
|
23
|
+
* is absent — a signer bug.
|
|
24
|
+
*/
|
|
25
|
+
export declare function signCavage(message: HttpMessage, params: CavageSignParams): Promise<Record<string, string>>;
|
|
26
|
+
/** Parameters for verifying a `draft-cavage` signature. */
|
|
27
|
+
export interface CavageVerifyParams {
|
|
28
|
+
resolveKey: KeyResolver;
|
|
29
|
+
requiredComponents?: string[];
|
|
30
|
+
now?: number;
|
|
31
|
+
toleranceSeconds?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Verify a `draft-cavage` signature on `message`. Never throws — failures are
|
|
35
|
+
* returned as `{ valid: false, reason }`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function verifyCavage(message: HttpMessage, params: CavageVerifyParams): Promise<VerifyResult>;
|
|
38
|
+
//# sourceMappingURL=cavage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cavage.d.ts","sourceRoot":"","sources":["../src/cavage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAElB,YAAY,EACb,MAAM,SAAS,CAAC;AAsDjB,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,kBAAkB,CAAC;IACxB,2FAA2F;IAC3F,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAuBjC;AAsCD,2DAA2D;AAC3D,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,WAAW,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAsBD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,YAAY,CAAC,CAmGvB"}
|
package/dist/cavage.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The legacy `draft-cavage-http-signatures` "Signature" profile that much of
|
|
3
|
+
* the fediverse still emits. A single `Signature` header carries the `keyId`,
|
|
4
|
+
* `algorithm`, the space-separated `headers` list, and the base64 `signature`;
|
|
5
|
+
* the signing string is the `headers` components joined by newlines, where
|
|
6
|
+
* `(request-target)`, `(created)`, and `(expires)` are pseudo-headers.
|
|
7
|
+
*/
|
|
8
|
+
import { deriveAlgFromKey, isSupportedAlgorithm, signBytes, validateKey, verifyBytes, } from "./algorithms";
|
|
9
|
+
import { base64ToBytes, bytesToBase64, utf8 } from "./base64";
|
|
10
|
+
import { derivationContext } from "./components";
|
|
11
|
+
/** Map our algorithm identifiers to a `draft-cavage` `algorithm` token. */
|
|
12
|
+
const CAVAGE_TOKEN = {
|
|
13
|
+
"rsa-v1_5-sha256": "rsa-sha256",
|
|
14
|
+
"rsa-pss-sha512": "hs2019",
|
|
15
|
+
"ecdsa-p256-sha256": "ecdsa-sha256",
|
|
16
|
+
"ecdsa-p384-sha384": "ecdsa-sha384",
|
|
17
|
+
ed25519: "ed25519",
|
|
18
|
+
};
|
|
19
|
+
/** Map a `draft-cavage` `algorithm` token to our identifier, or `null` for `hs2019`. */
|
|
20
|
+
function tokenToAlg(token) {
|
|
21
|
+
switch (token.toLowerCase()) {
|
|
22
|
+
case "rsa-sha256":
|
|
23
|
+
return "rsa-v1_5-sha256";
|
|
24
|
+
case "ecdsa-sha256":
|
|
25
|
+
return "ecdsa-p256-sha256";
|
|
26
|
+
case "ecdsa-sha384":
|
|
27
|
+
return "ecdsa-p384-sha384";
|
|
28
|
+
case "ed25519":
|
|
29
|
+
return "ed25519";
|
|
30
|
+
case "hs2019":
|
|
31
|
+
return "derive";
|
|
32
|
+
default:
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Build the `draft-cavage` signing string, or report the first missing component. */
|
|
37
|
+
function buildSigningString(ctx, components, times) {
|
|
38
|
+
const lines = [];
|
|
39
|
+
for (const raw of components) {
|
|
40
|
+
const name = raw.toLowerCase();
|
|
41
|
+
let value;
|
|
42
|
+
if (name === "(request-target)") {
|
|
43
|
+
value = `${ctx.message.method.toLowerCase()} ${ctx.url.pathname}${ctx.url.search}`;
|
|
44
|
+
}
|
|
45
|
+
else if (name === "(created)") {
|
|
46
|
+
value = times.created === undefined ? null : String(times.created);
|
|
47
|
+
}
|
|
48
|
+
else if (name === "(expires)") {
|
|
49
|
+
value = times.expires === undefined ? null : String(times.expires);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
value = ctx.headers.get(name) ?? null;
|
|
53
|
+
}
|
|
54
|
+
if (value === null)
|
|
55
|
+
return { missing: name };
|
|
56
|
+
lines.push(`${name}: ${value}`);
|
|
57
|
+
}
|
|
58
|
+
return { string: lines.join("\n") };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Sign `message` under the `draft-cavage` profile. Returns the single
|
|
62
|
+
* `Signature` header to merge into the request. Throws if a covered component
|
|
63
|
+
* is absent — a signer bug.
|
|
64
|
+
*/
|
|
65
|
+
export async function signCavage(message, params) {
|
|
66
|
+
const ctx = derivationContext(message);
|
|
67
|
+
if (ctx === null)
|
|
68
|
+
throw new Error("http-signatures: message.url is not a valid URL");
|
|
69
|
+
const built = buildSigningString(ctx, params.components, {
|
|
70
|
+
created: params.created,
|
|
71
|
+
expires: params.expires,
|
|
72
|
+
});
|
|
73
|
+
if ("missing" in built) {
|
|
74
|
+
throw new Error(`http-signatures: covered component "${built.missing}" is missing from the message`);
|
|
75
|
+
}
|
|
76
|
+
const signature = await signBytes(params.key, params.alg, utf8(built.string));
|
|
77
|
+
const parts = [
|
|
78
|
+
`keyId="${params.keyId}"`,
|
|
79
|
+
`algorithm="${CAVAGE_TOKEN[params.alg]}"`,
|
|
80
|
+
`headers="${params.components.map((c) => c.toLowerCase()).join(" ")}"`,
|
|
81
|
+
`signature="${bytesToBase64(new Uint8Array(signature))}"`,
|
|
82
|
+
];
|
|
83
|
+
if (params.created !== undefined)
|
|
84
|
+
parts.push(`created=${params.created}`);
|
|
85
|
+
if (params.expires !== undefined)
|
|
86
|
+
parts.push(`expires=${params.expires}`);
|
|
87
|
+
return { Signature: parts.join(",") };
|
|
88
|
+
}
|
|
89
|
+
/** Parse a `draft-cavage` `Signature` header into its named members. */
|
|
90
|
+
function parseCavageHeader(value) {
|
|
91
|
+
const out = new Map();
|
|
92
|
+
let i = 0;
|
|
93
|
+
while (i < value.length) {
|
|
94
|
+
while (i < value.length &&
|
|
95
|
+
(value[i] === " " || value[i] === "," || value[i] === "\t"))
|
|
96
|
+
i++;
|
|
97
|
+
const eq = value.indexOf("=", i);
|
|
98
|
+
if (eq < 0)
|
|
99
|
+
break;
|
|
100
|
+
const key = value.slice(i, eq).trim();
|
|
101
|
+
i = eq + 1;
|
|
102
|
+
let raw;
|
|
103
|
+
if (value[i] === '"') {
|
|
104
|
+
i++;
|
|
105
|
+
let s = "";
|
|
106
|
+
while (i < value.length && value[i] !== '"') {
|
|
107
|
+
if (value[i] === "\\" && i + 1 < value.length)
|
|
108
|
+
i++;
|
|
109
|
+
s += value[i];
|
|
110
|
+
i++;
|
|
111
|
+
}
|
|
112
|
+
i++; // closing quote
|
|
113
|
+
raw = s;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
let j = i;
|
|
117
|
+
while (j < value.length && value[j] !== ",")
|
|
118
|
+
j++;
|
|
119
|
+
raw = value.slice(i, j).trim();
|
|
120
|
+
i = j;
|
|
121
|
+
}
|
|
122
|
+
if (key)
|
|
123
|
+
out.set(key.toLowerCase(), raw);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
const DEFAULT_TOLERANCE_SECONDS = 300;
|
|
128
|
+
function getHeader(message, name) {
|
|
129
|
+
const lower = name.toLowerCase();
|
|
130
|
+
for (const [k, v] of Object.entries(message.headers)) {
|
|
131
|
+
if (k.toLowerCase() === lower)
|
|
132
|
+
return Array.isArray(v) ? v.join(", ") : v;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
function fail(reason) {
|
|
137
|
+
return { valid: false, profile: "cavage", reason };
|
|
138
|
+
}
|
|
139
|
+
function parseIntegerParam(raw) {
|
|
140
|
+
if (raw === undefined)
|
|
141
|
+
return undefined;
|
|
142
|
+
const n = Number(raw);
|
|
143
|
+
return Number.isInteger(n) ? n : null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Verify a `draft-cavage` signature on `message`. Never throws — failures are
|
|
147
|
+
* returned as `{ valid: false, reason }`.
|
|
148
|
+
*/
|
|
149
|
+
export async function verifyCavage(message, params) {
|
|
150
|
+
const header = getHeader(message, "signature");
|
|
151
|
+
if (header === null)
|
|
152
|
+
return fail("signature_missing");
|
|
153
|
+
const fields = parseCavageHeader(header);
|
|
154
|
+
const sigB64 = fields.get("signature");
|
|
155
|
+
if (sigB64 === undefined)
|
|
156
|
+
return fail("signature_malformed");
|
|
157
|
+
let signature;
|
|
158
|
+
try {
|
|
159
|
+
signature = base64ToBytes(sigB64);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return fail("signature_malformed");
|
|
163
|
+
}
|
|
164
|
+
const keyId = fields.get("keyid") ?? null;
|
|
165
|
+
if (keyId === null)
|
|
166
|
+
return fail("keyid_missing");
|
|
167
|
+
const token = fields.get("algorithm");
|
|
168
|
+
const createdRaw = parseIntegerParam(fields.get("created"));
|
|
169
|
+
if (createdRaw === null)
|
|
170
|
+
return fail("created_invalid");
|
|
171
|
+
const expiresRaw = parseIntegerParam(fields.get("expires"));
|
|
172
|
+
if (expiresRaw === null)
|
|
173
|
+
return fail("expires_invalid");
|
|
174
|
+
// Default covered-component list when no explicit `headers` is sent.
|
|
175
|
+
// draft-cavage-12 §2.1.6 specifies the default is `(created)` unconditionally.
|
|
176
|
+
// We deliberately diverge: when `created` is absent we fall back to `date`,
|
|
177
|
+
// matching the older "Signing HTTP Messages" rule that essentially every
|
|
178
|
+
// fediverse peer implements. In practice senders always send an explicit
|
|
179
|
+
// `headers`, so this branch is rarely reached; the divergence only affects
|
|
180
|
+
// the (non-conforming) case of a signature with neither `headers` nor
|
|
181
|
+
// `created`, where interop with real-world peers beats spec-literalism.
|
|
182
|
+
const headersList = fields.get("headers");
|
|
183
|
+
const components = headersList
|
|
184
|
+
? headersList.split(/\s+/).filter(Boolean)
|
|
185
|
+
: createdRaw !== undefined
|
|
186
|
+
? ["(created)"]
|
|
187
|
+
: ["date"];
|
|
188
|
+
// Resolve the algorithm. `hs2019` (and a missing token) defer to the key type.
|
|
189
|
+
let alg = null;
|
|
190
|
+
if (token !== undefined) {
|
|
191
|
+
const mapped = tokenToAlg(token);
|
|
192
|
+
if (mapped === null)
|
|
193
|
+
return fail("alg_unsupported");
|
|
194
|
+
if (mapped !== "derive")
|
|
195
|
+
alg = mapped;
|
|
196
|
+
}
|
|
197
|
+
const key = await params.resolveKey({ keyId, alg });
|
|
198
|
+
if (key === null)
|
|
199
|
+
return fail("key_unresolved");
|
|
200
|
+
if (alg === null) {
|
|
201
|
+
alg = deriveAlgFromKey(key);
|
|
202
|
+
if (alg === null || !isSupportedAlgorithm(alg))
|
|
203
|
+
return fail("alg_unsupported");
|
|
204
|
+
}
|
|
205
|
+
const rejection = validateKey(key, alg);
|
|
206
|
+
if (rejection !== null)
|
|
207
|
+
return fail(rejection);
|
|
208
|
+
// created / expires window.
|
|
209
|
+
const now = params.now ?? Math.floor(Date.now() / 1000);
|
|
210
|
+
const tolerance = params.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
211
|
+
if (createdRaw !== undefined && createdRaw > now + tolerance)
|
|
212
|
+
return fail("signature_future");
|
|
213
|
+
if (expiresRaw !== undefined && now > expiresRaw + tolerance)
|
|
214
|
+
return fail("signature_expired");
|
|
215
|
+
// RFC 9421 §2.3 / draft-cavage: `expires` MUST NOT precede `created`.
|
|
216
|
+
if (createdRaw !== undefined &&
|
|
217
|
+
expiresRaw !== undefined &&
|
|
218
|
+
expiresRaw < createdRaw)
|
|
219
|
+
return fail("expires_invalid");
|
|
220
|
+
// Required-component policy.
|
|
221
|
+
if (params.requiredComponents) {
|
|
222
|
+
const covered = new Set(components.map((c) => c.toLowerCase()));
|
|
223
|
+
for (const required of params.requiredComponents) {
|
|
224
|
+
if (!covered.has(required.toLowerCase()))
|
|
225
|
+
return fail("required_component_missing");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const ctx = derivationContext(message);
|
|
229
|
+
if (ctx === null)
|
|
230
|
+
return fail("covered_component_missing");
|
|
231
|
+
const built = buildSigningString(ctx, components, {
|
|
232
|
+
created: createdRaw,
|
|
233
|
+
expires: expiresRaw,
|
|
234
|
+
});
|
|
235
|
+
if ("missing" in built)
|
|
236
|
+
return fail("covered_component_missing");
|
|
237
|
+
const ok = await verifyBytes(key, alg, signature, utf8(built.string));
|
|
238
|
+
if (!ok)
|
|
239
|
+
return fail("signature_invalid");
|
|
240
|
+
return {
|
|
241
|
+
valid: true,
|
|
242
|
+
profile: "cavage",
|
|
243
|
+
keyId,
|
|
244
|
+
coveredComponents: components,
|
|
245
|
+
created: createdRaw,
|
|
246
|
+
expires: expiresRaw,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=cavage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cavage.js","sourceRoot":"","sources":["../src/cavage.ts"],"names":[],"mappings":"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"}
|