Skip to content

Commit

Permalink
Merge branch 'devbleo3' of github.com:blessio/keepassxc-B into devbleo3
Browse files Browse the repository at this point in the history
  • Loading branch information
blessio committed Nov 4, 2024
2 parents 63eab48 + d4e0fd3 commit 9e3763c
Show file tree
Hide file tree
Showing 35 changed files with 504 additions and 155 deletions.
1 change: 1 addition & 0 deletions COPYING
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ Copyright: 2022 KeePassXC Team <team@keepassxc.org>
License: MIT

Files: share/icons/application/scalable/actions/application-exit.svg
share/icons/application/scalable/actions/arrow-collapse-down.svg
share/icons/application/scalable/actions/attributes-copy.svg
share/icons/application/scalable/actions/auto-type.svg
share/icons/application/scalable/actions/bitwarden.svg
Expand Down
46 changes: 46 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
### Reporting Security Issues

The KeePassXC team takes security vulnerabilities very seriously and appreciates your responsible disclosure efforts. We will make every effort to acknowledge your contributions and handle them promptly.

To report a security issue, please use one of the following methods:

- **GitHub Security Advisory:** Use the ["Report a Vulnerability"](https://github.com/keepassxreboot/keepassxc/security/advisories/new) tab on our GitHub repository.
- **Private Matrix Message:** Contact any of the following KeePassXC team members privately (also encrypted):
- [@droidmonkey_kpxc](https://matrix.to/#/@droidmonkey_kpxc:matrix.org)
- [@varjolintu](https://matrix.to/#/@varjolintu:matrix.org)
- [@phoerious](https://matrix.to/#/@phoerious:matrix.org)
- **Send an Email:** Send your report to team@keepassxc.org. We recommend encrypting the email if possible.

Please **DO NOT** use public channels (e.g., GitHub issues, Matrix chat channels) for initial reporting of bona fide security vulnerabilities.

Once you report a security issue, our team will respond with the next steps. After our initial reply, we will keep you informed of the progress towards a fix and full announcement. We may ask for additional information or guidance during this process. If we disagree that your report constitutes a genuine security vulnerability, we will inform you and close the report. Your report may be turned into an issue for further tracking.

If you discover vulnerabilities in third-party modules used by KeePassXC, please report them to the maintainers of the respective modules. If the vulnerability impacts KeePassXC directly, we encourage you to notify us using the above methods. We will validate if the vulnerability is exploitable from KeePassXC code; please note that not all vulnerabilities are actually exploitable and do not constitute an immediate concern for the KeePassXC application.

### Example Security Vulnerabilities

When reporting, please ensure the issue falls under what can be considered a genuine security vulnerability for KeePassXC. Some examples include:

- Unauthorized access to sensitive user data (e.g., passwords).
- Remote code execution or escalation of privileges.
- Bypassing authentication or encryption mechanisms.
- Broken or improperly implemented encryption methods.

### Counter Examples

The following issues are **not** considered security vulnerabilities:

- Bugs caused by locally modifying the application (e.g., injecting DLLs, altering code).
- Crashes or misbehavior resulting from normal use (report this as a normal issue).
- Vulnerabilities found in third-party modules (should be reported to the module’s maintainers).

### CVE Reporting Policy

Please **DO NOT** submit a report to a Common Vulnerabilities and Exposures (CVE) Numbering Authority (CNA) before confirming the security vulnerability with the KeePassXC team. If we do not respond to your report within 30 days, this restriction no longer applies.


### Other Communication

For other inquiries (e.g., developer questions, user questions), please use the public channels on Matrix:
- **User's Channel:** [#keepassxc:mozilla.org](https://matrix.to/#/#keepassxc:mozilla.org)
- **Developer's Channel:** [#keepassxc-dev:mozilla.org](https://matrix.to/#/#keepassxc-dev:mozilla.org)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions share/icons/icons.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<file>application/256x256/apps/keepassxc.png</file>

<file>application/scalable/actions/application-exit.svg</file>
<file>application/scalable/actions/arrow-collapse-down.svg</file>
<file>application/scalable/actions/attributes-copy.svg</file>
<file>application/scalable/actions/auto-type.svg</file>
<file>application/scalable/actions/bitwarden.svg</file>
Expand Down
12 changes: 12 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6179,6 +6179,14 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
<source>Setup Remote Sync…</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Show Group Panel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Toggle Show Group Panel</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageDatabase</name>
Expand Down Expand Up @@ -9061,6 +9069,10 @@ This option is deprecated, use --set-key-file instead.</source>
<numerusform></numerusform>
</translation>
</message>
<message>
<source>Passkey</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtIOCompressor</name>
Expand Down
11 changes: 0 additions & 11 deletions src/browser/BrowserPasskeys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,6 @@ const QString BrowserPasskeys::REQUIREMENT_REQUIRED = QStringLiteral("required")
const QString BrowserPasskeys::PASSKEYS_ATTESTATION_DIRECT = QStringLiteral("direct");
const QString BrowserPasskeys::PASSKEYS_ATTESTATION_NONE = QStringLiteral("none");

const QString BrowserPasskeys::KPEX_PASSKEY_USERNAME = QStringLiteral("KPEX_PASSKEY_USERNAME");
const QString BrowserPasskeys::KPEX_PASSKEY_CREDENTIAL_ID = QStringLiteral("KPEX_PASSKEY_CREDENTIAL_ID");

// For compatibility with StrongBox
const QString BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID = QStringLiteral("KPEX_PASSKEY_GENERATED_USER_ID");
const QString BrowserPasskeys::KPXC_PASSKEY_USERNAME = QStringLiteral("KPXC_PASSKEY_USERNAME");

const QString BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM = QStringLiteral("KPEX_PASSKEY_PRIVATE_KEY_PEM");
const QString BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY = QStringLiteral("KPEX_PASSKEY_RELYING_PARTY");
const QString BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE = QStringLiteral("KPEX_PASSKEY_USER_HANDLE");

BrowserPasskeys* BrowserPasskeys::instance()
{
return s_browserPasskeys;
Expand Down
8 changes: 0 additions & 8 deletions src/browser/BrowserPasskeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,6 @@ class BrowserPasskeys : public QObject
static const QString PASSKEYS_ATTESTATION_DIRECT;
static const QString PASSKEYS_ATTESTATION_NONE;

static const QString KPXC_PASSKEY_USERNAME;
static const QString KPEX_PASSKEY_USERNAME;
static const QString KPEX_PASSKEY_CREDENTIAL_ID;
static const QString KPEX_PASSKEY_GENERATED_USER_ID;
static const QString KPEX_PASSKEY_PRIVATE_KEY_PEM;
static const QString KPEX_PASSKEY_RELYING_PARTY;
static const QString KPEX_PASSKEY_USER_HANDLE;

private:
QByteArray buildAttestationObject(const QJsonObject& credentialCreationOptions,
const QString& extensions,
Expand Down
25 changes: 13 additions & 12 deletions src/browser/BrowserService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "BrowserHost.h"
#include "BrowserMessageBuilder.h"
#include "BrowserSettings.h"
#include "core/EntryAttributes.h"
#include "core/Tools.h"
#include "gui/MainWindow.h"
#include "gui/MessageBox.h"
Expand Down Expand Up @@ -763,9 +764,9 @@ QJsonObject BrowserService::showPasskeysAuthenticationPrompt(const QJsonObject&
return getPasskeyError(ERROR_PASSKEYS_UNKNOWN_ERROR);
}

const auto privateKeyPem = selectedEntry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM);
const auto privateKeyPem = selectedEntry->attributes()->value(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM);
const auto credentialId = passkeyUtils()->getCredentialIdFromEntry(selectedEntry);
const auto userHandle = selectedEntry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE);
const auto userHandle = selectedEntry->attributes()->value(EntryAttributes::KPEX_PASSKEY_USER_HANDLE);

auto publicKeyCredential =
browserPasskeys()->buildGetPublicKeyCredential(assertionOptions, credentialId, userHandle, privateKeyPem);
Expand Down Expand Up @@ -843,11 +844,11 @@ void BrowserService::addPasskeyToEntry(Entry* entry,

entry->beginUpdate();

entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USERNAME, username);
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_CREDENTIAL_ID, credentialId, true);
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM, privateKey, true);
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY, rpId);
entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE, userHandle, true);
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_USERNAME, username);
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_CREDENTIAL_ID, credentialId, true);
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM, privateKey, true);
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY, rpId);
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_USER_HANDLE, userHandle, true);
entry->addTag(tr("Passkey"));

