]> git.saurik.com Git - apple/security.git/blob - KeychainSyncingOverIDSProxy/KeychainSyncingOverIDSProxy+SendMessage.m
Security-58286.70.7.tar.gz
[apple/security.git] / KeychainSyncingOverIDSProxy / KeychainSyncingOverIDSProxy+SendMessage.m
1 /*
2 * Copyright (c) 2012-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
25 #import <Foundation/NSArray.h>
26 #import <Foundation/Foundation.h>
27
28 #import <Security/SecBasePriv.h>
29 #import <Security/SecItemPriv.h>
30 #import <utilities/debugging.h>
31 #import <notify.h>
32
33 #include <Security/CKBridge/SOSCloudKeychainConstants.h>
34 #include <Security/SecureObjectSync/SOSARCDefines.h>
35 #include <Security/SecureObjectSync/SOSCloudCircle.h>
36 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
37
38 #import <IDS/IDS.h>
39 #import <os/activity.h>
40
41 #include <utilities/SecAKSWrappers.h>
42 #include <utilities/SecADWrapper.h>
43 #include <utilities/SecCFRelease.h>
44 #include <AssertMacros.h>
45
46 #import "IDSProxy.h"
47 #import "IDSPersistentState.h"
48 #import "KeychainSyncingOverIDSProxy+SendMessage.h"
49 #include <Security/SecItemInternal.h>
50
51
52 static NSString *const IDSSendMessageOptionForceEncryptionOffKey = @"IDSSendMessageOptionForceEncryptionOff";
53
54 static NSString *const kIDSNumberOfFragments = @"NumberOfIDSMessageFragments";
55 static NSString *const kIDSFragmentIndex = @"kFragmentIndex";
56 static NSString *const kIDSMessageUseACKModel = @"UsesAckModel";
57 static NSString *const kIDSMessageSendersDeviceID = @"SendersDeviceID";
58
59 static NSString *const kIDSDeviceID = @"deviceID";
60 static const int64_t kRetryTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for handling unhandled messages.
61 static const int64_t timeout = 5ull;
62 static const int64_t KVS_BACKOFF = 5;
63
64 static const NSUInteger kMaxIDSMessagePayloadSize = 64000;
65
66 @implementation KeychainSyncingOverIDSProxy (SendMessage)
67
68 -(bool) chunkAndSendKeychainPayload:(NSData*)keychainData deviceID:(NSString*)deviceName ourPeerID:(NSString*)ourPeerID theirPeerID:(NSString*) theirPeerID operation:(NSString*)operationTypeAsString uuid:(NSString*)uuidString senderDeviceID:(NSString*)senderDeviceID
69 error:(NSError**) error
70 {
71 __block BOOL result = true;
72
73 NSUInteger keychainDataLength = [keychainData length];
74 int fragmentIndex = 0;
75 int startingPosition = 0;
76
77 NSUInteger totalNumberOfFragments = (keychainDataLength + kMaxIDSMessagePayloadSize - 1)/kMaxIDSMessagePayloadSize;
78 secnotice("IDS Transport", "sending %lu number of fragments to: %@", (unsigned long)totalNumberOfFragments, deviceName);
79 NSMutableDictionary* fragmentDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
80 deviceName, kIDSDeviceID,
81 [NSNumber numberWithUnsignedInteger:totalNumberOfFragments], kIDSNumberOfFragments,
82 [NSNumber numberWithInt:fragmentIndex], kIDSFragmentIndex,
83 deviceName, kIDSMessageRecipientDeviceID, theirPeerID, kIDSMessageRecipientPeerID,
84 operationTypeAsString, kIDSOperationType,
85 uuidString, kIDSMessageUniqueID,
86 nil];
87
88 NSUInteger remainingLength = keychainDataLength;
89 while(remainingLength > 0 && result == true){
90 NSUInteger fragmentLength = MIN(remainingLength, kMaxIDSMessagePayloadSize);
91 NSData *fragment = [keychainData subdataWithRange:NSMakeRange(startingPosition, fragmentLength)];
92
93 // Insert the current fragment data in dictionary with key peerID and message key.
94 [fragmentDictionary setObject:@{theirPeerID:fragment}
95 forKey:(__bridge NSString*)kIDSMessageToSendKey];
96 // Insert the fragment number in the dictionary
97 [fragmentDictionary setObject:[NSNumber numberWithInt:fragmentIndex]
98 forKey:kIDSFragmentIndex];
99
100 result = [self sendIDSMessage:fragmentDictionary name:deviceName peer:ourPeerID senderDeviceID:senderDeviceID];
101 if(!result)
102 secerror("Could not send fragmented message");
103
104 startingPosition+=fragmentLength;
105 remainingLength-=fragmentLength;
106 fragmentIndex++;
107 }
108
109 return result;
110 }
111
112 - (void)sendToKVS: (NSString*) theirPeerID message: (NSData*) message
113 {
114 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
115 CFErrorRef cf_error = NULL;
116
117 bool success = SOSCCRequestSyncWithPeerOverKVS(((__bridge CFStringRef)theirPeerID), (__bridge CFDataRef)message, &cf_error);
118
119 if(success){
120 secnotice("IDS Transport", "rerouting message %@", message);
121 }
122 else{
123 secerror("could not route message to %@, error: %@", theirPeerID, cf_error);
124 }
125
126 CFReleaseNull(cf_error);
127 return NULL;
128 }];
129 }
130
131 - (void) sendMessageToKVS: (NSDictionary<NSString*, NSDictionary*>*) encapsulatedKeychainMessage
132 {
133 SecADAddValueForScalarKey(CFSTR("com.apple.security.sos.kvsreroute"), 1);
134 [encapsulatedKeychainMessage enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
135 if ([key isKindOfClass: [NSString class]] && [obj isKindOfClass:[NSData class]]) {
136 [self sendToKVS:key message:obj];
137 } else {
138 secerror("Couldn't send to KVS key: %@ obj: %@", key, obj);
139 }
140 }];
141 }
142
143
144 - (void)pingTimerFired:(NSString*)IDSid peerID:(NSString*)peerID identifier:(NSString*)identifier
145 {
146 //setting next time to send
147 [self updateNextTimeToSendFor5Minutes:IDSid];
148
149 secnotice("IDS Transport", "device ID: %@ !!!!!!!!!!!!!!!!Ping timeout is up!!!!!!!!!!!!", IDSid);
150 //call securityd to sync with device over KVS
151 __block CFErrorRef cf_error = NULL;
152 __block bool success = kHandleIDSMessageSuccess;
153
154 //cleanup timers
155 dispatch_async(self.pingQueue, ^{
156 dispatch_source_t timer = [[KeychainSyncingOverIDSProxy idsProxy].pingTimers objectForKey:IDSid]; //remove timer
157 dispatch_cancel(timer); //cancel timer
158 [[KeychainSyncingOverIDSProxy idsProxy].pingTimers removeObjectForKey:IDSid];
159 });
160
161 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
162
163 success = SOSCCRequestSyncWithPeerOverKVSUsingIDOnly(((__bridge CFStringRef)IDSid), &cf_error);
164
165 if(success){
166 secnotice("IDS Transport", "rerouting message for %@", peerID);
167 }
168 else{
169 secerror("Could not hand peerID: %@ to securityd, error: %@", IDSid, cf_error);
170 }
171
172 return NULL;
173 }];
174 CFReleaseSafe(cf_error);
175 }
176
177 -(void) pingDevices:(NSArray*)list peerID:(NSString*)peerID
178 {
179 NSDictionary *messageDictionary = @{(__bridge NSString*)kIDSOperationType : [NSString stringWithFormat:@"%d", kIDSPeerAvailability], (__bridge NSString*)kIDSMessageToSendKey : @"checking peers"};
180
181 [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * top) {
182 NSString* IDSid = (NSString*)obj;
183 NSString* identifier = [NSString string];
184 bool result = false;
185 secnotice("IDS Transport", "sending to id: %@", IDSid);
186
187 result = [self sendIDSMessage:messageDictionary name:IDSid peer:peerID senderDeviceID:[NSString string]];
188
189 if(!result){
190 secerror("Could not send message over IDS");
191 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
192 CFErrorRef kvsError = nil;
193 bool success = SOSCCRequestSyncWithPeerOverKVSUsingIDOnly(((__bridge CFStringRef)IDSid), &kvsError);
194
195 if(success){
196 secnotice("IDS Transport", "sent peerID: %@ to securityd to sync over KVS", IDSid);
197 }
198 else{
199 secerror("Could not hand peerID: %@ to securityd, error: %@", IDSid, kvsError);
200 }
201 CFReleaseNull(kvsError);
202 return NULL;
203 }];
204 }
205 else{
206 dispatch_async(self.pingQueue, ^{
207 //create a timer!
208 if( [self.pingTimers objectForKey:IDSid] == nil){
209 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
210 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
211 dispatch_source_set_event_handler(timer, ^{
212 [self pingTimerFired:IDSid peerID:peerID identifier:identifier];
213 });
214 dispatch_resume(timer);
215
216 [self.pingTimers setObject:timer forKey:IDSid];
217 }
218 });
219 }
220 }];
221 }
222
223 -(BOOL) shouldProxySendMessage:(NSString*)deviceName
224 {
225 BOOL result = false;
226
227 //checking peer cache to see if the message should be sent over IDS or back to KVS
228 if(self.peerNextSendCache == nil)
229 {
230 self.peerNextSendCache = [[NSMutableDictionary alloc]initWithCapacity:0];
231 }
232 NSDate *nextTimeToSend = [self.peerNextSendCache objectForKey:deviceName];
233 if(nextTimeToSend != nil)
234 {
235 //check if the timestamp is stale or set sometime in the future
236 NSDate *currentTime = [[NSDate alloc] init];
237 //if the current time is greater than the next time to send -> time to send!
238 if([[nextTimeToSend laterDate:currentTime] isEqual:currentTime]){
239 result = true;
240 }
241 }
242 else{ //next time to send is not set yet
243 result = true;
244 }
245 return result;
246 }
247
248 -(BOOL) isMessageAPing:(NSDictionary*)data
249 {
250 NSDictionary *messageDictionary = [data objectForKey: (__bridge NSString*)kIDSMessageToSendKey];
251 BOOL isPingMessage = false;
252
253 if(messageDictionary && ![messageDictionary isKindOfClass:[NSDictionary class]])
254 {
255 NSString* messageString = [data objectForKey: (__bridge NSString*)kIDSMessageToSendKey];
256 if(messageString && [messageString isKindOfClass:[NSString class]])
257 isPingMessage = true;
258 }
259 else if(!messageDictionary){
260 secerror("IDS Transport: message is null?");
261 }
262
263 return isPingMessage;
264 }
265
266 -(BOOL) sendFragmentedIDSMessages:(NSDictionary*)data name:(NSString*) deviceName peer:(NSString*) ourPeerID senderDeviceID:(NSString*)senderDeviceID error:(NSError**) error
267 {
268 BOOL result = false;
269 BOOL isPingMessage = false;
270
271 NSError* localError = nil;
272
273 NSString* operationTypeAsString = [data objectForKey: (__bridge NSString*)kIDSOperationType];
274 NSMutableDictionary *messageDictionary = [data objectForKey: (__bridge NSString*)kIDSMessageToSendKey];
275
276 isPingMessage = [self isMessageAPing:data];
277
278 //check the peer cache for the next time to send timestamp
279 //if the timestamp is set in the future, reroute the message to KVS
280 //otherwise send the message over IDS
281 if(![self shouldProxySendMessage:deviceName] && [KeychainSyncingOverIDSProxy idsProxy].allowKVSFallBack)
282 {
283 if(isPingMessage){
284 secnotice("IDS Transport", "peer negative cache check: peer cannot send yet. not sending ping message");
285 return true;
286 }
287 else{
288 [self sendMessageToKVS:messageDictionary];
289 return true;
290 }
291 }
292
293 if(isPingMessage){ //foward the ping message, no processing
294 result = [self sendIDSMessage:data
295 name:deviceName
296 peer:ourPeerID
297 senderDeviceID:senderDeviceID];
298 if(!result){
299 secerror("Could not send ping message");
300 }
301 return result;
302 }
303
304 NSString *localMessageIdentifier = [[NSUUID UUID] UUIDString];
305
306 bool fragment = [operationTypeAsString intValue] == kIDSKeychainSyncIDSFragmentation;
307 bool useAckModel = fragment && [[data objectForKey:kIDSMessageUseACKModel] compare: @"YES"] == NSOrderedSame;
308
309 __block NSData *keychainData = nil;
310 __block NSString *theirPeerID = nil;
311
312 [messageDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
313 if ([key isKindOfClass:[NSString class]] && [obj isKindOfClass:[NSData class]]) {
314 theirPeerID = (NSString*)key;
315 keychainData = (NSData*)obj;
316 }
317 *stop = YES;
318 }];
319
320 if(fragment && keychainData && [keychainData length] >= kMaxIDSMessagePayloadSize){
321 secnotice("IDS Transport","sending chunked keychain messages");
322 result = [self chunkAndSendKeychainPayload:keychainData
323 deviceID:deviceName
324 ourPeerID:ourPeerID
325 theirPeerID:theirPeerID
326 operation:operationTypeAsString
327 uuid:localMessageIdentifier
328 senderDeviceID:senderDeviceID
329 error:&localError];
330 }
331 else{
332 NSMutableDictionary* dataCopy = [NSMutableDictionary dictionaryWithDictionary:data];
333 [dataCopy setObject:localMessageIdentifier forKey:(__bridge NSString*)kIDSMessageUniqueID];
334
335 result = [self sendIDSMessage:dataCopy
336 name:deviceName
337 peer:ourPeerID
338 senderDeviceID:senderDeviceID];
339 }
340
341 if(result && useAckModel && [KeychainSyncingOverIDSProxy idsProxy].allowKVSFallBack){
342 secnotice("IDS Transport", "setting ack timer");
343 [self setMessageTimer:localMessageIdentifier deviceID:deviceName message:data];
344 }
345
346 secnotice("IDS Transport","returning result: %d, error: %@", result, error ? *error : nil);
347 return result;
348 }
349
350 -(void) updateNextTimeToSendFor5Minutes:(NSString*)ID
351 {
352 secnotice("IDS Transport", "Setting next time to send in 5 minutes for device: %@", ID);
353
354 NSTimeInterval backOffInterval = (KVS_BACKOFF * 60);
355 NSDate *nextTimeToTransmit = [NSDate dateWithTimeInterval:backOffInterval sinceDate:[NSDate date]];
356
357 [self.peerNextSendCache setObject:nextTimeToTransmit forKey:ID];
358 }
359
360 - (void)ackTimerFired:(NSString*)identifier deviceID:(NSString*)ID
361 {
362 secnotice("IDS Transport", "IDS device id: %@, Ping timeout is up for message identifier: %@", ID, identifier);
363
364 //call securityd to sync with device over KVS
365 NSMutableDictionary * __block message;
366 dispatch_sync(self.dataQueue, ^{
367 message = [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight objectForKey:identifier];
368 [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight removeObjectForKey:identifier];
369 });
370 if(!message){
371 return;
372 }
373 NSDictionary *mesageInFlight = [message objectForKey:(__bridge NSString*)kIDSMessageToSendKey];
374
375 [[KeychainSyncingOverIDSProxy idsProxy] printMessage:mesageInFlight state:@"timeout occured, rerouting to KVS"];
376
377 //cleanup timers
378 dispatch_async(self.pingQueue, ^{
379 dispatch_source_t timer = [[KeychainSyncingOverIDSProxy idsProxy].pingTimers objectForKey:identifier]; //remove timer
380 if(timer != nil)
381 dispatch_cancel(timer); //cancel timer
382 [[KeychainSyncingOverIDSProxy idsProxy].pingTimers removeObjectForKey:identifier];
383 });
384
385 [self sendMessageToKVS:mesageInFlight];
386
387 //setting next time to send
388 [self updateNextTimeToSendFor5Minutes:ID];
389
390 [[KeychainSyncingOverIDSProxy idsProxy] persistState];
391 }
392
393 -(void) setMessageTimer:(NSString*)identifier deviceID:(NSString*)ID message:(NSDictionary*)message
394 {
395 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
396 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
397
398 dispatch_source_set_event_handler(timer, ^{
399 [self ackTimerFired:identifier deviceID:ID];
400 });
401 dispatch_resume(timer);
402 //restructure message in flight
403
404
405 //set the timer for message id
406 dispatch_async(self.pingQueue, ^{
407 [self.pingTimers setObject:timer forKey:identifier];
408 });
409
410 dispatch_sync(self.dataQueue, ^{
411 [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight setObject:message forKey:identifier];
412 });
413 [[KeychainSyncingOverIDSProxy idsProxy] persistState];
414 }
415
416 //had an immediate error, remove it from messages in flight, and immediately send it over KVS
417 -(void) cleanupAfterHardIDSError:(NSDictionary*)data
418 {
419 NSString *messageIdentifier = [data objectForKey:(__bridge NSString*)kIDSMessageUniqueID];
420 NSMutableDictionary * __block messageToSendToKVS = nil;
421
422 if(messageIdentifier != nil){
423 secerror("removing message id: %@ from message timers", messageIdentifier);
424 dispatch_sync(self.dataQueue, ^{
425 messageToSendToKVS = [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight objectForKey:messageIdentifier];
426 [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight removeObjectForKey:messageIdentifier];
427 });
428 if(!messageToSendToKVS){
429 secnotice("IDS Transport", "no message for identifier: %@", messageIdentifier);
430 return;
431 }
432 [[KeychainSyncingOverIDSProxy idsProxy] printMessage:messageToSendToKVS state:@"IDS rejected send, message rerouted to KVS"];
433
434 //cleanup timer for message
435 dispatch_async(self.pingQueue, ^{
436 dispatch_source_t timer = [[KeychainSyncingOverIDSProxy idsProxy].pingTimers objectForKey:messageIdentifier]; //remove timer
437 if(timer)
438 dispatch_cancel(timer); //cancel timer
439 [[KeychainSyncingOverIDSProxy idsProxy].pingTimers removeObjectForKey:messageIdentifier];
440 });
441 }
442
443 NSDictionary *messageInFlight = [messageToSendToKVS objectForKey:(__bridge NSString*)kIDSMessageToSendKey];
444
445 if([messageInFlight isKindOfClass:[NSDictionary class]]){
446 [[KeychainSyncingOverIDSProxy idsProxy] printMessage:messageInFlight state:@"IDS rejected send, message rerouted to KVS"];
447 [self sendMessageToKVS:messageInFlight];
448 }
449 }
450
451 -(BOOL) sendIDSMessage:(NSDictionary*)data name:(NSString*) deviceName peer:(NSString*) peerID senderDeviceID:(NSString*)senderDeviceID
452 {
453
454 if(!self->_service){
455 secerror("Could not send message to peer: %@: IDS delegate uninitialized, can't use IDS to send this message", deviceName);
456 return NO;
457 }
458
459 NSMutableDictionary *dataCopy = [NSMutableDictionary dictionaryWithDictionary: data];
460
461 __block NSString* senderDeviceIDCopy = nil;
462 if(senderDeviceID){
463 senderDeviceIDCopy = [[NSString alloc]initWithString:senderDeviceID];
464 }
465 else{
466 secnotice("IDS Transport", "device id doesn't exist for peer:%@", peerID);
467 senderDeviceIDCopy = [NSString string];
468 }
469
470 dispatch_async(self.calloutQueue, ^{
471
472 IDSMessagePriority priority = IDSMessagePriorityHigh;
473 BOOL encryptionOff = YES;
474 NSString *sendersPeerIDKey = [ NSString stringWithUTF8String: kMessageKeySendersPeerID];
475
476 NSDictionary *options = @{IDSSendMessageOptionForceEncryptionOffKey : [NSNumber numberWithBool:encryptionOff] };
477
478 //set our peer id and a unique id for this message
479 [dataCopy setObject:peerID forKey:sendersPeerIDKey];
480 [dataCopy setObject:senderDeviceIDCopy forKey:kIDSMessageSendersDeviceID];
481 secnotice("IDS Transport","Our device Name: %@", senderDeviceID);
482 [[KeychainSyncingOverIDSProxy idsProxy] printMessage:dataCopy state:@"sending"];
483
484 NSDictionary *info;
485 NSInteger errorCode = 0;
486 NSUInteger numberOfDevices = 0;
487 NSString *errMessage = nil;
488 NSMutableSet *destinations = nil;
489 NSError *localError = nil;
490 NSString *identifier = nil;
491 IDSDevice *device = nil;
492 NSArray* listOfDevices = [self->_service devices];
493 numberOfDevices = [listOfDevices count];
494
495 require_action_quiet(numberOfDevices > 0, fail, errorCode = kSecIDSErrorNotRegistered; errMessage=createErrorString(@"Could not send message to peer: %@: IDS devices are not registered yet", deviceName));
496 secnotice("IDS Transport","List of devices: %@", [self->_service devices]);
497
498 destinations = [NSMutableSet set];
499 for(NSUInteger i = 0; i < numberOfDevices; i++){
500 device = listOfDevices[i];
501 if( [ deviceName compare:device.uniqueID ] == 0){
502 [destinations addObject: IDSCopyIDForDevice(device)];
503 }
504 }
505 require_action_quiet([destinations count] != 0, fail, errorCode = kSecIDSErrorCouldNotFindMatchingAuthToken; errMessage = createErrorString(@"Could not send message to peer: %@: IDS device ID for peer does not match any devices within an IDS Account", deviceName));
506
507 bool result = [self->_service sendMessage:dataCopy toDestinations:destinations priority:priority options:options identifier:&identifier error:&localError ] ;
508
509 [KeychainSyncingOverIDSProxy idsProxy].outgoingMessages++;
510 require_action_quiet(localError == nil && result, fail, errorCode = kSecIDSErrorFailedToSend; errMessage = createErrorString(@"Had an error sending IDS message to peer: %@", deviceName));
511
512 [[KeychainSyncingOverIDSProxy idsProxy] printMessage:dataCopy state:@"sent!"];
513 fail:
514
515 if(errMessage != nil){
516 info = [ NSDictionary dictionaryWithObjectsAndKeys:errMessage, NSLocalizedDescriptionKey, nil ];
517 localError = [[NSError alloc] initWithDomain:@"com.apple.security.ids.error" code:errorCode userInfo:info ];
518 secerror("%@", localError);
519 [self cleanupAfterHardIDSError: data];
520 }
521 });
522
523 return YES;
524 }
525
526 @end