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