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 | struct KdeConnectConfigPrivate { | 41 | struct KdeConnectConfigPrivate { | ||
42 | 42 | | |||
43 | // The Initializer object sets things up, and also does cleanup when it goes out of scope | 43 | QSslCertificate m_certificate; | ||
44 | // Note it's not being used anywhere. That's intended | | |||
45 | QCA::Initializer m_qcaInitializer; | | |||
46 | | ||||
47 | QCA::PrivateKey m_privateKey; | | |||
48 | QSslCertificate m_certificate; // Use QSslCertificate instead of QCA::Certificate due to compatibility with QSslSocket | | |||
49 | 44 | | |||
50 | QSettings* m_config; | 45 | QSettings* m_config; | ||
51 | QSettings* m_trustedDevices; | 46 | QSettings* m_trustedDevices; | ||
52 | 47 | | |||
53 | }; | 48 | }; | ||
54 | 49 | | |||
55 | KdeConnectConfig* KdeConnectConfig::instance() | 50 | KdeConnectConfig* KdeConnectConfig::instance() | ||
56 | { | 51 | { | ||
57 | static KdeConnectConfig* kcc = new KdeConnectConfig(); | 52 | static KdeConnectConfig* kcc = new KdeConnectConfig(); | ||
58 | return kcc; | 53 | return kcc; | ||
59 | } | 54 | } | ||
60 | 55 | | |||
61 | KdeConnectConfig::KdeConnectConfig() | 56 | KdeConnectConfig::KdeConnectConfig() | ||
62 | : d(new KdeConnectConfigPrivate) | 57 | : d(new KdeConnectConfigPrivate) | ||
63 | { | 58 | { | ||
64 | //qCDebug(KDECONNECT_CORE) << "QCA supported capabilities:" << QCA::supportedFeatures().join(","); | | |||
65 | if(!QCA::isSupported("rsa")) { | | |||
66 | qCritical() << "Could not find support for RSA in your QCA installation"; | | |||
67 | Daemon::instance()->reportError( | | |||
68 | i18n("KDE Connect failed to start"), | | |||
69 | i18n("Could not find support for RSA in your QCA installation. If your " | | |||
70 | "distribution provides separate packets for QCA-ossl and QCA-gnupg, " | | |||
71 | "make sure you have them installed and try again.")); | | |||
72 | return; | | |||
73 | } | | |||
74 | | ||||
75 | //Make sure base directory exists | 59 | //Make sure base directory exists | ||
76 | QDir().mkpath(baseConfigDir().path()); | 60 | QDir().mkpath(baseConfigDir().path()); | ||
77 | 61 | | |||
78 | //.config/kdeconnect/config | 62 | //.config/kdeconnect/config | ||
79 | d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); | 63 | d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); | ||
80 | d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat); | 64 | d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat); | ||
81 | 65 | | |||
82 | loadPrivateKey(); | | |||
83 | loadCertificate(); | 66 | loadCertificate(); | ||
84 | } | 67 | } | ||
85 | 68 | | |||
86 | QString KdeConnectConfig::name() | 69 | QString KdeConnectConfig::name() | ||
87 | { | 70 | { | ||
88 | QString username; | 71 | QString username; | ||
89 | #ifdef Q_OS_WIN | 72 | #ifdef Q_OS_WIN | ||
90 | username = qgetenv("USERNAME"); | 73 | username = qgetenv("USERNAME"); | ||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Line(s) | |||||
135 | } | 118 | } | ||
136 | 119 | | |||
137 | QStringList KdeConnectConfig::trustedDevices() | 120 | QStringList KdeConnectConfig::trustedDevices() | ||
138 | { | 121 | { | ||
139 | const QStringList& list = d->m_trustedDevices->childGroups(); | 122 | const QStringList& list = d->m_trustedDevices->childGroups(); | ||
140 | return list; | 123 | return list; | ||
141 | } | 124 | } | ||
142 | 125 | | |||
143 | | ||||
144 | void KdeConnectConfig::addTrustedDevice(const QString& id, const QString& name, const QString& type) | 126 | void KdeConnectConfig::addTrustedDevice(const QString& id, const QString& name, const QString& type) | ||
145 | { | 127 | { | ||
146 | d->m_trustedDevices->beginGroup(id); | 128 | d->m_trustedDevices->beginGroup(id); | ||
147 | d->m_trustedDevices->setValue(QStringLiteral("name"), name); | 129 | d->m_trustedDevices->setValue(QStringLiteral("name"), name); | ||
148 | d->m_trustedDevices->setValue(QStringLiteral("type"), type); | 130 | d->m_trustedDevices->setValue(QStringLiteral("type"), type); | ||
149 | d->m_trustedDevices->endGroup(); | 131 | d->m_trustedDevices->endGroup(); | ||
150 | d->m_trustedDevices->sync(); | 132 | d->m_trustedDevices->sync(); | ||
151 | 133 | | |||
Show All 32 Lines | |||||
184 | { | 166 | { | ||
185 | QString value; | 167 | QString value; | ||
186 | d->m_trustedDevices->beginGroup(deviceId); | 168 | d->m_trustedDevices->beginGroup(deviceId); | ||
187 | value = d->m_trustedDevices->value(key, defaultValue).toString(); | 169 | value = d->m_trustedDevices->value(key, defaultValue).toString(); | ||
188 | d->m_trustedDevices->endGroup(); | 170 | d->m_trustedDevices->endGroup(); | ||
189 | return value; | 171 | return value; | ||
190 | } | 172 | } | ||
191 | 173 | | |||
192 | | ||||
193 | QDir KdeConnectConfig::deviceConfigDir(const QString& deviceId) | 174 | QDir KdeConnectConfig::deviceConfigDir(const QString& deviceId) | ||
194 | { | 175 | { | ||
195 | QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); | 176 | QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); | ||
196 | return QDir(deviceConfigPath); | 177 | return QDir(deviceConfigPath); | ||
197 | } | 178 | } | ||
198 | 179 | | |||
199 | QDir KdeConnectConfig::pluginConfigDir(const QString& deviceId, const QString& pluginName) | 180 | QDir KdeConnectConfig::pluginConfigDir(const QString& deviceId, const QString& pluginName) | ||
200 | { | 181 | { | ||
201 | QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); | 182 | QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); | ||
202 | QString pluginConfigDir = QDir(deviceConfigPath).absoluteFilePath(pluginName); | 183 | QString pluginConfigDir = QDir(deviceConfigPath).absoluteFilePath(pluginName); | ||
203 | return QDir(pluginConfigDir); | 184 | return QDir(pluginConfigDir); | ||
204 | } | 185 | } | ||
205 | 186 | | |||
206 | void KdeConnectConfig::loadPrivateKey() | | |||
207 | { | | |||
208 | QString keyPath = privateKeyPath(); | | |||
209 | QFile privKey(keyPath); | | |||
210 | if (privKey.exists() && privKey.open(QIODevice::ReadOnly)) { | | |||
211 | | ||||
212 | d->m_privateKey = QCA::PrivateKey::fromPEM(privKey.readAll()); | | |||
213 | | ||||
214 | } else { | | |||
215 | | ||||
216 | d->m_privateKey = QCA::KeyGenerator().createRSA(2048); | | |||
217 | | ||||
218 | if (!privKey.open(QIODevice::ReadWrite | QIODevice::Truncate)) { | | |||
219 | Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store private key file: %1", keyPath)); | | |||
220 | } else { | | |||
221 | privKey.setPermissions(strict); | | |||
222 | privKey.write(d->m_privateKey.toPEM().toLatin1()); | | |||
223 | } | | |||
224 | } | | |||
225 | | ||||
226 | //Extra security check | | |||
227 | if (QFile::permissions(keyPath) != strict) { | | |||
228 | qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << keyPath; | | |||
229 | } | | |||
230 | } | | |||
231 | | ||||
232 | void KdeConnectConfig::loadCertificate() | 187 | void KdeConnectConfig::loadCertificate() | ||
233 | { | 188 | { | ||
234 | QString certPath = certificatePath(); | 189 | QFile cert(certificatePath()); | ||
235 | QFile cert(certPath); | 190 | QFile privKey(privateKeyPath()); | ||
236 | if (cert.exists() && cert.open(QIODevice::ReadOnly)) { | | |||
237 | | ||||
238 | d->m_certificate = QSslCertificate::fromPath(certPath).at(0); | | |||
239 | | ||||
240 | } else { | | |||
241 | 191 | | |||
192 | if (!cert.exists() || !privKey.exists()) { | ||||
242 | // No certificate yet. Probably first run. Let's generate one! | 193 | // No certificate yet. Probably first run. Let's generate one! | ||
243 | | ||||
244 | QString uuid = QUuid::createUuid().toString(); | 194 | QString uuid = QUuid::createUuid().toString(); | ||
245 | DbusHelper::filterNonExportableCharacters(uuid); | 195 | DbusHelper::filterNonExportableCharacters(uuid); | ||
246 | qCDebug(KDECONNECT_CORE) << "My id:" << uuid; | 196 | qCDebug(KDECONNECT_CORE) << "My id:" << uuid; | ||
247 | 197 | | |||
248 | // FIXME: We only use QCA here to generate the cert and key, would be nice to get rid of it completely. | 198 | int retVal = QProcess::execute("openssl", {"req", "-new", "-x509", "-sha256", "-newkey", "rsa:2048", "-nodes", "-keyout", privateKeyPath(), "-days", "3650", "-out", certificatePath(), "-subj", "/O=KDE/OU=KDEConnect/CN=" + uuid}); | ||
pinoUnsubmitted Not Done
pino: - missing handling of the return value of the command (what if fails?)
- please do not split… | |||||
249 | // The same thing we are doing with QCA could be done invoking openssl (although it's potentially less portable): | 199 | | ||
250 | // 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_" | 200 | if (retVal) { | ||
251 | 201 | Daemon::instance()->reportError(i18n("Could not pair device"), i18n("Could not generate certificate. openssl returned %1", retVal)); | |||
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 | |||||
nicolasfella: Daemon::reportError | |||||
252 | QCA::CertificateOptions certificateOptions = QCA::CertificateOptions(); | 202 | } | ||
253 | QDateTime startTime = QDateTime::currentDateTime().addYears(-1); | 203 | } | ||
254 | QDateTime endTime = startTime.addYears(10); | 204 | | ||
255 | QCA::CertificateInfo certificateInfo; | 205 | auto certificates = QSslCertificate::fromPath(certificatePath()); | ||
256 | certificateInfo.insert(QCA::CommonName, uuid); | 206 | if (!certificates.isEmpty()) { | ||
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 | |||||
257 | certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); | 207 | d->m_certificate = certificates.at(0); | ||
258 | certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); | | |||
259 | certificateOptions.setInfo(certificateInfo); | | |||
260 | certificateOptions.setFormat(QCA::PKCS10); | | |||
261 | certificateOptions.setSerialNumber(QCA::BigInteger(10)); | | |||
262 | certificateOptions.setValidityPeriod(startTime, endTime); | | |||
263 | | ||||
264 | d->m_certificate = QSslCertificate(QCA::Certificate(certificateOptions, d->m_privateKey).toPEM().toLatin1()); | | |||
265 | | ||||
266 | if (!cert.open(QIODevice::ReadWrite | QIODevice::Truncate)) { | | |||
267 | Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store certificate file: %1", certPath)); | | |||
268 | } else { | | |||
269 | cert.setPermissions(strict); | | |||
270 | cert.write(d->m_certificate.toPem()); | | |||
271 | } | 208 | } | ||
209 | | ||||
210 | //Extra security check | ||||
211 | const QFile::Permissions strict = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser; | ||||
212 | if (QFile::permissions(privateKeyPath()) != strict) { | ||||
213 | qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << privateKeyPath(); | ||||
272 | } | 214 | } | ||
273 | } | 215 | } |