/ KeychainCircle / KCJoiningAcceptSession.m
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