entry->endUpdate();
Expand Down Expand Up @@ -1042,7 +1043,7 @@ QList<Entry*> BrowserService::searchEntries(const QSharedPointer<Database>& db,

#ifdef WITH_XC_BROWSER_PASSKEYS
// With Passkeys, check for the Relying Party instead of URL
if (passkey && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) != siteUrl) {
if (passkey && entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY) != siteUrl) {
continue;
}
#endif
Expand Down Expand Up @@ -1384,7 +1385,7 @@ QList<Entry*> BrowserService::getPasskeyEntries(const QString& rpId, const Strin
{
QList<Entry*> entries;
for (const auto& entry : searchEntries(rpId, "", keyList, true)) {
if (entry->hasPasskey() && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
if (entry->hasPasskey() && entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
entries << entry;
}
}
Expand All @@ -1399,8 +1400,8 @@ QList<Entry*> BrowserService::getPasskeyEntriesWithUserHandle(const QString& rpI
{
QList<Entry*> entries;
for (const auto& entry : searchEntries(rpId, "", keyList, true)) {
if (entry->hasPasskey() && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId
&& entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE) == userId) {
if (entry->hasPasskey() && entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY) == rpId
&& entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_USER_HANDLE) == userId) {
entries << entry;
}
}
Expand All @@ -1425,7 +1426,7 @@ QList<Entry*> BrowserService::getPasskeyAllowedEntries(const QJsonObject& assert
// See: https://w3c.github.io/webauthn/#dom-authenticatorassertionresponse-userhandle
if (allowedCredentials.contains(passkeyUtils()->getCredentialIdFromEntry(entry))
|| (allowedCredentials.isEmpty()
&& entry->attributes()->hasKey(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE))) {
&& entry->attributes()->hasKey(EntryAttributes::KPEX_PASSKEY_USER_HANDLE))) {
entries << entry;
}
}
Expand Down
13 changes: 7 additions & 6 deletions src/browser/PasskeyUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "PasskeyUtils.h"
#include "BrowserMessageBuilder.h"
#include "BrowserPasskeys.h"
#include "core/EntryAttributes.h"
#include "core/Tools.h"
#include "gui/UrlTools.h"

Expand Down Expand Up @@ -389,9 +390,9 @@ QString PasskeyUtils::getCredentialIdFromEntry(const Entry* entry) const
return {};
}

return entry->attributes()->hasKey(BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID)
? entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_GENERATED_USER_ID)
: entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_CREDENTIAL_ID);
return entry->attributes()->hasKey(EntryAttributes::KPEX_PASSKEY_GENERATED_USER_ID)
? entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_GENERATED_USER_ID)
: entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_CREDENTIAL_ID);
}

