Replace QCA with a simple OpenSSL wrapper
This commit is contained in:
parent
956c88c964
commit
d948d882aa
6 changed files with 182 additions and 86 deletions
|
@ -10,7 +10,6 @@ project(kdeconnect VERSION ${RELEASE_SERVICE_VERSION})
|
|||
|
||||
set(KF_MIN_VERSION "5.101.0")
|
||||
set(QT_MIN_VERSION "5.15.2")
|
||||
set(QCA_MIN_VERSION "2.1.0")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
|
||||
|
@ -58,9 +57,6 @@ ecm_set_disabled_deprecation_versions(
|
|||
add_library(kdeconnectversion INTERFACE)
|
||||
target_include_directories(kdeconnectversion INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
find_package(Qca-qt${QT_MAJOR_VERSION} ${QCA_MIN_VERSION} REQUIRED)
|
||||
set(Qca_LIBRARY qca-qt${QT_MAJOR_VERSION})
|
||||
|
||||
set(KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service Solid Kirigami2 People WindowSystem GuiAddons DocTools)
|
||||
|
||||
set_package_properties(KF${QT_MAJOR_VERSION}Kirigami2 PROPERTIES
|
||||
|
|
|
@ -42,6 +42,7 @@ target_sources(kdeconnectcore PRIVATE
|
|||
compositefiletransferjob.cpp
|
||||
daemon.cpp
|
||||
device.cpp
|
||||
sslhelper.cpp
|
||||
core_debug.cpp
|
||||
notificationserverinfo.cpp
|
||||
openconfig.cpp
|
||||
|
@ -55,13 +56,16 @@ ecm_qt_declare_logging_category(kdeconnectcore
|
|||
|
||||
target_include_directories(kdeconnectcore PUBLIC ${PROJECT_SOURCE_DIR})
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
target_link_libraries(kdeconnectcore
|
||||
PUBLIC
|
||||
Qt::Network
|
||||
KF${QT_MAJOR_VERSION}::CoreAddons
|
||||
${Qca_LIBRARY}
|
||||
KF${QT_MAJOR_VERSION}::KIOCore
|
||||
KF${QT_MAJOR_VERSION}::KIOGui
|
||||
OpenSSL::Crypto
|
||||
OpenSSL::SSL
|
||||
PRIVATE
|
||||
Qt::DBus
|
||||
KF${QT_MAJOR_VERSION}::I18n
|
||||
|
|
|
@ -19,23 +19,19 @@
|
|||
#include <QStandardPaths>
|
||||
#include <QThread>
|
||||
#include <QUuid>
|
||||
#include <QtCrypto>
|
||||
|
||||
#include "core_debug.h"
|
||||
#include "daemon.h"
|
||||
#include "dbushelper.h"
|
||||
#include "deviceinfo.h"
|
||||
#include "pluginloader.h"
|
||||
#include "sslhelper.h"
|
||||
|
||||
const QFile::Permissions strictPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser;
|
||||
|
||||
struct KdeConnectConfigPrivate {
|
||||
// The Initializer object sets things up, and also does cleanup when it goes out of scope
|
||||
// Note it's not being used anywhere. That's intended
|
||||
QCA::Initializer m_qcaInitializer;
|
||||
|
||||
QCA::PrivateKey m_privateKey;
|
||||
QSslCertificate m_certificate; // Use QSslCertificate instead of QCA::Certificate due to compatibility with QSslSocket
|
||||
EVP_PKEY *m_privateKey;
|
||||
QSslCertificate m_certificate;
|
||||
|
||||
QSettings *m_config;
|
||||
QSettings *m_trustedDevices;
|
||||
|
@ -59,15 +55,6 @@ KdeConnectConfig &KdeConnectConfig::instance()
|
|||
KdeConnectConfig::KdeConnectConfig()
|
||||
: d(new KdeConnectConfigPrivate)
|
||||
{
|
||||
// qCDebug(KDECONNECT_CORE) << "QCA supported capabilities:" << QCA::supportedFeatures().join(",");
|
||||
if (!QCA::isSupported("rsa")) {
|
||||
qCritical() << "Could not find support for RSA in your QCA installation";
|
||||
Daemon::instance()->reportError(i18n("KDE Connect failed to start"),
|
||||
i18n("Could not find support for RSA in your QCA installation. If your "
|
||||
"distribution provides separate packets for QCA-ossl and QCA-gnupg, "
|
||||
"make sure you have them installed and try again."));
|
||||
}
|
||||
|
||||
// Make sure base directory exists
|
||||
QDir().mkpath(baseConfigDir().path());
|
||||
|
||||
|
@ -75,8 +62,7 @@ KdeConnectConfig::KdeConnectConfig()
|
|||
d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat);
|
||||
d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat);
|
||||
|
||||
loadPrivateKey();
|
||||
loadCertificate();
|
||||
loadOrGeneratePrivateKeyAndCertificate(privateKeyPath(), certificatePath());
|
||||
|
||||
if (name().isEmpty()) {
|
||||
setName(getDefaultDeviceName());
|
||||
|
@ -260,56 +246,48 @@ QDir KdeConnectConfig::pluginConfigDir(const QString &deviceId, const QString &p
|
|||
return QDir(pluginConfigDir);
|
||||
}
|
||||
|
||||
void KdeConnectConfig::loadPrivateKey()
|
||||
bool KdeConnectConfig::loadPrivateKey(const QString &keyPath)
|
||||
{
|
||||
QString keyPath = privateKeyPath();
|
||||
QFile privKey(keyPath);
|
||||
|
||||
bool needsToGenerateKey = false;
|
||||
if (privKey.exists() && privKey.open(QIODevice::ReadOnly)) {
|
||||
QCA::ConvertResult result;
|
||||
d->m_privateKey = QCA::PrivateKey::fromPEM(QString::fromLatin1(privKey.readAll()), QCA::SecureArray(), &result);
|
||||
if (result != QCA::ConvertResult::ConvertGood) {
|
||||
qCWarning(KDECONNECT_CORE) << "Private key from" << keyPath << "is not valid";
|
||||
needsToGenerateKey = true;
|
||||
d->m_privateKey = SslHelper::pemToRsaPrivateKey(privKey.readAll());
|
||||
if (d->m_privateKey == nullptr) {
|
||||
qCWarning(KDECONNECT_CORE) << "Private key from" << keyPath << "is not valid!";
|
||||
}
|
||||
}
|
||||
return (d->m_privateKey == nullptr);
|
||||
}
|
||||
|
||||
bool KdeConnectConfig::loadCertificate(const QString &certPath)
|
||||
{
|
||||
QFile cert(certPath);
|
||||
if (cert.exists() && cert.open(QIODevice::ReadOnly)) {
|
||||
auto loadedCerts = QSslCertificate::fromData(cert.readAll());
|
||||
if (loadedCerts.empty()) {
|
||||
qCWarning(KDECONNECT_CORE) << "Certificate from" << certPath << "is not valid";
|
||||
} else {
|
||||
needsToGenerateKey = true;
|
||||
d->m_certificate = loadedCerts.at(0);
|
||||
}
|
||||
}
|
||||
return d->m_certificate.isNull();
|
||||
}
|
||||
|
||||
void KdeConnectConfig::loadOrGeneratePrivateKeyAndCertificate(const QString &keyPath, const QString &certPath)
|
||||
{
|
||||
bool needsToGenerateKey = loadPrivateKey(keyPath);
|
||||
bool needsToGenerateCert = needsToGenerateKey || loadCertificate(certPath);
|
||||
|
||||
if (needsToGenerateKey) {
|
||||
generatePrivateKey(keyPath);
|
||||
}
|
||||
if (needsToGenerateCert) {
|
||||
generateCertificate(certPath);
|
||||
}
|
||||
|
||||
// Extra security check
|
||||
if (QFile::permissions(keyPath) != strictPermissions) {
|
||||
qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << keyPath;
|
||||
}
|
||||
}
|
||||
|
||||
void KdeConnectConfig::loadCertificate()
|
||||
{
|
||||
QString certPath = certificatePath();
|
||||
QFile cert(certPath);
|
||||
|
||||
bool needsToGenerateCert = false;
|
||||
if (cert.exists() && cert.open(QIODevice::ReadOnly)) {
|
||||
auto loadedCerts = QSslCertificate::fromPath(certPath);
|
||||
if (loadedCerts.empty()) {
|
||||
qCWarning(KDECONNECT_CORE) << "Certificate from" << certPath << "is not valid";
|
||||
needsToGenerateCert = true;
|
||||
} else {
|
||||
d->m_certificate = loadedCerts.at(0);
|
||||
}
|
||||
} else {
|
||||
needsToGenerateCert = true;
|
||||
}
|
||||
|
||||
if (needsToGenerateCert) {
|
||||
generateCertificate(certPath);
|
||||
}
|
||||
|
||||
// Extra security check
|
||||
if (QFile::permissions(certPath) != strictPermissions) {
|
||||
qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect certificate file has too open permissions " << certPath;
|
||||
}
|
||||
|
@ -319,16 +297,16 @@ void KdeConnectConfig::generatePrivateKey(const QString &keyPath)
|
|||
{
|
||||
qCDebug(KDECONNECT_CORE) << "Generating private key";
|
||||
|
||||
bool error = false;
|
||||
|
||||
d->m_privateKey = QCA::KeyGenerator().createRSA(2048);
|
||||
d->m_privateKey = SslHelper::generateRsaPrivateKey();
|
||||
QByteArray keyPem = SslHelper::privateKeyToPEM(d->m_privateKey);
|
||||
|
||||
QFile privKey(keyPath);
|
||||
bool error = false;
|
||||
if (!privKey.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
|
||||
error = true;
|
||||
} else {
|
||||
privKey.setPermissions(strictPermissions);
|
||||
int written = privKey.write(d->m_privateKey.toPEM().toLatin1());
|
||||
int written = privKey.write(keyPem);
|
||||
if (written <= 0) {
|
||||
error = true;
|
||||
}
|
||||
|
@ -343,37 +321,22 @@ void KdeConnectConfig::generateCertificate(const QString &certPath)
|
|||
{
|
||||
qCDebug(KDECONNECT_CORE) << "Generating certificate";
|
||||
|
||||
bool error = false;
|
||||
|
||||
QString uuid = QUuid::createUuid().toString();
|
||||
DBusHelper::filterNonExportableCharacters(uuid);
|
||||
qCDebug(KDECONNECT_CORE) << "My id:" << uuid;
|
||||
|
||||
// FIXME: We only use QCA here to generate the cert and key, would be nice to get rid of it completely.
|
||||
// The same thing we are doing with QCA could be done invoking openssl (although it's potentially less portable):
|
||||
// openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes -keyout privateKey.pem -days 3650 -out certificate.pem -subj "/O=KDE/OU=KDE
|
||||
// Connect/CN=_e6e29ad4_2b31_4b6d_8f7a_9872dbaa9095_"
|
||||
|
||||
QCA::CertificateOptions certificateOptions = QCA::CertificateOptions();
|
||||
QDateTime startTime = QDateTime::currentDateTime().addYears(-1);
|
||||
QDateTime endTime = startTime.addYears(10);
|
||||
QCA::CertificateInfo certificateInfo;
|
||||
certificateInfo.insert(QCA::CommonName, uuid);
|
||||
certificateInfo.insert(QCA::Organization, QStringLiteral("KDE"));
|
||||
certificateInfo.insert(QCA::OrganizationalUnit, QStringLiteral("Kde connect"));
|
||||
certificateOptions.setInfo(certificateInfo);
|
||||
certificateOptions.setFormat(QCA::PKCS10);
|
||||
certificateOptions.setSerialNumber(QCA::BigInteger(10));
|
||||
certificateOptions.setValidityPeriod(startTime, endTime);
|
||||
|
||||
d->m_certificate = QSslCertificate(QCA::Certificate(certificateOptions, d->m_privateKey).toPEM().toLatin1());
|
||||
X509 *certificate = SslHelper::generateSelfSignedCertificate(d->m_privateKey, uuid);
|
||||
QByteArray pemCertificate = SslHelper::certificateToPEM(certificate);
|
||||
X509_free(certificate);
|
||||
d->m_certificate = QSslCertificate(pemCertificate);
|
||||
|
||||
QFile cert(certPath);
|
||||
bool error = false;
|
||||
if (!cert.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
|
||||
error = true;
|
||||
} else {
|
||||
cert.setPermissions(strictPermissions);
|
||||
int written = cert.write(d->m_certificate.toPem());
|
||||
int written = cert.write(pemCertificate);
|
||||
if (written <= 0) {
|
||||
error = true;
|
||||
}
|
||||
|
|
|
@ -68,9 +68,10 @@ public:
|
|||
private:
|
||||
KdeConnectConfig();
|
||||
|
||||
void loadPrivateKey();
|
||||
void loadOrGeneratePrivateKeyAndCertificate(const QString &keyPath, const QString &certPath);
|
||||
bool loadPrivateKey(const QString &path);
|
||||
bool loadCertificate(const QString &path);
|
||||
void generatePrivateKey(const QString &path);
|
||||
void loadCertificate();
|
||||
void generateCertificate(const QString &path);
|
||||
|
||||
struct KdeConnectConfigPrivate *d;
|
||||
|
|
100
core/sslhelper.cpp
Normal file
100
core/sslhelper.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2023 Albert Vaca <albertvaka@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
#include "sslhelper.h"
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
namespace SslHelper
|
||||
{
|
||||
|
||||
X509 *generateSelfSignedCertificate(EVP_PKEY *privateKey, const QString &commonName)
|
||||
{
|
||||
X509 *x509 = X509_new();
|
||||
X509_set_version(x509, 2);
|
||||
|
||||
// Generate a random serial number for the certificate
|
||||
BIGNUM *serialNumber = BN_new();
|
||||
BN_rand(serialNumber, 160, -1, 0);
|
||||
ASN1_INTEGER *serial = X509_get_serialNumber(x509);
|
||||
BN_to_ASN1_INTEGER(serialNumber, serial);
|
||||
BN_free(serialNumber);
|
||||
|
||||
// Set the certificate subject and issuer (self-signed)
|
||||
X509_NAME *name = X509_NAME_new();
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, reinterpret_cast<const unsigned char *>(commonName.toLatin1().data()), -1, -1, 0); // Common Name
|
||||
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, reinterpret_cast<const unsigned char *>("KDE"), -1, -1, 0); // Organization
|
||||
X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, reinterpret_cast<const unsigned char *>("KDE Connect"), -1, -1, 0); // Organizational Unit
|
||||
X509_set_subject_name(x509, name);
|
||||
X509_set_issuer_name(x509, name);
|
||||
X509_NAME_free(name);
|
||||
|
||||
// Set the certificate validity period
|
||||
time_t now = time(nullptr);
|
||||
int a_year_in_seconds = 356 * 24 * 60 * 60;
|
||||
ASN1_TIME_set(X509_get_notBefore(x509), now - a_year_in_seconds);
|
||||
ASN1_TIME_set(X509_get_notAfter(x509), now + 10 * a_year_in_seconds);
|
||||
|
||||
// Set the public key for the certificate
|
||||
X509_set_pubkey(x509, privateKey);
|
||||
|
||||
// Sign the certificate with the private key
|
||||
X509_sign(x509, privateKey, EVP_sha256());
|
||||
|
||||
return x509;
|
||||
}
|
||||
|
||||
EVP_PKEY *generateRsaPrivateKey()
|
||||
{
|
||||
EVP_PKEY *privateKey = EVP_PKEY_new();
|
||||
RSA *rsa = RSA_new();
|
||||
|
||||
BIGNUM *exponent = BN_new();
|
||||
BN_set_word(exponent, RSA_F4);
|
||||
|
||||
RSA_generate_key_ex(rsa, 2048, exponent, nullptr);
|
||||
EVP_PKEY_assign_RSA(privateKey, rsa);
|
||||
|
||||
BN_free(exponent);
|
||||
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
QByteArray certificateToPEM(X509 *certificate)
|
||||
{
|
||||
BIO *bio = BIO_new(BIO_s_mem());
|
||||
PEM_write_bio_X509(bio, certificate);
|
||||
BUF_MEM *mem = nullptr;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
QByteArray pemData(mem->data, mem->length);
|
||||
BIO_free_all(bio);
|
||||
return pemData;
|
||||
}
|
||||
|
||||
QByteArray privateKeyToPEM(EVP_PKEY *privateKey)
|
||||
{
|
||||
BIO *bio = BIO_new(BIO_s_mem());
|
||||
PEM_write_bio_PrivateKey(bio, privateKey, nullptr, nullptr, 0, nullptr, nullptr);
|
||||
BUF_MEM *mem = nullptr;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
QByteArray pemData(mem->data, mem->length);
|
||||
BIO_free_all(bio);
|
||||
return pemData;
|
||||
}
|
||||
|
||||
EVP_PKEY *pemToRsaPrivateKey(const QByteArray &privateKeyPem)
|
||||
{
|
||||
const char *pemData = privateKeyPem.constData();
|
||||
BIO *bio = BIO_new_mem_buf(pemData, -1);
|
||||
EVP_PKEY *privateKey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
||||
BIO_free(bio);
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
} // namespace SslHelper
|
32
core/sslhelper.h
Normal file
32
core/sslhelper.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2023 Albert Vaca <albertvaka@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
#ifndef SSLHELPER_H
|
||||
#define SSLHELPER_H
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
namespace SslHelper
|
||||
{
|
||||
|
||||
// Delete with X509_free(certificate);
|
||||
X509 *generateSelfSignedCertificate(EVP_PKEY *privateKey, const QString &commonName);
|
||||
|
||||
// Delete with EVP_PKEY_free(privateKey);
|
||||
EVP_PKEY *generateRsaPrivateKey();
|
||||
|
||||
QByteArray certificateToPEM(X509 *certificate);
|
||||
|
||||
QByteArray privateKeyToPEM(EVP_PKEY *privateKey);
|
||||
EVP_PKEY *pemToRsaPrivateKey(const QByteArray &privateKeyPem);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue