mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
Inject passkeys scripts at document_start (#2432)
Load passkeys scripts at document_start
This commit is contained in:
parent
60355e8e1c
commit
78480bcf31
7 changed files with 298 additions and 234 deletions
21
dist/manifest_chromium.json
vendored
21
dist/manifest_chromium.json
vendored
|
|
@ -33,7 +33,7 @@
|
|||
"background": {
|
||||
"service_worker": "background/background_service.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
|
|
@ -45,7 +45,6 @@
|
|||
"js": [
|
||||
"common/browser-polyfill.min.js",
|
||||
"common/global.js",
|
||||
"common/global_ui.js",
|
||||
"common/sites.js",
|
||||
"content/ui.js",
|
||||
"content/banner.js",
|
||||
|
|
@ -61,11 +60,25 @@
|
|||
"content/pwgen.js",
|
||||
"content/totp-autocomplete.js",
|
||||
"content/totp-field.js",
|
||||
"content/username-field.js",
|
||||
"content/passkeys-utils.js"
|
||||
"content/username-field.js"
|
||||
],
|
||||
"run_at": "document_idle",
|
||||
"all_frames": true
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"exclude_matches": [
|
||||
"*://*/*.xml*",
|
||||
"file:///*.xml*"
|
||||
],
|
||||
"js": [
|
||||
"content/passkeys-inject.js",
|
||||
"content/passkeys-utils.js"
|
||||
],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true
|
||||
}
|
||||
],
|
||||
"commands": {
|
||||
|
|
|
|||
18
dist/manifest_firefox.json
vendored
18
dist/manifest_firefox.json
vendored
|
|
@ -73,11 +73,25 @@
|
|||
"content/pwgen.js",
|
||||
"content/totp-autocomplete.js",
|
||||
"content/totp-field.js",
|
||||
"content/username-field.js",
|
||||
"content/passkeys-utils.js"
|
||||
"content/username-field.js"
|
||||
],
|
||||
"run_at": "document_idle",
|
||||
"all_frames": true
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"exclude_matches": [
|
||||
"*://*/*.xml*",
|
||||
"file:///*.xml*"
|
||||
],
|
||||
"js": [
|
||||
"content/passkeys-inject.js",
|
||||
"content/passkeys-utils.js"
|
||||
],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true
|
||||
}
|
||||
],
|
||||
"commands": {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
const PASSKEYS_NO_LOGINS_FOUND = 15;
|
||||
const PASSKEYS_CREDENTIAL_IS_EXCLUDED = 21;
|
||||
const PASSKEYS_WAIT_FOR_LIFETIMER = 30;
|
||||
|
||||
// Contains already called method names
|
||||
const _called = {};
|
||||
_called.automaticRedetectCompleted = false;
|
||||
|
|
@ -863,83 +859,6 @@ kpxc.usePredefinedSites = function(currentLocation) {
|
|||
}
|
||||
};
|
||||
|
||||
// Apply a script to the page for intercepting Passkeys (WebAuthn) requests
|
||||
kpxc.enablePasskeys = function() {
|
||||
if (document?.documentElement?.ownerDocument?.contentType !== 'text/html') {
|
||||
return;
|
||||
}
|
||||
|
||||
const passkeys = document.createElement('script');
|
||||
passkeys.src = browser.runtime.getURL('content/passkeys.js');
|
||||
document.documentElement.appendChild(passkeys);
|
||||
|
||||
const startTimer = function(timeout) {
|
||||
return setTimeout(() => {
|
||||
throw new DOMException('lifetimeTimer has expired', 'NotAllowedError');
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
const stopTimer = function(lifetimeTimer) {
|
||||
if (lifetimeTimer) {
|
||||
clearTimeout(lifetimeTimer);
|
||||
}
|
||||
};
|
||||
|
||||
const letTimerRunOut = function (errorCode) {
|
||||
return (
|
||||
errorCode === PASSKEYS_WAIT_FOR_LIFETIMER ||
|
||||
errorCode === PASSKEYS_CREDENTIAL_IS_EXCLUDED ||
|
||||
errorCode === PASSKEYS_NO_LOGINS_FOUND
|
||||
);
|
||||
};
|
||||
|
||||
const sendResponse = async function(command, publicKey, callback) {
|
||||
const lifetimeTimer = startTimer(publicKey?.timeout);
|
||||
|
||||
const ret = await sendMessage(command, [ publicKey, window.location.origin ]);
|
||||
if (ret) {
|
||||
let errorMessage;
|
||||
if (ret.response && ret.response.errorCode) {
|
||||
errorMessage = await sendMessage('get_error_message', ret.response.errorCode);
|
||||
kpxcUI.createNotification('error', errorMessage);
|
||||
|
||||
if (kpxc.settings.passkeysFallback) {
|
||||
kpxcPasskeysUtils.sendPasskeysResponse(undefined, ret.response?.errorCode, errorMessage);
|
||||
} else if (letTimerRunOut(ret?.response?.errorCode)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logDebug('Passkey response', ret.response);
|
||||
kpxcPasskeysUtils.sendPasskeysResponse(ret.response, ret.response?.errorCode, errorMessage);
|
||||
stopTimer(lifetimeTimer);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('kpxc-passkeys-request', async (ev) => {
|
||||
if (!window.isSecureContext) {
|
||||
kpxcUI.createNotification('error', tr('errorMessagePasskeysContextIsNotSecure'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.detail.action === 'passkeys_create') {
|
||||
const publicKey = kpxcPasskeysUtils.buildCredentialCreationOptions(
|
||||
ev.detail.publicKey,
|
||||
ev.detail.sameOriginWithAncestors,
|
||||
);
|
||||
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('Passkey request', publicKey);
|
||||
await sendResponse('passkeys_get', publicKey);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Content script initialization.
|
||||
*/
|
||||
|
|
@ -964,10 +883,6 @@ const initContentScript = async function() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (kpxc.settings.passkeys) {
|
||||
kpxc.enablePasskeys();
|
||||
}
|
||||
|
||||
await kpxc.updateDatabaseState();
|
||||
await kpxc.initCredentialFields();
|
||||
|
||||
|
|
|
|||
109
keepassxc-browser/content/passkeys-inject.js
Normal file
109
keepassxc-browser/content/passkeys-inject.js
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
'use strict';
|
||||
|
||||
const PASSKEYS_NO_LOGINS_FOUND = 15;
|
||||
const PASSKEYS_CREDENTIAL_IS_EXCLUDED = 21;
|
||||
const PASSKEYS_WAIT_FOR_LIFETIMER = 30;
|
||||
|
||||
// Apply a script to the page for intercepting Passkeys (WebAuthn) requests
|
||||
const enablePasskeys = async function() {
|
||||
const passkeysLogDebug = function(message, extra) {
|
||||
if (kpxcPasskeysUtils.debugLogging) {
|
||||
debugLogMessage(message, extra);
|
||||
}
|
||||
};
|
||||
|
||||
const passkeys = document.createElement('script');
|
||||
passkeys.src = chrome.runtime.getURL('content/passkeys.js');
|
||||
document.documentElement.appendChild(passkeys);
|
||||
|
||||
const startTimer = function(timeout) {
|
||||
return setTimeout(() => {
|
||||
throw new DOMException('lifetimeTimer has expired', 'NotAllowedError');
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
const stopTimer = function(lifetimeTimer) {
|
||||
if (lifetimeTimer) {
|
||||
clearTimeout(lifetimeTimer);
|
||||
}
|
||||
};
|
||||
|
||||
const letTimerRunOut = function (errorCode) {
|
||||
return (
|
||||
errorCode === PASSKEYS_WAIT_FOR_LIFETIMER ||
|
||||
errorCode === PASSKEYS_CREDENTIAL_IS_EXCLUDED ||
|
||||
errorCode === PASSKEYS_NO_LOGINS_FOUND
|
||||
);
|
||||
};
|
||||
|
||||
const sendResponse = async function(command, publicKey, callback) {
|
||||
const lifetimeTimer = startTimer(publicKey?.timeout);
|
||||
|
||||
const ret = await chrome.runtime.sendMessage({ action: command, args: [ publicKey, window.location.origin ] });
|
||||
if (ret) {
|
||||
let errorMessage;
|
||||
if (ret.response && ret.response.errorCode) {
|
||||
errorMessage = await chrome.runtime.sendMessage({
|
||||
action: 'get_error_message',
|
||||
args: ret.response.errorCode,
|
||||
});
|
||||
kpxcUI.createNotification('error', errorMessage);
|
||||
|
||||
if (kpxcPasskeysUtils.passkeysFallback) {
|
||||
kpxcPasskeysUtils.sendPasskeysResponse(undefined, ret.response?.errorCode, errorMessage);
|
||||
} else if (letTimerRunOut(ret?.response?.errorCode)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
passkeysLogDebug('Passkey response', ret.response);
|
||||
kpxcPasskeysUtils.sendPasskeysResponse(ret.response, ret.response?.errorCode, errorMessage);
|
||||
stopTimer(lifetimeTimer);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('kpxc-passkeys-request', async (ev) => {
|
||||
if (!window.isSecureContext) {
|
||||
kpxcUI.createNotification('error', tr('errorMessagePasskeysContextIsNotSecure'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.detail.action === 'passkeys_create') {
|
||||
const publicKey = kpxcPasskeysUtils.buildCredentialCreationOptions(
|
||||
ev.detail.publicKey,
|
||||
ev.detail.sameOriginWithAncestors,
|
||||
);
|
||||
passkeysLogDebug('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,
|
||||
);
|
||||
passkeysLogDebug('Passkey request', publicKey);
|
||||
await sendResponse('passkeys_get', publicKey);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initContent = async () => {
|
||||
if (document?.documentElement?.ownerDocument?.contentType !== 'text/html'
|
||||
&& document?.documentElement?.ownerDocument?.contentType !== 'application/xhtml+xml'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = await chrome.runtime.sendMessage({ action: 'load_settings' });
|
||||
if (!settings) {
|
||||
console.log('Error: Cannot load extension settings');
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.passkeys) {
|
||||
kpxcPasskeysUtils.debugLogging = settings?.debugLogging;
|
||||
kpxcPasskeysUtils.passkeysFallback = settings?.passkeysFallback;
|
||||
enablePasskeys();
|
||||
}
|
||||
};
|
||||
|
||||
initContent();
|
||||
|
|
@ -46,8 +46,8 @@ const kpxcPasskeysUtils = {};
|
|||
// Sends response from KeePassXC back to the injected script
|
||||
kpxcPasskeysUtils.sendPasskeysResponse = function(publicKey, errorCode, errorMessage) {
|
||||
const response = errorCode
|
||||
? { errorCode: errorCode, errorMessage: errorMessage, fallback: kpxc.settings.passkeysFallback }
|
||||
: { publicKey: publicKey, fallback: kpxc.settings.passkeysFallback };
|
||||
? { errorCode: errorCode, errorMessage: errorMessage, fallback: kpxcPasskeysUtils?.passkeysFallback }
|
||||
: { publicKey: publicKey, fallback: kpxcPasskeysUtils?.passkeysFallback };
|
||||
const details = isFirefox() ? cloneInto(response, document.defaultView) : response;
|
||||
document.dispatchEvent(new CustomEvent('kpxc-passkeys-response', { detail: details }));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,145 +1,144 @@
|
|||
'use strict';
|
||||
|
||||
const PASSKEYS_ATTESTATION_NOT_SUPPORTED = 20;
|
||||
const PASSKEYS_CREDENTIAL_IS_EXCLUDED = 21;
|
||||
const PASSKEYS_REQUEST_CANCELED = 22;
|
||||
const PASSKEYS_INVALID_USER_VERIFICATION = 23;
|
||||
const PASSKEYS_EMPTY_PUBLIC_KEY = 24;
|
||||
const PASSKEYS_INVALID_URL_PROVIDED = 25;
|
||||
const PASSKEYS_ORIGIN_NOT_ALLOWED = 26;
|
||||
const PASSKEYS_DOMAIN_IS_NOT_VALID = 27;
|
||||
const PASSKEYS_DOMAIN_RPID_MISMATCH = 28;
|
||||
const PASSKEYS_NO_SUPPORTED_ALGORITHMS = 29;
|
||||
const PASSKEYS_WAIT_FOR_LIFETIMER = 30;
|
||||
const PASSKEYS_UNKNOWN_ERROR = 31;
|
||||
const PASSKEYS_INVALID_CHALLENGE = 32;
|
||||
const PASSKEYS_INVALID_USER_ID = 33;
|
||||
|
||||
const kpxcStringToArrayBuffer = function(str) {
|
||||
const arr = Uint8Array.from(str, c => c.charCodeAt(0));
|
||||
return arr.buffer;
|
||||
};
|
||||
|
||||
// From URL encoded base64 string to ArrayBuffer
|
||||
const kpxcBase64ToArrayBuffer = function(str) {
|
||||
return kpxcStringToArrayBuffer(window.atob(str?.replaceAll('-', '+').replaceAll('_', '/')));
|
||||
};
|
||||
|
||||
// Wraps response to AuthenticatorAttestationResponse object
|
||||
const createAttestationResponse = function(publicKey) {
|
||||
const response = {
|
||||
attestationObject: kpxcBase64ToArrayBuffer(publicKey.response.attestationObject),
|
||||
clientDataJSON: kpxcBase64ToArrayBuffer(publicKey.response.clientDataJSON),
|
||||
getAuthenticatorData: () => kpxcBase64ToArrayBuffer(publicKey.response?.authenticatorData),
|
||||
getPublicKey: () => null,
|
||||
getPublicKeyAlgorithm: () => publicKey.response?.publicKeyAlgorithm,
|
||||
getTransports: () => [ 'internal' ]
|
||||
};
|
||||
return Object.setPrototypeOf(response, AuthenticatorAttestationResponse.prototype);
|
||||
};
|
||||
|
||||
// Wraps response to AuthenticatorAssertionResponse object
|
||||
const createAssertionResponse = function(publicKey) {
|
||||
const response = {
|
||||
authenticatorData: kpxcBase64ToArrayBuffer(publicKey.response?.authenticatorData),
|
||||
clientDataJSON: kpxcBase64ToArrayBuffer(publicKey.response?.clientDataJSON),
|
||||
signature: kpxcBase64ToArrayBuffer(publicKey.response?.signature),
|
||||
userHandle: publicKey.response?.userHandle ? kpxcBase64ToArrayBuffer(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: kpxcBase64ToArrayBuffer(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) => {
|
||||
const ev = document;
|
||||
|
||||
const listener = ((messageEvent) => {
|
||||
const handler = (msg) => {
|
||||
if (msg && msg.type === 'kpxc-passkeys-response' && msg.detail) {
|
||||
messageEvent.removeEventListener('kpxc-passkeys-response', listener);
|
||||
resolve(msg.detail);
|
||||
return;
|
||||
}
|
||||
};
|
||||
return handler;
|
||||
})(ev);
|
||||
ev.addEventListener('kpxc-passkeys-response', listener);
|
||||
|
||||
// Send the request
|
||||
document.dispatchEvent(new CustomEvent('kpxc-passkeys-request', { detail: request }));
|
||||
});
|
||||
};
|
||||
|
||||
const isSameOriginWithAncestors = function() {
|
||||
try {
|
||||
return window.self.origin === window.top.origin;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Throws errors to a correct exceptions
|
||||
const throwError = function(errorCode, errorMessage) {
|
||||
if ((!errorCode && !errorMessage) || errorCode === PASSKEYS_REQUEST_CANCELED) {
|
||||
// No error or canceled by user. Stop the timer but throw no exception. Fallback with be called instead.
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode === PASSKEYS_WAIT_FOR_LIFETIMER || errorCode === PASSKEYS_CREDENTIAL_IS_EXCLUDED) {
|
||||
// Timer handled in the content script
|
||||
return;
|
||||
}
|
||||
|
||||
if ([ PASSKEYS_DOMAIN_RPID_MISMATCH, PASSKEYS_DOMAIN_IS_NOT_VALID ].includes(errorCode)) {
|
||||
throw new DOMException(errorMessage, DOMException.SECURITY_ERR);
|
||||
}
|
||||
|
||||
if (errorCode === PASSKEYS_NO_SUPPORTED_ALGORITHMS) {
|
||||
throw new DOMException(errorMessage, DOMException.NOT_SUPPORTED_ERR);
|
||||
}
|
||||
|
||||
if ([ PASSKEYS_INVALID_CHALLENGE, PASSKEYS_INVALID_USER_ID ].includes(errorCode)) {
|
||||
throw new TypeError(errorMessage);
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
PASSKEYS_ATTESTATION_NOT_SUPPORTED,
|
||||
PASSKEYS_INVALID_URL_PROVIDED,
|
||||
PASSKEYS_INVALID_USER_VERIFICATION,
|
||||
PASSKEYS_EMPTY_PUBLIC_KEY,
|
||||
PASSKEYS_UNKNOWN_ERROR,
|
||||
PASSKEYS_ORIGIN_NOT_ALLOWED,
|
||||
].includes(errorCode)
|
||||
) {
|
||||
throw new DOMException(errorMessage, 'NotAllowedError');
|
||||
}
|
||||
|
||||
throw new DOMException(errorMessage, 'UnknownError');
|
||||
};
|
||||
|
||||
|
||||
(async () => {
|
||||
const PASSKEYS_ATTESTATION_NOT_SUPPORTED = 20;
|
||||
const PASSKEYS_CREDENTIAL_IS_EXCLUDED = 21;
|
||||
const PASSKEYS_REQUEST_CANCELED = 22;
|
||||
const PASSKEYS_INVALID_USER_VERIFICATION = 23;
|
||||
const PASSKEYS_EMPTY_PUBLIC_KEY = 24;
|
||||
const PASSKEYS_INVALID_URL_PROVIDED = 25;
|
||||
const PASSKEYS_ORIGIN_NOT_ALLOWED = 26;
|
||||
const PASSKEYS_DOMAIN_IS_NOT_VALID = 27;
|
||||
const PASSKEYS_DOMAIN_RPID_MISMATCH = 28;
|
||||
const PASSKEYS_NO_SUPPORTED_ALGORITHMS = 29;
|
||||
const PASSKEYS_WAIT_FOR_LIFETIMER = 30;
|
||||
const PASSKEYS_UNKNOWN_ERROR = 31;
|
||||
const PASSKEYS_INVALID_CHALLENGE = 32;
|
||||
const PASSKEYS_INVALID_USER_ID = 33;
|
||||
|
||||
const kpxcStringToArrayBuffer = function(str) {
|
||||
const arr = Uint8Array.from(str, c => c.charCodeAt(0));
|
||||
return arr.buffer;
|
||||
};
|
||||
|
||||
// From URL encoded base64 string to ArrayBuffer
|
||||
const kpxcBase64ToArrayBuffer = function(str) {
|
||||
return kpxcStringToArrayBuffer(window.atob(str?.replaceAll('-', '+').replaceAll('_', '/')));
|
||||
};
|
||||
|
||||
// Wraps response to AuthenticatorAttestationResponse object
|
||||
const createAttestationResponse = function(publicKey) {
|
||||
const response = {
|
||||
attestationObject: kpxcBase64ToArrayBuffer(publicKey.response.attestationObject),
|
||||
clientDataJSON: kpxcBase64ToArrayBuffer(publicKey.response.clientDataJSON),
|
||||
getAuthenticatorData: () => kpxcBase64ToArrayBuffer(publicKey.response?.authenticatorData),
|
||||
getPublicKey: () => null,
|
||||
getPublicKeyAlgorithm: () => publicKey.response?.publicKeyAlgorithm,
|
||||
getTransports: () => [ 'internal' ]
|
||||
};
|
||||
return Object.setPrototypeOf(response, AuthenticatorAttestationResponse.prototype);
|
||||
};
|
||||
|
||||
// Wraps response to AuthenticatorAssertionResponse object
|
||||
const createAssertionResponse = function(publicKey) {
|
||||
const response = {
|
||||
authenticatorData: kpxcBase64ToArrayBuffer(publicKey.response?.authenticatorData),
|
||||
clientDataJSON: kpxcBase64ToArrayBuffer(publicKey.response?.clientDataJSON),
|
||||
signature: kpxcBase64ToArrayBuffer(publicKey.response?.signature),
|
||||
userHandle: publicKey.response?.userHandle ? kpxcBase64ToArrayBuffer(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: kpxcBase64ToArrayBuffer(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) => {
|
||||
const ev = document;
|
||||
|
||||
const listener = ((messageEvent) => {
|
||||
const handler = (msg) => {
|
||||
if (msg && msg.type === 'kpxc-passkeys-response' && msg.detail) {
|
||||
messageEvent.removeEventListener('kpxc-passkeys-response', listener);
|
||||
resolve(msg.detail);
|
||||
return;
|
||||
}
|
||||
};
|
||||
return handler;
|
||||
})(ev);
|
||||
ev.addEventListener('kpxc-passkeys-response', listener);
|
||||
|
||||
// Send the request
|
||||
document.dispatchEvent(new CustomEvent('kpxc-passkeys-request', { detail: request }));
|
||||
});
|
||||
};
|
||||
|
||||
const isSameOriginWithAncestors = function() {
|
||||
try {
|
||||
return window.self.origin === window.top.origin;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Throws errors to a correct exceptions
|
||||
const throwError = function(errorCode, errorMessage) {
|
||||
if ((!errorCode && !errorMessage) || errorCode === PASSKEYS_REQUEST_CANCELED) {
|
||||
// No error or canceled by user. Stop the timer but throw no exception. Fallback with be called instead.
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode === PASSKEYS_WAIT_FOR_LIFETIMER || errorCode === PASSKEYS_CREDENTIAL_IS_EXCLUDED) {
|
||||
// Timer handled in the content script
|
||||
return;
|
||||
}
|
||||
|
||||
if ([ PASSKEYS_DOMAIN_RPID_MISMATCH, PASSKEYS_DOMAIN_IS_NOT_VALID ].includes(errorCode)) {
|
||||
throw new DOMException(errorMessage, DOMException.SECURITY_ERR);
|
||||
}
|
||||
|
||||
if (errorCode === PASSKEYS_NO_SUPPORTED_ALGORITHMS) {
|
||||
throw new DOMException(errorMessage, DOMException.NOT_SUPPORTED_ERR);
|
||||
}
|
||||
|
||||
if ([ PASSKEYS_INVALID_CHALLENGE, PASSKEYS_INVALID_USER_ID ].includes(errorCode)) {
|
||||
throw new TypeError(errorMessage);
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
PASSKEYS_ATTESTATION_NOT_SUPPORTED,
|
||||
PASSKEYS_INVALID_URL_PROVIDED,
|
||||
PASSKEYS_INVALID_USER_VERIFICATION,
|
||||
PASSKEYS_EMPTY_PUBLIC_KEY,
|
||||
PASSKEYS_UNKNOWN_ERROR,
|
||||
PASSKEYS_ORIGIN_NOT_ALLOWED,
|
||||
].includes(errorCode)
|
||||
) {
|
||||
throw new DOMException(errorMessage, 'NotAllowedError');
|
||||
}
|
||||
|
||||
throw new DOMException(errorMessage, 'UnknownError');
|
||||
};
|
||||
|
||||
const originalCredentials = navigator.credentials;
|
||||
|
||||
const passkeysCredentials = {
|
||||
|
|
|
|||
|
|
@ -74,11 +74,25 @@
|
|||
"content/pwgen.js",
|
||||
"content/totp-autocomplete.js",
|
||||
"content/totp-field.js",
|
||||
"content/username-field.js",
|
||||
"content/passkeys-utils.js"
|
||||
"content/username-field.js"
|
||||
],
|
||||
"run_at": "document_idle",
|
||||
"all_frames": true
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"exclude_matches": [
|
||||
"*://*/*.xml*",
|
||||
"file:///*.xml*"
|
||||
],
|
||||
"js": [
|
||||
"content/passkeys-inject.js",
|
||||
"content/passkeys-utils.js"
|
||||
],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true
|
||||
}
|
||||
],
|
||||
"commands": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue