From 5e6e9458a4feb3411fce3a51abdaa9d62271c6ec Mon Sep 17 00:00:00 2001 From: =?utf8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Wed, 24 Sep 2014 15:08:00 +0200 Subject: [PATCH] Add -codesign option. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 --- src/macdeployqt/macdeployqt/main.cpp | 15 ++++ src/macdeployqt/shared/shared.cpp | 133 ++++++++++++++++++++++++++++++++++- src/macdeployqt/shared/shared.h | 2 + 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/macdeployqt/macdeployqt/main.cpp b/src/macdeployqt/macdeployqt/main.cpp index b488178..313a574 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= : Let the given executable use the deployed frameworks too"; qDebug() << " -qmldir= : Deploy imports used by .qml files in the given path"; qDebug() << " -always-overwrite : Copy files enven if the target file exists"; + qDebug() << " -codesign= : 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 903d5b2..faf4636 100644 --- a/src/macdeployqt/shared/shared.cpp +++ b/src/macdeployqt/shared/shared.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -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 getQtFrameworks(const QStringList &otoolLines, bool useDebugLibs) { @@ -289,7 +308,7 @@ QList getQtFrameworks(const QStringList &otoolLines, bool useDebu } } return libraries; -} +}; QList getQtFrameworks(const QString &path, bool useDebugLibs) { @@ -327,6 +346,35 @@ QList 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 frameworks, // Install_name_tool it a new id. changeIdentification(framework.deployedInstallName, deployedBinaryPath); + + // Check for framework dependencies QList dependencies = getQtFrameworks(deployedBinaryPath, useDebugLibs); @@ -679,8 +729,10 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl if (copyFilePrintStatus(sourcePath, destinationPath)) { runStrip(destinationPath); + QList 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 pendingBinaries; + QSet 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 0fe7400..257c3eb 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); -- 2.7.4