File indexing completed on 2024-09-08 04:18:38
0001 /* 0002 Copyright (C) 2007 Justin Karneges <justin@affinix.com> 0003 0004 Permission is hereby granted, free of charge, to any person obtaining a copy 0005 of this software and associated documentation files (the "Software"), to deal 0006 in the Software without restriction, including without limitation the rights 0007 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 0008 copies of the Software, and to permit persons to whom the Software is 0009 furnished to do so, subject to the following conditions: 0010 0011 The above copyright notice and this permission notice shall be included in 0012 all copies or substantial portions of the Software. 0013 0014 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 0015 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 0016 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 0017 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 0018 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 0019 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 0020 */ 0021 0022 #include "certitem.h" 0023 0024 #include "prompter.h" 0025 #include <QMessageBox> 0026 #include <QtCore> 0027 #include <QtCrypto> 0028 #include <QtGui> 0029 0030 typedef QMap<CertItemStore::IconType, QPixmap> CertItemIconset; 0031 0032 //---------------------------------------------------------------------------- 0033 // MyPrompter 0034 //---------------------------------------------------------------------------- 0035 class MyPrompter : public Prompter 0036 { 0037 Q_OBJECT 0038 private: 0039 QMap<QString, QCA::SecureArray> known; 0040 QMap<QString, QCA::SecureArray> maybe; 0041 0042 public: 0043 MyPrompter(QObject *parent = 0) 0044 : Prompter(parent) 0045 { 0046 } 0047 0048 void fileSuccess(const QString &fileName) 0049 { 0050 if (maybe.contains(fileName)) { 0051 known[fileName] = maybe[fileName]; 0052 maybe.remove(fileName); 0053 } 0054 } 0055 0056 void fileFailed(const QString &fileName) 0057 { 0058 maybe.remove(fileName); 0059 known.remove(fileName); 0060 } 0061 0062 protected: 0063 virtual QCA::SecureArray knownPassword(const QCA::Event &event) 0064 { 0065 if (event.source() == QCA::Event::Data && !event.fileName().isEmpty()) 0066 return known.value(event.fileName()); 0067 else 0068 return QCA::SecureArray(); 0069 } 0070 0071 virtual void userSubmitted(const QCA::SecureArray &password, const QCA::Event &event) 0072 { 0073 if (event.source() == QCA::Event::Data && !event.fileName().isEmpty()) 0074 maybe[event.fileName()] = password; 0075 } 0076 }; 0077 0078 //---------------------------------------------------------------------------- 0079 // CertItem 0080 //---------------------------------------------------------------------------- 0081 static QString escape(const QString &in) 0082 { 0083 QString out; 0084 for (int n = 0; n < in.length(); ++n) { 0085 if (in[n] == '\\') 0086 out += "\\\\"; 0087 else if (in[n] == ':') 0088 out += "\\c"; 0089 else if (in[n] == '\n') 0090 out += "\\n"; 0091 else 0092 out += in[n]; 0093 } 0094 return out; 0095 } 0096 0097 static QString unescape(const QString &in) 0098 { 0099 QString out; 0100 for (int n = 0; n < in.length(); ++n) { 0101 if (in[n] == '\\') { 0102 if (n + 1 < in.length()) { 0103 ++n; 0104 if (in[n] == '\\') 0105 out += '\\'; 0106 else if (in[n] == 'c') 0107 out += ':'; 0108 else if (in[n] == 'n') 0109 out += '\n'; 0110 } 0111 } else 0112 out += in[n]; 0113 } 0114 return out; 0115 } 0116 0117 class CertItem::Private : public QSharedData 0118 { 0119 public: 0120 QString name; 0121 QCA::CertificateChain chain; 0122 bool havePrivate; 0123 StorageType storageType; 0124 bool usable; 0125 0126 QString fileName; 0127 QCA::KeyStoreEntry keyStoreEntry; 0128 QString keyStoreEntryString; 0129 0130 Private() 0131 : havePrivate(false) 0132 , storageType(File) 0133 , usable(false) 0134 { 0135 } 0136 0137 QString toString() const 0138 { 0139 QStringList parts; 0140 0141 parts += name; 0142 parts += QString::number(chain.count()); 0143 foreach (const QCA::Certificate &cert, chain) 0144 parts += QCA::Base64().arrayToString(cert.toDER()); 0145 0146 if (havePrivate) { 0147 if (storageType == File) { 0148 parts += "privateFile"; 0149 parts += fileName; 0150 } else // KeyStoreEntry 0151 { 0152 parts += "privateEntry"; 0153 if (!keyStoreEntry.isNull()) 0154 parts += keyStoreEntry.toString(); 0155 else 0156 parts += keyStoreEntryString; 0157 } 0158 } 0159 0160 for (int n = 0; n < parts.count(); ++n) 0161 parts[n] = escape(parts[n]); 0162 return parts.join(":"); 0163 } 0164 0165 bool fromString(const QString &in) 0166 { 0167 const QStringList parts = in.split(':'); 0168 for (int n = 0; n < parts.count(); ++n) 0169 parts[n] = unescape(parts[n]); 0170 0171 if (parts.count() < 3) 0172 return false; 0173 0174 name = parts[0]; 0175 int chainCount = parts[1].toInt(); 0176 if (chainCount < 1 || chainCount > parts.count() - 2) 0177 return false; 0178 chain.clear(); 0179 for (int n = 0; n < chainCount; ++n) { 0180 QCA::Certificate cert = QCA::Certificate::fromDER(QCA::Base64().stringToArray(parts[n + 2]).toByteArray()); 0181 if (cert.isNull()) 0182 return false; 0183 chain += cert; 0184 } 0185 int at = chain.count() + 2; 0186 0187 if (at < parts.count()) { 0188 havePrivate = true; 0189 usable = false; 0190 0191 if (parts[at] == "privateFile") { 0192 storageType = File; 0193 fileName = parts[at + 1]; 0194 if (QFile::exists(fileName)) 0195 usable = true; 0196 } else if (parts[at] == "privateEntry") { 0197 storageType = KeyStore; 0198 keyStoreEntryString = parts[at + 1]; 0199 keyStoreEntry = QCA::KeyStoreEntry(keyStoreEntryString); 0200 if (!keyStoreEntry.isNull()) 0201 usable = true; 0202 } else 0203 return false; 0204 } 0205 0206 return true; 0207 } 0208 }; 0209 0210 CertItem::CertItem() 0211 { 0212 } 0213 0214 CertItem::CertItem(const CertItem &from) 0215 : d(from.d) 0216 { 0217 } 0218 0219 CertItem::~CertItem() 0220 { 0221 } 0222 0223 CertItem &CertItem::operator=(const CertItem &from) 0224 { 0225 d = from.d; 0226 return *this; 0227 } 0228 0229 QString CertItem::name() const 0230 { 0231 return d->name; 0232 } 0233 0234 QCA::CertificateChain CertItem::certificateChain() const 0235 { 0236 return d->chain; 0237 } 0238 0239 bool CertItem::havePrivate() const 0240 { 0241 return d->havePrivate; 0242 } 0243 0244 CertItem::StorageType CertItem::storageType() const 0245 { 0246 return d->storageType; 0247 } 0248 0249 bool CertItem::isUsable() const 0250 { 0251 return d->usable; 0252 } 0253 0254 //---------------------------------------------------------------------------- 0255 // CertItemStore 0256 //---------------------------------------------------------------------------- 0257 static MyPrompter *g_prompter = 0; 0258 static int g_prompter_refs = 0; 0259 0260 class CertItemStorePrivate : public QObject 0261 { 0262 Q_OBJECT 0263 public: 0264 CertItemStore *q; 0265 MyPrompter *prompter; 0266 QList<CertItem> list; 0267 QList<int> idList; 0268 CertItemIconset iconset; 0269 int next_id; 0270 int next_req_id; 0271 0272 class LoaderItem 0273 { 0274 public: 0275 int req_id; 0276 QCA::KeyLoader *keyLoader; 0277 QString fileName; 0278 }; 0279 0280 QList<LoaderItem> loaders; 0281 0282 CertItemStorePrivate(CertItemStore *_q) 0283 : QObject(_q) 0284 , q(_q) 0285 , next_id(0) 0286 , next_req_id(0) 0287 { 0288 if (!g_prompter) { 0289 g_prompter = new MyPrompter; 0290 g_prompter_refs = 1; 0291 } else 0292 ++g_prompter_refs; 0293 0294 prompter = g_prompter; 0295 } 0296 0297 ~CertItemStorePrivate() 0298 { 0299 foreach (const LoaderItem &i, loaders) 0300 delete i.keyLoader; 0301 0302 --g_prompter_refs; 0303 if (g_prompter_refs == 0) { 0304 delete g_prompter; 0305 g_prompter = 0; 0306 } 0307 } 0308 0309 QString getUniqueName(const QString &name) 0310 { 0311 int num = 1; 0312 while (1) { 0313 QString tryname; 0314 if (num == 1) 0315 tryname = name; 0316 else 0317 tryname = name + QString(" (%1)").arg(num); 0318 0319 bool found = false; 0320 foreach (const CertItem &i, list) { 0321 if (i.name() == tryname) { 0322 found = true; 0323 break; 0324 } 0325 } 0326 if (!found) 0327 return tryname; 0328 0329 ++num; 0330 } 0331 } 0332 0333 static QString convertErrorToString(QCA::ConvertResult r) 0334 { 0335 QString str; 0336 switch (r) { 0337 case QCA::ConvertGood: 0338 break; 0339 case QCA::ErrorPassphrase: 0340 str = tr("Incorrect passphrase."); 0341 case QCA::ErrorFile: 0342 str = tr("Unable to open or read file."); 0343 case QCA::ErrorDecode: 0344 default: 0345 str = tr("Unable to decode format."); 0346 } 0347 return str; 0348 } 0349 0350 public Q_SLOTS: 0351 void loader_finished() 0352 { 0353 QCA::KeyLoader *keyLoader = (QCA::KeyLoader *)sender(); 0354 int at = -1; 0355 for (int n = 0; n < loaders.count(); ++n) { 0356 if (loaders[n].keyLoader == keyLoader) { 0357 at = n; 0358 break; 0359 } 0360 } 0361 Q_ASSERT(at != -1); 0362 0363 int req_id = loaders[at].req_id; 0364 QString fileName = loaders[at].fileName; 0365 loaders.removeAt(at); 0366 0367 QCA::ConvertResult r = keyLoader->convertResult(); 0368 if (r != QCA::ConvertGood) { 0369 delete keyLoader; 0370 prompter->fileFailed(fileName); 0371 QMessageBox::information( 0372 0, 0373 tr("Error"), 0374 tr("Error importing certificate and private key.\nReason: %1").arg(convertErrorToString(r))); 0375 emit q->addFailed(req_id); 0376 return; 0377 } 0378 0379 prompter->fileSuccess(fileName); 0380 0381 QCA::KeyBundle kb = keyLoader->keyBundle(); 0382 delete keyLoader; 0383 0384 QCA::CertificateChain chain = kb.certificateChain(); 0385 QCA::Certificate cert = chain.primary(); 0386 0387 QString name = getUniqueName(cert.commonName()); 0388 0389 CertItem i; 0390 i.d = new CertItem::Private; 0391 i.d->name = name; 0392 i.d->chain = chain; 0393 i.d->havePrivate = true; 0394 i.d->storageType = CertItem::File; 0395 i.d->usable = true; 0396 i.d->fileName = fileName; 0397 0398 int id = next_id++; 0399 0400 q->beginInsertRows(QModelIndex(), list.size(), list.size()); 0401 list += i; 0402 idList += id; 0403 q->endInsertRows(); 0404 0405 emit q->addSuccess(req_id, id); 0406 } 0407 }; 0408 0409 CertItemStore::CertItemStore(QObject *parent) 0410 : QAbstractListModel(parent) 0411 { 0412 d = new CertItemStorePrivate(this); 0413 } 0414 0415 CertItemStore::~CertItemStore() 0416 { 0417 delete d; 0418 } 0419 0420 int CertItemStore::idFromRow(int row) const 0421 { 0422 return d->idList[row]; 0423 } 0424 0425 int CertItemStore::rowFromId(int id) const 0426 { 0427 for (int n = 0; n < d->idList.count(); ++n) { 0428 if (d->idList[n] == id) 0429 return n; 0430 } 0431 return -1; 0432 } 0433 0434 CertItem CertItemStore::itemFromId(int id) const 0435 { 0436 return d->list[rowFromId(id)]; 0437 } 0438 0439 CertItem CertItemStore::itemFromRow(int row) const 0440 { 0441 return d->list[row]; 0442 } 0443 0444 QList<CertItem> CertItemStore::items() const 0445 { 0446 return d->list; 0447 } 0448 0449 QStringList CertItemStore::save() const 0450 { 0451 QStringList out; 0452 foreach (const CertItem &i, d->list) 0453 out += i.d->toString(); 0454 return out; 0455 } 0456 0457 bool CertItemStore::load(const QStringList &in) 0458 { 0459 QList<CertItem> addList; 0460 QList<int> addIdList; 0461 foreach (const QString &s, in) { 0462 CertItem i; 0463 i.d = new CertItem::Private; 0464 if (i.d->fromString(s)) { 0465 addList += i; 0466 addIdList += d->next_id++; 0467 } 0468 } 0469 0470 if (addList.isEmpty()) 0471 return true; 0472 0473 beginInsertRows(QModelIndex(), d->list.size(), d->list.size() + addList.count() - 1); 0474 d->list += addList; 0475 d->idList += addIdList; 0476 endInsertRows(); 0477 0478 return true; 0479 } 0480 0481 int CertItemStore::addFromFile(const QString &fileName) 0482 { 0483 CertItemStorePrivate::LoaderItem i; 0484 i.req_id = d->next_req_id++; 0485 i.keyLoader = new QCA::KeyLoader(d); 0486 i.fileName = fileName; 0487 connect(i.keyLoader, SIGNAL(finished()), d, SLOT(loader_finished())); 0488 d->loaders += i; 0489 i.keyLoader->loadKeyBundleFromFile(fileName); 0490 return i.req_id; 0491 } 0492 0493 int CertItemStore::addFromKeyStore(const QCA::KeyStoreEntry &entry) 0494 { 0495 QCA::KeyBundle kb = entry.keyBundle(); 0496 0497 QCA::CertificateChain chain = kb.certificateChain(); 0498 QCA::Certificate cert = chain.primary(); 0499 0500 QString name = d->getUniqueName(entry.name()); 0501 0502 CertItem i; 0503 i.d = new CertItem::Private; 0504 i.d->name = name; 0505 i.d->chain = chain; 0506 i.d->havePrivate = true; 0507 i.d->storageType = CertItem::KeyStore; 0508 i.d->usable = true; 0509 i.d->keyStoreEntry = entry; 0510 0511 int id = d->next_id++; 0512 0513 beginInsertRows(QModelIndex(), d->list.size(), d->list.size()); 0514 d->list += i; 0515 d->idList += id; 0516 endInsertRows(); 0517 0518 int req_id = d->next_req_id++; 0519 QMetaObject::invokeMethod(this, "addSuccess", Qt::QueuedConnection, Q_ARG(int, req_id), Q_ARG(int, id)); 0520 return req_id; 0521 } 0522 0523 int CertItemStore::addUser(const QCA::CertificateChain &chain) 0524 { 0525 QCA::Certificate cert = chain.primary(); 0526 0527 QString name = d->getUniqueName(cert.commonName()); 0528 0529 CertItem i; 0530 i.d = new CertItem::Private; 0531 i.d->name = name; 0532 i.d->chain = chain; 0533 0534 int id = d->next_id++; 0535 0536 beginInsertRows(QModelIndex(), d->list.size(), d->list.size()); 0537 d->list += i; 0538 d->idList += id; 0539 endInsertRows(); 0540 0541 int req_id = d->next_req_id++; 0542 QMetaObject::invokeMethod(this, "addSuccess", Qt::QueuedConnection, Q_ARG(int, req_id), Q_ARG(int, id)); 0543 return req_id; 0544 } 0545 0546 void CertItemStore::updateChain(int id, const QCA::CertificateChain &chain) 0547 { 0548 int at = rowFromId(id); 0549 d->list[at].d->chain = chain; 0550 } 0551 0552 void CertItemStore::removeItem(int id) 0553 { 0554 int at = rowFromId(id); 0555 0556 beginRemoveRows(QModelIndex(), at, at); 0557 d->list.removeAt(at); 0558 d->idList.removeAt(at); 0559 endRemoveRows(); 0560 } 0561 0562 void CertItemStore::setIcon(IconType type, const QPixmap &icon) 0563 { 0564 d->iconset[type] = icon; 0565 } 0566 0567 int CertItemStore::rowCount(const QModelIndex &parent) const 0568 { 0569 Q_UNUSED(parent); 0570 return d->list.count(); 0571 } 0572 0573 QVariant CertItemStore::data(const QModelIndex &index, int role) const 0574 { 0575 if (!index.isValid()) 0576 return QVariant(); 0577 0578 int at = index.row(); 0579 QList<CertItem> &list = d->list; 0580 0581 if (at >= list.count()) 0582 return QVariant(); 0583 0584 if (role == Qt::DisplayRole) { 0585 QString str = list[at].name(); 0586 if (list[at].havePrivate() && !list[at].isUsable()) 0587 str += QString(" ") + tr("(not usable)"); 0588 return str; 0589 } else if (role == Qt::EditRole) 0590 return list[at].name(); 0591 else if (role == Qt::DecorationRole) { 0592 if (list[at].havePrivate()) 0593 return d->iconset[CertItemStore::IconKeyBundle]; 0594 else 0595 return d->iconset[CertItemStore::IconCert]; 0596 } else 0597 return QVariant(); 0598 } 0599 0600 Qt::ItemFlags CertItemStore::flags(const QModelIndex &index) const 0601 { 0602 if (!index.isValid()) 0603 return Qt::ItemIsEnabled; 0604 0605 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; 0606 } 0607 0608 bool CertItemStore::setData(const QModelIndex &index, const QVariant &value, int role) 0609 { 0610 if (index.isValid() && role == Qt::EditRole) { 0611 QString str = value.toString(); 0612 d->list[index.row()].d->name = str; 0613 emit dataChanged(index, index); 0614 return true; 0615 } 0616 return false; 0617 } 0618 0619 //---------------------------------------------------------------------------- 0620 // CertItemPrivateLoader 0621 //---------------------------------------------------------------------------- 0622 class CertItemPrivateLoaderPrivate : public QObject 0623 { 0624 Q_OBJECT 0625 public: 0626 CertItemPrivateLoader *q; 0627 CertItemStore *store; 0628 QCA::KeyLoader *loader; 0629 QString fileName; 0630 QCA::PrivateKey key; 0631 0632 CertItemPrivateLoaderPrivate(CertItemPrivateLoader *_q) 0633 : QObject(_q) 0634 , q(_q) 0635 { 0636 } 0637 0638 public Q_SLOTS: 0639 void loader_finished() 0640 { 0641 QCA::ConvertResult r = loader->convertResult(); 0642 if (r != QCA::ConvertGood) { 0643 delete loader; 0644 loader = 0; 0645 store->d->prompter->fileFailed(fileName); 0646 QMessageBox::information( 0647 0, 0648 tr("Error"), 0649 tr("Error accessing private key.\nReason: %1").arg(CertItemStorePrivate::convertErrorToString(r))); 0650 emit q->finished(); 0651 return; 0652 } 0653 0654 store->d->prompter->fileSuccess(fileName); 0655 0656 key = loader->keyBundle().privateKey(); 0657 delete loader; 0658 loader = 0; 0659 emit q->finished(); 0660 } 0661 }; 0662 0663 CertItemPrivateLoader::CertItemPrivateLoader(CertItemStore *store, QObject *parent) 0664 : QObject(parent) 0665 { 0666 d = new CertItemPrivateLoaderPrivate(this); 0667 d->store = store; 0668 } 0669 0670 CertItemPrivateLoader::~CertItemPrivateLoader() 0671 { 0672 delete d; 0673 } 0674 0675 void CertItemPrivateLoader::start(int id) 0676 { 0677 CertItem i = d->store->itemFromId(id); 0678 0679 if (i.storageType() == CertItem::KeyStore) { 0680 d->key = i.d->keyStoreEntry.keyBundle().privateKey(); 0681 QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); 0682 return; 0683 } 0684 0685 d->key = QCA::PrivateKey(); 0686 d->fileName = i.d->fileName; 0687 d->loader = new QCA::KeyLoader(d); 0688 connect(d->loader, SIGNAL(finished()), d, SLOT(loader_finished())); 0689 d->loader->loadKeyBundleFromFile(d->fileName); 0690 } 0691 0692 QCA::PrivateKey CertItemPrivateLoader::privateKey() const 0693 { 0694 return d->key; 0695 } 0696 0697 #include "certitem.moc"