@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/dist/sf.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A focused RFC 8941 structured-fields parser, scoped to the two fields HTTP
|
|
3
|
+
* Message Signatures use: `Signature-Input` (a Dictionary of Inner Lists with
|
|
4
|
+
* parameters) and `Signature` (a Dictionary of Byte Sequences).
|
|
5
|
+
*
|
|
6
|
+
* It is intentionally not a general structured-fields implementation. Parsing
|
|
7
|
+
* is strict — anything malformed throws {@link SfParseError}, which the callers
|
|
8
|
+
* turn into a verification failure rather than guessing at the sender's intent.
|
|
9
|
+
*/
|
|
10
|
+
/** A structured-fields bare item value, as the native JS shape we use it as. */
|
|
11
|
+
export type SfBareItem = string | number | boolean | Uint8Array;
|
|
12
|
+
/** One member of an `Signature-Input` inner list (a covered-component id). */
|
|
13
|
+
export interface SfInnerItem {
|
|
14
|
+
/** Exact source text of the item (bare item plus its parameters). */
|
|
15
|
+
raw: string;
|
|
16
|
+
/** The bare item, a string for every component identifier we accept. */
|
|
17
|
+
value: SfBareItem;
|
|
18
|
+
/** Item parameters, in source order. */
|
|
19
|
+
params: Array<[string, SfBareItem]>;
|
|
20
|
+
}
|
|
21
|
+
/** A parsed `Signature-Input` dictionary member. */
|
|
22
|
+
export interface SfSignatureInput {
|
|
23
|
+
/** Exact source text of the member value (inner list plus its parameters). */
|
|
24
|
+
raw: string;
|
|
25
|
+
items: SfInnerItem[];
|
|
26
|
+
params: Array<[string, SfBareItem]>;
|
|
27
|
+
}
|
|
28
|
+
/** Thrown on any malformed structured-field input. */
|
|
29
|
+
export declare class SfParseError extends Error {
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Parse a `Signature-Input` header into its labelled inner lists, preserving
|
|
33
|
+
* for each member the exact source text of its value (the `@signature-params`
|
|
34
|
+
* value the base must reproduce byte-for-byte).
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseSignatureInput(input: string): Map<string, SfSignatureInput>;
|
|
37
|
+
/**
|
|
38
|
+
* Parse an RFC 8941 Dictionary whose member values are Byte Sequences — the
|
|
39
|
+
* shape shared by the `Signature` header and the RFC 9530 `Content-Digest` /
|
|
40
|
+
* `Want-Content-Digest` fields. Member parameters are accepted and discarded.
|
|
41
|
+
* Keys follow sf-key rules (lowercase only), so an uppercase key is a parse
|
|
42
|
+
* error rather than something silently lowercased.
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseByteSequenceDictionary(input: string): Map<string, Uint8Array>;
|
|
45
|
+
/** Parse a `Signature` header into its labelled byte sequences. */
|
|
46
|
+
export declare function parseSignatureHeader(input: string): Map<string, Uint8Array>;
|
|
47
|
+
/** Serialize an sf-string: double-quoted with `"` and `\` escaped. */
|
|
48
|
+
export declare function serializeString(value: string): string;
|
|
49
|
+
//# sourceMappingURL=sf.d.ts.map
|
package/dist/sf.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sf.d.ts","sourceRoot":"","sources":["../src/sf.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,gFAAgF;AAChF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAEhE,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,KAAK,EAAE,UAAU,CAAC;IAClB,wCAAwC;IACxC,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;CACrC;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;CACrC;AAED,sDAAsD;AACtD,qBAAa,YAAa,SAAQ,KAAK;CAAG;AAiJ1C;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,GACZ,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAqB/B;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,MAAM,GACZ,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAmBzB;AAED,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAE3E;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD"}
|
package/dist/sf.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A focused RFC 8941 structured-fields parser, scoped to the two fields HTTP
|
|
3
|
+
* Message Signatures use: `Signature-Input` (a Dictionary of Inner Lists with
|
|
4
|
+
* parameters) and `Signature` (a Dictionary of Byte Sequences).
|
|
5
|
+
*
|
|
6
|
+
* It is intentionally not a general structured-fields implementation. Parsing
|
|
7
|
+
* is strict — anything malformed throws {@link SfParseError}, which the callers
|
|
8
|
+
* turn into a verification failure rather than guessing at the sender's intent.
|
|
9
|
+
*/
|
|
10
|
+
/** Thrown on any malformed structured-field input. */
|
|
11
|
+
export class SfParseError extends Error {
|
|
12
|
+
}
|
|
13
|
+
const KEY_START = /[a-z*]/;
|
|
14
|
+
const KEY_CHAR = /[a-z0-9_*.-]/;
|
|
15
|
+
const TOKEN_START = /[a-zA-Z*]/;
|
|
16
|
+
const TOKEN_CHAR = /[a-zA-Z0-9:/!#$%&'*+\-.^_`|~]/;
|
|
17
|
+
class Reader {
|
|
18
|
+
input;
|
|
19
|
+
pos = 0;
|
|
20
|
+
constructor(input) {
|
|
21
|
+
this.input = input;
|
|
22
|
+
}
|
|
23
|
+
get done() {
|
|
24
|
+
return this.pos >= this.input.length;
|
|
25
|
+
}
|
|
26
|
+
peek() {
|
|
27
|
+
return this.input[this.pos] ?? "";
|
|
28
|
+
}
|
|
29
|
+
next() {
|
|
30
|
+
const c = this.input[this.pos] ?? "";
|
|
31
|
+
this.pos++;
|
|
32
|
+
return c;
|
|
33
|
+
}
|
|
34
|
+
expect(c) {
|
|
35
|
+
if (this.next() !== c)
|
|
36
|
+
throw new SfParseError(`expected ${c}`);
|
|
37
|
+
}
|
|
38
|
+
skipSP() {
|
|
39
|
+
while (this.peek() === " ")
|
|
40
|
+
this.pos++;
|
|
41
|
+
}
|
|
42
|
+
skipOWS() {
|
|
43
|
+
while (this.peek() === " " || this.peek() === "\t")
|
|
44
|
+
this.pos++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function parseKey(r) {
|
|
48
|
+
if (!KEY_START.test(r.peek()))
|
|
49
|
+
throw new SfParseError("invalid key");
|
|
50
|
+
let key = r.next();
|
|
51
|
+
while (!r.done && KEY_CHAR.test(r.peek()))
|
|
52
|
+
key += r.next();
|
|
53
|
+
return key;
|
|
54
|
+
}
|
|
55
|
+
function parseString(r) {
|
|
56
|
+
r.expect('"');
|
|
57
|
+
let out = "";
|
|
58
|
+
for (;;) {
|
|
59
|
+
if (r.done)
|
|
60
|
+
throw new SfParseError("unterminated string");
|
|
61
|
+
const c = r.next();
|
|
62
|
+
if (c === "\\") {
|
|
63
|
+
const esc = r.next();
|
|
64
|
+
if (esc !== '"' && esc !== "\\")
|
|
65
|
+
throw new SfParseError("bad escape");
|
|
66
|
+
out += esc;
|
|
67
|
+
}
|
|
68
|
+
else if (c === '"') {
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const code = c.charCodeAt(0);
|
|
73
|
+
if (code < 0x20 || code >= 0x7f)
|
|
74
|
+
throw new SfParseError("bad string char");
|
|
75
|
+
out += c;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function parseToken(r) {
|
|
80
|
+
let tok = r.next();
|
|
81
|
+
while (!r.done && TOKEN_CHAR.test(r.peek()))
|
|
82
|
+
tok += r.next();
|
|
83
|
+
return tok;
|
|
84
|
+
}
|
|
85
|
+
function parseNumber(r) {
|
|
86
|
+
let s = "";
|
|
87
|
+
if (r.peek() === "-")
|
|
88
|
+
s += r.next();
|
|
89
|
+
while (!r.done && /[0-9.]/.test(r.peek()))
|
|
90
|
+
s += r.next();
|
|
91
|
+
const n = Number(s);
|
|
92
|
+
if (!Number.isFinite(n))
|
|
93
|
+
throw new SfParseError("invalid number");
|
|
94
|
+
return n;
|
|
95
|
+
}
|
|
96
|
+
function parseByteSequence(r) {
|
|
97
|
+
r.expect(":");
|
|
98
|
+
let b64 = "";
|
|
99
|
+
while (!r.done && r.peek() !== ":")
|
|
100
|
+
b64 += r.next();
|
|
101
|
+
r.expect(":");
|
|
102
|
+
try {
|
|
103
|
+
const binary = atob(b64);
|
|
104
|
+
const bytes = new Uint8Array(binary.length);
|
|
105
|
+
for (let i = 0; i < binary.length; i++)
|
|
106
|
+
bytes[i] = binary.charCodeAt(i);
|
|
107
|
+
return bytes;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw new SfParseError("invalid byte sequence");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function parseBareItem(r) {
|
|
114
|
+
const c = r.peek();
|
|
115
|
+
if (c === '"')
|
|
116
|
+
return parseString(r);
|
|
117
|
+
if (c === ":")
|
|
118
|
+
return parseByteSequence(r);
|
|
119
|
+
if (c === "?") {
|
|
120
|
+
r.next();
|
|
121
|
+
const b = r.next();
|
|
122
|
+
if (b === "0")
|
|
123
|
+
return false;
|
|
124
|
+
if (b === "1")
|
|
125
|
+
return true;
|
|
126
|
+
throw new SfParseError("invalid boolean");
|
|
127
|
+
}
|
|
128
|
+
if (c === "-" || /[0-9]/.test(c))
|
|
129
|
+
return parseNumber(r);
|
|
130
|
+
if (TOKEN_START.test(c))
|
|
131
|
+
return parseToken(r);
|
|
132
|
+
throw new SfParseError("invalid bare item");
|
|
133
|
+
}
|
|
134
|
+
function parseParameters(r) {
|
|
135
|
+
const params = [];
|
|
136
|
+
while (r.peek() === ";") {
|
|
137
|
+
r.next();
|
|
138
|
+
r.skipSP();
|
|
139
|
+
const key = parseKey(r);
|
|
140
|
+
let value = true;
|
|
141
|
+
if (r.peek() === "=") {
|
|
142
|
+
r.next();
|
|
143
|
+
value = parseBareItem(r);
|
|
144
|
+
}
|
|
145
|
+
params.push([key, value]);
|
|
146
|
+
}
|
|
147
|
+
return params;
|
|
148
|
+
}
|
|
149
|
+
function parseInnerList(r) {
|
|
150
|
+
r.expect("(");
|
|
151
|
+
const items = [];
|
|
152
|
+
for (;;) {
|
|
153
|
+
r.skipSP();
|
|
154
|
+
if (r.peek() === ")") {
|
|
155
|
+
r.next();
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if (r.done)
|
|
159
|
+
throw new SfParseError("unterminated inner list");
|
|
160
|
+
const start = r.pos;
|
|
161
|
+
const value = parseBareItem(r);
|
|
162
|
+
const params = parseParameters(r);
|
|
163
|
+
items.push({ raw: r.input.slice(start, r.pos), value, params });
|
|
164
|
+
}
|
|
165
|
+
const params = parseParameters(r);
|
|
166
|
+
return { items, params };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Parse a `Signature-Input` header into its labelled inner lists, preserving
|
|
170
|
+
* for each member the exact source text of its value (the `@signature-params`
|
|
171
|
+
* value the base must reproduce byte-for-byte).
|
|
172
|
+
*/
|
|
173
|
+
export function parseSignatureInput(input) {
|
|
174
|
+
const r = new Reader(input);
|
|
175
|
+
const out = new Map();
|
|
176
|
+
r.skipOWS();
|
|
177
|
+
if (r.done)
|
|
178
|
+
throw new SfParseError("empty dictionary");
|
|
179
|
+
for (;;) {
|
|
180
|
+
const key = parseKey(r);
|
|
181
|
+
if (r.peek() !== "=")
|
|
182
|
+
throw new SfParseError("dictionary member needs a value");
|
|
183
|
+
r.next();
|
|
184
|
+
if (r.peek() !== "(")
|
|
185
|
+
throw new SfParseError("signature-input value must be an inner list");
|
|
186
|
+
const start = r.pos;
|
|
187
|
+
const { items, params } = parseInnerList(r);
|
|
188
|
+
out.set(key, { raw: r.input.slice(start, r.pos), items, params });
|
|
189
|
+
r.skipOWS();
|
|
190
|
+
if (r.done)
|
|
191
|
+
break;
|
|
192
|
+
r.expect(",");
|
|
193
|
+
r.skipOWS();
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Parse an RFC 8941 Dictionary whose member values are Byte Sequences — the
|
|
199
|
+
* shape shared by the `Signature` header and the RFC 9530 `Content-Digest` /
|
|
200
|
+
* `Want-Content-Digest` fields. Member parameters are accepted and discarded.
|
|
201
|
+
* Keys follow sf-key rules (lowercase only), so an uppercase key is a parse
|
|
202
|
+
* error rather than something silently lowercased.
|
|
203
|
+
*/
|
|
204
|
+
export function parseByteSequenceDictionary(input) {
|
|
205
|
+
const r = new Reader(input);
|
|
206
|
+
const out = new Map();
|
|
207
|
+
r.skipOWS();
|
|
208
|
+
if (r.done)
|
|
209
|
+
throw new SfParseError("empty dictionary");
|
|
210
|
+
for (;;) {
|
|
211
|
+
const key = parseKey(r);
|
|
212
|
+
if (r.peek() !== "=")
|
|
213
|
+
throw new SfParseError("dictionary member needs a value");
|
|
214
|
+
r.next();
|
|
215
|
+
const bytes = parseByteSequence(r);
|
|
216
|
+
parseParameters(r);
|
|
217
|
+
out.set(key, bytes);
|
|
218
|
+
r.skipOWS();
|
|
219
|
+
if (r.done)
|
|
220
|
+
break;
|
|
221
|
+
r.expect(",");
|
|
222
|
+
r.skipOWS();
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
/** Parse a `Signature` header into its labelled byte sequences. */
|
|
227
|
+
export function parseSignatureHeader(input) {
|
|
228
|
+
return parseByteSequenceDictionary(input);
|
|
229
|
+
}
|
|
230
|
+
/** Serialize an sf-string: double-quoted with `"` and `\` escaped. */
|
|
231
|
+
export function serializeString(value) {
|
|
232
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=sf.js.map
|
package/dist/sf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sf.js","sourceRoot":"","sources":["../src/sf.ts"],"names":[],"mappings":"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"}
|
package/dist/sign.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified signing entry point. Dispatches on the wire profile and returns
|
|
3
|
+
* the header(s) to merge into the outbound request.
|
|
4
|
+
*/
|
|
5
|
+
import type { HttpMessage, SignatureAlgorithm, SignatureProfile } from "./types";
|
|
6
|
+
/** Inputs for {@link signMessage}. */
|
|
7
|
+
export interface SignParams {
|
|
8
|
+
/** Wire profile. Defaults to `"rfc9421"`. */
|
|
9
|
+
profile?: SignatureProfile;
|
|
10
|
+
/** The private {@link CryptoKey}, imported by the caller for `alg`. */
|
|
11
|
+
key: CryptoKey;
|
|
12
|
+
/** The `keyid` advertised so the verifier can resolve the public half. */
|
|
13
|
+
keyId: string;
|
|
14
|
+
/** Signature algorithm; must be in the allow-list. */
|
|
15
|
+
alg: SignatureAlgorithm;
|
|
16
|
+
/**
|
|
17
|
+
* Covered components, in signing order. For `"rfc9421"`: derived names like
|
|
18
|
+
* `@method`, `@target-uri`, `@authority`, plus lowercase header names. For
|
|
19
|
+
* `"cavage"`: `(request-target)`, `(created)`, `(expires)`, and lowercase
|
|
20
|
+
* header names.
|
|
21
|
+
*/
|
|
22
|
+
components: string[];
|
|
23
|
+
/** Signature creation time, epoch seconds. Defaults to now. */
|
|
24
|
+
created?: number;
|
|
25
|
+
/** Signature expiry, epoch seconds. Omitted when absent. */
|
|
26
|
+
expires?: number;
|
|
27
|
+
/** Anti-replay nonce (RFC 9421 only). */
|
|
28
|
+
nonce?: string;
|
|
29
|
+
/** Application tag (RFC 9421 only). */
|
|
30
|
+
tag?: string;
|
|
31
|
+
/** Signature label (RFC 9421 only). Defaults to `"sig1"`. */
|
|
32
|
+
label?: string;
|
|
33
|
+
/** Override "now" for the `created` default, epoch seconds (deterministic tests). */
|
|
34
|
+
now?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sign `message`, returning the headers to add to the request. The caller is
|
|
38
|
+
* responsible for having already set (and listed among `components`) any
|
|
39
|
+
* `content-digest` / `digest` header it wants covered — see
|
|
40
|
+
* {@link createContentDigest} / {@link createDigest}.
|
|
41
|
+
*
|
|
42
|
+
* @returns A map of header name → value (RFC 9421: `Signature-Input` and
|
|
43
|
+
* `Signature`; cavage: a single `Signature`).
|
|
44
|
+
*/
|
|
45
|
+
export declare function signMessage(message: HttpMessage, params: SignParams): Promise<Record<string, string>>;
|
|
46
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAEjB,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,uEAAuE;IACvE,GAAG,EAAE,SAAS,CAAC;IACf,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,GAAG,EAAE,kBAAkB,CAAC;IACxB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAsBjC"}
|
package/dist/sign.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified signing entry point. Dispatches on the wire profile and returns
|
|
3
|
+
* the header(s) to merge into the outbound request.
|
|
4
|
+
*/
|
|
5
|
+
import { signCavage } from "./cavage";
|
|
6
|
+
import { signRfc9421 } from "./rfc9421";
|
|
7
|
+
/**
|
|
8
|
+
* Sign `message`, returning the headers to add to the request. The caller is
|
|
9
|
+
* responsible for having already set (and listed among `components`) any
|
|
10
|
+
* `content-digest` / `digest` header it wants covered — see
|
|
11
|
+
* {@link createContentDigest} / {@link createDigest}.
|
|
12
|
+
*
|
|
13
|
+
* @returns A map of header name → value (RFC 9421: `Signature-Input` and
|
|
14
|
+
* `Signature`; cavage: a single `Signature`).
|
|
15
|
+
*/
|
|
16
|
+
export function signMessage(message, params) {
|
|
17
|
+
if (params.profile === "cavage") {
|
|
18
|
+
return signCavage(message, {
|
|
19
|
+
key: params.key,
|
|
20
|
+
keyId: params.keyId,
|
|
21
|
+
alg: params.alg,
|
|
22
|
+
components: params.components,
|
|
23
|
+
created: params.created,
|
|
24
|
+
expires: params.expires,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return signRfc9421(message, {
|
|
28
|
+
key: params.key,
|
|
29
|
+
keyId: params.keyId,
|
|
30
|
+
alg: params.alg,
|
|
31
|
+
components: params.components,
|
|
32
|
+
created: params.created ?? params.now ?? Math.floor(Date.now() / 1000),
|
|
33
|
+
expires: params.expires,
|
|
34
|
+
nonce: params.nonce,
|
|
35
|
+
tag: params.tag,
|
|
36
|
+
label: params.label ?? "sig1",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=sign.js.map
|
package/dist/sign.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.js","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAsCxC;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,OAAoB,EACpB,MAAkB;IAElB,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,UAAU,CAAC,OAAO,EAAE;YACzB,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,EAAE;QAC1B,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QACtE,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM;KAC9B,CAAC,CAAC;AACL,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plain-data types for {@link signMessage} and {@link verifyMessage}.
|
|
3
|
+
*
|
|
4
|
+
* The library never touches a live HTTP object: a request (or response) is
|
|
5
|
+
* described by its method, URL, and header values, so it unit-tests under Node
|
|
6
|
+
* with no Workers runtime.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Asymmetric signature algorithms this library accepts, named by their RFC 9421
|
|
10
|
+
* §3.3 registry identifiers.
|
|
11
|
+
*
|
|
12
|
+
* Symmetric (`hmac-sha256`) and unsigned (`none`) algorithms are deliberately
|
|
13
|
+
* excluded: an HTTP message signature must be made by the sender's private key
|
|
14
|
+
* whose public half the verifier resolves out of band.
|
|
15
|
+
*/
|
|
16
|
+
export type SignatureAlgorithm = "rsa-pss-sha512" | "rsa-v1_5-sha256" | "ecdsa-p256-sha256" | "ecdsa-p384-sha384" | "ed25519";
|
|
17
|
+
/**
|
|
18
|
+
* Wire profile.
|
|
19
|
+
*
|
|
20
|
+
* - `"rfc9421"` — the modern `Signature-Input` / `Signature` structured-field
|
|
21
|
+
* pair (RFC 9421).
|
|
22
|
+
* - `"cavage"` — the legacy single `Signature` header from
|
|
23
|
+
* `draft-cavage-http-signatures`, still emitted across the fediverse.
|
|
24
|
+
*/
|
|
25
|
+
export type SignatureProfile = "rfc9421" | "cavage";
|
|
26
|
+
/**
|
|
27
|
+
* A request (or response) reduced to the facts a signature is computed over.
|
|
28
|
+
*
|
|
29
|
+
* `headers` keys are matched case-insensitively; a value array models a field
|
|
30
|
+
* that appears multiple times (its values are joined with `", "` per RFC 9421
|
|
31
|
+
* §2.1).
|
|
32
|
+
*/
|
|
33
|
+
export interface HttpMessage {
|
|
34
|
+
/** HTTP method, e.g. `"POST"`. */
|
|
35
|
+
method: string;
|
|
36
|
+
/** Absolute request URI, e.g. `"https://example.com/inbox"`. */
|
|
37
|
+
url: string;
|
|
38
|
+
/** Header field values, keyed by (case-insensitive) field name. */
|
|
39
|
+
headers: Record<string, string | string[]>;
|
|
40
|
+
}
|
|
41
|
+
/** Stable, locale-independent failure codes returned in {@link VerifyResult.reason}. */
|
|
42
|
+
export type SignatureFailureReason = "signature_missing" | "signature_input_malformed" | "signature_malformed" | "label_ambiguous" | "label_missing" | "components_malformed" | "alg_unsupported" | "keyid_missing" | "key_unresolved" | "key_alg_mismatch" | "key_too_small" | "key_invalid" | "covered_component_missing" | "required_component_missing" | "created_invalid" | "created_required" | "signature_expired" | "signature_future" | "expires_invalid" | "signature_invalid" | "digest_missing" | "digest_unsupported" | "digest_mismatch";
|
|
43
|
+
/** Result of verifying an HTTP message signature. */
|
|
44
|
+
export interface VerifyResult {
|
|
45
|
+
/** Whether the signature is valid. */
|
|
46
|
+
valid: boolean;
|
|
47
|
+
/** The profile the signature was read under, when one could be identified. */
|
|
48
|
+
profile?: SignatureProfile;
|
|
49
|
+
/** The signature label that was verified (RFC 9421 only). */
|
|
50
|
+
label?: string;
|
|
51
|
+
/** The `keyid` the signature claimed (so the caller can audit the principal). */
|
|
52
|
+
keyId?: string;
|
|
53
|
+
/** The covered-component identifiers, in signing order. */
|
|
54
|
+
coveredComponents?: string[];
|
|
55
|
+
/** The signature's `created` time (epoch seconds), if present. */
|
|
56
|
+
created?: number;
|
|
57
|
+
/** The signature's `expires` time (epoch seconds), if present. */
|
|
58
|
+
expires?: number;
|
|
59
|
+
/** Stable failure code (see {@link SignatureFailureReason}) when `valid` is false. */
|
|
60
|
+
reason?: SignatureFailureReason;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAC1B,gBAAgB,GAChB,iBAAiB,GACjB,mBAAmB,GACnB,mBAAmB,GACnB,SAAS,CAAC;AAEd;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAC5C;AAED,wFAAwF;AACxF,MAAM,MAAM,sBAAsB,GAC9B,mBAAmB,GACnB,2BAA2B,GAC3B,qBAAqB,GACrB,iBAAiB,GACjB,eAAe,GACf,sBAAsB,GACtB,iBAAiB,GACjB,eAAe,GACf,gBAAgB,GAChB,kBAAkB,GAClB,eAAe,GACf,aAAa,GACb,2BAA2B,GAC3B,4BAA4B,GAC5B,iBAAiB,GACjB,kBAAkB,GAClB,mBAAmB,GACnB,kBAAkB,GAClB,iBAAiB,GACjB,mBAAmB,GACnB,gBAAgB,GAChB,oBAAoB,GACpB,iBAAiB,CAAC;AAEtB,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACjC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plain-data types for {@link signMessage} and {@link verifyMessage}.
|
|
3
|
+
*
|
|
4
|
+
* The library never touches a live HTTP object: a request (or response) is
|
|
5
|
+
* described by its method, URL, and header values, so it unit-tests under Node
|
|
6
|
+
* with no Workers runtime.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified verification entry point. Auto-detects the wire profile (unless
|
|
3
|
+
* one is forced), verifies the signature, and — when a body is supplied —
|
|
4
|
+
* checks any covered `content-digest` / `digest` against it so body tampering
|
|
5
|
+
* is caught alongside header tampering.
|
|
6
|
+
*/
|
|
7
|
+
import { type KeyResolver } from "./rfc9421";
|
|
8
|
+
import type { HttpMessage, SignatureProfile, VerifyResult } from "./types";
|
|
9
|
+
/** Inputs for {@link verifyMessage}. */
|
|
10
|
+
export interface VerifyParams {
|
|
11
|
+
/** Resolves the public {@link CryptoKey} for a `keyid` (and, when known, `alg`). */
|
|
12
|
+
resolveKey: KeyResolver;
|
|
13
|
+
/** Force a profile. When omitted, it is detected from the present headers. */
|
|
14
|
+
profile?: SignatureProfile;
|
|
15
|
+
/** Which labelled signature to verify (RFC 9421); required if more than one. */
|
|
16
|
+
label?: string;
|
|
17
|
+
/** Component identifiers that MUST be covered (e.g. `@method`, `date`, `content-digest`). */
|
|
18
|
+
requiredComponents?: string[];
|
|
19
|
+
/** Require a `created` parameter (RFC 9421). */
|
|
20
|
+
requireCreated?: boolean;
|
|
21
|
+
/** Current time, epoch seconds. Defaults to `Date.now()`. */
|
|
22
|
+
now?: number;
|
|
23
|
+
/** Allowed clock skew, in seconds, for `created`/`expires`. Defaults to 300. */
|
|
24
|
+
toleranceSeconds?: number;
|
|
25
|
+
/**
|
|
26
|
+
* The received body. When provided and a digest component is covered, the
|
|
27
|
+
* corresponding `content-digest` / `digest` header is recomputed over it; a
|
|
28
|
+
* mismatch fails verification with a `digest_*` reason.
|
|
29
|
+
*/
|
|
30
|
+
body?: Uint8Array | string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Verify the HTTP message signature on `message`. Never throws — every failure
|
|
34
|
+
* is `{ valid: false, reason }` with a stable {@link SignatureFailureReason}.
|
|
35
|
+
*/
|
|
36
|
+
export declare function verifyMessage(message: HttpMessage, params: VerifyParams): Promise<VerifyResult>;
|
|
37
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE3E,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,oFAAoF;IACpF,UAAU,EAAE,WAAW,CAAC;IACxB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,gDAAgD;IAChD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;CAC5B;AAeD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,CAAC,CA6CvB"}
|
package/dist/verify.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified verification entry point. Auto-detects the wire profile (unless
|
|
3
|
+
* one is forced), verifies the signature, and — when a body is supplied —
|
|
4
|
+
* checks any covered `content-digest` / `digest` against it so body tampering
|
|
5
|
+
* is caught alongside header tampering.
|
|
6
|
+
*/
|
|
7
|
+
import { verifyCavage } from "./cavage";
|
|
8
|
+
import { verifyContentDigest, verifyDigest } from "./digest";
|
|
9
|
+
import { verifyRfc9421 } from "./rfc9421";
|
|
10
|
+
function hasHeader(message, name) {
|
|
11
|
+
const lower = name.toLowerCase();
|
|
12
|
+
return Object.keys(message.headers).some((k) => k.toLowerCase() === lower);
|
|
13
|
+
}
|
|
14
|
+
function getHeader(message, name) {
|
|
15
|
+
const lower = name.toLowerCase();
|
|
16
|
+
for (const [k, v] of Object.entries(message.headers)) {
|
|
17
|
+
if (k.toLowerCase() === lower)
|
|
18
|
+
return Array.isArray(v) ? v.join(", ") : v;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Verify the HTTP message signature on `message`. Never throws — every failure
|
|
24
|
+
* is `{ valid: false, reason }` with a stable {@link SignatureFailureReason}.
|
|
25
|
+
*/
|
|
26
|
+
export async function verifyMessage(message, params) {
|
|
27
|
+
const profile = params.profile ??
|
|
28
|
+
(hasHeader(message, "signature-input") ? "rfc9421" : "cavage");
|
|
29
|
+
const result = profile === "rfc9421"
|
|
30
|
+
? await verifyRfc9421(message, {
|
|
31
|
+
resolveKey: params.resolveKey,
|
|
32
|
+
label: params.label,
|
|
33
|
+
requiredComponents: params.requiredComponents,
|
|
34
|
+
requireCreated: params.requireCreated,
|
|
35
|
+
now: params.now,
|
|
36
|
+
toleranceSeconds: params.toleranceSeconds,
|
|
37
|
+
})
|
|
38
|
+
: await verifyCavage(message, {
|
|
39
|
+
resolveKey: params.resolveKey,
|
|
40
|
+
requiredComponents: params.requiredComponents,
|
|
41
|
+
now: params.now,
|
|
42
|
+
toleranceSeconds: params.toleranceSeconds,
|
|
43
|
+
});
|
|
44
|
+
if (!result.valid || params.body === undefined)
|
|
45
|
+
return result;
|
|
46
|
+
// Body integrity: only when a digest component was actually covered.
|
|
47
|
+
const covered = new Set((result.coveredComponents ?? []).map((c) => c.toLowerCase()));
|
|
48
|
+
if (covered.has("content-digest")) {
|
|
49
|
+
const rejection = await verifyContentDigest(getHeader(message, "content-digest"), params.body);
|
|
50
|
+
if (rejection !== null)
|
|
51
|
+
return { ...result, valid: false, reason: rejection };
|
|
52
|
+
}
|
|
53
|
+
else if (covered.has("digest")) {
|
|
54
|
+
const rejection = await verifyDigest(getHeader(message, "digest"), params.body);
|
|
55
|
+
if (rejection !== null)
|
|
56
|
+
return { ...result, valid: false, reason: rejection };
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dwk/http-signatures",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "HTTP Message Signatures (RFC 9421) and legacy draft-cavage sign/verify. Cross-standard reusable; no Workers runtime dependency.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"http-signatures",
|
|
7
|
+
"rfc9421",
|
|
8
|
+
"cavage",
|
|
9
|
+
"message-signatures",
|
|
10
|
+
"activitypub",
|
|
11
|
+
"cloudflare-workers"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"author": "David W. Keith <me@dwk.io>",
|
|
16
|
+
"homepage": "https://github.com/davidwkeith/workers/tree/main/packages/http-signatures#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/davidwkeith/workers.git",
|
|
20
|
+
"directory": "packages/http-signatures"
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"src",
|
|
34
|
+
"!src/**/*.test.ts"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.build.json",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
42
|
+
"clean": "rm -rf dist"
|
|
43
|
+
}
|
|
44
|
+
}
|