view ui/createcertlistdialog.cpp @ 1390:f3e2df6b49ba

(issue181) Fix hardcoded values for RSA codesigning key size.
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 19 Jan 2015 15:42:20 +0100
parents d009da9cfa10
children
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "createcertlistdialog.h"
#include "sslhelp.h"
#include "administratorwindow.h"

#include "pubkey.h"

#include <QDebug>
#include <QMessageBox>
#include <QDir>
#include <QPushButton>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <QFileDialog>
#include <QStandardPaths>

#include <polarssl/pk.h>

CreateCertListDialog::CreateCertListDialog(AdministratorWindow *parent) :
    QDialog(parent),
    mAdminWindow(parent),
    mPk(NULL)
{
    setWindowTitle(tr("Save certificate list"));
    setupGUI();
    resize(500, 200);
    mKeyFile->setText(mAdminWindow->settings()->value("LastKey", QString()).toString());
    mSaveDir->setText(mAdminWindow->settings()->value("LastOutputDir", QString()).toString());
    if (!mKeyFile->text().isEmpty()) {
        loadKeyFile(mKeyFile->text());
    }
}

void CreateCertListDialog::setupGUI()
{
    /* Top level layout / widgets */
    QVBoxLayout *topLayout = new QVBoxLayout;
    QVBoxLayout *headerLayout = new QVBoxLayout;
    QHBoxLayout *headerSubLayout = new QHBoxLayout;
    QHBoxLayout *centerLayout = new QHBoxLayout;
    QHBoxLayout *bottomLayout = new QHBoxLayout;
    QVBoxLayout *labelLayout = new QVBoxLayout;
    QVBoxLayout *fieldLayout = new QVBoxLayout;
    QVBoxLayout *buttonLayout = new QVBoxLayout;

    QLabel *header = new QLabel("<h3>" + tr("Save certificate list") + "</h3>");
    QLabel *description = new QLabel(
        tr("Save all managed root certificates in a new, signed certificate list."));
    headerSubLayout->insertSpacing(0, 40);
    headerSubLayout->addWidget(description);
    QFrame *headerSeparator = new QFrame();
    headerSeparator->setFrameShape(QFrame::HLine);
    headerSeparator->setFrameShadow(QFrame::Sunken);
    headerLayout->addWidget(header);
    headerLayout->addLayout(headerSubLayout);
    headerLayout->addWidget(headerSeparator);
    headerLayout->insertSpacing(3, 10);

    QLabel *certLabel = new QLabel(tr("Select signing key:"));
    QLabel *saveLabel = new QLabel(tr("Select output folder:"));
    labelLayout->addWidget(certLabel);
    labelLayout->addWidget(saveLabel);

    mKeyFile = new QLineEdit();
    mSaveDir = new QLineEdit();
    fieldLayout->addWidget(mKeyFile);
    fieldLayout->addWidget(mSaveDir);

    QPushButton *certSelect = new QPushButton("...");
    certSelect->setFixedWidth(30);
    connect(certSelect, SIGNAL(clicked()), this, SLOT(openCertificateSelect()));
    QPushButton *saveSelect = new QPushButton("...");
    connect(saveSelect, SIGNAL(clicked()), this, SLOT(openSaveLocation()));
    saveSelect->setFixedWidth(30);
    buttonLayout->addWidget(certSelect);
    buttonLayout->addWidget(saveSelect);

    QString footerText = tr("In addition, each certificate list will be saved "
        "automatically in the archive directory:\n");
    footerText.append(QStandardPaths::writableLocation(
        QStandardPaths::DataLocation));
    QLabel *footer = new QLabel(footerText);

    centerLayout->addLayout(labelLayout);
    centerLayout->addLayout(fieldLayout);
    centerLayout->addLayout(buttonLayout);

    QPushButton *create = new QPushButton(" " + tr("Save list"));
    connect(create, SIGNAL(clicked()), this, SLOT(createList()));
    QPushButton *cancel = new QPushButton(" " + tr("Cancel"));
    connect(cancel, SIGNAL(clicked()), this, SLOT(close()));
    bottomLayout->insertStretch(0, 10);
    bottomLayout->addWidget(create);
    bottomLayout->addWidget(cancel);

    QFrame *bottomSeparator = new QFrame();
    bottomSeparator->setFrameShape(QFrame::HLine);
    bottomSeparator->setFrameShadow(QFrame::Sunken);

    topLayout->addLayout(headerLayout);
    topLayout->addLayout(centerLayout);
    topLayout->insertStretch(2, 10);
    topLayout->addWidget(footer);
    topLayout->insertSpacing(4, 10);
    topLayout->addWidget(bottomSeparator);
    topLayout->addLayout(bottomLayout);

    setLayout(topLayout);

    return;
}

void CreateCertListDialog::showErrorMessage(const QString &msg)
{
    QMessageBox::warning(this, tr("Error!"), msg);
}

