]> git.saurik.com Git - apple/security.git/blob - KeychainCircle/KCJoiningAcceptSession.m
Security-59754.41.1.tar.gz
[apple/security.git] / KeychainCircle / KCJoiningAcceptSession.m
1 //
2 // KCJoiningAcceptSession.m
3 // Security
4 //
5 //
6
7 #import <Foundation/Foundation.h>
8
9 #import <KeychainCircle/KCJoiningSession.h>
10
11 #import <KeychainCircle/KCError.h>
12 #import <KeychainCircle/KCDer.h>
13 #import <KeychainCircle/KCJoiningMessages.h>
14
15 #import <KeychainCircle/NSError+KCCreationHelpers.h>
16 #import "KCInitialMessageData.h"
17
18 #include <corecrypto/ccder.h>
19 #include <corecrypto/ccrng.h>
20 #include <corecrypto/ccsha2.h>
21 #include <corecrypto/ccdh_gp.h>
22 #include <utilities/debugging.h>
23 #include <notify.h>
24
25 #if OCTAGON
26 #import "keychain/ot/OTControl.h"
27 #import "keychain/ot/OTJoiningConfiguration.h"
28 #import "KeychainCircle/KCJoiningAcceptSession+Internal.h"
29 #import "keychain/ot/proto/generated_source/OTApplicantToSponsorRound2M1.h"
30 #import "keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.h"
31 #import "keychain/ot/proto/generated_source/OTSponsorToApplicantRound1M2.h"
32 #import "keychain/ot/proto/generated_source/OTGlobalEnums.h"
33 #import "keychain/ot/proto/generated_source/OTSupportSOSMessage.h"
34 #import "keychain/ot/proto/generated_source/OTSupportOctagonMessage.h"
35 #import "keychain/ot/proto/generated_source/OTPairingMessage.h"
36 #endif
37
38 typedef enum {
39 kExpectingA,
40 kExpectingM,
41 kExpectingPeerInfo,
42 kAcceptDone
43 } KCJoiningAcceptSessionState;
44
45 @interface KCJoiningAcceptSession ()
46 @property (readonly) uint64_t dsid;
47 @property (weak) id<KCJoiningAcceptSecretDelegate> secretDelegate;
48 @property (weak) id<KCJoiningAcceptCircleDelegate> circleDelegate;
49 @property (readonly) KCSRPServerContext* context;
50 @property (readonly) KCAESGCMDuplexSession* session;
51 @property (readonly) KCJoiningAcceptSessionState state;
52 @property (readwrite) NSData* startMessage;
53 @property (readwrite) NSString *piggy_uuid;
54 @property (readwrite) PiggyBackProtocolVersion piggy_version;
55 @property (readwrite) NSData* octagon;
56 #if OCTAGON
57 @property (nonatomic, strong) OTJoiningConfiguration* joiningConfiguration;
58 @property (nonatomic, strong) OTControl* otControl;
59 #endif
60 @property (nonatomic, strong) NSMutableDictionary *defaults;
61 @end
62
63 @implementation KCJoiningAcceptSession
64
65 + (nullable instancetype) sessionWithInitialMessage: (NSData*) message
66 secretDelegate: (NSObject<KCJoiningAcceptSecretDelegate>*) secretDelegate
67 circleDelegate: (NSObject<KCJoiningAcceptCircleDelegate>*) circleDelegate
68 dsid: (uint64_t) dsid
69 error: (NSError**) error {
70
71 int cc_error = 0;
72 struct ccrng_state * rng = ccrng(&cc_error);
73
74 if (rng == nil) {
75 CoreCryptoError(cc_error, error, @"RNG fetch failed");
76 return nil;
77 }
78
79 return [[KCJoiningAcceptSession alloc] initWithSecretDelegate: secretDelegate
80 circleDelegate: circleDelegate
81 dsid: dsid
82 rng: rng
83 error: error];
84 }
85
86 - (bool) setupSession: (NSError**) error {
87 NSData* key = [self->_context getKey];
88
89 if (key == nil) {
90 KCJoiningErrorCreate(kInternalError, error, @"No session key available");
91 return nil;
92 }
93
94 self->_session = [KCAESGCMDuplexSession sessionAsReceiver:key context:self.dsid];
95 #if OCTAGON
96 self.session.pairingUUID = self.joiningConfiguration.pairingUUID;
97 #endif
98 self.session.piggybackingVersion = self.piggy_version;
99
100 return self.session != nil;
101 }
102
103 - (nullable instancetype) initWithSecretDelegate: (NSObject<KCJoiningAcceptSecretDelegate>*) secretDelegate
104 circleDelegate: (NSObject<KCJoiningAcceptCircleDelegate>*) circleDelegate
105 dsid: (uint64_t) dsid
106 rng: (struct ccrng_state *)rng
107 error: (NSError**) error {
108 if ((self = [super init])) {
109
110 secnotice("accepting", "initWithSecretDelegate");
111
112 NSString* name = [NSString stringWithFormat: @"%llu", dsid];
113
114 self->_context = [[KCSRPServerContext alloc] initWithUser: name
115 password: [secretDelegate secret]
116 digestInfo: ccsha256_di()
117 group: ccsrp_gp_rfc5054_3072()
118 randomSource: rng];
119 self.secretDelegate = secretDelegate;
120 self.circleDelegate = circleDelegate;
121 self->_state = kExpectingA;
122 self->_dsid = dsid;
123 self->_piggy_uuid = nil;
124 self->_defaults = [NSMutableDictionary dictionary];
125
126 #if OCTAGON
127 self->_otControl = [OTControl controlObject:true error:error];
128 self->_piggy_version = KCJoiningOctagonPiggybackingEnabled()? kPiggyV2 : kPiggyV1;
129 self->_joiningConfiguration = [[OTJoiningConfiguration alloc]initWithProtocolType:@"OctagonPiggybacking"
130 uniqueDeviceID:@"acceptor-deviceid"
131 uniqueClientID:@"requester-deviceid"
132 pairingUUID:[[NSUUID UUID] UUIDString]
133 containerName:nil
134 contextID:OTDefaultContext
135 epoch:0
136 isInitiator:false];
137 #else
138 self->_piggy_version = kPiggyV1;
139 #endif
140 }
141 return self;
142 }
143
144 - (NSString*) stateString {
145 switch (self.state) {
146 case kExpectingA: return @"→A";
147 case kExpectingM: return @"→M";
148 case kExpectingPeerInfo: return @"→PeerInfo";
149 case kAcceptDone: return @"done";
150 default: return [NSString stringWithFormat:@"%d", self.state];
151 }
152 }
153
154 - (NSString *)description {
155 return [NSString stringWithFormat: @"<KCJoiningAcceptSession: %lld %@ %@ uuid: %@>", self.dsid, [self stateString], self.context, self.piggy_uuid];
156 }
157
158 - (NSData*) copyChallengeMessage: (NSError**) error {
159 NSData* challenge = [self.context copyChallengeFor: self.startMessage error: error];
160 if (challenge == nil) return nil;
161
162 NSData* srpMessage = [NSData dataWithEncodedSequenceData:self.context.salt data:challenge error:error];
163
164 if (![self setupSession:error]) return nil;
165
166 return srpMessage;
167 }
168
169 #if OCTAGON
170 - (BOOL)shouldAcceptOctagonRequests {
171 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
172 __block BOOL result = NO;
173
174 OTOperationConfiguration* configuration = [[OTOperationConfiguration alloc] init];
175 configuration.discretionaryNetwork = TRUE;
176
177 [self.otControl fetchTrustStatus:self.joiningConfiguration.containerName context:self.joiningConfiguration.self.contextID
178 configuration:configuration
179 reply:^(CliqueStatus status,
180 NSString* peerID,
181 NSNumber * _Nullable numberOfPeersInOctagon,
182 BOOL isExcluded, NSError* _Nullable error)
183 {
184 secerror("octagon haveSelfEgo: status %d: %@ %@ %d: %@", (int)status,
185 peerID, numberOfPeersInOctagon, isExcluded, error);
186
187 if (status == CliqueStatusIn) {
188 result = YES;
189 }
190 dispatch_semaphore_signal(sema);
191 }];
192
193 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 30)) != 0) {
194 secerror("octagon: timed out fetching trust status");
195 return NO;
196 }
197 return result;
198 }
199 #endif
200
201 - (NSData*) processInitialMessage: (NSData*) initialMessage error: (NSError**) error {
202 __block uint64_t version = 0;
203 NSString *uuid = nil;
204 NSData *octagon = nil;
205 NSError* localError = nil;
206
207 self.startMessage = extractStartFromInitialMessage(initialMessage, &version, &uuid, &octagon, error);
208 if (self.startMessage == NULL) {
209 return nil;
210 }
211 #if OCTAGON
212 if(version == kPiggyV2 && KCJoiningOctagonPiggybackingEnabled()){
213 /* before we go ahead with octagon, let see if we are an octagon peer */
214
215 if (![self shouldAcceptOctagonRequests]) {
216 secerror("octagon refusing octagon acceptor since we don't have a selfEgo");
217 version = kPiggyV1;
218 } else {
219 self.octagon = octagon;
220 }
221 localError = nil;
222 }
223 #endif
224 self.piggy_uuid = uuid;
225 self.piggy_version = (PiggyBackProtocolVersion)version;
226
227 NSData* srpMessage = [self copyChallengeMessage: error];
228 if (srpMessage == nil) {
229 return nil;
230 }
231
232 self->_state = kExpectingM;
233 #if OCTAGON
234 NSString* piggyVersionMessage = [[NSString alloc]initWithData:self.octagon encoding:NSUTF8StringEncoding];
235 __block NSError *captureError = nil;
236
237 if(version == kPiggyV2 && KCJoiningOctagonPiggybackingEnabled() && piggyVersionMessage && [piggyVersionMessage isEqualToString:@"o"]) {
238 __block NSData* next = nil;
239 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
240
241 //fetch epoch
242 [self.otControl rpcEpochWithConfiguration:self.joiningConfiguration reply:^(uint64_t epoch, NSError * _Nullable epochError) {
243 if(epochError){
244 secerror("error retrieving next message! :%@", epochError);
245 captureError = epochError;
246 }else{
247 OTPairingMessage* responseMessage = [[OTPairingMessage alloc] init];
248 responseMessage.supportsSOS = [[OTSupportSOSMessage alloc] init];
249 responseMessage.supportsOctagon = [[OTSupportOctagonMessage alloc] init];
250
251 responseMessage.epoch = [[OTSponsorToApplicantRound1M2 alloc] init];
252 responseMessage.epoch.epoch = epoch;
253
254 responseMessage.supportsSOS.supported = OctagonPlatformSupportsSOS() ? OTSupportType_supported : OTSupportType_not_supported;
255 responseMessage.supportsOctagon.supported = OTSupportType_supported;
256 next = responseMessage.data;
257 }
258 dispatch_semaphore_signal(sema);
259 }];
260
261 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 30)) != 0) {
262 secerror("octagon: timed out fetching epoch");
263 return nil;
264 }
265 if(error && captureError){
266 *error = captureError;
267 }
268 return [[KCJoiningMessage messageWithType:kChallenge
269 data:srpMessage
270 payload:next
271 error:error] der];
272 }
273 #endif
274 return [[KCJoiningMessage messageWithType:kChallenge
275 data:srpMessage
276 error:error] der];
277 }
278
279 - (NSData*) processResponse: (KCJoiningMessage*) message error:(NSError**) error {
280 if ([message type] != kResponse) {
281 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected response!");
282 return nil;
283 }
284
285 id<KCJoiningAcceptSecretDelegate> secretDelegate = self.secretDelegate;
286
287 // We handle failure, don't capture the error.
288 NSData* confirmation = [self.context copyConfirmationFor:message.firstData error:NULL];
289 if (!confirmation) {
290 // Find out what kind of error we should send.
291 NSData* errorData = nil;
292
293 KCRetryOrNot status = [secretDelegate verificationFailed: error];
294 secerror("processResponse: handle error: %d", (int)status);
295
296 switch (status) {
297 case kKCRetryError:
298 // We fill in an error if they didn't, but if they did this wont bother.
299 KCJoiningErrorCreate(kInternalError, error, @"Delegate returned error without filling in error: %@", secretDelegate);
300 return nil;
301 case kKCRetryWithSameChallenge:
302 errorData = [NSData data];
303 break;
304 case kKCRetryWithNewChallenge:
305 if ([self.context resetWithPassword:[secretDelegate secret] error:error]) {
306 errorData = [self copyChallengeMessage: error];
307 }
308 break;
309 }
310 if (errorData == nil) return nil;
311
312 return [[KCJoiningMessage messageWithType:kError
313 data:errorData
314 error:error] der];
315 }
316
317 NSData* encoded = [NSData dataWithEncodedString:[secretDelegate accountCode] error:error];
318 if (encoded == nil)
319 return nil;
320
321 NSData* encrypted = [self.session encrypt:encoded error:error];
322 if (encrypted == nil) return nil;
323
324 self->_state = kExpectingPeerInfo;
325
326 return [[KCJoiningMessage messageWithType:kVerification
327 data:confirmation
328 payload:encrypted
329 error:error] der];
330 }
331
332 - (NSData*) processSOSApplication: (NSData*) message error:(NSError**) error
333 {
334 NSData* decryptedPayload = [self.session decryptAndVerify:message error:error];
335 if (decryptedPayload == nil) return nil;
336
337 id<KCJoiningAcceptCircleDelegate> circleDelegate = self.circleDelegate;
338
339 CFErrorRef cfError = NULL;
340 SOSPeerInfoRef ref = SOSPeerInfoCreateFromData(NULL, &cfError, (__bridge CFDataRef) decryptedPayload);
341 if (ref == NULL) {
342 if (error) *error = (__bridge_transfer NSError*) cfError;
343 cfError = NULL;
344 return nil;
345 }
346
347 NSData* joinData = [circleDelegate circleJoinDataFor:ref error:error];
348 if(ref) {
349 CFRelease(ref);
350 ref = NULL;
351 }
352
353 if (joinData == nil) return nil;
354
355 SOSInitialSyncFlags flags = 0;
356 switch (self.piggy_version) {
357 case kPiggyV0:
358 break;
359 case kPiggyV1:
360 secnotice("acceptor", "piggy version is 1");
361 flags |= kSOSInitialSyncFlagTLKs | kSOSInitialSyncFlagiCloudIdentity;
362 break;
363 case kPiggyV2:
364 secnotice("acceptor", "piggy version is 2");
365 flags |= kSOSInitialSyncFlagiCloudIdentity;
366 break;
367 }
368
369 if (flags) {
370 //grab iCloud Identities, TLKs
371 NSError *localISVError = nil;
372 NSData* initialSyncData = [circleDelegate circleGetInitialSyncViews:flags error:&localISVError];
373 if(initialSyncData == NULL){
374 secnotice("piggy", "PB threw an error: %@", localISVError);
375 }
376
377 NSMutableData* growPacket = [[NSMutableData alloc] initWithData:joinData];
378 [growPacket appendData:initialSyncData];
379 joinData = growPacket;
380
381 }
382
383 NSData* encryptedOutgoing = [self.session encrypt:joinData error:error];
384 if (encryptedOutgoing == nil) return nil;
385 return encryptedOutgoing;
386 }
387
388 #if OCTAGON
389 - (OTPairingMessage *)createPairingMessageFromJoiningMessage:(KCJoiningMessage *)message error:(NSError**) error
390 {
391 NSData *decryptInitialMessage = [self.session decryptAndVerify:message.firstData error:error];
392 if(!decryptInitialMessage) {
393 secinfo("KeychainCircle", "Failed to decrypt message first data: %@. Trying legacy OTPairingMessage construction.", error ? *error : @"");
394 return [[OTPairingMessage alloc] initWithData:message.firstData];
395 } else {
396 KCInitialMessageData *initialMessage = [[KCInitialMessageData alloc] initWithData:decryptInitialMessage];
397 if(!initialMessage) {
398 secerror("Failed to parse InitialMessageData from decrypted message data");
399 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Failed to parse InitialMessageData from decrypted message data");
400 return nil;
401 }
402
403 if(!initialMessage.hasPrepareMessage) {
404 secerror("InitialMessageData does not contain prepare message");
405 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected prepare message inside InitialMessageData");
406 return nil;
407 }
408
409 return [[OTPairingMessage alloc] initWithData:initialMessage.prepareMessage];
410 }
411 }
412 #endif
413
414 - (NSData*) createTLKRequestResponse: (NSError**) error {
415 NSError* localError = NULL;
416 NSData* initialSync = [self.circleDelegate circleGetInitialSyncViews:kSOSInitialSyncFlagTLKsRequestOnly error:&localError];
417 if (!initialSync) {
418 secnotice("joining", "Failed to get initial sync view: %@", localError);
419 if ( error!=NULL && localError != NULL )
420 *error = localError;
421 return nil;
422 }
423
424 NSData* encryptedOutgoing = [self.session encrypt:initialSync error:&localError];
425 if (!encryptedOutgoing) {
426 secnotice("joining", "TLK request failed to encrypt: %@", localError);
427 if ( error!=NULL && localError != NULL )
428 *error = localError;
429 return nil;
430 }
431 self->_state = kAcceptDone;
432
433 secnotice("joining", "TLKRequest done.");
434
435 return [[KCJoiningMessage messageWithType:kTLKRequest
436 data:encryptedOutgoing
437 error:error] der];
438 }
439
440
441 - (BOOL)shouldProcessSOSApplication:(KCJoiningMessage*)message pairingMessage:(OTPairingMessage*)pairingMessage
442 {
443 BOOL shouldProcess = YES;
444
445 if (OctagonPlatformSupportsSOS() == NO) {
446 secnotice("joining", "platform does not support SOS");
447 shouldProcess = NO;
448 } else if (message.secondData == nil) {
449 secnotice("joining", "message does not contain SOS data");
450 shouldProcess = NO;
451 } else if (pairingMessage.hasSupportsSOS && pairingMessage.supportsSOS.supported == OTSupportType_not_supported) {
452 secnotice("joining", "requester explicitly does not support SOS");
453 shouldProcess = NO;
454 }
455
456 return shouldProcess;
457 }
458
459
460 - (NSData*) processApplication: (KCJoiningMessage*) message error:(NSError**) error {
461
462 if ([message type] == kTLKRequest) {
463 return [self createTLKRequestResponse: error];
464 }
465
466 if ([message type] != kPeerInfo) {
467 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected peerInfo!");
468 return nil;
469 }
470 #if OCTAGON
471 if(self.piggy_version == kPiggyV2 && KCJoiningOctagonPiggybackingEnabled()){
472 __block NSData* next = nil;
473 __block NSError* localError = nil;
474 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
475
476 OTPairingMessage *pairingMessage = [self createPairingMessageFromJoiningMessage:message error:error];
477 if(!pairingMessage) {
478 secerror("octagon, failed to create pairing message from JoiningMessage");
479 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Failed to create pairing message from JoiningMessage");
480 return nil;
481 }
482
483 if(!pairingMessage.hasPrepare) {
484 secerror("octagon, message does not contain prepare message");
485 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected prepare message!");
486 return nil;
487 }
488 OTApplicantToSponsorRound2M1 *prepareMessage = pairingMessage.prepare;
489
490 //handle identity, fetch voucher
491 [self.otControl rpcVoucherWithConfiguration:self.joiningConfiguration
492 peerID:prepareMessage.peerID
493 permanentInfo:prepareMessage.permanentInfo
494 permanentInfoSig:prepareMessage.permanentInfoSig
495 stableInfo:prepareMessage.stableInfo
496 stableInfoSig:prepareMessage.stableInfoSig reply:^(NSData *voucher,
497 NSData *voucherSig,
498 NSError *err) {
499 if(err){
500 secerror("error producing octagon voucher: %@", err);
501 localError = err;
502 }else{
503 OTPairingMessage *pairingResponse = [[OTPairingMessage alloc] init];
504 pairingResponse.supportsSOS = [[OTSupportSOSMessage alloc] init];
505 pairingResponse.supportsOctagon = [[OTSupportOctagonMessage alloc] init];
506 pairingResponse.voucher = [[OTSponsorToApplicantRound2M2 alloc] init];
507 pairingResponse.voucher.voucher = voucher;
508 pairingResponse.voucher.voucherSignature = voucherSig;
509
510 pairingMessage.supportsSOS.supported = OctagonPlatformSupportsSOS() ? OTSupportType_supported : OTSupportType_not_supported;
511 pairingMessage.supportsOctagon.supported = OTSupportType_supported;
512 next = pairingResponse.data;
513 }
514 dispatch_semaphore_signal(sema);
515 }];
516
517 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 30)) != 0) {
518 secerror("octagon: timed out producing octagon voucher");
519 return nil;
520 }
521 if (next == NULL) {
522 if(error && localError){
523 *error = localError;
524 }
525 return nil;
526 }
527
528 NSData* encryptedOutgoing = nil;
529 if ([self shouldProcessSOSApplication:message pairingMessage:pairingMessage]) {
530 secnotice("joining", "doing SOS processSOSApplication");
531 encryptedOutgoing = [self processSOSApplication: message.secondData error:error];
532 if (encryptedOutgoing == nil) {
533 secerror("joining: failed to process SOS application: %@", error && *error ? *error : nil);
534 KCJoiningErrorCreate(kProcessApplicationFailure, error, @"message failed to process application");
535 return nil;
536 }
537 }
538
539 self->_state = kAcceptDone;
540
541 //note we are stuffing SOS into the payload
542 return [[KCJoiningMessage messageWithType:kCircleBlob
543 data:next
544 payload:encryptedOutgoing
545 error:error] der];
546 }
547 #endif
548 NSData* encryptedOutgoing = [self processSOSApplication: message.firstData error:error];
549
550 self->_state = kAcceptDone;
551
552 secnotice("joining", "posting kSOSCCCircleOctagonKeysChangedNotification");
553 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
554
555 return [[KCJoiningMessage messageWithType:kCircleBlob
556 data:encryptedOutgoing
557 error:error] der];
558 }
559
560
561 - (nullable NSData*) processMessage: (NSData*) incomingMessage error: (NSError**) error {
562 NSData* result = nil;
563
564 secnotice("acceptor", "processMessages: %@", [self description]);
565
566 KCJoiningMessage *message = (self.state != kExpectingA) ? [KCJoiningMessage messageWithDER:incomingMessage error:error] : nil;
567
568 switch(self.state) {
569 case kExpectingA:
570 return [self processInitialMessage:incomingMessage error: error];
571 case kExpectingM:
572 if (message == nil) return nil;
573 return [self processResponse:message error: error];
574 break;
575 case kExpectingPeerInfo:
576 if (message == nil) return nil;
577 return [self processApplication:message error: error];
578 break;
579 case kAcceptDone:
580 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Unexpected message while done");
581 break;
582 }
583 return result;
584 }
585
586 - (bool) isDone {
587 return self.state == kAcceptDone;
588 }
589
590 /* for test*/
591 #if OCTAGON
592 - (void)setControlObject:(OTControl *)control
593 {
594 self.otControl = control;
595 }
596
597 - (void)setConfiguration:(OTJoiningConfiguration *)config
598 {
599 self.joiningConfiguration = config;
600 }
601
602 - (KCAESGCMDuplexSession*)accessSession
603 {
604 return self.session;
605 }
606 #endif
607
608 @end