]> git.saurik.com Git - apple/security.git/blob - Analytics/Clients/LocalKeychainAnalytics.m
Security-58286.251.4.tar.gz
[apple/security.git] / Analytics / Clients / LocalKeychainAnalytics.m
1 #include "LocalKeychainAnalytics.h"
2
3 #if __OBJC2__
4
5 #import "Security/SFAnalyticsDefines.h"
6
7 #include <sys/stat.h>
8 #include <notify.h>
9
10 #include <utilities/SecFileLocations.h>
11 #include <utilities/SecAKSWrappers.h>
12
13 @interface LKAUpgradeOutcomeReport : NSObject
14 @property LKAKeychainUpgradeOutcome outcome;
15 @property NSDictionary* attributes;
16 - (instancetype) initWithOutcome:(LKAKeychainUpgradeOutcome)outcome attributes:(NSDictionary*)attributes;
17 @end
18
19 @implementation LKAUpgradeOutcomeReport
20 - (instancetype) initWithOutcome:(LKAKeychainUpgradeOutcome)outcome attributes:(NSDictionary*)attributes {
21 if (self = [super init]) {
22 self.outcome = outcome;
23 self.attributes = attributes;
24 }
25 return self;
26 }
27 @end
28
29 // Public consts
30 // rdar://problem/41745059 SFAnalytics: collect keychain upgrade outcome information
31 LKAnalyticsFailableEvent const LKAEventUpgrade = (LKAnalyticsFailableEvent)@"LKAEventUpgrade";
32
33 // Internal consts
34 NSString* const LKAOldSchemaKey = @"oldschema";
35 NSString* const LKANewSchemaKey = @"newschema";
36 NSString* const LKAUpgradeOutcomeKey = @"upgradeoutcome";
37
38 @implementation LocalKeychainAnalytics {
39 BOOL _probablyInClassD;
40 NSMutableArray<LKAUpgradeOutcomeReport*>* _pendingReports;
41 dispatch_queue_t _queue;
42 int _notificationToken;
43 }
44
45 - (instancetype __nullable)init {
46 if (self = [super init]) {
47 _probablyInClassD = YES;
48 _pendingReports = [NSMutableArray<LKAUpgradeOutcomeReport*> new];
49 _queue = dispatch_queue_create("LKADataQueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
50 _notificationToken = NOTIFY_TOKEN_INVALID;
51 }
52 return self;
53 }
54
55 + (NSString*)databasePath {
56 return [self defaultAnalyticsDatabasePath:@"localkeychain"];
57 }
58
59 // MARK: Client-specific functionality
60
61 - (BOOL)canPersistMetrics {
62 @synchronized(self) {
63 if (!_probablyInClassD) {
64 return YES;
65 }
66 }
67
68 // If this gets busy we should start caching if AKS tells us no
69 bool hasBeenUnlocked = false;
70 if (!SecAKSGetHasBeenUnlocked(&hasBeenUnlocked, NULL) || !hasBeenUnlocked) {
71 static dispatch_once_t onceToken;
72 dispatch_once(&onceToken, ^{
73 notify_register_dispatch(kUserKeybagStateChangeNotification, &self->_notificationToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(int token) {
74 // For side effect of processing pending messages if out of class D
75 [self canPersistMetrics];
76 });
77 });
78 return NO;
79 }
80
81 @synchronized(self) {
82 _probablyInClassD = NO;
83 if (_notificationToken != NOTIFY_TOKEN_INVALID) {
84 notify_cancel(_notificationToken);
85 }
86 }
87
88 [self processPendingMessages];
89 return YES;
90 }
91
92 - (void)processPendingMessages {
93 dispatch_async(_queue, ^{
94 for (LKAUpgradeOutcomeReport* report in self->_pendingReports) {
95 [self reportKeychainUpgradeOutcome:report.outcome attributes:report.attributes];
96 }
97 });
98 }
99
100 - (void)reportKeychainUpgradeFrom:(int)oldVersion to:(int)newVersion outcome:(LKAKeychainUpgradeOutcome)outcome error:(NSError*)error {
101
102 NSMutableDictionary* attributes = [@{LKAOldSchemaKey : @(oldVersion),
103 LKANewSchemaKey : @(newVersion),
104 LKAUpgradeOutcomeKey : @(outcome),
105 } mutableCopy];
106 if (error) {
107 [attributes addEntriesFromDictionary:@{SFAnalyticsAttributeErrorDomain : error.domain,
108 SFAnalyticsAttributeErrorCode : @(error.code)}];
109 }
110
111 if (![self canPersistMetrics]) {
112 dispatch_async(_queue, ^{
113 [self->_pendingReports addObject:[[LKAUpgradeOutcomeReport alloc] initWithOutcome:outcome attributes:attributes]];
114 });
115 } else {
116 [self reportKeychainUpgradeOutcome:outcome attributes:attributes];
117 }
118 }
119
120 - (void)reportKeychainUpgradeOutcome:(LKAKeychainUpgradeOutcome)outcome attributes:(NSDictionary*)attributes {
121 if (outcome == LKAKeychainUpgradeOutcomeSuccess) {
122 [self logSuccessForEventNamed:LKAEventUpgrade];
123 } else {
124 // I could try and pick out the recoverable errors but I think we're good treating these all the same
125 [self logHardFailureForEventNamed:LKAEventUpgrade withAttributes:attributes];
126 }
127 }
128
129 @end
130
131 // MARK: C Bridging
132
133 void LKAReportKeychainUpgradeOutcome(int fromversion, int toversion, LKAKeychainUpgradeOutcome outcome) {
134 [[LocalKeychainAnalytics logger] reportKeychainUpgradeFrom:fromversion to:toversion outcome:outcome error:NULL];
135 }
136
137 void LKAReportKeychainUpgradeOutcomeWithError(int fromversion, int toversion, LKAKeychainUpgradeOutcome outcome, CFErrorRef error) {
138 [[LocalKeychainAnalytics logger] reportKeychainUpgradeFrom:fromversion to:toversion outcome:outcome error:(__bridge NSError*)error];
139 }
140
141 #else // not __OBJC2__
142
143 void LKAReportKeychainUpgradeOutcome(int fromversion, int toversion, LKAKeychainUpgradeOutcome outcome) {
144 // nothing to do on 32 bit
145 }
146
147 void LKAReportKeychainUpgradeOutcomeWithError(int fromversion, int toversion, LKAKeychainUpgradeOutcome outcome, CFErrorRef error) {
148 // nothing to do on 32 bit
149 }
150
151 #endif // __OBJC2__