xca/widgets/MainWindow.cpp
Christian Hohnstaedt 176c2f8169 Improve password input and handling
Use the Passwd class instead of QString

Move password input into the database model
to be available for commandline use.

Repeat in case of wrong password
2020-03-11 07:19:56 +01:00

849 lines
18 KiB
C++

/* vi: set sw=4 ts=4:
*
* Copyright (C) 2001 - 2015 Christian Hohnstaedt.
*
* All rights reserved.
*/
//#define MDEBUG
#include "MainWindow.h"
#include "XcaApplication.h"
#include "ImportMulti.h"
#include "dhgen.h"
#include "lib/Passwd.h"
#include "lib/entropy.h"
#include "lib/database_model.h"
#include <openssl/rand.h>
#include <QApplication>
#include <QClipboard>
#include <QMessageBox>
#include <QLabel>
#include <QPushButton>
#include <QListView>
#include <QLineEdit>
#include <QTextBrowser>
#include <QStatusBar>
#include <QList>
#include <QTimer>
#include <QThread>
#include <QMimeData>
#include <QInputDialog>
#include "lib/exception.h"
#include "lib/pki_evp.h"
#include "lib/pki_scard.h"
#include "lib/pki_pkcs12.h"
#include "lib/pki_multi.h"
#include "lib/load_obj.h"
#include "lib/pass_info.h"
#include "lib/func.h"
#include "lib/pkcs11.h"
#include "lib/builtin_curves.h"
#include "XcaDialog.h"
#include "PwDialog.h"
#include "OpenDb.h"
OidResolver *MainWindow::resolver = NULL;
void MainWindow::enableTokenMenu(bool enable)
{
foreach(QWidget *w, scardList) {
w->setEnabled(enable);
}
}
void MainWindow::initResolver()
{
bool shown = false;
QString search;
if (resolver) {
shown = resolver->isVisible();
search = resolver->input->text();
delete resolver;
}
resolver = new OidResolver(NULL);
resolver->setWindowTitle(XCA_TITLE);
if (shown)
resolver->searchOid(search);
}
MainWindow::MainWindow(database_model *m)
:QMainWindow(NULL)
{
dbindex = new QLabel();
dbindex->setFrameStyle(QFrame::Plain | QFrame::NoFrame);
dbindex->setMargin(6);
dn_translations_setup();
statusBar()->addWidget(dbindex, 1);
setupUi(this);
setWindowTitle(XCA_TITLE);
initResolver();
wdList << keyButtons << reqButtons << certButtons <<
tempButtons << crlButtons;
QStringList drivers = QSqlDatabase::drivers();
foreach(QString driver, drivers) {
QSqlDatabase d = QSqlDatabase::addDatabase(driver, driver +"_C");
qDebug() << "DB driver:" << driver;
}
historyMenu = NULL;
init_menu();
setItemEnabled(false);
init_images();
homedir = getHomeDir();
#ifdef MDEBUG
CRYPTO_malloc_debug_init();
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
qWarning() << "malloc() debugging on.";
#endif
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
EVP_add_digest_alias(SN_sha1,SN_ecdsa_with_SHA1);
EVP_add_digest_alias(SN_sha224,SN_ecdsa_with_SHA224);
EVP_add_digest_alias(SN_sha256,SN_ecdsa_with_SHA256);
EVP_add_digest_alias(SN_sha256,SN_dsa_with_SHA256);
EVP_add_digest_alias(SN_sha384,SN_ecdsa_with_SHA384);
EVP_add_digest_alias(SN_sha512,SN_ecdsa_with_SHA512);
setAcceptDrops(true);
searchEdit = new QLineEdit();
searchEdit->setPlaceholderText(tr("Search"));
keyView->setIconSize(QPixmap(":keyIco").size());
reqView->setIconSize(QPixmap(":reqIco").size());
certView->setIconSize(QPixmap(":validcertIco").size());
tempView->setIconSize(QPixmap(":templateIco").size());
crlView->setIconSize(QPixmap(":crlIco").size());
views << keyView << reqView << certView << crlView << tempView;
foreach(XcaTreeView *v, views)
v->setMainwin(this, searchEdit);
dhgen = NULL;
dhgenBar = new QProgressBar();
check_oom(dhgenBar);
dhgenBar->setMinimum(0);
dhgenBar->setMaximum(0);
models = NULL;
int ret = m ? init_database(m) : init_database(QString());
if (ret == 2)
throw errorEx(2);
}
void MainWindow::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasUrls()) {
QList<QUrl> urls = event->mimeData()->urls();
QUrl u;
QStringList files;
foreach(u, urls) {
QString s = u.toLocalFile();
files << s;
}
openURLs(files);
event->acceptProposedAction();
} else if (event->mimeData()->hasText()) {
event->acceptProposedAction();
pastePem(event->mimeData()->text());
}
}
void MainWindow::openURLs(QStringList &files)
{
urlsToOpen = files;
QTimer::singleShot(100, this, SLOT(openURLs()));
}
void MainWindow::openURLs()
{
importAnything(urlsToOpen);
urlsToOpen.clear();
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat(X_XCA_DRAG_DATA))
return;
if (event->mimeData()->hasUrls() || event->mimeData()->hasText())
event->acceptProposedAction();
}
void MainWindow::setItemEnabled(bool enable)
{
foreach(QWidget *w, wdList) {
w->setEnabled(enable);
}
foreach(QWidget *w, wdMenuList) {
w->setEnabled(enable);
}
foreach(QAction *a, acList) {
a->setEnabled(enable);
}
enableTokenMenu(pkcs11::loaded());
}
void MainWindow::init_images()
{
bigKey->setPixmap(QPixmap(":keyImg"));
bigCsr->setPixmap(QPixmap(":csrImg"));
bigCert->setPixmap(QPixmap(":certImg"));
bigTemp->setPixmap(QPixmap(":tempImg"));
bigRev->setPixmap(QPixmap(":revImg"));
setWindowIcon(QPixmap(":appIco"));
}
#if 0
QString file = filename2QString(arg);
if (force_load) {
if (changeDB(file) == 2)
exitApp = 1;
force_load = 0;
} else if (export_index) {
if (exportIndex(file, (export_index == 2)) == 2)
exitApp = 1;
export_index = 0;
} else {
int ret;
pki_multi *pki = probeAnything(file, &ret);
if (!pki) {
if (ret == 2)
exitApp = 1;
else if (ret == 1)
failed << file;
}
dlgi->addItem(pki);
}
cnt++;
}
dlgi->execute(1, failed); /* force showing of import dialog */
if (dlgi->result() == QDialog::Rejected)
exitApp = 1;
delete dlgi;
#endif
void MainWindow::loadPem()
{
load_pem l;
db_key *keys = models->model<db_key>();
if (keys)
keys->load_default(l);
}
bool MainWindow::pastePem(QString text, bool silent)
{
bool success = false;
QByteArray pemdata = text.toLatin1();
if (pemdata.size() == 0)
return false;
pki_multi *pem = NULL;
try {
pem = new pki_multi();
pem->fromPEMbyteArray(pemdata, QString());
success = pem->count() != 0;
importMulti(pem, 1);
}
catch (errorEx &err) {
delete pem;
if (!silent)
XCA_ERROR(err);
}
return success;
}
void MainWindow::pastePem()
{
QClipboard *cb = QApplication::clipboard();
QString text;
text = cb->text(QClipboard::Selection);
if (text.isEmpty())
text = cb->text(QClipboard::Clipboard);
if (!text.isEmpty())
if (pastePem(text, true))
return;
QTextEdit *textbox = new QTextEdit();
textbox->setPlainText(text);
XcaDialog *input = new XcaDialog(this, x509, textbox,
tr("Import PEM data"), QString());
input->noSpacer();
if (input->exec()) {
text = textbox->toPlainText();
if (!text.isEmpty())
pastePem(text);
}
delete input;
}
void MainWindow::initToken()
{
bool ok;
if (!pkcs11::loaded())
return;
try {
pkcs11 p11;
slotid slot;
Passwd pin;
int ret;
if (!p11.selectToken(&slot, this))
return;
tkInfo ti = p11.tokenInfo(slot);
QString slotname = QString("%1 (#%2)").
arg(ti.label()).arg(ti.serial());
pass_info p(XCA_TITLE,
tr("Please enter the original SO PIN (PUK) of the token '%1'").
arg(slotname) + "\n" + ti.pinInfo());
p.setPin();
if (ti.tokenInitialized()) {
ret = PwDialog::execute(&p, &pin, false);
} else {
p.setDescription(tr("Please enter the new SO PIN (PUK) for the token '%1'").
arg(slotname) + "\n" + ti.pinInfo());
ret = PwDialog::execute(&p, &pin, true);
}
if (ret != 1)
return;
QString label = QInputDialog::getText(this, XCA_TITLE,
tr("The new label of the token '%1'").
arg(slotname), QLineEdit::Normal, QString(), &ok);
if (!ok)
return;
p11.initToken(slot, pin.constUchar(), pin.size(), label);
} catch (errorEx &err) {
XCA_ERROR(err);
}
}
void MainWindow::changePin(bool so)
{
if (!pkcs11::loaded())
return;
try {
pkcs11 p11;
slotid slot;
if (!p11.selectToken(&slot, this))
return;
p11.changePin(slot, so);
} catch (errorEx &err) {
XCA_ERROR(err);
}
}
void MainWindow::changeSoPin()
{
changePin(true);
}
void MainWindow::initPin()
{
if (!pkcs11::loaded())
return;
try {
pkcs11 p11;
slotid slot;
if (!p11.selectToken(&slot, this))
return;
p11.initPin(slot);
} catch (errorEx &err) {
XCA_ERROR(err);
}
}
void MainWindow::manageToken()
{
pkcs11 p11;
slotid slot;
pki_scard *card = NULL;
pki_x509 *cert = NULL;
ImportMulti *dlgi = NULL;
if (!pkcs11::loaded())
return;
try {
if (!p11.selectToken(&slot, this))
return;
ImportMulti *dlgi = new ImportMulti(this);
dlgi->tokenInfo(slot);
QList<CK_OBJECT_HANDLE> objects;
QList<CK_MECHANISM_TYPE> ml = p11.mechanismList(slot);
if (ml.count() == 0)
ml << CKM_SHA1_RSA_PKCS;
pk11_attlist atts(pk11_attr_ulong(CKA_CLASS,
CKO_PUBLIC_KEY));
p11.startSession(slot);
p11.getRandom();
objects = p11.objectList(atts);
for (int j=0; j< objects.count(); j++) {
card = new pki_scard("");
try {
card->load_token(p11, objects[j]);
card->setMech_list(ml);
dlgi->addItem(card);
} catch (errorEx &err) {
XCA_ERROR(err);
delete card;
}
card = NULL;
}
atts.reset();
atts << pk11_attr_ulong(CKA_CLASS, CKO_CERTIFICATE) <<
pk11_attr_ulong(CKA_CERTIFICATE_TYPE,CKC_X_509);
objects = p11.objectList(atts);
for (int j=0; j< objects.count(); j++) {
cert = new pki_x509("");
try {
cert->load_token(p11, objects[j]);
dlgi->addItem(cert);
} catch (errorEx &err) {
XCA_ERROR(err);
delete cert;
}
cert = NULL;
}
if (dlgi->entries() == 0) {
tkInfo ti = p11.tokenInfo();
XCA_INFO(tr("The token '%1' did not contain any keys or certificates").arg(ti.label()));
} else {
dlgi->execute(true);
}
} catch (errorEx &err) {
XCA_ERROR(err);
}
if (card)
delete card;
if (cert)
delete cert;
if (dlgi)
delete dlgi;
}
MainWindow::~MainWindow()
{
ERR_free_strings();
EVP_cleanup();
OBJ_cleanup();
delete dbindex;
#ifdef MDEBUG
fprintf(stderr, "Memdebug:\n");
CRYPTO_mem_leaks_fp(stderr);
#endif
}
void MainWindow::closeEvent(QCloseEvent *e)
{
if (dhgen) {
if (!XCA_YESNO("Abort Diffie-Hellmann parameter generation?")){
e->ignore();
return;
}
dhgen->terminate();
}
if (resolver) {
delete resolver;
}
close_database();
QMainWindow::closeEvent(e);
}
int MainWindow::checkOldGetNewPass(Passwd &pass)
{
QString passHash = Settings["pwhash"];
if (!passHash.isEmpty()) {
pass_info p(tr("Current Password"),
tr("Please enter the current database password"), this);
/* Try empty password */
if (pki_evp::sha512passwT(pass, passHash) != passHash) {
/* Not the empty password, check it */
if (PwDialog::execute(&p, &pass, false) != 1)
return 0;
}
if (pki_evp::sha512passwT(pass, passHash) != passHash) {
XCA_WARN(tr("The entered password is wrong"));
return 0;
}
}
pass_info p(tr("New Password"), tr("Please enter the new password "
"to encrypt your private keys in the database-file"),
this);
return PwDialog::execute(&p, &pass, true) != 1 ? 0 : 1;
}
void MainWindow::changeDbPass()
{
Passwd pass;
XSqlQuery q;
QSqlDatabase db = QSqlDatabase::database();
db_key *keys = models->model<db_key>();
if (!checkOldGetNewPass(pass))
return;
QString salt = Entropy::makeSalt();
QString passhash = pki_evp::sha512passwT(pass, salt);
QList<pki_evp*> key_list = keys->sqlSELECTpki<pki_evp>(
"SELECT item FROM private_keys WHERE ownPass=0");
try {
Transaction;
if (!TransBegin()) {
errorEx e(tr("Transaction start failed"));
XCA_ERROR(e);
return;
}
foreach(pki_evp *key, key_list) {
EVP_PKEY *evp = key->decryptKey();
key->set_evp_key(evp);
key->encryptKey(pass.constData());
key->sqlUpdatePrivateKey();
}
Settings["pwhash"] = passhash;
TransCommit();
pki_evp::passHash = passhash;
pki_evp::passwd = pass;
} catch (errorEx &e) {
XCA_ERROR(e);
}
}
void MainWindow::connNewX509(NewX509 *nx)
{
db_x509req *reqs = models->model<db_x509req>();
db_key *keys = models->model<db_key>();
connect(nx, SIGNAL(genKey(QString)),
keys, SLOT(newItem(QString)));
connect(keys, SIGNAL(keyDone(pki_key*)),
nx, SLOT(newKeyDone(pki_key*)));
connect(nx, SIGNAL(showReq(pki_base*)),
reqs, SLOT(showPki(pki_base*)));
connect(reqs, SIGNAL(pkiChanged(pki_base*)),
nx, SLOT(itemChanged(pki_base*)));
}
void MainWindow::importAnything(QString file)
{
importAnything(QStringList(file));
}
void MainWindow::importAnything(const QStringList &files)
{
pki_multi *multi = new pki_multi();
foreach(QString s, files)
multi->probeAnything(s);
importMulti(multi, 1);
}
void MainWindow::importMulti(pki_multi *multi, int force)
{
if (!multi)
return;
ImportMulti *dlgi = new ImportMulti(this);
dlgi->addItem(multi);
dlgi->execute(force, multi->failed_files);
delete dlgi;
}
void MainWindow::openRemoteSqlDB()
{
OpenDb *opendb = new OpenDb(this, currentDB);
QString descriptor;
Passwd pass;
DbMap params;
if (opendb->exec()) {
descriptor = opendb->getDescriptor();
pass = opendb->dbPassword->text().toLatin1();
params = database_model::splitRemoteDbName(descriptor);
}
delete opendb;
if (descriptor.isEmpty())
return;
init_database(descriptor, pass);
}
int MainWindow::init_database(const QString &name, const Passwd &pass)
{
try {
close_database();
return init_database(new database_model(name, pass));
} catch (errorEx &err) {
if (err.info == 0 && !err.isEmpty()) {
XCA_ERROR(err);
return 1;
} else {
return err.info;
}
}
return 1;
}
int MainWindow::init_database(database_model *m)
{
if (!m)
return 1;
models = m;
setItemEnabled(true);
m->restart_timer();
currentDB = models->dbname();
dbindex->setText(tr("Database") + ": " + currentDB);
certView->setRootIsDecorated(db_x509::treeview);
set_geometry(Settings["mw_geometry"]);
if (pki_evp::passwd.isNull())
XCA_INFO(tr("Using or exporting private keys will not be possible without providing the correct password"));
enableTokenMenu(pkcs11::loaded());
hashBox hb(this);
if (hb.isInsecure()) {
XCA_WARN(tr("The currently used default hash '%1' is insecure. Please select at least 'SHA 224' for security reasons.").arg(hb.currentHashName()));
setOptions();
}
foreach(XcaTreeView *v, views)
v->setModels(models);
searchEdit->setText("");
searchEdit->show();
statusBar()->addWidget(searchEdit, 1);
db_x509 *certs = models->model<db_x509>();
db_x509req *reqs = models->model<db_x509req>();
connect(certs, SIGNAL(connNewX509(NewX509 *)),
this, SLOT(connNewX509(NewX509 *)));
connect(reqs, SIGNAL(connNewX509(NewX509 *)),
this, SLOT(connNewX509(NewX509 *)));
connect(reqs, SIGNAL(newCert(pki_x509req *)),
certs, SLOT(newCert(pki_x509req *)));
connect(tempView, SIGNAL(newCert(pki_temp *)),
certs, SLOT(newCert(pki_temp *)) );
connect(tempView, SIGNAL(newReq(pki_temp *)),
reqs, SLOT(newItem(pki_temp *)) );
return 0;
}
void MainWindow::set_geometry(QString geo)
{
QStringList sl = geo.split(",");
if (sl.size() != 3)
return;
resize(sl[0].toInt(), sl[1].toInt());
int i = sl[2].toInt();
if (i != -1)
tabView->setCurrentIndex(i);
}
void MainWindow::close_database()
{
if (!models)
return;
qDebug("Closing database: %s", CCHAR(currentDB));
Settings["mw_geometry"] = QString("%1,%2,%3")
.arg(size().width())
.arg(size().height())
.arg(tabView->currentIndex());
delete models;
models = NULL;
setItemEnabled(false);
dbindex->clear();
history.addEntry(currentDB);
update_history_menu();
currentDB.clear();
foreach(XcaTreeView *v, views)
v->setModel();
enableTokenMenu(pkcs11::loaded());
}
void MainWindow::exportIndex()
{
exportIndex(QFileDialog::getSaveFileName(this, XCA_TITLE,
Settings["workingdir"],
tr("Certificate Index ( index.txt )") + ";;" +
tr("All files ( * )")),
false);
}
void MainWindow::exportIndexHierarchy()
{
exportIndex(QFileDialog::getExistingDirectory(
this, XCA_TITLE, Settings["workingdir"]), true);
}
void MainWindow::exportIndex(const QString &fname, bool hierarchy) const
{
qDebug() << fname << hierarchy;
if (fname.isEmpty() || !models)
return;
db_x509 *certs = models->model<db_x509>();
certs->writeIndex(fname, hierarchy);
}
void MainWindow::generateDHparamDone()
{
statusBar()->removeWidget(dhgenBar);
errorEx e(dhgen->error);
if (e.isEmpty())
XCA_INFO(tr("Diffie-Hellman parameters saved as: %1")
.arg(dhgen->filename()));
else
XCA_ERROR(e);
dhgen->deleteLater();
dhgen = NULL;
}
void MainWindow::generateDHparam()
{
bool ok;
int bits;
if (dhgen)
return;
bits = QInputDialog::getDouble(this, XCA_TITLE, tr("Diffie-Hellman parameters are needed for different applications, but not handled by XCA.\nPlease enter the DH parameter bits"),
1024, 1024, 4096, 0, &ok);
if (!ok)
return;
/*
* 1024: 6 sec
* 2048: 38 sec
* 4096: 864 sec
*/
Entropy::seed_rng();
try {
QString fname = QString("%1/dh%2.pem").arg(homedir).arg(bits);
fname = QFileDialog::getSaveFileName(this, QString(),
fname, tr("All files ( * )"), NULL);
if (fname == "")
throw errorEx("");
dhgen = new DHgen(fname, bits);
check_oom(dhgen);
statusBar()->addPermanentWidget(dhgenBar, 1);
dhgenBar->show();
dhgen->start(QThread::LowestPriority);
connect(dhgen, SIGNAL(finished()),
this, SLOT(generateDHparamDone()));
} catch (errorEx &err) {
XCA_ERROR(err);
}
}
void MainWindow::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange) {
retranslateUi(this);
dn_translations_setup();
init_menu();
foreach(db_base *model, models->getModels())
model->updateHeaders();
if (!currentDB.isEmpty())
dbindex->setText(tr("Database") + ": " + currentDB);
searchEdit->setPlaceholderText(tr("Search"));
}
QMainWindow::changeEvent(event);
}
void MainWindow::keyPressEvent(QKeyEvent *e)
{
if (e->modifiers() != Qt::ControlModifier) {
QMainWindow::keyPressEvent(e);
return;
}
int siz = XcaApplication::tableFont.pointSize();
switch (e->key()) {
case Qt::Key_Plus:
XcaApplication::tableFont.setPointSize(siz +1);
break;
case Qt::Key_Minus:
if (siz > 4) {
XcaApplication::tableFont.setPointSize(siz -1);
}
break;
case Qt::Key_V:
if (e->modifiers() == Qt::ControlModifier) {
pastePem();
break;
}
/* FALLTHROUGH */
default:
QMainWindow::keyPressEvent(e);
return;
}
foreach(XcaTreeView *v, views) {
if (v) {
v->header()->resizeSections(
QHeaderView::ResizeToContents);
v->reset();
}
}
update();
}
void MainWindow::dump_database()
{
QString dirname = QFileDialog::getExistingDirectory(
NULL, XCA_TITLE, Settings["workingdir"]);
try {
if (models)
models->dump_database(dirname);
} catch (errorEx &err) {
XCA_ERROR(err);
}
}
void MainWindow::default_database()
{
database_model::as_default_database(currentDB);
}