Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code-sign client via mac-crafter so it may pass notarisation #7103

Merged
merged 9 commits into from
Sep 12, 2024
63 changes: 58 additions & 5 deletions admin/osx/mac-crafter/Sources/Utils/Codesign.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func isAppExtension(_ path: String) -> Bool {
func codesign(
identity: String,
path: String,
options: String = "--timestamp --force --preserve-metadata=entitlements --verbose=4 --options runtime"
options: String = "--timestamp --force --preserve-metadata=entitlements --verbose=4 --options runtime --deep"
) throws {
print("Code-signing \(path)...")
let command = "codesign -s \"\(identity)\" \(options) \(path)"
Expand All @@ -56,17 +56,70 @@ func recursivelyCodesign(path: String, identity: String) throws {
}
}

func saveCodesignEntitlements(target: String, path: String) throws {
let command = "codesign -d --entitlements \(path) --xml \(target)"
guard shell(command) == 0 else {
throw CodeSigningError.failedToCodeSign("Failed to save entitlements for \(target).")
}
}

func codesignClientAppBundle(
at clientAppDir: String, withCodeSignIdentity codeSignIdentity: String
) throws {
print("Code-signing Nextcloud Desktop Client libraries, frameworks and plugins...")

let clientContentsDir = "\(clientAppDir)/Contents"
let frameworksPath = "\(clientContentsDir)/Frameworks"
let pluginsPath = "\(clientContentsDir)/PlugIns"

try recursivelyCodesign(path: "\(clientContentsDir)/Frameworks", identity: codeSignIdentity)
try recursivelyCodesign(path: "\(clientContentsDir)/PlugIns", identity: codeSignIdentity)
try recursivelyCodesign(path: frameworksPath, identity: codeSignIdentity)
try recursivelyCodesign(path: pluginsPath, identity: codeSignIdentity)
try recursivelyCodesign(path: "\(clientContentsDir)/Resources", identity: codeSignIdentity)

print("Code-signing Nextcloud Desktop Client app bundle...")
try codesign(identity: codeSignIdentity, path: clientAppDir)
print("Code-signing QtWebEngineProcess...")
let qtWebEngineProcessPath =
"\(frameworksPath)/QtWebEngineCore.framework/Versions/A/Helpers/QtWebEngineProcess.app"
try codesign(identity: codeSignIdentity, path: qtWebEngineProcessPath)

print("Code-signing QtWebEngine...")
try codesign(identity: codeSignIdentity, path: "\(frameworksPath)/QtWebEngineCore.framework")

// Time to fix notarisation issues.
// Multiple components of the app will now have the get-task-allow entitlements.
// We need to strip these out manually.

print("Code-signing Sparkle autoupdater app (without entitlements)...")
let sparkleFrameworkPath = "\(frameworksPath)/Sparkle.framework"
try codesign(identity: codeSignIdentity,
path: "\(sparkleFrameworkPath)/Resources/Autoupdate.app/Contents/MacOS/*",
options: "--timestamp --force --verbose=4 --options runtime --deep")

print("Re-codesigning Sparkle library...")
try codesign(identity: codeSignIdentity, path: "\(sparkleFrameworkPath)/Sparkle")

print("Code-signing app extensions (removing get-task-allow entitlements)...")
let fm = FileManager.default
let appExtensionPaths =
try fm.contentsOfDirectory(atPath: pluginsPath).filter(isAppExtension)
for appExtension in appExtensionPaths {
let appExtensionPath = "\(pluginsPath)/\(appExtension)"
let tmpEntitlementXmlPath =
fm.temporaryDirectory.appendingPathComponent(UUID().uuidString).path.appending(".xml")
try saveCodesignEntitlements(target: appExtensionPath, path: tmpEntitlementXmlPath)
// Strip the get-task-allow entitlement from the XML entitlements file
let xmlEntitlements = try String(contentsOfFile: tmpEntitlementXmlPath)
let entitlementKeyValuePair = "<key>com.apple.security.get-task-allow</key><true/>"
let strippedEntitlements =
xmlEntitlements.replacingOccurrences(of: entitlementKeyValuePair, with: "")
try strippedEntitlements.write(toFile: tmpEntitlementXmlPath,
atomically: true,
encoding: .utf8)
try codesign(identity: codeSignIdentity,
path: appExtensionPath,
options: "--timestamp --force --verbose=4 --options runtime --deep --entitlements \(tmpEntitlementXmlPath)")
}

// Now we do the final codesign bit
print("Code-signing Nextcloud Desktop Client binaries...")
try codesign(identity: codeSignIdentity, path: "\(clientContentsDir)/MacOS/*")
}
44 changes: 33 additions & 11 deletions admin/osx/mac-crafter/Sources/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
import ArgumentParser
import Foundation

struct MacCrafter: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "A tool to easily build a fully-functional Nextcloud Desktop Client for macOS."
)
struct Build: ParsableCommand {
static let configuration = CommandConfiguration(abstract: "Client building script")

enum MacCrafterError: Error {
case failedEnumeration(String)
Expand Down Expand Up @@ -194,25 +192,49 @@ struct MacCrafter: ParsableCommand {
throw MacCrafterError.craftError("Error crafting Nextcloud Desktop Client.")
}

guard let codeSignIdentity else {
print("Crafted Nextcloud Desktop Client. Not codesigned.")
return
}

print("Code-signing Nextcloud Desktop Client libraries and frameworks...")
let clientAppDir = "\(clientBuildDir)/image-\(buildType)-master/\(appName).app"
try codesignClientAppBundle(at: clientAppDir, withCodeSignIdentity: codeSignIdentity)
if let codeSignIdentity {
print("Code-signing Nextcloud Desktop Client libraries and frameworks...")
try codesignClientAppBundle(at: clientAppDir, withCodeSignIdentity: codeSignIdentity)
}

print("Placing Nextcloud Desktop Client in \(productPath)...")
if !fm.fileExists(atPath: productPath) {
try fm.createDirectory(
atPath: productPath, withIntermediateDirectories: true, attributes: nil
)
}
if fm.fileExists(atPath: "\(productPath)/\(appName).app") {
try fm.removeItem(atPath: "\(productPath)/\(appName).app")
}
try fm.copyItem(atPath: clientAppDir, toPath: "\(productPath)/\(appName).app")

print("Done!")
}
}

struct Codesign: ParsableCommand {
static let configuration = CommandConfiguration(abstract: "Codesigning script for the client.")

@Argument(help: "Path to the Nextcloud Desktop Client app bundle.")
var appBundlePath = "\(FileManager.default.currentDirectoryPath)/product/Nextcloud.app"

@Option(name: [.short, .long], help: "Code signing identity for desktop client and libs.")
var codeSignIdentity: String

mutating func run() throws {
try codesignClientAppBundle(at: appBundlePath, withCodeSignIdentity: codeSignIdentity)
}
}

struct MacCrafter: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "A tool to easily build a fully-functional Nextcloud Desktop Client for macOS.",
subcommands: [Build.self, Codesign.self],
defaultSubcommand: Build.self
)


}

MacCrafter.main()
Loading