From a7e4092a93762e474b8cdfa96d608e2b93812de5 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Sat, 24 Aug 2024 19:48:37 +0200 Subject: [PATCH] feat(federation): :sparkles: Update signature code to Working Draft 4 of Versia --- bun.lockb | Bin 100844 -> 101612 bytes federation/cryptography/index.test.ts | 83 ++++++----------- federation/cryptography/index.ts | 126 +++++++++++--------------- package.json | 3 +- 4 files changed, 84 insertions(+), 128 deletions(-) diff --git a/bun.lockb b/bun.lockb index 8c77d39811ac2fff43490b91da49367393410a47..6c833b143520ad4f1971589b482ecea3aa47f9be 100755 GIT binary patch delta 18668 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 V?%hTYHI+up*N=y_q2*%9e*p&ZF~|S_ delta 18120 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 { let publicKey: CryptoKey; let body: string; let signature: string; - let date: string; + let nonce: string; beforeAll(async () => { const keys = await crypto.subtle.generateKey("Ed25519", true, [ @@ -25,8 +25,8 @@ describe("SignatureValidator", () => { "https://bob.org/users/6a18f2c3-120e-4949-bda4-2aa4c8264d51", ).sign("GET", new URL("https://example.com"), body); - signature = headers.get("Signature") ?? ""; - date = headers.get("Date") ?? ""; + signature = headers.get("X-Signature") ?? ""; + nonce = headers.get("X-Nonce") ?? ""; }); test("fromStringKey", async () => { @@ -46,8 +46,8 @@ describe("SignatureValidator", () => { const request = new Request("https://example.com", { method: "GET", headers: { - Signature: signature, - Date: date, + "X-Signature": signature, + "X-Nonce": nonce, }, body: body, }); @@ -55,47 +55,40 @@ describe("SignatureValidator", () => { expect(isValid).toBe(true); }); - test("should throw with an invalid signature", async () => { + test("should return false with an invalid signature", async () => { const request = new Request("https://example.com", { method: "GET", headers: { - Signature: "invalid", - Date: date, + "X-Signature": "invalid", + "X-Nonce": nonce, }, body: body, }); - expect(() => validator.validate(request)).toThrow(TypeError); + const isValid = await validator.validate(request); + + expect(isValid).toBe(false); }); - test("should throw with missing headers", async () => { + test("should throw with missing nonce", async () => { const request = new Request("https://example.com", { method: "GET", headers: { - Signature: signature, + "X-Signature": signature, }, body: body, }); - expect(() => validator.validate(request)).toThrow(TypeError); - }); - - test("should throw with missing date", async () => { - const request = new Request("https://example.com", { - method: "GET", - headers: { - Signature: signature, - }, - body: body, - }); - expect(() => validator.validate(request)).toThrow(TypeError); + expect(() => validator.validate(request)).toThrow( + "Headers are missing in request: X-Nonce", + ); }); test("should not verify a valid signature with a different body", async () => { const request = new Request("https://example.com", { method: "GET", headers: { - Signature: signature, - Date: date, + "X-Signature": signature, + "X-Nonce": nonce, }, body: "different", }); @@ -104,19 +97,19 @@ describe("SignatureValidator", () => { expect(isValid).toBe(false); }); - test("should not verify a signature with a wrong key", async () => { + test("should throw if signature is not base64", async () => { const request = new Request("https://example.com", { method: "GET", headers: { - Signature: - 'keyId="badbbadwrong",algorithm="ed25519",headers="(request-target) host date digest",signature="ohno"', - Date: date, + "X-Signature": "thisIsNotbase64OhNo$^รน", + "X-Nonce": nonce, }, body: body, }); - const isValid = await validator.validate(request); - expect(isValid).toBe(false); + expect(() => validator.validate(request)).toThrow( + "Signature is not valid base64", + ); }); }); }); @@ -158,27 +151,11 @@ describe("SignatureConstructor", () => { test("should correctly sign ", async () => { const url = new URL("https://example.com"); headers = (await ctor.sign("GET", url, body)).headers; - expect(headers.get("Signature")).toBeDefined(); - expect(headers.get("Date")).toBeDefined(); + expect(headers.get("X-Signature")).toBeDefined(); + expect(headers.get("X-Nonce")).toBeDefined(); - // Check structure of Signature - const signature = headers.get("Signature") ?? ""; - const parts = signature.split(","); - expect(parts).toHaveLength(4); - - expect(parts[0].split("=")[0]).toBe("keyId"); - expect(parts[1].split("=")[0]).toBe("algorithm"); - expect(parts[2].split("=")[0]).toBe("headers"); - expect(parts[3].split("=")[0]).toBe("signature"); - - expect(parts[0].split("=")[1]).toBe( - '"https://bob.org/users/6a18f2c3-120e-4949-bda4-2aa4c8264d51"', - ); - expect(parts[1].split("=")[1]).toBe('"ed25519"'); - expect(parts[2].split("=")[1]).toBe( - '"(request-target) host date digest"', - ); - expect(parts[3].split("=")[1]).toBeString(); + expect(headers.get("X-Nonce")?.length).toBeGreaterThan(10); + expect(headers.get("X-Signature")?.length).toBeGreaterThan(10); }); test("should correctly sign a Request", async () => { @@ -190,8 +167,8 @@ describe("SignatureConstructor", () => { const { request: newRequest } = await ctor.sign(request); headers = newRequest.headers; - expect(headers.get("Signature")).toBeDefined(); - expect(headers.get("Date")).toBeDefined(); + expect(headers.get("X-Signature")).toBeDefined(); + expect(headers.get("X-Nonce")).toBeDefined(); expect(await newRequest.text()).toBe(body); }); diff --git a/federation/cryptography/index.ts b/federation/cryptography/index.ts index 673d21d..fdaefb0 100644 --- a/federation/cryptography/index.ts +++ b/federation/cryptography/index.ts @@ -16,6 +16,9 @@ const base64ToArrayBuffer = (base64: string) => const arrayBufferToBase64 = (arrayBuffer: ArrayBuffer) => btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); +const uint8ArrayToBase64 = (uint8Array: Uint8Array) => + btoa(String.fromCharCode(...uint8Array)); + const checkEvironmentSupport = () => { // Check if WebCrypto is supported if (!globalThis.crypto?.subtle) { @@ -75,23 +78,23 @@ export class SignatureValidator { /** * Validates the signature of a request. * @param signature The signature string. - * @param date The date that the request was signed. + * @param nonce Signature nonce. * @param method The HTTP verb. * @param url The URL object. * @param body The request body. * @returns A Promise that resolves to a boolean indicating whether the signature is valid. * @throws TypeError if any required parameters are missing or empty. * @example - * const signature = "keyId=\"https://example.com\",algorithm=\"ed25519\",headers=\"(request-target) host date digest\",signature=\"base64Signature\""; - * const date = new Date("2021-01-01T00:00:00.000Z"); + * const signature = "k4QNt5Grl40KK8orIdiaq118Z+P5pa6vIeArq55wsvfL7wNy4cE3f2fhsGcpZql+PStm+x2ZjZIhudrAC/32Cg=="; + * const nonce = "bJzyhTNK2RXUCetKIpm0Fw=="; * const method = "GET"; * const url = new URL("https://example.com/users/ff54ee40-2ce9-4d2e-86ac-3cd06a1e1480"); * const body = "{ ... }"; - * const isValid = await validator.validate(signature, date, method, url, body); + * const isValid = await validator.validate(signature, nonce, method, url, body); */ async validate( signature: string, - date: Date, + nonce: string, method: HttpVerb, url: URL, body: string, @@ -99,28 +102,25 @@ export class SignatureValidator { async validate( requestOrSignature: Request | string, - date?: Date, + nonce?: string, method?: HttpVerb, url?: URL, body?: string, ): Promise { if (requestOrSignature instanceof Request) { - const signature = requestOrSignature.headers.get("Signature"); - const date = requestOrSignature.headers.get("Date"); + const signature = requestOrSignature.headers.get("X-Signature"); + const nonce = requestOrSignature.headers.get("X-Nonce"); const url = new URL(requestOrSignature.url); const body = await requestOrSignature.text(); const method = requestOrSignature.method as HttpVerb; const missingHeaders = [ - !signature && "Signature", - !date && "Date", - !method && "Method", - !url && "URL", - !body && "Body", + !signature && "X-Signature", + !nonce && "X-Nonce", ].filter(Boolean); // Check if all headers are present - if (!(signature && date && method && url && body)) { + if (!(signature && nonce && method && url && body)) { // Say which headers are missing throw new TypeError( `Headers are missing in request: ${missingHeaders.join( @@ -129,49 +129,30 @@ export class SignatureValidator { ); } - if (signature.split("signature=").length < 2) { - throw new TypeError( - "Invalid Signature header (wrong format or missing signature)", - ); - } - - const extractedSignature = signature - .split("signature=")[1] - .replace(/"/g, ""); - - if (!extractedSignature) { - throw new TypeError( - "Invalid Signature header (wrong format or missing signature)", - ); - } - - return this.validate( - extractedSignature, - new Date(date), - method as HttpVerb, - url, - body, - ); + return this.validate(signature, nonce, method, url, body); } - if (!(date && method && url && body)) { + if (!(nonce && method && url && body)) { throw new TypeError( - "Missing or empty required parameters: date, method, url or body", + "Missing or empty required parameters: nonce, method, url or body", ); } const signature = requestOrSignature; + // Check if signature is base64 + try { + atob(signature); + } catch { + throw new TypeError("Signature is not valid base64"); + } + const digest = await crypto.subtle.digest( "SHA-256", new TextEncoder().encode(body), ); - const expectedSignedString = - `(request-target): ${method.toLowerCase()} ${url.pathname}\n` + - `host: ${url.host}\n` + - `date: ${date.toISOString()}\n` + - `digest: SHA-256=${arrayBufferToBase64(digest)}\n`; + const expectedSignedString = `${method.toLowerCase()} ${encodeURIComponent(url.pathname)} ${nonce} ${arrayBufferToBase64(digest)}`; // Check if signed string is valid const isValid = await crypto.subtle.verify( @@ -193,15 +174,15 @@ export class SignatureConstructor { /** * Creates a new instance of SignatureConstructor. * @param privateKey The private key used for signature generation. - * @param keyId The key ID used for the Signature header. + * @param authorUri URI of the User who is signing the request. * @example * const privateKey = // CryptoKey - * const keyId = "https://example.com/users/6a18f2c3-120e-4949-bda4-2aa4c8264d51"; - * const constructor = new SignatureConstructor(privateKey, keyId); + * const authorUri = "https://example.com/users/6a18f2c3-120e-4949-bda4-2aa4c8264d51"; + * const constructor = new SignatureConstructor(privateKey, authorUri); */ constructor( private privateKey: CryptoKey, - private keyId: string, + private authorUri: URL | string, ) { checkEvironmentSupport(); } @@ -209,16 +190,16 @@ export class SignatureConstructor { /** * Creates a SignatureConstructor instance from a base64-encoded private key. * @param base64PrivateKey The base64-encoded private key. - * @param keyId The key ID used for the Signature header. + * @param authorUri URI of the User who is signing the request. * @returns A Promise that resolves to a SignatureConstructor instance. * @example * const privateKey = "base64PrivateKey"; - * const keyId = "https://example.com/users/6a18f2c3-120e-4949-bda4-2aa4c8264d51"; - * const constructor = await SignatureConstructor.fromStringKey(privateKey, keyId); + * const authorUri = "https://example.com/users/6a18f2c3-120e-4949-bda4-2aa4c8264d51"; + * const constructor = await SignatureConstructor.fromStringKey(privateKey, authorUri); */ static async fromStringKey( base64PrivateKey: string, - keyId: string, + authorUri: URL | string, ): Promise { return new SignatureConstructor( await crypto.subtle.importKey( @@ -228,7 +209,7 @@ export class SignatureConstructor { false, ["sign"], ), - keyId, + authorUri, ); } @@ -251,7 +232,7 @@ export class SignatureConstructor { * @param url The URL object. * @param body The request body. * @param headers The request headers. - * @param date The date that the request was signed (optional) + * @param nonce The signature nonce (optional). * @returns A Promise that resolves to the signed headers, and the signed string. * @throws TypeError if any required parameters are missing or empty. * @example @@ -265,7 +246,7 @@ export class SignatureConstructor { url: URL, body?: string, headers?: Headers, - date?: Date, + nonce?: string, ): Promise<{ headers: Headers; signedString: string; @@ -276,7 +257,7 @@ export class SignatureConstructor { url?: URL, body?: string, headers: Headers = new Headers(), - date?: Date, + nonce?: string, ): Promise< | { headers: Headers; @@ -295,13 +276,14 @@ export class SignatureConstructor { new URL(requestOrMethod.url), await requestOrMethod.text(), requestOrMethod.headers, - requestOrMethod.headers.get("Date") - ? new Date(requestOrMethod.headers.get("Date") ?? "") - : undefined, + requestOrMethod.headers.get("X-Nonce") ?? undefined, ); - request.headers.set("Date", headers.get("Date") ?? ""); - request.headers.set("Signature", headers.get("Signature") ?? ""); + request.headers.set("X-Nonce", headers.get("X-Nonce") ?? ""); + request.headers.set( + "X-Signature", + headers.get("X-Signature") ?? "", + ); return { request, signedString }; } @@ -312,20 +294,18 @@ export class SignatureConstructor { ); } - const finalDate = date?.toISOString() ?? new Date().toISOString(); + const finalNonce = + nonce || + uint8ArrayToBase64(crypto.getRandomValues(new Uint8Array(16))); const digest = await crypto.subtle.digest( "SHA-256", new TextEncoder().encode(body ?? ""), ); - const signedString = - `(request-target): ${requestOrMethod.toLowerCase()} ${ - url.pathname - }\n` + - `host: ${url.host}\n` + - `date: ${finalDate}\n` + - `digest: SHA-256=${arrayBufferToBase64(digest)}\n`; + const signedString = `${requestOrMethod.toLowerCase()} ${encodeURIComponent( + url.pathname, + )} ${finalNonce} ${arrayBufferToBase64(digest)}`; const signature = await crypto.subtle.sign( "Ed25519", @@ -335,11 +315,9 @@ export class SignatureConstructor { const signatureBase64 = arrayBufferToBase64(signature); - headers.set("Date", finalDate); - headers.set( - "Signature", - `keyId="${this.keyId}",algorithm="ed25519",headers="(request-target) host date digest",signature="${signatureBase64}"`, - ); + headers.set("X-Nonce", finalNonce); + headers.set("X-Signature", signatureBase64); + headers.set("X-Signed-By", this.authorUri.toString()); return { headers, diff --git a/package.json b/package.json index bb6a6be..82f9d58 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ }, "devDependencies": { "@biomejs/biome": "^1.8.3", - "@types/bun": "^1.1.6", + "@types/bun": "^1.1.7", + "@types/node": "^22.5.0", "bun-plugin-dts": "^0.2.3" }, "trustedDependencies": ["@biomejs/biome"],