mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-18 13:24:10 -05: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);
|
QCborStreamWriter writer(&result);
|
||||||
|
|
||||||
writer.startMap(extensions.keys().count());
|
writer.startMap(extensions.keys().count());
|
||||||
|
|
||||||
|
// https://w3c.github.io/webauthn/#sctn-authenticator-credential-properties-extension
|
||||||
if (extensions["credProps"].toBool()) {
|
if (extensions["credProps"].toBool()) {
|
||||||
writer.append("credProps");
|
writer.append("credProps");
|
||||||
writer.startMap(1);
|
writer.startMap(1);
|
||||||
@ -149,6 +151,7 @@ QByteArray BrowserCbor::cborEncodeExtensionData(const QJsonObject& extensions) c
|
|||||||
writer.endMap();
|
writer.endMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/webauthn/#sctn-uvm-extension
|
||||||
if (extensions["uvm"].toBool()) {
|
if (extensions["uvm"].toBool()) {
|
||||||
writer.append("uvm");
|
writer.append("uvm");
|
||||||
|
|
||||||
@ -167,6 +170,7 @@ QByteArray BrowserCbor::cborEncodeExtensionData(const QJsonObject& extensions) c
|
|||||||
writer.endArray();
|
writer.endArray();
|
||||||
writer.endArray();
|
writer.endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.endMap();
|
writer.endMap();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -106,6 +106,7 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso
|
|||||||
QJsonObject responseObject;
|
QJsonObject responseObject;
|
||||||
responseObject["attestationObject"] = browserMessageBuilder()->getBase64FromArray(attestationObject);
|
responseObject["attestationObject"] = browserMessageBuilder()->getBase64FromArray(attestationObject);
|
||||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
||||||
|
responseObject["clientExtensionResults"] = credentialCreationOptions["clientExtensionResults"];
|
||||||
|
|
||||||
// PublicKeyCredential
|
// PublicKeyCredential
|
||||||
QJsonObject publicKeyCredential;
|
QJsonObject publicKeyCredential;
|
||||||
@ -142,6 +143,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()->getBase64FromJson(clientDataJson);
|
||||||
|
responseObject["clientExtensionResults"] = assertionOptions["clientExtensionResults"];
|
||||||
responseObject["signature"] = browserMessageBuilder()->getBase64FromArray(signature);
|
responseObject["signature"] = browserMessageBuilder()->getBase64FromArray(signature);
|
||||||
responseObject["userHandle"] = userHandle;
|
responseObject["userHandle"] = userHandle;
|
||||||
|
|
||||||
|
@ -101,13 +101,14 @@ int BrowserPasskeysClient::getCredentialCreationOptions(const QJsonObject& publi
|
|||||||
// Extensions
|
// Extensions
|
||||||
auto extensionObject = publicKeyOptions["extensions"].toObject();
|
auto extensionObject = publicKeyOptions["extensions"].toObject();
|
||||||
const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
|
const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
|
||||||
const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData);
|
const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData.extensionData);
|
||||||
|
|
||||||
// Construct the final object
|
// Construct the final object
|
||||||
QJsonObject credentialCreationOptions;
|
QJsonObject credentialCreationOptions;
|
||||||
credentialCreationOptions["attestation"] = attestation; // Set this, even if only "none" is supported
|
credentialCreationOptions["attestation"] = attestation; // Set this, even if only "none" is supported
|
||||||
credentialCreationOptions["authenticatorAttachment"] = authenticatorAttachment;
|
credentialCreationOptions["authenticatorAttachment"] = authenticatorAttachment;
|
||||||
credentialCreationOptions["clientDataJSON"] = passkeyUtils()->buildClientDataJson(publicKeyOptions, origin, false);
|
credentialCreationOptions["clientDataJSON"] = passkeyUtils()->buildClientDataJson(publicKeyOptions, origin, false);
|
||||||
|
credentialCreationOptions["clientExtensionResults"] = extensionData.extensionObject;
|
||||||
credentialCreationOptions["credTypesAndPubKeyAlgs"] = pubKeyCredParams;
|
credentialCreationOptions["credTypesAndPubKeyAlgs"] = pubKeyCredParams;
|
||||||
credentialCreationOptions["excludeCredentials"] = publicKeyOptions["excludeCredentials"];
|
credentialCreationOptions["excludeCredentials"] = publicKeyOptions["excludeCredentials"];
|
||||||
credentialCreationOptions["extensions"] = extensions;
|
credentialCreationOptions["extensions"] = extensions;
|
||||||
@ -148,7 +149,7 @@ int BrowserPasskeysClient::getAssertionOptions(const QJsonObject& publicKeyOptio
|
|||||||
// Extensions
|
// Extensions
|
||||||
auto extensionObject = publicKeyOptions["extensions"].toObject();
|
auto extensionObject = publicKeyOptions["extensions"].toObject();
|
||||||
const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
|
const auto extensionData = passkeyUtils()->buildExtensionData(extensionObject);
|
||||||
const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData);
|
const auto extensions = browserMessageBuilder()->getBase64FromArray(extensionData.extensionData);
|
||||||
|
|
||||||
// clientDataJson
|
// clientDataJson
|
||||||
const auto clientDataJson = passkeyUtils()->buildClientDataJson(publicKeyOptions, origin, true);
|
const auto clientDataJson = passkeyUtils()->buildClientDataJson(publicKeyOptions, origin, true);
|
||||||
@ -163,6 +164,7 @@ int BrowserPasskeysClient::getAssertionOptions(const QJsonObject& publicKeyOptio
|
|||||||
QJsonObject assertionOptions;
|
QJsonObject assertionOptions;
|
||||||
assertionOptions["allowCredentials"] = publicKeyOptions["allowCredentials"];
|
assertionOptions["allowCredentials"] = publicKeyOptions["allowCredentials"];
|
||||||
assertionOptions["clientDataJson"] = clientDataJson;
|
assertionOptions["clientDataJson"] = clientDataJson;
|
||||||
|
assertionOptions["clientExtensionResults"] = extensionData.extensionObject;
|
||||||
assertionOptions["extensions"] = extensions;
|
assertionOptions["extensions"] = extensions;
|
||||||
assertionOptions["rpId"] = rpId;
|
assertionOptions["rpId"] = rpId;
|
||||||
assertionOptions["userPresence"] = true;
|
assertionOptions["userPresence"] = true;
|
||||||
|
@ -305,9 +305,8 @@ bool PasskeyUtils::isUserVerificationRequired(const QJsonObject& authenticatorSe
|
|||||||
&& BrowserPasskeys::SUPPORT_USER_VERIFICATION);
|
&& 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"};
|
const QStringList allowedKeys = {"credProps", "uvm"};
|
||||||
|
|
||||||
// Remove unsupported keys
|
// 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);
|
auto extensionData = m_browserCbor.cborEncodeExtensionData(extensionObject);
|
||||||
if (!extensionData.isEmpty()) {
|
if (!extensionData.isEmpty()) {
|
||||||
return extensionData;
|
ExtensionResult result;
|
||||||
|
result.extensionData = extensionData;
|
||||||
|
result.extensionObject = extensionJSON;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
@ -30,6 +30,12 @@
|
|||||||
#define DEFAULT_DISCOURAGED_TIMEOUT 120000
|
#define DEFAULT_DISCOURAGED_TIMEOUT 120000
|
||||||
#define PASSKEYS_SUCCESS 0
|
#define PASSKEYS_SUCCESS 0
|
||||||
|
|
||||||
|
struct ExtensionResult
|
||||||
|
{
|
||||||
|
QByteArray extensionData;
|
||||||
|
QJsonObject extensionObject;
|
||||||
|
};
|
||||||
|
|
||||||
class PasskeyUtils : public QObject
|
class PasskeyUtils : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -51,7 +57,7 @@ public:
|
|||||||
bool isResidentKeyRequired(const QJsonObject& authenticatorSelection) const;
|
bool isResidentKeyRequired(const QJsonObject& authenticatorSelection) const;
|
||||||
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;
|
||||||
QByteArray buildExtensionData(QJsonObject& extensionObject) const;
|
ExtensionResult buildExtensionData(QJsonObject& extensionObject) const;
|
||||||
QJsonObject buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const;
|
QJsonObject 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;
|
||||||
|
@ -459,9 +459,9 @@ void TestPasskeys::testExtensions()
|
|||||||
auto result = passkeyUtils()->buildExtensionData(extensions);
|
auto result = passkeyUtils()->buildExtensionData(extensions);
|
||||||
|
|
||||||
BrowserCbor cbor;
|
BrowserCbor cbor;
|
||||||
auto extensionJson = cbor.getJsonFromCborData(result);
|
auto extensionJson = cbor.getJsonFromCborData(result.extensionData);
|
||||||
auto uvmArray = extensionJson["uvm"].toArray();
|
auto uvmArray = result.extensionObject["uvm"].toArray();
|
||||||
QCOMPARE(extensionJson["credProps"].toObject()["rk"].toBool(), true);
|
QCOMPARE(result.extensionObject["credProps"].toObject()["rk"].toBool(), true);
|
||||||
QCOMPARE(uvmArray.size(), 1);
|
QCOMPARE(uvmArray.size(), 1);
|
||||||
QCOMPARE(uvmArray.first().toArray().size(), 3);
|
QCOMPARE(uvmArray.first().toArray().size(), 3);
|
||||||
|
|
||||||
@ -470,10 +470,10 @@ void TestPasskeys::testExtensions()
|
|||||||
auto partialData = passkeyUtils()->buildExtensionData(partial);
|
auto partialData = passkeyUtils()->buildExtensionData(partial);
|
||||||
auto faultyData = passkeyUtils()->buildExtensionData(faulty);
|
auto faultyData = passkeyUtils()->buildExtensionData(faulty);
|
||||||
|
|
||||||
auto partialJson = cbor.getJsonFromCborData(partialData);
|
auto partialJson = cbor.getJsonFromCborData(partialData.extensionData);
|
||||||
QCOMPARE(partialJson["uvm"].toArray().size(), 1);
|
QCOMPARE(partialJson["uvm"].toArray().size(), 1);
|
||||||
|
|
||||||
auto faultyJson = cbor.getJsonFromCborData(faultyData);
|
auto faultyJson = cbor.getJsonFromCborData(faultyData.extensionData);
|
||||||
QCOMPARE(faultyJson.size(), 0);
|
QCOMPARE(faultyJson.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user