Mercurial > trustbridge
view ui/createinstallerdialog.cpp @ 831:747a48996c1f
(Issue13) Precompile uninstaller
Create-dist-packge now creates a temporary installer that only
writes the uninstaller. Then it excutes this installer (using wine)
to create the uninstaller. That uninstaller is then packaged
normaly and packaged instead of the written uninstaller.
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Thu, 24 Jul 2014 15:59:00 +0200 |
parents | 60d3f59f0803 |
children | 637948e9e32d |
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 "https://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; } if (candidates.size() > 1) { showErrorMessage(tr("Unexpected additional .sh files in: %1") .arg(linuxDir.path())); return; } mProgress.setLabelText(tr("Signing Linux package...")); mProgress.cancel(); QString outFileName = options.value("setupname", "TrustBridge-default.exe" ).toString().replace(".exe", ".sh").arg(QString()); if (!appendTextSignatureToFile(linuxDir.path() + "/" + candidates.first(), outDir.path() + "/" + outFileName)) { qDebug() << "Failed to sign linux package."; 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); } #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); } #endif arguments << binDir.path() + "/trustbridge.nsi"; 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; } 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(); foreach (const QFileInfo& entry, sourceDir.entryInfoList()) { QString targetPath = target->path() + QString::fromLatin1("/") + entry.fileName(); if (entry.fileName() == "." || entry.fileName() == "..") { continue; } if (!QFile::copy(entry.absoluteFilePath(), targetPath)) { qDebug() << "Failed to copy: " << entry.absoluteFilePath() << " to: " << targetPath; showErrorMessage(tr("Failed to copy binaries to temporary location.")); mProgress.cancel(); return NULL; } 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()); 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); }