2 // KCJoiningSessionTest.m
7 #import <XCTest/XCTest.h>
9 #import <Foundation/Foundation.h>
11 #import <KeychainCircle/KCJoiningSession.h>
12 #import <KeychainCircle/KCError.h>
13 #import <KeychainCircle/NSError+KCCreationHelpers.h>
14 #import <KeychainCircle/KCAESGCMDuplexSession.h>
16 #include <Security/SecBase.h>
17 #include "keychain/SecureObjectSync/SOSFullPeerInfo.h"
18 #include "keychain/SecureObjectSync/SOSPeerInfoInternal.h"
21 static SecKeyRef GenerateFullECKey_internal(int keySize, NSError** error)
23 SecKeyRef full_key = NULL;
25 NSDictionary* keygen_parameters = @{ (__bridge NSString*)kSecAttrKeyType:(__bridge NSString*) kSecAttrKeyTypeEC,
26 (__bridge NSString*)kSecAttrKeySizeInBits: [NSNumber numberWithInt: keySize] };
29 (void) OSStatusError(SecKeyGeneratePair((__bridge CFDictionaryRef)keygen_parameters, NULL, &full_key), error, @"Generate Key failed");
34 static SecKeyRef GenerateFullECKey(int keySize, NSError** error) {
35 return GenerateFullECKey_internal(keySize, error);
39 @interface KCJoiningRequestTestDelegate : NSObject <KCJoiningRequestSecretDelegate, KCJoiningRequestCircleDelegate>
40 @property (readwrite) NSString* sharedSecret;
42 @property (readonly) NSString* accountCode;
43 @property (readonly) NSData* circleJoinData;
44 @property (readwrite) SOSPeerInfoRef peerInfo;
46 @property (readwrite) NSString* incorrectSecret;
47 @property (readwrite) int incorrectTries;
50 + (id) requestDelegateWithSecret:(NSString*) secret;
51 - (id) init NS_UNAVAILABLE;
52 - (id) initWithSecret: (NSString*) secret
53 incorrectSecret: (NSString*) wrongSecret
54 incorrectTries: (int) retries NS_DESIGNATED_INITIALIZER;
56 - (NSString*) verificationFailed: (bool) codeChanged;
57 - (SOSPeerInfoRef) copyPeerInfoError: (NSError**) error;
58 - (bool) processCircleJoinData: (NSData*) circleJoinData version:(PiggyBackProtocolVersion)version error: (NSError**)error ;
59 - (bool) processAccountCode: (NSString*) accountCode error: (NSError**)error;
63 @implementation KCJoiningRequestTestDelegate
71 + (id) requestDelegateWithSecret:(NSString*) secret {
72 return [[KCJoiningRequestTestDelegate alloc] initWithSecret:secret
77 + (id) requestDelegateWithSecret:(NSString*) secret
78 incorrectSecret:(NSString*) wrongSecret
79 incorrectTries:(int) retries {
80 return [[KCJoiningRequestTestDelegate alloc] initWithSecret:secret
81 incorrectSecret:wrongSecret
82 incorrectTries:retries];
86 - (id) initWithSecret: (NSString*) secret
87 incorrectSecret: (NSString*) incorrectSecret
88 incorrectTries: (int) retries {
89 if ((self = [super init])) {
91 SecKeyRef signingKey = GenerateFullECKey(256, NULL);
92 SecKeyRef octagonSigningKey = GenerateFullECKey(384, NULL);
93 SecKeyRef octagonEncryptionKey = GenerateFullECKey(384, NULL);
95 SOSPeerInfoRef newPeerInfo = SOSPeerInfoCreate(NULL, (__bridge CFDictionaryRef) @{(__bridge NSString*)kPIUserDefinedDeviceNameKey:@"Fakey"}, NULL, signingKey, octagonSigningKey, octagonEncryptionKey, NULL);
97 if (newPeerInfo == NULL) {
100 self.peerInfo = newPeerInfo;
101 CFRelease(newPeerInfo);
104 self.sharedSecret = secret;
105 self.incorrectSecret = incorrectSecret;
106 self.incorrectTries = retries;
112 - (NSString*) nextSecret {
113 if (self.incorrectTries > 0) {
114 self.incorrectTries -= 1;
115 return self.incorrectSecret;
117 return self.sharedSecret;
120 - (NSString*) secret {
121 return [self nextSecret];
124 - (NSString*) verificationFailed: (bool) codeChanged {
125 return [self nextSecret];
128 - (SOSPeerInfoRef) copyPeerInfoError: (NSError**) error {
133 return (SOSPeerInfoRef) CFRetain(self.peerInfo);
136 - (bool) processCircleJoinData: (NSData*) circleJoinData version:(PiggyBackProtocolVersion)version error: (NSError**)error {
137 self->_circleJoinData = circleJoinData;
141 - (bool) processAccountCode: (NSString*) accountCode error: (NSError**)error {
142 self->_accountCode = accountCode;
148 @interface KCJoiningAcceptTestDelegate : NSObject <KCJoiningAcceptSecretDelegate, KCJoiningAcceptCircleDelegate>
149 @property (readonly) NSArray<NSString*>* secrets;
150 @property (readwrite) NSUInteger currentSecret;
151 @property (readwrite) int retriesLeft;
152 @property (readwrite) int retriesPerSecret;
154 @property (readonly) NSString* codeToUse;
155 @property (readonly) NSData* circleJoinData;
156 @property (readonly) SOSPeerInfoRef peerInfo;
158 + (id) acceptDelegateWithSecret: (NSString*) secret code: (NSString*) code;
159 + (id) acceptDelegateWithSecrets: (NSArray<NSString*>*) secrets retries: (int) retries code: (NSString*) code;
160 - (id) initWithSecrets: (NSArray<NSString*>*) secrets retries: (int) retries code: (NSString*) code NS_DESIGNATED_INITIALIZER;
163 - (NSString*) secret;
164 - (NSString*) accountCode;
166 - (KCRetryOrNot) verificationFailed: (NSError**) error;
167 - (NSData*) circleJoinDataFor: (SOSPeerInfoRef) peer
168 error: (NSError**) error;
170 - (id) init NS_UNAVAILABLE;
174 @implementation KCJoiningAcceptTestDelegate
176 + (id) acceptDelegateWithSecrets: (NSArray<NSString*>*) secrets retries: (int) retries code: (NSString*) code {
177 return [[KCJoiningAcceptTestDelegate alloc] initWithSecrets:secrets retries:retries code:code];
181 + (id) acceptDelegateWithSecret: (NSString*) secret code: (NSString*) code {
182 return [[KCJoiningAcceptTestDelegate alloc] initWithSecret:secret code:code];
185 - (id) initWithSecret: (NSString*) secret code: (NSString*) code {
186 return [self initWithSecrets:@[secret] retries:3 code:code];
189 - (id) initWithSecrets: (NSArray<NSString*>*) secrets retries: (int) retries code: (NSString*) code {
190 if ((self = [super init])) {
191 self->_secrets = secrets;
192 self.currentSecret = 0;
193 self->_retriesPerSecret = retries;
194 self->_retriesLeft = self.retriesPerSecret;
196 self->_codeToUse = code;
198 uint8_t joinDataBuffer[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
199 self->_circleJoinData = [NSData dataWithBytes: joinDataBuffer length: sizeof(joinDataBuffer) ];
204 - (KCRetryOrNot) advanceSecret {
205 if (self.retriesLeft == 0) {
206 self.currentSecret += 1;
207 if (self.currentSecret >= [self.secrets count]) {
208 self.currentSecret = [self.secrets count] - 1;
210 self.retriesLeft = self.retriesPerSecret;
211 return kKCRetryWithNewChallenge;
213 self.retriesLeft -= 1;
214 return kKCRetryWithSameChallenge;
218 - (NSString*) secret {
219 return self.secrets[self.currentSecret];
221 - (NSString*) accountCode {
222 return self.codeToUse;
225 - (KCRetryOrNot) verificationFailed: (NSError**) error {
226 return [self advanceSecret];
229 - (NSData*) circleJoinDataFor: (SOSPeerInfoRef) peer
230 error: (NSError**) error {
231 uint8_t joinDataBuffer[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
233 self->_peerInfo = peer;
234 return [NSData dataWithBytes: joinDataBuffer length: sizeof(joinDataBuffer) ];
237 -(NSData*) circleGetInitialSyncViews:(SOSInitialSyncFlags)flags error:(NSError**) error{
238 return [NSData data];
244 @interface KCJoiningSessionTest : XCTestCase
248 @implementation KCJoiningSessionTest
252 // Put setup code here. This method is called before the invocation of each test method in the class.
256 // Put teardown code here. This method is called after the invocation of each test method in the class.
260 - (void)testJoiningSession {
261 NSError* error = nil;
263 NSString* secret = @"123456";
264 NSString* code = @"987654";
266 uint64_t dsid = 0x1234567887654321;
268 KCJoiningRequestTestDelegate* requestDelegate = [KCJoiningRequestTestDelegate requestDelegateWithSecret: secret];
269 KCJoiningRequestSecretSession *requestSession = [[KCJoiningRequestSecretSession alloc] initWithSecretDelegate:requestDelegate
274 NSData* initialMessage = [requestSession initialMessage: &error];
276 XCTAssertNotNil(initialMessage, @"No initial message");
277 XCTAssertNil(error, @"Got error %@", error);
279 KCJoiningAcceptTestDelegate* acceptDelegate = [KCJoiningAcceptTestDelegate acceptDelegateWithSecret:secret code:code];
280 KCJoiningAcceptSession* acceptSession = [[KCJoiningAcceptSession alloc] initWithSecretDelegate:acceptDelegate
281 circleDelegate:acceptDelegate
287 NSData* challenge = [acceptSession processMessage: initialMessage error: &error];
289 XCTAssertNotNil(challenge, @"No initial message");
290 XCTAssertNil(error, @"Got error %@", error);
293 NSData* response = [requestSession processMessage: challenge error: &error];
295 XCTAssertNotNil(response, @"No response message");
296 XCTAssertNil(error, @"Got error %@", error);
299 NSData* verification = [acceptSession processMessage: response error: &error];
301 XCTAssertNotNil(verification, @"No verification message");
302 XCTAssertNil(error, @"Got error %@", error);
305 NSData* doneMessage = [requestSession processMessage: verification error: &error];
307 XCTAssertNotNil(doneMessage, @"No response message");
308 XCTAssertNil(error, @"Got error %@", error);
310 XCTAssertTrue([requestSession isDone], @"SecretSession done");
311 XCTAssertFalse([acceptSession isDone], @"Unexpected accept session done");
313 KCAESGCMDuplexSession* aesSession = [requestSession session];
314 requestSession = nil;
316 KCJoiningRequestCircleSession* requestSecretSession = [KCJoiningRequestCircleSession sessionWithCircleDelegate:requestDelegate session:aesSession error:&error];
318 XCTAssertNotNil(requestSecretSession, @"No request secret session");
319 XCTAssertNil(error, @"Got error %@", error);
322 NSData* peerInfoMessage = [requestSecretSession initialMessage: &error];
324 XCTAssertNotNil(peerInfoMessage, @"No peerInfo message");
325 XCTAssertNil(error, @"Got error %@", error);
327 XCTAssertEqualObjects(requestDelegate.accountCode, acceptDelegate.codeToUse, @"Code made it");
330 NSData* blobMessage = [acceptSession processMessage:peerInfoMessage error: &error];
332 XCTAssertNotNil(blobMessage, @"No blob message");
333 XCTAssertNil(error, @"Got error %@", error);
335 // We have different peer_info types due to wierd linking of our tests.
336 // Compare the der representations:
337 NSData* rp_der = requestDelegate.peerInfo != nil ? (__bridge_transfer NSData*) SOSPeerInfoCopyEncodedData(requestDelegate.peerInfo, NULL, NULL) : nil;
338 NSData* ap_der = acceptDelegate.peerInfo != nil ? (__bridge_transfer NSData*) SOSPeerInfoCopyEncodedData(acceptDelegate.peerInfo, NULL, NULL) : nil;
340 XCTAssertEqualObjects(rp_der, ap_der, @"Peer infos match");
343 NSData* nothing = [requestSecretSession processMessage:blobMessage error: &error];
345 XCTAssertEqualObjects(requestDelegate.circleJoinData, acceptDelegate.circleJoinData);
347 XCTAssertNotNil(nothing, @"No initial message");
348 XCTAssertNil(error, @"Got error %@", error);
350 XCTAssertTrue([requestSecretSession isDone], @"requesor done");
351 XCTAssertTrue([acceptSession isDone], @"acceptor done");
355 - (void)testJoiningSessionRetry {
356 NSError* error = nil;
358 NSString* secret = @"123456";
359 NSString* code = @"987654";
361 uint64_t dsid = 0x1234567887654321;
363 KCJoiningRequestTestDelegate* requestDelegate = [KCJoiningRequestTestDelegate requestDelegateWithSecret: secret incorrectSecret:@"777888" incorrectTries:3];
364 KCJoiningRequestSecretSession *requestSession = [[KCJoiningRequestSecretSession alloc] initWithSecretDelegate:requestDelegate
369 NSData* initialMessage = [requestSession initialMessage: &error];
371 XCTAssertNotNil(initialMessage, @"No initial message");
372 XCTAssertNil(error, @"Got error %@", error);
374 KCJoiningAcceptTestDelegate* acceptDelegate = [KCJoiningAcceptTestDelegate acceptDelegateWithSecret:secret code:code];
375 KCJoiningAcceptSession* acceptSession = [[KCJoiningAcceptSession alloc] initWithSecretDelegate:acceptDelegate
376 circleDelegate:acceptDelegate
382 NSData* challenge = [acceptSession processMessage: initialMessage error: &error];
384 XCTAssertNotNil(challenge, @"No initial message");
385 XCTAssertNil(error, @"Got error %@", error);
387 NSData* response = nil;
388 NSData* verification = nil;
390 NSData* nextChallenge = challenge;
391 for (int tries = 0; tries < 4; ++tries) {
393 response = [requestSession processMessage: nextChallenge error: &error];
395 XCTAssertNotNil(response, @"No response message");
396 XCTAssertNil(error, @"Got error %@", error);
398 XCTAssertNotEqualObjects(requestDelegate.accountCode, acceptDelegate.codeToUse, @"Code should not make it");
401 verification = [acceptSession processMessage: response error: &error];
403 XCTAssertNotNil(verification, @"No verification message");
404 XCTAssertNil(error, @"Got error %@", error);
406 nextChallenge = verification;
410 NSData* doneMessage = [requestSession processMessage: verification error: &error];
412 XCTAssertNotNil(doneMessage, @"No response message");
413 XCTAssertNil(error, @"Got error %@", error);
415 XCTAssertTrue([requestSession isDone], @"SecretSession done");
416 XCTAssertFalse([acceptSession isDone], @"Unexpected accept session done");
418 KCAESGCMDuplexSession* aesSession = [requestSession session];
419 requestSession = nil;
422 KCJoiningRequestCircleSession* requestSecretSession = [KCJoiningRequestCircleSession sessionWithCircleDelegate:requestDelegate session:aesSession error:&error];
424 XCTAssertNotNil(requestSecretSession, @"No request secret session");
425 XCTAssertNil(error, @"Got error %@", error);
428 NSData* peerInfoMessage = [requestSecretSession initialMessage: &error];
430 XCTAssertNotNil(peerInfoMessage, @"No peerInfo message");
431 XCTAssertNil(error, @"Got error %@", error);
433 XCTAssertEqualObjects(requestDelegate.accountCode, acceptDelegate.codeToUse, @"Code made it");
436 NSData* blobMessage = [acceptSession processMessage:peerInfoMessage error: &error];
438 XCTAssertNotNil(blobMessage, @"No blob message");
439 XCTAssertNil(error, @"Got error %@", error);
441 // We have different peer_info types due to wierd linking of our tests.
442 // Compare the der representations:
443 NSData* rp_der = requestDelegate.peerInfo != nil ? (__bridge_transfer NSData*) SOSPeerInfoCopyEncodedData(requestDelegate.peerInfo, NULL, NULL) : nil;
444 NSData* ap_der = acceptDelegate.peerInfo != nil ? (__bridge_transfer NSData*) SOSPeerInfoCopyEncodedData(acceptDelegate.peerInfo, NULL, NULL) : nil;
446 XCTAssertEqualObjects(rp_der, ap_der, @"Peer infos match");
449 NSData* nothing = [requestSecretSession processMessage:blobMessage error: &error];
451 XCTAssertEqualObjects(requestDelegate.circleJoinData, acceptDelegate.circleJoinData);
453 XCTAssertNotNil(nothing, @"No initial message");
454 XCTAssertNil(error, @"Got error %@", error);
456 XCTAssertTrue([requestSecretSession isDone], @"requesor done");
457 XCTAssertTrue([acceptSession isDone], @"acceptor done");
461 - (void)testJoiningSessionCodeChange {
462 NSError* error = nil;
464 NSString* secret = @"123456";
465 NSString* code = @"987654";
467 uint64_t dsid = 0x1234567887654321;
469 KCJoiningRequestTestDelegate* requestDelegate = [KCJoiningRequestTestDelegate requestDelegateWithSecret: secret];
470 KCJoiningRequestSecretSession *requestSession = [[KCJoiningRequestSecretSession alloc] initWithSecretDelegate:requestDelegate
475 NSData* initialMessage = [requestSession initialMessage: &error];
477 XCTAssertNotNil(initialMessage, @"No initial message");
478 XCTAssertNil(error, @"Got error %@", error);
480 KCJoiningAcceptTestDelegate* acceptDelegate = [KCJoiningAcceptTestDelegate acceptDelegateWithSecrets:@[@"222222", @"3333333", secret] retries:1 code:code];
481 KCJoiningAcceptSession* acceptSession = [[KCJoiningAcceptSession alloc] initWithSecretDelegate:acceptDelegate
482 circleDelegate:acceptDelegate
488 NSData* challenge = [acceptSession processMessage: initialMessage error: &error];
490 XCTAssertNotNil(challenge, @"No initial message");
491 XCTAssertNil(error, @"Got error %@", error);
493 NSData* response = nil;
494 NSData* verification = nil;
496 NSData* nextChallenge = challenge;
497 for (int tries = 0; tries < 5; ++tries) {
499 response = [requestSession processMessage: nextChallenge error: &error];
501 XCTAssertNotNil(response, @"No response message");
502 XCTAssertNil(error, @"Got error %@", error);
504 XCTAssertNotEqualObjects(requestDelegate.accountCode, acceptDelegate.codeToUse, @"Code should not make it");
507 verification = [acceptSession processMessage: response error: &error];
509 XCTAssertNotNil(verification, @"No verification message");
510 XCTAssertNil(error, @"Got error %@", error);
512 nextChallenge = verification;
516 NSData* doneMessage = [requestSession processMessage: verification error: &error];
518 XCTAssertNotNil(doneMessage, @"No response message");
519 XCTAssertNil(error, @"Got error %@", error);
521 XCTAssertTrue([requestSession isDone], @"SecretSession done");
522 XCTAssertFalse([acceptSession isDone], @"Unexpected accept session done");
524 KCAESGCMDuplexSession* aesSession = [requestSession session];
525 requestSession = nil;
528 KCJoiningRequestCircleSession* requestSecretSession = [KCJoiningRequestCircleSession sessionWithCircleDelegate:requestDelegate session:aesSession error:&error];
530 XCTAssertNotNil(requestSecretSession, @"No request secret session");
531 XCTAssertNil(error, @"Got error %@", error);
534 NSData* peerInfoMessage = [requestSecretSession initialMessage: &error];
536 XCTAssertNotNil(peerInfoMessage, @"No peerInfo message");
537 XCTAssertNil(error, @"Got error %@", error);
539 XCTAssertEqualObjects(requestDelegate.accountCode, acceptDelegate.codeToUse, @"Code made it");
542 NSData* blobMessage = [acceptSession processMessage:peerInfoMessage error: &error];
544 XCTAssertNotNil(blobMessage, @"No blob message");
545 XCTAssertNil(error, @"Got error %@", error);
547 // We have different peer_info types due to wierd linking of our tests.
548 // Compare the der representations:
549 NSData* rp_der = requestDelegate.peerInfo != nil ? (__bridge_transfer NSData*) SOSPeerInfoCopyEncodedData(requestDelegate.peerInfo, NULL, NULL) : nil;
550 NSData* ap_der = acceptDelegate.peerInfo != nil ? (__bridge_transfer NSData*) SOSPeerInfoCopyEncodedData(acceptDelegate.peerInfo, NULL, NULL) : nil;
552 XCTAssertEqualObjects(rp_der, ap_der, @"Peer infos match");
555 NSData* nothing = [requestSecretSession processMessage:blobMessage error: &error];
557 XCTAssertEqualObjects(requestDelegate.circleJoinData, acceptDelegate.circleJoinData);
559 XCTAssertNotNil(nothing, @"No initial message");
560 XCTAssertNil(error, @"Got error %@", error);
562 XCTAssertTrue([requestSecretSession isDone], @"requesor done");
563 XCTAssertTrue([acceptSession isDone], @"acceptor done");