From f2e2d8f8fd396557bb67efd7a83eae2ee4d0789b Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sat, 22 Oct 2022 00:24:33 +0300 Subject: [PATCH] Things are progressing --- .../content/keepassxc-browser.js | 5 +- keepassxc-browser/content/webauthn.js | 115 ++++++++++++++---- 2 files changed, 92 insertions(+), 28 deletions(-) diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js index 1c7699a..5fda99a 100755 --- a/keepassxc-browser/content/keepassxc-browser.js +++ b/keepassxc-browser/content/keepassxc-browser.js @@ -845,6 +845,7 @@ browser.runtime.onMessage.addListener(async function(req, sender) { }); // Webauthn +// TODO: Add an option for this. Don't do it for every page. const webauthn = document.createElement('script'); webauthn.src = browser.runtime.getURL('content/webauthn.js'); document.documentElement.appendChild(webauthn); @@ -855,8 +856,8 @@ window.addEventListener('message', async (ev) => { console.log(ev.data.publicKey); const ret = await sendMessage('webauthn-register', [ ev.data.publicKey, ev.origin ]); - if (ret.response) { - window.postMessage({ action: 'webauthn-create-response', data: ret.response }, window.location.origin); + if (ret) { + window.postMessage({ action: 'webauthn-create-response', response: ret }, window.location.origin); } } }); diff --git a/keepassxc-browser/content/webauthn.js b/keepassxc-browser/content/webauthn.js index 3e8b372..92bad76 100644 --- a/keepassxc-browser/content/webauthn.js +++ b/keepassxc-browser/content/webauthn.js @@ -9,7 +9,7 @@ const postMessageToExtension = function(request) { const handler = (msg) => { if (msg && msg.data && msg.data.action === messageRequest + '-response') { messageEvent.removeEventListener('message', listener); - resolve(msg.data.data); + resolve(msg.data.response); return; } }; @@ -30,47 +30,110 @@ const stringToArrayBuffer = function(str) { const arrayToArrayBuffer = function(arr) { const buf = Uint8Array.from(arr); return buf.buffer; -} +}; +// URL encoding needs some characters replaced const base64ToArrayBuffer = function(str) { - return stringToArrayBuffer(atob(str)); + return stringToArrayBuffer(window.atob(str.replaceAll('-', '+').replaceAll('_', '/'))); +}; + +// URL encoding needs some characters replaced +const arrayBufferToBase64 = function(arr) { + var clonedArr = new ArrayBuffer(arr.byteLength); + new Uint8Array(clonedArr).set(new Uint8Array(arr)); + + return window.btoa(clonedArr).replaceAll('\\', '-').replaceAll('/', '_').replaceAll('=', ''); +}; + +/*const arrayBufferToBase64HexString = function(arr) { + return window.btoa(arrayBufferToHexString(arr)); +};*/ + +const arrayBufferToHexString = function(arr) { + return [ ...new Uint8Array(arr) ].map(c => c.toString(16).padStart(2, '0')).join(''); +}; + +const buildCredentialCreationOptions = function(options) { + const publicKey = {}; + publicKey.attestation = options.publicKey.attestation; + publicKey.authenticatorSelection = options.publicKey.authenticatorSelection; + //publicKey.challenge = arrayBufferToBase64(options.publicKey.challenge); + publicKey.challenge = arrayBufferToHexString(options.publicKey.challenge); + publicKey.extensions = options.publicKey.extensions; + publicKey.pubKeyCredParams = options.publicKey.pubKeyCredParams; + publicKey.rp = options.publicKey.rp; + publicKey.timeout = options.publicKey.timeout; + + publicKey.excludeCredentials = []; + for (const cred of options.publicKey.excludeCredentials) { + const arr = { + //id: arrayBufferToBase64(cred.id), + id: arrayBufferToHexString(cred.id), + transports: cred.transports, + type: cred.type + }; + + publicKey.excludeCredentials.push(arr); + } + + publicKey.user = {}; + publicKey.user.displayName = options.publicKey.user.displayName; + //publicKey.user.id = arrayBufferToBase64(options.publicKey.user.id); + publicKey.user.id = arrayBufferToHexString(options.publicKey.user.id); + publicKey.user.name = options.publicKey.user.name; + + return publicKey; +}; + +const buildCredentialRequestOptions = function(options) { + const publicKey = {}; + + return publicKey; }; (async () => { + const originalCredentials = navigator.credentials; + const webauthnCredentials = { async create(options) { - console.log('Overridden create'); + if (options.publicKey) { + console.log(options.publicKey); + const publicKey = buildCredentialCreationOptions(options); + console.log(publicKey); + const response = await postMessageToExtension({ action: 'webauthn-create', publicKey: publicKey }); + if (response.length === 0 || response.response === 'canceled') { + return; + } - const publicKey = {}; - publicKey.attestation = options.publicKey.attestation; - publicKey.authenticatorSelection = options.publicKey.authenticatorSelection; - publicKey.challenge = window.btoa(options.publicKey.challenge); // Use b64 instead - publicKey.pubKeyCredParams = options.publicKey.pubKeyCredParams; - publicKey.rp = options.publicKey.rp; - publicKey.timeout = options.publicKey.timeout; - publicKey.user = options.publicKey.user; - publicKey.user.id = window.btoa(options.publicKey.user.id); // Use b64 instead + // Parse + const publicKeyCredential = response.response; + publicKeyCredential.rawId = base64ToArrayBuffer(publicKeyCredential.id); + publicKeyCredential.response.attestationObject.authData = arrayToArrayBuffer(publicKeyCredential.response.attestationObject.authData); + publicKeyCredential.response.clientDataJSON = stringToArrayBuffer(JSON.stringify(publicKeyCredential.response.clientDataJSON)); + publicKeyCredential.getClientExtensionResults = () => {}; + return publicKeyCredential; + } - const publicKeyCredential = await postMessageToExtension({ action: 'webauthn-create', publicKey: publicKey }); - console.log('Response: ', publicKeyCredential); - - // Parse the response and change needed variables from b64 to ArrayBuffer/UInt8Array etc. - publicKeyCredential.rawId = base64ToArrayBuffer(publicKeyCredential.id); - publicKeyCredential.response.attestationObject.authData = arrayToArrayBuffer(publicKeyCredential.response.attestationObject.authData); - publicKeyCredential.response.clientDataJSON = stringToArrayBuffer(JSON.stringify(publicKeyCredential.response.clientDataJSON)); - - console.log('Final response: ', publicKeyCredential); - return publicKeyCredential; + //const createResponse = await originalCredentials.create(options); + //console.log(createResponse); + //return createResponse; + return originalCredentials.create(options); }, async get(options) { - console.log('Overridden get'); + if (options.publicKey) { + console.log(options.publicKey); + + const publicKey = buildCredentialRequestOptions(options); + } + + return originalCredentials.get(options); } }; // Overwrite navigator.credentials try { - Object.assign(navigator.credentials, webauthnCredentials); + Object.defineProperty(navigator, 'credentials', { value: webauthnCredentials }); } catch (err) { - console.log(err); + console.log('Cannot override navigator.credentials: ', err); } })();