Skip to content

Commit

Permalink
fix: correct behaviour of functions in the web module to correspond…
Browse files Browse the repository at this point in the history
… to the one from the `node` module (#46)
  • Loading branch information
bytemain committed Oct 4, 2022
1 parent 10e070d commit 19eb9e5
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 40 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
"lint": "prettier --check '{src,test}/**/*' README.md package.json",
"lint:fix": "prettier --write '{src,test}/**/*' README.md package.json",
"pretest": "npm run -s lint",
"test": "npm run -s test:node && npm run -s test:deno && npm run -s test:browser",
"test": "npm run -s test:node && npm run -s test:web",
"test:node": "jest --coverage",
"test:web": "npm run test:deno && npm run test:browser",
"pretest:web": "npm run -s build",
"test:deno": "cd test/deno && deno test",
"pretest:browser": "npm run -s build",
"test:browser": "node test/browser-test.js"
},
"repository": "github:octokit/webhooks-methods.js",
Expand Down
11 changes: 1 addition & 10 deletions src/node/sign.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import { createHmac } from "crypto";
import { Algorithm, SignOptions } from "../types";
import { VERSION } from "../version";

export enum Algorithm {
SHA1 = "sha1",
SHA256 = "sha256",
}

type SignOptions = {
secret: string;
algorithm?: Algorithm | "sha1" | "sha256";
};

export async function sign(
options: SignOptions | string,
payload: string
Expand Down
5 changes: 1 addition & 4 deletions src/node/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { Buffer } from "buffer";

import { sign } from "./sign";
import { VERSION } from "../version";

const getAlgorithm = (signature: string) => {
return signature.startsWith("sha256=") ? "sha256" : "sha1";
};
import { getAlgorithm } from "../utils";

export async function verify(
secret: string,
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export enum Algorithm {
SHA1 = "sha1",
SHA256 = "sha256",
}

export type AlgorithmLike = Algorithm | "sha1" | "sha256";

export type SignOptions = {
secret: string;
algorithm?: AlgorithmLike;
};
3 changes: 3 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const getAlgorithm = (signature: string) => {
return signature.startsWith("sha256=") ? "sha256" : "sha1";
};
85 changes: 65 additions & 20 deletions src/web.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
const enc = new TextEncoder();

export async function sign(secret: string, data: string) {
const signature = await crypto.subtle.sign(
"HMAC",
await importKey(secret),
enc.encode(data)
);
return UInt8ArrayToHex(signature);
}
import { Algorithm, AlgorithmLike, SignOptions } from "./types";
import { getAlgorithm } from "./utils";

export async function verify(secret: string, data: string, signature: string) {
return await crypto.subtle.verify(
"HMAC",
await importKey(secret),
hexToUInt8Array(signature),
enc.encode(data)
);
}
const enc = new TextEncoder();

function hexToUInt8Array(string: string) {
// convert string to pairs of 2 characters
Expand All @@ -36,16 +21,76 @@ function UInt8ArrayToHex(signature: ArrayBuffer) {
.join("");
}

async function importKey(secret: string) {
function getHMACHashName(algorithm: AlgorithmLike) {
return (
{
[Algorithm.SHA1]: "SHA-1",
[Algorithm.SHA256]: "SHA-256",
} as { [key in Algorithm]: string }
)[algorithm];
}

async function importKey(secret: string, algorithm: AlgorithmLike) {
// ref: https://developer.mozilla.org/en-US/docs/Web/API/HmacImportParams
return crypto.subtle.importKey(
"raw", // raw format of the key - should be Uint8Array
enc.encode(secret),
{
// algorithm details
name: "HMAC",
hash: { name: "SHA-256" },
hash: { name: getHMACHashName(algorithm) },
},
false, // export = false
["sign", "verify"] // what this key can do
);
}

export async function sign(options: SignOptions | string, payload: string) {
const { secret, algorithm } =
typeof options === "object"
? {
secret: options.secret,
algorithm: options.algorithm || Algorithm.SHA256,
}
: { secret: options, algorithm: Algorithm.SHA256 };

if (!secret || !payload) {
throw new TypeError(
"[@octokit/webhooks-methods] secret & payload required for sign()"
);
}

if (!Object.values(Algorithm).includes(algorithm as Algorithm)) {
throw new TypeError(
`[@octokit/webhooks] Algorithm ${algorithm} is not supported. Must be 'sha1' or 'sha256'`
);
}

const signature = await crypto.subtle.sign(
"HMAC",
await importKey(secret, algorithm),
enc.encode(payload)
);

return `${algorithm}=${UInt8ArrayToHex(signature)}`;
}

export async function verify(
secret: string,
eventPayload: string,
signature: string
) {
if (!secret || !eventPayload || !signature) {
throw new TypeError(
"[@octokit/webhooks-methods] secret, eventPayload & signature required"
);
}

const algorithm = getAlgorithm(signature);
return await crypto.subtle.verify(
"HMAC",
await importKey(secret, algorithm),
hexToUInt8Array(signature.replace(`${algorithm}=`, "")),
enc.encode(eventPayload)
);
}
2 changes: 1 addition & 1 deletion test/browser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async function runTests() {

strictEqual(
signature,
"1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"
"sha256=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"
);
strictEqual(verified, true);

Expand Down
6 changes: 3 additions & 3 deletions test/deno/web.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { sign, verify } from "../../src/web.ts";
import { sign, verify } from "../../pkg/dist-web/index.js";

import { assertEquals } from "std/testing/asserts.ts";

Deno.test("sign", async () => {
const actual = await sign("secret", "data");
const expected =
"1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
"sha256=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
assertEquals(actual, expected);
});

Deno.test("verify", async () => {
const signature =
"1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
"sha256=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
const actual = await verify("secret", "data", signature);
const expected = true;
assertEquals(actual, expected);
Expand Down

0 comments on commit 19eb9e5

Please sign in to comment.