// For compatibility with StrongBox (and other possible clients in the future)
Expand All @@ -401,7 +402,7 @@ QString PasskeyUtils::getUsernameFromEntry(const Entry* entry) const
return {};
}

return entry->attributes()->hasKey(BrowserPasskeys::KPXC_PASSKEY_USERNAME)
? entry->attributes()->value(BrowserPasskeys::KPXC_PASSKEY_USERNAME)
: entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USERNAME);
return entry->attributes()->hasKey(EntryAttributes::KPXC_PASSKEY_USERNAME)
? entry->attributes()->value(EntryAttributes::KPXC_PASSKEY_USERNAME)
: entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_USERNAME);
}
1 change: 1 addition & 0 deletions src/core/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::GUI_HideMenubar, {QS("GUI/HideMenubar"), Roaming, false}},
{Config::GUI_HideToolbar, {QS("GUI/HideToolbar"), Roaming, false}},
{Config::GUI_MovableToolbar, {QS("GUI/MovableToolbar"), Roaming, false}},
{Config::GUI_HideGroupPanel, {QS("GUI/HideGroupPanel"), Roaming, false}},
{Config::GUI_HidePreviewPanel, {QS("GUI/HidePreviewPanel"), Roaming, false}},
{Config::GUI_AlwaysOnTop, {QS("GUI/GUI_AlwaysOnTop"), Local, false}},
{Config::GUI_ToolButtonStyle, {QS("GUI/ToolButtonStyle"), Roaming, Qt::ToolButtonIconOnly}},
Expand Down
1 change: 1 addition & 0 deletions src/core/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class Config : public QObject
GUI_HideMenubar,
GUI_HideToolbar,
GUI_MovableToolbar,
GUI_HideGroupPanel,
GUI_HidePreviewPanel,
GUI_AlwaysOnTop,
GUI_ToolButtonStyle,
Expand Down
13 changes: 13 additions & 0 deletions src/core/EntryAttributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,20 @@ const QString EntryAttributes::SearchTextGroupName = "SearchText";

const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
const QString EntryAttributes::AdditionalUrlAttribute = "KP2A_URL";

