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