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