changeset 571:6c4fff146999

Implement codesigning in the administrator tool
author Andre Heinecke <aheinecke@intevation.de>
date Fri, 23 May 2014 16:17:18 +0000
parents c9d296f04995
children 2459a7122532
files ui/createinstallerdialog.cpp ui/createinstallerdialog.h ui/tests/data/NOTES
diffstat 3 files changed, 163 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- 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 <QStyle>
 #include <QApplication>
 #include <QMessageBox>
+#include <QTemporaryDir>
+
+/* 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)
--- 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 <QLineEdit>
 #include <QProcess>
 #include <QProgressDialog>
+#include <QDir>
 /**
  * @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);
--- 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

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