00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "certitem.h"
00023
00024 #include <QtCore>
00025 #include <QtGui>
00026 #include <QtCrypto>
00027 #include "prompter.h"
00028
00029 typedef QMap<CertItemStore::IconType,QPixmap> CertItemIconset;
00030
00031
00032
00033
00034 class MyPrompter : public Prompter
00035 {
00036 Q_OBJECT
00037 private:
00038 QMap<QString,QCA::SecureArray> known;
00039 QMap<QString,QCA::SecureArray> maybe;
00040
00041 public:
00042 MyPrompter(QObject *parent = 0) :
00043 Prompter(parent)
00044 {
00045 }
00046
00047 void fileSuccess(const QString &fileName)
00048 {
00049 if(maybe.contains(fileName))
00050 {
00051 known[fileName] = maybe[fileName];
00052 maybe.remove(fileName);
00053 }
00054 }
00055
00056 void fileFailed(const QString &fileName)
00057 {
00058 maybe.remove(fileName);
00059 known.remove(fileName);
00060 }
00061
00062 protected:
00063 virtual QCA::SecureArray knownPassword(const QCA::Event &event)
00064 {
00065 if(event.source() == QCA::Event::Data && !event.fileName().isEmpty())
00066 return known.value(event.fileName());
00067 else
00068 return QCA::SecureArray();
00069 }
00070
00071 virtual void userSubmitted(const QCA::SecureArray &password, const QCA::Event &event)
00072 {
00073 if(event.source() == QCA::Event::Data && !event.fileName().isEmpty())
00074 maybe[event.fileName()] = password;
00075 }
00076 };
00077
00078
00079
00080
00081 static QString escape(const QString &in)
00082 {
00083 QString out;
00084 for(int n = 0; n < in.length(); ++n)
00085 {
00086 if(in[n] == '\\')
00087 out += "\\\\";
00088 else if(in[n] == ':')
00089 out += "\\c";
00090 else if(in[n] == '\n')
00091 out += "\\n";
00092 else
00093 out += in[n];
00094 }
00095 return out;
00096 }
00097
00098 static QString unescape(const QString &in)
00099 {
00100 QString out;
00101 for(int n = 0; n < in.length(); ++n)
00102 {
00103 if(in[n] == '\\')
00104 {
00105 if(n + 1 < in.length())
00106 {
00107 ++n;
00108 if(in[n] == '\\')
00109 out += '\\';
00110 else if(in[n] == 'c')
00111 out += ':';
00112 else if(in[n] == 'n')
00113 out += '\n';
00114 }
00115 }
00116 else
00117 out += in[n];
00118 }
00119 return out;
00120 }
00121
00122 class CertItem::Private : public QSharedData
00123 {
00124 public:
00125 QString name;
00126 QCA::CertificateChain chain;
00127 bool havePrivate;
00128 StorageType storageType;
00129 bool usable;
00130
00131 QString fileName;
00132 QCA::KeyStoreEntry keyStoreEntry;
00133 QString keyStoreEntryString;
00134
00135 Private() :
00136 havePrivate(false),
00137 storageType(File),
00138 usable(false)
00139 {
00140 }
00141
00142 QString toString() const
00143 {
00144 QStringList parts;
00145
00146 parts += name;
00147 parts += QString::number(chain.count());
00148 foreach(const QCA::Certificate &cert, chain)
00149 parts += QCA::Base64().arrayToString(cert.toDER());
00150
00151 if(havePrivate)
00152 {
00153 if(storageType == File)
00154 {
00155 parts += "privateFile";
00156 parts += fileName;
00157 }
00158 else
00159 {
00160 parts += "privateEntry";
00161 if(!keyStoreEntry.isNull())
00162 parts += keyStoreEntry.toString();
00163 else
00164 parts += keyStoreEntryString;
00165 }
00166 }
00167
00168 for(int n = 0; n < parts.count(); ++n)
00169 parts[n] = escape(parts[n]);
00170 return parts.join(":");
00171 }
00172
00173 bool fromString(const QString &in)
00174 {
00175 QStringList parts = in.split(':');
00176 for(int n = 0; n < parts.count(); ++n)
00177 parts[n] = unescape(parts[n]);
00178
00179 if(parts.count() < 3)
00180 return false;
00181
00182 name = parts[0];
00183 int chainCount = parts[1].toInt();
00184 if(chainCount < 1 || chainCount > parts.count() - 2)
00185 return false;
00186 chain.clear();
00187 for(int n = 0; n < chainCount; ++n)
00188 {
00189 QCA::Certificate cert = QCA::Certificate::fromDER(QCA::Base64().stringToArray(parts[n + 2]).toByteArray());
00190 if(cert.isNull())
00191 return false;
00192 chain += cert;
00193 }
00194 int at = chain.count() + 2;
00195
00196 if(at < parts.count())
00197 {
00198 havePrivate = true;
00199 usable = false;
00200
00201 if(parts[at] == "privateFile")
00202 {
00203 storageType = File;
00204 fileName = parts[at + 1];
00205 if(QFile::exists(fileName))
00206 usable = true;
00207 }
00208 else if(parts[at] == "privateEntry")
00209 {
00210 storageType = KeyStore;
00211 keyStoreEntryString = parts[at + 1];
00212 keyStoreEntry = QCA::KeyStoreEntry(keyStoreEntryString);
00213 if(!keyStoreEntry.isNull())
00214 usable = true;
00215 }
00216 else
00217 return false;
00218 }
00219
00220 return true;
00221 }
00222 };
00223
00224 CertItem::CertItem()
00225 {
00226 }
00227
00228 CertItem::CertItem(const CertItem &from) :
00229 d(from.d)
00230 {
00231 }
00232
00233 CertItem::~CertItem()
00234 {
00235 }
00236
00237 CertItem & CertItem::operator=(const CertItem &from)
00238 {
00239 d = from.d;
00240 return *this;
00241 }
00242
00243 QString CertItem::name() const
00244 {
00245 return d->name;
00246 }
00247
00248 QCA::CertificateChain CertItem::certificateChain() const
00249 {
00250 return d->chain;
00251 }
00252
00253 bool CertItem::havePrivate() const
00254 {
00255 return d->havePrivate;
00256 }
00257
00258 CertItem::StorageType CertItem::storageType() const
00259 {
00260 return d->storageType;
00261 }
00262
00263 bool CertItem::isUsable() const
00264 {
00265 return d->usable;
00266 }
00267
00268
00269
00270
00271 static MyPrompter *g_prompter = 0;
00272 static int g_prompter_refs = 0;
00273
00274 class CertItemStorePrivate : public QObject
00275 {
00276 Q_OBJECT
00277 public:
00278 CertItemStore *q;
00279 MyPrompter *prompter;
00280 QList<CertItem> list;
00281 QList<int> idList;
00282 CertItemIconset iconset;
00283 int next_id;
00284 int next_req_id;
00285
00286 class LoaderItem
00287 {
00288 public:
00289 int req_id;
00290 QCA::KeyLoader *keyLoader;
00291 QString fileName;
00292 };
00293
00294 QList<LoaderItem> loaders;
00295
00296 CertItemStorePrivate(CertItemStore *_q) :
00297 QObject(_q),
00298 q(_q),
00299 next_id(0),
00300 next_req_id(0)
00301 {
00302 if(!g_prompter)
00303 {
00304 g_prompter = new MyPrompter;
00305 g_prompter_refs = 1;
00306 }
00307 else
00308 ++g_prompter_refs;
00309
00310 prompter = g_prompter;
00311 }
00312
00313 ~CertItemStorePrivate()
00314 {
00315 foreach(const LoaderItem &i, loaders)
00316 delete i.keyLoader;
00317
00318 --g_prompter_refs;
00319 if(g_prompter_refs == 0)
00320 {
00321 delete g_prompter;
00322 g_prompter = 0;
00323 }
00324 }
00325
00326 QString getUniqueName(const QString &name)
00327 {
00328 int num = 1;
00329 while(1)
00330 {
00331 QString tryname;
00332 if(num == 1)
00333 tryname = name;
00334 else
00335 tryname = name + QString(" (%1)").arg(num);
00336
00337 bool found = false;
00338 foreach(const CertItem &i, list)
00339 {
00340 if(i.name() == tryname)
00341 {
00342 found = true;
00343 break;
00344 }
00345 }
00346 if(!found)
00347 return tryname;
00348
00349 ++num;
00350 }
00351 }
00352
00353 static QString convertErrorToString(QCA::ConvertResult r)
00354 {
00355 QString str;
00356 switch(r)
00357 {
00358 case QCA::ConvertGood: break;
00359 case QCA::ErrorPassphrase: str = tr("Incorrect passphrase.");
00360 case QCA::ErrorFile: str = tr("Unable to open or read file.");
00361 case QCA::ErrorDecode:
00362 default: str = tr("Unable to decode format.");
00363 }
00364 return str;
00365 }
00366
00367 public slots:
00368 void loader_finished()
00369 {
00370 QCA::KeyLoader *keyLoader = (QCA::KeyLoader *)sender();
00371 int at = -1;
00372 for(int n = 0; n < loaders.count(); ++n)
00373 {
00374 if(loaders[n].keyLoader == keyLoader)
00375 {
00376 at = n;
00377 break;
00378 }
00379 }
00380 Q_ASSERT(at != -1);
00381
00382 int req_id = loaders[at].req_id;
00383 QString fileName = loaders[at].fileName;
00384 loaders.removeAt(at);
00385
00386 QCA::ConvertResult r = keyLoader->convertResult();
00387 if(r != QCA::ConvertGood)
00388 {
00389 delete keyLoader;
00390 prompter->fileFailed(fileName);
00391 QMessageBox::information(0, tr("Error"),
00392 tr("Error importing certificate and private key.\nReason: %1").arg(convertErrorToString(r)));
00393 emit q->addFailed(req_id);
00394 return;
00395 }
00396
00397 prompter->fileSuccess(fileName);
00398
00399 QCA::KeyBundle kb = keyLoader->keyBundle();
00400 delete keyLoader;
00401
00402 QCA::CertificateChain chain = kb.certificateChain();
00403 QCA::Certificate cert = chain.primary();
00404
00405 QString name = getUniqueName(cert.commonName());
00406
00407 CertItem i;
00408 i.d = new CertItem::Private;
00409 i.d->name = name;
00410 i.d->chain = chain;
00411 i.d->havePrivate = true;
00412 i.d->storageType = CertItem::File;
00413 i.d->usable = true;
00414 i.d->fileName = fileName;
00415
00416 int id = next_id++;
00417
00418 q->beginInsertRows(QModelIndex(), list.size(), list.size());
00419 list += i;
00420 idList += id;
00421 q->endInsertRows();
00422
00423 emit q->addSuccess(req_id, id);
00424 }
00425 };
00426
00427 CertItemStore::CertItemStore(QObject *parent) :
00428 QAbstractListModel(parent)
00429 {
00430 d = new CertItemStorePrivate(this);
00431 }
00432
00433 CertItemStore::~CertItemStore()
00434 {
00435 delete d;
00436 }
00437
00438 int CertItemStore::idFromRow(int row) const
00439 {
00440 return d->idList[row];
00441 }
00442
00443 int CertItemStore::rowFromId(int id) const
00444 {
00445 for(int n = 0; n < d->idList.count(); ++n)
00446 {
00447 if(d->idList[n] == id)
00448 return n;
00449 }
00450 return -1;
00451 }
00452
00453 CertItem CertItemStore::itemFromId(int id) const
00454 {
00455 return d->list[rowFromId(id)];
00456 }
00457
00458 CertItem CertItemStore::itemFromRow(int row) const
00459 {
00460 return d->list[row];
00461 }
00462
00463 QList<CertItem> CertItemStore::items() const
00464 {
00465 return d->list;
00466 }
00467
00468 QStringList CertItemStore::save() const
00469 {
00470 QStringList out;
00471 foreach(const CertItem &i, d->list)
00472 out += i.d->toString();
00473 return out;
00474 }
00475
00476 bool CertItemStore::load(const QStringList &in)
00477 {
00478 QList<CertItem> addList;
00479 QList<int> addIdList;
00480 foreach(const QString &s, in)
00481 {
00482 CertItem i;
00483 i.d = new CertItem::Private;
00484 if(i.d->fromString(s))
00485 {
00486 addList += i;
00487 addIdList += d->next_id++;
00488 }
00489 }
00490
00491 if(addList.isEmpty())
00492 return true;
00493
00494 beginInsertRows(QModelIndex(), d->list.size(), d->list.size() + addList.count() - 1);
00495 d->list += addList;
00496 d->idList += addIdList;
00497 endInsertRows();
00498
00499 return true;
00500 }
00501
00502 int CertItemStore::addFromFile(const QString &fileName)
00503 {
00504 CertItemStorePrivate::LoaderItem i;
00505 i.req_id = d->next_req_id++;
00506 i.keyLoader = new QCA::KeyLoader(d);
00507 i.fileName = fileName;
00508 connect(i.keyLoader, SIGNAL(finished()), d, SLOT(loader_finished()));
00509 d->loaders += i;
00510 i.keyLoader->loadKeyBundleFromFile(fileName);
00511 return i.req_id;
00512 }
00513
00514 int CertItemStore::addFromKeyStore(const QCA::KeyStoreEntry &entry)
00515 {
00516 QCA::KeyBundle kb = entry.keyBundle();
00517
00518 QCA::CertificateChain chain = kb.certificateChain();
00519 QCA::Certificate cert = chain.primary();
00520
00521 QString name = d->getUniqueName(entry.name());
00522
00523 CertItem i;
00524 i.d = new CertItem::Private;
00525 i.d->name = name;
00526 i.d->chain = chain;
00527 i.d->havePrivate = true;
00528 i.d->storageType = CertItem::KeyStore;
00529 i.d->usable = true;
00530 i.d->keyStoreEntry = entry;
00531
00532 int id = d->next_id++;
00533
00534 beginInsertRows(QModelIndex(), d->list.size(), d->list.size());
00535 d->list += i;
00536 d->idList += id;
00537 endInsertRows();
00538
00539 int req_id = d->next_req_id++;
00540 QMetaObject::invokeMethod(this, "addSuccess", Qt::QueuedConnection, Q_ARG(int, req_id), Q_ARG(int, id));
00541 return req_id;
00542 }
00543
00544 int CertItemStore::addUser(const QCA::CertificateChain &chain)
00545 {
00546 QCA::Certificate cert = chain.primary();
00547
00548 QString name = d->getUniqueName(cert.commonName());
00549
00550 CertItem i;
00551 i.d = new CertItem::Private;
00552 i.d->name = name;
00553 i.d->chain = chain;
00554
00555 int id = d->next_id++;
00556
00557 beginInsertRows(QModelIndex(), d->list.size(), d->list.size());
00558 d->list += i;
00559 d->idList += id;
00560 endInsertRows();
00561
00562 int req_id = d->next_req_id++;
00563 QMetaObject::invokeMethod(this, "addSuccess", Qt::QueuedConnection, Q_ARG(int, req_id), Q_ARG(int, id));
00564 return req_id;
00565 }
00566
00567 void CertItemStore::updateChain(int id, const QCA::CertificateChain &chain)
00568 {
00569 int at = rowFromId(id);
00570 d->list[at].d->chain = chain;
00571 }
00572
00573 void CertItemStore::removeItem(int id)
00574 {
00575 int at = rowFromId(id);
00576
00577 beginRemoveRows(QModelIndex(), at, at);
00578 d->list.removeAt(at);
00579 d->idList.removeAt(at);
00580 endRemoveRows();
00581 }
00582
00583 void CertItemStore::setIcon(IconType type, const QPixmap &icon)
00584 {
00585 d->iconset[type] = icon;
00586 }
00587
00588 int CertItemStore::rowCount(const QModelIndex &parent) const
00589 {
00590 Q_UNUSED(parent);
00591 return d->list.count();
00592 }
00593
00594 QVariant CertItemStore::data(const QModelIndex &index, int role) const
00595 {
00596 if(!index.isValid())
00597 return QVariant();
00598
00599 int at = index.row();
00600 QList<CertItem> &list = d->list;
00601
00602 if(at >= list.count())
00603 return QVariant();
00604
00605 if(role == Qt::DisplayRole)
00606 {
00607 QString str = list[at].name();
00608 if(list[at].havePrivate() && !list[at].isUsable())
00609 str += QString(" ") + tr("(not usable)");
00610 return str;
00611 }
00612 else if(role == Qt::EditRole)
00613 return list[at].name();
00614 else if(role == Qt::DecorationRole)
00615 {
00616 if(list[at].havePrivate())
00617 return d->iconset[CertItemStore::IconKeyBundle];
00618 else
00619 return d->iconset[CertItemStore::IconCert];
00620 }
00621 else
00622 return QVariant();
00623 }
00624
00625 Qt::ItemFlags CertItemStore::flags(const QModelIndex &index) const
00626 {
00627 if(!index.isValid())
00628 return Qt::ItemIsEnabled;
00629
00630 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
00631 }
00632
00633 bool CertItemStore::setData(const QModelIndex &index, const QVariant &value, int role)
00634 {
00635 if(index.isValid() && role == Qt::EditRole)
00636 {
00637 QString str = value.toString();
00638 d->list[index.row()].d->name = str;
00639 emit dataChanged(index, index);
00640 return true;
00641 }
00642 return false;
00643 }
00644
00645
00646
00647
00648 class CertItemPrivateLoaderPrivate : public QObject
00649 {
00650 Q_OBJECT
00651 public:
00652 CertItemPrivateLoader *q;
00653 CertItemStore *store;
00654 QCA::KeyLoader *loader;
00655 QString fileName;
00656 QCA::PrivateKey key;
00657
00658 CertItemPrivateLoaderPrivate(CertItemPrivateLoader *_q) :
00659 QObject(_q),
00660 q(_q)
00661 {
00662 }
00663
00664 public slots:
00665 void loader_finished()
00666 {
00667 QCA::ConvertResult r = loader->convertResult();
00668 if(r != QCA::ConvertGood)
00669 {
00670 delete loader;
00671 loader = 0;
00672 store->d->prompter->fileFailed(fileName);
00673 QMessageBox::information(0, tr("Error"),
00674 tr("Error accessing private key.\nReason: %1").arg(CertItemStorePrivate::convertErrorToString(r)));
00675 emit q->finished();
00676 return;
00677 }
00678
00679 store->d->prompter->fileSuccess(fileName);
00680
00681 key = loader->keyBundle().privateKey();
00682 delete loader;
00683 loader = 0;
00684 emit q->finished();
00685 }
00686 };
00687
00688 CertItemPrivateLoader::CertItemPrivateLoader(CertItemStore *store, QObject *parent) :
00689 QObject(parent)
00690 {
00691 d = new CertItemPrivateLoaderPrivate(this);
00692 d->store = store;
00693 }
00694
00695 CertItemPrivateLoader::~CertItemPrivateLoader()
00696 {
00697 delete d;
00698 }
00699
00700 void CertItemPrivateLoader::start(int id)
00701 {
00702 CertItem i = d->store->itemFromId(id);
00703
00704 if(i.storageType() == CertItem::KeyStore)
00705 {
00706 d->key = i.d->keyStoreEntry.keyBundle().privateKey();
00707 QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
00708 return;
00709 }
00710
00711 d->key = QCA::PrivateKey();
00712 d->fileName = i.d->fileName;
00713 d->loader = new QCA::KeyLoader(d);
00714 connect(d->loader, SIGNAL(finished()), d, SLOT(loader_finished()));
00715 d->loader->loadKeyBundleFromFile(d->fileName);
00716 }
00717
00718 QCA::PrivateKey CertItemPrivateLoader::privateKey() const
00719 {
00720 return d->key;
00721 }
00722
00723 #include "certitem.moc"