SecEscrowRequestTests.m [plain text]
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import <CoreCDP/CDPError.h>
#import <CoreCDP/CDPStateController.h>
#import <CloudServices/CloudServices.h>
#import "keychain/categories/NSError+UsefulConstructors.h"
#import "keychain/escrowrequest/Framework/SecEscrowRequest.h"
#import "keychain/escrowrequest/EscrowRequestServer.h"
#import "keychain/escrowrequest/EscrowRequestServerHelpers.h"
#import "keychain/escrowrequest/operations/EscrowRequestInformCloudServicesOperation.h"
#import "keychain/escrowrequest/operations/EscrowRequestPerformEscrowEnrollOperation.h"
#import "keychain/escrowrequest/EscrowRequestServerHelpers.h"
#import "keychain/escrowrequest/tests/MockSynchronousEscrowServer.h"
#include "keychain/ckks/CKKS.h"
#include "keychain/ckks/CKKSLockStateTracker.h"
#include "keychain/securityd/SecItemServer.h"
#include "keychain/securityd/spi.h"
#include "utilities/SecFileLocations.h"
#include "tests/secdmockaks/mockaks.h"
@interface SecEscrowRequestTests : XCTestCase
@property SecEscrowRequest* escrowRequest;
@property EscrowRequestServer* escrowServer;
@property CKKSLockStateTracker* lockStateTracker;
@property id escrowRequestServerClassMock;
@property id escrowRequestInformCloudServicesOperationMock;
@property id escrowRequestPerformEscrowEnrollOperationMock;
@end
@implementation SecEscrowRequestTests
+ (void)setUp {
securityd_init_local_spi();
EscrowRequestServerSetEnabled(true);
}
- (void)setUp {
NSString* testName = [self.name componentsSeparatedByString:@" "][1];
testName = [testName stringByReplacingOccurrencesOfString:@"]" withString:@""];
secnotice("secescrowtest", "Beginning test
[SecMockAKS unlockAllClasses];
// Make a new fake keychain
NSString* tmp_dir = [NSString stringWithFormat: @"/tmp/ [[NSFileManager defaultManager] createDirectoryAtPath:[NSString stringWithFormat: @"
SecCKKSDisable();
SecCKKSTestDisableSOS();
// Mock out the SBD layer
self.escrowRequestServerClassMock = OCMClassMock([EscrowRequestServer class]);
self.escrowRequestInformCloudServicesOperationMock = OCMClassMock([EscrowRequestInformCloudServicesOperation class]);
self.escrowRequestPerformEscrowEnrollOperationMock = OCMClassMock([EscrowRequestPerformEscrowEnrollOperation class]);
self.lockStateTracker = [[CKKSLockStateTracker alloc] init];
self.escrowServer = [[EscrowRequestServer alloc] initWithLockStateTracker:self.lockStateTracker];
id mockConnection = OCMPartialMock([[NSXPCConnection alloc] init]);
OCMStub([mockConnection remoteObjectProxyWithErrorHandler:[OCMArg any]]).andCall(self, @selector(escrowServer));
OCMStub([mockConnection synchronousRemoteObjectProxyWithErrorHandler:[OCMArg any]]).andCall(self, @selector(synchronousEscrowServer));
self.escrowRequest = [[SecEscrowRequest alloc] initWithConnection:mockConnection];
SecSetCustomHomeURLString((__bridge CFStringRef) tmp_dir);
SecKeychainDbReset(NULL);
// Actually load the database.
kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) { return false; });
}
- (NSData* _Nullable)mockTriggerCloudServicesPasscodeRequest:(NSString*)uuid error:(NSError**)error
{
return [@"certdata" dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSData* _Nullable)mockTriggerCloudServicesPasscodeRequestAndLockClassA:(NSString*)uuid error:(NSError**)error
{
[SecMockAKS lockClassA];
[self.lockStateTracker recheck];
return [@"certdata" dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSData* _Nullable)mockFailTriggerCloudServicesPasscodeRequest:(NSString*)uuid error:(NSError**)error
{
if(error) {
*error = [NSError errorWithDomain:@"NSURLErrorDomain"
code:-1009
description:@"The internet connection appears to be offline (mock)"];
}
return nil;
}
- (void)mockcdpUploadPrerecord:(SecEscrowPendingRecord*)recordToSend
secretType:(CDPDeviceSecretType)secretType
reply:(void (^)(bool didUpdate, NSError* _Nullable error))reply
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^ {
reply(YES, nil);
});
}
- (void)mockFailcdpUploadPrerecord:(SecEscrowPendingRecord*)recordToSend
secretType:(CDPDeviceSecretType)secretType
reply:(void (^)(bool didUpdate, NSError* _Nullable error))reply
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^ {
reply(NO, [NSError errorWithDomain:@"EscrowServiceErrorDomain" code:-6570 description:@"mock CLUBH error"]);
});
}
- (void)mockcdpFailBadPeerUploadPrerecord:(SecEscrowPendingRecord*)recordToSend
secretType:(CDPDeviceSecretType)secretType
reply:(void (^)(bool didUpdate, NSError* _Nullable error))reply
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^ {
reply(NO, [NSError errorWithDomain:kSecureBackupErrorDomain code:kSecureBackupInternalError description:@"SOS peer ID mismatch"]);
});
}
- (void)mockcdpFailNoSOSPeerUploadPrerecord:(SecEscrowPendingRecord*)recordToSend
secretType:(CDPDeviceSecretType)secretType
reply:(void (^)(bool didUpdate, NSError* _Nullable error))reply
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^ {
reply(NO, [NSError errorWithDomain:kSecureBackupErrorDomain code:kSecureBackupNotInSyncCircleError description:@"SOS peer not present"]);
});
}
- (void)mockcdpFailNoCDPPeerUploadPrerecord:(SecEscrowPendingRecord*)recordToSend
secretType:(CDPDeviceSecretType)secretType
reply:(void (^)(bool didUpdate, NSError* _Nullable error))reply
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^ {
reply(NO, [NSError errorWithDomain:CDPStateErrorDomain code:CDPStateErrorNoPeerIdFound description:@"SOS peer not present"]);
});
}
- (id<EscrowRequestXPCProtocol>)synchronousEscrowServer
{
return [[MockSynchronousEscrowServer alloc] initWithServer:self.escrowServer];
}
- (void)tearDown {
[self.escrowServer.controller.stateMachine haltOperation];
}
- (void)allCloudServicesCallsSucceed {
OCMStub([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
}
- (void)testStateMachineEntersNothingToDo
{
[self.escrowServer.controller.stateMachine startOperation];
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.stateConditions[EscrowRequestStateNothingToDo] wait:10*NSEC_PER_SEC], "State machine enters NothingToDo");
}
- (void)testTriggerUpdate {
[self allCloudServicesCallsSucceed];
NSError* error = nil;
XCTAssertNil([self.escrowRequest fetchRequestWaitingOnPasscode:&error], @"Should be no pending updates");
XCTAssertNil(error, @"Should be no error fetching pending updates");
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should churn through");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
}
- (void)testTriggerUpdateWhileLocked {
[self allCloudServicesCallsSucceed];
[SecMockAKS lockClassA];
NSError* error = nil;
XCTAssertNil([self.escrowRequest fetchRequestWaitingOnPasscode:&error], @"Should be no pending updates");
XCTAssertNil(error, @"Should be no error fetching pending updates");
XCTAssertFalse([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should not be able to trigger an update when locked");
XCTAssertNotNil(error, @"Should be an error triggering an escrow update (while locked)");
error = nil;
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should churn through");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNil(pendingUUID, "Should be no pending request UUID after a failed trigger");
}
- (void)testMultipleTriggers {
[self allCloudServicesCallsSucceed];
NSError* error = nil;
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
NSDictionary<NSString*, NSString*>* statuses = [self.escrowRequest fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 1, @"Should be one in-flight status");
}
- (void)testFetchUpdateWhileLocked {
[self allCloudServicesCallsSucceed];
NSError* error = nil;
XCTAssertNil([self.escrowRequest fetchRequestWaitingOnPasscode:&error], @"Should be no pending updates");
XCTAssertNil(error, @"Should be no error fetching pending updates");
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should churn through");
error = nil;
[SecMockAKS lockClassA];
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNotNil(error, @"Should be an error fetching a pending UUID while the device is locked");
XCTAssertNil(pendingUUID, "Should be no pending request UUID while device is locked");
}
- (void)testReaskCloudServicesWhenLocked {
// In this test, CloudServices is able to succeed (and cache a certificate), but we can't write down that the operation succeeded.
NSError* error = nil;
// No rate limiting for us!
self.escrowServer.controller.forceIgnoreCloudServicesRateLimiting = true;
// Pause the state machine for a bit...
[self.escrowServer.controller.stateMachine startOperation];
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause");
[self.escrowServer.controller.stateMachine haltOperation];
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
// First, let's ensure that we properly handle the case where the device is locked when we go to ask CloudServices about things
[SecMockAKS lockClassA];
[self.lockStateTracker recheck];
[self.escrowServer.controller.stateMachine startOperation];
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.stateConditions[EscrowRequestStateWaitForUnlock] wait:10*NSEC_PER_SEC], "State machine enters waitforunlock");
// Now, we should call CloudServices, but the device locks between CS success and us writing it down
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequestAndLockClassA:error:));
[SecMockAKS unlockAllClasses];
[self.lockStateTracker recheck];
OCMVerifyAllWithDelay(self.escrowRequestInformCloudServicesOperationMock, 10);
// and we should be back in wait for unlock:
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.stateConditions[EscrowRequestStateWaitForUnlock] wait:10*NSEC_PER_SEC], "State machine enters waitforunlock");
// Then, unlock one last time, and let everything succeed
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
[SecMockAKS unlockAllClasses];
[self.lockStateTracker recheck];
OCMVerifyAllWithDelay(self.escrowRequestInformCloudServicesOperationMock, 10);
}
- (void)testRateLimitSBDTriggerWhenFailing {
// Trigger an escrow update, which should call CloudServices and fail
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockFailTriggerCloudServicesPasscodeRequest:error:));
NSError* error = nil;
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
// And, the state machine should pause
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should rest");
// But, there should be no pending requests (as we need to retry CloudServices)
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNil(pendingUUID, "Should be no pending request UUID after a failed trigger");
// But, the controller will recover at some point (for now, disable rate limiting)
[self allCloudServicesCallsSucceed];
self.escrowServer.controller.forceIgnoreCloudServicesRateLimiting = true;
[self.escrowServer.controller.stateMachine pokeStateMachine];
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should rest");
pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after the state machine retries certificate caching");
}
- (void)testRateLimitSBDUploadWhenFailing {
// Trigger an escrow update, which should call CloudServices
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
NSError* error = nil;
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAllWithDelay(self.escrowRequestInformCloudServicesOperationMock, 5);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockFailcdpUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
// Wait for the upload to fail...
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should rest");
//////////////////////////
// Trigger another update, to check coalescing
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test2" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAllWithDelay(self.escrowRequestInformCloudServicesOperationMock, 5);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should rest");
NSString* pendingUUIDTheSecond = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNotNil(pendingUUIDTheSecond, @"Should have a request waiting on passcode");
XCTAssertEqualObjects(pendingUUID, pendingUUIDTheSecond, @"In-flight request should have been restarted");
//////////////
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockFailcdpUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUIDTheSecond
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should rest");
NSDictionary<NSString*, NSString*>* statuses = [self.escrowRequest fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 1, @"Should be one in-flight status");
// But if escrow plays nice again, kick state machine, and make sure we don't touch escrow proxy
[[[[self.escrowRequestPerformEscrowEnrollOperationMock stub] andCall:@selector(mockcdpUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
[self.escrowServer.controller.stateMachine startOperation];
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should rest");
/* item should still be in flight */
statuses = [self.escrowRequest fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 1, @"Should be one in-flight status");
}
- (void)testPrerecordCaching {
NSError* error = nil;
// Trigger an escrow update, which should call CloudServices
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should rest");
// Now, there should be a pending request
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
// Caching a prerecord will attempt an upload via the state machine. But, since we're testing prerecord fetching, we don't want that to happen.
// So, let the upload fail.
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockFailcdpUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
// Wait for the upload to fail...
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
NSString* nowPendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNil(nowPendingUUID, "Should be no pending request UUID after a prerecord cache");
NSData* d2 = [self.escrowRequest fetchPrerecord:pendingUUID
error:&error];
XCTAssertNotNil(d2, @"Should be able to retrieve a cached prerecord");
XCTAssertNil(error, @"Should be no error fetching a cached prerecord");
XCTAssertEqualObjects(d,d2, @"Cached prerecord should be equal to original");
}
- (void)testPrerecordCachingWhileLocked {
NSError* error = nil;
// Trigger an escrow update, which should call CloudServices
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should rest");
// Now, there should be a pending request
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
[SecMockAKS lockClassA];
// Caching a prerecord will fail, and should not invoke CDP.
// So, let the upload fail.
[[[self.escrowRequestPerformEscrowEnrollOperationMock reject] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertFalse([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be not able to cache a prerecord while the device is locked");
XCTAssertNotNil(error, @"Should be an error caching a prerecord while the device is locked");
// Ensure we don't invoke CDP
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should rest");
OCMVerifyAll(self.escrowRequestPerformEscrowEnrollOperationMock);
// And now, when we unlock, there's still a pending request
error = nil;
[SecMockAKS unlockAllClasses];
pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
}
- (void)testEscrowUploadViaStateMachine {
// This test should call CloudServices, which will succeed. It should then tell CoreCDP to upload the record.
[self allCloudServicesCallsSucceed];
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockcdpUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
[self.escrowServer.controller.stateMachine startOperation];
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.stateConditions[EscrowRequestStateNothingToDo] wait:10*NSEC_PER_SEC], "State machine enters NothingToDo when started");
NSError* error = nil;
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
// Don't call the "do it" RPC, but it should happen anyway
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
}
- (void)testEscrowUploadViaRPCAfterFailure {
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockFailcdpUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
NSError* error = nil;
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
// Now, the state machine should try and fail the upload
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
// And pause for a while
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
// But if escrow plays nice again, a 'try again' RPC should succeed
[[[[self.escrowRequestPerformEscrowEnrollOperationMock stub] andCall:@selector(mockcdpUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
uint64_t records = [self.escrowRequest storePrerecordsInEscrow:&error];
XCTAssertNil(error, @"Should be no error storing prerecords in escrow");
XCTAssertEqual(records, 1, @"Should have stored one record in escrow");
NSDictionary<NSString*, NSString*>* statuses = [self.escrowRequest fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 1, @"Should be one in-flight status");
for(id key in statuses.keyEnumerator) {
XCTAssertEqualObjects(statuses[key], @"complete", "Record should be in 'complete' state");
}
}
- (void)testEscrowUploadViaStateMachineAfterFailureDueToLockState {
// This test should call CloudServices, which will succeed. It should then tell CDP to upload the record, which will fail due to lock state.
[self allCloudServicesCallsSucceed];
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockcdpUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
[self.escrowServer.controller.stateMachine startOperation];
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.stateConditions[EscrowRequestStateNothingToDo] wait:10*NSEC_PER_SEC], "State machine enters NothingToDo when started");
NSError* error = nil;
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
// We need to lock directly after the prerecord is cached, but before the CDP attempt is attempted
[self.escrowServer.controller.stateMachine haltOperation];
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
[SecMockAKS lockClassA];
[self.escrowServer.controller.stateMachine startOperation];
// The state machine should notice the unlock, and try again
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.stateConditions[EscrowRequestStateWaitForUnlock] wait:10*NSEC_PER_SEC], "State machine enters waitforunlock");
// The upload should be tried after an unlock
[SecMockAKS unlockAllClasses];
[self.lockStateTracker recheck];
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
}
- (void)testEscrowUploadBadPeerFailure {
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockcdpFailBadPeerUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
NSError* error = nil;
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
// Now, the state machine should try and fail the upload
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
NSDictionary<NSString*, NSString*>* statuses = [self.escrowRequest fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 0, @"Should be zero in-flight status");
}
- (void)testEscrowUploadNoSOSPeerFailure {
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockcdpFailNoSOSPeerUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
NSError* error = nil;
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
// Now, the state machine should try and fail the upload
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
NSDictionary<NSString*, NSString*>* statuses = [self.escrowRequest fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 0, @"Should be zero in-flight status");
}
- (void)testEscrowUploadNoCDPSOSPeerFailure {
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockcdpFailNoCDPPeerUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
NSError* error = nil;
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
// Now, the state machine should try and fail the upload
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSDictionary<NSString*, NSString*>* statuses = [self.escrowRequest fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 0, @"Should be zero in-flight status");
}
- (void)testPendingPreRecordsCheck {
[self allCloudServicesCallsSucceed];
NSError* error = nil;
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
NSDictionary<NSString*, NSString*>* statuses = [self.escrowRequest fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 1, @"Should be one in-flight status");
BOOL result = [self.escrowRequest pendingEscrowUpload:&error];
XCTAssertNil(error, @"Should be no error checking for pending uploads");
XCTAssertEqual(result, true, @"pendingEscrowUpload should return true");
}
- (void)testClearedPreRecordsCheck {
[self allCloudServicesCallsSucceed];
NSError* error = nil;
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockcdpFailNoCDPPeerUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
// Now, the state machine should try and fail the upload
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
NSDictionary<NSString*, NSString*>* statuses = [self.escrowRequest fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 0, @"Should be zero in-flight status");
BOOL result = [self.escrowRequest pendingEscrowUpload:&error];
XCTAssertNil(error, @"Should be no error checking for pending uploads");
XCTAssertEqual(result, false, @"pendingEscrowUpload should return true");
}
- (void)testServerPendingPreRecordsCheck {
[self allCloudServicesCallsSucceed];
NSError* error = nil;
XCTAssertTrue([self.escrowServer triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
XCTAssertTrue([self.escrowServer triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
XCTAssertTrue([self.escrowServer triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
NSDictionary<NSString*, NSString*>* statuses = [self.escrowServer fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 1, @"Should be one in-flight status");
BOOL result = [self.escrowServer pendingEscrowUpload:&error];
XCTAssertNil(error, @"Should be no error checking for pending uploads");
XCTAssertEqual(result, true, @"pendingEscrowUpload should return true");
}
- (void)testServerClearedPreRecordsCheck {
[self allCloudServicesCallsSucceed];
NSError* error = nil;
[[[[self.escrowRequestPerformEscrowEnrollOperationMock expect] andCall:@selector(mockcdpFailNoCDPPeerUploadPrerecord:secretType:reply:)
onObject:self] ignoringNonObjectArgs]
cdpUploadPrerecord:[OCMArg any] secretType:0 reply:[OCMArg any]];
OCMExpect([self.escrowRequestInformCloudServicesOperationMock triggerCloudServicesPasscodeRequest:[OCMArg any] error:[OCMArg anyObjectRef]]).andCall(self, @selector(mockTriggerCloudServicesPasscodeRequest:error:));
XCTAssertTrue([self.escrowRequest triggerEscrowUpdate:@"test" error:&error], @"Should be able to trigger an update");
XCTAssertNil(error, @"Should be no error triggering an escrow update");
OCMVerifyAll(self.escrowRequestServerClassMock);
XCTAssertEqual(0, [self.escrowServer.controller.stateMachine.paused wait:10*NSEC_PER_SEC], @"State machine should pause within some time");
NSString* pendingUUID = [self.escrowRequest fetchRequestWaitingOnPasscode:&error];
XCTAssertNil(error, @"Should be no error fetching a pending UUID");
XCTAssertNotNil(pendingUUID, "Should be a pending request UUID after a trigger");
NSData* d = [[NSData alloc]initWithBase64EncodedString:@"YXNkZgo=" options:0];
XCTAssertTrue([self.escrowRequest cachePrerecord:pendingUUID
serializedPrerecord:d
error:&error], @"Should be able to cache a prerecord");
XCTAssertNil(error, @"Should be no error caching a prerecord");
// Now, the state machine should try and fail the upload
OCMVerifyAllWithDelay(self.escrowRequestPerformEscrowEnrollOperationMock, 10);
NSDictionary<NSString*, NSString*>* statuses = [self.escrowServer fetchStatuses:&error];
XCTAssertNotNil(statuses, @"Should be able to fetch statuses");
XCTAssertNil(error, @"Should be no error fetching statuses");
XCTAssertEqual(statuses.count, 0, @"Should be zero in-flight status");
BOOL result = [self.escrowServer pendingEscrowUpload:&error];
XCTAssertNil(error, @"Should be no error checking for pending uploads");
XCTAssertEqual(result, false, @"pendingEscrowUpload should return true");
}
@end