]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSAPSReceiver.m
Security-58286.251.4.tar.gz
[apple/security.git] / keychain / ckks / CKKSAPSReceiver.m
1 /*
2 * Copyright (c) 2016 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 "keychain/ckks/CKKSAPSReceiver.h"
27 #import "keychain/ckks/CKKSCondition.h"
28 #import <CloudKit/CloudKit_Private.h>
29 #include <utilities/debugging.h>
30
31 @implementation CKRecordZoneNotification (CKKSPushTracing)
32 - (void)setCkksPushTracingEnabled:(BOOL)ckksPushTracingEnabled {
33 objc_setAssociatedObject(self, "ckksPushTracingEnabled", ckksPushTracingEnabled ? @YES : @NO, OBJC_ASSOCIATION_RETAIN);
34 }
35
36 - (BOOL)ckksPushTracingEnabled {
37 return !![objc_getAssociatedObject(self, "ckksPushTracingEnabled") boolValue];
38 }
39
40 - (void)setCkksPushTracingUUID:(NSString*)ckksPushTracingUUID {
41 objc_setAssociatedObject(self, "ckksPushTracingUUID", ckksPushTracingUUID, OBJC_ASSOCIATION_RETAIN);
42 }
43
44 - (NSString*)ckksPushTracingUUID {
45 return objc_getAssociatedObject(self, "ckksPushTracingUUID");
46 }
47
48 - (void)setCkksPushReceivedDate:(NSDate*)ckksPushReceivedDate {
49 objc_setAssociatedObject(self, "ckksPushReceivedDate", ckksPushReceivedDate, OBJC_ASSOCIATION_RETAIN);
50 }
51
52 - (NSDate*)ckksPushReceivedDate {
53 return objc_getAssociatedObject(self, "ckksPushReceivedDate");
54 }
55 @end
56
57
58 @interface CKKSAPSReceiver()
59 // If we receive notifications for a CKRecordZoneID that hasn't been registered yet, send them a their updates when they register
60 @property NSMutableDictionary<CKRecordZoneID*, NSMutableSet<CKRecordZoneNotification*>*>* undeliveredUpdates;
61 @end
62
63 @implementation CKKSAPSReceiver
64
65 + (instancetype)receiverForEnvironment:(NSString *)environmentName
66 namedDelegatePort:(NSString*)namedDelegatePort
67 apsConnectionClass:(Class<CKKSAPSConnection>)apsConnectionClass {
68 static NSMutableDictionary<NSString*, CKKSAPSReceiver*>* environmentMap = nil;
69
70 if(environmentName == nil) {
71 secnotice("ckkspush", "No push environment; not bringing up APS.");
72 return nil;
73 }
74
75 @synchronized([self class]) {
76 if(environmentMap == nil) {
77 environmentMap = [[NSMutableDictionary alloc] init];
78 }
79
80 CKKSAPSReceiver* recv = [environmentMap valueForKey: environmentName];
81
82 if(recv == nil) {
83 recv = [[CKKSAPSReceiver alloc] initWithEnvironmentName: environmentName namedDelegatePort:namedDelegatePort apsConnectionClass: apsConnectionClass];
84 [environmentMap setValue: recv forKey: environmentName];
85 }
86
87 return recv;
88 }
89 }
90
91 + (dispatch_queue_t)apsDeliveryQueue {
92 static dispatch_queue_t aps_dispatch_queue;
93 static dispatch_once_t onceToken;
94 dispatch_once(&onceToken, ^{
95 aps_dispatch_queue = dispatch_queue_create("aps-callback-queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
96 });
97 return aps_dispatch_queue;
98 }
99
100 - (instancetype)initWithEnvironmentName:(NSString*)environmentName
101 namedDelegatePort:(NSString*)namedDelegatePort
102 apsConnectionClass:(Class<CKKSAPSConnection>)apsConnectionClass {
103 if(self = [super init]) {
104 _apsConnectionClass = apsConnectionClass;
105 _apsConnection = NULL;
106
107 _undeliveredUpdates = [NSMutableDictionary dictionary];
108
109 // APS might be slow. This doesn't need to happen immediately, so let it happen later.
110 __weak __typeof(self) weakSelf = self;
111 dispatch_async([CKKSAPSReceiver apsDeliveryQueue], ^{
112 __strong __typeof(self) strongSelf = weakSelf;
113 if(!strongSelf) {
114 return;
115 }
116 strongSelf.apsConnection = [[strongSelf.apsConnectionClass alloc] initWithEnvironmentName:environmentName namedDelegatePort:namedDelegatePort queue:[CKKSAPSReceiver apsDeliveryQueue]];
117 strongSelf.apsConnection.delegate = strongSelf;
118
119 // The following string should match: [[NSBundle mainBundle] bundleIdentifier]
120 NSString* topic = [kCKPushTopicPrefix stringByAppendingString:@"com.apple.securityd"];
121 [strongSelf.apsConnection setEnabledTopics:@[topic]];
122 });
123
124 _zoneMap = [NSMapTable strongToWeakObjectsMapTable];
125 }
126 return self;
127 }
128
129 - (CKKSCondition*)registerReceiver:(id<CKKSZoneUpdateReceiver>)receiver forZoneID:(CKRecordZoneID *)zoneID {
130 CKKSCondition* finished = [[CKKSCondition alloc] init];
131
132 __weak __typeof(self) weakSelf = self;
133 dispatch_async([CKKSAPSReceiver apsDeliveryQueue], ^{
134 __strong __typeof(self) strongSelf = weakSelf;
135 if(!strongSelf) {
136 secerror("ckks: received registration for released CKKSAPSReceiver");
137 return;
138 }
139
140 [self.zoneMap setObject:receiver forKey: zoneID];
141
142 NSMutableSet<CKRecordZoneNotification*>* currentPendingMessages = self.undeliveredUpdates[zoneID];
143 [self.undeliveredUpdates removeObjectForKey:zoneID];
144
145 for(CKRecordZoneNotification* message in currentPendingMessages.allObjects) {
146 // Now, send the receiver its notification!
147 secerror("ckks: sending stored push(%@) to newly-registered zone(%@): %@", message, zoneID, receiver);
148 [receiver notifyZoneChange:message];
149 }
150
151 [finished fulfill];
152 });
153
154 return finished;
155 }
156
157 #pragma mark - APS Delegate callbacks
158
159 - (void)connection:(APSConnection *)connection didReceivePublicToken:(NSData *)publicToken {
160 // no-op.
161 secnotice("ckkspush", "CKKSAPSDelegate initiated: %@", connection);
162 }
163
164 - (void)connection:(APSConnection *)connection didReceiveToken:(NSData *)token forTopic:(NSString *)topic identifier:(NSString *)identifier {
165 secnotice("ckkspush", "Received per-topic push token \"%@\" for topic \"%@\" identifier \"%@\" on connection %@", token, topic, identifier, connection);
166 }
167
168 - (void)connection:(APSConnection *)connection didReceiveIncomingMessage:(APSIncomingMessage *)message {
169 secnotice("ckkspush", "CKKSAPSDelegate received a message: %@ on connection %@", message, connection);
170
171 // Report back through APS that we received a message
172 if(message.tracingEnabled) {
173 [connection confirmReceiptForMessage:message];
174 }
175
176 CKNotification* notification = [CKNotification notificationFromRemoteNotificationDictionary:message.userInfo];
177
178 if(notification.notificationType == CKNotificationTypeRecordZone) {
179 CKRecordZoneNotification* rznotification = (CKRecordZoneNotification*) notification;
180 rznotification.ckksPushTracingEnabled = message.tracingEnabled;
181 rznotification.ckksPushTracingUUID = message.tracingUUID ? [[[NSUUID alloc] initWithUUIDBytes:message.tracingUUID.bytes] UUIDString] : nil;
182 rznotification.ckksPushReceivedDate = [NSDate date];
183
184 // Find receiever in map
185 id<CKKSZoneUpdateReceiver> recv = [self.zoneMap objectForKey:rznotification.recordZoneID];
186 if(recv) {
187 [recv notifyZoneChange:rznotification];
188 } else {
189 secerror("ckks: received push for unregistered zone: %@", rznotification);
190 if(rznotification.recordZoneID) {
191 NSMutableSet<CKRecordZoneNotification*>* currentPendingMessages = self.undeliveredUpdates[rznotification.recordZoneID];
192 if(currentPendingMessages) {
193 [currentPendingMessages addObject:rznotification];
194 } else {
195 self.undeliveredUpdates[rznotification.recordZoneID] = [NSMutableSet setWithObject:rznotification];
196 }
197 }
198 }
199 } else {
200 secerror("ckks: unexpected notification: %@", notification);
201 }
202 }
203
204 @end
205
206 #endif // OCTAGON