Skip to content

Commit 8dad5e3

Browse files
committed
Add signature with each key for directory responses
This adds a method to http-message-sig package so that directory responses can be signed as recommended in the draft. The draft change is pending and available in thibmeu/http-message-signatures-directory#40 This commit also updates the example worker to provide a signature over the directory. Tests are to be added. They could also serve as a basis to get test vectors in the directory draft.
1 parent 09fa8b3 commit 8dad5e3

File tree

6 files changed

+111
-20
lines changed

6 files changed

+111
-20
lines changed

examples/browser-extension/scripts/build_web_artifacts.mjs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ import * as fs from "node:fs";
1717
import path from "node:path";
1818
const { KeyObject } = await import("node:crypto");
1919
const { subtle } = globalThis.crypto;
20-
import pkg from '../package.json' with { type: "json" };
20+
import pkg from "../package.json" with { type: "json" };
2121

2222
function makePolicy(extensionID) {
23-
const MarkerString = "EXTENSION_ID_REPLACED_BY_NPM_RUN_BUNDLE_CHROME"
23+
const MarkerString = "EXTENSION_ID_REPLACED_BY_NPM_RUN_BUNDLE_CHROME";
2424
const policyPath = path.join(path.dirname("."), "policy");
2525
if (!fs.existsSync(policyPath)) {
2626
fs.mkdirSync(policyPath, { recursive: true });
@@ -37,8 +37,20 @@ function makePolicy(extensionID) {
3737
}
3838

3939
function setManifestVersion(version) {
40-
const manifestInputPath = path.join(path.dirname("."), "platform", "mv3", "chromium", 'manifest.json');
41-
const manifestOutputPath = path.join(path.dirname("."), "dist", "mv3", "chromium", 'manifest.json');
40+
const manifestInputPath = path.join(
41+
path.dirname("."),
42+
"platform",
43+
"mv3",
44+
"chromium",
45+
"manifest.json"
46+
);
47+
const manifestOutputPath = path.join(
48+
path.dirname("."),
49+
"dist",
50+
"mv3",
51+
"chromium",
52+
"manifest.json"
53+
);
4254
const manifestStr = fs.readFileSync(manifestInputPath, "utf8");
4355
const manifest = JSON.parse(manifestStr);
4456
manifest.version = version;
@@ -72,22 +84,22 @@ async function main() {
7284
});
7385

7486
const crx = new ChromeExtension({
75-
codebase: "http://localhost:8000/" + pkg.name + '.crx',
87+
codebase: "http://localhost:8000/" + pkg.name + ".crx",
7688
privateKey: skPEM,
7789
publicKey: pkBytes,
7890
});
7991

8092
setManifestVersion(pkg.version);
81-
await crx.load(path.join(path.dirname("."), "dist", "mv3", "chromium"))
93+
await crx.load(path.join(path.dirname("."), "dist", "mv3", "chromium"));
8294
const extensionBytes = await crx.pack();
8395
const extensionID = crx.generateAppId();
8496

8597
fs.writeFileSync("private_key.pem", skPEM);
86-
fs.writeFileSync(path.join(distPath, pkg.name + '.crx'), extensionBytes);
98+
fs.writeFileSync(path.join(distPath, pkg.name + ".crx"), extensionBytes);
8799
fs.writeFileSync(path.join(distPath, "update.xml"), crx.generateUpdateXML());
88100
makePolicy(extensionID);
89101

90-
console.log(`Build Extension with ID: ${extensionID}`)
91-
};
102+
console.log(`Build Extension with ID: ${extensionID}`);
103+
}
92104

93105
await main();

examples/verification-workers/src/index.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414

1515
import {
1616
Directory,
17+
HTTP_MESSAGE_SIGNATURES_DIRECTORY,
18+
MediaType,
19+
Signer,
1720
VerificationParams,
21+
directoryResponseHeaders,
1822
helpers,
1923
jwkToKeyID,
2024
signatureHeaders,
@@ -42,6 +46,10 @@ const getDirectory = async (): Promise<Directory> => {
4246
};
4347
};
4448

49+
const getSigner = async (): Promise<Signer> => {
50+
return Ed25519Signer.fromJWK(jwk);
51+
};
52+
4553
async function verifyEd25519(
4654
data: string,
4755
signature: Uint8Array,
@@ -84,12 +92,18 @@ export default {
8492
);
8593
}
8694

87-
if (
88-
url.pathname.startsWith("/.well-known/http-message-signatures-directory")
89-
) {
90-
return new Response(JSON.stringify(await getDirectory()), {
95+
if (url.pathname.startsWith(HTTP_MESSAGE_SIGNATURES_DIRECTORY)) {
96+
const directory = await getDirectory();
97+
98+
const signedHeaders = await directoryResponseHeaders(
99+
request,
100+
[await getSigner()],
101+
{ created: new Date(), expires: new Date(Date.now() + 300_000) }
102+
);
103+
return new Response(JSON.stringify(directory), {
91104
headers: {
92-
"content-type": "application/http-message-signatures-directory",
105+
...signedHeaders,
106+
"content-type": MediaType.HTTP_MESSAGE_SIGNATURES_DIRECTORY,
93107
},
94108
});
95109
}
@@ -118,11 +132,10 @@ export default {
118132
const request = new Request(env.TARGET_URL, { headers });
119133
const created = new Date(ctx.scheduledTime);
120134
const expires = new Date(created.getTime() + 300_000);
121-
const signedHeaders = await signatureHeaders(
122-
request,
123-
await Ed25519Signer.fromJWK(jwk),
124-
{ created, expires }
125-
);
135+
const signedHeaders = await signatureHeaders(request, await getSigner(), {
136+
created,
137+
expires,
138+
});
126139
await fetch(
127140
new Request(request.url, {
128141
headers: {
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
export const HTTP_MESSAGE_SIGNATURES_DIRECTORY =
2-
"./well-known/http-message-signatures-directory";
2+
"/.well-known/http-message-signatures-directory";
33

44
export enum MediaType {
55
HTTP_MESSAGE_SIGNATURES_DIRECTORY = "application/http-message-signatures-directory",
66
}
7+
8+
export enum Tag {
9+
HTTP_MESSAGE_SIGNAGURES_DIRECTORY = "http-message-signatures-directory",
10+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Tag } from "./consts";
2+
import { signatureHeaders } from "./sign";
3+
import { Component, RequestLike, SignatureHeaders, Signer } from "./types";
4+
5+
export const RESPONSE_COMPONENTS: Component[] = ["@authority"];
6+
7+
export interface SignatureParams {
8+
created: Date;
9+
expires: Date;
10+
}
11+
12+
export async function directoryResponseHeaders<T1 extends RequestLike>(
13+
request: T1, // request is used to derive @authority for the response
14+
signers: Signer[],
15+
params: SignatureParams
16+
): Promise<SignatureHeaders> {
17+
if (params.created.getTime() > params.expires.getTime()) {
18+
throw new Error("created should happen before expires");
19+
}
20+
21+
// TODO: consider validating the directory structure, and confirm we have one signer per key
22+
23+
const components: string[] = RESPONSE_COMPONENTS;
24+
25+
const headers = new Map<string, SignatureHeaders>();
26+
27+
for (let i = 0; i < signers.length; i += 1) {
28+
// eslint-disable-next-line security/detect-object-injection
29+
const signer = signers[i];
30+
if (headers.has(signer.keyid)) {
31+
throw new Error(`Duplicated signer with keyid ${signer.keyid}`);
32+
}
33+
34+
headers.set(
35+
signer.keyid,
36+
await signatureHeaders(request, {
37+
signer,
38+
components,
39+
created: params.created,
40+
expires: params.expires,
41+
keyid: signer.keyid,
42+
key: `binding${i}`,
43+
tag: Tag.HTTP_MESSAGE_SIGNAGURES_DIRECTORY,
44+
})
45+
);
46+
}
47+
48+
const SF_SEPARATOR = ", ";
49+
// Providing multiple signature as described in Section 4.3 of RFC 9421
50+
// https://datatracker.ietf.org/doc/html/rfc9421#name-multiple-signatures
51+
return {
52+
Signature: Array.from(headers.values())
53+
.map((h) => h.Signature)
54+
.join(SF_SEPARATOR),
55+
"Signature-Input": Array.from(headers.values())
56+
.map((h) => h["Signature-Input"])
57+
.join(SF_SEPARATOR),
58+
};
59+
}

packages/http-message-sig/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * as base64 from "./base64";
22
export { extractHeader } from "./build";
33
export * from "./consts";
4+
export * from "./directory";
45
export { parseAcceptSignatureHeader as parseAcceptSignature } from "./parse";
56
export * from "./sign";
67
export * from "./types";

packages/web-bot-auth/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export {
66
type SignatureHeaders,
77
type Signer,
88
type SignerSync,
9+
Tag,
10+
directoryResponseHeaders,
911
} from "http-message-sig";
1012
export { jwkThumbprint as jwkToKeyID } from "jsonwebkey-thumbprint";
1113

0 commit comments

Comments
 (0)