From: Ardis Lu Date: Thu, 31 Jul 2025 07:33:35 +0000 (-0700) Subject: Add Yubikey support (resident keys only) X-Git-Url: https://git.ardis.lu/?a=commitdiff_plain;h=c9bd16693aece7d01410750b8e96287d901167c2;p=static-encrypt-passkey Add Yubikey support (resident keys only) In the future, non-resident keys may be allowed by storing the credential `rawId` in `localStorage`. --- diff --git a/script.js b/script.js index b2cc09e..8a9a40d 100644 --- a/script.js +++ b/script.js @@ -1,5 +1,8 @@ /** - * Create a new passkey and get a pseudo-random value produced from the passkey PRF extension. + * Create a new resident passkey and get a pseudo-random value produced from the passkey PRF extension. + * + * If the passkey does not return a PRF value after creation, try to sign in immediately after passkey creation + * and get the PRF value from the sign in flow. * @see {@link https://www.w3.org/TR/webauthn-3/#prf-extension} * @returns {Promise} A 32 byte long `ArrayBuffer` containing a pseudo-random * value produced from the passkey PRF extension. @@ -8,12 +11,13 @@ async function createPrf() { return navigator.credentials.create({ publicKey: { rp: { name: '' }, - user: { id: new ArrayBuffer(1), name: '', displayName: '' }, + user: { id: new ArrayBuffer(1), name: crypto.randomUUID(), displayName: '' }, // Windows Hello requires non-empty id; Yubikey requires non-empty name pubKeyCredParams: [{ type: 'public-key', alg: -8 }, { type: 'public-key', alg: -7 }, { type: 'public-key', alg: -257 }], extensions: { prf: { eval: { first: new ArrayBuffer(0) } } }, - challenge: new ArrayBuffer(0) + challenge: new ArrayBuffer(0), + authenticatorSelection: { residentKey: 'required' } // Resident key is required because there is no server to store c.rawId } - }).then(c => c.getClientExtensionResults().prf.results.first); + }).then(c => c.getClientExtensionResults().prf?.results?.first ?? getPrf()); // Yubikey does not return PRF on creation, workaround is sign in immediately after creation } /** @@ -91,6 +95,7 @@ export async function encrypt(plaintext) { const info = encoder.encode('https://github.com/ardislu/static-encrypt-passkey'); const prf = await createPrf(); + const key = await getKey(prf, salt, info); const ciphertext = new Uint8Array(await crypto.subtle.encrypt( { name: 'AES-GCM', iv },