Passkeys: Pass extension JSON data to browser (#10615)

This commit is contained in:
Sami Vänttinen 2024-04-25 13:29:51 +03:00 committed by GitHub
parent 880621c1fb
commit 5b123e7944
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 51 additions and 11 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 {};

View File

@ -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;

View File

@ -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);
}