void CreateCertListDialog::loadKeyFile(const QString& fileName)
{
    if (mPk != NULL) {
        pk_free(mPk);
        delete mPk;
        mPk = NULL;
    }

    mPk = new pk_context;
    pk_init(mPk);
    int ret = pk_parse_keyfile(mPk, mKeyFile->text().toLocal8Bit().constData(), "");

    if (ret != 0) {
        showErrorMessage(tr("Failed to load certificate: %1")
                .arg(getPolarSSLErrorMsg(ret)));
        pk_free(mPk);
        delete mPk;
        mPk = NULL;
        return;
    }

    /* Check that it is a RSA key of the specified size */
    if (!mPk->pk_info || pk_get_size(mPk) != TRUSTBRIDGE_RSA_KEY_SIZE ||
            mPk->pk_info->type != POLARSSL_PK_RSA) {
        showErrorMessage(tr("Only %1 bit RSA keys are supported by the current format.").arg(
                    TRUSTBRIDGE_RSA_KEY_SIZE));
        pk_free(mPk);
        delete mPk;
        mPk = NULL;
        return;
    }
}

void CreateCertListDialog::openCertificateSelect()
{
    QString keyFile = QFileDialog::getOpenFileName(
        this, tr("Select certificate"), mKeyFile->text().isEmpty() ?
        QDir::homePath() : mKeyFile->text(), "*.pem");
    mKeyFile->setText(keyFile);

    mAdminWindow->settings()->setValue("LastKey", keyFile);
    loadKeyFile(keyFile);

    return;
}

void CreateCertListDialog::openSaveLocation()
{
    QString saveDir = QFileDialog::getExistingDirectory(
        this, tr("Select target location"),
        mSaveDir->text().isEmpty() ? QDir::homePath() : mSaveDir->text());
    mAdminWindow->settings()->setValue("LastOutputDir", saveDir);
    mSaveDir->setText(saveDir);
}

CreateCertListDialog::~CreateCertListDialog()
{
    if (mPk) {
        pk_free(mPk);
        delete mPk;
        mPk = NULL;
    }
}

bool CreateCertListDialog::writeList(const QList<Certificate>& certs,
                                     const QString& filePath,
                                     const QDateTime& listDate,
                                     pk_context *pk)
{
    /* Build up the list data */
    QByteArray listData("F:1\r\nD:");
    listData.append(listDate.toString(Qt::ISODate) + "\r\n");

    foreach (const Certificate& cert, certs) {
        listData.append(cert.base64Line() + "\r\n");
    }

    QByteArray signature = rsaSignSHA256Hash(sha256sum(listData), pk);
    if (signature.size() != TRUSTBRIDGE_RSA_KEY_SIZE / 8) {
        qDebug() << "Signature creation returned signature of invalid size.";
        return false;
    }
    listData.prepend("\r\n");
    listData.prepend(signature.toBase64());
    listData.prepend("S:");

    QFile outputFile(filePath);

    if (!outputFile.open(QIODevice::WriteOnly)) {
        qDebug() << "Failed to open output file: " << filePath;
        return false;
    }

    if (outputFile.write(listData) != listData.size()) {
        qDebug() << "Failed to write list: " << filePath;
        outputFile.close();
        return false;
    }
    outputFile.close();
    return true;
}

void CreateCertListDialog::createList()
{
    if (!mPk) {
        showErrorMessage(tr("Please select a valid rsa key."));
    }
    if (mSaveDir->text().isEmpty()) {
        showErrorMessage(tr("Please select an output location first."));
    }

    QDateTime currentDateTimeUtc = QDateTime::currentDateTimeUtc();

    QString fileName = QString::fromLatin1("certificates-")
            .append(currentDateTimeUtc.toString(("yyyyMMddHHmmss")))
            .append(".txt");

    QString filePath = mSaveDir->text().append("/").append(fileName);

    if (!writeList(mAdminWindow->certificates(), filePath,
                currentDateTimeUtc, mPk)) {
        showErrorMessage(tr("Failed to write list to: %1").arg(filePath));
    }

    QFile outputFile(filePath);

    /* Archive the list */
    QDir archiveDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
    if (!archiveDir.mkpath(archiveDir.path())) {
        showErrorMessage(tr("Failed to create archive location."));
        return;
    }

    if (!outputFile.copy(archiveDir.filePath(fileName))) {
        showErrorMessage(tr("Failed Archive a copy."));
        return;
    }

    QString curCerts = archiveDir.filePath("current_certificates.txt");

    if (QFile::exists(curCerts)) {
        if (!QFile::remove(curCerts)) {
            showErrorMessage(tr("Failed to update current_certificates.txt"));
            return;
        }
    }

    if (!outputFile.copy(curCerts)) {
        showErrorMessage(tr("Failed to write current_certificates file."));
        return;
    }


    QString keyFingerprint;

    {
        /* Calculate sha256 sum of the der key */
        unsigned char output_buf[16000]; /* Buf size taken from examples */
        int ret;
        ret = pk_write_key_der (mPk, output_buf, 16000);
        if (ret <= 0) {
            showErrorMessage(tr("Failed to calculate key hash."));
            return;
        }
        QByteArray derKey((const char*)output_buf, ret);
        QByteArray fp = sha256sum(derKey);

        for (int i=0; i < fp.size(); i++) {
            keyFingerprint += QString("%1").arg(
                    (unsigned char)(fp[i]), 0, 16).rightJustified(2, '0');
            if (i != fp.size() - 1) {
                keyFingerprint += ":";
            }
        }
        keyFingerprint = keyFingerprint.toUpper();
    }

    mAdminWindow->logChanges(curCerts, keyFingerprint);

    QMessageBox::information(this, "", tr("Saved certificate list:\n%1").arg(fileName));
    emit creationSuccessful();

    close();
}

http://wald.intevation.org/projects/trustbridge/