mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2026-03-11 08:54:48 +00:00
Passkeys: Add publicKey to register response (#12757)
This commit is contained in:
parent
d6c708e5a7
commit
ab7c4f87f7
3 changed files with 53 additions and 31 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue