123456789101112131415import { sendMessage } from "./App";16import type { PendingPasskeyRequest } from "../shared/types";1718function base64urlEncode(bytes: Uint8Array | ArrayBuffer): string {19 const u8 = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);20 let str = "";21 for (let i = 0; i < u8.length; i++) str += String.fromCharCode(u8[i]!);22 return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");23}2425function base64urlDecode(value: string): ArrayBuffer {26 const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((value.length + 3) % 4);27 const bin = atob(padded);28 const buf = new ArrayBuffer(bin.length);29 const view = new Uint8Array(buf);30 for (let i = 0; i < bin.length; i++) view[i] = bin.charCodeAt(i);31 return buf;32}3334export async function createFirefoxPasskey(35 pending: PendingPasskeyRequest36): Promise<{ ok: boolean; error?: string }> {37 if (!pending.challenge || !pending.userHandle) {38 return { ok: false, error: "Firefox pending request missing challenge or userHandle" };39 }4041 const challenge = base64urlDecode(pending.challenge);42 const userHandle = base64urlDecode(pending.userHandle);4344 let credential: PublicKeyCredential;45 try {46 const result = await navigator.credentials.create({47 publicKey: {48 challenge,49 rp: { id: pending.rpId, name: pending.rpName },50 user: {51 id: userHandle,52 name: pending.userName,53 displayName: pending.userDisplayName ?? pending.userName,54 },55 pubKeyCredParams: [56 { type: "public-key", alg: -7 }, 57 { type: "public-key", alg: -8 }, 58 ],59 authenticatorSelection: {60 residentKey: "preferred",61 userVerification: "preferred",62 },63 timeout: 60_000,64 },65 });66 if (!result || !(result instanceof PublicKeyCredential)) {67 return { ok: false, error: "no credential returned" };68 }69 credential = result;70 } catch (err) {71 if (err instanceof DOMException && err.name === "NotAllowedError") {72 return { ok: false, error: "user cancelled" };73 }74 return { ok: false, error: err instanceof Error ? err.message : "credential creation failed" };75 }7677 const response = credential.response as AuthenticatorAttestationResponse;78 const algorithm = response.getPublicKeyAlgorithm() === -8 ? -8 : -7;79 const publicKeySpki = response.getPublicKey();80 const transports = typeof response.getTransports === "function"81 ? response.getTransports()82 : ["internal"];8384 const completion = await sendMessage({85 type: "COMPLETE_FIREFOX_PASSKEY_CREATE",86 payload: {87 requestId: pending.requestId,88 rpId: pending.rpId,89 rpName: pending.rpName,90 userName: pending.userName,91 userDisplayName: pending.userDisplayName,92 userHandle: pending.userHandle,93 credentialId: credential.id,94 95 96 97 publicKeyCose: publicKeySpki ? base64urlEncode(publicKeySpki) : "",98 algorithm,99 transports,100 backupEligible: false,101 backupState: false,102 },103 });104105 if (!completion?.ok) {106 return { ok: false, error: completion?.error ?? "vault store failed" };107 }108 return { ok: true };109}