Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passkeys: Add support for importing Passkey to entry #9987

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,15 @@ Do you want to delete the entry?
<source>%1 (Passkey)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>KeePassXC: Update Passkey</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Entry already has a Passkey.
Do you want to overwrite the Passkey in %1 - %2?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>BrowserSettingsWidget</name>
Expand Down Expand Up @@ -5993,51 +6002,51 @@ Do you want to overwrite it?
<translation type="unfinished"></translation>
</message>
<message>
<source>Do you want to import the Passkey?</source>
<source>URL: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>URL: %1</source>
<source>Username: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Username: %1</source>
<source>Group</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use default group (Imported Passkeys)</source>
<source>Database</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Group</source>
<source>Import Passkey</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Database</source>
<source>Import</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select Database</source>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Import Passkey</source>
<source>Import the following Passkey:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Import</source>
<source>Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cancel</source>
<source>Import the following Passkey to this entry:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Database: %1</source>
<source>Create new entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Group:</source>
<source>Default Passkeys group (Imported Passkeys)</source>
<translation type="unfinished"></translation>
</message>
</context>
Expand Down Expand Up @@ -6075,6 +6084,12 @@ Do you want to overwrite it?
<source>Cannot import Passkey file &quot;%1&quot;. Private key is missing or malformed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot import Passkey file &quot;%1&quot;.
The following data is missing:
%2</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PasswordEditWidget</name>
Expand Down
47 changes: 42 additions & 5 deletions src/browser/BrowserService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEnt
const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnlyHttpAuth");
const QString BrowserService::OPTION_NOT_HTTP_AUTH = QStringLiteral("BrowserNotHttpAuth");
const QString BrowserService::OPTION_OMIT_WWW = QStringLiteral("BrowserOmitWww");
// Multiple URL's
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");

Q_GLOBAL_STATIC(BrowserService, s_browserService);

Expand Down Expand Up @@ -779,6 +777,20 @@ void BrowserService::addPasskeyToEntry(Entry* entry,
return;
}

// Ask confirmation if entry already contains a Passkey
if (entry->hasPasskey()) {
if (MessageBox::question(
m_currentDatabaseWidget,
tr("KeePassXC: Update Passkey"),
tr("Entry already has a Passkey.\nDo you want to overwrite the Passkey in %1 - %2?")
.arg(entry->title(), entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USERNAME)),
MessageBox::Overwrite | MessageBox::Cancel,
MessageBox::Cancel)
!= MessageBox::Overwrite) {
return;
}
}

entry->beginUpdate();

entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USERNAME, username);
Expand Down Expand Up @@ -1088,7 +1100,13 @@ void BrowserService::denyEntry(Entry* entry, const QString& siteHost, const QStr
QJsonObject BrowserService::prepareEntry(const Entry* entry)
{
QJsonObject res;
#ifdef WITH_XC_BROWSER_PASSKEYS
// Use Passkey's username instead if found
res["login"] = entry->hasPasskey() ? entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USERNAME)
: entry->resolveMultiplePlaceholders(entry->username());
#else
res["login"] = entry->resolveMultiplePlaceholders(entry->username());
#endif
res["password"] = entry->resolveMultiplePlaceholders(entry->password());
res["name"] = entry->resolveMultiplePlaceholders(entry->title());
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuidToHex());
Expand Down Expand Up @@ -1295,8 +1313,7 @@ QList<Entry*> BrowserService::getPasskeyEntries(const QString& rpId, const Strin
{
QList<Entry*> entries;
for (const auto& entry : searchEntries(rpId, "", keyList, true)) {
if (entry->attributes()->hasKey(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM)
&& entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
if (entry->hasPasskey() && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
entries << entry;
}
}
Expand Down Expand Up @@ -1423,14 +1440,34 @@ bool BrowserService::handleURL(const QString& entryUrl,
return false;
}

QSharedPointer<Database> BrowserService::getDatabase()
QSharedPointer<Database> BrowserService::getDatabase(const QUuid& rootGroupUuid)
{
if (!rootGroupUuid.isNull()) {
const auto openDatabases = getOpenDatabases();
for (const auto& db : openDatabases) {
if (db->rootGroup()->uuid() == rootGroupUuid) {
return db;
}
}
}

if (m_currentDatabaseWidget) {
return m_currentDatabaseWidget->database();
}
return {};
}

QList<QSharedPointer<Database>> BrowserService::getOpenDatabases()
{
QList<QSharedPointer<Database>> databaseList;
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
if (!dbWidget->isLocked()) {
databaseList << dbWidget->database();
}
}
return databaseList;
}

QSharedPointer<Database> BrowserService::selectedDatabase()
{
QList<DatabaseWidget*> databaseWidgets;
Expand Down
4 changes: 2 additions & 2 deletions src/browser/BrowserService.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ class BrowserService : public QObject
QString getCurrentTotp(const QString& uuid);
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
bool isPasswordGeneratorRequested() const;
QSharedPointer<Database> getDatabase(const QUuid& rootGroupUuid = {});
QSharedPointer<Database> selectedDatabase();
QList<QSharedPointer<Database>> getOpenDatabases();
#ifdef WITH_XC_BROWSER_PASSKEYS
QJsonObject
showPasskeysRegisterPrompt(const QJsonObject& publicKey, const QString& origin, const StringPairList& keyList);
Expand Down Expand Up @@ -124,7 +126,6 @@ class BrowserService : public QObject
static const QString OPTION_ONLY_HTTP_AUTH;
static const QString OPTION_NOT_HTTP_AUTH;
static const QString OPTION_OMIT_WWW;
static const QString ADDITIONAL_URL;

signals:
void requestUnlock();
Expand Down Expand Up @@ -191,7 +192,6 @@ private slots:
const QString& siteUrl,
const QString& formUrl,
const bool omitWwwSubdomain = false);
QSharedPointer<Database> getDatabase();
QString getDatabaseRootUuid();
QString getDatabaseRecycleBinUuid();
bool checkLegacySettings(QSharedPointer<Database> db);
Expand Down
8 changes: 7 additions & 1 deletion src/core/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ QStringList Entry::getAllUrls() const
}

for (const auto& key : m_attributes->keys()) {
if (key.startsWith("KP2A_URL")) {
if (key.startsWith(EntryAttributes::AdditionalUrlAttribute)
|| key == QString("%1_RELYING_PARTY").arg(EntryAttributes::PasskeyAttribute)) {
auto additionalUrl = m_attributes->value(key);
if (!additionalUrl.isEmpty()) {
urlList << resolveMultiplePlaceholders(additionalUrl);
Expand Down Expand Up @@ -545,6 +546,11 @@ bool Entry::hasTotp() const
return !m_data.totpSettings.isNull();
}

bool Entry::hasPasskey() const
{
return m_attributes->hasPasskey();
}

QString Entry::totp() const
{
if (hasTotp()) {
Expand Down
1 change: 1 addition & 0 deletions src/core/Entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class Entry : public ModifiableObject
void setExcludeFromReports(bool state);

bool hasTotp() const;
bool hasPasskey() const;
bool isExpired() const;
bool willExpireInDays(int days) const;
bool isRecycled() const;
Expand Down
13 changes: 13 additions & 0 deletions src/core/EntryAttributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const QString EntryAttributes::SearchInGroupName = "SearchIn";
const QString EntryAttributes::SearchTextGroupName = "SearchText";

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

EntryAttributes::EntryAttributes(QObject* parent)
Expand All @@ -52,6 +53,18 @@ bool EntryAttributes::hasKey(const QString& key) const
return m_attributes.contains(key);
}

bool EntryAttributes::hasPasskey() const
{
const auto keyList = keys();
for (const auto& key : keyList) {
if (isPasskeyAttribute(key)) {
return true;
}
}

return false;
}

QList<QString> EntryAttributes::customKeys() const
{
QList<QString> customKeys;
Expand Down
4 changes: 3 additions & 1 deletion src/core/EntryAttributes.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2023 KeePassXC Team <[email protected]>
* Copyright (C) 2012 Felix Geyer <[email protected]>
* Copyright (C) 2017 KeePassXC Team <[email protected]>
*
* 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 @@ -33,6 +33,7 @@ class EntryAttributes : public ModifiableObject
explicit EntryAttributes(QObject* parent = nullptr);
QList<QString> keys() const;
bool hasKey(const QString& key) const;
bool hasPasskey() const;
QList<QString> customKeys() const;
QString value(const QString& key) const;
QList<QString> values(const QList<QString>& keys) const;
Expand Down Expand Up @@ -61,6 +62,7 @@ class EntryAttributes : public ModifiableObject
static const QString NotesKey;
static const QStringList DefaultAttributes;
static const QString RememberCmdExecAttr;
static const QString AdditionalUrlAttribute;
static const QString PasskeyAttribute;
static bool isDefaultAttribute(const QString& key);
static bool isPasskeyAttribute(const QString& key);
Expand Down
14 changes: 14 additions & 0 deletions src/core/Tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "core/Global.h"

#include <QDateTime>
#include <QList>
#include <QProcessEnvironment>

class QIODevice;
Expand Down Expand Up @@ -100,6 +101,19 @@ namespace Tools
return version;
}

// Checks if all values are found inside the list. Returns a list of values not found.
template <typename T> QList<T> getMissingValuesFromList(const QList<T>& list, const QList<T>& required)
{
QList<T> missingValues;
for (const auto& r : required) {
if (!list.contains(r)) {
missingValues << r;
}
}

return missingValues;
}

QVariantMap qo2qvm(const QObject* object, const QStringList& ignoredProperties = {"objectName"});

QString substituteBackupFilePath(QString pattern, const QString& databasePath);
Expand Down
5 changes: 3 additions & 2 deletions src/format/OpVaultReaderBandEntry.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 KeePassXC Team <[email protected]>
* Copyright (C) 2023 KeePassXC Team <[email protected]>
*
* 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 @@ -243,7 +243,8 @@ bool OpVaultReader::fillAttributes(Entry* entry, const QJsonObject& bandEntry)
auto newUrl = urlObj["u"].toString();
if (newUrl != url) {
// Add this url if it isn't the base one
entry->attributes()->set(QString("KP2A_URL_%1").arg(i), newUrl);
entry->attributes()->set(
QString("%1_%2").arg(EntryAttributes::AdditionalUrlAttribute, QString::number(i)), newUrl);
++i;
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/gui/DatabaseTabWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,12 @@ void DatabaseTabWidget::showPasskeys()

void DatabaseTabWidget::importPasskey()
{
currentDatabaseWidget()->switchToImportPasskey();
currentDatabaseWidget()->showImportPasskeyDialog();
}

void DatabaseTabWidget::importPasskeyToEntry()
{
currentDatabaseWidget()->showImportPasskeyDialog(true);
}
#endif

Expand Down
1 change: 1 addition & 0 deletions src/gui/DatabaseTabWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public slots:
#ifdef WITH_XC_BROWSER_PASSKEYS
void showPasskeys();
void importPasskey();
void importPasskeyToEntry();
#endif
void performGlobalAutoType(const QString& search);
void performBrowserUnlock();
Expand Down
14 changes: 12 additions & 2 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1407,10 +1407,20 @@ void DatabaseWidget::switchToPasskeys()
m_reportsDialog->activatePasskeysPage();
}

void DatabaseWidget::switchToImportPasskey()
void DatabaseWidget::showImportPasskeyDialog(bool isEntry)
{
PasskeyImporter passkeyImporter;
passkeyImporter.importPasskey(m_db);

if (isEntry) {
auto currentEntry = currentSelectedEntry();
if (!currentEntry) {
return;
}

passkeyImporter.importPasskey(m_db, currentEntry);
} else {
passkeyImporter.importPasskey(m_db);
}
}
#endif

Expand Down
2 changes: 1 addition & 1 deletion src/gui/DatabaseWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public slots:
void switchToDatabaseSettings();
#ifdef WITH_XC_BROWSER_PASSKEYS
void switchToPasskeys();
void switchToImportPasskey();
void showImportPasskeyDialog(bool isEntry = false);
#endif
void switchToOpenDatabase();
void switchToOpenDatabase(const QString& filePath);
Expand Down
Loading