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
25using namespace QCA;
26
27namespace gpgQCAPlugin {
28
29Q_GLOBAL_STATIC(QMutex, ksl_mutex)
30
31static MyKeyStoreList *keyStoreList = nullptr;
32MyKeyStoreList::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
47MyKeyStoreList::~MyKeyStoreList()
48{
49 QMutexLocker locker(ksl_mutex());
50 keyStoreList = nullptr;
51}
52
53Provider::Context *MyKeyStoreList::clone() const
54{
55 return nullptr;
56}
57
58QString MyKeyStoreList::name(int) const
59{
60 return QStringLiteral("GnuPG Keyring");
61}
62
63KeyStore::Type MyKeyStoreList::type(int) const
64{
65 return KeyStore::PGPKeyring;
66}
67
68QString MyKeyStoreList::storeId(int) const
69{
70 return QStringLiteral("qca-gnupg");
71}
72
73QList<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
82void 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
93bool MyKeyStoreList::isReadOnly(int) const
94{
95 return false;
96}
97
98QList<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
106QList<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
134KeyStoreEntryContext *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
151KeyStoreEntryContext *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
179QString 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
195bool 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
211MyKeyStoreList *MyKeyStoreList::instance()
212{
213 QMutexLocker locker(ksl_mutex());
214 return keyStoreList;
215}
216
217void 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
226PGPKey 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
249PGPKey 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
275PGPKey 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
306PGPKey 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
337void 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
425void 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
435void MyKeyStoreList::pub_changed()
436{
437 pubdirty = true;
438 handleDirtyRings();
439}
440
441void MyKeyStoreList::sec_changed()
442{
443 secdirty = true;
444 handleDirtyRings();
445}
446
447void 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

source code of qca/plugins/qca-gnupg/mykeystorelist.cpp