diff --git a/src/browser/BrowserPasskeys.cpp b/src/browser/BrowserPasskeys.cpp index 287b42cca..2560b9428 100644 --- a/src/browser/BrowserPasskeys.cpp +++ b/src/browser/BrowserPasskeys.cpp @@ -79,7 +79,7 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso // Credential private key const auto alg = getAlgorithmFromPublicKey(credentialCreationOptions); - const auto privateKey = buildCredentialPrivateKey(alg, testingVariables.first, testingVariables.second); + const auto privateKey = buildCredentialPrivateKey(alg, testingVariables); if (privateKey.cborEncodedPublicKey.isEmpty() && privateKey.privateKeyPem.isEmpty()) { // Key creation failed return {}; @@ -103,6 +103,9 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso // Additions for extension side functions responseObject["authenticatorData"] = browserMessageBuilder()->getBase64FromArray(authenticatorData); + + // PublicKey + responseObject["publicKey"] = browserMessageBuilder()->getBase64FromArray(privateKey.spkiPublicKey); responseObject["publicKeyAlgorithm"] = alg; // PublicKeyCredential @@ -224,8 +227,7 @@ QByteArray BrowserPasskeys::buildAuthenticatorData(const QString& rpId, const QS } // See: https://w3c.github.io/webauthn/#sctn-encoded-credPubKey-examples -AttestationKeyPair -BrowserPasskeys::buildCredentialPrivateKey(int alg, const QString& predefinedFirst, const QString& predefinedSecond) +AttestationKeyPair BrowserPasskeys::buildCredentialPrivateKey(int alg, const TestingVariables& testingVariables) { // Only support -7, P256 (EC), -8 (EdDSA) and -257 (RSA) for now if (alg != WebAuthnAlgorithms::ES256 && alg != WebAuthnAlgorithms::RS256 && alg != WebAuthnAlgorithms::EDDSA) { @@ -234,21 +236,31 @@ BrowserPasskeys::buildCredentialPrivateKey(int alg, const QString& predefinedFir QByteArray firstPart; QByteArray secondPart; + QByteArray spki; QByteArray pem; - if (!predefinedFirst.isEmpty() && !predefinedSecond.isEmpty()) { - firstPart = browserMessageBuilder()->getArrayFromBase64(predefinedFirst); - secondPart = browserMessageBuilder()->getArrayFromBase64(predefinedSecond); + if (!testingVariables.first.isEmpty() && !testingVariables.second.isEmpty()) { + firstPart = browserMessageBuilder()->getArrayFromBase64(testingVariables.first); + secondPart = browserMessageBuilder()->getArrayFromBase64(testingVariables.second); } else { if (alg == WebAuthnAlgorithms::ES256) { try { - Botan::ECDSA_PrivateKey privateKey(*randomGen()->getRng(), Botan::EC_Group("secp256r1")); + // Use predefined data if found (only for testing private key creation) + const auto keyData = !testingVariables.data.isEmpty() + ? Botan::BigInt(testingVariables.data.toStdString()) + : Botan::BigInt(0); + Botan::ECDSA_PrivateKey privateKey(*randomGen()->getRng(), Botan::EC_Group("secp256r1"), keyData); const auto& publicPoint = privateKey.public_point(); auto x = publicPoint.get_affine_x(); auto y = publicPoint.get_affine_y(); firstPart = bigIntToQByteArray(x); secondPart = bigIntToQByteArray(y); + auto publicKey = + Botan::ECDSA_PublicKey(privateKey.algorithm_identifier(), privateKey.public_key_bits()); + auto publicKeySpki = publicKey.subject_public_key(); + spki = browserMessageBuilder()->getQByteArray(publicKeySpki.data(), publicKeySpki.size()); + auto privateKeyPem = Botan::PKCS8::PEM_encode(privateKey); pem = QByteArray::fromStdString(privateKeyPem); } catch (std::exception& e) { @@ -263,6 +275,10 @@ BrowserPasskeys::buildCredentialPrivateKey(int alg, const QString& predefinedFir firstPart = bigIntToQByteArray(modulus); secondPart = bigIntToQByteArray(exponent); + auto publicKey = Botan::RSA_PublicKey(privateKey.algorithm_identifier(), privateKey.public_key_bits()); + auto publicKeySpki = publicKey.subject_public_key(); + spki = browserMessageBuilder()->getQByteArray(publicKeySpki.data(), publicKeySpki.size()); + auto privateKeyPem = Botan::PKCS8::PEM_encode(privateKey); pem = QByteArray::fromStdString(privateKeyPem); } catch (std::exception& e) { @@ -271,17 +287,22 @@ BrowserPasskeys::buildCredentialPrivateKey(int alg, const QString& predefinedFir } } else if (alg == WebAuthnAlgorithms::EDDSA) { try { - Botan::Ed25519_PrivateKey key(*randomGen()->getRng()); - auto publicKey = key.get_public_key(); + Botan::Ed25519_PrivateKey privateKey(*randomGen()->getRng()); + auto publicKeyBits = privateKey.get_public_key(); #ifdef WITH_XC_BOTAN3 - auto privateKey = key.raw_private_key_bits(); + auto privateKeyBits = privateKey.raw_private_key_bits(); #else - auto privateKey = key.get_private_key(); + auto privateKeyBits = privateKey.get_private_key(); #endif - firstPart = browserMessageBuilder()->getQByteArray(publicKey.data(), publicKey.size()); - secondPart = browserMessageBuilder()->getQByteArray(privateKey.data(), privateKey.size()); + firstPart = browserMessageBuilder()->getQByteArray(publicKeyBits.data(), publicKeyBits.size()); + secondPart = browserMessageBuilder()->getQByteArray(privateKeyBits.data(), privateKeyBits.size()); - auto privateKeyPem = Botan::PKCS8::PEM_encode(key); + auto publicKey = + Botan::Ed25519_PublicKey(privateKey.algorithm_identifier(), privateKey.public_key_bits()); + auto publicKeySpki = publicKey.subject_public_key(); + spki = browserMessageBuilder()->getQByteArray(publicKeySpki.data(), publicKeySpki.size()); + + auto privateKeyPem = Botan::PKCS8::PEM_encode(privateKey); pem = QByteArray::fromStdString(privateKeyPem); } catch (std::exception& e) { qWarning("BrowserWebAuthn::buildCredentialPrivateKey: Could not create EdDSA private key: %s", @@ -299,6 +320,7 @@ BrowserPasskeys::buildCredentialPrivateKey(int alg, const QString& predefinedFir AttestationKeyPair attestationKeyPair; attestationKeyPair.cborEncodedPublicKey = result; attestationKeyPair.privateKeyPem = pem; + attestationKeyPair.spkiPublicKey = spki; return attestationKeyPair; } diff --git a/src/browser/BrowserPasskeys.h b/src/browser/BrowserPasskeys.h index 4955654c9..230dee63f 100644 --- a/src/browser/BrowserPasskeys.h +++ b/src/browser/BrowserPasskeys.h @@ -61,6 +61,7 @@ struct AttestationKeyPair { QByteArray cborEncodedPublicKey; QByteArray privateKeyPem; + QByteArray spkiPublicKey; }; // Predefined variables used for testing the class @@ -69,6 +70,7 @@ struct TestingVariables QString credentialId; QString first; QString second; + QString data; }; class BrowserPasskeys : public QObject @@ -81,7 +83,7 @@ public: static BrowserPasskeys* instance(); PublicKeyCredential buildRegisterPublicKeyCredential(const QJsonObject& credentialCreationOptions, - const TestingVariables& predefinedVariables = {}); + const TestingVariables& testingVariables = {}); QJsonObject buildGetPublicKeyCredential(const QJsonObject& assertionOptions, const QString& credentialId, const QString& userHandle, @@ -110,11 +112,9 @@ private: const QString& extensions, const QString& credentialId, const QByteArray& cborEncodedPublicKey, - const TestingVariables& predefinedVariables = {}); + const TestingVariables& testingVariables = {}); QByteArray buildAuthenticatorData(const QString& rpId, const QString& extensions); - AttestationKeyPair buildCredentialPrivateKey(int alg, - const QString& predefinedFirst = QString(), - const QString& predefinedSecond = QString()); + AttestationKeyPair buildCredentialPrivateKey(int alg, const TestingVariables& testingVariables = {}); QByteArray buildSignature(const QByteArray& authenticatorData, const QByteArray& clientData, const QString& privateKeyPem); QJsonObject parseAuthData(const QByteArray& authData) const; diff --git a/tests/TestPasskeys.cpp b/tests/TestPasskeys.cpp index fd25c2baf..878732e3e 100644 --- a/tests/TestPasskeys.cpp +++ b/tests/TestPasskeys.cpp @@ -77,7 +77,7 @@ const QString PublicKeyCredential = R"( "id": "yrzFJ5lwcpTwYMOdXSmxF5b5cYQlqBMzbbU_d-oFLO8", "rawId": "cabcc52799707294f060c39d5d29b11796f9718425a813336db53f77ea052cef", "response": { - "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBFAAAAAP2xQbJdhEQ-ijVGmMIFpQIAIMq8xSeZcHKU8GDDnV0psReW-XGEJagTM221P3fqBSzvpQECAyYgASFYIAbsrzRbYpFhbRlZA6ZQKsoxxJWoaeXwh-XUuDLNCIXdIlgg4u5_6Q8O6R0Hg0oDCdtCJLEL0yX_GDLhU5m3HUIE54M", + "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBFAAAAAP2xQbJdhEQ-ijVGmMIFpQIAIMq8xSeZcHKU8GDDnV0psReW-XGEJagTM221P3fqBSzvpQECAyYgASFYIHK1iVimeR02UYipyiEKrKhhfhJRMew8EbDWGKtMZ2wUIlggbtZ70X11SLx17QFDWVAR3_qqk5OqrRS--Whc7hyw9YU", "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibFZlSHpWeFdzcjhNUXhNa1pGMHRpNkZYaGRnTWxqcUt6Z0EtcV96azJNbmlpM2VKNDdWRjk3c3FVb1lrdFZDODVXQVoxdUlBU20tYV9sREZad3NMZnciLCJvcmlnaW4iOiJodHRwczovL3dlYmF1dGhuLmlvIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ" }, "type": "public-key" @@ -188,8 +188,8 @@ void TestPasskeys::testDecodeResponseData() QCOMPARE(publicKey["1"], 2); QCOMPARE(publicKey["3"], -7); QCOMPARE(publicKey["-1"], 1); - QCOMPARE(publicKey["-2"], QString("BuyvNFtikWFtGVkDplAqyjHElahp5fCH5dS4Ms0Ihd0")); - QCOMPARE(publicKey["-3"], QString("4u5_6Q8O6R0Hg0oDCdtCJLEL0yX_GDLhU5m3HUIE54M")); + QCOMPARE(publicKey["-2"], QString("crWJWKZ5HTZRiKnKIQqsqGF-ElEx7DwRsNYYq0xnbBQ")); + QCOMPARE(publicKey["-3"], QString("btZ70X11SLx17QFDWVAR3_qqk5OqrRS--Whc7hyw9YU")); } void TestPasskeys::testLoadingECPrivateKeyFromPem() @@ -276,10 +276,9 @@ void TestPasskeys::testCreatingAttestationObjectWithEC() auto rpIdHash = browserMessageBuilder()->getSha256HashAsBase64(QString("webauthn.io")); QCOMPARE(rpIdHash, QString("dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvA")); - TestingVariables testingVariables = {id, predefinedFirst, predefinedSecond}; + TestingVariables testingVariables = {id, predefinedFirst, predefinedSecond, QString()}; const auto alg = browserPasskeys()->getAlgorithmFromPublicKey(credentialCreationOptions); - const auto credentialPrivateKey = - browserPasskeys()->buildCredentialPrivateKey(alg, predefinedFirst, predefinedSecond); + const auto credentialPrivateKey = browserPasskeys()->buildCredentialPrivateKey(alg, testingVariables); auto result = browserPasskeys()->buildAttestationObject( credentialCreationOptions, "", id, credentialPrivateKey.cborEncodedPublicKey, testingVariables); QCOMPARE( @@ -344,10 +343,9 @@ void TestPasskeys::testCreatingAttestationObjectWithRSA() auto rpIdHash = browserMessageBuilder()->getSha256HashAsBase64(QString("webauthn.io")); QCOMPARE(rpIdHash, QString("dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvA")); - TestingVariables testingVariables = {id, predefinedModulus, predefinedExponent}; + TestingVariables testingVariables = {id, predefinedModulus, predefinedExponent, QString()}; const auto alg = browserPasskeys()->getAlgorithmFromPublicKey(credentialCreationOptions); - auto credentialPrivateKey = - browserPasskeys()->buildCredentialPrivateKey(alg, predefinedModulus, predefinedExponent); + auto credentialPrivateKey = browserPasskeys()->buildCredentialPrivateKey(alg, testingVariables); auto result = browserPasskeys()->buildAttestationObject( credentialCreationOptions, "", id, credentialPrivateKey.cborEncodedPublicKey, testingVariables); @@ -380,8 +378,7 @@ void TestPasskeys::testRegister() { // Predefined values for a desired outcome const auto predefinedId = QString("yrzFJ5lwcpTwYMOdXSmxF5b5cYQlqBMzbbU_d-oFLO8"); - const auto predefinedX = QString("BuyvNFtikWFtGVkDplAqyjHElahp5fCH5dS4Ms0Ihd0"); - const auto predefinedY = QString("4u5_6Q8O6R0Hg0oDCdtCJLEL0yX_GDLhU5m3HUIE54M"); + const auto predefinedData = QString("0x4B0E8AB07B1E62CCD4CB7B9D5BC9DE7B6EED7A3C8A3D466DB12897755E3D7E6D"); const auto origin = QString("https://webauthn.io"); const auto testDataPublicKey = browserMessageBuilder()->getJsonObject(PublicKeyCredential.toUtf8()); const auto testDataResponse = testDataPublicKey["response"]; @@ -392,7 +389,7 @@ void TestPasskeys::testRegister() publicKeyCredentialOptions, origin, &credentialCreationOptions); QVERIFY(creationResult == 0); - TestingVariables testingVariables = {predefinedId, predefinedX, predefinedY}; + TestingVariables testingVariables = {predefinedId, QString(), QString(), predefinedData}; auto result = browserPasskeys()->buildRegisterPublicKeyCredential(credentialCreationOptions, testingVariables); auto publicKeyCredential = result.response; QCOMPARE(publicKeyCredential["type"], QString("public-key")); @@ -402,6 +399,9 @@ void TestPasskeys::testRegister() auto response = publicKeyCredential["response"].toObject(); auto attestationObject = response["attestationObject"].toString(); auto clientDataJson = response["clientDataJSON"].toString(); + QCOMPARE(response["publicKey"], + QString("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcrWJWKZ5HTZRiKnKIQqsqGF-" + "ElEx7DwRsNYYq0xnbBRu1nvRfXVIvHXtAUNZUBHf-qqTk6qtFL75aFzuHLD1hQ")); QCOMPARE(attestationObject, testDataResponse["attestationObject"].toString()); // Parse clientDataJSON