diff --git a/packages/web-bot-auth/README.md b/packages/web-bot-auth/README.md index 46f10a9..6181487 100644 --- a/packages/web-bot-auth/README.md +++ b/packages/web-bot-auth/README.md @@ -29,6 +29,7 @@ More concrete examples are provided on [cloudflareresearch/web-bot-auth/examples ```typescript import { Algorithm, signatureHeaders } from "web-bot-auth"; +import { Ed25519Signer } from "web-bot-auth/crypto"; // The following simple request ios going to be signed const request = new Request("https://example.com"); @@ -42,44 +43,6 @@ const RFC_9421_ED25519_TEST_KEY = { x: "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs", }; -// Declare a signer for HTTP Message Signature -class Ed25519Signer { - public alg: Algorithm = "ed25519"; - public keyid: string; - private privateKey: CryptoKey; - - constructor(keyid: string, privateKey: CryptoKey) { - this.keyid = keyid; - this.privateKey = privateKey; - } - - static async fromJWK(jwk: JsonWebKey): Promise { - const key = await crypto.subtle.importKey( - "jwk", - jwk, - { name: "Ed25519" }, - true, - ["sign"] - ); - const keyid = await jwkToKeyID( - jwk, - helpers.WEBCRYPTO_SHA256, - helpers.BASE64URL_DECODE - ); - return new Ed25519Signer(keyid, key); - } - - async sign(data: string): Promise { - const message = new TextEncoder().encode(data); - const signature = await crypto.subtle.sign( - "ed25519", - this.privateKey, - message - ); - return new Uint8Array(signature); - } -} - const headers = signatureHeaders( request, Ed25519Signer.fromJWK(RFC_9421_ED25519_TEST_KEY), diff --git a/packages/web-bot-auth/package.json b/packages/web-bot-auth/package.json index ef84bee..4256157 100644 --- a/packages/web-bot-auth/package.json +++ b/packages/web-bot-auth/package.json @@ -10,10 +10,15 @@ "types": "./dist/index.d.ts", "require": "./dist/index.js", "import": "./dist/index.mjs" + }, + "./crypto": { + "types": "./dist/crypto.d.ts", + "require": "./dist/crypto.js", + "import": "./dist/crypto.mjs" } }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "build": "tsup src/index.ts src/crypto.ts --format cjs,esm --dts --clean", "generate-test-vectors": "node --experimental-transform-types scripts/test-vectors.ts test/test_data/web_bot_auth_architecture_v1.json", "prepublishOnly": "npm run build", "test": "vitest", diff --git a/packages/web-bot-auth/scripts/test-vectors.ts b/packages/web-bot-auth/scripts/test-vectors.ts index 478e843..4b4ce28 100644 --- a/packages/web-bot-auth/scripts/test-vectors.ts +++ b/packages/web-bot-auth/scripts/test-vectors.ts @@ -3,9 +3,9 @@ /// /// It takes one positional argument: [path] which is where the vectors should be written in JSON -const { generateNonce, helpers, jwkToKeyID, signatureHeaders } = await import( - "../src/index.ts" -); +const { generateNonce, signatureHeaders } = await import("../src/index.ts"); + +const { Ed25519Signer } = await import("../src/crypto.ts"); const fs = await import("fs"); const jwk = JSON.parse( @@ -15,43 +15,6 @@ const jwk = JSON.parse( const SIGNATURE_AGENT_DOMAIN = "signature-agent.test"; const ORIGIN_URL = "https://example.com/path/to/resource"; -class Ed25519Signer { - public alg: Algorithm = "ed25519"; - public keyid: string; - private privateKey: CryptoKey; - - constructor(keyid: string, privateKey: CryptoKey) { - this.keyid = keyid; - this.privateKey = privateKey; - } - - static async fromJWK(jwk: JsonWebKey): Promise { - const key = await crypto.subtle.importKey( - "jwk", - jwk, - { name: "Ed25519" }, - true, - ["sign"] - ); - const keyid = await jwkToKeyID( - jwk, - helpers.WEBCRYPTO_SHA256, - helpers.BASE64URL_DECODE - ); - return new Ed25519Signer(keyid, key); - } - - async sign(data: string): Promise { - const message = new TextEncoder().encode(data); - const signature = await crypto.subtle.sign( - "ed25519", - this.privateKey, - message - ); - return new Uint8Array(signature); - } -} - interface TestVector { key: JsonWebKey; target_url: string; diff --git a/packages/web-bot-auth/src/crypto.ts b/packages/web-bot-auth/src/crypto.ts new file mode 100644 index 0000000..942b3c7 --- /dev/null +++ b/packages/web-bot-auth/src/crypto.ts @@ -0,0 +1,46 @@ +import { type Algorithm } from "http-message-sig"; +import { jwkThumbprint as jwkToKeyID } from "jsonwebkey-thumbprint"; +import { b64ToB64NoPadding, b64ToB64URL, u8ToB64 } from "./base64"; + +export const helpers = { + WEBCRYPTO_SHA256: (b: BufferSource) => crypto.subtle.digest("SHA-256", b), + BASE64URL_DECODE: (u: ArrayBuffer) => + b64ToB64URL(b64ToB64NoPadding(u8ToB64(new Uint8Array(u)))), +}; + +export class Ed25519Signer { + public alg: Algorithm = "ed25519"; + public keyid: string; + private privateKey: CryptoKey; + + constructor(keyid: string, privateKey: CryptoKey) { + this.keyid = keyid; + this.privateKey = privateKey; + } + + static async fromJWK(jwk: JsonWebKey): Promise { + const key = await crypto.subtle.importKey( + "jwk", + jwk, + { name: "Ed25519" }, + true, + ["sign"] + ); + const keyid = await jwkToKeyID( + jwk, + helpers.WEBCRYPTO_SHA256, + helpers.BASE64URL_DECODE + ); + return new Ed25519Signer(keyid, key); + } + + async sign(data: string): Promise { + const message = new TextEncoder().encode(data); + const signature = await crypto.subtle.sign( + "ed25519", + this.privateKey, + message + ); + return new Uint8Array(signature); + } +} diff --git a/packages/web-bot-auth/src/index.ts b/packages/web-bot-auth/src/index.ts index 9751246..c49d505 100644 --- a/packages/web-bot-auth/src/index.ts +++ b/packages/web-bot-auth/src/index.ts @@ -7,7 +7,8 @@ export { } from "http-message-sig"; export { jwkThumbprint as jwkToKeyID } from "jsonwebkey-thumbprint"; -import { b64ToB64URL, b64ToB64NoPadding, b64Tou8, u8ToB64 } from "./base64"; +import { b64Tou8, u8ToB64 } from "./base64"; +export { helpers } from "./crypto"; export const HTTP_MESSAGE_SIGNAGURE_TAG = "web-bot-auth"; export const SIGNATURE_AGENT_HEADER = "signature-agent"; @@ -163,9 +164,3 @@ export function verify( export interface Directory extends httpsig.Directory { purpose: string; } - -export const helpers = { - WEBCRYPTO_SHA256: (b: BufferSource) => crypto.subtle.digest("SHA-256", b), - BASE64URL_DECODE: (u: ArrayBuffer) => - b64ToB64URL(b64ToB64NoPadding(u8ToB64(new Uint8Array(u)))), -}; diff --git a/packages/web-bot-auth/test/index.test.ts b/packages/web-bot-auth/test/index.test.ts index d7fd38b..fbfdf59 100644 --- a/packages/web-bot-auth/test/index.test.ts +++ b/packages/web-bot-auth/test/index.test.ts @@ -1,55 +1,17 @@ import { describe, it, expect } from "vitest"; import { generateNonce, - helpers, - jwkToKeyID, signatureHeaders, validateNonce, NONCE_LENGTH_IN_BYTES, SIGNATURE_AGENT_HEADER, } from "../src/index"; +import { Ed25519Signer } from "../src/crypto"; import { b64Tou8, u8ToB64 } from "../src/base64"; import vectors from "./test_data/web_bot_auth_architecture_v1.json"; type Vectors = (typeof vectors)[number]; -class Ed25519Signer { - public alg: Algorithm = "ed25519"; - public keyid: string; - private privateKey: CryptoKey; - - constructor(keyid: string, privateKey: CryptoKey) { - this.keyid = keyid; - this.privateKey = privateKey; - } - - static async fromJWK(jwk: JsonWebKey): Promise { - const key = await crypto.subtle.importKey( - "jwk", - jwk, - { name: "Ed25519" }, - true, - ["sign"] - ); - const keyid = await jwkToKeyID( - jwk, - helpers.WEBCRYPTO_SHA256, - helpers.BASE64URL_DECODE - ); - return new Ed25519Signer(keyid, key); - } - - async sign(data: string): Promise { - const message = new TextEncoder().encode(data); - const signature = await crypto.subtle.sign( - "ed25519", - this.privateKey, - message - ); - return new Uint8Array(signature); - } -} - describe.each(vectors)("Web-bot-auth-ed25519-Vector-%#", (v: Vectors) => { it("should pass IETF draft test vectors", async () => { const signer = await Ed25519Signer.fromJWK(v.key); diff --git a/packages/web-bot-auth/test/test_data/web_bot_auth_architecture_v1.json b/packages/web-bot-auth/test/test_data/web_bot_auth_architecture_v1.json index 21b72f9..beb6e15 100644 --- a/packages/web-bot-auth/test/test_data/web_bot_auth_architecture_v1.json +++ b/packages/web-bot-auth/test/test_data/web_bot_auth_architecture_v1.json @@ -32,4 +32,4 @@ "signature_input": "sig2=(\"@authority\" \"signature-agent\");created=1735689600;keyid=\"poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U\";alg=\"ed25519\";expires=1735693200;nonce=\"ZO3/XMEZjrvSnLtAP9M7jK0WGQf3J+pbmQRUpKDhF9/jsNCWqUh2sq+TH4WTX3/GpNoSZUa8eNWMKqxWp2/c2g==\";tag=\"web-bot-auth\"", "signature_agent": "signature-agent.test" } -] \ No newline at end of file +]