/* Copyright (C) 2007 Justin Karneges Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "pkcs11configdlg.h" #include "ui_pkcs11config.h" #include #include #include #include #include //---------------------------------------------------------------------------- // Pkcs11ProviderConfig //---------------------------------------------------------------------------- class Pkcs11ProviderConfig { public: bool allow_protected_authentication; bool cert_private; bool enabled; QString library; QString name; int private_mask; QString slotevent_method; int slotevent_timeout; Pkcs11ProviderConfig() : allow_protected_authentication(true) , cert_private(false) , enabled(false) , private_mask(0) , slotevent_method("auto") , slotevent_timeout(0) { } QVariantMap toVariantMap() const { QVariantMap out; out["allow_protected_authentication"] = allow_protected_authentication; out["cert_private"] = cert_private; out["enabled"] = enabled; out["library"] = library; out["name"] = name; out["private_mask"] = private_mask; out["slotevent_method"] = slotevent_method; out["slotevent_timeout"] = slotevent_timeout; return out; } bool fromVariantMap(const QVariantMap &in) { allow_protected_authentication = in["allow_protected_authentication"].toBool(); cert_private = in["cert_private"].toBool(); enabled = in["enabled"].toBool(); library = in["library"].toString(); name = in["name"].toString(); private_mask = in["private_mask"].toInt(); slotevent_method = in["slotevent_method"].toString(); slotevent_timeout = in["slotevent_timeout"].toInt(); return true; } }; //---------------------------------------------------------------------------- // Pkcs11Config //---------------------------------------------------------------------------- class Pkcs11Config { public: bool allow_load_rootca; bool allow_protected_authentication; int log_level; int pin_cache; QList providers; QVariantMap orig_config; Pkcs11Config() : allow_load_rootca(false) , allow_protected_authentication(true) , log_level(0) , pin_cache(-1) { } QVariantMap toVariantMap() const { QVariantMap out = orig_config; // form type out["formtype"] = "http://affinix.com/qca/forms/qca-pkcs11#1.0"; // base settings out["allow_load_rootca"] = allow_load_rootca; out["allow_protected_authentication"] = allow_protected_authentication; out["log_level"] = log_level; out["pin_cache"] = pin_cache; // provider settings (always write at least 10 providers) for (int n = 0; n < 10 || n < providers.count(); ++n) { QString prefix = QString().sprintf("provider_%02d_", n); Pkcs11ProviderConfig provider; if (n < providers.count()) provider = providers[n]; QVariantMap subconfig = provider.toVariantMap(); QMapIterator it(subconfig); while (it.hasNext()) { it.next(); out.insert(prefix + it.key(), it.value()); } } return out; } bool fromVariantMap(const QVariantMap &in) { if (in["formtype"] != "http://affinix.com/qca/forms/qca-pkcs11#1.0") return false; allow_load_rootca = in["allow_load_rootca"].toBool(); allow_protected_authentication = in["allow_protected_authentication"].toBool(); log_level = in["log_level"].toInt(); pin_cache = in["pin_cache"].toInt(); for (int n = 0;; ++n) { QString prefix = QString().sprintf("provider_%02d_", n); // collect all key/values with this prefix into a // a separate container, leaving out the prefix // from the keys. QVariantMap subconfig; QMapIterator it(in); while (it.hasNext()) { it.next(); if (it.key().startsWith(prefix)) subconfig.insert(it.key().mid(prefix.length()), it.value()); } // if there are no config items with this prefix, we're done if (subconfig.isEmpty()) break; Pkcs11ProviderConfig provider; if (!provider.fromVariantMap(subconfig)) return false; // skip unnamed entries if (provider.name.isEmpty()) continue; // skip duplicate entries bool have_name_already = false; foreach (const Pkcs11ProviderConfig &i, providers) { if (i.name == provider.name) { have_name_already = true; break; } } if (have_name_already) continue; providers += provider; } orig_config = in; return true; } }; //---------------------------------------------------------------------------- // ModuleListModel //---------------------------------------------------------------------------- class ModuleListModel : public QAbstractListModel { Q_OBJECT public: QList list; ModuleListModel(QObject *parent = 0) : QAbstractListModel(parent) { } int rowCount(const QModelIndex &parent = QModelIndex()) const { Q_UNUSED(parent); return list.count(); } QVariant data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= list.count()) return QVariant(); if (role == Qt::DisplayRole) return list[index.row()].name; else if (role == Qt::EditRole) return list[index.row()].name; else return QVariant(); } Qt::ItemFlags flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; } bool setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && role == Qt::EditRole) { QString str = value.toString(); if (str.isEmpty()) { emit editFailed(index, tr("Module name cannot be blank.")); return false; } bool have_name_already = false; int at = index.row(); for (int n = 0; n < list.count(); ++n) { const Pkcs11ProviderConfig &i = list[n]; // skip self if (n == at) continue; if (i.name == str) { have_name_already = true; break; } } if (have_name_already) { emit editFailed(index, tr("There is already a module with this name.")); return false; } list[index.row()].name = str; emit dataChanged(index, index); return true; } return false; } void addItems(const QList &items) { if (items.isEmpty()) return; beginInsertRows(QModelIndex(), list.size(), list.size() + items.count() - 1); list += items; endInsertRows(); } void addItem(const Pkcs11ProviderConfig &i) { beginInsertRows(QModelIndex(), list.size(), list.size()); list += i; endInsertRows(); } void removeItem(int at) { beginRemoveRows(QModelIndex(), at, at); list.removeAt(at); endRemoveRows(); } Q_SIGNALS: void editFailed(const QModelIndex &index, const QString &reasonString); }; //---------------------------------------------------------------------------- // Pkcs11ConfigDlg //---------------------------------------------------------------------------- static QCA::Provider *get_pkcs11_provider(QVariantMap *_config = 0) { QCA::ProviderList providers = QCA::providers(); providers += QCA::defaultProvider(); QCA::Provider *provider = 0; QVariantMap config; foreach (QCA::Provider *p, providers) { config = QCA::getProviderConfig(p->name()); if (!config.isEmpty() && config["formtype"] == "http://affinix.com/qca/forms/qca-pkcs11#1.0") { provider = p; break; } } if (provider && _config) *_config = config; return provider; } class Pkcs11ConfigDlg::Private : public QObject { Q_OBJECT public: Pkcs11ConfigDlg *q; Ui_Pkcs11Config ui; QString providerName; ModuleListModel *model; Pkcs11Config config; bool dirty; // for safe dialog closing behavior during QListView editing bool allow_close; bool done; // for ignoring modifications that we cause when populating fields bool ignore_dataChanged; Private(Pkcs11ConfigDlg *_q, const QString &_providerName, const QVariantMap &configmap) : QObject(_q) , q(_q) , providerName(_providerName) , dirty(false) , allow_close(true) , done(false) , ignore_dataChanged(true) { ui.setupUi(q); q->resize(q->minimumSize()); model = new ModuleListModel(this); qRegisterMetaType("QModelIndex"); // do this queued for two reasons: // 1) if we throw an error dialog, it will occur after the // row text has reverted, and the call stack completed // (the latter may not be required, but it helps me // sleep). // 2) if the user accepts/rejects the dialog while editing, // it is easy to ensure that the signal is not processed // (if it gets delivered at all). connect(model, SIGNAL(editFailed(const QModelIndex &, const QString &)), SLOT(model_editFailed(const QModelIndex &, const QString &)), Qt::QueuedConnection); // set up widgets ui.rb_pincache_nolimit->setChecked(true); ui.sb_pincache_time->setEnabled(false); ui.sb_pincache_time->setValue(300); ui.lv_modules->setModel(model); ui.lv_modules->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); ui.pb_remove->setEnabled(false); ui.tb_details->setEnabled(false); ui.gb_poll->setEnabled(false); ui.rb_polldefault->setChecked(true); ui.sb_pollcustom->setEnabled(false); ui.sb_pollcustom->setValue(5); ui.ck_modeauto->setChecked(true); // disable this by default, enable on dataChanged ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); // general connect(ui.ck_allowroot, SIGNAL(toggled(bool)), SLOT(dataChanged())); connect(ui.ck_allowprotected, SIGNAL(toggled(bool)), SLOT(dataChanged())); connect(ui.sb_loglevel, SIGNAL(valueChanged(int)), SLOT(dataChanged())); connect(ui.gb_pincache, SIGNAL(toggled(bool)), SLOT(dataChanged())); connect(ui.rb_pincache_nolimit, SIGNAL(toggled(bool)), SLOT(dataChanged())); connect(ui.rb_pincache_time, SIGNAL(toggled(bool)), ui.sb_pincache_time, SLOT(setEnabled(bool))); connect(ui.rb_pincache_time, SIGNAL(toggled(bool)), SLOT(dataChanged())); connect(ui.sb_pincache_time, SIGNAL(valueChanged(int)), SLOT(dataChanged())); // modules connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), SLOT(dataChanged())); connect(ui.lv_modules->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), SLOT(module_selectionChanged(const QItemSelection &, const QItemSelection &))); connect(ui.pb_add, SIGNAL(clicked()), SLOT(module_add())); connect(ui.pb_remove, SIGNAL(clicked()), SLOT(module_remove())); connect(ui.le_library, SIGNAL(textChanged(const QString &)), SLOT(dataChanged())); connect(ui.pb_browse, SIGNAL(clicked()), SLOT(library_browse())); connect(ui.cb_slotmethod, SIGNAL(currentIndexChanged(int)), SLOT(slotmethod_currentIndexChanged(int))); connect(ui.rb_polldefault, SIGNAL(toggled(bool)), SLOT(dataChanged())); connect(ui.rb_pollcustom, SIGNAL(toggled(bool)), ui.sb_pollcustom, SLOT(setEnabled(bool))); connect(ui.rb_pollcustom, SIGNAL(toggled(bool)), SLOT(dataChanged())); connect(ui.sb_pollcustom, SIGNAL(valueChanged(int)), SLOT(dataChanged())); connect(ui.ck_modallowprotected, SIGNAL(toggled(bool)), SLOT(dataChanged())); connect(ui.ck_certprivate, SIGNAL(toggled(bool)), SLOT(dataChanged())); connect(ui.ck_modeauto, SIGNAL(toggled(bool)), SLOT(modeauto_toggled(bool))); connect(ui.ck_modesign, SIGNAL(toggled(bool)), SLOT(modenonauto_toggled(bool))); connect(ui.ck_modesignrecover, SIGNAL(toggled(bool)), SLOT(modenonauto_toggled(bool))); connect(ui.ck_modedecrypt, SIGNAL(toggled(bool)), SLOT(modenonauto_toggled(bool))); connect(ui.ck_modeunwrap, SIGNAL(toggled(bool)), SLOT(modenonauto_toggled(bool))); // is this a valid config? if (!providerName.isEmpty() && config.fromVariantMap(configmap)) { // if so, load everything up ui.ck_allowroot->setChecked(config.allow_load_rootca); ui.ck_allowprotected->setChecked(config.allow_protected_authentication); ui.sb_loglevel->setValue(config.log_level); if (config.pin_cache != 0) { ui.gb_pincache->setChecked(true); if (config.pin_cache <= -1) ui.rb_pincache_nolimit->setChecked(true); else { ui.rb_pincache_time->setChecked(true); ui.sb_pincache_time->setValue(config.pin_cache); } } model->addItems(config.providers); if (!model->list.isEmpty()) { QModelIndex index = model->index(0); ui.lv_modules->setCurrentIndex(index); ui.lv_modules->selectionModel()->select( index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Current); } ui.buttonBox->setFocus(); ui.buttonBox->button(QDialogButtonBox::Cancel)->setFocus(); } else { // otherwise, disable everything ui.gb_general->setEnabled(false); ui.gb_modules->setEnabled(false); ui.buttonBox->setFocus(); ui.buttonBox->button(QDialogButtonBox::Cancel)->setFocus(); } ignore_dataChanged = false; } void save_module(int at) { // save all options (except the name, which is handled by the model) Pkcs11ProviderConfig &i = model->list[at]; i.library = ui.le_library->text(); i.enabled = true; int x = ui.cb_slotmethod->currentIndex(); if (x == 0) i.slotevent_method = "auto"; else if (x == 1) i.slotevent_method = "trigger"; else // 2 i.slotevent_method = "poll"; if (x == 2) { if (ui.rb_polldefault->isChecked()) i.slotevent_timeout = 0; else i.slotevent_timeout = ui.sb_pollcustom->value(); } else i.slotevent_timeout = 0; i.allow_protected_authentication = ui.ck_modallowprotected->isChecked(); i.cert_private = ui.ck_certprivate->isChecked(); i.private_mask = 0; if (ui.ck_modesign->isChecked()) i.private_mask |= 1; if (ui.ck_modesignrecover->isChecked()) i.private_mask |= 2; if (ui.ck_modedecrypt->isChecked()) i.private_mask |= 4; if (ui.ck_modeunwrap->isChecked()) i.private_mask |= 8; } void save() { // save currently selected module, which may not be saved yet QItemSelection selection = ui.lv_modules->selectionModel()->selection(); if (!selection.indexes().isEmpty()) { QModelIndex index = selection.indexes().first(); save_module(index.row()); } config.allow_load_rootca = ui.ck_allowroot->isChecked(); config.allow_protected_authentication = ui.ck_allowprotected->isChecked(); config.log_level = ui.sb_loglevel->value(); if (ui.gb_pincache->isChecked()) { if (ui.rb_pincache_nolimit->isChecked()) config.pin_cache = -1; else config.pin_cache = ui.sb_pincache_time->value(); } else config.pin_cache = 0; config.providers = model->list; QVariantMap configmap = config.toVariantMap(); QCA::setProviderConfig(providerName, configmap); QCA::saveProviderConfig(providerName); } private Q_SLOTS: void model_editFailed(const QModelIndex &index, const QString &reasonString) { // if the dialog has already been dismissed, then don't // bother with handling the editing failure if (done) return; // show error dialog, and don't allow dimissing the dialog // during. we need this, because the dismiss request // can be queued, and end up being invoked during the // QMessageBox nested eventloop. allow_close = false; QMessageBox::information(q, tr("Module Configuration"), reasonString); allow_close = true; // return to edit mode for the item ui.lv_modules->setFocus(); ui.lv_modules->setCurrentIndex(index); ui.lv_modules->selectionModel()->select( index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Current); ui.lv_modules->edit(index); } void dataChanged() { if (ignore_dataChanged) return; if (dirty) return; dirty = true; ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } void module_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { if (!deselected.indexes().isEmpty()) { QModelIndex index = deselected.indexes().first(); save_module(index.row()); } ignore_dataChanged = true; if (!selected.indexes().isEmpty()) { if (deselected.indexes().isEmpty()) { ui.pb_remove->setEnabled(true); ui.tb_details->setEnabled(true); } QModelIndex index = selected.indexes().first(); const Pkcs11ProviderConfig &i = model->list[index.row()]; ui.le_library->setText(i.library); if (i.slotevent_method == "trigger") ui.cb_slotmethod->setCurrentIndex(1); else if (i.slotevent_method == "poll") { ui.cb_slotmethod->setCurrentIndex(2); if (i.slotevent_timeout <= 0) ui.rb_polldefault->setChecked(true); else { ui.rb_pollcustom->setChecked(true); ui.sb_pollcustom->setValue(i.slotevent_timeout); } } else // auto ui.cb_slotmethod->setCurrentIndex(0); if (i.slotevent_method != "poll") { ui.rb_polldefault->setChecked(true); ui.sb_pollcustom->setValue(5); } ui.ck_modallowprotected->setChecked(i.allow_protected_authentication); ui.ck_certprivate->setChecked(i.cert_private); if (i.private_mask == 0) ui.ck_modeauto->setChecked(true); else { ui.ck_modesign->setChecked(i.private_mask & 1); ui.ck_modesignrecover->setChecked(i.private_mask & 2); ui.ck_modedecrypt->setChecked(i.private_mask & 4); ui.ck_modeunwrap->setChecked(i.private_mask & 8); } } else if (selected.indexes().isEmpty() && !deselected.indexes().isEmpty()) { // restore defaults for all details widgets ui.le_library->setText(QString()); ui.cb_slotmethod->setCurrentIndex(0); ui.rb_polldefault->setChecked(true); ui.sb_pollcustom->setValue(5); ui.ck_modallowprotected->setChecked(false); ui.ck_certprivate->setChecked(false); ui.ck_modeauto->setChecked(true); // flip to first page, disable ui.tb_details->setCurrentIndex(0); ui.pb_remove->setEnabled(false); ui.tb_details->setEnabled(false); } ignore_dataChanged = false; } void module_add() { // find unused default name QString name; for (int n = 1;; ++n) { if (n == 1) name = tr("New Module"); else name = tr("New Module (%1)").arg(n); bool have_name_already = false; for (int n = 0; n < model->list.count(); ++n) { const Pkcs11ProviderConfig &i = model->list[n]; if (i.name == name) { have_name_already = true; break; } } if (!have_name_already) break; } Pkcs11ProviderConfig i; i.name = name; i.enabled = true; model->addItem(i); dataChanged(); QModelIndex index = model->index(model->list.count() - 1); // flip to first page ui.tb_details->setCurrentIndex(0); // edit this item ui.lv_modules->setFocus(); ui.lv_modules->setCurrentIndex(index); ui.lv_modules->selectionModel()->select( index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Current); ui.lv_modules->edit(index); } void module_remove() { QItemSelection selection = ui.lv_modules->selectionModel()->selection(); if (selection.indexes().isEmpty()) return; QModelIndex index = selection.indexes().first(); model->removeItem(index.row()); dataChanged(); } void library_browse() { QString fileName = QFileDialog::getOpenFileName(q, tr("Select PKCS#11 Module"), QString(), tr("PKCS#11 Modules (*.*)")); if (fileName.isEmpty()) return; ui.le_library->setText(fileName); } void slotmethod_currentIndexChanged(int index) { if (index == 2) // Polling ui.gb_poll->setEnabled(true); else ui.gb_poll->setEnabled(false); dataChanged(); } void modeauto_toggled(bool checked) { if (checked) { if (ui.ck_modesign->isChecked()) ui.ck_modesign->setChecked(false); if (ui.ck_modesignrecover->isChecked()) ui.ck_modesignrecover->setChecked(false); if (ui.ck_modedecrypt->isChecked()) ui.ck_modedecrypt->setChecked(false); if (ui.ck_modeunwrap->isChecked()) ui.ck_modeunwrap->setChecked(false); } else { if (!ui.ck_modesign->isChecked() && !ui.ck_modesignrecover->isChecked() && !ui.ck_modedecrypt->isChecked() && !ui.ck_modeunwrap->isChecked()) { ui.ck_modesign->setChecked(true); ui.ck_modesignrecover->setChecked(true); ui.ck_modedecrypt->setChecked(true); ui.ck_modeunwrap->setChecked(true); } } dataChanged(); } void modenonauto_toggled(bool checked) { if (checked) { if (ui.ck_modeauto->isChecked()) ui.ck_modeauto->setChecked(false); } else { if (!ui.ck_modesign->isChecked() && !ui.ck_modesignrecover->isChecked() && !ui.ck_modedecrypt->isChecked() && !ui.ck_modeunwrap->isChecked()) { ui.ck_modeauto->setChecked(true); } } dataChanged(); } }; Pkcs11ConfigDlg::Pkcs11ConfigDlg(QWidget *parent) : QDialog(parent) { QVariantMap config; QCA::Provider *p = get_pkcs11_provider(&config); if (p) d = new Private(this, p->name(), config); else d = new Private(this, QString(), QVariantMap()); } Pkcs11ConfigDlg::Pkcs11ConfigDlg(const QString &providerName, const QVariantMap &config, QWidget *parent) : QDialog(parent) { d = new Private(this, providerName, config); } Pkcs11ConfigDlg::~Pkcs11ConfigDlg() { delete d; } void Pkcs11ConfigDlg::done(int r) { if (!d->allow_close) return; d->done = true; if (r == Accepted) d->save(); QDialog::done(r); } bool Pkcs11ConfigDlg::isSupported() { return (get_pkcs11_provider() ? true : false); } #include "pkcs11configdlg.moc"