@@ -13,6 +13,8 @@
|
||||
534FF36717D8E90A0020A51A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 534FF36517D8E90A0020A51A /* InfoPlist.strings */; };
|
||||
534FF36A17D8E90A0020A51A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 534FF36917D8E90A0020A51A /* main.m */; };
|
||||
534FF37317D8E9370020A51A /* com.github.Squirrel.TestApplication.TestService.xpc in Copy XPCServices */ = {isa = PBXBuildFile; fileRef = 534FF36017D8E90A0020A51A /* com.github.Squirrel.TestApplication.TestService.xpc */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
53710D7417D8F59700A992DE /* SQRLDeepCodesignSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */; };
|
||||
53710D8417D8F5CB00A992DE /* deep-codesign in Resources */ = {isa = PBXBuildFile; fileRef = 53710D7F17D8F5C300A992DE /* deep-codesign */; };
|
||||
5371815F18A29DC8005ED798 /* TestAppConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 5371815E18A29DC8005ED798 /* TestAppConstants.m */; };
|
||||
5371816018A29DC8005ED798 /* TestAppConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 5371815E18A29DC8005ED798 /* TestAppConstants.m */; };
|
||||
5374DCC6187AD0D8006B7056 /* SQRLAuthorization.h in Headers */ = {isa = PBXBuildFile; fileRef = 5374DCC4187AD0D8006B7056 /* SQRLAuthorization.h */; };
|
||||
@@ -267,8 +269,10 @@
|
||||
534FF36617D8E90A0020A51A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
534FF36817D8E90A0020A51A /* TestService-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TestService-Prefix.pch"; sourceTree = "<group>"; };
|
||||
534FF36917D8E90A0020A51A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLDeepCodesignSpec.m; sourceTree = "<group>"; };
|
||||
53710D7D17D8F5C300A992DE /* bootstrap */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = bootstrap; sourceTree = "<group>"; };
|
||||
53710D7E17D8F5C300A992DE /* cibuild */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = cibuild; sourceTree = "<group>"; };
|
||||
53710D7F17D8F5C300A992DE /* deep-codesign */ = {isa = PBXFileReference; lastKnownFileType = text; path = "deep-codesign"; sourceTree = "<group>"; };
|
||||
53710D8017D8F5C300A992DE /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.md; sourceTree = "<group>"; };
|
||||
53710D8117D8F5C300A992DE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; };
|
||||
53710D8217D8F5C300A992DE /* targets.awk */ = {isa = PBXFileReference; lastKnownFileType = text; path = targets.awk; sourceTree = "<group>"; };
|
||||
@@ -532,6 +536,7 @@
|
||||
children = (
|
||||
53710D7D17D8F5C300A992DE /* bootstrap */,
|
||||
53710D7E17D8F5C300A992DE /* cibuild */,
|
||||
53710D7F17D8F5C300A992DE /* deep-codesign */,
|
||||
53710D8017D8F5C300A992DE /* LICENSE.md */,
|
||||
53710D8117D8F5C300A992DE /* README.md */,
|
||||
53710D8217D8F5C300A992DE /* targets.awk */,
|
||||
@@ -929,6 +934,7 @@
|
||||
children = (
|
||||
D00F5B8D17E82DE6009A4818 /* NSProcessInfoExtensionsSpec.m */,
|
||||
D0EDBEE017B0E3650058BC3C /* SQRLCodeSignatureSpec.m */,
|
||||
53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */,
|
||||
D049059A180554BA004E683E /* SQRLDirectoryManagerSpec.m */,
|
||||
D09D244017B595470001FAF8 /* SQRLInstallerSpec.m */,
|
||||
D049059C18055671004E683E /* SQRLShipItRequestSpec.m */,
|
||||
@@ -1137,6 +1143,7 @@
|
||||
D08D4E4A17B451FD0012B22D /* TestApplication.app in Resources */,
|
||||
D09D244A17B59AB30001FAF8 /* TestApplication 2.1.app in Resources */,
|
||||
D000219717BAD34D0050109A /* TestApplication.app.zip in Resources */,
|
||||
53710D8417D8F5CB00A992DE /* deep-codesign in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1273,6 +1280,7 @@
|
||||
D06B58D118035B5A00656D97 /* SQRLTerminationListenerSpec.m in Sources */,
|
||||
5371815F18A29DC8005ED798 /* TestAppConstants.m in Sources */,
|
||||
D0EDBEE117B0E3650058BC3C /* SQRLCodeSignatureSpec.m in Sources */,
|
||||
53710D7417D8F59700A992DE /* SQRLDeepCodesignSpec.m in Sources */,
|
||||
D00F5B8E17E82DE6009A4818 /* NSProcessInfoExtensionsSpec.m in Sources */,
|
||||
D049059B180554BA004E683E /* SQRLDirectoryManagerSpec.m in Sources */,
|
||||
5397A69D187DF8610014A477 /* SQRLInstallerOwnedBundle.m in Sources */,
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// SQRLDeepCodesignSpec.m
|
||||
// Squirrel
|
||||
//
|
||||
// Created by Keith Duncan on 2013-09-05.
|
||||
// Copyright (c) 2013 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Nimble/Nimble.h>
|
||||
#import <Quick/Quick.h>
|
||||
#import <ReactiveCocoa/ReactiveCocoa.h>
|
||||
#import <Squirrel/Squirrel.h>
|
||||
|
||||
#import "QuickSpec+SQRLFixtures.h"
|
||||
|
||||
QuickSpecBegin(SQRLDeepCodesign)
|
||||
|
||||
NSMutableDictionary * (^environmentSuitableForChildProcess)(void) = ^ {
|
||||
// Remove environment variables that configure the Obj-C runtime
|
||||
// Specifically OBJC_DISABLE_GC so that child processes aren't forced to
|
||||
// adopt the GC preference of the test suite
|
||||
NSMutableDictionary *environment = [NSProcessInfo.processInfo.environment mutableCopy];
|
||||
NSSet *objcEnvironmentVariables = [environment keysOfEntriesPassingTest:^(NSString *variable, id obj, BOOL *stop) {
|
||||
return [variable hasPrefix:@"OBJC"];
|
||||
}];
|
||||
[environment removeObjectsForKeys:objcEnvironmentVariables.allObjects];
|
||||
return environment;
|
||||
};
|
||||
|
||||
NSTask * (^codesignTaskWithArguments)(NSArray *) = ^ (NSArray *arguments) {
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
task.launchPath = @"/usr/bin/xcrun";
|
||||
task.arguments = [@[ @"codesign" ] arrayByAddingObjectsFromArray:arguments];
|
||||
task.environment = environmentSuitableForChildProcess();
|
||||
return task;
|
||||
};
|
||||
|
||||
void (^resignTestApplicationPreserveEverythingButTheRequirements)(void) = ^{
|
||||
NSURL *testApplicationLocation = self.testApplicationURL;
|
||||
|
||||
NSTask *resignCodesignTask = codesignTaskWithArguments(@[
|
||||
@"--sign", @"-",
|
||||
@"--force",
|
||||
@"--verbose=4",
|
||||
@"--preserve-metadata=identifier,entitlements,resource-rules",
|
||||
testApplicationLocation.path,
|
||||
]);
|
||||
|
||||
[resignCodesignTask launch];
|
||||
[resignCodesignTask waitUntilExit];
|
||||
|
||||
expect(@(resignCodesignTask.terminationStatus)).to(equal(@0));
|
||||
};
|
||||
|
||||
void (^deepCodesignTestApplication)(void) = ^{
|
||||
NSURL *testApplicationLocation = self.testApplicationURL;
|
||||
|
||||
NSBundle *testsBundle = [NSBundle bundleForClass:self.class];
|
||||
NSURL *deepCodesignLocation = [testsBundle URLForResource:@"deep-codesign" withExtension:nil];
|
||||
|
||||
NSNumber *executable = nil;
|
||||
NSError *executableError = nil;
|
||||
BOOL getExecutable = [deepCodesignLocation getResourceValue:&executable forKey:NSURLIsExecutableKey error:&executableError];
|
||||
expect(@(getExecutable)).to(beTruthy());
|
||||
expect(@(executable.boolValue)).to(beTruthy());
|
||||
expect(executableError).to(beNil());
|
||||
|
||||
NSTask *deepCodesignTask = [[NSTask alloc] init];
|
||||
deepCodesignTask.launchPath = deepCodesignLocation.path;
|
||||
deepCodesignTask.standardError = [NSPipe pipe];
|
||||
deepCodesignTask.standardOutput = [NSPipe pipe];
|
||||
|
||||
NSMutableDictionary *environment = environmentSuitableForChildProcess();
|
||||
[environment addEntriesFromDictionary:@{
|
||||
@"CODE_SIGN_IDENTITY": @"-",
|
||||
@"CONFIGURATION_BUILD_DIR": testApplicationLocation.URLByDeletingLastPathComponent.path,
|
||||
@"FULL_PRODUCT_NAME": testApplicationLocation.lastPathComponent,
|
||||
}];
|
||||
|
||||
deepCodesignTask.environment = environment;
|
||||
|
||||
[deepCodesignTask launch];
|
||||
[deepCodesignTask waitUntilExit];
|
||||
|
||||
expect(@(deepCodesignTask.terminationStatus)).to(equal(@0));
|
||||
|
||||
/*
|
||||
By signing test application's contents, which are covered by test
|
||||
application's ResourceRules it's signature becomes invalid. Usually,
|
||||
Xcode would sign the application after deep-codesign has run so the
|
||||
signature would include the _signed_ child resources which are covered
|
||||
by test application's ResourceRules
|
||||
|
||||
test application is already signed, so we need to manually resign it
|
||||
|
||||
deep-codesign preserves the requirements, we can't use it to resign the
|
||||
root target, the new signing identity wouldn't verify against the
|
||||
original designated requirement
|
||||
*/
|
||||
resignTestApplicationPreserveEverythingButTheRequirements();
|
||||
};
|
||||
|
||||
BOOL (^deepVerify)(void) = ^ BOOL {
|
||||
NSTask *deepVerifyTask = codesignTaskWithArguments(@[
|
||||
@"--deep-verify",
|
||||
@"--verbose=4",
|
||||
self.testApplicationURL.path
|
||||
]);
|
||||
|
||||
[deepVerifyTask launch];
|
||||
[deepVerifyTask waitUntilExit];
|
||||
|
||||
return deepVerifyTask.terminationStatus == 0;
|
||||
};
|
||||
|
||||
it(@"should deep sign the test application", ^{
|
||||
deepCodesignTestApplication();
|
||||
});
|
||||
|
||||
xit(@"should deep verify after signing", ^{
|
||||
expect(@(deepVerify())).to(beFalsy());
|
||||
deepCodesignTestApplication();
|
||||
expect(@(deepVerify())).to(beTruthy());
|
||||
});
|
||||
|
||||
QuickSpecEnd
|
||||
Executable
+203
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
def exit_error(description)
|
||||
puts "{ \"error\": \"#{description}\" }"
|
||||
exit 1
|
||||
end
|
||||
|
||||
module Targets
|
||||
class Object
|
||||
def self.match(path)
|
||||
!File.symlink?(path)
|
||||
end
|
||||
|
||||
attr_reader :path
|
||||
|
||||
def initialize(path)
|
||||
@path = path
|
||||
end
|
||||
|
||||
def search_directories
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
class Bundle < Object
|
||||
def self.match(path)
|
||||
return false unless super
|
||||
|
||||
return false unless match = File.extname(path).match(/\.(.+)/)
|
||||
return match[1] == extension
|
||||
end
|
||||
|
||||
def contents_directories
|
||||
[ "Contents" ]
|
||||
end
|
||||
|
||||
def suffix_directories
|
||||
[
|
||||
"Frameworks",
|
||||
"SharedFrameworks",
|
||||
"XPCServices",
|
||||
"PlugIns",
|
||||
"Support",
|
||||
"SharedSupport",
|
||||
File.join("Library", "LoginItems"),
|
||||
File.join("Library", "LaunchServices"),
|
||||
]
|
||||
end
|
||||
|
||||
def search_directories
|
||||
contents_directories.product(suffix_directories).map do |combination|
|
||||
File.join(*combination)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class VersionedBundle < Bundle
|
||||
def contents_directories
|
||||
contents_directories = []
|
||||
versions_directory = File.join(@path, "Versions")
|
||||
Dir.foreach(versions_directory) do |current_version|
|
||||
next if current_version.start_with?('.')
|
||||
|
||||
full_path = File.join(versions_directory, current_version)
|
||||
next if File.symlink?(full_path)
|
||||
|
||||
contents_directories << File.join("Versions", current_version)
|
||||
end
|
||||
contents_directories
|
||||
end
|
||||
end
|
||||
|
||||
class App < Bundle
|
||||
def self.extension
|
||||
"app"
|
||||
end
|
||||
end
|
||||
|
||||
class Framework < VersionedBundle
|
||||
def self.extension
|
||||
"framework"
|
||||
end
|
||||
end
|
||||
|
||||
class XPCService < Bundle
|
||||
def self.extension
|
||||
"xpc"
|
||||
end
|
||||
end
|
||||
|
||||
class Tool < Object
|
||||
def self.match(path)
|
||||
return false unless super
|
||||
return false unless File.executable?(path)
|
||||
|
||||
# Look for an __TEXT,__info_plist section
|
||||
# This contains the CFBundleIdentifier necessary for signing
|
||||
`otool -l "#{path}"` =~ /__info_plist/
|
||||
end
|
||||
end
|
||||
|
||||
def self.all_targets
|
||||
[ App, Framework, XPCService, Tool ]
|
||||
end
|
||||
end
|
||||
|
||||
module Codesign
|
||||
class Task
|
||||
def self.sign(target, identity, timestamp)
|
||||
arguments = [ "--sign", identity, "--force", "--preserve-metadata=identifier,entitlements,requirements", "--verbose", target.path ]
|
||||
arguments.unshift("--timestamp=none") if not timestamp
|
||||
codesign_with_arguments(arguments)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.codesign_with_arguments(arguments)
|
||||
$stdout.flush
|
||||
|
||||
pid = fork
|
||||
if pid.nil?
|
||||
exec("/usr/bin/xcrun", *([ "codesign" ].concat(arguments)))
|
||||
else
|
||||
Process.wait(pid)
|
||||
exit_error("codesign failed") unless $?.exitstatus == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
codesign_identity = ENV['CODE_SIGN_IDENTITY']
|
||||
unless codesign_identity
|
||||
exit_error("No codesign identity provided")
|
||||
end
|
||||
|
||||
build_dir = ENV['CONFIGURATION_BUILD_DIR']
|
||||
product_name = ENV['FULL_PRODUCT_NAME']
|
||||
unless build_dir && product_name
|
||||
exit_error("Path to built product not provided")
|
||||
end
|
||||
|
||||
input_path = File.realpath(File.join(build_dir, product_name))
|
||||
unless File.exists?(input_path)
|
||||
exit_error("Built product doesn't exist at path provided")
|
||||
end
|
||||
|
||||
targets = [ ]
|
||||
|
||||
def match_path(path)
|
||||
Targets.all_targets.each do |target|
|
||||
return target.new(path) if target.match(path)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
root_target = match_path input_path
|
||||
if root_target.nil?
|
||||
exit_error("Built product is not a known file structure, cannot deep codesign")
|
||||
end
|
||||
|
||||
# Recursion base case can't use build_dir because there can be >1 product
|
||||
paths_to_search = root_target.search_directories.map do |current_dir|
|
||||
File.join(root_target.path, current_dir)
|
||||
end
|
||||
|
||||
while paths_to_search.size > 0
|
||||
new_search_paths = []
|
||||
paths_to_search.each do |search_path|
|
||||
next unless File.directory?(search_path)
|
||||
|
||||
Dir.foreach(search_path) do |current_directory_entry|
|
||||
next if current_directory_entry.start_with?(".")
|
||||
File.delete(File.join(search_path, current_directory_entry)) if current_directory_entry.end_with?(".cstemp")
|
||||
|
||||
target = match_path(File.join(search_path, current_directory_entry))
|
||||
next if target.nil?
|
||||
|
||||
targets << target
|
||||
|
||||
new_search_paths << target.search_directories.map do |current_search_directory|
|
||||
File.join(target.path, current_search_directory)
|
||||
end
|
||||
end
|
||||
end
|
||||
paths_to_search = new_search_paths.flatten
|
||||
end
|
||||
|
||||
puts "Signing these targets:"
|
||||
puts "="*22
|
||||
print "\n"
|
||||
|
||||
# Sort into nested-most first, outer targets might include inner targets
|
||||
# in their ResourceRules
|
||||
targets = targets.sort_by do |target|
|
||||
target.path.size
|
||||
end
|
||||
targets.reverse!
|
||||
|
||||
use_timestamp = ENV["CONFIGURATION"] == "Release"
|
||||
targets.each do |target|
|
||||
puts "=> #{target.path}"
|
||||
Codesign::Task.sign(target, codesign_identity, use_timestamp)
|
||||
end
|
||||
Reference in New Issue
Block a user