// Passkey related attributes
const QString EntryAttributes::PasskeyAttribute = "KPEX_PASSKEY";
const QString EntryAttributes::KPEX_PASSKEY_USERNAME = QStringLiteral("KPEX_PASSKEY_USERNAME");
const QString EntryAttributes::KPEX_PASSKEY_CREDENTIAL_ID = QStringLiteral("KPEX_PASSKEY_CREDENTIAL_ID");
const QString EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM = QStringLiteral("KPEX_PASSKEY_PRIVATE_KEY_PEM");
const QString EntryAttributes::KPEX_PASSKEY_RELYING_PARTY = QStringLiteral("KPEX_PASSKEY_RELYING_PARTY");
const QString EntryAttributes::KPEX_PASSKEY_USER_HANDLE = QStringLiteral("KPEX_PASSKEY_USER_HANDLE");
const QString EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_START = QStringLiteral("-----BEGIN PRIVATE KEY-----");
const QString EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_END = QStringLiteral("-----END PRIVATE KEY-----");

// For compatibility with StrongBox
const QString EntryAttributes::KPEX_PASSKEY_GENERATED_USER_ID = QStringLiteral("KPEX_PASSKEY_GENERATED_USER_ID");
const QString EntryAttributes::KPXC_PASSKEY_USERNAME = QStringLiteral("KPXC_PASSKEY_USERNAME");

EntryAttributes::EntryAttributes(QObject* parent)
: ModifiableObject(parent)
Expand Down
11 changes: 11 additions & 0 deletions src/core/EntryAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,18 @@ class EntryAttributes : public ModifiableObject
static const QStringList DefaultAttributes;
static const QString RememberCmdExecAttr;
static const QString AdditionalUrlAttribute;

static const QString PasskeyAttribute;
static const QString KPXC_PASSKEY_USERNAME;
static const QString KPEX_PASSKEY_USERNAME;
static const QString KPEX_PASSKEY_CREDENTIAL_ID;
static const QString KPEX_PASSKEY_GENERATED_USER_ID;
static const QString KPEX_PASSKEY_PRIVATE_KEY_PEM;
static const QString KPEX_PASSKEY_RELYING_PARTY;
static const QString KPEX_PASSKEY_USER_HANDLE;
static const QString KPEX_PASSKEY_PRIVATE_KEY_START;
static const QString KPEX_PASSKEY_PRIVATE_KEY_END;

static bool isDefaultAttribute(const QString& key);
static bool isPasskeyAttribute(const QString& key);

Expand Down
39 changes: 38 additions & 1 deletion src/format/BitwardenReader.cpp
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -81,6 +81,43 @@ namespace
entry->setTotp(Totp::parseSettings(totp));
}

// Parse passkey
if (loginMap.contains("fido2Credentials")) {
const auto fido2CredentialsMap = loginMap.value("fido2Credentials").toList();
for (const auto& fido2Credentials : fido2CredentialsMap) {
const auto passkey = fido2Credentials.toMap();

// Change from UUID to base64 byte array
const auto credentialIdValue = passkey.value("credentialId").toString();
if (!credentialIdValue.isEmpty()) {
const auto credentialUuid = Tools::uuidToHex(credentialIdValue);
const auto credentialIdArray = QByteArray::fromHex(credentialUuid.toUtf8());
const auto credentialId =
credentialIdArray.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_CREDENTIAL_ID, credentialId, true);
}

// Base64 needs to be changed from URL encoding back to normal, and the result as PEM string
const auto keyValue = passkey.value("keyValue").toString();
if (!keyValue.isEmpty()) {
const auto keyValueArray =
QByteArray::fromBase64(keyValue.toUtf8(), QByteArray::Base64UrlEncoding);
auto privateKey = keyValueArray.toBase64(QByteArray::Base64Encoding);
privateKey.insert(0, EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_START.toUtf8());
privateKey.append(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_END.toUtf8());
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM, privateKey, true);
}

entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_USERNAME,
passkey.value("userName").toString());
entry->attributes()->set(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY,
passkey.value("rpId").toString());
entry->attributes()->set(
EntryAttributes::KPEX_PASSKEY_USER_HANDLE, passkey.value("userHandle").toString(), true);
entry->addTag(QObject::tr("Passkey"));
}
}

// Set the entry url(s)
int i = 1;
for (const auto& urlObj : loginMap.value("uris").toList()) {
Expand Down
Loading

0 comments on commit 9e3763c

Please sign in to comment.