Passkeys improvements (#10318)

Refactors the Passkey implementation to include more checks and a structure that is more aligned with the official specification.
Notable changes:
- _BrowserService_ no longer does the checks by itself. A new class _BrowserPasskeysClient_ constructs the relevant objects, acting as a client. _BrowserService_ only acts as a bridge between the client and _BrowserPasskeys_ (authenticator) and calls the relevant popups for user interaction.
- A new helper class _PasskeyUtils_ includes the actual checks and parses the objects.
- _BrowserPasskeys_ is pretty much intact, but some functions have been moved to PasskeyUtils.
- Fixes Ed25519 encoding in _BrowserCBOR_.
- Adds new error messages.
- User confirmation for Passkey retrieval is also asked even if `discouraged` is used. This goes against the specification, but currently there's no other way to verify the user.
- `cross-platform` is also accepted for compatibility. This could be removed if there's a potential issue with it.
- Extension data is now handled correctly during Authentication.
- Allowed and excluded credentials are now handled correctly.
- `KPEX_PASSKEY_GENERATED_USER_ID` is renamed to `KPEX_PASSKEY_CREDENTIAL_ID`
- Adds a new option "Allow localhost with Passkeys" to Browser Integration -> Advanced tab. By default it's not allowed to access HTTP sites, but `http://localhost` can be allowed for debugging and testing purposes for local servers.
- Add tag `Passkey` to a Passkey entry, or an entry with an imported Passkey.

Fixes #10287.
This commit is contained in:
Sami Vänttinen
2024-03-06 14:42:01 +02:00
committed by GitHub
parent dff2f186ce
commit ac2b445db6
33 changed files with 1248 additions and 269 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@@ -167,6 +167,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::Browser_UseCustomBrowser, {QS("Browser/UseCustomBrowser"), Local, false}},
{Config::Browser_CustomBrowserType, {QS("Browser/CustomBrowserType"), Local, -1}},
{Config::Browser_CustomBrowserLocation, {QS("Browser/CustomBrowserLocation"), Local, {}}},
{Config::Browser_AllowLocalhostWithPasskeys, {QS("Browser/Browser_AllowLocalhostWithPasskeys"), Roaming, false}},
#ifdef QT_DEBUG
{Config::Browser_CustomExtensionId, {QS("Browser/CustomExtensionId"), Local, {}}},
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@@ -147,6 +147,7 @@ public:
Browser_UseCustomBrowser,
Browser_CustomBrowserType,
Browser_CustomBrowserLocation,
Browser_AllowLocalhostWithPasskeys,
#ifdef QT_DEBUG
Browser_CustomExtensionId,
#endif

View File

@@ -230,6 +230,13 @@ namespace Tools
return regexp.exactMatch(base64);
}
bool isAsciiString(const QString& str)
{
constexpr auto pattern = R"(^[\x00-\x7F]+$)";
QRegularExpression regexp(pattern, QRegularExpression::CaseInsensitiveOption);
return regexp.match(str).hasMatch();
}
void sleep(int ms)
{
Q_ASSERT(ms >= 0);

View File

@@ -1,6 +1,6 @@
/*
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,6 +37,7 @@ namespace Tools
bool readAllFromDevice(QIODevice* device, QByteArray& data);
bool isHex(const QByteArray& ba);
bool isBase64(const QByteArray& ba);
bool isAsciiString(const QString& str);
void sleep(int ms);
void wait(int ms);
QString uuidToHex(const QUuid& uuid);

View File

@@ -102,7 +102,7 @@ QString UrlTools::getTopLevelDomainFromUrl(const QString& url) const
cookie.setDomain(host);
// Check if dummy cookie's domain/TLD matches with public suffix list
if (!QNetworkCookieJar{}.setCookiesFromUrl(QList{cookie}, url)) {
if (!QNetworkCookieJar{}.setCookiesFromUrl(QList{cookie}, QUrl::fromUserInput(url))) {
return host;
}
}
@@ -112,7 +112,9 @@ QString UrlTools::getTopLevelDomainFromUrl(const QString& url) const
bool UrlTools::isIpAddress(const QString& host) const
{
QHostAddress address(host);
// Handle IPv6 host with brackets, e.g [::1]
const auto hostAddress = host.startsWith('[') && host.endsWith(']') ? host.mid(1, host.length() - 2) : host;
QHostAddress address(hostAddress);
return address.protocol() == QAbstractSocket::IPv4Protocol || address.protocol() == QAbstractSocket::IPv6Protocol;
}
#endif
@@ -171,3 +173,9 @@ bool UrlTools::isUrlValid(const QString& urlField) const
return true;
}
bool UrlTools::domainHasIllegalCharacters(const QString& domain) const
{
QRegularExpression re(R"([\s\^#|/:<>\?@\[\]\\])");
return re.match(domain).hasMatch();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@ public:
#endif
bool isUrlIdentical(const QString& first, const QString& second) const;
bool isUrlValid(const QString& urlField) const;
bool domainHasIllegalCharacters(const QString& domain) const;
private:
QUrl convertVariantToUrl(const QVariant& var) const;