]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSAPSReceiver.m
Security-58286.220.15.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 >0 notifications for a CKRecordZoneID that hasn't been registered yet, give them a fake update when they register
60 @property NSMutableSet<CKRecordZoneID*>* 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 = [[NSMutableSet alloc] init];
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 if([strongSelf.undeliveredUpdates containsObject:zoneID]) {
142 [strongSelf.undeliveredUpdates removeObject:zoneID];
143
144 // Now, send the receiver its fake notification!
145 secerror("ckks: sending fake push to newly-registered zone(%@): %@", zoneID, receiver);
146 [receiver notifyZoneChange:nil];
147 }
148
149 [finished fulfill];
150 });
151
152 return finished;
153 }
154
155 #pragma mark - APS Delegate callbacks
156
157 - (void)connection:(APSConnection *)connection didReceivePublicToken:(NSData *)publicToken {
158 // no-op.
159 secnotice("ckkspush", "CKKSAPSDelegate initiated: %@", connection);
160 }
161
162 - (void)connection:(APSConnection *)connection didReceiveToken:(NSData *)token forTopic:(NSString *)topic identifier:(NSString *)identifier {
163 secnotice("ckkspush", "Received per-topic push token \"%@\" for topic \"%@\" identifier \"%@\" on connection %@", token, topic, identifier, connection);
164 }
165
166 - (void)connection:(APSConnection *)connection didReceiveIncomingMessage:(APSIncomingMessage *)message {
167 secnotice("ckkspush", "CKKSAPSDelegate received a message: %@ on connection %@", message, connection);
168
169 // Report back through APS that we received a message
170 if(message.tracingEnabled) {
171 [connection confirmReceiptForMessage:message];
172 }
173
174 CKNotification* notification = [CKNotification notificationFromRemoteNotificationDictionary:message.userInfo];
175
176 if(notification.notificationType == CKNotificationTypeRecordZone) {
177 CKRecordZoneNotification* rznotification = (CKRecordZoneNotification*) notification;
178 rznotification.ckksPushTracingEnabled = message.tracingEnabled;
179 rznotification.ckksPushTracingUUID = message.tracingUUID ? [[[NSUUID alloc] initWithUUIDBytes:message.tracingUUID.bytes] UUIDString] : nil;
180 rznotification.ckksPushReceivedDate = [NSDate date];
181
182 // Find receiever in map
183 id<CKKSZoneUpdateReceiver> recv = [self.zoneMap objectForKey:rznotification.recordZoneID];
184 if(recv) {
185 [recv notifyZoneChange:rznotification];
186 } else {
187 secerror("ckks: received push for unregistered zone: %@", rznotification);
188 if(rznotification.recordZoneID) {
189 // TODO: save the rznofication itself
190 [self.undeliveredUpdates addObject: rznotification.recordZoneID];
191 }
192 }
193 } else {
194 secerror("ckks: unexpected notification: %@", notification);
195 }
196 }
197
198 @end
199
200 #endif // OCTAGON