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.
Files changed (94) hide show
  1. data/README.md +9 -1
  2. data/lib/appjam/generators/help.rb +1 -1
  3. data/lib/appjam/generators/templates/blank/EiffelApplication.xcodeproj/project.pbxproj +356 -0
  4. data/lib/appjam/generators/templates/blank/EiffelApplication.xcodeproj/project.xcworkspace/xcuserdata/eiffel.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  5. data/lib/appjam/generators/templates/blank/EiffelApplication/AppDelegate.h.tt +3 -3
  6. data/lib/appjam/generators/templates/blank/EiffelApplication/AppDelegate.m.tt +5 -5
  7. data/lib/appjam/generators/templates/blank/EiffelApplication/EiffelApplication-Info.plist +1 -1
  8. data/lib/appjam/generators/templates/blank/EiffelApplication/EiffelApplication-Prefix.pch.tt +1 -1
  9. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/Toast/Toast+UIView.h +51 -0
  10. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/Toast/Toast+UIView.m +315 -0
  11. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/frameworks/RaptureXML/RXMLElement.h +97 -0
  12. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/frameworks/RaptureXML/RXMLElement.m +450 -0
  13. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/Objective-C-HMTL-Parser/HTMLNode.h +111 -0
  14. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/Objective-C-HMTL-Parser/HTMLNode.m +412 -0
  15. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/Objective-C-HMTL-Parser/HTMLParser.h +37 -0
  16. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/Objective-C-HMTL-Parser/HTMLParser.m +130 -0
  17. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSArray+ObjectiveSugar.h +39 -0
  18. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSArray+ObjectiveSugar.m +145 -0
  19. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSDictionary+ObjectiveSugar.h +18 -0
  20. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSDictionary+ObjectiveSugar.m +42 -0
  21. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSMutableArray+ObjectiveSugar.h +18 -0
  22. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSMutableArray+ObjectiveSugar.m +37 -0
  23. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSNumber+ObjectiveSugar.h +45 -0
  24. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSNumber+ObjectiveSugar.m +117 -0
  25. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSSet+ObjectiveSugar.h +21 -0
  26. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSSet+ObjectiveSugar.m +50 -0
  27. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSString+ObjectiveSugar.h +17 -0
  28. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/NSString+ObjectiveSugar.m +43 -0
  29. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/ObjectiveSugar/ObjectiveSugar.h +10 -0
  30. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SSKeychain/SSKeychain.h +160 -0
  31. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SSKeychain/SSKeychain.m +79 -0
  32. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SSKeychain/SSKeychainQuery.h +80 -0
  33. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SSKeychain/SSKeychainQuery.m +228 -0
  34. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SecureUDID/SecureUDID.h +75 -0
  35. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SecureUDID/SecureUDID.m +679 -0
  36. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/NSString+Versioning.h +41 -0
  37. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/NSString+Versioning.m +71 -0
  38. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SS+Functions.h +38 -0
  39. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SS+Functions.m +122 -0
  40. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SS+System.h +41 -0
  41. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SS+System.m +132 -0
  42. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Helpers/SSArguments.h +156 -0
  43. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSArray+JavaScript.h +46 -0
  44. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSArray+JavaScript.m +135 -0
  45. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSDate+JavaScript.h +42 -0
  46. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSDate+JavaScript.m +90 -0
  47. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSDictionary+JavaScript.h +38 -0
  48. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSDictionary+JavaScript.m +38 -0
  49. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableArray+JavaScript.h +46 -0
  50. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableArray+JavaScript.m +131 -0
  51. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableDictionary+JavaScript.h +36 -0
  52. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableDictionary+JavaScript.m +42 -0
  53. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableString+JavaScript.h +36 -0
  54. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSMutableString+JavaScript.m +42 -0
  55. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSNumber+JavaScript.h +36 -0
  56. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSNumber+JavaScript.m +44 -0
  57. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSObject+JavaScript.h +50 -0
  58. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSObject+JavaScript.m +114 -0
  59. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSString+JavaScript.h +43 -0
  60. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/NSString+JavaScript.m +94 -0
  61. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/SS+JavaScript.h +52 -0
  62. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/SS+JavaScript.m +168 -0
  63. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/SS+Types.h +42 -0
  64. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/JavaScript/SS+Types.m +42 -0
  65. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/NSDictionary+NamedProperties.h +41 -0
  66. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/NSDictionary+NamedProperties.m +271 -0
  67. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/SS.h +36 -0
  68. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/SS.m +38 -0
  69. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/SubjectiveScript.h +67 -0
  70. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/SubjectiveScript.m +30 -0
  71. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSArray+SS.h +58 -0
  72. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSArray+SS.m +152 -0
  73. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSDate+SS.h +37 -0
  74. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSDate+SS.m +47 -0
  75. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSDictionary+SS.h +45 -0
  76. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSDictionary+SS.m +61 -0
  77. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableArray+SS.h +43 -0
  78. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableArray+SS.m +83 -0
  79. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableDictionary+SS.h +47 -0
  80. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableDictionary+SS.m +100 -0
  81. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableString+SS.h +42 -0
  82. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSMutableString+SS.m +70 -0
  83. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSNumber+SS.h +51 -0
  84. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSNumber+SS.m +86 -0
  85. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSObject+SS.h +54 -0
  86. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSObject+SS.m +153 -0
  87. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSString+SS.h +46 -0
  88. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/NSString+SS.m +91 -0
  89. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/SubjectiveScript/Types/SSTypes.h +67 -0
  90. data/lib/appjam/generators/templates/blank/EiffelApplicationTests/EiffelApplicationTests-Info.plist +1 -1
  91. data/lib/appjam/generators/templates/blank/EiffelApplicationTests/EiffelApplicationTests.h.tt +5 -5
  92. data/lib/appjam/generators/templates/blank/EiffelApplicationTests/EiffelApplicationTests.m.tt +7 -7
  93. data/lib/appjam/version.rb +1 -1
  94. 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
@@ -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;
@@ -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