]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSAnalytics.m
Security-58286.60.28.tar.gz
[apple/security.git] / keychain / ckks / CKKSAnalytics.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import <CloudKit/CloudKit.h>
27 #import <CloudKit/CloudKit_Private.h>
28 #import <os/log.h>
29
30 #import "keychain/ckks/CKKSAnalytics.h"
31 #import "keychain/ot/OTDefines.h"
32 #import "keychain/ckks/CKKS.h"
33 #import "keychain/ckks/CKKSViewManager.h"
34 #import "keychain/ckks/CKKSKeychainView.h"
35 #import "Analytics/SFAnalytics.h"
36 #include <utilities/SecFileLocations.h>
37 #include <sys/stat.h>
38
39 NSString* const CKKSAnalyticsInCircle = @"inCircle";
40 NSString* const CKKSAnalyticsHasTLKs = @"TLKs";
41 NSString* const CKKSAnalyticsSyncedClassARecently = @"inSyncA";
42 NSString* const CKKSAnalyticsSyncedClassCRecently = @"inSyncC";
43 NSString* const CKKSAnalyticsIncomingQueueIsErrorFree = @"IQNOE";
44 NSString* const CKKSAnalyticsOutgoingQueueIsErrorFree = @"OQNOE";
45 NSString* const CKKSAnalyticsInSync = @"inSync";
46 NSString* const CKKSAnalyticsValidCredentials = @"validCredentials";
47 NSString* const CKKSAnalyticsLastUnlock = @"lastUnlock";
48 NSString* const CKKSAnalyticsLastKeystateReady = @"lastKSR";
49 NSString* const CKKSAnalyticsLastInCircle = @"lastInCircle";
50
51 static NSString* const CKKSAnalyticsAttributeRecoverableError = @"recoverableError";
52 static NSString* const CKKSAnalyticsAttributeZoneName = @"zone";
53 static NSString* const CKKSAnalyticsAttributeErrorDomain = @"errorDomain";
54 static NSString* const CKKSAnalyticsAttributeErrorCode = @"errorCode";
55 static NSString* const CKKSAnalyticsAttributeErrorChain = @"errorChain";
56
57 CKKSAnalyticsFailableEvent* const CKKSEventProcessIncomingQueueClassA = (CKKSAnalyticsFailableEvent*)@"CKKSEventProcessIncomingQueueClassA";
58 CKKSAnalyticsFailableEvent* const CKKSEventProcessIncomingQueueClassC = (CKKSAnalyticsFailableEvent*)@"CKKSEventProcessIncomingQueueClassC";
59 CKKSAnalyticsFailableEvent* const CKKSEventProcessOutgoingQueue = (CKKSAnalyticsFailableEvent*)@"CKKSEventProcessOutgoingQueue";
60 CKKSAnalyticsFailableEvent* const CKKSEventUploadChanges = (CKKSAnalyticsFailableEvent*)@"CKKSEventUploadChanges";
61 CKKSAnalyticsFailableEvent* const CKKSEventStateError = (CKKSAnalyticsFailableEvent*)@"CKKSEventStateError";
62 CKKSAnalyticsFailableEvent* const CKKSEventProcessHealKeyHierarchy = (CKKSAnalyticsFailableEvent *)@"CKKSEventProcessHealKeyHierarchy";
63
64 NSString* const OctagonEventFailureReason = @"FailureReason";
65
66 CKKSAnalyticsFailableEvent* const OctagonEventPreflightBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventPreflightBottle";
67 CKKSAnalyticsFailableEvent* const OctagonEventLaunchBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventLaunchBottle";
68 CKKSAnalyticsFailableEvent* const OctagonEventRestoreBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventRestoreBottle";
69 CKKSAnalyticsFailableEvent* const OctagonEventScrubBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventScrubBottle";
70 CKKSAnalyticsFailableEvent* const OctagonEventSignIn = (CKKSAnalyticsFailableEvent *)@"OctagonEventSignIn";
71 CKKSAnalyticsFailableEvent* const OctagonEventSignOut = (CKKSAnalyticsFailableEvent *)@"OctagonEventSignIn";
72 CKKSAnalyticsFailableEvent* const OctagonEventRamp = (CKKSAnalyticsFailableEvent *)@"OctagonEventRamp";
73 CKKSAnalyticsFailableEvent* const OctagonEventBottleCheck = (CKKSAnalyticsFailableEvent *)@"OctagonEventBottleCheck";
74 CKKSAnalyticsFailableEvent* const OctagonEventCoreFollowUp = (CKKSAnalyticsFailableEvent *)@"OctagonEventCoreFollowUp";
75
76 CKKSAnalyticsSignpostEvent* const CKKSEventPushNotificationReceived = (CKKSAnalyticsSignpostEvent*)@"CKKSEventPushNotificationReceived";
77 CKKSAnalyticsSignpostEvent* const CKKSEventItemAddedToOutgoingQueue = (CKKSAnalyticsSignpostEvent*)@"CKKSEventItemAddedToOutgoingQueue";
78 CKKSAnalyticsSignpostEvent* const CKKSEventMissingLocalItemsFound = (CKKSAnalyticsSignpostEvent*)@"CKKSEventMissingLocalItemsFound";
79 CKKSAnalyticsSignpostEvent* const CKKSEventReachabilityTimerExpired = (CKKSAnalyticsSignpostEvent *)@"CKKSEventReachabilityTimerExpired";
80
81 CKKSAnalyticsActivity* const CKKSActivityOTFetchRampState = (CKKSAnalyticsActivity *)@"CKKSActivityOTFetchRampState";
82 CKKSAnalyticsActivity* const CKKSActivityOctagonSignIn = (CKKSAnalyticsActivity *)@"CKKSActivityOctagonSignIn";
83 CKKSAnalyticsActivity* const CKKSActivityOctagonPreflightBottle = (CKKSAnalyticsActivity *)@"CKKSActivityOctagonPreflightBottle";
84 CKKSAnalyticsActivity* const CKKSActivityOctagonLaunchBottle = (CKKSAnalyticsActivity *)@"CKKSActivityOctagonLaunchBottle";
85 CKKSAnalyticsActivity* const CKKSActivityOctagonRestore = (CKKSAnalyticsActivity *)@"CKKSActivityOctagonRestore";
86 CKKSAnalyticsActivity* const CKKSActivityScrubBottle = (CKKSAnalyticsActivity *)@"CKKSActivityScrubBottle";
87 CKKSAnalyticsActivity* const CKKSActivityBottleCheck = (CKKSAnalyticsActivity *)@"CKKSActivityBottleCheck";
88
89 @implementation CKKSAnalytics
90
91 + (NSString*)databasePath
92 {
93 // This block exists because we moved database locations in 11.3 for easier sandboxing of securityuploadd, so we're cleaning up.
94 static dispatch_once_t onceToken;
95 dispatch_once(&onceToken, ^{
96 WithPathInKeychainDirectory(CFSTR("ckks_analytics_v2.db"), ^(const char *filename) {
97 remove(filename);
98 });
99 WithPathInKeychainDirectory(CFSTR("ckks_analytics_v2.db-wal"), ^(const char *filename) {
100 remove(filename);
101 });
102 WithPathInKeychainDirectory(CFSTR("ckks_analytics_v2.db-shm"), ^(const char *filename) {
103 remove(filename);
104 });
105 });
106
107 WithPathInKeychainDirectory(CFSTR("Analytics"), ^(const char *path) {
108 #if TARGET_OS_IPHONE
109 mode_t permissions = 0775;
110 #else
111 mode_t permissions = 0700;
112 #endif // TARGET_OS_IPHONE
113 int ret = mkpath_np(path, permissions);
114 if (!(ret == 0 || ret == EEXIST)) {
115 secerror("could not create path: %s (%s)", path, strerror(ret));
116 }
117 chmod(path, permissions);
118 });
119 return [(__bridge_transfer NSURL*)SecCopyURLForFileInKeychainDirectory((__bridge CFStringRef)@"Analytics/ckks_analytics.db") path];
120 }
121
122 + (instancetype)logger
123 {
124 // just here because I want it in the header for discoverability
125 return [super logger];
126 }
127
128 - (void)logSuccessForEvent:(CKKSAnalyticsFailableEvent*)event inView:(CKKSKeychainView*)view
129 {
130 [self logSuccessForEventNamed:[NSString stringWithFormat:@"%@-%@", view.zoneName, event]];
131 [self setDateProperty:[NSDate date] forKey:[NSString stringWithFormat:@"last_success_%@-%@", view.zoneName, event]];
132 }
133
134 - (bool)isCKPartialError:(NSError *)error
135 {
136 return [error.domain isEqualToString:CKErrorDomain] && error.code == CKErrorPartialFailure;
137 }
138
139 - (void)addCKPartialError:(NSMutableDictionary *)errorDictionary error:(NSError *)error depth:(NSUInteger)depth
140 {
141 // capture one random underlaying error
142 if ([self isCKPartialError:error]) {
143 NSDictionary<NSString *,NSError *> *partialErrors = error.userInfo[CKPartialErrorsByItemIDKey];
144 if ([partialErrors isKindOfClass:[NSDictionary class]]) {
145 for (NSString *key in partialErrors) {
146 NSError* ckError = partialErrors[key];
147 if (![ckError isKindOfClass:[NSError class]])
148 continue;
149 if ([ckError.domain isEqualToString:CKErrorDomain] && ckError.code == CKErrorBatchRequestFailed) {
150 continue;
151 }
152 NSDictionary *res = [self errorChain:ckError depth:(depth + 1)];
153 if (res) {
154 errorDictionary[@"oneCloudKitPartialFailure"] = res;
155 break;
156 }
157 }
158 }
159 }
160 }
161
162 // if we have underlying errors, capture the chain below the top-most error
163 - (NSDictionary *)errorChain:(NSError *)error depth:(NSUInteger)depth
164 {
165 NSMutableDictionary *errorDictionary = nil;
166
167 if (depth > 5 || ![error isKindOfClass:[NSError class]])
168 return nil;
169
170 errorDictionary = [@{
171 @"domain" : error.domain,
172 @"code" : @(error.code),
173 } mutableCopy];
174
175 errorDictionary[@"child"] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:(depth + 1)];
176 [self addCKPartialError:errorDictionary error:error depth:(depth + 1)];
177
178 return errorDictionary;
179 }
180 - (void)logRecoverableError:(NSError*)error forEvent:(CKKSAnalyticsFailableEvent*)event zoneName:(NSString*)zoneName withAttributes:(NSDictionary *)attributes
181 {
182 if (error == nil){
183 return;
184 }
185 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
186
187 /* Don't allow caller to overwrite our attributes, lets merge them first */
188 if (attributes) {
189 [eventAttributes setValuesForKeysWithDictionary:attributes];
190 }
191
192 [eventAttributes setValuesForKeysWithDictionary:@{
193 CKKSAnalyticsAttributeRecoverableError : @(YES),
194 CKKSAnalyticsAttributeZoneName : zoneName,
195 CKKSAnalyticsAttributeErrorDomain : error.domain,
196 CKKSAnalyticsAttributeErrorCode : @(error.code)
197 }];
198
199 eventAttributes[CKKSAnalyticsAttributeErrorChain] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:0];
200 [self addCKPartialError:eventAttributes error:error depth:0];
201
202 [super logSoftFailureForEventNamed:event withAttributes:eventAttributes];
203 }
204 - (void)logRecoverableError:(NSError*)error forEvent:(CKKSAnalyticsFailableEvent*)event inView:(CKKSKeychainView*)view withAttributes:(NSDictionary *)attributes
205 {
206 if (error == nil){
207 return;
208 }
209 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
210
211 /* Don't allow caller to overwrite our attributes, lets merge them first */
212 if (attributes) {
213 [eventAttributes setValuesForKeysWithDictionary:attributes];
214 }
215
216 [eventAttributes setValuesForKeysWithDictionary:@{
217 CKKSAnalyticsAttributeRecoverableError : @(YES),
218 CKKSAnalyticsAttributeZoneName : view.zoneName,
219 CKKSAnalyticsAttributeErrorDomain : error.domain,
220 CKKSAnalyticsAttributeErrorCode : @(error.code)
221 }];
222
223 eventAttributes[CKKSAnalyticsAttributeErrorChain] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:0];
224 [self addCKPartialError:eventAttributes error:error depth:0];
225
226 [super logSoftFailureForEventNamed:event withAttributes:eventAttributes];
227 }
228
229 - (void)logUnrecoverableError:(NSError*)error forEvent:(CKKSAnalyticsFailableEvent*)event inView:(CKKSKeychainView*)view withAttributes:(NSDictionary *)attributes
230 {
231 if (error == nil){
232 return;
233 }
234 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
235 if (attributes) {
236 [eventAttributes setValuesForKeysWithDictionary:attributes];
237 }
238
239 eventAttributes[CKKSAnalyticsAttributeErrorChain] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:0];
240 [self addCKPartialError:eventAttributes error:error depth:0];
241
242 [eventAttributes setValuesForKeysWithDictionary:@{
243 CKKSAnalyticsAttributeRecoverableError : @(NO),
244 CKKSAnalyticsAttributeZoneName : view.zoneName,
245 CKKSAnalyticsAttributeErrorDomain : error.domain,
246 CKKSAnalyticsAttributeErrorCode : @(error.code)
247 }];
248
249 [self logHardFailureForEventNamed:event withAttributes:eventAttributes];
250 }
251
252 - (void)logUnrecoverableError:(NSError*)error forEvent:(CKKSAnalyticsFailableEvent*)event withAttributes:(NSDictionary *)attributes
253 {
254 if (error == nil){
255 return;
256 }
257 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
258
259 /* Don't allow caller to overwrite our attributes, lets merge them first */
260 if (attributes) {
261 [eventAttributes setValuesForKeysWithDictionary:attributes];
262 }
263
264 eventAttributes[CKKSAnalyticsAttributeErrorChain] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:0];
265 [self addCKPartialError:eventAttributes error:error depth:0];
266
267 [eventAttributes setValuesForKeysWithDictionary:@{
268 CKKSAnalyticsAttributeRecoverableError : @(NO),
269 CKKSAnalyticsAttributeZoneName : OctagonEventAttributeZoneName,
270 CKKSAnalyticsAttributeErrorDomain : error.domain,
271 CKKSAnalyticsAttributeErrorCode : @(error.code)
272 }];
273
274 [self logHardFailureForEventNamed:event withAttributes:eventAttributes];
275 }
276
277 - (void)noteEvent:(CKKSAnalyticsSignpostEvent*)event
278 {
279 [self noteEventNamed:event];
280 }
281 - (void)noteEvent:(CKKSAnalyticsSignpostEvent*)event inView:(CKKSKeychainView*)view
282 {
283 [self noteEventNamed:[NSString stringWithFormat:@"%@-%@", view.zoneName, event]];
284 }
285
286 - (NSDate*)dateOfLastSuccessForEvent:(CKKSAnalyticsFailableEvent*)event inView:(CKKSKeychainView*)view
287 {
288 return [self datePropertyForKey:[NSString stringWithFormat:@"last_success_%@-%@", view.zoneName, event]];
289 }
290
291 - (void)setDateProperty:(NSDate*)date forKey:(NSString*)key inView:(CKKSKeychainView *)view
292 {
293 [self setDateProperty:date forKey:[NSString stringWithFormat:@"%@-%@", key, view.zoneName]];
294 }
295 - (NSDate *)datePropertyForKey:(NSString *)key inView:(CKKSKeychainView *)view
296 {
297 return [self datePropertyForKey:[NSString stringWithFormat:@"%@-%@", key, view.zoneName]];
298 }
299
300 @end
301
302 #endif // OCTAGON