File indexing completed on 2024-04-28 04:43:37

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"