Files
feeddeck/supabase/functions/_cmd/tools/apple-secret-key.ts
Rico Berger b8fdbf17a2 Rework Edge Functions (#247)
Instead of using an `import_map.json` file to define the versions for
dependencies, they are now defined directly within the import. Since the
`import_map.json` file should not be used anymore and instead a
`deno.json` file per function should be used, we decided to define them
directly with the code. The overhead compared to a `deno.json` file per
function shouldn't be that large and it makes using functions in a
self-hosted setup easier.
2025-04-23 17:00:21 +02:00

83 lines
1.8 KiB
TypeScript

const base64URL = (value: string) => {
return globalThis
.btoa(value)
.replace(/[=]/g, "")
.replace(/[+]/g, "-")
.replace(/[\/]/g, "_");
};
const stringToArrayBuffer = (value: string): ArrayBuffer => {
const buf = new ArrayBuffer(value.length);
const bufView = new Uint8Array(buf);
for (let i = 0; i < value.length; i++) {
bufView[i] = value.charCodeAt(i);
}
return buf;
};
const arrayBufferToString = (buf: ArrayBuffer): string => {
return String.fromCharCode(...new Uint8Array(buf));
};
export const generateAppleSecretKey = async (
kid: string,
iss: string,
sub: string,
file: string,
): Promise<{ kid: string; jwt: string; exp: number }> => {
const contents = await Deno.readTextFile(file);
if (
!contents.match(/^\s*-+BEGIN PRIVATE KEY-+[^-]+-+END PRIVATE KEY-+\s*$/i)
) {
throw new Error(
`Chosen file does not appear to be a PEM encoded PKCS8 private key file.`,
);
}
// remove PEM headers and spaces
const pkcs8 = stringToArrayBuffer(
globalThis.atob(contents.replace(/-+[^-]+-+/g, "").replace(/\s+/g, "")),
);
const privateKey = await globalThis.crypto.subtle.importKey(
"pkcs8",
pkcs8,
{
name: "ECDSA",
namedCurve: "P-256",
},
true,
["sign"],
);
const iat = Math.floor(Date.now() / 1000);
const exp = iat + 180 * 24 * 60 * 60;
const jwt = [
base64URL(JSON.stringify({ typ: "JWT", kid, alg: "ES256" })),
base64URL(
JSON.stringify({
iss,
sub,
iat,
exp,
aud: "https://appleid.apple.com",
}),
),
];
const signature = await globalThis.crypto.subtle.sign(
{
name: "ECDSA",
hash: "SHA-256",
},
privateKey,
stringToArrayBuffer(jwt.join(".")),
);
jwt.push(base64URL(arrayBufferToString(signature)));
return { kid, jwt: jwt.join("."), exp };
};