From cc6d9cde69e721c11ab5e92e5a7af874f94ed67b Mon Sep 17 00:00:00 2001 From: varjolintu Date: Mon, 15 Apr 2024 06:36:18 +0300 Subject: [PATCH] Passkeys: Wrap response to PublicKeyCredential --- .../content/keepassxc-browser.js | 17 +++--- keepassxc-browser/content/passkeys-utils.js | 43 ------------- keepassxc-browser/content/passkeys.js | 61 ++++++++++++++++--- 3 files changed, 61 insertions(+), 60 deletions(-) diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js index c3a07de..9ee0e16 100755 --- a/keepassxc-browser/content/keepassxc-browser.js +++ b/keepassxc-browser/content/keepassxc-browser.js @@ -838,10 +838,7 @@ kpxc.enablePasskeys = function() { let errorMessage; if (ret.response && ret.response.errorCode) { errorMessage = await sendMessage('get_error_message', ret.response.errorCode); - // Do not create a notification for this error - if (ret?.response?.errorCode !== PASSKEYS_WAIT_FOR_LIFETIMER) { - kpxcUI.createNotification('error', errorMessage); - } + kpxcUI.createNotification('error', errorMessage); if (kpxc.settings.passkeysFallback) { kpxcPasskeysUtils.sendPasskeysResponse(undefined, ret.response?.errorCode, errorMessage); @@ -850,8 +847,8 @@ kpxc.enablePasskeys = function() { } } - const responsePublicKey = callback(ret.response); - kpxcPasskeysUtils.sendPasskeysResponse(responsePublicKey, ret.response?.errorCode, errorMessage); + logDebug('Passkey response', ret.response); + kpxcPasskeysUtils.sendPasskeysResponse(ret.response, ret.response?.errorCode, errorMessage); stopTimer(lifetimeTimer); } }; @@ -867,15 +864,15 @@ kpxc.enablePasskeys = function() { ev.detail.publicKey, ev.detail.sameOriginWithAncestors, ); - logDebug('publicKey', publicKey); - await sendResponse('passkeys_register', publicKey, kpxcPasskeysUtils.parsePublicKeyCredential); + logDebug('Passkey request', publicKey); + await sendResponse('passkeys_register', publicKey); } else if (ev.detail.action === 'passkeys_get') { const publicKey = kpxcPasskeysUtils.buildCredentialRequestOptions( ev.detail.publicKey, ev.detail.sameOriginWithAncestors, ); - logDebug('publicKey', publicKey); - await sendResponse('passkeys_get', publicKey, kpxcPasskeysUtils.parseGetPublicKeyCredential); + logDebug('Passkey request', publicKey); + await sendResponse('passkeys_get', publicKey); } }); }; diff --git a/keepassxc-browser/content/passkeys-utils.js b/keepassxc-browser/content/passkeys-utils.js index a12a93a..5440c7a 100644 --- a/keepassxc-browser/content/passkeys-utils.js +++ b/keepassxc-browser/content/passkeys-utils.js @@ -4,16 +4,6 @@ const MINIMUM_TIMEOUT = 15000; const DEFAULT_TIMEOUT = 30000; const DISCOURAGED_TIMEOUT = 120000; -const stringToArrayBuffer = function(str) { - const arr = Uint8Array.from(str, c => c.charCodeAt(0)); - return arr.buffer; -}; - -// From URL encoded base64 string to ArrayBuffer -const base64ToArrayBuffer = function(str) { - return stringToArrayBuffer(window.atob(str.replaceAll('-', '+').replaceAll('_', '/'))); -}; - // From ArrayBuffer to URL encoded base64 string const arrayBufferToBase64 = function(buf) { const str = [ ...new Uint8Array(buf) ].map(c => String.fromCharCode(c)).join(''); @@ -158,36 +148,3 @@ kpxcPasskeysUtils.buildCredentialRequestOptions = function(pkOptions, sameOrigin console.log(e); } }; - -// Parse register response back from base64 strings to ByteArrays -kpxcPasskeysUtils.parsePublicKeyCredential = function(publicKeyCredential) { - if (!publicKeyCredential || !publicKeyCredential.type) { - return undefined; - } - - publicKeyCredential.rawId = base64ToArrayBuffer(publicKeyCredential.id); - publicKeyCredential.response.attestationObject = - base64ToArrayBuffer(publicKeyCredential.response.attestationObject); - publicKeyCredential.response.clientDataJSON = base64ToArrayBuffer(publicKeyCredential.response.clientDataJSON); - - return publicKeyCredential; -}; - -// Parse authentication response back from base64 strings to ByteArrays -kpxcPasskeysUtils.parseGetPublicKeyCredential = function(publicKeyCredential) { - if (!publicKeyCredential || !publicKeyCredential.type) { - return undefined; - } - - publicKeyCredential.rawId = base64ToArrayBuffer(publicKeyCredential.id); - publicKeyCredential.response.authenticatorData = - base64ToArrayBuffer(publicKeyCredential.response.authenticatorData); - publicKeyCredential.response.clientDataJSON = base64ToArrayBuffer(publicKeyCredential.response.clientDataJSON); - publicKeyCredential.response.signature = base64ToArrayBuffer(publicKeyCredential.response.signature); - - if (publicKeyCredential.response.userHandle) { - publicKeyCredential.response.userHandle = base64ToArrayBuffer(publicKeyCredential.response.userHandle); - } - - return publicKeyCredential; -}; diff --git a/keepassxc-browser/content/passkeys.js b/keepassxc-browser/content/passkeys.js index d2fd106..a96128c 100644 --- a/keepassxc-browser/content/passkeys.js +++ b/keepassxc-browser/content/passkeys.js @@ -15,6 +15,58 @@ const PASSKEYS_UNKNOWN_ERROR = 31; const PASSKEYS_INVALID_CHALLENGE = 32; const PASSKEYS_INVALID_USER_ID = 33; +const stringToArrayBuffer = function(str) { + const arr = Uint8Array.from(str, c => c.charCodeAt(0)); + return arr.buffer; +}; + +// From URL encoded base64 string to ArrayBuffer +const base64ToArrayBuffer = function(str) { + return stringToArrayBuffer(window.atob(str?.replaceAll('-', '+').replaceAll('_', '/'))); +}; + +// Wraps response to AuthenticatorAttestationResponse object +const createAttestationResponse = function(publicKey) { + const response = { + attestationObject: base64ToArrayBuffer(publicKey.response.attestationObject), + clientDataJSON: base64ToArrayBuffer(publicKey.response.clientDataJSON), + getAuthenticatorData: () => base64ToArrayBuffer(publicKey.response?.authenticatorData), + getTransports: () => [ 'internal' ] + }; + + return Object.setPrototypeOf(response, AuthenticatorAttestationResponse.prototype); +}; + +// Wraps response to AuthenticatorAssertionResponse object +const createAssertionResponse = function(publicKey) { + const response = { + authenticatorData: base64ToArrayBuffer(publicKey.response?.authenticatorData), + clientDataJSON: base64ToArrayBuffer(publicKey.response?.clientDataJSON), + signature: base64ToArrayBuffer(publicKey.response?.signature), + userHandle: publicKey.response?.userHandle ? base64ToArrayBuffer(publicKey.response?.userHandle) : null + }; + + return Object.setPrototypeOf(response, AuthenticatorAssertionResponse.prototype); +}; + +// Wraps public key to PublicKeyCredential object +const createPublicKeyCredential = function(publicKey) { + const authenticatorResponse = publicKey?.response?.attestationObject + ? createAttestationResponse(publicKey) + : createAssertionResponse(publicKey); + const publicKeyCredential = { + authenticatorAttachment: publicKey.authenticatorAttachment, + id: publicKey.id, + rawId: base64ToArrayBuffer(publicKey.id), + response: authenticatorResponse, + type: publicKey.type, + clientExtensionResults: () => publicKey?.response?.clientExtensionResults || {}, + getClientExtensionResults: () => publicKey?.response?.clientExtensionResults || {} + }; + + return Object.setPrototypeOf(publicKeyCredential, PublicKeyCredential.prototype); +}; + // Posts a message to extension's content script and waits for response const postMessageToExtension = function(request) { return new Promise((resolve, reject) => { @@ -109,10 +161,7 @@ const throwError = function(errorCode, errorMessage) { return response.fallback ? originalCredentials.create(options) : null; } - response.publicKey.getClientExtensionResults = () => {}; - response.publicKey.clientExtensionResults = () => {}; - response.publicKey.response.getTransports = () => []; - return response.publicKey; + return createPublicKeyCredential(response.publicKey); }, async get(options) { if (!options.publicKey || options?.mediation === 'silent') { @@ -137,9 +186,7 @@ const throwError = function(errorCode, errorMessage) { return response.fallback ? originalCredentials.get(options) : null; } - response.publicKey.getClientExtensionResults = () => {}; - response.publicKey.clientExtensionResults = () => {}; - return response.publicKey; + return createPublicKeyCredential(response.publicKey); } };