diff --git a/src/browser/BrowserCbor.cpp b/src/browser/BrowserCbor.cpp index e0f05d34e..7b151c410 100644 --- a/src/browser/BrowserCbor.cpp +++ b/src/browser/BrowserCbor.cpp @@ -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; diff --git a/src/browser/BrowserPasskeys.cpp b/src/browser/BrowserPasskeys.cpp index b1dfe5120..3e81a25f7 100644 --- a/src/browser/BrowserPasskeys.cpp +++ b/src/browser/BrowserPasskeys.cpp @@ -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; diff --git a/src/browser/BrowserPasskeysClient.cpp b/src/browser/BrowserPasskeysClient.cpp index 15c5ffae2..64857a72a 100644 --- a/src/browser/BrowserPasskeysClient.cpp +++ b/src/browser/BrowserPasskeysClient.cpp @@ -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; diff --git a/src/browser/PasskeyUtils.cpp b/src/browser/PasskeyUtils.cpp index a40caa1e4..fac9af09b 100644 --- a/src/browser/PasskeyUtils.cpp +++ b/src/browser/PasskeyUtils.cpp @@ -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 {}; diff --git a/src/browser/PasskeyUtils.h b/src/browser/PasskeyUtils.h index 0ac689ae6..ec8e47668 100644 --- a/src/browser/PasskeyUtils.h +++ b/src/browser/PasskeyUtils.h @@ -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; diff --git a/tests/TestPasskeys.cpp b/tests/TestPasskeys.cpp index 160087b6e..1618dce28 100644 --- a/tests/TestPasskeys.cpp +++ b/tests/TestPasskeys.cpp @@ -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); }