Use EC keys instead of RSA

Use smaller and safer EC keys, replacing 2048 bit RSA.

NID_X9_62_prime256v1 is roughly as secure as a 3072 bit RSA key, but way shorter. 
Since we have to embed the key in the identity packet that is sent over UDP and
some stacks aren't happy with large UDP messages (notably: macos), I switched to
EC instead of to a longer RSA key.

This seems to be compatible with other clients even on older systems like Android 5.0.

I did stick with NID_X9_62_prime256v1 because stronger EC like NID_secp384r1 failed
the handshake (I didn't investigate why).

We now store the kind of key in the config, so we can know which kind of key we are loading.
This commit is contained in:
Albert Vaca Cintora 2024-05-19 10:04:43 +00:00
parent e408e4e3bd
commit 9a39eaa237
5 changed files with 84 additions and 13 deletions

View file

@ -527,14 +527,7 @@ void LanLinkProvider::configureSslSocket(QSslSocket *socket, const QString &devi
// Configure for ssl
QSslConfiguration sslConfig;
sslConfig.setLocalCertificate(KdeConnectConfig::instance().certificate());
QFile privateKeyFile(KdeConnectConfig::instance().privateKeyPath());
QSslKey privateKey;
if (privateKeyFile.open(QIODevice::ReadOnly)) {
privateKey = QSslKey(privateKeyFile.readAll(), QSsl::Rsa);
}
privateKeyFile.close();
sslConfig.setPrivateKey(privateKey);
sslConfig.setPrivateKey(KdeConnectConfig::instance().privateKey());
if (isDeviceTrusted) {
QSslCertificate certificate = KdeConnectConfig::instance().getTrustedDeviceCertificate(deviceId);

View file

@ -117,6 +117,11 @@ QSslCertificate KdeConnectConfig::certificate()
return d->m_certificate;
}
QSslKey KdeConnectConfig::privateKey()
{
return d->m_privateKey;
}
DeviceInfo KdeConnectConfig::deviceInfo()
{
const auto incoming = PluginLoader::instance()->incomingCapabilities();
@ -130,6 +135,16 @@ DeviceInfo KdeConnectConfig::deviceInfo()
QSet(outgoing.begin(), outgoing.end()));
}
QSsl::KeyAlgorithm KdeConnectConfig::privateKeyAlgorithm()
{
QString value = d->m_config->value(QStringLiteral("keyAlgorithm"), QStringLiteral("RSA")).toString();
if (value == QLatin1String("EC")) {
return QSsl::KeyAlgorithm::Ec;
} else {
return QSsl::KeyAlgorithm::Rsa;
}
}
QDir KdeConnectConfig::baseConfigDir()
{
QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
@ -250,7 +265,7 @@ bool KdeConnectConfig::loadPrivateKey(const QString &keyPath)
{
QFile privKey(keyPath);
if (privKey.exists() && privKey.open(QIODevice::ReadOnly)) {
d->m_privateKey = QSslKey(privKey.readAll(), QSsl::KeyAlgorithm::Rsa);
d->m_privateKey = QSslKey(privKey.readAll(), privateKeyAlgorithm());
if (d->m_privateKey.isNull()) {
qCWarning(KDECONNECT_CORE) << "Private key from" << keyPath << "is not valid!";
}
@ -295,7 +310,7 @@ void KdeConnectConfig::generatePrivateKey(const QString &keyPath)
{
qCDebug(KDECONNECT_CORE) << "Generating private key";
d->m_privateKey = SslHelper::generateRsaPrivateKey();
d->m_privateKey = SslHelper::generateEcPrivateKey();
if (d->m_privateKey.isNull()) {
qCritical() << "Could not generate the private key";
Daemon::instance()->reportError(i18n("KDE Connect failed to start"), i18n("Could not generate the private key."));
@ -313,6 +328,9 @@ void KdeConnectConfig::generatePrivateKey(const QString &keyPath)
}
}
d->m_config->setValue(QStringLiteral("keyAlgorithm"), QStringLiteral("EC"));
d->m_config->sync();
if (error) {
Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store private key file: %1", keyPath));
}

View file

@ -8,6 +8,7 @@
#define KDECONNECTCONFIG_H
#include <QDir>
#include <QSslKey>
#include "deviceinfo.h"
#include "kdeconnectcore_export.h"
@ -26,8 +27,10 @@ public:
QString deviceId();
QString name();
DeviceType deviceType();
QSslKey privateKey();
QSslCertificate certificate();
DeviceInfo deviceInfo();
QSsl::KeyAlgorithm privateKeyAlgorithm();
QString privateKeyPath();
QString certificatePath();

View file

@ -26,6 +26,61 @@ QString getSslError()
return QString::fromLatin1(buf);
}
QSslKey generateEcPrivateKey()
{
// Initialize context.
auto pctxRaw = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr);
auto pctx = std::unique_ptr<EVP_PKEY_CTX, decltype(&::EVP_PKEY_CTX_free)>(pctxRaw, ::EVP_PKEY_CTX_free);
if (!pctx) {
qCWarning(KDECONNECT_CORE) << "Generate EC Private Key failed to allocate context " << getSslError();
return QSslKey();
}
if (EVP_PKEY_keygen_init(pctx.get()) <= 0) {
qCWarning(KDECONNECT_CORE) << "Generate EC Private Key failed to initialize context " << getSslError();
return QSslKey();
}
// Set the curve.
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx.get(), NID_X9_62_prime256v1) <= 0) {
qCWarning(KDECONNECT_CORE) << "Generate EC Private Key failed to set curve " << getSslError();
return QSslKey();
}
// Generate private key.
auto pkey = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
if (!pkey) {
qCWarning(KDECONNECT_CORE) << "Generate EC Private Key failed to allocate private key " << getSslError();
return QSslKey();
}
auto pkey_raw = pkey.get();
if (EVP_PKEY_keygen(pctx.get(), &pkey_raw) <= 0) {
qCWarning(KDECONNECT_CORE) << "Generate EC Private Key failed to generate private key " << getSslError();
return QSslKey();
}
// Convert private key format to PEM as required by QSslKey.
auto bio = std::unique_ptr<BIO, decltype(&::BIO_free_all)>(BIO_new(BIO_s_mem()), ::BIO_free_all);
if (!bio) {
qCWarning(KDECONNECT_CORE) << "Generate EC Private Key failed to allocate I/O abstraction " << getSslError();
return QSslKey();
}
if (!PEM_write_bio_PrivateKey(bio.get(), pkey_raw, nullptr, nullptr, 0, nullptr, nullptr)) {
qCWarning(KDECONNECT_CORE) << "Generate EC Private Key failed write PEM format private key to BIO " << getSslError();
return QSslKey();
}
BUF_MEM *mem = nullptr;
if (!BIO_get_mem_ptr(bio.get(), &mem)) {
qCWarning(KDECONNECT_CORE) << "Generate EC Private Key failed get PEM format address " << getSslError();
return QSslKey();
}
return QSslKey(QByteArray(mem->data, mem->length), QSsl::KeyAlgorithm::Ec);
}
QSslKey generateRsaPrivateKey()
{
// Initialize context.
@ -86,11 +141,12 @@ QSslCertificate generateSelfSignedCertificate(const QSslKey &qtPrivateKey, const
// Create certificate.
auto x509 = std::unique_ptr<X509, decltype(&::X509_free)>(X509_new(), ::X509_free);
if (!x509) {
qCWarning(KDECONNECT_CORE) << "Generate Self Signed Certificate failed to allocate certifcate " << getSslError();
qCWarning(KDECONNECT_CORE) << "Generate Self Signed Certificate failed to allocate certificate " << getSslError();
return QSslCertificate();
}
if (!X509_set_version(x509.get(), 2)) {
constexpr int x509version = 3 - 1; // version is 0-indexed, so we need the -1
if (!X509_set_version(x509.get(), x509version)) {
qCWarning(KDECONNECT_CORE) << "Generate Self Signed Certificate failed to set version " << getSslError();
return QSslCertificate();
}
@ -169,7 +225,7 @@ QSslCertificate generateSelfSignedCertificate(const QSslKey &qtPrivateKey, const
}
// Sign the certificate with private key.
if (!X509_sign(x509.get(), pkey.get(), EVP_sha256())) {
if (!X509_sign(x509.get(), pkey.get(), EVP_sha512())) {
qCWarning(KDECONNECT_CORE) << "Generate Self Signed Certificate failed to sign certificate " << getSslError();
return QSslCertificate();
}

View file

@ -13,6 +13,7 @@
namespace SslHelper
{
QSslKey generateEcPrivateKey();
QSslKey generateRsaPrivateKey();
QSslCertificate generateSelfSignedCertificate(const QSslKey &privateKey, const QString &commonName);
}