aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
aheinecke@404:  * Software engineering by Intevation GmbH
aheinecke@404:  *
aheinecke@404:  * This file is Free Software under the GNU GPL (v>=2)
aheinecke@404:  * and comes with ABSOLUTELY NO WARRANTY!
aheinecke@404:  * See LICENSE.txt for details.
aheinecke@404:  */
rrenkert@367: #include "createcertlistdialog.h"
aheinecke@454: #include "sslhelp.h"
aheinecke@454: #include "administratorwindow.h"
aheinecke@454: 
rrenkert@367: #include <QDebug>
aheinecke@454: #include <QMessageBox>
rrenkert@367: #include <QDir>
rrenkert@367: #include <QPushButton>
rrenkert@367: #include <QGroupBox>
rrenkert@367: #include <QHBoxLayout>
rrenkert@367: #include <QVBoxLayout>
rrenkert@367: #include <QLabel>
rrenkert@367: #include <QFileDialog>
rrenkert@413: #include <QStandardPaths>
rrenkert@367: 
aheinecke@454: #include <polarssl/pk.h>
aheinecke@454: 
aheinecke@454: CreateCertListDialog::CreateCertListDialog(AdministratorWindow *parent) :
aheinecke@454:     QDialog(parent),
aheinecke@454:     mAdminWindow(parent),
aheinecke@454:     mPk(NULL)
rrenkert@367: {
rrenkert@413:     setWindowTitle(tr("Save certificate list"));
rrenkert@367:     setupGUI();
rrenkert@413:     resize(500, 200);
aheinecke@465:     mKeyFile->setText(mAdminWindow->settings()->value("LastKey", QString()).toString());
aheinecke@465:     mSaveDir->setText(mAdminWindow->settings()->value("LastOutputDir", QString()).toString());
aheinecke@465:     if (!mKeyFile->text().isEmpty()) {
aheinecke@465:         loadKeyFile(mKeyFile->text());
aheinecke@465:     }
rrenkert@367: }
rrenkert@367: 
rrenkert@367: void CreateCertListDialog::setupGUI()
rrenkert@367: {
rrenkert@367:     /* Top level layout / widgets */
rrenkert@367:     QVBoxLayout *topLayout = new QVBoxLayout;
rrenkert@413:     QVBoxLayout *headerLayout = new QVBoxLayout;
rrenkert@413:     QHBoxLayout *headerSubLayout = new QHBoxLayout;
rrenkert@428:     QHBoxLayout *centerLayout = new QHBoxLayout;
rrenkert@367:     QHBoxLayout *bottomLayout = new QHBoxLayout;
rrenkert@428:     QVBoxLayout *labelLayout = new QVBoxLayout;
rrenkert@428:     QVBoxLayout *fieldLayout = new QVBoxLayout;
rrenkert@428:     QVBoxLayout *buttonLayout = new QVBoxLayout;
rrenkert@367: 
rrenkert@413:     QLabel *header = new QLabel("<h3>" + tr("Save certificate list") + "</h3>");
rrenkert@413:     QLabel *description = new QLabel(
rrenkert@426:         tr("Save all managed root certificates in a new, signed certificate list."));
rrenkert@413:     headerSubLayout->insertSpacing(0, 40);
rrenkert@413:     headerSubLayout->addWidget(description);
rrenkert@413:     QFrame *headerSeparator = new QFrame();
rrenkert@413:     headerSeparator->setFrameShape(QFrame::HLine);
rrenkert@413:     headerSeparator->setFrameShadow(QFrame::Sunken);
rrenkert@413:     headerLayout->addWidget(header);
rrenkert@413:     headerLayout->addLayout(headerSubLayout);
rrenkert@413:     headerLayout->addWidget(headerSeparator);
rrenkert@428:     headerLayout->insertSpacing(3, 10);
rrenkert@367: 
rrenkert@522:     QLabel *certLabel = new QLabel(tr("Select signing key:"));
rrenkert@522:     QLabel *saveLabel = new QLabel(tr("Select output folder:"));
rrenkert@428:     labelLayout->addWidget(certLabel);
rrenkert@428:     labelLayout->addWidget(saveLabel);
rrenkert@428: 
aheinecke@465:     mKeyFile = new QLineEdit();
aheinecke@465:     mSaveDir = new QLineEdit();
aheinecke@465:     fieldLayout->addWidget(mKeyFile);
aheinecke@465:     fieldLayout->addWidget(mSaveDir);
rrenkert@428: 
rrenkert@367:     QPushButton *certSelect = new QPushButton("...");
rrenkert@367:     certSelect->setFixedWidth(30);
rrenkert@428:     connect(certSelect, SIGNAL(clicked()), this, SLOT(openCertificateSelect()));
rrenkert@367:     QPushButton *saveSelect = new QPushButton("...");
rrenkert@367:     connect(saveSelect, SIGNAL(clicked()), this, SLOT(openSaveLocation()));
rrenkert@367:     saveSelect->setFixedWidth(30);
rrenkert@428:     buttonLayout->addWidget(certSelect);
rrenkert@428:     buttonLayout->addWidget(saveSelect);
rrenkert@413: 
rrenkert@426:     QString footerText = tr("In addition, each certificate list will be saved "
rrenkert@413:         "automatically in the archive directory:\n");
rrenkert@426:     footerText.append(QStandardPaths::writableLocation(
rrenkert@426:         QStandardPaths::DataLocation));
rrenkert@413:     QLabel *footer = new QLabel(footerText);
rrenkert@367: 
rrenkert@428:     centerLayout->addLayout(labelLayout);
rrenkert@428:     centerLayout->addLayout(fieldLayout);
rrenkert@428:     centerLayout->addLayout(buttonLayout);
rrenkert@367: 
rrenkert@428:     QPushButton *create = new QPushButton(tr("Save list"));
rrenkert@367:     connect(create, SIGNAL(clicked()), this, SLOT(createList()));
rrenkert@413:     QPushButton *cancel = new QPushButton(tr("Cancel"));
rrenkert@413:     connect(cancel, SIGNAL(clicked()), this, SLOT(close()));
rrenkert@367:     bottomLayout->insertStretch(0, 10);
rrenkert@367:     bottomLayout->addWidget(create);
rrenkert@413:     bottomLayout->addWidget(cancel);
rrenkert@413: 
rrenkert@413:     QFrame *bottomSeparator = new QFrame();
rrenkert@413:     bottomSeparator->setFrameShape(QFrame::HLine);
rrenkert@413:     bottomSeparator->setFrameShadow(QFrame::Sunken);
rrenkert@367: 
rrenkert@367:     topLayout->addLayout(headerLayout);
rrenkert@367:     topLayout->addLayout(centerLayout);
rrenkert@367:     topLayout->insertStretch(2, 10);
rrenkert@413:     topLayout->addWidget(footer);
rrenkert@428:     topLayout->insertSpacing(4, 10);
rrenkert@413:     topLayout->addWidget(bottomSeparator);
rrenkert@367:     topLayout->addLayout(bottomLayout);
rrenkert@367: 
rrenkert@367:     setLayout(topLayout);
rrenkert@367: 
rrenkert@367:     return;
rrenkert@367: }
rrenkert@367: 
aheinecke@454: void CreateCertListDialog::showErrorMessage(const QString &msg)
aheinecke@454: {
aheinecke@454:     QMessageBox::warning(this, tr("Error!"), msg);
aheinecke@454: }
aheinecke@454: 
aheinecke@465: void CreateCertListDialog::loadKeyFile(const QString& fileName)
rrenkert@367: {
aheinecke@454:     if (mPk != NULL) {
aheinecke@454:         pk_free(mPk);
aheinecke@454:         delete mPk;
aheinecke@454:         mPk = NULL;
aheinecke@454:     }
aheinecke@454: 
aheinecke@454:     mPk = new pk_context;
aheinecke@454:     pk_init(mPk);
aheinecke@465:     int ret = pk_parse_keyfile(mPk, mKeyFile->text().toLocal8Bit().constData(), "");
aheinecke@454: 
aheinecke@454:     if (ret != 0) {
aheinecke@454:         showErrorMessage(tr("Failed to load certificate: %1")
aheinecke@454:                 .arg(getPolarSSLErrorMsg(ret)));
aheinecke@465:         pk_free(mPk);
aheinecke@465:         delete mPk;
aheinecke@465:         mPk = NULL;
aheinecke@454:         return;
aheinecke@454:     }
aheinecke@465: 
aheinecke@465:     /* Check that it is a 3072 bit RSA key as specified */
aheinecke@465:     if (!mPk->pk_info || pk_get_size(mPk) != 3072 ||
aheinecke@465:             mPk->pk_info->type != POLARSSL_PK_RSA) {
aheinecke@465:         showErrorMessage(tr("Only 3072 bit RSA keys are supported by the current format."));
aheinecke@465:         pk_free(mPk);
aheinecke@465:         delete mPk;
aheinecke@465:         mPk = NULL;
aheinecke@465:         return;
aheinecke@465:     }
aheinecke@465: }
aheinecke@465: 
aheinecke@465: void CreateCertListDialog::openCertificateSelect()
aheinecke@465: {
aheinecke@465:     QString keyFile = QFileDialog::getOpenFileName(
aheinecke@465:         this, tr("Select certificate"), mKeyFile->text().isEmpty() ?
aheinecke@465:         QDir::homePath() : mKeyFile->text(), "*.pem");
aheinecke@465:     mKeyFile->setText(keyFile);
aheinecke@465: 
aheinecke@465:     mAdminWindow->settings()->setValue("LastKey", keyFile);
aheinecke@465:     loadKeyFile(keyFile);
aheinecke@465: 
aheinecke@465:     return;
rrenkert@367: }
rrenkert@367: 
rrenkert@367: void CreateCertListDialog::openSaveLocation()
rrenkert@367: {
aheinecke@465:     QString saveDir = QFileDialog::getExistingDirectory(
aheinecke@465:         this, tr("Select target location"),
aheinecke@465:         mSaveDir->text().isEmpty() ? QDir::homePath() : mSaveDir->text());
aheinecke@465:     mAdminWindow->settings()->setValue("LastOutputDir", saveDir);
aheinecke@465:     mSaveDir->setText(saveDir);
aheinecke@465: }
aheinecke@465: 
aheinecke@465: CreateCertListDialog::~CreateCertListDialog()
aheinecke@465: {
aheinecke@465:     if (mPk) {
aheinecke@465:         pk_free(mPk);
aheinecke@465:         delete mPk;
aheinecke@465:         mPk = NULL;
aheinecke@465:     }
rrenkert@367: }
rrenkert@367: 
aheinecke@466: bool CreateCertListDialog::writeList(const QList<Certificate>& certs,
aheinecke@466:                                      const QString& filePath,
aheinecke@466:                                      const QDateTime& listDate,
aheinecke@466:                                      pk_context *pk)
aheinecke@466: {
aheinecke@466:     /* Build up the list data */
aheinecke@473:     QByteArray listData("F:1\r\nD:");
aheinecke@466:     listData.append(listDate.toString(Qt::ISODate) + "\r\n");
aheinecke@466: 
aheinecke@466:     foreach (const Certificate& cert, certs) {
aheinecke@473:         listData.append(cert.base64Line() + "\r\n");
aheinecke@466:     }
aheinecke@466: 
aheinecke@466:     QByteArray signature = rsaSignSHA256Hash(sha256sum(listData), pk);
aheinecke@466:     if (signature.size() != 3072 / 8) {
aheinecke@466:         qDebug() << "Signature creation returned signature of invalid size.";
aheinecke@466:         return false;
aheinecke@466:     }
aheinecke@466:     listData.prepend("\r\n");
aheinecke@466:     listData.prepend(signature.toBase64());
aheinecke@466:     listData.prepend("S:");
aheinecke@466: 
aheinecke@466:     QFile outputFile(filePath);
aheinecke@466: 
aheinecke@466:     if (!outputFile.open(QIODevice::WriteOnly)) {
aheinecke@466:         qDebug() << "Failed to open output file: " << filePath;
aheinecke@466:         return false;
aheinecke@466:     }
aheinecke@466: 
aheinecke@466:     if (outputFile.write(listData) != listData.size()) {
aheinecke@466:         qDebug() << "Failed to write list: " << filePath;
aheinecke@466:         outputFile.close();
aheinecke@466:         return false;
aheinecke@466:     }
aheinecke@466:     outputFile.close();
aheinecke@466:     return true;
aheinecke@466: }
aheinecke@466: 
rrenkert@367: void CreateCertListDialog::createList()
rrenkert@367: {
aheinecke@465:     if (!mPk) {
aheinecke@465:         showErrorMessage(tr("Please select a valid rsa key."));
aheinecke@465:     }
aheinecke@465:     if (mSaveDir->text().isEmpty()) {
aheinecke@465:         showErrorMessage(tr("Please select an output location first."));
aheinecke@465:     }
aheinecke@454: 
aheinecke@465:     QDateTime currentDateTimeUtc = QDateTime::currentDateTimeUtc();
aheinecke@465: 
aheinecke@465:     QString fileName = QString::fromLatin1("certificates-")
aheinecke@465:             .append(currentDateTimeUtc.toString(("yyyyMMddHHmmss")))
aheinecke@465:             .append(".txt");
aheinecke@465: 
aheinecke@465:     QString filePath = mSaveDir->text().append("/").append(fileName);
aheinecke@465: 
aheinecke@466:     if (!writeList(mAdminWindow->certificates(), filePath,
aheinecke@466:                 currentDateTimeUtc, mPk)) {
aheinecke@466:         showErrorMessage(tr("Failed to write list to: %1").arg(filePath));
aheinecke@465:     }
aheinecke@465: 
aheinecke@466:     QFile outputFile(filePath);
aheinecke@465: 
aheinecke@465:     /* Archive the list */
aheinecke@465:     QDir archiveDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
aheinecke@465:     if (!archiveDir.mkpath(archiveDir.path())) {
aheinecke@465:         showErrorMessage(tr("Failed to create archive location."));
aheinecke@465:         return;
aheinecke@465:     }
aheinecke@465: 
aheinecke@465:     if (!outputFile.copy(archiveDir.filePath(fileName))) {
aheinecke@465:         showErrorMessage(tr("Failed Archive a copy."));
aheinecke@465:         return;
aheinecke@465:     }
aheinecke@465: 
aheinecke@515:     QString curCerts = archiveDir.filePath("current_certificates.txt");
aheinecke@515: 
aheinecke@515:     if (QFile::exists(curCerts)) {
aheinecke@515:         if (!QFile::remove(curCerts)) {
aheinecke@515:             showErrorMessage(tr("Failed to update current_certificates.txt"));
aheinecke@515:             return;
aheinecke@515:         }
aheinecke@515:     }
aheinecke@515: 
aheinecke@515:     if (!outputFile.copy(curCerts)) {
aheinecke@465:         showErrorMessage(tr("Failed to write current_certificates file."));
aheinecke@465:         return;
aheinecke@465:     }
aheinecke@465: 
andre@679: 
andre@679:     QString keyFingerprint;
andre@679: 
andre@679:     {
andre@679:         /* Calculate sha256 sum of the der key */
andre@679:         unsigned char output_buf[16000]; /* Buf size taken from examples */
andre@679:         int ret;
andre@679:         ret = pk_write_key_der (mPk, output_buf, 16000);
andre@679:         if (ret <= 0) {
andre@679:             showErrorMessage(tr("Failed to calculate key hash."));
andre@679:             return;
andre@679:         }
andre@679:         QByteArray derKey((const char*)output_buf, ret);
andre@679:         QByteArray fp = sha256sum(derKey);
andre@679: 
andre@679:         for (int i=0; i < fp.size(); i++) {
andre@679:             keyFingerprint += QString("%1").arg(
andre@679:                     (unsigned char)(fp[i]), 0, 16).rightJustified(2, '0');
andre@679:             if (i != fp.size() - 1) {
andre@679:                 keyFingerprint += ":";
andre@679:             }
andre@679:         }
andre@679:         keyFingerprint = keyFingerprint.toUpper();
andre@679:     }
andre@679: 
andre@679:     mAdminWindow->logChanges(curCerts, keyFingerprint);
andre@679: 
aheinecke@465:     QMessageBox::information(this, "", tr("Saved certificate list:\n%1").arg(fileName));
aheinecke@465:     close();
rrenkert@367: }