mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-07-06 12:25:11 -04:00
271 lines
7.2 KiB
C++
271 lines
7.2 KiB
C++
/*
|
|
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "BrowserCbor.h"
|
|
#include "BrowserMessageBuilder.h"
|
|
#include <QCborStreamReader>
|
|
#include <QCborStreamWriter>
|
|
#include <QJsonDocument>
|
|
|
|
// https://w3c.github.io/webauthn/#sctn-none-attestation
|
|
// https://w3c.github.io/webauthn/#sctn-generating-an-attestation-object
|
|
QByteArray BrowserCbor::cborEncodeAttestation(const QByteArray& authData) const
|
|
{
|
|
QByteArray result;
|
|
QCborStreamWriter writer(&result);
|
|
|
|
writer.startMap(3);
|
|
|
|
writer.append("fmt");
|
|
writer.append("none");
|
|
|
|
writer.append("attStmt");
|
|
writer.startMap(0);
|
|
writer.endMap();
|
|
|
|
writer.append("authData");
|
|
writer.appendByteString(authData.constData(), authData.size());
|
|
|
|
writer.endMap();
|
|
|
|
return result;
|
|
}
|
|
|
|
// https://w3c.github.io/webauthn/#authdata-attestedcredentialdata-credentialpublickey
|
|
QByteArray BrowserCbor::cborEncodePublicKey(int alg, const QByteArray& first, const QByteArray& second) const
|
|
{
|
|
const auto keyType = getCoseKeyType(alg);
|
|
if (keyType == 0) {
|
|
return {};
|
|
}
|
|
|
|
const auto curveParameter = getCurveParameter(alg);
|
|
if ((alg == WebAuthnAlgorithms::ES256 || alg == WebAuthnAlgorithms::EDDSA) && curveParameter == 0) {
|
|
return {};
|
|
}
|
|
|
|
QByteArray result;
|
|
QCborStreamWriter writer(&result);
|
|
|
|
if (alg == WebAuthnAlgorithms::ES256) {
|
|
writer.startMap(5);
|
|
|
|
// Key type
|
|
writer.append(1);
|
|
writer.append(keyType);
|
|
|
|
// Signature algorithm
|
|
writer.append(3);
|
|
writer.append(alg);
|
|
|
|
// Curve parameter
|
|
writer.append(-1);
|
|
writer.append(curveParameter);
|
|
|
|
// Key x-coordinate
|
|
writer.append(-2);
|
|
writer.append(first);
|
|
|
|
// Key y-coordinate
|
|
writer.append(-3);
|
|
writer.append(second);
|
|
|
|
writer.endMap();
|
|
} else if (alg == WebAuthnAlgorithms::RS256) {
|
|
writer.startMap(4);
|
|
|
|
// Key type
|
|
writer.append(1);
|
|
writer.append(keyType);
|
|
|
|
// Signature algorithm
|
|
writer.append(3);
|
|
writer.append(alg);
|
|
|
|
// Key modulus
|
|
writer.append(-1);
|
|
writer.append(first);
|
|
|
|
// Key exponent
|
|
writer.append(-2);
|
|
writer.append(second);
|
|
|
|
writer.endMap();
|
|
} else if (alg == WebAuthnAlgorithms::EDDSA) {
|
|
writer.startMap(4);
|
|
|
|
// Key type
|
|
writer.append(1);
|
|
writer.append(keyType);
|
|
|
|
// Algorithm
|
|
writer.append(3);
|
|
writer.append(alg);
|
|
|
|
// Curve parameter
|
|
writer.append(-1);
|
|
writer.append(curveParameter);
|
|
|
|
// Public key
|
|
writer.append(-2);
|
|
writer.append(first);
|
|
|
|
writer.endMap();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// See: https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#user-verification-methods
|
|
QByteArray BrowserCbor::cborEncodeExtensionData(const QJsonObject& extensions) const
|
|
{
|
|
if (extensions.empty()) {
|
|
return {};
|
|
}
|
|
|
|
QByteArray result;
|
|
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);
|
|
writer.append("rk");
|
|
writer.append(true);
|
|
writer.endMap();
|
|
}
|
|
|
|
// https://w3c.github.io/webauthn/#sctn-uvm-extension
|
|
if (extensions["uvm"].toBool()) {
|
|
writer.append("uvm");
|
|
|
|
writer.startArray(1);
|
|
writer.startArray(3);
|
|
|
|
// userVerificationMethod (USER_VERIFY_PRESENCE_INTERNAL "presence_internal", 0x00000001)
|
|
writer.append(quint32(1));
|
|
|
|
// keyProtectionType (KEY_PROTECTION_SOFTWARE "software", 0x0001)
|
|
writer.append(quint16(1));
|
|
|
|
// matcherProtectionType (MATCHER_PROTECTION_SOFTWARE "software", 0x0001)
|
|
writer.append(quint16(1));
|
|
|
|
writer.endArray();
|
|
writer.endArray();
|
|
}
|
|
|
|
writer.endMap();
|
|
|
|
return result;
|
|
}
|
|
|
|
QJsonObject BrowserCbor::getJsonFromCborData(const QByteArray& byteArray) const
|
|
{
|
|
auto reader = QCborStreamReader(byteArray);
|
|
auto contents = QCborValue::fromCbor(reader);
|
|
if (reader.lastError()) {
|
|
return {};
|
|
}
|
|
|
|
const auto ret = handleCborValue(contents);
|
|
|
|
// Parse variant result to QJsonDocument
|
|
const auto jsonDocument = QJsonDocument::fromVariant(ret);
|
|
if (jsonDocument.isNull() || jsonDocument.isEmpty()) {
|
|
return {};
|
|
}
|
|
|
|
return jsonDocument.object();
|
|
}
|
|
|
|
QVariant BrowserCbor::handleCborArray(const QCborArray& array) const
|
|
{
|
|
QVariantList result;
|
|
result.reserve(array.size());
|
|
|
|
for (auto a : array) {
|
|
result.append(handleCborValue(a));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QVariant BrowserCbor::handleCborMap(const QCborMap& map) const
|
|
{
|
|
QVariantMap result;
|
|
for (auto pair : map) {
|
|
result.insert(handleCborValue(pair.first).toString(), handleCborValue(pair.second));
|
|
}
|
|
|
|
return QVariant::fromValue(result);
|
|
}
|
|
|
|
QVariant BrowserCbor::handleCborValue(const QCborValue& value) const
|
|
{
|
|
if (value.isArray()) {
|
|
return handleCborArray(value.toArray());
|
|
} else if (value.isMap()) {
|
|
return handleCborMap(value.toMap());
|
|
} else if (value.isByteArray()) {
|
|
auto ba = value.toByteArray();
|
|
|
|
// Return base64 instead of raw byte array
|
|
auto base64Str = browserMessageBuilder()->getBase64FromArray(ba);
|
|
return QVariant::fromValue(base64Str);
|
|
}
|
|
|
|
return value.toVariant();
|
|
}
|
|
|
|
// https://www.rfc-editor.org/rfc/rfc8152#section-13.1
|
|
unsigned int BrowserCbor::getCurveParameter(int alg) const
|
|
{
|
|
switch (alg) {
|
|
case WebAuthnAlgorithms::ES256:
|
|
return WebAuthnCurveKey::P256;
|
|
case WebAuthnAlgorithms::ES384:
|
|
return WebAuthnCurveKey::P384;
|
|
case WebAuthnAlgorithms::ES512:
|
|
return WebAuthnCurveKey::P521;
|
|
case WebAuthnAlgorithms::EDDSA:
|
|
return WebAuthnCurveKey::ED25519;
|
|
default:
|
|
return WebAuthnCurveKey::INVALID_CURVE_KEY;
|
|
}
|
|
}
|
|
|
|
// See: https://www.rfc-editor.org/rfc/rfc8152
|
|
// AES/HMAC/ChaCha20 etc. carries symmetric keys (4) and OKP not supported currently.
|
|
unsigned int BrowserCbor::getCoseKeyType(int alg) const
|
|
{
|
|
switch (alg) {
|
|
case WebAuthnAlgorithms::ES256:
|
|
return WebAuthnCoseKeyType::EC2;
|
|
case WebAuthnAlgorithms::ES384:
|
|
case WebAuthnAlgorithms::ES512:
|
|
return WebAuthnCoseKeyType::INVALID_COSE_KEY_TYPE;
|
|
case WebAuthnAlgorithms::EDDSA:
|
|
return WebAuthnCoseKeyType::OKP;
|
|
case WebAuthnAlgorithms::RS256:
|
|
return WebAuthnCoseKeyType::RSA;
|
|
default:
|
|
return WebAuthnCoseKeyType::INVALID_COSE_KEY_TYPE;
|
|
}
|
|
}
|