summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Johan Sørvig <morten.sorvig@digia.com>2014-09-24 15:08:00 +0200
committerMorten Johan Sørvig <morten.sorvig@digia.com>2014-10-01 10:50:30 +0200
commit5e6e9458a4feb3411fce3a51abdaa9d62271c6ec (patch)
tree5ea3bfb2fdc7c6d2c1c4d4547bd9a162636bb830
parenta58505987ca43850cec8c8f33665feb33c1187fb (diff)
Add -codesign option.
Use the "codesign" tool with the provided signing identity to sign all code binaries in the bundle. Application code binaries include the app executable(s), plugin and framework/dylib dependencies. Finally, run "codesign --verify" to verify that the app bundle is correctly signed. Change-Id: Idfff030a2b218e1dc1ad1bc279a32a330665b347 Reviewed-by: Morten Johan Sørvig <morten.sorvig@digia.com>
-rw-r--r--src/macdeployqt/macdeployqt/main.cpp15
-rw-r--r--src/macdeployqt/shared/shared.cpp133
-rw-r--r--src/macdeployqt/shared/shared.h2
3 files changed, 149 insertions, 1 deletions
diff --git a/src/macdeployqt/macdeployqt/main.cpp b/src/macdeployqt/macdeployqt/main.cpp
index b4881789e..313a57488 100644
--- a/src/macdeployqt/macdeployqt/main.cpp
+++ b/src/macdeployqt/macdeployqt/main.cpp
@@ -59,6 +59,7 @@ int main(int argc, char **argv)
qDebug() << " -executable=<path> : Let the given executable use the deployed frameworks too";
qDebug() << " -qmldir=<path> : Deploy imports used by .qml files in the given path";
qDebug() << " -always-overwrite : Copy files enven if the target file exists";
+ qDebug() << " -codesign=<ident> : Run codesing with the given identity on all executables";
qDebug() << "";
qDebug() << "macdeployqt takes an application bundle as input and makes it";
qDebug() << "self-contained by copying in the Qt frameworks and plugins that";
@@ -89,6 +90,8 @@ int main(int argc, char **argv)
extern bool alwaysOwerwriteEnabled;
QStringList additionalExecutables;
QStringList qmlDirs;
+ extern bool runCodesign;
+ extern QString codesignIdentiy;
for (int i = 2; i < argc; ++i) {
QByteArray argument = QByteArray(argv[i]);
@@ -131,6 +134,15 @@ int main(int argc, char **argv)
} else if (argument == QByteArray("-always-overwrite")) {
LogDebug() << "Argument found:" << argument;
alwaysOwerwriteEnabled = true;
+ } else if (argument.startsWith(QByteArray("-codesign"))) {
+ LogDebug() << "Argument found:" << argument;
+ int index = argument.indexOf("=");
+ if (index < 0 || index >= argument.size()) {
+ LogError() << "Missing code signing identity";
+ } else {
+ runCodesign = true;
+ codesignIdentiy = argument.mid(index+1);
+ }
} else if (argument.startsWith("-")) {
LogError() << "Unknown argument" << argument << "\n";
return 0;
@@ -157,6 +169,9 @@ int main(int argc, char **argv)
if (!qmlDirs.isEmpty())
deployQmlImports(appBundlePath, qmlDirs);
+ if (runCodesign)
+ codesign(codesignIdentiy, appBundlePath);
+
if (dmg) {
LogNormal();
createDiskImage(appBundlePath);
diff --git a/src/macdeployqt/shared/shared.cpp b/src/macdeployqt/shared/shared.cpp
index 903d5b2c5..faf46360f 100644
--- a/src/macdeployqt/shared/shared.cpp
+++ b/src/macdeployqt/shared/shared.cpp
@@ -46,6 +46,7 @@
#include <QDir>
#include <QRegExp>
#include <QSet>
+#include <QStack>
#include <QDirIterator>
#include <QLibraryInfo>
#include <QJsonDocument>
@@ -56,6 +57,8 @@
bool runStripEnabled = true;
bool alwaysOwerwriteEnabled = false;
+bool runCodesign = false;
+QString codesignIdentiy;
int logLevel = 1;
using std::cout;
@@ -276,6 +279,22 @@ QStringList findAppLibraries(const QString &appBundlePath)
return result;
}
+QStringList findAppBundleFiles(const QString &appBundlePath)
+{
+ QStringList result;
+
+ QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*"),
+ QDir::Files, QDirIterator::Subdirectories);
+
+ while (iter.hasNext()) {
+ iter.next();
+ if (iter.fileInfo().isSymLink())
+ continue;
+ result << iter.fileInfo().filePath();
+ }
+
+ return result;
+}
QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, bool useDebugLibs)
{
@@ -289,7 +308,7 @@ QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, bool useDebu
}
}
return libraries;
-}
+};
QList<FrameworkInfo> getQtFrameworks(const QString &path, bool useDebugLibs)
{
@@ -327,6 +346,35 @@ QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, bool useD
return result;
}
+QStringList getBinaryDependencies(const QString executablePath, const QString &path)
+{
+ QStringList binaries;
+
+ QProcess otool;
+ otool.start("otool", QStringList() << "-L" << path);
+ otool.waitForFinished();
+
+ if (otool.exitCode() != 0) {
+ LogError() << otool.readAllStandardError();
+ }
+
+ QString output = otool.readAllStandardOutput();
+ QStringList outputLines = output.split("\n");
+ outputLines.removeFirst(); // remove line containing the binary path
+
+ // return bundle-local dependencies. (those starting with @executable_path)
+ foreach (const QString &line, outputLines) {
+ QString trimmedLine = line.mid(0, line.indexOf("(")).trimmed(); // remove "(compatibility version ...)" and whitespace
+ if (trimmedLine.startsWith("@executable_path/")) {
+ QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length()));
+ if (binary != path)
+ binaries.append(binary);
+ }
+ }
+
+ return binaries;
+}
+
// copies everything _inside_ sourcePath to destinationPath
void recursiveCopy(const QString &sourcePath, const QString &destinationPath)
{
@@ -561,6 +609,8 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
// Install_name_tool it a new id.
changeIdentification(framework.deployedInstallName, deployedBinaryPath);
+
+
// Check for framework dependencies
QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, useDebugLibs);
@@ -679,8 +729,10 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
if (copyFilePrintStatus(sourcePath, destinationPath)) {
runStrip(destinationPath);
+
QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, useDebugLibs);
deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
+
}
}
}
@@ -836,6 +888,85 @@ void changeQtFrameworks(const QString appPath, const QString &qtPath, bool useDe
}
}
+void codesignFile(const QString &identity, const QString &filePath)
+{
+ if (!runCodesign)
+ return;
+
+ LogNormal() << "codesign" << filePath;
+
+ QProcess codesign;
+ codesign.start("codesign", QStringList() << "--preserve-metadata=identifier,entitlements,resource-rules"
+ << "--force" << "-s" << identity << filePath);
+ codesign.waitForFinished(-1);
+
+ QByteArray err = codesign.readAllStandardError();
+ if (codesign.exitCode() > 0) {
+ LogError() << "Codesign signing error:";
+ LogError() << err;
+ } else if (!err.isEmpty()) {
+ LogDebug() << err;
+ }
+}
+
+void codesign(const QString &identity, const QString &appBundlePath)
+{
+ // Code sign all binaries in the app bundle. This needs to
+ // be done inside-out, e.g sign framework dependencies
+ // before the main app binary. The codesign tool itself has
+ // a "--deep" option to do this, but usage when signing is
+ // not recommended: "Signing with --deep is for emergency
+ // repairs and temporary adjustments only."
+
+ LogNormal() << "";
+ LogNormal() << "Signing" << appBundlePath << "with identity" << identity;
+
+ QStack<QString> pendingBinaries;
+ QSet<QString> signedBinaries;
+
+ // Create the root code-binary set. This set consists of the application
+ // executable(s) and the plugins.
+ QString rootBinariesPath = appBundlePath + "/Contents/MacOS/";
+ QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() << "*", QDir::Files);
+ foreach (const QString &binary, foundRootBinaries)
+ pendingBinaries.push(rootBinariesPath + binary);
+
+ QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath + "/Contents/PlugIns/");
+ foreach (const QString &binary, foundPluginBinaries)
+ pendingBinaries.push(binary);
+
+
+ // Sign all binares; use otool to find and sign dependencies first.
+ while (!pendingBinaries.isEmpty()) {
+ QString binary = pendingBinaries.pop();
+ if (signedBinaries.contains(binary))
+ continue;
+
+ // Check if there are unsigned dependencies, sign these first
+ QStringList dependencies = getBinaryDependencies(rootBinariesPath, binary).toSet().subtract(signedBinaries).toList();
+ if (!dependencies.isEmpty()) {
+ pendingBinaries.push(binary);
+ foreach (const QString &dependency, dependencies)
+ pendingBinaries.push(dependency);
+ continue;
+ }
+ // All dependencies are signed, now sign this binary
+ codesignFile(identity, binary);
+ signedBinaries.insert(binary);
+ }
+
+ // Verify code signature
+ QProcess codesign;
+ codesign.start("codesign", QStringList() << "--deep" << "-v" << appBundlePath);
+ codesign.waitForFinished(-1);
+ QByteArray err = codesign.readAllStandardError();
+ if (codesign.exitCode() > 0) {
+ LogError() << "codesign verification error:";
+ LogError() << err;
+ } else if (!err.isEmpty()) {
+ LogDebug() << err;
+ }
+}
void createDiskImage(const QString &appBundlePath)
{
diff --git a/src/macdeployqt/shared/shared.h b/src/macdeployqt/shared/shared.h
index 0fe7400fb..257c3eb34 100644
--- a/src/macdeployqt/shared/shared.h
+++ b/src/macdeployqt/shared/shared.h
@@ -111,6 +111,8 @@ void changeIdentification(const QString &id, const QString &binaryPath);
void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath);
void runStrip(const QString &binaryPath);
QString findAppBinary(const QString &appBundlePath);
+void codesignFile(const QString &identity, const QString &filePath);
+void codesign(const QString &identity, const QString &appBundlePath);
void createDiskImage(const QString &appBundlePath);