@algovoi/rfc9421-verifier 0.1.1 → 0.2.1
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/dist/parse.d.ts +8 -0
- package/dist/parse.js +4 -0
- package/dist/signing-base.d.ts +22 -0
- package/dist/signing-base.js +11 -1
- package/dist/verify.d.ts +10 -1
- package/dist/verify.js +7 -1
- package/package.json +62 -62
- package/src/parse.ts +148 -135
- package/src/signing-base.ts +139 -100
- package/src/verify.ts +256 -238
package/src/signing-base.ts
CHANGED
|
@@ -1,100 +1,139 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RFC 9421 Section 2.5 signing-base construction.
|
|
3
|
-
*
|
|
4
|
-
* Mirror of the Python algovoi_rfc9421_verifier.signing_base module.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export class SigningBaseError extends Error {
|
|
8
|
-
constructor(message: string) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = "SigningBaseError";
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
value =
|
|
93
|
-
break;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9421 Section 2.5 signing-base construction.
|
|
3
|
+
*
|
|
4
|
+
* Mirror of the Python algovoi_rfc9421_verifier.signing_base module.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class SigningBaseError extends Error {
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "SigningBaseError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type SigningBaseMode = "algovoi-v0" | "rfc9421";
|
|
15
|
+
|
|
16
|
+
export interface SigningBaseInput {
|
|
17
|
+
coveredComponents: string[];
|
|
18
|
+
method?: string;
|
|
19
|
+
authority?: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
targetUri?: string;
|
|
22
|
+
scheme?: string;
|
|
23
|
+
status?: number;
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
parameters?: Record<string, string | number>;
|
|
26
|
+
/**
|
|
27
|
+
* Signing-base mode.
|
|
28
|
+
* - "algovoi-v0" (default): preserves the v0.1.0 behaviour for
|
|
29
|
+
* backward compatibility with the AlgoVoi internal fixture and
|
|
30
|
+
* the rfc9421_proxy_chain_v0 conformance set. @method is
|
|
31
|
+
* lowercased and no @signature-params line is appended.
|
|
32
|
+
* - "rfc9421": full RFC 9421 §2.5 compliance. @method is preserved
|
|
33
|
+
* as-supplied (HTTP convention is uppercase), and a final
|
|
34
|
+
* "@signature-params" line is appended carrying
|
|
35
|
+
* signatureParamsRaw verbatim. This is the shape required to
|
|
36
|
+
* verify external fixtures (Envoys envoys-rfc9421, Hippo
|
|
37
|
+
* hippo-rfc9421, RFC 9421 §B test vectors, and any other
|
|
38
|
+
* RFC-compliant implementation).
|
|
39
|
+
*/
|
|
40
|
+
mode?: SigningBaseMode;
|
|
41
|
+
/**
|
|
42
|
+
* The post-label portion of the Signature-Input header value, i.e.
|
|
43
|
+
* the Inner List + parameters block exactly as it appeared on the
|
|
44
|
+
* wire. Required when mode is "rfc9421".
|
|
45
|
+
*/
|
|
46
|
+
signatureParamsRaw?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildSigningBase(input: SigningBaseInput): string {
|
|
50
|
+
const mode: SigningBaseMode = input.mode ?? "algovoi-v0";
|
|
51
|
+
if (mode !== "algovoi-v0" && mode !== "rfc9421") {
|
|
52
|
+
throw new SigningBaseError(
|
|
53
|
+
`mode must be "algovoi-v0" or "rfc9421", got ${JSON.stringify(mode)}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (mode === "rfc9421" && input.signatureParamsRaw === undefined) {
|
|
57
|
+
throw new SigningBaseError(
|
|
58
|
+
"rfc9421 mode requires signatureParamsRaw (the post-label portion of the Signature-Input header)",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const normHeaders: Record<string, string> = {};
|
|
63
|
+
for (const [k, v] of Object.entries(input.headers ?? {})) {
|
|
64
|
+
normHeaders[k.toLowerCase()] = v;
|
|
65
|
+
}
|
|
66
|
+
const parameters = input.parameters ?? {};
|
|
67
|
+
const lines: string[] = [];
|
|
68
|
+
|
|
69
|
+
for (const component of input.coveredComponents) {
|
|
70
|
+
const c = component.toLowerCase();
|
|
71
|
+
let value: string;
|
|
72
|
+
|
|
73
|
+
switch (c) {
|
|
74
|
+
case "@method":
|
|
75
|
+
if (input.method === undefined)
|
|
76
|
+
throw new SigningBaseError("@method covered but method not supplied");
|
|
77
|
+
value = mode === "rfc9421" ? input.method : input.method.toLowerCase();
|
|
78
|
+
break;
|
|
79
|
+
case "@authority":
|
|
80
|
+
if (input.authority === undefined)
|
|
81
|
+
throw new SigningBaseError("@authority covered but authority not supplied");
|
|
82
|
+
value = input.authority.toLowerCase();
|
|
83
|
+
break;
|
|
84
|
+
case "@path":
|
|
85
|
+
if (input.path === undefined)
|
|
86
|
+
throw new SigningBaseError("@path covered but path not supplied");
|
|
87
|
+
value = input.path;
|
|
88
|
+
break;
|
|
89
|
+
case "@target-uri":
|
|
90
|
+
if (input.targetUri === undefined)
|
|
91
|
+
throw new SigningBaseError("@target-uri covered but targetUri not supplied");
|
|
92
|
+
value = input.targetUri;
|
|
93
|
+
break;
|
|
94
|
+
case "@scheme":
|
|
95
|
+
if (input.scheme === undefined)
|
|
96
|
+
throw new SigningBaseError("@scheme covered but scheme not supplied");
|
|
97
|
+
value = input.scheme.toLowerCase();
|
|
98
|
+
break;
|
|
99
|
+
case "@status":
|
|
100
|
+
if (input.status === undefined)
|
|
101
|
+
throw new SigningBaseError("@status covered but status not supplied");
|
|
102
|
+
value = String(input.status);
|
|
103
|
+
break;
|
|
104
|
+
case "created":
|
|
105
|
+
if (!("created" in parameters))
|
|
106
|
+
throw new SigningBaseError(
|
|
107
|
+
"'created' covered but no 'created' parameter in Signature-Input",
|
|
108
|
+
);
|
|
109
|
+
value = String(parameters["created"]);
|
|
110
|
+
break;
|
|
111
|
+
case "expires":
|
|
112
|
+
if (!("expires" in parameters))
|
|
113
|
+
throw new SigningBaseError(
|
|
114
|
+
"'expires' covered but no 'expires' parameter in Signature-Input",
|
|
115
|
+
);
|
|
116
|
+
value = String(parameters["expires"]);
|
|
117
|
+
break;
|
|
118
|
+
default:
|
|
119
|
+
if (c.startsWith("@")) {
|
|
120
|
+
throw new SigningBaseError(`unsupported derived component: ${component}`);
|
|
121
|
+
}
|
|
122
|
+
if (!(c in normHeaders)) {
|
|
123
|
+
throw new SigningBaseError(
|
|
124
|
+
`covered header ${JSON.stringify(component)} not present in supplied headers`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
value = normHeaders[c];
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
lines.push(`"${c}": ${value}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (mode === "rfc9421") {
|
|
135
|
+
lines.push(`"@signature-params": ${input.signatureParamsRaw}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|