Last active
June 2, 2023 17:22
-
-
Save max-lt/cbb0d71126c6ff603f50f6e8e5e593d2 to your computer and use it in GitHub Desktop.
Signatures with browser crypto APIs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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