]> git.saurik.com Git - apple/security.git/blob - keychain/securityd/PolicyReporter.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / securityd / PolicyReporter.m
1 #include "PolicyReporter.h"
2
3 #import <CloudKit/CKContainer_Private.h>
4 #import <CloudKit/CloudKit.h>
5 #import <CoreFoundation/CFPriv.h>
6 #import <Foundation/Foundation.h>
7 #import <Foundation/NSURLSession.h>
8 #import <dispatch/dispatch.h>
9 #import <os/feature_private.h>
10 #import <os/variant_private.h>
11
12 #include <notify.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <xpc/private.h>
18 #include <Security/SecItem.h>
19 #include <Security/SecItemPriv.h>
20 #include <Security/Security.h>
21 #include <keychain/SecureObjectSync/SOSCloudCircle.h>
22 #include <keychain/SecureObjectSync/SOSViews.h>
23 #include <utilities/SecAKSWrappers.h>
24 #import "utilities/debugging.h"
25 #import "TrustedPeers/TrustedPeers.h"
26
27 // Stolen from keychain/SecureObjectSync/SOSEngine.c
28
29 static NSString* getSOSView(id object, NSString* itemClass) {
30 if (![object isKindOfClass:[NSDictionary class]]) {
31 return nil;
32 }
33
34 NSString *viewHint = object[(NSString*)kSecAttrSyncViewHint];
35 if (viewHint != nil) {
36 return viewHint;
37 } else {
38 NSString *ag = object[(NSString*)kSecAttrAccessGroup];
39 if ([itemClass isEqualToString: (NSString*)kSecClassKey] && [ag isEqualToString: @"com.apple.security.sos"]) {
40 return (NSString*)kSOSViewiCloudIdentity;
41 } else if ([ag isEqualToString: @"com.apple.cfnetwork"]) {
42 return (NSString*)kSOSViewAutofillPasswords;
43 } else if ([ag isEqualToString: @"com.apple.safari.credit-cards"]) {
44 return (NSString*)kSOSViewSafariCreditCards;
45 } else if ([itemClass isEqualToString: (NSString*)kSecClassGenericPassword]) {
46 if ([ag isEqualToString: @"apple"] &&
47 [object[(NSString*)kSecAttrService] isEqualToString: @"AirPort"]) {
48 return (NSString*)kSOSViewWiFi;
49 } else if ([ag isEqualToString: @"com.apple.sbd"]) {
50 return (NSString*)kSOSViewBackupBagV0;
51 } else {
52 return (NSString*)kSOSViewOtherSyncable; // (genp)
53 }
54 } else {
55 return (NSString*)kSOSViewOtherSyncable; // (inet || keys)
56 }
57 }
58 }
59
60 static inline NSNumber *now_msecs() {
61 return @(((long)[[NSDate date] timeIntervalSince1970] * 1000));
62 }
63
64 static NSString* cloudKitDeviceID() {
65 __block NSString* ret = nil;
66
67 if (!os_variant_has_internal_diagnostics("com.apple.security")) {
68 return nil;
69 }
70 CKContainer *container = [CKContainer containerWithIdentifier:@"com.apple.security.keychain"];
71 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
72 [container fetchCurrentDeviceIDWithCompletionHandler:^(NSString* deviceID, NSError* error) {
73 if (error != nil) {
74 NSLog(@"failed to fetch CK deviceID: %@", error);
75 } else {
76 ret = deviceID;
77 }
78 dispatch_semaphore_signal(sem);
79 }];
80 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
81 return ret;
82 }
83
84 static void reportStats(unsigned expected_mismatches, unsigned real_mismatches, NSArray<NSDictionary*>* reportedMismatches) {
85 NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
86 NSURLSession *session = [NSURLSession sessionWithConfiguration:defaultConfiguration];
87 NSURL *endpoint = [NSURL URLWithString:@"https://xp.apple.com/report/2/xp_sear_keysync"];
88 NSMutableURLRequest *req = [[NSMutableURLRequest alloc] init];
89 req.URL = endpoint;
90 req.HTTPMethod = @"POST";
91 NSMutableDictionary *dict = [@{
92 @"expectedMismatches": @(expected_mismatches),
93 @"realMismatches": @(real_mismatches),
94 @"eventTime": now_msecs(),
95 @"topic": @"xp_sear_keysync",
96 @"eventType": @"policy-dryrun",
97 } mutableCopy];
98 NSDictionary *version = CFBridgingRelease(_CFCopySystemVersionDictionary());
99 NSString *build = version[(__bridge NSString *)_kCFSystemVersionBuildVersionKey];
100 if (build != nil) {
101 dict[@"build"] = build;
102 } else {
103 NSLog(@"Unable to find out build version");
104 }
105 NSString *product = version[(__bridge NSString *)_kCFSystemVersionProductNameKey];
106 if (product != nil) {
107 dict[@"product"] = product;
108 } else {
109 NSLog(@"Unable to find out build product");
110 }
111 NSString* ckDeviceID = cloudKitDeviceID();
112 if (ckDeviceID) {
113 dict[@"SFAnalyticsDeviceID"] = ckDeviceID;
114 dict[@"ckdeviceID"] = ckDeviceID;
115 } else {
116 NSLog(@"Unable to fetch CK device ID");
117 }
118
119 dict[@"mismatches"] = reportedMismatches;
120
121 NSArray<NSDictionary*>* events = @[dict];
122 NSDictionary *wrapper = @{
123 @"postTime": @([[NSDate date] timeIntervalSince1970] * 1000),
124 @"events": events,
125 };
126 NSError *encodeError = nil;
127 NSData* post_data = [NSJSONSerialization dataWithJSONObject:wrapper options:0 error:&encodeError];
128 if (post_data == nil || encodeError != nil) {
129 NSLog(@"failed to encode data: %@", encodeError);
130 return;
131 }
132 NSLog(@"logging %@, %@", wrapper, post_data);
133 req.HTTPBody = post_data;
134 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
135 NSURLSessionDataTask *task = [session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
136 if (error != nil) {
137 NSLog(@"splunk upload failed: %@", error);
138 return;
139 }
140 NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*)response;
141 if (httpResp.statusCode != 200) {
142 NSLog(@"HTTP Post error: %@", httpResp);
143 }
144 NSLog(@"%@", httpResp);
145 if (data != nil) {
146 size_t length = [data length];
147 char *buf = malloc(length);
148 [data getBytes:buf length:length];
149 NSLog(@"%.*s", (int)length, buf);
150 free(buf);
151 }
152 dispatch_semaphore_signal(sem);
153 }];
154 [task resume];
155 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
156 }
157
158 static void oneReport(void) {
159 NSError* error = nil;
160
161 // From Swift.policy, policy v5
162 TPPolicyDocument *tpd = [TPPolicyDocument policyDocWithHash:@"SHA256:O/ECQlWhvNlLmlDNh2+nal/yekUC87bXpV3k+6kznSo="
163 data:[[NSData alloc] initWithBase64EncodedString:
164 @"CAUSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSDAoEaVBvZBIEZnVsbBILCgNNYWMSBGZ1bGwSDAoEaU1hYxIEZnVsbBINCgdBcHBsZVRWEgJ0dhIOCgVXYXRjaBIFd2F0Y2gSFwoOQXVkaW9BY2Nlc3NvcnkSBWF1ZGlvGhsKDEFwcGxpY2F0aW9ucxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaHAoNRGV2aWNlUGFpcmluZxIEZnVsbBIFd2F0Y2gaGgoLQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhUKBkhlYWx0aBIEZnVsbBIFd2F0Y2gaLQoTTGltaXRlZFBlZXJzQWxsb3dlZBIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxokChVQcm90ZWN0ZWRDbG91ZFN0b3JhZ2USBGZ1bGwSBXdhdGNoGhcKCEFwcGxlUGF5EgRmdWxsEgV3YXRjaBoZCgpBdXRvVW5sb2NrEgRmdWxsEgV3YXRjaBoWCgdNYW5hdGVlEgRmdWxsEgV3YXRjaBoYCglQYXNzd29yZHMSBGZ1bGwSBXdhdGNoGhUKBkVuZ3JhbRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoTCgRIb21lEgRmdWxsEgV3YXRjaCIbCgVhdWRpbxIEZnVsbBIFd2F0Y2gSBWF1ZGlvIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYiFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoMiIKFgAEIhICBHZ3aHQKCl5BcHBsZVBheSQSCEFwcGxlUGF5MiYKGAAEIhQCBHZ3aHQKDF5BdXRvVW5sb2NrJBIKQXV0b1VubG9jazIeChQABCIQAgR2d2h0CgheRW5ncmFtJBIGRW5ncmFtMh4KFAAEIhACBHZ3aHQKCF5IZWFsdGgkEgZIZWFsdGgyGgoSAAQiDgIEdndodAoGXkhvbWUkEgRIb21lMiAKFQAEIhECBHZ3aHQKCV5NYW5hdGVlJBIHTWFuYXRlZTI4CiEABCIdAgR2d2h0ChVeTGltaXRlZFBlZXJzQWxsb3dlZCQSE0xpbWl0ZWRQZWVyc0FsbG93ZWQyXQpQAAISHgAEIhoCBHZ3aHQKEl5Db250aW51aXR5VW5sb2NrJBIVAAQiEQIEdndodAoJXkhvbWVLaXQkEhUABCIRAgR2d2h0CgleQXBwbGVUViQSCU5vdFN5bmNlZDIrChsABCIXAgRhZ3JwCg9eWzAtOUEtWl17MTB9XC4SDEFwcGxpY2F0aW9uczLFAQqwAQACEjQAAQoTAAQiDwIFY2xhc3MKBl5nZW5wJAobAAQiFwIEYWdycAoPXmNvbS5hcHBsZS5zYmQkEj0AAQoTAAQiDwIFY2xhc3MKBl5rZXlzJAokAAQiIAIEYWdycAoYXmNvbS5hcHBsZS5zZWN1cml0eS5zb3MkEhkABCIVAgR2d2h0Cg1eQmFja3VwQmFnVjAkEhwABCIYAgR2d2h0ChBeaUNsb3VkSWRlbnRpdHkkEhBTZWN1cmVPYmplY3RTeW5jMmMKWwACEhIABCIOAgR2d2h0CgZeV2lGaSQSQwABChMABCIPAgVjbGFzcwoGXmdlbnAkChMABCIPAgRhZ3JwCgdeYXBwbGUkChUABCIRAgRzdmNlCgleQWlyUG9ydCQSBFdpRmkynQMKgwMAAhIYAAQiFAIEdndodAoMXlBDUy1CYWNrdXAkEhoABCIWAgR2d2h0Cg5eUENTLUNsb3VkS2l0JBIYAAQiFAIEdndodAoMXlBDUy1Fc2Nyb3ckEhUABCIRAgR2d2h0CgleUENTLUZERSQSGgAEIhYCBHZ3aHQKDl5QQ1MtRmVsZHNwYXIkEhoABCIWAgR2d2h0Cg5eUENTLU1haWxEcm9wJBIaAAQiFgIEdndodAoOXlBDUy1NYWlsZHJvcCQSGwAEIhcCBHZ3aHQKD15QQ1MtTWFzdGVyS2V5JBIXAAQiEwIEdndodAoLXlBDUy1Ob3RlcyQSGAAEIhQCBHZ3aHQKDF5QQ1MtUGhvdG9zJBIZAAQiFQIEdndodAoNXlBDUy1TaGFyaW5nJBIeAAQiGgIEdndodAoSXlBDUy1pQ2xvdWRCYWNrdXAkEh0ABCIZAgR2d2h0ChFeUENTLWlDbG91ZERyaXZlJBIaAAQiFgIEdndodAoOXlBDUy1pTWVzc2FnZSQSFVByb3RlY3RlZENsb3VkU3RvcmFnZTI6CisABCInAgRhZ3JwCh9eY29tLmFwcGxlLnNhZmFyaS5jcmVkaXQtY2FyZHMkEgtDcmVkaXRDYXJkczIuCiEABCIdAgRhZ3JwChVeY29tLmFwcGxlLmNmbmV0d29yayQSCVBhc3N3b3JkczJtClwAAhIeAAQiGgIEdndodAoSXkFjY2Vzc29yeVBhaXJpbmckEhoABCIWAgR2d2h0Cg5eTmFub1JlZ2lzdHJ5JBIcAAQiGAIEdndodAoQXldhdGNoTWlncmF0aW9uJBINRGV2aWNlUGFpcmluZzIOCgIABhIIQmFja3N0b3A=" options:0]];
165
166 TPPolicy *policy = [tpd policyWithSecrets:@{} decrypter:nil error:&error];
167 if (error != nil) {
168 NSLog(@"policy error: %@", error);
169 return;
170 }
171 if (policy == nil) {
172 NSLog(@"policy is nil");
173 return;
174 }
175 TPSyncingPolicy* syncingPolicy = [policy syncingPolicyForModel:@"iPhone"
176 syncUserControllableViews:TPPBPeerStableInfo_UserControllableViewStatus_UNKNOWN
177 error:&error];
178 if(syncingPolicy == nil || error != nil) {
179 NSLog(@"syncing policy is nil: %@", error);
180 return;
181 }
182
183 unsigned real_mismatches = 0;
184 unsigned expected_mismatches = 0;
185 NSMutableArray<NSDictionary*>* reportedMismatches = [[NSMutableArray<NSDictionary*> alloc] init];
186
187 NSArray<NSString*>* keychainClasses = @[(id)kSecClassInternetPassword,
188 (id)kSecClassGenericPassword,
189 (id)kSecClassKey,
190 (id)kSecClassCertificate];
191
192 for(NSString* itemClass in keychainClasses) {
193 NSDictionary *query = @{ (id)kSecMatchLimit : (id)kSecMatchLimitAll,
194 (id)kSecClass : (id)itemClass,
195 (id)kSecReturnAttributes : @YES,
196 (id)kSecAttrSynchronizable: @YES,
197 (id)kSecUseDataProtectionKeychain: @YES,
198 (id)kSecUseAuthenticationUI : (id)kSecUseAuthenticationUISkip,
199 };
200
201 NSArray *result;
202
203 OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (void*)&result);
204 if (status) {
205 if (status == errSecItemNotFound) {
206 NSLog(@"no items found matching: %@", query);
207 continue;
208 } else {
209 NSLog(@"SecItemCopyMatching(%@) failed: %d", query, (int)status);
210 return;
211 }
212 }
213
214 if (![result isKindOfClass:[NSArray class]]) {
215 NSLog(@"expected NSArray result from SecItemCopyMatching");
216 return;
217 }
218
219 for (id a in result) {
220 NSLog(@"%@", a);
221 NSString *oldView = getSOSView(a, itemClass);
222 if (oldView != nil) {
223 NSLog(@"old: %@", oldView);
224 }
225
226 NSMutableDictionary* mutA = [a mutableCopy];
227 mutA[(id)kSecClass] = (id)itemClass;
228
229 NSString* newView = [syncingPolicy mapDictionaryToView:mutA];
230 if (newView != nil) {
231 NSLog(@"new: %@", newView);
232 }
233 if(oldView == nil ^ newView == nil) {
234 NSLog(@"real mismatch: old view (%@) != new view (%@)", oldView, newView);
235 ++real_mismatches;
236 [reportedMismatches addObject: a];
237
238 } else if (oldView && newView && ![oldView isEqualToString: newView]) {
239 if ([oldView hasPrefix:@"PCS-"] && [newView isEqualToString: @"ProtectedCloudStorage"]) {
240 NSLog(@"(expected PCS mismatch): old view (%@) != new view (%@)", oldView, newView);
241 ++expected_mismatches;
242
243 } else if([oldView isEqualToString:@"OtherSyncable"] && [newView isEqualToString: @"Applications"]) {
244 NSLog(@"(expected 3rd party mismatch): old view (%@) != new view (%@)", oldView, newView);
245 ++expected_mismatches;
246
247 } else if([oldView isEqualToString:@"OtherSyncable"] && [newView isEqualToString: @"Backstop"]) {
248 NSLog(@"(expected backstop mismatch): old view (%@) != new view (%@)", oldView, newView);
249 ++expected_mismatches;
250
251 } else if([newView isEqualToString:@"NotSynced"]) {
252 NSLog(@"(expected NotSynced mismatch): old view (%@) != new view (%@)", oldView, newView);
253 ++expected_mismatches;
254
255 } else if(([oldView isEqualToString:@"BackupBagV0"] || [oldView isEqualToString:@"iCloudIdentity"]) && [newView isEqualToString:@"SecureObjectSync"]) {
256 NSLog(@"(expected BackupBag - SecureObjectSync mismatch): old view (%@) != new view (%@)", oldView, newView);
257 ++expected_mismatches;
258
259 } else if(([oldView isEqualToString:@"AccessoryPairing"]
260 || [oldView isEqualToString:@"NanoRegistry"]
261 || [oldView isEqualToString:@"WatchMigration"]) && [newView isEqualToString:@"DevicePairing"]) {
262 NSLog(@"(expected DevicePairing mismatch): old view (%@) != new view (%@)", oldView, newView);
263 ++expected_mismatches;
264
265 } else {
266 NSLog(@"real mismatch: old view (%@) != new view (%@)", oldView, newView);
267 ++real_mismatches;
268 [reportedMismatches addObject: a];
269 }
270 }
271 }
272 }
273
274 NSLog(@"%d expected_mismatches", expected_mismatches);
275 NSLog(@"%d real_mismatches", real_mismatches);
276 reportStats(expected_mismatches, real_mismatches, reportedMismatches);
277 }
278
279 static dispatch_queue_t queue;
280
281 static void report(void);
282
283 static void maybeReport(void) {
284 CFErrorRef error = NULL;
285 bool locked = true;
286 if (!SecAKSGetIsLocked(&locked, &error)) {
287 secerror("PolicyReporter: %@", error);
288 CFReleaseNull(error);
289 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
290 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, random() % 60);
291 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, 30);
292 xpc_dictionary_set_bool(options, XPC_ACTIVITY_REPEATING, false);
293 xpc_dictionary_set_bool(options, XPC_ACTIVITY_ALLOW_BATTERY, true);
294 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
295 xpc_dictionary_set_bool(options, XPC_ACTIVITY_REQUIRE_NETWORK_CONNECTIVITY, true);
296 #if TARGET_OS_IPHONE
297 xpc_dictionary_set_bool(options, XPC_ACTIVITY_REQUIRES_CLASS_A, true);
298 #endif
299 xpc_activity_register("com.apple.security.securityd.policy-reporting2",
300 options, ^(xpc_activity_t activity) {
301 report();
302 });
303 return;
304 }
305
306 if (locked) {
307 int token = 0;
308 notify_register_dispatch(kUserKeybagStateChangeNotification, &token, queue, ^(int t) {
309 report();
310 });
311 return;
312 }
313 oneReport();
314 }
315
316 static void report() {
317 @autoreleasepool {
318 maybeReport();
319 }
320 }
321
322 void InitPolicyReporter(void) {
323 if (!os_feature_enabled(Security, securitydReportPolicy)) {
324 secnotice("securityd-PolicyReporter", "not enabled by feature flag");
325 return;
326 }
327
328 srandom(getpid() ^ (int)time(NULL));
329 queue = dispatch_queue_create("com.apple.security.securityd_reporting", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
330 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
331 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, random() % 3600);
332 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, 1800);
333 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_INTERVAL, 3600);
334 xpc_dictionary_set_bool(options, XPC_ACTIVITY_REPEATING, true);
335 xpc_dictionary_set_bool(options, XPC_ACTIVITY_ALLOW_BATTERY, true);
336 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
337 xpc_dictionary_set_bool(options, XPC_ACTIVITY_REQUIRE_NETWORK_CONNECTIVITY, true);
338 #if TARGET_OS_IPHONE
339 xpc_dictionary_set_bool(options, XPC_ACTIVITY_REQUIRES_CLASS_A, true);
340 #endif
341
342 xpc_activity_register("com.apple.security.securityd.policy-reporting",
343 options, ^(xpc_activity_t activity) {
344 report();
345 });
346 }