Skip to content

Commit

Permalink
Add support for 2048-bit RSA key
Browse files Browse the repository at this point in the history
  • Loading branch information
varjolintu committed May 8, 2023
1 parent f6e8191 commit eb11e04
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 87 deletions.
60 changes: 42 additions & 18 deletions src/browser/BrowserCbor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,34 +44,56 @@ QByteArray BrowserCbor::cborEncodeAttestation(const QByteArray& authData) const
return result;
}

QByteArray BrowserCbor::cborEncodePublicKey(int alg, const QByteArray& xPart, const QByteArray& yPart) const
QByteArray BrowserCbor::cborEncodePublicKey(int alg, const QByteArray& first, const QByteArray& second) const
{
QByteArray result;
QCborStreamWriter writer(&result);

writer.startMap(5);
if (alg == WebAuthnAlgorithms::ES256) {
writer.startMap(5);

// Key type
writer.append(1);
writer.append(getCoseKeyType(alg));
// Key type
writer.append(1);
writer.append(getCoseKeyType(alg));

// Signature algorithm
writer.append(3);
writer.append(alg);
// Signature algorithm
writer.append(3);
writer.append(alg);

// Curve parameter
writer.append(-1);
writer.append(getCurveParameter(alg));
// Curve parameter
writer.append(-1);
writer.append(getCurveParameter(alg));

// Key x-coordinate
writer.append(-2);
writer.append(xPart);
// Key x-coordinate
writer.append(-2);
writer.append(first);

// Key y-coordinate
writer.append(-3);
writer.append(yPart);
// Key y-coordinate
writer.append(-3);
writer.append(second);

writer.endMap();
writer.endMap();
} else if (alg == WebAuthnAlgorithms::RS256) {
writer.startMap(4);

// Key type
writer.append(1);
writer.append(getCoseKeyType(alg));

// 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();
}

return result;
}
Expand Down Expand Up @@ -203,6 +225,8 @@ unsigned int BrowserCbor::getCoseKeyType(int alg) const
return WebAuthnCoseKeyType::EC2;
case WebAuthnAlgorithms::EDDSA:
return WebAuthnCoseKeyType::OKP;
case WebAuthnAlgorithms::RS256:
return WebAuthnCoseKeyType::RSA;
default:
return WebAuthnCoseKeyType::EC2;
}
Expand Down
6 changes: 4 additions & 2 deletions src/browser/BrowserCbor.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,19 @@ enum WebAuthnCurveKey : int
};

// https://www.rfc-editor.org/rfc/rfc8152
// For RSA: https://www.rfc-editor.org/rfc/rfc8230#section-4
enum WebAuthnCoseKeyType : int
{
OKP = 1, // Octet Keypair
EC2 = 2 // Elliptic Curve
EC2 = 2, // Elliptic Curve
RSA = 3 // RSA
};

class BrowserCbor
{
public:
QByteArray cborEncodeAttestation(const QByteArray& authData) const;
QByteArray cborEncodePublicKey(int alg, const QByteArray& xPart, const QByteArray& yPart) const;
QByteArray cborEncodePublicKey(int alg, const QByteArray& first, const QByteArray& second) const;
QByteArray cborEncodeExtensionData(const QJsonObject& extensions) const;
QJsonObject getJsonFromCborData(const QByteArray& byteArray) const;
QVariant handleCborArray(const QCborArray& array) const;
Expand Down
115 changes: 70 additions & 45 deletions src/browser/BrowserWebAuthn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <botan/ecdsa.h>
#include <botan/pkcs8.h>
#include <botan/pubkey.h>
#include <botan/rsa.h>
#include <botan/sodium.h>

#include <bitset>
Expand Down Expand Up @@ -181,8 +182,9 @@ PrivateKey BrowserWebAuthn::buildAttestationObject(const QJsonObject& publicKey,
QByteArray::Base64UrlEncoding));

// Credential private key
const auto alg = getAlgorithmFromPublicKey(publicKey);
const auto credentialPublicKey =
buildCredentialPrivateKey(WebAuthnAlgorithms::ES256, predefinedVariables.x, predefinedVariables.y);
buildCredentialPrivateKey(alg, predefinedVariables.first, predefinedVariables.second);
result.append(credentialPublicKey.cborEncoded);

// Add extension data if available
Expand Down Expand Up @@ -219,38 +221,55 @@ QByteArray BrowserWebAuthn::buildGetAttestationObject(const QJsonObject& publicK
}

