xca/widgets/MainWindow.cpp
Christian Hohnstaedt 595cf9a722 Drop connNewX509() signal and slot
in the past it was used to connect the NewX509 dialog
with requests keys and certs.

The NewX509 dialog knows mainwin since some time
and thus can connect itself to models and views

mainwin: use model<T>() instead of models->model<T>()
2020-03-13 12:13:27 +01:00

791 lines
17 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 <QApplication>
#include <QClipboard>
#include <QFileDialog>
#include <QLabel>
#include <QLineEdit>
#include <QTextBrowser>
#include <QStatusBar>
#include <QList>
#include <QTimer>
#include <QThread>
#include <QMimeData>
#include <QInputDialog>
#include <openssl/err.h>
#include "lib/Passwd.h"
#include "lib/database_model.h"
#include "lib/exception.h"
#include "lib/pki_evp.h"
#include "lib/pki_multi.h"
#include "lib/pki_scard.h"
#include "XcaDialog.h"
#include "XcaWarning.h"
#include "PwDialog.h"
#include "OpenDb.h"
#include "OidResolver.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);
OpenDb::checkSqLite();
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;
enum open_result result = m ? init_database(m) :
init_database(QString());
if (result == pw_exit)
throw pw_exit;
}
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"));
}
void MainWindow::loadPem()
{
load_pem l;
db_key *keys = 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->failed_files.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);
}
delete card;
delete cert;
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();
}
delete resolver;
resolver = NULL;
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 = 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::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);
}
enum open_result MainWindow::init_database(const QString &name,
const Passwd &pass)
{
try {
close_database();
return init_database(new database_model(name, pass));
} catch (errorEx &err) {
XCA_ERROR(err);
return open_abort;
} catch (enum open_result r) {
return r;
}
return pw_ok;
}
enum open_result MainWindow::init_database(database_model *m)
{
if (!m)
return open_abort;
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();
}
keyView->setModel(model<db_key>());
reqView->setModel(model<db_x509req>());
certView->setModel(model<db_x509>());
tempView->setModel(model<db_temp>());
crlView->setModel(model<db_crl>());
searchEdit->setText("");
searchEdit->show();
statusBar()->addWidget(searchEdit, 1);
db_x509 *certs = model<db_x509>();
db_x509req *reqs = model<db_x509req>();
connect(tempView, SIGNAL(newCert(pki_temp *)),
certs, SLOT(newCert(pki_temp *)) );
connect(tempView, SIGNAL(newReq(pki_temp *)),
reqs, SLOT(newItem(pki_temp *)) );
return pw_ok;
}
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(NULL);
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 = 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);
}