Mercurial > trustbridge
view ui/createinstallerdialog.cpp @ 1102:3d03aaeca6d4
(issue111) Use active property to handle manually changed certificates
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Mon, 15 Sep 2014 13:56:43 +0200 |
parents | 01128d63226d |
children | a162f4cbba75 |
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 "createinstallerdialog.h" #include "sslhelp.h" #include <QDebug> #include <QTextEdit> #include <QDir> #include <QPushButton> #include <QGroupBox> #include <QHBoxLayout> #include <QVBoxLayout> #include <QLabel> #include <QFileDialog> #include <QSaveFile> #include <QSettings> #include <QStyle> #include <QApplication> #include <QMessageBox> #include <QTemporaryDir> #include <polarssl/pk.h> /* Static information used in codesigning */ #ifndef SIGN_HASH #define SIGN_HASH "sha256" #endif #ifndef SIGN_URL #define SIGN_URL "http://wald.intevation.org/projects/trustbridge/" #endif #ifndef SIGN_PUBLISHER #define SIGN_PUBLISHER "TrustBridge Test with ümlaut" #endif CreateInstallerDialog::CreateInstallerDialog(QMainWindow *parent) : QDialog(parent), mProgress(this), mInstallerPath(), mCurrentWorkingDir(NULL) { QSettings settings; setWindowTitle(tr("Create binary installer")); setupGUI(); resize(500, 250); mCertFile->setText(settings.value("CodeSignCert", QString()).toString()); mBinaryFolder->setText(settings.value("LastBinaryFolder", QString()).toString()); mSaveFile->setText(settings.value("LastBinOutputFolder", QString()).toString()); connect(&mNSISProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus))); connect(&mNSISProc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); } void CreateInstallerDialog::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("Create binary installer") + "</h3>"); QLabel *description = new QLabel( tr("Create and sign a TrustBridge binary installer.")); 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(4, 10); QLabel *archiveLabel = new QLabel(tr("Select binary folder:")); QLabel *certLabel = new QLabel(tr("Select code signing certificate:")); QLabel *saveLabel = new QLabel(tr("Select output folder:")); labelLayout->addWidget(archiveLabel); labelLayout->addWidget(certLabel); labelLayout->addWidget(saveLabel); mBinaryFolder = new QLineEdit(); mCertFile = new QLineEdit(); mSaveFile = new QLineEdit(); fieldLayout->addWidget(mBinaryFolder); fieldLayout->addWidget(mCertFile); fieldLayout->addWidget(mSaveFile); QPushButton *archiveSelect = new QPushButton("..."); connect(archiveSelect, SIGNAL(clicked()), this, SLOT(openFolderSelect())); archiveSelect->setFixedWidth(30); QPushButton *certSelect = new QPushButton("..."); connect(certSelect, SIGNAL(clicked()), this, SLOT(openCertificateSelect())); certSelect->setFixedWidth(30); QPushButton *saveSelect = new QPushButton("..."); connect(saveSelect, SIGNAL(clicked()), this, SLOT(openSaveLocation())); saveSelect->setFixedWidth(30); buttonLayout->addWidget(archiveSelect); buttonLayout->addWidget(certSelect); buttonLayout->addWidget(saveSelect); centerLayout->addLayout(labelLayout); centerLayout->addLayout(fieldLayout); centerLayout->addLayout(buttonLayout); QPushButton *create = new QPushButton(tr("Create installer")); connect(create, SIGNAL(clicked()), this, SLOT(createInstaller())); 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); centerLayout->insertSpacing(3, 10); topLayout->addWidget(bottomSeparator); topLayout->addLayout(bottomLayout); setLayout(topLayout); mProgress.setWindowModality(Qt::WindowModal); mProgress.setCancelButton(0); mProgress.setRange(0,0); mProgress.setMinimumDuration(0); return; } void CreateInstallerDialog::openCertificateSelect() { QSettings settings; QString certFile = QFileDialog::getOpenFileName( this, tr("Select certificate"), mCertFile->text().isEmpty() ? QDir::homePath() : mCertFile->text(), "*.pem *.der *.crt"); settings.setValue("CodeSignCert", certFile); mCertFile->setText(certFile); } void CreateInstallerDialog::openFolderSelect() { QSettings settings; QString archiveFolder = QFileDialog::getExistingDirectory( this, tr("Select binary folder"), mBinaryFolder->text().isEmpty() ? QDir::homePath() : mBinaryFolder->text()); mBinaryFolder->setText(archiveFolder); settings.setValue("LastBinaryFolder", archiveFolder); } void CreateInstallerDialog::openSaveLocation() { QSettings settings; QString saveFile = QFileDialog::getExistingDirectory( this, tr("Select target location"), mSaveFile->text().isEmpty() ? QDir::homePath() : mSaveFile->text()); mSaveFile->setText(saveFile); settings.setValue("LastBinOutputFolder", saveFile); } void CreateInstallerDialog::showErrorMessage(const QString &msg) { QMessageBox::warning(this, tr("Error!"), msg); } void CreateInstallerDialog::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (mCurrentWorkingDir) { delete mCurrentWorkingDir; mCurrentWorkingDir = NULL; } FinishedDialog *fin = new FinishedDialog(0, tr("Created installer in %1.") .arg(mSaveFile->text()), mNSISProc.readAll(), false); qDebug() << "Finished: " << mNSISProc.readAll(); mProgress.setLabelText(tr("Signing installer package...")); if (!signFile(mInstallerPath)) { showErrorMessage(tr("Failed to sign installer package.")); QFile::remove(mInstallerPath); } mProgress.cancel(); fin->show(); close(); } void CreateInstallerDialog::processError(QProcess::ProcessError error) { if (mCurrentWorkingDir) { delete mCurrentWorkingDir; mCurrentWorkingDir = NULL; } qDebug() << "Error: " << mNSISProc.readAll(); mProgress.cancel(); } void CreateInstallerDialog::createInstaller() { mProgress.setLabelText(tr("Creating installer package...")); QDir binDir(mBinaryFolder->text()); QDir outDir(mSaveFile->text()); if (mBinaryFolder->text().isEmpty() || !binDir.exists()) { showErrorMessage(tr("Please select an existing input folder.")); return; } if (mCertFile->text().isEmpty()) { showErrorMessage(tr("Please select a codesigning certificate.")); return; } if (mSaveFile->text().isEmpty() || !outDir.exists()) { showErrorMessage(tr("Please select a output folder.")); return; } QSettings options(binDir.filePath("meta.ini"), QSettings::IniFormat); options.sync(); QStringList keys = options.allKeys(); if (options.status() != QSettings::NoError || keys.size() < 1) { showErrorMessage(tr("Folder %1 does not appear to contain a meta.ini") .arg(binDir.path())); return; } /* Sign the linux installer */ QDir linuxDir(binDir.path() + "/linux"); if (!linuxDir.exists()) { showErrorMessage(tr("Failed to find the directory for linux binaries: %1") .arg(linuxDir.path())); return; } QStringList nameFilter; nameFilter << "*.sh"; QStringList candidates = linuxDir.entryList(nameFilter, QDir::Files | QDir::Readable); if (candidates.isEmpty()) { showErrorMessage(tr("Failed to find a readable *.sh file in: %1") .arg(linuxDir.path())); return; } foreach (const QString& candidate, candidates) { mProgress.setLabelText(tr("Signing Linux package...")); mProgress.cancel(); bool x86arch; if (candidate.endsWith("-i386.sh")) { x86arch = true; } else if (candidate.endsWith("-amd64.sh")) { x86arch = false; } else { qDebug() << "Could not detrmine architecture of " << candidate; qDebug() << "Skipping."; continue; } QString outFileName = options.value("setupname", "TrustBridge-default.exe" ).toString().replace(".exe", x86arch ? "-i386.sh" : "-amd64.sh").arg(QString()); if (!appendTextSignatureToFile(linuxDir.path() + "/" + candidate, outDir.path() + "/" + outFileName)) { showErrorMessage(tr("Failed to sign linux package: %1").arg(candidate)); mProgress.close(); return; } } /* The Windows installer */ mCurrentWorkingDir = codesignBinaries(binDir.path() + "/windows"); if (!mCurrentWorkingDir) { /* Error messages should have been shown by the codesign function */ mProgress.close(); return; } mProgress.setLabelText(tr("Creating NSIS package...")); /* Copy windows directory contents to tmpdir */ QStringList arguments; mNSISProc.setProgram("makensis"); mNSISProc.setProcessChannelMode(QProcess::MergedChannels); mNSISProc.setWorkingDirectory(outDir.path()); #ifdef Q_OS_WIN arguments << QString::fromLatin1("/Dfiles_dir=") + mCurrentWorkingDir->path().replace("/", "\\"); arguments << "/Dpath_sep=\\"; foreach (const QString &key, keys) { QString value = options.value(key, QString()).toString(); if (key == "setupname") { value = value.arg(outDir.path().replace("/", "\\") + "\\"); mInstallerPath = value; } arguments << QString::fromLatin1("/D%1=%2").arg(key, value); } arguments << QString(binDir.path() + "/trustbridge.nsi").replace("/", "\\"); #else arguments << QString::fromLatin1("-Dfiles_dir=") + mCurrentWorkingDir->path(); arguments << "-Dpath_sep=/"; foreach (const QString &key, keys) { QString value = options.value(key, QString()).toString(); if (key == "setupname") { value = value.arg(outDir.path() + "/"); mInstallerPath = value; } arguments << QString::fromLatin1("-D%1=%2").arg(key, value); } arguments << binDir.path() + "/trustbridge.nsi"; #endif QFileInfo nsiFile (binDir.path() + "/trustbridge.nsi"); if (!nsiFile.exists()) { showErrorMessage(tr("Failed to find installer script at: %1 ").arg(nsiFile.filePath())); } qDebug() << "Starting makensis with arguments: " << arguments; mNSISProc.setArguments(arguments); mNSISProc.start(); mProgress.show(); if (!mNSISProc.waitForStarted() || mNSISProc.state() == QProcess::NotRunning) { showErrorMessage(tr("Failed to start makensis.\n" "Please ensure that makensis is installed and in your PATH variable.")); } } bool CreateInstallerDialog::signFile(QString filePath) { QProcess signProc; signProc.setProcessChannelMode(QProcess::MergedChannels); signProc.setProgram("osslsigncode"); QStringList arguments; QSettings mySettings; QString publisher = mySettings.value("sign_publisher", SIGN_PUBLISHER).toString(); QString url = mySettings.value("sign_url", SIGN_URL).toString(); QString hash = mySettings.value("sign_hash", SIGN_HASH).toString(); arguments << "sign" << "-certs" << mCertFile->text() << "-key" << mCertFile->text() << "-n" << publisher << "-i" << url << "-h" << hash << "-in" << filePath << "-out" << filePath + ".signed"; qDebug() << "Starting osslsigncode with arguments: " << arguments; signProc.setArguments(arguments); signProc.start(); if (!signProc.waitForFinished(30000)) { qDebug() << "Signing takes longer then 30 seconds. Aborting."; return false; } if (signProc.exitStatus() != QProcess::NormalExit || signProc.exitCode() != 0) { qDebug() << "Error process returned: " << signProc.exitCode(); qDebug() << "Output: " << signProc.readAllStandardOutput(); return false; } if (!QFile::remove(filePath)) { qDebug() << "Failed to remove file."; return false; } if (!QFile::copy(filePath + ".signed", filePath)) { qDebug() << "Failed to copy signed file in place."; return false; } if (!QFile::remove(filePath + ".signed")) { qDebug() << "Failed to remove signed."; return false; } return true; } bool copyPath(QString src, QString dst) { QDir dir(src); if (! dir.exists()) { qDebug() << "Source directory does not exist."; return false; } foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { QString dst_path = dst + QDir::separator() + d; dir.mkpath(dst_path); if (!copyPath(src+ QDir::separator() + d, dst_path)) { qDebug() << "Failed to copy subdirectory; " << d; return false; } } foreach (QString f, dir.entryList(QDir::Files)) { if (!QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f)) { qDebug() << "Failed to copy: " << f; return false; } } return true; } QTemporaryDir *CreateInstallerDialog::codesignBinaries(const QDir& sourceDir) { QTemporaryDir* target = new QTemporaryDir(); /* Copy all files from the source dir to a temporary location */ mProgress.setLabelText(tr("Signing binaries...")); mProgress.show(); /* Copy the whole directory. */ if (!copyPath(sourceDir.path(), target->path())) { qDebug() << "Copy failed."; showErrorMessage(tr("Failed to copy binaries to temporary location.")); mProgress.cancel(); return NULL; } /* Sign the top level .exe files */ foreach (const QFileInfo& entry, sourceDir.entryInfoList()) { QString targetPath = target->path() + QString::fromLatin1("/") + entry.fileName(); if (entry.fileName() == "." || entry.fileName() == "..") { continue; } if (entry.suffix() == "exe") { if (!signFile(targetPath)) { showErrorMessage(tr("Failed to sign binaries with osslsigncode.\n" "Please check that %1 is a valid code signing certificate and that" "osslsigncode can be found in the PATH.").arg(mCertFile->text())); mProgress.cancel(); return NULL; } } } mProgress.cancel(); return target; } bool CreateInstallerDialog::appendTextSignatureToFile(const QString& input, const QString& output) { QFile inFile(input); pk_context pk; pk_init(&pk); int ret = pk_parse_keyfile(&pk, mCertFile->text().toLocal8Bit().constData(), ""); if (ret != 0) { showErrorMessage(tr("Failed to load certificate: %1") .arg(getPolarSSLErrorMsg(ret))); pk_free(&pk); return false; } /* Check that it is a 3072 bit RSA key as specified */ if (!pk.pk_info || pk_get_size(&pk) != 3072 || pk.pk_info->type != POLARSSL_PK_RSA) { qDebug() << pk.pk_info->type << "type"; qDebug() << POLARSSL_PK_RSA << "rsa"; qDebug() << "size " << pk_get_size(&pk); showErrorMessage(tr("Only 3072 bit RSA keys are supported by the current format.")); pk_free(&pk); return false; } if (!inFile.open(QIODevice::ReadOnly)) { showErrorMessage(tr("Failed to open input file: %1").arg(inFile.fileName())); pk_free(&pk); return false; } const QByteArray inputContent = inFile.readAll(); // Memory is cheap :) inFile.close(); if (inputContent.isEmpty()) { showErrorMessage(tr("Failed to read input file: %1").arg(inFile.fileName())); pk_free(&pk); return false; } const QByteArray signature = rsaSignSHA256Hash(sha256sum(inputContent), &pk); pk_free(&pk); if (signature.size() != 3072 / 8) { qDebug() << "Signature creation returned signature of invalid size."; return false; } QSaveFile outFile(output); outFile.open(QIODevice::WriteOnly); outFile.write(inputContent); outFile.write("\r\nS:"); outFile.write(signature.toBase64()); outFile.write("\n"); return outFile.commit(); } FinishedDialog::FinishedDialog(QDialog *parent, QString msg, QString details, bool isErr): QDialog(parent) { QVBoxLayout *topLayout = new QVBoxLayout; QHBoxLayout *buttonLayout = new QHBoxLayout; QLabel *msgLabel = new QLabel; QTextEdit *detailsWindow = new QTextEdit; detailsWindow->insertPlainText(details); detailsWindow->setReadOnly(true); detailsWindow->hide(); if (!isErr) { setWindowTitle(tr("Successfully created installation package")); msgLabel->setPixmap(QApplication::style()->standardIcon( QStyle::SP_MessageBoxInformation).pixmap(16, 16)); } else { setWindowTitle(tr("Error!")); msgLabel->setPixmap(QApplication::style()->standardIcon( QStyle::SP_MessageBoxCritical).pixmap(16, 16)); } msgLabel->setText(msg); topLayout->addWidget(msgLabel); topLayout->addWidget(detailsWindow); QPushButton *detailsBtn = new QPushButton(tr("Details")); connect(detailsBtn, SIGNAL(clicked()), detailsWindow, SLOT(show())); buttonLayout->addWidget(detailsBtn); QPushButton *okBtn = new QPushButton(tr("OK")); okBtn->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogOkButton)); connect(okBtn, SIGNAL(clicked()), this, SLOT(close())); buttonLayout->insertStretch(0, 100); buttonLayout->addWidget(okBtn); topLayout->addLayout(buttonLayout); setLayout(topLayout); }