From f8ade5289b4473bc5378c86ddb6dde7ed307225f Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sun, 16 Jun 2024 09:47:51 -0600 Subject: [PATCH] save passkey DID in accounts, consolidate more data --- src/db/tables/accounts.ts | 6 +-- src/libs/didPeer.ts | 45 ++++++++++++++------- src/views/TestView.vue | 82 ++++++++++++++++++++++++++------------- 3 files changed, 88 insertions(+), 45 deletions(-) diff --git a/src/db/tables/accounts.ts b/src/db/tables/accounts.ts index a793f9c..de405fa 100644 --- a/src/db/tables/accounts.ts +++ b/src/db/tables/accounts.ts @@ -35,12 +35,12 @@ export type Account = { mnemonic?: string; /** - * The Webauthn credential ID, if this is from a passkey + * The Webauthn credential ID in hex, if this is from a passkey */ - passkeyCredId?: string; + passkeyCredIdHex?: string; /** - * The public key in hexadecimal format. + * The public key in hexadecimal format */ publicKeyHex: string; }; diff --git a/src/libs/didPeer.ts b/src/libs/didPeer.ts index dacd8e0..a395765 100644 --- a/src/libs/didPeer.ts +++ b/src/libs/didPeer.ts @@ -43,12 +43,12 @@ function arrayToBase64Url(anything: Uint8Array) { return toBase64Url(Buffer.from(anything).toString("base64")); } -export async function registerCredential(userId: Uint8Array) { +export async function registerCredential(passkeyName?: string) { const options: PublicKeyCredentialCreationOptionsJSON = await generateRegistrationOptions({ rpName: "Time Safari", rpID: window.location.hostname, - userName: "Current-User", + userName: passkeyName || "Time Safari User", // Don't prompt users for additional information about the authenticator // (Recommended for smoother UX) attestationType: "none", @@ -75,13 +75,17 @@ export async function registerCredential(userId: Uint8Array) { // https://chatgpt.com/share/78a5c91d-099d-46dc-aa6d-fc0c916509fa // https://chatgpt.com/share/3c13f061-6031-45bc-a2d7-3347c1e7a2d7 + const credIdBase64Url = verification.registrationInfo?.credentialID as string; + const credIdHex = Buffer.from( + base64URLStringToArrayBuffer(credIdBase64Url), + ).toString("hex"); const { publicKeyJwk } = cborToKeys( verification.registrationInfo?.credentialPublicKey as Uint8Array, ); return { authData: verification.registrationInfo?.attestationObject, - credId: verification.registrationInfo?.credentialID as string, + credIdHex: credIdHex, rawId: new Uint8Array(new Buffer(attResp.rawId, "base64")), publicKeyJwk: publicKeyJwk, publicKeyBytes: verification.registrationInfo @@ -110,16 +114,17 @@ export class PeerSetup { public clientDataJsonBase64Url?: Base64URLString; public signature?: Base64URLString; - public async createJwtSimplewebauthn( - fullPayload: object, - credentialId: string, - ) { + public async createJwtSimplewebauthn(fullPayload: object, credIdHex: string) { + const credentialId = arrayBufferToBase64URLString( + Buffer.from(credIdHex, "hex").buffer, + ); this.challenge = new Uint8Array(Buffer.from(JSON.stringify(fullPayload))); // const payloadHash: Uint8Array = sha256(this.challenge); const options: PublicKeyCredentialRequestOptionsJSON = await generateAuthenticationOptions({ challenge: this.challenge, rpID: window.location.hostname, + allowCredentials: [{ id: credentialId }], }); // console.log("simple authentication options", options); @@ -155,14 +160,21 @@ export class PeerSetup { return headerBase64 + "." + payloadBase64 + "." + signature; } - public async createJwtNavigator(fullPayload: object, credentialId: string) { + public async createJwtNavigator(fullPayload: object, credIdHex: string) { const dataToSignString = JSON.stringify(fullPayload); const dataToSignBuffer = Buffer.from(dataToSignString); + const credentialId = Buffer.from(credIdHex, "hex"); // console.log("lower credentialId", credentialId); this.challenge = new Uint8Array(dataToSignBuffer); const options = { publicKey: { + allowCredentials: [ + { + id: credentialId, + type: "public-key", + }, + ], challenge: this.challenge.buffer, rpID: window.location.hostname, userVerification: "preferred", @@ -173,11 +185,11 @@ export class PeerSetup { // console.log("nav credential get", credential); this.authenticatorData = credential?.response.authenticatorData; - const authenticatorDataBase64Url = bufferToBase64URLString( + const authenticatorDataBase64Url = arrayBufferToBase64URLString( this.authenticatorData, ); - this.clientDataJsonBase64Url = bufferToBase64URLString( + this.clientDataJsonBase64Url = arrayBufferToBase64URLString( credential?.response.clientDataJSON, ); @@ -268,12 +280,12 @@ export class PeerSetup { } // I'd love to use this but it doesn't verify. -// Pequires: +// Requires: // npm install @noble/curves // ... and this import: // import { p256 } from "@noble/curves/p256"; export async function verifyJwtP256( - credId: Base64URLString, + credIdHex: string, rawId: Uint8Array, did: string, authenticatorData: ArrayBuffer, @@ -302,7 +314,7 @@ export async function verifyJwtP256( } export async function verifyJwtSimplewebauthn( - credId: Base64URLString, + credIdHex: string, rawId: Uint8Array, did: string, authenticatorData: ArrayBuffer, @@ -312,6 +324,9 @@ export async function verifyJwtSimplewebauthn( ) { const authData = arrayToBase64Url(Buffer.from(authenticatorData)); const publicKeyBytes = peerDidToPublicKeyBytes(did); + const credId = arrayBufferToBase64URLString( + Buffer.from(credIdHex, "hex").buffer, + ); const authOpts: VerifyAuthenticationResponseOpts = { authenticator: { credentialID: credId, @@ -458,7 +473,7 @@ function base64urlEncode(buffer: ArrayBuffer) { } // from @simplewebauthn/browser -function bufferToBase64URLString(buffer) { +function arrayBufferToBase64URLString(buffer) { const bytes = new Uint8Array(buffer); let str = ""; for (const charCode of bytes) { @@ -469,7 +484,7 @@ function bufferToBase64URLString(buffer) { } // from @simplewebauthn/browser -function base64URLStringToBuffer(base64URLString) { +function base64URLStringToArrayBuffer(base64URLString: string) { const base64 = base64URLString.replace(/-/g, "+").replace(/_/g, "/"); const padLength = (4 - (base64.length % 4)) % 4; const padded = base64.padEnd(base64.length + padLength, "="); diff --git a/src/views/TestView.vue b/src/views/TestView.vue index a5c6ac6..e8bc171 100644 --- a/src/views/TestView.vue +++ b/src/views/TestView.vue @@ -172,6 +172,10 @@

Passkeys

+ See console for results. +
+ Active DID: {{ activeDid }} + {{ credIdHex ? "has passkey ID" : "has no passkey ID" }}
Register