mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
Add support for Related Origin Requests with passkeys (#2828)
This commit is contained in:
parent
8d4e46882d
commit
bf0969aefe
4 changed files with 74 additions and 18 deletions
2
dist/manifest_chromium.json
vendored
2
dist/manifest_chromium.json
vendored
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "KeePassXC-Browser",
|
"name": "KeePassXC-Browser",
|
||||||
"version": "1.9.11",
|
"version": "1.9.11",
|
||||||
"version_name": "1.9.11",
|
"version_name": "1.9.11",
|
||||||
"minimum_chrome_version": "93",
|
"minimum_chrome_version": "124",
|
||||||
"description": "__MSG_extensionDescription__",
|
"description": "__MSG_extensionDescription__",
|
||||||
"author": "KeePassXC Team",
|
"author": "KeePassXC Team",
|
||||||
"icons": {
|
"icons": {
|
||||||
|
|
|
||||||
2
dist/manifest_firefox.json
vendored
2
dist/manifest_firefox.json
vendored
|
|
@ -185,7 +185,7 @@
|
||||||
"applications": {
|
"applications": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "keepassxc-browser@keepassxc.org",
|
"id": "keepassxc-browser@keepassxc.org",
|
||||||
"strict_min_version": "96.0"
|
"strict_min_version": "100.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,23 @@ keepass.featuresList = {
|
||||||
passkeysDefaultGroup: false,
|
passkeysDefaultGroup: false,
|
||||||
requiredKeePassXCVersionFound: false,
|
requiredKeePassXCVersionFound: false,
|
||||||
};
|
};
|
||||||
keepass.keyPair = { publicKey: null, secretKey: null };
|
keepass.cacheTimeout = 30 * 1000; // Milliseconds
|
||||||
keepass.serverPublicKey = '';
|
|
||||||
keepass.clientID = '';
|
keepass.clientID = '';
|
||||||
|
keepass.currentKeePassXC = '';
|
||||||
|
keepass.databaseHash = '';
|
||||||
keepass.isConnected = false;
|
keepass.isConnected = false;
|
||||||
keepass.isDatabaseClosed = true;
|
keepass.isDatabaseClosed = true;
|
||||||
keepass.isKeePassXCAvailable = false;
|
|
||||||
keepass.isEncryptionKeyUnrecognized = false;
|
keepass.isEncryptionKeyUnrecognized = false;
|
||||||
keepass.currentKeePassXC = '';
|
keepass.isKeePassXCAvailable = false;
|
||||||
keepass.requiredKeePassXC = '2.6.0';
|
keepass.keyPair = { publicKey: null, secretKey: null };
|
||||||
keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest';
|
keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest';
|
||||||
keepass.cacheTimeout = 30 * 1000; // Milliseconds
|
|
||||||
keepass.databaseHash = '';
|
|
||||||
keepass.previousDatabaseHash = '';
|
keepass.previousDatabaseHash = '';
|
||||||
keepass.reconnectLoop = null;
|
keepass.reconnectLoop = null;
|
||||||
|
keepass.requiredKeePassXC = '2.6.0';
|
||||||
|
keepass.serverPublicKey = '';
|
||||||
|
|
||||||
|
const DEFAULT_FETCH_TIMEOUT = 5000; // ms
|
||||||
|
const MAX_RELATED_ORIGIN_LABELS = 60;
|
||||||
|
|
||||||
const kpActions = {
|
const kpActions = {
|
||||||
SET_LOGIN: 'set-login',
|
SET_LOGIN: 'set-login',
|
||||||
|
|
@ -341,7 +344,13 @@ keepass.getDatabaseHash = async function(tab, args = []) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const request = keepassClient.buildRequest(kpAction, keepassClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock);
|
const request = keepassClient.buildRequest(
|
||||||
|
kpAction,
|
||||||
|
keepassClient.encrypt(messageData, nonce),
|
||||||
|
nonce,
|
||||||
|
keepass.clientID,
|
||||||
|
triggerUnlock,
|
||||||
|
);
|
||||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout);
|
const response = await keepassClient.sendNativeMessage(request, enableTimeout);
|
||||||
if (response.message && response.nonce) {
|
if (response.message && response.nonce) {
|
||||||
const res = keepassClient.decrypt(response.message, response.nonce);
|
const res = keepassClient.decrypt(response.message, response.nonce);
|
||||||
|
|
@ -616,11 +625,14 @@ keepass.passkeysRegister = async function(tab, args = []) {
|
||||||
const kpAction = kpActions.PASSKEYS_REGISTER;
|
const kpAction = kpActions.PASSKEYS_REGISTER;
|
||||||
const nonce = keepassClient.getNonce();
|
const nonce = keepassClient.getNonce();
|
||||||
const [ publicKey, origin ] = args;
|
const [ publicKey, origin ] = args;
|
||||||
|
const passkeyPublicKey = JSON.parse(JSON.stringify(publicKey));
|
||||||
|
const relatedOrigins = await keepass.getPasskeysRelatedOrigins(passkeyPublicKey?.rp?.id);
|
||||||
|
|
||||||
const messageData = {
|
const messageData = {
|
||||||
action: kpAction,
|
action: kpAction,
|
||||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
publicKey: passkeyPublicKey,
|
||||||
origin: origin,
|
origin: origin,
|
||||||
|
relatedOrigins: relatedOrigins,
|
||||||
groupName: page?.settings?.defaultPasskeyGroup,
|
groupName: page?.settings?.defaultPasskeyGroup,
|
||||||
keys: keepass.getCryptoKeys()
|
keys: keepass.getCryptoKeys()
|
||||||
};
|
};
|
||||||
|
|
@ -648,13 +660,15 @@ keepass.passkeysGet = async function(tab, args = []) {
|
||||||
|
|
||||||
const kpAction = kpActions.PASSKEYS_GET;
|
const kpAction = kpActions.PASSKEYS_GET;
|
||||||
const nonce = keepassClient.getNonce();
|
const nonce = keepassClient.getNonce();
|
||||||
const publicKey = args[0];
|
const [ publicKey, origin ] = args;
|
||||||
const origin = args[1];
|
const passkeyPublicKey = JSON.parse(JSON.stringify(publicKey));
|
||||||
|
const relatedOrigins = await keepass.getPasskeysRelatedOrigins(passkeyPublicKey?.rp?.id);
|
||||||
|
|
||||||
const messageData = {
|
const messageData = {
|
||||||
action: kpAction,
|
action: kpAction,
|
||||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
publicKey: passkeyPublicKey,
|
||||||
origin: origin,
|
origin: origin,
|
||||||
|
relatedOrigins: relatedOrigins,
|
||||||
keys: keepass.getCryptoKeys()
|
keys: keepass.getCryptoKeys()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -810,7 +824,9 @@ keepass.disableAutomaticReconnect = function() {
|
||||||
keepass.reconnect = async function(tab = null, connectionTimeout = 1500) {
|
keepass.reconnect = async function(tab = null, connectionTimeout = 1500) {
|
||||||
keepassClient.connectToNative();
|
keepassClient.connectToNative();
|
||||||
keepass.generateNewKeyPair();
|
keepass.generateNewKeyPair();
|
||||||
const keyChangeResult = await keepass.changePublicKeys(tab, !!connectionTimeout, connectionTimeout).catch(() => false);
|
const keyChangeResult = await keepass
|
||||||
|
.changePublicKeys(tab, !!connectionTimeout, connectionTimeout)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
// Change public keys timeout
|
// Change public keys timeout
|
||||||
if (!keyChangeResult) {
|
if (!keyChangeResult) {
|
||||||
|
|
@ -866,7 +882,9 @@ keepass.setcurrentKeePassXCVersion = function(version) {
|
||||||
keepass.keePassXCUpdateAvailable = async function() {
|
keepass.keePassXCUpdateAvailable = async function() {
|
||||||
const checkUpdate = Number(page.settings.checkUpdateKeePassXC);
|
const checkUpdate = Number(page.settings.checkUpdateKeePassXC);
|
||||||
if (checkUpdate !== CHECK_UPDATE_NEVER) {
|
if (checkUpdate !== CHECK_UPDATE_NEVER) {
|
||||||
const lastChecked = (keepass.latestKeePassXC.lastChecked) ? new Date(keepass.latestKeePassXC.lastChecked) : new Date(1986, 11, 21);
|
const lastChecked = keepass.latestKeePassXC.lastChecked
|
||||||
|
? new Date(keepass.latestKeePassXC.lastChecked)
|
||||||
|
: new Date(1986, 11, 21);
|
||||||
const daysSinceLastCheck = Math.floor(((new Date()).getTime() - lastChecked.getTime()) / 86400000);
|
const daysSinceLastCheck = Math.floor(((new Date()).getTime() - lastChecked.getTime()) / 86400000);
|
||||||
if (daysSinceLastCheck >= checkUpdate) {
|
if (daysSinceLastCheck >= checkUpdate) {
|
||||||
await keepass.checkForNewKeePassXCVersion();
|
await keepass.checkForNewKeePassXCVersion();
|
||||||
|
|
@ -882,7 +900,7 @@ keepass.checkForNewKeePassXCVersion = async function() {
|
||||||
let version = -1;
|
let version = -1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(keepass.latestVersionUrl);
|
const response = await fetch(keepass.latestVersionUrl, { signal: AbortSignal.timeout(DEFAULT_FETCH_TIMEOUT) });
|
||||||
const jsonData = await response.json();
|
const jsonData = await response.json();
|
||||||
if (jsonData?.tag_name && jsonData?.prerelease === false) {
|
if (jsonData?.tag_name && jsonData?.prerelease === false) {
|
||||||
version = jsonData.tag_name;
|
version = jsonData.tag_name;
|
||||||
|
|
@ -894,6 +912,44 @@ keepass.checkForNewKeePassXCVersion = async function() {
|
||||||
keepass.latestKeePassXC.lastChecked = new Date().valueOf();
|
keepass.latestKeePassXC.lastChecked = new Date().valueOf();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Implements retrieval of Related Origin Requests for passkeys
|
||||||
|
// https://www.w3.org/TR/webauthn-3/#sctn-related-origins
|
||||||
|
keepass.getPasskeysRelatedOrigins = async function(rpId) {
|
||||||
|
if (!rpId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`https://${rpId}/.well-known/webauthn`, {
|
||||||
|
signal: AbortSignal.timeout(DEFAULT_FETCH_TIMEOUT),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Basic reply validation, see: https://www.w3.org/TR/webauthn-3/#sctn-validating-relation-origin
|
||||||
|
const isJson = response?.headers?.get('content-type')?.includes('application/json');
|
||||||
|
if (!isJson) {
|
||||||
|
logError('getRelatedOrigins error: Content-Type is not JSON');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonData = await response.json();
|
||||||
|
if (!Array.isArray(jsonData?.origins)
|
||||||
|
|| jsonData?.origins?.length === 0
|
||||||
|
|| jsonData?.origins?.length > MAX_RELATED_ORIGIN_LABELS
|
||||||
|
|| !jsonData?.origins?.every((origin) => typeof origin === 'string')) {
|
||||||
|
logError(
|
||||||
|
`getRelatedOrigins error: origins is not a list of strings, or it exceeds the maximum count of ${MAX_RELATED_ORIGIN_LABELS}`,
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonData.origins;
|
||||||
|
} catch (ex) {
|
||||||
|
logError(`getRelatedOrigins error: ${ex}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
keepass.clearErrorMessage = function(tab) {
|
keepass.clearErrorMessage = function(tab) {
|
||||||
if (tab && page.tabs[tab.id]) {
|
if (tab && page.tabs[tab.id]) {
|
||||||
page.tabs[tab.id].errorMessage = undefined;
|
page.tabs[tab.id].errorMessage = undefined;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "KeePassXC-Browser",
|
"name": "KeePassXC-Browser",
|
||||||
"version": "1.9.11",
|
"version": "1.9.11",
|
||||||
"version_name": "1.9.11",
|
"version_name": "1.9.11",
|
||||||
"minimum_chrome_version": "93",
|
"minimum_chrome_version": "124",
|
||||||
"description": "__MSG_extensionDescription__",
|
"description": "__MSG_extensionDescription__",
|
||||||
"author": "KeePassXC Team",
|
"author": "KeePassXC Team",
|
||||||
"icons": {
|
"icons": {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue