Skip to content

Instantly share code, notes, and snippets.

@max-lt
Last active June 2, 2023 17:22
Show Gist options
  • Save max-lt/cbb0d71126c6ff603f50f6e8e5e593d2 to your computer and use it in GitHub Desktop.
Save max-lt/cbb0d71126c6ff603f50f6e8e5e593d2 to your computer and use it in GitHub Desktop.
Signatures with browser crypto APIs
/// <reference lib="webworker" />
/// <reference lib="es2017" />
declare const env: {
GITHUB_SECRET: string;
};
if (!env.GITHUB_SECRET) {
throw new Error("Invalid or missing GITHUB_SECRET");
}
const ghSecret = Uint8Array.from(env.GITHUB_SECRET, (c) => c.charCodeAt(0));
function fromHex(hex: string): Uint8Array {
return new Uint8Array(
hex.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16))
);
}
async function verifySignature(request: Request) {
const sigHead = request.headers.get("X-Hub-Signature-256") ?? null;
if (!sigHead) {
throw new UnauthorizedError("Missing X-Hub-Signature-256 header");
}
// Remove 'sha256=' prefix and convert hex string to Uint8Array
const sig = fromHex(sigHead.slice(7));
const opt: KeyUsage[] = ["verify"];
const alg: HmacImportParams = { name: "HMAC", hash: "SHA-256" };
const key = await crypto.subtle.importKey("raw", ghSecret, alg, false, opt);
const buf = await request.clone().arrayBuffer();
const verified = await crypto.subtle.verify(alg, key, sig, buf);
if (!verified) {
throw new UnauthorizedError("Invalid signature");
}
}
/// <reference lib="webworker" />
/// <reference lib="es2017" />
declare const env: {
S3_ENDPOINT: string;
S3_REGION: string;
ACCESS_KEY: string;
SECRET_KEY: string;
BUCKET_NAME: string;
};
async function HMAC(key: string, message: string) {
const k = Uint8Array.from(key, (c) => c.charCodeAt(0));
const m = Uint8Array.from(message, (c) => c.charCodeAt(0));
const algorithm = { name: 'HMAC', hash: 'SHA-1' };
const cryptoKey = await crypto.subtle.importKey('raw', k, algorithm, true, ['sign', 'verify']);
const signature = await crypto.subtle.sign(algorithm, cryptoKey, m);
return btoa(String.fromCharCode(...new Uint8Array(signature)));
}
const ACCESS_KEY = env.ACCESS_KEY;
const SECRET_KEY = env.SECRET_KEY;
const BUCKET_NAME = env.BUCKET_NAME;
async function getObject(key: string, head = false) {
const url = new URL(`${S3_ENDPOINT}/${BUCKET_NAME}/${key}`);
const headers = new Headers();
const date = new Date().toUTCString();
headers.set('Date', date);
const method = head ? 'HEAD' : 'GET';
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html
const data = `${method}\n\n\n${date}\n/${env.BUCKET_NAME}/${key}`;
const signature = await HMAC(SECRET_KEY, data);
const authorization = `AWS ${ACCESS_KEY}:${signature}`;
headers.set('Authorization', authorization);
console.log(`Fetching ${url.pathname} (${method})`);
return fetch(url.toString(), { headers, method })
.then((res) => {
if (res.status === 200) {
return res;
}
throw res;
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment