]> git.saurik.com Git - apple/security.git/blob - KeychainCircle/KCJoiningRequestSecretSession.m
81b9aac8acf62b64b4ea8157e9b8133d9d2238d1
[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 (weak) id<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 (readwrite) NSString* sessionUUID;
75
76 @property (nonatomic, strong) OTControl *otControl;
77 #endif
78 @property (nonatomic, strong) NSMutableDictionary *defaults;
79 @end
80
81 @implementation KCJoiningRequestSecretSession : NSObject
82
83
84 - (nullable NSData*) createUUID
85 {
86 NSUUID *uuid = [NSUUID UUID];
87 uuid_t uuidBytes;
88
89 self.piggy_uuid = [uuid UUIDString];
90 [uuid getUUIDBytes:uuidBytes];
91 NSData *uuidData = [NSData dataWithBytes:uuidBytes length:sizeof(uuid_t)];
92 return uuidData;
93 }
94
95 - (nullable NSData*) initialMessage: (NSError**) error {
96 NSData* start = [self->_context copyStart: error];
97 if (start == nil) return nil;
98
99 NSMutableData* initialMessage = NULL;
100 secnotice("joining", "joining: KCJoiningRequestSecretSession initialMessage called");
101
102 if(self.piggy_version == kPiggyV2){
103 #if OCTAGON
104 if(KCJoiningOctagonPiggybackingEnabled()){
105 NSData* uuidData = [self createUUID];
106
107 NSString* version = @"o";
108 NSData* octagonVersion = [version dataUsingEncoding:kCFStringEncodingUTF8];
109
110 initialMessage = [NSMutableData dataWithLength: sizeof_initialmessage_version2(start, kPiggyV1, uuidData, octagonVersion)];
111
112 if (NULL == encode_initialmessage_version2(start, uuidData, octagonVersion, error, initialMessage.mutableBytes, initialMessage.mutableBytes + initialMessage.length)){
113 secerror("failed to create version 2 message");
114 return nil;
115 }
116 }
117 #endif
118 }
119 else if(self.piggy_version == kPiggyV1){
120 NSData* uuidData = [self createUUID];
121 initialMessage = [NSMutableData dataWithLength: sizeof_initialmessage_version1(start, kPiggyV1, uuidData)];
122
123 if (NULL == encode_initialmessage_version1(start, uuidData, kPiggyV1, error, initialMessage.mutableBytes, initialMessage.mutableBytes + initialMessage.length)){
124 secerror("failed to create version 1 message: %@", *error);
125 return nil;
126 }
127 }
128 else{
129 initialMessage = [NSMutableData dataWithLength: sizeof_initialmessage(start)];
130 if (NULL == encode_initialmessage(start, error, initialMessage.mutableBytes, initialMessage.mutableBytes + initialMessage.length)){
131 return nil;
132 }
133 }
134
135 return initialMessage;
136 }
137
138 - (bool) isDone {
139 return self->_state == kRequestSecretDone;
140 }
141
142 - (bool) setupSession: (NSError**) error {
143 NSData* key = [self->_context getKey];
144
145 if (key == nil) {
146 KCJoiningErrorCreate(kInternalError, error, @"No session key available");
147 return nil;
148 }
149
150 self->_session = [KCAESGCMDuplexSession sessionAsSender:key context:self.dsid];
151 self.session.pairingUUID = self.sessionUUID;
152 self.session.piggybackingVersion = self.piggy_version;
153
154 return self.session != nil;
155 }
156
157 - (nullable NSData*) copyResponseForChallenge:(NSData*) challenge
158 salt:(NSData*) salt
159 secret: (NSString*) password
160 error: (NSError**) error {
161
162 secnotice("joining", "joining: KCJoiningRequestSecretSession copyResponseForChallenge called");
163 NSData* response = [self->_context copyResposeToChallenge:challenge
164 password:password
165 salt:salt
166 error:error];
167
168 if (!response) {
169 // @@@ return error to other side???
170 return nil;
171 } else {
172 if (![self setupSession: error]) return nil;
173
174 self.challenge = challenge;
175 self.salt = salt;
176
177 self->_state = kExpectingHAMK;
178 return [[KCJoiningMessage messageWithType:kResponse
179 data:response
180 error:error] der];
181 }
182 }
183
184
185 - (nullable NSData*) copyResponseForSecret: (NSString*) password
186 error: (NSError**) error {
187 return [self copyResponseForChallenge:self.challenge salt:self.salt secret:password error:error];
188 }
189
190 - (nullable NSData*) handleChallengeData: (NSData*) challengeData
191 secret: (NSString*) password
192 error: (NSError**) error {
193 secnotice("joining", "joining: KCJoiningRequestSecretSession handleChallengeData called");
194 NSData* challenge = nil;
195 NSData* salt = nil;
196
197 if (![challengeData decodeSequenceData:&salt data:&challenge error:error]) return nil;
198
199 return [self copyResponseForChallenge:challenge salt:salt secret:password error:error];
200
201 }
202
203 - (nullable NSData*) handleChallenge: (KCJoiningMessage*) message
204 secret: (NSString*) password
205 error: (NSError**)error {
206 secnotice("joining", "joining: KCJoiningRequestSecretSession handleChallenge called");
207 // Parse the challenge message
208 // Salt and Challenge packet
209 if ([message type] != kChallenge) {
210 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected challenge!");
211 return nil;
212 }
213 #if OCTAGON
214 //handle octagon data if it exists
215 if(KCJoiningOctagonPiggybackingEnabled()){
216 self.piggy_version = [message secondData] ? kPiggyV2 : kPiggyV1;
217
218 // The session may or may not exist at this point. If it doesn't, the version will be set at object creation time.
219 self.session.piggybackingVersion = self.piggy_version;
220
221 if(self.piggy_version == kPiggyV2){
222 OTPairingMessage* pairingMessage = [[OTPairingMessage alloc]initWithData: [message secondData]];
223 if(pairingMessage.hasEpoch) {
224 secnotice("octagon", "received epoch message: %@", [pairingMessage.epoch dictionaryRepresentation]);
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 id<KCJoiningRequestSecretDelegate> secretDelegate = self.secretDelegate;
249
250 if ([message type] == kError) {
251 bool newCode = [[message firstData] length] == 0;
252 NSString* nextSecret = [secretDelegate verificationFailed: newCode];
253
254 if (nextSecret) {
255 if (newCode) {
256 return [self copyResponseForSecret:nextSecret error:error];
257 } else {
258 return [self handleChallengeData:[message firstData] secret:nextSecret error:error];
259 }
260 } else {
261 return nil;
262 }
263 }
264
265 if ([message type] != kVerification) {
266 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected verification!");
267 return nil;
268 }
269
270 if (![self.context verifyConfirmation:[message firstData] error:error]) {
271 // Sender thought we had it right, but he can't prove he has it right!
272 KCJoiningErrorCreate(kInternalError, error, @"Got verification but acceptor doesn't have matching secret: %@", self);
273 secnotice("request-session", "Verification failed: %@", self);
274 return nil;
275 }
276
277 {
278 NSData* payload = [self.session decryptAndVerify:[message secondData] error:error];
279 if (payload == nil) return nil;
280
281 NSString* accountCode = [NSString decodeFromDER:payload error:error];
282 if (accountCode == nil) return nil;
283
284 if (![secretDelegate processAccountCode:accountCode error:error]) return nil;
285 }
286
287 self->_state = kRequestSecretDone;
288
289 return [NSData data];
290 }
291
292 - (NSData*) processMessage: (NSData*) incomingMessage error: (NSError**) error {
293 secnotice("joining", "joining: KCJoiningRequestSecretSession processMessage called");
294 NSData* result = nil;
295 KCJoiningMessage* message = [KCJoiningMessage messageWithDER: incomingMessage error: error];
296 if (message == nil) return nil;
297
298 switch(self->_state) {
299 case kExpectingB:
300 return [self handleChallenge:message error: error];
301 break;
302 case kExpectingHAMK:
303 return [self handleVerification:message error:error];
304 break;
305 case kRequestSecretDone:
306 KCJoiningErrorCreate(kUnexpectedMessage, error, @"Done, no messages expected.");
307 break;
308 }
309
310 return result;
311 }
312
313 + (nullable instancetype)sessionWithSecretDelegate: (NSObject<KCJoiningRequestSecretDelegate>*) secretDelegate
314 dsid: (uint64_t)dsid
315 error: (NSError**) error {
316 return [[KCJoiningRequestSecretSession alloc] initWithSecretDelegate:secretDelegate
317 dsid:dsid
318 error:error];
319 }
320
321 - (nullable instancetype)initWithSecretDelegate: (NSObject<KCJoiningRequestSecretDelegate>*) secretDelegate
322 dsid: (uint64_t)dsid
323 error: (NSError**)error {
324 int cc_error = 0;
325 struct ccrng_state * rng = ccrng(&cc_error);
326
327 if (rng == nil) {
328 CoreCryptoError(cc_error, error, @"RNG fetch failed");
329 return nil;
330 }
331
332 return [self initWithSecretDelegate: secretDelegate
333 dsid: dsid
334 rng: rng
335 error: error];
336 }
337
338 - (nullable instancetype)initWithSecretDelegate: (NSObject<KCJoiningRequestSecretDelegate>*) secretDelegate
339 dsid: (uint64_t)dsid
340 rng: (struct ccrng_state *)rng
341 error: (NSError**)error {
342 secnotice("joining", "joining: initWithSecretDelegate called");
343 self = [super init];
344
345 self->_secretDelegate = secretDelegate;
346 self->_state = kExpectingB;
347 self->_dsid = dsid;
348 self->_defaults = [NSMutableDictionary dictionary];
349
350 #if OCTAGON
351 self->_piggy_version = KCJoiningOctagonPiggybackingEnabled() ? kPiggyV2 : kPiggyV1;
352 self->_otControl = [OTControl controlObject:true error:error];
353
354 _sessionUUID = [[NSUUID UUID] UUIDString];
355 #else
356 self->_piggy_version = kPiggyV1;
357 #endif
358
359 secnotice("joining", "joining: initWithSecretDelegate called, uuid=%@", self.sessionUUID);
360
361 NSString* name = [NSString stringWithFormat: @"%llu", dsid];
362
363 self->_context = [[KCSRPClientContext alloc] initWithUser: name
364 digestInfo: ccsha256_di()
365 group: ccsrp_gp_rfc5054_3072()
366 randomSource: rng];
367
368 return self;
369 }
370
371 - (NSString*) stateString {
372 switch (self.state) {
373 case kExpectingB: return @"→B";
374 case kExpectingHAMK: return @"→HAMK";
375 case kRequestSecretDone: return @"SecretDone";
376 default: return [NSString stringWithFormat:@"%d", self.state];
377 }
378 }
379
380 - (NSString *)description {
381 return [NSString stringWithFormat: @"<KCJoiningAcceptSession@%p %lld %@ %@>", self, self.dsid, [self stateString], self.context];
382 }
383 #if OCTAGON
384 /* for test */
385 -(void)setControlObject:(OTControl*)control
386 {
387 self.otControl = control;
388 }
389 #endif
390
391 @end