File indexing completed on 2024-04-28 04:43: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 "prompter.h"
0023 
0024 #include <QApplication>
0025 #include <QInputDialog>
0026 #include <QMessageBox>
0027 #include <QtCore>
0028 #include <QtCrypto>
0029 #include <QtGui>
0030 
0031 class Prompter::Private : public QObject
0032 {
0033     Q_OBJECT
0034 public:
0035     Prompter *q;
0036 
0037     class Item
0038     {
0039     public:
0040         int        id;
0041         QCA::Event event;
0042     };
0043 
0044     QCA::EventHandler handler;
0045     QList<Item>       pending;
0046     bool              prompting;
0047     QMessageBox      *token_prompt;
0048     bool              auto_accept;
0049 
0050     QCA::KeyStoreManager   ksm;
0051     QList<QCA::KeyStore *> keyStores;
0052 
0053     Private(Prompter *_q)
0054         : QObject(_q)
0055         , q(_q)
0056         , handler(this)
0057         , prompting(false)
0058         , token_prompt(0)
0059         , ksm(this)
0060     {
0061         connect(&handler, SIGNAL(eventReady(int, const QCA::Event &)), SLOT(ph_eventReady(int, const QCA::Event &)));
0062         handler.start();
0063 
0064         connect(&ksm, SIGNAL(keyStoreAvailable(const QString &)), SLOT(ks_available(const QString &)));
0065         foreach (const QString &keyStoreId, ksm.keyStores())
0066             ks_available(keyStoreId);
0067     }
0068 
0069     ~Private()
0070     {
0071         qDeleteAll(keyStores);
0072 
0073         while (!pending.isEmpty())
0074             handler.reject(pending.takeFirst().id);
0075     }
0076 
0077 private Q_SLOTS:
0078     void ph_eventReady(int id, const QCA::Event &event)
0079     {
0080         Item i;
0081         i.id    = id;
0082         i.event = event;
0083         pending += i;
0084         nextEvent();
0085     }
0086 
0087     void nextEvent()
0088     {
0089         if (prompting || pending.isEmpty())
0090             return;
0091 
0092         prompting = true;
0093 
0094         const Item       &i     = pending.first();
0095         const int        &id    = i.id;
0096         const QCA::Event &event = i.event;
0097 
0098         if (event.type() == QCA::Event::Password) {
0099             QCA::SecureArray known = q->knownPassword(event);
0100             if (!known.isEmpty()) {
0101                 handler.submitPassword(id, known);
0102                 goto end;
0103             }
0104 
0105             QString type = Prompter::tr("password");
0106             if (event.passwordStyle() == QCA::Event::StylePassphrase)
0107                 type = Prompter::tr("passphrase");
0108             else if (event.passwordStyle() == QCA::Event::StylePIN)
0109                 type = Prompter::tr("PIN");
0110 
0111             QString str;
0112             if (event.source() == QCA::Event::KeyStore) {
0113                 QString            name;
0114                 QCA::KeyStoreEntry entry = event.keyStoreEntry();
0115                 if (!entry.isNull()) {
0116                     name = entry.name();
0117                 } else {
0118                     if (event.keyStoreInfo().type() == QCA::KeyStore::SmartCard)
0119                         name = Prompter::tr("the '%1' token").arg(event.keyStoreInfo().name());
0120                     else
0121                         name = event.keyStoreInfo().name();
0122                 }
0123                 str = Prompter::tr("Enter %1 for %2").arg(type, name);
0124             } else if (!event.fileName().isEmpty()) {
0125                 QFileInfo fi(event.fileName());
0126                 str = Prompter::tr("Enter %1 for %2:").arg(type, fi.fileName());
0127             } else
0128                 str = Prompter::tr("Enter %1:").arg(type);
0129 
0130             bool    ok;
0131             QString pass = QInputDialog::getText(0,
0132                                                  QApplication::instance()->applicationName() + ": " + tr("Prompt"),
0133                                                  str,
0134                                                  QLineEdit::Password,
0135                                                  QString(),
0136                                                  &ok);
0137             if (ok) {
0138                 QCA::SecureArray password = pass.toUtf8();
0139                 q->userSubmitted(password, event);
0140                 handler.submitPassword(id, password);
0141             } else
0142                 handler.reject(id);
0143         } else if (event.type() == QCA::Event::Token) {
0144             // even though we're being prompted for a missing token,
0145             //   we should still check if the token is present, due to
0146             //   a possible race between insert and token request.
0147             bool found = false;
0148 
0149             // token-only
0150             if (event.keyStoreEntry().isNull()) {
0151                 foreach (QCA::KeyStore *ks, keyStores) {
0152                     if (ks->id() == event.keyStoreInfo().id()) {
0153                         found = true;
0154                         break;
0155                     }
0156                 }
0157             }
0158             // token-entry
0159             else {
0160                 QCA::KeyStoreEntry kse = event.keyStoreEntry();
0161 
0162                 QCA::KeyStore *ks = 0;
0163                 foreach (QCA::KeyStore *i, keyStores) {
0164                     if (i->id() == event.keyStoreInfo().id()) {
0165                         ks = i;
0166                         break;
0167                     }
0168                 }
0169                 if (ks) {
0170                     QList<QCA::KeyStoreEntry> list = ks->entryList();
0171                     foreach (const QCA::KeyStoreEntry &e, list) {
0172                         if (e.id() == kse.id() && kse.isAvailable()) {
0173                             found = true;
0174                             break;
0175                         }
0176                     }
0177                 }
0178             }
0179             if (found) {
0180                 // auto-accept
0181                 handler.tokenOkay(id);
0182                 return;
0183             }
0184 
0185             QCA::KeyStoreEntry entry = event.keyStoreEntry();
0186             QString            name;
0187             if (!entry.isNull()) {
0188                 name = Prompter::tr("Please make %1 (of %2) available").arg(entry.name(), entry.storeName());
0189             } else {
0190                 name = Prompter::tr("Please insert the '%1' token").arg(event.keyStoreInfo().name());
0191             }
0192 
0193             QString str = Prompter::tr("%1 and click OK.").arg(name);
0194 
0195             QMessageBox msgBox(QMessageBox::Information,
0196                                QApplication::instance()->applicationName() + ": " + tr("Prompt"),
0197                                str,
0198                                QMessageBox::Ok | QMessageBox::Cancel,
0199                                0);
0200             token_prompt = &msgBox;
0201             auto_accept  = false;
0202             if (msgBox.exec() == QMessageBox::Ok || auto_accept)
0203                 handler.tokenOkay(id);
0204             else
0205                 handler.reject(id);
0206             token_prompt = 0;
0207         } else
0208             handler.reject(id);
0209 
0210     end:
0211         pending.removeFirst();
0212         prompting = false;
0213 
0214         if (!pending.isEmpty())
0215             QMetaObject::invokeMethod(this, "nextEvent", Qt::QueuedConnection);
0216     }
0217 
0218     void ks_available(const QString &keyStoreId)
0219     {
0220         QCA::KeyStore *ks = new QCA::KeyStore(keyStoreId, &ksm);
0221         connect(ks, SIGNAL(updated()), SLOT(ks_updated()));
0222         connect(ks, SIGNAL(unavailable()), SLOT(ks_unavailable()));
0223         keyStores += ks;
0224         ks->startAsynchronousMode();
0225 
0226         // are we currently in a token-only prompt?
0227         if (token_prompt && pending.first().event.type() == QCA::Event::Token &&
0228             pending.first().event.keyStoreEntry().isNull()) {
0229             // was the token we're looking for just inserted?
0230             if (pending.first().event.keyStoreInfo().id() == keyStoreId) {
0231                 // auto-accept
0232                 auto_accept = true;
0233                 token_prompt->accept();
0234             }
0235         }
0236     }
0237 
0238     void ks_unavailable()
0239     {
0240         QCA::KeyStore *ks = (QCA::KeyStore *)sender();
0241         keyStores.removeAll(ks);
0242         delete ks;
0243     }
0244 
0245     void ks_updated()
0246     {
0247         QCA::KeyStore *ks = (QCA::KeyStore *)sender();
0248 
0249         // are we currently in a token-entry prompt?
0250         if (token_prompt && pending.first().event.type() == QCA::Event::Token &&
0251             !pending.first().event.keyStoreEntry().isNull()) {
0252             QCA::KeyStoreEntry kse = pending.first().event.keyStoreEntry();
0253 
0254             // was the token of the entry we're looking for updated?
0255             if (pending.first().event.keyStoreInfo().id() == ks->id()) {
0256                 // is the entry available?
0257                 bool                      avail = false;
0258                 QList<QCA::KeyStoreEntry> list  = ks->entryList();
0259                 foreach (const QCA::KeyStoreEntry &e, list) {
0260                     if (e.id() == kse.id()) {
0261                         avail = kse.isAvailable();
0262                         break;
0263                     }
0264                 }
0265                 if (avail) {
0266                     // auto-accept
0267                     auto_accept = true;
0268                     token_prompt->accept();
0269                 }
0270             }
0271         }
0272     }
0273 };
0274 
0275 Prompter::Prompter(QObject *parent)
0276     : QObject(parent)
0277 {
0278     d = new Private(this);
0279 }
0280 
0281 Prompter::~Prompter()
0282 {
0283     delete d;
0284 }
0285 
0286 QCA::SecureArray Prompter::knownPassword(const QCA::Event &event)
0287 {
0288     Q_UNUSED(event);
0289     return QCA::SecureArray();
0290 }
0291 
0292 void Prompter::userSubmitted(const QCA::SecureArray &password, const QCA::Event &event)
0293 {
0294     Q_UNUSED(password);
0295     Q_UNUSED(event);
0296 }
0297 
0298 #include "prompter.moc"