mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-07-24 15:25:31 -04:00
Passkeys: Fix ordering of clientDataJSON
This commit is contained in:
parent
f32ed71dfc
commit
5cb6ad6335
4 changed files with 21 additions and 25 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -71,7 +71,7 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto authenticatorAttachment = credentialCreationOptions["authenticatorAttachment"];
|
const auto authenticatorAttachment = credentialCreationOptions["authenticatorAttachment"];
|
||||||
const auto clientDataJson = credentialCreationOptions["clientDataJSON"].toObject();
|
const auto clientDataJson = credentialCreationOptions["clientDataJSON"].toString();
|
||||||
const auto extensions = credentialCreationOptions["extensions"].toString();
|
const auto extensions = credentialCreationOptions["extensions"].toString();
|
||||||
const auto credentialId = testingVariables.credentialId.isEmpty()
|
const auto credentialId = testingVariables.credentialId.isEmpty()
|
||||||
? browserMessageBuilder()->getRandomBytesAsBase64(ID_BYTES)
|
? browserMessageBuilder()->getRandomBytesAsBase64(ID_BYTES)
|
||||||
|
@ -98,7 +98,7 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso
|
||||||
// Response
|
// Response
|
||||||
QJsonObject responseObject;
|
QJsonObject responseObject;
|
||||||
responseObject["attestationObject"] = browserMessageBuilder()->getBase64FromArray(attestationObject);
|
responseObject["attestationObject"] = browserMessageBuilder()->getBase64FromArray(attestationObject);
|
||||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromArray(clientDataJson.toUtf8());
|
||||||
responseObject["clientExtensionResults"] = credentialCreationOptions["clientExtensionResults"];
|
responseObject["clientExtensionResults"] = credentialCreationOptions["clientExtensionResults"];
|
||||||
|
|
||||||
// Additions for extension side functions
|
// Additions for extension side functions
|
||||||
|
@ -130,8 +130,8 @@ QJsonObject BrowserPasskeys::buildGetPublicKeyCredential(const QJsonObject& asse
|
||||||
|
|
||||||
const auto authenticatorData =
|
const auto authenticatorData =
|
||||||
buildAuthenticatorData(assertionOptions["rpId"].toString(), assertionOptions["extensions"].toString());
|
buildAuthenticatorData(assertionOptions["rpId"].toString(), assertionOptions["extensions"].toString());
|
||||||
const auto clientDataJson = assertionOptions["clientDataJson"].toObject();
|
const auto clientDataJson = assertionOptions["clientDataJson"].toString();
|
||||||
const auto clientDataArray = QJsonDocument(clientDataJson).toJson(QJsonDocument::Compact);
|
const auto clientDataArray = clientDataJson.toUtf8();
|
||||||
|
|
||||||
const auto signature = buildSignature(authenticatorData, clientDataArray, privateKeyPem);
|
const auto signature = buildSignature(authenticatorData, clientDataArray, privateKeyPem);
|
||||||
if (signature.isEmpty()) {
|
if (signature.isEmpty()) {
|
||||||
|
@ -140,7 +140,7 @@ QJsonObject BrowserPasskeys::buildGetPublicKeyCredential(const QJsonObject& asse
|
||||||
|
|
||||||
QJsonObject responseObject;
|
QJsonObject responseObject;
|
||||||
responseObject["authenticatorData"] = browserMessageBuilder()->getBase64FromArray(authenticatorData);
|
responseObject["authenticatorData"] = browserMessageBuilder()->getBase64FromArray(authenticatorData);
|
||||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromArray(clientDataArray);
|
||||||
responseObject["clientExtensionResults"] = assertionOptions["clientExtensionResults"];
|
responseObject["clientExtensionResults"] = assertionOptions["clientExtensionResults"];
|
||||||
responseObject["signature"] = browserMessageBuilder()->getBase64FromArray(signature);
|
responseObject["signature"] = browserMessageBuilder()->getBase64FromArray(signature);
|
||||||
responseObject["userHandle"] = userHandle;
|
responseObject["userHandle"] = userHandle;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -53,8 +53,8 @@ bool PasskeyUtils::checkCredentialCreationOptions(const QJsonObject& credentialC
|
||||||
{
|
{
|
||||||
if (!credentialCreationOptions["attestation"].isString()
|
if (!credentialCreationOptions["attestation"].isString()
|
||||||
|| credentialCreationOptions["attestation"].toString().isEmpty()
|
|| credentialCreationOptions["attestation"].toString().isEmpty()
|
||||||
|| !credentialCreationOptions["clientDataJSON"].isObject()
|
|| !credentialCreationOptions["clientDataJSON"].isString()
|
||||||
|| credentialCreationOptions["clientDataJSON"].toObject().isEmpty()
|
|| credentialCreationOptions["clientDataJSON"].toString().isEmpty()
|
||||||
|| !credentialCreationOptions["rp"].isObject() || credentialCreationOptions["rp"].toObject().isEmpty()
|
|| !credentialCreationOptions["rp"].isObject() || credentialCreationOptions["rp"].toObject().isEmpty()
|
||||||
|| !credentialCreationOptions["user"].isObject() || credentialCreationOptions["user"].toObject().isEmpty()
|
|| !credentialCreationOptions["user"].isObject() || credentialCreationOptions["user"].toObject().isEmpty()
|
||||||
|| !credentialCreationOptions["residentKey"].isBool() || credentialCreationOptions["residentKey"].isUndefined()
|
|| !credentialCreationOptions["residentKey"].isBool() || credentialCreationOptions["residentKey"].isUndefined()
|
||||||
|
@ -75,7 +75,7 @@ bool PasskeyUtils::checkCredentialCreationOptions(const QJsonObject& credentialC
|
||||||
// Basic check for the object that it contains necessary variables in a correct form
|
// Basic check for the object that it contains necessary variables in a correct form
|
||||||
bool PasskeyUtils::checkCredentialAssertionOptions(const QJsonObject& assertionOptions) const
|
bool PasskeyUtils::checkCredentialAssertionOptions(const QJsonObject& assertionOptions) const
|
||||||
{
|
{
|
||||||
if (!assertionOptions["clientDataJson"].isObject() || assertionOptions["clientDataJson"].toObject().isEmpty()
|
if (!assertionOptions["clientDataJson"].isString() || assertionOptions["clientDataJson"].toString().isEmpty()
|
||||||
|| !assertionOptions["rpId"].isString() || assertionOptions["rpId"].toString().isEmpty()
|
|| !assertionOptions["rpId"].isString() || assertionOptions["rpId"].toString().isEmpty()
|
||||||
|| !assertionOptions["userPresence"].isBool() || assertionOptions["userPresence"].isUndefined()
|
|| !assertionOptions["userPresence"].isBool() || assertionOptions["userPresence"].isUndefined()
|
||||||
|| !assertionOptions["userVerification"].isBool() || assertionOptions["userVerification"].isUndefined()) {
|
|| !assertionOptions["userVerification"].isBool() || assertionOptions["userVerification"].isUndefined()) {
|
||||||
|
@ -352,15 +352,11 @@ ExtensionResult PasskeyUtils::buildExtensionData(QJsonObject& extensionObject) c
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject PasskeyUtils::buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const
|
// Serialization order: https://w3c.github.io/webauthn/#clientdatajson-serialization
|
||||||
|
QString PasskeyUtils::buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const
|
||||||
{
|
{
|
||||||
QJsonObject clientData;
|
return QString("{\"type\":\"%1\",\"challenge\":\"%2\",\"origin\":\"%3\",\"crossOrigin\":false}")
|
||||||
clientData["challenge"] = publicKey["challenge"];
|
.arg((get ? QString("webauthn.get") : QString("webauthn.create")), publicKey["challenge"].toString(), origin);
|
||||||
clientData["crossOrigin"] = false;
|
|
||||||
clientData["origin"] = origin;
|
|
||||||
clientData["type"] = get ? QString("webauthn.get") : QString("webauthn.create");
|
|
||||||
|
|
||||||
return clientData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList PasskeyUtils::getAllowedCredentialsFromAssertionOptions(const QJsonObject& assertionOptions) const
|
QStringList PasskeyUtils::getAllowedCredentialsFromAssertionOptions(const QJsonObject& assertionOptions) const
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -58,7 +58,7 @@ public:
|
||||||
bool isUserVerificationRequired(const QJsonObject& authenticatorSelection) const;
|
bool isUserVerificationRequired(const QJsonObject& authenticatorSelection) const;
|
||||||
bool isOriginAllowedWithLocalhost(bool allowLocalhostWithPasskeys, const QString& origin) const;
|
bool isOriginAllowedWithLocalhost(bool allowLocalhostWithPasskeys, const QString& origin) const;
|
||||||
ExtensionResult buildExtensionData(QJsonObject& extensionObject) const;
|
ExtensionResult buildExtensionData(QJsonObject& extensionObject) const;
|
||||||
QJsonObject buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const;
|
QString buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const;
|
||||||
QStringList getAllowedCredentialsFromAssertionOptions(const QJsonObject& assertionOptions) const;
|
QStringList getAllowedCredentialsFromAssertionOptions(const QJsonObject& assertionOptions) const;
|
||||||
QString getCredentialIdFromEntry(const Entry* entry) const;
|
QString getCredentialIdFromEntry(const Entry* entry) const;
|
||||||
QString getUsernameFromEntry(const Entry* entry) const;
|
QString getUsernameFromEntry(const Entry* entry) const;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -440,12 +440,12 @@ void TestPasskeys::testGet()
|
||||||
auto response = publicKeyCredential["response"].toObject();
|
auto response = publicKeyCredential["response"].toObject();
|
||||||
QCOMPARE(response["authenticatorData"].toString(), QString("dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvAFAAAAAA"));
|
QCOMPARE(response["authenticatorData"].toString(), QString("dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvAFAAAAAA"));
|
||||||
QCOMPARE(response["clientDataJSON"].toString(),
|
QCOMPARE(response["clientDataJSON"].toString(),
|
||||||
QString("eyJjaGFsbGVuZ2UiOiI5ejM2dlRmUVRMOTVMZjdXblpneXRlN29oR2VGLVhSaUx4a0wtTHVHVTF6b3BSbU1JVUExTFZ3ekdwe"
|
QString("eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiOXozNnZUZlFUTDk1TGY3V25aZ3l0ZTdvaEdlRi1YUmlMeGtML"
|
||||||
"UltMWZPQm4xUW5SYTBRSDI3QURBYUpHSHlzUSIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdX"
|
"Ux1R1Uxem9wUm1NSVVBMUxWd3pHcHlJbTFmT0JuMVFuUmEwUUgyN0FEQWFKR0h5c1EiLCJvcmlnaW4iOiJodHRwczovL3dlYm"
|
||||||
"Robi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5nZXQifQ"));
|
"F1dGhuLmlvIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ"));
|
||||||
QCOMPARE(
|
QCOMPARE(
|
||||||
response["signature"].toString(),
|
response["signature"].toString(),
|
||||||
QString("MEUCIHFv0lOOGGloi_XoH5s3QDSs__8yAp9ZTMEjNiacMpOxAiEA04LAfO6TE7j12XNxd3zHQpn4kZN82jQFPntPiPBSD5c"));
|
QString("MEYCIQCpbDaYJ4b2ofqWBxfRNbH3XCpsyao7Iui5lVuJRU9HIQIhAPl5moNZgJu5zmurkKK_P900Ct6wd3ahVIqCEqTeeRdE"));
|
||||||
|
|
||||||
auto clientDataJson = response["clientDataJSON"].toString();
|
auto clientDataJson = response["clientDataJSON"].toString();
|
||||||
auto clientDataByteArray = browserMessageBuilder()->getArrayFromBase64(clientDataJson);
|
auto clientDataByteArray = browserMessageBuilder()->getArrayFromBase64(clientDataJson);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue