Add support for Related Origin Requests with passkeys (#2828)

This commit is contained in:
Sami Vänttinen 2026-02-16 18:08:26 +02:00 committed by GitHub
parent 8d4e46882d
commit bf0969aefe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 74 additions and 18 deletions

View file

@ -3,7 +3,7 @@
"name": "KeePassXC-Browser",
"version": "1.9.11",
"version_name": "1.9.11",
"minimum_chrome_version": "93",
"minimum_chrome_version": "124",
"description": "__MSG_extensionDescription__",
"author": "KeePassXC Team",
"icons": {

View file

@ -185,7 +185,7 @@
"applications": {
"gecko": {
"id": "keepassxc-browser@keepassxc.org",
"strict_min_version": "96.0"
"strict_min_version": "100.0"
}
},
"storage": {

View file

@ -10,20 +10,23 @@ keepass.featuresList = {
passkeysDefaultGroup: false,
requiredKeePassXCVersionFound: false,
};
keepass.keyPair = { publicKey: null, secretKey: null };
keepass.serverPublicKey = '';
keepass.cacheTimeout = 30 * 1000; // Milliseconds
keepass.clientID = '';
keepass.currentKeePassXC = '';
keepass.databaseHash = '';
keepass.isConnected = false;
keepass.isDatabaseClosed = true;
keepass.isKeePassXCAvailable = false;
keepass.isEncryptionKeyUnrecognized = false;
keepass.currentKeePassXC = '';
keepass.requiredKeePassXC = '2.6.0';
keepass.isKeePassXCAvailable = false;
keepass.keyPair = { publicKey: null, secretKey: null };
keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest';
keepass.cacheTimeout = 30 * 1000; // Milliseconds
keepass.databaseHash = '';
keepass.previousDatabaseHash = '';
keepass.reconnectLoop = null;
keepass.requiredKeePassXC = '2.6.0';
keepass.serverPublicKey = '';
const DEFAULT_FETCH_TIMEOUT = 5000; // ms
const MAX_RELATED_ORIGIN_LABELS = 60;
const kpActions = {
SET_LOGIN: 'set-login',
@ -341,7 +344,13 @@ keepass.getDatabaseHash = async function(tab, args = []) {
}
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);
if (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 nonce = keepassClient.getNonce();
const [ publicKey, origin ] = args;
const passkeyPublicKey = JSON.parse(JSON.stringify(publicKey));
const relatedOrigins = await keepass.getPasskeysRelatedOrigins(passkeyPublicKey?.rp?.id);
const messageData = {
action: kpAction,
publicKey: JSON.parse(JSON.stringify(publicKey)),
publicKey: passkeyPublicKey,
origin: origin,
relatedOrigins: relatedOrigins,
groupName: page?.settings?.defaultPasskeyGroup,
keys: keepass.getCryptoKeys()
};
@ -648,13 +660,15 @@ keepass.passkeysGet = async function(tab, args = []) {
const kpAction = kpActions.PASSKEYS_GET;
const nonce = keepassClient.getNonce();
const publicKey = args[0];
const origin = args[1];
const [ publicKey, origin ] = args;
const passkeyPublicKey = JSON.parse(JSON.stringify(publicKey));
const relatedOrigins = await keepass.getPasskeysRelatedOrigins(passkeyPublicKey?.rp?.id);
const messageData = {
action: kpAction,
publicKey: JSON.parse(JSON.stringify(publicKey)),
publicKey: passkeyPublicKey,
origin: origin,
relatedOrigins: relatedOrigins,
keys: keepass.getCryptoKeys()
};
@ -810,7 +824,9 @@ keepass.disableAutomaticReconnect = function() {
keepass.reconnect = async function(tab = null, connectionTimeout = 1500) {
keepassClient.connectToNative();
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
if (!keyChangeResult) {
@ -866,7 +882,9 @@ keepass.setcurrentKeePassXCVersion = function(version) {
keepass.keePassXCUpdateAvailable = async function() {
const checkUpdate = Number(page.settings.checkUpdateKeePassXC);
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);
if (daysSinceLastCheck >= checkUpdate) {
await keepass.checkForNewKeePassXCVersion();
@ -882,7 +900,7 @@ keepass.checkForNewKeePassXCVersion = async function() {
let version = -1;
try {
const response = await fetch(keepass.latestVersionUrl);
const response = await fetch(keepass.latestVersionUrl, { signal: AbortSignal.timeout(DEFAULT_FETCH_TIMEOUT) });
const jsonData = await response.json();
if (jsonData?.tag_name && jsonData?.prerelease === false) {
version = jsonData.tag_name;
@ -894,6 +912,44 @@ keepass.checkForNewKeePassXCVersion = async function() {
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) {
if (tab && page.tabs[tab.id]) {
page.tabs[tab.id].errorMessage = undefined;

View file

@ -3,7 +3,7 @@
"name": "KeePassXC-Browser",
"version": "1.9.11",
"version_name": "1.9.11",
"minimum_chrome_version": "93",
"minimum_chrome_version": "124",
"description": "__MSG_extensionDescription__",
"author": "KeePassXC Team",
"icons": {