QCA

certitem.cpp
1/*
2 Copyright (C) 2007 Justin Karneges <justin@affinix.com>
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20*/
21
22#include "certitem.h"
23
24#include "prompter.h"
25#include <QMessageBox>
26#include <QtCore>
27#include <QtCrypto>
28#include <QtGui>
29
31
32//----------------------------------------------------------------------------
33// MyPrompter
34//----------------------------------------------------------------------------
35class MyPrompter : public Prompter
36{
38private:
41
42public:
43 MyPrompter(QObject *parent = 0)
44 : Prompter(parent)
45 {
46 }
47
48 void fileSuccess(const QString &fileName)
49 {
50 if (maybe.contains(fileName)) {
51 known[fileName] = maybe[fileName];
52 maybe.remove(fileName);
53 }
54 }
55
56 void fileFailed(const QString &fileName)
57 {
58 maybe.remove(fileName);
59 known.remove(fileName);
60 }
61
62protected:
63 virtual QCA::SecureArray knownPassword(const QCA::Event &event)
64 {
65 if (event.source() == QCA::Event::Data && !event.fileName().isEmpty())
66 return known.value(event.fileName());
67 else
68 return QCA::SecureArray();
69 }
70
71 virtual void userSubmitted(const QCA::SecureArray &password, const QCA::Event &event)
72 {
73 if (event.source() == QCA::Event::Data && !event.fileName().isEmpty())
74 maybe[event.fileName()] = password;
75 }
76};
77
78//----------------------------------------------------------------------------
79// CertItem
80//----------------------------------------------------------------------------
81static QString escape(const QString &in)
82{
83 QString out;
84 for (int n = 0; n < in.length(); ++n) {
85 if (in[n] == '\\')
86 out += "\\\\";
87 else if (in[n] == ':')
88 out += "\\c";
89 else if (in[n] == '\n')
90 out += "\\n";
91 else
92 out += in[n];
93 }
94 return out;
95}
96
97static QString unescape(const QString &in)
98{
99 QString out;
100 for (int n = 0; n < in.length(); ++n) {
101 if (in[n] == '\\') {
102 if (n + 1 < in.length()) {
103 ++n;
104 if (in[n] == '\\')
105 out += '\\';
106 else if (in[n] == 'c')
107 out += ':';
108 else if (in[n] == 'n')
109 out += '\n';
110 }
111 } else
112 out += in[n];
113 }
114 return out;
115}
116
117class CertItem::Private : public QSharedData
118{
119public:
120 QString name;
122 bool havePrivate;
123 StorageType storageType;
124 bool usable;
125
126 QString fileName;
127 QCA::KeyStoreEntry keyStoreEntry;
128 QString keyStoreEntryString;
129
130 Private()
131 : havePrivate(false)
132 , storageType(File)
133 , usable(false)
134 {
135 }
136
137 QString toString() const
138 {
139 QStringList parts;
140
141 parts += name;
142 parts += QString::number(chain.count());
143 foreach (const QCA::Certificate &cert, chain)
144 parts += QCA::Base64().arrayToString(cert.toDER());
145
146 if (havePrivate) {
147 if (storageType == File) {
148 parts += "privateFile";
149 parts += fileName;
150 } else // KeyStoreEntry
151 {
152 parts += "privateEntry";
153 if (!keyStoreEntry.isNull())
154 parts += keyStoreEntry.toString();
155 else
156 parts += keyStoreEntryString;
157 }
158 }
159
160 for (int n = 0; n < parts.count(); ++n)
161 parts[n] = escape(parts[n]);
162 return parts.join(":");
163 }
164
165 bool fromString(const QString &in)
166 {
167 const QStringList parts = in.split(':');
168 for (int n = 0; n < parts.count(); ++n)
169 parts[n] = unescape(parts[n]);
170
171 if (parts.count() < 3)
172 return false;
173
174 name = parts[0];
175 int chainCount = parts[1].toInt();
176 if (chainCount < 1 || chainCount > parts.count() - 2)
177 return false;
178 chain.clear();
179 for (int n = 0; n < chainCount; ++n) {
180 QCA::Certificate cert = QCA::Certificate::fromDER(QCA::Base64().stringToArray(parts[n + 2]).toByteArray());
181 if (cert.isNull())
182 return false;
183 chain += cert;
184 }
185 int at = chain.count() + 2;
186
187 if (at < parts.count()) {
188 havePrivate = true;
189 usable = false;
190
191 if (parts[at] == "privateFile") {
192 storageType = File;
193 fileName = parts[at + 1];
194 if (QFile::exists(fileName))
195 usable = true;
196 } else if (parts[at] == "privateEntry") {
197 storageType = KeyStore;
198 keyStoreEntryString = parts[at + 1];
199 keyStoreEntry = QCA::KeyStoreEntry(keyStoreEntryString);
200 if (!keyStoreEntry.isNull())
201 usable = true;
202 } else
203 return false;
204 }
205
206 return true;
207 }
208};
209
210CertItem::CertItem()
211{
212}
213
214CertItem::CertItem(const CertItem &from)
215 : d(from.d)
216{
217}
218
219CertItem::~CertItem()
220{
221}
222
223CertItem &CertItem::operator=(const CertItem &from)
224{
225 d = from.d;
226 return *this;
227}
228
229QString CertItem::name() const
230{
231 return d->name;
232}
233
234QCA::CertificateChain CertItem::certificateChain() const
235{
236 return d->chain;
237}
238
239bool CertItem::havePrivate() const
240{
241 return d->havePrivate;
242}
243
244CertItem::StorageType CertItem::storageType() const
245{
246 return d->storageType;
247}
248
249bool CertItem::isUsable() const
250{
251 return d->usable;
252}
253
254//----------------------------------------------------------------------------
255// CertItemStore
256//----------------------------------------------------------------------------
257static MyPrompter *g_prompter = 0;
258static int g_prompter_refs = 0;
259
260class CertItemStorePrivate : public QObject
261{
263public:
264 CertItemStore *q;
265 MyPrompter *prompter;
266 QList<CertItem> list;
267 QList<int> idList;
268 CertItemIconset iconset;
269 int next_id;
270 int next_req_id;
271
272 class LoaderItem
273 {
274 public:
275 int req_id;
276 QCA::KeyLoader *keyLoader;
277 QString fileName;
278 };
279
280 QList<LoaderItem> loaders;
281
282 CertItemStorePrivate(CertItemStore *_q)
283 : QObject(_q)
284 , q(_q)
285 , next_id(0)
286 , next_req_id(0)
287 {
288 if (!g_prompter) {
289 g_prompter = new MyPrompter;
290 g_prompter_refs = 1;
291 } else
292 ++g_prompter_refs;
293
294 prompter = g_prompter;
295 }
296
297 ~CertItemStorePrivate()
298 {
299 foreach (const LoaderItem &i, loaders)
300 delete i.keyLoader;
301
302 --g_prompter_refs;
303 if (g_prompter_refs == 0) {
304 delete g_prompter;
305 g_prompter = 0;
306 }
307 }
308
309 QString getUniqueName(const QString &name)
310 {
311 int num = 1;
312 while (1) {
313 QString tryname;
314 if (num == 1)
315 tryname = name;
316 else
317 tryname = name + QString(" (%1)").arg(num);
318
319 bool found = false;
320 foreach (const CertItem &i, list) {
321 if (i.name() == tryname) {
322 found = true;
323 break;
324 }
325 }
326 if (!found)
327 return tryname;
328
329 ++num;
330 }
331 }
332
333 static QString convertErrorToString(QCA::ConvertResult r)
334 {
335 QString str;
336 switch (r) {
337 case QCA::ConvertGood:
338 break;
340 str = tr("Incorrect passphrase.");
341 case QCA::ErrorFile:
342 str = tr("Unable to open or read file.");
343 case QCA::ErrorDecode:
344 default:
345 str = tr("Unable to decode format.");
346 }
347 return str;
348 }
349
350public Q_SLOTS:
351 void loader_finished()
352 {
353 QCA::KeyLoader *keyLoader = (QCA::KeyLoader *)sender();
354 int at = -1;
355 for (int n = 0; n < loaders.count(); ++n) {
356 if (loaders[n].keyLoader == keyLoader) {
357 at = n;
358 break;
359 }
360 }
361 Q_ASSERT(at != -1);
362
363 int req_id = loaders[at].req_id;
364 QString fileName = loaders[at].fileName;
365 loaders.removeAt(at);
366
367 QCA::ConvertResult r = keyLoader->convertResult();
368 if (r != QCA::ConvertGood) {
369 delete keyLoader;
370 prompter->fileFailed(fileName);
372 0,
373 tr("Error"),
374 tr("Error importing certificate and private key.\nReason: %1").arg(convertErrorToString(r)));
375 emit q->addFailed(req_id);
376 return;
377 }
378
379 prompter->fileSuccess(fileName);
380
381 QCA::KeyBundle kb = keyLoader->keyBundle();
382 delete keyLoader;
383
385 QCA::Certificate cert = chain.primary();
386
387 QString name = getUniqueName(cert.commonName());
388
389 CertItem i;
390 i.d = new CertItem::Private;
391 i.d->name = name;
392 i.d->chain = chain;
393 i.d->havePrivate = true;
394 i.d->storageType = CertItem::File;
395 i.d->usable = true;
396 i.d->fileName = fileName;
397
398 int id = next_id++;
399
401 list += i;
402 idList += id;
403 q->endInsertRows();
404
405 emit q->addSuccess(req_id, id);
406 }
407};
408
409CertItemStore::CertItemStore(QObject *parent)
410 : QAbstractListModel(parent)
411{
412 d = new CertItemStorePrivate(this);
413}
414
415CertItemStore::~CertItemStore()
416{
417 delete d;
418}
419
420int CertItemStore::idFromRow(int row) const
421{
422 return d->idList[row];
423}
424
425int CertItemStore::rowFromId(int id) const
426{
427 for (int n = 0; n < d->idList.count(); ++n) {
428 if (d->idList[n] == id)
429 return n;
430 }
431 return -1;
432}
433
434CertItem CertItemStore::itemFromId(int id) const
435{
436 return d->list[rowFromId(id)];
437}
438
439CertItem CertItemStore::itemFromRow(int row) const
440{
441 return d->list[row];
442}
443
444QList<CertItem> CertItemStore::items() const
445{
446 return d->list;
447}
448
449QStringList CertItemStore::save() const
450{
451 QStringList out;
452 foreach (const CertItem &i, d->list)
453 out += i.d->toString();
454 return out;
455}
456
457bool CertItemStore::load(const QStringList &in)
458{
459 QList<CertItem> addList;
460 QList<int> addIdList;
461 foreach (const QString &s, in) {
462 CertItem i;
463 i.d = new CertItem::Private;
464 if (i.d->fromString(s)) {
465 addList += i;
466 addIdList += d->next_id++;
467 }
468 }
469
470 if (addList.isEmpty())
471 return true;
472
473 beginInsertRows(QModelIndex(), d->list.size(), d->list.size() + addList.count() - 1);
474 d->list += addList;
475 d->idList += addIdList;
477
478 return true;
479}
480
481int CertItemStore::addFromFile(const QString &fileName)
482{
483 CertItemStorePrivate::LoaderItem i;
484 i.req_id = d->next_req_id++;
485 i.keyLoader = new QCA::KeyLoader(d);
486 i.fileName = fileName;
487 connect(i.keyLoader, SIGNAL(finished()), d, SLOT(loader_finished()));
488 d->loaders += i;
489 i.keyLoader->loadKeyBundleFromFile(fileName);
490 return i.req_id;
491}
492
493int CertItemStore::addFromKeyStore(const QCA::KeyStoreEntry &entry)
494{
495 QCA::KeyBundle kb = entry.keyBundle();
496
498 QCA::Certificate cert = chain.primary();
499
500 QString name = d->getUniqueName(entry.name());
501
502 CertItem i;
503 i.d = new CertItem::Private;
504 i.d->name = name;
505 i.d->chain = chain;
506 i.d->havePrivate = true;
507 i.d->storageType = CertItem::KeyStore;
508 i.d->usable = true;
509 i.d->keyStoreEntry = entry;
510
511 int id = d->next_id++;
512
513 beginInsertRows(QModelIndex(), d->list.size(), d->list.size());
514 d->list += i;
515 d->idList += id;
517
518 int req_id = d->next_req_id++;
519 QMetaObject::invokeMethod(this, "addSuccess", Qt::QueuedConnection, Q_ARG(int, req_id), Q_ARG(int, id));
520 return req_id;
521}
522
523int CertItemStore::addUser(const QCA::CertificateChain &chain)
524{
525 QCA::Certificate cert = chain.primary();
526
527 QString name = d->getUniqueName(cert.commonName());
528
529 CertItem i;
530 i.d = new CertItem::Private;
531 i.d->name = name;
532 i.d->chain = chain;
533
534 int id = d->next_id++;
535
536 beginInsertRows(QModelIndex(), d->list.size(), d->list.size());
537 d->list += i;
538 d->idList += id;
540
541 int req_id = d->next_req_id++;
542 QMetaObject::invokeMethod(this, "addSuccess", Qt::QueuedConnection, Q_ARG(int, req_id), Q_ARG(int, id));
543 return req_id;
544}
545
546void CertItemStore::updateChain(int id, const QCA::CertificateChain &chain)
547{
548 int at = rowFromId(id);
549 d->list[at].d->chain = chain;
550}
551
552void CertItemStore::removeItem(int id)
553{
554 int at = rowFromId(id);
555
556 beginRemoveRows(QModelIndex(), at, at);
557 d->list.removeAt(at);
558 d->idList.removeAt(at);
560}
561
562void CertItemStore::setIcon(IconType type, const QPixmap &icon)
563{
564 d->iconset[type] = icon;
565}
566
567int CertItemStore::rowCount(const QModelIndex &parent) const
568{
569 Q_UNUSED(parent);
570 return d->list.count();
571}
572
573QVariant CertItemStore::data(const QModelIndex &index, int role) const
574{
575 if (!index.isValid())
576 return QVariant();
577
578 int at = index.row();
579 QList<CertItem> &list = d->list;
580
581 if (at >= list.count())
582 return QVariant();
583
584 if (role == Qt::DisplayRole) {
585 QString str = list[at].name();
586 if (list[at].havePrivate() && !list[at].isUsable())
587 str += QString(" ") + tr("(not usable)");
588 return str;
589 } else if (role == Qt::EditRole)
590 return list[at].name();
591 else if (role == Qt::DecorationRole) {
592 if (list[at].havePrivate())
593 return d->iconset[CertItemStore::IconKeyBundle];
594 else
595 return d->iconset[CertItemStore::IconCert];
596 } else
597 return QVariant();
598}
599
600Qt::ItemFlags CertItemStore::flags(const QModelIndex &index) const
601{
602 if (!index.isValid())
603 return Qt::ItemIsEnabled;
604
606}
607
608bool CertItemStore::setData(const QModelIndex &index, const QVariant &value, int role)
609{
610 if (index.isValid() && role == Qt::EditRole) {
611 QString str = value.toString();
612 d->list[index.row()].d->name = str;
613 emit dataChanged(index, index);
614 return true;
615 }
616 return false;
617}
618
619//----------------------------------------------------------------------------
620// CertItemPrivateLoader
621//----------------------------------------------------------------------------
622class CertItemPrivateLoaderPrivate : public QObject
623{
625public:
626 CertItemPrivateLoader *q;
627 CertItemStore *store;
628 QCA::KeyLoader *loader;
629 QString fileName;
630 QCA::PrivateKey key;
631
632 CertItemPrivateLoaderPrivate(CertItemPrivateLoader *_q)
633 : QObject(_q)
634 , q(_q)
635 {
636 }
637
638public Q_SLOTS:
639 void loader_finished()
640 {
641 QCA::ConvertResult r = loader->convertResult();
642 if (r != QCA::ConvertGood) {
643 delete loader;
644 loader = 0;
645 store->d->prompter->fileFailed(fileName);
647 0,
648 tr("Error"),
649 tr("Error accessing private key.\nReason: %1").arg(CertItemStorePrivate::convertErrorToString(r)));
650 emit q->finished();
651 return;
652 }
653
654 store->d->prompter->fileSuccess(fileName);
655
656 key = loader->keyBundle().privateKey();
657 delete loader;
658 loader = 0;
659 emit q->finished();
660 }
661};
662
663CertItemPrivateLoader::CertItemPrivateLoader(CertItemStore *store, QObject *parent)
664 : QObject(parent)
665{
666 d = new CertItemPrivateLoaderPrivate(this);
667 d->store = store;
668}
669
670CertItemPrivateLoader::~CertItemPrivateLoader()
671{
672 delete d;
673}
674
675void CertItemPrivateLoader::start(int id)
676{
677 CertItem i = d->store->itemFromId(id);
678
679 if (i.storageType() == CertItem::KeyStore) {
680 d->key = i.d->keyStoreEntry.keyBundle().privateKey();
682 return;
683 }
684
685 d->key = QCA::PrivateKey();
686 d->fileName = i.d->fileName;
687 d->loader = new QCA::KeyLoader(d);
688 connect(d->loader, SIGNAL(finished()), d, SLOT(loader_finished()));
689 d->loader->loadKeyBundleFromFile(d->fileName);
690}
691
692QCA::PrivateKey CertItemPrivateLoader::privateKey() const
693{
694 return d->key;
695}
696
697#include "certitem.moc"
Base64 encoding / decoding
A chain of related Certificates.
Definition qca_cert.h:1226
const Certificate & primary() const
Return the primary (end-user) Certificate.
Definition qca_cert.h:1249
Public Key (X.509) certificate.
Definition qca_cert.h:857
QString commonName() const
The common name of the subject of the certificate.
static Certificate fromDER(const QByteArray &a, ConvertResult *result=nullptr, const QString &provider=QString())
Import the certificate from DER.
bool isNull() const
Test if the certificate is empty (null)
QByteArray toDER() const
Export the Certificate into a DER format.
An asynchronous event.
Definition qca_core.h:1391
@ Data
File or bytearray generated the event.
Definition qca_core.h:1419
Certificate chain and private key pair.
Definition qca_cert.h:2176
CertificateChain certificateChain() const
The public certificate part of this bundle.
PrivateKey privateKey() const
The private key part of this bundle.
Asynchronous private key loader.
Definition qca_cert.h:2613
void loadKeyBundleFromFile(const QString &fileName)
Initiate an asynchronous loading of a KeyBundle from a file.
ConvertResult convertResult() const
The result of the loading process.
KeyBundle keyBundle() const
The key bundle that has been loaded.
Single entry in a KeyStore.
QString toString() const
Serialize into a string for use as a passive entry.
KeyBundle keyBundle() const
If a KeyBundle is stored in this object, return that bundle.
QString name() const
The name associated with the key stored in this object.
bool isNull() const
Test if this key is empty (null)
Generic private key.
Secure array of bytes.
Definition qca_tools.h:317
QString arrayToString(const MemoryRegion &a)
Process an array in the "forward" direction, returning a QString.
Type type(const QSqlDatabase &db)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QString name(StandardShortcut id)
ConvertResult
Return value from a format conversion.
@ ErrorFile
Failure because of incorrect file.
@ ErrorPassphrase
Failure because of incorrect passphrase.
@ ConvertGood
Conversion succeeded, results should be valid.
@ ErrorDecode
General failure in the decode stage.
void beginInsertRows(const QModelIndex &parent, int first, int last)
void beginRemoveRows(const QModelIndex &parent, int first, int last)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
bool exists() const const
void clear()
qsizetype count() const const
bool isEmpty() const const
void removeAt(qsizetype i)
qsizetype size() const const
bool contains(const Key &key) const const
size_type remove(const Key &key)
T value(const Key &key, const T &defaultValue) const const
StandardButton information(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
bool isValid() const const
int row() const const
Q_OBJECTQ_OBJECT
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
QObject * parent() const const
QObject * sender() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(Args &&... args) const const
qsizetype length() const const
QString number(double n, char format, int precision)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
QueuedConnection
DisplayRole
typedef ItemFlags
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:26 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.