Changeset View
Changeset View
Standalone View
Standalone View
core/kdeconnectconfig.cpp
Show All 26 Lines | |||||
27 | #include <QFileInfo> | 27 | #include <QFileInfo> | ||
28 | #include <QUuid> | 28 | #include <QUuid> | ||
29 | #include <QDir> | 29 | #include <QDir> | ||
30 | #include <QStandardPaths> | 30 | #include <QStandardPaths> | ||
31 | #include <QCoreApplication> | 31 | #include <QCoreApplication> | ||
32 | #include <QHostInfo> | 32 | #include <QHostInfo> | ||
33 | #include <QSettings> | 33 | #include <QSettings> | ||
34 | #include <QSslCertificate> | 34 | #include <QSslCertificate> | ||
35 | #include <QtCrypto> | | |||
36 | #include <QThread> | 35 | #include <QThread> | ||
36 | #include <QProcess> | ||||
37 | 37 | | |||
38 | #include "core_debug.h" | 38 | #include "core_debug.h" | ||
39 | #include "dbushelper.h" | 39 | #include "dbushelper.h" | ||
40 | #include "daemon.h" | 40 | #include "daemon.h" | ||
41 | 41 | | |||
42 | const QFile::Permissions strictPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser; | 42 | const QFile::Permissions strictPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser; | ||
43 | 43 | | |||
44 | struct KdeConnectConfigPrivate { | 44 | struct KdeConnectConfigPrivate { | ||
45 | 45 | | |||
46 | // The Initializer object sets things up, and also does cleanup when it goes out of scope | 46 | QSslCertificate m_certificate; | ||
47 | // Note it's not being used anywhere. That's intended | | |||
48 | QCA::Initializer m_qcaInitializer; | | |||
49 | | ||||
50 | QCA::PrivateKey m_privateKey; | | |||
51 | QSslCertificate m_certificate; // Use QSslCertificate instead of QCA::Certificate due to compatibility with QSslSocket | | |||
52 | 47 | | |||
53 | QSettings* m_config; | 48 | QSettings* m_config; | ||
54 | QSettings* m_trustedDevices; | 49 | QSettings* m_trustedDevices; | ||
55 | 50 | | |||
56 | #ifdef USE_PRIVATE_DBUS | 51 | #ifdef USE_PRIVATE_DBUS | ||
57 | QString m_privateDBusAddress; // Private DBus Address cache | 52 | QString m_privateDBusAddress; // Private DBus Address cache | ||
58 | #endif | 53 | #endif | ||
59 | }; | 54 | }; | ||
60 | 55 | | |||
61 | KdeConnectConfig* KdeConnectConfig::instance() | 56 | KdeConnectConfig* KdeConnectConfig::instance() | ||
62 | { | 57 | { | ||
63 | static KdeConnectConfig* kcc = new KdeConnectConfig(); | 58 | static KdeConnectConfig* kcc = new KdeConnectConfig(); | ||
64 | return kcc; | 59 | return kcc; | ||
65 | } | 60 | } | ||
66 | 61 | | |||
67 | KdeConnectConfig::KdeConnectConfig() | 62 | KdeConnectConfig::KdeConnectConfig() | ||
68 | : d(new KdeConnectConfigPrivate) | 63 | : d(new KdeConnectConfigPrivate) | ||
69 | { | 64 | { | ||
70 | //qCDebug(KDECONNECT_CORE) << "QCA supported capabilities:" << QCA::supportedFeatures().join(","); | | |||
71 | if(!QCA::isSupported("rsa")) { | | |||
72 | qCritical() << "Could not find support for RSA in your QCA installation"; | | |||
73 | Daemon::instance()->reportError( | | |||
74 | i18n("KDE Connect failed to start"), | | |||
75 | i18n("Could not find support for RSA in your QCA installation. If your " | | |||
76 | "distribution provides separate packets for QCA-ossl and QCA-gnupg, " | | |||
77 | "make sure you have them installed and try again.")); | | |||
78 | } | | |||
79 | | ||||
80 | //Make sure base directory exists | 65 | //Make sure base directory exists | ||
81 | QDir().mkpath(baseConfigDir().path()); | 66 | QDir().mkpath(baseConfigDir().path()); | ||
82 | 67 | | |||
83 | //.config/kdeconnect/config | 68 | //.config/kdeconnect/config | ||
84 | d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); | 69 | d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); | ||
85 | d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat); | 70 | d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat); | ||
86 | 71 | | |||
87 | loadPrivateKey(); | 72 | loadCertificateAndPrivateKey(); | ||
88 | loadCertificate(); | | |||
89 | } | 73 | } | ||
90 | 74 | | |||
91 | QString KdeConnectConfig::name() | 75 | QString KdeConnectConfig::name() | ||
92 | { | 76 | { | ||
93 | QString username; | 77 | QString username; | ||
94 | #ifdef Q_OS_WIN | 78 | #ifdef Q_OS_WIN | ||
95 | username = QString::fromLatin1(qgetenv("USERNAME")); | 79 | username = QString::fromLatin1(qgetenv("USERNAME")); | ||
96 | #else | 80 | #else | ||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Line(s) | |||||
140 | } | 124 | } | ||
141 | 125 | | |||
142 | QStringList KdeConnectConfig::trustedDevices() | 126 | QStringList KdeConnectConfig::trustedDevices() | ||
143 | { | 127 | { | ||
144 | const QStringList& list = d->m_trustedDevices->childGroups(); | 128 | const QStringList& list = d->m_trustedDevices->childGroups(); | ||
145 | return list; | 129 | return list; | ||
146 | } | 130 | } | ||
147 | 131 | | |||
148 | | ||||
149 | void KdeConnectConfig::addTrustedDevice(const QString& id, const QString& name, const QString& type) | 132 | void KdeConnectConfig::addTrustedDevice(const QString& id, const QString& name, const QString& type) | ||
150 | { | 133 | { | ||
151 | d->m_trustedDevices->beginGroup(id); | 134 | d->m_trustedDevices->beginGroup(id); | ||
152 | d->m_trustedDevices->setValue(QStringLiteral("name"), name); | 135 | d->m_trustedDevices->setValue(QStringLiteral("name"), name); | ||
153 | d->m_trustedDevices->setValue(QStringLiteral("type"), type); | 136 | d->m_trustedDevices->setValue(QStringLiteral("type"), type); | ||
154 | d->m_trustedDevices->endGroup(); | 137 | d->m_trustedDevices->endGroup(); | ||
155 | d->m_trustedDevices->sync(); | 138 | d->m_trustedDevices->sync(); | ||
156 | 139 | | |||
Show All 32 Lines | |||||
189 | { | 172 | { | ||
190 | QString value; | 173 | QString value; | ||
191 | d->m_trustedDevices->beginGroup(deviceId); | 174 | d->m_trustedDevices->beginGroup(deviceId); | ||
192 | value = d->m_trustedDevices->value(key, defaultValue).toString(); | 175 | value = d->m_trustedDevices->value(key, defaultValue).toString(); | ||
193 | d->m_trustedDevices->endGroup(); | 176 | d->m_trustedDevices->endGroup(); | ||
194 | return value; | 177 | return value; | ||
195 | } | 178 | } | ||
196 | 179 | | |||
197 | | ||||
198 | QDir KdeConnectConfig::deviceConfigDir(const QString& deviceId) | 180 | QDir KdeConnectConfig::deviceConfigDir(const QString& deviceId) | ||
199 | { | 181 | { | ||
200 | QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); | 182 | QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); | ||
201 | return QDir(deviceConfigPath); | 183 | return QDir(deviceConfigPath); | ||
202 | } | 184 | } | ||
203 | 185 | | |||
204 | QDir KdeConnectConfig::pluginConfigDir(const QString& deviceId, const QString& pluginName) | 186 | QDir KdeConnectConfig::pluginConfigDir(const QString& deviceId, const QString& pluginName) | ||
205 | { | 187 | { | ||
206 | QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); | 188 | QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); | ||
207 | QString pluginConfigDir = QDir(deviceConfigPath).absoluteFilePath(pluginName); | 189 | QString pluginConfigDir = QDir(deviceConfigPath).absoluteFilePath(pluginName); | ||
208 | return QDir(pluginConfigDir); | 190 | return QDir(pluginConfigDir); | ||
209 | } | 191 | } | ||
210 | 192 | | |||
211 | void KdeConnectConfig::loadPrivateKey() | 193 | void KdeConnectConfig::loadCertificateAndPrivateKey() | ||
212 | { | | |||
213 | QString keyPath = privateKeyPath(); | | |||
214 | QFile privKey(keyPath); | | |||
215 | | ||||
216 | bool needsToGenerateKey = false; | | |||
217 | if (privKey.exists() && privKey.open(QIODevice::ReadOnly)) { | | |||
218 | QCA::ConvertResult result; | | |||
219 | d->m_privateKey = QCA::PrivateKey::fromPEM(QString::fromLatin1(privKey.readAll()), QCA::SecureArray(), &result); | | |||
220 | if (result != QCA::ConvertResult::ConvertGood) { | | |||
221 | qCWarning(KDECONNECT_CORE) << "Private key from" << keyPath << "is not valid"; | | |||
222 | needsToGenerateKey = true; | | |||
223 | } | | |||
224 | } else { | | |||
225 | needsToGenerateKey = true; | | |||
226 | } | | |||
227 | | ||||
228 | if (needsToGenerateKey) { | | |||
229 | generatePrivateKey(keyPath); | | |||
230 | } | | |||
231 | | ||||
232 | //Extra security check | | |||
233 | if (QFile::permissions(keyPath) != strictPermissions) { | | |||
234 | qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << keyPath; | | |||
235 | } | | |||
236 | } | | |||
237 | | ||||
238 | void KdeConnectConfig::loadCertificate() | | |||
239 | { | 194 | { | ||
240 | QString certPath = certificatePath(); | 195 | QString certPath = certificatePath(); | ||
241 | QFile cert(certPath); | 196 | QFile cert(certPath); | ||
242 | 197 | | |||
198 | QString privKeyPath = privateKeyPath(); | ||||
pinoUnsubmitted Not Done
pino: - missing handling of the return value of the command (what if fails?)
- please do not split… | |||||
199 | QFile privKey(privKeyPath); | ||||
200 | | ||||
243 | bool needsToGenerateCert = false; | 201 | bool needsToGenerateCert = false; | ||
if QSslCertificate::fromPath fails, the return is an empty list, and this will misbehave pino: if QSslCertificate::fromPath fails, the return is an empty list, and this will misbehave | |||||
244 | if (cert.exists() && cert.open(QIODevice::ReadOnly)) { | 202 | if (cert.exists() && cert.open(QIODevice::ReadOnly)) { | ||
245 | auto loadedCerts = QSslCertificate::fromPath(certPath); | 203 | auto loadedCerts = QSslCertificate::fromPath(certPath); | ||
246 | if (loadedCerts.empty()) { | 204 | if (loadedCerts.empty()) { | ||
247 | qCWarning(KDECONNECT_CORE) << "Certificate from" << certPath << "is not valid"; | 205 | qCWarning(KDECONNECT_CORE) << "Certificate from" << certPath << "is not valid"; | ||
248 | needsToGenerateCert = true; | 206 | needsToGenerateCert = true; | ||
hint: output the wrong permissions too, so at least the warning gives more clues pino: hint: output the wrong permissions too, so at least the warning gives more clues | |||||
249 | } else { | 207 | } else { | ||
250 | d->m_certificate = loadedCerts.at(0); | 208 | d->m_certificate = loadedCerts.at(0); | ||
251 | } | 209 | } | ||
252 | } else { | 210 | } else { | ||
253 | needsToGenerateCert = true; | 211 | needsToGenerateCert = true; | ||
254 | } | 212 | } | ||
255 | 213 | | |||
214 | // We don't actually need to load the private key here, but let's do some sanity checking | ||||
215 | if (!privKey.exists() || privKey.size() == 0) { | ||||
216 | needsToGenerateCert = true; | ||||
217 | } | ||||
218 | | ||||
256 | if (needsToGenerateCert) { | 219 | if (needsToGenerateCert) { | ||
257 | generateCertificate(certPath); | 220 | generateCertificateAndPrivateKey(); | ||
258 | } | 221 | } | ||
259 | 222 | | |||
260 | //Extra security check | 223 | //Extra security check | ||
261 | if (QFile::permissions(certPath) != strictPermissions) { | 224 | if (QFile::permissions(certPath) != strictPermissions) { | ||
262 | qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect certificate file has too open permissions " << certPath; | 225 | qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect certificate file has too open permissions " << certPath; | ||
263 | } | 226 | } | ||
264 | } | | |||
265 | | ||||
266 | void KdeConnectConfig::generatePrivateKey(const QString& keyPath) | | |||
267 | { | | |||
268 | qCDebug(KDECONNECT_CORE) << "Generating private key"; | | |||
269 | 227 | | |||
270 | bool error = false; | 228 | if (QFile::permissions(privKeyPath) != strictPermissions) { | ||
271 | 229 | qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << certPath; | |||
272 | d->m_privateKey = QCA::KeyGenerator().createRSA(2048); | | |||
273 | | ||||
274 | QFile privKey(keyPath); | | |||
275 | if (!privKey.open(QIODevice::ReadWrite | QIODevice::Truncate)) { | | |||
276 | error = true; | | |||
277 | } else { | | |||
278 | privKey.setPermissions(strictPermissions); | | |||
279 | int written = privKey.write(d->m_privateKey.toPEM().toLatin1()); | | |||
280 | if (written <= 0) { | | |||
281 | error = true; | | |||
282 | } | 230 | } | ||
283 | } | 231 | } | ||
284 | 232 | | |||
285 | if (error) { | 233 | void KdeConnectConfig::generateCertificateAndPrivateKey() | ||
286 | Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store private key file: %1", keyPath)); | | |||
287 | } | | |||
288 | | ||||
289 | } | | |||
290 | | ||||
291 | void KdeConnectConfig::generateCertificate(const QString& certPath) | | |||
292 | { | 234 | { | ||
293 | qCDebug(KDECONNECT_CORE) << "Generating certificate"; | 235 | qCDebug(KDECONNECT_CORE) << "Generating certificate"; | ||
236 | QFile cert(certificatePath()); | ||||
237 | QFile privKey(privateKeyPath()); | ||||
294 | 238 | | |||
295 | bool error = false; | 239 | if (!cert.exists() || !privKey.exists()) { | ||
296 | 240 | // No certificate yet. Probably first run. Let's generate one! | |||
297 | QString uuid = QUuid::createUuid().toString(); | 241 | QString uuid = QUuid::createUuid().toString(); | ||
298 | DbusHelper::filterNonExportableCharacters(uuid); | 242 | DbusHelper::filterNonExportableCharacters(uuid); | ||
299 | qCDebug(KDECONNECT_CORE) << "My id:" << uuid; | 243 | qCDebug(KDECONNECT_CORE) << "My id:" << uuid; | ||
300 | 244 | | |||
301 | // FIXME: We only use QCA here to generate the cert and key, would be nice to get rid of it completely. | 245 | 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}); | ||
302 | // The same thing we are doing with QCA could be done invoking openssl (although it's potentially less portable): | | |||
303 | // 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_" | | |||
304 | | ||||
305 | QCA::CertificateOptions certificateOptions = QCA::CertificateOptions(); | | |||
306 | QDateTime startTime = QDateTime::currentDateTime().addYears(-1); | | |||
307 | QDateTime endTime = startTime.addYears(10); | | |||
308 | QCA::CertificateInfo certificateInfo; | | |||
309 | certificateInfo.insert(QCA::CommonName, uuid); | | |||
310 | certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); | | |||
311 | certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); | | |||
312 | certificateOptions.setInfo(certificateInfo); | | |||
313 | certificateOptions.setFormat(QCA::PKCS10); | | |||
314 | certificateOptions.setSerialNumber(QCA::BigInteger(10)); | | |||
315 | certificateOptions.setValidityPeriod(startTime, endTime); | | |||
316 | 246 | | |||
317 | d->m_certificate = QSslCertificate(QCA::Certificate(certificateOptions, d->m_privateKey).toPEM().toLatin1()); | 247 | if (retVal) { | ||
318 | 248 | Daemon::instance()->reportError(i18n("Could not pair device"), i18n("Could not generate certificate. openssl returned %1", retVal)); | |||
319 | QFile cert(certPath); | 249 | } | ||
nicolasfella: Daemon::reportError | |||||
320 | if (!cert.open(QIODevice::ReadWrite | QIODevice::Truncate)) { | | |||
321 | error = true; | | |||
322 | } else { | | |||
323 | cert.setPermissions(strictPermissions); | | |||
324 | int written = cert.write(d->m_certificate.toPem()); | | |||
325 | if (written <= 0) { | | |||
326 | error = true; | | |||
327 | } | 250 | } | ||
251 | | ||||
252 | auto certificates = QSslCertificate::fromPath(certificatePath()); | ||||
253 | if (!certificates.isEmpty()) { | ||||
254 | d->m_certificate = certificates.at(0); | ||||
328 | } | 255 | } | ||
329 | 256 | | |||
330 | if (error) { | 257 | //Extra security check | ||
331 | Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store certificate file: %1", certPath)); | 258 | const QFile::Permissions strict = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser; | ||
259 | if (QFile::permissions(privateKeyPath()) != strict) { | ||||
260 | qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << privateKeyPath(); | ||||
332 | } | 261 | } | ||
333 | } | 262 | } | ||
334 | 263 | | |||
335 | #ifdef USE_PRIVATE_DBUS | 264 | #ifdef USE_PRIVATE_DBUS | ||
336 | QString KdeConnectConfig::privateDBusAddressPath() | 265 | QString KdeConnectConfig::privateDBusAddressPath() | ||
337 | { | 266 | { | ||
338 | return baseConfigDir().absoluteFilePath(QStringLiteral("private_dbus_address")); | 267 | return baseConfigDir().absoluteFilePath(QStringLiteral("private_dbus_address")); | ||
339 | } | 268 | } | ||
Show All 38 Lines |