/ 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