mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-25 07:19:42 -05:00
Support passkeys with Bitwarden import (#11401)
This commit is contained in:
parent
95bae8377c
commit
6e0baf9f2c
@ -9061,6 +9061,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>
|
||||
|
@ -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;
|
||||
|
@ -105,14 +105,6 @@ public:
|
||||
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,
|
||||
|
@ -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"
|
||||
@ -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);
|
||||
@ -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();
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
@ -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)
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -64,7 +64,18 @@ public:
|
||||
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);
|
||||
|
||||
|
@ -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
|
||||
@ -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()) {
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "browser/BrowserPasskeys.h"
|
||||
#include "browser/PasskeyUtils.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include <QFile>
|
||||
@ -94,12 +95,12 @@ void PasskeyExporter::exportSelectedEntry(const Entry* entry, const QString& fol
|
||||
}
|
||||
|
||||
QJsonObject passkeyObject;
|
||||
passkeyObject["relyingParty"] = entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY);
|
||||
passkeyObject["relyingParty"] = entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY);
|
||||
passkeyObject["url"] = entry->url();
|
||||
passkeyObject["username"] = passkeyUtils()->getUsernameFromEntry(entry);
|
||||
passkeyObject["credentialId"] = passkeyUtils()->getCredentialIdFromEntry(entry);
|
||||
passkeyObject["userHandle"] = entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_USER_HANDLE);
|
||||
passkeyObject["privateKey"] = entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM);
|
||||
passkeyObject["userHandle"] = entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_USER_HANDLE);
|
||||
passkeyObject["privateKey"] = entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM);
|
||||
|
||||
QJsonDocument document(passkeyObject);
|
||||
if (passkeyFile.write(document.toJson()) < 0) {
|
||||
|
@ -76,8 +76,8 @@ void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer<Database>&
|
||||
tr("Cannot import passkey"),
|
||||
tr("Cannot import passkey file \"%1\".\nThe following data is missing:\n%2")
|
||||
.arg(file.fileName(), missingKeys.join(", ")));
|
||||
} else if (!privateKey.startsWith("-----BEGIN PRIVATE KEY-----")
|
||||
|| !privateKey.trimmed().endsWith("-----END PRIVATE KEY-----")) {
|
||||
} else if (!privateKey.startsWith(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_START)
|
||||
|| !privateKey.trimmed().endsWith(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_END)) {
|
||||
MessageBox::information(
|
||||
m_parent,
|
||||
tr("Cannot import passkey"),
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "browser/BrowserPasskeys.h"
|
||||
#include "browser/PasskeyUtils.h"
|
||||
#include "core/AsyncTask.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "gui/GuiTools.h"
|
||||
@ -75,7 +76,7 @@ PasskeyList::PasskeyList(const QSharedPointer<Database>& db)
|
||||
}
|
||||
|
||||
for (auto entry : group->entries()) {
|
||||
if (entry->isRecycled() || !entry->attributes()->hasKey(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM)) {
|
||||
if (entry->isRecycled() || !entry->attributes()->hasKey(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -134,7 +135,7 @@ void ReportsWidgetPasskeys::addPasskeyRow(Group* group, Entry* entry)
|
||||
row << new QStandardItem(Icons::entryIconPixmap(entry), title);
|
||||
row << new QStandardItem(Icons::groupIconPixmap(group), group->hierarchy().join("/"));
|
||||
row << new QStandardItem(passkeyUtils()->getUsernameFromEntry(entry));
|
||||
row << new QStandardItem(entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY));
|
||||
row << new QStandardItem(entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY));
|
||||
row << new QStandardItem(urlList.join('\n'));
|
||||
|
||||
// Set tooltips
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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
|
||||
@ -282,3 +282,36 @@ void TestImports::testBitwardenEncrypted()
|
||||
}
|
||||
QVERIFY(db);
|
||||
}
|
||||
|
||||
void TestImports::testBitwardenPasskey()
|
||||
{
|
||||
auto bitwardenPath =
|
||||
QStringLiteral("%1/%2").arg(KEEPASSX_TEST_DATA_DIR, QStringLiteral("/bitwarden_passkey_export.json"));
|
||||
|
||||
BitwardenReader reader;
|
||||
auto db = reader.convert(bitwardenPath);
|
||||
QVERIFY2(!reader.hasError(), qPrintable(reader.errorString()));
|
||||
QVERIFY(db);
|
||||
|
||||
// Confirm Login fields
|
||||
auto entry = db->rootGroup()->findEntryByPath("/webauthn.io");
|
||||
QVERIFY(entry);
|
||||
QCOMPARE(entry->title(), QStringLiteral("webauthn.io"));
|
||||
QCOMPARE(entry->username(), QStringLiteral("KPXC_BITWARDEN"));
|
||||
QCOMPARE(entry->url(), QStringLiteral("https://webauthn.io/"));
|
||||
|
||||
// Confirm passkey attributes
|
||||
auto attr = entry->attributes();
|
||||
QCOMPARE(attr->value(EntryAttributes::KPEX_PASSKEY_CREDENTIAL_ID), QStringLiteral("o-FfiyfBQq6Qz6YVrYeFTw"));
|
||||
QCOMPARE(
|
||||
attr->value(EntryAttributes::KPEX_PASSKEY_PRIVATE_KEY_PEM),
|
||||
QStringLiteral(
|
||||
"-----BEGIN PRIVATE "
|
||||
"KEY-----"
|
||||
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgmr4GQQjerojFuf0ZouOuUllMvAwxZSZAfB6gwDYcLiehRANCAAT0WR5zVS"
|
||||
"p6ieusvjkLkzaGc7fjGBmwpiuLPxR/d+ZjqMI9L2DKh+takp6wGt2x0n4jzr1KA352NZg0vjZX9CHh-----END PRIVATE KEY-----"));
|
||||
QCOMPARE(attr->value(EntryAttributes::KPEX_PASSKEY_USERNAME), QStringLiteral("KPXC_BITWARDEN"));
|
||||
QCOMPARE(attr->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY), QStringLiteral("webauthn.io"));
|
||||
QCOMPARE(attr->value(EntryAttributes::KPEX_PASSKEY_USER_HANDLE),
|
||||
QStringLiteral("aTFtdmFnOHYtS2dxVEJ0by1rSFpLWGg0enlTVC1iUVJReDZ5czJXa3c2aw"));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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
|
||||
@ -30,6 +30,7 @@ private slots:
|
||||
void testOPVault();
|
||||
void testBitwarden();
|
||||
void testBitwardenEncrypted();
|
||||
void testBitwardenPasskey();
|
||||
};
|
||||
|
||||
#endif /* TEST_IMPORTS_H */
|
||||
|
49
tests/data/bitwarden_passkey_export.json
Normal file
49
tests/data/bitwarden_passkey_export.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"encrypted": false,
|
||||
"folders": [],
|
||||
"items": [
|
||||
{
|
||||
"passwordHistory": null,
|
||||
"revisionDate": "2024-10-23T16:38:08.870Z",
|
||||
"creationDate": "2024-10-23T16:38:08.606Z",
|
||||
"deletedDate": null,
|
||||
"id": "a8e579f0-98c2-4ac9-a126-b212011225f8",
|
||||
"organizationId": null,
|
||||
"folderId": null,
|
||||
"type": 1,
|
||||
"reprompt": 0,
|
||||
"name": "webauthn.io",
|
||||
"notes": null,
|
||||
"favorite": false,
|
||||
"login": {
|
||||
"fido2Credentials": [
|
||||
{
|
||||
"credentialId": "a3e15f8b-27c1-42ae-90cf-a615ad87854f",
|
||||
"keyType": "public-key",
|
||||
"keyAlgorithm": "ECDSA",
|
||||
"keyCurve": "P-256",
|
||||
"keyValue": "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgmr4GQQjerojFuf0ZouOuUllMvAwxZSZAfB6gwDYcLiehRANCAAT0WR5zVSp6ieusvjkLkzaGc7fjGBmwpiuLPxR_d-ZjqMI9L2DKh-takp6wGt2x0n4jzr1KA352NZg0vjZX9CHh",
|
||||
"rpId": "webauthn.io",
|
||||
"userHandle": "aTFtdmFnOHYtS2dxVEJ0by1rSFpLWGg0enlTVC1iUVJReDZ5czJXa3c2aw",
|
||||
"userName": "KPXC_BITWARDEN",
|
||||
"counter": "0",
|
||||
"rpName": "webauthn.io",
|
||||
"userDisplayName": "KPXC_BITWARDEN",
|
||||
"discoverable": "true",
|
||||
"creationDate": "2024-10-23T16:38:08.617Z"
|
||||
}
|
||||
],
|
||||
"uris": [
|
||||
{
|
||||
"match": null,
|
||||
"uri": "https://webauthn.io/"
|
||||
}
|
||||
],
|
||||
"username": "KPXC_BITWARDEN",
|
||||
"password": null,
|
||||
"totp": null
|
||||
},
|
||||
"collectionIds": null
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user