Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passkeys: Pass extension JSON data to browser #10615

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading