/ KeychainCircle / KCJoiningRequestSecretSession.m
KCJoiningRequestSecretSession.m
  1  //
  2  //  KCJoiningSession.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/KCSRPContext.h>
 14  
 15  #import <KeychainCircle/KCJoiningMessages.h>
 16  
 17  #include <corecrypto/ccrng.h>
 18  #include <corecrypto/ccsha2.h>
 19  #include <corecrypto/ccdh_gp.h>
 20  #include <corecrypto/ccder.h>
 21  #import <Security/SecureObjectSync/SOSTypes.h>
 22  #include <utilities/debugging.h>
 23  
 24  #if OCTAGON
 25  #import <Security/OTConstants.h>
 26  #import "keychain/ot/OTControl.h"
 27  #import "keychain/ot/OTControlProtocol.h"
 28  #import "keychain/ot/OctagonControlServer.h"
 29  #import "keychain/ot/OTJoiningConfiguration.h"
 30  #import "KeychainCircle/KCJoiningRequestSession+Internal.h"
 31  
 32  #import "keychain/ot/proto/generated_source/OTApplicantToSponsorRound2M1.h"
 33  #import "keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.h"
 34  #import "keychain/ot/proto/generated_source/OTSponsorToApplicantRound1M2.h"
 35  #import "keychain/ot/proto/generated_source/OTGlobalEnums.h"
 36  #import "keychain/ot/proto/generated_source/OTSupportSOSMessage.h"
 37  #import "keychain/ot/proto/generated_source/OTSupportOctagonMessage.h"
 38  #import "keychain/ot/proto/generated_source/OTPairingMessage.h"
 39  #endif
 40  #import <KeychainCircle/NSError+KCCreationHelpers.h>
 41  
 42  typedef enum {
 43      kExpectingB,
 44      kExpectingHAMK,
 45      kRequestSecretDone
 46  } KCJoiningRequestSecretSessionState;
 47  
 48  #if OCTAGON
 49  static bool KCJoiningOctagonPiggybackingDefault = false;
 50  bool KCSetJoiningOctagonPiggybackingEnabled(bool value)
 51  {
 52      KCJoiningOctagonPiggybackingDefault = value;
 53      return value;
 54  }
 55  
 56  // defaults write com.apple.security.octagon enable -bool YES
 57  bool KCJoiningOctagonPiggybackingEnabled() {
 58      bool result = KCJoiningOctagonPiggybackingDefault ? KCJoiningOctagonPiggybackingDefault : OctagonIsEnabled();
 59      secnotice("octagon", "Octagon Piggybacking is %@ ", result ? @"on" : @"off");
 60      return result;
 61  }
 62  #endif
 63  
 64  
 65  @interface KCJoiningRequestSecretSession ()
 66  @property (weak) id<KCJoiningRequestSecretDelegate> secretDelegate;
 67  @property (readonly) KCSRPClientContext* context;
 68  @property (readonly) uint64_t dsid;
 69  @property (readonly) KCJoiningRequestSecretSessionState state;
 70  @property (readwrite) NSString* piggy_uuid;
 71  @property (readwrite) uint64_t piggy_version;
 72  @property (readwrite) uint64_t epoch;
 73  @property (readwrite) NSData* challenge;
 74  @property (readwrite) NSData* salt;
 75  @property (readwrite) NSString* sessionUUID;
 76  
 77  #if OCTAGON
 78  @property (nonatomic, strong) OTControl *otControl;
 79  #endif
 80  @property (nonatomic, strong) NSMutableDictionary *defaults;
 81  @end
 82  
 83  @implementation KCJoiningRequestSecretSession : NSObject
 84  
 85  
 86  - (nullable NSData*) createUUID
 87  {
 88      NSUUID *uuid = [NSUUID UUID];
 89      uuid_t uuidBytes;
 90  
 91      self.piggy_uuid = [uuid UUIDString];
 92      [uuid getUUIDBytes:uuidBytes];
 93      NSData *uuidData = [NSData dataWithBytes:uuidBytes length:sizeof(uuid_t)];
 94      return uuidData;
 95  }
 96  
 97  - (nullable NSData*) initialMessage: (NSError**) error {
 98      NSData* start = [self->_context copyStart: error];
 99      if (start == nil) return nil;
100      
101      NSMutableData* initialMessage = NULL;
102      secnotice("joining", "joining: KCJoiningRequestSecretSession initialMessage called");
103  
104      if(self.piggy_version == kPiggyV2){
105  #if OCTAGON
106          if(KCJoiningOctagonPiggybackingEnabled()){
107              NSData* uuidData = [self createUUID];
108  
109              NSString* version = @"o";
110              NSData* octagonVersion = [version dataUsingEncoding:kCFStringEncodingUTF8];
111  
112              initialMessage = [NSMutableData dataWithLength: sizeof_initialmessage_version2(start, kPiggyV1, uuidData, octagonVersion)];
113  
114              if (NULL == encode_initialmessage_version2(start, uuidData, octagonVersion, error, initialMessage.mutableBytes, initialMessage.mutableBytes + initialMessage.length)){
115                  secerror("failed to create version 2 message");
116                  return nil;
117              }
118          }
119  #endif
120      }
121      else if(self.piggy_version == kPiggyV1){
122          NSData* uuidData = [self createUUID];
123          initialMessage = [NSMutableData dataWithLength: sizeof_initialmessage_version1(start, kPiggyV1, uuidData)];
124  
125          if (NULL == encode_initialmessage_version1(start, uuidData, kPiggyV1, error, initialMessage.mutableBytes, initialMessage.mutableBytes + initialMessage.length)){
126              secerror("failed to create version 1 message: %@", *error);
127              return nil;
128          }
129      }
130      else{
131          initialMessage = [NSMutableData dataWithLength: sizeof_initialmessage(start)];
132          if (NULL == encode_initialmessage(start, error, initialMessage.mutableBytes, initialMessage.mutableBytes + initialMessage.length)){
133              return nil;
134          }
135      }
136      
137      return initialMessage;
138  }
139  
140  - (bool) isDone {
141      return self->_state == kRequestSecretDone;
142  }
143  
144  - (bool) setupSession: (NSError**) error {
145      NSData* key = [self->_context getKey];
146  
147      if (key == nil) {
148          KCJoiningErrorCreate(kInternalError, error, @"No session key available");
149          return nil;
150      }
151  
152      self->_session = [KCAESGCMDuplexSession sessionAsSender:key context:self.dsid];
153      self.session.pairingUUID = self.sessionUUID;
154      self.session.piggybackingVersion = self.piggy_version;
155  
156      return self.session != nil;
157  }
158  
159  - (nullable NSData*) copyResponseForChallenge:(NSData*) challenge
160                                           salt:(NSData*) salt
161                                         secret: (NSString*) password
162                                          error: (NSError**) error {
163  
164      secnotice("joining", "joining: KCJoiningRequestSecretSession copyResponseForChallenge called");
165      NSData* response = [self->_context copyResposeToChallenge:challenge
166                                                       password:password
167                                                           salt:salt
168                                                          error:error];
169  
170      if (!response) {
171          // @@@ return error to other side???
172          return nil;
173      } else {
174          if (![self setupSession: error]) return nil;
175  
176          self.challenge = challenge;
177          self.salt = salt;
178  
179          self->_state = kExpectingHAMK;
180          return [[KCJoiningMessage messageWithType:kResponse
181                                               data:response
182                                              error:error] der];
183      }
184  }
185  
186  
187  - (nullable NSData*) copyResponseForSecret: (NSString*) password
188                                       error: (NSError**) error {
189      return [self copyResponseForChallenge:self.challenge salt:self.salt secret:password error:error];
190  }
191  
192  - (nullable NSData*) handleChallengeData: (NSData*) challengeData
193                                    secret: (NSString*) password
194                                     error: (NSError**) error {
195      secnotice("joining", "joining: KCJoiningRequestSecretSession handleChallengeData called");
196      NSData* challenge = nil;
197      NSData* salt = nil;
198  
199      if (![challengeData decodeSequenceData:&salt data:&challenge error:error]) return nil;
200  
201      return [self copyResponseForChallenge:challenge salt:salt secret:password error:error];
202  
203  }
204  
205  - (nullable NSData*) handleChallenge: (KCJoiningMessage*) message
206                                secret: (NSString*) password
207                                 error: (NSError**)error {
208      secnotice("joining", "joining: KCJoiningRequestSecretSession handleChallenge called");
209      // Parse the challenge message
210      // Salt and Challenge packet
211      if ([message type] != kChallenge) {
212          KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected challenge!");
213          return nil;
214      }
215  #if OCTAGON
216      //handle octagon data if it exists
217      if (KCJoiningOctagonPiggybackingEnabled()){
218          self.piggy_version = [message secondData] ? kPiggyV2 : kPiggyV1;
219  
220          // The session may or may not exist at this point. If it doesn't, the version will be set at object creation time.
221          self.session.piggybackingVersion = self.piggy_version;
222  
223          if (self.piggy_version == kPiggyV2){
224              OTPairingMessage* pairingMessage = [[OTPairingMessage alloc]initWithData: [message secondData]];
225              if (pairingMessage.hasEpoch) {
226                  secnotice("octagon", "received epoch message: %@", [pairingMessage.epoch dictionaryRepresentation]);
227                  self.epoch = pairingMessage.epoch.epoch;
228              }
229              else{
230                  secerror("octagon: acceptor did not send its epoch. discontinuing octagon protocol. downgrading to verison 1");
231                  self.piggy_version = kPiggyV1;
232              }
233          }
234      }else{
235          self.piggy_version = kPiggyV1;
236      }
237  #endif
238      return [self handleChallengeData:[message firstData] secret:password error:error];
239  }
240  
241  - (NSData*) handleChallenge: (KCJoiningMessage*) message error: (NSError**)error {
242      return [self handleChallenge:message
243                            secret:[self.secretDelegate secret]
244                             error:error];
245  
246  }
247  
248  - (NSData*) handleVerification: (KCJoiningMessage*) message error: (NSError**) error {
249      secnotice("joining", "joining: KCJoiningRequestSecretSession handleVerification called");
250      id<KCJoiningRequestSecretDelegate> secretDelegate = self.secretDelegate;
251  
252      if ([message type] == kError) {
253          bool newCode = [[message firstData] length] == 0;
254          NSString* nextSecret = [secretDelegate verificationFailed: newCode];
255  
256          if (nextSecret) {
257              if (newCode) {
258                  return [self copyResponseForSecret:nextSecret error:error];
259              } else {
260                  return [self handleChallengeData:[message firstData] secret:nextSecret error:error];
261              }
262          } else {
263              return nil;
264          }
265      }
266  
267      if ([message type] != kVerification) {
268          KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected verification!");
269          return nil;
270      }
271  
272      if (![self.context verifyConfirmation:[message firstData] error:error]) {
273          // Sender thought we had it right, but he can't prove he has it right!
274          KCJoiningErrorCreate(kInternalError, error, @"Got verification but  acceptor doesn't have matching secret: %@", self);
275          secnotice("request-session", "Verification failed: %@", self);
276          return nil;
277      }
278  
279      {
280          NSData* payload = [self.session decryptAndVerify:[message secondData] error:error];
281          if (payload == nil) return nil;
282  
283          NSString* accountCode = [NSString decodeFromDER:payload error:error];
284          if (accountCode == nil) return nil;
285  
286          if (![secretDelegate processAccountCode:accountCode error:error]) return nil;
287      }
288  
289      self->_state = kRequestSecretDone;
290  
291      return [NSData data];
292  }
293  
294  - (NSData*) processMessage: (NSData*) incomingMessage error: (NSError**) error {
295      secnotice("joining", "joining: KCJoiningRequestSecretSession processMessage called");
296      NSData* result = nil;
297      KCJoiningMessage* message = [KCJoiningMessage messageWithDER: incomingMessage error: error];
298      if (message == nil) return nil;
299  
300      switch(self->_state) {
301          case kExpectingB:
302              return [self handleChallenge:message error: error];
303              break;
304          case kExpectingHAMK:
305              return [self handleVerification:message error:error];
306              break;
307          case kRequestSecretDone:
308              KCJoiningErrorCreate(kUnexpectedMessage, error, @"Done, no messages expected.");
309              break;
310      }
311  
312      return result;
313  }
314  
315  + (nullable instancetype)sessionWithSecretDelegate: (NSObject<KCJoiningRequestSecretDelegate>*) secretDelegate
316                                                dsid: (uint64_t)dsid
317                                               error: (NSError**) error {
318      return [[KCJoiningRequestSecretSession alloc] initWithSecretDelegate:secretDelegate
319                                                                      dsid:dsid
320                                                                     error:error];
321  }
322  
323  - (nullable instancetype)initWithSecretDelegate: (NSObject<KCJoiningRequestSecretDelegate>*) secretDelegate
324                                             dsid: (uint64_t)dsid
325                                            error: (NSError**)error {
326      int cc_error = 0;
327      struct ccrng_state * rng = ccrng(&cc_error);
328  
329      if (rng == nil) {
330          CoreCryptoError(cc_error, error, @"RNG fetch failed");
331          return nil;
332      }
333  
334      return [self initWithSecretDelegate: secretDelegate
335                                     dsid: dsid
336                                      rng: rng
337                                    error: error];
338  }
339  
340  - (nullable instancetype)initWithSecretDelegate: (NSObject<KCJoiningRequestSecretDelegate>*) secretDelegate
341                                             dsid: (uint64_t)dsid
342                                              rng: (struct ccrng_state *)rng
343                                            error: (NSError**)error {
344      secnotice("joining", "joining: initWithSecretDelegate called");
345      if ((self = [super init])) {
346          self->_secretDelegate = secretDelegate;
347          self->_state = kExpectingB;
348          self->_dsid = dsid;
349          self->_defaults = [NSMutableDictionary dictionary];
350  
351  #if OCTAGON
352          self->_piggy_version = KCJoiningOctagonPiggybackingEnabled() ? kPiggyV2 : kPiggyV1;
353          self->_otControl = [OTControl controlObject:true error:error];
354  
355          _sessionUUID = [[NSUUID UUID] UUIDString];
356  #else
357          self->_piggy_version = kPiggyV1;
358  #endif
359  
360          secnotice("joining", "joining: initWithSecretDelegate called, uuid=%@", self.sessionUUID);
361  
362          NSString* name = [NSString stringWithFormat: @"%llu", dsid];
363      
364          self->_context = [[KCSRPClientContext alloc] initWithUser: name
365                                                         digestInfo: ccsha256_di()
366                                                              group: ccsrp_gp_rfc5054_3072()
367                                                       randomSource: rng];
368      }
369      return self;
370  }
371  
372  - (NSString*) stateString {
373      switch (self.state) {
374          case kExpectingB: return @"→B";
375          case kExpectingHAMK: return @"→HAMK";
376          case kRequestSecretDone: return @"SecretDone";
377          default: return [NSString stringWithFormat:@"%d", self.state];
378      }
379  }
380  
381  - (NSString *)description {
382      return [NSString stringWithFormat: @"<KCJoiningAcceptSession@%p %lld %@ %@>", self, self.dsid, [self stateString], self.context];
383  }
384  #if OCTAGON
385  /* for test */
386  -(void)setControlObject:(OTControl*)control
387  {
388      self.otControl = control;
389  }
390  #endif
391  
392  @end