// See: https://w3c.github.io/webauthn/#sctn-encoded-credPubKey-examples
PrivateKey BrowserWebAuthn::buildCredentialPrivateKey(int alg, const QString& predefinedX, const QString& predefinedY)
PrivateKey
BrowserWebAuthn::buildCredentialPrivateKey(int alg, const QString& predefinedFirst, const QString& predefinedSecond)
{
// Only support -7, P256 (EC) for now
if (alg != WebAuthnAlgorithms::ES256) {
// Only support -7, P256 (EC) and -257 (RSA) for now
if (alg != WebAuthnAlgorithms::ES256 && alg != WebAuthnAlgorithms::RS256) {
return {};
}

QByteArray xPart;
QByteArray yPart;
QByteArray firstPart;
QByteArray secondPart;
QByteArray pem;

if (!predefinedX.isEmpty() && !predefinedY.isEmpty()) {
xPart = browserMessageBuilder()->getArrayFromBase64(predefinedX);
yPart = browserMessageBuilder()->getArrayFromBase64(predefinedY);
if (!predefinedFirst.isEmpty() && !predefinedSecond.isEmpty()) {
firstPart = browserMessageBuilder()->getArrayFromBase64(predefinedFirst);
secondPart = browserMessageBuilder()->getArrayFromBase64(predefinedSecond);
} else {
try {
Botan::ECDSA_PrivateKey privateKey(*randomGen()->getRng(), Botan::EC_Group("secp256r1"));
const auto& publicPoint = privateKey.public_point();
auto x = publicPoint.get_affine_x();
auto y = publicPoint.get_affine_y();
xPart = bigIntToQByteArray(x);
yPart = bigIntToQByteArray(y);

auto privateKeyPem = Botan::PKCS8::PEM_encode(privateKey);
pem = QByteArray::fromStdString(privateKeyPem);
} catch (std::exception& e) {
qWarning("BrowserWebAuthn::buildCredentialPrivateKey: Could not create private key: %s", e.what());
return {};
if (alg == WebAuthnAlgorithms::ES256) {
try {
Botan::ECDSA_PrivateKey privateKey(*randomGen()->getRng(), Botan::EC_Group("secp256r1"));
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 privateKeyPem = Botan::PKCS8::PEM_encode(privateKey);
pem = QByteArray::fromStdString(privateKeyPem);
} catch (std::exception& e) {
qWarning("BrowserWebAuthn::buildCredentialPrivateKey: Could not create EC2 private key: %s", e.what());
return {};
}
} else if (alg == WebAuthnAlgorithms::RS256) {
try {
Botan::RSA_PrivateKey privateKey(*randomGen()->getRng(), RSA_BITS, RSA_EXPONENT);
auto modulus = privateKey.get_n();
auto exponent = privateKey.get_e();
firstPart = bigIntToQByteArray(modulus);
secondPart = bigIntToQByteArray(exponent);

auto privateKeyPem = Botan::PKCS8::PEM_encode(privateKey);
pem = QByteArray::fromStdString(privateKeyPem);
} catch (std::exception& e) {
qWarning("BrowserWebAuthn::buildCredentialPrivateKey: Could not create RSA private key: %s", e.what());
return {};
}
}
}

auto result = m_browserCbor.cborEncodePublicKey(alg, xPart, yPart);
auto result = m_browserCbor.cborEncodePublicKey(alg, firstPart, secondPart);
return {result, pem};
}

Expand All @@ -268,16 +287,27 @@ QByteArray BrowserWebAuthn::buildSignature(const QByteArray& authenticatorData,

const auto key = Botan::PKCS8::load_key(dataSource).release();
const auto privateKeyBytes = key->private_key_bits();
const auto algId = getAlgorithmIdentifier();
if (algId.parameters_are_empty() || algId.parameters_are_null()) {
const auto algName = key->algo_name();
const auto algId = key->algorithm_identifier();

std::vector<uint8_t> rawSignature;
if (algName == "ECDSA") {
Botan::ECDSA_PrivateKey privateKey(algId, privateKeyBytes);
Botan::PK_Signer signer(privateKey, *randomGen()->getRng(), "EMSA1(SHA-256)", Botan::DER_SEQUENCE);

signer.update(reinterpret_cast<const uint8_t*>(attToBeSigned.constData()), attToBeSigned.size());
rawSignature = signer.signature(*randomGen()->getRng());
} else if (algName == "RSA") {
Botan::RSA_PrivateKey privateKey(algId, privateKeyBytes);
Botan::PK_Signer signer(privateKey, *randomGen()->getRng(), "EMSA3(SHA-256)");

signer.update(reinterpret_cast<const uint8_t*>(attToBeSigned.constData()), attToBeSigned.size());
rawSignature = signer.signature(*randomGen()->getRng());
} else {
qWarning("BrowserWebAuthn::buildSignature: Algorithm not supported");
return {};
}

// Sign
Botan::ECDSA_PrivateKey privateKey(algId, privateKeyBytes);
Botan::PK_Signer signer(privateKey, *randomGen()->getRng(), "EMSA1(SHA-256)", Botan::DER_SEQUENCE);
signer.update(reinterpret_cast<const uint8_t*>(attToBeSigned.constData()), attToBeSigned.size());
auto rawSignature = signer.signature(*randomGen()->getRng());
auto signature = QByteArray(reinterpret_cast<char*>(rawSignature.data()), rawSignature.size());
return signature;
} catch (std::exception& e) {
Expand Down Expand Up @@ -372,26 +402,21 @@ char BrowserWebAuthn::setFlagsFromJson(const QJsonObject& flags) const
return flagBits;
}

Botan::AlgorithmIdentifier BrowserWebAuthn::getAlgorithmIdentifier() const
// Returns the first supported algorithm from the pubKeyCredParams list (only support ES256 and RS256 for now)
WebAuthnAlgorithms BrowserWebAuthn::getAlgorithmFromPublicKey(const QJsonObject& publicKey) const
{
try {
const auto oidStr = QStringLiteral("1.2.840.10045.2.1"); // Elliptic curve public key cryptography
const auto parameters = QVector<uint8_t>({6, 8, 42, 134, 72, 206, 61, 3, 1, 7}).toStdVector();
const auto oid = Botan::OID(oidStr.toStdString());
Botan::AlgorithmIdentifier algId(oid, parameters);

return algId;
} catch (std::exception& e) {
qWarning("BrowserWebAuthn::getAlgorithmIdentifier: Could not create AlgorithmIdentifier: %s", e.what());
return {};
const auto pubKeyCredParams = publicKey["pubKeyCredParams"].toArray();
if (!pubKeyCredParams.isEmpty()) {
const auto alg = pubKeyCredParams.first()["alg"].toInt();
if (alg == WebAuthnAlgorithms::ES256 || alg == WebAuthnAlgorithms::RS256) {
return static_cast<WebAuthnAlgorithms>(alg);
}
}

return WebAuthnAlgorithms::ES256;
}

QByteArray BrowserWebAuthn::bigIntToQByteArray(Botan::BigInt& bigInt) const
{
if (bigInt.bytes() < HASH_BYTES) {
return {};
}

return browserMessageBuilder()->getArrayFromHexString(bigInt.to_hex_string().c_str());
}
13 changes: 8 additions & 5 deletions src/browser/BrowserWebAuthn.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#define HASH_BYTES 32
#define DEFAULT_TIMEOUT 300000
#define DEFAULT_DISCOURAGED_TIMEOUT 120000
#define RSA_BITS 2048
#define RSA_EXPONENT 65537

enum AuthDataOffsets : int
{
Expand Down Expand Up @@ -67,8 +69,8 @@ struct PrivateKey
struct PredefinedVariables
{
QString credentialId;
QString x;
QString y;
QString first;
QString second;
};

class BrowserWebAuthn : public QObject
Expand Down Expand Up @@ -103,15 +105,16 @@ class BrowserWebAuthn : public QObject
const QString& id,
const PredefinedVariables& predefinedVariables = {});
QByteArray buildGetAttestationObject(const QJsonObject& publicKey);
PrivateKey
buildCredentialPrivateKey(int alg, const QString& predefinedX = QString(), const QString& predefinedY = QString());
PrivateKey buildCredentialPrivateKey(int alg,
const QString& predefinedFirst = QString(),
const QString& predefinedSecond = QString());
QByteArray
buildSignature(const QByteArray& authenticatorData, const QByteArray& clientData, const QString& privateKeyPem);
QByteArray buildExtensionData(QJsonObject& extensionObject) const;
QJsonObject parseAuthData(const QByteArray& authData) const;
QJsonObject parseFlags(const QByteArray& flags) const;
char setFlagsFromJson(const QJsonObject& flags) const;
Botan::AlgorithmIdentifier getAlgorithmIdentifier() const;
WebAuthnAlgorithms getAlgorithmFromPublicKey(const QJsonObject& publicKey) const;
QByteArray bigIntToQByteArray(Botan::BigInt& bigInt) const;

Q_DISABLE_COPY(BrowserWebAuthn);
Expand Down
Loading

0 comments on commit eb11e04

Please sign in to comment.