/ 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