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