File indexing completed on 2024-04-14 04:51:41

0001 /**
0002  * SPDX-FileCopyrightText: 2015 Albert Vaca <albertvaka@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "kdeconnectconfig.h"
0008 
0009 #include <KLocalizedString>
0010 
0011 #include <QCoreApplication>
0012 #include <QDebug>
0013 #include <QDir>
0014 #include <QFile>
0015 #include <QFileInfo>
0016 #include <QHostInfo>
0017 #include <QSettings>
0018 #include <QSslCertificate>
0019 #include <QStandardPaths>
0020 #include <QThread>
0021 #include <QUuid>
0022 
0023 #include "core_debug.h"
0024 #include "daemon.h"
0025 #include "dbushelper.h"
0026 #include "deviceinfo.h"
0027 #include "pluginloader.h"
0028 #include "sslhelper.h"
0029 
0030 const QFile::Permissions strictPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser;
0031 
0032 struct KdeConnectConfigPrivate {
0033     QSslKey m_privateKey;
0034     QSslCertificate m_certificate;
0035 
0036     QSettings *m_config;
0037     QSettings *m_trustedDevices;
0038 
0039 #ifdef Q_OS_MAC
0040     QString m_privateDBusAddress; // Private DBus Address cache
0041 #endif
0042 };
0043 
0044 static QString getDefaultDeviceName()
0045 {
0046     return QHostInfo::localHostName();
0047 }
0048 
0049 KdeConnectConfig &KdeConnectConfig::instance()
0050 {
0051     static KdeConnectConfig kcc;
0052     return kcc;
0053 }
0054 
0055 KdeConnectConfig::KdeConnectConfig()
0056     : d(new KdeConnectConfigPrivate)
0057 {
0058     // Make sure base directory exists
0059     QDir().mkpath(baseConfigDir().path());
0060 
0061     //.config/kdeconnect/config
0062     d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat);
0063     d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat);
0064 
0065     loadOrGeneratePrivateKeyAndCertificate(privateKeyPath(), certificatePath());
0066 
0067     if (name().isEmpty()) {
0068         setName(getDefaultDeviceName());
0069     }
0070 }
0071 
0072 QString KdeConnectConfig::name()
0073 {
0074     return d->m_config->value(QStringLiteral("name")).toString();
0075 }
0076 
0077 void KdeConnectConfig::setName(const QString &name)
0078 {
0079     d->m_config->setValue(QStringLiteral("name"), name);
0080     d->m_config->sync();
0081 }
0082 
0083 DeviceType KdeConnectConfig::deviceType()
0084 {
0085     const QByteArrayList platforms = qgetenv("PLASMA_PLATFORM").split(':');
0086 
0087     if (platforms.contains("phone")) {
0088         return DeviceType::Phone;
0089     } else if (platforms.contains("tablet")) {
0090         return DeviceType::Tablet;
0091     } else if (platforms.contains("mediacenter")) {
0092         return DeviceType::Tv;
0093     }
0094 
0095     // TODO non-Plasma mobile platforms
0096 
0097     return DeviceType::Desktop;
0098 }
0099 
0100 QString KdeConnectConfig::deviceId()
0101 {
0102     return d->m_certificate.subjectInfo(QSslCertificate::CommonName).constFirst();
0103 }
0104 
0105 QString KdeConnectConfig::privateKeyPath()
0106 {
0107     return baseConfigDir().absoluteFilePath(QStringLiteral("privateKey.pem"));
0108 }
0109 
0110 QString KdeConnectConfig::certificatePath()
0111 {
0112     return baseConfigDir().absoluteFilePath(QStringLiteral("certificate.pem"));
0113 }
0114 
0115 QSslCertificate KdeConnectConfig::certificate()
0116 {
0117     return d->m_certificate;
0118 }
0119 
0120 DeviceInfo KdeConnectConfig::deviceInfo()
0121 {
0122     const auto incoming = PluginLoader::instance()->incomingCapabilities();
0123     const auto outgoing = PluginLoader::instance()->outgoingCapabilities();
0124     return DeviceInfo(deviceId(),
0125                       certificate(),
0126                       name(),
0127                       deviceType(),
0128                       NetworkPacket::s_protocolVersion,
0129                       QSet(incoming.begin(), incoming.end()),
0130                       QSet(outgoing.begin(), outgoing.end()));
0131 }
0132 
0133 QDir KdeConnectConfig::baseConfigDir()
0134 {
0135     QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0136     QString kdeconnectConfigPath = QDir(configPath).absoluteFilePath(QStringLiteral("kdeconnect"));
0137     return QDir(kdeconnectConfigPath);
0138 }
0139 
0140 QStringList KdeConnectConfig::trustedDevices()
0141 {
0142     const QStringList &list = d->m_trustedDevices->childGroups();
0143     return list;
0144 }
0145 
0146 void KdeConnectConfig::addTrustedDevice(const DeviceInfo &deviceInfo)
0147 {
0148     d->m_trustedDevices->beginGroup(deviceInfo.id);
0149     d->m_trustedDevices->setValue(QStringLiteral("name"), deviceInfo.name);
0150     d->m_trustedDevices->setValue(QStringLiteral("type"), deviceInfo.type.toString());
0151     QString certString = QString::fromLatin1(deviceInfo.certificate.toPem());
0152     d->m_trustedDevices->setValue(QStringLiteral("certificate"), certString);
0153     d->m_trustedDevices->endGroup();
0154     d->m_trustedDevices->sync();
0155 
0156     QDir().mkpath(deviceConfigDir(deviceInfo.id).path());
0157 }
0158 
0159 void KdeConnectConfig::updateTrustedDeviceInfo(const DeviceInfo &deviceInfo)
0160 {
0161     if (!trustedDevices().contains(deviceInfo.id)) {
0162         // do not store values for untrusted devices (it would make them trusted)
0163         return;
0164     }
0165 
0166     d->m_trustedDevices->beginGroup(deviceInfo.id);
0167     d->m_trustedDevices->setValue(QStringLiteral("name"), deviceInfo.name);
0168     d->m_trustedDevices->setValue(QStringLiteral("type"), deviceInfo.type.toString());
0169     d->m_trustedDevices->endGroup();
0170     d->m_trustedDevices->sync();
0171 }
0172 
0173 QSslCertificate KdeConnectConfig::getTrustedDeviceCertificate(const QString &id)
0174 {
0175     d->m_trustedDevices->beginGroup(id);
0176     QString certString = d->m_trustedDevices->value(QStringLiteral("certificate"), QString()).toString();
0177     d->m_trustedDevices->endGroup();
0178     return QSslCertificate(certString.toLatin1());
0179 }
0180 
0181 DeviceInfo KdeConnectConfig::getTrustedDevice(const QString &id)
0182 {
0183     d->m_trustedDevices->beginGroup(id);
0184 
0185     QString certString = d->m_trustedDevices->value(QStringLiteral("certificate"), QString()).toString();
0186     QSslCertificate certificate(certString.toLatin1());
0187     QString name = d->m_trustedDevices->value(QStringLiteral("name"), QLatin1String("unnamed")).toString();
0188     QString typeString = d->m_trustedDevices->value(QStringLiteral("type"), QLatin1String("unknown")).toString();
0189     DeviceType type = DeviceType::FromString(typeString);
0190 
0191     d->m_trustedDevices->endGroup();
0192 
0193     return DeviceInfo(id, certificate, name, type);
0194 }
0195 
0196 void KdeConnectConfig::removeTrustedDevice(const QString &deviceId)
0197 {
0198     d->m_trustedDevices->remove(deviceId);
0199     d->m_trustedDevices->sync();
0200     // We do not remove the config files.
0201 }
0202 
0203 // Utility functions to set and get a value
0204 void KdeConnectConfig::setDeviceProperty(const QString &deviceId, const QString &key, const QString &value)
0205 {
0206     // do not store values for untrusted devices (it would make them trusted)
0207     if (!trustedDevices().contains(deviceId))
0208         return;
0209 
0210     d->m_trustedDevices->beginGroup(deviceId);
0211     d->m_trustedDevices->setValue(key, value);
0212     d->m_trustedDevices->endGroup();
0213     d->m_trustedDevices->sync();
0214 }
0215 
0216 QString KdeConnectConfig::getDeviceProperty(const QString &deviceId, const QString &key, const QString &defaultValue)
0217 {
0218     QString value;
0219     d->m_trustedDevices->beginGroup(deviceId);
0220     value = d->m_trustedDevices->value(key, defaultValue).toString();
0221     d->m_trustedDevices->endGroup();
0222     return value;
0223 }
0224 
0225 void KdeConnectConfig::setCustomDevices(const QStringList &addresses)
0226 {
0227     d->m_config->setValue(QStringLiteral("customDevices"), addresses);
0228     d->m_config->sync();
0229 }
0230 
0231 QStringList KdeConnectConfig::customDevices() const
0232 {
0233     return d->m_config->value(QStringLiteral("customDevices")).toStringList();
0234 }
0235 
0236 QDir KdeConnectConfig::deviceConfigDir(const QString &deviceId)
0237 {
0238     QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId);
0239     return QDir(deviceConfigPath);
0240 }
0241 
0242 QDir KdeConnectConfig::pluginConfigDir(const QString &deviceId, const QString &pluginName)
0243 {
0244     QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId);
0245     QString pluginConfigDir = QDir(deviceConfigPath).absoluteFilePath(pluginName);
0246     return QDir(pluginConfigDir);
0247 }
0248 
0249 bool KdeConnectConfig::loadPrivateKey(const QString &keyPath)
0250 {
0251     QFile privKey(keyPath);
0252     if (privKey.exists() && privKey.open(QIODevice::ReadOnly)) {
0253         d->m_privateKey = QSslKey(privKey.readAll(), QSsl::KeyAlgorithm::Rsa);
0254         if (d->m_privateKey.isNull()) {
0255             qCWarning(KDECONNECT_CORE) << "Private key from" << keyPath << "is not valid!";
0256         }
0257     }
0258     return d->m_privateKey.isNull();
0259 }
0260 
0261 bool KdeConnectConfig::loadCertificate(const QString &certPath)
0262 {
0263     QFile cert(certPath);
0264     if (cert.exists() && cert.open(QIODevice::ReadOnly)) {
0265         d->m_certificate = QSslCertificate(cert.readAll());
0266         if (d->m_certificate.isNull()) {
0267             qCWarning(KDECONNECT_CORE) << "Certificate from" << certPath << "is not valid";
0268         }
0269     }
0270     return d->m_certificate.isNull();
0271 }
0272 
0273 void KdeConnectConfig::loadOrGeneratePrivateKeyAndCertificate(const QString &keyPath, const QString &certPath)
0274 {
0275     bool needsToGenerateKey = loadPrivateKey(keyPath);
0276     bool needsToGenerateCert = needsToGenerateKey || loadCertificate(certPath);
0277 
0278     if (needsToGenerateKey) {
0279         generatePrivateKey(keyPath);
0280     }
0281     if (needsToGenerateCert) {
0282         generateCertificate(certPath);
0283     }
0284 
0285     // Extra security check
0286     if (QFile::permissions(keyPath) != strictPermissions) {
0287         qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << keyPath;
0288     }
0289     if (QFile::permissions(certPath) != strictPermissions) {
0290         qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect certificate file has too open permissions " << certPath;
0291     }
0292 }
0293 
0294 void KdeConnectConfig::generatePrivateKey(const QString &keyPath)
0295 {
0296     qCDebug(KDECONNECT_CORE) << "Generating private key";
0297 
0298     d->m_privateKey = SslHelper::generateRsaPrivateKey();
0299     if (d->m_privateKey.isNull()) {
0300         qCritical() << "Could not generate the private key";
0301         Daemon::instance()->reportError(i18n("KDE Connect failed to start"), i18n("Could not generate the private key."));
0302     }
0303 
0304     QFile privKey(keyPath);
0305     bool error = false;
0306     if (!privKey.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
0307         error = true;
0308     } else {
0309         privKey.setPermissions(strictPermissions);
0310         int written = privKey.write(d->m_privateKey.toPem());
0311         if (written <= 0) {
0312             error = true;
0313         }
0314     }
0315 
0316     if (error) {
0317         Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store private key file: %1", keyPath));
0318     }
0319 }
0320 
0321 void KdeConnectConfig::generateCertificate(const QString &certPath)
0322 {
0323     qCDebug(KDECONNECT_CORE) << "Generating certificate";
0324 
0325     QString uuid = QUuid::createUuid().toString();
0326     DBusHelper::filterNonExportableCharacters(uuid);
0327     qCDebug(KDECONNECT_CORE) << "My id:" << uuid;
0328 
0329     d->m_certificate = SslHelper::generateSelfSignedCertificate(d->m_privateKey, uuid);
0330     if (d->m_certificate.isNull()) {
0331         qCritical() << "Could not generate a certificate";
0332         Daemon::instance()->reportError(i18n("KDE Connect failed to start"), i18n("Could not generate the device certificate."));
0333     }
0334 
0335     QFile cert(certPath);
0336     bool error = false;
0337     if (!cert.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
0338         error = true;
0339     } else {
0340         cert.setPermissions(strictPermissions);
0341         int written = cert.write(d->m_certificate.toPem());
0342         if (written <= 0) {
0343             error = true;
0344         }
0345     }
0346 
0347     if (error) {
0348         Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store certificate file: %1", certPath));
0349     }
0350 }
0351 
0352 #ifdef Q_OS_MAC
0353 QString KdeConnectConfig::privateDBusAddressPath()
0354 {
0355     return baseConfigDir().absoluteFilePath(QStringLiteral("private_dbus_address"));
0356 }
0357 
0358 QString KdeConnectConfig::privateDBusAddress()
0359 {
0360     if (d->m_privateDBusAddress.length() != 0)
0361         return d->m_privateDBusAddress;
0362 
0363     QString dbusAddressPath = privateDBusAddressPath();
0364     QFile dbusAddressFile(dbusAddressPath);
0365 
0366     if (!dbusAddressFile.open(QFile::ReadOnly | QFile::Text)) {
0367         qCCritical(KDECONNECT_CORE) << "Private DBus enabled but error read private dbus address conf";
0368         exit(1);
0369     }
0370 
0371     QTextStream in(&dbusAddressFile);
0372 
0373     qCDebug(KDECONNECT_CORE) << "Waiting for private dbus";
0374 
0375     int retry = 0;
0376     QString addr = in.readLine();
0377     while (addr.length() == 0 && retry < 5) {
0378         qCDebug(KDECONNECT_CORE) << "Retry reading private DBus address after 3s";
0379         QThread::sleep(3);
0380         retry++;
0381         addr = in.readLine(); // Read until first not empty line
0382     }
0383 
0384     if (addr.length() == 0) {
0385         qCCritical(KDECONNECT_CORE) << "Private DBus enabled but read private dbus address failed";
0386         exit(1);
0387     }
0388 
0389     qCDebug(KDECONNECT_CORE) << "Private dbus address: " << addr;
0390 
0391     d->m_privateDBusAddress = addr;
0392 
0393     return addr;
0394 }
0395 #endif