appjam 0.1.8.7 → 0.1.8.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +9 -1
- data/lib/appjam/generators/help.rb +1 -1
- data/lib/appjam/generators/templates/blank/EiffelApplication.xcodeproj/project.pbxproj +356 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication.xcodeproj/project.xcworkspace/xcuserdata/eiffel.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/AppDelegate.h.tt +3 -3
- data/lib/appjam/generators/templates/blank/EiffelApplication/AppDelegate.m.tt +5 -5
- data/lib/appjam/generators/templates/blank/EiffelApplication/EiffelApplication-Info.plist +1 -1
- data/lib/appjam/generators/templates/blank/EiffelApplication/EiffelApplication-Prefix.pch.tt +1 -1
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/Toast/Toast+UIView.h +51 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/Toast/Toast+UIView.m +315 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/frameworks/RaptureXML/RXMLElement.h +97 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/frameworks/RaptureXML/RXMLElement.m +450 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/Objective-C-HMTL-Parser/HTMLNode.h +111 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/Objective-C-HMTL-Parser/HTMLNode.m +412 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/Objective-C-HMTL-Parser/HTMLParser.h +37 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/Objective-C-HMTL-Parser/HTMLParser.m +130 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSArray+ObjectiveSugar.h +39 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSArray+ObjectiveSugar.m +145 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSDictionary+ObjectiveSugar.h +18 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSDictionary+ObjectiveSugar.m +42 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSMutableArray+ObjectiveSugar.h +18 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSMutableArray+ObjectiveSugar.m +37 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSNumber+ObjectiveSugar.h +45 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSNumber+ObjectiveSugar.m +117 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSSet+ObjectiveSugar.h +21 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSSet+ObjectiveSugar.m +50 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSString+ObjectiveSugar.h +17 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSString+ObjectiveSugar.m +43 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/ObjectiveSugar.h +10 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SSKeychain/SSKeychain.h +160 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SSKeychain/SSKeychain.m +79 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SSKeychain/SSKeychainQuery.h +80 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SSKeychain/SSKeychainQuery.m +228 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SecureUDID/SecureUDID.h +75 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SecureUDID/SecureUDID.m +679 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/NSString+Versioning.h +41 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/NSString+Versioning.m +71 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SS+Functions.h +38 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SS+Functions.m +122 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SS+System.h +41 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SS+System.m +132 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SSArguments.h +156 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSArray+JavaScript.h +46 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSArray+JavaScript.m +135 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSDate+JavaScript.h +42 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSDate+JavaScript.m +90 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSDictionary+JavaScript.h +38 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSDictionary+JavaScript.m +38 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableArray+JavaScript.h +46 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableArray+JavaScript.m +131 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableDictionary+JavaScript.h +36 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableDictionary+JavaScript.m +42 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableString+JavaScript.h +36 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableString+JavaScript.m +42 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSNumber+JavaScript.h +36 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSNumber+JavaScript.m +44 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSObject+JavaScript.h +50 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSObject+JavaScript.m +114 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSString+JavaScript.h +43 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSString+JavaScript.m +94 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/SS+JavaScript.h +52 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/SS+JavaScript.m +168 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/SS+Types.h +42 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/SS+Types.m +42 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/NSDictionary+NamedProperties.h +41 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/NSDictionary+NamedProperties.m +271 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/SS.h +36 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/SS.m +38 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/SubjectiveScript.h +67 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/SubjectiveScript.m +30 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSArray+SS.h +58 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSArray+SS.m +152 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSDate+SS.h +37 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSDate+SS.m +47 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSDictionary+SS.h +45 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSDictionary+SS.m +61 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableArray+SS.h +43 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableArray+SS.m +83 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableDictionary+SS.h +47 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableDictionary+SS.m +100 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableString+SS.h +42 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableString+SS.m +70 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSNumber+SS.h +51 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSNumber+SS.m +86 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSObject+SS.h +54 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSObject+SS.m +153 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSString+SS.h +46 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSString+SS.m +91 -0
- data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/SSTypes.h +67 -0
- data/lib/appjam/generators/templates/blank/EiffelApplicationTests/EiffelApplicationTests-Info.plist +1 -1
- data/lib/appjam/generators/templates/blank/EiffelApplicationTests/EiffelApplicationTests.h.tt +5 -5
- data/lib/appjam/generators/templates/blank/EiffelApplicationTests/EiffelApplicationTests.m.tt +7 -7
- data/lib/appjam/version.rb +1 -1
- metadata +84 -3
@@ -0,0 +1,228 @@
|
|
1
|
+
//
|
2
|
+
// SSKeychainQuery.m
|
3
|
+
// SSKeychain
|
4
|
+
//
|
5
|
+
// Created by Caleb Davenport on 3/19/13.
|
6
|
+
// Copyright (c) 2013 Sam Soffes. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
#import "SSKeychainQuery.h"
|
10
|
+
#import "SSKeychain.h"
|
11
|
+
|
12
|
+
@implementation SSKeychainQuery
|
13
|
+
|
14
|
+
@synthesize account = _account;
|
15
|
+
@synthesize service = _service;
|
16
|
+
@synthesize label = _label;
|
17
|
+
@synthesize passwordData = _passwordData;
|
18
|
+
|
19
|
+
#if __IPHONE_3_0 && TARGET_OS_IPHONE
|
20
|
+
@synthesize accessGroup = _accessGroup;
|
21
|
+
#endif
|
22
|
+
|
23
|
+
|
24
|
+
#pragma mark - Public
|
25
|
+
|
26
|
+
- (BOOL)save:(NSError *__autoreleasing *)error {
|
27
|
+
OSStatus status = SSKeychainErrorBadArguments;
|
28
|
+
if (!self.service || !self.account || !self.passwordData) {
|
29
|
+
if (error) {
|
30
|
+
*error = [[self class] errorWithCode:status];
|
31
|
+
}
|
32
|
+
return NO;
|
33
|
+
}
|
34
|
+
|
35
|
+
[self delete:nil];
|
36
|
+
|
37
|
+
NSMutableDictionary *query = [self query];
|
38
|
+
[query setObject:self.passwordData forKey:(__bridge id)kSecValueData];
|
39
|
+
if (self.label) {
|
40
|
+
[query setObject:self.label forKey:(__bridge id)kSecAttrLabel];
|
41
|
+
}
|
42
|
+
#if __IPHONE_4_0 && TARGET_OS_IPHONE
|
43
|
+
CFTypeRef accessibilityType = [SSKeychain accessibilityType];
|
44
|
+
if (accessibilityType) {
|
45
|
+
[query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible];
|
46
|
+
}
|
47
|
+
#endif
|
48
|
+
status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
|
49
|
+
|
50
|
+
if (status != errSecSuccess && error != NULL) {
|
51
|
+
*error = [[self class] errorWithCode:status];
|
52
|
+
}
|
53
|
+
|
54
|
+
return (status == errSecSuccess);
|
55
|
+
}
|
56
|
+
|
57
|
+
|
58
|
+
- (BOOL)delete:(NSError *__autoreleasing *)error {
|
59
|
+
OSStatus status = SSKeychainErrorBadArguments;
|
60
|
+
if (!self.service || !self.account) {
|
61
|
+
if (error) {
|
62
|
+
*error = [[self class] errorWithCode:status];
|
63
|
+
}
|
64
|
+
return NO;
|
65
|
+
}
|
66
|
+
|
67
|
+
NSMutableDictionary *query = [self query];
|
68
|
+
#if TARGET_OS_IPHONE
|
69
|
+
status = SecItemDelete((__bridge CFDictionaryRef)query);
|
70
|
+
#else
|
71
|
+
CFTypeRef result = NULL;
|
72
|
+
[query setObject:@YES forKey:(__bridge id)kSecReturnRef];
|
73
|
+
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
|
74
|
+
if (status == errSecSuccess) {
|
75
|
+
status = SecKeychainItemDelete((SecKeychainItemRef)result);
|
76
|
+
CFRelease(result);
|
77
|
+
}
|
78
|
+
#endif
|
79
|
+
|
80
|
+
if (status != errSecSuccess && error != NULL) {
|
81
|
+
*error = [[self class] errorWithCode:status];
|
82
|
+
}
|
83
|
+
|
84
|
+
return (status == errSecSuccess);
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
- (NSArray *)fetchAll:(NSError *__autoreleasing *)error {
|
89
|
+
OSStatus status = SSKeychainErrorBadArguments;
|
90
|
+
NSMutableDictionary *query = [self query];
|
91
|
+
[query setObject:@YES forKey:(__bridge id)kSecReturnAttributes];
|
92
|
+
[query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
|
93
|
+
|
94
|
+
CFTypeRef result = NULL;
|
95
|
+
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
|
96
|
+
if (status != errSecSuccess && error != NULL) {
|
97
|
+
*error = [[self class] errorWithCode:status];
|
98
|
+
return nil;
|
99
|
+
}
|
100
|
+
|
101
|
+
return (__bridge NSArray *)result;
|
102
|
+
}
|
103
|
+
|
104
|
+
|
105
|
+
- (BOOL)fetch:(NSError *__autoreleasing *)error {
|
106
|
+
OSStatus status = SSKeychainErrorBadArguments;
|
107
|
+
if (!self.service || !self.account) {
|
108
|
+
if (error) {
|
109
|
+
*error = [[self class] errorWithCode:status];
|
110
|
+
}
|
111
|
+
return NO;
|
112
|
+
}
|
113
|
+
|
114
|
+
CFTypeRef result = NULL;
|
115
|
+
NSMutableDictionary *query = [self query];
|
116
|
+
[query setObject:@YES forKey:(__bridge_transfer id)kSecReturnData];
|
117
|
+
[query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
|
118
|
+
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
|
119
|
+
|
120
|
+
if (status != errSecSuccess && error != NULL) {
|
121
|
+
*error = [[self class] errorWithCode:status];
|
122
|
+
return NO;
|
123
|
+
}
|
124
|
+
|
125
|
+
self.passwordData = (__bridge_transfer NSData *)result;
|
126
|
+
return YES;
|
127
|
+
}
|
128
|
+
|
129
|
+
|
130
|
+
#pragma mark - Accessors
|
131
|
+
|
132
|
+
- (void)setPassword:(NSString *)password {
|
133
|
+
self.passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
|
134
|
+
}
|
135
|
+
|
136
|
+
|
137
|
+
- (NSString *)password {
|
138
|
+
if (self.passwordData) {
|
139
|
+
return [[NSString alloc] initWithData:self.passwordData encoding:NSUTF8StringEncoding];
|
140
|
+
}
|
141
|
+
return nil;
|
142
|
+
}
|
143
|
+
|
144
|
+
|
145
|
+
#pragma mark - Private
|
146
|
+
|
147
|
+
- (NSMutableDictionary *)query {
|
148
|
+
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:3];
|
149
|
+
[dictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
|
150
|
+
|
151
|
+
if (self.service) {
|
152
|
+
[dictionary setObject:self.service forKey:(__bridge id)kSecAttrService];
|
153
|
+
}
|
154
|
+
|
155
|
+
if (self.account) {
|
156
|
+
[dictionary setObject:self.account forKey:(__bridge id)kSecAttrAccount];
|
157
|
+
}
|
158
|
+
|
159
|
+
#if __IPHONE_3_0 && TARGET_OS_IPHONE
|
160
|
+
if (self.accessGroup) {
|
161
|
+
[dictionary setObject:self.accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
|
162
|
+
}
|
163
|
+
#endif
|
164
|
+
|
165
|
+
return dictionary;
|
166
|
+
}
|
167
|
+
|
168
|
+
|
169
|
+
+ (NSError *)errorWithCode:(OSStatus) code {
|
170
|
+
NSString *message = nil;
|
171
|
+
switch (code) {
|
172
|
+
case errSecSuccess: return nil;
|
173
|
+
case SSKeychainErrorBadArguments: message = @"Some of the arguments were invalid"; break;
|
174
|
+
|
175
|
+
#if TARGET_OS_IPHONE
|
176
|
+
case errSecUnimplemented: {
|
177
|
+
message = @"Function or operation not implemented";
|
178
|
+
break;
|
179
|
+
}
|
180
|
+
case errSecParam: {
|
181
|
+
message = @"One or more parameters passed to a function were not valid";
|
182
|
+
break;
|
183
|
+
}
|
184
|
+
case errSecAllocate: {
|
185
|
+
message = @"Failed to allocate memory";
|
186
|
+
break;
|
187
|
+
}
|
188
|
+
case errSecNotAvailable: {
|
189
|
+
message = @"No keychain is available. You may need to restart your computer";
|
190
|
+
break;
|
191
|
+
}
|
192
|
+
case errSecDuplicateItem: {
|
193
|
+
message = @"The specified item already exists in the keychain";
|
194
|
+
break;
|
195
|
+
}
|
196
|
+
case errSecItemNotFound: {
|
197
|
+
message = @"The specified item could not be found in the keychain";
|
198
|
+
break;
|
199
|
+
}
|
200
|
+
case errSecInteractionNotAllowed: {
|
201
|
+
message = @"User interaction is not allowed";
|
202
|
+
break;
|
203
|
+
}
|
204
|
+
case errSecDecode: {
|
205
|
+
message = @"Unable to decode the provided data";
|
206
|
+
break;
|
207
|
+
}
|
208
|
+
case errSecAuthFailed: {
|
209
|
+
message = @"The user name or passphrase you entered is not correct";
|
210
|
+
break;
|
211
|
+
}
|
212
|
+
default: {
|
213
|
+
message = @"Refer to SecBase.h for description";
|
214
|
+
}
|
215
|
+
#else
|
216
|
+
default:
|
217
|
+
message = (__bridge_transfer NSString *)SecCopyErrorMessageString(code, NULL);
|
218
|
+
#endif
|
219
|
+
}
|
220
|
+
|
221
|
+
NSDictionary *userInfo = nil;
|
222
|
+
if (message != nil) {
|
223
|
+
userInfo = @{ NSLocalizedDescriptionKey : message };
|
224
|
+
}
|
225
|
+
return [NSError errorWithDomain:kSSKeychainErrorDomain code:code userInfo:userInfo];
|
226
|
+
}
|
227
|
+
|
228
|
+
@end
|
data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SecureUDID/SecureUDID.h
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
//
|
2
|
+
// SecureUDID.h
|
3
|
+
// SecureUDID
|
4
|
+
//
|
5
|
+
// Created by Crashlytics Team on 3/22/12.
|
6
|
+
// Copyright (c) 2012 Crashlytics, Inc. All rights reserved.
|
7
|
+
// http://www.crashlytics.com
|
8
|
+
// info@crashlytics.com
|
9
|
+
//
|
10
|
+
|
11
|
+
/*
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
13
|
+
this software and associated documentation files (the "Software"), to deal in
|
14
|
+
the Software without restriction, including without limitation the rights to
|
15
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
16
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
17
|
+
so, subject to the following conditions:
|
18
|
+
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
20
|
+
copies or substantial portions of the Software.
|
21
|
+
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
28
|
+
SOFTWARE.
|
29
|
+
*/
|
30
|
+
|
31
|
+
#import <Foundation/Foundation.h>
|
32
|
+
|
33
|
+
@interface SecureUDID : NSObject
|
34
|
+
|
35
|
+
/*
|
36
|
+
Returns a unique id for the device, sandboxed to the domain and salt provided. This is a potentially
|
37
|
+
expensive call. You should not do this on the main thread, especially during launch.
|
38
|
+
|
39
|
+
retrieveUDIDForDomain:usingKey:completion: is provided as an alternative for your 4.0+ coding convenience.
|
40
|
+
|
41
|
+
Example usage:
|
42
|
+
#import "SecureUDID.h"
|
43
|
+
|
44
|
+
NSString *udid = [SecureUDID UDIDForDomain:@"com.example.myapp" key:@"difficult-to-guess-key"];
|
45
|
+
|
46
|
+
*/
|
47
|
+
+ (NSString *)UDIDForDomain:(NSString *)domain usingKey:(NSString *)key;
|
48
|
+
|
49
|
+
/*
|
50
|
+
Getting a SecureUDID can be very expensive. Use this call to derive an identifier in the background,
|
51
|
+
and invoke a block when ready. Use of this method implies a device running >= iOS 4.0.
|
52
|
+
|
53
|
+
Example usage:
|
54
|
+
#import "SecureUDID.h"
|
55
|
+
|
56
|
+
[SecureUDID retrieveUDIDForDomain:@"com.example.myapp" usingKey:@"difficult-to-guess-key" completion:^(NSString *identifier) {
|
57
|
+
// make use of identifier here
|
58
|
+
}];
|
59
|
+
|
60
|
+
*/
|
61
|
+
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000
|
62
|
+
+ (void)retrieveUDIDForDomain:(NSString *)domain usingKey:(NSString *)key completion:(void (^)(NSString* identifier))completion;
|
63
|
+
#endif
|
64
|
+
|
65
|
+
/*
|
66
|
+
Indicates that the system has been disabled via the Opt-Out mechansim.
|
67
|
+
*/
|
68
|
+
+ (BOOL)isOptedOut;
|
69
|
+
|
70
|
+
@end
|
71
|
+
|
72
|
+
/*
|
73
|
+
This identifier is returned when Opt-Out is enabled.
|
74
|
+
*/
|
75
|
+
extern NSString *const SUUIDDefaultIdentifier;
|
data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SecureUDID/SecureUDID.m
ADDED
@@ -0,0 +1,679 @@
|
|
1
|
+
//
|
2
|
+
// SecureUDID.m
|
3
|
+
// SecureUDID
|
4
|
+
//
|
5
|
+
// Created by Crashlytics Team on 3/22/12.
|
6
|
+
// Copyright (c) 2012 Crashlytics, Inc. All rights reserved.
|
7
|
+
// http://www.crashlytics.com
|
8
|
+
// info@crashlytics.com
|
9
|
+
//
|
10
|
+
|
11
|
+
/*
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
13
|
+
this software and associated documentation files (the "Software"), to deal in
|
14
|
+
the Software without restriction, including without limitation the rights to
|
15
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
16
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
17
|
+
so, subject to the following conditions:
|
18
|
+
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
20
|
+
copies or substantial portions of the Software.
|
21
|
+
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
28
|
+
SOFTWARE.
|
29
|
+
*/
|
30
|
+
|
31
|
+
#import "SecureUDID.h"
|
32
|
+
#import <UIKit/UIKit.h>
|
33
|
+
#import <CommonCrypto/CommonCryptor.h>
|
34
|
+
#import <CommonCrypto/CommonDigest.h>
|
35
|
+
#import <sys/sysctl.h>
|
36
|
+
|
37
|
+
#define SUUID_SCHEMA_VERSION (1)
|
38
|
+
#define SUUID_MAX_STORAGE_LOCATIONS (64)
|
39
|
+
|
40
|
+
NSString *const SUUIDDefaultIdentifier = @"00000000-0000-0000-0000-000000000000";
|
41
|
+
|
42
|
+
NSString *const SUUIDTypeDataDictionary = @"public.secureudid";
|
43
|
+
NSString *const SUUIDTimeStampKey = @"SUUIDTimeStampKey";
|
44
|
+
NSString *const SUUIDOwnerKey = @"SUUIDOwnerKey";
|
45
|
+
NSString *const SUUIDLastAccessedKey = @"SUUIDLastAccessedKey";
|
46
|
+
NSString *const SUUIDIdentifierKey = @"SUUIDIdentifierKey";
|
47
|
+
NSString *const SUUIDOptOutKey = @"SUUIDOptOutKey";
|
48
|
+
NSString *const SUUIDModelHashKey = @"SUUIDModelHashKey";
|
49
|
+
NSString *const SUUIDSchemaVersionKey = @"SUUIDSchemaVersionKey";
|
50
|
+
NSString *const SUUIDPastboardFileFormat = @"org.secureudid-%d";
|
51
|
+
|
52
|
+
NSData *SUUIDCryptorToData(CCOperation operation, NSData *value, NSData *key);
|
53
|
+
NSString *SUUIDCryptorToString(CCOperation operation, NSData *value, NSData *key);
|
54
|
+
NSData *SUUIDHash(NSData* data);
|
55
|
+
NSData *SUUIDModelHash(void);
|
56
|
+
|
57
|
+
void SUUIDMarkOptedOut(void);
|
58
|
+
void SUUIDMarkOptedIn(void);
|
59
|
+
void SUUIDRemoveAllSecureUDIDData(void);
|
60
|
+
NSString *SUUIDPasteboardNameForNumber(NSInteger number);
|
61
|
+
NSInteger SUUIDStorageLocationForOwnerKey(NSData *key, NSMutableDictionary** dictionary);
|
62
|
+
NSDictionary *SUUIDDictionaryForStorageLocation(NSInteger number);
|
63
|
+
NSDictionary *SUUIDMostRecentDictionary(void);
|
64
|
+
void SUUIDWriteDictionaryToStorageLocation(NSInteger number, NSDictionary* dictionary);
|
65
|
+
void SUUIDDeleteStorageLocation(NSInteger number);
|
66
|
+
|
67
|
+
BOOL SUUIDValidTopLevelObject(id object);
|
68
|
+
BOOL SUUIDValidOwnerObject(id object);
|
69
|
+
|
70
|
+
@implementation SecureUDID
|
71
|
+
|
72
|
+
/*
|
73
|
+
Returns a unique id for the device, sandboxed to the domain and salt provided.
|
74
|
+
|
75
|
+
Example usage:
|
76
|
+
#import "SecureUDID.h"
|
77
|
+
|
78
|
+
NSString *udid = [SecureUDID UDIDForDomain:@"com.example.myapp" salt:@"superSecretCodeHere!@##%#$#%$^"];
|
79
|
+
|
80
|
+
*/
|
81
|
+
+ (NSString *)UDIDForDomain:(NSString *)domain usingKey:(NSString *)key {
|
82
|
+
NSString *identifier = SUUIDDefaultIdentifier;
|
83
|
+
|
84
|
+
// Salt the domain to make the crypt keys affectively unguessable.
|
85
|
+
NSData *domainAndKey = [[NSString stringWithFormat:@"%@%@", domain, key] dataUsingEncoding:NSUTF8StringEncoding];
|
86
|
+
NSData *ownerKey = SUUIDHash(domainAndKey);
|
87
|
+
|
88
|
+
// Encrypt the salted domain key and load the pasteboard on which to store data
|
89
|
+
NSData *encryptedOwnerKey = SUUIDCryptorToData(kCCEncrypt, [domain dataUsingEncoding:NSUTF8StringEncoding], ownerKey);
|
90
|
+
|
91
|
+
// @synchronized introduces an implicit @try-@finally, so care needs to be taken with the return value
|
92
|
+
@synchronized (self) {
|
93
|
+
NSMutableDictionary *topLevelDictionary = nil;
|
94
|
+
|
95
|
+
// Retrieve an appropriate storage index for this owner
|
96
|
+
NSInteger ownerIndex = SUUIDStorageLocationForOwnerKey(encryptedOwnerKey, &topLevelDictionary);
|
97
|
+
|
98
|
+
// If the model hash key is present, verify it, otherwise add it
|
99
|
+
NSData *storedModelHash = [topLevelDictionary objectForKey:SUUIDModelHashKey];
|
100
|
+
NSData *modelHash = SUUIDModelHash();
|
101
|
+
|
102
|
+
if (storedModelHash) {
|
103
|
+
if (![modelHash isEqual:storedModelHash]) {
|
104
|
+
// The model hashes do not match - this structure is invalid
|
105
|
+
[topLevelDictionary removeAllObjects];
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
// store the current model hash
|
110
|
+
[topLevelDictionary setObject:modelHash forKey:SUUIDModelHashKey];
|
111
|
+
|
112
|
+
// check for the opt-out flag and return the default identifier if we find it
|
113
|
+
if ([[topLevelDictionary objectForKey:SUUIDOptOutKey] boolValue] == YES) {
|
114
|
+
return identifier;
|
115
|
+
}
|
116
|
+
|
117
|
+
// If we encounter a schema version greater than we support, there is no simple alternative
|
118
|
+
// other than to simulate Opt Out. Any writes to the store risk corruption.
|
119
|
+
if ([[topLevelDictionary objectForKey:SUUIDSchemaVersionKey] intValue] > SUUID_SCHEMA_VERSION) {
|
120
|
+
return identifier;
|
121
|
+
}
|
122
|
+
|
123
|
+
// Attempt to get the owner's dictionary. Should we get back nil from the encryptedDomain key, we'll still
|
124
|
+
// get a valid, empty mutable dictionary
|
125
|
+
NSMutableDictionary *ownerDictionary = [NSMutableDictionary dictionaryWithDictionary:[topLevelDictionary objectForKey:encryptedOwnerKey]];
|
126
|
+
|
127
|
+
// Set our last access time and claim ownership for this storage location.
|
128
|
+
NSDate* lastAccessDate = [NSDate date];
|
129
|
+
|
130
|
+
[ownerDictionary setObject:lastAccessDate forKey:SUUIDLastAccessedKey];
|
131
|
+
[topLevelDictionary setObject:lastAccessDate forKey:SUUIDTimeStampKey];
|
132
|
+
[topLevelDictionary setObject:encryptedOwnerKey forKey:SUUIDOwnerKey];
|
133
|
+
|
134
|
+
[topLevelDictionary setObject:[NSNumber numberWithInt:SUUID_SCHEMA_VERSION] forKey:SUUIDSchemaVersionKey];
|
135
|
+
|
136
|
+
// Make sure our owner dictionary is in the top level structure
|
137
|
+
[topLevelDictionary setObject:ownerDictionary forKey:encryptedOwnerKey];
|
138
|
+
|
139
|
+
|
140
|
+
NSData *identifierData = [ownerDictionary objectForKey:SUUIDIdentifierKey];
|
141
|
+
if (identifierData) {
|
142
|
+
identifier = SUUIDCryptorToString(kCCDecrypt, identifierData, ownerKey);
|
143
|
+
if (!identifier) {
|
144
|
+
// We've failed to decrypt our identifier. This is a sign of storage corruption.
|
145
|
+
SUUIDDeleteStorageLocation(ownerIndex);
|
146
|
+
|
147
|
+
// return here - do not write values back to the store
|
148
|
+
return SUUIDDefaultIdentifier;
|
149
|
+
}
|
150
|
+
} else {
|
151
|
+
// Otherwise, create a new RFC-4122 Version 4 UUID
|
152
|
+
// http://en.wikipedia.org/wiki/Universally_unique_identifier
|
153
|
+
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
|
154
|
+
identifier = [(NSString*)CFUUIDCreateString(kCFAllocatorDefault, uuid) autorelease];
|
155
|
+
CFRelease(uuid);
|
156
|
+
|
157
|
+
// Encrypt it for storage.
|
158
|
+
NSData *data = SUUIDCryptorToData(kCCEncrypt, [identifier dataUsingEncoding:NSUTF8StringEncoding], ownerKey);
|
159
|
+
|
160
|
+
[ownerDictionary setObject:data forKey:SUUIDIdentifierKey];
|
161
|
+
}
|
162
|
+
|
163
|
+
SUUIDWriteDictionaryToStorageLocation(ownerIndex, topLevelDictionary);
|
164
|
+
}
|
165
|
+
|
166
|
+
return identifier;
|
167
|
+
}
|
168
|
+
|
169
|
+
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000
|
170
|
+
+ (void)retrieveUDIDForDomain:(NSString *)domain usingKey:(NSString *)key completion:(void (^)(NSString* identifier))completion {
|
171
|
+
// retreive the identifier on a low-priority thread
|
172
|
+
|
173
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
174
|
+
NSString* identifier;
|
175
|
+
|
176
|
+
identifier = [SecureUDID UDIDForDomain:domain usingKey:key];
|
177
|
+
|
178
|
+
completion(identifier);
|
179
|
+
});
|
180
|
+
}
|
181
|
+
#endif
|
182
|
+
|
183
|
+
/*
|
184
|
+
API to determine if a device has opted out of SecureUDID.
|
185
|
+
*/
|
186
|
+
+ (BOOL)isOptedOut {
|
187
|
+
for (NSInteger i = 0; i < SUUID_MAX_STORAGE_LOCATIONS; ++i) {
|
188
|
+
NSDictionary* topLevelDictionary;
|
189
|
+
|
190
|
+
topLevelDictionary = SUUIDDictionaryForStorageLocation(i);
|
191
|
+
if (!topLevelDictionary) {
|
192
|
+
continue;
|
193
|
+
}
|
194
|
+
|
195
|
+
if ([[topLevelDictionary objectForKey:SUUIDOptOutKey] boolValue] == YES) {
|
196
|
+
return YES;
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
return NO;
|
201
|
+
}
|
202
|
+
|
203
|
+
/*
|
204
|
+
Applies the operation (encrypt or decrypt) to the NSData value with the provided NSData key
|
205
|
+
and returns the value as NSData.
|
206
|
+
*/
|
207
|
+
NSData *SUUIDCryptorToData(CCOperation operation, NSData *value, NSData *key) {
|
208
|
+
NSMutableData *output = [NSMutableData dataWithLength:value.length + kCCBlockSizeAES128];
|
209
|
+
|
210
|
+
size_t numBytes = 0;
|
211
|
+
CCCryptorStatus cryptStatus = CCCrypt(operation,
|
212
|
+
kCCAlgorithmAES128,
|
213
|
+
kCCOptionPKCS7Padding,
|
214
|
+
[key bytes],
|
215
|
+
kCCKeySizeAES128,
|
216
|
+
NULL,
|
217
|
+
value.bytes,
|
218
|
+
value.length,
|
219
|
+
output.mutableBytes,
|
220
|
+
output.length,
|
221
|
+
&numBytes);
|
222
|
+
|
223
|
+
if (cryptStatus == kCCSuccess) {
|
224
|
+
return [NSData dataWithBytes:output.bytes length:numBytes];
|
225
|
+
}
|
226
|
+
|
227
|
+
return nil;
|
228
|
+
}
|
229
|
+
|
230
|
+
/*
|
231
|
+
Applies the operation (encrypt or decrypt) to the NSData value with the provided NSData key
|
232
|
+
and returns the value as an NSString.
|
233
|
+
*/
|
234
|
+
NSString *SUUIDCryptorToString(CCOperation operation, NSData *value, NSData *key) {
|
235
|
+
NSData* data;
|
236
|
+
|
237
|
+
data = SUUIDCryptorToData(operation, value, key);
|
238
|
+
if (!data) {
|
239
|
+
return nil;
|
240
|
+
}
|
241
|
+
|
242
|
+
return [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
|
243
|
+
}
|
244
|
+
|
245
|
+
/*
|
246
|
+
Compute a SHA1 of the input.
|
247
|
+
*/
|
248
|
+
NSData *SUUIDHash(NSData *data) {
|
249
|
+
uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
|
250
|
+
|
251
|
+
CC_SHA1(data.bytes, data.length, digest);
|
252
|
+
|
253
|
+
return [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
|
254
|
+
}
|
255
|
+
|
256
|
+
NSData* SUUIDModelHash(void) {
|
257
|
+
NSString* result;
|
258
|
+
|
259
|
+
result = nil;
|
260
|
+
|
261
|
+
do {
|
262
|
+
size_t size;
|
263
|
+
|
264
|
+
// first get the size
|
265
|
+
if (sysctlbyname("hw.machine", NULL, &size, NULL, 0) != 0) {
|
266
|
+
break;
|
267
|
+
}
|
268
|
+
|
269
|
+
char value[size];
|
270
|
+
|
271
|
+
// now get the value
|
272
|
+
if (sysctlbyname("hw.machine", value, &size, NULL, 0) != 0) {
|
273
|
+
break;
|
274
|
+
}
|
275
|
+
|
276
|
+
// convert the value to an NSString
|
277
|
+
result = [NSString stringWithCString:value encoding:NSUTF8StringEncoding];
|
278
|
+
} while (0);
|
279
|
+
|
280
|
+
if (!result) {
|
281
|
+
result = @"Unknown";
|
282
|
+
}
|
283
|
+
|
284
|
+
return SUUIDHash([result dataUsingEncoding:NSUTF8StringEncoding]);
|
285
|
+
}
|
286
|
+
|
287
|
+
/*
|
288
|
+
Finds the most recent structure, and adds the Opt-Out flag to it. Then writes that structure back
|
289
|
+
out to all used storage locations, making sure to preserve ownership.
|
290
|
+
*/
|
291
|
+
void SUUIDMarkOptedOut(void) {
|
292
|
+
NSMutableDictionary* mostRecentDictionary;
|
293
|
+
|
294
|
+
mostRecentDictionary = [NSMutableDictionary dictionaryWithDictionary:SUUIDMostRecentDictionary()];
|
295
|
+
|
296
|
+
[mostRecentDictionary setObject:[NSDate date] forKey:SUUIDTimeStampKey];
|
297
|
+
[mostRecentDictionary setObject:[NSNumber numberWithBool:YES] forKey:SUUIDOptOutKey];
|
298
|
+
|
299
|
+
for (NSInteger i = 0; i < SUUID_MAX_STORAGE_LOCATIONS; ++i) {
|
300
|
+
NSData* owner;
|
301
|
+
|
302
|
+
// Inherit the owner, if it is present. This makes some schema assumptions.
|
303
|
+
owner = [SUUIDDictionaryForStorageLocation(i) objectForKey:SUUIDOwnerKey];
|
304
|
+
if (owner) {
|
305
|
+
[mostRecentDictionary setObject:owner forKey:SUUIDOwnerKey];
|
306
|
+
}
|
307
|
+
|
308
|
+
// write the opt-out data even if the location was previously empty
|
309
|
+
SUUIDWriteDictionaryToStorageLocation(i, mostRecentDictionary);
|
310
|
+
}
|
311
|
+
}
|
312
|
+
|
313
|
+
void SUUIDMarkOptedIn(void) {
|
314
|
+
NSDate* accessedDate;
|
315
|
+
|
316
|
+
accessedDate = [NSDate date];
|
317
|
+
|
318
|
+
// Opting back in is trickier. We need to remove top-level Opt-Out markers. Also makes some
|
319
|
+
// schema assumptions.
|
320
|
+
for (NSInteger i = 0; i < SUUID_MAX_STORAGE_LOCATIONS; ++i) {
|
321
|
+
NSMutableDictionary* dictionary;
|
322
|
+
|
323
|
+
dictionary = [NSMutableDictionary dictionaryWithDictionary:SUUIDDictionaryForStorageLocation(i)];
|
324
|
+
if (!dictionary) {
|
325
|
+
// This is a possible indiction of storage corruption. However, SUUIDDictionaryForStorageLocation
|
326
|
+
// will have already cleaned it up for us, so there's not much to do here.
|
327
|
+
continue;
|
328
|
+
}
|
329
|
+
|
330
|
+
[dictionary removeObjectForKey:SUUIDOptOutKey];
|
331
|
+
|
332
|
+
// quick check for the minimum set of keys. If the dictionary previously held just
|
333
|
+
// an Opt-Out marker + timestamp, dictionary is not invalid. Writing will fail in this
|
334
|
+
// case, leaving the data that was there. We need to delete.
|
335
|
+
if (!SUUIDValidTopLevelObject(dictionary)) {
|
336
|
+
SUUIDDeleteStorageLocation(i);
|
337
|
+
continue;
|
338
|
+
}
|
339
|
+
|
340
|
+
[dictionary setObject:accessedDate forKey:SUUIDTimeStampKey];
|
341
|
+
|
342
|
+
SUUIDWriteDictionaryToStorageLocation(i, dictionary);
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
/*
|
347
|
+
Removes all SecureUDID data from storage with the exception of Opt-Out flags, which
|
348
|
+
are never removed. Removing the Opt-Out flags would effectively opt a user back in.
|
349
|
+
*/
|
350
|
+
void SUUIDRemoveAllSecureUDIDData(void) {
|
351
|
+
NSDictionary* optOutPlaceholder = nil;
|
352
|
+
|
353
|
+
if ([SecureUDID isOptedOut]) {
|
354
|
+
optOutPlaceholder = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:SUUIDOptOutKey];
|
355
|
+
}
|
356
|
+
|
357
|
+
for (NSInteger i = 0; i < SUUID_MAX_STORAGE_LOCATIONS; ++i) {
|
358
|
+
if (optOutPlaceholder) {
|
359
|
+
SUUIDWriteDictionaryToStorageLocation(i, optOutPlaceholder);
|
360
|
+
continue;
|
361
|
+
}
|
362
|
+
|
363
|
+
SUUIDDeleteStorageLocation(i);
|
364
|
+
}
|
365
|
+
}
|
366
|
+
|
367
|
+
/*
|
368
|
+
Returns an NSString formatted with the supplied number.
|
369
|
+
*/
|
370
|
+
NSString *SUUIDPasteboardNameForNumber(NSInteger number) {
|
371
|
+
return [NSString stringWithFormat:SUUIDPastboardFileFormat, number];
|
372
|
+
}
|
373
|
+
|
374
|
+
/*
|
375
|
+
Reads a dictionary from a storage location. Validation occurs once data
|
376
|
+
is read, but before it is returned. If something fails, or if the read structure
|
377
|
+
is invalid, the location is cleared.
|
378
|
+
|
379
|
+
Returns the data dictionary, or nil on failure.
|
380
|
+
*/
|
381
|
+
NSDictionary *SUUIDDictionaryForStorageLocation(NSInteger number) {
|
382
|
+
id decodedObject;
|
383
|
+
UIPasteboard* pasteboard;
|
384
|
+
NSData* data;
|
385
|
+
|
386
|
+
// Don't even bother if the index is outside our limits
|
387
|
+
if (number < 0 || number >= SUUID_MAX_STORAGE_LOCATIONS) {
|
388
|
+
return nil;
|
389
|
+
}
|
390
|
+
|
391
|
+
pasteboard = [UIPasteboard pasteboardWithName:SUUIDPasteboardNameForNumber(number) create:NO];
|
392
|
+
if (!pasteboard) {
|
393
|
+
return nil;
|
394
|
+
}
|
395
|
+
|
396
|
+
data = [pasteboard valueForPasteboardType:SUUIDTypeDataDictionary];
|
397
|
+
if (!data) {
|
398
|
+
return nil;
|
399
|
+
}
|
400
|
+
|
401
|
+
@try {
|
402
|
+
decodedObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];
|
403
|
+
} @catch (NSException* exception) {
|
404
|
+
// Catching an exception like this is risky. However, crashing here is
|
405
|
+
// not acceptable, and unarchiveObjectWithData can throw.
|
406
|
+
[pasteboard setData:nil forPasteboardType:SUUIDTypeDataDictionary];
|
407
|
+
|
408
|
+
return nil;
|
409
|
+
}
|
410
|
+
|
411
|
+
if (!SUUIDValidTopLevelObject(decodedObject)) {
|
412
|
+
[pasteboard setData:nil forPasteboardType:SUUIDTypeDataDictionary];
|
413
|
+
|
414
|
+
return nil;
|
415
|
+
}
|
416
|
+
|
417
|
+
return decodedObject;
|
418
|
+
}
|
419
|
+
|
420
|
+
NSDictionary *SUUIDMostRecentDictionary(void) {
|
421
|
+
NSDictionary* mostRecentDictionary;
|
422
|
+
BOOL found;
|
423
|
+
|
424
|
+
mostRecentDictionary = [NSDictionary dictionaryWithObject:[NSDate distantPast] forKey:SUUIDTimeStampKey];
|
425
|
+
|
426
|
+
// scan all locations looking for the most recent
|
427
|
+
for (NSUInteger i = 0; i < SUUID_MAX_STORAGE_LOCATIONS; ++i) {
|
428
|
+
NSDictionary* dictionary;
|
429
|
+
NSDate* date;
|
430
|
+
|
431
|
+
dictionary = SUUIDDictionaryForStorageLocation(i);
|
432
|
+
if (!dictionary) {
|
433
|
+
continue;
|
434
|
+
}
|
435
|
+
|
436
|
+
// Schema assumption
|
437
|
+
date = [dictionary objectForKey:SUUIDTimeStampKey];
|
438
|
+
if ([date compare:[mostRecentDictionary objectForKey:SUUIDTimeStampKey]] == NSOrderedDescending) {
|
439
|
+
mostRecentDictionary = dictionary;
|
440
|
+
found = YES;
|
441
|
+
}
|
442
|
+
}
|
443
|
+
|
444
|
+
if (!found) {
|
445
|
+
return nil;
|
446
|
+
}
|
447
|
+
|
448
|
+
return mostRecentDictionary;
|
449
|
+
}
|
450
|
+
|
451
|
+
/*
|
452
|
+
Writes out a dictionary to a storage location. That dictionary must be a 'valid'
|
453
|
+
SecureUDID structure, and the location must be within range. A new location is
|
454
|
+
created if is didn't already exist.
|
455
|
+
*/
|
456
|
+
void SUUIDWriteDictionaryToStorageLocation(NSInteger number, NSDictionary* dictionary) {
|
457
|
+
UIPasteboard* pasteboard;
|
458
|
+
|
459
|
+
// be sure to respect our limits
|
460
|
+
if (number < 0 || number >= SUUID_MAX_STORAGE_LOCATIONS) {
|
461
|
+
return;
|
462
|
+
}
|
463
|
+
|
464
|
+
// only write out valid structures
|
465
|
+
if (!SUUIDValidTopLevelObject(dictionary)) {
|
466
|
+
return;
|
467
|
+
}
|
468
|
+
|
469
|
+
pasteboard = [UIPasteboard pasteboardWithName:SUUIDPasteboardNameForNumber(number) create:YES];
|
470
|
+
if (!pasteboard) {
|
471
|
+
return;
|
472
|
+
}
|
473
|
+
|
474
|
+
pasteboard.persistent = YES;
|
475
|
+
|
476
|
+
[pasteboard setData:[NSKeyedArchiver archivedDataWithRootObject:dictionary]
|
477
|
+
forPasteboardType:SUUIDTypeDataDictionary];
|
478
|
+
}
|
479
|
+
|
480
|
+
/*
|
481
|
+
Clear a storage location, removing anything stored there. Useful for dealing with
|
482
|
+
potential corruption. Be careful with this function, as it can remove Opt-Out markers.
|
483
|
+
*/
|
484
|
+
void SUUIDDeleteStorageLocation(NSInteger number) {
|
485
|
+
UIPasteboard* pasteboard;
|
486
|
+
NSString* name;
|
487
|
+
|
488
|
+
if (number < 0 || number >= SUUID_MAX_STORAGE_LOCATIONS) {
|
489
|
+
return;
|
490
|
+
}
|
491
|
+
|
492
|
+
name = SUUIDPasteboardNameForNumber(number);
|
493
|
+
pasteboard = [UIPasteboard pasteboardWithName:name create:NO];
|
494
|
+
if (!pasteboard)
|
495
|
+
return;
|
496
|
+
|
497
|
+
// While setting pasteboard data to nil seems to always remove contents, the
|
498
|
+
// removePasteboardWithName: call doesn't appear to always work. Using both seems
|
499
|
+
// like the safest thing to do
|
500
|
+
[pasteboard setData:nil forPasteboardType:SUUIDTypeDataDictionary];
|
501
|
+
[UIPasteboard removePasteboardWithName:name];
|
502
|
+
}
|
503
|
+
|
504
|
+
/*
|
505
|
+
SecureUDID leverages UIPasteboards to persistently store its data.
|
506
|
+
UIPasteboards marked as 'persistent' have the following attributes:
|
507
|
+
- They persist across application relaunches, device reboots, and OS upgrades.
|
508
|
+
- They are destroyed when the application that created them is deleted from the device.
|
509
|
+
|
510
|
+
To protect against the latter case, SecureUDID leverages multiple pasteboards (up to
|
511
|
+
SUUID_MAX_STORAGE_LOCATIONS), creating one for each distinct domain/app that
|
512
|
+
leverages the system. The permanence of SecureUDIDs increases exponentially with the
|
513
|
+
number of apps that use it.
|
514
|
+
|
515
|
+
This function searches for a suitable storage location for a SecureUDID structure. It
|
516
|
+
attempts to find the structure written by ownerKey. If no owner is found and there are
|
517
|
+
still open locations, the lowest numbered location is selected. If there are no
|
518
|
+
available locations, the last-written is selected.
|
519
|
+
|
520
|
+
Once a spot is found, the most-recent data is re-written over this location. The location
|
521
|
+
is then, finally, returned.
|
522
|
+
*/
|
523
|
+
NSInteger SUUIDStorageLocationForOwnerKey(NSData *ownerKey, NSMutableDictionary** ownerDictionary) {
|
524
|
+
NSInteger ownerIndex;
|
525
|
+
NSInteger lowestUnusedIndex;
|
526
|
+
NSInteger oldestUsedIndex;
|
527
|
+
NSDate* mostRecentDate;
|
528
|
+
NSDate* oldestUsedDate;
|
529
|
+
NSDictionary* mostRecentDictionary;
|
530
|
+
BOOL optedOut;
|
531
|
+
|
532
|
+
ownerIndex = -1;
|
533
|
+
lowestUnusedIndex = -1;
|
534
|
+
oldestUsedIndex = 0; // make sure this value is always in range
|
535
|
+
mostRecentDate = [NSDate distantPast];
|
536
|
+
oldestUsedDate = [NSDate distantFuture];
|
537
|
+
mostRecentDictionary = nil;
|
538
|
+
optedOut = NO;
|
539
|
+
|
540
|
+
// The array of SecureUDID pasteboards can be sparse, since any number of
|
541
|
+
// apps may have been deleted. To find a pasteboard owned by the the current
|
542
|
+
// domain, iterate all of them.
|
543
|
+
for (NSInteger i = 0; i < SUUID_MAX_STORAGE_LOCATIONS; ++i) {
|
544
|
+
NSDate* modifiedDate;
|
545
|
+
NSDictionary* dictionary;
|
546
|
+
|
547
|
+
dictionary = SUUIDDictionaryForStorageLocation(i);
|
548
|
+
if (!dictionary) {
|
549
|
+
if (lowestUnusedIndex == -1) {
|
550
|
+
lowestUnusedIndex = i;
|
551
|
+
}
|
552
|
+
|
553
|
+
continue;
|
554
|
+
}
|
555
|
+
|
556
|
+
// Check the 'modified' timestamp of this pasteboard
|
557
|
+
modifiedDate = [dictionary valueForKey:SUUIDTimeStampKey];
|
558
|
+
optedOut = optedOut || [[dictionary valueForKey:SUUIDOptOutKey] boolValue];
|
559
|
+
|
560
|
+
// Hold a copy of the data if this is the newest we've found so far.
|
561
|
+
if ([modifiedDate compare:mostRecentDate] == NSOrderedDescending) {
|
562
|
+
mostRecentDate = modifiedDate;
|
563
|
+
mostRecentDictionary = dictionary;
|
564
|
+
}
|
565
|
+
|
566
|
+
// Check for the oldest entry in the structure, used for eviction
|
567
|
+
if ([modifiedDate compare:oldestUsedDate] == NSOrderedAscending) {
|
568
|
+
oldestUsedDate = modifiedDate;
|
569
|
+
oldestUsedIndex = i;
|
570
|
+
}
|
571
|
+
|
572
|
+
// Finally, check if this is the pasteboard owned by the requesting domain.
|
573
|
+
if ([[dictionary objectForKey:SUUIDOwnerKey] isEqual:ownerKey]) {
|
574
|
+
ownerIndex = i;
|
575
|
+
}
|
576
|
+
}
|
577
|
+
|
578
|
+
// If no pasteboard is owned by this domain, establish a new one to increase the
|
579
|
+
// likelihood of permanence.
|
580
|
+
if (ownerIndex == -1) {
|
581
|
+
// Unless there are no available slots, then evict the oldest entry
|
582
|
+
if ((lowestUnusedIndex < 0) || (lowestUnusedIndex >= SUUID_MAX_STORAGE_LOCATIONS)) {
|
583
|
+
ownerIndex = oldestUsedIndex;
|
584
|
+
} else {
|
585
|
+
ownerIndex = lowestUnusedIndex;
|
586
|
+
}
|
587
|
+
}
|
588
|
+
|
589
|
+
// pass back the dictionary, by reference
|
590
|
+
*ownerDictionary = [NSMutableDictionary dictionaryWithDictionary:mostRecentDictionary];
|
591
|
+
|
592
|
+
// make sure our Opt-Out flag is consistent
|
593
|
+
if (optedOut) {
|
594
|
+
[*ownerDictionary setObject:[NSNumber numberWithBool:YES] forKey:SUUIDOptOutKey];
|
595
|
+
}
|
596
|
+
|
597
|
+
// Make sure to write the most recent structure to the new location
|
598
|
+
SUUIDWriteDictionaryToStorageLocation(ownerIndex, mostRecentDictionary);
|
599
|
+
|
600
|
+
return ownerIndex;
|
601
|
+
}
|
602
|
+
|
603
|
+
/*
|
604
|
+
Attempts to validate the full SecureUDID structure.
|
605
|
+
*/
|
606
|
+
BOOL SUUIDValidTopLevelObject(id object) {
|
607
|
+
if (![object isKindOfClass:[NSDictionary class]]) {
|
608
|
+
return NO;
|
609
|
+
}
|
610
|
+
|
611
|
+
// Now, we need to verify the current schema. There are a few possible valid states:
|
612
|
+
// - SUUIDTimeStampKey + SUUIDOwnerKey + at least one additional key that is not SUUIDOptOutKey
|
613
|
+
// - SUUIDTimeStampKey + SUUIDOwnerKey + SUUIDOptOutKey
|
614
|
+
|
615
|
+
if ([(NSDictionary *)object objectForKey:SUUIDTimeStampKey] && [(NSDictionary *)object objectForKey:SUUIDOwnerKey]) {
|
616
|
+
NSMutableDictionary* ownersOnlyDictionary;
|
617
|
+
NSData* ownerField;
|
618
|
+
|
619
|
+
if ([(NSDictionary *)object objectForKey:SUUIDOptOutKey]) {
|
620
|
+
return YES;
|
621
|
+
}
|
622
|
+
|
623
|
+
// We have to trust future schema versions. Note that the lack of a schema version key will
|
624
|
+
// always fail this check, since the first schema version was 1.
|
625
|
+
if ([[(NSDictionary *)object objectForKey:SUUIDSchemaVersionKey] intValue] > SUUID_SCHEMA_VERSION) {
|
626
|
+
return YES;
|
627
|
+
}
|
628
|
+
|
629
|
+
ownerField = [(NSDictionary *)object objectForKey:SUUIDOwnerKey];
|
630
|
+
if (![ownerField isKindOfClass:[NSData class]]) {
|
631
|
+
return NO;
|
632
|
+
}
|
633
|
+
|
634
|
+
ownersOnlyDictionary = [NSMutableDictionary dictionaryWithDictionary:object];
|
635
|
+
|
636
|
+
[ownersOnlyDictionary removeObjectForKey:SUUIDTimeStampKey];
|
637
|
+
[ownersOnlyDictionary removeObjectForKey:SUUIDOwnerKey];
|
638
|
+
[ownersOnlyDictionary removeObjectForKey:SUUIDOptOutKey];
|
639
|
+
[ownersOnlyDictionary removeObjectForKey:SUUIDModelHashKey];
|
640
|
+
[ownersOnlyDictionary removeObjectForKey:SUUIDSchemaVersionKey];
|
641
|
+
|
642
|
+
// now, iterate through to verify each internal structure
|
643
|
+
for (id key in [ownersOnlyDictionary allKeys]) {
|
644
|
+
if ([key isEqual:SUUIDTimeStampKey] || [key isEqual:SUUIDOwnerKey] || [key isEqual:SUUIDOptOutKey])
|
645
|
+
continue;
|
646
|
+
|
647
|
+
if (![key isKindOfClass:[NSData class]]) {
|
648
|
+
return NO;
|
649
|
+
}
|
650
|
+
|
651
|
+
if (!SUUIDValidOwnerObject([ownersOnlyDictionary objectForKey:key])) {
|
652
|
+
return NO;
|
653
|
+
}
|
654
|
+
}
|
655
|
+
|
656
|
+
// if all these tests pass, this structure is valid
|
657
|
+
return YES;
|
658
|
+
}
|
659
|
+
|
660
|
+
// Maybe just the SUUIDOptOutKey, on its own
|
661
|
+
if ([[(NSDictionary *)object objectForKey:SUUIDOptOutKey] boolValue] == YES) {
|
662
|
+
return YES;
|
663
|
+
}
|
664
|
+
|
665
|
+
return NO;
|
666
|
+
}
|
667
|
+
|
668
|
+
/*
|
669
|
+
Attempts to validate the structure for an "owner dictionary".
|
670
|
+
*/
|
671
|
+
BOOL SUUIDValidOwnerObject(id object) {
|
672
|
+
if (![object isKindOfClass:[NSDictionary class]]) {
|
673
|
+
return NO;
|
674
|
+
}
|
675
|
+
|
676
|
+
return [object valueForKey:SUUIDLastAccessedKey] && [object valueForKey:SUUIDIdentifierKey];
|
677
|
+
}
|
678
|
+
|
679
|
+
@end
|