diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,11 +28,8 @@ set(QT_MIN_VERSION "5.6.0") set(KF5_REQUIRED_COMPONENTS I18n DBusAddons CoreAddons IconThemes Config) set(KF5_OPTIONAL_COMPONENTS) - set(QCA_MIN_VERSION 2.0.0) pkg_search_module(SFOS REQUIRED sailfishapp) - pkg_check_modules(QCA2 qca2-qt5>=${QCA_MIN_VERSION} REQUIRED) add_definitions(-DSAILFISHOS) - include_directories(${QCA2_INCLUDEDIR}) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) else() set(KF5_MIN_VERSION "5.42.0") @@ -42,8 +39,7 @@ if(UNIX) set(KF5_OPTIONAL_COMPONENTS ${KF5_OPTIONAL_COMPONENTS} Runner) endif() - set(QCA_MIN_VERSION "2.1.0") - find_package(Qca-qt5 ${QCA_MIN_VERSION} REQUIRED) + if(NOT WIN32 AND NOT APPLE) find_package(KF5PulseAudioQt REQUIRED) endif() @@ -69,10 +65,16 @@ TYPE RUNTIME ) +find_program(OPENSSL openssl DOC "OpenSSL is needed for certificate generation") +if(${OPENSSL} STREQUAL "OPENSSL-NOTFOUND") + message(WARNING "openssl executable is needed to run KDE Connect") +endif() + option(PRIVATE_DBUS_ENABLED "Use private dbus session for kdeconnect" OFF) if(PRIVATE_DBUS_ENABLED OR APPLE) add_compile_definitions(USE_PRIVATE_DBUS) endif() + add_subdirectory(core) if(NOT SAILFISHOS) add_subdirectory(kcm) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -56,7 +56,6 @@ Qt5::Network KF5::CoreAddons KF5::KIOCore - qca-qt5 PRIVATE Qt5::DBus KF5::I18n diff --git a/core/kdeconnectconfig.h b/core/kdeconnectconfig.h --- a/core/kdeconnectconfig.h +++ b/core/kdeconnectconfig.h @@ -80,10 +80,8 @@ private: KdeConnectConfig(); - void loadPrivateKey(); - void generatePrivateKey(const QString& path); - void loadCertificate(); - void generateCertificate(const QString& path); + void loadCertificateAndPrivateKey(); + void generateCertificateAndPrivateKey(); struct KdeConnectConfigPrivate* d; }; diff --git a/core/kdeconnectconfig.cpp b/core/kdeconnectconfig.cpp --- a/core/kdeconnectconfig.cpp +++ b/core/kdeconnectconfig.cpp @@ -32,8 +32,8 @@ #include #include #include -#include #include +#include #include "core_debug.h" #include "dbushelper.h" @@ -43,12 +43,7 @@ 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 + QSslCertificate m_certificate; QSettings* m_config; QSettings* m_trustedDevices; @@ -67,25 +62,14 @@ 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()); //.config/kdeconnect/config 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(); + loadCertificateAndPrivateKey(); } QString KdeConnectConfig::name() @@ -145,7 +129,6 @@ return list; } - void KdeConnectConfig::addTrustedDevice(const QString& id, const QString& name, const QString& type) { d->m_trustedDevices->beginGroup(id); @@ -194,7 +177,6 @@ return value; } - QDir KdeConnectConfig::deviceConfigDir(const QString& deviceId) { QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); @@ -208,38 +190,14 @@ return QDir(pluginConfigDir); } -void KdeConnectConfig::loadPrivateKey() -{ - 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; - } - } else { - needsToGenerateKey = true; - } - - if (needsToGenerateKey) { - generatePrivateKey(keyPath); - } - - //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() +void KdeConnectConfig::loadCertificateAndPrivateKey() { QString certPath = certificatePath(); QFile cert(certPath); + QString privKeyPath = privateKeyPath(); + QFile privKey(privKeyPath); + bool needsToGenerateCert = false; if (cert.exists() && cert.open(QIODevice::ReadOnly)) { auto loadedCerts = QSslCertificate::fromPath(certPath); @@ -253,82 +211,53 @@ needsToGenerateCert = true; } + // We don't actually need to load the private key here, but let's do some sanity checking + if (!privKey.exists() || privKey.size() == 0) { + needsToGenerateCert = true; + } + if (needsToGenerateCert) { - generateCertificate(certPath); + generateCertificateAndPrivateKey(); } //Extra security check if (QFile::permissions(certPath) != strictPermissions) { qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect certificate file has too open permissions " << certPath; } -} - -void KdeConnectConfig::generatePrivateKey(const QString& keyPath) -{ - qCDebug(KDECONNECT_CORE) << "Generating private key"; - - bool error = false; - - d->m_privateKey = QCA::KeyGenerator().createRSA(2048); - QFile privKey(keyPath); - if (!privKey.open(QIODevice::ReadWrite | QIODevice::Truncate)) { - error = true; - } else { - privKey.setPermissions(strictPermissions); - int written = privKey.write(d->m_privateKey.toPEM().toLatin1()); - if (written <= 0) { - error = true; - } + if (QFile::permissions(privKeyPath) != strictPermissions) { + qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << certPath; } - - if (error) { - Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store private key file: %1", keyPath)); - } - } -void KdeConnectConfig::generateCertificate(const QString& certPath) +void KdeConnectConfig::generateCertificateAndPrivateKey() { qCDebug(KDECONNECT_CORE) << "Generating certificate"; + QFile cert(certificatePath()); + QFile privKey(privateKeyPath()); - bool error = false; + if (!cert.exists() || !privKey.exists()) { + // No certificate yet. Probably first run. Let's generate one! + QString uuid = QUuid::createUuid().toString(); + DbusHelper::filterNonExportableCharacters(uuid); + qCDebug(KDECONNECT_CORE) << "My id:" << uuid; - QString uuid = QUuid::createUuid().toString(); - DbusHelper::filterNonExportableCharacters(uuid); - qCDebug(KDECONNECT_CORE) << "My id:" << uuid; + int retVal = QProcess::execute(QStringLiteral("openssl"), {QStringLiteral("req"), QStringLiteral("-new"), QStringLiteral("-x509"), QStringLiteral("-sha256"), QStringLiteral("-newkey"), QStringLiteral("rsa:2048"), QStringLiteral("-nodes"), QStringLiteral("-keyout"), privateKeyPath(), QStringLiteral("-days"), QStringLiteral("3650"), QStringLiteral("-out"), certificatePath(), QStringLiteral("-subj"), QStringLiteral("/O=KDE/OU=KDEConnect/CN=") + 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()); - - QFile cert(certPath); - if (!cert.open(QIODevice::ReadWrite | QIODevice::Truncate)) { - error = true; - } else { - cert.setPermissions(strictPermissions); - int written = cert.write(d->m_certificate.toPem()); - if (written <= 0) { - error = true; + if (retVal) { + Daemon::instance()->reportError(i18n("Could not pair device"), i18n("Could not generate certificate. openssl returned %1", retVal)); } } - if (error) { - Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store certificate file: %1", certPath)); + auto certificates = QSslCertificate::fromPath(certificatePath()); + if (!certificates.isEmpty()) { + d->m_certificate = certificates.at(0); + } + + //Extra security check + const QFile::Permissions strict = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser; + if (QFile::permissions(privateKeyPath()) != strict) { + qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << privateKeyPath(); } } @@ -369,7 +298,7 @@ } qCDebug(KDECONNECT_CORE) << "Private dbus address: " << addr; - + d->m_privateDBusAddress = addr; return addr; diff --git a/sfos/rpm/kdeconnect-sfos.spec b/sfos/rpm/kdeconnect-sfos.spec --- a/sfos/rpm/kdeconnect-sfos.spec +++ b/sfos/rpm/kdeconnect-sfos.spec @@ -22,7 +22,6 @@ BuildRequires: pkgconfig(Qt5Qml) BuildRequires: pkgconfig(Qt5Quick) BuildRequires: pkgconfig(nemonotifications-qt5) -BuildRequires: pkgconfig(qca2-qt5) >= 2.0.0 BuildRequires: desktop-file-utils BuildRequires: cmake >= 3.0 BuildRequires: extra-cmake-modules >= 5.31.0 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,7 +15,6 @@ Qt5::DBus Qt5::Network Qt5::Test - qca-qt5 ) set(kdeconnect_sms_libraries diff --git a/tests/lanlinkprovidertest.cpp b/tests/lanlinkprovidertest.cpp --- a/tests/lanlinkprovidertest.cpp +++ b/tests/lanlinkprovidertest.cpp @@ -31,9 +31,9 @@ #include #include #include -#include #include + /* * This class tests the working of LanLinkProvider under different conditions that when identity packet is received over TCP, over UDP and same when the device is paired. * It depends on KdeConnectConfig since LanLinkProvider internally uses it. @@ -85,10 +85,10 @@ // Attributes for test device QString m_deviceId; QString m_name; - QCA::PrivateKey m_privateKey; QSslCertificate m_certificate; + QTemporaryFile m_privateKeyFile; - QSslCertificate generateCertificate(QString&, QCA::PrivateKey&); + QSslCertificate generateCertificate(QString&); void addTrustedDevice(); void removeTrustedDevice(); void setSocketAttributes(QSslSocket* socket); @@ -102,8 +102,7 @@ m_deviceId = QStringLiteral("testdevice"); m_name = QStringLiteral("Test Device"); - m_privateKey = QCA::KeyGenerator().createRSA(2048); - m_certificate = generateCertificate(m_deviceId, m_privateKey); + m_certificate = generateCertificate(m_deviceId); m_identityPacket = QStringLiteral("{\"id\":1439365924847,\"type\":\"kdeconnect.identity\",\"body\":{\"deviceId\":\"testdevice\",\"deviceName\":\"Test Device\",\"protocolVersion\":6,\"deviceType\":\"phone\",\"tcpPort\":") + QString::number(TEST_PORT) + QStringLiteral("}}"); } @@ -405,28 +404,23 @@ QVERIFY2(body.contains(QStringLiteral("deviceType")), "Device type not found in identity packet"); } -QSslCertificate LanLinkProviderTest::generateCertificate(QString& commonName, QCA::PrivateKey& privateKey) +QSslCertificate LanLinkProviderTest::generateCertificate(QString& commonName) { - QDateTime startTime = QDateTime::currentDateTime(); - QDateTime endTime = startTime.addYears(10); - QCA::CertificateInfo certificateInfo; - certificateInfo.insert(QCA::CommonName,commonName); - certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); - certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); - - QCA::CertificateOptions certificateOptions(QCA::PKCS10); - certificateOptions.setSerialNumber(10); - certificateOptions.setInfo(certificateInfo); - certificateOptions.setValidityPeriod(startTime, endTime); - certificateOptions.setFormat(QCA::PKCS10); - - QSslCertificate certificate = QSslCertificate(QCA::Certificate(certificateOptions, privateKey).toPEM().toLatin1()); + QTemporaryFile cert; + cert.open(); + m_privateKeyFile.open(); + + QProcess::execute(QStringLiteral("openssl"), {QStringLiteral("req"), QStringLiteral("-new"), QStringLiteral("-x509"), QStringLiteral("-sha256"), QStringLiteral("-newkey"), + QStringLiteral("rsa:2048"), QStringLiteral("-nodes"), QStringLiteral("-keyout"), m_privateKeyFile.fileName(), QStringLiteral("-days"), + QStringLiteral("3650"), QStringLiteral("-out"), cert.fileName(), QStringLiteral("-subj"), QStringLiteral("/O=KDE/OU=KDEConnect/CN=%1").arg(commonName)}); + + QSslCertificate certificate = QSslCertificate::fromPath(cert.fileName()).at(0); return certificate; } void LanLinkProviderTest::setSocketAttributes(QSslSocket* socket) { - socket->setPrivateKey(QSslKey(m_privateKey.toPEM().toLatin1(), QSsl::Rsa)); + socket->setPrivateKey(m_privateKeyFile.fileName()); socket->setLocalCertificate(m_certificate); } diff --git a/tests/testsslsocketlinereader.cpp b/tests/testsslsocketlinereader.cpp --- a/tests/testsslsocketlinereader.cpp +++ b/tests/testsslsocketlinereader.cpp @@ -22,10 +22,11 @@ #include "../core/backends/lan/socketlinereader.h" #include -#include #include #include #include +#include +#include /* * This class tests the behaviour of socket line reader when the connection if over ssl. Since SSL takes part below application layer, @@ -54,7 +55,6 @@ const int PORT = 7894; const int TIMEOUT = 4 * 1000; QTimer m_timer; - QCA::Initializer m_qcaInitializer; QEventLoop m_loop; QList m_packets; Server* m_server; @@ -333,23 +333,17 @@ void TestSslSocketLineReader::setSocketAttributes(QSslSocket* socket, QString deviceName) { - QDateTime startTime = QDateTime::currentDateTime(); - QDateTime endTime = startTime.addYears(10); - QCA::CertificateInfo certificateInfo; - certificateInfo.insert(QCA::CommonName,deviceName); - certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); - certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); + QTemporaryFile cert; + cert.open(); + QTemporaryFile priv; + priv.open(); - QCA::CertificateOptions certificateOptions(QCA::PKCS10); - certificateOptions.setSerialNumber(10); - certificateOptions.setInfo(certificateInfo); - certificateOptions.setValidityPeriod(startTime, endTime); - certificateOptions.setFormat(QCA::PKCS10); + QProcess::execute(QStringLiteral("openssl"), {QStringLiteral("req"), QStringLiteral("-new"), QStringLiteral("-x509"), QStringLiteral("-sha256"), QStringLiteral("-newkey"), + QStringLiteral("rsa:2048"), QStringLiteral("-nodes"), QStringLiteral("-keyout"), priv.fileName(), QStringLiteral("-days"), + QStringLiteral("3650"), QStringLiteral("-out"), cert.fileName(), QStringLiteral("-subj"), QStringLiteral("/O=KDE/OU=KDEConnect/CN=%1").arg(deviceName)}); + QSslCertificate certificate = QSslCertificate::fromPath(cert.fileName()).at(0); - QCA::PrivateKey privKey = QCA::KeyGenerator().createRSA(2048); - QSslCertificate certificate = QSslCertificate(QCA::Certificate(certificateOptions, privKey).toPEM().toLatin1()); - - socket->setPrivateKey(QSslKey(privKey.toPEM().toLatin1(), QSsl::Rsa)); + socket->setPrivateKey(priv.fileName()); socket->setLocalCertificate(certificate); }