# HG changeset patch # User Andre Heinecke # Date 1400861838 0 # Node ID 6c4fff1469994e339dc0a4bb71afd08eeec256a0 # Parent c9d296f04995982d23bf18c830a9212af7a6755f Implement codesigning in the administrator tool diff -r c9d296f04995 -r 6c4fff146999 ui/createinstallerdialog.cpp --- a/ui/createinstallerdialog.cpp Fri May 23 16:16:33 2014 +0000 +++ b/ui/createinstallerdialog.cpp Fri May 23 16:17:18 2014 +0000 @@ -19,10 +19,25 @@ #include #include #include +#include + +/* 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) + mProgress(this), + mInstallerPath(), + mCurrentWorkingDir(NULL) { QSettings settings; setWindowTitle(tr("Create binary installer")); @@ -116,7 +131,6 @@ setLayout(topLayout); mProgress.setWindowModality(Qt::WindowModal); - mProgress.setLabelText(tr("Creating installer package...")); mProgress.setCancelButton(0); mProgress.setRange(0,0); mProgress.setMinimumDuration(0); @@ -162,9 +176,18 @@ 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(); @@ -172,12 +195,17 @@ 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()) { @@ -201,28 +229,39 @@ return; } + QTemporaryDir *signedFilesDir = codesignBinaries(binDir.path() + "/windows"); + + if (!signedFilesDir) { + /* Error messages should have been shown by the codesign function */ + 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=") + binDir.path().replace("/", "\\") + "\\windows"; + arguments << QString::fromLatin1("/Dfiles_dir=") + signedFilesDir->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=") + binDir.path() + "/windows"; + arguments << QString::fromLatin1("-Dfiles_dir=") + signedFilesDir->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); } @@ -242,6 +281,85 @@ } } +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; +} + FinishedDialog::FinishedDialog(QDialog *parent, QString msg, QString details, bool isErr): QDialog(parent) diff -r c9d296f04995 -r 6c4fff146999 ui/createinstallerdialog.h --- a/ui/createinstallerdialog.h Fri May 23 16:16:33 2014 +0000 +++ b/ui/createinstallerdialog.h Fri May 23 16:17:18 2014 +0000 @@ -13,12 +13,14 @@ #include #include #include +#include /** * @file createinstallerdialog.h * @brief The dialog to show settings and create an installer. */ class QListWidget; +class QTemporaryDir; class CreateInstallerDialog : public QDialog { @@ -38,6 +40,8 @@ QProcess mNSISProc; QProgressDialog mProgress; + QString mInstallerPath; + QTemporaryDir *mCurrentWorkingDir; /** @brief show an error message with QMessageBox * @@ -49,8 +53,44 @@ void openCertificateSelect(); void openFolderSelect(); void openSaveLocation(); + + /**@brief entry point for installer creation + * + * check the selected parameters (certificate / folder etc.) and + * create the nsis installer. This also creates the signatures. */ void createInstaller(); + /**@brief Create tempoary dir with signed binaries from sourcedir + * + * Copies all files from the sourceDir to a temporary directory + * and signs all .exe files in that directory. + * + * The caller needs to delete the temporary directory. If an error + * occurs NULL is returned. + * + * @param[in] sourceDir the directory with the binaries to sign + * @returns a pointer to a temporary dir containing the signed binaries + * or NULL. + */ + QTemporaryDir *codesignBinaries(const QDir& sourceDir); + + /**@brief Sign a file with the codesigning certificate from mCertFile + * + * Calls osslsigncode to sign the file pointed to in filePath. + * The signing operation is logged. + * + * Sign information (hash algo / publisher / url) can be set at + * build time or in the settings with the variables: + * + * sign_hash # the hash algorithm to use. Values are the same as in singtool + * sign_publisher # the publisher information + * sign_url # the product url to use in the signature + * + * @param[in] filePath the absolute path to the file. + * @returns true on success, false on error + */ + bool signFile(QString filePath); + /* Slots for the creator process */ void processError(QProcess::ProcessError error); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); diff -r c9d296f04995 -r 6c4fff146999 ui/tests/data/NOTES --- a/ui/tests/data/NOTES Fri May 23 16:16:33 2014 +0000 +++ b/ui/tests/data/NOTES Fri May 23 16:17:18 2014 +0000 @@ -114,5 +114,6 @@ osslsigncode sign -certs codesigning.pem -key codesigning.key \ -n "TrustBridgeTest" -i https://wald.intevation.org/projects/trustbridge/ \ + -h sha256 \ -in ~/ubuntu/src/m13-repo/build-windows/TrustBridge-0.6+21-aee3eb10bbba.exe \ -out TrustBridge-0.6+21-aee3eb10bbba-signed.exe