Skip to content

Commit

Permalink
Passkeys: Pass extension JSON data to browser (#10615)
Browse files Browse the repository at this point in the history
  • Loading branch information
varjolintu committed Apr 25, 2024
1 parent 880621c commit 5b123e7
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 11 deletions.
4 changes: 4 additions & 0 deletions src/browser/BrowserCbor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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");

Expand All @@ -167,6 +170,7 @@ QByteArray BrowserCbor::cborEncodeExtensionData(const QJsonObject& extensions) c
writer.endArray();
writer.endArray();
}

writer.endMap();

return result;
Expand Down
2 changes: 2 additions & 0 deletions src/browser/BrowserPasskeys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
6 changes: 4 additions & 2 deletions src/browser/BrowserPasskeysClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
32 changes: 29 additions & 3 deletions src/browser/PasskeyUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {};
Expand Down
8 changes: 7 additions & 1 deletion src/browser/PasskeyUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,7 +57,7 @@ class PasskeyUtils : public QObject
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;
Expand Down
10 changes: 5 additions & 5 deletions tests/TestPasskeys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

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

Expand Down

0 comments on commit 5b123e7

Please sign in to comment.