mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-15 10:36:39 -04:00
176 lines
7.8 KiB
C++
176 lines
7.8 KiB
C++
/*
|
|
* 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
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "BrowserPasskeysClient.h"
|
|
#include "BrowserMessageBuilder.h"
|
|
#include "BrowserPasskeys.h"
|
|
#include "PasskeyUtils.h"
|
|
|
|
#include <QJsonDocument>
|
|
|
|
Q_GLOBAL_STATIC(BrowserPasskeysClient, s_browserPasskeysClient);
|
|
|
|
BrowserPasskeysClient* BrowserPasskeysClient::instance()
|
|
{
|
|
return s_browserPasskeysClient;
|
|
}
|
|
|
|
// Constructs CredentialCreationOptions from the original PublicKeyCredential
|
|
// https://www.w3.org/TR/2019/REC-webauthn-1-20190304/#createCredential
|
|
int BrowserPasskeysClient::getCredentialCreationOptions(const QJsonObject& publicKeyOptions,
|
|
const QString& origin,
|
|
QJsonObject* result) const
|
|
{
|
|
if (!result || publicKeyOptions.isEmpty()) {
|
|
return ERROR_PASSKEYS_EMPTY_PUBLIC_KEY;
|
|
}
|
|
|
|
// Check validity of some basic values
|
|
const auto checkResultError = passkeyUtils()->checkLimits(publicKeyOptions);
|
|
if (checkResultError > 0) {
|
|
return checkResultError;
|
|
}
|
|
|
|
// Get effective domain
|
|
QString effectiveDomain;
|
|
const auto effectiveDomainResponse = passkeyUtils()->getEffectiveDomain(origin, &effectiveDomain);
|
|
if (effectiveDomainResponse > 0) {
|
|
return effectiveDomainResponse;
|
|
}
|
|
|
|
// Validate RP ID
|
|
QString rpId;
|
|
const auto rpName = publicKeyOptions["rp"]["name"].toString();
|
|
const auto rpIdResponse = passkeyUtils()->validateRpId(publicKeyOptions["rp"]["id"], effectiveDomain, &rpId);
|
|
if (rpIdResponse > 0) {
|
|
return rpIdResponse;
|
|
}
|
|
|
|
// Check PublicKeyCredentialTypes
|
|
const auto pubKeyCredParams = passkeyUtils()->parseCredentialTypes(publicKeyOptions["pubKeyCredParams"].toArray());
|
|
if (pubKeyCredParams.isEmpty() && !publicKeyOptions["pubKeyCredParams"].toArray().isEmpty()) {
|
|
return ERROR_PASSKEYS_NO_SUPPORTED_ALGORITHMS;
|
|
}
|
|
|
|
// Check Attestation
|
|
const auto attestation = passkeyUtils()->parseAttestation(publicKeyOptions["attestation"].toString());
|
|
|
|
// Check validity of AuthenticatorSelection
|
|
auto authenticatorSelection = publicKeyOptions["authenticatorSelection"].toObject();
|
|
const bool isAuthenticatorSelectionValid = passkeyUtils()->isAuthenticatorSelectionValid(authenticatorSelection);
|
|
if (!isAuthenticatorSelectionValid) {
|
|
return ERROR_PASSKEYS_WAIT_FOR_LIFETIMER;
|
|
}
|
|
|
|
// Add default values for compatibility
|
|
if (authenticatorSelection.isEmpty()) {
|
|
authenticatorSelection = QJsonObject({{"userVerification", BrowserPasskeys::REQUIREMENT_PREFERRED}});
|
|
} else if (authenticatorSelection["userVerification"].toString().isEmpty()) {
|
|
authenticatorSelection["userVerification"] = BrowserPasskeys::REQUIREMENT_PREFERRED;
|
|
}
|
|
|
|
auto authenticatorAttachment = authenticatorSelection["authenticatorAttachment"].toString();
|
|
if (authenticatorAttachment.isEmpty()) {
|
|
authenticatorAttachment = BrowserPasskeys::ATTACHMENT_PLATFORM;
|
|
}
|
|
|
|
// Unknown values are ignored, but a warning will be still shown just in case
|
|
const auto userVerification = authenticatorSelection["userVerification"].toString();
|
|
if (!passkeyUtils()->isUserVerificationValid(userVerification)) {
|
|
qWarning() << browserMessageBuilder()->getErrorMessage(ERROR_PASSKEYS_INVALID_USER_VERIFICATION);
|
|
}
|
|
|
|
// Parse requireResidentKey and userVerification
|
|
const auto isResidentKeyRequired = passkeyUtils()->isResidentKeyRequired(authenticatorSelection);
|
|
const auto isUserVerificationRequired = passkeyUtils()->isUserVerificationRequired(authenticatorSelection);
|
|
|
|
// Extensions
|
|
auto extensionObject = publicKeyOptions["extensions"].toObject();
|
|
const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
|
|
const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData.extensionData);
|
|
|
|
// Construct the final object
|
|
QJsonObject credentialCreationOptions;
|
|
credentialCreationOptions["attestation"] = attestation; // Set this, even if only "none" is supported
|
|
credentialCreationOptions["authenticatorAttachment"] = authenticatorAttachment;
|
|
credentialCreationOptions["clientDataJSON"] = passkeyUtils()->buildClientDataJson(publicKeyOptions, origin, false);
|
|
credentialCreationOptions["clientExtensionResults"] = extensionData.extensionObject;
|
|
credentialCreationOptions["credTypesAndPubKeyAlgs"] = pubKeyCredParams;
|
|
credentialCreationOptions["excludeCredentials"] = publicKeyOptions["excludeCredentials"];
|
|
credentialCreationOptions["extensions"] = extensions;
|
|
credentialCreationOptions["residentKey"] = isResidentKeyRequired;
|
|
credentialCreationOptions["rp"] = QJsonObject({{"id", rpId}, {"name", rpName}});
|
|
credentialCreationOptions["user"] = publicKeyOptions["user"];
|
|
credentialCreationOptions["userPresence"] = !isUserVerificationRequired;
|
|
credentialCreationOptions["userVerification"] = isUserVerificationRequired;
|
|
|
|
*result = credentialCreationOptions;
|
|
return 0;
|
|
}
|
|
|
|
// Use an existing credential
|
|
// https://www.w3.org/TR/2019/REC-webauthn-1-20190304/#getAssertion
|
|
int BrowserPasskeysClient::getAssertionOptions(const QJsonObject& publicKeyOptions,
|
|
const QString& origin,
|
|
QJsonObject* result) const
|
|
{
|
|
if (!result || publicKeyOptions.isEmpty()) {
|
|
return ERROR_PASSKEYS_EMPTY_PUBLIC_KEY;
|
|
}
|
|
|
|
// Get effective domain
|
|
QString effectiveDomain;
|
|
const auto effectiveDomainResponse = passkeyUtils()->getEffectiveDomain(origin, &effectiveDomain);
|
|
if (effectiveDomainResponse > 0) {
|
|
return effectiveDomainResponse;
|
|
}
|
|
|
|
// Validate RP ID
|
|
QString rpId;
|
|
const auto rpIdResponse = passkeyUtils()->validateRpId(publicKeyOptions["rpId"], effectiveDomain, &rpId);
|
|
if (rpIdResponse > 0) {
|
|
return rpIdResponse;
|
|
}
|
|
|
|
// Extensions
|
|
auto extensionObject = publicKeyOptions["extensions"].toObject();
|
|
const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
|
|
const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData.extensionData);
|
|
|
|
// clientDataJson
|
|
const auto clientDataJson = passkeyUtils()->buildClientDataJson(publicKeyOptions, origin, true);
|
|
|
|
// Unknown values are ignored, but a warning will be still shown just in case
|
|
const auto userVerification = publicKeyOptions["userVerification"].toString();
|
|
if (!passkeyUtils()->isUserVerificationValid(userVerification)) {
|
|
qWarning() << browserMessageBuilder()->getErrorMessage(ERROR_PASSKEYS_INVALID_USER_VERIFICATION);
|
|
}
|
|
const auto isUserVerificationRequired = passkeyUtils()->isUserVerificationRequired(publicKeyOptions);
|
|
|
|
QJsonObject assertionOptions;
|
|
assertionOptions["allowCredentials"] = publicKeyOptions["allowCredentials"];
|
|
assertionOptions["clientDataJson"] = clientDataJson;
|
|
assertionOptions["clientExtensionResults"] = extensionData.extensionObject;
|
|
assertionOptions["extensions"] = extensions;
|
|
assertionOptions["rpId"] = rpId;
|
|
assertionOptions["userPresence"] = true;
|
|
assertionOptions["userVerification"] = isUserVerificationRequired;
|
|
|
|
*result = assertionOptions;
|
|
return 0;
|
|
}
|