UNPKG

@pedwise/next-firebase-auth-edge

Version:

Next.js 13 Firebase Authentication for Edge and server runtimes. Dedicated for Next 13 server components. Compatible with Next.js middleware.

275 lines (235 loc) 6.77 kB
import { base64StringToByteArray } from "./jwt/utils"; function berToJavaScript(byteArray: Uint8Array): ASN1 { const result: Partial<ASN1> = {}; let position = 0; result.cls = getClass(); result.structured = getStructured(); result.tag = getTag(); let length = getLength(); // As encoded, which may be special value 0 if (length === 0x80) { length = 0; while ( byteArray[position + length] !== 0 || byteArray[position + length + 1] !== 0 ) { length += 1; } result.byteLength = position + length + 2; result.contents = byteArray.subarray(position, position + length); } else { result.byteLength = position + length; result.contents = byteArray.subarray(position, result.byteLength); } result.raw = byteArray.subarray(0, result.byteLength); // May not be the whole input array return result as ASN1; function getClass() { return (byteArray[position] & 0xc0) / 64; } function getStructured() { // Consumes no bytes return (byteArray[0] & 0x20) === 0x20; } function getTag() { let tag = byteArray[0] & 0x1f; position += 1; if (tag === 0x1f) { tag = 0; while (byteArray[position] >= 0x80) { tag = tag * 128 + byteArray[position] - 0x80; position += 1; } tag = tag * 128 + byteArray[position] - 0x80; position += 1; } return tag; } function getLength() { let length: number; if (byteArray[position] < 0x80) { length = byteArray[position]; position += 1; } else { const numberOfDigits = byteArray[position] & 0x7f; position += 1; length = 0; for (let i = 0; i < numberOfDigits; i++) { length = length * 256 + byteArray[position]; position += 1; } } return length; } } function berListToJavaScript(byteArray: Uint8Array): ASN1[] { const result = []; let nextPosition = 0; while (nextPosition < byteArray.length) { const nextPiece = berToJavaScript(byteArray.subarray(nextPosition)); result.push(nextPiece); nextPosition += nextPiece.byteLength; } return result; } interface ASN1 { cls: number; tag: number; structured: boolean; contents: Uint8Array; byteLength: number; raw: Uint8Array; } interface TBSCertificate { asn1: ASN1; version: ASN1; serialNumber: ASN1; signature: AlgorithmIdentifier; issuer: ASN1; validity: ASN1; subject: ASN1; subjectPublicKeyInfo: SPKI; } function parseTBSCertificate(asn1: ASN1): TBSCertificate { if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error("This can't be a TBSCertificate. Wrong data type."); } const pieces = berListToJavaScript(asn1.contents); if (pieces.length < 7) { throw new Error( "Bad TBS Certificate. There are fewer than the seven required children." ); } return { asn1, version: pieces[0], serialNumber: pieces[1], signature: parseAlgorithmIdentifier(pieces[2]), issuer: pieces[3], validity: pieces[4], subject: pieces[5], subjectPublicKeyInfo: parseSubjectPublicKeyInfo(pieces[6]), }; } interface AlgorithmIdentifier { asn1: ASN1; algorithm: string; parameters: { asn1: ASN1 } | null; } function parseAlgorithmIdentifier(asn1: ASN1): AlgorithmIdentifier { if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error("Bad algorithm identifier. Not a SEQUENCE."); } const pieces = berListToJavaScript(asn1.contents); if (pieces.length > 2) { throw new Error( "Bad algorithm identifier. Contains too many child objects." ); } const encodedAlgorithm = pieces[0]; if ( encodedAlgorithm.cls !== 0 || encodedAlgorithm.tag !== 6 || encodedAlgorithm.structured ) { throw new Error( "Bad algorithm identifier. Does not begin with an OBJECT IDENTIFIER." ); } return { asn1, algorithm: berObjectIdentifierValue(encodedAlgorithm.contents), parameters: pieces.length === 2 ? { asn1: pieces[1] } : null, }; } function berObjectIdentifierValue(byteArray: Uint8Array): string { let oid = Math.floor(byteArray[0] / 40) + "." + (byteArray[0] % 40); let position = 1; while (position < byteArray.length) { let nextInteger = 0; while (byteArray[position] >= 0x80) { nextInteger = nextInteger * 0x80 + (byteArray[position] & 0x7f); position += 1; } nextInteger = nextInteger * 0x80 + byteArray[position]; position += 1; oid += "." + nextInteger; } return oid; } interface SPKI { asn1: ASN1; algorithm: AlgorithmIdentifier; bits: Bits; } function parseSubjectPublicKeyInfo(asn1: ASN1): SPKI { if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error("Bad SPKI. Not a SEQUENCE."); } const pieces = berListToJavaScript(asn1.contents); if (pieces.length !== 2) { throw new Error("Bad SubjectPublicKeyInfo. Wrong number of child objects."); } return { asn1, algorithm: parseAlgorithmIdentifier(pieces[0]), bits: berBitStringValue(pieces[1].contents), }; } interface Bits { unusedBits: number; bytes: Uint8Array; } function berBitStringValue(byteArray: Uint8Array): Bits { return { unusedBits: byteArray[0], bytes: byteArray.subarray(1), }; } const parseSignatureAlgorithm = parseAlgorithmIdentifier; interface Sig { asn1: ASN1; bits: Bits; } function parseSignatureValue(asn1: ASN1): Sig { if (asn1.cls !== 0 || asn1.tag !== 3 || asn1.structured) { throw new Error("Bad signature value. Not a BIT STRING."); } return { asn1, bits: berBitStringValue(asn1.contents), }; } interface Cert { asn1: ASN1; tbsCertificate: TBSCertificate; signatureAlgorithm: AlgorithmIdentifier; signatureValue: Sig; } function parseCertificate(byteArray: Uint8Array): Cert { const asn1 = berToJavaScript(byteArray); if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error("This can't be an X.509 certificate. Wrong data type."); } const pieces = berListToJavaScript(asn1.contents); if (pieces.length !== 3) { throw new Error( "Certificate contains more than the three specified children." ); } return { asn1, tbsCertificate: parseTBSCertificate(pieces[0]), signatureAlgorithm: parseSignatureAlgorithm(pieces[1]), signatureValue: parseSignatureValue(pieces[2]), }; } export function pemToPublicKey(pem: string): Promise<CryptoKey> { const der = base64StringToByteArray(pem); const certificate = parseCertificate(der); return crypto.subtle.importKey( "spki", certificate.tbsCertificate.subjectPublicKeyInfo.asn1.raw, { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }, true, ["verify"] ); }