mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Passkeys: Pass extension JSON data to browser (#10615)
This commit is contained in:
parent
880621c1fb
commit
5b123e7944
@ -141,6 +141,8 @@ QByteArray BrowserCbor::cborEncodeExtensionData(const QJsonObject& extensions) c
|
||||
QCborStreamWriter writer(&result);
|
||||
|
||||
writer.startMap(extensions.keys().count());
|
||||
|
||||
// https://w3c.github.io/webauthn/#sctn-authenticator-credential-properties-extension
|
||||
if (extensions["credProps"].toBool()) {
|
||||
writer.append("credProps");
|
||||
writer.startMap(1);
|
||||
@ -149,6 +151,7 @@ QByteArray BrowserCbor::cborEncodeExtensionData(const QJsonObject& extensions) c
|
||||
writer.endMap();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webauthn/#sctn-uvm-extension
|
||||
if (extensions["uvm"].toBool()) {
|
||||
writer.append("uvm");
|
||||
|
||||
@ -167,6 +170,7 @@ QByteArray BrowserCbor::cborEncodeExtensionData(const QJsonObject& extensions) c
|
||||
writer.endArray();
|
||||
writer.endArray();
|
||||
}
|
||||
|
||||
writer.endMap();
|
||||
|
||||
return result;
|
||||
|
@ -106,6 +106,7 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso
|
||||
QJsonObject responseObject;
|
||||
responseObject["attestationObject"] = browserMessageBuilder()->getBase64FromArray(attestationObject);
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
||||
responseObject["clientExtensionResults"] = credentialCreationOptions["clientExtensionResults"];
|
||||
|
||||
// PublicKeyCredential
|
||||
QJsonObject publicKeyCredential;
|
||||
@ -142,6 +143,7 @@ QJsonObject BrowserPasskeys::buildGetPublicKeyCredential(const QJsonObject& asse
|
||||
QJsonObject responseObject;
|
||||
responseObject["authenticatorData"] = browserMessageBuilder()->getBase64FromArray(authenticatorData);
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
||||
responseObject["clientExtensionResults"] = assertionOptions["clientExtensionResults"];
|
||||
responseObject["signature"] = browserMessageBuilder()->getBase64FromArray(signature);
|
||||
responseObject["userHandle"] = userHandle;
|
||||
|
||||
|
@ -101,13 +101,14 @@ int BrowserPasskeysClient::getCredentialCreationOptions(const QJsonObject& publi
|
||||
// Extensions
|
||||
auto extensionObject = publicKeyOptions["extensions"].toObject();
|
||||
const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
|
||||
const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData);
|
||||
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;
|
||||
@ -148,7 +149,7 @@ int BrowserPasskeysClient::getAssertionOptions(const QJsonObject& publicKeyOptio
|
||||
// Extensions
|
||||
auto extensionObject = publicKeyOptions["extensions"].toObject();
|
||||
const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
|
||||
const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData);
|
||||
const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData.extensionData);
|
||||
|
||||
// clientDataJson
|
||||
const auto clientDataJson = passkeyUtils()->buildClientDataJson(publicKeyOptions, origin, true);
|
||||
@ -163,6 +164,7 @@ int BrowserPasskeysClient::getAssertionOptions(const QJsonObject& publicKeyOptio
|
||||
QJsonObject assertionOptions;
|
||||
assertionOptions["allowCredentials"] = publicKeyOptions["allowCredentials"];
|
||||
assertionOptions["clientDataJson"] = clientDataJson;
|
||||
assertionOptions["clientExtensionResults"] = extensionData.extensionObject;
|
||||
assertionOptions["extensions"] = extensions;
|
||||
assertionOptions["rpId"] = rpId;
|
||||
assertionOptions["userPresence"] = true;
|
||||
|
@ -305,9 +305,8 @@ bool PasskeyUtils::isUserVerificationRequired(const QJsonObject& authenticatorSe
|
||||
&& BrowserPasskeys::SUPPORT_USER_VERIFICATION);
|
||||
}
|
||||
|
||||
QByteArray PasskeyUtils::buildExtensionData(QJsonObject& extensionObject) const
|
||||
ExtensionResult PasskeyUtils::buildExtensionData(QJsonObject& extensionObject) const
|
||||
{
|
||||
// Only supports "credProps" and "uvm" for now
|
||||
const QStringList allowedKeys = {"credProps", "uvm"};
|
||||
|
||||
// Remove unsupported keys
|
||||
@ -317,9 +316,36 @@ QByteArray PasskeyUtils::buildExtensionData(QJsonObject& extensionObject) const
|
||||
}
|
||||
}
|
||||
|
||||
// Create response object
|
||||
QJsonObject extensionJSON;
|
||||
|
||||
// https://w3c.github.io/webauthn/#sctn-authenticator-credential-properties-extension
|
||||
if (extensionObject.contains("credProps") && extensionObject["credProps"].toBool()) {
|
||||
extensionJSON["credProps"] = QJsonObject({{"rk", true}});
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webauthn/#sctn-uvm-extension
|
||||
if (extensionObject.contains("uvm") && extensionObject["uvm"].toBool()) {
|
||||
QJsonArray uvmResponse;
|
||||
QJsonArray uvmArray = {
|
||||
1, // userVerificationMethod (USER_VERIFY_PRESENCE_INTERNAL "presence_internal", 0x00000001)
|
||||
1, // keyProtectionType (KEY_PROTECTION_SOFTWARE "software", 0x0001)
|
||||
1, // matcherProtectionType (MATCHER_PROTECTION_SOFTWARE "software", 0x0001)
|
||||
};
|
||||
uvmResponse.append(uvmArray);
|
||||
extensionJSON["uvm"] = uvmResponse;
|
||||
}
|
||||
|
||||
if (extensionJSON.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto extensionData = m_browserCbor.cborEncodeExtensionData(extensionObject);
|
||||
if (!extensionData.isEmpty()) {
|
||||
return extensionData;
|
||||
ExtensionResult result;
|
||||
result.extensionData = extensionData;
|
||||
result.extensionObject = extensionJSON;
|
||||
return result;
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -30,6 +30,12 @@
|
||||
#define DEFAULT_DISCOURAGED_TIMEOUT 120000
|
||||
#define PASSKEYS_SUCCESS 0
|
||||
|
||||
struct ExtensionResult
|
||||
{
|
||||
QByteArray extensionData;
|
||||
QJsonObject extensionObject;
|
||||
};
|
||||
|
||||
class PasskeyUtils : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -51,7 +57,7 @@ public:
|
||||
bool isResidentKeyRequired(const QJsonObject& authenticatorSelection) const;
|
||||
bool isUserVerificationRequired(const QJsonObject& authenticatorSelection) const;
|
||||
bool isOriginAllowedWithLocalhost(bool allowLocalhostWithPasskeys, const QString& origin) const;
|
||||
QByteArray buildExtensionData(QJsonObject& extensionObject) const;
|
||||
ExtensionResult buildExtensionData(QJsonObject& extensionObject) const;
|
||||
QJsonObject buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const;
|
||||
QStringList getAllowedCredentialsFromAssertionOptions(const QJsonObject& assertionOptions) const;
|
||||
QString getCredentialIdFromEntry(const Entry* entry) const;
|
||||
|
@ -459,9 +459,9 @@ void TestPasskeys::testExtensions()
|
||||
auto result = passkeyUtils()->buildExtensionData(extensions);
|
||||
|
||||
BrowserCbor cbor;
|
||||
auto extensionJson = cbor.getJsonFromCborData(result);
|
||||
auto uvmArray = extensionJson["uvm"].toArray();
|
||||
QCOMPARE(extensionJson["credProps"].toObject()["rk"].toBool(), true);
|
||||
auto extensionJson = cbor.getJsonFromCborData(result.extensionData);
|
||||
auto uvmArray = result.extensionObject["uvm"].toArray();
|
||||
QCOMPARE(result.extensionObject["credProps"].toObject()["rk"].toBool(), true);
|
||||
QCOMPARE(uvmArray.size(), 1);
|
||||
QCOMPARE(uvmArray.first().toArray().size(), 3);
|
||||
|
||||
@ -470,10 +470,10 @@ void TestPasskeys::testExtensions()
|
||||
auto partialData = passkeyUtils()->buildExtensionData(partial);
|
||||
auto faultyData = passkeyUtils()->buildExtensionData(faulty);
|
||||
|
||||
auto partialJson = cbor.getJsonFromCborData(partialData);
|
||||
auto partialJson = cbor.getJsonFromCborData(partialData.extensionData);
|
||||
QCOMPARE(partialJson["uvm"].toArray().size(), 1);
|
||||
|
||||
auto faultyJson = cbor.getJsonFromCborData(faultyData);
|
||||
auto faultyJson = cbor.getJsonFromCborData(faultyData.extensionData);
|
||||
QCOMPARE(faultyJson.size(), 0);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user