keepassxc/src/browser/BrowserCbor.cpp
2024-04-25 06:29:51 -04:00

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;
}
}