1 | /* |
2 | * Copyright (C) 2003-2008 Justin Karneges <justin@affinix.com> |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2.1 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
17 | */ |
18 | |
19 | #include "mykeystorelist.h" |
20 | #include "mypgpkeycontext.h" |
21 | #include "utils.h" |
22 | #include <QFileInfo> |
23 | #include <QMutexLocker> |
24 | |
25 | using namespace QCA; |
26 | |
27 | namespace gpgQCAPlugin { |
28 | |
29 | Q_GLOBAL_STATIC(QMutex, ksl_mutex) |
30 | |
31 | static MyKeyStoreList *keyStoreList = nullptr; |
32 | MyKeyStoreList::MyKeyStoreList(Provider *p) |
33 | : KeyStoreListContext(p) |
34 | , initialized(false) |
35 | , gpg(find_bin(), this) |
36 | , pubdirty(false) |
37 | , secdirty(false) |
38 | , ringWatch(this) |
39 | { |
40 | QMutexLocker locker(ksl_mutex()); |
41 | keyStoreList = this; |
42 | |
43 | connect(sender: &gpg, signal: &GpgOp::finished, context: this, slot: &MyKeyStoreList::gpg_finished); |
44 | connect(sender: &ringWatch, signal: &RingWatch::changed, context: this, slot: &MyKeyStoreList::ring_changed); |
45 | } |
46 | |
47 | MyKeyStoreList::~MyKeyStoreList() |
48 | { |
49 | QMutexLocker locker(ksl_mutex()); |
50 | keyStoreList = nullptr; |
51 | } |
52 | |
53 | Provider::Context *MyKeyStoreList::clone() const |
54 | { |
55 | return nullptr; |
56 | } |
57 | |
58 | QString MyKeyStoreList::name(int) const |
59 | { |
60 | return QStringLiteral("GnuPG Keyring" ); |
61 | } |
62 | |
63 | KeyStore::Type MyKeyStoreList::type(int) const |
64 | { |
65 | return KeyStore::PGPKeyring; |
66 | } |
67 | |
68 | QString MyKeyStoreList::storeId(int) const |
69 | { |
70 | return QStringLiteral("qca-gnupg" ); |
71 | } |
72 | |
73 | QList<int> MyKeyStoreList::keyStores() |
74 | { |
75 | // we just support one fixed keyring, if any |
76 | QList<int> list; |
77 | if (initialized) |
78 | list += 0; |
79 | return list; |
80 | } |
81 | |
82 | void MyKeyStoreList::start() |
83 | { |
84 | // kick start our init procedure: |
85 | // ensure gpg is installed |
86 | // obtain keyring file names for monitoring |
87 | // cache initial keyrings |
88 | |
89 | init_step = 0; |
90 | gpg.doCheck(); |
91 | } |
92 | |
93 | bool MyKeyStoreList::isReadOnly(int) const |
94 | { |
95 | return false; |
96 | } |
97 | |
98 | QList<KeyStoreEntry::Type> MyKeyStoreList::entryTypes(int) const |
99 | { |
100 | QList<KeyStoreEntry::Type> list; |
101 | list += KeyStoreEntry::TypePGPSecretKey; |
102 | list += KeyStoreEntry::TypePGPPublicKey; |
103 | return list; |
104 | } |
105 | |
106 | QList<KeyStoreEntryContext *> MyKeyStoreList::entryList(int) |
107 | { |
108 | QMutexLocker locker(&ringMutex); |
109 | |
110 | QList<KeyStoreEntryContext *> out; |
111 | |
112 | foreach (const GpgOp::Key &pkey, pubkeys) { |
113 | PGPKey pub, sec; |
114 | |
115 | const QString id = pkey.keyItems.first().id; |
116 | |
117 | MyPGPKeyContext *kc = new MyPGPKeyContext(provider()); |
118 | // not secret, in keyring |
119 | kc->set(i: pkey, isSecret: false, inKeyring: true, isTrusted: pkey.isTrusted); |
120 | pub.change(c: kc); |
121 | |
122 | // optional |
123 | sec = getSecKey(keyId: id, userIdsOverride: pkey.userIds); |
124 | |
125 | MyKeyStoreEntry *c = new MyKeyStoreEntry(pub, sec, provider()); |
126 | c->_storeId = storeId(0); |
127 | c->_storeName = name(0); |
128 | out.append(t: c); |
129 | } |
130 | |
131 | return out; |
132 | } |
133 | |
134 | KeyStoreEntryContext *MyKeyStoreList::entry(int, const QString &entryId) |
135 | { |
136 | QMutexLocker locker(&ringMutex); |
137 | |
138 | PGPKey pub = getPubKey(keyId: entryId); |
139 | if (pub.isNull()) |
140 | return nullptr; |
141 | |
142 | // optional |
143 | const PGPKey sec = getSecKey(keyId: entryId, userIdsOverride: static_cast<MyPGPKeyContext *>(pub.context())->_props.userIds); |
144 | |
145 | MyKeyStoreEntry *c = new MyKeyStoreEntry(pub, sec, provider()); |
146 | c->_storeId = storeId(0); |
147 | c->_storeName = name(0); |
148 | return c; |
149 | } |
150 | |
151 | KeyStoreEntryContext *MyKeyStoreList::entryPassive(const QString &serialized) |
152 | { |
153 | QMutexLocker locker(&ringMutex); |
154 | |
155 | const QStringList parts = serialized.split(sep: QLatin1Char(':')); |
156 | if (parts.count() < 2) |
157 | return nullptr; |
158 | if (unescape_string(in: parts[0]) != QLatin1String("qca-gnupg-1" )) |
159 | return nullptr; |
160 | |
161 | QString entryId = unescape_string(in: parts[1]); |
162 | if (entryId.isEmpty()) |
163 | return nullptr; |
164 | |
165 | PGPKey pub = getPubKey(keyId: entryId); |
166 | if (pub.isNull()) |
167 | return nullptr; |
168 | |
169 | // optional |
170 | const PGPKey sec = getSecKey(keyId: entryId, userIdsOverride: static_cast<MyPGPKeyContext *>(pub.context())->_props.userIds); |
171 | |
172 | MyKeyStoreEntry *c = new MyKeyStoreEntry(pub, sec, provider()); |
173 | c->_storeId = storeId(0); |
174 | c->_storeName = name(0); |
175 | return c; |
176 | } |
177 | |
178 | // TODO: cache should reflect this change immediately |
179 | QString MyKeyStoreList::writeEntry(int, const PGPKey &key) |
180 | { |
181 | const MyPGPKeyContext *kc = static_cast<const MyPGPKeyContext *>(key.context()); |
182 | const QByteArray buf = kc->toBinary(); |
183 | |
184 | GpgOp gpg(find_bin()); |
185 | gpg.doImport(in: buf); |
186 | gpg_waitForFinished(gpg: &gpg); |
187 | gpg_keyStoreLog(str: gpg.readDiagnosticText()); |
188 | if (!gpg.success()) |
189 | return QString(); |
190 | |
191 | return kc->_props.keyId; |
192 | } |
193 | |
194 | // TODO: cache should reflect this change immediately |
195 | bool MyKeyStoreList::removeEntry(int, const QString &entryId) |
196 | { |
197 | ringMutex.lock(); |
198 | PGPKey pub = getPubKey(keyId: entryId); |
199 | ringMutex.unlock(); |
200 | |
201 | const MyPGPKeyContext *kc = static_cast<const MyPGPKeyContext *>(pub.context()); |
202 | QString fingerprint = kc->_props.fingerprint; |
203 | |
204 | GpgOp gpg(find_bin()); |
205 | gpg.doDeleteKey(key_fingerprint: fingerprint); |
206 | gpg_waitForFinished(gpg: &gpg); |
207 | gpg_keyStoreLog(str: gpg.readDiagnosticText()); |
208 | return gpg.success(); |
209 | } |
210 | |
211 | MyKeyStoreList *MyKeyStoreList::instance() |
212 | { |
213 | QMutexLocker locker(ksl_mutex()); |
214 | return keyStoreList; |
215 | } |
216 | |
217 | void MyKeyStoreList::ext_keyStoreLog(const QString &str) |
218 | { |
219 | if (str.isEmpty()) |
220 | return; |
221 | |
222 | // FIXME: collect and emit in one pass |
223 | QMetaObject::invokeMethod(obj: this, member: "diagnosticText" , c: Qt::QueuedConnection, Q_ARG(QString, str)); |
224 | } |
225 | |
226 | PGPKey MyKeyStoreList::getPubKey(const QString &keyId) const |
227 | { |
228 | int at = -1; |
229 | for (int n = 0; n < pubkeys.count(); ++n) { |
230 | if (pubkeys[n].keyItems.first().id == keyId) { |
231 | at = n; |
232 | break; |
233 | } |
234 | } |
235 | if (at == -1) |
236 | return PGPKey(); |
237 | |
238 | const GpgOp::Key &pkey = pubkeys[at]; |
239 | |
240 | PGPKey pub; |
241 | MyPGPKeyContext *kc = new MyPGPKeyContext(provider()); |
242 | // not secret, in keyring |
243 | kc->set(i: pkey, isSecret: false, inKeyring: true, isTrusted: pkey.isTrusted); |
244 | pub.change(c: kc); |
245 | |
246 | return pub; |
247 | } |
248 | |
249 | PGPKey MyKeyStoreList::getSecKey(const QString &keyId, const QStringList &userIdsOverride) const |
250 | { |
251 | Q_UNUSED(userIdsOverride); |
252 | |
253 | int at = -1; |
254 | for (int n = 0; n < seckeys.count(); ++n) { |
255 | if (seckeys[n].keyItems.first().id == keyId) { |
256 | at = n; |
257 | break; |
258 | } |
259 | } |
260 | if (at == -1) |
261 | return PGPKey(); |
262 | |
263 | const GpgOp::Key &skey = seckeys[at]; |
264 | |
265 | PGPKey sec; |
266 | MyPGPKeyContext *kc = new MyPGPKeyContext(provider()); |
267 | // secret, in keyring, trusted |
268 | kc->set(i: skey, isSecret: true, inKeyring: true, isTrusted: true); |
269 | // kc->_props.userIds = userIdsOverride; |
270 | sec.change(c: kc); |
271 | |
272 | return sec; |
273 | } |
274 | |
275 | PGPKey MyKeyStoreList::publicKeyFromId(const QString &keyId) |
276 | { |
277 | QMutexLocker locker(&ringMutex); |
278 | |
279 | int at = -1; |
280 | for (int n = 0; n < pubkeys.count(); ++n) { |
281 | const GpgOp::Key &pkey = pubkeys[n]; |
282 | for (int k = 0; k < pkey.keyItems.count(); ++k) { |
283 | const GpgOp::KeyItem &ki = pkey.keyItems[k]; |
284 | if (ki.id == keyId) { |
285 | at = n; |
286 | break; |
287 | } |
288 | } |
289 | if (at != -1) |
290 | break; |
291 | } |
292 | if (at == -1) |
293 | return PGPKey(); |
294 | |
295 | const GpgOp::Key &pkey = pubkeys[at]; |
296 | |
297 | PGPKey pub; |
298 | MyPGPKeyContext *kc = new MyPGPKeyContext(provider()); |
299 | // not secret, in keyring |
300 | kc->set(i: pkey, isSecret: false, inKeyring: true, isTrusted: pkey.isTrusted); |
301 | pub.change(c: kc); |
302 | |
303 | return pub; |
304 | } |
305 | |
306 | PGPKey MyKeyStoreList::secretKeyFromId(const QString &keyId) |
307 | { |
308 | QMutexLocker locker(&ringMutex); |
309 | |
310 | int at = -1; |
311 | for (int n = 0; n < seckeys.count(); ++n) { |
312 | const GpgOp::Key &skey = seckeys[n]; |
313 | for (int k = 0; k < skey.keyItems.count(); ++k) { |
314 | const GpgOp::KeyItem &ki = skey.keyItems[k]; |
315 | if (ki.id == keyId) { |
316 | at = n; |
317 | break; |
318 | } |
319 | } |
320 | if (at != -1) |
321 | break; |
322 | } |
323 | if (at == -1) |
324 | return PGPKey(); |
325 | |
326 | const GpgOp::Key &skey = seckeys[at]; |
327 | |
328 | PGPKey sec; |
329 | MyPGPKeyContext *kc = new MyPGPKeyContext(provider()); |
330 | // secret, in keyring, trusted |
331 | kc->set(i: skey, isSecret: true, inKeyring: true, isTrusted: true); |
332 | sec.change(c: kc); |
333 | |
334 | return sec; |
335 | } |
336 | |
337 | void MyKeyStoreList::gpg_finished() |
338 | { |
339 | gpg_keyStoreLog(str: gpg.readDiagnosticText()); |
340 | |
341 | if (!initialized) { |
342 | // any steps that fail during init, just give up completely |
343 | if (!gpg.success()) { |
344 | ringWatch.clear(); |
345 | emit busyEnd(); |
346 | return; |
347 | } |
348 | |
349 | // check |
350 | if (init_step == 0) { |
351 | // obtain keyring file names for monitoring |
352 | init_step = 1; |
353 | homeDir = gpg.homeDir(); |
354 | gpg.doSecretKeyringFile(); |
355 | } |
356 | // secret keyring filename |
357 | else if (init_step == 1) { |
358 | secring = QFileInfo(gpg.keyringFile()).canonicalFilePath(); |
359 | |
360 | if (secring.isEmpty()) { |
361 | secring = homeDir + QStringLiteral("/secring.gpg" ); |
362 | } |
363 | ringWatch.add(filePath: secring); |
364 | |
365 | // obtain keyring file names for monitoring |
366 | init_step = 2; |
367 | gpg.doPublicKeyringFile(); |
368 | } |
369 | // public keyring filename |
370 | else if (init_step == 2) { |
371 | pubring = QFileInfo(gpg.keyringFile()).canonicalFilePath(); |
372 | if (pubring.isEmpty()) { |
373 | pubring = homeDir + QStringLiteral("/pubring.gpg" ); |
374 | } |
375 | ringWatch.add(filePath: pubring); |
376 | |
377 | // cache initial keyrings |
378 | init_step = 3; |
379 | gpg.doSecretKeys(); |
380 | } else if (init_step == 3) { |
381 | ringMutex.lock(); |
382 | seckeys = gpg.keys(); |
383 | ringMutex.unlock(); |
384 | |
385 | // cache initial keyrings |
386 | init_step = 4; |
387 | gpg.doPublicKeys(); |
388 | } else if (init_step == 4) { |
389 | ringMutex.lock(); |
390 | pubkeys = gpg.keys(); |
391 | ringMutex.unlock(); |
392 | |
393 | initialized = true; |
394 | handleDirtyRings(); |
395 | emit busyEnd(); |
396 | } |
397 | } else { |
398 | if (!gpg.success()) |
399 | return; |
400 | |
401 | const GpgOp::Type op = gpg.op(); |
402 | if (op == GpgOp::SecretKeys) { |
403 | ringMutex.lock(); |
404 | seckeys = gpg.keys(); |
405 | ringMutex.unlock(); |
406 | |
407 | secdirty = false; |
408 | } else if (op == GpgOp::PublicKeys) { |
409 | ringMutex.lock(); |
410 | pubkeys = gpg.keys(); |
411 | ringMutex.unlock(); |
412 | |
413 | pubdirty = false; |
414 | } |
415 | |
416 | if (!secdirty && !pubdirty) { |
417 | emit storeUpdated(id: 0); |
418 | return; |
419 | } |
420 | |
421 | handleDirtyRings(); |
422 | } |
423 | } |
424 | |
425 | void MyKeyStoreList::ring_changed(const QString &filePath) |
426 | { |
427 | ext_keyStoreLog(QStringLiteral("ring_changed: [%1]\n" ).arg(a: filePath)); |
428 | |
429 | if (filePath == secring) |
430 | sec_changed(); |
431 | else if (filePath == pubring) |
432 | pub_changed(); |
433 | } |
434 | |
435 | void MyKeyStoreList::pub_changed() |
436 | { |
437 | pubdirty = true; |
438 | handleDirtyRings(); |
439 | } |
440 | |
441 | void MyKeyStoreList::sec_changed() |
442 | { |
443 | secdirty = true; |
444 | handleDirtyRings(); |
445 | } |
446 | |
447 | void MyKeyStoreList::handleDirtyRings() |
448 | { |
449 | if (!initialized || gpg.isActive()) |
450 | return; |
451 | |
452 | if (secdirty) |
453 | gpg.doSecretKeys(); |
454 | else if (pubdirty) |
455 | gpg.doPublicKeys(); |
456 | } |
457 | |
458 | } // end namespace gpgQCAPlugin |
459 | |