wtapack 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3dc6a0f4020613af0a67a7cc7276e689b595fe57
4
+ data.tar.gz: 662ebe71614a404c1893add26c317bce10dbb7e5
5
+ SHA512:
6
+ metadata.gz: e72e8366278bf4bf09a64fc7f0a9790681ff2fa65a7e30757d6e72326503c4beb9ccccd6b123e68002f15aaa2694f531717d7582b354774ec2ad0624c61476ea
7
+ data.tar.gz: da4c0a2f0fcdd50bf9ece15e093cd64fb6232c7abf224597148c24adc632bb77b371a24052acfe6b592b55e3b11b9c21d8fdeee1ae34c7a1953312dc4b5a1c12
@@ -0,0 +1,14 @@
1
+ //
2
+ // CodeSigner.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+
11
+ @interface CodeSigner : NSObject
12
+ + (void)signBinary:(NSURL*)binaryURL;
13
+ + (void)signFramework:(NSURL*)frameworkURL;
14
+ @end
@@ -0,0 +1,197 @@
1
+ //
2
+ // CodeSigner.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "CodeSigner.h"
10
+ #import <objc/runtime.h>
11
+ #import "ErrorHandler.h"
12
+
13
+ void validateSigningIdentity(NSString* identity);
14
+
15
+ @implementation CodeSigner
16
+ + (NSString*)signingIdentity
17
+ {
18
+ NSString* result = objc_getAssociatedObject(self, @selector(signingIdentity));
19
+ if (!result)
20
+ {
21
+ result = [[NSUserDefaults standardUserDefaults] stringForKey:@"sign"];
22
+ //validateSigningIdentity(result); /* Rakefile does this */
23
+ objc_setAssociatedObject(self, @selector(signingIdentity), result, OBJC_ASSOCIATION_COPY_NONATOMIC);
24
+ }
25
+
26
+ return result;
27
+ }
28
+
29
+ + (NSString*)codesignFormatString
30
+ {
31
+ NSString* result = objc_getAssociatedObject(self, @selector(codesignFormatString));
32
+ if (!result)
33
+ {
34
+ NSString* signingIdentity = [self signingIdentity];
35
+ result = [NSString stringWithFormat:@"/usr/bin/codesign --force --preserve-metadata=identifier,entitlements,resource-rules --sign %@ --resource-rules=\"%%@\" --entitlements \"%%@\" \"%%@\"", signingIdentity];
36
+
37
+ objc_setAssociatedObject(self, @selector(codesignFormatString), result, OBJC_ASSOCIATION_COPY_NONATOMIC);
38
+ }
39
+
40
+ return result;
41
+ }
42
+
43
+ + (void)signBinary:(NSURL *)binaryURL
44
+ {
45
+ NSString* codesign = [NSString stringWithFormat:[self codesignFormatString], [binaryURL URLByAppendingPathComponent:@"ResourceRules.plist"].path, [binaryURL URLByAppendingPathComponent:@"Entitlements.plist"].path, binaryURL.path];
46
+
47
+ printf("%s\n", codesign.UTF8String);
48
+
49
+ [self codesign:codesign failureMessage:@"Codesigning Failed!"];
50
+ }
51
+
52
+ + (void)signFramework:(NSURL *)frameworkURL
53
+ {
54
+ NSString* codesign = [NSString stringWithFormat:@"/usr/bin/codesign --force -v --sign \"%@\" \"%@\"",
55
+ [self signingIdentity],
56
+ frameworkURL.path];
57
+
58
+ printf("%s\n", codesign.UTF8String);
59
+
60
+ [self codesign:codesign failureMessage:@"Framework Codesigning Failed!"];
61
+ }
62
+
63
+ + (void)codesign:(NSString*)codesignString failureMessage:(NSString*)message
64
+ {
65
+ // Lots of nonsense here so we can capture the output of the codesign command to display as
66
+ // we please instead of as it pleases.
67
+
68
+ const char* template = "/tmp/codesignOut.XXXXXXX";
69
+ size_t length = strlen(template);
70
+ char* temp = calloc(1, length);
71
+ strncpy(temp, template, length);
72
+
73
+ int fileno = mkstemp(temp);
74
+ int retVal = system([codesignString stringByAppendingString:[NSString stringWithFormat:@">& %s", temp]].UTF8String);
75
+
76
+ close(fileno);
77
+ NSString* tempFilePath = [NSString stringWithUTF8String:temp];
78
+ NSString* codesignOuput = [[NSString alloc] initWithContentsOfFile:tempFilePath
79
+ encoding:NSUTF8StringEncoding
80
+ error:NULL];
81
+ [[NSFileManager defaultManager] removeItemAtPath:tempFilePath
82
+ error:NULL];
83
+ free(temp);
84
+
85
+ if (retVal != 0)
86
+ {
87
+ [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:@"%@\ncodesign output: %@",
88
+ message,
89
+ codesignOuput]
90
+ exitCode:retVal];
91
+ }
92
+ else
93
+ {
94
+ fprintf(stdout, "%s", codesignOuput.UTF8String);
95
+ }
96
+ }
97
+ @end
98
+
99
+ // It turned out the Rakefile already does the below, so no point in doing it twice.
100
+
101
+ // So the lesson of this function is that the keychain API is very confusing and difficult
102
+ // to work with. This function loops through all the certificates in the keychain, and checks the
103
+ // SHA1 digest (which has to be calculated, it cannot be gotten from the keychain API directly)
104
+ // against the one passed in. If it finds a match, it verifies that the certificate is trusted.
105
+ //void validateSigningIdentity(NSString* identity)
106
+ //{
107
+ // NSDictionary* query = @{ (__bridge id)kSecClass : (__bridge id)kSecClassCertificate,
108
+ // (__bridge id)kSecReturnRef : (__bridge id)kCFBooleanTrue,
109
+ // (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll };
110
+ //
111
+ // CFTypeRef result = NULL;
112
+ // OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &result);
113
+ // if (status)
114
+ // {
115
+ // CFStringRef errorString = SecCopyErrorMessageString(status, 0);
116
+ // NSLog(@"status is %@, result is %@", errorString, (__bridge id)result);
117
+ // CFRelease(errorString);
118
+ // [ErrorHandler fatalErrorWithMessage:@"Could not find signing identity"
119
+ // exitCode:status];
120
+ // }
121
+ //
122
+ // BOOL certificateFound = NO;
123
+ //
124
+ // for (id certificate in (__bridge NSArray*)result)
125
+ // {
126
+ // CFErrorRef error = NULL;
127
+ // SecCertificateRef certificateRef = (__bridge SecCertificateRef)certificate;
128
+ // SecTransformRef transformRef = SecDigestTransformCreate(kSecDigestSHA1, 0, &error);
129
+ // CFDataRef certData = SecCertificateCopyData(certificateRef);
130
+ // SecTransformSetAttribute(transformRef, kSecTransformInputAttributeName, certData, &error);
131
+ // if (error)
132
+ // {
133
+ // NSLog(@"%@", (__bridge NSError*)error);
134
+ // }
135
+ // CFDataRef output = SecTransformExecute(transformRef, &error);
136
+ // if (error)
137
+ // {
138
+ // NSLog(@"%@", (__bridge NSError*)error);
139
+ // }
140
+ // CFRelease(certData);
141
+ //
142
+ // NSData* outputData = (__bridge NSData*)output;
143
+ //
144
+ // NSCharacterSet* greaterLessSet = [NSCharacterSet characterSetWithCharactersInString:@"<>"];
145
+ // NSString* description = [[outputData.description stringByTrimmingCharactersInSet:greaterLessSet] stringByReplacingOccurrencesOfString:@" "
146
+ // withString:@""];
147
+ // CFRelease(output);
148
+ // CFRelease(transformRef);
149
+ // if(error)
150
+ // {
151
+ // CFRelease(error);
152
+ // }
153
+ //
154
+ // if ([description compare:identity options:NSCaseInsensitiveSearch] == NSOrderedSame)
155
+ // {
156
+ // SecTrustRef trustRef = NULL;
157
+ // SecPolicyRef policyRef = SecPolicyCreateBasicX509();
158
+ // status = SecTrustCreateWithCertificates(certificateRef, policyRef, &trustRef);
159
+ // if (status)
160
+ // {
161
+ // CFStringRef errorString = SecCopyErrorMessageString(status, 0);
162
+ // NSLog(@"%@", errorString);
163
+ // CFRelease(errorString);
164
+ // }
165
+ // SecTrustResultType trustResult;
166
+ // status = SecTrustEvaluate(trustRef, &trustResult);
167
+ // if (status)
168
+ // {
169
+ // CFStringRef errorString = SecCopyErrorMessageString(status, 0);
170
+ // NSLog(@"%@", errorString);
171
+ // CFRelease(errorString);
172
+ // }
173
+ //
174
+ // if ((trustResult != kSecTrustResultDeny) &&
175
+ // (trustResult != kSecTrustResultFatalTrustFailure) &&
176
+ // (trustResult != kSecTrustResultInvalid) &&
177
+ // (trustResult != kSecTrustResultOtherError) &&
178
+ // (trustResult != kSecTrustResultRecoverableTrustFailure))
179
+ // {
180
+ // certificateFound = YES;
181
+ // }
182
+ // CFRelease(certificateRef);
183
+ // CFRelease(trustRef);
184
+ // CFRelease(policyRef);
185
+ // break;
186
+ // }
187
+ //
188
+ // CFRelease(certificateRef);
189
+ //
190
+ // }
191
+ //
192
+ // if (!certificateFound)
193
+ // {
194
+ // [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:@"No valid certificate with SHA1 %@ found", identity]
195
+ // exitCode:EX_DATAERR];
196
+ // }
197
+ //}
@@ -0,0 +1,19 @@
1
+ //
2
+ // ErrorHandler.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+ #import <sysexits.h>
11
+
12
+ @interface ErrorHandler : NSObject
13
+ // Used for cleaning up after ourselves in the event of an error
14
+ + (void)setTemporaryDirectory:(NSURL*)tempDir;
15
+
16
+ // Display an error meesage to stderr then exit cleanly.
17
+ + (void)fatalErrorWithMessage:(NSString*)message exitCode:(int)code;
18
+ + (void)fatalErrorWithError:(NSError*)error additionalMessage:(NSString*)message;
19
+ @end
@@ -0,0 +1,53 @@
1
+ //
2
+ // ErrorHandler.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "ErrorHandler.h"
10
+ #import <objc/runtime.h>
11
+
12
+ @implementation ErrorHandler
13
+ + (NSURL*)temporaryDirectory
14
+ {
15
+ return objc_getAssociatedObject(self, @selector(temporaryDirectory));
16
+ }
17
+
18
+ + (void)setTemporaryDirectory:(NSURL *)tempDir
19
+ {
20
+ NSURL* result = [self temporaryDirectory];
21
+ if ([result isEqual:tempDir])
22
+ {
23
+ return;
24
+ }
25
+
26
+ objc_setAssociatedObject(self, @selector(temporaryDirectory), tempDir, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
27
+ }
28
+
29
+ + (void)fatalErrorWithMessage:(NSString *)message exitCode:(int)code
30
+ {
31
+ fprintf(stderr, "%s\n", message.UTF8String);
32
+
33
+ [self cleanupAndExitWithCode:code];
34
+ }
35
+
36
+ + (void)fatalErrorWithError:(NSError *)error additionalMessage:(NSString *)message
37
+ {
38
+ fprintf(stderr, "%s with error: %s", message.UTF8String, [NSString stringWithFormat:@"%@", error].UTF8String);
39
+ [self cleanupAndExitWithCode:error.code];
40
+ }
41
+
42
+ + (void)cleanupAndExitWithCode:(NSInteger)code
43
+ {
44
+ NSURL* tempDir = [self temporaryDirectory];
45
+ if (tempDir)
46
+ {
47
+ [[NSFileManager defaultManager] removeItemAtURL:tempDir
48
+ error:NULL];
49
+ }
50
+
51
+ exit((int)code);
52
+ }
53
+ @end
@@ -0,0 +1,15 @@
1
+ //
2
+ // NSArray+WTAMap.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+
11
+ typedef id (^arrayObjectFunc)(id obj);
12
+
13
+ @interface NSArray (WTAMap)
14
+ - (NSArray*)wta_map:(arrayObjectFunc)func;
15
+ @end
@@ -0,0 +1,24 @@
1
+ //
2
+ // NSArray+WTAMap.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "NSArray+WTAMap.h"
10
+
11
+ @implementation NSArray (WTAMap)
12
+ - (NSArray *)wta_map:(arrayObjectFunc)func
13
+ {
14
+ NSMutableArray* newArray = [NSMutableArray arrayWithCapacity:self.count];
15
+
16
+ dispatch_apply(self.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
17
+ ^(size_t i) {
18
+ id obj = self[i];
19
+ [newArray addObject:func(obj)];
20
+ });
21
+
22
+ return [NSArray arrayWithArray:newArray];
23
+ }
24
+ @end
@@ -0,0 +1,18 @@
1
+ //
2
+ // ProfileManager.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+
11
+ @interface ProfileManager : NSObject
12
+ - (instancetype)initWithKey:(NSString*)profileUUIDKey bundleURL:(NSURL*)bundleURL;
13
+
14
+ @property (strong, nonatomic, readonly) NSDictionary* provisioningProfile;
15
+
16
+ - (void)replaceProfile;
17
+ - (void)replaceEntitlements;
18
+ @end
@@ -0,0 +1,151 @@
1
+ //
2
+ // ProfileManager.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "ProfileManager.h"
10
+ #import "ErrorHandler.h"
11
+ @import Security;
12
+
13
+ @interface ProfileManager ()
14
+ {
15
+ __strong NSDictionary* _provisioningProfile;
16
+ }
17
+ @property (strong, nonatomic) NSURL* profileURL;
18
+ @property (strong, nonatomic) NSURL* bundleURL;
19
+ @end
20
+
21
+ @implementation ProfileManager
22
+ #pragma mark Initializers
23
+ - (instancetype)initWithKey:(NSString *)profileUUIDKey bundleURL:(NSURL *)bundleURL
24
+ {
25
+ self = [super init];
26
+ if (self)
27
+ {
28
+ NSString* profileUUID = [[NSUserDefaults standardUserDefaults] stringForKey:profileUUIDKey];
29
+ if (!profileUUID)
30
+ {
31
+ NSString* errorString = [NSString stringWithFormat:@"You must specify a profile for key \"%@\".",
32
+ profileUUIDKey];
33
+ [ErrorHandler fatalErrorWithMessage:errorString
34
+ exitCode:EX_USAGE];
35
+ }
36
+
37
+ NSString* provisioningProfilePath = [[NSString stringWithFormat:@"~/Library/MobileDevice/Provisioning Profiles/%@.mobileprovision",
38
+ profileUUID] stringByExpandingTildeInPath];
39
+
40
+ if ([[NSFileManager defaultManager] fileExistsAtPath:provisioningProfilePath])
41
+ {
42
+ _profileURL = [NSURL fileURLWithPath:provisioningProfilePath];
43
+ }
44
+ else
45
+ {
46
+ NSString* errorString = [NSString stringWithFormat:@"Specifed provisioning profile %@ does not exist",
47
+ profileUUID];
48
+
49
+ [ErrorHandler fatalErrorWithMessage:errorString
50
+ exitCode:EX_NOINPUT];
51
+ }
52
+
53
+ _bundleURL = bundleURL;
54
+ }
55
+
56
+ return self;
57
+ }
58
+
59
+ #pragma mark Instance Methods
60
+ - (void)replaceProfile
61
+ {
62
+ NSFileManager* fm = [NSFileManager defaultManager];
63
+
64
+ NSError* error = nil;
65
+
66
+ NSURL* destinationPPURL = [self.bundleURL URLByAppendingPathComponent:@"embedded.mobileprovision"];
67
+ // Delete the old one (we don't care if this fails)
68
+ [fm removeItemAtURL:destinationPPURL
69
+ error:NULL];
70
+
71
+ // Copy in the new one
72
+ BOOL success = [fm copyItemAtURL:self.profileURL
73
+ toURL:destinationPPURL
74
+ error:&error];
75
+
76
+ if (!success)
77
+ {
78
+ [ErrorHandler fatalErrorWithError:error
79
+ additionalMessage:@"Failed to copy new profile into place."];
80
+ }
81
+ }
82
+
83
+ - (void)replaceEntitlements
84
+ {
85
+ NSMutableDictionary* entitlementsDict = self.provisioningProfile[@"Entitlements"];
86
+
87
+ entitlementsDict[@"get-task-allow"] = @NO;
88
+
89
+ NSError* error = nil;
90
+ NSData* entitlementsData = [NSPropertyListSerialization dataWithPropertyList:entitlementsDict
91
+ format:NSPropertyListXMLFormat_v1_0
92
+ options:0
93
+ error:&error];
94
+ if (error)
95
+ {
96
+ [ErrorHandler fatalErrorWithError:error
97
+ additionalMessage:@"Failed to serialize entitlements plist"];
98
+ }
99
+
100
+ BOOL success = [entitlementsData writeToURL:[self.bundleURL URLByAppendingPathComponent:@"Entitlements.plist"]
101
+ atomically:YES];
102
+ if (!success)
103
+ {
104
+ NSString* errorString = [NSString stringWithFormat:@"Failed to write Entitlements.plist to %@",
105
+ self.bundleURL.path];
106
+ [ErrorHandler fatalErrorWithMessage:errorString
107
+ exitCode:EX_CANTCREAT];
108
+ }
109
+ }
110
+
111
+ #pragma mark Getters/Setters
112
+ - (NSDictionary*)provisioningProfile
113
+ {
114
+ if (!_provisioningProfile)
115
+ {
116
+ _provisioningProfile = [self.class getProvisioningProfile:self.profileURL.path];
117
+ }
118
+
119
+ return _provisioningProfile;
120
+ }
121
+
122
+ #pragma mark Utility Methods
123
+ + (NSDictionary *)getProvisioningProfile:(NSString *)profilePath
124
+ {
125
+ CMSDecoderRef decoder;
126
+ CMSDecoderCreate(&decoder);
127
+ NSData* encodedData = [NSData dataWithContentsOfFile:profilePath];
128
+ CMSDecoderUpdateMessage(decoder, encodedData.bytes, encodedData.length);
129
+ CMSDecoderFinalizeMessage(decoder);
130
+
131
+ CFDataRef decodedDataRef;
132
+ CMSDecoderCopyContent(decoder, &decodedDataRef);
133
+
134
+ NSPropertyListFormat format;
135
+ NSError* error = nil;
136
+ NSDictionary* profile = [NSPropertyListSerialization propertyListWithData:(__bridge NSData*)decodedDataRef
137
+ options:NSPropertyListMutableContainersAndLeaves
138
+ format:&format
139
+ error:&error];
140
+ CFRelease(decoder);
141
+ CFRelease(decodedDataRef);
142
+
143
+ if (error)
144
+ {
145
+ [ErrorHandler fatalErrorWithError:error
146
+ additionalMessage:@"Failed to deserialize provisioning profile."];
147
+ }
148
+
149
+ return profile;
150
+ }
151
+ @end
@@ -0,0 +1,8 @@
1
+ require 'mkmf'
2
+ extension_name = 'wtapack'
3
+ dir_config(extension_name)
4
+ MODULE_CACHE = "#{ENV['HOME']}/Library/Developer/Xcode/DerivedData/ModuleCache"
5
+ ISYSROOT_PATH = `xcrun -sdk macosx10.9 --show-sdk-path`
6
+ $CFLAGS = "-fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -std=gnu99 -fobjc-arc -fmodules -fmodules-cache-path=#{MODULE_CACHE} -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -isysroot #{ISYSROOT_PATH} -mmacosx-version-min=10.9 -fasm-blocks -fstrict-aliasing -Os -march=native -flto -Wall -Wpedantic -Werror"
7
+ $LFLAGS = "-isysroot $(ISYSROOT_PATH) -mmacosx-version-min=10.9 -flto -framework Foundation"
8
+ create_makefile(extension_name)
@@ -0,0 +1,7 @@
1
+ #ifndef MAIN_H
2
+ #define MAIN_H
3
+
4
+ int rb_main(int argc, char** argv);
5
+
6
+ #endif
7
+
@@ -0,0 +1,250 @@
1
+ //
2
+ // main.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/10/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+ #import "ProfileManager.h"
11
+ #import "CodeSigner.h"
12
+ #import "updateBundleID.h"
13
+ #import "NSArray+WTAMap.h"
14
+ #import "ErrorHandler.h"
15
+ #import <ruby.h>
16
+
17
+ static NSString* const usageString = @"Usage: %s -input <path to original .app> \
18
+ -profile <provisioning profile UUID> -sign <SHA1 for valid certificate> \
19
+ [-bundleID <new bundleID>] [-extensions <comma-separated list of extensions>] \
20
+ [-<extension name> <extension provisioning profile UUID> ...] -output <path to output ipa>";
21
+
22
+ void showUsageAndExit(void);
23
+
24
+ VALUE rb_main(int argc, const char * argv[]) {
25
+ @autoreleasepool {
26
+
27
+ NSMutableArray* arguments = [NSMutableArray arrayWithCapacity: argc];
28
+ for (int i = 0; i < argc; i++)
29
+ {
30
+ NSString* string = [[NSString stringWithUTF8String:argv[i]] stringByReplacingOccurrencesOfString:@"\"" withString:@""];
31
+ NSCharacterSet* comma = [NSCharacterSet characterSetWithCharactersInString:@","];
32
+ string = [string stringByTrimmingCharactersInSet:comma];
33
+ [arguments addObject:string];
34
+ }
35
+
36
+ for (NSString* argument in arguments)
37
+ {
38
+ if ([argument isEqualToString:@"-help"])
39
+ {
40
+ showUsageAndExit();
41
+ }
42
+ }
43
+
44
+ NSMutableDictionary* registrationDict = [NSMutableDictionary dictionary];
45
+ for (int i = 1; i < arguments.count; i+=2)
46
+ {
47
+ NSString* key = [arguments[i] substringFromIndex:1];
48
+ registrationDict[key] = arguments[i+1];
49
+ }
50
+
51
+ NSLog(@"%@", registrationDict);
52
+ NSFileManager* fm = [NSFileManager defaultManager];
53
+ NSUserDefaults* standardDefaults = [NSUserDefaults standardUserDefaults];
54
+ [standardDefaults registerDefaults: registrationDict];
55
+
56
+ NSError* error = nil;
57
+
58
+ // Read in the input directory, bail if it doesn't exist
59
+ NSString* origAppName = [standardDefaults stringForKey:@"input"];
60
+ if (!origAppName)
61
+ {
62
+ showUsageAndExit();
63
+ }
64
+
65
+ // Check to see if output key exists
66
+ NSString* outputPathString = [standardDefaults valueForKey:@"output"];
67
+ if (!outputPathString)
68
+ {
69
+ showUsageAndExit();
70
+ }
71
+
72
+ NSString* newBundleID = [standardDefaults stringForKey:@"bundleID"];
73
+
74
+ // Create a temporary location to do our work
75
+ NSURL* tmpDirURL = [fm URLForDirectory:NSItemReplacementDirectory
76
+ inDomain:NSUserDomainMask
77
+ appropriateForURL:[NSURL fileURLWithPath:@"PackageApplicationDir" isDirectory:YES]
78
+ create:YES
79
+ error:&error];
80
+ if (error)
81
+ {
82
+ [ErrorHandler fatalErrorWithMessage:@"Failed to create temporary Directory!"
83
+ exitCode:EX_CANTCREAT];
84
+ }
85
+
86
+ // Set up our error handler so we don't leave files laying around in the case of an error
87
+ [ErrorHandler setTemporaryDirectory:tmpDirURL];
88
+
89
+ NSString* appName = [origAppName lastPathComponent];
90
+ NSURL* destAppDirURL = [tmpDirURL URLByAppendingPathComponent:@"Payload" isDirectory:YES];
91
+ NSURL* destApp = [destAppDirURL URLByAppendingPathComponent:appName isDirectory:YES];
92
+
93
+ // Make sure the app is actually there and display an error message if it isn't
94
+ BOOL origAppIsDirectory = NO;
95
+ BOOL origAppExists = [fm fileExistsAtPath:origAppName isDirectory:&origAppIsDirectory];
96
+ if (!origAppExists || !origAppIsDirectory)
97
+ {
98
+ [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:@"%@ does not exist!\n", origAppName]
99
+ exitCode:EX_NOINPUT];
100
+ }
101
+
102
+ NSURL* origAppURL = [NSURL fileURLWithPath:origAppName isDirectory:YES];
103
+
104
+ // Create destination directory
105
+ [fm createDirectoryAtURL:destAppDirURL
106
+ withIntermediateDirectories:YES
107
+ attributes:nil
108
+ error:&error];
109
+ if (error)
110
+ {
111
+ [ErrorHandler fatalErrorWithError:error
112
+ additionalMessage:@"Failed to create destination Payload directory."];
113
+ }
114
+
115
+ // Copy original app to work directory
116
+ [fm copyItemAtURL:origAppURL
117
+ toURL:destApp
118
+ error:&error];
119
+
120
+ if (error)
121
+ {
122
+ [ErrorHandler fatalErrorWithError:error
123
+ additionalMessage:@"Failed to copy original app to work directory."];
124
+ }
125
+
126
+ // Set up the object that handles provisioning profiles and the entitlements contained
127
+ // therein.
128
+ ProfileManager* mainProfileManager = [[ProfileManager alloc] initWithKey:@"profile"
129
+ bundleURL:destApp];
130
+ if (newBundleID)
131
+ {
132
+ // Sometimes we may not be changing the bundle ID
133
+ updateBundleID(destApp, newBundleID);
134
+ }
135
+
136
+ [mainProfileManager replaceEntitlements];
137
+
138
+ [mainProfileManager replaceProfile];
139
+
140
+ // Extensions handling starts here
141
+ NSString* extensionsString = [standardDefaults stringForKey:@"extensions"];
142
+ NSArray* extensions = [[extensionsString componentsSeparatedByString:@","] wta_map: ^(NSString* string) {
143
+ return [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
144
+ }];
145
+
146
+ NSURL* pluginPath = [destApp URLByAppendingPathComponent:@"PlugIns"];
147
+
148
+ dispatch_apply(extensions.count,
149
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
150
+ 0),
151
+ ^(size_t i) {
152
+ NSString* extension = extensions[i];
153
+ NSString* extensionName = [NSString stringWithFormat:@"%@.appex", extension];
154
+ NSURL* extensionURL = [pluginPath URLByAppendingPathComponent:extensionName];
155
+
156
+ ProfileManager* extensionProfileManager = [[ProfileManager alloc] initWithKey:extension
157
+ bundleURL:extensionURL];
158
+ if (newBundleID)
159
+ {
160
+ NSString* extensionBundleName = [extension stringByReplacingOccurrencesOfString:@" " withString:@"-"];
161
+ NSString* newExtensionBundleID = [newBundleID stringByAppendingString:[NSString stringWithFormat:@".%@", extensionBundleName]];
162
+
163
+ updateBundleID(extensionURL, newExtensionBundleID);
164
+ }
165
+
166
+ [extensionProfileManager replaceEntitlements];
167
+
168
+ [extensionProfileManager replaceProfile];
169
+
170
+ // Code signing
171
+ [CodeSigner signBinary:extensionURL];
172
+ });
173
+
174
+ // Code signing of frameworks. We don't care if this call fails, because many apps don't
175
+ // even have Framewoks.
176
+ NSArray* frameworks = [fm contentsOfDirectoryAtURL:[destApp URLByAppendingPathComponent:@"Frameworks"]
177
+ includingPropertiesForKeys:nil
178
+ options:NSDirectoryEnumerationSkipsHiddenFiles
179
+ error:NULL];
180
+
181
+ dispatch_apply(frameworks.count,
182
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
183
+ 0),
184
+ ^(size_t i) {
185
+ [CodeSigner signFramework:frameworks[i]];
186
+ });
187
+
188
+ // Finally sign whole app.
189
+ [CodeSigner signBinary:destApp];
190
+
191
+ // Remove whatever is at the output path, if it exists
192
+ // (or else the zip command will just append)
193
+ [fm removeItemAtPath:outputPathString
194
+ error:NULL];
195
+
196
+ NSString* currentWorkingDirectory = [fm currentDirectoryPath];
197
+ [fm changeCurrentDirectoryPath:tmpDirURL.path];
198
+ // Create the final ipa
199
+ int retVal = system([NSString stringWithFormat:@"/usr/bin/zip --symlinks --recurse-paths --verbose -9 \"%@\" .",
200
+ outputPathString].UTF8String);
201
+ if (retVal != 0)
202
+ {
203
+ [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:@"Failed to create ipa at %@",
204
+ outputPathString]
205
+ exitCode:retVal];
206
+ }
207
+
208
+ // Clean up after ourselves
209
+ [fm changeCurrentDirectoryPath:currentWorkingDirectory];
210
+ [fm removeItemAtURL:tmpDirURL error:NULL];
211
+ }
212
+ return rb_int2inum(0);
213
+ }
214
+
215
+ void showUsageAndExit(void)
216
+ {
217
+ [ErrorHandler fatalErrorWithMessage:[NSString stringWithFormat:usageString, [[NSProcessInfo processInfo].arguments.firstObject UTF8String]]
218
+ exitCode:EX_USAGE];
219
+ }
220
+ // This is left here for historical interest, in case anyone wants to know how to do this.
221
+ //NSMutableDictionary* readRawEntitlements(NSURL* rawEntitlementsPath)
222
+ //{
223
+ // NSData* rawFileData = [[NSFileManager defaultManager] contentsAtPath:rawEntitlementsPath.path];
224
+ //
225
+ // uint32_t formatTag;
226
+ // [rawFileData getBytes:&formatTag length:4];
227
+ //
228
+ // if (CFSwapInt32(formatTag) != 0xfade7171)
229
+ // {
230
+ // exit(1);
231
+ // }
232
+ //
233
+ // int length;
234
+ // [rawFileData getBytes:&length range:NSMakeRange(4, 4)];
235
+ //
236
+ // length = CFSwapInt32(length);
237
+ //
238
+ // NSData* plistData = [rawFileData subdataWithRange:NSMakeRange(8, length - 8)];
239
+ // NSPropertyListFormat format;
240
+ // NSMutableDictionary* result = [NSPropertyListSerialization propertyListWithData:plistData
241
+ // options:NSPropertyListMutableContainersAndLeaves
242
+ // format:&format
243
+ // error:NULL];
244
+ //
245
+ // [[NSFileManager defaultManager] removeItemAtURL:rawEntitlementsPath
246
+ // error:NULL];
247
+ //
248
+ // return result;
249
+ //}
250
+
@@ -0,0 +1,11 @@
1
+ //
2
+ // updateBundleID.h
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ @import Foundation;
10
+
11
+ void updateBundleID(NSURL* appURL, NSString* newBundleID);
@@ -0,0 +1,54 @@
1
+ //
2
+ // updateBundleID.m
3
+ // WTAPackageApplication
4
+ //
5
+ // Created by Robert Thompson on 10/13/14.
6
+ // Copyright (c) 2014 WillowTree Apps. All rights reserved.
7
+ //
8
+
9
+ #import "updateBundleID.h"
10
+ #import "ErrorHandler.h"
11
+
12
+ void updateBundleID(NSURL* appURL, NSString* newBundleID)
13
+ {
14
+ NSFileManager* fm = [NSFileManager defaultManager];
15
+
16
+ NSPropertyListFormat format;
17
+ NSURL* infoPlistURL = [appURL URLByAppendingPathComponent:@"Info.plist"];
18
+ NSData* infoPlistData = [fm contentsAtPath:infoPlistURL.path];
19
+ NSError* error = nil;
20
+ NSMutableDictionary* infoPlist = [NSPropertyListSerialization propertyListWithData:infoPlistData
21
+ options:NSPropertyListMutableContainersAndLeaves
22
+ format:&format
23
+ error:&error];
24
+ if (error)
25
+ {
26
+ [ErrorHandler fatalErrorWithError:error
27
+ additionalMessage:[NSString stringWithFormat:@"Failed to deserialize Info.plist in %@",
28
+ appURL.path]];
29
+ }
30
+
31
+ infoPlist[@"CFBundleIdentifier"] = newBundleID;
32
+
33
+ infoPlistData = [NSPropertyListSerialization dataWithPropertyList:infoPlist
34
+ format:format
35
+ options:0
36
+ error:&error];
37
+ if (error)
38
+ {
39
+ [ErrorHandler fatalErrorWithError:error
40
+ additionalMessage:[NSString stringWithFormat:@"Failed to serialize new Info.plist for %@",
41
+ appURL.path]];
42
+ }
43
+
44
+ BOOL success = [infoPlistData writeToURL:infoPlistURL
45
+ atomically:YES];
46
+
47
+ if (!success)
48
+ {
49
+ NSString* errorString = [NSString stringWithFormat:@"Failed to write new Info.plist in %@",
50
+ appURL.path];
51
+ [ErrorHandler fatalErrorWithMessage:errorString
52
+ exitCode:EX_CANTCREAT];
53
+ }
54
+ }
@@ -0,0 +1,32 @@
1
+ #include <ruby.h>
2
+ #include "main.h"
3
+
4
+ static VALUE hello_world(VALUE mod)
5
+ {
6
+ return rb_str_new2("hello world");
7
+ }
8
+
9
+ static VALUE native_pack(VALUE mod, VALUE argc, VALUE argv)
10
+ {
11
+ struct RArray *argv_array = RARRAY(argv);
12
+
13
+ char** real_argv = ALLOC_N(char*, RARRAY_LEN(argv_array));
14
+ for (int i = 0; i < RARRAY_LEN(argv_array); i++)
15
+ {
16
+ struct RString* string = RSTRING(RARRAY_CONST_PTR(argv_array)[i]);
17
+ int length = RSTRING_LEN(string);
18
+ real_argv[i] = ALLOC_N(char, length + 1);
19
+ memcpy(real_argv[i], RSTRING_PTR(string), length);
20
+ real_argv[i][length] = '\0';
21
+ }
22
+
23
+ return rb_main(NUM2INT(argc), real_argv);
24
+ }
25
+
26
+ void Init_wtapack()
27
+ {
28
+ VALUE mWtapack = rb_define_module("Wtapack");
29
+ rb_define_singleton_method(mWtapack, "hello_world", hello_world, 0);
30
+ rb_define_singleton_method(mWtapack, "native_pack", native_pack, 2);
31
+ }
32
+
data/lib/wtapack.rb ADDED
@@ -0,0 +1,33 @@
1
+ require File.expand_path('../wtapack.bundle', __FILE__)
2
+
3
+ module Wtapack
4
+ class Packer
5
+ attr_accessor :profile, :extensions, :input, :sign, :bundleID, :extension_profiles, :output
6
+
7
+ def pack
8
+ argv = ["wtapack", "-profile", "#{self.profile}", "-sign", "#{self.sign}", "-input", "\"#{self.input}\"", "-output", "\"#{self.output}\"", "-bundleID", "#{self.bundleID}"]
9
+ unless self.extensions.nil?
10
+ extensions = ["-extensions"]
11
+ extension_list = ""
12
+ self.extensions.each do |ex|
13
+ extension_list = extension_list + "\"#{ex}\"" + ","
14
+ end
15
+ argv = argv + extensions + [extension_list]
16
+ end
17
+ unless self.extension_profiles.nil?
18
+ self.extension_profiles.each do |extension, profile|
19
+
20
+ argv = argv + ["\"-#{extension}\"", "#{profile}"]
21
+
22
+ end
23
+ end
24
+ if self.extensions.nil?
25
+ self.extensions = []
26
+ end
27
+
28
+ argc = argv.count
29
+
30
+ Wtapack::native_pack(argc, argv)
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wtapack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Robert Thompson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake-compiler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Will re-sign, re-bundle-identifier, and re-provision an iOS app, and
28
+ then package the result as an ipa.
29
+ email: robert.thompson@willowtreeapps.com
30
+ executables: []
31
+ extensions:
32
+ - ext/wtapack/extconf.rb
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ext/wtapack/CodeSigner.h
36
+ - ext/wtapack/CodeSigner.m
37
+ - ext/wtapack/ErrorHandler.h
38
+ - ext/wtapack/ErrorHandler.m
39
+ - ext/wtapack/NSArray+WTAMap.h
40
+ - ext/wtapack/NSArray+WTAMap.m
41
+ - ext/wtapack/ProfileManager.h
42
+ - ext/wtapack/ProfileManager.m
43
+ - ext/wtapack/extconf.rb
44
+ - ext/wtapack/main.h
45
+ - ext/wtapack/main.m
46
+ - ext/wtapack/updateBundleID.h
47
+ - ext/wtapack/updateBundleID.m
48
+ - ext/wtapack/wtapack.c
49
+ - lib/wtapack.rb
50
+ homepage: https://github.com/willowtreeapps/wtapack
51
+ licenses:
52
+ - MIT
53
+ metadata: {}
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 2.4.2
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: Codesigning and ipa packaging
74
+ test_files: []