From b3971512e61ecab68b17ca7ffe6c8c600310a026 Mon Sep 17 00:00:00 2001 From: Apple Date: Fri, 1 May 2020 18:19:48 +0000 Subject: [PATCH] Security-59306.101.1.tar.gz --- Analytics/SFAnalytics+Internal.h | 1 + Analytics/SFAnalytics.m | 39 + Analytics/SFAnalytics.plist | 8 + Analytics/SFAnalyticsDefines.h | 1 + CircleJoinRequested/CircleJoinRequested.m | 8 + .../KCJoiningAcceptSession+Internal.h | 3 + KeychainCircle/KCJoiningAcceptSession.m | 37 + KeychainCircle/KCJoiningMessages.h | 2 + .../KCJoiningRequestCircleSession.m | 13 +- .../KCJoiningRequestSecretSession.m | 26 +- .../KCJoiningRequestSession+Internal.h | 5 +- KeychainCircle/PairingChannel.h | 4 + KeychainCircle/PairingChannel.m | 34 +- KeychainCircle/Tests/FakeSOSControl.m | 29 +- KeychainCircle/Tests/KCJoiningSessionTest.m | 18 - KeychainCircle/Tests/KCTLKRequestTest.m | 323 +++ .../KNAppDelegate.m | 2 + OSX/authd/authorization.plist | 13 +- .../en.lproj/authorization.prompts.strings | 2 + OSX/libsecurity_cdsa_utilities/lib/cssmdb.h | 36 +- .../lib/diskimagerep.cpp | 10 +- .../lib/diskimagerep.h | 3 +- .../lib/notarization.cpp | 16 +- OSX/libsecurity_codesigning/lib/signer.cpp | 7 +- OSX/libsecurity_keychain/lib/SecItem.cpp | 3 +- OSX/libsecurity_smime/lib/cmsdigest.c | 308 +-- OSX/libsecurity_smime/lib/cmspriv.h | 2 +- OSX/libsecurity_smime/lib/cmsutil.c | 42 +- OSX/libsecurityd/lib/sstransit.cpp | 5 +- OSX/sec/Security/SecAccessControl.m | 16 +- OSX/sec/Security/SecCTKKey.m | 3 +- OSX/sec/Security/SecCertificate.c | 17 +- OSX/sec/Security/SecExports.exp-in | 1 + OSX/sec/Security/SecItem.c | 42 +- OSX/sec/Security/SecItemInternal.h | 2 - OSX/sec/Security/SecOTRPacketData.h | 2 +- OSX/sec/Security/SecPolicy.c | 73 +- OSX/sec/Security/SecPolicy.list | 2 + OSX/sec/Security/SecuritydXPC.c | 30 - OSX/sec/ipc/securityd_client.h | 38 - OSX/sec/ipc/server.c | 221 +- OSX/shared_regressions/shared_regressions.h | 1 - OSX/shared_regressions/si-44-seckey-aks.m | 86 +- OSX/utilities/SecCFWrappers.h | 9 + OSX/utilities/SecDb.c | 20 + OSX/utilities/SecDb.h | 2 + OSX/utilities/SecXPCHelper.h | 2 +- OSX/utilities/SecXPCHelper.m | 2 +- Security.exp-in | 2 + Security.xcodeproj/project.pbxproj | 415 ++-- .../xcshareddata/xcschemes/CKKSTests.xcscheme | 9 +- .../xcschemes/OctagonTests.xcscheme | 6 +- .../xcschemes/TrustTests_ios.xcscheme | 28 +- .../xcschemes/TrustTests_macos.xcscheme | 28 +- .../xcschemes/TrustedPeers.xcscheme | 28 +- .../xcschemes/ios - Debug.xcscheme | 6 +- .../xcschemes/ios - Release.xcscheme | 6 +- .../xcschemes/ios - secdtests.xcscheme | 24 +- .../xcschemes/osx - World.xcscheme | 6 +- .../xcschemes/osx - secdtests.xcscheme | 2 +- .../xcschemes/osx - sectests.xcscheme | 10 +- .../xcschemes/secdmockaks.xcscheme | 6 +- SecurityTool/sharedTool/sos.m | 23 +- SecurityTool/sharedTool/sub_commands.h | 1 - experiment/SecExperimentInternal.h | 2 +- .../reset_ick_account | 58 + .../Regressions/SOSCircle_regressions.h | 1 - .../Regressions/sc-150-ring.m | 181 -- .../SecureObjectSync/Regressions/sc-kvstool.m | 356 ---- keychain/SecureObjectSync/SOSAccount.h | 8 - keychain/SecureObjectSync/SOSAccount.m | 360 +--- keychain/SecureObjectSync/SOSAccountBackup.m | 30 - .../SOSAccountConfiguration.proto | 1 - .../SecureObjectSync/SOSAccountCredentials.m | 15 - keychain/SecureObjectSync/SOSAccountPeers.m | 71 - .../SecureObjectSync/SOSAccountPersistence.m | 3 +- keychain/SecureObjectSync/SOSAccountPriv.h | 17 +- .../SecureObjectSync/SOSAccountRecovery.m | 2 - keychain/SecureObjectSync/SOSAccountRings.m | 68 - .../SecureObjectSync/SOSAccountTransaction.m | 17 +- keychain/SecureObjectSync/SOSAccountUpdate.m | 2 - .../SecureObjectSync/SOSBackupInformation.m | 75 - keychain/SecureObjectSync/SOSCircle.c | 10 +- keychain/SecureObjectSync/SOSCloudCircle.h | 64 - keychain/SecureObjectSync/SOSCloudCircle.m | 351 +--- .../SecureObjectSync/SOSCloudCircleInternal.h | 26 - keychain/SecureObjectSync/SOSControlHelper.m | 5 +- keychain/SecureObjectSync/SOSControlServer.m | 16 +- keychain/SecureObjectSync/SOSExports.exp-in | 24 - keychain/SecureObjectSync/SOSFullPeerInfo.h | 2 - keychain/SecureObjectSync/SOSFullPeerInfo.m | 7 - keychain/SecureObjectSync/SOSPeerInfo.h | 2 - keychain/SecureObjectSync/SOSPeerInfo.m | 34 - keychain/SecureObjectSync/SOSPeerOTRTimer.m | 1 - keychain/SecureObjectSync/SOSRing.h | 4 - keychain/SecureObjectSync/SOSRingTypes.m | 57 - keychain/SecureObjectSync/SOSRingUtils.c | 6 - keychain/SecureObjectSync/SOSRingV0.m | 17 - keychain/SecureObjectSync/SOSTypes.h | 6 +- .../SecureObjectSync/Tool/keychain_sync.h | 22 +- .../SecureObjectSync/Tool/keychain_sync.m | 515 ++--- keychain/SecureObjectSync/Tool/recovery_key.m | 1 + keychain/SecureObjectSync/Tool/syncbackup.m | 146 -- .../SOSAccountConfiguration.h | 8 +- .../SOSAccountConfiguration.m | 50 - keychain/Trieste/.swiftlint.yml | 3 + .../OctagonTriesteTests/OctagonTests.swift | 22 +- .../BottledPeer/BottledPeer.swift | 2 +- .../BottledPeer/EscrowKeys.swift | 35 +- keychain/TrustedPeersHelper/Client.swift | 247 ++- keychain/TrustedPeersHelper/Container.swift | 1833 +++++++++-------- .../TrustedPeersHelper/ContainerMap.swift | 9 +- .../Container_BottledPeers.swift | 81 + .../Container_MachineIDs.swift | 117 +- .../Container_RecoveryKey.swift | 91 + .../TrustedPeersHelper/CuttlefishErrors.swift | 2 +- keychain/TrustedPeersHelper/Policy.swift | 162 +- .../RecoveryKey/RecoverKeySet.swift | 28 +- .../SetValueTransformer.swift | 12 +- .../TrustedPeersHelper_2.xcdatamodel/contents | 9 +- .../TrustedPeersHelperProtocol.h | 53 +- .../TrustedPeersHelperProtocol.m | 4 +- keychain/TrustedPeersHelper/main.swift | 12 +- .../.swiftlint.yml | 3 + .../ContainerSync.swift | 71 +- .../FakeCuttlefish.swift | 53 +- .../MockCuttlefish.swift | 6 +- .../TrustedPeersHelperUnitTests.swift | 831 ++++++-- keychain/ckks/CKKS.m | 21 +- keychain/ckks/CKKSAnalytics.h | 8 + keychain/ckks/CKKSAnalytics.m | 9 + keychain/ckks/CKKSIncomingQueueEntry.m | 2 +- keychain/ckks/CKKSIncomingQueueOperation.m | 2 - keychain/ckks/CKKSKey.m | 6 +- keychain/ckks/CKKSKeychainView.h | 8 +- keychain/ckks/CKKSKeychainView.m | 121 +- keychain/ckks/CKKSNewTLKOperation.m | 14 +- keychain/ckks/CKKSOutgoingQueueOperation.m | 3 +- keychain/ckks/CKKSSQLDatabaseObject.h | 42 +- keychain/ckks/CKKSSQLDatabaseObject.m | 115 +- keychain/ckks/CKKSScanLocalItemsOperation.h | 2 + keychain/ckks/CKKSScanLocalItemsOperation.m | 72 +- keychain/ckks/CKKSViewManager.h | 52 +- keychain/ckks/CKKSViewManager.m | 338 ++- keychain/ckks/CKKSZone.m | 7 + keychain/ckks/tests/CKKSCloudKitTests.m | 20 +- keychain/ckks/tests/CKKSDispatchTests.m | 249 --- .../ckks/tests/CKKSMockSOSPresentAdapter.h | 2 + .../ckks/tests/CKKSMockSOSPresentAdapter.m | 3 +- keychain/ckks/tests/CKKSSQLTests.m | 39 +- keychain/ckks/tests/CKKSTests+API.m | 61 + .../ckks/tests/CKKSTests+CurrentPointerAPI.m | 29 +- keychain/ckks/tests/CKKSTests+MultiZone.h | 5 + keychain/ckks/tests/CKKSTests+MultiZone.m | 244 ++- keychain/ckks/tests/CKKSTests.m | 110 +- .../tests/CloudKitKeychainSyncingFixupTests.m | 6 +- .../tests/CloudKitKeychainSyncingMockXCTest.h | 9 + .../tests/CloudKitKeychainSyncingMockXCTest.m | 32 +- .../tests/CloudKitKeychainSyncingTestsBase.h | 3 - .../tests/CloudKitKeychainSyncingTestsBase.m | 6 +- keychain/ckks/tests/CloudKitMockXCTest.h | 24 +- keychain/ckks/tests/CloudKitMockXCTest.m | 95 +- keychain/ckks/tests/gen_test_plist.py | 75 + .../ckks/tests/testrunner/KeychainCKKS.plist | 37 - keychain/ckksctl/ckksctl.m | 42 +- .../generated_source/SecEscrowPendingRecord.m | 12 +- keychain/headers/SecItem.h | 3 +- keychain/headers/SecKey.h | 2 +- keychain/ot/CuttlefishXPCWrapper.m | 124 +- keychain/ot/OT.m | 7 +- keychain/ot/OTAuthKitAdapter.h | 1 + keychain/ot/OTAuthKitAdapter.m | 29 +- keychain/ot/OTCheckHealthOperation.h | 1 + keychain/ot/OTCheckHealthOperation.m | 20 +- keychain/ot/OTClientStateMachine.m | 1 - keychain/ot/OTClique.h | 61 +- keychain/ot/OTClique.m | 279 ++- keychain/ot/OTControl.h | 25 +- keychain/ot/OTControl.m | 30 +- keychain/ot/OTControlProtocol.h | 13 +- keychain/ot/OTControlProtocol.m | 2 +- keychain/ot/OTCuttlefishAccountStateHolder.h | 5 +- keychain/ot/OTCuttlefishAccountStateHolder.m | 6 +- keychain/ot/OTCuttlefishContext.h | 21 +- keychain/ot/OTCuttlefishContext.m | 757 ++++--- keychain/ot/OTDefines.h | 2 + .../OTDetermineCDPBitStatusOperation.h} | 32 +- .../ot/OTDetermineCDPBitStatusOperation.m | 149 ++ keychain/ot/OTEstablishOperation.m | 16 +- keychain/ot/OTFetchCKKSKeysOperation.m | 2 +- keychain/ot/OTFetchViewsOperation.h | 9 +- keychain/ot/OTFetchViewsOperation.m | 96 +- keychain/ot/OTFollowup.h | 5 + keychain/ot/OTFollowup.m | 34 +- keychain/ot/OTJoinWithVoucherOperation.h | 4 +- keychain/ot/OTJoinWithVoucherOperation.m | 37 +- keychain/ot/OTJoiningConfiguration.h | 8 - keychain/ot/OTJoiningConfiguration.m | 18 - keychain/ot/OTManager.h | 35 +- keychain/ot/OTManager.m | 193 +- keychain/ot/OTOperationDependencies.h | 2 +- keychain/ot/OTPrepareOperation.h | 4 + keychain/ot/OTPrepareOperation.m | 51 +- .../ot/OTResetCKKSZonesLackingTLKsOperation.m | 3 +- keychain/ot/OTSOSAdapter.h | 10 +- keychain/ot/OTSOSAdapter.m | 64 +- .../ot/OTSOSUpdatePreapprovalsOperation.m | 19 +- keychain/ot/OTSOSUpgradeOperation.h | 6 +- keychain/ot/OTSOSUpgradeOperation.m | 155 +- .../OTSetCDPBitOperation.h} | 36 +- keychain/ot/OTSetCDPBitOperation.m | 71 + keychain/ot/OTStates.h | 20 +- keychain/ot/OTStates.m | 29 +- keychain/ot/OTUpdateTPHOperation.h | 1 + keychain/ot/OTUpdateTPHOperation.m | 8 +- .../ot/OTUpdateTrustedDeviceListOperation.m | 13 +- keychain/ot/OTUploadNewCKKSTLKsOperation.m | 3 +- keychain/ot/OTVouchWithBottleOperation.m | 89 +- keychain/ot/OTVouchWithRecoveryKeyOperation.h | 2 - keychain/ot/OTVouchWithRecoveryKeyOperation.m | 83 +- keychain/ot/OctagonStateMachine.m | 2 - .../OTAccountMetadataClassC+KeychainSupport.h | 6 + .../OTAccountMetadataClassC+KeychainSupport.m | 23 + .../ot/categories/OctagonEscrowRecoverer.h | 1 + .../ot/proto/OTAccountMetadataClassC.proto | 15 + keychain/ot/proto/OTPairingMessage.proto | 16 +- .../OTAccountMetadataClassC.h | 48 + .../OTAccountMetadataClassC.m | 174 +- .../proto/generated_source/OTPairingMessage.h | 9 +- .../proto/generated_source/OTPairingMessage.m | 52 - .../ot/proto/generated_source/OTSOSMessage.h | 48 - .../ot/proto/generated_source/OTSOSMessage.m | 229 -- .../OTSponsorToApplicantRound2M2.h | 8 - .../OTSponsorToApplicantRound2M2.m | 67 - keychain/ot/proto/source/OTSOSMessage.m | 229 -- keychain/ot/tests/octagon/.swiftlint.yml | 3 + .../octagon/OctagonDataPersistenceTests.swift | 2 + .../ot/tests/octagon/OctagonTestMocks.swift | 4 + .../tests/octagon/OctagonTests+Account.swift | 568 +++++ .../ot/tests/octagon/OctagonTests+CKKS.swift | 18 +- .../OctagonTests+CKKSConfiguration.swift | 149 ++ .../OctagonTests+CloudKitAccount.swift | 162 +- .../octagon/OctagonTests+CoreFollowUp.swift | 69 +- .../octagon/OctagonTests+DeviceList.swift | 103 +- .../octagon/OctagonTests+ErrorHandling.swift | 82 +- .../octagon/OctagonTests+EscrowRecovery.swift | 198 +- .../OctagonTests+ForwardCompatibility.swift | 555 +++++ .../octagon/OctagonTests+HealthCheck.swift | 176 +- .../tests/octagon/OctagonTests+Helpers.swift | 33 + .../octagon/OctagonTests+RecoveryKey.swift | 339 ++- .../ot/tests/octagon/OctagonTests+Reset.swift | 180 +- .../ot/tests/octagon/OctagonTests+SOS.swift | 233 ++- .../octagon/OctagonTests+SOSUpgrade.swift | 171 +- .../octagon/OctagonTests-BridgingHeader.h | 8 + keychain/ot/tests/octagon/OctagonTests.swift | 1170 +++++------ .../OctagonPairingTests+Piggybacking.swift | 87 +- ...OctagonPairingTests+ProxMultiClients.swift | 144 +- .../OctagonPairingTests+ProximitySetup.swift | 649 ++---- .../octagon/Pairing/OctagonPairingTests.swift | 326 ++- keychain/otctl/OTControlCLI.h | 3 + keychain/otctl/OTControlCLI.m | 85 + keychain/otctl/otctl-Entitlements.plist | 8 + keychain/otctl/otctl.m | 24 + .../securityd/Regressions/SOSAccountTesting.h | 60 +- .../Regressions/secd-62-account-backup.m | 14 - .../secd-65-account-retirement-reset.m | 10 +- .../Regressions/secd-66-account-recovery.m | 60 +- .../Regressions/secd-76-idstransport.m | 315 --- .../Regressions/secd-95-escrow-persistence.m | 178 -- .../Regressions/secd_77_ids_messaging.m | 296 --- .../securityd/Regressions/secd_regressions.h | 1 - keychain/securityd/SOSCloudCircleServer.h | 29 - keychain/securityd/SOSCloudCircleServer.m | 408 +--- .../SecDbBackupKeyClassSigningKey.m | 2 +- .../SecDbBackupMetadataClassKey.m | 2 +- .../generated_source/SecDbBackupRecoverySet.m | 2 +- keychain/securityd/SecDbKeychainItem.m | 11 +- keychain/securityd/SecItemDataSource.c | 19 +- keychain/securityd/SecItemDataSource.h | 2 + keychain/securityd/SecItemServer.c | 31 +- keychain/securityd/SecItemServer.h | 2 + keychain/securityd/spi.c | 17 - keychain/tpctl/main.swift | 35 +- libsecurity_smime/lib/cmsdigest.c | 247 +-- libsecurity_smime/lib/cmspriv.h | 5 +- libsecurity_smime/lib/cmsutil.c | 68 +- protocol/SecProtocolPriv.h | 2 +- rio.yml | 0 securityd/etc/com.apple.securityd.sb | 3 +- .../project.pbxproj | 5 +- securityd/src/kckey.cpp | 4 +- securityd/src/securityd.entitlements | 2 + supd/Tests/SFAnalyticsTests.m | 5 + supd/Tests/SupdTests.m | 105 +- supd/supd.h | 4 + supd/supd.m | 393 +++- supd/supdProtocol.h | 3 +- supdctl/main.m | 35 +- .../EvaluationTests/ExceptionTests.m | 331 +++ .../EvaluationTests/ExceptionTests_data.h | 278 +-- .../EvaluationTests/RevocationTests.m | 148 +- .../EvaluationTests/RevocationTests_data.h | 294 +++ .../EvaluationTests/TrustEvaluationTestCase.h | 1 + .../EvaluationTests/TrustEvaluationTestCase.m | 19 +- tests/TrustTests/TrustEvaluationTestHelpers.h | 1 + tests/TrustTests/TrustEvaluationTestHelpers.m | 4 + tests/secdmockaks/mockaksKeychain.m | 12 +- trust/headers/SecPolicyPriv.h | 41 +- trust/trustd/SecCertificateServer.c | 19 + trust/trustd/SecCertificateServer.h | 1 + trust/trustd/SecOCSPCache.c | 4 +- trust/trustd/SecPolicyServer.c | 49 +- trust/trustd/SecPolicyServer.h | 9 +- trust/trustd/SecRevocationServer.c | 19 +- trust/trustd/SecRevocationServer.h | 3 + trust/trustd/SecTrustServer.c | 5 + 316 files changed, 12878 insertions(+), 10328 deletions(-) create mode 100644 KeychainCircle/Tests/KCTLKRequestTest.m create mode 100644 keychain/ResetCloudKeychainAccount/reset_ick_account delete mode 100644 keychain/SecureObjectSync/Regressions/sc-150-ring.m delete mode 100644 keychain/SecureObjectSync/Regressions/sc-kvstool.m delete mode 100644 keychain/SecureObjectSync/SOSBackupInformation.m delete mode 100644 keychain/SecureObjectSync/Tool/syncbackup.m create mode 100644 keychain/Trieste/.swiftlint.yml create mode 100644 keychain/TrustedPeersHelper/Container_BottledPeers.swift create mode 100644 keychain/TrustedPeersHelper/Container_RecoveryKey.swift create mode 100644 keychain/TrustedPeersHelperUnitTests/.swiftlint.yml delete mode 100644 keychain/ckks/tests/CKKSDispatchTests.m create mode 100644 keychain/ckks/tests/gen_test_plist.py delete mode 100644 keychain/ckks/tests/testrunner/KeychainCKKS.plist rename keychain/{SecureObjectSync/Tool/syncbackup.h => ot/OTDetermineCDPBitStatusOperation.h} (59%) create mode 100644 keychain/ot/OTDetermineCDPBitStatusOperation.m rename keychain/{SecureObjectSync/SOSBackupInformation.h => ot/OTSetCDPBitOperation.h} (59%) create mode 100644 keychain/ot/OTSetCDPBitOperation.m delete mode 100644 keychain/ot/proto/generated_source/OTSOSMessage.h delete mode 100644 keychain/ot/proto/generated_source/OTSOSMessage.m delete mode 100644 keychain/ot/proto/source/OTSOSMessage.m create mode 100644 keychain/ot/tests/octagon/.swiftlint.yml create mode 100644 keychain/ot/tests/octagon/OctagonTests+Account.swift create mode 100644 keychain/ot/tests/octagon/OctagonTests+CKKSConfiguration.swift create mode 100644 keychain/ot/tests/octagon/OctagonTests+ForwardCompatibility.swift create mode 100644 keychain/ot/tests/octagon/OctagonTests+Helpers.swift delete mode 100644 keychain/securityd/Regressions/secd-76-idstransport.m delete mode 100644 keychain/securityd/Regressions/secd-95-escrow-persistence.m delete mode 100644 keychain/securityd/Regressions/secd_77_ids_messaging.m delete mode 100644 rio.yml create mode 100644 tests/TrustTests/EvaluationTests/ExceptionTests.m rename OSX/sec/Security/Regressions/secitem/si-27-sectrust-exceptions.c => tests/TrustTests/EvaluationTests/ExceptionTests_data.h (58%) diff --git a/Analytics/SFAnalytics+Internal.h b/Analytics/SFAnalytics+Internal.h index 05aa19a7..2d5554db 100644 --- a/Analytics/SFAnalytics+Internal.h +++ b/Analytics/SFAnalytics+Internal.h @@ -31,6 +31,7 @@ @interface SFAnalytics (Internal) - (void)logMetric:(NSNumber*)metric withName:(NSString*)metricName oncePerReport:(BOOL)once; ++ (NSString*)hwModelID; @end diff --git a/Analytics/SFAnalytics.m b/Analytics/SFAnalytics.m index 4f6933a4..a51eb5f9 100644 --- a/Analytics/SFAnalytics.m +++ b/Analytics/SFAnalytics.m @@ -40,6 +40,12 @@ #import +#if TARGET_OS_OSX +#include +#else +#import +#endif + // SFAnalyticsDefines constants NSString* const SFAnalyticsTableSuccessCount = @"success_count"; NSString* const SFAnalyticsTableHardFailures = @"hard_failures"; @@ -53,6 +59,7 @@ NSString* const SFAnalyticsColumnSoftFailureCount = @"soft_failure_count"; NSString* const SFAnalyticsColumnSampleValue = @"value"; NSString* const SFAnalyticsColumnSampleName = @"name"; +NSString* const SFAnalyticsPostTime = @"postTime"; NSString* const SFAnalyticsEventTime = @"eventTime"; NSString* const SFAnalyticsEventType = @"eventType"; NSString* const SFAnalyticsEventTypeErrorEvent = @"errorEvent"; @@ -130,6 +137,7 @@ NSString* const SFAnalyticsErrorDomain = @"com.apple.security.sfanalytics"; // Local constants NSString* const SFAnalyticsEventBuild = @"build"; NSString* const SFAnalyticsEventProduct = @"product"; +NSString* const SFAnalyticsEventModelID = @"modelid"; NSString* const SFAnalyticsEventInternal = @"internal"; const NSTimeInterval SFAnalyticsSamplerIntervalOncePerReport = -1.0; @@ -314,11 +322,37 @@ const NSTimeInterval SFAnalyticsSamplerIntervalOncePerReport = -1.0; return result; } ++ (NSString*)hwModelID +{ + static NSString *hwModel = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ +#if TARGET_OS_SIMULATOR + // Asking for a real value in the simulator gives the results for the underlying mac. Not particularly useful. + hwModel = [NSString stringWithFormat:@"%s", getenv("SIMULATOR_MODEL_IDENTIFIER")]; +#elif TARGET_OS_OSX + size_t size; + sysctlbyname("hw.model", NULL, &size, NULL, 0); + char *sysctlString = malloc(size); + sysctlbyname("hw.model", sysctlString, &size, NULL, 0); + hwModel = [[NSString alloc] initWithUTF8String:sysctlString]; + free(sysctlString); +#else + struct utsname systemInfo; + uname(&systemInfo); + + hwModel = [NSString stringWithCString:systemInfo.machine + encoding:NSUTF8StringEncoding]; +#endif + }); + return hwModel; +} + (void)addOSVersionToEvent:(NSMutableDictionary*)eventDict { static dispatch_once_t onceToken; static NSString *build = NULL; static NSString *product = NULL; + static NSString *modelID = nil; static BOOL internal = NO; dispatch_once(&onceToken, ^{ NSDictionary *version = CFBridgingRelease(_CFCopySystemVersionDictionary()); @@ -327,6 +361,8 @@ const NSTimeInterval SFAnalyticsSamplerIntervalOncePerReport = -1.0; build = version[(__bridge NSString *)_kCFSystemVersionBuildVersionKey]; product = version[(__bridge NSString *)_kCFSystemVersionProductNameKey]; internal = os_variant_has_internal_diagnostics("com.apple.security"); + + modelID = [self hwModelID]; }); if (build) { eventDict[SFAnalyticsEventBuild] = build; @@ -334,6 +370,9 @@ const NSTimeInterval SFAnalyticsSamplerIntervalOncePerReport = -1.0; if (product) { eventDict[SFAnalyticsEventProduct] = product; } + if (modelID) { + eventDict[SFAnalyticsEventModelID] = modelID; + } if (internal) { eventDict[SFAnalyticsEventInternal] = @YES; } diff --git a/Analytics/SFAnalytics.plist b/Analytics/SFAnalytics.plist index 76b0892b..ec44cc55 100644 --- a/Analytics/SFAnalytics.plist +++ b/Analytics/SFAnalytics.plist @@ -4,6 +4,8 @@ KeySyncTopic + uploadSizeLimit + 1000000 splunk_allowInsecureCertificate splunk_topic @@ -13,6 +15,8 @@ CloudServicesTopic + uploadSizeLimit + 1000000 splunk_allowInsecureCertificate splunk_topic @@ -24,6 +28,8 @@ TrustTopic + uploadSizeLimit + 1000000 splunk_allowInsecureCertificate splunk_topic @@ -35,6 +41,8 @@ TransparencyTopic + uploadSizeLimit + 10000 splunk_allowInsecureCertificate splunk_topic diff --git a/Analytics/SFAnalyticsDefines.h b/Analytics/SFAnalyticsDefines.h index 2291ca02..abf89eec 100644 --- a/Analytics/SFAnalyticsDefines.h +++ b/Analytics/SFAnalyticsDefines.h @@ -38,6 +38,7 @@ extern NSString* const SFAnalyticsColumnSoftFailureCount; extern NSString* const SFAnalyticsColumnSampleValue; extern NSString* const SFAnalyticsColumnSampleName; +extern NSString* const SFAnalyticsPostTime; extern NSString* const SFAnalyticsEventTime; extern NSString* const SFAnalyticsEventType; extern NSString* const SFAnalyticsEventTypeErrorEvent; diff --git a/CircleJoinRequested/CircleJoinRequested.m b/CircleJoinRequested/CircleJoinRequested.m index 38e7c8ea..2d9caabd 100644 --- a/CircleJoinRequested/CircleJoinRequested.m +++ b/CircleJoinRequested/CircleJoinRequested.m @@ -144,6 +144,8 @@ static void keybagDidUnlock() NSError *localError = nil; CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair]; CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; + + secnotice("followup", "Posting a follow up (for SOS) of type repair"); [cdpd postFollowUpWithContext:context error:&localError ]; secnotice("cjr", "account is icdp"); if(localError){ @@ -628,6 +630,8 @@ static void kickOutChoice(CFUserNotificationRef userNotification, CFOptionFlags CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; NSError *localError = nil; CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair]; + + secnotice("followup", "Posting a follow up (for SOS) of type repair"); [cdpd postFollowUpWithContext:context error:&localError ]; if(localError){ secnotice("cjr", "request to CoreCDP to follow up failed: %@", localError); @@ -772,6 +776,8 @@ static void askForCDPFollowup() { NSError *localError = nil; CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair]; + + secnotice("followup", "Posting a follow up (for SOS) of type repair"); [cdpd postFollowUpWithContext:context error:&localError ]; if(localError){ secnotice("cjr", "request to CoreCDP to follow up failed: %@", localError); @@ -910,6 +916,8 @@ static bool processEvents() NSError *localError = nil; CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair]; + + secnotice("followup", "Posting a follow up (for SOS) of type repair"); [cdpd postFollowUpWithContext:context error:&localError ]; if(localError){ secnotice("cjr", "request to CoreCDP to follow up failed: %@", localError); diff --git a/KeychainCircle/KCJoiningAcceptSession+Internal.h b/KeychainCircle/KCJoiningAcceptSession+Internal.h index ea2c1be2..a0a9d584 100644 --- a/KeychainCircle/KCJoiningAcceptSession+Internal.h +++ b/KeychainCircle/KCJoiningAcceptSession+Internal.h @@ -31,6 +31,9 @@ #import "KCJoiningSession.h" @interface KCJoiningAcceptSession (Internal) + +- (KCAESGCMDuplexSession*)accessSession; + -(void)setControlObject:(OTControl*)control; - (void)setConfiguration:(OTJoiningConfiguration *)config; @end diff --git a/KeychainCircle/KCJoiningAcceptSession.m b/KeychainCircle/KCJoiningAcceptSession.m index 01285024..931144a8 100644 --- a/KeychainCircle/KCJoiningAcceptSession.m +++ b/KeychainCircle/KCJoiningAcceptSession.m @@ -127,6 +127,7 @@ typedef enum { self->_joiningConfiguration = [[OTJoiningConfiguration alloc]initWithProtocolType:@"OctagonPiggybacking" uniqueDeviceID:@"acceptor-deviceid" uniqueClientID:@"requester-deviceid" + pairingUUID:[[NSUUID UUID] UUIDString] containerName:nil contextID:OTDefaultContext epoch:0 @@ -402,7 +403,38 @@ typedef enum { } #endif +- (NSData*) createTLKRequestResponse: (NSError**) error { + NSError* localError = NULL; + NSData* initialSync = [self.circleDelegate circleGetInitialSyncViews:kSOSInitialSyncFlagTLKs error:&localError]; + if (!initialSync) { + secnotice("joining", "Failed to get initial sync view: %@", localError); + if ( error!=NULL && localError != NULL ) + *error = localError; + return nil; + } + + NSData* encryptedOutgoing = [self.session encrypt:initialSync error:&localError]; + if (!encryptedOutgoing) { + secnotice("joining", "TLK request failed to encrypt: %@", localError); + if ( error!=NULL && localError != NULL ) + *error = localError; + return nil; + } + self->_state = kAcceptDone; + + secnotice("joining", "TLKRequest done."); + + return [[KCJoiningMessage messageWithType:kTLKRequest + data:encryptedOutgoing + error:error] der]; +} + - (NSData*) processApplication: (KCJoiningMessage*) message error:(NSError**) error { + + if ([message type] == kTLKRequest) { + return [self createTLKRequestResponse: error]; + } + if ([message type] != kPeerInfo) { KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected peerInfo!"); return nil; @@ -531,6 +563,11 @@ typedef enum { { self.joiningConfiguration = config; } + +- (KCAESGCMDuplexSession*)accessSession +{ + return self.session; +} #endif @end diff --git a/KeychainCircle/KCJoiningMessages.h b/KeychainCircle/KCJoiningMessages.h index a6ad9e38..5dc66c70 100644 --- a/KeychainCircle/KCJoiningMessages.h +++ b/KeychainCircle/KCJoiningMessages.h @@ -84,6 +84,8 @@ typedef enum { kPeerInfo = 4, kCircleBlob = 5, + kTLKRequest = 6, + kError = 0, kUnknown = 255, diff --git a/KeychainCircle/KCJoiningRequestCircleSession.m b/KeychainCircle/KCJoiningRequestCircleSession.m index ed9db950..a8778b29 100644 --- a/KeychainCircle/KCJoiningRequestCircleSession.m +++ b/KeychainCircle/KCJoiningRequestCircleSession.m @@ -52,9 +52,13 @@ typedef enum { - (void)setControlObject:(OTControl *)control{ self.otControl = control; } -- (void)setJoiningConfigurationObject:(OTJoiningConfiguration *)joiningConfiguration +- (void)setContextIDOnJoiningConfiguration:(NSString*)contextID { - self.joiningConfiguration = joiningConfiguration; + self.joiningConfiguration.contextID = contextID; +} +- (KCAESGCMDuplexSession*)accessSession +{ + return self.session; } #endif @@ -205,7 +209,10 @@ typedef enum { OTSponsorToApplicantRound2M2 *voucher = pairingMessage.voucher; //handle voucher message then join octagon - [self.otControl rpcJoinWithConfiguration:self.joiningConfiguration vouchData:voucher.voucher vouchSig:voucher.voucherSignature preapprovedKeys:voucher.preapprovedKeys reply:^(NSError * _Nullable err) { + [self.otControl rpcJoinWithConfiguration:self.joiningConfiguration + vouchData:voucher.voucher + vouchSig:voucher.voucherSignature + reply:^(NSError * _Nullable err) { if(err){ secerror("octagon: error joining octagon: %@", err); localError = err; diff --git a/KeychainCircle/KCJoiningRequestSecretSession.m b/KeychainCircle/KCJoiningRequestSecretSession.m index 2cb52bcf..81b9aac8 100644 --- a/KeychainCircle/KCJoiningRequestSecretSession.m +++ b/KeychainCircle/KCJoiningRequestSecretSession.m @@ -71,7 +71,8 @@ bool KCJoiningOctagonPiggybackingEnabled() { @property (readwrite) NSData* challenge; @property (readwrite) NSData* salt; #if OCTAGON -@property (nonatomic, strong) OTJoiningConfiguration* joiningConfiguration; +@property (readwrite) NSString* sessionUUID; + @property (nonatomic, strong) OTControl *otControl; #endif @property (nonatomic, strong) NSMutableDictionary *defaults; @@ -147,7 +148,7 @@ bool KCJoiningOctagonPiggybackingEnabled() { } self->_session = [KCAESGCMDuplexSession sessionAsSender:key context:self.dsid]; - self.session.pairingUUID = self.joiningConfiguration.pairingUUID; + self.session.pairingUUID = self.sessionUUID; self.session.piggybackingVersion = self.piggy_version; return self.session != nil; @@ -219,9 +220,8 @@ bool KCJoiningOctagonPiggybackingEnabled() { if(self.piggy_version == kPiggyV2){ OTPairingMessage* pairingMessage = [[OTPairingMessage alloc]initWithData: [message secondData]]; - - if(pairingMessage.epoch.epoch){ - secnotice("octagon", "received epoch"); + if(pairingMessage.hasEpoch) { + secnotice("octagon", "received epoch message: %@", [pairingMessage.epoch dictionaryRepresentation]); self.epoch = pairingMessage.epoch.epoch; } else{ @@ -350,18 +350,13 @@ bool KCJoiningOctagonPiggybackingEnabled() { #if OCTAGON self->_piggy_version = KCJoiningOctagonPiggybackingEnabled() ? kPiggyV2 : kPiggyV1; self->_otControl = [OTControl controlObject:true error:error]; - self->_joiningConfiguration = [[OTJoiningConfiguration alloc]initWithProtocolType:OTProtocolPiggybacking - uniqueDeviceID:@"requester-id" - uniqueClientID:@"requester-id" - containerName:nil - contextID:OTDefaultContext - epoch:0 - isInitiator:true]; + + _sessionUUID = [[NSUUID UUID] UUIDString]; #else self->_piggy_version = kPiggyV1; #endif - secnotice("joining", "joining: initWithSecretDelegate called, uuid=%@", self.joiningConfiguration.pairingUUID); + secnotice("joining", "joining: initWithSecretDelegate called, uuid=%@", self.sessionUUID); NSString* name = [NSString stringWithFormat: @"%llu", dsid]; @@ -391,11 +386,6 @@ bool KCJoiningOctagonPiggybackingEnabled() { { self.otControl = control; } - -- (void)setConfiguration:(OTJoiningConfiguration *)config -{ - self.joiningConfiguration = config; -} #endif @end diff --git a/KeychainCircle/KCJoiningRequestSession+Internal.h b/KeychainCircle/KCJoiningRequestSession+Internal.h index 917529c3..340d23cb 100644 --- a/KeychainCircle/KCJoiningRequestSession+Internal.h +++ b/KeychainCircle/KCJoiningRequestSession+Internal.h @@ -32,13 +32,14 @@ @interface KCJoiningRequestSecretSession (Internal) - (void)setControlObject:(OTControl*)control; -- (void)setConfiguration:(OTJoiningConfiguration *)config; @end @interface KCJoiningRequestCircleSession (Internal) +- (KCAESGCMDuplexSession*)accessSession; + - (void)setControlObject:(OTControl*)control; -- (void)setJoiningConfigurationObject:(OTJoiningConfiguration *)config; +- (void)setContextIDOnJoiningConfiguration:(NSString*)contextID; @end #endif /* Header_h */ #endif diff --git a/KeychainCircle/PairingChannel.h b/KeychainCircle/PairingChannel.h index 6f65dd6f..d6c19033 100644 --- a/KeychainCircle/PairingChannel.h +++ b/KeychainCircle/PairingChannel.h @@ -61,5 +61,9 @@ extern KCPairingIntent_Type KCPairingIntent_Type_UserDriven; - (void)setOctagonMessageFailForTesting:(BOOL)value; + (bool)isSupportedPlatform; - (void)setSessionSupportsOctagonForTesting:(bool)value; + ++ (NSData *)pairingChannelCompressData:(NSData *)data; ++ (NSData *)pairingChannelDecompressData:(NSData *)data; + @end diff --git a/KeychainCircle/PairingChannel.m b/KeychainCircle/PairingChannel.m index b65d3f6b..3627feae 100644 --- a/KeychainCircle/PairingChannel.m +++ b/KeychainCircle/PairingChannel.m @@ -18,7 +18,6 @@ #import "keychain/ot/OctagonControlServer.h" #import "keychain/ot/OTJoiningConfiguration.h" #import "keychain/ot/proto/generated_source/OTPairingMessage.h" -#import "keychain/ot/proto/generated_source/OTSOSMessage.h" #import "keychain/ot/proto/generated_source/OTApplicantToSponsorRound2M1.h" #import "keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.h" #import "keychain/ot/proto/generated_source/OTSponsorToApplicantRound1M2.h" @@ -109,6 +108,7 @@ typedef void(^OTNextState)(NSData *inData, OTPairingInternalCompletion complete) @property (assign) bool initiator; @property (assign) unsigned counter; @property (assign) bool acceptorWillSendInitialSyncCredentials; +@property (assign) uint32_t acceptorInitialSyncCredentialsFlags; @property (strong) NSXPCConnection *connection; @property (strong) OTControl *otControl; @property (strong) NSString* contextID; @@ -170,6 +170,7 @@ typedef void(^OTNextState)(NSData *inData, OTPairingInternalCompletion complete) _joiningConfiguration = [[OTJoiningConfiguration alloc]initWithProtocolType:OTProtocolPairing uniqueDeviceID:peerVersionContext.uniqueDeviceID uniqueClientID:peerVersionContext.uniqueClientID + pairingUUID:[[NSUUID UUID] UUIDString] containerName:nil contextID:OTDefaultContext epoch:0 @@ -207,7 +208,7 @@ typedef void(^OTNextState)(NSData *inData, OTPairingInternalCompletion complete) const compression_algorithm pairingCompression = COMPRESSION_LZFSE; #define EXTRA_SIZE 100 -- (NSData *)compressData:(NSData *)data ++ (NSData *)pairingChannelCompressData:(NSData *)data { NSMutableData *scratch = [NSMutableData dataWithLength:compression_encode_scratch_buffer_size(pairingCompression)]; @@ -226,7 +227,7 @@ const compression_algorithm pairingCompression = COMPRESSION_LZFSE; return o; } -- (NSData *)decompressData:(NSData *)data ++ (NSData *)pairingChannelDecompressData:(NSData *)data { NSMutableData *scratch = [NSMutableData dataWithLength:compression_decode_scratch_buffer_size(pairingCompression)]; @@ -499,7 +500,10 @@ const compression_algorithm pairingCompression = COMPRESSION_LZFSE; OTSponsorToApplicantRound2M2 *voucher = pairingMessage.voucher; //handle voucher and join octagon - [self.otControl rpcJoinWithConfiguration:self.joiningConfiguration vouchData:voucher.voucher vouchSig:voucher.voucherSignature preapprovedKeys:voucher.preapprovedKeys reply:^(NSError *error) { + [self.otControl rpcJoinWithConfiguration:self.joiningConfiguration + vouchData:voucher.voucher + vouchSig:voucher.voucherSignature + reply:^(NSError *error) { if (error || self.testFailOctagon) { secerror("ot-pairing: failed to create %d message: %@", self.counter, error); complete(true, NULL, error); @@ -507,7 +511,7 @@ const compression_algorithm pairingCompression = COMPRESSION_LZFSE; }else{ secnotice(pairingScope, "initiatorThirdPacket successfully joined Octagon"); typeof(self) strongSelf = weakSelf; - if(OctagonPlatformSupportsSOS() && strongSelf->_acceptorWillSendInitialSyncCredentials == true) { + if(OctagonPlatformSupportsSOS() && strongSelf->_acceptorWillSendInitialSyncCredentials) { strongSelf.nextState = ^(NSDictionary *nsdata, KCPairingInternalCompletion kscomplete){ [weakSelf initiatorFourthPacket:nsdata complete:kscomplete]; }; @@ -567,6 +571,11 @@ const compression_algorithm pairingCompression = COMPRESSION_LZFSE; if (self.sessionSupportsSOS && indata[@"d"]) { secnotice("pairing", "acceptor initialSyncCredentials requested"); self.acceptorWillSendInitialSyncCredentials = true; + self.acceptorInitialSyncCredentialsFlags = + SOSControlInitialSyncFlagTLK| + SOSControlInitialSyncFlagPCS| + SOSControlInitialSyncFlagBluetoothMigration; + } if (indata[@"o"] == nil) { @@ -749,6 +758,12 @@ const compression_algorithm pairingCompression = COMPRESSION_LZFSE; response.voucher = [[OTSponsorToApplicantRound2M2 alloc] init]; response.voucher.voucher = voucher; response.voucher.voucherSignature = voucherSig; + + if (self.acceptorWillSendInitialSyncCredentials) { + // no need to share TLKs over the pairing channel, that's provided by octagon + self.acceptorInitialSyncCredentialsFlags &= ~(SOSControlInitialSyncFlagTLK | SOSControlInitialSyncFlagPCS); + } + reply[@"o"] = response.data; secnotice("pairing", "acceptor reply to packet 2"); @@ -761,10 +776,7 @@ const compression_algorithm pairingCompression = COMPRESSION_LZFSE; { secnotice("pairing", "acceptor packet 3"); - const uint32_t initialSyncCredentialsFlags = - SOSControlInitialSyncFlagTLK| - SOSControlInitialSyncFlagPCS| - SOSControlInitialSyncFlagBluetoothMigration; + const uint32_t initialSyncCredentialsFlags = self.acceptorInitialSyncCredentialsFlags; [[self.connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) { complete(true, NULL, error); @@ -824,7 +836,7 @@ const compression_algorithm pairingCompression = COMPRESSION_LZFSE; if (inputCompressedData) { - NSData *data = [self decompressData:inputCompressedData]; + NSData *data = [[self class] pairingChannelDecompressData:inputCompressedData]; if (data == NULL) { secnotice("pairing", "failed to decompress"); complete(true, NULL, NULL); @@ -849,7 +861,7 @@ const compression_algorithm pairingCompression = COMPRESSION_LZFSE; if (outdata == NULL && error) error = error2; if (outdata) - compressedData = [self compressData:outdata]; + compressedData = [[self class] pairingChannelCompressData:outdata]; if (compressedData) { NSString *key = [NSString stringWithFormat:@"com.apple.ckks.pairing.packet-size.%s.%u", diff --git a/KeychainCircle/Tests/FakeSOSControl.m b/KeychainCircle/Tests/FakeSOSControl.m index b8edbae9..eafb80c6 100644 --- a/KeychainCircle/Tests/FakeSOSControl.m +++ b/KeychainCircle/Tests/FakeSOSControl.m @@ -131,15 +131,32 @@ - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete { - complete(@[], NULL); + // Make up a fake TLK + NSMutableArray *items = [NSMutableArray array]; + if (flags & SOSControlInitialSyncFlagTLK) { + NSString *tlkUUID = [[NSUUID UUID] UUIDString]; + NSDictionary *fakeTLK = @{ + @"class": @"inet", + @"agrp": @"com.apple.security.ckks", + @"vwht": @"PCS-master", + @"pdmn": @"ck", + @"desc": @"tlk", + @"srvr": @"fakeZone", + @"acct": tlkUUID, + @"path": tlkUUID, + @"v_Data": [NSData data], + }; + [items addObject:fakeTLK]; + } + complete(items, nil); } - (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete { - complete(true, NULL); + complete(true, nil); } -- (void)rpcTriggerSync:(NSArray *)peers complete:(void(^)(bool success, NSError *))complete +- (void)triggerSync:(NSArray *)peers complete:(void(^)(bool success, NSError *))complete { complete(true, NULL); } @@ -328,11 +345,7 @@ complete(nil, nil); } -- (void)rpcTriggerBackup:(NSArray *)backupPeers complete:(void (^)(NSError *))complete { - complete(nil); -} - -- (void)rpcTriggerRingUpdate:(void (^)(NSError *))complete { +- (void)triggerBackup:(NSArray *)backupPeers complete:(void (^)(NSError *))complete { complete(nil); } diff --git a/KeychainCircle/Tests/KCJoiningSessionTest.m b/KeychainCircle/Tests/KCJoiningSessionTest.m index a3d71d0f..2cd57264 100644 --- a/KeychainCircle/Tests/KCJoiningSessionTest.m +++ b/KeychainCircle/Tests/KCJoiningSessionTest.m @@ -20,24 +20,6 @@ #include -__unused static SOSFullPeerInfoRef SOSNSFullPeerInfoCreate(NSDictionary* gestalt, - NSData* backupKey, SecKeyRef signingKey, - SecKeyRef octagonSigningKey, - SecKeyRef octagonEncryptionKey, - NSError**error) -{ - CFErrorRef errorRef = NULL; - - SOSFullPeerInfoRef result = SOSFullPeerInfoCreate(NULL, (__bridge CFDictionaryRef) gestalt, (__bridge CFDataRef) backupKey, signingKey, octagonSigningKey, octagonEncryptionKey, &errorRef); - - if (errorRef && error) { - *error = (__bridge_transfer NSError*) errorRef; - errorRef = NULL; - } - - return result; -} - static SecKeyRef GenerateFullECKey_internal(int keySize, NSError** error) { SecKeyRef full_key = NULL; diff --git a/KeychainCircle/Tests/KCTLKRequestTest.m b/KeychainCircle/Tests/KCTLKRequestTest.m new file mode 100644 index 00000000..325f198c --- /dev/null +++ b/KeychainCircle/Tests/KCTLKRequestTest.m @@ -0,0 +1,323 @@ +#import + +#import + +#import +#import +#import +#import +#import +#import + +#include +#include "keychain/SecureObjectSync/SOSFullPeerInfo.h" +#include "keychain/SecureObjectSync/SOSPeerInfoInternal.h" + +#include + + +static SecKeyRef GenerateFullECKey_internal(int keySize, NSError** error) +{ + SecKeyRef full_key = NULL; + + NSDictionary* keygen_parameters = @{ (__bridge NSString*)kSecAttrKeyType:(__bridge NSString*) kSecAttrKeyTypeEC, + (__bridge NSString*)kSecAttrKeySizeInBits: [NSNumber numberWithInt: keySize] }; + + + (void) OSStatusError(SecKeyGeneratePair((__bridge CFDictionaryRef)keygen_parameters, NULL, &full_key), error, @"Generate Key failed"); + + return full_key; +} + +static SecKeyRef GenerateFullECKey(int keySize, NSError** error) { + return GenerateFullECKey_internal(keySize, error); +} + +static NSData* createTlkRequestMessage (KCAESGCMDuplexSession* aesSession) { + char someData[] = {1,2,3,4,5,6}; + NSError* error = NULL; + NSData* rndPadding = [NSData dataWithBytes:(void*)someData length:sizeof(someData)]; + KCJoiningMessage* tlkRequestMessage = [KCJoiningMessage messageWithType: kTLKRequest data:rndPadding error:&error]; + return [tlkRequestMessage der]; +} + +@interface KCJoiningRequestTestDelegate : NSObject +@property (readwrite) NSString* sharedSecret; + +@property (readonly) NSString* accountCode; +@property (readonly) NSData* circleJoinData; + +@property (readwrite) NSString* incorrectSecret; +@property (readwrite) int incorrectTries; + + ++ (id) requestDelegateWithSecret:(NSString*) secret; +- (id) init NS_UNAVAILABLE; +- (id) initWithSecret: (NSString*) secret + incorrectSecret: (NSString*) wrongSecret + incorrectTries: (int) retries NS_DESIGNATED_INITIALIZER; +- (NSString*) secret; +- (NSString*) verificationFailed: (bool) codeChanged; +- (SOSPeerInfoRef) copyPeerInfoError: (NSError**) error; +- (bool) processCircleJoinData: (NSData*) circleJoinData version:(PiggyBackProtocolVersion)version error: (NSError**)error ; +- (bool) processAccountCode: (NSString*) accountCode error: (NSError**)error; + +@end + +@implementation KCJoiningRequestTestDelegate + ++ (id) requestDelegateWithSecret:(NSString*) secret { + return [[KCJoiningRequestTestDelegate alloc] initWithSecret:secret + incorrectSecret:@"" + incorrectTries:0]; +} + ++ (id) requestDelegateWithSecret:(NSString*) secret + incorrectSecret:(NSString*) wrongSecret + incorrectTries:(int) retries { + return [[KCJoiningRequestTestDelegate alloc] initWithSecret:secret + incorrectSecret:wrongSecret + incorrectTries:retries]; +} + + +- (id) initWithSecret: (NSString*) secret + incorrectSecret: (NSString*) incorrectSecret + incorrectTries: (int) retries { + if ( self = [super init] ) { + self.sharedSecret = secret; + self.incorrectSecret = incorrectSecret; + self.incorrectTries = retries; + } + return self; +} + +- (NSString*) nextSecret { + if (self.incorrectTries > 0) { + self.incorrectTries -= 1; + return self.incorrectSecret; + } + return self.sharedSecret; +} + +- (NSString*) secret { + return [self nextSecret]; +} + +- (NSString*) verificationFailed: (bool) codeChanged { + return [self nextSecret]; +} + +- (SOSPeerInfoRef) copyPeerInfoError: (NSError**) error { + return NULL; +} + +- (bool) processCircleJoinData: (NSData*) circleJoinData version:(PiggyBackProtocolVersion)version error: (NSError**)error { + self->_circleJoinData = circleJoinData; + return true; +} + +- (bool) processAccountCode: (NSString*) accountCode error: (NSError**)error { + self->_accountCode = accountCode; + return true; +} + +@end + +@interface KCJoiningAcceptTestDelegate : NSObject +@property (readonly) NSArray* secrets; +@property (readwrite) NSUInteger currentSecret; +@property (readwrite) int retriesLeft; +@property (readwrite) int retriesPerSecret; + +@property (readonly) NSString* codeToUse; +@property (readonly) NSData* circleJoinData; +@property (readonly) SOSPeerInfoRef peerInfo; + ++ (id) acceptDelegateWithSecret: (NSString*) secret code: (NSString*) code; ++ (id) acceptDelegateWithSecrets: (NSArray*) secrets retries: (int) retries code: (NSString*) code; +- (id) initWithSecrets: (NSArray*) secrets retries: (int) retries code: (NSString*) code NS_DESIGNATED_INITIALIZER; + +- (NSString*) secret; +- (NSString*) accountCode; + +- (KCRetryOrNot) verificationFailed: (NSError**) error; +- (NSData*) circleJoinDataFor: (SOSPeerInfoRef) peer + error: (NSError**) error; + +- (id) init NS_UNAVAILABLE; + +@end + +@implementation KCJoiningAcceptTestDelegate + ++ (id) acceptDelegateWithSecrets: (NSArray*) secrets retries: (int) retries code: (NSString*) code { + return [[KCJoiningAcceptTestDelegate alloc] initWithSecrets:secrets retries:retries code:code]; + +} + ++ (id) acceptDelegateWithSecret: (NSString*) secret code: (NSString*) code { + return [[KCJoiningAcceptTestDelegate alloc] initWithSecret:secret code:code]; +} + +- (id) initWithSecret: (NSString*) secret code: (NSString*) code { + return [self initWithSecrets:@[secret] retries:3 code:code]; +} + +- (id) initWithSecrets: (NSArray*) secrets retries: (int) retries code: (NSString*) code { + self = [super init]; + + self->_secrets = secrets; + self.currentSecret = 0; + self->_retriesPerSecret = retries; + self->_retriesLeft = self.retriesPerSecret; + + self->_codeToUse = code; + + uint8_t joinDataBuffer[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; + self->_circleJoinData = [NSData dataWithBytes: joinDataBuffer length: sizeof(joinDataBuffer) ]; + + return self; +} + +- (KCRetryOrNot) advanceSecret { + if (self.retriesLeft == 0) { + self.currentSecret += 1; + if (self.currentSecret >= [self.secrets count]) { + self.currentSecret = [self.secrets count] - 1; + } + self.retriesLeft = self.retriesPerSecret; + return kKCRetryWithNewChallenge; + } else { + self.retriesLeft -= 1; + return kKCRetryWithSameChallenge; + } +} + +- (NSString*) secret { + return self.secrets[self.currentSecret]; +} +- (NSString*) accountCode { + return self.codeToUse; +} + +- (KCRetryOrNot) verificationFailed: (NSError**) error { + return [self advanceSecret]; +} + +- (NSData*) circleJoinDataFor: (SOSPeerInfoRef) peer + error: (NSError**) error { + uint8_t joinDataBuffer[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; + + self->_peerInfo = peer; + return [NSData dataWithBytes: joinDataBuffer length: sizeof(joinDataBuffer) ]; +} + +-(NSData*) circleGetInitialSyncViews:(SOSInitialSyncFlags)flags error:(NSError**) error{ + char testData[] = {0,1,2,3,4,5,6,7,8,9}; + return [NSData dataWithBytes:testData length:sizeof(testData)]; + //return [[KCJoiningAcceptAccountCircleDelegate delegate] circleGetInitialSyncViews:flags error:error]; //Need security entitlements! +} + +@end + +@interface KCTLKRequestTest : XCTestCase + +@end + +@implementation KCTLKRequestTest + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testTLKRequest { + NSError* error = nil; + + NSString* secret = @"123456"; + NSString* code = @"987654"; + + uint64_t dsid = 0x1234567887654321; + + KCJoiningRequestTestDelegate* requestDelegate = [KCJoiningRequestTestDelegate requestDelegateWithSecret: secret]; + KCJoiningRequestSecretSession *requestSession = [[KCJoiningRequestSecretSession alloc] initWithSecretDelegate:requestDelegate + dsid:dsid + rng:ccDRBGGetRngState() + error:&error]; + + NSData* initialMessage = [requestSession initialMessage: &error]; + + XCTAssertNotNil(initialMessage, @"No initial message"); + XCTAssertNil(error, @"Got error %@", error); + + KCJoiningAcceptTestDelegate* acceptDelegate = [KCJoiningAcceptTestDelegate acceptDelegateWithSecret:secret code:code]; + KCJoiningAcceptSession* acceptSession = [[KCJoiningAcceptSession alloc] initWithSecretDelegate:acceptDelegate + circleDelegate:acceptDelegate + dsid:dsid + rng:ccDRBGGetRngState() + error:&error]; + + error = nil; + NSData* challenge = [acceptSession processMessage: initialMessage error: &error]; + + XCTAssertNotNil(challenge, @"No initial message"); + XCTAssertNil(error, @"Got error %@", error); + + error = nil; + NSData* response = [requestSession processMessage: challenge error: &error]; + + XCTAssertNotNil(response, @"No response message"); + XCTAssertNil(error, @"Got error %@", error); + + error = nil; + NSData* verification = [acceptSession processMessage: response error: &error]; + + XCTAssertNotNil(verification, @"No verification message"); + XCTAssertNil(error, @"Got error %@", error); + + error = nil; + NSData* doneMessage = [requestSession processMessage: verification error: &error]; + + XCTAssertNotNil(doneMessage, @"No response message"); + XCTAssertNil(error, @"Got error %@", error); + + XCTAssertTrue([requestSession isDone], @"SecretSession done"); + XCTAssertFalse([acceptSession isDone], @"Unexpected accept session done"); + + KCAESGCMDuplexSession* aesSession = [requestSession session]; + requestSession = nil; + + KCJoiningRequestCircleSession* requestSecretSession = [KCJoiningRequestCircleSession sessionWithCircleDelegate:requestDelegate session:aesSession error:&error]; + + XCTAssertNotNil(requestSecretSession, @"No request secret session"); + XCTAssertNil(error, @"Got error %@", error); + + NSData* tlkRequestMessage = createTlkRequestMessage(aesSession); + XCTAssertNotNil(tlkRequestMessage, @"No TLKRequest message"); + + NSData* tlkMessage = [acceptSession processMessage:tlkRequestMessage error:&error]; + XCTAssertNotNil(tlkMessage, @"No tlkData message"); + XCTAssertNil(error, @"Got error %@", error); + + KCJoiningMessage* receivedKCJoinMessage = [KCJoiningMessage messageWithDER:tlkMessage error:&error]; + XCTAssertNotNil(receivedKCJoinMessage, @"No receivedKCJoinMessage message"); + XCTAssertNil(error, @"Got error %@", error); + + NSData* tlkDecryptedData = [aesSession decryptAndVerify:receivedKCJoinMessage.firstData error:&error]; + XCTAssertNotNil(tlkDecryptedData, @"No tlkDecryptedData message"); + XCTAssertNil(error, @"Got error %@", error); + + //check for tlkc content + NSData* initialSync = [acceptDelegate circleGetInitialSyncViews:kSOSInitialSyncFlagTLKs error:&error]; + XCTAssertNotNil(initialSync, @"No initialSync data"); + XCTAssertNil(error, @"Got error %@", error); + + XCTAssertEqualObjects(initialSync, tlkDecryptedData, @"TLK data is different."); +} +@end diff --git a/OSX/Keychain Circle Notification/KNAppDelegate.m b/OSX/Keychain Circle Notification/KNAppDelegate.m index 2c913826..742abf4a 100644 --- a/OSX/Keychain Circle Notification/KNAppDelegate.m +++ b/OSX/Keychain Circle Notification/KNAppDelegate.m @@ -104,6 +104,8 @@ static void PSKeychainSyncIsUsingICDP(void) NSError *localError = NULL; CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair]; + + secnotice("followup", "Posting a follow up (for SOS) of type repair"); [cdpd postFollowUpWithContext:context error:&localError ]; if(localError){ secnotice("kcn", "request to CoreCDP to follow up failed: %@", localError); diff --git a/OSX/authd/authorization.plist b/OSX/authd/authorization.plist index 2a1930aa..43774837 100644 --- a/OSX/authd/authorization.plist +++ b/OSX/authd/authorization.plist @@ -1670,7 +1670,18 @@ See remaining rules for examples. timeout 300 - com.apple.system-extensions.admin + com.apple.tcc.util.admin + + comment + For modification of TCC settings. + class + rule + rule + authenticate-admin-nonshared + shared + + + com.apple.system-extensions.admin comment Authorize a 3rd party application which wants to manipulate system extensions. diff --git a/OSX/lib/en.lproj/authorization.prompts.strings b/OSX/lib/en.lproj/authorization.prompts.strings index 1686dbd6..43870767 100644 --- a/OSX/lib/en.lproj/authorization.prompts.strings +++ b/OSX/lib/en.lproj/authorization.prompts.strings @@ -173,3 +173,5 @@ "com.apple.configurationprofiles.userenrollment.install" = "__APPNAME__ is trying to enroll you in a remote management (MDM) service."; "com.apple.system-extensions.admin" = "__APPNAME__ is trying to modify a System Extension."; + +"com.apple.tcc.util.admin" = "__APPNAME__ is trying to modify your Security & Privacy settings."; diff --git a/OSX/libsecurity_cdsa_utilities/lib/cssmdb.h b/OSX/libsecurity_cdsa_utilities/lib/cssmdb.h index 186e7669..66c9bcb2 100644 --- a/OSX/libsecurity_cdsa_utilities/lib/cssmdb.h +++ b/OSX/libsecurity_cdsa_utilities/lib/cssmdb.h @@ -199,8 +199,13 @@ public: CssmDlDbHandle *handles() const { return CssmDlDbHandle::overlay(DLDBHandle); } CssmDlDbHandle * &handles() { return CssmDlDbHandle::overlayVar(DLDBHandle); } - CssmDlDbHandle &operator [] (uint32 ix) const - { assert(ix < count()); return CssmDlDbHandle::overlay(DLDBHandle[ix]); } + CssmDlDbHandle &operator [] (uint32 ix) const { + if (ix >= count()) { + secemergency("CssmDlDbList: attempt to index beyond bounds"); + abort(); + } + return CssmDlDbHandle::overlay(DLDBHandle[ix]); + } void setDlDbList(uint32 n, CSSM_DL_DB_HANDLE *list) { count() = n; handles() = CssmDlDbHandle::overlay(list); } @@ -345,8 +350,13 @@ public: { return CssmDbAttributeInfo::overlayVar(AttributeInfo); } CssmDbAttributeInfo *attributes() const { return CssmDbAttributeInfo::overlay(AttributeInfo); } - CssmDbAttributeInfo &at(uint32 ix) const - { assert(ix < size()); return attributes()[ix]; } + CssmDbAttributeInfo &at(uint32 ix) const { + if (ix >= size()) { + secemergency("CssmDbRecordAttributeInfo: attempt to index beyond bounds"); + abort(); + } + return attributes()[ix]; + } CssmDbAttributeInfo &operator [] (uint32 ix) const { return at(ix); } }; @@ -481,8 +491,13 @@ public: { return CssmDbAttributeData::overlay(AttributeData); } // Attributes by position - CssmDbAttributeData &at(unsigned int ix) const - { assert(ix < size()); return attributes()[ix]; } + CssmDbAttributeData &at(unsigned int ix) const { + if (ix >= size()) { + secemergency("CssmDbRecordAttributeData: attempt to index beyond bounds"); + abort(); + } + return attributes()[ix]; + } CssmDbAttributeData &operator [] (unsigned int ix) const { return at(ix); } @@ -596,8 +611,13 @@ public: CssmSelectionPredicate *predicates() const { return CssmSelectionPredicate::overlay(SelectionPredicate); } - CssmSelectionPredicate &at(uint32 ix) const - { assert(ix < size()); return predicates()[ix]; } + CssmSelectionPredicate &at(uint32 ix) const { + if (ix >= size()) { + secemergency("CssmDbRecordAttributeData: attempt to index beyond bounds"); + abort(); + } + return predicates()[ix]; + } CssmSelectionPredicate &operator[] (uint32 ix) const { return at(ix); } diff --git a/OSX/libsecurity_codesigning/lib/diskimagerep.cpp b/OSX/libsecurity_codesigning/lib/diskimagerep.cpp index be2443d1..86b056c0 100644 --- a/OSX/libsecurity_codesigning/lib/diskimagerep.cpp +++ b/OSX/libsecurity_codesigning/lib/diskimagerep.cpp @@ -69,13 +69,19 @@ bool DiskImageRep::readHeader(FileDesc& fd, UDIFFileHeader& header) // Object management. // DiskImageRep::DiskImageRep(const char *path) - : SingleDiskRep(path) + : SingleDiskRep(path), mSigningData(NULL) { this->setup(); } +DiskImageRep::~DiskImageRep() +{ + free((void*)mSigningData); +} + void DiskImageRep::setup() { + free((void*)mSigningData); mSigningData = NULL; // the UDIF "header" is in fact the last 512 bytes of the file, with no particular alignment @@ -211,7 +217,7 @@ void DiskImageRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef // void DiskImageRep::Writer::flush() { - delete mSigningData; // ditch previous blob just in case + free((void*)mSigningData); // ditch previous blob just in case mSigningData = Maker::make(); // assemble new signature SuperBlob // write signature superblob diff --git a/OSX/libsecurity_codesigning/lib/diskimagerep.h b/OSX/libsecurity_codesigning/lib/diskimagerep.h index 91a09193..06a556e8 100644 --- a/OSX/libsecurity_codesigning/lib/diskimagerep.h +++ b/OSX/libsecurity_codesigning/lib/diskimagerep.h @@ -43,6 +43,7 @@ namespace CodeSigning { class DiskImageRep : public SingleDiskRep { public: DiskImageRep(const char *path); + virtual ~DiskImageRep(); CFDataRef identification(); CFDataRef component(CodeDirectory::SpecialSlot slot); @@ -70,7 +71,7 @@ private: UDIFFileHeader mHeader; // disk image header (all fields NBO) size_t mEndOfDataOffset; // end of payload data (data fork + XML) size_t mHeaderOffset; // trailing header offset - const EmbeddedSignatureBlob *mSigningData; // pointer to signature SuperBlob (in mapped memory) + const EmbeddedSignatureBlob *mSigningData; // pointer to signature SuperBlob (malloc'd memory during setup) }; diff --git a/OSX/libsecurity_codesigning/lib/notarization.cpp b/OSX/libsecurity_codesigning/lib/notarization.cpp index fa75c0d0..f645fd2e 100644 --- a/OSX/libsecurity_codesigning/lib/notarization.cpp +++ b/OSX/libsecurity_codesigning/lib/notarization.cpp @@ -49,9 +49,9 @@ registerStapledTicketWithSystem(CFDataRef data) secinfo("notarization", "Registering stapled ticket with system"); #if TARGET_OS_OSX - CFErrorRef error = NULL; - if (!SecAssessmentTicketRegister(data, &error)) { - secerror("Error registering stapled ticket: %@", data); + CFRef error; + if (!SecAssessmentTicketRegister(data, &error.aref())) { + secerror("Error registering stapled ticket: %@", error.get()); } #endif // TARGET_OS_OSX } @@ -208,7 +208,7 @@ registerStapledTicketInPackage(const std::string& path) goto lb_exit; } - data = CFDataCreateWithBytesNoCopy(NULL, ticketData, trailer.length, NULL); + data.take(makeCFDataMalloc(ticketData, trailer.length)); if (data.get() == NULL) { secerror("unable to create cfdata for notarization"); goto lb_exit; @@ -221,9 +221,6 @@ lb_exit: if (fd) { close(fd); } - if (ticketData) { - free(ticketData); - } } void @@ -277,7 +274,7 @@ registerStapledTicketInBundle(const std::string& path) goto lb_exit; } - data = CFDataCreateWithBytesNoCopy(NULL, ticketData, ticketLength, NULL); + data.take(makeCFDataMalloc(ticketData, ticketLength)); if (data.get() == NULL) { secerror("unable to create cfdata for notarization"); goto lb_exit; @@ -290,9 +287,6 @@ lb_exit: if (fd) { close(fd); } - if (ticketData) { - free(ticketData); - } } void diff --git a/OSX/libsecurity_codesigning/lib/signer.cpp b/OSX/libsecurity_codesigning/lib/signer.cpp index 89279238..eff950ab 100644 --- a/OSX/libsecurity_codesigning/lib/signer.cpp +++ b/OSX/libsecurity_codesigning/lib/signer.cpp @@ -429,11 +429,12 @@ void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const std::string relpath, Rule *rule) { bool isSymlink = (ent->fts_info == FTS_SL); + bool isNested = (ruleFlags & ResourceBuilder::nested); const std::string path(ent->fts_path); const std::string accpath(ent->fts_accpath); this->state.mLimitedAsync->perform(groupRef, ^{ CFRef seal; - if (ruleFlags & ResourceBuilder::nested) { + if (isNested) { seal.take(signNested(path, relpath)); } else if (isSymlink) { char target[PATH_MAX]; @@ -445,6 +446,10 @@ void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase } else { seal.take(resources.hashFile(accpath.c_str(), digestAlgorithms(), signingFlags() & kSecCSSignStrictPreflight)); } + if (seal.get() == NULL) { + secerror("Failed to generate sealed resource: %d, %d, %s", isNested, isSymlink, accpath.c_str()); + MacOSError::throwMe(errSecCSBadResource); + } if (ruleFlags & ResourceBuilder::optional) CFDictionaryAddValue(seal, CFSTR("optional"), kCFBooleanTrue); CFTypeRef hash; diff --git a/OSX/libsecurity_keychain/lib/SecItem.cpp b/OSX/libsecurity_keychain/lib/SecItem.cpp index b1b571c2..2cdb17f9 100644 --- a/OSX/libsecurity_keychain/lib/SecItem.cpp +++ b/OSX/libsecurity_keychain/lib/SecItem.cpp @@ -41,6 +41,7 @@ #include "TrustSettingsSchema.h" #include #include "utilities/array_size.h" +#include "utilities/SecCFWrappers.h" #include #include @@ -4309,7 +4310,7 @@ SecItemCreateFromAttributeDictionary_osx(CFDictionaryRef refAttributes) { CFTypeRef v; Item item = Item(item_class, &attrs, 0, ""); - v = CFDictionaryGetValue(refAttributes, kSecValuePersistentRef); + v = CFCast(CFData, CFDictionaryGetValue(refAttributes, kSecValuePersistentRef)); if (v) { item->setPersistentRef((CFDataRef)v); } diff --git a/OSX/libsecurity_smime/lib/cmsdigest.c b/OSX/libsecurity_smime/lib/cmsdigest.c index cbdc0912..102b8a4f 100644 --- a/OSX/libsecurity_smime/lib/cmsdigest.c +++ b/OSX/libsecurity_smime/lib/cmsdigest.c @@ -3,25 +3,25 @@ * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ - * + * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. - * + * * The Original Code is the Netscape security libraries. - * + * * The Initial Developer of the Original Code is Netscape - * Communications Corporation. Portions created by Netscape are + * Communications Corporation. Portions created by Netscape are * Copyright (C) 1994-2000 Netscape Communications Corporation. All * Rights Reserved. - * + * * Contributor(s): - * + * * Alternatively, the contents of this file may be used under the * terms of the GNU General Public License Version 2 or later (the - * "GPL"), in which case the provisions of the GPL are applicable - * instead of those above. If you wish to allow use of your + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your * version of this file only under the terms of the GPL and not to * allow others to use your version of this file under the MPL, * indicate your decision by deleting the provisions above and @@ -34,24 +34,30 @@ /* * CMS digesting. */ +#include #include "cmslocal.h" -#include "secitem.h" +#include "SecAsn1Item.h" #include "secoid.h" #include -#include +#include + +#include #include -/* Return the maximum value between S and T */ +/* Return the maximum value between S and T (and U) */ #define MAX(S, T) ({__typeof__(S) _max_s = S; __typeof__(T) _max_t = T; _max_s > _max_t ? _max_s : _max_t;}) +#define MAX_OF_3(S, T, U) ({__typeof__(U) _max_st = MAX(S,T); MAX(_max_st,U);}) struct SecCmsDigestContextStr { - Boolean saw_contents; - int digcnt; - CSSM_CC_HANDLE * digobjs; + PLArenaPool * poolp; + Boolean saw_contents; + int digcnt; + void ** digobjs; + SECAlgorithmID ** digestalgs; }; /* @@ -61,25 +67,38 @@ struct SecCmsDigestContextStr { SecCmsDigestContextRef SecCmsDigestContextStartMultiple(SECAlgorithmID **digestalgs) { - SecCmsDigestContextRef cmsdigcx; - CSSM_CC_HANDLE digobj; + PLArenaPool *poolp; + SecCmsDigestContextRef cmsdigcx = NULL; + void * digobj; int digcnt; int i; + poolp = PORT_NewArena(1024); + if (poolp == NULL) { + goto loser; + } + digcnt = (digestalgs == NULL) ? 0 : SecCmsArrayCount((void **)digestalgs); - cmsdigcx = (SecCmsDigestContextRef)PORT_ZAlloc(sizeof(struct SecCmsDigestContextStr)); - if (cmsdigcx == NULL) - return NULL; + cmsdigcx = (SecCmsDigestContextRef)PORT_ArenaAlloc(poolp, sizeof(struct SecCmsDigestContextStr)); + if (cmsdigcx == NULL) { + goto loser; + } + cmsdigcx->poolp = poolp; if (digcnt > 0) { /* Security check to prevent under-allocation */ - if (digcnt >= (int)(INT_MAX/sizeof(CSSM_CC_HANDLE))) { + if (digcnt >= (int)((INT_MAX/(MAX(sizeof(void *),sizeof(SECAlgorithmID *))))-1)) { + goto loser; + } + cmsdigcx->digobjs = (void**)PORT_ArenaAlloc(poolp, digcnt * sizeof(void *)); + if (cmsdigcx->digobjs == NULL) { + goto loser; + } + cmsdigcx->digestalgs = (SECAlgorithmID **)PORT_ArenaZAlloc(poolp, (digcnt + 1) * sizeof(SECAlgorithmID *)); + if (cmsdigcx->digestalgs == NULL) { goto loser; } - cmsdigcx->digobjs = (CSSM_CC_HANDLE *)PORT_ZAlloc(digcnt * sizeof(CSSM_CC_HANDLE)); - if (cmsdigcx->digobjs == NULL) - goto loser; } cmsdigcx->digcnt = 0; @@ -88,27 +107,27 @@ SecCmsDigestContextStartMultiple(SECAlgorithmID **digestalgs) * Create a digest object context for each algorithm. */ for (i = 0; i < digcnt; i++) { - digobj = SecCmsUtilGetHashObjByAlgID(digestalgs[i]); - /* - * Skip any algorithm we do not even recognize; obviously, - * this could be a problem, but if it is critical then the - * result will just be that the signature does not verify. - * We do not necessarily want to error out here, because - * the particular algorithm may not actually be important, - * but we cannot know that until later. - */ - if (digobj) - { - CSSM_RETURN result; - result = CSSM_DigestDataInit(digobj); - if (result != CSSM_OK) - { - goto loser; - } + digobj = SecCmsUtilGetHashObjByAlgID(digestalgs[i]); + /* + * Skip any algorithm we do not even recognize; obviously, + * this could be a problem, but if it is critical then the + * result will just be that the signature does not verify. + * We do not necessarily want to error out here, because + * the particular algorithm may not actually be important, + * but we cannot know that until later. + */ + + cmsdigcx->digobjs[cmsdigcx->digcnt] = digobj; + cmsdigcx->digestalgs[cmsdigcx->digcnt] = PORT_ArenaAlloc(poolp, sizeof(SECAlgorithmID)); + if (SECITEM_CopyItem(poolp, + &(cmsdigcx->digestalgs[cmsdigcx->digcnt]->algorithm), + &(digestalgs[i]->algorithm)) + || SECITEM_CopyItem(poolp, + &(cmsdigcx->digestalgs[cmsdigcx->digcnt]->parameters), + &(digestalgs[i]->parameters))) { + goto loser; } - - cmsdigcx->digobjs[cmsdigcx->digcnt] = digobj; - cmsdigcx->digcnt++; + cmsdigcx->digcnt++; } cmsdigcx->saw_contents = PR_FALSE; @@ -116,13 +135,15 @@ SecCmsDigestContextStartMultiple(SECAlgorithmID **digestalgs) return cmsdigcx; loser: - if (cmsdigcx) { - if (cmsdigcx->digobjs) { - PORT_Free(cmsdigcx->digobjs); - cmsdigcx->digobjs = NULL; - cmsdigcx->digcnt = 0; - } + if (poolp) { + PORT_FreeArena(poolp, PR_FALSE); + } + if (cmsdigcx && cmsdigcx->digobjs) { + PORT_Free(cmsdigcx->digobjs); + cmsdigcx->digobjs = NULL; + cmsdigcx->digcnt = 0; } + return NULL; } @@ -133,7 +154,7 @@ loser: SecCmsDigestContextRef SecCmsDigestContextStartSingle(SECAlgorithmID *digestalg) { - SECAlgorithmID *digestalgs[] = { NULL, NULL }; /* fake array */ + SECAlgorithmID *digestalgs[] = { NULL, NULL }; /* fake array */ digestalgs[0] = digestalg; return SecCmsDigestContextStartMultiple(digestalgs); @@ -145,15 +166,40 @@ SecCmsDigestContextStartSingle(SECAlgorithmID *digestalg) void SecCmsDigestContextUpdate(SecCmsDigestContextRef cmsdigcx, const unsigned char *data, size_t len) { - CSSM_DATA dataBuf; + SecAsn1Item dataBuf; int i; dataBuf.Length = len; - dataBuf.Data = (uint8 *)data; + dataBuf.Data = (uint8_t *)data; cmsdigcx->saw_contents = PR_TRUE; - for (i = 0; i < cmsdigcx->digcnt; i++) - if (cmsdigcx->digobjs && cmsdigcx->digobjs[i]) - CSSM_DigestDataUpdate(cmsdigcx->digobjs[i], &dataBuf, 1); + for (i = 0; i < cmsdigcx->digcnt; i++) { + if (cmsdigcx->digobjs[i]) { + /* 64 bits cast: worst case is we truncate the length and we dont hash all the data. + This may cause an invalid CMS blob larger than 4GB to be validated. Unlikely, but + possible security issue. There is no way to return an error here, but a check at + the upper level may happen. */ + /* + rdar://problem/20642513 + Let's just die a horrible death rather than have the security issue. + CMS blob over 4GB? Oh well. + */ + if (len > UINT32_MAX) { + /* Ugh. */ + abort(); + } + assert(len<=UINT32_MAX); /* Debug check. Correct as long as CC_LONG is uint32_t */ + switch (SECOID_GetAlgorithmTag(cmsdigcx->digestalgs[i])) { + case SEC_OID_SHA1: CC_SHA1_Update((CC_SHA1_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_MD5: CC_MD5_Update((CC_MD5_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_SHA224: CC_SHA224_Update((CC_SHA256_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_SHA256: CC_SHA256_Update((CC_SHA256_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_SHA384: CC_SHA384_Update((CC_SHA512_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_SHA512: CC_SHA512_Update((CC_SHA512_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + default: + break; + } + } + } } /* @@ -164,109 +210,113 @@ SecCmsDigestContextCancel(SecCmsDigestContextRef cmsdigcx) { int i; - for (i = 0; i < cmsdigcx->digcnt; i++) + for (i = 0; i < cmsdigcx->digcnt; i++) { if (cmsdigcx->digobjs && cmsdigcx->digobjs[i]) { - CSSM_DeleteContext(cmsdigcx->digobjs[i]); - cmsdigcx->digobjs[i] = 0; + free(cmsdigcx->digobjs[i]); + cmsdigcx->digobjs[i] = NULL; } + } + + PORT_FreeArena(cmsdigcx->poolp, PR_TRUE); } /* - * SecCmsDigestContextFinishMultiple - finish the digests and put them - * into an array of CSSM_DATAs (allocated on poolp) + * SecCmsDigestContextFinishMultiple - finish the digests + * Note that on macOS, this call cancels and frees the digest context (because the digests are allocated from an input arena pool). + * The iOS version only frees the digest objects and requires a call to SecCmsDisgestContextDestroy + * or SecCmsDisgestContextCancel (because the digests are allocated out of the context's pool). */ OSStatus -SecCmsDigestContextFinishMultiple(SecCmsDigestContextRef cmsdigcx, SecArenaPoolRef poolp, - CSSM_DATA_PTR **digestsp) +SecCmsDigestContextFinishMultiple(SecCmsDigestContextRef cmsdigcx, + SecArenaPoolRef poolp, + SecAsn1Item * **digestsp) { - CSSM_CC_HANDLE digobj; - CSSM_DATA_PTR *digests, digest; + void * digobj; + SecAsn1Item **digests, *digest; int i; void *mark; OSStatus rv = SECFailure; + assert(cmsdigcx != NULL); + /* no contents? do not update digests */ if (digestsp == NULL || !cmsdigcx->saw_contents) { - for (i = 0; i < cmsdigcx->digcnt; i++) + for (i = 0; i < cmsdigcx->digcnt; i++) { if (cmsdigcx->digobjs && cmsdigcx->digobjs[i]) { - CSSM_DeleteContext(cmsdigcx->digobjs[i]); - cmsdigcx->digobjs[i] = 0; + free(cmsdigcx->digobjs[i]); } - rv = SECSuccess; - goto cleanup; + } + rv = SECSuccess; + goto cleanup; } + assert(digestsp != NULL); + mark = PORT_ArenaMark ((PLArenaPool *)poolp); /* Security check to prevent under-allocation */ - if (cmsdigcx->digcnt >= (int)((INT_MAX/(MAX(sizeof(CSSM_DATA_PTR),sizeof(CSSM_DATA))))-1)) { + if (cmsdigcx->digcnt >= (int)((INT_MAX/(MAX(sizeof(SecAsn1Item *),sizeof(SecAsn1Item))))-1)) { goto loser; } - /* allocate digest array & CSSM_DATAs on arena */ - digests = (CSSM_DATA_PTR *)PORT_ArenaAlloc((PLArenaPool *)poolp, (cmsdigcx->digcnt+1) * sizeof(CSSM_DATA_PTR)); - digest = (CSSM_DATA_PTR)PORT_ArenaZAlloc((PLArenaPool *)poolp, cmsdigcx->digcnt * sizeof(CSSM_DATA)); + /* allocate digest array & SecAsn1Items on arena */ + digests = (SecAsn1Item * *)PORT_ArenaZAlloc((PLArenaPool *)poolp, (cmsdigcx->digcnt+1) * sizeof(SecAsn1Item *)); + digest = (SecAsn1Item *)PORT_ArenaZAlloc((PLArenaPool *)poolp, cmsdigcx->digcnt * sizeof(SecAsn1Item)); if (digests == NULL || digest == NULL) { - goto loser; + goto loser; } for (i = 0; i < cmsdigcx->digcnt; i++, digest++) { - if (cmsdigcx->digobjs) { - digobj = cmsdigcx->digobjs[i]; - } else { - digobj = 0; + SECOidTag hash_alg = SECOID_GetAlgorithmTag(cmsdigcx->digestalgs[i]); + int diglength = 0; + + switch (hash_alg) { + case SEC_OID_SHA1: diglength = CC_SHA1_DIGEST_LENGTH; break; + case SEC_OID_MD5: diglength = CC_MD5_DIGEST_LENGTH; break; + case SEC_OID_SHA224: diglength = CC_SHA224_DIGEST_LENGTH; break; + case SEC_OID_SHA256: diglength = CC_SHA256_DIGEST_LENGTH; break; + case SEC_OID_SHA384: diglength = CC_SHA384_DIGEST_LENGTH; break; + case SEC_OID_SHA512: diglength = CC_SHA512_DIGEST_LENGTH; break; + default: goto loser; } - CSSM_QUERY_SIZE_DATA dataSize; - rv = CSSM_QuerySize(digobj, CSSM_FALSE, 1, &dataSize); - if (rv != CSSM_OK) - { - goto loser; - } - - int diglength = dataSize.SizeOutputBlock; - - if (digobj) - { - digest->Data = (unsigned char*)PORT_ArenaAlloc((PLArenaPool *)poolp, diglength); - if (digest->Data == NULL) - goto loser; - digest->Length = diglength; - rv = CSSM_DigestDataFinal(digobj, digest); - if (rv != CSSM_OK) - { + digobj = cmsdigcx->digobjs[i]; + if (digobj) { + digest->Data = (unsigned char*)PORT_ArenaAlloc((PLArenaPool *)poolp, diglength); + if (digest->Data == NULL) goto loser; + digest->Length = diglength; + switch (hash_alg) { + case SEC_OID_SHA1: CC_SHA1_Final(digest->Data, digobj); break; + case SEC_OID_MD5: CC_MD5_Final(digest->Data, digobj); break; + case SEC_OID_SHA224: CC_SHA224_Final(digest->Data, digobj); break; + case SEC_OID_SHA256: CC_SHA256_Final(digest->Data, digobj); break; + case SEC_OID_SHA384: CC_SHA384_Final(digest->Data, digobj); break; + case SEC_OID_SHA512: CC_SHA512_Final(digest->Data, digobj); break; + default: goto loser; } - - CSSM_DeleteContext(digobj); - cmsdigcx->digobjs[i] = 0; - } - else - { - digest->Data = NULL; - digest->Length = 0; - } - - digests[i] = digest; - } + + free(digobj); + digests[i] = digest; + } else { + digest->Data = NULL; + digest->Length = 0; + } + } digests[i] = NULL; *digestsp = digests; rv = SECSuccess; loser: - if (rv == SECSuccess) - PORT_ArenaUnmark((PLArenaPool *)poolp, mark); - else - PORT_ArenaRelease((PLArenaPool *)poolp, mark); + if (rv == SECSuccess) { + PORT_ArenaUnmark((PLArenaPool *)poolp, mark); + } else { + PORT_ArenaRelease((PLArenaPool *)poolp, mark); + } cleanup: - if (cmsdigcx->digcnt > 0) { - SecCmsDigestContextCancel(cmsdigcx); - PORT_Free(cmsdigcx->digobjs); - cmsdigcx->digobjs = NULL; - cmsdigcx->digcnt = 0; - } - PORT_Free(cmsdigcx); + cmsdigcx->digcnt = 0; // We've already freed the digests above + SecCmsDigestContextCancel(cmsdigcx); return rv; } @@ -277,28 +327,30 @@ cleanup: */ OSStatus SecCmsDigestContextFinishSingle(SecCmsDigestContextRef cmsdigcx, SecArenaPoolRef poolp, - CSSM_DATA_PTR digest) + SecAsn1Item * digest) { OSStatus rv = SECFailure; - CSSM_DATA_PTR *dp; + SecAsn1Item **dp; PLArenaPool *arena = NULL; if ((arena = PORT_NewArena(1024)) == NULL) goto loser; /* get the digests into arena, then copy the first digest into poolp */ - if (SecCmsDigestContextFinishMultiple(cmsdigcx, (SecArenaPoolRef)arena, &dp) != SECSuccess) - goto loser; + if (SecCmsDigestContextFinishMultiple(cmsdigcx, (SecArenaPoolRef)arena, &dp) != SECSuccess) { + goto loser; + } /* now copy it into poolp */ - if (SECITEM_CopyItem((PLArenaPool *)poolp, digest, dp[0]) != SECSuccess) - goto loser; + if (SECITEM_CopyItem((PLArenaPool *)poolp, digest, dp[0]) != SECSuccess) { + goto loser; + } rv = SECSuccess; loser: - if (arena) - PORT_FreeArena(arena, PR_FALSE); - + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } return rv; } diff --git a/OSX/libsecurity_smime/lib/cmspriv.h b/OSX/libsecurity_smime/lib/cmspriv.h index 5e41d83e..cd7220b6 100644 --- a/OSX/libsecurity_smime/lib/cmspriv.h +++ b/OSX/libsecurity_smime/lib/cmspriv.h @@ -95,7 +95,7 @@ SecCmsAlgArrayGetIndexByAlgID(SECAlgorithmID **algorithmArray, SECAlgorithmID *a extern int SecCmsAlgArrayGetIndexByAlgTag(SECAlgorithmID **algorithmArray, SECOidTag algtag); -extern CSSM_CC_HANDLE +extern void * SecCmsUtilGetHashObjByAlgID(SECAlgorithmID *algid); /* diff --git a/OSX/libsecurity_smime/lib/cmsutil.c b/OSX/libsecurity_smime/lib/cmsutil.c index c0f89054..f07f98e4 100644 --- a/OSX/libsecurity_smime/lib/cmsutil.c +++ b/OSX/libsecurity_smime/lib/cmsutil.c @@ -48,6 +48,7 @@ #include #include #include +#include /* @@ -213,21 +214,42 @@ SecCmsAlgArrayGetIndexByAlgTag(SECAlgorithmID **algorithmArray, return i; } -CSSM_CC_HANDLE +void * SecCmsUtilGetHashObjByAlgID(SECAlgorithmID *algid) { SECOidData *oidData = SECOID_FindOID(&(algid->algorithm)); if (oidData) { - CSSM_ALGORITHMS alg = oidData->cssmAlgorithm; - if (alg) - { - CSSM_CC_HANDLE digobj; - CSSM_CSP_HANDLE cspHandle = SecCspHandleForAlgorithm(alg); - - if (!CSSM_CSP_CreateDigestContext(cspHandle, alg, &digobj)) - return digobj; - } + void *digobj = NULL; + switch (oidData->offset) { + case SEC_OID_SHA1: + digobj = calloc(1, sizeof(CC_SHA1_CTX)); + CC_SHA1_Init(digobj); + break; + case SEC_OID_MD5: + digobj = calloc(1, sizeof(CC_MD5_CTX)); + CC_MD5_Init(digobj); + break; + case SEC_OID_SHA224: + digobj = calloc(1, sizeof(CC_SHA256_CTX)); + CC_SHA224_Init(digobj); + break; + case SEC_OID_SHA256: + digobj = calloc(1, sizeof(CC_SHA256_CTX)); + CC_SHA256_Init(digobj); + break; + case SEC_OID_SHA384: + digobj = calloc(1, sizeof(CC_SHA512_CTX)); + CC_SHA384_Init(digobj); + break; + case SEC_OID_SHA512: + digobj = calloc(1, sizeof(CC_SHA512_CTX)); + CC_SHA512_Init(digobj); + break; + default: + break; + } + return digobj; } return 0; diff --git a/OSX/libsecurityd/lib/sstransit.cpp b/OSX/libsecurityd/lib/sstransit.cpp index a59d7b3f..cf209e67 100644 --- a/OSX/libsecurityd/lib/sstransit.cpp +++ b/OSX/libsecurityd/lib/sstransit.cpp @@ -155,7 +155,10 @@ DataRetrieval::~DataRetrieval() { if (mAddr) { relocate(mAddr, mBase); - assert(mAttributes->size() == mAddr->size()); + if (mAttributes->size() != mAddr->size()) { + secemergency("~DataRetrieval: size mismatch, %u != %u", mAttributes->size(), mAddr->size()); + abort(); + } // global (per-record) fields mAttributes->recordType(mAddr->recordType()); diff --git a/OSX/sec/Security/SecAccessControl.m b/OSX/sec/Security/SecAccessControl.m index 849426be..652e72e8 100644 --- a/OSX/sec/Security/SecAccessControl.m +++ b/OSX/sec/Security/SecAccessControl.m @@ -52,11 +52,23 @@ static void dumpValue(id value, NSMutableString *target, NSString *separator) { if (value == nil) { // Do nothing. } else if (CFGetTypeID((__bridge CFTypeRef)value) == CFBooleanGetTypeID()) { - [target appendString:[value boolValue] ? @"true" : @"false"]; + NSNumber *boolValue = value; + [target appendString:boolValue.boolValue ? @"true" : @"false"]; } else if ([value isKindOfClass:NSNumber.class]) { - [target appendString:[value string]]; + NSNumber *numberValue = value; + [target appendString:numberValue.stringValue]; } else if ([value isKindOfClass:NSString.class]) { [target appendString:value]; + } else if ([value isKindOfClass:NSData.class]) { + NSData *dataValue = value; + const uint8_t *dataBuffer = dataValue.bytes; + NSUInteger dumpLength = dataValue.length > 64 ? 64 : dataValue.length; + for (NSUInteger i = 0; i < dumpLength; i++) { + [target appendFormat:@"%02X", (unsigned)dataBuffer[i]]; + } + if (dumpLength < dataValue.length) { + [target appendFormat:@"...(%db)", (int)dataValue.length]; + } } else if ([value isKindOfClass:NSDictionary.class]) { [value enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { [target appendString:separator]; diff --git a/OSX/sec/Security/SecCTKKey.m b/OSX/sec/Security/SecCTKKey.m index 5ebebb06..9d43e27a 100644 --- a/OSX/sec/Security/SecCTKKey.m +++ b/OSX/sec/Security/SecCTKKey.m @@ -36,6 +36,7 @@ #include #include #include +#include #include "OSX/sec/Security/SecItemShim.h" #include "SecECKey.h" @@ -321,7 +322,7 @@ static Boolean SecCTKKeySetParameter(SecKeyRef key, CFStringRef name, CFProperty if (CFEqual(name, kSecUseAuthenticationContext)) { // Preprocess LAContext to ACMRef value. if (value != NULL) { - require_quiet(acm_reference = SecItemAttributesCopyPreparedAuthContext(value, error), out); + require_quiet(acm_reference = LACopyACMContext(value, error), out); value = acm_reference; } name = kSecUseCredentialReference; diff --git a/OSX/sec/Security/SecCertificate.c b/OSX/sec/Security/SecCertificate.c index d9b3f39d..204ec41a 100644 --- a/OSX/sec/Security/SecCertificate.c +++ b/OSX/sec/Security/SecCertificate.c @@ -1359,7 +1359,7 @@ static bool isAppleExtensionOID(const DERItem *extnID) static const uint8_t appleComponentExtensionArc[8] = { 0x2a,0x86,0x48,0x86,0xf7,0x63,0x64,0x0b }; static const uint8_t appleSigningExtensionArc[8] = { 0x2a,0x86,0x48,0x86,0xf7,0x63,0x64,0x0c }; static const uint8_t appleEncryptionExtensionArc[8] = { 0x2a,0x86,0x48,0x86,0xf7,0x63,0x64,0x0d }; - if (!extnID && !extnID->data && extnID->length <= sizeof(appleExtensionArc)) { + if (!extnID || !extnID->data || (extnID->length <= sizeof(appleExtensionArc))) { return false; } return (!memcmp(extnID->data, appleExtensionArc, sizeof(appleExtensionArc)) || @@ -1368,6 +1368,19 @@ static bool isAppleExtensionOID(const DERItem *extnID) !memcmp(extnID->data, appleEncryptionExtensionArc, sizeof(appleEncryptionExtensionArc))); } +static bool isCCCExtensionOID(const DERItem *extnID) +{ + static const uint8_t cccVehicleCA[] = { 0x2B,0x06,0x01,0x04,0x01,0x82,0xC4,0x69,0x05,0x09 }; + static const uint8_t cccIntermediateCA[] = { 0x2B,0x06,0x01,0x04,0x01,0x82,0xC4,0x69,0x05,0x08 }; + static const uint8_t cccVehicle[] = { 0x2B,0x06,0x01,0x04,0x01,0x82,0xC4,0x69,0x05,0x01 }; + if (!extnID || !extnID->data || (extnID->length != sizeof(cccVehicleCA))) { + return false; + } + return (!memcmp(extnID->data, cccVehicleCA, sizeof(cccVehicleCA)) || + !memcmp(extnID->data, cccIntermediateCA, sizeof(cccIntermediateCA)) || + !memcmp(extnID->data, cccVehicle, sizeof(cccVehicle))); +} + /* Given the contents of an X.501 Name return the contents of a normalized X.501 name. */ CFDataRef createNormalizedX501Name(CFAllocatorRef allocator, @@ -1841,7 +1854,7 @@ static bool SecCertificateParse(SecCertificateRef certificate) } require_quiet(parseResult || !certificate->_extensions[ix].critical, badCert); } else if (certificate->_extensions[ix].critical) { - if (isAppleExtensionOID(&extn.extnID)) { + if (isAppleExtensionOID(&extn.extnID) || isCCCExtensionOID(&extn.extnID)) { continue; } secdebug("cert", "Found unknown critical extension"); diff --git a/OSX/sec/Security/SecExports.exp-in b/OSX/sec/Security/SecExports.exp-in index 7067b56d..d5f82152 100644 --- a/OSX/sec/Security/SecExports.exp-in +++ b/OSX/sec/Security/SecExports.exp-in @@ -90,6 +90,7 @@ _kSecPolicyNameAppleParsecService _kSecPolicyNameApplePPQService _kSecPolicyNameApplePushService _kSecPolicyNameAppleSiriService +_kSecPolicyNameAppleUpdatesService _kSecPolicyNameEAPClient _kSecPolicyNameEAPServer _kSecPolicyNameIPSecClient diff --git a/OSX/sec/Security/SecItem.c b/OSX/sec/Security/SecItem.c index b526d632..98fb9085 100644 --- a/OSX/sec/Security/SecItem.c +++ b/OSX/sec/Security/SecItem.c @@ -674,16 +674,17 @@ static void infer_cert_label(SecCFDictionaryCOW *attributes) static CFDataRef CreateTokenPersistentRefData(CFTypeRef class, CFDictionaryRef attributes) { CFDataRef tokenPersistentRef = NULL; - CFStringRef tokenId = CFDictionaryGetValue(attributes, kSecAttrTokenID); + CFStringRef tokenId; CFDictionaryRef itemValue = NULL; + require_quiet(tokenId = CFCast(CFString, CFDictionaryGetValue(attributes, kSecAttrTokenID)), out); if (CFEqual(class, kSecClassIdentity)) { itemValue = SecTokenItemValueCopy(CFDictionaryGetValue(attributes, kSecAttrIdentityCertificateData), NULL); } else { itemValue = SecTokenItemValueCopy(CFDictionaryGetValue(attributes, kSecValueData), NULL); } - require(itemValue, out); + require_quiet(itemValue, out); CFDataRef oid = CFDictionaryGetValue(itemValue, kSecTokenValueObjectIDKey); - require(oid, out); + require_quiet(oid, out); CFArrayRef array = CFArrayCreateForCFTypes(kCFAllocatorDefault, class, tokenId, oid, NULL); tokenPersistentRef = CFPropertyListCreateDERData(kCFAllocatorDefault, array, NULL); CFRelease(array); @@ -825,12 +826,18 @@ static CFDataRef SecTokenItemValueCreate(CFDataRef oid, CFDataRef access_control CFDictionaryRef SecTokenItemValueCopy(CFDataRef db_value, CFErrorRef *error) { CFPropertyListRef plist = NULL; + require_quiet(CFCastWithError(CFData, db_value, error), out); const uint8_t *der = CFDataGetBytePtr(db_value); const uint8_t *der_end = der + CFDataGetLength(db_value); require_quiet(der = der_decode_plist(0, kCFPropertyListImmutable, &plist, error, der, der_end), out); require_action_quiet(der == der_end, out, SecError(errSecDecode, error, CFSTR("trailing garbage at end of token data field"))); - require_action_quiet(CFDictionaryGetValue(plist, kSecTokenValueObjectIDKey) != NULL, out, + CFTypeRef value = CFDictionaryGetValue(plist, kSecTokenValueObjectIDKey); + require_action_quiet(CFCast(CFData, value) != NULL, out, SecError(errSecInternal, error, CFSTR("token based item data does not have OID"))); + value = CFDictionaryGetValue(plist, kSecTokenValueAccessControlKey); + require_quiet(value == NULL || CFCastWithError(CFData, value, error), out); + value = CFDictionaryGetValue(plist, kSecTokenValueDataKey); + require_quiet(value == NULL || CFCastWithError(CFData, value, error), out); out: return plist; @@ -894,6 +901,7 @@ static bool SecTokenItemCreateFromAttributes(CFDictionaryRef attributes, CFDicti CFMutableDictionaryRef attrs = CFDictionaryCreateMutableCopy(NULL, 0, attributes); CFTypeRef token_id = CFDictionaryGetValue(attributes, kSecAttrTokenID); if (token_id != NULL && object_id != NULL) { + require_quiet(CFCastWithError(CFString, token_id, error), out); if (CFRetainSafe(token) == NULL) { require_quiet(token = SecTokenCreate(token_id, &auth_params, error), out); } @@ -946,9 +954,11 @@ static bool SecItemResultCopyPrepared(CFTypeRef raw_result, TKTokenRef token, if (token == NULL) { if (CFGetTypeID(raw_result) == CFDictionaryGetTypeID()) { token_id = CFDictionaryGetValue(raw_result, kSecAttrTokenID); + require_quiet(token_id == NULL || CFCastWithError(CFString, token_id, error) != NULL, out); token_item = (token_id != NULL); cert_token_id = CFDictionaryGetValue(raw_result, kSecAttrIdentityCertificateTokenID); + require_quiet(cert_token_id == NULL || CFCastWithError(CFString, cert_token_id, error) != NULL, out); cert_token_item = (cert_token_id != NULL); } } else { @@ -1137,23 +1147,6 @@ out: return ok; } -CFDataRef SecItemAttributesCopyPreparedAuthContext(CFTypeRef la_context, CFErrorRef *error) { - void *la_lib = NULL; - CFDataRef acm_context = NULL; - require_action_quiet(la_lib = dlopen("/System/Library/Frameworks/LocalAuthentication.framework/LocalAuthentication", RTLD_LAZY), out, - SecError(errSecInternal, error, CFSTR("failed to open LocalAuthentication.framework"))); - LAFunctionCopyExternalizedContext fnCopyExternalizedContext = NULL; - require_action_quiet(fnCopyExternalizedContext = dlsym(la_lib, "LACopyExternalizedContext"), out, - SecError(errSecInternal, error, CFSTR("failed to obtain LACopyExternalizedContext"))); - require_action_quiet(acm_context = fnCopyExternalizedContext(la_context), out, - SecError(errSecInternal, error, CFSTR("failed to get ACM handle from LAContext"))); -out: - if (la_lib != NULL) { - dlclose(la_lib); - } - return acm_context; -} - static bool SecItemAttributesPrepare(SecCFDictionaryCOW *attrs, bool forQuery, CFErrorRef *error) { bool ok = false; CFDataRef ac_data = NULL, acm_context = NULL; @@ -1199,7 +1192,7 @@ static bool SecItemAttributesPrepare(SecCFDictionaryCOW *attrs, bool forQuery, C if (la_context) { require_action_quiet(!CFDictionaryContainsKey(attrs->dictionary, kSecUseCredentialReference), out, SecError(errSecParam, error, CFSTR("kSecUseAuthenticationContext cannot be used together with kSecUseCredentialReference"))); - require_quiet(acm_context = SecItemAttributesCopyPreparedAuthContext(la_context, error), out); + require_quiet(acm_context = LACopyACMContext(la_context, error), out); CFDictionaryRemoveValue(SecCFDictionaryCOWGetMutable(attrs), kSecUseAuthenticationContext); CFDictionarySetValue(SecCFDictionaryCOWGetMutable(attrs), kSecUseCredentialReference, acm_context); } @@ -1227,7 +1220,9 @@ static bool SecItemAttributesPrepare(SecCFDictionaryCOW *attrs, bool forQuery, C value = CFDictionaryGetValue(attrs->dictionary, kSecAttrIssuer); if (value) { /* convert DN to canonical issuer, if value is DN (top level sequence) */ - const DERItem name = { (unsigned char *)CFDataGetBytePtr(value), CFDataGetLength(value) }; + CFDataRef issuer; + require_quiet(issuer = CFCastWithError(CFData, value, error), out); + const DERItem name = { (unsigned char *)CFDataGetBytePtr(issuer), CFDataGetLength(issuer) }; DERDecodedInfo content; if (DERDecodeItem(&name, &content) == DR_Success && content.tag == ASN1_CONSTR_SEQUENCE) { CFDataRef canonical_issuer = createNormalizedX501Name(kCFAllocatorDefault, &content.content); @@ -1475,6 +1470,7 @@ bool SecItemAuthDoQuery(SecCFDictionaryCOW *query, SecCFDictionaryCOW *attribute // Prepare connection to target token if it is present. CFStringRef token_id = CFDictionaryGetValue(query->dictionary, kSecAttrTokenID); + require_quiet(token_id == NULL || CFCastWithError(CFString, token_id, error) != NULL, out); if (secItemOperation != SecItemCopyMatching && token_id != NULL) { require_quiet(CFAssignRetained(token, SecTokenCreate(token_id, &auth_params, error)), out); } diff --git a/OSX/sec/Security/SecItemInternal.h b/OSX/sec/Security/SecItemInternal.h index c3b02279..50cc4c6a 100644 --- a/OSX/sec/Security/SecItemInternal.h +++ b/OSX/sec/Security/SecItemInternal.h @@ -101,8 +101,6 @@ TKTokenRef SecTokenCreate(CFStringRef token_id, SecCFDictionaryCOW *auth_params, CFDictionaryRef SecTokenItemValueCopy(CFDataRef db_value, CFErrorRef *error); -CFDataRef SecItemAttributesCopyPreparedAuthContext(CFTypeRef la_context, CFErrorRef *error); - CFArrayRef SecItemCopyParentCertificates_ios(CFDataRef normalizedIssuer, CFArrayRef accessGroups, CFErrorRef *error); bool SecItemCertificateExists(CFDataRef normalizedIssuer, CFDataRef serialNumber, CFArrayRef accessGroups, CFErrorRef *error); diff --git a/OSX/sec/Security/SecOTRPacketData.h b/OSX/sec/Security/SecOTRPacketData.h index 81dc4409..3fe80994 100644 --- a/OSX/sec/Security/SecOTRPacketData.h +++ b/OSX/sec/Security/SecOTRPacketData.h @@ -85,7 +85,7 @@ OSStatus ReadDATA(const uint8_t**bytesPtr, size_t*sizePtr, size_t* dataSize, uin static CC_NONNULL((1,2,3)) OSStatus CreatePublicKey(const uint8_t**bytesPtr, size_t*sizePtr, SecOTRPublicIdentityRef* publicId); -static CC_NONNULL((1,2,3)) +static CC_NONNULL((2,3)) CFMutableDataRef CFDataCreateMutableFromOTRDATA(CFAllocatorRef allocator, const uint8_t**bytesPtr, size_t*sizePtr); static CC_NONNULL((1)) diff --git a/OSX/sec/Security/SecPolicy.c b/OSX/sec/Security/SecPolicy.c index 98db0bf9..bc7bb235 100644 --- a/OSX/sec/Security/SecPolicy.c +++ b/OSX/sec/Security/SecPolicy.c @@ -132,6 +132,7 @@ SEC_CONST_DECL (kSecPolicyNameAppleParsecService, "Parsec"); SEC_CONST_DECL (kSecPolicyNameAppleAMPService, "AMP"); SEC_CONST_DECL (kSecPolicyNameAppleSiriService, "Siri"); SEC_CONST_DECL (kSecPolicyNameAppleHomeAppClipUploadService, "HomeAppClipUploadService"); +SEC_CONST_DECL (kSecPolicyNameAppleUpdatesService, "Updates"); #define kSecPolicySHA1Size 20 #define kSecPolicySHA256Size 32 @@ -390,9 +391,10 @@ SecPolicyRef SecPolicyCreateWithProperties(CFTypeRef policyIdentifier, policy = SecPolicyCreateAppleComponentCertificate(rootDigest); } /* For a couple of common patterns we use the macro, but some of the - * policies are deprecated, so we need to ignore the warning. */ + * policies are deprecated (or not yet available), so we need to ignore the warning. */ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wunguarded-availability" #define _P_OPTION_ #define _P_OPTION_N name #define _P_PROPERTIES_(NAME, IN_NAME, FUNCTION) @@ -3561,8 +3563,6 @@ errOut: /* This one is special because the intermediate has no marker OID */ SecPolicyRef SecPolicyCreateAppleSoftwareSigning(void) { CFMutableDictionaryRef options = NULL; - CFDictionaryRef keySizes = NULL; - CFNumberRef rsaSize = NULL, ecSize = NULL; SecPolicyRef result = NULL; require(options = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, @@ -3598,9 +3598,6 @@ SecPolicyRef SecPolicyCreateAppleSoftwareSigning(void) { errOut: CFReleaseSafe(options); - CFReleaseSafe(keySizes); - CFReleaseSafe(rsaSize); - CFReleaseSafe(ecSize); return result; } @@ -4114,3 +4111,67 @@ errOut: CFReleaseNull(options); return result; } + +SecPolicyRef SecPolicyCreateAlisha(void) { + CFMutableDictionaryRef options = NULL; + SecPolicyRef result = NULL; + CFDictionaryRef keySizes = NULL; + CFNumberRef ecSize = NULL; + + require(options = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), errOut); + + /* Alisha certs don't expire */ + SecPolicyAddBasicCertOptions(options); + + /* RSA key sizes are disallowed. EC key sizes are P-256 or larger. */ + require(ecSize = CFNumberCreateWithCFIndex(NULL, 256), errOut); + require(keySizes = CFDictionaryCreate(NULL, (const void**)&kSecAttrKeyTypeEC, + (const void**)&ecSize, 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), errOut); + add_element(options, kSecPolicyCheckKeySize, keySizes); + + /* Check for weak hashes */ + require(SecPolicyRemoveWeakHashOptions(options), errOut); + + require(result = SecPolicyCreate(kSecPolicyAppleAlisha, + kSecPolicyNameAlisha, options), errOut); +errOut: + CFReleaseNull(options); + CFReleaseNull(keySizes); + CFReleaseNull(ecSize); + return result; +} + +SecPolicyRef SecPolicyCreateMeasuredBootPolicySigning(void) { + CFMutableDictionaryRef options = NULL; + SecPolicyRef result = NULL; + + require(options = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), errOut); + + /* No expiration check. */ + SecPolicyAddBasicCertOptions(options); + + /* Exactly 3 certs in the chain */ + require(SecPolicyAddChainLengthOptions(options, 3), errOut); + + /* Corporate Signing subCA */ + add_element(options, kSecPolicyCheckIntermediateMarkerOid, CFSTR("1.2.840.113635.100.6.24.17")); + + /* Measured Boot Policy Signing Leaf OID */ + add_leaf_marker_string(options, CFSTR("1.2.840.113635.100.6.26.6.1")); + + /* RSA key sizes are 2048-bit or larger. EC key sizes are P-256 or larger. */ + require(SecPolicyAddStrongKeySizeOptions(options), errOut); + + require(result = SecPolicyCreate(kSecPolicyAppleMeasuredBootPolicySigning, + kSecPolicyNameMeasuredBootPolicySigning, options), errOut); + +errOut: + CFReleaseSafe(options); + return result; +} diff --git a/OSX/sec/Security/SecPolicy.list b/OSX/sec/Security/SecPolicy.list index 6dfb4452..c3ee5011 100644 --- a/OSX/sec/Security/SecPolicy.list +++ b/OSX/sec/Security/SecPolicy.list @@ -99,3 +99,5 @@ POLICYMACRO(FDRProvisioning, 91, , FDRProvisioning, POLICYMACRO(ComponentCertificate, 92, E, Component, , , AppleComponentCertificate) POLICYMACRO(KeyTransparency, 93, E, KT, N, Y, AppleKeyTransparency) POLICYMACRO(LegacySSL, 94, E, legacySSL, , , LegacySSL) +POLICYMACRO(Alisha, 95, E, Alisha, , Y, Alisha) +POLICYMACRO(MeasuredBootPolicySigning, 96, E, MeasuredBootPolicySigning, , Y, MeasuredBootPolicySigning) diff --git a/OSX/sec/Security/SecuritydXPC.c b/OSX/sec/Security/SecuritydXPC.c index 696d1f2c..698ae145 100644 --- a/OSX/sec/Security/SecuritydXPC.c +++ b/OSX/sec/Security/SecuritydXPC.c @@ -101,8 +101,6 @@ CFStringRef SOSCCGetOperationDescription(enum SecXPCOperation op) return CFSTR("OTASecExperimentGetAsset"); case kSecXPCOpAcceptApplicants: return CFSTR("AcceptApplicants"); - case kSecXPCOpApplyToARing: - return CFSTR("ApplyToARing"); case kSecXPCOpBailFromCircle: return CFSTR("BailFromCircle"); case kSecXPCOpCanAuthenticate: @@ -115,8 +113,6 @@ CFStringRef SOSCCGetOperationDescription(enum SecXPCOperation op) return CFSTR("CopyEngineState"); case kSecXPCOpCopyGenerationPeerInfo: return CFSTR("CopyGenerationPeerInfo"); - case kSecXPCOpCopyIncompatibilityInfo: - return CFSTR("CopyIncompatibilityInfo"); case kSecXPCOpCopyMyPeerInfo: return CFSTR("CopyMyPeerInfo"); case kSecXPCOpCopyNotValidPeerPeerInfo: @@ -131,10 +127,6 @@ CFStringRef SOSCCGetOperationDescription(enum SecXPCOperation op) return CFSTR("CopyViewUnawarePeerInfo"); case kSecXPCOpDeviceInCircle: return CFSTR("DeviceInCircle"); - case kSecXPCOpEnableRing: - return CFSTR("EnableRing"); - case kSecXPCOpGetAllTheRings: - return CFSTR("GetAllTheRings"); case kSecXPCOpGetLastDepartureReason: return CFSTR("GetLastDepartureReason"); case kSecXPCOpLoggedOutOfAccount: @@ -157,8 +149,6 @@ CFStringRef SOSCCGetOperationDescription(enum SecXPCOperation op) return CFSTR("RemovePeersFromCircle"); case kSecXPCOpRemovePeersFromCircleWithAnalytics: return CFSTR("RemovePeersFromCircleWithAnalytics"); - case kSecXPCOpRequestEnsureFreshParameters: - return CFSTR("RequestEnsureFreshParameters"); case kSecXPCOpRequestToJoin: return CFSTR("RequestToJoin"); case kSecXPCOpRequestToJoinWithAnalytics: @@ -173,8 +163,6 @@ CFStringRef SOSCCGetOperationDescription(enum SecXPCOperation op) return CFSTR("ResetToEmptyWithAnalytics"); case kSecXPCOpResetToOffering: return CFSTR("ResetToOffering"); - case kSecXPCOpRingStatus: - return CFSTR("RingStatus"); case kSecXPCOpRollKeys: return CFSTR("RollKeys"); case kSecXPCOpSetBagForAllSlices: @@ -195,8 +183,6 @@ CFStringRef SOSCCGetOperationDescription(enum SecXPCOperation op) return CFSTR("ValidateUserPublic"); case kSecXPCOpView: return CFSTR("View"); - case kSecXPCOpWithdrawlFromARing: - return CFSTR("WithdrawlFromARing"); case sec_add_shared_web_credential_id: return CFSTR("add_shared_web_credential"); case sec_copy_shared_web_credential_id: @@ -259,24 +245,10 @@ CFStringRef SOSCCGetOperationDescription(enum SecXPCOperation op) return CFSTR("ocsp_cache_flush"); case soscc_EnsurePeerRegistration_id: return CFSTR("EnsurePeerRegistration"); - case kSecXPCOpSetEscrowRecord: - return CFSTR("SetEscrowRecord"); - case kSecXPCOpGetEscrowRecord: - return CFSTR("GetEscrowRecord"); case kSecXPCOpWhoAmI: return CFSTR("WhoAmI"); case kSecXPCOpTransmogrifyToSyncBubble: return CFSTR("TransmogrifyToSyncBubble"); - case kSecXPCOpWrapToBackupSliceKeyBagForView: - return CFSTR("WrapToBackupSliceKeyBagForView"); - case kSecXPCOpCopyAccountData: - return CFSTR("CopyAccountDataFromKeychain"); - case kSecXPCOpDeleteAccountData: - return CFSTR("DeleteAccountDataFromKeychain"); - case kSecXPCOpCopyEngineData: - return CFSTR("CopyEngineDataFromKeychain"); - case kSecXPCOpDeleteEngineData: - return CFSTR("DeleteEngineDataFromKeychain"); case sec_item_update_token_items_id: return CFSTR("UpdateTokenItems"); case sec_delete_items_with_access_groups_id: @@ -287,8 +259,6 @@ CFStringRef SOSCCGetOperationDescription(enum SecXPCOperation op) return CFSTR("RegisterRecoveryPublicKey"); case kSecXPCOpGetRecoveryPublicKey: return CFSTR("GetRecoveryPublicKey"); - case kSecXPCOpCopyBackupInformation: - return CFSTR("CopyBackupInformation"); case kSecXPCOpMessageFromPeerIsPending: return CFSTR("MessageFromPeerIsPending"); case kSecXPCOpSendToPeerIsPending: diff --git a/OSX/sec/ipc/securityd_client.h b/OSX/sec/ipc/securityd_client.h index afbd42d6..ed945f01 100644 --- a/OSX/sec/ipc/securityd_client.h +++ b/OSX/sec/ipc/securityd_client.h @@ -203,12 +203,6 @@ enum SecXPCOperation { sec_set_xpc_log_settings_id, sec_set_circle_log_settings_id, soscc_EnsurePeerRegistration_id, - kSecXPCOpRequestEnsureFreshParameters, - kSecXPCOpGetAllTheRings, - kSecXPCOpApplyToARing, - kSecXPCOpWithdrawlFromARing, - kSecXPCOpEnableRing, - kSecXPCOpRingStatus, kSecXPCOpRequestDeviceID, kSecXPCOpSetDeviceID, kSecXPCOpHandleIDSMessage, @@ -253,7 +247,6 @@ enum SecXPCOperation { kSecXPCOpCopyGenerationPeerInfo, kSecXPCOpGetLastDepartureReason, kSecXPCOpSetLastDepartureReason, - kSecXPCOpCopyIncompatibilityInfo, kSecXPCOpCopyRetirementPeerInfo, kSecXPCOpCopyViewUnawarePeerInfo, kSecXPCOpCopyEngineState, @@ -263,38 +256,26 @@ enum SecXPCOperation { kSecXPCOpSetBagForAllSlices, kSecXPCOpWaitForInitialSync, kSecXPCOpWaitForInitialSyncWithAnalytics, - kSecXPCOpCopyYetToSyncViews, - kSecXPCOpSetEscrowRecord, - kSecXPCOpGetEscrowRecord, kSecXPCOpCheckPeerAvailability, - kSecXPCOpCopyAccountData, - kSecXPCOpDeleteAccountData, - kSecXPCOpCopyEngineData, - kSecXPCOpDeleteEngineData, kSecXPCOpCopyApplication, kSecXPCOpCopyCircleJoiningBlob, kSecXPCOpJoinWithCircleJoiningBlob, kSecXPCOpKVSKeyCleanup, - kSecXPCOpPopulateKVS, kSecXPCOpAccountHasPublicKey, - kSecXPCOpAccountIsNew, kSecXPCOpClearKVSPeerMessage, kSecXPCOpRegisterRecoveryPublicKey, kSecXPCOpGetRecoveryPublicKey, - kSecXPCOpCopyBackupInformation, kSecXPCOpCopyInitialSyncBlob, /* after this is free for all */ kSecXPCOpWhoAmI, kSecXPCOpTransmogrifyToSyncBubble, kSecXPCOpTransmogrifyToSystemKeychain, - kSecXPCOpWrapToBackupSliceKeyBagForView, sec_item_update_token_items_id, kSecXPCOpDeleteUserView, sec_trust_store_copy_all_id, sec_trust_store_copy_usage_constraints_id, sec_ocsp_cache_flush_id, sec_delete_items_with_access_groups_id, - kSecXPCOpIsThisDeviceLastBackup, sec_keychain_backup_keybag_uuid_id, kSecXPCOpPeersHaveViewsEnabled, kSecXPCOpProcessSyncWithPeers, @@ -378,12 +359,6 @@ struct securityd { bool (*soscc_RequestToJoinCircleWithAnalytics)(CFDataRef parentEvent, CFErrorRef* error); bool (*soscc_RequestToJoinCircleAfterRestore)(CFErrorRef* error); bool (*soscc_RequestToJoinCircleAfterRestoreWithAnalytics)(CFDataRef parentEvent, CFErrorRef* error); - bool (*soscc_RequestEnsureFreshParameters)(CFErrorRef* error); - CFStringRef (*soscc_GetAllTheRings)(CFErrorRef *error); - bool (*soscc_ApplyToARing)(CFStringRef ringName, CFErrorRef* error); - bool (*soscc_WithdrawlFromARing)(CFStringRef ringName, CFErrorRef* error); - bool (*soscc_EnableRing)(CFStringRef ringName, CFErrorRef* error); - SOSRingStatus (*soscc_RingStatus)(CFStringRef ringName, CFErrorRef* error); bool (*soscc_SetToNew)(CFErrorRef *error); bool (*soscc_ResetToOffering)(CFErrorRef* error); bool (*soscc_ResetToEmpty)(CFErrorRef* error); @@ -414,7 +389,6 @@ struct securityd { // Not sure why these are below the last entry in the enum order above, but they are: CFArrayRef (*soscc_CopyPeerInfo)(CFErrorRef* error); CFArrayRef (*soscc_CopyConcurringPeerInfo)(CFErrorRef* error); - CFStringRef (*soscc_CopyIncompatibilityInfo)(CFErrorRef* error); enum DepartureReason (*soscc_GetLastDepartureReason)(CFErrorRef* error); bool (*soscc_SetLastDepartureReason)(enum DepartureReason, CFErrorRef* error); CFSetRef (*soscc_ProcessSyncWithPeers)(CFSetRef peerIDs, CFSetRef backupPeerIDs, CFErrorRef* error); @@ -427,25 +401,13 @@ struct securityd { SOSPeerInfoRef (*soscc_CopyMyPeerInfo)(CFErrorRef*); bool (*soscc_WaitForInitialSync)(CFErrorRef*); bool (*soscc_WaitForInitialSyncWithAnalytics)(CFDataRef parentEvent, CFErrorRef *error); - CFArrayRef (*soscc_CopyYetToSyncViewsList)(CFErrorRef*); - bool (*soscc_SetEscrowRecords)(CFStringRef escrow_label, uint64_t tries, CFErrorRef *error); - CFDictionaryRef (*soscc_CopyEscrowRecords)(CFErrorRef *error); - CFDictionaryRef (*soscc_CopyBackupInformation)(CFErrorRef *error); bool (*soscc_PeerAvailability)(CFErrorRef *error); - bool (*sosbskb_WrapToBackupSliceKeyBagForView)(CFStringRef viewName, CFDataRef input, CFDataRef* output, CFDataRef* bskbEncoded, CFErrorRef* error); - CFDataRef (*soscc_CopyAccountState)(CFErrorRef *error); - bool (*soscc_DeleteAccountState)(CFErrorRef *error); - CFDataRef (*soscc_CopyEngineData)(CFErrorRef *error); - bool (*soscc_DeleteEngineState)(CFErrorRef *error); SOSPeerInfoRef (*soscc_CopyApplicant)(CFErrorRef *error); CFDataRef (*soscc_CopyCircleJoiningBlob)(SOSPeerInfoRef applicant, CFErrorRef *error); CFDataRef (*soscc_CopyInitialSyncData)(SOSInitialSyncFlags flags, CFErrorRef *error); bool (*soscc_JoinWithCircleJoiningBlob)(CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error); bool (*soscc_SOSCCCleanupKVSKeys)(CFErrorRef *error); - bool (*soscc_SOSCCTestPopulateKVSWithBadKeys)(CFErrorRef *error); bool (*soscc_AccountHasPublicKey)(CFErrorRef *error); - bool (*soscc_AccountIsNew)(CFErrorRef *error); - bool (*soscc_IsThisDeviceLastBackup)(CFErrorRef *error); bool (*soscc_requestSyncWithPeerOverKVS)(CFStringRef peerID, CFDataRef message, CFErrorRef *error); CFBooleanRef (*soscc_SOSCCPeersHaveViewsEnabled)(CFArrayRef views, CFErrorRef *error); bool (*socc_clearPeerMessageKeyInKVS)(CFStringRef peerID, CFErrorRef *error); diff --git a/OSX/sec/ipc/server.c b/OSX/sec/ipc/server.c index 917ac3e6..df744076 100644 --- a/OSX/sec/ipc/server.c +++ b/OSX/sec/ipc/server.c @@ -909,12 +909,7 @@ static void securityd_xpc_dictionary_handler(const xpc_connection_t connection, SOSCCAccountHasPublicKey_Server(&error)); } break; - case kSecXPCOpAccountIsNew: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, - SOSCCAccountIsNew_Server(&error)); - } - break; + case kSecXPCOpRequestToJoinAfterRestore: if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, @@ -932,49 +927,6 @@ static void securityd_xpc_dictionary_handler(const xpc_connection_t connection, CFReleaseNull(parentEvent); } break; - case kSecXPCOpRequestEnsureFreshParameters: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, - SOSCCRequestEnsureFreshParameters_Server(&error)); - } - break; - case kSecXPCOpGetAllTheRings: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFStringRef ringDescriptions = SOSCCGetAllTheRings_Server(&error); - xpc_object_t xpc_dictionary = _CFXPCCreateXPCObjectFromCFObject(ringDescriptions); - xpc_dictionary_set_value(replyMessage, kSecXPCKeyResult, xpc_dictionary); - xpc_release(xpc_dictionary); - CFReleaseNull(ringDescriptions); - } - break; - case kSecXPCOpApplyToARing: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFStringRef ringName = SecXPCDictionaryCopyString(event, kSecXPCKeyString, &error); - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, SOSCCApplyToARing_Server(ringName, &error)); - CFReleaseNull(ringName); - } - break; - case kSecXPCOpWithdrawlFromARing: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFStringRef ringName = SecXPCDictionaryCopyString(event, kSecXPCKeyString, &error); - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, SOSCCWithdrawlFromARing_Server(ringName, &error)); - CFReleaseNull(ringName); - } - break; - case kSecXPCOpRingStatus: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFStringRef ringName = SecXPCDictionaryCopyString(event, kSecXPCKeyString, &error); - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, SOSCCRingStatus_Server(ringName, &error)); - CFReleaseNull(ringName); - } - break; - case kSecXPCOpEnableRing: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFStringRef ringName = SecXPCDictionaryCopyString(event, kSecXPCKeyString, &error); - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, SOSCCEnableRing_Server(ringName, &error)); - CFReleaseNull(ringName); - } - break; case kSecXPCOpRequestDeviceID: case kSecXPCOpSetDeviceID: case kSecXPCOpHandleIDSMessage: @@ -1057,14 +1009,6 @@ static void securityd_xpc_dictionary_handler(const xpc_connection_t connection, SOSCCLoggedOutOfAccount_Server(&error)); } break; - case kSecXPCOpBailFromCircle: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - // 0 is valid; ok for this parameter to be unset or incorrect type. Note: kSecXPCLimitInMinutes is actually seconds, not minutes - uint64_t limit_in_seconds = xpc_dictionary_get_uint64(event, kSecXPCLimitInMinutes); - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, - SOSCCBailFromCircle_Server(limit_in_seconds, &error)); - } - break; case kSecXPCOpAcceptApplicants: if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { xpc_object_t xapplicants = xpc_dictionary_get_value(event, kSecXPCKeyPeerInfoArray); @@ -1190,73 +1134,29 @@ static void securityd_xpc_dictionary_handler(const xpc_connection_t connection, SOSCCCopyViewUnawarePeerInfo_Server(&error), &error); } - break; - case kSecXPCOpCopyAccountData: - { - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - xpc_object_t xpc_account_object = NULL; - CFDataRef accountData = SOSCCCopyAccountState_Server(&error); - if(accountData) - xpc_account_object = _CFXPCCreateXPCObjectFromCFObject(accountData); - - xpc_dictionary_set_value(replyMessage, kSecXPCKeyResult, xpc_account_object); - CFReleaseNull(accountData); - } - break; - } - case kSecXPCOpDeleteAccountData: - { - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - bool status = SOSCCDeleteAccountState_Server(&error); - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, status); - } - break; - } - case kSecXPCOpCopyEngineData: - { - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - - xpc_object_t xpc_engine_object = NULL; - CFDataRef engineData = SOSCCCopyEngineData_Server(&error); - if(engineData) - xpc_engine_object = _CFXPCCreateXPCObjectFromCFObject(engineData); - - xpc_dictionary_set_value(replyMessage, kSecXPCKeyResult, xpc_engine_object); - CFReleaseNull(engineData); - - } break; - } - case kSecXPCOpDeleteEngineData: - { - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - bool status = SOSCCDeleteEngineState_Server(&error); - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, status); - } - break; - } case kSecXPCOpCopyEngineState: { - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFArrayRef array = SOSCCCopyEngineState_Server(&error); - CFDataRef derData = NULL; + if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { + CFArrayRef array = SOSCCCopyEngineState_Server(&error); + CFDataRef derData = NULL; - require_quiet(array, done); - derData = CFPropertyListCreateDERData(kCFAllocatorDefault, array, &error); + require_quiet(array, done); + derData = CFPropertyListCreateDERData(kCFAllocatorDefault, array, &error); - require_quiet(derData, done); - xpc_dictionary_set_data(replyMessage, kSecXPCKeyResult, CFDataGetBytePtr(derData), CFDataGetLength(derData)); + require_quiet(derData, done); + xpc_dictionary_set_data(replyMessage, kSecXPCKeyResult, CFDataGetBytePtr(derData),CFDataGetLength(derData)); done: CFReleaseNull(derData); CFReleaseNull(array); } } break; - case kSecXPCOpCopyPeerPeerInfo: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - xpc_dictionary_set_and_consume_PeerInfoArray(replyMessage, kSecXPCKeyResult, - SOSCCCopyPeerPeerInfo_Server(&error), - &error); + case kSecXPCOpCopyPeerPeerInfo: + if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { + xpc_dictionary_set_and_consume_PeerInfoArray(replyMessage, kSecXPCKeyResult, + SOSCCCopyPeerPeerInfo_Server(&error), + &error); } break; case kSecXPCOpCopyConcurringPeerPeerInfo: @@ -1320,13 +1220,6 @@ static void securityd_xpc_dictionary_handler(const xpc_connection_t connection, SOSCCProcessEnsurePeerRegistration_Server(&error)); } break; - case kSecXPCOpCopyIncompatibilityInfo: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFStringRef iis = SOSCCCopyIncompatibilityInfo_Server(&error); - SecXPCDictionarySetString(replyMessage, kSecXPCKeyResult, iis, &error); - CFReleaseSafe(iis); - } - break; case kSecXPCOpRollKeys: { // false is valid, so it's safe for this parameter to be unset or incorrect type @@ -1353,59 +1246,6 @@ static void securityd_xpc_dictionary_handler(const xpc_connection_t connection, CFReleaseNull(parentEvent); } break; - case kSecXPCOpCopyYetToSyncViews: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFArrayRef array = SOSCCCopyYetToSyncViewsList_Server(&error); - if (array) { - xpc_object_t xpc_array = _CFXPCCreateXPCObjectFromCFObject(array); - xpc_dictionary_set_value(replyMessage, kSecXPCKeyResult, xpc_array); - xpc_release(xpc_array); - } - CFReleaseNull(array); - } - break; - case kSecXPCOpSetEscrowRecord: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFStringRef escrow_label = SecXPCDictionaryCopyString(event, kSecXPCKeyEscrowLabel, &error); // NULL checked below - uint64_t tries = xpc_dictionary_get_int64(event, kSecXPCKeyTriesLabel); // 0 is acceptable; safe for this parameter to be unset or incorrect type - - if (escrow_label != NULL) { - bool result = SOSCCSetEscrowRecord_Server(escrow_label, tries, &error); - if (result) { - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, result); - } - CFReleaseNull(escrow_label); - } - } - break; - case kSecXPCOpGetEscrowRecord: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFDictionaryRef record = SOSCCCopyEscrowRecord_Server(&error); - if (record) { - xpc_object_t xpc_dictionary = _CFXPCCreateXPCObjectFromCFObject(record); - xpc_dictionary_set_value(replyMessage, kSecXPCKeyResult, xpc_dictionary); - xpc_release(xpc_dictionary); - } - CFReleaseNull(record); - } - break; - case kSecXPCOpCopyBackupInformation: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - CFDictionaryRef record = SOSCCCopyBackupInformation_Server(&error); - if (record) { - xpc_object_t xpc_dictionary = _CFXPCCreateXPCObjectFromCFObject(record); - xpc_dictionary_set_value(replyMessage, kSecXPCKeyResult, xpc_dictionary); - xpc_release(xpc_dictionary); - } - CFReleaseNull(record); - } - break; - - case kSecXPCOpIsThisDeviceLastBackup: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, SOSCCkSecXPCOpIsThisDeviceLastBackup_Server(&error)); - } - break; case kSecXPCOpPeersHaveViewsEnabled: { CFArrayRef viewSet = SecXPCDictionaryCopyArray(event, kSecXPCKeyArray, &error); @@ -1475,34 +1315,6 @@ static void securityd_xpc_dictionary_handler(const xpc_connection_t connection, } } break; - case kSecXPCOpWrapToBackupSliceKeyBagForView: - { - CFStringRef viewname = SecXPCDictionaryCopyString(event, kSecXPCKeyViewName, &error); - if(viewname) { - CFDataRef plaintext = SecXPCDictionaryCopyData(event, kSecXPCData, &error); - if (plaintext) { - CFDataRef ciphertext = NULL; - CFDataRef bskbEncoded = NULL; - - bool result = SOSWrapToBackupSliceKeyBagForView_Server(viewname, plaintext, &ciphertext, &bskbEncoded, &error); - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, result); - - if(!error && result) { - if(ciphertext) { - xpc_dictionary_set_data(replyMessage, kSecXPCData, CFDataGetBytePtr(ciphertext), CFDataGetLength(ciphertext)); - } - if(bskbEncoded) { - xpc_dictionary_set_data(replyMessage, kSecXPCKeyKeybag, CFDataGetBytePtr(bskbEncoded), CFDataGetLength(bskbEncoded)); - } - } - CFReleaseSafe(ciphertext); - CFReleaseSafe(bskbEncoded); - } - CFReleaseSafe(plaintext); - } - CFReleaseNull(viewname); - } - break; case kSecXPCOpCopyApplication: if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementCircleJoin, &error)) { SOSPeerInfoRef peerInfo = SOSCCCopyApplication_Server(&error); @@ -1565,12 +1377,7 @@ static void securityd_xpc_dictionary_handler(const xpc_connection_t connection, xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, retval); } break; - case kSecXPCOpPopulateKVS: - if (EntitlementPresentAndTrue(operation, client.task, kSecEntitlementKeychainCloudCircle, &error)) { - bool retval = SOSCCTestPopulateKVSWithBadKeys_Server(&error); - xpc_dictionary_set_bool(replyMessage, kSecXPCKeyResult, retval); - } - break; + case kSecXPCOpMessageFromPeerIsPending: { SOSPeerInfoRef peer = SecXPCDictionaryCopyPeerInfo(event, kSecXPCKeyPeerInfo, &error); diff --git a/OSX/shared_regressions/shared_regressions.h b/OSX/shared_regressions/shared_regressions.h index 534f8cd5..83786104 100644 --- a/OSX/shared_regressions/shared_regressions.h +++ b/OSX/shared_regressions/shared_regressions.h @@ -21,7 +21,6 @@ ONE_TEST(si_24_sectrust_digicert_malaysia) ONE_TEST(si_24_sectrust_passbook) ONE_TEST(si_25_cms_skid) ONE_TEST(si_26_sectrust_copyproperties) -ONE_TEST(si_27_sectrust_exceptions) ONE_TEST(si_28_sectrustsettings) ONE_TEST(si_29_cms_chain_mode) ONE_TEST(si_32_sectrust_pinning_required) diff --git a/OSX/shared_regressions/si-44-seckey-aks.m b/OSX/shared_regressions/si-44-seckey-aks.m index 048929d5..e276aeb7 100644 --- a/OSX/shared_regressions/si-44-seckey-aks.m +++ b/OSX/shared_regressions/si-44-seckey-aks.m @@ -410,6 +410,88 @@ static void rewrapTest(void) { ok([decrypted isEqualToData:message], "Decrypted data differs: %@ vs %@", decrypted, message); } +static void keychainTest(void) { + id accessControl = CFBridgingRelease(SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenUnlocked, kSecAccessControlPrivateKeyUsage, NULL)); + NSDictionary *keyAttributes = @{ (id)kSecAttrTokenID : (id)kSecAttrTokenIDAppleKeyStore, + (id)kSecAttrKeyType : (id)kSecAttrKeyTypeECSECPrimeRandom, + (id)kSecPrivateKeyAttrs : @{ + (id)kSecAttrAccessControl : accessControl, + (id)kSecAttrIsPermanent : @YES, + (id)kSecAttrLabel : @"si_44_seckey_aks_1", + }, + }; + NSError *error; + id key = (__bridge_transfer id)SecKeyCreateRandomKey((CFDictionaryRef)keyAttributes, (void *)&error); + ok(key, "failed to create random key %@", error); + id pubKey = CFBridgingRelease(SecKeyCopyPublicKey((SecKeyRef)key)); + ok(pubKey, "failed to get public key from SEP key"); + key = nil; + + NSDictionary *query = @{ + (id)kSecReturnRef: @YES, + (id)kSecClass: (id)kSecClassKey, + (id)kSecAttrLabel: @"si_44_seckey_aks_1", + }; + OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (void *)&key); + is(status, errSecSuccess, "getting SEP key from keychain failed"); + + NSError *err; + NSData *data = [@"message" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *sig = CFBridgingRelease(SecKeyCreateSignature((SecKeyRef)key, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (CFDataRef)data, (void *)&err)); + ok(sig, "failed to create signature: %@", err); + ok(SecKeyVerifySignature((SecKeyRef)pubKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (CFDataRef)data, (CFDataRef)sig, (void *)&err), "failed to verify signature: %@", err); + + status = SecItemDelete((CFDictionaryRef)query); + is(status, errSecSuccess, "deleting SEP key from keychain failed"); + + status = SecItemCopyMatching((CFDictionaryRef)query, (void *)&key); + is(status, errSecItemNotFound, "SEP key was not deleted from keychain"); +} + +static void secAccessControlDescriptionTest(void) { + NSError *error; + NSObject *ac = CFBridgingRelease(SecAccessControlCreate(kCFAllocatorDefault, (void *)&error)); + ok(ac, "failed to create ac: %@", error); + ok(SecAccessControlSetProtection((__bridge SecAccessControlRef)ac, kSecAttrAccessibleWhenUnlocked, (void *)&error), "failed to set protection: %@", error); + + NSString *desc = ac.description; + ok([desc isEqualToString:@""], "unexpected desc: %@", desc); + + SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{}); + desc = ac.description; + ok([desc isEqualToString:@""], "unexpected desc: %@", desc); + + SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": (__bridge id)kCFBooleanTrue}); + desc = ac.description; + ok([desc isEqualToString:@""], "unexpected desc: %@", desc); + + SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": (__bridge id)kCFBooleanTrue, @"oe": (__bridge id)kCFBooleanFalse}); + desc = ac.description; + ok([desc isEqualToString:@""], "unexpected desc: %@", desc); + + SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": @"huh"}); + desc = ac.description; + ok([desc isEqualToString:@""], "unexpected desc: %@", desc); + + SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": @2}); + desc = ac.description; + ok([desc isEqualToString:@""], "unexpected desc: %@", desc); + + NSData *shortData = [NSData dataWithBytes:"\x01\x02\x03" length:3]; + SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": shortData}); + desc = ac.description; + ok([desc isEqualToString:@""], "unexpected desc: %@", desc); + + NSData *longData = [NSMutableData dataWithLength:128]; + SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": longData}); + desc = ac.description; + ok([desc isEqualToString:@""], "unexpected desc: %@", desc); + + SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": @{@"kofn": @2}}); + desc = ac.description; + ok([desc isEqualToString:@""], "unexpected desc: %@", desc); +} + int si_44_seckey_aks(int argc, char *const *argv) { @autoreleasepool { BOOL testPKA = YES; @@ -428,12 +510,14 @@ int si_44_seckey_aks(int argc, char *const *argv) { testPKA = NO; #endif - plan_tests(testPKA ? 102 : 87); + plan_tests(testPKA ? 119 : 104); + secAccessControlDescriptionTest(); secKeySepTest(testPKA); attestationTest(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, NO); attestationTest(kSecAttrAccessibleUntilReboot, YES); keyFromBlobTest(); + keychainTest(); // Put SEP keys into test-keybag mode. Available only when running in direct-mode, not with extension. SecKeySetParameter(NULL, kSecAttrTokenIDAppleKeyStore, kCFBooleanTrue, NULL); diff --git a/OSX/utilities/SecCFWrappers.h b/OSX/utilities/SecCFWrappers.h index f1efbf3f..1cfb109b 100644 --- a/OSX/utilities/SecCFWrappers.h +++ b/OSX/utilities/SecCFWrappers.h @@ -223,6 +223,15 @@ static inline bool isNull(CFTypeRef cfType) { return cfType && CFGetTypeID(cfType) == CFNullGetTypeID(); } +// Usage: void foo(CFTypeRef value) { CFDataRef data = CFCast(CFData, value); } +#define CFCast(type, value) \ + ((value != NULL) && CFGetTypeID(value) == type ## GetTypeID() ? (type ## Ref)(value) : NULL) + +#define CFCastWithError(type, value, error) \ + ((value != NULL) && CFGetTypeID(value) == type ## GetTypeID() ? \ + (type ## Ref)(value) : \ + (SecError(errSecParam, error, CFSTR("Unexpected type")), NULL)) + // // MARK CFEqual Helpers // diff --git a/OSX/utilities/SecDb.c b/OSX/utilities/SecDb.c index 729520a4..f09ef95f 100644 --- a/OSX/utilities/SecDb.c +++ b/OSX/utilities/SecDb.c @@ -1241,6 +1241,26 @@ void SecDbReleaseAllConnections(SecDbRef db) { }); } +// Please make sure you want to do this. Any use of the outstanding connections to this DB will cause a crash. +void SecDbForceClose(SecDbRef db) { + dispatch_sync(db->queue, ^{ + CFArrayForEach(db->connections, ^(const void* p) { + SecDbConnectionRef connection = (SecDbConnectionRef)p; + + // this pointer is claimed to be nonretained + connection->db = NULL; + + if(connection->handle) { + sqlite3_close(connection->handle); + connection->handle = NULL; + } + }); + CFArrayRemoveAllValues(db->connections); + dispatch_semaphore_signal(db->write_semaphore); + dispatch_semaphore_signal(db->read_semaphore); + }); +} + bool SecDbPerformRead(SecDbRef db, CFErrorRef *error, void (^perform)(SecDbConnectionRef dbconn)) { SecDbConnectionRef dbconn = SecDbConnectionAcquire(db, true, error); bool success = false; diff --git a/OSX/utilities/SecDb.h b/OSX/utilities/SecDb.h index b3c90402..06ea04e4 100644 --- a/OSX/utilities/SecDb.h +++ b/OSX/utilities/SecDb.h @@ -125,6 +125,8 @@ bool SecDbPerformWrite(SecDbRef db, CFErrorRef *error, void (^perform)(SecDbConn CFIndex SecDbIdleConnectionCount(SecDbRef db); void SecDbReleaseAllConnections(SecDbRef db); +void SecDbForceClose(SecDbRef db); + CFStringRef SecDbGetPath(SecDbRef db); // MARK: - diff --git a/OSX/utilities/SecXPCHelper.h b/OSX/utilities/SecXPCHelper.h index 905eca6d..4b926795 100644 --- a/OSX/utilities/SecXPCHelper.h +++ b/OSX/utilities/SecXPCHelper.h @@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SecXPCHelper : NSObject + (NSSet *)safeErrorClasses; -+ (NSError *)cleanseErrorForXPC:(NSError * _Nullable)error; ++ (NSError * _Nullable)cleanseErrorForXPC:(NSError * _Nullable)error; /* * Some NSError objects contain non-NSSecureCoding-compliant userInfo. diff --git a/OSX/utilities/SecXPCHelper.m b/OSX/utilities/SecXPCHelper.m index d9d80e09..5980d182 100644 --- a/OSX/utilities/SecXPCHelper.m +++ b/OSX/utilities/SecXPCHelper.m @@ -143,7 +143,7 @@ return NSStringFromClass([object class]); } -+ (NSError *)cleanseErrorForXPC:(NSError * _Nullable)error ++ (NSError * _Nullable)cleanseErrorForXPC:(NSError * _Nullable)error { if (!error) { return nil; diff --git a/Security.exp-in b/Security.exp-in index a027eee7..a3efacd0 100644 --- a/Security.exp-in +++ b/Security.exp-in @@ -347,6 +347,7 @@ __OctagonSignpostLogSystem _OTCliqueStatusToString _OTCliqueStatusFromString +_OTCDPStatusToString _OTCliqueCDPContextTypeNone _OTCliqueCDPContextTypeSignIn @@ -2100,6 +2101,7 @@ _SFAnalyticsColumnHardFailureCount _SFAnalyticsColumnSoftFailureCount _SFAnalyticsColumnSampleValue _SFAnalyticsColumnSampleName +_SFAnalyticsPostTime _SFAnalyticsEventTime _SFAnalyticsEventType _SFAnalyticsEventTypeErrorEvent diff --git a/Security.xcodeproj/project.pbxproj b/Security.xcodeproj/project.pbxproj index fbfb182c..d469fbf5 100644 --- a/Security.xcodeproj/project.pbxproj +++ b/Security.xcodeproj/project.pbxproj @@ -56,6 +56,17 @@ name = Security_frameworks_ios; productName = kernel; }; + 0CA378E123876DD100090B7E /* reset_account */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 0CA378E323876DD100090B7E /* Build configuration list for PBXAggregateTarget "reset_account" */; + buildPhases = ( + 0CA378E623876DEC00090B7E /* CopyFiles */, + ); + dependencies = ( + ); + name = reset_account; + productName = codesigning_DTrace; + }; 4C541F840F250BF500E508AE /* Security_executables_ios */ = { isa = PBXAggregateTarget; buildConfigurationList = 4C541FA30F250C8C00E508AE /* Build configuration list for PBXAggregateTarget "Security_executables_ios" */; @@ -371,6 +382,7 @@ buildPhases = ( ); dependencies = ( + 0CA378EB23876E1000090B7E /* PBXTargetDependency */, D4E0E9BC2224E15500A802E0 /* PBXTargetDependency */, D45D8F5C2224D9F100D6C124 /* PBXTargetDependency */, D45D8F5A2224D8A100D6C124 /* PBXTargetDependency */, @@ -389,6 +401,7 @@ buildPhases = ( ); dependencies = ( + 0CA378E923876E0900090B7E /* PBXTargetDependency */, D4EB53C9223C4AB5009101F8 /* PBXTargetDependency */, D4E0E9BE2224E15E00A802E0 /* PBXTargetDependency */, D45D8F902224DC9900D6C124 /* PBXTargetDependency */, @@ -887,6 +900,7 @@ 0C9FB40720D872A600864612 /* CoreCDP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C9FB40120D8729A00864612 /* CoreCDP.framework */; }; 0C9FB40920D8735500864612 /* CoreCDP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C9FB40120D8729A00864612 /* CoreCDP.framework */; }; 0CA2282F2187A5CA00A1C56C /* BottledPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0C4F84216FB56B00C14C61 /* BottledPeer.swift */; }; + 0CA378E723876DFC00090B7E /* reset_ick_account in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0C7382F023863AD5004F98CB /* reset_ick_account */; }; 0CA4B4722171410200B17169 /* EscrowKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0C4F83216FB55600C14C61 /* EscrowKeys.swift */; }; 0CA4EBF3202B8D9C002B1D96 /* CloudKitKeychainSyncingTestsBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CA4EBF2202B8D1D002B1D96 /* CloudKitKeychainSyncingTestsBase.m */; }; 0CA4EBF4202B8DBE002B1D96 /* CloudKitKeychainSyncingTestsBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CA4EBF2202B8D1D002B1D96 /* CloudKitKeychainSyncingTestsBase.m */; }; @@ -899,7 +913,6 @@ 0CAD1E591E1C5CBD00537693 /* secd-52-offering-gencount-reset.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78C4F1D8085D800865A7C /* secd-52-offering-gencount-reset.m */; }; 0CAD1E5A1E1C5CD100537693 /* secd-71-engine-save.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78C641D8085D800865A7C /* secd-71-engine-save.m */; }; 0CAD1E5D1E1C5CF900537693 /* secd-80-views-alwayson.m in Sources */ = {isa = PBXBuildFile; fileRef = 7281E08B1DFD0A380021E1B7 /* secd-80-views-alwayson.m */; }; - 0CAD1E5E1E1C5D0600537693 /* secd-95-escrow-persistence.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78C741D8085D800865A7C /* secd-95-escrow-persistence.m */; }; 0CADDF0721545CF100DF8B06 /* OctagonPairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CADDF0421545C8E00DF8B06 /* OctagonPairingTests.swift */; }; 0CB50A0D20AA486800FE4675 /* SOSAccountTrustClassic+Expansion.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CE760471E12F2F200B4381E /* SOSAccountTrustClassic+Expansion.m */; }; 0CB50A0E20AA4C2F00FE4675 /* SOSAccountTrustClassic+Circle.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CE760491E12F30200B4381E /* SOSAccountTrustClassic+Circle.m */; }; @@ -915,7 +928,6 @@ 0CB5C678218B803C0044F730 /* OTPrivateKey+SF.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C3BB3522188E18A0018FC14 /* OTPrivateKey+SF.m */; }; 0CB72D9D21E42FCF00D8BC9B /* OTApplicantToSponsorRound2M1.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C9AE28E214054F6003BFDB5 /* OTApplicantToSponsorRound2M1.m */; }; 0CB72D9E21E42FCF00D8BC9B /* OTPairingMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C9AE2A2214055CF003BFDB5 /* OTPairingMessage.m */; }; - 0CB72D9F21E42FCF00D8BC9B /* OTSOSMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CE9C98A21B8891A006BDD80 /* OTSOSMessage.m */; }; 0CB72DA021E42FCF00D8BC9B /* OTSponsorToApplicantRound1M2.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C9AE28D214054F6003BFDB5 /* OTSponsorToApplicantRound1M2.m */; }; 0CB72DA121E42FCF00D8BC9B /* OTSponsorToApplicantRound2M2.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C9AE290214054F7003BFDB5 /* OTSponsorToApplicantRound2M2.m */; }; 0CB8DC9A2194B14C0021A7C8 /* OTVouchWithBottleOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CB8DC992194B1440021A7C8 /* OTVouchWithBottleOperation.m */; }; @@ -969,6 +981,7 @@ 0CE887D22299A8CF0082D120 /* libMobileGestalt.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BE53602C209BBF2F0027E25A /* libMobileGestalt.tbd */; }; 0CE887D32299A9090082D120 /* libutilities.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC0BCC361D8C684F00070CB0 /* libutilities.a */; }; 0CE887D52299A9C70082D120 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E71F3E3016EA69A900FAF9B4 /* SystemConfiguration.framework */; }; + 0CE902352395D0A3005E3F8C /* AuthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4A7DD7320A26CF900F51F3F /* AuthKit.framework */; }; 0CF406522072E422003D6A7F /* SFSignInAnalyticsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF405F42072E2BF003D6A7F /* SFSignInAnalyticsTests.m */; }; 0CF70BD9218BED1000EC3515 /* CuttlefishExtensionWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF70BD6218BECF500EC3515 /* CuttlefishExtensionWorkaround.swift */; }; 0CF70BDA218BEFAE00EC3515 /* CuttlefishExtensionWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF70BD6218BECF500EC3515 /* CuttlefishExtensionWorkaround.swift */; }; @@ -995,7 +1008,6 @@ 1B5EAADD2252ABCD008D27E7 /* OTFetchViewsOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B5EAADB2252ABCC008D27E7 /* OTFetchViewsOperation.m */; }; 1B8341B92239AD3A002BF18A /* TPPBPolicyKeyViewMapping.proto in Sources */ = {isa = PBXBuildFile; fileRef = 1B8341B72239AD39002BF18A /* TPPBPolicyKeyViewMapping.proto */; }; 1B8D2D96226E1FA500C94238 /* SetValueTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE15E2A222DF63500B7EAA5 /* SetValueTransformer.swift */; }; - 1B916CD0223FFF25006657FD /* ProtocolBuffer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C0B0C441E2537CC007F95E5 /* ProtocolBuffer.framework */; }; 1B995259226681FA00A2D6CD /* PolicyReporter.h in Sources */ = {isa = PBXBuildFile; fileRef = 1B995256226681ED00A2D6CD /* PolicyReporter.h */; }; 1B99525A226681FA00A2D6CD /* PolicyReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B995258226681EE00A2D6CD /* PolicyReporter.m */; }; 1BB1CAB7232C05BD001D0C71 /* CuttlefishXPCWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BB1CAB4232C05BB001D0C71 /* CuttlefishXPCWrapper.m */; }; @@ -1470,8 +1482,6 @@ 483E798F1DC87605005C0008 /* secd-67-prefixedKeyIDs.m in Sources */ = {isa = PBXBuildFile; fileRef = 483E79891DC875F2005C0008 /* secd-67-prefixedKeyIDs.m */; }; 48AC7B73232B1DA600F02B6F /* SOSIntervalEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 48AC7B71232B1A7000F02B6F /* SOSIntervalEvent.m */; }; 48CC589F1DA5FF2700EBD9DB /* secd-66-account-recovery.m in Sources */ = {isa = PBXBuildFile; fileRef = 48CC58971DA5FF0B00EBD9DB /* secd-66-account-recovery.m */; }; - 48E617211DBEC6BA0098EAAD /* SOSBackupInformation.m in Sources */ = {isa = PBXBuildFile; fileRef = 48E6171A1DBEC40D0098EAAD /* SOSBackupInformation.m */; }; - 48E617221DBEC6C60098EAAD /* SOSBackupInformation.h in Headers */ = {isa = PBXBuildFile; fileRef = 48E6171B1DBEC40D0098EAAD /* SOSBackupInformation.h */; }; 48FE669620E6E69D00FAEF17 /* SOSAuthKitHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 48FE668F20E6E69B00FAEF17 /* SOSAuthKitHelpers.m */; }; 48FE669720E6E69D00FAEF17 /* SOSAuthKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 48FE669520E6E69C00FAEF17 /* SOSAuthKitHelpers.h */; }; 4AF7000115AFB73800B9D400 /* SecOTRMath.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AF7FFF715AFB73800B9D400 /* SecOTRMath.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -1603,7 +1613,6 @@ 4C7391790B01745000C4CBFA /* vmdh.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C7391770B01745000C4CBFA /* vmdh.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4C7416040F1D71A2008E0E4D /* SecSCEP.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C7416020F1D71A2008E0E4D /* SecSCEP.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4C7608B30AC34A8100980096 /* SecCertificatePriv.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C7608B10AC34A8100980096 /* SecCertificatePriv.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 4C7913251799A5CC00A9633E /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C7913241799A5CB00A9633E /* MobileCoreServices.framework */; }; 4C7CE5700DC7DC6600AE53FC /* SecEntitlements.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C7CE56E0DC7DB0A00AE53FC /* SecEntitlements.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4C84DA551720698900AEE225 /* AppleAccount.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C84DA541720698900AEE225 /* AppleAccount.framework */; }; 4C87F3A80D611C26000E7104 /* SecTrustPriv.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C87F3A70D611C26000E7104 /* SecTrustPriv.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -1668,6 +1677,7 @@ 52A23EDD161DEC3F00E271E0 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 52A23EDB161DEC3700E271E0 /* Default-568h@2x.png */; }; 52D82BDF16A621F70078DFE5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7FCBE431314471B000DE34E /* Foundation.framework */; }; 52D82BEE16A622370078DFE5 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D82BD316A5EADA0078DFE5 /* Security.framework */; }; + 52DA3C7123C7E63600FEEDFF /* KCTLKRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 52DA3C6F23C7E63500FEEDFF /* KCTLKRequestTest.m */; }; 5328C0521738903F00708984 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C32C0AF0A4975F6002891BD /* Security.framework */; }; 533B5D4F177CD63100995334 /* SpringBoardServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52222CC0167BDAE100EDD09C /* SpringBoardServices.framework */; }; 5346480217331E1200FE9172 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7FCBE431314471B000DE34E /* Foundation.framework */; }; @@ -2437,6 +2447,10 @@ D4707A2D2114C1E8005BCFDA /* SecCmsContentInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = D4707A2B2114B31A005BCFDA /* SecCmsContentInfo.h */; settings = {ATTRIBUTES = (Private, ); }; }; D4707A2F2114C315005BCFDA /* SecCmsDigestContext.h in Headers */ = {isa = PBXBuildFile; fileRef = D4707A2E2114C30A005BCFDA /* SecCmsDigestContext.h */; settings = {ATTRIBUTES = (Private, ); }; }; D4707A302114C316005BCFDA /* SecCmsDigestContext.h in Headers */ = {isa = PBXBuildFile; fileRef = D4707A2E2114C30A005BCFDA /* SecCmsDigestContext.h */; settings = {ATTRIBUTES = (Private, ); }; }; + D477CB78237E482800C02355 /* si-88-sectrust-valid-data in Resources */ = {isa = PBXBuildFile; fileRef = D477CB76237E453C00C02355 /* si-88-sectrust-valid-data */; }; + D477CB79237E484300C02355 /* si-88-sectrust-valid-data in Resources */ = {isa = PBXBuildFile; fileRef = D477CB76237E453C00C02355 /* si-88-sectrust-valid-data */; }; + D477CB7B237E4BD700C02355 /* ExceptionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D477CB7A237E4BD700C02355 /* ExceptionTests.m */; }; + D477CB7C237E4BD700C02355 /* ExceptionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D477CB7A237E4BD700C02355 /* ExceptionTests.m */; }; D479F6E21F980FAB00388D28 /* Trust.strings in Resources */ = {isa = PBXBuildFile; fileRef = D479F6DF1F980F8F00388D28 /* Trust.strings */; }; D479F6E31F981FD600388D28 /* OID.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4C198F1F0ACDB4BF00AAB142 /* OID.strings */; }; D479F6E41F981FD600388D28 /* Certificate.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4C198F1D0ACDB4BF00AAB142 /* Certificate.strings */; }; @@ -2531,7 +2545,6 @@ D4B68C66211A8186009FED69 /* trustd_spi.h in Headers */ = {isa = PBXBuildFile; fileRef = D4B68C64211A8186009FED69 /* trustd_spi.h */; }; D4B68C68211A827C009FED69 /* trustd_spi.c in Sources */ = {isa = PBXBuildFile; fileRef = D4B68C65211A8186009FED69 /* trustd_spi.c */; }; D4B6D57C2069D8450099FBEF /* si-34-cms-timestamp.m in Sources */ = {isa = PBXBuildFile; fileRef = D4B6D57B2069D8450099FBEF /* si-34-cms-timestamp.m */; }; - D4B858671D370D9A003B2D95 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4B858661D370D9A003B2D95 /* MobileCoreServices.framework */; }; D4BD5E83228A6823001650A7 /* util.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BC6F79621C9955D005ED67A /* util.m */; }; D4BD5E85228A6854001650A7 /* util.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BC6F79621C9955D005ED67A /* util.m */; }; D4BD5E86228A6855001650A7 /* util.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BC6F79621C9955D005ED67A /* util.m */; }; @@ -2660,6 +2673,7 @@ DC00C92420B3B82600628BEB /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D4119E72202BDF2B0048587B /* libz.tbd */; }; DC00C93420B48B4100628BEB /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E71F3E3016EA69A900FAF9B4 /* SystemConfiguration.framework */; }; DC00C93520B48BA800628BEB /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CBCE5A90BE7F69100FF81F5 /* IOKit.framework */; }; + DC03592D235FCCD500F14883 /* KCInitialMessageData.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A47FFB5228F5E9000F781B8 /* KCInitialMessageData.m */; }; DC047081218BB21E0078BDAA /* OTCuttlefishAccountStateHolder.h in Headers */ = {isa = PBXBuildFile; fileRef = DC04707F218BB21E0078BDAA /* OTCuttlefishAccountStateHolder.h */; }; DC047082218BB21E0078BDAA /* OTCuttlefishAccountStateHolder.m in Sources */ = {isa = PBXBuildFile; fileRef = DC047080218BB21E0078BDAA /* OTCuttlefishAccountStateHolder.m */; }; DC047087218BCEF20078BDAA /* OTOperationDependencies.h in Headers */ = {isa = PBXBuildFile; fileRef = DC047085218BCEF20078BDAA /* OTOperationDependencies.h */; }; @@ -3166,6 +3180,11 @@ DC0BD4F621BB0610006B9154 /* CKKSKeychainBackedKey.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0BD4F021BB05F2006B9154 /* CKKSKeychainBackedKey.m */; }; DC0C343821FA7DEB00417D04 /* SecEscrowRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = DC90A4C021F27680001300EB /* SecEscrowRequest.h */; settings = {ATTRIBUTES = (Private, ); }; }; DC0C343A21FA7DEB00417D04 /* SecEscrowRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = DC90A4C021F27680001300EB /* SecEscrowRequest.h */; settings = {ATTRIBUTES = (Private, ); }; }; + DC0D16012363A1D6007F0951 /* OTSetCDPBitOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0D15FF2363A1D6007F0951 /* OTSetCDPBitOperation.h */; }; + DC0D16022363A1D6007F0951 /* OTSetCDPBitOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0D16002363A1D6007F0951 /* OTSetCDPBitOperation.m */; }; + DC0D16062363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0D16042363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.h */; }; + DC0D16072363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0D16052363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.m */; }; + DC0DE87123750340006E2EAE /* OTPairingMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C9AE2A2214055CF003BFDB5 /* OTPairingMessage.m */; }; DC0EF8F2208697C600AB9E95 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0EF8F1208697C600AB9E95 /* main.swift */; }; DC0FA6B02291F63F00FE01C4 /* OctagonPendingFlag.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA6AE2291F63F00FE01C4 /* OctagonPendingFlag.h */; }; DC0FA6B12291F63F00FE01C4 /* OctagonPendingFlag.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0FA6AF2291F63F00FE01C4 /* OctagonPendingFlag.m */; }; @@ -3177,6 +3196,7 @@ DC14478A1F5764C600236DB4 /* CKKSResultOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC1447881F5764C600236DB4 /* CKKSResultOperation.h */; }; DC14478C1F5764C600236DB4 /* CKKSResultOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC1447891F5764C600236DB4 /* CKKSResultOperation.m */; }; DC1447961F5766D200236DB4 /* NSOperationCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = DC1447941F5766D200236DB4 /* NSOperationCategories.h */; }; + DC14C4C223AAACED007F673F /* Container_BottledPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3A9B2523A9D6120073ED06 /* Container_BottledPeers.swift */; }; DC15F7661E67A6F6003B9A40 /* CKKSHealKeyHierarchyOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC15F7641E67A6F6003B9A40 /* CKKSHealKeyHierarchyOperation.h */; }; DC15F7681E67A6F6003B9A40 /* CKKSHealKeyHierarchyOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC15F7651E67A6F6003B9A40 /* CKKSHealKeyHierarchyOperation.m */; }; DC15F79C1E68EAD5003B9A40 /* CKKSTests+API.m in Sources */ = {isa = PBXBuildFile; fileRef = DC15F79B1E68EAD5003B9A40 /* CKKSTests+API.m */; }; @@ -3414,6 +3434,8 @@ DC311E7A2124B8EF002F5EAE /* aks_real_witness.h in Headers */ = {isa = PBXBuildFile; fileRef = DC311E782124B8EF002F5EAE /* aks_real_witness.h */; }; DC311E7B2124B8EF002F5EAE /* aks_real_witness.c in Sources */ = {isa = PBXBuildFile; fileRef = DC311E792124B8EF002F5EAE /* aks_real_witness.c */; }; DC337B1F1EA04E2100B3A1F0 /* SecBase64.h in Headers */ = {isa = PBXBuildFile; fileRef = 18351B8F14CB65870097860E /* SecBase64.h */; settings = {ATTRIBUTES = (Private, ); }; }; + DC33D7BD2374FD0500A68155 /* OTSponsorToApplicantRound1M2.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C9AE28D214054F6003BFDB5 /* OTSponsorToApplicantRound1M2.m */; }; + DC33D7BE2374FD0A00A68155 /* OTSponsorToApplicantRound2M2.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C9AE290214054F7003BFDB5 /* OTSponsorToApplicantRound2M2.m */; }; DC340C54208E828F004D7EEC /* TrustedPeers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BEF88C281EAFFC3F00357577 /* TrustedPeers.framework */; }; DC3502B81E0208BE00BC0587 /* CKKSTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3502B71E0208BE00BC0587 /* CKKSTests.m */; }; DC3502C51E020D5100BC0587 /* libASN1.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC8834081D8A218F00CE0ACA /* libASN1.a */; }; @@ -3461,6 +3483,8 @@ DC3A81D61D99D57F000C7419 /* libcoretls.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CFC029B1D41650700E6283B /* libcoretls.dylib */; }; DC3A81D71D99D58A000C7419 /* libcoretls_cfhelpers.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC3A81D41D99D567000C7419 /* libcoretls_cfhelpers.dylib */; }; DC3A81EC1D99F568000C7419 /* libcoretls.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CFC029B1D41650700E6283B /* libcoretls.dylib */; }; + DC3A9B2723A9D8BD0073ED06 /* Container_BottledPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3A9B2523A9D6120073ED06 /* Container_BottledPeers.swift */; }; + DC3A9B2823A9D8C40073ED06 /* Container_BottledPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3A9B2523A9D6120073ED06 /* Container_BottledPeers.swift */; }; DC3AA2782097DF70007CA68A /* readline.c in Sources */ = {isa = PBXBuildFile; fileRef = DC65E7BE1D8CBB1500152EF0 /* readline.c */; }; DC3AA2792097DF71007CA68A /* readline.c in Sources */ = {isa = PBXBuildFile; fileRef = DC65E7BE1D8CBB1500152EF0 /* readline.c */; }; DC3AA27A2097DF84007CA68A /* not_on_this_platorm.c in Sources */ = {isa = PBXBuildFile; fileRef = DC0BCDB41D8C6A5B00070CB0 /* not_on_this_platorm.c */; }; @@ -3533,8 +3557,10 @@ DC4269101E82FD9F002B7110 /* server_security_helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4269061E82FBDF002B7110 /* server_security_helpers.m */; }; DC4269111E82FDA0002B7110 /* server_security_helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4269061E82FBDF002B7110 /* server_security_helpers.m */; }; DC4269121E82FDA1002B7110 /* server_security_helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4269061E82FBDF002B7110 /* server_security_helpers.m */; }; + DC4415B423610BF40087981C /* OctagonTests+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4415B323610BF40087981C /* OctagonTests+Account.swift */; }; DC45D43D22EB619D00CEB6B7 /* OctagonStateMachineObservers.h in Headers */ = {isa = PBXBuildFile; fileRef = DC45D43B22EB619D00CEB6B7 /* OctagonStateMachineObservers.h */; }; DC45D43E22EB619D00CEB6B7 /* OctagonStateMachineObservers.m in Sources */ = {isa = PBXBuildFile; fileRef = DC45D43C22EB619D00CEB6B7 /* OctagonStateMachineObservers.m */; }; + DC4A73C5235E69D800DB1E6E /* OTApplicantToSponsorRound2M1.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C9AE28E214054F6003BFDB5 /* OTApplicantToSponsorRound2M1.m */; }; DC4A76A02212676D006F2D8F /* CloudServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C8A38C817B93DF10001B4C0 /* CloudServices.framework */; }; DC4A76A3221267D4006F2D8F /* EscrowRequestServerHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4A76A2221267D4006F2D8F /* EscrowRequestServerHelpers.m */; }; DC4A76A5221268A6006F2D8F /* CloudServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C8A38C817B93DF10001B4C0 /* CloudServices.framework */; }; @@ -3546,6 +3572,7 @@ DC4A76AC221269E4006F2D8F /* CloudServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C8A38C817B93DF10001B4C0 /* CloudServices.framework */; }; DC4A76AD22126A17006F2D8F /* CloudServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C8A38C817B93DF10001B4C0 /* CloudServices.framework */; }; DC4A76AE22126C49006F2D8F /* CloudServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C8A38C817B93DF10001B4C0 /* CloudServices.framework */; }; + DC4CD9842372294E00EF55FC /* OctagonTests+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4CD9822372294D00EF55FC /* OctagonTests+Helpers.swift */; }; DC4DB1501E24692100CD6769 /* CKKSKey.h in Headers */ = {isa = PBXBuildFile; fileRef = DC4DB14E1E24692100CD6769 /* CKKSKey.h */; }; DC4DB1521E24692100CD6769 /* CKKSKey.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4DB14F1E24692100CD6769 /* CKKSKey.m */; }; DC4DB15F1E2590B100CD6769 /* CKKSAESSIVEncryptionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4DB15E1E2590B100CD6769 /* CKKSAESSIVEncryptionTests.m */; }; @@ -3630,7 +3657,6 @@ DC52EC381D80CFDB00B0A59C /* secToolFileIO.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D931D8085F200865A7C /* secToolFileIO.c */; }; DC52EC391D80CFDF00B0A59C /* secViewDisplay.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D9E1D8085F200865A7C /* secViewDisplay.c */; }; DC52EC3A1D80CFE400B0A59C /* keychain_log.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D9B1D8085F200865A7C /* keychain_log.m */; }; - DC52EC3B1D80CFE900B0A59C /* syncbackup.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D9D1D8085F200865A7C /* syncbackup.m */; }; DC52EC4E1D80D01F00B0A59C /* swcagent_client.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78EA01D80860C00865A7C /* swcagent_client.c */; }; DC52EC4F1D80D02400B0A59C /* SecuritydXPC.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78E9A1D8085FC00865A7C /* SecuritydXPC.c */; }; DC52EC5D1D80D06300B0A59C /* SecLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78E651D8085FC00865A7C /* SecLogging.c */; }; @@ -3642,7 +3668,6 @@ DC52EC731D80D12E00B0A59C /* sc-20-keynames.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78CFD1D8085F200865A7C /* sc-20-keynames.m */; }; DC52EC741D80D13500B0A59C /* SOSTestDataSource.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D0C1D8085F200865A7C /* SOSTestDataSource.c */; }; DC52EC751D80D13B00B0A59C /* sc-42-circlegencount.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D021D8085F200865A7C /* sc-42-circlegencount.c */; }; - DC52EC761D80D13F00B0A59C /* sc-150-ring.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D061D8085F200865A7C /* sc-150-ring.m */; }; DC52EC771D80D14400B0A59C /* sc-130-resignationticket.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D041D8085F200865A7C /* sc-130-resignationticket.c */; }; DC52EC781D80D14800B0A59C /* SOSRegressionUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D0A1D8085F200865A7C /* SOSRegressionUtilities.m */; }; DC52EC791D80D14D00B0A59C /* sc-45-digestvector.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78D031D8085F200865A7C /* sc-45-digestvector.c */; }; @@ -3691,7 +3716,6 @@ DC52ECE81D80D2FA00B0A59C /* pbkdf2-00-hmac-sha1.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DA41D8085FC00865A7C /* pbkdf2-00-hmac-sha1.c */; }; DC52ECE91D80D2FA00B0A59C /* spbkdf-00-hmac-sha1.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DA51D8085FC00865A7C /* spbkdf-00-hmac-sha1.c */; }; DC52ECEA1D80D30900B0A59C /* so_01_serverencryption.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78E171D8085FC00865A7C /* so_01_serverencryption.c */; }; - DC52ED9E1D80D4ED00B0A59C /* secd-95-escrow-persistence.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78C741D8085D800865A7C /* secd-95-escrow-persistence.m */; }; DC52ED9F1D80D4F200B0A59C /* SOSTransportTestTransports.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78C7C1D8085D800865A7C /* SOSTransportTestTransports.m */; }; DC52EDA01D80D4F700B0A59C /* sd-10-policytree.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78C3D1D8085D800865A7C /* sd-10-policytree.m */; }; DC52EDB51D80D5C500B0A59C /* secd-03-corrupted-items.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78C391D8085D800865A7C /* secd-03-corrupted-items.m */; }; @@ -3757,7 +3781,6 @@ DC52EE4A1D80D71900B0A59C /* si-24-sectrust-itms.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DC31D8085FC00865A7C /* si-24-sectrust-itms.c */; }; DC52EE4C1D80D71900B0A59C /* si-24-sectrust-passbook.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DC51D8085FC00865A7C /* si-24-sectrust-passbook.c */; }; DC52EE4D1D80D71900B0A59C /* si-26-sectrust-copyproperties.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DC61D8085FC00865A7C /* si-26-sectrust-copyproperties.c */; }; - DC52EE4E1D80D71900B0A59C /* si-27-sectrust-exceptions.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DC71D8085FC00865A7C /* si-27-sectrust-exceptions.c */; }; DC52EE4F1D80D71900B0A59C /* si-28-sectrustsettings.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DC81D8085FC00865A7C /* si-28-sectrustsettings.m */; }; DC52EE531D80D73800B0A59C /* si-44-seckey-gen.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DD31D8085FC00865A7C /* si-44-seckey-gen.m */; }; DC52EE541D80D73800B0A59C /* si-44-seckey-rsa.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DD41D8085FC00865A7C /* si-44-seckey-rsa.m */; }; @@ -4120,6 +4143,7 @@ DC797E1A1DD3F9A400CC9E42 /* CKKSSQLDatabaseObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DC797E131DD3F88300CC9E42 /* CKKSSQLDatabaseObject.m */; }; DC7A17ED1E36ABC200EF14CE /* CKKSProcessReceivedKeysOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7A17EB1E36ABC200EF14CE /* CKKSProcessReceivedKeysOperation.h */; }; DC7A17EF1E36ABC200EF14CE /* CKKSProcessReceivedKeysOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC7A17EC1E36ABC200EF14CE /* CKKSProcessReceivedKeysOperation.m */; }; + DC7F6A7D233D7FAC00DF5769 /* OctagonTests+ForwardCompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC7F6A7C233D7FAC00DF5769 /* OctagonTests+ForwardCompatibility.swift */; }; DC7F79B622EA4ED4001FB69A /* OctagonTests+CKKS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC7F79B522EA4ED4001FB69A /* OctagonTests+CKKS.swift */; }; DC7F79BA22EA5C73001FB69A /* OTLocalCKKSResetOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7F79B822EA5C72001FB69A /* OTLocalCKKSResetOperation.h */; }; DC7F79BB22EA5C73001FB69A /* OTLocalCKKSResetOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC7F79B922EA5C72001FB69A /* OTLocalCKKSResetOperation.m */; }; @@ -4170,6 +4194,8 @@ DC8834931D8A21AB00CE0ACA /* oidsattr.c in Sources */ = {isa = PBXBuildFile; fileRef = DC88344B1D8A21AA00CE0ACA /* oidsattr.c */; }; DC8834961D8A21AB00CE0ACA /* oidsocsp.c in Sources */ = {isa = PBXBuildFile; fileRef = DC88344E1D8A21AA00CE0ACA /* oidsocsp.c */; }; DC88466B22373A5E00738068 /* TPPBDictionaryMatchingRule.proto in Sources */ = {isa = PBXBuildFile; fileRef = DC88466922373A4000738068 /* TPPBDictionaryMatchingRule.proto */; }; + DC89608D2395C75600D339D9 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC89608C2395C75500D339D9 /* CoreServices.framework */; }; + DC89608E2395C76300D339D9 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC89608C2395C75500D339D9 /* CoreServices.framework */; }; DC8D238D2064649400E163C8 /* CKKSAPSHandlingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DC8D238C2064649400E163C8 /* CKKSAPSHandlingTests.m */; }; DC8DF6DC212F8A7C007B3FE8 /* OTSOSAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB9475C2127562100ED9272 /* OTSOSAdapter.m */; }; DC8DF6DF212F8A7D007B3FE8 /* OTSOSAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB9475C2127562100ED9272 /* OTSOSAdapter.m */; }; @@ -4265,6 +4291,9 @@ DCA9D84821FFEE9800B27421 /* EscrowRequestXPCProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = DCA9D84521FFE7CF00B27421 /* EscrowRequestXPCProtocol.m */; }; DCA9D84D21FFF04700B27421 /* EscrowRequestServer.h in Headers */ = {isa = PBXBuildFile; fileRef = DCA9D84B21FFF04600B27421 /* EscrowRequestServer.h */; }; DCA9D84E21FFF04700B27421 /* EscrowRequestServer.m in Sources */ = {isa = PBXBuildFile; fileRef = DCA9D84C21FFF04700B27421 /* EscrowRequestServer.m */; }; + DCAA209A23AAF8F600DCB594 /* Container_RecoveryKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA209823AAF8BD00DCB594 /* Container_RecoveryKey.swift */; }; + DCAA209B23AAF8FD00DCB594 /* Container_RecoveryKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA209823AAF8BD00DCB594 /* Container_RecoveryKey.swift */; }; + DCAA209C23AAF93700DCB594 /* Container_RecoveryKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA209823AAF8BD00DCB594 /* Container_RecoveryKey.swift */; }; DCAB14271E40039600C81511 /* libASN1.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC8834081D8A218F00CE0ACA /* libASN1.a */; }; DCAB17CE21FFF75B00E1DFCF /* MockSynchronousEscrowServer.m in Sources */ = {isa = PBXBuildFile; fileRef = DCAB17CC21FFF6C400E1DFCF /* MockSynchronousEscrowServer.m */; }; DCAB17D12200D26900E1DFCF /* SecEscrowPendingRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = DCAB17CF2200D26700E1DFCF /* SecEscrowPendingRecord.h */; }; @@ -4709,7 +4738,6 @@ DCC093801D80B0B700F984E4 /* SecCFAllocator.h in Headers */ = {isa = PBXBuildFile; fileRef = D47F514B1C3B812500A7CEFE /* SecCFAllocator.h */; }; DCC19518214C53FD00C9E0B6 /* AuthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4A7DD7320A26CF900F51F3F /* AuthKit.framework */; }; DCC1951C214C668A00C9E0B6 /* AppleAccount.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C84DA541720698900AEE225 /* AppleAccount.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - DCC19F711EB9151B00B7D70F /* KeychainCKKS.plist in Copy BATS Test Discovery Plist */ = {isa = PBXBuildFile; fileRef = 6CB5F4781E402E5700DBF3F0 /* KeychainCKKS.plist */; }; DCC51C99209B7C1500A40387 /* print_cert.c in Sources */ = {isa = PBXBuildFile; fileRef = DC52EA951D80CC2A00B0A59C /* print_cert.c */; }; DCC54181225C05180095D926 /* OTUploadNewCKKSTLKsOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC5417F225C05170095D926 /* OTUploadNewCKKSTLKsOperation.h */; }; DCC54182225C05180095D926 /* OTUploadNewCKKSTLKsOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC54180225C05180095D926 /* OTUploadNewCKKSTLKsOperation.m */; }; @@ -5348,6 +5376,7 @@ DCE278DF1ED789EF0083B485 /* CKKSCurrentItemPointer.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE278DC1ED789EF0083B485 /* CKKSCurrentItemPointer.m */; }; DCE278E81ED7A5B40083B485 /* CKKSUpdateCurrentItemPointerOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE278E61ED7A5B40083B485 /* CKKSUpdateCurrentItemPointerOperation.h */; }; DCE278EA1ED7A5B40083B485 /* CKKSUpdateCurrentItemPointerOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE278E71ED7A5B40083B485 /* CKKSUpdateCurrentItemPointerOperation.m */; }; + DCE405C523A04A7F00C4343B /* OctagonTests+CKKSConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE405C423A04A7F00C4343B /* OctagonTests+CKKSConfiguration.swift */; }; DCE4E6921D7A37FA00AFB96E /* security_tool_commands.c in Sources */ = {isa = PBXBuildFile; fileRef = E7104A0B169E171900DB0045 /* security_tool_commands.c */; }; DCE4E6931D7A37FA00AFB96E /* NSFileHandle+Formatting.m in Sources */ = {isa = PBXBuildFile; fileRef = E78A9AD91D34959200006B5B /* NSFileHandle+Formatting.m */; }; DCE4E6961D7A37FA00AFB96E /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E43C48C1B00D07000E5ECB2 /* CoreFoundation.framework */; }; @@ -6049,7 +6078,6 @@ EB5E3BCC2003C67A00F1631B /* SecSignpost.h in Headers */ = {isa = PBXBuildFile; fileRef = EB5E3BC62003C66300F1631B /* SecSignpost.h */; settings = {ATTRIBUTES = (Private, ); }; }; EB5E3BCD2003C67B00F1631B /* SecSignpost.h in Headers */ = {isa = PBXBuildFile; fileRef = EB5E3BC62003C66300F1631B /* SecSignpost.h */; settings = {ATTRIBUTES = (Private, ); }; }; EB627A73233E339200F32437 /* MockAKSOptionalParameters.proto in Sources */ = {isa = PBXBuildFile; fileRef = EB627A6F233E323600F32437 /* MockAKSOptionalParameters.proto */; }; - EB627A79233E375A00F32437 /* MockAKSOptionalParameters.proto in Sources */ = {isa = PBXBuildFile; fileRef = EB627A6F233E323600F32437 /* MockAKSOptionalParameters.proto */; }; EB627A7E233E3C1300F32437 /* MockAKSOptionalParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = EB627A77233E342B00F32437 /* MockAKSOptionalParameters.m */; }; EB627A7F233E3C1600F32437 /* MockAKSRefKey.m in Sources */ = {isa = PBXBuildFile; fileRef = EB627A75233E342800F32437 /* MockAKSRefKey.m */; }; EB6667C7204CD69F000B404F /* testPlistDER.m in Sources */ = {isa = PBXBuildFile; fileRef = EB6667BE204CD65E000B404F /* testPlistDER.m */; }; @@ -6229,7 +6257,6 @@ EBD531772198AF19003A57E6 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CF4C19C171E0EA600877419 /* Accounts.framework */; }; EBDAA7E920EC4838003EA6E5 /* SecurityLocalKeychain.plist in Install BATS plist */ = {isa = PBXBuildFile; fileRef = EBDAA7E320EC46CF003EA6E5 /* SecurityLocalKeychain.plist */; }; EBDAF15D21C75FF200EAE89F /* NSXPCConnectionMock.h in Headers */ = {isa = PBXBuildFile; fileRef = EBDAF15B21C75FF200EAE89F /* NSXPCConnectionMock.h */; }; - EBDCC001233DD3E000806566 /* MockAKSRefKey.proto in Sources */ = {isa = PBXBuildFile; fileRef = EBDCBFFE233DD31700806566 /* MockAKSRefKey.proto */; }; EBDCC002233DD45700806566 /* MockAKSRefKey.proto in Sources */ = {isa = PBXBuildFile; fileRef = EBDCBFFE233DD31700806566 /* MockAKSRefKey.proto */; }; EBDE5E0E22BA3DE900A229C8 /* CKKSMockOctagonAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = EBDE5DFA22BA3D5D00A229C8 /* CKKSMockOctagonAdapter.m */; }; EBDE5E0F22BA3DEA00A229C8 /* CKKSMockOctagonAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = EBDE5DFA22BA3D5D00A229C8 /* CKKSMockOctagonAdapter.m */; }; @@ -6419,6 +6446,20 @@ remoteGlobalIDString = DC1789031D77980500B50D50; remoteInfo = Security_osx; }; + 0CA378E823876E0900090B7E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4C35DB69094F906D002917C4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0CA378E123876DD100090B7E; + remoteInfo = reset_account; + }; + 0CA378EA23876E1000090B7E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4C35DB69094F906D002917C4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0CA378E123876DD100090B7E; + remoteInfo = reset_account; + }; 0CC593F72299EDFC006C34B5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 4C35DB69094F906D002917C4 /* Project object */; @@ -10401,6 +10442,16 @@ name = "Embed OCMock"; runOnlyForDeploymentPostprocessing = 0; }; + 0CA378E623876DEC00090B7E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/local/bin; + dstSubfolderSpec = 0; + files = ( + 0CA378E723876DFC00090B7E /* reset_ick_account in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 1; + }; 0CF4064A2072E3E3003D6A7F /* Embed OCMock */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -11021,17 +11072,6 @@ ); runOnlyForDeploymentPostprocessing = 1; }; - DC7162D41EB4154D000D2BB5 /* Copy BATS Test Discovery Plist */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 8; - dstPath = /AppleInternal/CoreOS/BATS/unit_tests; - dstSubfolderSpec = 0; - files = ( - DCC19F711EB9151B00B7D70F /* KeychainCKKS.plist in Copy BATS Test Discovery Plist */, - ); - name = "Copy BATS Test Discovery Plist"; - runOnlyForDeploymentPostprocessing = 1; - }; DC7FC44F21EE9175003C39B8 /* Install Security FeatureFlags plist */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; @@ -11653,6 +11693,7 @@ 0C664AB2175926B20092D3D9 /* secdtests-entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "secdtests-entitlements.plist"; sourceTree = ""; }; 0C6C2B682258211800C53C96 /* AppleAccount.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppleAccount.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.Internal.sdk/System/Library/PrivateFrameworks/AppleAccount.framework; sourceTree = DEVELOPER_DIR; }; 0C6C2B6C2258295D00C53C96 /* UIKitCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKitCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.Internal.sdk/System/Library/PrivateFrameworks/UIKitCore.framework; sourceTree = DEVELOPER_DIR; }; + 0C7382F023863AD5004F98CB /* reset_ick_account */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = reset_ick_account; sourceTree = ""; }; 0C75AC642141F18D0073A2F9 /* KeychainCircle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainCircle.framework; path = System/Library/PrivateFrameworks/KeychainCircle.framework; sourceTree = SDKROOT; }; 0C78F1C916A5E13400654E08 /* sectask_regressions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sectask_regressions.h; sourceTree = ""; }; 0C78F1CA16A5E1BF00654E08 /* sectask-10-sectask.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sectask-10-sectask.c"; sourceTree = ""; }; @@ -11747,8 +11788,6 @@ 0CE760531E13155100B4381E /* SOSAccountTrustClassic+Circle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SOSAccountTrustClassic+Circle.h"; path = "SecureObjectSync/SOSAccountTrustClassic+Circle.h"; sourceTree = ""; }; 0CE760551E1316E900B4381E /* SOSAccountTrustClassic+Retirement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SOSAccountTrustClassic+Retirement.h"; path = "SecureObjectSync/SOSAccountTrustClassic+Retirement.h"; sourceTree = ""; }; 0CE98B5B1FA9360700CF1D54 /* libprequelite.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libprequelite.tbd; path = usr/lib/libprequelite.tbd; sourceTree = SDKROOT; }; - 0CE9C98921B88919006BDD80 /* OTSOSMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OTSOSMessage.h; sourceTree = ""; }; - 0CE9C98A21B8891A006BDD80 /* OTSOSMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OTSOSMessage.m; sourceTree = ""; }; 0CF0E2E31F8EE3B000BD18E4 /* SFSignInAnalytics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SFSignInAnalytics.m; sourceTree = ""; }; 0CF0E2E71F8EE40700BD18E4 /* SFSignInAnalytics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SFSignInAnalytics.h; sourceTree = ""; }; 0CF405F42072E2BF003D6A7F /* SFSignInAnalyticsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SFSignInAnalyticsTests.m; sourceTree = ""; }; @@ -11961,8 +12000,6 @@ 48C2F9321E4BCFC30093D70C /* accountCirclesViewsPrint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = accountCirclesViewsPrint.m; sourceTree = ""; }; 48C2F9331E4BCFC30093D70C /* accountCirclesViewsPrint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = accountCirclesViewsPrint.h; sourceTree = ""; }; 48CC58971DA5FF0B00EBD9DB /* secd-66-account-recovery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-66-account-recovery.m"; sourceTree = ""; }; - 48E6171A1DBEC40D0098EAAD /* SOSBackupInformation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOSBackupInformation.m; sourceTree = ""; }; - 48E6171B1DBEC40D0098EAAD /* SOSBackupInformation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOSBackupInformation.h; sourceTree = ""; }; 48FE668F20E6E69B00FAEF17 /* SOSAuthKitHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOSAuthKitHelpers.m; sourceTree = ""; }; 48FE669520E6E69C00FAEF17 /* SOSAuthKitHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOSAuthKitHelpers.h; sourceTree = ""; }; 4AF7FFF315AFB73800B9D400 /* SecOTR.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SecOTR.h; sourceTree = ""; }; @@ -12096,6 +12133,7 @@ 52AA92881E662A4A004301A6 /* SecBackupKeybagEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecBackupKeybagEntry.m; sourceTree = ""; }; 52D82BD316A5EADA0078DFE5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 52D82BDE16A621F70078DFE5 /* CloudKeychainProxy.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CloudKeychainProxy.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 52DA3C6F23C7E63500FEEDFF /* KCTLKRequestTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KCTLKRequestTest.m; path = Tests/KCTLKRequestTest.m; sourceTree = ""; }; 5346480117331E1200FE9172 /* KeychainSyncAccountNotification.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KeychainSyncAccountNotification.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 5346480517331E1200FE9172 /* KeychainSyncAccountNotification-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "KeychainSyncAccountNotification-Info.plist"; sourceTree = ""; }; 5346480717331E1200FE9172 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -12228,7 +12266,6 @@ 6CA837612210C5E7002770F1 /* kc-45-change-password.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = "kc-45-change-password.c"; path = "regressions/kc-45-change-password.c"; sourceTree = ""; }; 6CAA8D201F842FB3007B6E03 /* securityuploadd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = securityuploadd; sourceTree = BUILT_PRODUCTS_DIR; }; 6CB5F4751E4025AB00DBF3F0 /* CKKSCloudKitTestsInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = CKKSCloudKitTestsInfo.plist; sourceTree = ""; }; - 6CB5F4781E402E5700DBF3F0 /* KeychainCKKS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = KeychainCKKS.plist; path = testrunner/KeychainCKKS.plist; sourceTree = ""; }; 6CB5F4791E402E5700DBF3F0 /* KeychainEntitledTestRunner-Entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "KeychainEntitledTestRunner-Entitlements.plist"; sourceTree = ""; }; 6CB5F47A1E402E5700DBF3F0 /* KeychainEntitledTestRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KeychainEntitledTestRunner.m; sourceTree = ""; }; 6CB6CC022198D4BC0080AD6F /* SecDbBackupRecoverySet.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SecDbBackupRecoverySet.proto; sourceTree = ""; }; @@ -12646,6 +12683,9 @@ D4707A282113ECA0005BCFDA /* SecCmsMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SecCmsMessage.h; path = CMS/SecCmsMessage.h; sourceTree = ""; }; D4707A2B2114B31A005BCFDA /* SecCmsContentInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SecCmsContentInfo.h; path = CMS/SecCmsContentInfo.h; sourceTree = ""; }; D4707A2E2114C30A005BCFDA /* SecCmsDigestContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SecCmsDigestContext.h; path = CMS/SecCmsDigestContext.h; sourceTree = ""; }; + D477CB76237E453C00C02355 /* si-88-sectrust-valid-data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "si-88-sectrust-valid-data"; path = "SecurityTests/si-88-sectrust-valid-data"; sourceTree = ""; }; + D477CB7A237E4BD700C02355 /* ExceptionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ExceptionTests.m; path = tests/TrustTests/EvaluationTests/ExceptionTests.m; sourceTree = ""; }; + D477CB7D237F321400C02355 /* ExceptionTests_data.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ExceptionTests_data.h; path = tests/TrustTests/EvaluationTests/ExceptionTests_data.h; sourceTree = ""; }; D479F6E01F980F8F00388D28 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = en.lproj/Trust.strings; sourceTree = ""; }; D47AB2CA2356AD72005A3801 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = System/Library/Frameworks/Network.framework; sourceTree = SDKROOT; }; D47C56AB1DCA831C00E18518 /* lib_ios_x64.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = lib_ios_x64.xcconfig; path = xcconfig/lib_ios_x64.xcconfig; sourceTree = ""; }; @@ -13292,6 +13332,10 @@ DC0BCDB41D8C6A5B00070CB0 /* not_on_this_platorm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = not_on_this_platorm.c; sourceTree = ""; }; DC0BD4EF21BB05F2006B9154 /* CKKSKeychainBackedKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CKKSKeychainBackedKey.h; sourceTree = ""; }; DC0BD4F021BB05F2006B9154 /* CKKSKeychainBackedKey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSKeychainBackedKey.m; sourceTree = ""; }; + DC0D15FF2363A1D6007F0951 /* OTSetCDPBitOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTSetCDPBitOperation.h; sourceTree = ""; }; + DC0D16002363A1D6007F0951 /* OTSetCDPBitOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OTSetCDPBitOperation.m; sourceTree = ""; }; + DC0D16042363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTDetermineCDPBitStatusOperation.h; sourceTree = ""; }; + DC0D16052363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OTDetermineCDPBitStatusOperation.m; sourceTree = ""; }; DC0EF8EF208697C600AB9E95 /* tpctl */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = tpctl; sourceTree = BUILT_PRODUCTS_DIR; }; DC0EF8F1208697C600AB9E95 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; DC0FA6AE2291F63F00FE01C4 /* OctagonPendingFlag.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OctagonPendingFlag.h; sourceTree = ""; }; @@ -13540,6 +13584,7 @@ DC3A4B601D91EAC500E46D4A /* com.apple.CodeSigningHelper.sb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = com.apple.CodeSigningHelper.sb; sourceTree = ""; }; DC3A4B621D91EAC500E46D4A /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; usesTabs = 1; }; DC3A81D41D99D567000C7419 /* libcoretls_cfhelpers.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcoretls_cfhelpers.dylib; path = usr/lib/libcoretls_cfhelpers.dylib; sourceTree = SDKROOT; }; + DC3A9B2523A9D6120073ED06 /* Container_BottledPeers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container_BottledPeers.swift; sourceTree = ""; }; DC3AA27C2097DF94007CA68A /* security_tool_commands_table.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = security_tool_commands_table.h; sourceTree = ""; }; DC3AF52A2229E6C0006577E8 /* CKKSListenerCollection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CKKSListenerCollection.h; sourceTree = ""; }; DC3AF52B2229E6C0006577E8 /* CKKSListenerCollection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSListenerCollection.m; sourceTree = ""; }; @@ -13549,11 +13594,13 @@ DC4269031E82EDAC002B7110 /* SecItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecItem.m; sourceTree = ""; }; DC4269061E82FBDF002B7110 /* server_security_helpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = server_security_helpers.m; sourceTree = ""; }; DC4269071E82FBDF002B7110 /* server_security_helpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = server_security_helpers.h; sourceTree = ""; }; + DC4415B323610BF40087981C /* OctagonTests+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OctagonTests+Account.swift"; sourceTree = ""; }; DC45D43B22EB619D00CEB6B7 /* OctagonStateMachineObservers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OctagonStateMachineObservers.h; sourceTree = ""; }; DC45D43C22EB619D00CEB6B7 /* OctagonStateMachineObservers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OctagonStateMachineObservers.m; sourceTree = ""; }; DC4A76A2221267D4006F2D8F /* EscrowRequestServerHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EscrowRequestServerHelpers.m; sourceTree = ""; }; DC4A76A4221267FB006F2D8F /* EscrowRequestServerHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EscrowRequestServerHelpers.h; sourceTree = ""; }; DC4A76A92212698B006F2D8F /* CloudServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.Internal.sdk/System/Library/PrivateFrameworks/CloudServices.framework; sourceTree = DEVELOPER_DIR; }; + DC4CD9822372294D00EF55FC /* OctagonTests+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OctagonTests+Helpers.swift"; sourceTree = ""; }; DC4D49D81F857728007AF2B8 /* CKKSSerializedKey.proto */ = {isa = PBXFileReference; lastKnownFileType = text; path = CKKSSerializedKey.proto; sourceTree = ""; }; DC4DB14E1E24692100CD6769 /* CKKSKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKKSKey.h; sourceTree = ""; }; DC4DB14F1E24692100CD6769 /* CKKSKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKKSKey.m; sourceTree = ""; }; @@ -13865,6 +13912,7 @@ DC7EB920211E17E500516452 /* OTAccountMetadataClassC+KeychainSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OTAccountMetadataClassC+KeychainSupport.h"; sourceTree = ""; }; DC7EB921211E17E500516452 /* OTAccountMetadataClassC+KeychainSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OTAccountMetadataClassC+KeychainSupport.m"; sourceTree = ""; }; DC7EB928211E20DF00516452 /* OctagonDataPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OctagonDataPersistenceTests.swift; sourceTree = ""; }; + DC7F6A7C233D7FAC00DF5769 /* OctagonTests+ForwardCompatibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OctagonTests+ForwardCompatibility.swift"; sourceTree = ""; }; DC7F79B522EA4ED4001FB69A /* OctagonTests+CKKS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OctagonTests+CKKS.swift"; sourceTree = ""; }; DC7F79B822EA5C72001FB69A /* OTLocalCKKSResetOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTLocalCKKSResetOperation.h; sourceTree = ""; }; DC7F79B922EA5C72001FB69A /* OTLocalCKKSResetOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OTLocalCKKSResetOperation.m; sourceTree = ""; }; @@ -13936,6 +13984,7 @@ DC88466C2237407500738068 /* TPDictionaryMatchingRuleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TPDictionaryMatchingRuleTests.m; sourceTree = ""; }; DC88467F2237431400738068 /* TPDictionaryMatchingRules.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TPDictionaryMatchingRules.h; sourceTree = ""; }; DC8846802237431400738068 /* TPDictionaryMatchingRules.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TPDictionaryMatchingRules.m; sourceTree = ""; }; + DC89608C2395C75500D339D9 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; DC8D238C2064649400E163C8 /* CKKSAPSHandlingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSAPSHandlingTests.m; sourceTree = ""; }; DC8E04901D7F6780006D80EB /* lib_ios.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = lib_ios.xcconfig; path = xcconfig/lib_ios.xcconfig; sourceTree = ""; }; DC9061B822B02BA30071474D /* TPTypes.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TPTypes.m; sourceTree = ""; }; @@ -13983,6 +14032,7 @@ DCA9D84521FFE7CF00B27421 /* EscrowRequestXPCProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EscrowRequestXPCProtocol.m; sourceTree = ""; }; DCA9D84B21FFF04600B27421 /* EscrowRequestServer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EscrowRequestServer.h; sourceTree = ""; }; DCA9D84C21FFF04700B27421 /* EscrowRequestServer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EscrowRequestServer.m; sourceTree = ""; }; + DCAA209823AAF8BD00DCB594 /* Container_RecoveryKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container_RecoveryKey.swift; sourceTree = ""; }; DCAB17CB21FFF6C400E1DFCF /* MockSynchronousEscrowServer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockSynchronousEscrowServer.h; sourceTree = ""; }; DCAB17CC21FFF6C400E1DFCF /* MockSynchronousEscrowServer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockSynchronousEscrowServer.m; sourceTree = ""; }; DCAB17CF2200D26700E1DFCF /* SecEscrowPendingRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecEscrowPendingRecord.h; sourceTree = ""; }; @@ -14436,7 +14486,6 @@ DCC78C701D8085D800865A7C /* secd-83-item-match-valid-on-date.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-83-item-match-valid-on-date.m"; sourceTree = ""; }; DCC78C711D8085D800865A7C /* secd-83-item-match-trusted.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-83-item-match-trusted.m"; sourceTree = ""; }; DCC78C721D8085D800865A7C /* secd-83-item-match.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "secd-83-item-match.h"; sourceTree = ""; }; - DCC78C741D8085D800865A7C /* secd-95-escrow-persistence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-95-escrow-persistence.m"; sourceTree = ""; }; DCC78C751D8085D800865A7C /* secd-100-initialsync.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-100-initialsync.m"; sourceTree = ""; }; DCC78C761D8085D800865A7C /* secd-130-other-peer-views.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-130-other-peer-views.m"; sourceTree = ""; }; DCC78C771D8085D800865A7C /* secd-154-engine-backoff.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-154-engine-backoff.m"; sourceTree = ""; }; @@ -14486,7 +14535,6 @@ DCC78D021D8085F200865A7C /* sc-42-circlegencount.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-42-circlegencount.c"; sourceTree = ""; }; DCC78D031D8085F200865A7C /* sc-45-digestvector.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-45-digestvector.c"; sourceTree = ""; }; DCC78D041D8085F200865A7C /* sc-130-resignationticket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-130-resignationticket.c"; sourceTree = ""; }; - DCC78D061D8085F200865A7C /* sc-150-ring.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "sc-150-ring.m"; sourceTree = ""; }; DCC78D071D8085F200865A7C /* sc-150-backupkeyderivation.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-150-backupkeyderivation.c"; sourceTree = ""; }; DCC78D081D8085F200865A7C /* sc-153-backupslicekeybag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-153-backupslicekeybag.c"; sourceTree = ""; }; DCC78D091D8085F200865A7C /* SOSCircle_regressions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SOSCircle_regressions.h; sourceTree = ""; }; @@ -14617,8 +14665,6 @@ DCC78D991D8085F200865A7C /* keychain_sync_test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = keychain_sync_test.m; sourceTree = ""; }; DCC78D9A1D8085F200865A7C /* keychain_log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = keychain_log.h; sourceTree = ""; }; DCC78D9B1D8085F200865A7C /* keychain_log.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = keychain_log.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - DCC78D9C1D8085F200865A7C /* syncbackup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = syncbackup.h; sourceTree = ""; }; - DCC78D9D1D8085F200865A7C /* syncbackup.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = syncbackup.m; sourceTree = ""; }; DCC78D9E1D8085F200865A7C /* secViewDisplay.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = secViewDisplay.c; sourceTree = ""; }; DCC78D9F1D8085F200865A7C /* secViewDisplay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = secViewDisplay.h; sourceTree = ""; }; DCC78DA21D8085FC00865A7C /* Security_regressions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Security_regressions.h; path = Regressions/Security_regressions.h; sourceTree = ""; }; @@ -14650,7 +14696,6 @@ DCC78DC31D8085FC00865A7C /* si-24-sectrust-itms.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-24-sectrust-itms.c"; sourceTree = ""; }; DCC78DC51D8085FC00865A7C /* si-24-sectrust-passbook.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-24-sectrust-passbook.c"; sourceTree = ""; }; DCC78DC61D8085FC00865A7C /* si-26-sectrust-copyproperties.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-26-sectrust-copyproperties.c"; sourceTree = ""; }; - DCC78DC71D8085FC00865A7C /* si-27-sectrust-exceptions.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-27-sectrust-exceptions.c"; sourceTree = ""; }; DCC78DC81D8085FC00865A7C /* si-28-sectrustsettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "si-28-sectrustsettings.m"; sourceTree = ""; }; DCC78DC91D8085FC00865A7C /* si-28-sectrustsettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "si-28-sectrustsettings.h"; sourceTree = ""; }; DCC78DCA1D8085FC00865A7C /* si-30-keychain-upgrade.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-30-keychain-upgrade.c"; sourceTree = ""; }; @@ -15147,6 +15192,7 @@ DCE278DC1ED789EF0083B485 /* CKKSCurrentItemPointer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSCurrentItemPointer.m; sourceTree = ""; }; DCE278E61ED7A5B40083B485 /* CKKSUpdateCurrentItemPointerOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CKKSUpdateCurrentItemPointerOperation.h; sourceTree = ""; }; DCE278E71ED7A5B40083B485 /* CKKSUpdateCurrentItemPointerOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSUpdateCurrentItemPointerOperation.m; sourceTree = ""; }; + DCE405C423A04A7F00C4343B /* OctagonTests+CKKSConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OctagonTests+CKKSConfiguration.swift"; sourceTree = ""; }; DCE4E6A41D7A37FA00AFB96E /* security2 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = security2; sourceTree = BUILT_PRODUCTS_DIR; }; DCE4E6A71D7A38C000AFB96E /* security2.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = security2.1; sourceTree = ""; }; DCE4E6D41D7A41E400AFB96E /* bc-10-knife-on-bread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "bc-10-knife-on-bread.m"; path = "OSX/Breadcrumb/bc-10-knife-on-bread.m"; sourceTree = ""; }; @@ -15960,6 +16006,7 @@ buildActionMask = 2147483647; files = ( 0C84D83D1FCF449700B822E3 /* Security.framework in Frameworks */, + 0CE902352395D0A3005E3F8C /* AuthKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -16226,12 +16273,12 @@ 0C9FB40720D872A600864612 /* CoreCDP.framework in Frameworks */, 43DB54551BB1F8920083C3F1 /* ProtectedCloudStorage.framework in Frameworks */, 4C8A38C917B93DF10001B4C0 /* CloudServices.framework in Frameworks */, - 4C7913251799A5CC00A9633E /* MobileCoreServices.framework in Frameworks */, 4381603B1B4DCEFF00C54D58 /* AggregateDictionary.framework in Frameworks */, 4C3DD6BD179760280093F9D8 /* libMobileGestalt.dylib in Frameworks */, 533B5D4F177CD63100995334 /* SpringBoardServices.framework in Frameworks */, 7200D76F177B9999009BB396 /* ManagedConfiguration.framework in Frameworks */, 433E519E1B66D5F600482618 /* AppSupport.framework in Frameworks */, + DC89608D2395C75600D339D9 /* CoreServices.framework in Frameworks */, 4C84DA551720698900AEE225 /* AppleAccount.framework in Frameworks */, 4CF4C19D171E0EA600877419 /* Accounts.framework in Frameworks */, 438168C41B4ED43800C54D58 /* CoreFoundation.framework in Frameworks */, @@ -16631,12 +16678,12 @@ DC00AB9A1D821D8800513D74 /* libSWCAgent.a in Frameworks */, DCD22D981D8CCF78001C9B81 /* libutilities.a in Frameworks */, BE442BB318B7FDB800F24DAE /* Security.framework in Frameworks */, + DC89608E2395C76300D339D9 /* CoreServices.framework in Frameworks */, BE442BB418B7FDB800F24DAE /* SystemConfiguration.framework in Frameworks */, BE442BB618B7FDB800F24DAE /* CFNetwork.framework in Frameworks */, BE25C41618B83491003320E0 /* Foundation.framework in Frameworks */, BE442BB718B7FDB800F24DAE /* IOKit.framework in Frameworks */, 438168C61B4ED43F00C54D58 /* CoreFoundation.framework in Frameworks */, - D4B858671D370D9A003B2D95 /* MobileCoreServices.framework in Frameworks */, BE442BB818B7FDB800F24DAE /* libsqlite3.dylib in Frameworks */, BE442BB918B7FDB800F24DAE /* libbsm.dylib in Frameworks */, ); @@ -16709,7 +16756,6 @@ files = ( DC730E2922401F5E0051DD48 /* ProtocolBuffer.framework in Frameworks */, DC730E2522401E310051DD48 /* TrustedPeers.framework in Frameworks */, - 1B916CD0223FFF25006657FD /* ProtocolBuffer.framework in Frameworks */, BE72782C209D2C1400F0DA77 /* SecurityFoundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -18158,12 +18204,18 @@ isa = PBXGroup; children = ( 0C9FB40120D8729A00864612 /* CoreCDP.framework */, - DCD067E71D8CDF7E007602F1 /* SecCodeHostLib.h */, - DCD067E81D8CDF7E007602F1 /* SecCodeHostLib.c */, ); name = "Recovered References"; sourceTree = ""; }; + 0C7382E52386379E004F98CB /* ResetCloudKeychainAccount */ = { + isa = PBXGroup; + children = ( + 0C7382F023863AD5004F98CB /* reset_ick_account */, + ); + path = ResetCloudKeychainAccount; + sourceTree = ""; + }; 0C78F1C816A5E13400654E08 /* regressions */ = { isa = PBXGroup; children = ( @@ -19310,8 +19362,6 @@ 0C9AE28E214054F6003BFDB5 /* OTApplicantToSponsorRound2M1.m */, 0C9AE2A1214055CE003BFDB5 /* OTPairingMessage.h */, 0C9AE2A2214055CF003BFDB5 /* OTPairingMessage.m */, - 0CE9C98921B88919006BDD80 /* OTSOSMessage.h */, - 0CE9C98A21B8891A006BDD80 /* OTSOSMessage.m */, 0C9AE289214054F4003BFDB5 /* OTSponsorToApplicantRound1M2.h */, 0C9AE28D214054F6003BFDB5 /* OTSponsorToApplicantRound1M2.m */, 0C9AE28A214054F5003BFDB5 /* OTSponsorToApplicantRound2M2.h */, @@ -19395,6 +19445,8 @@ DC754C712228B57B00A39C8E /* TrustedPeersHelperProtocol.m */, BE55C77B2044D0C90045863D /* Client.swift */, BE9F8D0F206C099800B53D16 /* Container.swift */, + DC3A9B2523A9D6120073ED06 /* Container_BottledPeers.swift */, + DCAA209823AAF8BD00DCB594 /* Container_RecoveryKey.swift */, DCAD8F8422C43EAD007C3872 /* Container_MachineIDs.swift */, BE9F8D18206C4AD300B53D16 /* ContainerMap.swift */, BE9F8D11206C121400B53D16 /* Decrypter.swift */, @@ -19683,6 +19735,8 @@ D4AC5767214E195300A32C01 /* ECTests_data.h */, D458C4AE214E198E0043D982 /* EvaluationBasicTests_data.h */, D458C4AF214E198E0043D982 /* EvaluationBasicTests.m */, + D477CB7A237E4BD700C02355 /* ExceptionTests.m */, + D477CB7D237F321400C02355 /* ExceptionTests_data.h */, D458C4B5214E19AE0043D982 /* iAPTests.m */, D458C4AD214E198E0043D982 /* iAPTests_data.h */, D4AC5768214E195400A32C01 /* KeySizeTests.m */, @@ -19733,6 +19787,7 @@ D458C51B214E2CFF0043D982 /* si-20-sectrust-policies-data */, D4A0F8C1211E6A2F00443CA1 /* si-82-sectrust-ct-data */, D4056A1E22712D750026E24E /* si-87-sectrust-name-constraints */, + D477CB76237E453C00C02355 /* si-88-sectrust-valid-data */, D4056A1D22712D740026E24E /* ssl-policy-certs */, ); name = TestData; @@ -21840,6 +21895,7 @@ BEF88C451EAFFFED00357577 /* TrustedPeers */, BEAA002C202A832500E51F45 /* TrustedPeersHelper */, BED987D42099145300607A5F /* TrustedPeersHelperUnitTests */, + 0C7382E52386379E004F98CB /* ResetCloudKeychainAccount */, ); path = keychain; sourceTree = ""; @@ -21994,14 +22050,18 @@ DCC0A4C52152C4AB000AF654 /* Pairing */, DC27C3C820EADD8200F7839C /* OctagonTests-BridgingHeader.h */, DC85687C2284E7850088D3EF /* OctagonTestMocks.swift */, + DC4CD9822372294D00EF55FC /* OctagonTests+Helpers.swift */, DC27C3C020EAD9C300F7839C /* OctagonTests.swift */, + DC4415B323610BF40087981C /* OctagonTests+Account.swift */, DC7F79B522EA4ED4001FB69A /* OctagonTests+CKKS.swift */, + DCE405C423A04A7F00C4343B /* OctagonTests+CKKSConfiguration.swift */, + DC5BEACC2217509A001681F0 /* OctagonTests+CloudKitAccount.swift */, DC5F2BBD2310B941001ADA5D /* OctagonTests+CoreFollowUp.swift */, DC2819B822F8F6FE007829F5 /* OctagonTests+DeviceList.swift */, + DC7F6A7C233D7FAC00DF5769 /* OctagonTests+ForwardCompatibility.swift */, 0C4CDE6D22922E360050C499 /* OctagonTests+RecoveryKey.swift */, DC07090222936BCC002711B9 /* OctagonTests+ErrorHandling.swift */, DCDF03112284E34B008055BA /* OctagonTests+EscrowRecovery.swift */, - DC5BEACC2217509A001681F0 /* OctagonTests+CloudKitAccount.swift */, 0C5824A322860001009E8C15 /* OctagonTests+HealthCheck.swift */, DC72502D229600A800493D88 /* OctagonTests+Reset.swift */, DCB947592127534C00ED9272 /* OctagonTests+SOSUpgrade.swift */, @@ -22086,7 +22146,6 @@ DCA4D2121E5651950056214F /* Tests (Live CloudKit) */ = { isa = PBXGroup; children = ( - 6CB5F4781E402E5700DBF3F0 /* KeychainCKKS.plist */, 6CF4A0B51E45488B00ECD7B5 /* KeychainEntitledTestApp_mac */, 6CF4A0E11E4549F200ECD7B5 /* KeychainEntitledTestApp_ios */, 6CB5F4771E402D6D00DBF3F0 /* testrunner */, @@ -22702,6 +22761,10 @@ 0CC8A9012123AEF7005D7F6A /* OTJoinWithVoucherOperation.m */, 0C66046E2134985100BFBBB8 /* OTEstablishOperation.h */, 0C6604692134983900BFBBB8 /* OTEstablishOperation.m */, + DC0D15FF2363A1D6007F0951 /* OTSetCDPBitOperation.h */, + DC0D16002363A1D6007F0951 /* OTSetCDPBitOperation.m */, + DC0D16042363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.h */, + DC0D16052363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.m */, DCFF82722162876400D54B02 /* OTResetOperation.h */, DCFF82732162876400D54B02 /* OTResetOperation.m */, 0C00FC85217A972E00C8BF00 /* OTLocalCuttlefishReset.h */, @@ -22794,7 +22857,6 @@ DCC78C701D8085D800865A7C /* secd-83-item-match-valid-on-date.m */, DCC78C711D8085D800865A7C /* secd-83-item-match-trusted.m */, DCC78C721D8085D800865A7C /* secd-83-item-match.h */, - DCC78C741D8085D800865A7C /* secd-95-escrow-persistence.m */, DCC78C751D8085D800865A7C /* secd-100-initialsync.m */, DCC78C761D8085D800865A7C /* secd-130-other-peer-views.m */, DCC78C771D8085D800865A7C /* secd-154-engine-backoff.m */, @@ -22901,7 +22963,6 @@ DCC78D021D8085F200865A7C /* sc-42-circlegencount.c */, DCC78D031D8085F200865A7C /* sc-45-digestvector.c */, DCC78D041D8085F200865A7C /* sc-130-resignationticket.c */, - DCC78D061D8085F200865A7C /* sc-150-ring.m */, DCC78D071D8085F200865A7C /* sc-150-backupkeyderivation.c */, DCC78D081D8085F200865A7C /* sc-153-backupslicekeybag.c */, DCC78D091D8085F200865A7C /* SOSCircle_regressions.h */, @@ -22948,8 +23009,6 @@ DCC78D2A1D8085F200865A7C /* SOSBackupSliceKeyBag.h */, 48776C731DA5BB4200CC09B9 /* SOSRecoveryKeyBag.m */, 48776C741DA5BB4200CC09B9 /* SOSRecoveryKeyBag.h */, - 48E6171A1DBEC40D0098EAAD /* SOSBackupInformation.m */, - 48E6171B1DBEC40D0098EAAD /* SOSBackupInformation.h */, DCC78D2B1D8085F200865A7C /* SOSUserKeygen.m */, DCC78D2C1D8085F200865A7C /* SOSUserKeygen.h */, 485B64081DC16E8300B771B9 /* SOSKeyedPubKeyIdentifier.c */, @@ -23131,8 +23190,6 @@ DCC78D9B1D8085F200865A7C /* keychain_log.m */, 0C0CEC9D1DA45EA200C22FBC /* recovery_key.h */, 0C0CEC9E1DA45EA200C22FBC /* recovery_key.m */, - DCC78D9D1D8085F200865A7C /* syncbackup.m */, - DCC78D9C1D8085F200865A7C /* syncbackup.h */, DCC78D9E1D8085F200865A7C /* secViewDisplay.c */, DCC78D9F1D8085F200865A7C /* secViewDisplay.h */, 48C2F9321E4BCFC30093D70C /* accountCirclesViewsPrint.m */, @@ -23240,7 +23297,6 @@ DC0B62261D90973900D43BCB /* si-25-cms-skid.h */, DC0B62271D90973900D43BCB /* si-25-cms-skid.m */, DCC78DC61D8085FC00865A7C /* si-26-sectrust-copyproperties.c */, - DCC78DC71D8085FC00865A7C /* si-27-sectrust-exceptions.c */, DCC78DC81D8085FC00865A7C /* si-28-sectrustsettings.m */, DCC78DC91D8085FC00865A7C /* si-28-sectrustsettings.h */, D4AA0D9922FB959600D77FA4 /* si-29-cms-chain-mode.m */, @@ -25128,6 +25184,7 @@ E7D848031C6BEFAB0025BB44 /* Tests */ = { isa = PBXGroup; children = ( + 52DA3C6F23C7E63500FEEDFF /* KCTLKRequestTest.m */, E7CFF7221C8660A000E3484E /* KeychainCircle.plist */, E7D848061C6BEFFA0025BB44 /* Info.plist */, E7D848041C6BEFC10025BB44 /* KCSRPTests.m */, @@ -25145,6 +25202,7 @@ E7FCBE401314471B000DE34E /* Frameworks */ = { isa = PBXGroup; children = ( + DC89608C2395C75500D339D9 /* CoreServices.framework */, BEC6A9142331992800080069 /* Network.framework */, D47AB2CA2356AD72005A3801 /* Network.framework */, 0C6C2B6C2258295D00C53C96 /* UIKitCore.framework */, @@ -26674,6 +26732,7 @@ DC9082C61EA027DB00D0C1C5 /* CKKSZoneChangeFetcher.h in Headers */, DC614C5122A9BDB500E16ADA /* CKKSZoneModifier.h in Headers */, DCA4D2151E5684220056214F /* CKKSReencryptOutgoingItemsOperation.h in Headers */, + DC0D16012363A1D6007F0951 /* OTSetCDPBitOperation.h in Headers */, 0C38AA96212B2D1E00C90A1D /* OTClientVoucherOperation.h in Headers */, DC378B3C1DF0CA7200A3DAFA /* CKKSIncomingQueueEntry.h in Headers */, DC5F65AE2225C22C0051E9FA /* CKKSProvideKeySetOperation.h in Headers */, @@ -26682,6 +26741,7 @@ DC52E7E61D80BE7B00B0A59C /* SecItemDb.h in Headers */, DCF12673218A757A000124C6 /* OTLeaveCliqueOperation.h in Headers */, DCAD9B441F8D939C00C5E2AE /* CKKSFixups.h in Headers */, + DC0D16062363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.h in Headers */, DC9C95B41F79CFD1000D19E5 /* CKKSControl.h in Headers */, DC52E7EA1D80BE9500B0A59C /* SecItemSchema.h in Headers */, DC7F79BA22EA5C73001FB69A /* OTLocalCKKSResetOperation.h in Headers */, @@ -26747,7 +26807,6 @@ DC52E9161D80C41A00B0A59C /* SOSPeerInfoInternal.h in Headers */, DC3C735A1D837C0000F6A832 /* SOSPeerInfoPriv.h in Headers */, DC52E91A1D80C43500B0A59C /* SOSRing.h in Headers */, - 48E617221DBEC6C60098EAAD /* SOSBackupInformation.h in Headers */, CD198F971DE27B9E00F6FB83 /* SOSAccountPriv.h in Headers */, DC52E9231D80C47100B0A59C /* SOSTransportCircleKVS.h in Headers */, DC52E92C1D80C4AF00B0A59C /* SOSTransportKeyParameter.h in Headers */, @@ -29632,7 +29691,6 @@ DC3502B11E0208BE00BC0587 /* Sources */, DC3502B21E0208BE00BC0587 /* Frameworks */, DC9A2C791EB40A64008FAC27 /* Embed OCMock */, - DC7162D41EB4154D000D2BB5 /* Copy BATS Test Discovery Plist */, DC7162D61EB4157D000D2BB5 /* ShellScript */, ); buildRules = ( @@ -31199,7 +31257,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1000; - LastUpgradeCheck = 1000; + LastUpgradeCheck = 1120; TargetAttributes = { 4381690B1B4EDCBD00C54D58 = { CreatedOnToolsVersion = 7.0; @@ -31843,6 +31901,7 @@ EB7E90F12193F90700B1FA21 /* Build C2 Metrics */, 3D58392D21890FFB000ACA44 /* SecExperimentTests */, 5A442F81233C330F00918373 /* experimentTool */, + 0CA378E123876DD100090B7E /* reset_account */, ); }; /* End PBXProject section */ @@ -32019,6 +32078,7 @@ D458C51A214E2CC80043D982 /* si-20-sectrust-policies-data in Resources */, D453A4BC2122236D00850A26 /* si-82-sectrust-ct-data in Resources */, D4FD4226217D7C41002B7EE2 /* si-87-sectrust-name-constraints in Resources */, + D477CB79237E484300C02355 /* si-88-sectrust-valid-data in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -32043,6 +32103,7 @@ D458C517214E2C690043D982 /* si-20-sectrust-policies-data in Resources */, D4A0F8C2211E6A2F00443CA1 /* si-82-sectrust-ct-data in Resources */, D4FD4227217D7C4F002B7EE2 /* si-87-sectrust-name-constraints in Resources */, + D477CB78237E482800C02355 /* si-88-sectrust-valid-data in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -32628,7 +32689,7 @@ ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "[ \"$(whoami)\" == \"root\" ] || exit 0\nchown -f root:wheel ${DSTROOT}/AppleInternal/CoreOS/BATS/unit_tests/*.plist\n"; + shellScript = "CKKSTESTS_DIR=${SRCROOT}/keychain/ckks/tests\npython ${CKKSTESTS_DIR}/gen_test_plist.py ${CKKSTESTS_DIR} ${DSTROOT}/AppleInternal/CoreOS/BATS/unit_tests/KeychainCKKS.plist\n\n[ \"$(whoami)\" == \"root\" ] || exit 0\nchown -f root:wheel ${DSTROOT}/AppleInternal/CoreOS/BATS/unit_tests/*.plist\n"; }; DC82FFE61D90D3F60085674B /* security_utilities DTrace */ = { isa = PBXShellScriptBuildPhase; @@ -33692,9 +33753,11 @@ 0CB582D1218920090040C5F2 /* OTAuthenticatedCiphertext.m in Sources */, 0CF70BD9218BED1000EC3515 /* CuttlefishExtensionWorkaround.swift in Sources */, DC754C742228B59000A39C8E /* TrustedPeersHelperProtocol.m in Sources */, + DC3A9B2823A9D8C40073ED06 /* Container_BottledPeers.swift in Sources */, DC391FA621C04D1500772585 /* OctagonPeerKeys.swift in Sources */, DCF6320521C074F30030CCC0 /* CuttlefishAPIHelpers.swift in Sources */, DC391F8C21BF222B00772585 /* CKKSTLKShare.m in Sources */, + DCAA209C23AAF93700DCB594 /* Container_RecoveryKey.swift in Sources */, BE9F8D19206C4AD300B53D16 /* ContainerMap.swift in Sources */, BE9F4F8C2072D881004A52C2 /* Cuttlefish.pb.swift in Sources */, DC391F9D21BF2F8100772585 /* CKKSConstants.m in Sources */, @@ -33768,6 +33831,7 @@ BED987E120991B9B00607A5F /* Decrypter.swift in Sources */, DC391FA721C04D6800772585 /* OctagonPeerKeys.swift in Sources */, BE536033209BC3B30027E25A /* server_entitlement_helpers.c in Sources */, + DCAA209B23AAF8FD00DCB594 /* Container_RecoveryKey.swift in Sources */, BED987E020991B1100607A5F /* TrustedPeersHelper.xcdatamodeld in Sources */, BED987D62099145300607A5F /* TrustedPeersHelperUnitTests.swift in Sources */, BE4C6AB820CAF4F700EAD6BE /* ContainerSync.swift in Sources */, @@ -33781,6 +33845,7 @@ DCAD8F8922C43EDB007C3872 /* Container_MachineIDs.swift in Sources */, 0CF70BDA218BEFAE00EC3515 /* CuttlefishExtensionWorkaround.swift in Sources */, DCD24DEC228C9A480052604C /* SetValueTransformer.swift in Sources */, + DC14C4C223AAACED007F673F /* Container_BottledPeers.swift in Sources */, BEC0A96520B362EC00DBD772 /* Utils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -33949,6 +34014,7 @@ D4FD4221217D7B2E002B7EE2 /* PathScoringTests.m in Sources */, D40881F42175738C00180E81 /* SecPolicyLeafCallbacks.c in Sources */, D40881F32175733F00180E81 /* SecPolicy.c in Sources */, + D477CB7C237E4BD700C02355 /* ExceptionTests.m in Sources */, D40881F22175733500180E81 /* SecSignatureVerificationSupport.c in Sources */, D40881F12175732E00180E81 /* SecCertificate.c in Sources */, D40881F02175732600180E81 /* SecTrustStore.c in Sources */, @@ -34026,6 +34092,7 @@ D458C525214E33440043D982 /* VerifyDateTests.m in Sources */, D49A370623873BD30065719F /* TrustDaemonTestCase.m in Sources */, D458C515214E286C0043D982 /* PolicyTests.m in Sources */, + D477CB7B237E4BD700C02355 /* ExceptionTests.m in Sources */, D4EF32182156DDEB000A31A5 /* TrustSettingsInterfaceTests.m in Sources */, D44282FF22D68564001746B3 /* TrustEvaluationTestHelpers.m in Sources */, ); @@ -34685,6 +34752,7 @@ DCDCCB901DF7B8D4006E840E /* CKKSItem.m in Sources */, DC1ED8C11DD5197E002BDCFA /* CKKSItemEncrypter.m in Sources */, 0CDD6F79226E83F6009094C2 /* OTTriggerEscrowUpdateOperation.m in Sources */, + DC0D16072363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.m in Sources */, DC6D2C921DD2835A00BE372D /* CKKSOutgoingQueueEntry.m in Sources */, DC378B3D1DF0CA7200A3DAFA /* CKKSIncomingQueueEntry.m in Sources */, 6C880FCB21C3351400D38D66 /* SecDbBackupMetadataClassKey.m in Sources */, @@ -34711,6 +34779,7 @@ DCB41DFC216D5E5B00F219E0 /* OTAccountMetadataClassC+KeychainSupport.m in Sources */, 6C880FCC21C3351400D38D66 /* SecDbBackupRecoverySet.m in Sources */, DCA4D1FF1E552DD50056214F /* CKKSCurrentKeyPointer.m in Sources */, + DC0D16022363A1D6007F0951 /* OTSetCDPBitOperation.m in Sources */, EB7E91212194849900B1FA21 /* SECC2MPMetric.m in Sources */, DA6AA1651FE88AFB004565B0 /* CKKSControlServer.m in Sources */, DCFE1C531F1825F7007640C8 /* CKKSUpdateDeviceStateOperation.m in Sources */, @@ -34844,7 +34913,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 48E617211DBEC6BA0098EAAD /* SOSBackupInformation.m in Sources */, DC52E8F11D80C34000B0A59C /* SOSAccount.m in Sources */, DC52E8F31D80C34000B0A59C /* SOSAccountBackup.m in Sources */, DCB332591F478C3C00178C30 /* SOSUserKeygen.m in Sources */, @@ -34912,7 +34980,6 @@ files = ( DCD7EE841F4E46F9007D9804 /* accountCirclesViewsPrint.m in Sources */, 0C0CECA41DA45ED700C22FBC /* recovery_key.m in Sources */, - DC52EC3B1D80CFE900B0A59C /* syncbackup.m in Sources */, DC52EC3A1D80CFE400B0A59C /* keychain_log.m in Sources */, DC52EC391D80CFDF00B0A59C /* secViewDisplay.c in Sources */, DC52EC381D80CFDB00B0A59C /* secToolFileIO.c in Sources */, @@ -34947,7 +35014,6 @@ DC52EC791D80D14D00B0A59C /* sc-45-digestvector.c in Sources */, DC52EC781D80D14800B0A59C /* SOSRegressionUtilities.m in Sources */, DC52EC771D80D14400B0A59C /* sc-130-resignationticket.c in Sources */, - DC52EC761D80D13F00B0A59C /* sc-150-ring.m in Sources */, DC52EC751D80D13B00B0A59C /* sc-42-circlegencount.c in Sources */, DC52EC741D80D13500B0A59C /* SOSTestDataSource.c in Sources */, DC52EC731D80D12E00B0A59C /* sc-20-keynames.m in Sources */, @@ -35016,7 +35082,6 @@ files = ( DC52EDA01D80D4F700B0A59C /* sd-10-policytree.m in Sources */, DC52ED9F1D80D4F200B0A59C /* SOSTransportTestTransports.m in Sources */, - DC52ED9E1D80D4ED00B0A59C /* secd-95-escrow-persistence.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -35038,7 +35103,6 @@ DC52EDF51D80D62E00B0A59C /* SecdTestKeychainUtilities.c in Sources */, DC52EDF61D80D62E00B0A59C /* SOSTransportTestTransports.m in Sources */, EB9C02481E8A15B40040D3C6 /* secd-37-pairing-initial-sync.m in Sources */, - 0CAD1E5E1E1C5D0600537693 /* secd-95-escrow-persistence.m in Sources */, DC52EDB51D80D5C500B0A59C /* secd-03-corrupted-items.m in Sources */, 0CAD1E5D1E1C5CF900537693 /* secd-80-views-alwayson.m in Sources */, DC52EDB61D80D5C500B0A59C /* secd-04-corrupted-items.m in Sources */, @@ -35133,7 +35197,6 @@ DC52EE4C1D80D71900B0A59C /* si-24-sectrust-passbook.c in Sources */, DC52EE4D1D80D71900B0A59C /* si-26-sectrust-copyproperties.c in Sources */, 5E7793751E5F025A0074A2D1 /* si-44-seckey-aks.m in Sources */, - DC52EE4E1D80D71900B0A59C /* si-27-sectrust-exceptions.c in Sources */, DC52EE4F1D80D71900B0A59C /* si-28-sectrustsettings.m in Sources */, DC52EE531D80D73800B0A59C /* si-44-seckey-gen.m in Sources */, DC52EE541D80D73800B0A59C /* si-44-seckey-rsa.m in Sources */, @@ -35382,6 +35445,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DCE405C523A04A7F00C4343B /* OctagonTests+CKKSConfiguration.swift in Sources */, 1B8D2D96226E1FA500C94238 /* SetValueTransformer.swift in Sources */, DC5F2BBE2310B941001ADA5D /* OctagonTests+CoreFollowUp.swift in Sources */, DCB468E520EC262C00BA7E5B /* ContainerMap.swift in Sources */, @@ -35396,6 +35460,8 @@ DC27C3C920EADEE700F7839C /* MockCloudKit.m in Sources */, 0CF70BE0218CF26600EC3515 /* BottledPeer.swift in Sources */, DC99B86B20EACA470065B73B /* spi.c in Sources */, + DC4CD9842372294E00EF55FC /* OctagonTests+Helpers.swift in Sources */, + DCAA209A23AAF8F600DCB594 /* Container_RecoveryKey.swift in Sources */, DCDF03122284E34B008055BA /* OctagonTests+EscrowRecovery.swift in Sources */, DCFF82712162834D00D54B02 /* OctagonTestsXPCConnections.swift in Sources */, DC5BEACD2217509A001681F0 /* OctagonTests+CloudKitAccount.swift in Sources */, @@ -35405,6 +35471,7 @@ 0CBEF3432242CA0600015691 /* TestsObjcTranslation.m in Sources */, 5A04BB0222982733001848A0 /* OTFollowupTests.m in Sources */, DCB24B45221B901700BE73FE /* CKKSMockSOSPresentAdapter.m in Sources */, + DC33D7BE2374FD0A00A68155 /* OTSponsorToApplicantRound2M2.m in Sources */, 0CE15E31222DF63600B7EAA4 /* RecoverKeySet.swift in Sources */, 0CADDF0721545CF100DF8B06 /* OctagonPairingTests.swift in Sources */, DC99B86D20EACA470065B73B /* SecdWatchdog.m in Sources */, @@ -35417,15 +35484,19 @@ DCB0C291222F5E130083AECB /* CuttlefishErrors.swift in Sources */, DC99B86F20EACA470065B73B /* SecFramework.c in Sources */, 0C5258BB21BB128000B32C96 /* FakeSOSControl.m in Sources */, + DC3A9B2723A9D8BD0073ED06 /* Container_BottledPeers.swift in Sources */, DC99B87020EACA470065B73B /* server_endpoint.m in Sources */, 0CD5797A21498F8200C43496 /* OctagonPairingTests+Piggybacking.swift in Sources */, + DC4A73C5235E69D800DB1E6E /* OTApplicantToSponsorRound2M1.m in Sources */, DCAD8F8722C43ECA007C3872 /* Container_MachineIDs.swift in Sources */, + DC03592D235FCCD500F14883 /* KCInitialMessageData.m in Sources */, DC99B87120EACA470065B73B /* server_security_helpers.m in Sources */, 0CF70BE1218CF27600EC3515 /* EscrowKeys.swift in Sources */, DC7F79B622EA4ED4001FB69A /* OctagonTests+CKKS.swift in Sources */, 0CBA047D214C4E4D005B3A2F /* OctagonPairingTests+ProxMultiClients.swift in Sources */, DCF6320821C09DCE0030CCC0 /* CuttlefishAPIHelpers.swift in Sources */, 0CE15E44222DF6A800B7EAA4 /* Recovery.m in Sources */, + DC4415B423610BF40087981C /* OctagonTests+Account.swift in Sources */, DC725030229600C000493D88 /* OctagonTests+Reset.swift in Sources */, 0C3E316B21372FA50093C04B /* OctagonPairingTests+ProximitySetup.swift in Sources */, DC2819B922F8F6FE007829F5 /* OctagonTests+DeviceList.swift in Sources */, @@ -35435,6 +35506,7 @@ 0CF70BE2218CF2AA00EC3515 /* OTAuthenticatedCiphertext.m in Sources */, DCB41E01216D5FE500F219E0 /* OctagonDataPersistenceTests.swift in Sources */, DC85687E2284E7860088D3EF /* OctagonTestMocks.swift in Sources */, + DC0DE87123750340006E2EAE /* OTPairingMessage.m in Sources */, DC99B87320EACA470065B73B /* Decrypter.swift in Sources */, DC99B87420EACA470065B73B /* server_entitlement_helpers.c in Sources */, DC99B87520EACA470065B73B /* TrustedPeersHelper.xcdatamodeld in Sources */, @@ -35442,7 +35514,9 @@ DC27C3C120EAD9C300F7839C /* OctagonTests.swift in Sources */, 0CE15E3F222DF6A800B7EAA4 /* OTRecovery.m in Sources */, DCB9475A2127534C00ED9272 /* OctagonTests+SOSUpgrade.swift in Sources */, + DC7F6A7D233D7FAC00DF5769 /* OctagonTests+ForwardCompatibility.swift in Sources */, DC99B87720EACA470065B73B /* ContainerSync.swift in Sources */, + DC33D7BD2374FD0500A68155 /* OTSponsorToApplicantRound1M2.m in Sources */, 0C61F1F62194FC79009566D4 /* OTPrivateKey+SF.m in Sources */, DC391FA821C04DAE00772585 /* OctagonPeerKeys.swift in Sources */, DC99B87820EACA470065B73B /* Container.swift in Sources */, @@ -36449,7 +36523,6 @@ 0CB72DA121E42FCF00D8BC9B /* OTSponsorToApplicantRound2M2.m in Sources */, E7F480151C73980D00390FDB /* KCJoiningRequestSecretSession.m in Sources */, E7F480331C73FC4C00390FDB /* KCAESGCMDuplexSession.m in Sources */, - 0CB72D9F21E42FCF00D8BC9B /* OTSOSMessage.m in Sources */, DC6063B221B09AB200069B82 /* KCJoiningRequestCircleSession.m in Sources */, E794BB001C7598F900339A0F /* KCJoiningMessages.m in Sources */, 5A47FFB9228F5F2A00F781B8 /* KCInitialMessageData.m in Sources */, @@ -36472,6 +36545,7 @@ E7F4809C1C74E85200390FDB /* KCDerTest.m in Sources */, E7D848051C6BEFCD0025BB44 /* KCSRPTests.m in Sources */, E7F4809E1C74E86D00390FDB /* KCAESGCMTest.m in Sources */, + 52DA3C7123C7E63600FEEDFF /* KCTLKRequestTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -36548,7 +36622,6 @@ EB49B2D9202DF1F7003F34A0 /* server_security_helpers.m in Sources */, EBC73F2B2099785900AE3350 /* SFObjCType.m in Sources */, 480ADDB22155A0CE00318FC6 /* SOSAnalytics.m in Sources */, - EB627A79233E375A00F32437 /* MockAKSOptionalParameters.proto in Sources */, EB49B2E0202DF5D7003F34A0 /* server_entitlement_helpers.c in Sources */, 5A061196229ED6E8006AF14A /* NSDate+SFAnalytics.m in Sources */, EBC73F2A20996AD400AE3350 /* SFSQLiteStatement.m in Sources */, @@ -36561,7 +36634,6 @@ EB49B2D1202DF15F003F34A0 /* SFAnalyticsActivityTracker.m in Sources */, EB49B2D0202DF14D003F34A0 /* SFAnalytics.m in Sources */, EBC73F2820993FDA00AE3350 /* SFAnalyticsSampler.m in Sources */, - EBDCC001233DD3E000806566 /* MockAKSRefKey.proto in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -36779,6 +36851,16 @@ target = DC1789031D77980500B50D50 /* Security_osx */; targetProxy = 0C9AEEB920783FE000BF6237 /* PBXContainerItemProxy */; }; + 0CA378E923876E0900090B7E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0CA378E123876DD100090B7E /* reset_account */; + targetProxy = 0CA378E823876E0900090B7E /* PBXContainerItemProxy */; + }; + 0CA378EB23876E1000090B7E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0CA378E123876DD100090B7E /* reset_account */; + targetProxy = 0CA378EA23876E1000090B7E /* PBXContainerItemProxy */; + }; 0CC593F82299EDFC006C34B5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DC52E7731D80BC8000B0A59C /* libsecurityd_ios */; @@ -40080,7 +40162,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = ""; @@ -40155,7 +40236,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = ""; @@ -40225,6 +40305,20 @@ }; name = Release; }; + 0CA378E423876DD100090B7E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 0CA378E523876DD100090B7E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; 0CF4064E2072E3E3003D6A7F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -40954,7 +41048,6 @@ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -41003,7 +41096,6 @@ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -41143,7 +41235,6 @@ CLANG_WARN_COMMA = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -41178,7 +41269,6 @@ CLANG_WARN_COMMA = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -41211,7 +41301,6 @@ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -41258,7 +41347,6 @@ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -42031,7 +42119,6 @@ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -42086,7 +42173,6 @@ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -43124,7 +43210,7 @@ CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -43211,7 +43297,7 @@ CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -43287,7 +43373,6 @@ buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_SUSPICIOUS_MOVES = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -43306,7 +43391,6 @@ buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_SUSPICIOUS_MOVES = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -43532,7 +43616,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -43543,7 +43626,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -43737,12 +43819,12 @@ "$(OTHER_LDFLAGS_AKS_LIBRARY)", "$(OTHER_LDFLAGS_UPWARD_PROTOCOLBUFFER)", "$(OTHER_LDFLAGS_UPWARD_FOUNDATION)", - "$(OTHER_LDFLAGS_UPWARD_SECURITY)", "$(OTHER_LDFLAGS_UPWARD_SECURITYFOUNDATION)", "$(OTHER_LDFLAGS_APPLEIDAUTHSUPPORT)", ); PRODUCT_BUNDLE_IDENTIFIER = com.apple.TrustedPeers; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + TAPI_VERIFY_MODE = Pedantic; VERSION_INFO_PREFIX = ""; }; name = Debug; @@ -43772,12 +43854,12 @@ "$(OTHER_LDFLAGS_AKS_LIBRARY)", "$(OTHER_LDFLAGS_UPWARD_PROTOCOLBUFFER)", "$(OTHER_LDFLAGS_UPWARD_FOUNDATION)", - "$(OTHER_LDFLAGS_UPWARD_SECURITY)", "$(OTHER_LDFLAGS_UPWARD_SECURITYFOUNDATION)", "$(OTHER_LDFLAGS_APPLEIDAUTHSUPPORT)", ); PRODUCT_BUNDLE_IDENTIFIER = com.apple.TrustedPeers; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + TAPI_VERIFY_MODE = Pedantic; VERSION_INFO_PREFIX = ""; }; name = Release; @@ -43896,7 +43978,6 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -43919,7 +44000,6 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44042,7 +44122,6 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44075,7 +44154,6 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44106,7 +44184,6 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44143,7 +44220,6 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44446,7 +44522,6 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44465,7 +44540,6 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44793,7 +44867,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; @@ -44808,7 +44881,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; @@ -44823,7 +44895,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; @@ -44838,7 +44909,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; @@ -44869,7 +44939,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44908,7 +44977,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44946,7 +45014,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -44975,7 +45042,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -45006,7 +45072,6 @@ CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -45036,7 +45101,6 @@ CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -45062,7 +45126,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -45076,7 +45139,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -45090,7 +45152,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -45103,7 +45164,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -45117,7 +45177,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -45137,7 +45196,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -45156,7 +45214,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -45176,7 +45233,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -45193,7 +45249,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -45204,7 +45259,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -45216,7 +45270,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -45233,7 +45286,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -45250,7 +45302,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; HEADER_SEARCH_PATHS = ( @@ -45267,7 +45318,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; HEADER_SEARCH_PATHS = ( @@ -45284,7 +45334,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -45297,7 +45346,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -45310,7 +45358,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = NO; @@ -45324,7 +45371,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = NO; @@ -45338,7 +45384,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -45351,7 +45396,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -45364,7 +45408,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; @@ -45379,7 +45422,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; @@ -45394,7 +45436,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -45407,7 +45448,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -45420,7 +45460,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -45433,7 +45472,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -45572,7 +45610,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -45585,7 +45622,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -45776,7 +45812,6 @@ CLANG_WARN_COMMA = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -45814,7 +45849,6 @@ CLANG_WARN_COMMA = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; @@ -45849,7 +45883,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -45896,7 +45929,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -45942,7 +45974,6 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -45962,7 +45993,6 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -46029,6 +46059,8 @@ "-framework", Security, "$(OTHER_LDFLAGS_FOR_SECURITYD)", + "-framework", + TrustedPeers, ); PRODUCT_BUNDLE_IDENTIFIER = com.apple.security.CKKSTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -46090,6 +46122,8 @@ "-framework", Security, "$(OTHER_LDFLAGS_FOR_SECURITYD)", + "-framework", + TrustedPeers, ); PRODUCT_BUNDLE_IDENTIFIER = com.apple.security.CKKSTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -46104,7 +46138,6 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -46125,7 +46158,6 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -46144,7 +46176,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -46173,7 +46204,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -46722,7 +46752,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -46759,7 +46788,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -46797,7 +46825,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; @@ -46834,7 +46861,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; @@ -46868,7 +46894,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; @@ -46913,7 +46938,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; @@ -46961,7 +46985,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47003,7 +47026,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47086,7 +47108,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47118,7 +47139,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47150,7 +47170,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47180,7 +47199,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47211,7 +47229,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47242,7 +47259,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47285,7 +47301,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; @@ -47301,7 +47316,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; @@ -47374,7 +47388,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -47397,7 +47410,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -47499,7 +47511,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47541,7 +47552,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47577,7 +47587,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47619,7 +47628,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47655,7 +47663,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47697,7 +47704,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47733,7 +47739,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47775,7 +47780,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47811,7 +47815,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47853,7 +47856,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47889,7 +47891,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47931,7 +47932,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -47967,7 +47967,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -48009,7 +48008,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -48045,7 +48043,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -48087,7 +48084,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -48123,7 +48119,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -48165,7 +48160,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -48304,7 +48298,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -48317,7 +48310,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -48330,7 +48322,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -48344,7 +48335,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -48358,7 +48348,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -48371,7 +48360,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -48384,7 +48372,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -48397,7 +48384,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -48410,7 +48396,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; @@ -48428,7 +48413,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; @@ -48447,7 +48431,6 @@ buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; @@ -48462,7 +48445,6 @@ buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; @@ -48726,7 +48708,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -48768,7 +48749,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -48798,7 +48778,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -48812,7 +48791,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -48826,7 +48804,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -48840,7 +48817,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -49264,7 +49240,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49295,7 +49270,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49328,7 +49302,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49382,7 +49355,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49437,7 +49409,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49481,7 +49452,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49523,7 +49493,6 @@ CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49556,7 +49525,6 @@ CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49593,7 +49561,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49641,7 +49608,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49685,7 +49651,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49729,7 +49694,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49812,7 +49776,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49854,7 +49817,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -49885,7 +49847,6 @@ buildSettings = { APPLY_RULES_IN_COPY_FILES = NO; CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_STRICT_PROTOTYPES = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -49909,7 +49870,6 @@ buildSettings = { APPLY_RULES_IN_COPY_FILES = NO; CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_STRICT_PROTOTYPES = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -49932,7 +49892,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -49956,7 +49915,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -49980,7 +49938,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -49993,7 +49950,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -50006,7 +49962,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -50019,7 +49974,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -50032,7 +49986,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -50045,7 +49998,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -50063,7 +50015,6 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -50097,7 +50048,6 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -50123,7 +50073,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = YES; @@ -50136,7 +50085,6 @@ baseConfigurationReference = DC0067911D87816C005AF8DB /* macos_legacy_lib.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; MTL_ENABLE_DEBUG_INFO = NO; @@ -50154,7 +50102,6 @@ CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -50196,7 +50143,6 @@ CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -50238,7 +50184,6 @@ CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -50273,7 +50218,6 @@ CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -50308,7 +50252,6 @@ CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -50352,7 +50295,6 @@ CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -50698,7 +50640,6 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = ( - "$(OTHER_LDFLAGS_MOBILEGESTALT)", "$(OTHER_LDFLAGS_UPWARD_FOUNDATION)", "$(OTHER_LDFLAGS_UPWARD_PROTOCOLBUFFER)", ); @@ -50748,7 +50689,6 @@ MODULEMAP_FILE = Modules/KeychainCircle.modulemap; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = ( - "$(OTHER_LDFLAGS_MOBILEGESTALT)", "$(OTHER_LDFLAGS_UPWARD_FOUNDATION)", "$(OTHER_LDFLAGS_UPWARD_PROTOCOLBUFFER)", ); @@ -51035,7 +50975,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -51077,7 +51016,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; @@ -51109,7 +51047,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; COPY_PHASE_STRIP = NO; @@ -51136,7 +51073,6 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; COPY_PHASE_STRIP = NO; @@ -51906,7 +51842,6 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -51943,7 +51878,6 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -52494,7 +52428,6 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -52516,7 +52449,6 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -52541,7 +52473,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; @@ -52578,7 +52509,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; @@ -52730,6 +52660,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 0CA378E323876DD100090B7E /* Build configuration list for PBXAggregateTarget "reset_account" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0CA378E423876DD100090B7E /* Debug */, + 0CA378E523876DD100090B7E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 0CF4064D2072E3E3003D6A7F /* Build configuration list for PBXNativeTarget "SignInAnalyticsTests_ios" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Security.xcodeproj/xcshareddata/xcschemes/CKKSTests.xcscheme b/Security.xcodeproj/xcshareddata/xcschemes/CKKSTests.xcscheme index c30a62b4..577b55c3 100644 --- a/Security.xcodeproj/xcshareddata/xcschemes/CKKSTests.xcscheme +++ b/Security.xcodeproj/xcshareddata/xcschemes/CKKSTests.xcscheme @@ -1,6 +1,6 @@ + + @@ -50,8 +53,6 @@ - - - - - - - - + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + - - - - - - - - + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + @@ -40,17 +49,6 @@ - - - - - - - - - - diff --git a/Security.xcodeproj/xcshareddata/xcschemes/ios - Release.xcscheme b/Security.xcodeproj/xcshareddata/xcschemes/ios - Release.xcscheme index 11eaea1e..06344ef9 100644 --- a/Security.xcodeproj/xcshareddata/xcschemes/ios - Release.xcscheme +++ b/Security.xcodeproj/xcshareddata/xcschemes/ios - Release.xcscheme @@ -1,6 +1,6 @@ - - diff --git a/Security.xcodeproj/xcshareddata/xcschemes/ios - secdtests.xcscheme b/Security.xcodeproj/xcshareddata/xcschemes/ios - secdtests.xcscheme index d1fcb4cf..cf5cb28f 100644 --- a/Security.xcodeproj/xcshareddata/xcschemes/ios - secdtests.xcscheme +++ b/Security.xcodeproj/xcshareddata/xcschemes/ios - secdtests.xcscheme @@ -1,6 +1,6 @@ + + + + @@ -39,17 +48,6 @@ - - - - - - - - - - diff --git a/Security.xcodeproj/xcshareddata/xcschemes/osx - secdtests.xcscheme b/Security.xcodeproj/xcshareddata/xcschemes/osx - secdtests.xcscheme index 30a0789a..034c0652 100644 --- a/Security.xcodeproj/xcshareddata/xcschemes/osx - secdtests.xcscheme +++ b/Security.xcodeproj/xcshareddata/xcschemes/osx - secdtests.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - - - - - -/* - * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - - - -#include -#include -#include - -#include "keychain/SecureObjectSync/SOSRing.h" -#include "keychain/SecureObjectSync/SOSRingTypes.h" -#include "keychain/SecureObjectSync/SOSRingUtils.h" -#include -#include "keychain/SecureObjectSync/SOSInternal.h" -#include "keychain/SecureObjectSync/SOSUserKeygen.h" - -#include - -#include - -#include -#include - -#include "SOSCircle_regressions.h" -#include "SOSRegressionUtilities.h" - -static SOSFullPeerInfoRef SOSCreateApplicantFullPeerInfoFromName(CFStringRef peerName, - SecKeyRef user_private_key, - SecKeyRef* outSigningKey, - SecKeyRef* outOctagonSigningKey, - SecKeyRef* outOctagonEncryptionKey, - CFErrorRef *error) -{ - SOSFullPeerInfoRef result = NULL; - SOSFullPeerInfoRef fullPeer = SOSCreateFullPeerInfoFromName(peerName, outSigningKey, outOctagonSigningKey, outOctagonEncryptionKey, error); - - if (fullPeer && SOSFullPeerInfoPromoteToApplication(fullPeer, user_private_key, error)) - CFTransferRetained(result, fullPeer); - - CFReleaseNull(fullPeer); - return result; -} - -static int kTestTestCount = 24; -static void tests(void) -{ - - //SecKeyRef publicKey = NULL; - SecKeyRef dev_a_key = NULL; - SecKeyRef dev_b_key = NULL; - SecKeyRef dev_c_key = NULL; - SecKeyRef oct_dev_as_key = NULL; - SecKeyRef oct_dev_aw_key = NULL; - SecKeyRef oct_dev_bs_key = NULL; - SecKeyRef oct_dev_bw_key = NULL; - SecKeyRef oct_dev_cs_key = NULL; - SecKeyRef oct_dev_cw_key = NULL; - CFErrorRef error = NULL; - CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10); - - ok(cfpassword, "no password"); - - CFDataRef parameters = SOSUserKeyCreateGenerateParameters(&error); - ok(parameters, "No parameters!"); - ok(error == NULL, "Error: (%@)", error); - CFReleaseNull(error); - - SecKeyRef user_privkey = SOSUserKeygen(cfpassword, parameters, &error); - CFReleaseNull(parameters); - - SecKeyRef user_pubkey = SecKeyCreatePublicFromPrivate(user_privkey); - - - SOSFullPeerInfoRef peer_a_full_info = SOSCreateApplicantFullPeerInfoFromName(CFSTR("Peer A"), user_privkey, &dev_a_key, &oct_dev_as_key, &oct_dev_aw_key, NULL); - SOSFullPeerInfoRef peer_b_full_info = SOSCreateApplicantFullPeerInfoFromName(CFSTR("Peer B"), user_privkey, &dev_b_key, &oct_dev_bs_key, &oct_dev_bw_key, NULL); - SOSFullPeerInfoRef peer_c_full_info = SOSCreateApplicantFullPeerInfoFromName(CFSTR("Peer C"), user_privkey, &dev_c_key, &oct_dev_cs_key, &oct_dev_cw_key, NULL); - CFStringRef peerID_a = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(peer_a_full_info)); - CFStringRef peerID_b = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(peer_b_full_info)); - SOSRingRef Ring = SOSRingCreate(CFSTR("TESTRING"), peerID_a, kSOSRingBase, NULL); - - ok(Ring, "Ring creation"); - - - ok(0 == SOSRingCountPeers(Ring), "Zero peers"); - - ok(SOSRingApply(Ring, user_pubkey, peer_a_full_info, NULL)); - ok(SOSRingApply(Ring, user_pubkey, peer_b_full_info, NULL)); - - ok(2 == SOSRingCountPeers(Ring), "Two peers"); - - ok(SOSRingWithdraw(Ring, user_privkey, peer_b_full_info, NULL)); - - ok(1 == SOSRingCountPeers(Ring), "One peer"); - - ok(kSOSRingMember == SOSRingDeviceIsInRing(Ring, peerID_a), "peer_a is in Ring"); - ok(kSOSRingNotInRing == SOSRingDeviceIsInRing(Ring, peerID_b), "peer_b is not in Ring"); - CFStringRef lastmod = SOSRingGetLastModifier(Ring); - ok(CFEqual(lastmod, peerID_b), "peer_b_full_info did last mod"); - - ok(SOSRingResetToEmpty(Ring, peerID_a, NULL), "Reset the circle"); - ok(kSOSRingNotInRing == SOSRingDeviceIsInRing(Ring, peerID_a), "peer_a is not in Ring"); - - ok(SOSRingResetToOffering(Ring, NULL, peer_a_full_info, NULL), "Reset Ring to Offering for PeerA"); - ok(kSOSRingMember == SOSRingDeviceIsInRing(Ring, peerID_a), "peer_a is in Ring"); - ok(kSOSRingNotInRing == SOSRingDeviceIsInRing(Ring, peerID_b), "peer_b is not in Ring"); - - CFDataRef ringDER = SOSRingCopyEncodedData(Ring, NULL); - ok(ringDER, "Successful encoding to DER of Ring"); - SOSRingRef Ring2 = SOSRingCreateFromData(NULL, ringDER); - ok(Ring2, "Successful decoding of DER to Ring"); - - ok(CFEqualSafe(Ring, Ring2), "Compares"); - - ok(SOSRingApply(Ring, user_pubkey, peer_c_full_info, NULL)); - ok(SOSRingApply(Ring, user_pubkey, peer_b_full_info, NULL)); - - CFReleaseNull(ringDER); - CFReleaseNull(Ring2); - ringDER = SOSRingCopyEncodedData(Ring, NULL); - Ring2 = SOSRingCreateFromData(NULL, ringDER); - ok(CFEqualSafe(Ring, Ring2), "Compares"); - - CFReleaseNull(ringDER); - CFReleaseNull(Ring2); - CFReleaseNull(dev_a_key); - CFReleaseNull(dev_b_key); - CFReleaseNull(dev_c_key); - CFReleaseNull(oct_dev_as_key); - CFReleaseNull(oct_dev_aw_key); - CFReleaseNull(oct_dev_bs_key); - CFReleaseNull(oct_dev_bw_key); - CFReleaseNull(oct_dev_cs_key); - CFReleaseNull(oct_dev_cw_key); - CFReleaseNull(cfpassword); - - CFReleaseNull(user_privkey); - CFReleaseNull(user_pubkey); - - CFReleaseNull(peer_a_full_info); - CFReleaseNull(peer_b_full_info); - CFReleaseNull(peer_c_full_info); - CFReleaseNull(Ring); -} - -int sc_150_Ring(int argc, char *const *argv) -{ - plan_tests(kTestTestCount); - - tests(); - - return 0; -} diff --git a/keychain/SecureObjectSync/Regressions/sc-kvstool.m b/keychain/SecureObjectSync/Regressions/sc-kvstool.m deleted file mode 100644 index 63660dee..00000000 --- a/keychain/SecureObjectSync/Regressions/sc-kvstool.m +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - - - -// Run on a device: -// /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_kvstool -v -- --dump -// /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_kvstool -v -- --clear -// /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_kvstool -v -- --putcircle -// /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_kvstool -v -- --direct --dump -// /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_kvstool -v -- --direct --putcircle - -#include -#include -#include -#include -#include - -#include "SOSCircle_regressions.h" - -#include - -#include -#include - -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include "SOSCircle_regressions.h" -#include "CKDLocalKeyValueStore.h" -#include "SOSRegressionUtilities.h" -#include "SOSTestDataSource.h" -#include "SOSTestTransport.h" -#include "SOSCloudKeychainClient.h" - -#import "SOSDirectCloudTransport.h" - -// MARK: ----- Constants ----- - -static CFStringRef circleKey = CFSTR("Circle"); - -// MARK: ----- start of all tests ----- - -static void putCircleInCloud(SOSCircleRef circle, dispatch_queue_t work_queue, dispatch_group_t work_group) -{ - CFErrorRef error = NULL; - CFDataRef newCloudCircleEncoded = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, &error); - ok(newCloudCircleEncoded, "Encoded as: %@ [%@]", newCloudCircleEncoded, error); - - // Send the circle with our application request back to cloud - testPutObjectInCloud(circleKey, newCloudCircleEncoded, &error, work_group, work_queue); -} - -static void createAndPutInitialCircle(void) -{ - dispatch_queue_t work_queue = dispatch_queue_create("capic", DISPATCH_QUEUE_CONCURRENT); - dispatch_group_t work_group = dispatch_group_create(); - - CFErrorRef error = NULL; - CFStringRef cflabel = CFSTR("TEST_USERKEY"); - CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10); - SOSCCRegisterUserCredentials(cflabel, cfpassword, &error); - - CFErrorRef localError = NULL; - - SOSDataSourceFactoryRef our_data_source_factory = SOSTestDataSourceFactoryCreate(); - SOSDataSourceRef our_data_source = SOSTestDataSourceCreate(); - SOSTestDataSourceFactoryAddDataSource(our_data_source_factory, circleKey, our_data_source); - - CFDictionaryRef gestalt = SOSCreatePeerGestaltFromName(CFSTR("Alice")); - - SOSAccountRef our_account = SOSAccountCreate(kCFAllocatorDefault, gestalt, our_data_source_factory, - ^(CFArrayRef keys) - { - pass("SOSAccountKeyInterestBlock"); - }, - ^ bool (CFDictionaryRef keys, CFErrorRef *error) - { - pass("SOSAccountDataUpdateBlock"); - return false; - }, - NULL); - SOSAccountEnsureCircle(our_account, circleKey); - - SOSFullPeerInfoRef our_full_peer_info = SOSAccountGetMyFullPeerInCircleNamed(our_account, circleKey, &error); - SOSPeerInfoRef our_peer_info = SOSFullPeerInfoGetPeerInfo(our_full_peer_info); - CFRetain(our_peer_info); - - SOSCircleRef circle = SOSAccountFindCircle(our_account, circleKey); - CFRetain(circle); - -// SecKeyRef user_privkey = SOSUserGetPrivKey(&localError); - SecKeyRef user_privkey = NULL; // TODO: this will not work - ok(SOSCircleRequestAdmission(circle, user_privkey, our_full_peer_info, &localError), "Requested admission (%@)", our_peer_info); - ok(SOSCircleAcceptRequests(circle, user_privkey, our_full_peer_info, &localError), "Accepted self"); - - putCircleInCloud(circle, work_queue, work_group); - pass("Put circle in cloud: (%@)", circle); - - CFRelease(circle); -} - -static void postCircleChangeNotification() -{ - NSArray *keys = [NSArray arrayWithObjects:(id)circleKey, nil]; - NSDictionary* userInfo = [[NSDictionary alloc] initWithObjectsAndKeys: - keys, NSUbiquitousKeyValueStoreChangedKeysKey, - [NSNumber numberWithInt:NSUbiquitousKeyValueStoreServerChange], NSUbiquitousKeyValueStoreChangeReasonKey, - nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:NULL userInfo:userInfo]; - [userInfo release]; -} - -static void requestSynchronization(dispatch_queue_t processQueue, dispatch_group_t dgroup) -{ - testSynchronize(processQueue, dgroup); -} - -static void displayCircles(CFTypeRef objects) -{ - // SOSCCCopyApplicantPeerInfo doesn't display all info, e.g. in the case where we are not in circle - CFDictionaryForEach(objects, ^(const void *key, const void *value) - { - if (SOSKVSKeyGetKeyType(key) == kCircleKey) - { - CFErrorRef localError = NULL; - if (isData(value)) - { - SOSCircleRef circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, &localError); - pass("circle: %@ %@", key, circle); - CFReleaseSafe(circle); - } - else - pass("non-circle: %@ %@", key, value); - } - }); -} - - -static void dumpCircleInfo() -{ - CFErrorRef error = NULL; - CFArrayRef applicantPeerInfos = NULL; - CFArrayRef peerInfos = NULL; - int idx; - - NSArray *ccmsgs = @[@"Error", @"InCircle", @"NotInCircle", @"RequestPending", @"CircleAbsent"]; - - SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(&error); - pass("ccstatus: %d, error: %@", ccstatus, error); - idx = ccstatus-kSOSCCError; - if (0<=idx && idx<(int)[ccmsgs count]) - pass("ccstatus: %d (%@)", ccstatus, ccmsgs[idx]); - - // Now look at current applicants - applicantPeerInfos = SOSCCCopyApplicantPeerInfo(&error); - if (applicantPeerInfos) - { - pass("Applicants: %ld, error: %@", (long)CFArrayGetCount(applicantPeerInfos), error); - CFArrayForEach(applicantPeerInfos, ^(const void *value) { - SOSPeerInfoRef peer = (SOSPeerInfoRef)value; - CFStringRef peerName = SOSPeerInfoGetPeerName(peer); - pass("Applicant: %@", peerName); - }); - } - else - pass("No applicants, error: %@", error); - - - peerInfos = SOSCCCopyPeerPeerInfo(&error); - if (peerInfos) - { - pass("Peers: %ld, error: %@", (long)CFArrayGetCount(applicantPeerInfos), error); - CFArrayForEach(peerInfos, ^(const void *value) { - SOSPeerInfoRef peer = (SOSPeerInfoRef)value; - CFStringRef peerName = SOSPeerInfoGetPeerName(peer); - pass("Peer: %@", peerName); - }); - } - else - pass("No peers, error: %@", error); -} - -// define the options table for the command line -static const struct option options[] = -{ - { "verbose", optional_argument, NULL, 'v' }, - { "dump", optional_argument, NULL, 'd' }, - { "clear", optional_argument, NULL, 'C' }, - { "putcircle", optional_argument, NULL, 'p' }, - { "direct", optional_argument, NULL, 'D' }, - { "notify", optional_argument, NULL, 'n' }, - { "sync", optional_argument, NULL, 's' }, - { "info", optional_argument, NULL, 'i' }, - { } -}; - -static int kTestCount = 10; - -static void usage(void) -{ - printf("Usage:\n"); - printf(" --dump [itemName] Dump the contents of the kvs store (through proxy)\n"); - printf(" --clear Clear the contents of the kvs store (through proxy)\n"); - printf(" --putcircle Put a new circle into the kvs store (through proxy)\n"); - printf(" --direct Go directly to KVS (bypass proxy)\n"); - printf(" --notify Post a notification that the circle key has changed\n"); - printf(" --sync Post a notification that the circle key has changed\n"); - printf(" --info Dump info about circle and peer status\n"); -} - -enum kvscommands -{ - kCommandClear = 1, - kCommandDump = 2, - kCommandPutCircle = 3, - kCommandNotify = 4, - kCommandSynchronize = 5, - kCommandInfo = 6, - kCommandHelp -}; - -static int command = kCommandHelp; -static bool useDirect = false; - -static void tests(const char *itemName) -{ - dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL); - dispatch_group_t work_group = dispatch_group_create(); - -#if 0 - if (useDirect) - { - SOSCloudKeychainServerInit(); - } -#endif - SOSCloudKeychainSetCallbackMethodXPC(); // call this first - - pass("Command: %d", command); - switch (command) - { - case kCommandClear: - ok(testClearAll(generalq, work_group), "test Clear All"); - break; - case kCommandDump: - { - CFArrayRef keysToGet = NULL; - if (itemName) - { - CFStringRef itemStr = CFStringCreateWithCString(kCFAllocatorDefault, itemName, kCFStringEncodingUTF8); - pass("Retrieving : %@", itemStr); - keysToGet = CFArrayCreateForCFTypes(kCFAllocatorDefault, itemStr); - CFReleaseSafe(itemStr); - } - CFTypeRef objects = testGetObjectsFromCloud(keysToGet, generalq, work_group); - CFReleaseSafe(keysToGet); - pass(" : %@", objects); - displayCircles(objects); - } - break; - case kCommandPutCircle: - createAndPutInitialCircle(); - break; - case kCommandNotify: - postCircleChangeNotification(); - break; - case kCommandSynchronize: - requestSynchronization(generalq, work_group); - break; - case kCommandInfo: - dumpCircleInfo(); - break; - default: - case kCommandHelp: - usage(); - break; - } -} - -int sc_kvstool(int argc, char *const *argv) -{ - char *itemName = NULL; -// extern int optind; - extern char *optarg; - int arg, argSlot; - - while (argSlot = -1, (arg = getopt_long(argc, (char * const *)argv, "ivChpdns", options, &argSlot)) != -1) - switch (arg) - { - case 'd': - itemName = (char *)(optarg); - command = kCommandDump; - break; - case 'C': // should set up to call testClearAll - command = kCommandClear; - break; - case 'p': - command = kCommandPutCircle; - break; - case 'n': - command = kCommandNotify; - break; - case 's': - command = kCommandSynchronize; - break; - case 'i': - command = kCommandInfo; - break; - case 'D': - useDirect = true; - printf("Using direct calls to KVS\n"); - break; - default: - secerror("arg: %s", optarg); - break; - } - - plan_tests(kTestCount); - - secerror("Command: %d", command); - printf("Command: %d\n", command); - - tests(itemName); - - return 0; -} diff --git a/keychain/SecureObjectSync/SOSAccount.h b/keychain/SecureObjectSync/SOSAccount.h index a50be3ee..f7674f30 100644 --- a/keychain/SecureObjectSync/SOSAccount.h +++ b/keychain/SecureObjectSync/SOSAccount.h @@ -188,11 +188,6 @@ CF_RETURNS_RETAINED CFSetRef SOSAccountCopyBackupPeersAndForceSync(SOSAccountTra bool SOSAccountScanForRetired(SOSAccount* account, SOSCircleRef circle, CFErrorRef *error); CF_RETURNS_RETAINED SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccount* account, SOSCircleRef starting_circle, CFErrorRef *error); -// -// MARK: Version incompatibility Functions -// -CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccount* account, CFErrorRef* error); - // // MARK: Backup functions // @@ -209,9 +204,6 @@ bool SOSAccountSetBSKBagForAllSlices(SOSAccount* account, CFDataRef backupSlice CF_RETURNS_RETAINED SOSBackupSliceKeyBagRef SOSAccountBackupSliceKeyBagForView(SOSAccount* account, CFStringRef viewName, CFErrorRef* error); -bool SOSAccountIsLastBackupPeer(SOSAccount* account, CFErrorRef *error); - - // // MARK: Recovery Public Key Functions // diff --git a/keychain/SecureObjectSync/SOSAccount.m b/keychain/SecureObjectSync/SOSAccount.m index cd5951a2..b75b48cf 100644 --- a/keychain/SecureObjectSync/SOSAccount.m +++ b/keychain/SecureObjectSync/SOSAccount.m @@ -113,7 +113,6 @@ static NSDictionary* SOSStateMap(void); @property (readwrite) CKKSPBFileStorage* accountConfiguration; @property CKKSNearFutureScheduler *performBackups; -@property CKKSNearFutureScheduler *performRingUpdates; @end #endif @@ -124,11 +123,6 @@ static NSDictionary* SOSStateMap(void); CFReleaseNull(self->_accountKey); CFReleaseNull(self->_accountPrivateKey); CFReleaseNull(self->_previousAccountKey); -#if OCTAGON - [self.performBackups cancel]; - [self.performRingUpdates cancel]; - [self.stateMachine haltOperation]; -#endif } } @@ -170,11 +164,16 @@ static NSDictionary* SOSStateMap(void); -(bool) ensureFactoryCircles { - if (self.factory == nil){ + if (!self){ + return false; + } + + if (!self.factory){ return false; } - NSString* circle_name = CFBridgingRelease(SOSDataSourceFactoryCopyName(self.factory)); + NSString* circle_name = (__bridge_transfer NSString*)SOSDataSourceFactoryCopyName(self.factory); + if (!circle_name){ return false; } @@ -238,9 +237,6 @@ static NSDictionary* SOSStateMap(void); self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite]; - [self ensureFactoryCircles]; - SOSAccountEnsureUUID(self); - #if OCTAGON [self setupStateMachine]; #endif @@ -248,13 +244,6 @@ static NSDictionary* SOSStateMap(void); return self; } -- (void)startStateMachine -{ -#if OCTAGON - [self.stateMachine startOperation]; -#endif -} - -(BOOL)isEqual:(id) object { if(![object isKindOfClass:[SOSAccount class]]) @@ -726,7 +715,7 @@ static bool Flush(CFErrorRef *error) { CFReleaseNull(error); } -- (void)rpcTriggerSync:(NSArray *)peers complete:(void(^)(bool success, NSError *))complete +- (void)triggerSync:(NSArray *)peers complete:(void(^)(bool success, NSError *))complete { __block CFErrorRef localError = NULL; __block bool res = false; @@ -751,7 +740,7 @@ static bool Flush(CFErrorRef *error) { CFReleaseNull(localError); } -- (void)rpcTriggerBackup:(NSArray* _Nullable)backupPeers complete:(void (^)(NSError *error))complete +- (void)triggerBackup:(NSArray* _Nullable)backupPeers complete:(void (^)(NSError *error))complete { __block CFErrorRef localError = NULL; @@ -768,14 +757,6 @@ static bool Flush(CFErrorRef *error) { CFReleaseNull(localError); } -- (void)rpcTriggerRingUpdate:(void (^)(NSError *error))complete -{ -#if OCTAGON - [self triggerRingUpdate]; -#endif - complete(NULL); -} - - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete { // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class @@ -881,10 +862,10 @@ SOSAccount* SOSAccountCreate(CFAllocatorRef allocator, SOSDataSourceFactoryRef factory) { SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory]; - dispatch_sync(a.queue, ^{ - secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate"); - a.key_interests_need_updating = true; - }); + [a ensureFactoryCircles]; + SOSAccountEnsureUUID(a); + secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate"); + a.key_interests_need_updating = true; return a; } @@ -1026,26 +1007,6 @@ void SOSAccountSetToNew(SOSAccount* a) a.key_interests_need_updating = true; } -bool SOSAccountIsNew(SOSAccount* account, CFErrorRef *error){ - bool result = false; - SOSAccountTrustClassic* trust = account.trust; - if(account.accountKeyIsTrusted != false || trust.departureCode != kSOSNeverAppliedToCircle || - CFSetGetCount((__bridge CFSetRef)trust.retirees) != 0) - return result; - - if(trust.retirees != nil) - return result; - if(trust.expansion != nil) - return result; - - if(account.user_private_timer != NULL || account.lock_notification_token != NOTIFY_TOKEN_INVALID) - return result; - - result = true; - - return result; -} - dispatch_queue_t SOSAccountGetQueue(SOSAccount* account) { return account.queue; } @@ -1308,42 +1269,6 @@ bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* return retval; } -bool sosAccountLeaveRing(SOSAccount* account, SOSRingRef ring, CFErrorRef* error) { - SOSAccountTrustClassic *trust = account.trust; - SOSFullPeerInfoRef identity = trust.fullPeerInfo; - - SOSFullPeerInfoRef fpi = identity; - if(!fpi) return false; - SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi); - CFStringRef peerID = SOSPeerInfoGetPeerID(pi); - - CFErrorRef localError = NULL; - - bool retval = false; - bool writeRing = false; - bool writePeerInfo = false; - - if(SOSRingHasPeerID(ring, peerID)) { - writePeerInfo = true; - } - - if(writePeerInfo || writeRing) { - SOSRingWithdraw(ring, NULL, fpi, error); - } - - if (writeRing) { - CFDataRef ring_data = SOSRingCopyEncodedData(ring, error); - - if (ring_data) { - [account.circle_transport kvsRingPostRing:SOSRingGetName(ring) ring:ring_data err:NULL]; - } - CFReleaseNull(ring_data); - } - retval = true; - CFReleaseNull(localError); - return retval; -} - bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) { bool result = false; if (account.circle_transport) { @@ -1435,28 +1360,26 @@ bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleR // -- (bool)_onQueueEnsureInBackupRings { +bool SOSAccountEnsureInBackupRings(SOSAccount* account) { __block bool result = false; __block CFErrorRef error = NULL; secnotice("backup", "Ensuring in rings"); - dispatch_assert_queue(self.queue); - - if(!self.backup_key){ + if(!account.backup_key){ return true; } - if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)self.backup_key, &error)){ + if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)account.backup_key, &error)){ secnotice("backupkey", "account backup key isn't valid: %@", error); - self.backup_key = nil; + account.backup_key = nil; CFReleaseNull(error); return false; } - NSData *peerBackupKey = (__bridge_transfer NSData*)SOSPeerInfoCopyBackupKey(self.peerInfo); - if(![peerBackupKey isEqual:self.backup_key]) { - result = SOSAccountUpdatePeerInfo(self, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) { - return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(self.backup_key), error); + NSData *peerBackupKey = (__bridge_transfer NSData*)SOSPeerInfoCopyBackupKey(account.peerInfo); + if(![peerBackupKey isEqual:account.backup_key]) { + result = SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) { + return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(account.backup_key), error); }); if (!result) { secnotice("backupkey", "Failed to setup backup public key in peerInfo from account: %@", error); @@ -1473,12 +1396,12 @@ bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleR CFReleaseNull(localError); // Setup backups the new way. - SOSAccountForEachBackupView(self, ^(const void *value) { + SOSAccountForEachBackupView(account, ^(const void *value) { CFStringRef viewName = asString(value, NULL); - bool resetRing = SOSAccountValidateBackupRingForView(self, viewName, NULL); + bool resetRing = SOSAccountValidateBackupRingForView(account, viewName, NULL); if(resetRing) { - SOSAccountUpdateBackupRing(self, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) { - SOSRingRef newRing = SOSAccountCreateBackupRingForView(self, viewName, error); + SOSAccountUpdateBackupRing(account, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) { + SOSRingRef newRing = SOSAccountCreateBackupRingForView(account, viewName, error); return newRing; }); } @@ -1936,11 +1859,6 @@ bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFE return success; } - -CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccount* account, CFErrorRef* error) { - return CFSTR("We're compatible, go away"); -} - enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) { SOSAccountTrustClassic *trust = account.trust; return trust.departureCode; @@ -2009,7 +1927,7 @@ bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) { SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError); if (localError) secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError); - CFReleaseNull(localError); + CFReleaseSafe(localError); } }); @@ -2028,27 +1946,7 @@ CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef * return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]); } -bool SOSAccountAddEscrowRecords(SOSAccount* account, CFStringRef dsid, CFDictionaryRef record, CFErrorRef *error){ - CFMutableDictionaryRef escrowRecords = (CFMutableDictionaryRef)SOSAccountGetValue(account, kSOSEscrowRecord, error); - CFMutableDictionaryRef escrowCopied = NULL; - bool success = false; - - if(isDictionary(escrowRecords) && escrowRecords != NULL) - escrowCopied = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(escrowRecords), escrowRecords); - else - escrowCopied = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - - CFDictionaryAddValue(escrowCopied, dsid, record); - SOSAccountSetValue(account, kSOSEscrowRecord, escrowCopied, error); - - if(*error == NULL) - success = true; - - CFReleaseNull(escrowCopied); - - return success; - -} + bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){ bool success = false; @@ -2059,16 +1957,13 @@ bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPe return success; } -- (void)_onQueueRecordRetiredPeersInCircle { - - dispatch_assert_queue(self.queue); - - if (![self isInCircle:NULL]) { +void SOSAccountRecordRetiredPeersInCircle(SOSAccount* account) { + if (![account isInCircle:NULL]) { return; } __block bool updateRings = false; - SOSAccountTrustClassic *trust = self.trust; - [trust modifyCircle:self.circle_transport err:NULL action:^bool (SOSCircleRef circle) { + SOSAccountTrustClassic *trust = account.trust; + [trust modifyCircle:account.circle_transport err:NULL action:^bool (SOSCircleRef circle) { __block bool updated = false; CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){ SOSPeerInfoRef retiree = asSOSPeerInfo(element); @@ -2077,7 +1972,7 @@ bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPe updated = true; secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle); CFErrorRef cleanupError = NULL; - if (![self.trust cleanupAfterPeer:self.kvs_message_transport circleTransport:self.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError]) + if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError]) secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError); CFReleaseSafe(cleanupError); updateRings = true; @@ -2086,7 +1981,7 @@ bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPe return updated; }]; if(updateRings) { - SOSAccountProcessBackupRings(self, NULL); + SOSAccountProcessBackupRings(account, NULL); } } @@ -2235,32 +2130,6 @@ bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error) } -bool SOSAccountPopulateKVSWithBadKeys(SOSAccount* account, CFErrorRef* error) { - - NSMutableDictionary *testKeysAndValues = [NSMutableDictionary dictionary]; - [testKeysAndValues setObject:@"deadbeef" forKey:@"-ak|asdfjkl;asdfjk;"]; - [testKeysAndValues setObject:@"foobar" forKey:@"ak|asdfasdfasdf:qwerqwerqwer"]; - [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"poak|asdfasdfasdfasdf"]; - [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"po|asdfasdfasdfasdfasdfasdf"]; - [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"k>KeyParm"]; - - dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0); - dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds); - dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - - CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){ - if (error){ - secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error); - } - dispatch_semaphore_signal(waitSemaphore); - }; - - SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(testKeysAndValues), processQueue, replyBlock); - dispatch_semaphore_wait(waitSemaphore, finishTime); - - return true; -} - SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) { SOSPeerInfoRef applicant = NULL; SOSAccountTrustClassic *trust = account.trust; @@ -2926,12 +2795,10 @@ void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* p */ OctagonFlag* SOSFlagTriggerBackup = (OctagonFlag*)@"trigger_backup"; -OctagonFlag* SOSFlagTriggerRingUpdate = (OctagonFlag*)@"trigger_ring_update"; OctagonState* SOSStateReady = (OctagonState*)@"ready"; OctagonState* SOSStateError = (OctagonState*)@"error"; OctagonState* SOSStatePerformBackup = (OctagonState*)@"perform_backup"; -OctagonState* SOSStatePerformRingUpdate = (OctagonState*)@"perform_ring_update"; static NSDictionary* SOSStateMap(void) { static NSDictionary* map = nil; @@ -2941,7 +2808,6 @@ static NSDictionary* SOSStateMap(void) { SOSStateReady: @0U, SOSStateError: @1U, SOSStatePerformBackup: @2U, - SOSStatePerformRingUpdate: @3U, }; }); return map; @@ -2952,8 +2818,7 @@ static NSSet* SOSFlagsSet(void) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ set = [NSSet setWithArray:@[ - SOSFlagTriggerBackup, - SOSFlagTriggerRingUpdate, + SOSFlagTriggerBackup ]]; }); return set; @@ -2995,31 +2860,15 @@ static NSSet* SOSFlagsSet(void) { [self addBackupFlag]; }]; - self.performRingUpdates = [[CKKSNearFutureScheduler alloc] initWithName:@"performRingUpdates" - initialDelay:5*NSEC_PER_SEC - expontialBackoff:2.0 - maximumDelay:15*60*NSEC_PER_SEC - keepProcessAlive:YES - dependencyDescriptionCode:CKKSResultDescriptionNone - block:^{ - STRONGIFY(self); - [self addRingUpdateFlag]; - }]; - SOSAccountConfiguration *conf = self.accountConfiguration.storage; if (conf.pendingBackupPeers.count) { [self addBackupFlag]; } - if (conf.ringUpdateFlag) { - [self addRingUpdateFlag]; - } -} + [self.stateMachine startOperation]; +} -/* - * Flag adding to state machine - */ - (void)addBackupFlag { OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerBackup @@ -3027,122 +2876,34 @@ static NSSet* SOSFlagsSet(void) { [self.stateMachine handlePendingFlag:pendingFlag]; } -- (void)addRingUpdateFlag { - OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerRingUpdate - conditions:OctagonPendingConditionsDeviceUnlocked]; - [self.stateMachine handlePendingFlag:pendingFlag]; -} - -//Mark: -- Set up state for state machine - - - (void)triggerBackupForPeers:(NSArray*)backupPeers { - NSMutableSet *pending = [NSMutableSet set]; - if (backupPeers) { - [pending addObjectsFromArray:backupPeers]; - } - - WEAKIFY(self); - dispatch_async(self.stateMachineQueue, ^{ - STRONGIFY(self); - + dispatch_sync(self.stateMachineQueue, ^{ SOSAccountConfiguration *storage = self.accountConfiguration.storage; + NSMutableSet *pending = [NSMutableSet set]; if (storage.pendingBackupPeers) { [pending addObjectsFromArray:storage.pendingBackupPeers]; } + if (backupPeers) { + [pending addObjectsFromArray:backupPeers]; + } storage.pendingBackupPeers = [[pending allObjects] mutableCopy]; [self.accountConfiguration setStorage:storage]; [self.performBackups trigger]; - secnotice("sos-sm", "trigger backup for peers: %@ at %@", - backupPeers, self.performBackups.nextFireTime); + secnotice("sos-sm", "trigger backup for peers: %@", backupPeers); }); } -- (void)triggerRingUpdate -{ - - WEAKIFY(self); - dispatch_async(self.stateMachineQueue, ^{ - STRONGIFY(self); - SOSAccountConfiguration *storage = self.accountConfiguration.storage; - storage.ringUpdateFlag = YES; - [self.accountConfiguration setStorage:storage]; - [self.performRingUpdates trigger]; - secnotice("sos-sm", "trigger ring update at %@", - self.performRingUpdates.nextFireTime); - }); -} - -//Mark: -- State machine and opertions - -- (OctagonStateTransitionOperation *)performBackup { - - WEAKIFY(self); - return [OctagonStateTransitionOperation named:@"perform-backup-state" - intending:SOSStateReady - errorState:SOSStateError - withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) { - STRONGIFY(self); - SOSAccountConfiguration *storage = self.accountConfiguration.storage; - - secnotice("sos-sm", "performing backup for %@", storage.pendingBackupPeers); - - if (storage.pendingBackupPeers.count) { - SOSCCRequestSyncWithBackupPeerList((__bridge CFArrayRef)storage.pendingBackupPeers); - [storage clearPendingBackupPeers]; - } - [self.accountConfiguration setStorage:storage]; - - op.nextState = SOSStateReady; - }]; -} - -- (OctagonStateTransitionOperation *)performRingUpdate { - - WEAKIFY(self); - return [OctagonStateTransitionOperation named:@"perform-ring-update" - intending:SOSStateReady - errorState:SOSStateError - withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) { - STRONGIFY(self); - - SOSAccountConfiguration *storage = self.accountConfiguration.storage; - storage.ringUpdateFlag = NO; - [self.accountConfiguration setStorage:storage]; - - [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { - - [self _onQueueRecordRetiredPeersInCircle]; - - SOSAccountEnsureRecoveryRing(self); - [self _onQueueEnsureInBackupRings]; - - CFErrorRef localError = NULL; - if(![self.circle_transport flushChanges:&localError]){ - secerror("flush circle failed %@", localError); - } - CFReleaseNull(localError); - - if(!SecCKKSTestDisableSOS()) { - SOSAccountNotifyEngines(self); - } - }]; - - op.nextState = SOSStateReady; - }]; - -} - - - (CKKSResultOperation* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState flags:(nonnull OctagonFlags *)flags pendingFlags:(nonnull id)pendingFlagHandler { dispatch_assert_queue(self.stateMachineQueue); - secnotice("sos-sm", "Entering state: %@ [flags: %@]", currentState, flags); + WEAKIFY(self); + + secnotice("sos-sm", "currentState: %@ [flags: %@]", currentState, flags); if ([currentState isEqualToString:SOSStateReady]) { if([flags _onqueueContains:SOSFlagTriggerBackup]) { @@ -3150,21 +2911,30 @@ static NSSet* SOSFlagsSet(void) { return [OctagonStateTransitionOperation named:@"perform-backup-flag" entering:SOSStatePerformBackup]; } - - if ([flags _onqueueContains:SOSFlagTriggerRingUpdate]) { - [flags _onqueueRemoveFlag:SOSFlagTriggerRingUpdate]; - return [OctagonStateTransitionOperation named:@"perform-ring-update-flag" - entering:SOSStatePerformRingUpdate]; - } + secnotice("sos-sm", "Entering state ready"); return nil; - } else if ([currentState isEqualToString:SOSStateError]) { + secnotice("sos-sm", "Entering state error"); return nil; - } else if ([currentState isEqualToString:SOSStatePerformRingUpdate]) { - return [self performRingUpdate]; - } else if ([currentState isEqualToString:SOSStatePerformBackup]) { - return [self performBackup]; + + return [OctagonStateTransitionOperation named:@"perform-backup-state" + intending:SOSStateReady + errorState:SOSStateError + withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) { + STRONGIFY(self); + SOSAccountConfiguration *storage = self.accountConfiguration.storage; + + secnotice("sos-sm", "performing backup for %@", storage.pendingBackupPeers); + + if (storage.pendingBackupPeers.count) { + SOSCCRequestSyncWithBackupPeerList((__bridge CFArrayRef)storage.pendingBackupPeers); + [storage clearPendingBackupPeers]; + } + [self.accountConfiguration setStorage:storage]; + + op.nextState = SOSStateReady; + }]; } return nil; diff --git a/keychain/SecureObjectSync/SOSAccountBackup.m b/keychain/SecureObjectSync/SOSAccountBackup.m index 3a631f1f..17c724d5 100644 --- a/keychain/SecureObjectSync/SOSAccountBackup.m +++ b/keychain/SecureObjectSync/SOSAccountBackup.m @@ -629,33 +629,3 @@ exit: return bskb; } - -bool SOSAccountIsLastBackupPeer(SOSAccount* account, CFErrorRef *error) { - __block bool retval = false; - SOSPeerInfoRef pi = account.peerInfo; - - if(![account isInCircle:error]) { - return retval; - } - - if(!SOSPeerInfoHasBackupKey(pi)) - return retval; - - SOSCircleRef circle = [account.trust getCircle:error]; - - if(SOSCircleCountValidSyncingPeers(circle, SOSAccountGetTrustedPublicCredential(account, error)) == 1){ - retval = true; - return retval; - } - // We're in a circle with more than 1 ActiveValidPeers - are they in the backups? - SOSAccountForEachBackupView(account, ^(const void *value) { - CFStringRef viewname = (CFStringRef) value; - SOSBackupSliceKeyBagRef keybag = SOSAccountBackupSliceKeyBagForView(account, viewname, error); - require_quiet(keybag, inner_errOut); - retval |= ((SOSBSKBCountPeers(keybag) == 1) && (SOSBSKBPeerIsInKeyBag(keybag, pi))); - inner_errOut: - CFReleaseNull(keybag); - }); - - return retval; -} diff --git a/keychain/SecureObjectSync/SOSAccountConfiguration.proto b/keychain/SecureObjectSync/SOSAccountConfiguration.proto index bb26993a..8bba993c 100644 --- a/keychain/SecureObjectSync/SOSAccountConfiguration.proto +++ b/keychain/SecureObjectSync/SOSAccountConfiguration.proto @@ -8,5 +8,4 @@ package SOS; message AccountConfiguration { repeated string pendingBackupPeers = 1; - optional bool ringUpdateFlag = 2; } diff --git a/keychain/SecureObjectSync/SOSAccountCredentials.m b/keychain/SecureObjectSync/SOSAccountCredentials.m index a2d80366..31f28a5c 100644 --- a/keychain/SecureObjectSync/SOSAccountCredentials.m +++ b/keychain/SecureObjectSync/SOSAccountCredentials.m @@ -204,21 +204,6 @@ CFDataRef SOSAccountGetCachedPassword(SOSAccount* account, CFErrorRef* error) static NSString *SOSUserCredentialAccount = @"SOSUserCredential"; static NSString *SOSUserCredentialAccessGroup = @"com.apple.security.sos-usercredential"; -__unused static void SOSAccountDeleteStashedAccountKey(SOSAccount* account) -{ - NSString *dsid = (__bridge NSString *)SOSAccountGetValue(account, kSOSDSIDKey, NULL); - if (dsid == NULL) - return; - - NSDictionary * attributes = @{ - (__bridge id)kSecClass : (__bridge id)kSecClassInternetPassword, - (__bridge id)kSecAttrAccount : SOSUserCredentialAccount, - (__bridge id)kSecAttrServer : dsid, - (__bridge id)kSecAttrAccessGroup : SOSUserCredentialAccessGroup, - }; - (void)SecItemDelete((__bridge CFDictionaryRef)attributes); -} - void SOSAccountStashAccountKey(SOSAccount* account) { OSStatus status; diff --git a/keychain/SecureObjectSync/SOSAccountPeers.m b/keychain/SecureObjectSync/SOSAccountPeers.m index 474335b3..cae09dfb 100644 --- a/keychain/SecureObjectSync/SOSAccountPeers.m +++ b/keychain/SecureObjectSync/SOSAccountPeers.m @@ -154,77 +154,6 @@ CFArrayRef SOSAccountCopyPeers(SOSAccount* account, CFErrorRef *error) { }); } -CFDataRef SOSAccountCopyAccountStateFromKeychain(CFErrorRef *error){ - CFMutableDictionaryRef query = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFTypeRef result = NULL; - CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword); - CFDictionaryAddValue(query, kSecAttrAccessGroup, CFSTR("com.apple.security.sos")); - CFDictionaryAddValue(query, kSecAttrAccessible, CFSTR("dku")); - CFDictionaryAddValue(query, kSecAttrTombstone, kCFBooleanFalse); - CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanFalse); - CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue); - - SecItemCopyMatching(query, &result); - - if(!isData(result)){ - SOSErrorCreate(kSOSErrorUnexpectedType, error, NULL, CFSTR("Expected CFData, got: %@"), result); - CFReleaseNull(result); - return NULL; - } - return result; -} - -bool SOSAccountDeleteAccountStateFromKeychain(CFErrorRef *error){ - CFMutableDictionaryRef query = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - bool result = false; - CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword); - CFDictionaryAddValue(query, kSecAttrAccessGroup, CFSTR("com.apple.security.sos")); - CFDictionaryAddValue(query, kSecAttrAccessible, CFSTR("dku")); - CFDictionaryAddValue(query, kSecAttrTombstone, kCFBooleanFalse); - CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanFalse); - - result = SecItemDelete(query); - return result; -} - -CFDataRef SOSAccountCopyEngineStateFromKeychain(CFErrorRef *error){ - CFMutableDictionaryRef query = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFTypeRef result = NULL; - CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword); - CFDictionaryAddValue(query, kSecAttrAccount, CFSTR("engine-state")); - CFDictionaryAddValue(query, kSecAttrAccessGroup, CFSTR("com.apple.security.sos")); - CFDictionaryAddValue(query, kSecAttrAccessible, CFSTR("dk")); - CFDictionaryAddValue(query, kSecAttrService, CFSTR("SOSDataSource-ak")); - CFDictionaryAddValue(query, kSecAttrTombstone, kCFBooleanFalse); - CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanFalse); - CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue); - - SecItemCopyMatching(query, &result); - - if(!isData(result)){ - SOSErrorCreate(kSOSErrorUnexpectedType, error, NULL, CFSTR("Expected CFData, got: %@"), result); - CFReleaseNull(result); - return NULL; - } - return result; -} - -bool SOSAccountDeleteEngineStateFromKeychain(CFErrorRef *error){ - CFMutableDictionaryRef query = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - bool result = false; - CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword); - CFDictionaryAddValue(query, kSecAttrAccount, CFSTR("engine-state")); - CFDictionaryAddValue(query, kSecAttrAccessGroup, CFSTR("com.apple.security.sos")); - CFDictionaryAddValue(query, kSecAttrAccessible, CFSTR("dk")); - CFDictionaryAddValue(query, kSecAttrService, CFSTR("SOSDataSource-ak")); - CFDictionaryAddValue(query, kSecAttrTombstone, kCFBooleanFalse); - CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanFalse); - - result = SecItemDelete(query); - return result; -} - - CFArrayRef SOSAccountCopyActivePeers(SOSAccount* account, CFErrorRef *error) { return SOSAccountCopySortedPeerArray(account, error, ^(SOSCircleRef circle, CFMutableArrayRef appendPeersTo) { SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) { diff --git a/keychain/SecureObjectSync/SOSAccountPersistence.m b/keychain/SecureObjectSync/SOSAccountPersistence.m index eb4b7a0f..5b63e744 100644 --- a/keychain/SecureObjectSync/SOSAccountPersistence.m +++ b/keychain/SecureObjectSync/SOSAccountPersistence.m @@ -457,10 +457,9 @@ static SOSAccount* SOSAccountCreateFromDER(CFAllocatorRef allocator, } CFReleaseNull(oldPI); + SOSAccountEnsureRecoveryRing(account); [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) { - SOSAccountEnsureRecoveryRing(account); - secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreateFromDER"); account.key_interests_need_updating = true; }]; diff --git a/keychain/SecureObjectSync/SOSAccountPriv.h b/keychain/SecureObjectSync/SOSAccountPriv.h index 798d1fb1..249a8e00 100644 --- a/keychain/SecureObjectSync/SOSAccountPriv.h +++ b/keychain/SecureObjectSync/SOSAccountPriv.h @@ -128,7 +128,7 @@ typedef void (^SOSAccountSaveBlock)(CFDataRef flattenedAccount, CFErrorRef flatt -(id) init NS_UNAVAILABLE; -(id) initWithGestalt:(CFDictionaryRef)gestalt factory:(SOSDataSourceFactoryRef)factory; -- (void)startStateMachine; +//- (void)startStateMachine; void SOSAccountAddSyncablePeerBlock(SOSAccount* a, CFStringRef ds_name, @@ -145,7 +145,6 @@ void SOSAccountAddSyncablePeerBlock(SOSAccount* a, #if OCTAGON - (void)triggerBackupForPeers:(NSArray*)backupPeer; -- (void)triggerRingUpdate; #endif @@ -206,6 +205,8 @@ bool SOSAccountHandleCircleMessage(SOSAccount* account, CF_RETURNS_RETAINED CFDictionaryRef SOSAccountHandleRetirementMessages(SOSAccount* account, CFDictionaryRef circle_retirement_messages, CFErrorRef *error); +void SOSAccountRecordRetiredPeersInCircle(SOSAccount* account); + bool SOSAccountHandleUpdateCircle(SOSAccount* account, SOSCircleRef prospective_circle, bool writeUpdate, @@ -271,10 +272,10 @@ void SOSAccountPurgeIdentity(SOSAccount*); bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error); bool sosAccountLeaveCircleWithAnalytics(SOSAccount* account, SOSCircleRef circle, NSData* parentData, CFErrorRef* error); -bool sosAccountLeaveRing(SOSAccount* account, SOSRingRef ring, CFErrorRef* error); bool SOSAccountForEachRing(SOSAccount* account, SOSRingRef (^action)(CFStringRef name, SOSRingRef ring)); bool SOSAccountUpdateBackUp(SOSAccount* account, CFStringRef viewname, CFErrorRef *error); void SOSAccountEnsureRecoveryRing(SOSAccount* account); +bool SOSAccountEnsureInBackupRings(SOSAccount* account); bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error); @@ -298,15 +299,11 @@ bool SOSAccountClearValue(SOSAccount* account, CFStringRef key, CFErrorRef *erro CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error); bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error); -bool SOSAccountAddEscrowRecords(SOSAccount* account, CFStringRef dsid, CFDictionaryRef record, CFErrorRef *error); void SOSAccountRemoveRing(SOSAccount* a, CFStringRef ringName); SOSRingRef SOSAccountCopyRingNamed(SOSAccount* a, CFStringRef ringName, CFErrorRef *error); -SOSRingRef SOSAccountRingCreateForName(SOSAccount* a, CFStringRef ringName, CFErrorRef *error); bool SOSAccountUpdateRingFromRemote(SOSAccount* account, SOSRingRef newRing, CFErrorRef *error); bool SOSAccountUpdateRing(SOSAccount* account, SOSRingRef newRing, CFErrorRef *error); bool SOSAccountRemoveBackupPeers(SOSAccount* account, CFArrayRef peerIDs, CFErrorRef *error); -bool SOSAccountResetRing(SOSAccount* account, CFStringRef ringName, CFErrorRef *error); -bool SOSAccountCheckPeerAvailability(SOSAccount* account, CFErrorRef *error); bool SOSAccountUpdateNamedRing(SOSAccount* account, CFStringRef ringName, CFErrorRef *error, SOSRingRef (^create)(CFStringRef ringName, CFErrorRef *error), SOSRingRef (^copyModified)(SOSRingRef existing, CFErrorRef *error)); @@ -322,12 +319,7 @@ bool SOSAccountUpdateBackupRing(SOSAccount* account, CFStringRef viewName, CFEr // Security tool test/debug functions // bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error); -CFDataRef SOSAccountCopyAccountStateFromKeychain(CFErrorRef *error); -bool SOSAccountDeleteAccountStateFromKeychain(CFErrorRef *error); -CFDataRef SOSAccountCopyEngineStateFromKeychain(CFErrorRef *error); -bool SOSAccountDeleteEngineStateFromKeychain(CFErrorRef *error); -bool SOSAccountIsNew(SOSAccount* account, CFErrorRef *error); bool SOSAccountCheckForAlwaysOnViews(SOSAccount* account); // UUID, no setter just getter and ensuring value. void SOSAccountEnsureUUID(SOSAccount* account); @@ -351,7 +343,6 @@ NSArray* SOSAccountSortTLKS(NSArray* tlks); #endif bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error); -bool SOSAccountPopulateKVSWithBadKeys(SOSAccount* account, CFErrorRef* error); @end diff --git a/keychain/SecureObjectSync/SOSAccountRecovery.m b/keychain/SecureObjectSync/SOSAccountRecovery.m index 39d74993..cd272013 100644 --- a/keychain/SecureObjectSync/SOSAccountRecovery.m +++ b/keychain/SecureObjectSync/SOSAccountRecovery.m @@ -196,8 +196,6 @@ static void sosRecoveryAlertAndNotify(SOSAccount* account, SOSRecoveryKeyBagRef } void SOSAccountEnsureRecoveryRing(SOSAccount* account) { - dispatch_assert_queue(account.queue); - static SOSRecoveryKeyBagRef oldRingRKBG = NULL; SOSRecoveryKeyBagRef acctRKBG = SOSAccountCopyRecoveryKeyBagEntry(kCFAllocatorDefault, account, NULL); if(!CFEqualSafe(acctRKBG, oldRingRKBG)) { diff --git a/keychain/SecureObjectSync/SOSAccountRings.m b/keychain/SecureObjectSync/SOSAccountRings.m index 30e18ed5..2ebcd729 100644 --- a/keychain/SecureObjectSync/SOSAccountRings.m +++ b/keychain/SecureObjectSync/SOSAccountRings.m @@ -34,72 +34,12 @@ const CFStringRef kSOSRingOtherSyncable = CFSTR("Ring-OtherSyncable"); const CFStringRef kSOSRingKey = CFSTR("trusted_rings"); -static CFSetRef allCurrentRings(void) { - static dispatch_once_t dot; - static CFMutableSetRef allRings = NULL; - dispatch_once(&dot, ^{ - allRings = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks); - CFSetAddValue(allRings, kSOSRingCircleV2); - CFSetAddValue(allRings, kSOSRingKeychainV0); - CFSetAddValue(allRings, kSOSRingPCSHyperion); - CFSetAddValue(allRings, kSOSRingPCSBladerunner); - CFSetAddValue(allRings, kSOSRingPCSLiverpool); - CFSetAddValue(allRings, kSOSRingPCSEscrow); - CFSetAddValue(allRings, kSOSRingPCSPianoMover); - CFSetAddValue(allRings, kSOSRingPCSNotes); - CFSetAddValue(allRings, kSOSRingPCSFeldspar); - CFSetAddValue(allRings, kSOSRingAppleTV); - CFSetAddValue(allRings, kSOSRingHomeKit); - CFSetAddValue(allRings, kSOSRingWifi); - CFSetAddValue(allRings, kSOSRingPasswords); - CFSetAddValue(allRings, kSOSRingCreditCards); - CFSetAddValue(allRings, kSOSRingiCloudIdentity); - CFSetAddValue(allRings, kSOSRingOtherSyncable); - }); - return allRings; -} - typedef struct ringDef_t { CFStringRef name; SOSRingType ringType; bool dropWhenLeaving; } ringDef, *ringDefPtr; -static ringDefPtr getRingDef(CFStringRef ringName) { - static ringDef retval; - - // Defaults - retval.name = ringName; - retval.dropWhenLeaving = true; - retval.ringType = kSOSRingEntropyKeyed; - - - if(CFSetContainsValue(allCurrentRings(), ringName)) { - retval.ringType = kSOSRingBase; - retval.dropWhenLeaving = false; - } else { - retval.ringType = kSOSRingBackup; - retval.dropWhenLeaving = false; - } - return &retval; -} - -__unused static inline void SOSAccountRingForEachRingMatching(SOSAccount* a, void (^action)(SOSRingRef ring), bool (^condition)(SOSRingRef ring)) { - CFSetRef allRings = allCurrentRings(); - CFSetForEach(allRings, ^(const void *value) { - CFStringRef ringName = (CFStringRef) value; - SOSRingRef ring = [a.trust copyRing:ringName err:NULL]; - if (condition(ring)) { - action(ring); - } - CFReleaseNull(ring); - }); -} - - - - - static void SOSAccountSetRings(SOSAccount* a, CFMutableDictionaryRef newrings){ SOSAccountTrustClassic *trust = a.trust; [trust.expansion setObject:(__bridge NSMutableDictionary*)newrings forKey:(__bridge NSString* _Nonnull)(kSOSRingKey)]; @@ -175,14 +115,6 @@ SOSRingRef SOSAccountCopyRingNamed(SOSAccount* a, CFStringRef ringName, CFErrorR return found; } -/* Unused? */ -SOSRingRef SOSAccountRingCreateForName(SOSAccount* a, CFStringRef ringName, CFErrorRef *error) { - ringDefPtr rdef = getRingDef(ringName); - if(!rdef) return NULL; - SOSRingRef retval = SOSRingCreate(rdef->name, (__bridge CFStringRef) a.peerID, rdef->ringType, error); - return retval; -} - bool SOSAccountUpdateRingFromRemote(SOSAccount* account, SOSRingRef newRing, CFErrorRef *error) { require_quiet(SOSAccountHasPublicKey(account, error), errOut); diff --git a/keychain/SecureObjectSync/SOSAccountTransaction.m b/keychain/SecureObjectSync/SOSAccountTransaction.m index 168d096d..1c014b29 100644 --- a/keychain/SecureObjectSync/SOSAccountTransaction.m +++ b/keychain/SecureObjectSync/SOSAccountTransaction.m @@ -277,10 +277,18 @@ static void SOSViewsSetCachedStatus(SOSAccount *account) { } if(self.account.circle_rings_retirements_need_attention){ - self.account.circle_rings_retirements_need_attention = false; -#if OCTAGON - [self.account triggerRingUpdate]; -#endif + SOSAccountRecordRetiredPeersInCircle(self.account); + + SOSAccountEnsureRecoveryRing(self.account); + SOSAccountEnsureInBackupRings(self.account); + + CFErrorRef localError = NULL; + if(![self.account.circle_transport flushChanges:&localError]){ + secerror("flush circle failed %@", localError); + } + CFReleaseSafe(localError); + + notifyEngines = true; } if (notifyEngines) { @@ -297,6 +305,7 @@ static void SOSViewsSetCachedStatus(SOSAccount *account) { SOSUpdateKeyInterest(self.account); } + self.account.circle_rings_retirements_need_attention = false; self.account.engine_peer_state_needs_repair = false; [self.account flattenToSaveBlock]; diff --git a/keychain/SecureObjectSync/SOSAccountUpdate.m b/keychain/SecureObjectSync/SOSAccountUpdate.m index f24e6713..9c0cad82 100644 --- a/keychain/SecureObjectSync/SOSAccountUpdate.m +++ b/keychain/SecureObjectSync/SOSAccountUpdate.m @@ -162,8 +162,6 @@ bool SOSAccountSyncingV0(SOSAccount* account) { void SOSAccountNotifyEngines(SOSAccount* account) { - dispatch_assert_queue(account.queue); - SOSAccountTrustClassic *trust = account.trust; SOSFullPeerInfoRef identity = trust.fullPeerInfo; SOSCircleRef circle = trust.trustedCircle; diff --git a/keychain/SecureObjectSync/SOSBackupInformation.m b/keychain/SecureObjectSync/SOSBackupInformation.m deleted file mode 100644 index 8b9b9177..00000000 --- a/keychain/SecureObjectSync/SOSBackupInformation.m +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2016 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -// -// SOSBackupInformation.c -// Security -// - -#include "SOSBackupInformation.h" -#include "SOSAccountPriv.h" -#include -#include - -const CFStringRef kSOSBkpInfoStatus = CFSTR("BkpInfoStatus"); -const CFStringRef kSOSBkpInfoBSKB = CFSTR("BkpInfoBSKB"); -const CFStringRef kSOSBkpInfoRKBG = CFSTR("BkpInfoRKBG"); - -CFDictionaryRef SOSBackupInformation(SOSAccountTransaction* txn, CFErrorRef *error) { - CFNumberRef status = NULL; - int ibkpInfoStatus; - __block bool havebskbcontent = false; - SOSAccount *a = txn.account; - - CFMutableDictionaryRef retval = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - require_action_quiet(txn && a, errOut, ibkpInfoStatus = noTxnorAcct); - require_action_quiet(retval, errOut, ibkpInfoStatus = noAlloc); - require_action_quiet(txn, errOut, ibkpInfoStatus = noTxnorAcct); - - require_action_quiet(a.accountKey && a.accountKeyIsTrusted, errOut, ibkpInfoStatus = noTrustedPubKey); - CFMutableDictionaryRef bskbders = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - SOSAccountForEachRing(a, ^SOSRingRef(CFStringRef name, SOSRingRef ring) { - if(SOSRingGetType(ring) == kSOSRingBackup) { - CFDataRef bskbder = SOSRingGetPayload(ring, NULL); - if(bskbder) CFDictionaryAddValue(bskbders, name, bskbder); - havebskbcontent = true; - } else if(SOSRingGetType(ring) == kSOSRingRecovery) { - CFDataRef rkbgder = SOSRingGetPayload(ring, NULL); - if(rkbgder) CFDictionaryAddValue(retval, kSOSBkpInfoRKBG, rkbgder); - } - return NULL; // we're reporting - never changing the ring - }); - if(havebskbcontent) { - ibkpInfoStatus = noError; - CFDictionaryAddValue(retval, kSOSBkpInfoBSKB, bskbders); - } else { - ibkpInfoStatus = noBSKBs; - } - CFReleaseNull(bskbders); - -errOut: - status = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &ibkpInfoStatus); - CFDictionaryAddValue(retval, kSOSBkpInfoStatus, status); - CFReleaseNull(status); - return retval; -} diff --git a/keychain/SecureObjectSync/SOSCircle.c b/keychain/SecureObjectSync/SOSCircle.c index 68d47e37..f953e859 100644 --- a/keychain/SecureObjectSync/SOSCircle.c +++ b/keychain/SecureObjectSync/SOSCircle.c @@ -581,11 +581,6 @@ static inline bool SOSCircleIsDegenerateReset(SOSCircleRef deGenCircle){ return SOSCircleHasDegenerateGeneration(deGenCircle) && SOSCircleIsEmpty(deGenCircle); } - -__unused static inline bool SOSCircleIsResignOffering(SOSCircleRef circle, SecKeyRef pubkey) { - return SOSCircleCountActiveValidPeers(circle, pubkey) == 1; -} - static inline SOSConcordanceStatus GetSignersStatus(SOSCircleRef signers_circle, SOSCircleRef status_circle, SecKeyRef user_pubKey, SOSPeerInfoRef exclude, CFErrorRef *error) { CFStringRef excluded_id = exclude ? SOSPeerInfoGetPeerID(exclude) : NULL; @@ -831,8 +826,9 @@ static CFStringRef SOSCircleCopyFormatDescription(CFTypeRef aObj, CFDictionaryRe } CFStringRef SOSCircleGetName(SOSCircleRef circle) { - assert(circle); - assert(circle->name); + if(!circle || !circle->name) { + return NULL; + } return circle->name; } diff --git a/keychain/SecureObjectSync/SOSCloudCircle.h b/keychain/SecureObjectSync/SOSCloudCircle.h index 46bbae1e..0f3df9b6 100644 --- a/keychain/SecureObjectSync/SOSCloudCircle.h +++ b/keychain/SecureObjectSync/SOSCloudCircle.h @@ -160,14 +160,6 @@ bool SOSCCRegisterUserCredentials(CFStringRef user_label, CFDataRef user_passwor bool SOSCCWaitForInitialSync(CFErrorRef* error); bool SOSCCWaitForInitialSyncWithAnalytics(CFDataRef parentEvent, CFErrorRef* error); -/*! - @function SOSCCCopyYetToSyncViewsList - @abstract returns views not yet synced - @param error error to fill in if we have one - @return List of view names that we haven't synced yet. - */ -CFArrayRef SOSCCCopyYetToSyncViewsList(CFErrorRef* error); - /*! @function SOSCCCanAuthenticate @abstract Determines whether we currently have valid credentials to authenticate a circle operation. @@ -268,14 +260,6 @@ bool SOSCCRequestToJoinCircleWithAnalytics(CFDataRef parentEvent, CFErrorRef* er bool SOSCCRequestToJoinCircleAfterRestore(CFErrorRef* error); bool SOSCCRequestToJoinCircleAfterRestoreWithAnalytics(CFDataRef parentEvent, CFErrorRef* error); -/*! - @function SOSCCRequestEnsureFreshParameters - @abstract function to help debug problems with EnsureFreshParameters - @param error What went wrong if we tried to refresh parameters - @result true if we successfully retrieved fresh parameters. False if we failed. -*/ -bool SOSCCRequestEnsureFreshParameters(CFErrorRef* error); - /*! @function SOSCCAccountSetToNew @abstract reset account to new @@ -345,18 +329,6 @@ bool SOSCCLoggedOutOfAccount(CFErrorRef* error); */ bool SOSCCBailFromCircle_BestEffort(uint64_t limit_in_seconds, CFErrorRef* error); -/*! - @function SOSCCSignedOut - @abstract Attempts to publish a retirement ticket for the current device. - @param immediate If we should remove the device immediately or to leave the circle with best effort. - @param error What went wrong trying to remove ourselves. - @result true if we posted the ticket. False if there was an error. - @discussion This attempts to post a retirement ticket that should - result in other devices removing this device from the circle. It does so - with a 5 second timeout or immediately. - */ -bool SOSCCSignedOut(bool immediate, CFErrorRef* error); - /*! @function SOSCCCopyApplicantPeerInfo @abstract Get the list of peers wishing admittance. @@ -490,14 +462,6 @@ enum DepartureReason SOSCCGetLastDepartureReason(CFErrorRef *error); bool SOSCCSetLastDepartureReason(enum DepartureReason reason, CFErrorRef *error); -/*! - @function SOSCCGetIncompatibilityInfo - @abstract Returns the information (string, hopefully URL) that will lead to an explanation of why you have an incompatible circle. - @param error What went wrong if we returned NULL. - */ -CFStringRef SOSCCCopyIncompatibilityInfo(CFErrorRef *error); - - /* Views @@ -653,34 +617,6 @@ CFDataRef SOSCopyDeviceBackupPublicKey(CFDataRef entropy, CFErrorRef *error); */ bool SOSCCRegisterSingleRecoverySecret(CFDataRef aks_bag, bool forV0Only, CFErrorRef *error); - -/*! - @function SOSCCIsThisDeviceLastBackup - @param error Why this query can't be accepted. - @result true if this is the last backup device, false otherwise. - */ - -bool SOSCCIsThisDeviceLastBackup(CFErrorRef *error); - -/*! - @function SOSCCSetEscrowRecord - @param escrow_label Account label - @param tries Number of attempts - @param error What went wrong trying to set the escrow label - @result true if we saved the escrow record, false if we had an error - @discussion persist escrow records in the account object or the peer info - */ -bool SOSCCSetEscrowRecord(CFStringRef escrow_label, uint64_t tries, CFErrorRef *error); - -/*! - @function SOSCCCopyEscrowRecord - @param error What went wrong trying to set the escrow label - @result dictionary of the escrow record, false if we had an error, dictionary will be of format: [account label: ], dictionary will contain (ex): "Burned Recovery Attempt Attestation Date" = "[2015-08-19 15:21]"; - "Burned Recovery Attempt Count" = 8; - @discussion for debugging - retrieve the escrow record - */ -CFDictionaryRef SOSCCCopyEscrowRecord(CFErrorRef *error); - /*! @function SOSCCCopyApplication @param error What went wrong getting the applicant peerInfo. diff --git a/keychain/SecureObjectSync/SOSCloudCircle.m b/keychain/SecureObjectSync/SOSCloudCircle.m index d1af2e1a..a186bb76 100644 --- a/keychain/SecureObjectSync/SOSCloudCircle.m +++ b/keychain/SecureObjectSync/SOSCloudCircle.m @@ -142,65 +142,6 @@ static bool sfsigninanalytics_bool_error_request(enum SecXPCOperation op, CFData return result; } -static bool cfstring_to_error_request(enum SecXPCOperation op, CFStringRef string, CFErrorRef* error) -{ - __block bool result = false; - - secdebug("sosops","enter - operation: %d", op); - securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) { - xpc_object_t xString = _CFXPCCreateXPCObjectFromCFObject(string); - bool success = false; - if (xString){ - xpc_dictionary_set_value(message, kSecXPCKeyString, xString); - success = true; - xString = nil; - } - return success; - }, ^bool(xpc_object_t response, __unused CFErrorRef *error) { - result = xpc_dictionary_get_bool(response, kSecXPCKeyResult); - return result; - }); - return result; -} - -static SOSRingStatus cfstring_to_uint64_request(enum SecXPCOperation op, CFStringRef string, CFErrorRef* error) -{ - __block bool result = false; - - secdebug("sosops","enter - operation: %d", op); - securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) { - xpc_object_t xString = _CFXPCCreateXPCObjectFromCFObject(string); - bool success = false; - if (xString){ - xpc_dictionary_set_value(message, kSecXPCKeyString, xString); - success = true; - xString = nil; - } - return success; - }, ^bool(xpc_object_t response, __unused CFErrorRef *error) { - result = xpc_dictionary_get_int64(response, kSecXPCKeyResult); - return result; - }); - return result; -} - -static CFStringRef simple_cfstring_error_request(enum SecXPCOperation op, CFErrorRef* error) -{ - __block CFStringRef result = NULL; - - secdebug("sosops","enter - operation: %d", op); - securityd_send_sync_and_do(op, error, NULL, ^bool(xpc_object_t response, __unused CFErrorRef *error) { - const char *c_string = xpc_dictionary_get_string(response, kSecXPCKeyResult); - - if (c_string) { - result = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)c_string, strlen(c_string), kCFStringEncodingUTF8, false); - } - - return c_string != NULL; - }); - return result; -} - static bool simple_bool_error_request(enum SecXPCOperation op, CFErrorRef* error) { __block bool result = false; @@ -270,33 +211,6 @@ static CFSetRef cfset_cfset_to_cfset_error_request(enum SecXPCOperation op, CFSe return result; } - -static bool escrow_to_bool_error_request(enum SecXPCOperation op, CFStringRef escrow_label, uint64_t tries, CFErrorRef* error) -{ - __block bool result = false; - - securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) { - - bool success = false; - xpc_object_t xEscrowLabel = _CFXPCCreateXPCObjectFromCFObject(escrow_label); - if (xEscrowLabel){ - xpc_dictionary_set_value(message, kSecXPCKeyEscrowLabel, xEscrowLabel); - success = true; - xEscrowLabel = nil; - } - if(tries){ - xpc_dictionary_set_int64(message, kSecXPCKeyTriesLabel, tries); - success = true; - } - - return success; - }, ^bool(xpc_object_t response, __unused CFErrorRef *error) { - result = xpc_dictionary_get_bool(response, kSecXPCKeyResult); - return result; - }); - return result; -} - static CF_RETURNS_RETAINED CFArrayRef simple_array_error_request(enum SecXPCOperation op, CFErrorRef* error) { __block CFArrayRef result = NULL; @@ -335,28 +249,6 @@ static CF_RETURNS_RETAINED CFArrayRef der_array_error_request(enum SecXPCOperati return result; } -static CFDictionaryRef strings_to_dictionary_error_request(enum SecXPCOperation op, CFErrorRef* error) -{ - __block CFDictionaryRef result = NULL; - - secdebug("sosops","enter - operation: %d", op); - - if (securityd_send_sync_and_do(op, error, NULL, ^bool(xpc_object_t response, CFErrorRef *error) { - xpc_object_t temp_result = xpc_dictionary_get_value(response, kSecXPCKeyResult); - if(temp_result) - result = _CFXPCCreateCFObjectFromXPCObject(temp_result); - return result != NULL; - })){ - - if (!isDictionary(result)) { - SOSErrorCreate(kSOSErrorUnexpectedType, error, NULL, CFSTR("Expected dictionary, got: %@"), result); - CFReleaseNull(result); - } - - } - return result; -} - static int simple_int_error_request(enum SecXPCOperation op, CFErrorRef* error) { __block int result = 0; @@ -578,7 +470,7 @@ static bool uint64_t_to_bool_error_request(enum SecXPCOperation op, CFErrorRef* error) { __block bool result = false; - + securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) { xpc_dictionary_set_uint64(message, kSecXPCLimitInMinutes, number); return true; @@ -586,46 +478,6 @@ static bool uint64_t_to_bool_error_request(enum SecXPCOperation op, result = xpc_dictionary_get_bool(response, kSecXPCKeyResult); return result; }); - - return result; -} - -static bool cfstring_and_cfdata_to_cfdata_cfdata_error_request(enum SecXPCOperation op, CFStringRef viewName, CFDataRef input, CFDataRef* data, CFDataRef* data2, CFErrorRef* error) { - secdebug("sosops", "enter - operation: %d", op); - __block bool result = false; - securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) { - xpc_object_t xviewname = _CFXPCCreateXPCObjectFromCFObject(viewName); - xpc_object_t xinput = _CFXPCCreateXPCObjectFromCFObject(input); - bool success = false; - if (xviewname && xinput){ - xpc_dictionary_set_value(message, kSecXPCKeyViewName, xviewname); - xpc_dictionary_set_value(message, kSecXPCData, xinput); - success = true; - xviewname = nil; - xinput = nil; - } - return success; - }, ^bool(xpc_object_t response, __unused CFErrorRef *error) { - result = xpc_dictionary_get_bool(response, kSecXPCKeyResult); - - xpc_object_t temp_result = xpc_dictionary_get_value(response, kSecXPCData); - if ((NULL != temp_result) && data) { - *data = _CFXPCCreateCFObjectFromXPCObject(temp_result); - } - temp_result = xpc_dictionary_get_value(response, kSecXPCKeyKeybag); - if ((NULL != temp_result) && data2) { - *data2 = _CFXPCCreateCFObjectFromXPCObject(temp_result); - } - - return result; - }); - - if (data &&!isData(*data)) { - SOSErrorCreate(kSOSErrorUnexpectedType, error, NULL, CFSTR("Expected CFData, got: %@"), *data); - } - if (data2 &&!isData(*data2)) { - SOSErrorCreate(kSOSErrorUnexpectedType, error, NULL, CFSTR("Expected CFData, got: %@"), *data2); - } return result; } @@ -731,16 +583,6 @@ bool SOSCCAccountHasPublicKey(CFErrorRef *error) } -bool SOSCCAccountIsNew(CFErrorRef *error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_bool_api(^{ - do_if_registered(soscc_AccountIsNew, error); - - return simple_bool_error_request(kSecXPCOpAccountIsNew, error); - }, NULL) -} - bool SOSCCWaitForInitialSyncWithAnalytics(CFDataRef parentEvent, CFErrorRef* error) { sec_trace_enter_api(NULL); @@ -761,75 +603,6 @@ bool SOSCCWaitForInitialSync(CFErrorRef* error) }, NULL) } -CFArrayRef SOSCCCopyYetToSyncViewsList(CFErrorRef* error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_api(CFArrayRef, ^{ - do_if_registered(soscc_CopyYetToSyncViewsList, error); - - return simple_array_error_request(kSecXPCOpCopyYetToSyncViews, error); - }, NULL) -} - -bool SOSCCRequestEnsureFreshParameters(CFErrorRef* error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_bool_api(^{ - do_if_registered(soscc_RequestEnsureFreshParameters, error); - - return simple_bool_error_request(kSecXPCOpRequestEnsureFreshParameters, error); - }, NULL) -} - -CFStringRef SOSCCGetAllTheRings(CFErrorRef *error){ - sec_trace_enter_api(NULL); - sec_trace_return_api(CFStringRef, ^{ - do_if_registered(soscc_GetAllTheRings, error); - - - return simple_cfstring_error_request(kSecXPCOpGetAllTheRings, error); - }, NULL) -} -bool SOSCCApplyToARing(CFStringRef ringName, CFErrorRef* error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_bool_api(^{ - do_if_registered(soscc_ApplyToARing, ringName, error); - - return cfstring_to_error_request(kSecXPCOpApplyToARing, ringName, error); - }, NULL) -} - -bool SOSCCWithdrawlFromARing(CFStringRef ringName, CFErrorRef* error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_bool_api(^{ - do_if_registered(soscc_WithdrawlFromARing, ringName, error); - - return cfstring_to_error_request(kSecXPCOpWithdrawlFromARing, ringName, error); - }, NULL) -} - -SOSRingStatus SOSCCRingStatus(CFStringRef ringName, CFErrorRef* error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_api(SOSRingStatus, ^{ - do_if_registered(soscc_RingStatus, ringName, error); - - return cfstring_to_uint64_request(kSecXPCOpRingStatus, ringName, error); - }, CFSTR("SOSCCStatus=%d")) -} - -bool SOSCCEnableRing(CFStringRef ringName, CFErrorRef* error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_bool_api(^{ - do_if_registered(soscc_EnableRing, ringName, error); - - return cfstring_to_error_request(kSecXPCOpEnableRing, ringName, error); - }, NULL) -} - bool SOSCCAccountSetToNew(CFErrorRef *error) { secwarning("SOSCCAccountSetToNew called"); @@ -927,22 +700,11 @@ bool SOSCCBailFromCircle_BestEffort(uint64_t limit_in_seconds, CFErrorRef* error sec_trace_enter_api(NULL); sec_trace_return_bool_api(^{ do_if_registered(soscc_BailFromCircle, limit_in_seconds, error); - + return uint64_t_to_bool_error_request(kSecXPCOpBailFromCircle, limit_in_seconds, error); }, NULL) } -bool SOSCCSignedOut(bool immediate, CFErrorRef* error) -{ - uint64_t limit = strtoul(optarg, NULL, 10); - - if(immediate) - return SOSCCRemoveThisDeviceFromCircle(error); - else - return SOSCCBailFromCircle_BestEffort(limit, error); - -} - CFArrayRef SOSCCCopyPeerPeerInfo(CFErrorRef* error) { sec_trace_enter_api(NULL); @@ -1032,43 +794,6 @@ CFArrayRef SOSCCCopyViewUnawarePeerInfo(CFErrorRef* error) }, CFSTR("return=%@")); } -CFDataRef SOSCCCopyAccountState(CFErrorRef* error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_api(CFDataRef, ^{ - do_if_registered(soscc_CopyAccountState, error); - - return data_to_error_request(kSecXPCOpCopyAccountData, error); - }, CFSTR("return=%@")); -} - -bool SOSCCDeleteAccountState(CFErrorRef *error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_api(bool, ^{ - do_if_registered(soscc_DeleteAccountState, error); - return simple_bool_error_request(kSecXPCOpDeleteAccountData, error); - }, NULL); -} -CFDataRef SOSCCCopyEngineData(CFErrorRef* error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_api(CFDataRef, ^{ - do_if_registered(soscc_CopyEngineData, error); - - return data_to_error_request(kSecXPCOpCopyEngineData, error); - }, CFSTR("return=%@")); -} - -bool SOSCCDeleteEngineState(CFErrorRef *error) -{ - sec_trace_enter_api(NULL); - sec_trace_return_api(bool, ^{ - do_if_registered(soscc_DeleteEngineState, error); - return simple_bool_error_request(kSecXPCOpDeleteEngineData, error); - }, NULL); -} - SOSPeerInfoRef SOSCCCopyMyPeerInfo(CFErrorRef *error) { sec_trace_enter_api(NULL); @@ -1412,15 +1137,6 @@ bool SOSCCSetLastDepartureReason(enum DepartureReason reason, CFErrorRef *error) }, NULL) } -CFStringRef SOSCCCopyIncompatibilityInfo(CFErrorRef* error) { - sec_trace_enter_api(NULL); - sec_trace_return_api(CFStringRef, ^{ - do_if_registered(soscc_CopyIncompatibilityInfo, error); - - return simple_cfstring_error_request(kSecXPCOpCopyIncompatibilityInfo, error); - }, NULL) -} - bool SOSCCProcessEnsurePeerRegistration(CFErrorRef* error){ secnotice("updates", "enter SOSCCProcessEnsurePeerRegistration"); sec_trace_enter_api(NULL); @@ -1667,47 +1383,6 @@ bool SOSCCIsContinuityUnlockSyncing(void) { return sosIsViewSetSyncing(sizeof(views)/sizeof(views[0]), views); } - -bool SOSCCSetEscrowRecord(CFStringRef escrow_label, uint64_t tries, CFErrorRef *error ){ - secnotice("escrow", "enter SOSCCSetEscrowRecord"); - sec_trace_enter_api(NULL); - sec_trace_return_bool_api(^{ - do_if_registered(soscc_SetEscrowRecords, escrow_label, tries, error); - - return escrow_to_bool_error_request(kSecXPCOpSetEscrowRecord, escrow_label, tries, error); - }, NULL) -} - -CFDictionaryRef SOSCCCopyEscrowRecord(CFErrorRef *error){ - secnotice("escrow", "enter SOSCCCopyEscrowRecord"); - sec_trace_enter_api(NULL); - sec_trace_return_api(CFDictionaryRef, ^{ - do_if_registered(soscc_CopyEscrowRecords, error); - - return strings_to_dictionary_error_request(kSecXPCOpGetEscrowRecord, error); - }, CFSTR("return=%@")) - -} - -CFDictionaryRef SOSCCCopyBackupInformation(CFErrorRef *error) { - secnotice("escrow", "enter SOSCCCopyBackupInformation"); - sec_trace_enter_api(NULL); - sec_trace_return_api(CFDictionaryRef, ^{ - do_if_registered(soscc_CopyBackupInformation, error); - return strings_to_dictionary_error_request(kSecXPCOpCopyBackupInformation, error); - }, CFSTR("return=%@")) -} - -bool SOSWrapToBackupSliceKeyBagForView(CFStringRef viewName, CFDataRef input, CFDataRef* output, CFDataRef* bskbEncoded, CFErrorRef* error) { - sec_trace_enter_api(NULL); - sec_trace_return_bool_api(^{ - do_if_registered(sosbskb_WrapToBackupSliceKeyBagForView, viewName, input, output, bskbEncoded, error); - - return cfstring_and_cfdata_to_cfdata_cfdata_error_request(kSecXPCOpWrapToBackupSliceKeyBagForView, viewName, input, output, bskbEncoded, error); - }, NULL) -} - - SOSPeerInfoRef SOSCCCopyApplication(CFErrorRef *error) { secnotice("hsa2PB", "enter SOSCCCopyApplication applicant"); sec_trace_enter_api(NULL); @@ -1730,18 +1405,6 @@ bool SOSCCCleanupKVSKeys(CFErrorRef *error) { return false; } -bool SOSCCTestPopulateKVSWithBadKeys(CFErrorRef *error) { - secnotice("cleanup-keys", "enter SOSCCPopulateKVSWithBadKeys"); - sec_trace_enter_api(NULL); - sec_trace_return_bool_api(^{ - do_if_registered(soscc_SOSCCTestPopulateKVSWithBadKeys, error); - - return simple_bool_error_request(kSecXPCOpPopulateKVS, error); - }, NULL) - - return false; -} - CFDataRef SOSCCCopyCircleJoiningBlob(SOSPeerInfoRef applicant, CFErrorRef *error) { secnotice("hsa2PB", "enter SOSCCCopyCircleJoiningBlob approver"); sec_trace_enter_api(NULL); @@ -1776,16 +1439,6 @@ bool SOSCCJoinWithCircleJoiningBlob(CFDataRef joiningBlob, PiggyBackProtocolVers }, NULL) } -bool SOSCCIsThisDeviceLastBackup(CFErrorRef *error) { - secnotice("peer", "enter SOSCCIsThisDeviceLastBackup"); - sec_trace_enter_api(NULL); - sec_trace_return_bool_api(^{ - do_if_registered(soscc_IsThisDeviceLastBackup, error); - - return simple_bool_error_request(kSecXPCOpIsThisDeviceLastBackup, error); - }, NULL) -} - CFBooleanRef SOSCCPeersHaveViewsEnabled(CFArrayRef viewNames, CFErrorRef *error) { secnotice("view-enabled", "enter SOSCCPeersHaveViewsEnabled"); sec_trace_enter_api(NULL); diff --git a/keychain/SecureObjectSync/SOSCloudCircleInternal.h b/keychain/SecureObjectSync/SOSCloudCircleInternal.h index 36e5dfbd..b174896f 100644 --- a/keychain/SecureObjectSync/SOSCloudCircleInternal.h +++ b/keychain/SecureObjectSync/SOSCloudCircleInternal.h @@ -56,7 +56,6 @@ bool SOSCCPurgeUserCredentials(CFErrorRef* error); CFStringRef SOSCCGetStatusDescription(SOSCCStatus status); CFStringRef SOSCCGetViewResultDescription(SOSViewResultCode vrc); bool SOSCCAccountHasPublicKey(CFErrorRef *error); -bool SOSCCAccountIsNew(CFErrorRef *error); /*! @function SOSCCProcessSyncWithPeers @@ -75,14 +74,6 @@ SyncWithAllPeersReason SOSCCProcessSyncWithAllPeers(CFErrorRef* error); bool SOSCCProcessEnsurePeerRegistration(CFErrorRef* error); -//Rings -CFStringRef SOSCCGetAllTheRings(CFErrorRef *error); - -bool SOSCCApplyToARing(CFStringRef ringName, CFErrorRef* error); -bool SOSCCWithdrawlFromARing(CFStringRef ringName, CFErrorRef* error); -int SOSCCRingStatus(CFStringRef ringName, CFErrorRef* error); // TODO: this returns SOSRingStatus -bool SOSCCEnableRing(CFStringRef ringName, CFErrorRef* error); - bool SOSCCCleanupKVSKeys(CFErrorRef *error); @@ -93,27 +84,10 @@ bool SOSCCCleanupKVSKeys(CFErrorRef *error); */ SOSPeerInfoRef SOSCCCopyMyPeerInfo(CFErrorRef *error); -/*! - @function SOSWrapToBackupSliceKeyBagForView - @abstract Encrypts the given plaintext, and wraps the encryption key to the backup slice keybag for this view - @param viewName The view to wrap to - @param input The plaintext to encrypt - @param output The ciphertext - @param bskbEncoded The encoded backup slice keybag used to wrap the data - @param error What went wrong if we returned false - */ -bool SOSWrapToBackupSliceKeyBagForView(CFStringRef viewName, CFDataRef input, CFDataRef* output, CFDataRef* bskbEncoded, CFErrorRef* error); - // // Security Tool calls // -CFDataRef SOSCCCopyAccountState(CFErrorRef* error); -bool SOSCCDeleteAccountState(CFErrorRef *error); -CFDataRef SOSCCCopyEngineData(CFErrorRef* error); -bool SOSCCDeleteEngineState(CFErrorRef *error); CFDataRef SOSCCCopyRecoveryPublicKey(CFErrorRef *error); -CFDictionaryRef SOSCCCopyBackupInformation(CFErrorRef *error); -bool SOSCCTestPopulateKVSWithBadKeys(CFErrorRef *error); CFDataRef SOSCCCopyInitialSyncData(SOSInitialSyncFlags flags, CFErrorRef *error); void SOSCCForEachEngineStateAsStringFromArray(CFArrayRef states, void (^block)(CFStringRef oneStateString)); diff --git a/keychain/SecureObjectSync/SOSControlHelper.m b/keychain/SecureObjectSync/SOSControlHelper.m index aaf58c28..d700b69e 100644 --- a/keychain/SecureObjectSync/SOSControlHelper.m +++ b/keychain/SecureObjectSync/SOSControlHelper.m @@ -62,10 +62,9 @@ _SOSControlSetupInterface(NSXPCInterface *interface) [interface setClasses:errClasses forSelector:@selector(joinCircleWithBlob:version:complete:) argumentIndex:1 ofReply:YES]; [interface setClasses:errClasses forSelector:@selector(initialSyncCredentials:complete:) argumentIndex:1 ofReply:YES]; [interface setClasses:errClasses forSelector:@selector(importInitialSyncCredentials:complete:) argumentIndex:1 ofReply:YES]; + [interface setClasses:errClasses forSelector:@selector(triggerSync:complete:) argumentIndex:1 ofReply:YES]; [interface setClasses:errClasses forSelector:@selector(getWatchdogParameters:) argumentIndex:1 ofReply:YES]; [interface setClasses:errClasses forSelector:@selector(setWatchdogParmeters:complete:) argumentIndex:0 ofReply:YES]; [interface setClasses:errClasses forSelector:@selector(ghostBust:complete:) argumentIndex:1 ofReply:YES]; - [interface setClasses:errClasses forSelector:@selector(rpcTriggerSync:complete:) argumentIndex:1 ofReply:YES]; - [interface setClasses:errClasses forSelector:@selector(rpcTriggerBackup:complete:) argumentIndex:0 ofReply:YES]; - [interface setClasses:errClasses forSelector:@selector(rpcTriggerRingUpdate:) argumentIndex:0 ofReply:YES]; + [interface setClasses:errClasses forSelector:@selector(triggerBackup:complete:) argumentIndex:0 ofReply:YES]; } diff --git a/keychain/SecureObjectSync/SOSControlServer.m b/keychain/SecureObjectSync/SOSControlServer.m index 5f8a748d..46332efa 100644 --- a/keychain/SecureObjectSync/SOSControlServer.m +++ b/keychain/SecureObjectSync/SOSControlServer.m @@ -149,9 +149,14 @@ [self.account importInitialSyncCredentials:items complete:complete]; } -- (void)rpcTriggerSync:(NSArray *)peers complete:(void(^)(bool success, NSError *))complete +- (void)triggerSync:(NSArray *)peers complete:(void(^)(bool success, NSError *))complete { - [self.account rpcTriggerSync:peers complete:complete]; + if (![self checkEntitlement:(__bridge NSString *)kSecEntitlementKeychainCloudCircle]) { + complete(false, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSEntitlementMissing userInfo:NULL]); + return; + } + + [self.account triggerSync:peers complete:complete]; } - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete @@ -180,14 +185,11 @@ [self.account ghostBustInfo:complete]; } -- (void)rpcTriggerBackup:(NSArray* _Nullable)backupPeers complete:(void (^)(NSError *error))complete +- (void)triggerBackup:(NSArray* _Nullable)backupPeers complete:(void (^)(NSError *error))complete { - [self.account rpcTriggerBackup:backupPeers complete:complete]; + [self.account triggerBackup:backupPeers complete:complete]; } -- (void)rpcTriggerRingUpdate:(void (^)(NSError *))complete { - [self.account rpcTriggerRingUpdate:complete]; -} @end diff --git a/keychain/SecureObjectSync/SOSExports.exp-in b/keychain/SecureObjectSync/SOSExports.exp-in index 66d25d5e..4417dd34 100644 --- a/keychain/SecureObjectSync/SOSExports.exp-in +++ b/keychain/SecureObjectSync/SOSExports.exp-in @@ -10,20 +10,14 @@ _SOSCCAccountGetAccountPrivateCredential _SOSCCAccountGetPublicKey _SOSCCAccountGetKeyCircleGeneration _SOSCCAccountHasPublicKey -_SOSCCAccountIsNew _SOSCCAccountSetToNew _SOSCCBailFromCircle_BestEffort _SOSCCCanAuthenticate -_SOSCCCopyAccountState _SOSCCCopyApplicantPeerInfo _SOSCCCopyApplication -_SOSCCCopyBackupInformation _SOSCCCopyCircleJoiningBlob _SOSCCCopyConcurringPeerPeerInfo -_SOSCCCopyEngineData -_SOSCCCopyEscrowRecord _SOSCCCopyGenerationPeerInfo -_SOSCCCopyIncompatibilityInfo _SOSCCCopyMyPeerInfo _SOSCCCopyMyPeerWithNewDeviceRecoverySecret _SOSCCCopyNotValidPeerPeerInfo @@ -32,11 +26,7 @@ _SOSCCCopyRecoveryPublicKey _SOSCCCopyRetirementPeerInfo _SOSCCCopyValidPeerPeerInfo _SOSCCCopyViewUnawarePeerInfo -_SOSCCCopyYetToSyncViewsList -_SOSCCDeleteAccountState -_SOSCCDeleteEngineState _SOSCCCleanupKVSKeys -_SOSCCTestPopulateKVSWithBadKeys _SOSCCForEachEngineStateAsString _SOSCCForEachEngineStateAsStringFromArray _SOSCCGetLastDepartureReason @@ -64,7 +54,6 @@ _SOSCCRemovePeersFromCircle _SOSCCRemovePeersFromCircleWithAnalytics _SOSCCRemoveThisDeviceFromCircle _SOSCCRemoveThisDeviceFromCircleWithAnalytics -_SOSCCRequestEnsureFreshParameters _SOSCCRequestToJoinCircle _SOSCCRequestToJoinCircleWithAnalytics _SOSCCRequestToJoinCircleAfterRestore @@ -73,12 +62,10 @@ _SOSCCResetToEmpty _SOSCCResetToEmptyWithAnalytics _SOSCCResetToOffering _SOSCCSendToPeerIsPending -_SOSCCSetEscrowRecord _SOSCCSetLastDepartureReason _SOSCCSetUserCredentials _SOSCCSetUserCredentialsAndDSID _SOSCCSetUserCredentialsAndDSIDWithAnalytics -_SOSCCSignedOut _SOSCCThisDeviceIsInCircle _SOSCCThisDeviceIsInCircleNonCached _SOSCCTryUserCredentials @@ -126,14 +113,12 @@ _SOSPeerInfoCopyBackupKey _SOSPeerInfoCopyDeviceID _SOSPeerInfoCopyEnabledViews _SOSPeerInfoCopyEncodedData -_SOSPeerInfoCopyEscrowRecord _SOSPeerInfoCopyOctagonSigningPublicKey _SOSPeerInfoCopyOctagonEncryptionPublicKey _SOSPeerInfoCopyPeerGestalt _SOSPeerInfoCopyPubKey _SOSPeerInfoCopyTransportType _SOSPeerInfoCopyWithBackupKeyUpdate -_SOSPeerInfoCopyWithEscrowRecordUpdate _SOSPeerInfoCopyWithGestaltUpdate _SOSPeerInfoCopyWithPing _SOSPeerInfoCopyWithReplacedEscrowRecords @@ -189,13 +174,6 @@ _SOSFullPeerInfoGetPeerInfo _SOSCircleAcceptPeerFromHSA2 _SOSFullPeerInfoUpdate -_SOSCCGetAllTheRings -_SOSCCApplyToARing -_SOSCCWithdrawlFromARing -_SOSCCRingStatus -_SOSCCEnableRing -_SOSCCIsThisDeviceLastBackup - _SOSCloudKeychainRemoveKeys _SOSCloudTransportSetDefaultTransport @@ -246,7 +224,6 @@ _der_encode_BackupSliceKeyBag _der_sizeof_BackupSliceKeyBag _bskbRkbgPrefix -_SOSWrapToBackupSliceKeyBagForView _SOSBSKBHasRecoveryKey _SOSBSKBHasThisRecoveryKey @@ -415,7 +392,6 @@ _SOSCircleVerifyPeerSignatureExists _SOSCircleWithdrawRequest _debugDumpCircle -_SOSFullPeerInfoAddEscrowRecord _SOSFullPeerInfoCopyDeviceKey _SOSFullPeerInfoCopyEncodedData _SOSFullPeerInfoCopyFullPeerInfo diff --git a/keychain/SecureObjectSync/SOSFullPeerInfo.h b/keychain/SecureObjectSync/SOSFullPeerInfo.h index 2d265c6e..dce3a3f8 100644 --- a/keychain/SecureObjectSync/SOSFullPeerInfo.h +++ b/keychain/SecureObjectSync/SOSFullPeerInfo.h @@ -80,8 +80,6 @@ bool SOSFullPeerInfoUpdateV2Dictionary(SOSFullPeerInfoRef peer, CFDictionaryRef bool SOSFullPeerInfoUpdateBackupKey(SOSFullPeerInfoRef peer, CFDataRef backupKey, CFErrorRef* error); -bool SOSFullPeerInfoAddEscrowRecord(SOSFullPeerInfoRef peer, CFStringRef dsid, CFDictionaryRef escrowRecord, CFErrorRef* error); - bool SOSFullPeerInfoReplaceEscrowRecords(SOSFullPeerInfoRef peer, CFDictionaryRef escrowRecords, CFErrorRef* error); bool SOSFullPeerInfoUpdateToCurrent(SOSFullPeerInfoRef peer, CFSetRef minimumViews, CFSetRef excludedViews); diff --git a/keychain/SecureObjectSync/SOSFullPeerInfo.m b/keychain/SecureObjectSync/SOSFullPeerInfo.m index 7b2bee13..adea2788 100644 --- a/keychain/SecureObjectSync/SOSFullPeerInfo.m +++ b/keychain/SecureObjectSync/SOSFullPeerInfo.m @@ -354,13 +354,6 @@ bool SOSFullPeerInfoUpdateBackupKey(SOSFullPeerInfoRef peer, CFDataRef backupKey }); } -bool SOSFullPeerInfoAddEscrowRecord(SOSFullPeerInfoRef peer, CFStringRef dsid, CFDictionaryRef escrowRecord, CFErrorRef* error) -{ - return SOSFullPeerInfoUpdate(peer, error, ^SOSPeerInfoRef(SOSPeerInfoRef peer, SecKeyRef key, CFErrorRef *error) { - return SOSPeerInfoCopyWithEscrowRecordUpdate(kCFAllocatorDefault, peer, dsid, escrowRecord, key, error); - }); -} - bool SOSFullPeerInfoReplaceEscrowRecords(SOSFullPeerInfoRef peer, CFDictionaryRef escrowRecords, CFErrorRef* error) { return SOSFullPeerInfoUpdate(peer, error, ^SOSPeerInfoRef(SOSPeerInfoRef peer, SecKeyRef key, CFErrorRef *error) { diff --git a/keychain/SecureObjectSync/SOSPeerInfo.h b/keychain/SecureObjectSync/SOSPeerInfo.h index 25389178..0e6579b4 100644 --- a/keychain/SecureObjectSync/SOSPeerInfo.h +++ b/keychain/SecureObjectSync/SOSPeerInfo.h @@ -77,7 +77,6 @@ bool SOSPeerInfoVersionIsCurrent(SOSPeerInfoRef pi); bool SOSPeerInfoVersionHasV2Data(SOSPeerInfoRef pi); SOSPeerInfoRef SOSPeerInfoCopyWithGestaltUpdate(CFAllocatorRef allocator, SOSPeerInfoRef toCopy, CFDictionaryRef gestalt, SecKeyRef signingKey, CFErrorRef* error); SOSPeerInfoRef SOSPeerInfoCopyWithBackupKeyUpdate(CFAllocatorRef allocator, SOSPeerInfoRef toCopy, CFDataRef backupKey, SecKeyRef signingKey, CFErrorRef* error); -SOSPeerInfoRef SOSPeerInfoCopyWithEscrowRecordUpdate(CFAllocatorRef allocator, SOSPeerInfoRef toCopy, CFStringRef dsid, CFDictionaryRef escrowRecord, SecKeyRef signingKey, CFErrorRef *error); SOSPeerInfoRef SOSPeerInfoCopyWithReplacedEscrowRecords(CFAllocatorRef allocator, SOSPeerInfoRef toCopy, CFDictionaryRef escrowRecords, SecKeyRef signingKey, CFErrorRef *error); @@ -104,7 +103,6 @@ CF_RETURNS_RETAINED CFDateRef SOSPeerInfoGetApplicationDate(SOSPeerInfoRef pi); // bool SOSPeerInfoHasBackupKey(SOSPeerInfoRef peer); CFDataRef SOSPeerInfoCopyBackupKey(SOSPeerInfoRef peer); -CFMutableDictionaryRef SOSPeerInfoCopyEscrowRecord(SOSPeerInfoRef peer); // // DER Import Export diff --git a/keychain/SecureObjectSync/SOSPeerInfo.m b/keychain/SecureObjectSync/SOSPeerInfo.m index f75c3321..915cc403 100644 --- a/keychain/SecureObjectSync/SOSPeerInfo.m +++ b/keychain/SecureObjectSync/SOSPeerInfo.m @@ -456,36 +456,6 @@ SOSPeerInfoRef SOSPeerInfoCopyWithBackupKeyUpdate(CFAllocatorRef allocator, SOSP }); } -static CFDictionaryRef SOSPeerInfoUpdateAndCopyRecord(SOSPeerInfoRef peer, CFStringRef dsid, CFDictionaryRef escrowRecord){ - - CFMutableDictionaryRef existingEscrowRecords = SOSPeerInfoCopyEscrowRecord(peer); - - if(escrowRecord == NULL && existingEscrowRecords != NULL) - { - CFDictionaryRemoveValue(existingEscrowRecords, dsid); - return existingEscrowRecords; - } - - if(existingEscrowRecords == NULL) - existingEscrowRecords = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - - CFDictionarySetValue(existingEscrowRecords, dsid, escrowRecord); - - return existingEscrowRecords; -} - - -SOSPeerInfoRef SOSPeerInfoCopyWithEscrowRecordUpdate(CFAllocatorRef allocator, SOSPeerInfoRef toCopy, CFStringRef dsid, CFDictionaryRef escrowRecord, SecKeyRef signingKey, CFErrorRef *error) { - return SOSPeerInfoCopyWithModification(allocator, toCopy, signingKey, error, - ^bool(SOSPeerInfoRef peerToModify, CFErrorRef *error) { - - CFDictionaryRef updatedEscrowRecords = SOSPeerInfoUpdateAndCopyRecord(peerToModify, dsid, escrowRecord); - SOSPeerInfoV2DictionarySetValue(peerToModify, sEscrowRecord, updatedEscrowRecords); - CFReleaseNull(updatedEscrowRecords); - return true; - }); -} - SOSPeerInfoRef SOSPeerInfoCopyWithReplacedEscrowRecords(CFAllocatorRef allocator, SOSPeerInfoRef toCopy, CFDictionaryRef escrowRecords, SecKeyRef signingKey, CFErrorRef *error) { return SOSPeerInfoCopyWithModification(allocator, toCopy, signingKey, error, ^bool(SOSPeerInfoRef peerToModify, CFErrorRef *error) { @@ -500,10 +470,6 @@ CFDataRef SOSPeerInfoCopyBackupKey(SOSPeerInfoRef peer) { return SOSPeerInfoV2DictionaryCopyData(peer, sBackupKeyKey); } -CFMutableDictionaryRef SOSPeerInfoCopyEscrowRecord(SOSPeerInfoRef peer){ - return SOSPeerInfoV2DictionaryCopyDictionary(peer, sEscrowRecord); -} - bool SOSPeerInfoHasBackupKey(SOSPeerInfoRef peer) { CFDataRef bk = SOSPeerInfoCopyBackupKey(peer); bool success = bk != NULL; diff --git a/keychain/SecureObjectSync/SOSPeerOTRTimer.m b/keychain/SecureObjectSync/SOSPeerOTRTimer.m index 5562db28..87c682fe 100644 --- a/keychain/SecureObjectSync/SOSPeerOTRTimer.m +++ b/keychain/SecureObjectSync/SOSPeerOTRTimer.m @@ -25,7 +25,6 @@ //AGGD NSString* const SecSOSAggdMaxRenegotiation = @"com.apple.security.sos.otrrenegotiationmaxretries"; -__unused static int initialOTRTimeoutValue = 60; //best round trip time in KVS plus extra for good measure static int maxRetryCount = 7; //max number of times to attempt restarting OTR negotiation bool SOSPeerOTRTimerHaveReachedMaxRetryAllowance(SOSAccount* account, NSString* peerid){ diff --git a/keychain/SecureObjectSync/SOSRing.h b/keychain/SecureObjectSync/SOSRing.h index 25dd226b..8c97f793 100644 --- a/keychain/SecureObjectSync/SOSRing.h +++ b/keychain/SecureObjectSync/SOSRing.h @@ -45,10 +45,6 @@ CFTypeID SOSRingGetTypeID(void); SOSRingRef SOSRingCreate(CFStringRef name, CFStringRef myPeerID, SOSRingType type, CFErrorRef *error); bool SOSRingResetToEmpty(SOSRingRef ring, CFStringRef myPeerID, CFErrorRef *error); -bool SOSRingResetToOffering(SOSRingRef ring, __unused SecKeyRef user_privkey, SOSFullPeerInfoRef requestor, CFErrorRef *error); -SOSRingStatus SOSRingDeviceIsInRing(SOSRingRef ring, CFStringRef peerID); -bool SOSRingApply(SOSRingRef ring, SecKeyRef user_pubkey, SOSFullPeerInfoRef requestor, CFErrorRef *error); -bool SOSRingWithdraw(SOSRingRef ring, SecKeyRef user_privkey, SOSFullPeerInfoRef requestor, CFErrorRef *error); bool SOSRingGenerationSign(SOSRingRef ring, SecKeyRef user_privkey, SOSFullPeerInfoRef requestor, CFErrorRef *error); bool SOSRingConcordanceSign(SOSRingRef ring, SOSFullPeerInfoRef requestor, CFErrorRef *error); SOSConcordanceStatus SOSRingConcordanceTrust(SOSFullPeerInfoRef me, CFSetRef peers, diff --git a/keychain/SecureObjectSync/SOSRingTypes.m b/keychain/SecureObjectSync/SOSRingTypes.m index 6a5ed6be..e7b6e995 100644 --- a/keychain/SecureObjectSync/SOSRingTypes.m +++ b/keychain/SecureObjectSync/SOSRingTypes.m @@ -80,63 +80,6 @@ bool SOSRingResetToEmpty(SOSRingRef ring, CFStringRef myPeerID, CFErrorRef *erro return ringTypes[type]->sosRingResetToEmpty(ring, myPeerID, error); } -bool SOSRingResetToOffering(SOSRingRef ring, SecKeyRef user_privkey, SOSFullPeerInfoRef requestor, CFErrorRef *error) { - SOSRingAssertStable(ring); - SOSRingType type = SOSRingGetType(ring); - if(!SOSRingValidType(type)){ - SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Not valid ring type"), NULL, error); - return false; - } - if(!ringTypes[type]->sosRingResetToOffering){ - SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Not valid ring type"), NULL, error); - return false; - } - return ringTypes[type]->sosRingResetToOffering(ring, user_privkey, requestor, error); -} - -SOSRingStatus SOSRingDeviceIsInRing(SOSRingRef ring, CFStringRef peerID) { - SOSRingAssertStable(ring); - SOSRingType type = SOSRingGetType(ring); - if(!(SOSRingValidType(type))){ - return kSOSRingError; - } - if(!(ringTypes[type]->sosRingDeviceIsInRing)){ - return kSOSRingError; - } - return ringTypes[type]->sosRingDeviceIsInRing(ring, peerID); -} - -bool SOSRingApply(SOSRingRef ring, SecKeyRef user_pubkey, SOSFullPeerInfoRef fpi, CFErrorRef *error) { - SOSRingAssertStable(ring); - SOSRingType type = SOSRingGetType(ring); - if(!(SOSRingValidType(type))){ - SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Not valid ring type"), NULL, error); - return false; - } - if(!(ringTypes[type]->sosRingApply)){ - return true; - } - if(!(SOSPeerInfoApplicationVerify(SOSFullPeerInfoGetPeerInfo(fpi), user_pubkey, error))){ - SOSCreateError(kSOSErrorBadSignature, CFSTR("FullPeerInfo fails userkey signature check"), NULL, error); - return false; - } - - return ringTypes[type]->sosRingApply(ring, user_pubkey, fpi, error); -} - -bool SOSRingWithdraw(SOSRingRef ring, SecKeyRef user_privkey, SOSFullPeerInfoRef requestor, CFErrorRef *error) { - SOSRingAssertStable(ring); - SOSRingType type = SOSRingGetType(ring); - if(!SOSRingValidType(type)){ - SOSCreateError(kSOSErrorUnexpectedType, CFSTR("Not valid ring type"), NULL, error); - return false; - } - if(!ringTypes[type]->sosRingWithdraw) - return true; - - return ringTypes[type]->sosRingWithdraw(ring, user_privkey, requestor, error); -} - bool SOSRingGenerationSign(SOSRingRef ring, SecKeyRef user_privkey, SOSFullPeerInfoRef requestor, CFErrorRef *error) { SOSRingAssertStable(ring); SOSRingType type = SOSRingGetType(ring); diff --git a/keychain/SecureObjectSync/SOSRingUtils.c b/keychain/SecureObjectSync/SOSRingUtils.c index bb0d6221..ddff4c19 100644 --- a/keychain/SecureObjectSync/SOSRingUtils.c +++ b/keychain/SecureObjectSync/SOSRingUtils.c @@ -82,12 +82,6 @@ SOSRingRef SOSRingAllocate(void) { return (SOSRingRef) CFTypeAllocate(SOSRing, struct __OpaqueSOSRing, ALLOCATOR); } -__unused static bool addValueToDict(CFMutableDictionaryRef thedict, CFStringRef key, CFTypeRef value) { - if(!value) return false; - CFDictionaryAddValue(thedict, key, value); - return true; -} - static bool setValueInDict(CFMutableDictionaryRef thedict, CFStringRef key, CFTypeRef value) { if(!value) return false; CFDictionarySetValue(thedict, key, value); diff --git a/keychain/SecureObjectSync/SOSRingV0.m b/keychain/SecureObjectSync/SOSRingV0.m index 84579836..32e28056 100644 --- a/keychain/SecureObjectSync/SOSRingV0.m +++ b/keychain/SecureObjectSync/SOSRingV0.m @@ -117,23 +117,6 @@ static bool SOSRingConcordanceSign_V0(SOSRingRef ring, SOSFullPeerInfoRef reques } -__unused static bool SOSRingSetPayload_V0(SOSRingRef ring, SecKeyRef user_privkey, CFDataRef payload, SOSFullPeerInfoRef requestor, CFErrorRef *error) { - CFStringRef myPeerID = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(requestor)); - SecKeyRef priv = SOSFullPeerInfoCopyDeviceKey(requestor, error); - bool retval = priv && myPeerID && - SOSRingSetLastModifier(ring, myPeerID) && - SOSRingSetPayload_Internal(ring, payload) && - SOSRingGenerationSign_Internal(ring, priv, error); - if(user_privkey) SOSRingConcordanceSign_Internal(ring, user_privkey, error); - CFReleaseNull(priv); - return retval; -} - -__unused static CFDataRef SOSRingGetPayload_V0(SOSRingRef ring, CFErrorRef *error) { - return SOSRingGetPayload_Internal(ring); -} - - ringFuncStruct ringsV0 = { "V0", 1, diff --git a/keychain/SecureObjectSync/SOSTypes.h b/keychain/SecureObjectSync/SOSTypes.h index a9e748ae..65f2d9e6 100644 --- a/keychain/SecureObjectSync/SOSTypes.h +++ b/keychain/SecureObjectSync/SOSTypes.h @@ -124,14 +124,12 @@ typedef NS_OPTIONS(uint32_t, SOSAccountGhostBustingOptions) { - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete; - (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete; -- (void)rpcTriggerSync:(NSArray *)peers complete:(void(^)(bool success, NSError *))complete; +- (void)triggerSync:(NSArray *)peers complete:(void(^)(bool success, NSError *))complete; - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete; - (void)setWatchdogParmeters:(NSDictionary*)parameters complete:(void (^)(NSError* error))complete; -- (void)rpcTriggerBackup:(NSArray*)backupPeers complete:(void (^)(NSError *error))complete; -- (void)rpcTriggerRingUpdate:(void (^)(NSError *error))complete; - +- (void)triggerBackup:(NSArray*)backupPeers complete:(void (^)(NSError *error))complete; @end #endif diff --git a/keychain/SecureObjectSync/Tool/keychain_sync.h b/keychain/SecureObjectSync/Tool/keychain_sync.h index 141de9d9..6d7d9ec5 100644 --- a/keychain/SecureObjectSync/Tool/keychain_sync.h +++ b/keychain/SecureObjectSync/Tool/keychain_sync.h @@ -35,28 +35,14 @@ SECURITY_COMMAND( "\n" "Account/Circle Management\n" " -a accept all applicants\n" - " -l [reason] sign out of circle + set custom departure reason\n" - " -q sign out of circle\n" " -r reject all applicants\n" - " -E ensure fresh parameters\n" " -b device|all|single Register a backup bag - THIS RESETS BACKUPS!\n" - " -A Apply to a ring\n" - " -B Withdrawl from a ring\n" - " -G Enable Ring\n" - " -F Ring Status\n" - " -I Dump Ring Information\n" " -N (re-)set to new account (USE WITH CARE: device will not leave circle before resetting account!)\n" " -O reset to offering\n" " -R reset circle\n" - " -X [limit] best effort bail from circle in limit seconds\n" " -o list view unaware peers in circle\n" " -0 boot view unaware peers from circle\n" - " -1 grab account state from the keychain\n" - " -2 delete account state from the keychain\n" - " -3 grab engine state from the keychain\n" - " -4 delete engine state from the keychain\n" " -5 cleanup old KVS keys in KVS\n" - " -6 [test]populate KVS with garbage KVS keys\n" "\n" "Circle Tools\n" " --remove-peer SPID Remove a peer identified by the first 8 or more\n" @@ -79,9 +65,7 @@ SECURITY_COMMAND( " wifi|passwords|creditcards|icloudidentity|othersyncable\n" " -L list all known view and their status\n" " -U purge private key material cache\n" - " -V Report View Sync Status on all known clients.\n" - " -Y Report yet to initial sync views\n" - " -H Set escrow record.\n" - " -J Get the escrow record.\n" - " -M Check peer availability.\n", + " -V Report View Sync Status on all known clients.\n", "Keychain Syncing controls." ) + + diff --git a/keychain/SecureObjectSync/Tool/keychain_sync.m b/keychain/SecureObjectSync/Tool/keychain_sync.m index 4d17e4e5..5cb4adc9 100644 --- a/keychain/SecureObjectSync/Tool/keychain_sync.m +++ b/keychain/SecureObjectSync/Tool/keychain_sync.m @@ -59,7 +59,6 @@ #include "keychain_sync.h" #include "keychain_log.h" -#include "syncbackup.h" #include "secToolFileIO.h" #include "secViewDisplay.h" @@ -329,30 +328,6 @@ static bool clientViewStatus(CFErrorRef *error) { return false; } - -static bool dumpYetToSync(CFErrorRef *error) { - CFArrayRef yetToSyncViews = SOSCCCopyYetToSyncViewsList(error); - - bool hadError = yetToSyncViews; - - if (yetToSyncViews) { - __block CFStringRef separator = CFSTR(""); - - printmsg(CFSTR("Yet to sync views: ["), NULL); - - CFArrayForEach(yetToSyncViews, ^(const void *value) { - if (isString(value)) { - printmsg(CFSTR("%@%@"), separator, value); - - separator = CFSTR(", "); - } - }); - printmsg(CFSTR("]\n"), NULL); - } - - return !hadError; -} - #pragma mark - #pragma mark --remove-peer @@ -468,30 +443,20 @@ keychain_sync(int argc, char * const *argv) " "Account/Circle Management" " -a accept all applicants" - " -l [reason] sign out of circle + set custom departure reason" - " -q sign out of circle" " -r reject all applicants" - " -E ensure fresh parameters" " -b device|all|single Register a backup bag - THIS RESETS BACKUPS!\n" - " -A Apply to a ring\n" - " -B Withdrawl from a ring\n" - " -G Enable Ring\n" - " -F Ring Status\n" - " -I Dump Ring Information\n" " -N (re-)set to new account (USE WITH CARE: device will not leave circle before resetting account!)" " -O reset to offering" " -R reset circle" - " -X [limit] best effort bail from circle in limit seconds" " -o list view unaware peers in circle" " -0 boot view unaware peers from circle" - " -1 grab account state from the keychain" - " -2 delete account state from the keychain" - " -3 grab engine state from the keychain" - " -4 delete engine state from the keychain" " -5 cleanup old KVS keys in KVS" - " -6 [test]populate KVS with garbage KVS keys " + "Circle Tools\n" + " --remove-peer SPID Remove a peer identified by the first 8 or more\n" + " characters of its spid. Specify multiple times to\n" + " remove more than one peer.\n" "Password" " -P [label:]password set password (optionally for a given label) for sync" " -T [label:]password try password (optionally for a given label) for sync" @@ -509,9 +474,6 @@ keychain_sync(int argc, char * const *argv) " -L list all known view and their status" " -U purge private key material cache\n" " -V Report View Sync Status on all known clients.\n" - " -H Set escrow record.\n" - " -J Get the escrow record.\n" - " -M Check peer availability.\n" */ enum { SYNC_REMOVE_PEER, @@ -527,359 +489,156 @@ keychain_sync(int argc, char * const *argv) CFMutableArrayRef peers2remove = NULL; SOSLogSetOutputTo(NULL, NULL); - while ((ch = getopt_long(argc, argv, "ab:deg:hikl:mopq:rSv:w:x:zA:B:MNJCDEF:HG:ILOP:RT:UWX:VY0123456", longopts, NULL)) != -1) + while ((ch = getopt_long(argc, argv, "ab:deikmorv:NCDLOP:RT:UWV05", longopts, NULL)) != -1) switch (ch) { - case 'l': - { - fprintf(outFile, "Signing out of circle\n"); - hadError = !SOSCCSignedOut(true, &error); - if (!hadError) { - errno = 0; - int reason = (int) strtoul(optarg, NULL, 10); - if (errno != 0 || - reason < kSOSDepartureReasonError || - reason >= kSOSNumDepartureReasons) { - fprintf(errFile, "Invalid custom departure reason %s\n", optarg); - } else { - fprintf(outFile, "Setting custom departure reason %d\n", reason); - hadError = !SOSCCSetLastDepartureReason(reason, &error); - notify_post(kSOSCCCircleChangedNotification); - } - } - break; - } - - case 'q': - { - fprintf(outFile, "Signing out of circle\n"); - bool signOutImmediately = false; - if (strcasecmp(optarg, "true") == 0) { - signOutImmediately = true; - } else if (strcasecmp(optarg, "false") == 0) { - signOutImmediately = false; - } else { - fprintf(outFile, "Please provide a \"true\" or \"false\" whether you'd like to leave the circle immediately\n"); - } - hadError = !SOSCCSignedOut(signOutImmediately, &error); - notify_post(kSOSCCCircleChangedNotification); - break; - } - case 'e': - fprintf(outFile, "Turning ON keychain syncing\n"); - hadError = requestToJoinCircle(&error); - break; - - case 'd': - fprintf(outFile, "Turning OFF keychain syncing\n"); - hadError = !SOSCCRemoveThisDeviceFromCircle(&error); - break; - - case 'a': - { - CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL); - if (applicants) { - hadError = !SOSCCAcceptApplicants(applicants, &error); - CFRelease(applicants); - } else { - fprintf(errFile, "No applicants to accept\n"); - } - break; - } - - case 'r': - { - CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL); - if (applicants) { - hadError = !SOSCCRejectApplicants(applicants, &error); - CFRelease(applicants); - } else { - fprintf(errFile, "No applicants to reject\n"); - } - break; - } - - case 'i': - SOSCCDumpCircleInformation(); - SOSCCDumpEngineInformation(); - break; - - case 'k': - notify_post("com.apple.security.cloudkeychain.forceupdate"); - break; - - case 'o': - { - SOSCCDumpViewUnwarePeers(); - break; - } - - case '0': - { - CFArrayRef unawares = SOSCCCopyViewUnawarePeerInfo(&error); - if (unawares) { - hadError = !SOSCCRemovePeersFromCircle(unawares, &error); - } else { - hadError = true; + case 'a': + { + CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL); + if (applicants) { + hadError = !SOSCCAcceptApplicants(applicants, &error); + CFRelease(applicants); + } else { + fprintf(errFile, "No applicants to accept\n"); + } + break; } - CFReleaseNull(unawares); - break; - } - case '1': - { - CFDataRef accountState = SOSCCCopyAccountState(&error); - if (accountState) { - printmsg(CFSTR(" %@\n"), CFDataCopyHexString(accountState)); - } else { - hadError = true; + case 'b': + { + hadError = setBag(optarg, &error); + break; } - CFReleaseNull(accountState); - break; - } - case '2': - { - bool status = SOSCCDeleteAccountState(&error); - if (status) { - printmsg(CFSTR("Deleted account from the keychain %d\n"), status); - } else { - hadError = true; + case 'd': + { + fprintf(outFile, "Turning OFF keychain syncing\n"); + hadError = !SOSCCRemoveThisDeviceFromCircle(&error); + break; } - break; - } - case '3': - { - CFDataRef engineState = SOSCCCopyEngineData(&error); - if (engineState) { - printmsg(CFSTR(" %@\n"), CFDataCopyHexString(engineState)); - } else { - hadError = true; + case 'e': + { + fprintf(outFile, "Turning ON keychain syncing\n"); + hadError = requestToJoinCircle(&error); + break; } - CFReleaseNull(engineState); - break; - } - case '4': - { - bool status = SOSCCDeleteEngineState(&error); - if (status) { - printmsg(CFSTR("Deleted engine-state from the keychain %d\n"), status); - } else { - hadError = true; + case 'i': + { + SOSCCDumpCircleInformation(); + SOSCCDumpEngineInformation(); + break; } - break; - } - case '5' : - { - bool result = SOSCCCleanupKVSKeys(&error); - if(result) + case 'k': { - printmsg(CFSTR("Got all the keys from KVS %d\n"), result); - }else { - hadError = true; + notify_post("com.apple.security.cloudkeychain.forceupdate"); + break; } - break; - } - case '6' : - { - bool result = SOSCCTestPopulateKVSWithBadKeys(&error); - if(result) - { - printmsg(CFSTR("Populated KVS with garbage %d\n"), result); - }else { + case 'm': + { + hadError = !dumpMyPeer(&error); + break; + } + case 'o': + { + SOSCCDumpViewUnwarePeers(); + break; + } + case 'r': + { + CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL); + if (applicants) { + hadError = !SOSCCRejectApplicants(applicants, &error); + CFRelease(applicants); + } else { + fprintf(errFile, "No applicants to reject\n"); + } + break; + } + case 'v': + { + hadError = !viewcmd(optarg, &error); + break; + } + case 'C': + { + hadError = clearAllKVS(&error); + break; + } + case 'D': + { + (void)SOSCCDumpCircleKVSInformation(optarg); + break; + } + case 'L': + { + hadError = !listviewcmd(&error); + break; + } + case 'N': + { + hadError = !SOSCCAccountSetToNew(&error); + if (!hadError) + notify_post(kSOSCCCircleChangedNotification); + break; + } + case 'O': + { + hadError = !SOSCCResetToOffering(&error); + break; + } + case 'P': + { + hadError = setPassword(optarg, &error); + break; + } + case 'R': + { + hadError = !SOSCCResetToEmpty(&error); + break; + } + case 'T': + { + hadError = tryPassword(optarg, &error); + break; + } + case 'U': + { + hadError = !SOSCCPurgeUserCredentials(&error); + break; + } + case 'V': + { + hadError = clientViewStatus(&error); + break; + } + case 'W': + { + hadError = syncAndWait(&error); + break; + } + case '0': + { + CFArrayRef unawares = SOSCCCopyViewUnawarePeerInfo(&error); + if (unawares) { + hadError = !SOSCCRemovePeersFromCircle(unawares, &error); + } else { hadError = true; } + CFReleaseNull(unawares); break; - } - case 'E': - { - fprintf(outFile, "Ensuring Fresh Parameters\n"); - bool result = SOSCCRequestEnsureFreshParameters(&error); - if (error) { - hadError = true; - break; - } - if (result) { - fprintf(outFile, "Refreshed Parameters Ensured!\n"); - } else { - fprintf(outFile, "Problem trying to ensure fresh parameters\n"); - } - break; - } - case 'A': - { - fprintf(outFile, "Applying to Ring\n"); - CFStringRef ringName = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8); - hadError = SOSCCApplyToARing(ringName, &error); - CFReleaseNull(ringName); - break; - } - case 'B': - { - fprintf(outFile, "Withdrawing from Ring\n"); - CFStringRef ringName = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8); - hadError = SOSCCWithdrawlFromARing(ringName, &error); - CFReleaseNull(ringName); - break; - } - case 'F': - { - fprintf(outFile, "Status of this device in the Ring\n"); - CFStringRef ringName = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8); - hadError = SOSCCRingStatus(ringName, &error); - CFReleaseNull(ringName); - break; - } - case 'G': - { - fprintf(outFile, "Enabling Ring\n"); - CFStringRef ringName = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8); - hadError = SOSCCEnableRing(ringName, &error); - CFReleaseNull(ringName); - break; - } - case 'H': - { - fprintf(outFile, "Setting random escrow record\n"); - bool success = SOSCCSetEscrowRecord(CFSTR("label"), 8, &error); - if(success) - hadError = false; - else - hadError = true; - break; - } - case 'J': - { - CFDictionaryRef attempts = SOSCCCopyEscrowRecord(&error); - if(attempts){ - CFDictionaryForEach(attempts, ^(const void *key, const void *value) { - if(isString(key)){ - char *keyString = CFStringToCString(key); - fprintf(outFile, "%s:\n", keyString); - free(keyString); - } - if(isDictionary(value)){ - CFDictionaryForEach(value, ^(const void *key, const void *value) { - if(isString(key)){ - char *keyString = CFStringToCString(key); - fprintf(outFile, "%s: ", keyString); - free(keyString); - } - if(isString(value)){ - char *time = CFStringToCString(value); - fprintf(outFile, "timestamp: %s\n", time); - free(time); - } - else if(isNumber(value)){ - uint64_t tries; - CFNumberGetValue(value, kCFNumberLongLongType, &tries); - fprintf(outFile, "date: %llu\n", tries); - } - }); - } - - }); } - CFReleaseNull(attempts); - hadError = false; - break; - } - case 'I': - { - fprintf(outFile, "Printing all the rings\n"); - CFStringRef ringdescription = SOSCCGetAllTheRings(&error); - if(!ringdescription) - hadError = true; - else - fprintf(outFile, "Rings: %s", CFStringToCString(ringdescription)); - - break; - } - - case 'N': - hadError = !SOSCCAccountSetToNew(&error); - if (!hadError) - notify_post(kSOSCCCircleChangedNotification); - break; - - case 'R': - hadError = !SOSCCResetToEmpty(&error); - break; - - case 'O': - hadError = !SOSCCResetToOffering(&error); - break; - - case 'm': - hadError = !dumpMyPeer(&error); - break; - - case 'C': - hadError = clearAllKVS(&error); - break; - - case 'P': - hadError = setPassword(optarg, &error); - break; - - case 'T': - hadError = tryPassword(optarg, &error); - break; - - case 'X': - { - uint64_t limit = strtoul(optarg, NULL, 10); - hadError = !SOSCCBailFromCircle_BestEffort(limit, &error); - break; - } - - case 'U': - hadError = !SOSCCPurgeUserCredentials(&error); - break; - - case 'D': - (void)SOSCCDumpCircleKVSInformation(optarg); - break; - - case 'W': - hadError = syncAndWait(&error); - break; - - case 'v': - hadError = !viewcmd(optarg, &error); - break; - - case 'V': - hadError = clientViewStatus(&error); - break; - case 'L': - hadError = !listviewcmd(&error); - break; - - case 'b': - hadError = setBag(optarg, &error); - break; - - case 'Y': - hadError = dumpYetToSync(&error); - break; - case 0: - switch (action) { - case SYNC_REMOVE_PEER: { - CFStringRef optstr; - optstr = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8); - if (peers2remove == NULL) { - peers2remove = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + case '5' : + { + bool result = SOSCCCleanupKVSKeys(&error); + if(result) + { + printmsg(CFSTR("Got all the keys from KVS %d\n"), result); + }else { + hadError = true; } - CFArrayAppendValue(peers2remove, optstr); - CFRelease(optstr); break; } + case '?': default: return SHOW_USAGE_MESSAGE; - } - break; - case '?': - default: - return SHOW_USAGE_MESSAGE; - } + } if (peers2remove != NULL) { hadError = !doRemovePeers(peers2remove, &error); diff --git a/keychain/SecureObjectSync/Tool/recovery_key.m b/keychain/SecureObjectSync/Tool/recovery_key.m index f836de1f..beca9f35 100644 --- a/keychain/SecureObjectSync/Tool/recovery_key.m +++ b/keychain/SecureObjectSync/Tool/recovery_key.m @@ -133,6 +133,7 @@ recovery_key(int argc, char * const *argv) CDPFollowUpContext *context = [CDPFollowUpContext contextForRecoveryKeyRepair]; context.force = true; + secnotice("followup", "Posting a follow up (for SOS) of type recovery key"); [cdpd postFollowUpWithContext:context error:&localError]; if(localError){ printmsg(CFSTR("Request to CoreCDP to follow up failed: %@\n"), localError); diff --git a/keychain/SecureObjectSync/Tool/syncbackup.m b/keychain/SecureObjectSync/Tool/syncbackup.m deleted file mode 100644 index 5216ad19..00000000 --- a/keychain/SecureObjectSync/Tool/syncbackup.m +++ /dev/null @@ -1,146 +0,0 @@ - -/* - * Copyright (c) 2003-2007,2009-2010,2013-2016 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - * - */ - -// -// syncbackup.c -// sec -// -// -// - -#include "syncbackup.h" - - -#include -#include - -#include -#include -#include "keychain/SecureObjectSync/SOSBackupInformation.h" -#include "keychain/SecureObjectSync/SOSRecoveryKeyBag.h" -#include - -#include - -#include "SecurityTool/sharedTool/readline.h" -#include "secToolFileIO.h" - - -static bool dumpBackupInfo(CFErrorRef *error) { - CFReleaseNull(*error); - bool isLast = SOSCCIsThisDeviceLastBackup(error); - - printmsg(CFSTR("This %s the last backup peer.\n"), (isLast) ? "is": "isn't"); - return *error != NULL; -} - -static bool longListing(CFErrorRef *error) { - CFDataRef rkbgder = NULL; - CFDictionaryRef bskbders = NULL; - - CFDictionaryRef backupInfo = SOSCCCopyBackupInformation(error); - SOSRecoveryKeyBagRef rkbg = NULL; - CFNumberRef status = CFDictionaryGetValue(backupInfo, kSOSBkpInfoStatus); - int infoStatus; - CFNumberGetValue(status, kCFNumberIntType, &infoStatus); - - switch(infoStatus) { - case noError: - rkbgder = CFDictionaryGetValue(backupInfo, kSOSBkpInfoRKBG); - bskbders = CFDictionaryGetValue(backupInfo, kSOSBkpInfoBSKB); - break; - case noTxnorAcct: - break; - case noAlloc: - break; - case noTrustedPubKey: - break; - case noBSKBs: - rkbgder = CFDictionaryGetValue(backupInfo, kSOSBkpInfoRKBG); - break; - default: - break; - } - - if(rkbgder) { - rkbg = SOSRecoveryKeyBagCreateFromData(kCFAllocatorDefault, rkbgder, NULL); - printmsg(CFSTR("Recovery Keybag: %@\n"), rkbg); - } - - if(bskbders) { - CFDataRef rkPub = NULL; - if(rkbg) rkPub = SOSRecoveryKeyBagGetKeyData(rkbg, NULL); - CFDictionaryForEach(bskbders, ^(const void *key, const void *value) { - CFDataRef bskbder = asData(value, NULL); - SOSBackupSliceKeyBagRef bskb = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, bskbder, NULL); - if(bskb) { - bool reckeyPresent = (rkPub && SOSBKSBPrefixedKeyIsInKeyBag(bskb, bskbRkbgPrefix, rkPub)); - printmsg(CFSTR("BackupSliceKeybag %@: Recovery Key %s; %@\n"), key, (reckeyPresent) ? "Present": "Absent ", bskb); - CFReleaseNull(bskb); - } - }); - } - CFReleaseNull(backupInfo); - CFReleaseNull(rkbg); - return *error != NULL; -} - - - -int -syncbackup(int argc, char * const *argv) -{ - /* - "Circle Backup Information" - " -i info (current status)" - - */ - SOSLogSetOutputTo(NULL, NULL); - - int ch, result = 0; - CFErrorRef error = NULL; - bool hadError = false; - - while ((ch = getopt(argc, argv, "il")) != -1) - switch (ch) { - - case 'i': - hadError = dumpBackupInfo(&error); - break; - - case 'l': - hadError = longListing(&error); - break; - - case '?': - default: - return SHOW_USAGE_MESSAGE; - } - - if (hadError) - printerr(CFSTR("Error: %@\n"), error); - - return result; -} diff --git a/keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.h b/keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.h index c381e08a..1930811b 100644 --- a/keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.h +++ b/keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.h @@ -15,9 +15,9 @@ __attribute__((visibility("hidden"))) @interface SOSAccountConfiguration : PBCodable { NSMutableArray *_pendingBackupPeers; - BOOL _ringUpdateFlag; + BOOL _sbdBackup; struct { - int ringUpdateFlag:1; + int sbdBackup:1; } _has; } @@ -29,8 +29,8 @@ __attribute__((visibility("hidden"))) - (NSString *)pendingBackupPeersAtIndex:(NSUInteger)idx; + (Class)pendingBackupPeersType; -@property (nonatomic) BOOL hasRingUpdateFlag; -@property (nonatomic) BOOL ringUpdateFlag; +@property (nonatomic) BOOL hasSbdBackup; +@property (nonatomic) BOOL sbdBackup; // Performs a shallow copy into other - (void)copyTo:(SOSAccountConfiguration *)other; diff --git a/keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.m b/keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.m index 0314479a..f394355f 100644 --- a/keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.m +++ b/keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.m @@ -38,20 +38,6 @@ { return [NSString class]; } -@synthesize ringUpdateFlag = _ringUpdateFlag; -- (void)setRingUpdateFlag:(BOOL)v -{ - _has.ringUpdateFlag = YES; - _ringUpdateFlag = v; -} -- (void)setHasRingUpdateFlag:(BOOL)f -{ - _has.ringUpdateFlag = f; -} -- (BOOL)hasRingUpdateFlag -{ - return _has.ringUpdateFlag; -} - (NSString *)description { @@ -65,10 +51,6 @@ { [dict setObject:self->_pendingBackupPeers forKey:@"pendingBackupPeers"]; } - if (self->_has.ringUpdateFlag) - { - [dict setObject:[NSNumber numberWithBool:self->_ringUpdateFlag] forKey:@"ringUpdateFlag"]; - } return dict; } @@ -97,12 +79,6 @@ BOOL SOSAccountConfigurationReadFrom(__unsafe_unretained SOSAccountConfiguration } } break; - case 2 /* ringUpdateFlag */: - { - self->_has.ringUpdateFlag = YES; - self->_ringUpdateFlag = PBReaderReadBOOL(reader); - } - break; default: if (!PBReaderSkipValueWithTag(reader, tag, aType)) return NO; @@ -125,13 +101,6 @@ BOOL SOSAccountConfigurationReadFrom(__unsafe_unretained SOSAccountConfiguration PBDataWriterWriteStringField(writer, s_pendingBackupPeers, 1); } } - /* ringUpdateFlag */ - { - if (self->_has.ringUpdateFlag) - { - PBDataWriterWriteBOOLField(writer, self->_ringUpdateFlag, 2); - } - } } - (void)copyTo:(SOSAccountConfiguration *)other @@ -145,11 +114,6 @@ BOOL SOSAccountConfigurationReadFrom(__unsafe_unretained SOSAccountConfiguration [other addPendingBackupPeers:[self pendingBackupPeersAtIndex:i]]; } } - if (self->_has.ringUpdateFlag) - { - other->_ringUpdateFlag = _ringUpdateFlag; - other->_has.ringUpdateFlag = YES; - } } - (id)copyWithZone:(NSZone *)zone @@ -160,11 +124,6 @@ BOOL SOSAccountConfigurationReadFrom(__unsafe_unretained SOSAccountConfiguration NSString *vCopy = [v copyWithZone:zone]; [copy addPendingBackupPeers:vCopy]; } - if (self->_has.ringUpdateFlag) - { - copy->_ringUpdateFlag = _ringUpdateFlag; - copy->_has.ringUpdateFlag = YES; - } return copy; } @@ -174,8 +133,6 @@ BOOL SOSAccountConfigurationReadFrom(__unsafe_unretained SOSAccountConfiguration return [other isMemberOfClass:[self class]] && ((!self->_pendingBackupPeers && !other->_pendingBackupPeers) || [self->_pendingBackupPeers isEqual:other->_pendingBackupPeers]) - && - ((self->_has.ringUpdateFlag && other->_has.ringUpdateFlag && ((self->_ringUpdateFlag && other->_ringUpdateFlag) || (!self->_ringUpdateFlag && !other->_ringUpdateFlag))) || (!self->_has.ringUpdateFlag && !other->_has.ringUpdateFlag)) ; } @@ -184,8 +141,6 @@ BOOL SOSAccountConfigurationReadFrom(__unsafe_unretained SOSAccountConfiguration return 0 ^ [self->_pendingBackupPeers hash] - ^ - (self->_has.ringUpdateFlag ? PBHashInt((NSUInteger)self->_ringUpdateFlag) : 0) ; } @@ -195,11 +150,6 @@ BOOL SOSAccountConfigurationReadFrom(__unsafe_unretained SOSAccountConfiguration { [self addPendingBackupPeers:iter_pendingBackupPeers]; } - if (other->_has.ringUpdateFlag) - { - self->_ringUpdateFlag = other->_ringUpdateFlag; - self->_has.ringUpdateFlag = YES; - } } @end diff --git a/keychain/Trieste/.swiftlint.yml b/keychain/Trieste/.swiftlint.yml new file mode 100644 index 00000000..9ddd0d11 --- /dev/null +++ b/keychain/Trieste/.swiftlint.yml @@ -0,0 +1,3 @@ +disabled_rules: + - force_cast + - force_try diff --git a/keychain/Trieste/OctagonTriesteTests/Tests/OctagonTriesteTests/OctagonTests.swift b/keychain/Trieste/OctagonTriesteTests/Tests/OctagonTriesteTests/OctagonTests.swift index a97ab461..9689ecae 100644 --- a/keychain/Trieste/OctagonTriesteTests/Tests/OctagonTriesteTests/OctagonTests.swift +++ b/keychain/Trieste/OctagonTriesteTests/Tests/OctagonTriesteTests/OctagonTests.swift @@ -77,7 +77,7 @@ final class OctagonTests: CDTTestCase { } addTeardownBlock { - if (self.signedIn) { + if self.signedIn { do { let listAccounts = try device.executeFile(atPath: "/usr/local/bin/accounts_tool", withArguments: ["--no-confirmation", "deleteAccountsForUsername", self.username!]) XCTAssertEqual(listAccounts.returnCode, 0, "deleteAccountsForUsername") @@ -89,7 +89,7 @@ final class OctagonTests: CDTTestCase { device.relinquish() } - if (self.username != nil) { + if self.username != nil { CDALog(at: .infoLevel, "Signing in to iCloud here \(self.username!)") let listAccounts = try device.executeFile(atPath: "/usr/local/bin/accounts_tool", withArguments: ["listAccounts", "-v"]) @@ -150,13 +150,13 @@ final class OctagonTests: CDTTestCase { func sosStatus(_ device: CDAIOSDevice, verbose: Bool = false) throws { let result = try device.executeFile(atPath: securityTool, withArguments: ["sync", "-i"]) - if (verbose) { + if verbose { print("security sync -i\n\(String(data: result.standardOutput, encoding: .utf8)!)\n") } } func ckksStatus(_ device: CDAIOSDevice, verbose: Bool = false) throws -> NSDictionary { let ckks = try device.executeFile(atPath: self.ckksTool, withArguments: ["status", "--json"]) - if (verbose) { + if verbose { print("ckks status\n\(String(data: ckks.standardOutput, encoding: .utf8)!)\n") } @@ -164,7 +164,7 @@ final class OctagonTests: CDTTestCase { } func sosApplication(_ device: CDAIOSDevice, verbose: Bool = false) throws { - if (self.password != nil) { + if self.password != nil { print("submitting application\n") @@ -182,7 +182,7 @@ final class OctagonTests: CDTTestCase { } func sosApprove(_ device: CDAIOSDevice, verbose: Bool = false) throws { - if (self.password != nil) { + if self.password != nil { print("approving applications\n") @@ -200,7 +200,7 @@ final class OctagonTests: CDTTestCase { } func forceResetSOS(_ device: CDAIOSDevice, resetCKKS: Bool = false) throws { - if (self.password != nil) { + if self.password != nil { _ = try device.executeFile(atPath: securityTool, withArguments: ["sync", "-P", self.password!]) _ = try device.executeFile(atPath: securityTool, withArguments: ["sync", "-R"]) _ = try device.executeFile(atPath: securityTool, withArguments: ["sync", "-C"]) @@ -213,7 +213,7 @@ final class OctagonTests: CDTTestCase { print("sleeping some to allow cdpd, cloudd and friends to catch up \n") sleep(4) - if (resetCKKS) { + if resetCKKS { _ = try device.executeFile(atPath: self.ckksTool, withArguments: ["reset-cloudkit"]) print("sleeps some after ckksctl reset (should be removed)\n") @@ -234,7 +234,7 @@ final class OctagonTests: CDTTestCase { } func test2DeviceSOS() throws { - if (self.password == nil) { + if self.password == nil { print("this test only works with password") return } @@ -272,9 +272,9 @@ final class OctagonTests: CDTTestCase { for i in 0..<2 { CDALog(at: .infoLevel, "Reset \(i)") - octagon.octagonReset("altDSID", complete: { _, error in + octagon.octagonReset("altDSID") { _, error in CDTAssert(error == nil, "Octagon wasn't reset, error was \(String(describing: error))") - }) + } } } diff --git a/keychain/TrustedPeersHelper/BottledPeer/BottledPeer.swift b/keychain/TrustedPeersHelper/BottledPeer/BottledPeer.swift index ff8a8929..d18c8a9f 100644 --- a/keychain/TrustedPeersHelper/BottledPeer/BottledPeer.swift +++ b/keychain/TrustedPeersHelper/BottledPeer/BottledPeer.swift @@ -166,7 +166,7 @@ class BottledPeer: NSObject { let ciphertext = _SFAuthenticatedCiphertext.init(ciphertext: ac.ciphertext, authenticationCode: ac.authenticationCode, initializationVector: ac.initializationVector) let clearContentsData = try op.decrypt(ciphertext, with: escrowKeys.symmetricKey) - if clearContentsData.count == 0 { + if clearContentsData.isEmpty { throw Error.OTErrorDecryptionFailure } diff --git a/keychain/TrustedPeersHelper/BottledPeer/EscrowKeys.swift b/keychain/TrustedPeersHelper/BottledPeer/EscrowKeys.swift index 04bb5f27..4669d72f 100644 --- a/keychain/TrustedPeersHelper/BottledPeer/EscrowKeys.swift +++ b/keychain/TrustedPeersHelper/BottledPeer/EscrowKeys.swift @@ -28,7 +28,7 @@ let OT_ESCROW_SIGNING_HKDF_SIZE = 56 let OT_ESCROW_ENCRYPTION_HKDF_SIZE = 56 let OT_ESCROW_SYMMETRIC_HKDF_SIZE = 32 -enum escrowKeyType: Int { +enum EscrowKeyType: Int { case kOTEscrowKeySigning = 1 case kOTEscrowKeyEncryption = 2 case kOTEscrowKeySymmetric = 3 @@ -46,13 +46,13 @@ class EscrowKeys: NSObject { self.secret = secret self.bottleSalt = bottleSalt - let encryptionKeyData = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret, bottleSalt: bottleSalt) + let encryptionKeyData = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret, bottleSalt: bottleSalt) self.encryptionKey = _SFECKeyPair.init(secKey: try EscrowKeys.createSecKey(keyData: encryptionKeyData)) - let signingKeyData = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: secret, bottleSalt: bottleSalt) + let signingKeyData = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySigning, masterSecret: secret, bottleSalt: bottleSalt) self.signingKey = _SFECKeyPair.init(secKey: try EscrowKeys.createSecKey(keyData: signingKeyData)) - let symmetricKeyData = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret, bottleSalt: bottleSalt) + let symmetricKeyData = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret, bottleSalt: bottleSalt) let specifier = _SFAESKeySpecifier.init(bitSize: TPHObjectiveC.aes256BitSize()) self.symmetricKey = try _SFAESKey.init(data: symmetricKeyData, specifier: specifier) @@ -62,34 +62,31 @@ class EscrowKeys: NSObject { _ = try EscrowKeys.storeEscrowedSymmetricKey(keyData: self.symmetricKey.keyData, label: escrowSigningPubKeyHash) } - class func generateEscrowKey(keyType: escrowKeyType, masterSecret: Data, bottleSalt: String) throws -> (Data) { + class func generateEscrowKey(keyType: EscrowKeyType, masterSecret: Data, bottleSalt: String) throws -> (Data) { var keyLength: Int var info: Data var derivedKey: Data var finalKey = Data() switch keyType { - case escrowKeyType.kOTEscrowKeySymmetric: + case EscrowKeyType.kOTEscrowKeySymmetric: keyLength = OT_ESCROW_SYMMETRIC_HKDF_SIZE let infoString = Array("Escrow Symmetric Key".utf8) info = Data(bytes: infoString, count: infoString.count) - break - case escrowKeyType.kOTEscrowKeyEncryption: + case EscrowKeyType.kOTEscrowKeyEncryption: keyLength = OT_ESCROW_ENCRYPTION_HKDF_SIZE let infoString = Array("Escrow Encryption Private Key".utf8) info = Data(bytes: infoString, count: infoString.count) - break - case escrowKeyType.kOTEscrowKeySigning: + case EscrowKeyType.kOTEscrowKeySigning: keyLength = OT_ESCROW_SIGNING_HKDF_SIZE let infoString = Array("Escrow Signing Private Key".utf8) info = Data(bytes: infoString, count: infoString.count) - break } guard let cp = ccec_cp_384() else { @@ -119,10 +116,10 @@ class EscrowKeys: NSObject { throw EscrowKeysError.corecryptoKeyGeneration(corecryptoError: status) } - if(keyType == escrowKeyType.kOTEscrowKeySymmetric) { + if keyType == EscrowKeyType.kOTEscrowKeySymmetric { finalKey = Data(buffer: derivedKeyBytes.bindMemory(to: UInt8.self)) return - } else if(keyType == escrowKeyType.kOTEscrowKeyEncryption || keyType == escrowKeyType.kOTEscrowKeySigning) { + } else if keyType == EscrowKeyType.kOTEscrowKeyEncryption || keyType == EscrowKeyType.kOTEscrowKeySigning { status = ccec_generate_key_deterministic(cp, derivedKeyBytes.count, derivedKeyBytes.bindMemory(to: UInt8.self).baseAddress!, ccDRBGGetRngState(), @@ -156,7 +153,7 @@ class EscrowKeys: NSObject { return key } - class func setKeyMaterialInKeychain(query: Dictionary) throws -> (Bool) { + class func setKeyMaterialInKeychain(query: [CFString: Any]) throws -> (Bool) { var result = false var results: CFTypeRef? @@ -165,7 +162,7 @@ class EscrowKeys: NSObject { if status == errSecSuccess { result = true } else if status == errSecDuplicateItem { - var updateQuery: Dictionary = query + var updateQuery: [CFString: Any] = query updateQuery[kSecClass] = nil status = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary) @@ -240,8 +237,8 @@ class EscrowKeys: NSObject { return try EscrowKeys.setKeyMaterialInKeychain(query: query) } - class func retrieveEscrowKeysFromKeychain(label: String) throws -> [Dictionary ]? { - var keySet: [Dictionary]? + class func retrieveEscrowKeysFromKeychain(label: String) throws -> [ [CFString: Any]]? { + var keySet: [[CFString: Any]]? let query: [CFString: Any] = [ kSecClass: kSecClassKey, @@ -261,10 +258,10 @@ class EscrowKeys: NSObject { } if result != nil { - if let dictionaryArray = result as? [Dictionary] { + if let dictionaryArray = result as? [[CFString: Any]] { keySet = dictionaryArray } else { - if let dictionary = result as? Dictionary { + if let dictionary = result as? [CFString: Any] { keySet = [dictionary] } else { keySet = nil diff --git a/keychain/TrustedPeersHelper/Client.swift b/keychain/TrustedPeersHelper/Client.swift index 0a062b79..64dfe9f1 100644 --- a/keychain/TrustedPeersHelper/Client.swift +++ b/keychain/TrustedPeersHelper/Client.swift @@ -39,9 +39,9 @@ class Client: TrustedPeersHelperProtocol { func logComplete(function: String, container: ContainerName, error: Error?) { if let error = error { - os_log("%@ errored for %@: %@", log: tplogDebug, type: .default, function, container.description, error as CVarArg) + os_log("%{public}@ errored for %{public}@: %{public}@", log: tplogDebug, type: .default, function, container.description, error as CVarArg) } else { - os_log("%@ finished for %@", log: tplogDebug, type: .default, function, container.description) + os_log("%{public}@ finished for %{public}@", log: tplogDebug, type: .default, function, container.description) } } @@ -53,14 +53,14 @@ class Client: TrustedPeersHelperProtocol { func dump(withContainer container: String, context: String, reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Dumping for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Dumping for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.dump { result, error in self.logComplete(function: "Dumping", container: container.name, error: error) reply(result, CKXPCSuitableError(error)) } } catch { - os_log("Dumping failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Dumping failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, CKXPCSuitableError(error)) } } @@ -70,14 +70,14 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Dumping peer for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Dumping peer for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.dumpEgoPeer { peerID, perm, stable, dyn, error in self.logComplete(function: "Dumping peer", container: container.name, error: error) reply(peerID, perm, stable, dyn, CKXPCSuitableError(error)) } } catch { - os_log("Dumping peer failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Dumping peer failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, nil, nil, nil, CKXPCSuitableError(error)) } } @@ -88,7 +88,7 @@ class Client: TrustedPeersHelperProtocol { let container = try self.containerMap.findOrCreate(name: containerName) container.trustStatus(reply: reply) } catch { - os_log("Trust status failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Trust status failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(TrustedPeersHelperEgoPeerStatus(egoPeerID: nil, status: TPPeerStatus.unknown, viablePeerCountsByModelID: [:], @@ -102,11 +102,11 @@ class Client: TrustedPeersHelperProtocol { func fetchTrustState(withContainer container: String, context: String, reply: @escaping (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Fetch Trust State for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Fetch Trust State for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.fetchTrustState(reply: reply) } catch { - os_log("Fetch Trust State failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Fetch Trust State failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, nil, CKXPCSuitableError(error)) } } @@ -114,13 +114,13 @@ class Client: TrustedPeersHelperProtocol { func reset(withContainer container: String, context: String, resetReason: CuttlefishResetReason, reply: @escaping (Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Resetting for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Resetting for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.reset(resetReason: resetReason) { error in self.logComplete(function: "Resetting", container: container.name, error: error) reply(CKXPCSuitableError(error)) } } catch { - os_log("Resetting failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Resetting failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(CKXPCSuitableError(error)) } } @@ -128,14 +128,14 @@ class Client: TrustedPeersHelperProtocol { func localReset(withContainer container: String, context: String, reply: @escaping (Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Performing local reset for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Performing local reset for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.localReset { error in self.logComplete(function: "Local reset", container: container.name, error: error) reply(CKXPCSuitableError(error)) } } catch { - os_log("Local reset failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Local reset failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(CKXPCSuitableError(error)) } } @@ -143,17 +143,18 @@ class Client: TrustedPeersHelperProtocol { func setAllowedMachineIDsWithContainer(_ container: String, context: String, allowedMachineIDs: Set, + honorIDMSListChanges: Bool, reply: @escaping (Bool, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Setting allowed machineIDs for %@ to %@", log: tplogDebug, type: .default, containerName.description, allowedMachineIDs) + os_log("Setting allowed machineIDs for %{public}@ to %{public}@", log: tplogDebug, type: .default, containerName.description, allowedMachineIDs) let container = try self.containerMap.findOrCreate(name: containerName) - container.setAllowedMachineIDs(allowedMachineIDs) { differences, error in + container.setAllowedMachineIDs(allowedMachineIDs, honorIDMSListChanges: honorIDMSListChanges) { differences, error in self.logComplete(function: "Setting allowed machineIDs", container: container.name, error: error) reply(differences, CKXPCSuitableError(error)) } } catch { - os_log("Setting allowed machineIDs failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Setting allowed machineIDs failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(false, CKXPCSuitableError(error)) } } @@ -164,14 +165,14 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Adding allowed machineIDs for %@: %@", log: tplogDebug, type: .default, containerName.description, machineIDs) + os_log("Adding allowed machineIDs for %{public}@: %{public}@", log: tplogDebug, type: .default, containerName.description, machineIDs) let container = try self.containerMap.findOrCreate(name: containerName) container.addAllow(machineIDs) { error in self.logComplete(function: "Adding allowed machineIDs", container: container.name, error: error) reply(CKXPCSuitableError(error)) } } catch { - os_log("Adding allowed machineID failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Adding allowed machineID failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(CKXPCSuitableError(error)) } } @@ -182,14 +183,14 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Removing allowed machineIDs for %@: %@", log: tplogDebug, type: .default, containerName.description, machineIDs) + os_log("Removing allowed machineIDs for %{public}@: %{public}@", log: tplogDebug, type: .default, containerName.description, machineIDs) let container = try self.containerMap.findOrCreate(name: containerName) container.removeAllow(machineIDs) { error in self.logComplete(function: "Removing allowed machineIDs", container: container.name, error: error) reply(CKXPCSuitableError(error)) } } catch { - os_log("Removing allowed machineID failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Removing allowed machineID failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(CKXPCSuitableError(error)) } } @@ -197,14 +198,14 @@ class Client: TrustedPeersHelperProtocol { func fetchAllowedMachineIDs(withContainer container: String, context: String, reply: @escaping (Set?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Fetching allowed machineIDs for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Fetching allowed machineIDs for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) - container.fetchAllowedMachineIDs() { mids, error in + container.fetchAllowedMachineIDs { mids, error in self.logComplete(function: "Fetched allowed machineIDs", container: container.name, error: error) reply(mids, CKXPCSuitableError(error)) } } catch { - os_log("Fetching allowed machineIDs failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Fetching allowed machineIDs failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, CKXPCSuitableError(error)) } } @@ -212,13 +213,13 @@ class Client: TrustedPeersHelperProtocol { func fetchEgoEpoch(withContainer container: String, context: String, reply: @escaping (UInt64, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("retrieving epoch for %@", log: tplogDebug, type: .default, containerName.description) + os_log("retrieving epoch for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.getEgoEpoch { epoch, error in reply(epoch, CKXPCSuitableError(error)) } } catch { - os_log("Epoch retrieval failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Epoch retrieval failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(0, CKXPCSuitableError(error)) } } @@ -233,14 +234,14 @@ class Client: TrustedPeersHelperProtocol { deviceName: String?, serialNumber: String, osVersion: String, - policyVersion: NSNumber?, + policyVersion: TPPolicyVersion?, policySecrets: [String: Data]?, signingPrivKeyPersistentRef: Data?, encPrivKeyPersistentRef: Data?, - reply: @escaping (String?, Data?, Data?, Data?, Data?, Error?) -> Void) { + reply: @escaping (String?, Data?, Data?, Data?, Data?, Set?, TPPolicy?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Preparing new identity for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Preparing new identity for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.prepare(epoch: epoch, machineID: machineID, @@ -250,16 +251,16 @@ class Client: TrustedPeersHelperProtocol { deviceName: deviceName, serialNumber: serialNumber, osVersion: osVersion, - policyVersion: policyVersion?.uint64Value, + policyVersion: policyVersion, policySecrets: policySecrets, signingPrivateKeyPersistentRef: signingPrivKeyPersistentRef, - encryptionPrivateKeyPersistentRef: encPrivKeyPersistentRef) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encryptionPrivateKeyPersistentRef: encPrivKeyPersistentRef) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, views, policy, error in self.logComplete(function: "Prepare", container: container.name, error: error) - reply(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, CKXPCSuitableError(error)) + reply(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, views, policy, CKXPCSuitableError(error)) } } catch { - os_log("Prepare failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) - reply(nil, nil, nil, nil, nil, CKXPCSuitableError(error)) + os_log("Prepare failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) + reply(nil, nil, nil, nil, nil, nil, nil, CKXPCSuitableError(error)) } } @@ -271,7 +272,7 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (String?, [CKRecord]?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Establishing %@", log: tplogDebug, type: .default, containerName.description) + os_log("Establishing %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.establish(ckksKeys: ckksKeys, tlkShares: tlkShares, @@ -279,7 +280,7 @@ class Client: TrustedPeersHelperProtocol { self.logComplete(function: "Establishing", container: container.name, error: error) reply(peerID, keyHierarchyRecords, CKXPCSuitableError(error)) } } catch { - os_log("Establishing failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Establishing failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, nil, CKXPCSuitableError(error)) } } @@ -295,7 +296,7 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (Data?, Data?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Vouching %@", log: tplogDebug, type: .default, containerName.description) + os_log("Vouching %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.vouch(peerID: peerID, permanentInfo: permanentInfo, @@ -306,7 +307,7 @@ class Client: TrustedPeersHelperProtocol { self.logComplete(function: "Vouching", container: container.name, error: error) reply(voucher, voucherSig, CKXPCSuitableError(error)) } } catch { - os_log("Vouching failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Vouching failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, nil, CKXPCSuitableError(error)) } } @@ -314,17 +315,17 @@ class Client: TrustedPeersHelperProtocol { func preflightVouchWithBottle(withContainer container: String, context: String, bottleID: String, - reply: @escaping (String?, Error?) -> Void) { + reply: @escaping (String?, Set?, TPPolicy?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Preflight Vouch With Bottle %@", log: tplogDebug, type: .default, containerName.description) + os_log("Preflight Vouch With Bottle %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) - container.preflightVouchWithBottle(bottleID: bottleID) { peerID, error in + container.preflightVouchWithBottle(bottleID: bottleID) { peerID, viewSet, policy, error in self.logComplete(function: "Preflight Vouch With Bottle", container: container.name, error: error) - reply(peerID, CKXPCSuitableError(error)) } + reply(peerID, viewSet, policy, CKXPCSuitableError(error)) } } catch { - os_log("Preflighting Vouch With Bottle failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) - reply(nil, CKXPCSuitableError(error)) + os_log("Preflighting Vouch With Bottle failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) + reply(nil, nil, nil, CKXPCSuitableError(error)) } } @@ -334,17 +335,35 @@ class Client: TrustedPeersHelperProtocol { entropy: Data, bottleSalt: String, tlkShares: [CKKSTLKShare], - reply: @escaping (Data?, Data?, Error?) -> Void) { + reply: @escaping (Data?, Data?, Int64, Int64, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Vouching With Bottle %@", log: tplogDebug, type: .default, containerName.description) + os_log("Vouching With Bottle %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) - container.vouchWithBottle(bottleID: bottleID, entropy: entropy, bottleSalt: bottleSalt, tlkShares: tlkShares) { voucher, voucherSig, error in + container.vouchWithBottle(bottleID: bottleID, entropy: entropy, bottleSalt: bottleSalt, tlkShares: tlkShares) { voucher, voucherSig, uniqueTLKsRecovered, totalTLKSharesRecovered, error in self.logComplete(function: "Vouching With Bottle", container: container.name, error: error) - reply(voucher, voucherSig, CKXPCSuitableError(error)) } + reply(voucher, voucherSig, uniqueTLKsRecovered, totalTLKSharesRecovered, CKXPCSuitableError(error)) } } catch { - os_log("Vouching with Bottle failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) - reply(nil, nil, CKXPCSuitableError(error)) + os_log("Vouching with Bottle failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) + reply(nil, nil, 0, 0, CKXPCSuitableError(error)) + } + } + + func preflightVouchWithRecoveryKey(withContainer container: String, + context: String, + recoveryKey: String, + salt: String, + reply: @escaping (String?, Set?, TPPolicy?, Error?) -> Void) { + do { + let containerName = ContainerName(container: container, context: context) + os_log("Preflight Vouch With RecoveryKey %{public}@", log: tplogDebug, type: .default, containerName.description) + let container = try self.containerMap.findOrCreate(name: containerName) + container.preflightVouchWithRecoveryKey(recoveryKey: recoveryKey, salt: salt) { rkID, viewSet, policy, error in + self.logComplete(function: "Preflight Vouch With RecoveryKey", container: container.name, error: error) + reply(rkID, viewSet, policy, CKXPCSuitableError(error)) } + } catch { + os_log("Preflighting Vouch With RecoveryKey failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) + reply(nil, nil, nil, CKXPCSuitableError(error)) } } @@ -356,13 +375,13 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (Data?, Data?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Vouching With Recovery Key %@", log: tplogDebug, type: .default, containerName.description) + os_log("Vouching With Recovery Key %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.vouchWithRecoveryKey(recoveryKey: recoveryKey, salt: salt, tlkShares: tlkShares) { voucher, voucherSig, error in self.logComplete(function: "Vouching With Recovery Key", container: container.name, error: error) reply(voucher, voucherSig, CKXPCSuitableError(error)) } } catch { - os_log("Vouching with Recovery Key failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("Vouching with Recovery Key failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, nil, CKXPCSuitableError(error)) } } @@ -374,18 +393,22 @@ class Client: TrustedPeersHelperProtocol { ckksKeys: [CKKSKeychainBackedKeySet], tlkShares: [CKKSTLKShare], preapprovedKeys: [Data], - reply: @escaping (String?, [CKRecord]?, Error?) -> Void) { + reply: @escaping (String?, [CKRecord]?, Set?, TPPolicy?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Joining %@", log: tplogDebug, type: .default, containerName.description) + os_log("Joining %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.join(voucherData: voucherData, voucherSig: voucherSig, ckksKeys: ckksKeys, tlkShares: tlkShares, - preapprovedKeys: preapprovedKeys) { peerID, keyHierarchyRecords, error in reply(peerID, keyHierarchyRecords, CKXPCSuitableError(error)) } + preapprovedKeys: preapprovedKeys) { peerID, keyHierarchyRecords, views, policy, error in + reply(peerID, keyHierarchyRecords, views, policy, CKXPCSuitableError(error)) + + } } catch { - reply(nil, nil, CKXPCSuitableError(error)) + os_log("Joining failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) + reply(nil, nil, nil, nil, CKXPCSuitableError(error)) } } @@ -394,10 +417,11 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (Bool, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Attempting to preflight a preapproved join for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Attempting to preflight a preapproved join for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.preflightPreapprovedJoin { success, error in reply(success, CKXPCSuitableError(error)) } } catch { + os_log("preflightPreapprovedJoin failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(false, CKXPCSuitableError(error)) } } @@ -407,16 +431,18 @@ class Client: TrustedPeersHelperProtocol { ckksKeys: [CKKSKeychainBackedKeySet], tlkShares: [CKKSTLKShare], preapprovedKeys: [Data], - reply: @escaping (String?, [CKRecord]?, Error?) -> Void) { + reply: @escaping (String?, [CKRecord]?, Set?, TPPolicy?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Attempting a preapproved join for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Attempting a preapproved join for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.preapprovedJoin(ckksKeys: ckksKeys, tlkShares: tlkShares, - preapprovedKeys: preapprovedKeys) { peerID, keyHierarchyRecords, error in reply(peerID, keyHierarchyRecords, CKXPCSuitableError(error)) } + preapprovedKeys: preapprovedKeys) { peerID, keyHierarchyRecords, viewSet, policy, error in + reply(peerID, keyHierarchyRecords, viewSet, policy, CKXPCSuitableError(error)) } } catch { - reply(nil, nil, CKXPCSuitableError(error)) + os_log("attemptPreapprovedJoin failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) + reply(nil, nil, nil, nil, CKXPCSuitableError(error)) } } @@ -430,7 +456,7 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Updating %@", log: tplogDebug, type: .default, containerName.description) + os_log("Updating %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.update(deviceName: deviceName, serialNumber: serialNumber, @@ -438,21 +464,23 @@ class Client: TrustedPeersHelperProtocol { policyVersion: policyVersion?.uint64Value, policySecrets: policySecrets) { state, error in reply(state, CKXPCSuitableError(error)) } } catch { + os_log("update failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, CKXPCSuitableError(error)) } } func setPreapprovedKeysWithContainer(_ container: String, - context: String, - preapprovedKeys: [Data], - reply: @escaping (Error?) -> Void) { + context: String, + preapprovedKeys: [Data], + reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Updating %@", log: tplogDebug, type: .default, containerName.description) + os_log("setPreapprovedKeysWithContainer %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) - container.set(preapprovedKeys: preapprovedKeys) { error in reply(CKXPCSuitableError(error)) } + container.set(preapprovedKeys: preapprovedKeys) { state, error in reply(state, CKXPCSuitableError(error)) } } catch { - reply(CKXPCSuitableError(error)) + os_log("setPreapprovedKeysWithContainer failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) + reply(nil, CKXPCSuitableError(error)) } } @@ -463,12 +491,13 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping ([CKRecord]?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Updating TLKs for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Updating TLKs for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.updateTLKs(ckksKeys: ckksKeys, tlkShares: tlkShares, reply: reply) } catch { + os_log("updateTLKs failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, CKXPCSuitableError(error)) } } @@ -478,12 +507,13 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Departing %@", log: tplogDebug, type: .default, containerName.description) + os_log("Departing %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.departByDistrustingSelf { error in reply(CKXPCSuitableError(error)) } } catch { + os_log("departByDistrustingSelf failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(CKXPCSuitableError(error)) } } @@ -494,12 +524,13 @@ class Client: TrustedPeersHelperProtocol { reply: @escaping (Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Distrusting %@ in %@", log: tplogDebug, type: .default, peerIDs, containerName.description) + os_log("Distrusting %{public}@ in %{public}@", log: tplogDebug, type: .default, peerIDs, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.distrust(peerIDs: peerIDs) { error in reply(CKXPCSuitableError(error)) } } catch { + os_log("distrustPeerIDs failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(CKXPCSuitableError(error)) } } @@ -507,12 +538,13 @@ class Client: TrustedPeersHelperProtocol { func fetchViableBottles(withContainer container: String, context: String, reply: @escaping ([String]?, [String]?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("fetchViableBottles in %@", log: tplogDebug, type: .default, containerName.description) + os_log("fetchViableBottles in %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.fetchViableBottles { sortedBottleIDs, partialBottleIDs, error in reply(sortedBottleIDs, partialBottleIDs, CKXPCSuitableError(error)) } } catch { + os_log("fetchViableBottles failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, nil, CKXPCSuitableError(error)) } } @@ -520,43 +552,46 @@ class Client: TrustedPeersHelperProtocol { func fetchEscrowContents(withContainer container: String, context: String, reply: @escaping (Data?, String?, Data?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("fetchEscrowContents in %@", log: tplogDebug, type: .default, containerName.description) + os_log("fetchEscrowContents in %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.fetchEscrowContents { entropy, bottleID, signingPublicKey, error in reply(entropy, bottleID, signingPublicKey, CKXPCSuitableError(error)) } } catch { + os_log("fetchEscrowContents failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, nil, nil, CKXPCSuitableError(error)) } } - func fetchPolicy(withContainer container: String, - context: String, - reply: @escaping (TPPolicy?, Error?) -> Void) { + func fetchCurrentPolicy(withContainer container: String, + context: String, + reply: @escaping (Set?, TPPolicy?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Fetching policy for %@", log: tplogDebug, type: .default, containerName.description) + os_log("Fetching policy+views for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) - container.fetchPolicy { policy, error in - reply(policy, CKXPCSuitableError(error)) + container.fetchCurrentPolicy { viewList, policy, error in + reply(viewList, policy, CKXPCSuitableError(error)) } } catch { - reply(nil, CKXPCSuitableError(error)) + os_log("fetchCurrentPolicy failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) + reply(nil, nil, CKXPCSuitableError(error)) } } func fetchPolicyDocuments(withContainer container: String, context: String, - keys: [NSNumber: String], - reply: @escaping ([NSNumber: [String]]?, Error?) -> Void) { + versions: Set, + reply: @escaping ([TPPolicyVersion: Data]?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Fetching policy documents %@ with keys: %@", log: tplogDebug, type: .default, containerName.description, keys) + os_log("Fetching policy documents %{public}@ with versions: %{public}@", log: tplogDebug, type: .default, containerName.description, versions) let container = try self.containerMap.findOrCreate(name: containerName) - container.fetchPolicyDocuments(keys: keys) { entries, error in + container.fetchPolicyDocuments(versions: versions) { entries, error in reply(entries, CKXPCSuitableError(error)) } } catch { + os_log("fetchPolicyDocuments failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, CKXPCSuitableError(error)) } } @@ -564,7 +599,7 @@ class Client: TrustedPeersHelperProtocol { func validatePeers(withContainer container: String, context: String, reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("ValidatePeers for %@", log: tplogDebug, type: .default, containerName.description) + os_log("ValidatePeers for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) let request = ValidatePeersRequest() container.validatePeers(request: request) { result, error in @@ -572,7 +607,7 @@ class Client: TrustedPeersHelperProtocol { reply(result, CKXPCSuitableError(error)) } } catch { - os_log("ValidatePeers failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("ValidatePeers failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, CKXPCSuitableError(error)) } } @@ -580,14 +615,14 @@ class Client: TrustedPeersHelperProtocol { func setRecoveryKeyWithContainer(_ container: String, context: String, recoveryKey: String, salt: String, ckksKeys: [CKKSKeychainBackedKeySet], reply: @escaping (Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("SetRecoveryKey for %@", log: tplogDebug, type: .default, containerName.description) + os_log("SetRecoveryKey for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.setRecoveryKey(recoveryKey: recoveryKey, salt: salt, ckksKeys: ckksKeys) { error in self.logComplete(function: "setRecoveryKey", container: container.name, error: error) reply(CKXPCSuitableError(error)) } } catch { - os_log("SetRecoveryKey failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("SetRecoveryKey failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(CKXPCSuitableError(error)) } } @@ -595,7 +630,7 @@ class Client: TrustedPeersHelperProtocol { func reportHealth(withContainer container: String, context: String, stateMachineState: String, trustState: String, reply: @escaping (Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("ReportHealth for %@", log: tplogDebug, type: .default, containerName.description) + os_log("ReportHealth for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) let request = ReportHealthRequest.with { $0.stateMachineState = stateMachineState @@ -605,7 +640,7 @@ class Client: TrustedPeersHelperProtocol { reply(CKXPCSuitableError(error)) } } catch { - os_log("ReportHealth failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("ReportHealth failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(CKXPCSuitableError(error)) } } @@ -613,56 +648,42 @@ class Client: TrustedPeersHelperProtocol { func pushHealthInquiry(withContainer container: String, context: String, reply: @escaping (Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("PushHealthInquiry for %@", log: tplogDebug, type: .default, containerName.description) + os_log("PushHealthInquiry for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.pushHealthInquiry { error in self.logComplete(function: "pushHealthInquiry", container: container.name, error: error) reply(CKXPCSuitableError(error)) } } catch { - os_log("PushHealthInquiry failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("PushHealthInquiry failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(CKXPCSuitableError(error)) } } - func getViewsWithContainer(_ container: String, context: String, inViews: [String], reply: @escaping ([String]?, Error?) -> Void) { - do { - let containerName = ContainerName(container: container, context: context) - os_log("GetViews (%@) for %@", log: tplogDebug, type: .default, inViews, containerName.description) - let container = try self.containerMap.findOrCreate(name: containerName) - container.getViews(inViews: inViews) { outViews, error in - reply(outViews, CKXPCSuitableError(error)) - } - } catch { - os_log("GetViews failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) - reply(nil, CKXPCSuitableError(error)) - } - } - - func requestHealthCheck(withContainer container: String, context: String, requiresEscrowCheck: Bool, reply: @escaping (Bool, Bool, Bool, Error?) -> Void) { + func requestHealthCheck(withContainer container: String, context: String, requiresEscrowCheck: Bool, reply: @escaping (Bool, Bool, Bool, Bool, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("Health Check! requiring escrow check? %d for %@", log: tplogDebug, type: .default, requiresEscrowCheck, containerName.description) + os_log("Health Check! requiring escrow check? %d for %{public}@", log: tplogDebug, type: .default, requiresEscrowCheck, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) - container.requestHealthCheck(requiresEscrowCheck: requiresEscrowCheck) { postRepair, postEscrow, postReset, error in - reply(postRepair, postEscrow, postReset, CKXPCSuitableError(error)) + container.requestHealthCheck(requiresEscrowCheck: requiresEscrowCheck) { postRepair, postEscrow, postReset, leaveTrust, error in + reply(postRepair, postEscrow, postReset, leaveTrust, CKXPCSuitableError(error)) } } catch { - os_log("Health Check! failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) - reply(false, false, false, CKXPCSuitableError(error)) + os_log("Health Check! failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) + reply(false, false, false, false, CKXPCSuitableError(error)) } } func getSupportAppInfo(withContainer container: String, context: String, reply: @escaping (Data?, Error?) -> Void) { do { let containerName = ContainerName(container: container, context: context) - os_log("getSupportInfo %d for %@", log: tplogDebug, type: .default, containerName.description) + os_log("getSupportInfo %d for %{public}@", log: tplogDebug, type: .default, containerName.description) let container = try self.containerMap.findOrCreate(name: containerName) container.getSupportAppInfo { info, error in reply(info, CKXPCSuitableError(error)) } } catch { - os_log("getSupportInfo failed for (%@, %@): %@", log: tplogDebug, type: .default, container, context, error as CVarArg) + os_log("getSupportInfo failed for (%{public}@, %{public}@): %{public}@", log: tplogDebug, type: .default, container, context, error as CVarArg) reply(nil, CKXPCSuitableError(error)) } diff --git a/keychain/TrustedPeersHelper/Container.swift b/keychain/TrustedPeersHelper/Container.swift index fffd68b1..b5ac7067 100644 --- a/keychain/TrustedPeersHelper/Container.swift +++ b/keychain/TrustedPeersHelper/Container.swift @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Apple Inc. All Rights Reserved. + * Copyright (c) 2018 - 2020 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -99,6 +99,7 @@ public enum ContainerError: Error { case failedToStoreSecret(errorCode: Int) case unknownSecurityFoundationError case failedToSerializeData + case unknownInternalError } extension ContainerError: LocalizedError { @@ -188,6 +189,8 @@ extension ContainerError: LocalizedError { return "SecurityFoundation returned an unknown type" case .failedToSerializeData: return "Failed to encode protobuf data" + case .unknownInternalError: + return "Internal code failed, but didn't return error" } } } @@ -286,6 +289,8 @@ extension ContainerError: CustomNSError { return 43 case .failedToSerializeData: return 44 + case .unknownInternalError: + return 45 } } @@ -427,14 +432,14 @@ func loadEgoKeyPair(identifier: String, resultHandler: @escaping (_SFECKeyPair?, func loadEgoKeys(peerID: String, resultHandler: @escaping (OctagonSelfPeerKeys?, Error?) -> Void) { loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: peerID)) { signingKey, error in guard let signingKey = signingKey else { - os_log("Unable to load signing key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + os_log("Unable to load signing key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") resultHandler(nil, error) return } loadEgoKeyPair(identifier: encryptionKeyIdentifier(peerID: peerID)) { encryptionKey, error in guard let encryptionKey = encryptionKey else { - os_log("Unable to load encryption key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + os_log("Unable to load encryption key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") resultHandler(nil, error) return } @@ -543,8 +548,12 @@ func makeTLKShares(ckksTLKs: [CKKSKeychainBackedKey]?, asPeer: CKKSSelfPeer, toP }.compactMap { $0 } } -func extract(tlkShares: [CKKSTLKShare], peer: CKKSSelfPeer) { - os_log("Attempting to recover %d TLK shares for peer %@", log: tplogDebug, type: .default, tlkShares.count, peer.peerID) +@discardableResult +func extract(tlkShares: [CKKSTLKShare], peer: OctagonSelfPeerKeys, model: TPModel) -> (Int64, Int64) { + os_log("Attempting to recover %d TLK shares for peer %{public}@", log: tplogDebug, type: .default, tlkShares.count, peer.peerID) + var tlksRecovered: Set = Set() + var sharesRecovered: Int64 = 0 + for share in tlkShares { guard share.receiverPeerID == peer.peerID else { os_log("Skipping %@ (wrong peerID)", log: tplogDebug, type: .default, share) @@ -552,18 +561,39 @@ func extract(tlkShares: [CKKSTLKShare], peer: CKKSSelfPeer) { } do { - // TODO: how should we handle peer sets here? + var trustedPeers: [AnyHashable] = [peer] + + if let egoPeer = model.peer(withID: peer.peerID) { + egoPeer.trustedPeerIDs.forEach { trustedPeerID in + if let peer = model.peer(withID: trustedPeerID) { + let peerObj = CKKSActualPeer(peerID: trustedPeerID, + encryptionPublicKey: (peer.permanentInfo.encryptionPubKey as! _SFECPublicKey), + signing: (peer.permanentInfo.signingPubKey as! _SFECPublicKey), + viewList: []) + + trustedPeers.append(peerObj) + } else { + os_log("No peer for trusted ID %{public}@", log: tplogDebug, type: .default, trustedPeerID) + } + } + } else { + os_log("No ego peer in model; no trusted peers", log: tplogDebug, type: .default) + } + let key = try share.recoverTLK(peer, - trustedPeers: [peer as! AnyHashable], + trustedPeers: Set(trustedPeers), ckrecord: nil) try key.saveMaterialToKeychain() + tlksRecovered.insert(key.uuid) + sharesRecovered += 1 os_log("Recovered %@ (from %@)", log: tplogDebug, type: .default, key, share) } catch { - os_log("Failed to recover share %@: %@", log: tplogDebug, type: .default, share, error as CVarArg) + os_log("Failed to recover share %@: %{public}@", log: tplogDebug, type: .default, share, error as CVarArg) } } + return (Int64(tlksRecovered.count), sharesRecovered) } struct ContainerState { @@ -581,8 +611,6 @@ internal struct StableChanges { let osVersion: String? let policyVersion: UInt64? let policySecrets: [String: Data]? - let recoverySigningPubKey: Data? - var recoveryEncryptionPubKey: Data? } // CoreData doesn't handle creating an identical model from an identical URL. Help it out. @@ -617,6 +645,17 @@ struct ContainerName: Hashable, CustomStringConvertible { } } +extension ContainerMO { + func egoStableInfo() -> TPPeerStableInfo? { + guard let egoStableData = self.egoPeerStableInfo, + let egoStableSig = self.egoPeerStableInfoSig else { + return nil + } + + return TPPeerStableInfo(data: egoStableData, sig: egoStableSig) + } +} + /// This maps to a Cuttlefish service backed by a CloudKit container, /// and a corresponding local Core Data persistent container. /// @@ -645,7 +684,6 @@ class Container: NSObject { // moc.perform() or moc.performAndWait(). internal var containerMO: ContainerMO internal var model: TPModel - /** Construct a Container. @@ -713,7 +751,6 @@ class Container: NSObject { self.containerMO = containerMO! self.cuttlefish = cuttlefish self.model = model! - super.init() } @@ -736,26 +773,26 @@ class Container: NSObject { do { try model.update(stableInfo, forPeerWithID: permanentInfo.peerID) } catch { - os_log("loadModel unable to update stable info for peer(%@): %@", log: tplogDebug, type: .default, peer, error as CVarArg) + os_log("loadModel unable to update stable info for peer(%{public}@): %{public}@", log: tplogDebug, type: .default, peer, error as CVarArg) } } else { - os_log("loadModel: peer %@ has unparseable stable info", log: tplogDebug, type: .default, permanentInfo.peerID) + os_log("loadModel: peer %{public}@ has unparseable stable info", log: tplogDebug, type: .default, permanentInfo.peerID) } } else { - os_log("loadModel: peer %@ has no stable info", log: tplogDebug, type: .default, permanentInfo.peerID) + os_log("loadModel: peer %{public}@ has no stable info", log: tplogDebug, type: .default, permanentInfo.peerID) } if let data = peer.dynamicInfo, let sig = peer.dynamicInfoSig { if let dynamicInfo = TPPeerDynamicInfo(data: data as Data, sig: sig as Data) { do { try model.update(dynamicInfo, forPeerWithID: permanentInfo.peerID) } catch { - os_log("loadModel unable to update dynamic info for peer(%@): %@", log: tplogDebug, type: .default, peer, error as CVarArg) + os_log("loadModel unable to update dynamic info for peer(%{public}@): %{public}@", log: tplogDebug, type: .default, peer, error as CVarArg) } } else { - os_log("loadModel: peer %@ has unparseable dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID) + os_log("loadModel: peer %{public}@ has unparseable dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID) } } else { - os_log("loadModel: peer %@ has no dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID) + os_log("loadModel: peer %{public}@ has no dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID) } peer.vouchers?.forEach { let v = $0 as! VoucherMO @@ -767,6 +804,20 @@ class Container: NSObject { } } + if let recoveryKeySigningSPKI = containerMO.recoveryKeySigningSPKI, + let recoveryKeyEncyryptionSPKI = containerMO.recoveryKeyEncryptionSPKI { + model.setRecoveryKeys(TPRecoveryKeyPair(signingSPKI: recoveryKeySigningSPKI, encryptionSPKI: recoveryKeyEncyryptionSPKI)) + } else { + // If the ego peer has an RK set, tell the model to use that one + // This is a hack to work around TPH databases which don't have the RK set on the container due to previously running old software + if let egoStableInfo = containerMO.egoStableInfo(), + egoStableInfo.recoverySigningPublicKey.count > 0, + egoStableInfo.recoveryEncryptionPublicKey.count > 0 { + os_log("loadModel: recovery key not set in model, but is set on ego peer", log: tplogDebug, type: .default) + model.setRecoveryKeys(TPRecoveryKeyPair(signingSPKI: egoStableInfo.recoverySigningPublicKey, encryptionSPKI: egoStableInfo.recoveryEncryptionPublicKey)) + } + } + // Register persisted policies (cached from cuttlefish) let policies = containerMO.policies as? Set policies?.forEach { policyMO in @@ -787,10 +838,10 @@ class Container: NSObject { let allowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.allowed.rawValue }.compactMap { $0.machineID }) let disallowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.disallowed.rawValue }.compactMap { $0.machineID }) - os_log("loadModel: allowedMachineIDs: %@", log: tplogDebug, type: .default, allowedMachineIDs) - os_log("loadModel: disallowedMachineIDs: %@", log: tplogDebug, type: .default, disallowedMachineIDs) + os_log("loadModel: allowedMachineIDs: %{public}@", log: tplogDebug, type: .default, allowedMachineIDs) + os_log("loadModel: disallowedMachineIDs: %{public}@", log: tplogDebug, type: .default, disallowedMachineIDs) - if allowedMachineIDs.count == 0 { + if allowedMachineIDs.isEmpty { os_log("loadModel: no allowedMachineIDs?", log: tplogDebug, type: .default) } @@ -866,7 +917,7 @@ class Container: NSObject { guard returnError == nil else { var isLocked = false if let error = (loadError as NSError?) { - os_log("trust status: Unable to load ego keys: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("trust status: Unable to load ego keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg) if error.code == errSecItemNotFound && error.domain == NSOSStatusErrorDomain { os_log("trust status: Lost the ego key pair, returning 'excluded' in hopes of fixing up the identity", log: tplogDebug, type: .debug) isExcluded = true @@ -943,7 +994,7 @@ class Container: NSObject { let reply: (TrustedPeersHelperEgoPeerStatus, Error?) -> Void = { // Suppress logging of successful replies here; it's not that useful let logType: OSLogType = $1 == nil ? .debug : .info - os_log("trustStatus complete: %@ %@", + os_log("trustStatus complete: %{public}@ %{public}@", log: tplogTrace, type: logType, TPPeerStatusToString($0.egoStatus), traceError($1)) self.semaphore.signal() @@ -955,7 +1006,7 @@ class Container: NSObject { self.fetchAndPersistChanges { fetchError in guard fetchError == nil else { if let error = fetchError { - os_log("Unable to fetch changes, trust status is unknown: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("Unable to fetch changes, trust status is unknown: %{public}@", log: tplogDebug, type: .default, error as CVarArg) } let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil, @@ -980,7 +1031,7 @@ class Container: NSObject { func fetchTrustState(reply: @escaping (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void) { let reply: (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void = { - os_log("fetch trust state complete: %@ %@", + os_log("fetch trust state complete: %{public}@ %{public}@", log: tplogTrace, type: .info, String(reflecting: $0), traceError($2)) reply($0, $1, $2) } @@ -998,7 +1049,7 @@ class Container: NSObject { } let isPreapproved = self.model.hasPotentiallyTrustedPeerPreapprovingKey(permanentInfo.signingPubKey.spki()) - os_log("fetchTrustState: ego peer is %@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved") + os_log("fetchTrustState: ego peer is %{public}@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved") let egoStableInfo = self.model.getStableInfoForPeer(withID: egoPeerID) @@ -1015,22 +1066,21 @@ class Container: NSObject { egoPeer.trustedPeerIDs.forEach { trustedPeerID in if let peer = self.model.peer(withID: trustedPeerID) { let peerViews = try? self.model.getViewsForPeer(peer.permanentInfo, - stableInfo: peer.stableInfo, - inViews: Set()) + stableInfo: peer.stableInfo) tphPeers.append(TrustedPeersHelperPeer(peerID: trustedPeerID, signingSPKI: peer.permanentInfo.signingPubKey.spki(), encryptionSPKI: peer.permanentInfo.encryptionPubKey.spki(), viewList: peerViews ?? Set())) } else { - os_log("No peer for trusted ID %@", log: tplogDebug, type: .default, trustedPeerID) + os_log("No peer for trusted ID %{public}@", log: tplogDebug, type: .default, trustedPeerID) } } } else { os_log("No ego peer in model; no trusted peers", log: tplogDebug, type: .default) } - os_log("Returning trust state: %@ %@", log: tplogDebug, type: .default, egoPeerStatus, tphPeers) + os_log("Returning trust state: %{public}@ %@", log: tplogDebug, type: .default, egoPeerStatus, tphPeers) reply(egoPeerStatus, tphPeers, nil) } else { // With no ego peer ID, there are no trusted peers @@ -1042,7 +1092,7 @@ class Container: NSObject { func dump(reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) { let reply: ([AnyHashable: Any]?, Error?) -> Void = { - os_log("dump complete: %@", + os_log("dump complete: %{public}@", log: tplogTrace, type: .info, traceError($1)) reply($0, $1) } @@ -1083,7 +1133,7 @@ class Container: NSObject { func dumpEgoPeer(reply: @escaping (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void) { let reply: (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void = { - os_log("dumpEgoPeer complete: %@", log: tplogTrace, type: .info, traceError($4)) + os_log("dumpEgoPeer complete: %{public}@", log: tplogTrace, type: .info, traceError($4)) reply($0, $1, $2, $3, $4) } self.moc.performAndWait { @@ -1104,15 +1154,15 @@ class Container: NSObject { func validatePeers(request: ValidatePeersRequest, reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) { self.semaphore.wait() let reply: ([AnyHashable: Any]?, Error?) -> Void = { - os_log("validatePeers complete %@", log: tplogTrace, type: .info, traceError($1)) + os_log("validatePeers complete %{public}@", log: tplogTrace, type: .info, traceError($1)) self.semaphore.signal() reply($0, $1) } self.cuttlefish.validatePeers(request) { response, error in - os_log("ValidatePeers(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + os_log("ValidatePeers(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") guard let response = response, error == nil else { - os_log("validatePeers failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("validatePeers failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(nil, error ?? ContainerError.cloudkitResponseMissing) return } @@ -1125,49 +1175,10 @@ class Container: NSObject { } } - func getViews(inViews: [String], reply: @escaping ([String]?, Error?) -> Void) { - let reply: ([String]?, Error?) -> Void = { - os_log("getViews complete %@", log: tplogTrace, type: .info, traceError($1)) - reply($0, $1) - } - self.moc.performAndWait { - guard let egoPeerID = self.containerMO.egoPeerID, - let egoPermData = self.containerMO.egoPeerPermanentInfo, - let egoPermSig = self.containerMO.egoPeerPermanentInfoSig, - let egoStableData = self.containerMO.egoPeerStableInfo, - let egoStableSig = self.containerMO.egoPeerStableInfoSig - else { - os_log("getViews failed to find ego peer information", log: tplogDebug, type: .error) - reply(nil, ContainerError.noPreparedIdentity) - return - } - guard let stableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else { - os_log("getViews failed to create TPPeerStableInfo", log: tplogDebug, type: .error) - reply(nil, ContainerError.invalidStableInfoOrSig) - return - } - - let keyFactory = TPECPublicKeyFactory() - guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { - os_log("getViews failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error) - reply(nil, ContainerError.invalidPermanentInfoOrSig) - return - } - - do { - let views = try self.model.getViewsForPeer(permanentInfo, stableInfo: stableInfo, inViews: Set(inViews)) - reply(Array(views), nil) - } catch { - reply(nil, error) - return - } - } - } - func reset(resetReason: CuttlefishResetReason, reply: @escaping (Error?) -> Void) { self.semaphore.wait() let reply: (Error?) -> Void = { - os_log("reset complete %@", log: tplogTrace, type: .info, traceError($0)) + os_log("reset complete %{public}@", log: tplogTrace, type: .info, traceError($0)) self.semaphore.signal() reply($0) } @@ -1178,9 +1189,9 @@ class Container: NSObject { $0.resetReason = resetReason } self.cuttlefish.reset(request) { response, error in - os_log("Reset(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + os_log("Reset(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") guard let response = response, error == nil else { - os_log("reset failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("reset failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(error ?? ContainerError.cloudkitResponseMissing) return } @@ -1196,7 +1207,7 @@ class Container: NSObject { os_log("reset succeded", log: tplogDebug, type: .default) reply(nil) } catch { - os_log("reset persist failed: %@", log: tplogDebug, type: .default, (error as CVarArg)) + os_log("reset persist failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg)) reply(error) } } @@ -1207,7 +1218,7 @@ class Container: NSObject { func localReset(reply: @escaping (Error?) -> Void) { self.semaphore.wait() let reply: (Error?) -> Void = { - os_log("localReset complete %@", log: tplogTrace, type: .info, traceError($0)) + os_log("localReset complete %{public}@", log: tplogTrace, type: .info, traceError($0)) self.semaphore.signal() reply($0) } @@ -1240,7 +1251,7 @@ class Container: NSObject { } } - // policyVersion should only be non-nil for testing, to override prevailingPolicyVersion + // policyVersion should only be non-nil for testing, to override prevailingPolicyVersion.versionNumber func prepare(epoch: UInt64, machineID: String, bottleSalt: String, @@ -1249,17 +1260,17 @@ class Container: NSObject { deviceName: String?, serialNumber: String, osVersion: String, - policyVersion: UInt64?, + policyVersion: TPPolicyVersion?, policySecrets: [String: Data]?, signingPrivateKeyPersistentRef: Data?, encryptionPrivateKeyPersistentRef: Data?, - reply: @escaping (String?, Data?, Data?, Data?, Data?, Error?) -> Void) { + reply: @escaping (String?, Data?, Data?, Data?, Data?, Set?, TPPolicy?, Error?) -> Void) { self.semaphore.wait() - let reply: (String?, Data?, Data?, Data?, Data?, Error?) -> Void = { - os_log("prepare complete peerID: %@ %@", - log: tplogTrace, type: .info, ($0 ?? "NULL") as CVarArg, traceError($5)) + let reply: (String?, Data?, Data?, Data?, Data?, Set?, TPPolicy?, Error?) -> Void = { + os_log("prepare complete peerID: %{public}@ %{public}@", + log: tplogTrace, type: .info, ($0 ?? "NULL") as CVarArg, traceError($7)) self.semaphore.signal() - reply($0, $1, $2, $3, $4, $5) + reply($0, $1, $2, $3, $4, $5, $6, $7) } // Create a new peer identity with random keys, and store the keys in keychain @@ -1270,6 +1281,7 @@ class Container: NSObject { signingKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: signingPrivateKeyPersistentRef) encryptionKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: encryptionPrivateKeyPersistentRef) + // Octagon: use epoch transmitted across pairing channel permanentInfo = try TPPeerPermanentInfo(machineID: machineID, modelID: modelID, epoch: 1, @@ -1278,7 +1290,7 @@ class Container: NSObject { peerIDHashAlgo: TPHashAlgo.SHA256) } catch { - reply(nil, nil, nil, nil, nil, error) + reply(nil, nil, nil, nil, nil, nil, nil, error) return } @@ -1294,63 +1306,73 @@ class Container: NSObject { _ = try saveSecret(bottle.secret, label: peerID) } catch { - os_log("bottle creation failed: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, nil, nil, nil, nil, error) + os_log("bottle creation failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, nil, nil, nil, nil, nil, nil, error) return } saveEgoKeyPair(signingKeyPair, identifier: signingKeyIdentifier(peerID: peerID)) { success, error in guard success else { - os_log("Unable to save signing key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") - reply(nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity) + os_log("Unable to save signing key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + reply(nil, nil, nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity) return } saveEgoKeyPair(encryptionKeyPair, identifier: encryptionKeyIdentifier(peerID: peerID)) { success, error in guard success else { - os_log("Unable to save encryption key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") - reply(nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity) + os_log("Unable to save encryption key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + reply(nil, nil, nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity) return } - // Save the prepared identity as containerMO.egoPeer* and its bottle - self.moc.performAndWait { - do { - let policyVersion = policyVersion ?? prevailingPolicyVersion - let policyDoc = try self.getPolicyDoc(policyVersion) - - let stableInfo = TPPeerStableInfo(clock: 1, - policyVersion: policyDoc.policyVersion, - policyHash: policyDoc.policyHash, - policySecrets: policySecrets, - deviceName: deviceName, - serialNumber: serialNumber, - osVersion: osVersion, - signing: signingKeyPair, - recoverySigningPubKey: nil, - recoveryEncryptionPubKey: nil, - error: nil) - - self.containerMO.egoPeerID = permanentInfo.peerID - self.containerMO.egoPeerPermanentInfo = permanentInfo.data - self.containerMO.egoPeerPermanentInfoSig = permanentInfo.sig - self.containerMO.egoPeerStableInfo = stableInfo.data - self.containerMO.egoPeerStableInfoSig = stableInfo.sig - - let bottleMO = BottleMO(context: self.moc) - bottleMO.peerID = bottle.peerID - bottleMO.bottleID = bottle.bottleID - bottleMO.escrowedSigningSPKI = bottle.escrowSigningSPKI - bottleMO.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey - bottleMO.signatureUsingPeerKey = bottle.signatureUsingPeerKey - bottleMO.contents = bottle.contents - - self.containerMO.addToBottles(bottleMO) + let policyVersion = policyVersion ?? prevailingPolicyVersion + self.fetchPolicyDocumentWithSemaphore(version: policyVersion) { policyDoc, policyFetchError in + guard let policyDoc = policyDoc, policyFetchError == nil else { + os_log("Unable to fetch policy: %{public}@", log: tplogDebug, type: .default, (policyFetchError as CVarArg?) ?? "error missing") + reply(nil, nil, nil, nil, nil, nil, nil, error ?? ContainerError.unknownInternalError) + return + } - try self.moc.save() + // Save the prepared identity as containerMO.egoPeer* and its bottle + self.moc.performAndWait { + do { - reply(permanentInfo.peerID, permanentInfo.data, permanentInfo.sig, stableInfo.data, stableInfo.sig, nil) - } catch { - reply(nil, nil, nil, nil, nil, error) + let stableInfo = TPPeerStableInfo(clock: 1, + frozenPolicyVersion: frozenPolicyVersion, + flexiblePolicyVersion: policyDoc.version, + policySecrets: policySecrets, + deviceName: deviceName, + serialNumber: serialNumber, + osVersion: osVersion, + signing: signingKeyPair, + recoverySigningPubKey: nil, + recoveryEncryptionPubKey: nil, + error: nil) + + self.containerMO.egoPeerID = permanentInfo.peerID + self.containerMO.egoPeerPermanentInfo = permanentInfo.data + self.containerMO.egoPeerPermanentInfoSig = permanentInfo.sig + self.containerMO.egoPeerStableInfo = stableInfo.data + self.containerMO.egoPeerStableInfoSig = stableInfo.sig + + let bottleMO = BottleMO(context: self.moc) + bottleMO.peerID = bottle.peerID + bottleMO.bottleID = bottle.bottleID + bottleMO.escrowedSigningSPKI = bottle.escrowSigningSPKI + bottleMO.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey + bottleMO.signatureUsingPeerKey = bottle.signatureUsingPeerKey + bottleMO.contents = bottle.contents + + self.containerMO.addToBottles(bottleMO) + + let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: permanentInfo, stableInfo: stableInfo) + + try self.moc.save() + + reply(permanentInfo.peerID, permanentInfo.data, permanentInfo.sig, stableInfo.data, stableInfo.sig, syncingViews, policy, nil) + } catch { + os_log("Unable to save identity: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, nil, nil, nil, nil, nil, nil, error) + } } } } @@ -1358,7 +1380,7 @@ class Container: NSObject { } func getEgoEpoch(reply: @escaping (UInt64, Error?) -> Void) { let reply: (UInt64, Error?) -> Void = { - os_log("getEgoEpoch complete: %d %@", log: tplogTrace, type: .info, $0, traceError($1)) + os_log("getEgoEpoch complete: %d %{public}@", log: tplogTrace, type: .info, $0, traceError($1)) reply($0, $1) } @@ -1381,7 +1403,7 @@ class Container: NSObject { reply: @escaping (String?, [CKRecord], Error?) -> Void) { self.semaphore.wait() let reply: (String?, [CKRecord], Error?) -> Void = { - os_log("establish complete peer: %@ %@", + os_log("establish complete peer: %{public}@ %{public}@", log: tplogTrace, type: .default, ($0 ?? "NULL") as CVarArg, traceError($2)) self.semaphore.signal() reply($0, $1, $2) @@ -1391,7 +1413,9 @@ class Container: NSObject { self.onqueueEstablish(ckksKeys: ckksKeys, tlkShares: tlkShares, preapprovedKeys: preapprovedKeys, - reply: reply) + reply: { peerID, ckrecords, _, _, error in + reply(peerID, ckrecords, error) + }) } } @@ -1402,10 +1426,76 @@ class Container: NSObject { ttr.trigger() } + func fetchAfterEstablish(ckksKeys: [CKKSKeychainBackedKeySet], + tlkShares: [CKKSTLKShare], + reply: @escaping (String?, [CKRecord], Set?, TPPolicy?, Error?) -> Void) { + self.moc.performAndWait { + do { + try self.deleteLocalCloudKitData() + } catch { + os_log("fetchAfterEstablish failed to reset local data: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, [], nil, nil, error) + return + } + self.onqueueFetchAndPersistChanges { error in + guard error == nil else { + os_log("fetchAfterEstablish failed to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + reply(nil, [], nil, nil, error) + return + } + + self.moc.performAndWait { + guard let egoPeerID = self.containerMO.egoPeerID, + let egoPermData = self.containerMO.egoPeerPermanentInfo, + let egoPermSig = self.containerMO.egoPeerPermanentInfoSig, + let egoStableData = self.containerMO.egoPeerStableInfo, + let egoStableSig = self.containerMO.egoPeerStableInfoSig + else { + os_log("fetchAfterEstablish: failed to fetch egoPeerID", log: tplogDebug, type: .default) + reply(nil, [], nil, nil, ContainerError.noPreparedIdentity) + return + } + guard self.model.hasPeer(withID: egoPeerID) else { + os_log("fetchAfterEstablish: did not find peer %{public}@ in model", log: tplogDebug, type: .default, egoPeerID) + reply(nil, [], nil, nil, ContainerError.invalidPeerID) + return + } + let keyFactory = TPECPublicKeyFactory() + guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { + reply(nil, [], nil, nil, ContainerError.invalidPermanentInfoOrSig) + return + } + guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else { + os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default) + reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig) + return + } + self.onqueueUpdateTLKs(ckksKeys: ckksKeys, tlkShares: tlkShares) { ckrecords, error in + guard error == nil else { + os_log("fetchAfterEstablish failed to update TLKs: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + reply(nil, [], nil, nil, error) + return + } + + do { + let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: selfPermanentInfo, + stableInfo: selfStableInfo) + os_log("fetchAfterEstablish succeeded", log: tplogDebug, type: .default) + reply(egoPeerID, ckrecords ?? [], syncingViews, policy, nil) + } catch { + os_log("fetchAfterEstablish failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg)) + reply(nil, [], nil, nil, error) + } + } + } + } + } + } + func onqueueEstablish(ckksKeys: [CKKSKeychainBackedKeySet], tlkShares: [CKKSTLKShare], preapprovedKeys: [Data]?, - reply: @escaping (String?, [CKRecord], Error?) -> Void) { + reply: @escaping (String?, [CKRecord], Set?, TPPolicy?, Error?) -> Void) { // Fetch ego peer identity from local storage. guard let egoPeerID = self.containerMO.egoPeerID, let egoPermData = self.containerMO.egoPeerPermanentInfo, @@ -1413,31 +1503,31 @@ class Container: NSObject { let egoStableData = self.containerMO.egoPeerStableInfo, let egoStableSig = self.containerMO.egoPeerStableInfoSig else { - reply(nil, [], ContainerError.noPreparedIdentity) + reply(nil, [], nil, nil, ContainerError.noPreparedIdentity) return } let keyFactory = TPECPublicKeyFactory() guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { - reply(nil, [], ContainerError.invalidPermanentInfoOrSig) + reply(nil, [], nil, nil, ContainerError.invalidPermanentInfoOrSig) return } guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else { os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default) - reply(nil, [], ContainerError.invalidStableInfoOrSig) + reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig) return } guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else { - os_log("establish: self machineID %@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID) + os_log("establish: self machineID %{public}@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID) self.onqueueTTRUntrusted() - reply(nil, [], ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID)) + reply(nil, [], nil, nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID)) return } loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in guard let egoPeerKeys = egoPeerKeys else { - os_log("Don't have my own peer keys; can't establish: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") - reply(nil, [], error) + os_log("Don't have my own peer keys; can't establish: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + reply(nil, [], nil, nil, error) return } self.moc.performAndWait { @@ -1449,8 +1539,8 @@ class Container: NSObject { allTLKShares = octagonShares + sosShares } catch { - os_log("Unable to make TLKShares for self: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, [], error) + os_log("Unable to make TLKShares for self: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, [], nil, nil, error) return } @@ -1464,9 +1554,9 @@ class Container: NSObject { signing: egoPeerKeys.signingKey, currentMachineIDs: self.onqueueCurrentMIDList()) - os_log("dynamic info: %@", log: tplogDebug, type: .default, dynamicInfo) + os_log("dynamic info: %{public}@", log: tplogDebug, type: .default, dynamicInfo) } catch { - reply(nil, [], error) + reply(nil, [], nil, nil, error) return } @@ -1483,24 +1573,24 @@ class Container: NSObject { do { bottle = try self.assembleBottle(egoPeerID: egoPeerID) } catch { - reply(nil, [], error) + reply(nil, [], nil, nil, error) return } - os_log("Beginning establish for peer %@", log: tplogDebug, type: .default, egoPeerID) - os_log("Establish permanentInfo: %@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString()) - os_log("Establish permanentInfoSig: %@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString()) - os_log("Establish stableInfo: %@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString()) - os_log("Establish stableInfoSig: %@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString()) - os_log("Establish dynamicInfo: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString()) - os_log("Establish dynamicInfoSig: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString()) + os_log("Beginning establish for peer %{public}@", log: tplogDebug, type: .default, egoPeerID) + os_log("Establish permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString()) + os_log("Establish permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString()) + os_log("Establish stableInfo: %{public}@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString()) + os_log("Establish stableInfoSig: %{public}@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString()) + os_log("Establish dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString()) + os_log("Establish dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString()) os_log("Establish introducing %d key sets, %d tlk shares", log: tplogDebug, type: .default, viewKeys.count, allTLKShares.count) do { - os_log("Establish bottle: %@", log: tplogDebug, type: .debug, try bottle.serializedData().base64EncodedString()) - os_log("Establish peer: %@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString()) + os_log("Establish bottle: %{public}@", log: tplogDebug, type: .debug, try bottle.serializedData().base64EncodedString()) + os_log("Establish peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString()) } catch { - os_log("Establish unable to encode bottle/peer: %@", log: tplogDebug, type: .debug, error as CVarArg) + os_log("Establish unable to encode bottle/peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg) } let request = EstablishRequest.with { @@ -1510,16 +1600,23 @@ class Container: NSObject { $0.tlkShares = allTLKShares } self.cuttlefish.establish(request) { response, error in - os_log("Establish(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") - os_log("Establish: viewKeys: %@", String(describing: viewKeys)) + os_log("Establish(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + os_log("Establish: viewKeys: %{public}@", String(describing: viewKeys)) guard let response = response, error == nil else { - os_log("establish failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") - reply(nil, [], error ?? ContainerError.cloudkitResponseMissing) - return + switch error { + case CuttlefishErrorMatcher(code: CuttlefishErrorCode.establishFailed): + os_log("establish returned failed, trying fetch", log: tplogDebug, type: .default) + self.fetchAfterEstablish(ckksKeys: ckksKeys, tlkShares: tlkShares, reply: reply) + return + default: + os_log("establish failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + reply(nil, [], nil, nil, error ?? ContainerError.cloudkitResponseMissing) + return + } } do { - os_log("Establish returned changes: %@", log: tplogDebug, type: .default, try response.changes.jsonString()) + os_log("Establish returned changes: %{public}@", log: tplogDebug, type: .default, try response.changes.jsonString()) } catch { os_log("Establish returned changes, but they can't be serialized", log: tplogDebug, type: .default) } @@ -1527,6 +1624,9 @@ class Container: NSObject { let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } do { + let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: selfPermanentInfo, + stableInfo: selfStableInfo) + try self.persist(changes: response.changes) guard response.changes.more == false else { @@ -1535,22 +1635,22 @@ class Container: NSObject { self.fetchAndPersistChanges { fetchError in guard fetchError == nil else { // This is an odd error condition: we might be able to fetch again and be in a good state... - os_log("fetch-after-establish failed: %@", log: tplogDebug, type: .default, (fetchError as CVarArg?) ?? "no error") - reply(nil, keyHierarchyRecords, fetchError) + os_log("fetch-after-establish failed: %{public}@", log: tplogDebug, type: .default, (fetchError as CVarArg?) ?? "no error") + reply(nil, keyHierarchyRecords, nil, nil, fetchError) return } os_log("fetch-after-establish succeeded", log: tplogDebug, type: .default) - reply(egoPeerID, keyHierarchyRecords, nil) + reply(egoPeerID, keyHierarchyRecords, nil, nil, nil) } return } os_log("establish succeeded", log: tplogDebug, type: .default) - reply(egoPeerID, keyHierarchyRecords, nil) + reply(egoPeerID, keyHierarchyRecords, syncingViews, policy, nil) } catch { - os_log("establish handling failed: %@", log: tplogDebug, type: .default, (error as CVarArg)) - reply(nil, keyHierarchyRecords, error) + os_log("establish handling failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg)) + reply(nil, keyHierarchyRecords, nil, nil, error) } } } @@ -1560,7 +1660,7 @@ class Container: NSObject { func setRecoveryKey(recoveryKey: String, salt: String, ckksKeys: [CKKSKeychainBackedKeySet], reply: @escaping (Error?) -> Void) { self.semaphore.wait() let reply: (Error?) -> Void = { - os_log("setRecoveryKey complete: %@", log: tplogTrace, type: .info, traceError($0)) + os_log("setRecoveryKey complete: %{public}@", log: tplogTrace, type: .info, traceError($0)) self.semaphore.signal() reply($0) } @@ -1578,7 +1678,7 @@ class Container: NSObject { do { recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt) } catch { - os_log("failed to create recovery keys: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("failed to create recovery keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg) reply(ContainerError.failedToCreateRecoveryKey) return } @@ -1586,8 +1686,8 @@ class Container: NSObject { let signingPublicKey: Data = recoveryKeys.peerKeys.signingVerificationKey.keyData let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionVerificationKey.keyData - os_log("setRecoveryKey signingPubKey: %@", log: tplogDebug, type: .debug, signingPublicKey.base64EncodedString()) - os_log("setRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .debug, encryptionPublicKey.base64EncodedString()) + os_log("setRecoveryKey signingPubKey: %@", log: tplogDebug, type: .default, signingPublicKey.base64EncodedString()) + os_log("setRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .default, encryptionPublicKey.base64EncodedString()) guard let stableInfoData = self.containerMO.egoPeerStableInfo else { os_log("stableInfo does not exist", log: tplogDebug, type: .default) @@ -1623,7 +1723,7 @@ class Container: NSObject { loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in guard let signingKeyPair = signingKeyPair else { - os_log("handle: no signing key pair: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("handle: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(error) return } @@ -1634,12 +1734,12 @@ class Container: NSObject { toPeer: recoveryKeys.peerKeys, epoch: Int(permanentInfo.epoch)) - let policyVersion = stableInfo.policyVersion - let policyDoc = try self.getPolicyDoc(policyVersion) + let policyVersion = stableInfo.bestPolicyVersion() + let policyDoc = try self.getPolicyDoc(policyVersion.versionNumber) let updatedStableInfo = TPPeerStableInfo(clock: stableInfo.clock + 1, - policyVersion: policyDoc.policyVersion, - policyHash: policyDoc.policyHash, + frozenPolicyVersion: frozenPolicyVersion, + flexiblePolicyVersion: policyDoc.version, policySecrets: stableInfo.policySecrets, deviceName: stableInfo.deviceName, serialNumber: stableInfo.serialNumber, @@ -1660,9 +1760,9 @@ class Container: NSObject { } self.cuttlefish.setRecoveryKey(request) { response, error in - os_log("SetRecoveryKey(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + os_log("SetRecoveryKey(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") guard let response = response, error == nil else { - os_log("setRecoveryKey failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("setRecoveryKey failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(error ?? ContainerError.cloudkitResponseMissing) return } @@ -1676,7 +1776,7 @@ class Container: NSObject { os_log("setRecoveryKey succeeded", log: tplogDebug, type: .default) reply(nil) } catch { - os_log("setRecoveryKey handling failed: %@", log: tplogDebug, type: .default, (error as CVarArg)) + os_log("setRecoveryKey handling failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg)) reply(error) } } @@ -1715,13 +1815,13 @@ class Container: NSObject { if shouldPerformFetch == true { self.fetchViableBottlesWithSemaphore { _, _, error in guard error == nil else { - os_log("fetchViableBottles failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("fetchViableBottles failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(nil, error) return } guard let newBottles = self.containerMO.bottles as? Set else { - os_log("no bottles on container: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("no bottles on container: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(nil, ContainerError.noBottlesPresent) return } @@ -1731,7 +1831,7 @@ class Container: NSObject { return } - os_log("onqueueFindBottle found bottle: %@", log: tplogDebug, type: .default, newBottles) + os_log("onqueueFindBottle found bottle: %{public}@", log: tplogDebug, type: .default, newBottles) bottles = newBottles.filter { $0.bottleID == bottleID @@ -1784,7 +1884,7 @@ class Container: NSObject { _ = try BottledPeer.verifyBottleSignature(data: bottledContents, signature: signatureUsingPeerKey, pubKey: signingKey) } catch { - os_log("Verification of bottled signature failed: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("Verification of bottled signature failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg) throw ContainerError.failedToCreateBottledPeer } @@ -1804,84 +1904,61 @@ class Container: NSObject { signatureUsingEscrow: signatureUsingEscrowKey, signatureUsingPeerKey: signatureUsingPeerKey) } catch { - os_log("Creation of Bottled Peer failed: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("Creation of Bottled Peer failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg) throw ContainerError.failedToCreateBottledPeer } } } - func preflightVouchWithBottle(bottleID: String, - reply: @escaping (String?, Error?) -> Void) { - self.semaphore.wait() - let reply: (String?, Error?) -> Void = { - os_log("preflightVouchWithBottle complete: %@", - log: tplogTrace, type: .info, traceError($1)) - self.semaphore.signal() - reply($0, $1) - } - - self.moc.performAndWait { - self.onqueueFindBottle(bottleID: bottleID) { bottleMO, error in - guard let bottleMO = bottleMO else { - os_log("preflightVouchWithBottle found no bottle: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") - reply(nil, error) - return - } - - reply(bottleMO.peerID, nil) - } - } - } - func vouchWithBottle(bottleID: String, entropy: Data, bottleSalt: String, tlkShares: [CKKSTLKShare], - reply: @escaping (Data?, Data?, Error?) -> Void) { + reply: @escaping (Data?, Data?, Int64, Int64, Error?) -> Void) { self.semaphore.wait() - let reply: (Data?, Data?, Error?) -> Void = { - os_log("vouchWithBottle complete: %@", - log: tplogTrace, type: .info, traceError($2)) + let reply: (Data?, Data?, Int64, Int64, Error?) -> Void = { + os_log("vouchWithBottle complete: %{public}@", + log: tplogTrace, type: .info, traceError($4)) self.semaphore.signal() - reply($0, $1, $2) + reply($0, $1, $2, $3, $4) } - self.fetchAndPersistChanges { error in + self.fetchAndPersistChangesIfNeeded { error in guard error == nil else { - os_log("vouchWithBottle unable to fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") - reply(nil, nil, error) + os_log("vouchWithBottle unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") + reply(nil, nil, 0, 0, error) return } self.onqueueFindBottle(bottleID: bottleID) { returnedBMO, error in self.moc.performAndWait { guard error == nil else { - os_log("vouchWithBottle unable to find bottle for escrow record id: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") - reply(nil, nil, error) + os_log("vouchWithBottle unable to find bottle for escrow record id: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") + reply(nil, nil, 0, 0, error) return } guard let bmo: BottleMO = returnedBMO else { - os_log("vouchWithBottle bottle is nil: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") - reply(nil, nil, error) + os_log("vouchWithBottle bottle is nil: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") + reply(nil, nil, 0, 0, error) return } guard let bottledContents = bmo.contents else { - reply(nil, nil, ContainerError.bottleDoesNotContainContents) + reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainContents) return } guard let signatureUsingEscrowKey = bmo.signatureUsingEscrowKey else { - reply(nil, nil, ContainerError.bottleDoesNotContainEscrowKeySignature) + reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainEscrowKeySignature) return } guard let signatureUsingPeerKey = bmo.signatureUsingPeerKey else { - reply(nil, nil, ContainerError.bottleDoesNotContainerPeerKeySignature) + reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainerPeerKeySignature) return } guard let sponsorPeerID = bmo.peerID else { - reply(nil, nil, ContainerError.bottleDoesNotContainPeerID) + reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainPeerID) return } @@ -1889,19 +1966,19 @@ class Container: NSObject { do { guard let sponsorPeer = self.model.peer(withID: sponsorPeerID) else { os_log("vouchWithBottle: Unable to find peer that created the bottle", log: tplogDebug, type: .default) - reply(nil, nil, ContainerError.bottleCreatingPeerNotFound) + reply(nil, nil, 0, 0, ContainerError.bottleCreatingPeerNotFound) return } guard let signingKey: _SFECPublicKey = sponsorPeer.permanentInfo.signingPubKey as? _SFECPublicKey else { os_log("vouchWithBottle: Unable to create a sponsor public key", log: tplogDebug, type: .default) - reply(nil, nil, ContainerError.signatureVerificationFailed) + reply(nil, nil, 0, 0, ContainerError.signatureVerificationFailed) return } _ = try BottledPeer.verifyBottleSignature(data: bottledContents, signature: signatureUsingPeerKey, pubKey: signingKey) } catch { - os_log("vouchWithBottle: Verification of bottled signature failed: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, nil, ContainerError.failedToCreateBottledPeer) + os_log("vouchWithBottle: Verification of bottled signature failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, nil, 0, 0, ContainerError.failedToCreateBottledPeer) return } @@ -1924,53 +2001,53 @@ class Container: NSObject { signatureUsingPeerKey: signatureUsingPeerKey) } catch { - os_log("Creation of Bottled Peer failed: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, nil, ContainerError.failedToCreateBottledPeer) + os_log("Creation of Bottled Peer failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, nil, 0, 0, ContainerError.failedToCreateBottledPeer) return } } - os_log("Have a bottle for peer %@", log: tplogDebug, type: .default, bottledPeer.peerID) + os_log("Have a bottle for peer %{public}@", log: tplogDebug, type: .default, bottledPeer.peerID) // Extract any TLKs we have been given - extract(tlkShares: tlkShares, peer: bottledPeer.peerKeys) + let (uniqueTLKsRecovered, totalSharesRecovered) = extract(tlkShares: tlkShares, peer: bottledPeer.peerKeys, model: self.model) self.moc.performAndWait { // I must have an ego identity in order to vouch using bottle guard let egoPeerID = self.containerMO.egoPeerID else { os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default) - reply(nil, nil, ContainerError.nonMember) + reply(nil, nil, 0, 0, ContainerError.nonMember) return } guard let permanentInfo = self.containerMO.egoPeerPermanentInfo else { os_log("permanentInfo does not exist", log: tplogDebug, type: .default) - reply(nil, nil, ContainerError.nonMember) + reply(nil, nil, 0, 0, ContainerError.nonMember) return } guard let permanentInfoSig = self.containerMO.egoPeerPermanentInfoSig else { os_log("permanentInfoSig does not exist", log: tplogDebug, type: .default) - reply(nil, nil, ContainerError.nonMember) + reply(nil, nil, 0, 0, ContainerError.nonMember) return } guard let stableInfo = self.containerMO.egoPeerStableInfo else { os_log("stableInfo does not exist", log: tplogDebug, type: .default) - reply(nil, nil, ContainerError.nonMember) + reply(nil, nil, 0, 0, ContainerError.nonMember) return } guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else { os_log("stableInfoSig does not exist", log: tplogDebug, type: .default) - reply(nil, nil, ContainerError.nonMember) + reply(nil, nil, 0, 0, ContainerError.nonMember) return } let keyFactory = TPECPublicKeyFactory() guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else { os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default) - reply(nil, nil, ContainerError.invalidPermanentInfoOrSig) + reply(nil, nil, 0, 0, ContainerError.invalidPermanentInfoOrSig) return } guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else { os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default) - reply(nil, nil, ContainerError.invalidStableInfoOrSig) + reply(nil, nil, 0, 0, ContainerError.invalidStableInfoOrSig) return } @@ -1980,11 +2057,11 @@ class Container: NSObject { withSponsorID: sponsorPeerID, reason: TPVoucherReason.restore, signing: bottledPeer.peerKeys.signingKey) - reply(voucher.data, voucher.sig, nil) + reply(voucher.data, voucher.sig, uniqueTLKsRecovered, totalSharesRecovered, nil) return } catch { - os_log("Error creating voucher: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, nil, error) + os_log("Error creating voucher with bottle: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, nil, 0, 0, error) return } } @@ -1999,7 +2076,7 @@ class Container: NSObject { reply: @escaping (Data?, Data?, Error?) -> Void) { self.semaphore.wait() let reply: (Data?, Data?, Error?) -> Void = { - os_log("vouchWithRecoveryKey complete: %@", + os_log("vouchWithRecoveryKey complete: %{public}@", log: tplogTrace, type: .info, traceError($2)) self.semaphore.signal() reply($0, $1, $2) @@ -2051,18 +2128,18 @@ class Container: NSObject { do { recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt) } catch { - os_log("failed to create recovery keys: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("failed to create recovery keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg) reply(nil, nil, ContainerError.failedToCreateRecoveryKey) return } - extract(tlkShares: tlkShares, peer: recoveryKeys.peerKeys) + extract(tlkShares: tlkShares, peer: recoveryKeys.peerKeys, model: self.model) let signingPublicKey: Data = recoveryKeys.peerKeys.signingKey.publicKey.keyData let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionKey.publicKey.keyData - os_log("vouchWithRecoveryKey signingPubKey: %@", log: tplogDebug, type: .debug, signingPublicKey.base64EncodedString()) - os_log("vouchWithRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .debug, encryptionPublicKey.base64EncodedString()) + os_log("vouchWithRecoveryKey signingPubKey: %@", log: tplogDebug, type: .default, signingPublicKey.base64EncodedString()) + os_log("vouchWithRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .default, encryptionPublicKey.base64EncodedString()) guard self.model.isRecoveryKeyEnrolled() else { os_log("Recovery Key is not enrolled", log: tplogDebug, type: .default) @@ -2086,7 +2163,7 @@ class Container: NSObject { reply(voucher.data, voucher.sig, nil) return } catch { - os_log("Error creating voucher using recovery key set: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("Error creating voucher using recovery key set: %{public}@", log: tplogDebug, type: .default, error as CVarArg) reply(nil, nil, error) return } @@ -2102,7 +2179,7 @@ class Container: NSObject { reply: @escaping (Data?, Data?, Error?) -> Void) { self.semaphore.wait() let reply: (Data?, Data?, Error?) -> Void = { - os_log("vouch complete: %@", log: tplogTrace, type: .info, traceError($2)) + os_log("vouch complete: %{public}@", log: tplogTrace, type: .info, traceError($2)) self.semaphore.signal() reply($0, $1, $2) } @@ -2138,65 +2215,79 @@ class Container: NSObject { loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in guard let egoPeerKeys = egoPeerKeys else { - os_log("Don't have my own keys: can't vouch for %@: %@", log: tplogDebug, type: .default, beneficiaryPermanentInfo, (error as CVarArg?) ?? "no error") + os_log("Don't have my own keys: can't vouch for %{public}@(%{public}@): %{public}@", log: tplogDebug, type: .default, peerID, beneficiaryPermanentInfo, (error as CVarArg?) ?? "no error") reply(nil, nil, error) return } - self.moc.performAndWait { - let voucher: TPVoucher - do { - voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo, - stableInfo: beneficiaryStableInfo, - withSponsorID: egoPeerID, - reason: TPVoucherReason.secureChannel, - signing: egoPeerKeys.signingKey) - } catch { - os_log("Error creating voucher: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, nil, error) + self.fetchPolicyDocumentsWithSemaphore(versions: Set([beneficiaryStableInfo.bestPolicyVersion()])) { _, policyFetchError in + guard policyFetchError == nil else { + os_log("Unknown policy for beneficiary: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + reply(nil, nil, policyFetchError) return } - // And generate and upload any tlkShares - // Note that this might not be the whole list: Octagon: Limited Peers - let tlkShares: [TLKShare] - do { - // Note: we only want to send up TLKs for uploaded ckks zones - let ckksTLKs = ckksKeys.filter { !$0.newUpload }.map { $0.tlk } + self.moc.performAndWait { + let voucher: TPVoucher + do { + voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo, + stableInfo: beneficiaryStableInfo, + withSponsorID: egoPeerID, + reason: TPVoucherReason.secureChannel, + signing: egoPeerKeys.signingKey) + } catch { + os_log("Error creating voucher: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, nil, error) + return + } - tlkShares = try makeTLKShares(ckksTLKs: ckksTLKs, - asPeer: egoPeerKeys, - toPeer: beneficiaryPermanentInfo, - epoch: Int(selfPermanentInfo.epoch)) - } catch { - os_log("Unable to make TLKShares for beneficiary %@: %@", log: tplogDebug, type: .default, beneficiaryPermanentInfo, error as CVarArg) - reply(nil, nil, error) - return - } + // And generate and upload any tlkShares + let tlkShares: [TLKShare] + do { + // Note that this might not be the whole list, so filter some of them out + let peerViews = try? self.model.getViewsForPeer(beneficiaryPermanentInfo, + stableInfo: beneficiaryStableInfo) + + // Note: we only want to send up TLKs for uploaded ckks zones + let ckksTLKs = ckksKeys + .filter { !$0.newUpload } + .filter { peerViews?.contains($0.tlk.zoneID.zoneName) ?? false } + .map { $0.tlk } + + tlkShares = try makeTLKShares(ckksTLKs: ckksTLKs, + asPeer: egoPeerKeys, + toPeer: beneficiaryPermanentInfo, + epoch: Int(selfPermanentInfo.epoch)) + } catch { + os_log("Unable to make TLKShares for beneficiary %{public}@(%{public}@): %{public}@", log: tplogDebug, type: .default, peerID, beneficiaryPermanentInfo, error as CVarArg) + reply(nil, nil, error) + return + } - guard !tlkShares.isEmpty else { - os_log("No TLKShares to upload for new peer, returning voucher", log: tplogDebug, type: .default) - reply(voucher.data, voucher.sig, nil) - return - } + guard !tlkShares.isEmpty else { + os_log("No TLKShares to upload for new peer, returning voucher", log: tplogDebug, type: .default) + reply(voucher.data, voucher.sig, nil) + return + } - self.cuttlefish.updateTrust(changeToken: self.containerMO.changeToken ?? "", - peerID: egoPeerID, - stableInfoAndSig: nil, - dynamicInfoAndSig: nil, - tlkShares: tlkShares, - viewKeys: []) { response, error in - guard let response = response, error == nil else { - os_log("Unable to upload new tlkshares: %@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error") - reply(voucher.data, voucher.sig, error ?? ContainerError.cloudkitResponseMissing) - return - } - - let newKeyRecords = response.zoneKeyHierarchyRecords.map(CKRecord.init) - os_log("Uploaded new tlkshares: %@", log: tplogDebug, type: .default, newKeyRecords) - // We don't need to save these; CKKS will refetch them as needed - - reply(voucher.data, voucher.sig, nil) + self.cuttlefish.updateTrust(changeToken: self.containerMO.changeToken ?? "", + peerID: egoPeerID, + stableInfoAndSig: nil, + dynamicInfoAndSig: nil, + tlkShares: tlkShares, + viewKeys: []) { response, error in + guard let response = response, error == nil else { + os_log("Unable to upload new tlkshares: %{public}@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error") + reply(voucher.data, voucher.sig, error ?? ContainerError.cloudkitResponseMissing) + return + } + + let newKeyRecords = response.zoneKeyHierarchyRecords.map(CKRecord.init) + os_log("Uploaded new tlkshares: %@", log: tplogDebug, type: .default, newKeyRecords) + // We don't need to save these; CKKS will refetch them as needed + + reply(voucher.data, voucher.sig, nil) + } } } } @@ -2206,7 +2297,7 @@ class Container: NSObject { func departByDistrustingSelf(reply: @escaping (Error?) -> Void) { self.semaphore.wait() let reply: (Error?) -> Void = { - os_log("departByDistrustingSelf complete: %@", log: tplogTrace, type: .info, traceError($0)) + os_log("departByDistrustingSelf complete: %{public}@", log: tplogTrace, type: .info, traceError($0)) self.semaphore.signal() reply($0) } @@ -2226,7 +2317,7 @@ class Container: NSObject { reply: @escaping (Error?) -> Void) { self.semaphore.wait() let reply: (Error?) -> Void = { - os_log("distrust complete: %@", log: tplogTrace, type: .info, traceError($0)) + os_log("distrust complete: %{public}@", log: tplogTrace, type: .info, traceError($0)) self.semaphore.signal() reply($0) } @@ -2259,7 +2350,7 @@ class Container: NSObject { loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in guard let signingKeyPair = signingKeyPair else { - os_log("No longer have signing key pair; can't sign distrust: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil") + os_log("No longer have signing key pair; can't sign distrust: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil") reply(error) return } @@ -2275,13 +2366,13 @@ class Container: NSObject { currentMachineIDs: self.onqueueCurrentMIDList()) } catch { - os_log("Error preparing dynamic info: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil") + os_log("Error preparing dynamic info: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil") reply(error) return } let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo) - os_log("attempting distrust for %@ with: %@", log: tplogDebug, type: .default, peerIDs, dynamicInfo) + os_log("attempting distrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, peerIDs, dynamicInfo) let request = UpdateTrustRequest.with { $0.changeToken = self.containerMO.changeToken ?? "" @@ -2289,9 +2380,9 @@ class Container: NSObject { $0.dynamicInfoAndSig = signedDynamicInfo } self.cuttlefish.updateTrust(request) { response, error in - os_log("UpdateTrust(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + os_log("UpdateTrust(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") guard let response = response, error == nil else { - os_log("updateTrust failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("updateTrust failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(error ?? ContainerError.cloudkitResponseMissing) return } @@ -2301,7 +2392,7 @@ class Container: NSObject { os_log("distrust succeeded", log: tplogDebug, type: .default) reply(nil) } catch { - os_log("distrust handling failed: %@", log: tplogDebug, type: .default, (error as CVarArg)) + os_log("distrust handling failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg)) reply(error) } } @@ -2312,7 +2403,7 @@ class Container: NSObject { func fetchEscrowContents(reply: @escaping (Data?, String?, Data?, Error?) -> Void) { self.semaphore.wait() let reply: (Data?, String?, Data?, Error?) -> Void = { - os_log("fetchEscrowContents complete: %@", log: tplogTrace, type: .info, traceError($3)) + os_log("fetchEscrowContents complete: %{public}@", log: tplogTrace, type: .info, traceError($3)) self.semaphore.signal() reply($0, $1, $2, $3) } @@ -2344,7 +2435,7 @@ class Container: NSObject { } entropy = loaded } catch { - os_log("fetchEscrowContents failed to load entropy: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("fetchEscrowContents failed to load entropy: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(nil, nil, nil, error) return } @@ -2361,7 +2452,7 @@ class Container: NSObject { func fetchViableBottles(reply: @escaping ([String]?, [String]?, Error?) -> Void) { self.semaphore.wait() let reply: ([String]?, [String]?, Error?) -> Void = { - os_log("fetchViableBottles complete: %@", log: tplogTrace, type: .info, traceError($2)) + os_log("fetchViableBottles complete: %{public}@", log: tplogTrace, type: .info, traceError($2)) self.semaphore.signal() reply($0, $1, $2) } @@ -2397,7 +2488,7 @@ class Container: NSObject { let cachedBottles: TPCachedViableBottles = self.model.currentCachedViableBottlesSet() self.moc.performAndWait { if self.onqueueCachedBottlesContainEgoPeerBottle(cachedBottles: cachedBottles) - && (cachedBottles.viableBottles.count > 0 || cachedBottles.partialBottles.count > 0) { + && (!cachedBottles.viableBottles.isEmpty || !cachedBottles.partialBottles.isEmpty) { os_log("returning from fetchViableBottles, using cached bottles", log: tplogDebug, type: .default) reply(cachedBottles.viableBottles, cachedBottles.partialBottles, nil) return @@ -2405,7 +2496,7 @@ class Container: NSObject { self.cuttlefish.fetchViableBottles { response, error in guard error == nil else { - os_log("fetchViableBottles failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("fetchViableBottles failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(nil, nil, error) return } @@ -2413,7 +2504,7 @@ class Container: NSObject { self.moc.performAndWait { guard let escrowPairs = response?.viableBottles else { - os_log("fetchViableBottles returned no viable bottles: %@", log: tplogDebug, type: .default) + os_log("fetchViableBottles returned no viable bottles", log: tplogDebug, type: .default) reply([], [], nil) return } @@ -2422,14 +2513,14 @@ class Container: NSObject { if let partial = response?.partialBottles { partialPairs = partial } else { - os_log("fetchViableBottles returned no partially viable bottles, but that's ok: %@", log: tplogDebug, type: .default) + os_log("fetchViableBottles returned no partially viable bottles, but that's ok", log: tplogDebug, type: .default) } let viableBottleIDs = escrowPairs.compactMap { $0.bottle.bottleID } - os_log("fetchViableBottles returned viable bottles: %@", log: tplogDebug, type: .default, viableBottleIDs) + os_log("fetchViableBottles returned viable bottles: %{public}@", log: tplogDebug, type: .default, viableBottleIDs) let partialBottleIDs = partialPairs.compactMap { $0.bottle.bottleID } - os_log("fetchViableBottles returned partial bottles: %@", log: tplogDebug, type: .default, partialBottleIDs) + os_log("fetchViableBottles returned partial bottles: %{public}@", log: tplogDebug, type: .default, partialBottleIDs) escrowPairs.forEach { pair in let bottle = pair.bottle @@ -2458,7 +2549,7 @@ class Container: NSObject { bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey bmo.contents = bottle.contents - os_log("fetchViableBottles saving new bottle: %@", log: tplogDebug, type: .default, bmo) + os_log("fetchViableBottles saving new bottle: %{public}@", log: tplogDebug, type: .default, bmo) self.containerMO.addToBottles(bmo) } @@ -2489,7 +2580,7 @@ class Container: NSObject { bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey bmo.contents = bottle.contents - os_log("fetchViableBottles saving new bottle: %@", log: tplogDebug, type: .default, bmo) + os_log("fetchViableBottles saving new bottle: %{public}@", log: tplogDebug, type: .default, bmo) self.containerMO.addToBottles(bmo) } @@ -2500,7 +2591,7 @@ class Container: NSObject { self.model.setViableBottles(cached) reply(viableBottleIDs, partialBottleIDs, nil) } catch { - os_log("fetchViableBottles unable to save bottles: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("fetchViableBottles unable to save bottles: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(nil, nil, error) } @@ -2509,124 +2600,124 @@ class Container: NSObject { } } - func fetchPolicy(reply: @escaping (TPPolicy?, Error?) -> Void) { + func fetchCurrentPolicy(reply: @escaping (Set?, TPPolicy?, Error?) -> Void) { self.semaphore.wait() - let reply: (TPPolicy?, Error?) -> Void = { - os_log("fetchPolicy complete: %@", log: tplogTrace, type: .info, traceError($1)) + let reply: (Set?, TPPolicy?, Error?) -> Void = { + os_log("fetchCurrentPolicy complete: %{public}@", log: tplogTrace, type: .info, traceError($2)) self.semaphore.signal() - reply($0, $1) + reply($0, $1, $2) } self.moc.performAndWait { - var keys: [NSNumber: String] = [:] - - guard let stableInfoData = self.containerMO.egoPeerStableInfo, - let stableInfoSig = self.containerMO.egoPeerStableInfoSig else { - os_log("fetchPolicy failed to find ego peer stableinfodata/sig", log: tplogDebug, type: .error) - reply(nil, ContainerError.noPreparedIdentity) - return - } - guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else { - os_log("fetchPolicy failed to create TPPeerStableInfo", log: tplogDebug, type: .error) - reply(nil, ContainerError.invalidStableInfoOrSig) - return - } - - let policyVersionCounter = stableInfo.policyVersion - let policyVersion = NSNumber(value: policyVersionCounter) - keys[policyVersion] = stableInfo.policyHash - - if let policyDocument = self.model.policy(withVersion: policyVersionCounter) { - os_log("fetchPolicy: have a local version of policy %@: %@", log: tplogDebug, type: .default, policyVersion, policyDocument) - do { - let policy = try policyDocument.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter()) - reply(policy, nil) - return - } catch { - os_log("TPPolicyDocument failed: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, error) + guard let egoPeerID = self.containerMO.egoPeerID, + let egoPermData = self.containerMO.egoPeerPermanentInfo, + let egoPermSig = self.containerMO.egoPeerPermanentInfoSig, + let stableInfoData = self.containerMO.egoPeerStableInfo, + let stableInfoSig = self.containerMO.egoPeerStableInfoSig else { + os_log("fetchCurrentPolicy failed to find ego peer information", log: tplogDebug, type: .error) + reply(nil, nil, ContainerError.noPreparedIdentity) return - } } - self.fetchPolicyDocuments(keys: keys) { result, error in - guard error == nil else { - reply(nil, error) - return - } - guard let result = result else { - os_log("fetchPolicy: nil policies returned") - reply(nil, ContainerError.policyDocumentDoesNotValidate) - return - } - guard result.count == 1 else { - os_log("fetchPolicy: wrong length returned") - reply(nil, ContainerError.policyDocumentDoesNotValidate) - return - } - guard let r = result[policyVersion] else { - os_log("fetchPolicy: version not found") - reply(nil, ContainerError.unknownPolicyVersion(policyVersion.uint64Value)) - return - } - guard let data = r[1].data(using: .utf8) else { - os_log("fetchPolicy: failed to convert data") - reply(nil, ContainerError.unknownPolicyVersion(policyVersion.uint64Value)) - return - } - guard let pd = TPPolicyDocument.policyDoc(withHash: r[0], data: data) else { - os_log("fetchPolicy: pd is nil") - reply(nil, ContainerError.policyDocumentDoesNotValidate) - return - } - do { - let policy = try pd.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter()) - reply(policy, nil) - } catch { - os_log("TPPolicyDocument: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, error) - } + let keyFactory = TPECPublicKeyFactory() + guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { + os_log("fetchCurrentPolicy failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error) + reply(nil, nil, ContainerError.invalidPermanentInfoOrSig) + return + } + guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else { + os_log("fetchCurrentPolicy failed to create TPPeerStableInfo", log: tplogDebug, type: .error) + reply(nil, nil, ContainerError.invalidStableInfoOrSig) + return + } + + do { + let (views, policy) = try self.policyAndViewsFor(permanentInfo: permanentInfo, stableInfo: stableInfo) + reply(views, policy, nil) + return + } catch { + os_log("TPPolicyDocument failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, nil, error) + return } } } + func policyAndViewsFor(permanentInfo: TPPeerPermanentInfo, stableInfo: TPPeerStableInfo) throws -> (Set, TPPolicy) { + let policyVersion = stableInfo.bestPolicyVersion() + guard let policyDocument = self.model.policy(withVersion: policyVersion.versionNumber) else { + os_log("current policy is missing?", log: tplogDebug, type: .default) + throw ContainerError.unknownPolicyVersion(policyVersion.versionNumber) + } + + let policy = try policyDocument.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter()) + let views = try policy.views(forModel: permanentInfo.modelID) + + return (views, policy) + } + // All-or-nothing: return an error in case full list cannot be returned. // Completion handler data format: [version : [hash, data]] - func fetchPolicyDocuments(keys: [NSNumber: String], - reply: @escaping ([NSNumber: [String]]?, Error?) -> Void) { + func fetchPolicyDocuments(versions: Set, + reply: @escaping ([TPPolicyVersion: Data]?, Error?) -> Void) { self.semaphore.wait() - let reply: ([NSNumber: [String]]?, Error?) -> Void = { - os_log("fetchPolicyDocuments complete: %@", log: tplogTrace, type: .info, traceError($1)) + let reply: ([TPPolicyVersion: Data]?, Error?) -> Void = { + os_log("fetchPolicyDocuments complete: %{public}@", log: tplogTrace, type: .info, traceError($1)) self.semaphore.signal() reply($0, $1) } - var keys = keys - var docs: [NSNumber: [String]] = [:] + self.fetchPolicyDocumentsWithSemaphore(versions: versions) { policyDocuments, fetchError in + reply(policyDocuments.flatMap { $0.mapValues { policyDoc in policyDoc.protobuf } }, fetchError) + } + } + + func fetchPolicyDocumentWithSemaphore(version: TPPolicyVersion, + reply: @escaping (TPPolicyDocument?, Error?) -> Void) { + self.fetchPolicyDocumentsWithSemaphore(versions: Set([version])) { versions, fetchError in + guard fetchError == nil else { + reply(nil, fetchError) + return + } + + guard let doc = versions?[version] else { + os_log("fetchPolicyDocument: didn't return policy of version: %{public}@", log: tplogDebug, versions ?? "no versions") + reply(nil, ContainerError.unknownPolicyVersion(version.versionNumber)) + return + } + + reply(doc, nil) + } + } + + func fetchPolicyDocumentsWithSemaphore(versions: Set, + reply: @escaping ([TPPolicyVersion: TPPolicyDocument]?, Error?) -> Void) { + var remaining = versions + var docs: [TPPolicyVersion: TPPolicyDocument] = [:] self.moc.performAndWait { - for (version, hash) in keys { - if let policydoc = try? self.getPolicyDoc(version.uint64Value), policydoc.policyHash == hash { - docs[version] = [policydoc.policyHash, policydoc.protobuf.base64EncodedString()] - keys[version] = nil + for version in remaining { + if let policydoc = try? self.getPolicyDoc(version.versionNumber), policydoc.version.policyHash == version.policyHash { + docs[policydoc.version] = policydoc + remaining.remove(version) } } } - if keys.isEmpty { + guard !remaining.isEmpty else { reply(docs, nil) return } let request = FetchPolicyDocumentsRequest.with { - $0.keys = keys.map { key, value in - PolicyDocumentKey.with { $0.version = key.uint64Value; $0.hash = value }} + $0.keys = remaining.map { version in + PolicyDocumentKey.with { $0.version = version.versionNumber; $0.hash = version.policyHash }} } self.cuttlefish.fetchPolicyDocuments(request) { response, error in - os_log("FetchPolicyDocuments(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + os_log("FetchPolicyDocuments(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") guard let response = response, error == nil else { - os_log("FetchPolicyDocuments failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("FetchPolicyDocuments failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(nil, error ?? ContainerError.cloudkitResponseMissing) return } @@ -2636,20 +2727,28 @@ class Container: NSObject { // TODO: validate the policy's signature guard let doc = TPPolicyDocument.policyDoc(withHash: mapEntry.key.hash, data: mapEntry.value) else { - os_log("Can't make policy document with hash %@ and data %@", + os_log("Can't make policy document with hash %{public}@ and data %{public}@", log: tplogDebug, type: .default, mapEntry.key.hash, mapEntry.value.base64EncodedString()) reply(nil, ContainerError.policyDocumentDoesNotValidate) return } - guard let hash = keys[NSNumber(value: doc.policyVersion)], hash == doc.policyHash else { - os_log("Requested hash %@ does not match fetched hash %@", log: tplogDebug, type: .default, - keys[NSNumber(value: doc.policyVersion)] ?? "", doc.policyHash) + guard let expectedVersion = (remaining.filter { $0.versionNumber == doc.version.versionNumber }.first) else { + os_log("Received a policy version we didn't request: %d", log: tplogDebug, type: .default, doc.version.versionNumber) + reply(nil, ContainerError.policyDocumentDoesNotValidate) + return + } + + guard expectedVersion.policyHash == doc.version.policyHash else { + os_log("Requested hash %{public}@ does not match fetched hash %{public}@", log: tplogDebug, type: .default, + expectedVersion.policyHash, doc.version.policyHash) reply(nil, ContainerError.policyDocumentDoesNotValidate) return } - keys[NSNumber(value: doc.policyVersion)] = nil // Server responses should be unique, let's enforce - docs[NSNumber(value: doc.policyVersion)] = [doc.policyHash, doc.protobuf.base64EncodedString()] + + remaining.remove(expectedVersion) // Server responses should be unique, let's enforce + + docs[doc.version] = doc self.model.register(doc) } @@ -2660,13 +2759,14 @@ class Container: NSObject { return } - if !keys.isEmpty { - let (unknownVersion, _) = keys.first! - reply(nil, ContainerError.unknownPolicyVersion(unknownVersion.uint64Value)) + // Determine if there's anything left to fetch + guard let unfetchedVersion = remaining.first else { + // Nothing remaining? Success! + reply(docs, nil) return } - reply(docs, nil) + reply(nil, ContainerError.unknownPolicyVersion(unfetchedVersion.versionNumber)) } } } @@ -2711,10 +2811,10 @@ class Container: NSObject { /* Returns any new CKKS keys that need uploading, as well as any TLKShares necessary for those keys */ func makeSharesForNewKeySets(ckksKeys: [CKKSKeychainBackedKeySet], - tlkShares: [CKKSTLKShare], - egoPeerKeys: OctagonSelfPeerKeys, - egoPeerDynamicInfo: TPPeerDynamicInfo, - epoch: Int) throws -> ([ViewKeys], [TLKShare]) { + tlkShares: [CKKSTLKShare], + egoPeerKeys: OctagonSelfPeerKeys, + egoPeerDynamicInfo: TPPeerDynamicInfo, + epoch: Int) throws -> ([ViewKeys], [TLKShare]) { let newCKKSKeys = ckksKeys.filter { $0.newUpload } let newViewKeys: [ViewKeys] = newCKKSKeys.map(ViewKeys.convert) @@ -2730,7 +2830,7 @@ class Container: NSObject { do { let peerIDsWithAccess = try self.model.getPeerIDsTrustedByPeer(with: egoPeerDynamicInfo, toAccessView: keyset.tlk.zoneID.zoneName) - os_log("Planning to share %@ with peers %@", log: tplogDebug, type: .default, String(describing: keyset.tlk), peerIDsWithAccess) + os_log("Planning to share %@ with peers %{public}@", log: tplogDebug, type: .default, String(describing: keyset.tlk), peerIDsWithAccess) let peers = peerIDsWithAccess.compactMap { self.model.peer(withID: $0) } let viewPeerShares = try peers.map { receivingPeer in @@ -2741,10 +2841,10 @@ class Container: NSObject { poisoned: 0)) } - peerShares = peerShares + viewPeerShares + peerShares += viewPeerShares } catch { - os_log("Unable to create TLKShares for keyset %@: %@", log: tplogDebug, type: .default, String(describing: keyset), error as CVarArg) + os_log("Unable to create TLKShares for keyset %@: %{public}@", log: tplogDebug, type: .default, String(describing: keyset), error as CVarArg) } } @@ -2768,6 +2868,7 @@ class Container: NSObject { let newStableInfo = try self.createNewStableInfoIfNeeded(stableChanges: nil, egoPeerID: egoPeerID, + existingStableInfo: stableInfo, dynamicInfo: dynamicInfo, signingKeyPair: egoPeerKeys.signingKey) @@ -2787,161 +2888,180 @@ class Container: NSObject { ckksKeys: [CKKSKeychainBackedKeySet], tlkShares: [CKKSTLKShare], preapprovedKeys: [Data]?, - reply: @escaping (String?, [CKRecord], Error?) -> Void) { + reply: @escaping (String?, [CKRecord], Set?, TPPolicy?, Error?) -> Void) { self.semaphore.wait() - let reply: (String?, [CKRecord], Error?) -> Void = { - os_log("join complete: %@", log: tplogTrace, type: .info, traceError($2)) + let reply: (String?, [CKRecord], Set?, TPPolicy?, Error?) -> Void = { + os_log("join complete: %{public}@", log: tplogTrace, type: .info, traceError($4)) self.semaphore.signal() - reply($0, $1, $2) + reply($0, $1, $2, $3, $4) } self.fetchAndPersistChanges { error in guard error == nil else { - reply(nil, [], error) + reply(nil, [], nil, nil, error) return } - self.moc.performAndWait { - guard let voucher = TPVoucher(infoWith: voucherData, sig: voucherSig) else { - reply(nil, [], ContainerError.invalidVoucherOrSig) - return - } - guard let sponsor = self.model.peer(withID: voucher.sponsorID) else { - reply(nil, [], ContainerError.sponsorNotRegistered(voucher.sponsorID)) - return + + // To join, you must know all policies that exist + let allPolicyVersions = self.model.allPolicyVersions() + self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in + if let error = policyFetchError { + os_log("join: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg) } - // Fetch ego peer identity from local storage. - guard let egoPeerID = self.containerMO.egoPeerID, - let egoPermData = self.containerMO.egoPeerPermanentInfo, - let egoPermSig = self.containerMO.egoPeerPermanentInfoSig, - let egoStableData = self.containerMO.egoPeerStableInfo, - let egoStableSig = self.containerMO.egoPeerStableInfoSig - else { - reply(nil, [], ContainerError.noPreparedIdentity) + self.moc.performAndWait { + guard let voucher = TPVoucher(infoWith: voucherData, sig: voucherSig) else { + reply(nil, [], nil, nil, ContainerError.invalidVoucherOrSig) return - } + } + guard let sponsor = self.model.peer(withID: voucher.sponsorID) else { + reply(nil, [], nil, nil, ContainerError.sponsorNotRegistered(voucher.sponsorID)) + return + } - let keyFactory = TPECPublicKeyFactory() - guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { - reply(nil, [], ContainerError.invalidPermanentInfoOrSig) - return - } - guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else { - reply(nil, [], ContainerError.invalidStableInfoOrSig) - return - } - guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else { - os_log("join: self machineID %@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID) - self.onqueueTTRUntrusted() - reply(nil, [], ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID)) - return - } + // Fetch ego peer identity from local storage. + guard let egoPeerID = self.containerMO.egoPeerID, + let egoPermData = self.containerMO.egoPeerPermanentInfo, + let egoPermSig = self.containerMO.egoPeerPermanentInfoSig, + let egoStableData = self.containerMO.egoPeerStableInfo, + let egoStableSig = self.containerMO.egoPeerStableInfoSig + else { + reply(nil, [], nil, nil, ContainerError.noPreparedIdentity) + return + } - loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in - guard let egoPeerKeys = egoPeerKeys else { - os_log("Don't have my own peer keys; can't join: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") - reply(nil, [], error) + let keyFactory = TPECPublicKeyFactory() + guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { + reply(nil, [], nil, nil, ContainerError.invalidPermanentInfoOrSig) + return + } + guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else { + reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig) + return + } + guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else { + os_log("join: self machineID %{public}@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID) + self.onqueueTTRUntrusted() + reply(nil, [], nil, nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID)) return } - self.moc.performAndWait { - let peer: Peer - let newDynamicInfo: TPPeerDynamicInfo - do { - (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID, - peerPermanentInfo: selfPermanentInfo, - stableInfo: selfStableInfo, - sponsorID: sponsor.peerID, - preapprovedKeys: preapprovedKeys, - vouchers: [SignedVoucher.with { - $0.voucher = voucher.data - $0.sig = voucher.sig - }, ], - egoPeerKeys: egoPeerKeys) - } catch { - os_log("Unable to create peer for joining: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, [], error) - return - } - let allTLKShares: [TLKShare] - let viewKeys: [ViewKeys] - do { - (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys, - tlkShares: tlkShares, - egoPeerKeys: egoPeerKeys, - egoPeerDynamicInfo: newDynamicInfo, - epoch: Int(selfPermanentInfo.epoch)) - } catch { - os_log("Unable to process keys before joining: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, [], error) + loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in + guard let egoPeerKeys = egoPeerKeys else { + os_log("Don't have my own peer keys; can't join: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + reply(nil, [], nil, nil, error) return } + self.moc.performAndWait { + let peer: Peer + let newDynamicInfo: TPPeerDynamicInfo + do { + (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID, + peerPermanentInfo: selfPermanentInfo, + stableInfo: selfStableInfo, + sponsorID: sponsor.peerID, + preapprovedKeys: preapprovedKeys, + vouchers: [SignedVoucher.with { + $0.voucher = voucher.data + $0.sig = voucher.sig + }, ], + egoPeerKeys: egoPeerKeys) + } catch { + os_log("Unable to create peer for joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, [], nil, nil, error) + return + } - do { - try self.model.checkIntroduction(forCandidate: selfPermanentInfo, - stableInfo: peer.stableInfoAndSig.toStableInfo(), - withSponsorID: sponsor.peerID) - } catch { - os_log("Error checking introduction: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, [], error) - return - } + guard let peerStableInfo = peer.stableInfoAndSig.toStableInfo() else { + os_log("Unable to create new peer stable new for joining", log: tplogDebug, type: .default) + reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig) + return + } - var bottle: Bottle - do { - bottle = try self.assembleBottle(egoPeerID: egoPeerID) - } catch { - reply(nil, [], error) - return - } + let allTLKShares: [TLKShare] + let viewKeys: [ViewKeys] + do { + (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys, + tlkShares: tlkShares, + egoPeerKeys: egoPeerKeys, + egoPeerDynamicInfo: newDynamicInfo, + epoch: Int(selfPermanentInfo.epoch)) + } catch { + os_log("Unable to process keys before joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, [], nil, nil, error) + return + } - os_log("Beginning join for peer %@", log: tplogDebug, type: .default, egoPeerID) - os_log("Join permanentInfo: %@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString()) - os_log("Join permanentInfoSig: %@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString()) - os_log("Join stableInfo: %@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.peerStableInfo.base64EncodedString()) - os_log("Join stableInfoSig: %@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.sig.base64EncodedString()) - os_log("Join dynamicInfo: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString()) - os_log("Join dynamicInfoSig: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString()) + do { + try self.model.checkIntroduction(forCandidate: selfPermanentInfo, + stableInfo: peer.stableInfoAndSig.toStableInfo(), + withSponsorID: sponsor.peerID) + } catch { + os_log("Error checking introduction: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, [], nil, nil, error) + return + } - os_log("Join vouchers: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() }) - os_log("Join voucher signatures: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() }) + var bottle: Bottle + do { + bottle = try self.assembleBottle(egoPeerID: egoPeerID) + } catch { + reply(nil, [], nil, nil, error) + return + } - os_log("Uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count) + os_log("Beginning join for peer %{public}@", log: tplogDebug, type: .default, egoPeerID) + os_log("Join permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString()) + os_log("Join permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString()) + os_log("Join stableInfo: %{public}@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.peerStableInfo.base64EncodedString()) + os_log("Join stableInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.sig.base64EncodedString()) + os_log("Join dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString()) + os_log("Join dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString()) - do { - os_log("Join peer: %@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString()) - } catch { - os_log("Join unable to encode peer: %@", log: tplogDebug, type: .debug, error as CVarArg) - } + os_log("Join vouchers: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() }) + os_log("Join voucher signatures: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() }) - let changeToken = self.containerMO.changeToken ?? "" - let request = JoinWithVoucherRequest.with { - $0.changeToken = changeToken - $0.peer = peer - $0.bottle = bottle - $0.tlkShares = allTLKShares - $0.viewKeys = viewKeys - } - self.cuttlefish.joinWithVoucher(request) { response, error in - os_log("JoinWithVoucher(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") - guard let response = response, error == nil else { - os_log("joinWithVoucher failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") - reply(nil, [], error ?? ContainerError.cloudkitResponseMissing) - return + os_log("Uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count) + + do { + os_log("Join peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString()) + } catch { + os_log("Join unable to encode peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg) } - self.moc.performAndWait { - do { - self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo - self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig - try self.onQueuePersist(changes: response.changes) - os_log("JoinWithVoucher succeeded", log: tplogDebug) + let changeToken = self.containerMO.changeToken ?? "" + let request = JoinWithVoucherRequest.with { + $0.changeToken = changeToken + $0.peer = peer + $0.bottle = bottle + $0.tlkShares = allTLKShares + $0.viewKeys = viewKeys + } + self.cuttlefish.joinWithVoucher(request) { response, error in + os_log("JoinWithVoucher(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + guard let response = response, error == nil else { + os_log("joinWithVoucher failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + reply(nil, [], nil, nil, error ?? ContainerError.cloudkitResponseMissing) + return + } - let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } - reply(egoPeerID, keyHierarchyRecords, nil) - } catch { - os_log("JoinWithVoucher failed: %@", log: tplogDebug, String(describing: error)) - reply(nil, [], error) + self.moc.performAndWait { + do { + self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo + self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig + + let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: selfPermanentInfo, + stableInfo: peerStableInfo) + + try self.onQueuePersist(changes: response.changes) + os_log("JoinWithVoucher succeeded", log: tplogDebug) + + let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } + reply(egoPeerID, keyHierarchyRecords, syncingViews, policy, nil) + } catch { + os_log("JoinWithVoucher failed: %{public}@", log: tplogDebug, String(describing: error)) + reply(nil, [], nil, nil, error) + } } } } @@ -2951,12 +3071,12 @@ class Container: NSObject { } } - func requestHealthCheck(requiresEscrowCheck: Bool, reply: @escaping (Bool, Bool, Bool, Error?) -> Void) { + func requestHealthCheck(requiresEscrowCheck: Bool, reply: @escaping (Bool, Bool, Bool, Bool, Error?) -> Void) { self.semaphore.wait() - let reply: (Bool, Bool, Bool, Error?) -> Void = { - os_log("health check complete: %@", log: tplogTrace, type: .info, traceError($3)) + let reply: (Bool, Bool, Bool, Bool, Error?) -> Void = { + os_log("health check complete: %{public}@", log: tplogTrace, type: .info, traceError($4)) self.semaphore.signal() - reply($0, $1, $2, $3) + reply($0, $1, $2, $3, $4) } os_log("requestHealthCheck requiring escrow check: %d", log: tplogDebug, type: .default, requiresEscrowCheck) @@ -2965,7 +3085,7 @@ class Container: NSObject { guard let egoPeerID = self.containerMO.egoPeerID else { // No identity, nothing to do os_log("requestHealthCheck: No identity.", log: tplogDebug, type: .default) - reply(false, false, false, ContainerError.noPreparedIdentity) + reply(false, false, false, false, ContainerError.noPreparedIdentity) return } let request = GetRepairActionRequest.with { @@ -2975,34 +3095,34 @@ class Container: NSObject { self.cuttlefish.getRepairAction(request) { response, error in guard error == nil else { - reply(false, false, false, error) + reply(false, false, false, false, error) return } guard let action = response?.repairAction else { - os_log("repair response is empty, returning false: %@", log: tplogDebug, type: .default) - reply(false, false, false, nil) + os_log("repair response is empty, returning false", log: tplogDebug, type: .default) + reply(false, false, false, false, nil) return } var postRepairAccount: Bool = false var postRepairEscrow: Bool = false var resetOctagon: Bool = false + var leaveTrust: Bool = false switch action { case .noAction: break case .postRepairAccount: postRepairAccount = true - break case .postRepairEscrow: postRepairEscrow = true - break case .resetOctagon: resetOctagon = true - break + case .leaveTrust: + leaveTrust = true case .UNRECOGNIZED: break } - reply(postRepairAccount, postRepairEscrow, resetOctagon, nil) + reply(postRepairAccount, postRepairEscrow, resetOctagon, leaveTrust, nil) } } } @@ -3010,16 +3130,16 @@ class Container: NSObject { func getSupportAppInfo(reply: @escaping (Data?, Error?) -> Void) { self.semaphore.wait() let reply: (Data?, Error?) -> Void = { - os_log("getSupportAppInfo complete: %@", log: tplogTrace, type: .info, traceError($1)) + os_log("getSupportAppInfo complete: %{public}@", log: tplogTrace, type: .info, traceError($1)) self.semaphore.signal() reply($0, $1) } self.cuttlefish.getSupportAppInfo { response, error in - os_log("getSupportAppInfo(): %@, error: %@", log: tplogDebug, + os_log("getSupportAppInfo(): %{public}@, error: %{public}@", log: tplogDebug, "(\(String(describing: response))", "\(String(describing: error))") guard let response = response, error == nil else { - os_log("getSupportAppInfo failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("getSupportAppInfo failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(nil, error ?? ContainerError.cloudkitResponseMissing) return } @@ -3037,67 +3157,75 @@ class Container: NSObject { func preflightPreapprovedJoin(reply: @escaping (Bool, Error?) -> Void) { self.semaphore.wait() let reply: (Bool, Error?) -> Void = { - os_log("preflightPreapprovedJoin complete: %@", log: tplogTrace, type: .info, traceError($1)) + os_log("preflightPreapprovedJoin complete: %{public}@", log: tplogTrace, type: .info, traceError($1)) self.semaphore.signal() reply($0, $1) } self.fetchAndPersistChanges { error in guard error == nil else { - os_log("preflightPreapprovedJoin unable to fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") + os_log("preflightPreapprovedJoin unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") reply(false, error) return } - // We explicitly ignore the machine ID list here; we're only interested in the peer states: do they preapprove us? + // We need to try to have all policy versions that our peers claim to behave + let allPolicyVersions = self.model.allPolicyVersions() + self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in + if let error = policyFetchError { + os_log("preflightPreapprovedJoin: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg) + } - guard !self.model.allPeerIDs().isEmpty else { - // If, after fetch and handle changes, there's no peers, then we can likely establish. - reply(true, nil) - return - } + // We explicitly ignore the machine ID list here; we're only interested in the peer states: do they preapprove us? - guard let egoPeerID = self.containerMO.egoPeerID, - let egoPermData = self.containerMO.egoPeerPermanentInfo, - let egoPermSig = self.containerMO.egoPeerPermanentInfoSig - else { - os_log("preflightPreapprovedJoin: no prepared identity", log: tplogDebug, type: .debug) - reply(false, ContainerError.noPreparedIdentity) + guard !self.model.allPeerIDs().isEmpty else { + // If, after fetch and handle changes, there's no peers, then we can likely establish. + reply(true, nil) return - } + } - let keyFactory = TPECPublicKeyFactory() - guard let egoPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { - os_log("preflightPreapprovedJoin: invalid permanent info", log: tplogDebug, type: .debug) - reply(false, ContainerError.invalidPermanentInfoOrSig) - return - } + guard let egoPeerID = self.containerMO.egoPeerID, + let egoPermData = self.containerMO.egoPeerPermanentInfo, + let egoPermSig = self.containerMO.egoPeerPermanentInfoSig + else { + os_log("preflightPreapprovedJoin: no prepared identity", log: tplogDebug, type: .debug) + reply(false, ContainerError.noPreparedIdentity) + return + } - guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPermanentInfo.signingPubKey.spki()) else { - os_log("preflightPreapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug) - reply(false, ContainerError.noPeersPreapprovePreparedIdentity) - return - } + let keyFactory = TPECPublicKeyFactory() + guard let egoPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { + os_log("preflightPreapprovedJoin: invalid permanent info", log: tplogDebug, type: .debug) + reply(false, ContainerError.invalidPermanentInfoOrSig) + return + } - reply(true, nil) + guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPermanentInfo.signingPubKey.spki()) else { + os_log("preflightPreapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug) + reply(false, ContainerError.noPeersPreapprovePreparedIdentity) + return + } + + reply(true, nil) + } } } func preapprovedJoin(ckksKeys: [CKKSKeychainBackedKeySet], tlkShares: [CKKSTLKShare], preapprovedKeys: [Data]?, - reply: @escaping (String?, [CKRecord], Error?) -> Void) { + reply: @escaping (String?, [CKRecord], Set?, TPPolicy?, Error?) -> Void) { self.semaphore.wait() - let reply: (String?, [CKRecord], Error?) -> Void = { - os_log("preapprovedJoin complete: %@", log: tplogTrace, type: .info, traceError($2)) + let reply: (String?, [CKRecord], Set?, TPPolicy?, Error?) -> Void = { + os_log("preapprovedJoin complete: %{public}@", log: tplogTrace, type: .info, traceError($4)) self.semaphore.signal() - reply($0, $1, $2) + reply($0, $1, $2, $3, $4) } self.fetchAndPersistChangesIfNeeded { error in guard error == nil else { - os_log("preapprovedJoin unable to fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") - reply(nil, [], error) + os_log("preapprovedJoin unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") + reply(nil, [], nil, nil, error) return } self.moc.performAndWait { @@ -3121,36 +3249,36 @@ class Container: NSObject { let egoStableSig = self.containerMO.egoPeerStableInfoSig else { os_log("preapprovedJoin: no prepared identity", log: tplogDebug, type: .debug) - reply(nil, [], ContainerError.noPreparedIdentity) + reply(nil, [], nil, nil, ContainerError.noPreparedIdentity) return } let keyFactory = TPECPublicKeyFactory() guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { - reply(nil, [], ContainerError.invalidPermanentInfoOrSig) + reply(nil, [], nil, nil, ContainerError.invalidPermanentInfoOrSig) return } guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else { - reply(nil, [], ContainerError.invalidStableInfoOrSig) + reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig) return } guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else { - os_log("preapprovedJoin: self machineID %@ (me) not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID) + os_log("preapprovedJoin: self machineID %{public}@ (me) not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID) self.onqueueTTRUntrusted() - reply(nil, [], ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID)) + reply(nil, [], nil, nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID)) return } loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in guard let egoPeerKeys = egoPeerKeys else { os_log("preapprovedJoin: Don't have my own keys: can't join", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") - reply(nil, [], error) + reply(nil, [], nil, nil, error) return } guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPeerKeys.signingKey.publicKey().spki()) else { os_log("preapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug) - reply(nil, [], ContainerError.noPeersPreapprovePreparedIdentity) + reply(nil, [], nil, nil, ContainerError.noPeersPreapprovePreparedIdentity) return } @@ -3167,8 +3295,14 @@ class Container: NSObject { vouchers: [], egoPeerKeys: egoPeerKeys) } catch { - os_log("Unable to create peer for joining: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, [], error) + os_log("Unable to create peer for joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, [], nil, nil, error) + return + } + + guard let peerStableInfo = peer.stableInfoAndSig.toStableInfo() else { + os_log("Unable to create new peer stable new for joining", log: tplogDebug, type: .default) + reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig) return } @@ -3181,8 +3315,8 @@ class Container: NSObject { egoPeerDynamicInfo: newDynamicInfo, epoch: Int(selfPermanentInfo.epoch)) } catch { - os_log("Unable to process keys before joining: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(nil, [], error) + os_log("Unable to process keys before joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, [], nil, nil, error) return } @@ -3190,27 +3324,27 @@ class Container: NSObject { do { bottle = try self.assembleBottle(egoPeerID: egoPeerID) } catch { - reply(nil, [], error) + reply(nil, [], nil, nil, error) return } - os_log("Beginning preapprovedJoin for peer %@", log: tplogDebug, type: .default, egoPeerID) - os_log("preapprovedJoin permanentInfo: %@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString()) - os_log("preapprovedJoin permanentInfoSig: %@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString()) - os_log("preapprovedJoin stableInfo: %@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString()) - os_log("preapprovedJoin stableInfoSig: %@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString()) - os_log("preapprovedJoin dynamicInfo: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString()) - os_log("preapprovedJoin dynamicInfoSig: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString()) + os_log("Beginning preapprovedJoin for peer %{public}@", log: tplogDebug, type: .default, egoPeerID) + os_log("preapprovedJoin permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString()) + os_log("preapprovedJoin permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString()) + os_log("preapprovedJoin stableInfo: %{public}@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString()) + os_log("preapprovedJoin stableInfoSig: %{public}@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString()) + os_log("preapprovedJoin dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString()) + os_log("preapprovedJoin dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString()) - os_log("preapprovedJoin vouchers: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() }) - os_log("preapprovedJoin voucher signatures: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() }) + os_log("preapprovedJoin vouchers: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() }) + os_log("preapprovedJoin voucher signatures: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() }) os_log("preapprovedJoin: uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count) do { - os_log("preapprovedJoin peer: %@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString()) + os_log("preapprovedJoin peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString()) } catch { - os_log("preapprovedJoin unable to encode peer: %@", log: tplogDebug, type: .debug, error as CVarArg) + os_log("preapprovedJoin unable to encode peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg) } let changeToken = self.containerMO.changeToken ?? "" @@ -3222,10 +3356,10 @@ class Container: NSObject { $0.viewKeys = viewKeys } self.cuttlefish.joinWithVoucher(request) { response, error in - os_log("preapprovedJoin(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + os_log("preapprovedJoin(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") guard let response = response, error == nil else { - os_log("preapprovedJoin failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") - reply(nil, [], error ?? ContainerError.cloudkitResponseMissing) + os_log("preapprovedJoin failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + reply(nil, [], nil, nil, error ?? ContainerError.cloudkitResponseMissing) return } @@ -3233,14 +3367,18 @@ class Container: NSObject { do { self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig + + let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: selfPermanentInfo, + stableInfo: peerStableInfo) + try self.onQueuePersist(changes: response.changes) os_log("preapprovedJoin succeeded", log: tplogDebug) let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } - reply(egoPeerID, keyHierarchyRecords, nil) + reply(egoPeerID, keyHierarchyRecords, syncingViews, policy, nil) } catch { - os_log("preapprovedJoin failed: %@", log: tplogDebug, String(describing: error)) - reply(nil, [], error) + os_log("preapprovedJoin failed: %{public}@", log: tplogDebug, String(describing: error)) + reply(nil, [], nil, nil, error) } } } @@ -3258,7 +3396,7 @@ class Container: NSObject { reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) { self.semaphore.wait() let reply: (TrustedPeersHelperPeerState?, Error?) -> Void = { - os_log("update complete: %@", log: tplogTrace, type: .info, traceError($1)) + os_log("update complete: %{public}@", log: tplogTrace, type: .info, traceError($1)) self.semaphore.signal() reply($0, $1) } @@ -3268,19 +3406,17 @@ class Container: NSObject { serialNumber: serialNumber, osVersion: osVersion, policyVersion: policyVersion, - policySecrets: policySecrets, - recoverySigningPubKey: nil, - recoveryEncryptionPubKey: nil) + policySecrets: policySecrets) self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges, reply: reply) } func set(preapprovedKeys: [Data], - reply: @escaping (Error?) -> Void) { + reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) { self.semaphore.wait() - let reply: (Error?) -> Void = { - os_log("setPreapprovedKeys complete: %@", log: tplogTrace, type: .info, traceError($0)) + let reply: (TrustedPeersHelperPeerState?, Error?) -> Void = { + os_log("setPreapprovedKeys complete: %{public}@", log: tplogTrace, type: .info, traceError($1)) self.semaphore.signal() - reply($0) + reply($0, $1) } self.moc.performAndWait { @@ -3289,13 +3425,13 @@ class Container: NSObject { guard let egoPeerID = self.containerMO.egoPeerID else { // No identity, nothing to do os_log("setPreapprovedKeys: No identity.", log: tplogDebug, type: .default) - reply(ContainerError.noPreparedIdentity) + reply(nil, ContainerError.noPreparedIdentity) return } loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in guard let signingKeyPair = signingKeyPair else { - os_log("setPreapprovedKeys: no signing key pair: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") - reply(error ?? ContainerError.unableToCreateKeyPair) + os_log("setPreapprovedKeys: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + reply(nil, error ?? ContainerError.unableToCreateKeyPair) return } @@ -3309,44 +3445,37 @@ class Container: NSObject { signing: signingKeyPair, currentMachineIDs: self.onqueueCurrentMIDList()) } catch { - os_log("setPreapprovedKeys: couldn't calculate dynamic info: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(error) + os_log("setPreapprovedKeys: couldn't calculate dynamic info: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, error) return } - os_log("setPreapprovedKeys: produced a dynamicInfo: %@", log: tplogDebug, type: .default, dynamicInfo) + os_log("setPreapprovedKeys: produced a dynamicInfo: %{public}@", log: tplogDebug, type: .default, dynamicInfo) if dynamicInfo == self.model.peer(withID: egoPeerID)?.dynamicInfo { os_log("setPreapprovedKeys: no change; nothing to do.", log: tplogDebug, type: .default) - reply(nil) + + // Calling this will fill in the peer status + self.updateTrustIfNeeded(reply: reply) return } - os_log("setPreapprovedKeys: attempting updateTrust for %@ with: %@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo) + os_log("setPreapprovedKeys: attempting updateTrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo) let request = UpdateTrustRequest.with { $0.changeToken = self.containerMO.changeToken ?? "" $0.peerID = egoPeerID $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo) } - self.cuttlefish.updateTrust(request) { response, error in - os_log("setPreapprovedKeys(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") - guard let response = response, error == nil else { - os_log("setPreapprovedKeys failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") - reply(error ?? ContainerError.cloudkitResponseMissing) - return - } - os_log("setPreapprovedKeys: updateTrust suceeded", log: tplogDebug, type: .default) - - do { - try self.persist(changes: response.changes) - } catch { - os_log("setPreapprovedKeys: could not persist changes: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(error) + self.perform(updateTrust: request) { state, error in + guard error == nil else { + os_log("setPreapprovedKeys: failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error") + reply(state, error) return } - reply(nil) + os_log("setPreapprovedKeys: updateTrust succeeded", log: tplogDebug, type: .default) + reply(state, nil) } } } @@ -3358,7 +3487,7 @@ class Container: NSObject { reply: @escaping ([CKRecord]?, Error?) -> Void) { self.semaphore.wait() let reply: ([CKRecord]?, Error?) -> Void = { - os_log("updateTLKs complete: %@", log: tplogTrace, type: .info, traceError($1)) + os_log("updateTLKs complete: %{public}@", log: tplogTrace, type: .info, traceError($1)) self.semaphore.signal() reply($0, $1) } @@ -3366,67 +3495,73 @@ class Container: NSObject { os_log("Uploading some new TLKs: %@", log: tplogDebug, type: .default, ckksKeys) self.moc.performAndWait { - guard let egoPeerID = self.containerMO.egoPeerID, - let egoPermData = self.containerMO.egoPeerPermanentInfo, - let egoPermSig = self.containerMO.egoPeerPermanentInfoSig - else { - os_log("Have no self identity, can't make tlk shares", log: tplogDebug, type: .default) - reply(nil, ContainerError.noPreparedIdentity) - return - } + self.onqueueUpdateTLKs(ckksKeys: ckksKeys, tlkShares: tlkShares, reply: reply) + } + } - guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: TPECPublicKeyFactory()) else { - os_log("Couldn't parse self identity", log: tplogDebug, type: .default, ckksKeys) - reply(nil, ContainerError.invalidPermanentInfoOrSig) + func onqueueUpdateTLKs(ckksKeys: [CKKSKeychainBackedKeySet], + tlkShares: [CKKSTLKShare], + reply: @escaping ([CKRecord]?, Error?) -> Void) { + guard let egoPeerID = self.containerMO.egoPeerID, + let egoPermData = self.containerMO.egoPeerPermanentInfo, + let egoPermSig = self.containerMO.egoPeerPermanentInfoSig + else { + os_log("Have no self identity, can't make tlk shares", log: tplogDebug, type: .default) + reply(nil, ContainerError.noPreparedIdentity) + return + } + + guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: TPECPublicKeyFactory()) else { + os_log("Couldn't parse self identity", log: tplogDebug, type: .default, ckksKeys) + reply(nil, ContainerError.invalidPermanentInfoOrSig) + return + } + + loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in + guard let egoPeerKeys = egoPeerKeys else { + os_log("Don't have my own peer keys; can't upload new TLKs: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + reply(nil, error) return } + self.moc.performAndWait { + guard let egoPeerDynamicInfo = self.model.getDynamicInfoForPeer(withID: egoPeerID) else { + os_log("Unable to fetch dynamic info for self", log: tplogDebug, type: .default) + reply(nil, ContainerError.missingDynamicInfo) + return + } - loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in - guard let egoPeerKeys = egoPeerKeys else { - os_log("Don't have my own peer keys; can't upload new TLKs: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing") + let allTLKShares: [TLKShare] + let viewKeys: [ViewKeys] + do { + (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys, + tlkShares: tlkShares, + egoPeerKeys: egoPeerKeys, + egoPeerDynamicInfo: egoPeerDynamicInfo, + epoch: Int(selfPermanentInfo.epoch)) + } catch { + os_log("Unable to process keys before uploading: %{public}@", log: tplogDebug, type: .default, error as CVarArg) reply(nil, error) return } - self.moc.performAndWait { - guard let egoPeerDynamicInfo = self.model.getDynamicInfoForPeer(withID: egoPeerID) else { - os_log("Unable to fetch dynamic info for self", log: tplogDebug, type: .default) - reply(nil, ContainerError.missingDynamicInfo) - return - } - let allTLKShares: [TLKShare] - let viewKeys: [ViewKeys] - do { - (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys, - tlkShares: tlkShares, - egoPeerKeys: egoPeerKeys, - egoPeerDynamicInfo: egoPeerDynamicInfo, - epoch: Int(selfPermanentInfo.epoch)) - } catch { - os_log("Unable to process keys before uploading: %@", log: tplogDebug, type: .default, error as CVarArg) + let request = UpdateTrustRequest.with { + $0.changeToken = self.containerMO.changeToken ?? "" + $0.peerID = egoPeerID + $0.tlkShares = allTLKShares + $0.viewKeys = viewKeys + } + + self.cuttlefish.updateTrust(request) { response, error in + os_log("UpdateTrust(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + + guard error == nil else { reply(nil, error) return } - let request = UpdateTrustRequest.with { - $0.changeToken = self.containerMO.changeToken ?? "" - $0.peerID = egoPeerID - $0.tlkShares = allTLKShares - $0.viewKeys = viewKeys - } - - self.cuttlefish.updateTrust(request) { response, error in - os_log("UpdateTrust(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") - - guard error == nil else { - reply(nil, error) - return - } - - let keyHierarchyRecords = response?.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } ?? [] - os_log("Recevied %d CKRecords back", log: tplogDebug, type: .default, keyHierarchyRecords.count) - reply(keyHierarchyRecords, nil) - } + let keyHierarchyRecords = response?.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } ?? [] + os_log("Recevied %d CKRecords back", log: tplogDebug, type: .default, keyHierarchyRecords.count) + reply(keyHierarchyRecords, nil) } } } @@ -3435,7 +3570,7 @@ class Container: NSObject { func getState(reply: @escaping (ContainerState) -> Void) { self.semaphore.wait() let reply: (ContainerState) -> Void = { - os_log("getState complete: %@", log: tplogTrace, type: .info, $0.egoPeerID ?? "") + os_log("getState complete: %{public}@", log: tplogTrace, type: .info, $0.egoPeerID ?? "") self.semaphore.signal() reply($0) } @@ -3459,7 +3594,7 @@ class Container: NSObject { } // This will only fetch changes if no changes have ever been fetched before - private func fetchAndPersistChangesIfNeeded(reply: @escaping (Error?) -> Void) { + func fetchAndPersistChangesIfNeeded(reply: @escaping (Error?) -> Void) { self.moc.performAndWait { if self.containerMO.changeToken == nil { self.onqueueFetchAndPersistChanges(reply: reply) @@ -3479,10 +3614,10 @@ class Container: NSObject { let request = FetchChangesRequest.with { $0.changeToken = self.containerMO.changeToken ?? "" } - os_log("Fetching with change token: %@", log: tplogDebug, type: .default, request.changeToken.count > 0 ? request.changeToken : "empty") + os_log("Fetching with change token: %{public}@", log: tplogDebug, type: .default, !request.changeToken.isEmpty ? request.changeToken : "empty") self.cuttlefish.fetchChanges(request) { response, error in - os_log("FetchChanges(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + os_log("FetchChanges(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") guard let response = response, error == nil else { switch error { case CuttlefishErrorMatcher(code: CuttlefishErrorCode.changeTokenExpired): @@ -3492,7 +3627,7 @@ class Container: NSObject { do { try self.deleteLocalCloudKitData() } catch { - os_log("Failed to reset local data: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("Failed to reset local data: %{public}@", log: tplogDebug, type: .default, error as CVarArg) reply(error) return } @@ -3502,10 +3637,10 @@ class Container: NSObject { return default: - os_log("Fetch error is an unknown error: %@", log: tplogDebug, type: .default, String(describing: error)) + os_log("Fetch error is an unknown error: %{public}@", log: tplogDebug, type: .default, String(describing: error)) } - os_log("Could not fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("Could not fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(error) return } @@ -3513,7 +3648,7 @@ class Container: NSObject { do { try self.persist(changes: response.changes) } catch { - os_log("Could not persist changes: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("Could not persist changes: %{public}@", log: tplogDebug, type: .default, error as CVarArg) reply(error) return } @@ -3536,13 +3671,13 @@ class Container: NSObject { guard let oldDynamicInfo = oldDynamicInfo else { return true } - if (newDynamicInfo.includedPeerIDs != oldDynamicInfo.includedPeerIDs) { + if newDynamicInfo.includedPeerIDs != oldDynamicInfo.includedPeerIDs { return true } - if (newDynamicInfo.excludedPeerIDs != oldDynamicInfo.excludedPeerIDs) { + if newDynamicInfo.excludedPeerIDs != oldDynamicInfo.excludedPeerIDs { return true } - if (newDynamicInfo.preapprovals != oldDynamicInfo.preapprovals) { + if newDynamicInfo.preapprovals != oldDynamicInfo.preapprovals { return true } return false @@ -3558,16 +3693,16 @@ class Container: NSObject { // the caller's responsibility to release it after it completes // (i.e. after reply is invoked). internal func fetchChangesAndUpdateTrustIfNeeded(stableChanges: StableChanges? = nil, - changesPending: Bool = false, - reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) { + peerChanges: Bool = false, + reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) { self.fetchAndPersistChanges { error in if let error = error { - os_log("fetchChangesAndUpdateTrustIfNeeded: fetching failed: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("fetchChangesAndUpdateTrustIfNeeded: fetching failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg) reply(nil, error) return } - self.updateTrustIfNeeded(stableChanges: stableChanges, changesPending: changesPending, reply: reply) + self.updateTrustIfNeeded(stableChanges: stableChanges, peerChanges: peerChanges, reply: reply) } } @@ -3581,132 +3716,152 @@ class Container: NSObject { // the caller's responsibility to release it after it completes // (i.e. after reply is invoked). private func updateTrustIfNeeded(stableChanges: StableChanges? = nil, - changesPending: Bool = false, + peerChanges: Bool = false, reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) { self.moc.performAndWait { guard let egoPeerID = self.containerMO.egoPeerID else { // No identity, nothing to do os_log("updateTrustIfNeeded: No identity.", log: tplogDebug, type: .default) - reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: changesPending, unknownMachineIDs: false, osVersion: nil), nil) + reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: peerChanges, unknownMachineIDs: false, osVersion: nil), nil) return } loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in guard let signingKeyPair = signingKeyPair else { - os_log("updateTrustIfNeeded: no signing key pair: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") - reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: changesPending, unknownMachineIDs: false, osVersion: nil), error) + os_log("updateTrustIfNeeded: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: peerChanges, unknownMachineIDs: false, osVersion: nil), error) return } - guard self.model.hasPeer(withID: egoPeerID) else { + guard let currentSelfInModel = self.model.peer(withID: egoPeerID) else { // Not in circle, nothing to do let isPreapproved = self.model.hasPotentiallyTrustedPeerPreapprovingKey(signingKeyPair.publicKey().spki()) - os_log("updateTrustIfNeeded: ego peer is not in model, is %@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved") + os_log("updateTrustIfNeeded: ego peer is not in model, is %{public}@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved") reply(TrustedPeersHelperPeerState(peerID: egoPeerID, isPreapproved: isPreapproved, status: .unknown, - memberChanges: changesPending, + memberChanges: peerChanges, unknownMachineIDs: false, osVersion: nil), nil) return } - self.moc.performAndWait { - let dynamicInfo: TPPeerDynamicInfo - var stableInfo: TPPeerStableInfo? - do { - // FIXME We should be able to calculate the contents of dynamicInfo without the signingKeyPair, - // and then only load the key if it has changed and we need to sign a new one. This would also - // help make our detection of change immune from non-canonical serialization of dynamicInfo. - dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID, - addingPeerIDs: nil, - removingPeerIDs: nil, - preapprovedKeys: nil, - signing: signingKeyPair, - currentMachineIDs: self.onqueueCurrentMIDList()) - stableInfo = try self.createNewStableInfoIfNeeded(stableChanges: stableChanges, - egoPeerID: egoPeerID, - dynamicInfo: dynamicInfo, - signingKeyPair: signingKeyPair) - } catch { - os_log("updateTrustIfNeeded: couldn't calculate dynamic info: %@", log: tplogDebug, type: .default, error as CVarArg) - reply(TrustedPeersHelperPeerState(peerID: egoPeerID, - isPreapproved: false, - status: self.model.statusOfPeer(withID: egoPeerID), - memberChanges: changesPending, - unknownMachineIDs: false, - osVersion: nil), - error) - return + // We need to try to have all policy versions that our peers claim to behave + let allPolicyVersions = self.model.allPolicyVersions() + self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in + if let error = policyFetchError { + os_log("updateTrustIfNeeded: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg) } - os_log("updateTrustIfNeeded: produced a stableInfo: %@", log: tplogDebug, type: .default, String(describing: stableInfo)) - os_log("updateTrustIfNeeded: produced a dynamicInfo: %@", log: tplogDebug, type: .default, dynamicInfo) - - let peer = self.model.peer(withID: egoPeerID) - if (stableInfo == nil || stableInfo == peer?.stableInfo) && - dynamicInfo == peer?.dynamicInfo { - os_log("updateTrustIfNeeded: complete.", log: tplogDebug, type: .default) - // No change to the dynamicInfo: update the MID list now that we've reached a steady state + self.moc.performAndWait { + let dynamicInfo: TPPeerDynamicInfo + var stableInfo: TPPeerStableInfo? do { - self.onqueueUpdateMachineIDListFromModel(dynamicInfo: dynamicInfo) - try self.moc.save() + + // FIXME We should be able to calculate the contents of dynamicInfo without the signingKeyPair, + // and then only load the key if it has changed and we need to sign a new one. This would also + // help make our detection of change immune from non-canonical serialization of dynamicInfo. + dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID, + addingPeerIDs: nil, + removingPeerIDs: nil, + preapprovedKeys: nil, + signing: signingKeyPair, + currentMachineIDs: self.onqueueCurrentMIDList()) + + stableInfo = try self.createNewStableInfoIfNeeded(stableChanges: stableChanges, + egoPeerID: egoPeerID, + existingStableInfo: currentSelfInModel.stableInfo, + dynamicInfo: dynamicInfo, + signingKeyPair: signingKeyPair) } catch { - os_log("updateTrustIfNeeded: unable to remove untrusted MachineIDs: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("updateTrustIfNeeded: couldn't calculate dynamic info: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(TrustedPeersHelperPeerState(peerID: egoPeerID, + isPreapproved: false, + status: self.model.statusOfPeer(withID: egoPeerID), + memberChanges: peerChanges, + unknownMachineIDs: false, + osVersion: nil), + error) + return } - reply(TrustedPeersHelperPeerState(peerID: egoPeerID, - isPreapproved: false, - status: self.model.statusOfPeer(withID: egoPeerID), - memberChanges: changesPending, - unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful(), - osVersion: peer?.stableInfo?.osVersion), - nil) - return - } - // Check if we change that should trigger a notification that should trigger TLKShare updates - let haveChanges = changesPending || self.haveTrustMemberChanges(newDynamicInfo: dynamicInfo, oldDynamicInfo: peer?.dynamicInfo) + os_log("updateTrustIfNeeded: produced a stableInfo: %{public}@", log: tplogDebug, type: .default, String(describing: stableInfo)) + os_log("updateTrustIfNeeded: produced a dynamicInfo: %{public}@", log: tplogDebug, type: .default, dynamicInfo) + + let peer = self.model.peer(withID: egoPeerID) + if (stableInfo == nil || stableInfo == peer?.stableInfo) && + dynamicInfo == peer?.dynamicInfo { + os_log("updateTrustIfNeeded: complete.", log: tplogDebug, type: .default) + // No change to the dynamicInfo: update the MID list now that we've reached a steady state + do { + self.onqueueUpdateMachineIDListFromModel(dynamicInfo: dynamicInfo) + try self.moc.save() + } catch { + os_log("updateTrustIfNeeded: unable to remove untrusted MachineIDs: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + } - let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo) - os_log("updateTrustIfNeeded: attempting updateTrust for %@ with: %@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo) - var request = UpdateTrustRequest.with { - $0.changeToken = self.containerMO.changeToken ?? "" - $0.peerID = egoPeerID - $0.dynamicInfoAndSig = signedDynamicInfo - } - if let stableInfo = stableInfo { - request.stableInfoAndSig = SignedPeerStableInfo(stableInfo) - } - self.cuttlefish.updateTrust(request) { response, error in - os_log("UpdateTrust(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") - guard let response = response, error == nil else { - os_log("UpdateTrust failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") - reply(nil, error ?? ContainerError.cloudkitResponseMissing) + reply(TrustedPeersHelperPeerState(peerID: egoPeerID, + isPreapproved: false, + status: self.model.statusOfPeer(withID: egoPeerID), + memberChanges: peerChanges, + unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful(), + osVersion: peer?.stableInfo?.osVersion), + nil) return } + // Check if we change that should trigger a notification that should trigger TLKShare updates + let havePeerChanges = peerChanges || self.haveTrustMemberChanges(newDynamicInfo: dynamicInfo, oldDynamicInfo: peer?.dynamicInfo) - do { - try self.persist(changes: response.changes) - } catch { - os_log("updateTrust failed: %@", log: tplogDebug, String(describing: error)) - reply(nil, error) - return + let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo) + os_log("updateTrustIfNeeded: attempting updateTrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo) + var request = UpdateTrustRequest.with { + $0.changeToken = self.containerMO.changeToken ?? "" + $0.peerID = egoPeerID + $0.dynamicInfoAndSig = signedDynamicInfo } - - if response.changes.more { - self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges, - changesPending: haveChanges, - reply: reply) - } else { - self.updateTrustIfNeeded(stableChanges: stableChanges, - changesPending: haveChanges, - reply: reply) + if let stableInfo = stableInfo { + request.stableInfoAndSig = SignedPeerStableInfo(stableInfo) } + + self.perform(updateTrust: request, stableChanges: stableChanges, peerChanges: havePeerChanges, reply: reply) } } } } } + private func perform(updateTrust request: UpdateTrustRequest, + stableChanges: StableChanges? = nil, + peerChanges: Bool = false, + reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) { + + self.cuttlefish.updateTrust(request) { response, error in + os_log("UpdateTrust(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + guard let response = response, error == nil else { + os_log("UpdateTrust failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + reply(nil, error ?? ContainerError.cloudkitResponseMissing) + return + } + + do { + try self.persist(changes: response.changes) + } catch { + os_log("UpdateTrust failed: %{public}@", log: tplogDebug, String(describing: error)) + reply(nil, error) + return + } + + if response.changes.more { + self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges, + peerChanges: peerChanges, + reply: reply) + } else { + self.updateTrustIfNeeded(stableChanges: stableChanges, + peerChanges: peerChanges, + reply: reply) + } + } + } + private func persist(changes: Changes) throws { // This is some nonsense: I can't figure out how to tell swift to throw an exception across performAndWait. // So, do it ourself @@ -3716,7 +3871,7 @@ class Container: NSObject { os_log("persist: Received %d peer differences, more: %d", log: tplogDebug, type: .default, changes.differences.count, changes.more) - os_log("persist: New change token: %@", log: tplogDebug, type: .default, changes.changeToken) + os_log("persist: New change token: %{public}@", log: tplogDebug, type: .default, changes.changeToken) do { try self.onQueuePersist(changes: changes) @@ -3736,7 +3891,7 @@ class Container: NSObject { self.containerMO.changeToken = changes.changeToken self.containerMO.moreChanges = changes.more - if changes.differences.count > 0 { + if !changes.differences.isEmpty { self.model.clearViableBottles() } @@ -3769,7 +3924,7 @@ class Container: NSObject { let signingKey = changes.recoverySigningPubKey let encryptionKey = changes.recoveryEncryptionPubKey - if signingKey.count > 0 && encryptionKey.count > 0 { + if !signingKey.isEmpty && !encryptionKey.isEmpty { self.addOrUpdate(signingKey: signingKey, encryptionKey: encryptionKey) } try self.moc.save() @@ -3797,7 +3952,7 @@ class Container: NSObject { self.model = Container.loadModel(from: self.containerMO) try self.moc.save() } catch { - os_log("Local delete failed: %@", log: tplogDebug, type: .default, error as CVarArg) + os_log("Local delete failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg) throw error } @@ -3808,6 +3963,9 @@ class Container: NSObject { private func addOrUpdate(signingKey: Data, encryptionKey: Data) { self.model.setRecoveryKeys( TPRecoveryKeyPair(signingSPKI: signingKey, encryptionSPKI: encryptionKey)) + + self.containerMO.recoveryKeySigningSPKI = signingKey + self.containerMO.recoveryKeyEncryptionSPKI = encryptionKey } // Must be on moc queue to call this. @@ -3875,9 +4033,9 @@ class Container: NSObject { guard let policyDoc = self.model.policy(withVersion: policyVersion) else { throw ContainerError.unknownPolicyVersion(policyVersion) } - assert(policyVersion == policyDoc.policyVersion) - if policyVersion == prevailingPolicyVersion { - assert(policyDoc.policyHash == prevailingPolicyHash) + assert(policyVersion == policyDoc.version.versionNumber) + if policyVersion == prevailingPolicyVersion.versionNumber { + assert(policyDoc.version.policyHash == prevailingPolicyVersion.policyHash) } return policyDoc } @@ -3885,42 +4043,49 @@ class Container: NSObject { // Must be on moc queue to call this. private func createNewStableInfoIfNeeded(stableChanges: StableChanges?, egoPeerID: String, + existingStableInfo: TPPeerStableInfo?, dynamicInfo: TPPeerDynamicInfo, signingKeyPair: _SFECKeyPair) throws -> TPPeerStableInfo? { func noChange(_ change: T?, _ existing: T?) -> Bool { return (nil == change) || change == existing } - let existingStableInfo = self.model.peer(withID: egoPeerID)?.stableInfo + + let policyOfPeers = try? self.model.policy(forPeerIDs: dynamicInfo.includedPeerIDs, + candidatePeerID: egoPeerID, + candidateStableInfo: existingStableInfo) + + // Pick the best version of: + // 1. The policy version asked for by the client + // 2. The max of our existing policyVersion, the highest policy used by our trusted peers, and the compile-time prevailing policy version + let optimalPolicyVersionNumber = stableChanges?.policyVersion ?? + max(existingStableInfo?.bestPolicyVersion().versionNumber ?? prevailingPolicyVersion.versionNumber, + policyOfPeers?.version.versionNumber ?? prevailingPolicyVersion.versionNumber, + prevailingPolicyVersion.versionNumber) + + // Determine which recovery key we'd like to be using, given our current idea of who to trust + let optimalRecoveryKey = self.model.bestRecoveryKey(for: existingStableInfo, dynamicInfo: dynamicInfo) + if noChange(stableChanges?.deviceName, existingStableInfo?.deviceName) && noChange(stableChanges?.serialNumber, existingStableInfo?.serialNumber) && noChange(stableChanges?.osVersion, existingStableInfo?.osVersion) && - noChange(stableChanges?.policyVersion, existingStableInfo?.policyVersion) && + noChange(optimalPolicyVersionNumber, existingStableInfo?.bestPolicyVersion().versionNumber) && noChange(stableChanges?.policySecrets, existingStableInfo?.policySecrets) && - noChange(stableChanges?.recoverySigningPubKey, existingStableInfo?.recoverySigningPublicKey) && - noChange(stableChanges?.recoveryEncryptionPubKey, existingStableInfo?.recoveryEncryptionPublicKey) && - self.model.doesPeerRecoveryKeyMatchPeers(egoPeerID) { + noChange(optimalRecoveryKey?.signingSPKI, existingStableInfo?.recoverySigningPublicKey) && + noChange(optimalRecoveryKey?.encryptionSPKI, existingStableInfo?.recoveryEncryptionPublicKey) { return nil } - let policyHash: String? - if let policyVersion = stableChanges?.policyVersion { - let policyDoc = try self.getPolicyDoc(policyVersion) - policyHash = policyDoc.policyHash - } else { - policyHash = nil - } - // Determine which recovery key we'd like to be using, given our current idea of who to trust - let newRecoveryKeys = self.model.bestRecoveryKey(with: dynamicInfo) + let optimalPolicyVersion = try self.getPolicyDoc(optimalPolicyVersionNumber).version - return try self.model.createStableInfo(withPolicyVersion: stableChanges?.policyVersion ?? existingStableInfo?.policyVersion ?? prevailingPolicyVersion, - policyHash: policyHash ?? existingStableInfo?.policyHash ?? prevailingPolicyHash, + return try self.model.createStableInfo(withFrozenPolicyVersion: frozenPolicyVersion, + flexiblePolicyVersion: optimalPolicyVersion, policySecrets: stableChanges?.policySecrets ?? existingStableInfo?.policySecrets, deviceName: stableChanges?.deviceName ?? existingStableInfo?.deviceName ?? "", serialNumber: stableChanges?.serialNumber ?? existingStableInfo?.serialNumber ?? "", osVersion: stableChanges?.osVersion ?? existingStableInfo?.osVersion ?? "", signing: signingKeyPair, - recoverySigningPubKey: newRecoveryKeys?.signingSPKI ?? existingStableInfo?.recoverySigningPublicKey, - recoveryEncryptionPubKey: newRecoveryKeys?.encryptionSPKI ?? existingStableInfo?.recoveryEncryptionPublicKey) + recoverySigningPubKey: optimalRecoveryKey?.signingSPKI, + recoveryEncryptionPubKey: optimalRecoveryKey?.encryptionSPKI) } private func assembleBottle(egoPeerID: String) throws -> Bottle { @@ -3982,7 +4147,7 @@ class Container: NSObject { func reportHealth(request: ReportHealthRequest, reply: @escaping (Error?) -> Void) { self.semaphore.wait() let reply: (Error?) -> Void = { - os_log("reportHealth complete %@", log: tplogTrace, type: .info, traceError($0)) + os_log("reportHealth complete %{public}@", log: tplogTrace, type: .info, traceError($0)) self.semaphore.signal() reply($0) } @@ -3997,7 +4162,7 @@ class Container: NSObject { self.moc.performAndWait { self.cuttlefish.reportHealth(updatedRequest) { response, error in - os_log("reportHealth(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") + os_log("reportHealth(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))") guard error == nil else { reply(error) return @@ -4010,14 +4175,14 @@ class Container: NSObject { func pushHealthInquiry(reply: @escaping (Error?) -> Void) { self.semaphore.wait() let reply: (Error?) -> Void = { - os_log("reportHealth complete %@", log: tplogTrace, type: .info, traceError($0)) + os_log("reportHealth complete %{public}@", log: tplogTrace, type: .info, traceError($0)) self.semaphore.signal() reply($0) } self.moc.performAndWait { self.cuttlefish.pushHealthInquiry(PushHealthInquiryRequest()) { response, error in - os_log("pushHealthInquiry(): %@, error: %@", log: tplogDebug, "\(String(describing: response))", "\(String(describing: error))") + os_log("pushHealthInquiry(): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: response))", "\(String(describing: error))") guard error == nil else { reply(error) return diff --git a/keychain/TrustedPeersHelper/ContainerMap.swift b/keychain/TrustedPeersHelper/ContainerMap.swift index 59f539dc..73a8fdfa 100644 --- a/keychain/TrustedPeersHelper/ContainerMap.swift +++ b/keychain/TrustedPeersHelper/ContainerMap.swift @@ -191,7 +191,7 @@ public class MyCodeConnection: CloudKitCode.Invocable { operation.requestCompletedBlock = requestCompletion let loggingCompletion = { (response: ResponseType?, error: Error?) -> Void in - os_log("%@(%@): %@, error: %@", + os_log("%{public}@(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, function, "\(String(describing: request))", @@ -284,4 +284,11 @@ class ContainerMap { let filename = name.container + "-" + name.context + ".TrustedPeersHelper.db" return SecCopyURLForFileInKeychainDirectory(filename as CFString) as URL } + + // To be called via test only + func removeAllContainers() { + queue.sync { + self.containers.removeAll() + } + } } diff --git a/keychain/TrustedPeersHelper/Container_BottledPeers.swift b/keychain/TrustedPeersHelper/Container_BottledPeers.swift new file mode 100644 index 00000000..ff281dae --- /dev/null +++ b/keychain/TrustedPeersHelper/Container_BottledPeers.swift @@ -0,0 +1,81 @@ +import CoreData +import Foundation + +extension Container { + func preflightVouchWithBottle(bottleID: String, + reply: @escaping (String?, Set?, TPPolicy?, Error?) -> Void) { + self.semaphore.wait() + let reply: (String?, Set?, TPPolicy?, Error?) -> Void = { + os_log("preflightVouchWithBottle complete: %{public}@", + log: tplogTrace, type: .info, traceError($3)) + self.semaphore.signal() + reply($0, $1, $2, $3) + } + + self.fetchAndPersistChangesIfNeeded { fetchError in + guard fetchError == nil else { + os_log("preflightVouchWithBottle unable to fetch current peers: %{public}@", log: tplogDebug, type: .default, (fetchError as CVarArg?) ?? "") + reply(nil, nil, nil, fetchError) + return + } + + // Ensure we have all policy versions claimed by peers, including our sponsor + let allPolicyVersions = self.model.allPolicyVersions() + self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, fetchPolicyDocumentsError in + guard fetchPolicyDocumentsError == nil else { + os_log("preflightVouchWithBottle unable to fetch policy documents: %{public}@", log: tplogDebug, type: .default, (fetchPolicyDocumentsError as CVarArg?) ?? "no error") + reply(nil, nil, nil, fetchPolicyDocumentsError) + return + } + + self.moc.performAndWait { + guard let egoPeerID = self.containerMO.egoPeerID, + let egoPermData = self.containerMO.egoPeerPermanentInfo, + let egoPermSig = self.containerMO.egoPeerPermanentInfoSig else { + os_log("fetchCurrentPolicy failed to find ego peer information", log: tplogDebug, type: .error) + reply(nil, nil, nil, ContainerError.noPreparedIdentity) + return + } + + let keyFactory = TPECPublicKeyFactory() + guard let egoPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { + os_log("fetchCurrentPolicy failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error) + reply(nil, nil, nil, ContainerError.invalidPermanentInfoOrSig) + return + } + + self.onqueueFindBottle(bottleID: bottleID) { bottleMO, error in + guard let bottleMO = bottleMO else { + os_log("preflightVouchWithBottle found no bottle: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") + reply(nil, nil, nil, error) + return + } + + guard let sponsorPeer = self.model.peer(withID: bottleMO.peerID ?? "") else { + os_log("preflightVouchWithBottle found no peer to match bottle", log: tplogDebug, type: .default) + reply(nil, nil, nil, ContainerError.sponsorNotRegistered(bottleMO.peerID ?? "no peer ID given")) + return + } + + guard let sponsorPeerStableInfo = sponsorPeer.stableInfo else { + os_log("preflightVouchWithBottle sponsor peer has no stable info", log: tplogDebug, type: .default) + reply(nil, nil, nil, ContainerError.sponsorNotRegistered(bottleMO.peerID ?? "no peer ID given")) + return + } + + do { + // We need to extract the syncing policy that the remote peer would have used (if they were the type of device that we are) + // So, figure out their policy version... + let (views, policy) = try self.policyAndViewsFor(permanentInfo: egoPermanentInfo, stableInfo: sponsorPeerStableInfo) + + reply(bottleMO.peerID, views, policy, nil) + } catch { + os_log("preflightVouchWithBottle failed to fetch policy: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "") + reply(nil, nil, nil, error) + } + } + } + } + } + } +} diff --git a/keychain/TrustedPeersHelper/Container_MachineIDs.swift b/keychain/TrustedPeersHelper/Container_MachineIDs.swift index 0a8dd0bc..19e76cbb 100644 --- a/keychain/TrustedPeersHelper/Container_MachineIDs.swift +++ b/keychain/TrustedPeersHelper/Container_MachineIDs.swift @@ -57,7 +57,7 @@ extension Container { // Once we run this upgrade, we will set the allowed bool to false, since it's unused. // Therefore, if we have a single record with "allowed" set, we haven't run the upgrade. - let runUpgrade = knownMachineMOs.filter { $0.allowed }.count > 0 + let runUpgrade = !knownMachineMOs.filter { $0.allowed }.isEmpty if runUpgrade { knownMachineMOs.forEach { mo in if mo.allowed { @@ -70,15 +70,29 @@ extension Container { } } - func setAllowedMachineIDs(_ allowedMachineIDs: Set, reply: @escaping (Bool, Error?) -> Void) { + func enforceIDMSListChanges(knownMachines: Set) -> Bool { + if self.containerMO.honorIDMSListChanges == "YES"{ + return true + } else if self.containerMO.honorIDMSListChanges == "NO" { + return false + } else if self.containerMO.honorIDMSListChanges == "UNKNOWN" && knownMachines.isEmpty { + return false + } else if self.containerMO.honorIDMSListChanges == "UNKNOWN" && !knownMachines.isEmpty { + return true + } else { + return true + } + } + + func setAllowedMachineIDs(_ allowedMachineIDs: Set, honorIDMSListChanges: Bool, reply: @escaping (Bool, Error?) -> Void) { self.semaphore.wait() let reply: (Bool, Error?) -> Void = { - os_log("setAllowedMachineIDs complete: %@", log: tplogTrace, type: .info, traceError($1)) + os_log("setAllowedMachineIDs complete: %{public}@", log: tplogTrace, type: .info, traceError($1)) self.semaphore.signal() reply($0, $1) } - os_log("Setting allowed machine IDs: %@", log: tplogDebug, type: .default, allowedMachineIDs) + os_log("Setting allowed machine IDs: %{public}@", log: tplogDebug, type: .default, allowedMachineIDs) // Note: we currently ignore any machineIDs that are set in the model, but never appeared on the // Trusted Devices list. We should give them a grace period (1wk?) then kick them out. @@ -86,20 +100,21 @@ extension Container { self.moc.performAndWait { do { var differences = false + self.containerMO.honorIDMSListChanges = honorIDMSListChanges ? "YES" : "NO" var knownMachines = containerMO.machines as? Set ?? Set() let knownMachineIDs = Set(knownMachines.compactMap { $0.machineID }) knownMachines.forEach { machine in guard let mid = machine.machineID else { - os_log("Machine has no ID: %@", log: tplogDebug, type: .default, machine) + os_log("Machine has no ID: %{public}@", log: tplogDebug, type: .default, machine) return } if allowedMachineIDs.contains(mid) { if machine.status == TPMachineIDStatus.allowed.rawValue { - os_log("Machine ID still trusted: %@", log: tplogDebug, type: .default, String(describing: machine.machineID)) + os_log("Machine ID still trusted: %{public}@", log: tplogDebug, type: .default, String(describing: machine.machineID)) } else { - os_log("Machine ID newly retrusted: %@", log: tplogDebug, type: .default, String(describing: machine.machineID)) + os_log("Machine ID newly retrusted: %{public}@", log: tplogDebug, type: .default, String(describing: machine.machineID)) differences = true } machine.status = Int64(TPMachineIDStatus.allowed.rawValue) @@ -115,14 +130,14 @@ extension Container { if machine.seenOnFullList { machine.status = Int64(TPMachineIDStatus.disallowed.rawValue) machine.modified = Date() - os_log("Newly distrusted machine ID: %@", log: tplogDebug, type: .default, String(describing: machine.machineID)) + os_log("Newly distrusted machine ID: %{public}@", log: tplogDebug, type: .default, String(describing: machine.machineID)) differences = true } else { if machine.modifiedInPast(hours: cutoffHours) { - os_log("Allowed-but-unseen machine ID isn't on full list, last modified %@, ignoring: %@", log: tplogDebug, type: .default, machine.modifiedDate(), String(describing: machine.machineID)) + os_log("Allowed-but-unseen machine ID isn't on full list, last modified %{public}@, ignoring: %{public}@", log: tplogDebug, type: .default, machine.modifiedDate(), String(describing: machine.machineID)) } else { - os_log("Allowed-but-unseen machine ID isn't on full list, last modified %@, distrusting: %@", log: tplogDebug, type: .default, machine.modifiedDate(), String(describing: machine.machineID)) + os_log("Allowed-but-unseen machine ID isn't on full list, last modified %{public}@, distrusting: %{public}@", log: tplogDebug, type: .default, machine.modifiedDate(), String(describing: machine.machineID)) machine.status = Int64(TPMachineIDStatus.disallowed.rawValue) machine.modified = Date() differences = true @@ -131,9 +146,9 @@ extension Container { } else if machine.status == TPMachineIDStatus.unknown.rawValue { if machine.modifiedInPast(hours: cutoffHours) { - os_log("Unknown machine ID last modified %@; leaving unknown: %@", log: tplogDebug, type: .default, machine.modifiedDate(), String(describing: machine.machineID)) + os_log("Unknown machine ID last modified %{public}@; leaving unknown: %{public}@", log: tplogDebug, type: .default, machine.modifiedDate(), String(describing: machine.machineID)) } else { - os_log("Unknown machine ID last modified %@; distrusting: %@", log: tplogDebug, type: .default, machine.modifiedDate(), String(describing: machine.machineID)) + os_log("Unknown machine ID last modified %{public}@; distrusting: %{public}@", log: tplogDebug, type: .default, machine.modifiedDate(), String(describing: machine.machineID)) machine.status = Int64(TPMachineIDStatus.disallowed.rawValue) machine.modified = Date() differences = true @@ -144,7 +159,7 @@ extension Container { // Do we need to create any further objects? allowedMachineIDs.forEach { machineID in - if(!knownMachineIDs.contains(machineID)) { + if !knownMachineIDs.contains(machineID) { // We didn't know about this machine before; it's newly trusted! let machine = MachineMO(context: self.moc) machine.machineID = machineID @@ -152,7 +167,7 @@ extension Container { machine.seenOnFullList = true machine.modified = Date() machine.status = Int64(TPMachineIDStatus.allowed.rawValue) - os_log("Newly trusted machine ID: %@", log: tplogDebug, type: .default, String(describing: machine.machineID)) + os_log("Newly trusted machine ID: %{public}@", log: tplogDebug, type: .default, String(describing: machine.machineID)) differences = true self.containerMO.addToMachines(machine) @@ -160,13 +175,12 @@ extension Container { } } - // if this account is not a demo account... - if knownMachines.count > 0 { + if self.enforceIDMSListChanges(knownMachines: knownMachines) { // Are there any machine IDs in the model that aren't in the list? If so, add them as "unknown" let modelMachineIDs = self.model.allMachineIDs() modelMachineIDs.forEach { peerMachineID in if !knownMachineIDs.contains(peerMachineID) && !allowedMachineIDs.contains(peerMachineID) { - os_log("Peer machineID is unknown, beginning grace period: %@", log: tplogDebug, type: .default, peerMachineID) + os_log("Peer machineID is unknown, beginning grace period: %{public}@", log: tplogDebug, type: .default, peerMachineID) let machine = MachineMO(context: self.moc) machine.machineID = peerMachineID machine.container = containerMO @@ -179,7 +193,7 @@ extension Container { } } } else { - os_log("Believe we're in a demo account; not starting an unknown machine ID grace period", log: tplogDebug, type: .default) + os_log("Believe we're in a demo account, not enforcing IDMS list", log: tplogDebug, type: .default) } // We no longer use allowed machine IDs. @@ -189,7 +203,7 @@ extension Container { reply(differences, nil) } catch { - os_log("Error setting machine ID list: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") + os_log("Error setting machine ID list: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error") reply(false, error) } } @@ -198,12 +212,12 @@ extension Container { func addAllow(_ machineIDs: [String], reply: @escaping (Error?) -> Void) { self.semaphore.wait() let reply: (Error?) -> Void = { - os_log("addAllow complete: %@", log: tplogTrace, type: .info, traceError($0)) + os_log("addAllow complete: %{public}@", log: tplogTrace, type: .info, traceError($0)) self.semaphore.signal() reply($0) } - os_log("Adding allowed machine IDs: %@", log: tplogDebug, type: .default, machineIDs) + os_log("Adding allowed machine IDs: %{public}@", log: tplogDebug, type: .default, machineIDs) self.moc.performAndWait { do { @@ -218,7 +232,7 @@ extension Container { if machine.machineID == machineID { machine.status = Int64(TPMachineIDStatus.allowed.rawValue) machine.modified = Date() - os_log("Continue to trust machine ID: %@", log: tplogDebug, type: .default, String(describing: machine.machineID)) + os_log("Continue to trust machine ID: %{public}@", log: tplogDebug, type: .default, String(describing: machine.machineID)) } } @@ -229,7 +243,7 @@ extension Container { machine.seenOnFullList = false machine.modified = Date() machine.status = Int64(TPMachineIDStatus.allowed.rawValue) - os_log("Newly trusted machine ID: %@", log: tplogDebug, type: .default, String(describing: machine.machineID)) + os_log("Newly trusted machine ID: %{public}@", log: tplogDebug, type: .default, String(describing: machine.machineID)) self.containerMO.addToMachines(machine) knownMachines.insert(machine) @@ -247,12 +261,12 @@ extension Container { func removeAllow(_ machineIDs: [String], reply: @escaping (Error?) -> Void) { self.semaphore.wait() let reply: (Error?) -> Void = { - os_log("removeAllow complete: %@", log: tplogTrace, type: .info, traceError($0)) + os_log("removeAllow complete: %{public}@", log: tplogTrace, type: .info, traceError($0)) self.semaphore.signal() reply($0) } - os_log("Removing allowed machine IDs: %@", log: tplogDebug, type: .default, machineIDs) + os_log("Removing allowed machine IDs: %{public}@", log: tplogDebug, type: .default, machineIDs) self.moc.performAndWait { do { @@ -268,7 +282,7 @@ extension Container { if machine.machineID == machineID { machine.status = Int64(TPMachineIDStatus.unknown.rawValue) machine.modified = Date.distantPast - os_log("Now suspicious of machine ID: %@", log: tplogDebug, type: .default, String(describing: machine.machineID)) + os_log("Now suspicious of machine ID: %{public}@", log: tplogDebug, type: .default, String(describing: machine.machineID)) } } @@ -278,7 +292,7 @@ extension Container { machine.container = containerMO machine.status = Int64(TPMachineIDStatus.unknown.rawValue) machine.modified = Date.distantPast - os_log("Suspicious of new machine ID: %@", log: tplogDebug, type: .default, String(describing: machine.machineID)) + os_log("Suspicious of new machine ID: %{public}@", log: tplogDebug, type: .default, String(describing: machine.machineID)) self.containerMO.addToMachines(machine) knownMachines.insert(machine) @@ -296,7 +310,7 @@ extension Container { func fetchAllowedMachineIDs(reply: @escaping (Set?, Error?) -> Void) { self.semaphore.wait() let reply: (Set?, Error?) -> Void = { - os_log("fetchAllowedMachineIDs complete: %@", log: tplogTrace, type: .info, traceError($1)) + os_log("fetchAllowedMachineIDs complete: %{public}@", log: tplogTrace, type: .info, traceError($1)) self.semaphore.signal() reply($0, $1) } @@ -305,34 +319,34 @@ extension Container { self.moc.performAndWait { let knownMachines = containerMO.machines as? Set ?? Set() - let allowedMachineIDs = knownMachines.filter { $0.status == Int64(TPMachineIDStatus.allowed.rawValue) }.compactMap({ $0.machineID }) + let allowedMachineIDs = knownMachines.filter { $0.status == Int64(TPMachineIDStatus.allowed.rawValue) }.compactMap { $0.machineID } reply(Set(allowedMachineIDs), nil) } } func onqueueMachineIDAllowedByIDMS(machineID: String) -> Bool { + // For Demo accounts, if the list is entirely empty, then everything is allowed - let machines = containerMO.machines as? Set ?? Set() - if machines.count == 0 { - os_log("machineID list is empty; allowing %@", log: tplogDebug, type: .debug, machineID) + let knownMachines = containerMO.machines as? Set ?? Set() + + if !self.enforceIDMSListChanges(knownMachines: knownMachines) { + os_log("not enforcing idms list changes; allowing %{public}@", log: tplogDebug, type: .debug, machineID) return true } // Note: this function rejects grey devices: machineIDs that are neither allowed nor disallowed - for mo in machines { - if mo.machineID == machineID { - if mo.status == TPMachineIDStatus.allowed.rawValue { - return true - } else { - os_log("machineID %@ not explicitly allowed: %@", log: tplogDebug, type: .debug, machineID, mo) - return false - } + for mo in knownMachines where mo.machineID == machineID { + if mo.status == TPMachineIDStatus.allowed.rawValue { + return true + } else { + os_log("machineID %{public}@ not explicitly allowed: %{public}@", log: tplogDebug, type: .debug, machineID, mo) + return false } } // Didn't find it? reject. - os_log("machineID %@ not found on list", log: tplogDebug, type: .debug, machineID) + os_log("machineID %{public}@ not found on list", log: tplogDebug, type: .debug, machineID) return false } @@ -358,9 +372,9 @@ extension Container { let trustedMachineIDs = Set(dynamicInfo.includedPeerIDs.compactMap { self.model.peer(withID: $0)?.permanentInfo.machineID }) // if this account is not a demo account... - if machines.count > 0 { + if self.enforceIDMSListChanges(knownMachines: machines) { for peerMachineID in trustedMachineIDs.subtracting(knownMachineIDs) { - os_log("Peer machineID is unknown, beginning grace period: %@", log: tplogDebug, type: .default, peerMachineID) + os_log("Peer machineID is unknown, beginning grace period: %{public}@", log: tplogDebug, type: .default, peerMachineID) let machine = MachineMO(context: self.moc) machine.machineID = peerMachineID machine.container = self.containerMO @@ -371,14 +385,12 @@ extension Container { self.containerMO.addToMachines(machine) } } else { - os_log("Believe we're in a demo account; not starting an unknown machine ID timer", log: tplogDebug, type: .default) + os_log("Not enforcing IDMS list changes", log: tplogDebug, type: .default) } - for mo in (machines) { - if mo.status == TPMachineIDStatus.disallowed.rawValue { - os_log("Dropping knowledge of machineID %@", log: tplogDebug, type: .debug, String(describing: mo.machineID)) - self.containerMO.removeFromMachines(mo) - } + for mo in (machines) where mo.status == TPMachineIDStatus.disallowed.rawValue { + os_log("Dropping knowledge of machineID %{public}@", log: tplogDebug, type: .debug, String(describing: mo.machineID)) + self.containerMO.removeFromMachines(mo) } } @@ -386,9 +398,14 @@ extension Container { // Useful means that there's an unknown MID whose modification date is before the cutoff // A full list fetch would either confirm it as 'untrusted' or make it trusted again func onqueueFullIDMSListWouldBeHelpful() -> Bool { + + if self.containerMO.honorIDMSListChanges == "UNKNOWN" { + return true + } + let unknownMOs = (containerMO.machines as? Set ?? Set()).filter { $0.status == TPMachineIDStatus.unknown.rawValue } let outdatedMOs = unknownMOs.filter { !$0.modifiedInPast(hours: cutoffHours) } - return outdatedMOs.count > 0 + return !outdatedMOs.isEmpty } } diff --git a/keychain/TrustedPeersHelper/Container_RecoveryKey.swift b/keychain/TrustedPeersHelper/Container_RecoveryKey.swift new file mode 100644 index 00000000..165764a9 --- /dev/null +++ b/keychain/TrustedPeersHelper/Container_RecoveryKey.swift @@ -0,0 +1,91 @@ +import CoreData +import Foundation + +extension Container { + func preflightVouchWithRecoveryKey(recoveryKey: String, + salt: String, + reply: @escaping (String?, Set?, TPPolicy?, Error?) -> Void) { + self.semaphore.wait() + let reply: (String?, Set?, TPPolicy?, Error?) -> Void = { + os_log("preflightRecoveryKey complete: %{public}@", + log: tplogTrace, type: .info, traceError($3)) + self.semaphore.signal() + reply($0, $1, $2, $3) + } + + self.fetchAndPersistChangesIfNeeded { fetchError in + guard fetchError == nil else { + os_log("preflightRecoveryKey unable to fetch current peers: %{public}@", log: tplogDebug, type: .default, (fetchError as CVarArg?) ?? "") + reply(nil, nil, nil, fetchError) + return + } + + // Ensure we have all policy versions claimed by peers, including our sponsor + self.fetchPolicyDocumentsWithSemaphore(versions: self.model.allPolicyVersions()) { _, fetchPolicyDocumentsError in + guard fetchPolicyDocumentsError == nil else { + os_log("preflightRecoveryKey unable to fetch policy documents: %{public}@", log: tplogDebug, type: .default, (fetchPolicyDocumentsError as CVarArg?) ?? "no error") + reply(nil, nil, nil, fetchPolicyDocumentsError) + return + } + + self.moc.performAndWait { + guard let egoPeerID = self.containerMO.egoPeerID, + let egoPermData = self.containerMO.egoPeerPermanentInfo, + let egoPermSig = self.containerMO.egoPeerPermanentInfoSig else { + os_log("preflightRecoveryKey: no ego peer ID", log: tplogDebug, type: .default) + reply(nil, nil, nil, ContainerError.noPreparedIdentity) + return + } + + let keyFactory = TPECPublicKeyFactory() + guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else { + reply(nil, nil, nil, ContainerError.invalidPermanentInfoOrSig) + return + } + + var recoveryKeys: RecoveryKey + do { + recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt) + } catch { + os_log("preflightRecoveryKey: failed to create recovery keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, nil, nil, ContainerError.failedToCreateRecoveryKey) + return + } + + // Dear model: if i were to use this recovery key, what peers would I end up using? + guard self.model.isRecoveryKeyEnrolled() else { + os_log("preflightRecoveryKey: recovery Key is not enrolled", log: tplogDebug, type: .default) + reply(nil, nil, nil, ContainerError.recoveryKeysNotEnrolled) + return + } + + guard let sponsorPeerID = self.model.peerIDThatTrustsRecoveryKeys(TPRecoveryKeyPair(signingSPKI: recoveryKeys.peerKeys.signingKey.publicKey.keyData, + encryptionSPKI: recoveryKeys.peerKeys.encryptionKey.publicKey.keyData)) else { + os_log("preflightRecoveryKey Untrusted recovery key set", log: tplogDebug, type: .default) + reply(nil, nil, nil, ContainerError.untrustedRecoveryKeys) + return + } + + guard let sponsor = self.model.peer(withID: sponsorPeerID) else { + os_log("preflightRecoveryKey Failed to find peer with ID", log: tplogDebug, type: .default) + reply(nil, nil, nil, ContainerError.sponsorNotRegistered(sponsorPeerID)) + return + } + + do { + let bestPolicy = try self.model.policy(forPeerIDs: sponsor.dynamicInfo?.includedPeerIDs ?? [sponsor.peerID], + candidatePeerID: egoPeerID, + candidateStableInfo: sponsor.stableInfo) + + let views = try bestPolicy.views(forModel: selfPermanentInfo.modelID) + reply(recoveryKeys.peerKeys.peerID, views, bestPolicy, nil) + } catch { + os_log("preflightRecoveryKey: error fetching policy: %{public}@", log: tplogDebug, type: .default, error as CVarArg) + reply(nil, nil, nil, error) + return + } + } + } + } + } +} diff --git a/keychain/TrustedPeersHelper/CuttlefishErrors.swift b/keychain/TrustedPeersHelper/CuttlefishErrors.swift index 04d9b902..c10415de 100644 --- a/keychain/TrustedPeersHelper/CuttlefishErrors.swift +++ b/keychain/TrustedPeersHelper/CuttlefishErrors.swift @@ -5,7 +5,7 @@ struct CuttlefishErrorMatcher { } // Use a 'pattern match operator' to make pretty case statements matching Cuttlefish errors -func ~=(pattern: CuttlefishErrorMatcher, value: Error?) -> Bool { +func ~= (pattern: CuttlefishErrorMatcher, value: Error?) -> Bool { guard let error = value else { return false } diff --git a/keychain/TrustedPeersHelper/Policy.swift b/keychain/TrustedPeersHelper/Policy.swift index 2cbece09..ce1c3eef 100644 --- a/keychain/TrustedPeersHelper/Policy.swift +++ b/keychain/TrustedPeersHelper/Policy.swift @@ -24,22 +24,24 @@ import Foundation struct RawPolicy { - let policyVersion: Int - let policyHash: String + let version: TPPolicyVersion let policyData: String let plaintextPolicy: TPPolicyDocument } -let prevailingPolicyVersion: UInt64 = 5 -let prevailingPolicyHash: String = "SHA256:O/ECQlWhvNlLmlDNh2+nal/yekUC87bXpV3k+6kznSo=" +let prevailingPolicyVersion = TPPolicyVersion(version: 6, hash: "SHA256:L2Px1aYyR1tgChe8dIyTBSmCHCWEFJirZ3ELMFXz2PY=") + +// Some peers don't know how to handle new policies when pairing. If we're pairing with one of those, +// we must prepare our identity using this policy. +let frozenPolicyVersion = TPPolicyVersion(version: 5, hash: "SHA256:O/ECQlWhvNlLmlDNh2+nal/yekUC87bXpV3k+6kznSo=") func builtInPolicyDocuments() -> [TPPolicyDocument] { + // swiftlint:disable force_try // These bytes are generated by tppolicy let rawPolicies = [ RawPolicy( - policyVersion: 1, - policyHash: "SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=", + version: TPPolicyVersion(version: 1, hash: "SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs="), policyData: "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2", plaintextPolicy: try! TPPolicyDocument(version: 1, modelToCategory: [ @@ -66,8 +68,7 @@ func builtInPolicyDocuments() -> [TPPolicyDocument] { ), RawPolicy( - policyVersion: 2, - policyHash: "SHA256:ZL1WBUCyO155rHBJQeghomCCKGmfjtS0jvsK+UEvx5o=", + version: TPPolicyVersion(version: 2, hash: "SHA256:ZL1WBUCyO155rHBJQeghomCCKGmfjtS0jvsK+UEvx5o="), policyData: "CAISDgoGaUN5Y2xlEgRmdWxsEg4KBmlQaG9uZRIEZnVsbBIMCgRpUGFkEgRmdWxsEgsKA01hYxIEZnVsbBIMCgRpTWFjEgRmdWxsEg0KB0FwcGxlVFYSAnR2Eg4KBVdhdGNoEgV3YXRjaBoRCglQQ1NFc2Nyb3cSBGZ1bGwaFwoEV2lGaRIEZnVsbBICdHYSBXdhdGNoGhkKEVNhZmFyaUNyZWRpdENhcmRzEgRmdWxsIgwKBGZ1bGwSBGZ1bGwiFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIg4KAnR2EgRmdWxsEgJ0dg==", plaintextPolicy: try! TPPolicyDocument(version: 2, modelToCategory: [ @@ -94,8 +95,7 @@ func builtInPolicyDocuments() -> [TPPolicyDocument] { hashAlgo: .SHA256) ), - RawPolicy(policyVersion: 3, - policyHash: "SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=", + RawPolicy(version: TPPolicyVersion(version: 3, hash: "SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg="), policyData: "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A=", plaintextPolicy: try! TPPolicyDocument(version: 3, modelToCategory: [ @@ -210,8 +210,7 @@ func builtInPolicyDocuments() -> [TPPolicyDocument] { ], hashAlgo: .SHA256) ), - RawPolicy(policyVersion: 4, - policyHash: "SHA256:Tjdu5QrWGvKWMx7k3VWFrEWSsBDPZAwCql9ybDkvFs8=", + RawPolicy(version: TPPolicyVersion(version: 4, hash: "SHA256:Tjdu5QrWGvKWMx7k3VWFrEWSsBDPZAwCql9ybDkvFs8="), policyData: "CAQSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxoTCgRIb21lEgRmdWxsEgV3YXRjaBobCgxBcHBsaWNhdGlvbnMSBGZ1bGwSBXdhdGNoGh4KBFdpRmkSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaFwoIQXBwbGVQYXkSBGZ1bGwSBXdhdGNoGhUKBkhlYWx0aBIEZnVsbBIFd2F0Y2gaFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaLQoTTGltaXRlZFBlZXJzQWxsb3dlZBIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxokChVQcm90ZWN0ZWRDbG91ZFN0b3JhZ2USBGZ1bGwSBXdhdGNoGhgKCVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaHAoNRGV2aWNlUGFpcmluZxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaFQoGRW5ncmFtEgRmdWxsEgV3YXRjaBoaCgtDcmVkaXRDYXJkcxIEZnVsbBIFd2F0Y2giGwoFYXVkaW8SBGZ1bGwSBXdhdGNoEgVhdWRpbyITCgRmdWxsEgRmdWxsEgV3YXRjaCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giFQoCdHYSBGZ1bGwSBXdhdGNoEgJ0djIiChYABCISAgR2d2h0CgpeQXBwbGVQYXkkEghBcHBsZVBheTImChgABCIUAgR2d2h0CgxeQXV0b1VubG9jayQSCkF1dG9VbmxvY2syHgoUAAQiEAIEdndodAoIXkVuZ3JhbSQSBkVuZ3JhbTIeChQABCIQAgR2d2h0CgheSGVhbHRoJBIGSGVhbHRoMhoKEgAEIg4CBHZ3aHQKBl5Ib21lJBIESG9tZTIgChUABCIRAgR2d2h0CgleTWFuYXRlZSQSB01hbmF0ZWUyOAohAAQiHQIEdndodAoVXkxpbWl0ZWRQZWVyc0FsbG93ZWQkEhNMaW1pdGVkUGVlcnNBbGxvd2VkMl0KUAACEh4ABCIaAgR2d2h0ChJeQ29udGludWl0eVVubG9jayQSFQAEIhECBHZ3aHQKCV5Ib21lS2l0JBIVAAQiEQIEdndodAoJXkFwcGxlVFYkEglOb3RTeW5jZWQyKwobAAQiFwIEYWdycAoPXlswLTlBLVpdezEwfVwuEgxBcHBsaWNhdGlvbnMyxQEKsAEAAhI0AAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKGwAEIhcCBGFncnAKD15jb20uYXBwbGUuc2JkJBI9AAEKEwAEIg8CBWNsYXNzCgZea2V5cyQKJAAEIiACBGFncnAKGF5jb20uYXBwbGUuc2VjdXJpdHkuc29zJBIZAAQiFQIEdndodAoNXkJhY2t1cEJhZ1YwJBIcAAQiGAIEdndodAoQXmlDbG91ZElkZW50aXR5JBIQU2VjdXJlT2JqZWN0U3luYzJjClsAAhISAAQiDgIEdndodAoGXldpRmkkEkMAAQoTAAQiDwIFY2xhc3MKBl5nZW5wJAoTAAQiDwIEYWdycAoHXmFwcGxlJAoVAAQiEQIEc3ZjZQoJXkFpclBvcnQkEgRXaUZpMucCCs0CAAISGgAEIhYCBHZ3aHQKDl5QQ1MtQ2xvdWRLaXQkEhgABCIUAgR2d2h0CgxeUENTLUVzY3JvdyQSFQAEIhECBHZ3aHQKCV5QQ1MtRkRFJBIaAAQiFgIEdndodAoOXlBDUy1GZWxkc3BhciQSGgAEIhYCBHZ3aHQKDl5QQ1MtTWFpbERyb3AkEhsABCIXAgR2d2h0Cg9eUENTLU1hc3RlcktleSQSFwAEIhMCBHZ3aHQKC15QQ1MtTm90ZXMkEhgABCIUAgR2d2h0CgxeUENTLVBob3RvcyQSGQAEIhUCBHZ3aHQKDV5QQ1MtU2hhcmluZyQSHgAEIhoCBHZ3aHQKEl5QQ1MtaUNsb3VkQmFja3VwJBIdAAQiGQIEdndodAoRXlBDUy1pQ2xvdWREcml2ZSQSGgAEIhYCBHZ3aHQKDl5QQ1MtaU1lc3NhZ2UkEhVQcm90ZWN0ZWRDbG91ZFN0b3JhZ2UyOgorAAQiJwIEYWdycAofXmNvbS5hcHBsZS5zYWZhcmkuY3JlZGl0LWNhcmRzJBILQ3JlZGl0Q2FyZHMyLgohAAQiHQIEYWdycAoVXmNvbS5hcHBsZS5jZm5ldHdvcmskEglQYXNzd29yZHMybQpcAAISHgAEIhoCBHZ3aHQKEl5BY2Nlc3NvcnlQYWlyaW5nJBIaAAQiFgIEdndodAoOXk5hbm9SZWdpc3RyeSQSHAAEIhgCBHZ3aHQKEF5XYXRjaE1pZ3JhdGlvbiQSDURldmljZVBhaXJpbmc=", plaintextPolicy: try! TPPolicyDocument(version: 4, modelToCategory: [ @@ -322,8 +321,7 @@ func builtInPolicyDocuments() -> [TPPolicyDocument] { hashAlgo: .SHA256) ), - RawPolicy(policyVersion: 5, - policyHash: "SHA256:O/ECQlWhvNlLmlDNh2+nal/yekUC87bXpV3k+6kznSo=", + RawPolicy(version: TPPolicyVersion(version: 5, hash: "SHA256:O/ECQlWhvNlLmlDNh2+nal/yekUC87bXpV3k+6kznSo="), policyData: "CAUSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSDAoEaVBvZBIEZnVsbBILCgNNYWMSBGZ1bGwSDAoEaU1hYxIEZnVsbBINCgdBcHBsZVRWEgJ0dhIOCgVXYXRjaBIFd2F0Y2gSFwoOQXVkaW9BY2Nlc3NvcnkSBWF1ZGlvGhsKDEFwcGxpY2F0aW9ucxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaHAoNRGV2aWNlUGFpcmluZxIEZnVsbBIFd2F0Y2gaGgoLQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhUKBkhlYWx0aBIEZnVsbBIFd2F0Y2gaLQoTTGltaXRlZFBlZXJzQWxsb3dlZBIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxokChVQcm90ZWN0ZWRDbG91ZFN0b3JhZ2USBGZ1bGwSBXdhdGNoGhcKCEFwcGxlUGF5EgRmdWxsEgV3YXRjaBoZCgpBdXRvVW5sb2NrEgRmdWxsEgV3YXRjaBoWCgdNYW5hdGVlEgRmdWxsEgV3YXRjaBoYCglQYXNzd29yZHMSBGZ1bGwSBXdhdGNoGhUKBkVuZ3JhbRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoTCgRIb21lEgRmdWxsEgV3YXRjaCIbCgVhdWRpbxIEZnVsbBIFd2F0Y2gSBWF1ZGlvIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYiFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoMiIKFgAEIhICBHZ3aHQKCl5BcHBsZVBheSQSCEFwcGxlUGF5MiYKGAAEIhQCBHZ3aHQKDF5BdXRvVW5sb2NrJBIKQXV0b1VubG9jazIeChQABCIQAgR2d2h0CgheRW5ncmFtJBIGRW5ncmFtMh4KFAAEIhACBHZ3aHQKCF5IZWFsdGgkEgZIZWFsdGgyGgoSAAQiDgIEdndodAoGXkhvbWUkEgRIb21lMiAKFQAEIhECBHZ3aHQKCV5NYW5hdGVlJBIHTWFuYXRlZTI4CiEABCIdAgR2d2h0ChVeTGltaXRlZFBlZXJzQWxsb3dlZCQSE0xpbWl0ZWRQZWVyc0FsbG93ZWQyXQpQAAISHgAEIhoCBHZ3aHQKEl5Db250aW51aXR5VW5sb2NrJBIVAAQiEQIEdndodAoJXkhvbWVLaXQkEhUABCIRAgR2d2h0CgleQXBwbGVUViQSCU5vdFN5bmNlZDIrChsABCIXAgRhZ3JwCg9eWzAtOUEtWl17MTB9XC4SDEFwcGxpY2F0aW9uczLFAQqwAQACEjQAAQoTAAQiDwIFY2xhc3MKBl5nZW5wJAobAAQiFwIEYWdycAoPXmNvbS5hcHBsZS5zYmQkEj0AAQoTAAQiDwIFY2xhc3MKBl5rZXlzJAokAAQiIAIEYWdycAoYXmNvbS5hcHBsZS5zZWN1cml0eS5zb3MkEhkABCIVAgR2d2h0Cg1eQmFja3VwQmFnVjAkEhwABCIYAgR2d2h0ChBeaUNsb3VkSWRlbnRpdHkkEhBTZWN1cmVPYmplY3RTeW5jMmMKWwACEhIABCIOAgR2d2h0CgZeV2lGaSQSQwABChMABCIPAgVjbGFzcwoGXmdlbnAkChMABCIPAgRhZ3JwCgdeYXBwbGUkChUABCIRAgRzdmNlCgleQWlyUG9ydCQSBFdpRmkynQMKgwMAAhIYAAQiFAIEdndodAoMXlBDUy1CYWNrdXAkEhoABCIWAgR2d2h0Cg5eUENTLUNsb3VkS2l0JBIYAAQiFAIEdndodAoMXlBDUy1Fc2Nyb3ckEhUABCIRAgR2d2h0CgleUENTLUZERSQSGgAEIhYCBHZ3aHQKDl5QQ1MtRmVsZHNwYXIkEhoABCIWAgR2d2h0Cg5eUENTLU1haWxEcm9wJBIaAAQiFgIEdndodAoOXlBDUy1NYWlsZHJvcCQSGwAEIhcCBHZ3aHQKD15QQ1MtTWFzdGVyS2V5JBIXAAQiEwIEdndodAoLXlBDUy1Ob3RlcyQSGAAEIhQCBHZ3aHQKDF5QQ1MtUGhvdG9zJBIZAAQiFQIEdndodAoNXlBDUy1TaGFyaW5nJBIeAAQiGgIEdndodAoSXlBDUy1pQ2xvdWRCYWNrdXAkEh0ABCIZAgR2d2h0ChFeUENTLWlDbG91ZERyaXZlJBIaAAQiFgIEdndodAoOXlBDUy1pTWVzc2FnZSQSFVByb3RlY3RlZENsb3VkU3RvcmFnZTI6CisABCInAgRhZ3JwCh9eY29tLmFwcGxlLnNhZmFyaS5jcmVkaXQtY2FyZHMkEgtDcmVkaXRDYXJkczIuCiEABCIdAgRhZ3JwChVeY29tLmFwcGxlLmNmbmV0d29yayQSCVBhc3N3b3JkczJtClwAAhIeAAQiGgIEdndodAoSXkFjY2Vzc29yeVBhaXJpbmckEhoABCIWAgR2d2h0Cg5eTmFub1JlZ2lzdHJ5JBIcAAQiGAIEdndodAoQXldhdGNoTWlncmF0aW9uJBINRGV2aWNlUGFpcmluZzIOCgIABhIIQmFja3N0b3A=", plaintextPolicy: try! TPPolicyDocument(version: 5, modelToCategory: [ @@ -439,16 +437,142 @@ func builtInPolicyDocuments() -> [TPPolicyDocument] { ], hashAlgo: .SHA256) ), + + RawPolicy(version: TPPolicyVersion(version: 6, hash: "SHA256:L2Px1aYyR1tgChe8dIyTBSmCHCWEFJirZ3ELMFXz2PY="), + policyData: "CAYSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSDAoEaVBvZBIEZnVsbBILCgNNYWMSBGZ1bGwSDAoEaU1hYxIEZnVsbBINCgdBcHBsZVRWEgJ0dhIOCgVXYXRjaBIFd2F0Y2gSFwoOQXVkaW9BY2Nlc3NvcnkSBWF1ZGlvGhoKC0NyZWRpdENhcmRzEgRmdWxsEgV3YXRjaBofChBTZWN1cmVPYmplY3RTeW5jEgRmdWxsEgV3YXRjaBoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoGhkKCkF1dG9VbmxvY2sSBGZ1bGwSBXdhdGNoGh4KBFdpRmkSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aHgoESG9tZRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxokChVQcm90ZWN0ZWRDbG91ZFN0b3JhZ2USBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aGAoJUGFzc3dvcmRzEgRmdWxsEgV3YXRjaBobCgxBcHBsaWNhdGlvbnMSBGZ1bGwSBXdhdGNoGhwKDURldmljZVBhaXJpbmcSBGZ1bGwSBXdhdGNoGhcKCEFwcGxlUGF5EgRmdWxsEgV3YXRjaBoWCgdNYW5hdGVlEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFQoCdHYSBGZ1bGwSBXdhdGNoEgJ0diIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giEwoEZnVsbBIEZnVsbBIFd2F0Y2gyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsoBCrUBAAISNgABChMABCIPAgVjbGFzcwoGXmdlbnAkCh0ABCIZAgRhZ3JwChFeY29tXC5hcHBsZVwuc2JkJBJAAAEKEwAEIg8CBWNsYXNzCgZea2V5cyQKJwAEIiMCBGFncnAKG15jb21cLmFwcGxlXC5zZWN1cml0eVwuc29zJBIZAAQiFQIEdndodAoNXkJhY2t1cEJhZ1YwJBIcAAQiGAIEdndodAoQXmlDbG91ZElkZW50aXR5JBIQU2VjdXJlT2JqZWN0U3luYzJjClsAAhISAAQiDgIEdndodAoGXldpRmkkEkMAAQoTAAQiDwIFY2xhc3MKBl5nZW5wJAoTAAQiDwIEYWdycAoHXmFwcGxlJAoVAAQiEQIEc3ZjZQoJXkFpclBvcnQkEgRXaUZpMp0DCoMDAAISGAAEIhQCBHZ3aHQKDF5QQ1MtQmFja3VwJBIaAAQiFgIEdndodAoOXlBDUy1DbG91ZEtpdCQSGAAEIhQCBHZ3aHQKDF5QQ1MtRXNjcm93JBIVAAQiEQIEdndodAoJXlBDUy1GREUkEhoABCIWAgR2d2h0Cg5eUENTLUZlbGRzcGFyJBIaAAQiFgIEdndodAoOXlBDUy1NYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1MtTWFpbGRyb3AkEhsABCIXAgR2d2h0Cg9eUENTLU1hc3RlcktleSQSFwAEIhMCBHZ3aHQKC15QQ1MtTm90ZXMkEhgABCIUAgR2d2h0CgxeUENTLVBob3RvcyQSGQAEIhUCBHZ3aHQKDV5QQ1MtU2hhcmluZyQSHgAEIhoCBHZ3aHQKEl5QQ1MtaUNsb3VkQmFja3VwJBIdAAQiGQIEdndodAoRXlBDUy1pQ2xvdWREcml2ZSQSGgAEIhYCBHZ3aHQKDl5QQ1MtaU1lc3NhZ2UkEhVQcm90ZWN0ZWRDbG91ZFN0b3JhZ2UyPQouAAQiKgIEYWdycAoiXmNvbVwuYXBwbGVcLnNhZmFyaVwuY3JlZGl0LWNhcmRzJBILQ3JlZGl0Q2FyZHMyMAojAAQiHwIEYWdycAoXXmNvbVwuYXBwbGVcLmNmbmV0d29yayQSCVBhc3N3b3JkczJtClwAAhIeAAQiGgIEdndodAoSXkFjY2Vzc29yeVBhaXJpbmckEhoABCIWAgR2d2h0Cg5eTmFub1JlZ2lzdHJ5JBIcAAQiGAIEdndodAoQXldhdGNoTWlncmF0aW9uJBINRGV2aWNlUGFpcmluZzIOCgIABhIIQmFja3N0b3A=", + plaintextPolicy: try! TPPolicyDocument(version: 6, + modelToCategory: [ + ["prefix": "iPhone", "category": "full"], + ["prefix": "iPad", "category": "full"], + ["prefix": "iPod", "category": "full"], + ["prefix": "Mac", "category": "full"], + ["prefix": "iMac", "category": "full"], + ["prefix": "AppleTV", "category": "tv"], + ["prefix": "Watch", "category": "watch"], + ["prefix": "AudioAccessory", "category": "audio"], + ], + categoriesByView: [ + "AutoUnlock": ["full", "watch"], + "ApplePay": ["full", "watch"], + "Engram": ["full", "watch"], + "Health": ["full", "watch"], + "Home": ["full", "watch", "tv", "audio"], + "LimitedPeersAllowed": ["full", "watch", "tv", "audio"], + "Manatee": ["full", "watch"], + "Applications": ["full", "watch"], + "SecureObjectSync": ["full", "watch"], + "WiFi": ["full", "watch", "tv", "audio"], + "ProtectedCloudStorage": ["full", "watch"], + "CreditCards": ["full", "watch"], + "Passwords": ["full", "watch"], + "DevicePairing": ["full", "watch"], + ], + introducersByCategory: [ + "full": ["full", "watch"], + "watch": ["full", "watch"], + "tv": ["full", "watch", "tv"], + "audio": ["full", "watch", "audio"], + ], + redactions: [:], + keyViewMapping: [ + TPPBPolicyKeyViewMapping(view: "ApplePay", matchingRule: TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^ApplePay$")), + TPPBPolicyKeyViewMapping(view: "AutoUnlock", matchingRule: TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^AutoUnlock$")), + TPPBPolicyKeyViewMapping(view: "Engram", matchingRule: TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^Engram$")), + TPPBPolicyKeyViewMapping(view: "Health", matchingRule: TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^Health$")), + TPPBPolicyKeyViewMapping(view: "Home", matchingRule: TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^Home$")), + TPPBPolicyKeyViewMapping(view: "Manatee", matchingRule: TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^Manatee$")), + TPPBPolicyKeyViewMapping(view: "LimitedPeersAllowed", matchingRule: TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^LimitedPeersAllowed$")), + + // These items will not be synced by Octagon + TPPBPolicyKeyViewMapping(view: "NotSynced", matchingRule: + TPDictionaryMatchingRule.orMatch([ + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^ContinuityUnlock$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^HomeKit$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^AppleTV$"), + ])), + + TPPBPolicyKeyViewMapping(view: "Applications", matchingRule: + TPDictionaryMatchingRule.fieldMatch("agrp", fieldRegex: "^[0-9A-Z]{10}\\.")), + + TPPBPolicyKeyViewMapping(view: "SecureObjectSync", matchingRule: + TPDictionaryMatchingRule.orMatch([ + TPDictionaryMatchingRule.andMatch([ + TPDictionaryMatchingRule.fieldMatch("class", fieldRegex: "^genp$"), + TPDictionaryMatchingRule.fieldMatch("agrp", fieldRegex: "^com\\.apple\\.sbd$"), + ]), + TPDictionaryMatchingRule.andMatch([ + TPDictionaryMatchingRule.fieldMatch("class", fieldRegex: "^keys$"), + TPDictionaryMatchingRule.fieldMatch("agrp", fieldRegex: "^com\\.apple\\.security\\.sos$"), + ]), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^BackupBagV0$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^iCloudIdentity$"), + ])), + + TPPBPolicyKeyViewMapping(view: "WiFi", matchingRule: + TPDictionaryMatchingRule.orMatch([ + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^WiFi$"), + TPDictionaryMatchingRule.andMatch([ + TPDictionaryMatchingRule.fieldMatch("class", fieldRegex: "^genp$"), + TPDictionaryMatchingRule.fieldMatch("agrp", fieldRegex: "^apple$"), + TPDictionaryMatchingRule.fieldMatch("svce", fieldRegex: "^AirPort$"), + ]), + ])), + + TPPBPolicyKeyViewMapping(view: "ProtectedCloudStorage", matchingRule: + TPDictionaryMatchingRule.orMatch([ + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-Backup$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-CloudKit$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-Escrow$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-FDE$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-Feldspar$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-MailDrop$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-Maildrop$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-MasterKey$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-Notes$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-Photos$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-Sharing$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-iCloudBackup$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-iCloudDrive$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^PCS-iMessage$"), + ])), + + TPPBPolicyKeyViewMapping(view: "CreditCards", + matchingRule: TPDictionaryMatchingRule.fieldMatch("agrp", fieldRegex: "^com\\.apple\\.safari\\.credit-cards$")), + + TPPBPolicyKeyViewMapping(view: "Passwords", + matchingRule: TPDictionaryMatchingRule.fieldMatch("agrp", fieldRegex: "^com\\.apple\\.cfnetwork$")), + + TPPBPolicyKeyViewMapping(view: "DevicePairing", matchingRule: + TPDictionaryMatchingRule.orMatch([ + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^AccessoryPairing$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^NanoRegistry$"), + TPDictionaryMatchingRule.fieldMatch("vwht", fieldRegex: "^WatchMigration$"), + ])), + + TPPBPolicyKeyViewMapping(view: "Backstop", matchingRule: + TPDictionaryMatchingRule.trueMatch()), + ], + hashAlgo: .SHA256) + ), ] + // swiftlint:enable force_try - assert(rawPolicies.filter { prevailingPolicyVersion == $0.policyVersion }.count == 1) + assert(rawPolicies.filter { prevailingPolicyVersion.versionNumber == $0.version.versionNumber }.count == 1) return rawPolicies.map { raw in let data = Data(base64Encoded: raw.policyData)! - let doc = TPPolicyDocument.policyDoc(withHash: raw.policyHash, data: data)! - assert(doc.policyVersion == raw.policyVersion) - if raw.policyVersion == prevailingPolicyVersion { - assert(prevailingPolicyHash == raw.policyHash) + let doc = TPPolicyDocument.policyDoc(withHash: raw.version.policyHash, data: data)! + + if(!doc.isEqual(to: raw.plaintextPolicy)) { + let bodyData = raw.plaintextPolicy.protobuf + let bodyBase64 = bodyData.base64EncodedString() + let hash = TPHashBuilder.hash(with: .SHA256, of: bodyData) + os_log("raw policy doesn't match encoded bytes, new hash would be: %{public}@ new data: %{public}@", log: tplogDebug, hash, bodyBase64) + } + + assert(doc.version.versionNumber == raw.version.versionNumber) + if raw.version.versionNumber == prevailingPolicyVersion.versionNumber { + assert(prevailingPolicyVersion.policyHash == raw.version.policyHash) } assert(doc.isEqual(to: raw.plaintextPolicy)) return doc diff --git a/keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift b/keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift index 1ab70461..5499b200 100644 --- a/keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift +++ b/keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift @@ -27,7 +27,7 @@ import SecurityFoundation let OT_RECOVERY_SIGNING_HKDF_SIZE = 56 let OT_RECOVERY_ENCRYPTION_HKDF_SIZE = 56 -enum recoveryKeyType: Int { +enum RecoveryKeyType: Int { case kOTRecoveryKeySigning = 1 case kOTRecoveryKeyEncryption = 2 } @@ -43,10 +43,10 @@ class RecoveryKeySet: NSObject { self.secret = secret self.recoverySalt = recoverySalt - let encryptionKeyData = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret, recoverySalt: recoverySalt) + let encryptionKeyData = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret, recoverySalt: recoverySalt) self.encryptionKey = _SFECKeyPair.init(secKey: try RecoveryKeySet.createSecKey(keyData: encryptionKeyData)) - let signingKeyData = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret, recoverySalt: recoverySalt) + let signingKeyData = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret, recoverySalt: recoverySalt) self.signingKey = _SFECKeyPair.init(secKey: try RecoveryKeySet.createSecKey(keyData: signingKeyData)) let RecoverySigningPubKeyHash = try RecoveryKeySet.hashRecoveryedSigningPublicKey(keyData: self.signingKey.publicKey().spki()) @@ -58,27 +58,25 @@ class RecoveryKeySet: NSObject { return SecRKCreateRecoveryKeyString(nil) as String } - class func generateRecoveryKey(keyType: recoveryKeyType, masterSecret: Data, recoverySalt: String) throws -> (Data) { + class func generateRecoveryKey(keyType: RecoveryKeyType, masterSecret: Data, recoverySalt: String) throws -> (Data) { var keyLength: Int var info: Data var derivedKey: Data var finalKey = Data() switch keyType { - case recoveryKeyType.kOTRecoveryKeyEncryption: + case RecoveryKeyType.kOTRecoveryKeyEncryption: keyLength = OT_RECOVERY_ENCRYPTION_HKDF_SIZE let infoString = Array("Recovery Encryption Private Key".utf8) info = Data(bytes: infoString, count: infoString.count) - break - case recoveryKeyType.kOTRecoveryKeySigning: + case RecoveryKeyType.kOTRecoveryKeySigning: keyLength = OT_RECOVERY_SIGNING_HKDF_SIZE let infoString = Array("Recovery Signing Private Key".utf8) info = Data(bytes: infoString, count: infoString.count) - break } guard let cp = ccec_cp_384() else { @@ -108,7 +106,7 @@ class RecoveryKeySet: NSObject { throw RecoveryKeySetError.corecryptoKeyGeneration(corecryptoError: status) } - if(keyType == recoveryKeyType.kOTRecoveryKeyEncryption || keyType == recoveryKeyType.kOTRecoveryKeySigning) { + if keyType == RecoveryKeyType.kOTRecoveryKeyEncryption || keyType == RecoveryKeyType.kOTRecoveryKeySigning { status = ccec_generate_key_deterministic(cp, derivedKeyBytes.count, derivedKeyBytes.bindMemory(to: UInt8.self).baseAddress!, ccDRBGGetRngState(), @@ -143,7 +141,7 @@ class RecoveryKeySet: NSObject { return key } - class func setKeyMaterialInKeychain(query: Dictionary) throws -> (Bool) { + class func setKeyMaterialInKeychain(query: [CFString: Any]) throws -> (Bool) { var result = false var results: CFTypeRef? @@ -152,7 +150,7 @@ class RecoveryKeySet: NSObject { if status == errSecSuccess { result = true } else if status == errSecDuplicateItem { - var updateQuery: Dictionary = query + var updateQuery: [CFString: Any] = query updateQuery[kSecClass] = nil status = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary) @@ -213,8 +211,8 @@ class RecoveryKeySet: NSObject { return try RecoveryKeySet.setKeyMaterialInKeychain(query: query) } - class func retrieveRecoveryKeysFromKeychain(label: String) throws -> [Dictionary ]? { - var keySet: [Dictionary]? + class func retrieveRecoveryKeysFromKeychain(label: String) throws -> [ [CFString: Any]]? { + var keySet: [[CFString: Any]]? let query: [CFString: Any] = [ kSecClass: kSecClassKey, @@ -234,10 +232,10 @@ class RecoveryKeySet: NSObject { } if result != nil { - if let dictionaryArray = result as? [Dictionary] { + if let dictionaryArray = result as? [[CFString: Any]] { keySet = dictionaryArray } else { - if let dictionary = result as? Dictionary { + if let dictionary = result as? [CFString: Any] { keySet = [dictionary] } else { keySet = nil diff --git a/keychain/TrustedPeersHelper/SetValueTransformer.swift b/keychain/TrustedPeersHelper/SetValueTransformer.swift index 2529be27..e670dbde 100644 --- a/keychain/TrustedPeersHelper/SetValueTransformer.swift +++ b/keychain/TrustedPeersHelper/SetValueTransformer.swift @@ -13,7 +13,9 @@ class SetValueTransformer: ValueTransformer { override func transformedValue(_ value: Any?) -> Any? { do { - guard let value = value else { return nil } + guard let value = value else { + return nil + } return try NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true) } catch { os_log("Failed to serialize a Set: %@", log: tplogDebug, type: .default, error as CVarArg) @@ -23,8 +25,12 @@ class SetValueTransformer: ValueTransformer { override func reverseTransformedValue(_ value: Any?) -> Any? { do { - guard let dataOp = value as? Data? else { return nil } - guard let data = dataOp else { return nil } + guard let dataOp = value as? Data? else { + return nil + } + guard let data = dataOp else { + return nil + } let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data) return unarchiver.decodeObject(of: [NSSet.self], forKey: NSKeyedArchiveRootObjectKey) diff --git a/keychain/TrustedPeersHelper/TrustedPeersHelper.xcdatamodeld/TrustedPeersHelper_2.xcdatamodel/contents b/keychain/TrustedPeersHelper/TrustedPeersHelper.xcdatamodeld/TrustedPeersHelper_2.xcdatamodel/contents index 34e02638..83d8766c 100644 --- a/keychain/TrustedPeersHelper/TrustedPeersHelper.xcdatamodeld/TrustedPeersHelper_2.xcdatamodel/contents +++ b/keychain/TrustedPeersHelper/TrustedPeersHelper.xcdatamodeld/TrustedPeersHelper_2.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -18,8 +18,11 @@ + + + @@ -65,11 +68,11 @@ - + - \ No newline at end of file + diff --git a/keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h b/keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h index 197d419b..0f37bed2 100644 --- a/keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h +++ b/keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h @@ -129,6 +129,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)setAllowedMachineIDsWithContainer:(NSString *)container context:(NSString *)context allowedMachineIDs:(NSSet *)allowedMachineIDs + honorIDMSListChanges:(BOOL)honorIDMSListChanges reply:(void (^)(BOOL listDifferences, NSError * _Nullable error))reply; - (void)addAllowedMachineIDsWithContainer:(NSString *)container @@ -160,7 +161,7 @@ NS_ASSUME_NONNULL_BEGIN deviceName:(nullable NSString*)deviceName serialNumber:(NSString *)serialNumber osVersion:(NSString *)osVersion - policyVersion:(nullable NSNumber *)policyVersion + policyVersion:(nullable TPPolicyVersion *)policyVersion policySecrets:(nullable NSDictionary *)policySecrets signingPrivKeyPersistentRef:(nullable NSData *)spkPr encPrivKeyPersistentRef:(nullable NSData*)epkPr @@ -169,6 +170,8 @@ NS_ASSUME_NONNULL_BEGIN NSData * _Nullable permanentInfoSig, NSData * _Nullable stableInfo, NSData * _Nullable stableInfoSig, + NSSet* _Nullable syncingViewList, + TPPolicy* _Nullable syncingPolicy, NSError * _Nullable error))reply; // If there already are existing CKKSViews, please pass in their key sets anyway. @@ -196,12 +199,15 @@ NS_ASSUME_NONNULL_BEGIN NSData * _Nullable voucherSig, NSError * _Nullable error))reply; -// Preflighting a vouch will return the peer ID associated with the bottle you will be recovering. +// Preflighting a vouch will return the peer ID associated with the bottle you will be recovering, as well as +// the syncing policy used by that peer, and, // You can then use that peer ID to filter the tlkshares provided to vouchWithBottle. - (void)preflightVouchWithBottleWithContainer:(NSString *)container context:(NSString *)context bottleID:(NSString*)bottleID reply:(void (^)(NSString* _Nullable peerID, + NSSet* _Nullable peerSyncingViewList, + TPPolicy * _Nullable peerSyncingPolicy, NSError * _Nullable error))reply; // Returns a voucher for our own identity, created by the identity inside this bottle @@ -213,8 +219,21 @@ NS_ASSUME_NONNULL_BEGIN tlkShares:(NSArray *)tlkShares reply:(void (^)(NSData * _Nullable voucher, NSData * _Nullable voucherSig, + int64_t uniqueTLKsRecovered, + int64_t totalTLKSharesRecovered, NSError * _Nullable error))reply; +// Preflighting a vouch will return the RK ID, view list and policy associated with the RK you will be recovering. +// You can then use that peer ID to filter the tlkshares provided to vouchWithRecoveryKey. +- (void)preflightVouchWithRecoveryKeyWithContainer:(NSString*)container + context:(NSString*)context + recoveryKey:(NSString*)recoveryKey + salt:(NSString*)salt + reply:(void (^)(NSString* _Nullable recoveryKeyID, + NSSet* _Nullable peerSyncingViewList, + TPPolicy * _Nullable peerSyncingPolicy, + NSError * _Nullable error))reply; + // Returns a voucher for our own identity, using recovery key - (void)vouchWithRecoveryKeyWithContainer:(NSString *)container context:(NSString *)context @@ -237,6 +256,8 @@ NS_ASSUME_NONNULL_BEGIN preapprovedKeys:(NSArray *)preapprovedKeys reply:(void (^)(NSString * _Nullable peerID, NSArray* _Nullable keyHierarchyRecords, + NSSet* _Nullable syncingViewList, + TPPolicy* _Nullable syncingPolicy, NSError * _Nullable error))reply; // Preflighting a preapproved join suggests whether or not you expect to succeed in an immediate preapprovedJoin() call @@ -258,6 +279,8 @@ NS_ASSUME_NONNULL_BEGIN preapprovedKeys:(NSArray *)preapprovedKeys reply:(void (^)(NSString * _Nullable peerID, NSArray* _Nullable keyHierarchyRecords, + NSSet* _Nullable syncingViewList, + TPPolicy* _Nullable syncingPolicy, NSError * _Nullable error))reply; // TODO: if the new policy causes someone to lose access to a view, how should this API work? @@ -273,7 +296,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)setPreapprovedKeysWithContainer:(NSString *)container context:(NSString *)context preapprovedKeys:(NSArray *)preapprovedKeys - reply:(void (^)(NSError * _Nullable error))reply; + reply:(void (^)(TrustedPeersHelperPeerState* _Nullable peerState, NSError * _Nullable error))reply; /* Rather thin pass-through for uploading new TLKs (for zones which may have disappeared) */ - (void)updateTLKsWithContainer:(NSString *)container @@ -293,19 +316,18 @@ NS_ASSUME_NONNULL_BEGIN NSData* _Nullable signingPublicKey, NSError* _Nullable error))reply; -// The argument contains N [version:hash] keys, -// the reply block contains 0<=N [version:[hash, data]] entries. - (void)fetchPolicyDocumentsWithContainer:(NSString*)container context:(NSString*)context - keys:(NSDictionary*)keys - reply:(void (^)(NSDictionary*>* _Nullable entries, + versions:(NSSet*)versions + reply:(void (^)(NSDictionary* _Nullable entries, NSError * _Nullable error))reply; -// Fetch the policy for current peer. -- (void)fetchPolicyWithContainer:(NSString*)container - context:(NSString*)context - reply:(void (^)(TPPolicy * _Nullable policy, - NSError * _Nullable error))reply; +// Fetch the policy and view list for current peer. +- (void)fetchCurrentPolicyWithContainer:(NSString*)container + context:(NSString*)context + reply:(void (^)(NSSet* _Nullable syncingViewList, + TPPolicy * _Nullable syncingPolicy, + NSError * _Nullable error))reply; - (void)validatePeersWithContainer:(NSString *)container context:(NSString *)context @@ -336,15 +358,10 @@ NS_ASSUME_NONNULL_BEGIN context:(NSString *)context reply:(void (^)(NSError* _Nullable error))reply; -- (void)getViewsWithContainer:(NSString *)container - context:(NSString *)context - inViews:(NSArray*)inViews - reply:(void (^)(NSArray* _Nullable, NSError* _Nullable))reply; - - (void)requestHealthCheckWithContainer:(NSString *)container context:(NSString *)context requiresEscrowCheck:(BOOL)requiresEscrowCheck - reply:(void (^)(BOOL postRepairCFU, BOOL postEscrowCFU, BOOL resetOctagon, NSError* _Nullable))reply; + reply:(void (^)(BOOL postRepairCFU, BOOL postEscrowCFU, BOOL resetOctagon, BOOL leaveTrust, NSError* _Nullable))reply; - (void)getSupportAppInfoWithContainer:(NSString *)container context:(NSString *)context diff --git a/keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.m b/keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.m index 66031113..fb2f1557 100644 --- a/keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.m +++ b/keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.m @@ -95,9 +95,9 @@ NSXPCInterface* TrustedPeersHelperSetupProtocol(NSXPCInterface* interface) tlkShares: reply:) argumentIndex:4 ofReply:NO]; - [interface setClasses:[NSSet setWithObject:[TPPolicy class]] forSelector:@selector(fetchPolicyWithContainer: + [interface setClasses:[NSSet setWithObject:[TPPolicy class]] forSelector:@selector(fetchCurrentPolicyWithContainer: context: - reply:) argumentIndex:0 ofReply:YES]; + reply:) argumentIndex:1 ofReply:YES]; [interface setClasses:trustedPeersHelperPeerState forSelector:@selector(updateWithContainer: context: diff --git a/keychain/TrustedPeersHelper/main.swift b/keychain/TrustedPeersHelper/main.swift index 7111e5b9..9bfa5fb6 100644 --- a/keychain/TrustedPeersHelper/main.swift +++ b/keychain/TrustedPeersHelper/main.swift @@ -30,23 +30,23 @@ class ServiceDelegate: NSObject, NSXPCListenerDelegate { func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { let tphEntitlement = "com.apple.private.trustedpeershelper.client" - os_log("Received a new client: %@", log: tplogDebug, type: .default, newConnection) + os_log("Received a new client: %{public}@", log: tplogDebug, type: .default, newConnection) switch newConnection.value(forEntitlement: tphEntitlement) { case 1 as Int: - os_log("client has entitlement '%@'", log: tplogDebug, type: .default, tphEntitlement) + os_log("client has entitlement '%{public}@'", log: tplogDebug, type: .default, tphEntitlement) case true as Bool: - os_log("client has entitlement '%@'", log: tplogDebug, type: .default, tphEntitlement) + os_log("client has entitlement '%{public}@'", log: tplogDebug, type: .default, tphEntitlement) case let someInt as Int: - os_log("client(%@) has wrong integer value for '%@' (%d), rejecting", log: tplogDebug, type: .default, newConnection, tphEntitlement, someInt) + os_log("client(%{public}@) has wrong integer value for '%{public}@' (%d), rejecting", log: tplogDebug, type: .default, newConnection, tphEntitlement, someInt) return false case let someBool as Bool: - os_log("client(%@) has wrong boolean value for '%@' (%d), rejecting", log: tplogDebug, type: .default, newConnection, tphEntitlement, someBool) + os_log("client(%{public}@) has wrong boolean value for '%{public}@' (%d), rejecting", log: tplogDebug, type: .default, newConnection, tphEntitlement, someBool) return false default: - os_log("client(%@) is missing entitlement '%@', rejecting", log: tplogDebug, type: .default, newConnection, tphEntitlement) + os_log("client(%{public}@) is missing entitlement '%{public}@', rejecting", log: tplogDebug, type: .default, newConnection, tphEntitlement) return false } diff --git a/keychain/TrustedPeersHelperUnitTests/.swiftlint.yml b/keychain/TrustedPeersHelperUnitTests/.swiftlint.yml new file mode 100644 index 00000000..9ddd0d11 --- /dev/null +++ b/keychain/TrustedPeersHelperUnitTests/.swiftlint.yml @@ -0,0 +1,3 @@ +disabled_rules: + - force_cast + - force_try diff --git a/keychain/TrustedPeersHelperUnitTests/ContainerSync.swift b/keychain/TrustedPeersHelperUnitTests/ContainerSync.swift index f3b3eb19..655bfdf8 100644 --- a/keychain/TrustedPeersHelperUnitTests/ContainerSync.swift +++ b/keychain/TrustedPeersHelperUnitTests/ContainerSync.swift @@ -52,13 +52,15 @@ extension Container { deviceName: String = "test device name", serialNumber: String = "456", osVersion: String = "123", - policyVersion: UInt64? = nil, + policyVersion: TPPolicyVersion? = nil, policySecrets: [String: Data]? = nil, signingPrivateKeyPersistentRef: Data? = nil, encryptionPrivateKeyPersistentRef: Data? = nil - ) -> (String?, Data?, Data?, Data?, Data?, Error?) { + ) -> (String?, Data?, Data?, Data?, Data?, Set?, TPPolicy?, Error?) { let expectation = XCTestExpectation(description: "prepare replied") var reta: String?, retb: Data?, retc: Data?, retd: Data?, rete: Data?, reterr: Error? + var retviews: Set? + var retpolicy: TPPolicy? self.prepare(epoch: epoch, machineID: machineID, bottleSalt: bottleSalt, @@ -71,17 +73,19 @@ extension Container { policySecrets: policySecrets, signingPrivateKeyPersistentRef: signingPrivateKeyPersistentRef, encryptionPrivateKeyPersistentRef: encryptionPrivateKeyPersistentRef - ) { a, b, c, d, e, err in + ) { a, b, c, d, e, f, g, err in reta = a retb = b retc = c retd = d rete = e + retviews = f + retpolicy = g reterr = err expectation.fulfill() } test.wait(for: [expectation], timeout: 10.0) - return (reta, retb, retc, retd, rete, reterr) + return (reta, retb, retc, retd, rete, retviews, retpolicy, reterr) } func establishSync(test: XCTestCase, @@ -126,29 +130,34 @@ extension Container { return (reta, retb, reterr) } - func preflightVouchWithBottleSync(test: XCTestCase, bottleID: String) -> (String?, Error?) { + func preflightVouchWithBottleSync(test: XCTestCase, bottleID: String) -> (String?, Set?, TPPolicy?, Error?) { let expectation = XCTestExpectation(description: "preflightVouchWithBottle replied") var reta: String?, reterr: Error? - self.preflightVouchWithBottle(bottleID: bottleID) { a, err in + var retviews: Set?, retpolicy: TPPolicy? + self.preflightVouchWithBottle(bottleID: bottleID) { a, views, policy, err in reta = a + retviews = views + retpolicy = policy reterr = err expectation.fulfill() } test.wait(for: [expectation], timeout: 10.0) - return (reta, reterr) + return (reta, retviews, retpolicy, reterr) } - func vouchWithBottleSync(test: XCTestCase, b: String, entropy: Data, bottleSalt: String, tlkShares: [CKKSTLKShare]) -> (Data?, Data?, Error?) { + func vouchWithBottleSync(test: XCTestCase, b: String, entropy: Data, bottleSalt: String, tlkShares: [CKKSTLKShare]) -> (Data?, Data?, Int64, Int64, Error?) { let expectation = XCTestExpectation(description: "vouchWithBottle replied") - var reta: Data?, retb: Data?, reterr: Error? - self.vouchWithBottle(bottleID: b, entropy: entropy, bottleSalt: bottleSalt, tlkShares: tlkShares) { a, b, err in + var reta: Data?, retb: Data?, retc: Int64 = 0, retd: Int64 = 0, reterr: Error? + self.vouchWithBottle(bottleID: b, entropy: entropy, bottleSalt: bottleSalt, tlkShares: tlkShares) { a, b, c, d, err in reta = a retb = b + retc = c + retd = d reterr = err expectation.fulfill() } test.wait(for: [expectation], timeout: 10.0) - return (reta, retb, reterr) + return (reta, retb, retc, retd, reterr) } func joinSync(test: XCTestCase, @@ -156,41 +165,48 @@ extension Container { voucherSig: Data, ckksKeys: [CKKSKeychainBackedKeySet], tlkShares: [CKKSTLKShare], - preapprovedKeys: [Data]? = nil) -> (String?, [CKRecord]?, Error?) { + preapprovedKeys: [Data]? = nil) -> (String?, [CKRecord]?, Set?, TPPolicy?, Error?) { let expectation = XCTestExpectation(description: "join replied") var reta: String?, retkhr: [CKRecord]?, reterr: Error? + var retviews: Set?, retpolicy: TPPolicy? self.join(voucherData: voucherData, voucherSig: voucherSig, ckksKeys: ckksKeys, tlkShares: tlkShares, - preapprovedKeys: preapprovedKeys) { a, khr, err in + preapprovedKeys: preapprovedKeys) { a, khr, views, policy, err in reta = a retkhr = khr + retviews = views + retpolicy = policy reterr = err expectation.fulfill() } test.wait(for: [expectation], timeout: 10.0) - return (reta, retkhr, reterr) + return (reta, retkhr, retviews, retpolicy, reterr) } func preapprovedJoinSync(test: XCTestCase, ckksKeys: [CKKSKeychainBackedKeySet], tlkShares: [CKKSTLKShare], - preapprovedKeys: [Data]? = nil) -> (String?, [CKRecord]?, Error?) { + preapprovedKeys: [Data]? = nil) -> (String?, [CKRecord]?, Set?, TPPolicy?, Error?) { let expectation = XCTestExpectation(description: "preapprovedjoin replied") var reta: String? var retkhr: [CKRecord]? + var retviews: Set? + var retpolicy: TPPolicy? var reterr: Error? self.preapprovedJoin(ckksKeys: ckksKeys, tlkShares: tlkShares, - preapprovedKeys: preapprovedKeys) { a, khr, err in + preapprovedKeys: preapprovedKeys) { a, khr, views, policy, err in reta = a retkhr = khr + retviews = views + retpolicy = policy reterr = err expectation.fulfill() } test.wait(for: [expectation], timeout: 10.0) - return (reta, retkhr, reterr) + return (reta, retkhr, retviews, retpolicy, reterr) } func updateSync(test: XCTestCase, @@ -215,10 +231,11 @@ extension Container { return (retstate, reterr) } - func setAllowedMachineIDsSync(test: XCTestCase, allowedMachineIDs: Set, listDifference: Bool = true) -> (Error?) { + func setAllowedMachineIDsSync(test: XCTestCase, allowedMachineIDs: Set, accountIsDemo: Bool, listDifference: Bool = true) -> (Error?) { let expectation = XCTestExpectation(description: "setAllowedMachineIDs replied") var reterr: Error? - self.setAllowedMachineIDs(allowedMachineIDs) { differences, err in + let honorIDMSListChanges = accountIsDemo ? false : true + self.setAllowedMachineIDs(allowedMachineIDs, honorIDMSListChanges: honorIDMSListChanges) { differences, err in XCTAssertEqual(differences, listDifference, "Reported list difference should match expectation") reterr = err expectation.fulfill() @@ -253,7 +270,7 @@ extension Container { let expectation = XCTestExpectation(description: "fetchMIDList replied") var retlist: Set? var reterr: Error? - self.fetchAllowedMachineIDs() { list, err in + self.fetchAllowedMachineIDs { list, err in retlist = list reterr = err expectation.fulfill() @@ -352,10 +369,10 @@ extension Container { } func fetchPolicyDocumentsSync(test: XCTestCase, - keys: [NSNumber: String]) -> ([NSNumber: [String]]?, Error?) { + versions: Set) -> ([TPPolicyVersion: Data]?, Error?) { let expectation = XCTestExpectation(description: "fetchPolicyDocuments replied") - var reta: [NSNumber: [String]]?, reterr: Error? - self.fetchPolicyDocuments(keys: keys) { a, err in + var reta: [TPPolicyVersion: Data]?, reterr: Error? + self.fetchPolicyDocuments(versions: versions) { a, err in reta = a reterr = err expectation.fulfill() @@ -383,22 +400,24 @@ extension Container { return (retentropy, retbottleID, retspki, reterror) } - func requestHealthCheckSync(requiresEscrowCheck: Bool, test: XCTestCase) -> (Bool, Bool, Bool, Error?) { + func requestHealthCheckSync(requiresEscrowCheck: Bool, test: XCTestCase) -> (Bool, Bool, Bool, Bool, Error?) { let expectation = XCTestExpectation(description: "requestHealthCheck replied") var retrepairaccount: Bool = false var retrepairescrow: Bool = false var retresetoctagon: Bool = false + var retleavetrust: Bool = false var reterror: Error? - self.requestHealthCheck(requiresEscrowCheck: requiresEscrowCheck) { repairAccount, repairEscrow, resetOctagon, error in + self.requestHealthCheck(requiresEscrowCheck: requiresEscrowCheck) { repairAccount, repairEscrow, resetOctagon, leaveTrust, error in retrepairaccount = repairAccount retrepairescrow = repairEscrow retresetoctagon = resetOctagon + retleavetrust = leaveTrust reterror = error expectation.fulfill() } test.wait(for: [expectation], timeout: 10.0) - return (retrepairaccount, retrepairescrow, retresetoctagon, reterror) + return (retrepairaccount, retrepairescrow, retresetoctagon, retleavetrust, reterror) } } diff --git a/keychain/TrustedPeersHelperUnitTests/FakeCuttlefish.swift b/keychain/TrustedPeersHelperUnitTests/FakeCuttlefish.swift index fbef651a..23267a01 100644 --- a/keychain/TrustedPeersHelperUnitTests/FakeCuttlefish.swift +++ b/keychain/TrustedPeersHelperUnitTests/FakeCuttlefish.swift @@ -66,16 +66,19 @@ struct FakeCuttlefishAssertion: CustomStringConvertible { } } -@objc class FakeCuttlefishNotify: NSObject { +@objc +class FakeCuttlefishNotify: NSObject { let pushes: (Data) -> Void let containerName: String - @objc init(_ containerName: String, pushes: @escaping (Data) -> Void) { + @objc + init(_ containerName: String, pushes: @escaping (Data) -> Void) { self.containerName = containerName self.pushes = pushes } - @objc public func notify(_ function: String) throws { - let notification: [String: Dictionary] = [ + @objc + public func notify(_ function: String) throws { + let notification: [String: [String: Any]] = [ "aps": ["content-available": 1], "cf": [ "f": function, @@ -99,7 +102,7 @@ extension ViewKey { record[SecCKRecordWrappedKeyKey] = self.wrappedkeyBase64 - switch(self.keyclass) { + switch self.keyclass { case .tlk: record[SecCKRecordKeyClassKey] = "tlk" case .classA: @@ -110,7 +113,7 @@ extension ViewKey { abort() } - if self.parentkeyUuid.count > 0 { + if !self.parentkeyUuid.isEmpty { // TODO: no idea how to tell it about the 'verify' action record[SecCKRecordParentKeyRefKey] = CKRecord.Reference(recordID: CKRecord.ID(__recordName: self.parentkeyUuid, zoneID: zoneID), action: .none) } @@ -120,7 +123,7 @@ extension ViewKey { func fakeKeyPointer(zoneID: CKRecordZone.ID) -> CKRecord { let recordName: String - switch(self.keyclass) { + switch self.keyclass { case .tlk: recordName = "tlk" case .classA: @@ -199,6 +202,7 @@ class FakeCuttlefishServer: CuttlefishAPIAsync { var returnRepairAccountResponse: Bool = false var returnRepairEscrowResponse: Bool = false var returnResetOctagonResponse: Bool = false + var returnLeaveTrustResponse: Bool = false var returnRepairErrorResponse: Error? var fetchChangesCalledCount: Int = 0 @@ -211,6 +215,10 @@ class FakeCuttlefishServer: CuttlefishAPIAsync { var healthListener: ((GetRepairActionRequest) -> NSError?)? var fetchViableBottlesListener: ((FetchViableBottlesRequest) -> NSError?)? var resetListener: ((ResetRequest) -> NSError?)? + var setRecoveryKeyListener: ((SetRecoveryKeyRequest) -> NSError?)? + + // Any policies in here will be returned by FetchPolicy before any inbuilt policies + var policyOverlay: [TPPolicyDocument] = [] var fetchViableBottlesDontReturnBottleWithID: String? @@ -259,7 +267,7 @@ class FakeCuttlefishServer: CuttlefishAPIAsync { return Changes.with { changes in changes.changeToken = self.currentChangeToken - changes.differences = self.state.peersByID.compactMap({ (key: String, value: Peer) -> PeerDifference? in + changes.differences = self.state.peersByID.compactMap { (key: String, value: Peer) -> PeerDifference? in let old = snapshot.peersByID[key] if old == nil { return PeerDifference.with { @@ -272,7 +280,7 @@ class FakeCuttlefishServer: CuttlefishAPIAsync { } else { return nil } - }) + } snapshot.peersByID.forEach { (key: String, _: Peer) in if nil == self.state.peersByID[key] { changes.differences.append(PeerDifference.with { @@ -562,6 +570,15 @@ class FakeCuttlefishServer: CuttlefishAPIAsync { func setRecoveryKey(_ request: SetRecoveryKeyRequest, completion: @escaping (SetRecoveryKeyResponse?, Error?) -> Void) { print("FakeCuttlefish: setRecoveryKey called") + + if let listener = self.setRecoveryKeyListener { + let operationError = listener(request) + guard operationError == nil else { + completion(nil, operationError) + return + } + } + guard let snapshot = self.snapshotsByChangeToken[request.changeToken] else { completion(nil, FakeCuttlefishError.unknownChangeToken) return @@ -648,8 +665,15 @@ class FakeCuttlefishServer: CuttlefishAPIAsync { var response = FetchPolicyDocumentsResponse() let policies = builtInPolicyDocuments() - let dummyPolicies = Dictionary(uniqueKeysWithValues: policies.map({ ($0.policyVersion, ($0.policyHash, $0.protobuf)) })) + let dummyPolicies = Dictionary(uniqueKeysWithValues: policies.map { ($0.version.versionNumber, ($0.version.policyHash, $0.protobuf)) }) + let overlayPolicies = Dictionary(uniqueKeysWithValues: self.policyOverlay.map { ($0.version.versionNumber, ($0.version.policyHash, $0.protobuf)) }) + for key in request.keys { + if let (hash, data) = overlayPolicies[key.version], hash == key.hash { + response.entries.append(PolicyDocumentMapEntry.with { $0.key = key; $0.value = data }) + continue + } + guard let (hash, data) = dummyPolicies[key.version] else { continue } @@ -703,6 +727,11 @@ class FakeCuttlefishServer: CuttlefishAPIAsync { $0.repairAction = .resetOctagon } completion(response, nil) + } else if returnLeaveTrustResponse { + let response = GetRepairActionResponse.with { + $0.repairAction = .leaveTrust + } + completion(response, nil) } else if self.returnNoActionResponse { let response = GetRepairActionResponse.with { $0.repairAction = .noAction @@ -718,6 +747,10 @@ class FakeCuttlefishServer: CuttlefishAPIAsync { } } + func getClubCertificates(_: GetClubCertificatesRequest, completion: @escaping (GetClubCertificatesResponse?, Error?) -> Void) { + completion(GetClubCertificatesResponse(), nil) + } + func getSupportAppInfo(_: GetSupportAppInfoRequest, completion: @escaping (GetSupportAppInfoResponse?, Error?) -> Void) { completion(GetSupportAppInfoResponse(), nil) } diff --git a/keychain/TrustedPeersHelperUnitTests/MockCuttlefish.swift b/keychain/TrustedPeersHelperUnitTests/MockCuttlefish.swift index 5d8d2c33..8289a40e 100644 --- a/keychain/TrustedPeersHelperUnitTests/MockCuttlefish.swift +++ b/keychain/TrustedPeersHelperUnitTests/MockCuttlefish.swift @@ -15,7 +15,7 @@ enum Handler { case updateTrust((UpdateTrustRequest, @escaping (UpdateTrustResponse?, Error?) -> Void) -> Void) case setRecoveryKey((SetRecoveryKeyRequest, @escaping (SetRecoveryKeyResponse?, Error?) -> Void) -> Void) case fetchChanges((FetchChangesRequest, @escaping (FetchChangesResponse?, Error?) -> Void) -> Void) - case fetchViableBottles((FetchViableBottlesRequest, @escaping (FetchViableBottlesResponse?, Error?) -> Void) ->Void) + case fetchViableBottles((FetchViableBottlesRequest, @escaping (FetchViableBottlesResponse?, Error?) -> Void) -> Void) case fetchPolicyDocuments((FetchPolicyDocumentsRequest, @escaping (FetchPolicyDocumentsResponse?, Error?) -> Void) -> Void) } @@ -169,5 +169,7 @@ class MockCuttlefishAPIAsyncClient: CuttlefishAPIAsync { func getSupportAppInfo(_: GetSupportAppInfoRequest, completion: @escaping (GetSupportAppInfoResponse?, Error?) -> Void) { completion(GetSupportAppInfoResponse(), nil) } - + func getClubCertificates(_: GetClubCertificatesRequest, completion: @escaping (GetClubCertificatesResponse?, Error?) -> Void) { + completion(GetClubCertificatesResponse(), nil) + } } diff --git a/keychain/TrustedPeersHelperUnitTests/TrustedPeersHelperUnitTests.swift b/keychain/TrustedPeersHelperUnitTests/TrustedPeersHelperUnitTests.swift index 7d7ecae1..c5f2b3f7 100644 --- a/keychain/TrustedPeersHelperUnitTests/TrustedPeersHelperUnitTests.swift +++ b/keychain/TrustedPeersHelperUnitTests/TrustedPeersHelperUnitTests.swift @@ -157,18 +157,19 @@ class TrustedPeersHelperUnitTests: XCTestCase { func establish(reload: Bool, store: NSPersistentStoreDescription) throws -> (Container, String) { - return try self.establish(reload: reload, contextID: OTDefaultContext, store: store) + return try self.establish(reload: reload, contextID: OTDefaultContext, accountIsDemo: false, store: store) } func establish(reload: Bool, contextID: String, allowedMachineIDs: Set = Set(["aaa", "bbb", "ccc"]), + accountIsDemo: Bool, store: NSPersistentStoreDescription) throws -> (Container, String) { var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish) - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: allowedMachineIDs, listDifference: allowedMachineIDs.count > 0), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: allowedMachineIDs, accountIsDemo: accountIsDemo, listDifference: !allowedMachineIDs.isEmpty), "should be able to set allowed machine IDs") - let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = container.getStateSync(test: self) XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer") @@ -183,11 +184,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { _ = container.dumpSync(test: self) - if (reload) { + if reload { do { container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish) } catch { - XCTFail() + XCTFail("Creating container errored: \(error)") } } @@ -216,7 +217,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { let description = tmpStoreDescription(name: "container.db") let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) - let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = container.getStateSync(test: self) XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer") @@ -232,7 +233,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { // Note that an empty machine ID list means "all are allowed", so an establish now will succeed // Now set up a machine ID list that positively does not have our peer - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs") let (peerID3, _, error3) = container.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: []) XCTAssertNotNil(peerID3, "Should get a peer when you establish a now allow-listed peer") @@ -243,15 +244,16 @@ class TrustedPeersHelperUnitTests: XCTestCase { containerID: String, machineID: String, machineIDs: Set, + accountIsDemo: Bool, store: NSPersistentStoreDescription) throws -> (Container, String) { let c = try Container(name: ContainerName(container: containerID, context: OTDefaultContext), persistentStoreDescription: store, cuttlefish: cuttlefish) - XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, listDifference: machineIDs.count > 0), "Should be able to set machine IDs") + XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: accountIsDemo, listDifference: !machineIDs.isEmpty), "Should be able to set machine IDs") print("preparing \(containerID)") - let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) = + let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error) = c.prepareSync(test: self, epoch: 1, machineID: machineID, bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") XCTAssertNil(error) XCTAssertNotNil(peerID) @@ -280,11 +282,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { assertTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) print("\(containerID) joins") - let (joinedPeerID, _, joinError) = c.joinSync(test: self, - voucherData: voucherData!, - voucherSig: voucherSig!, - ckksKeys: [], - tlkShares: []) + let (joinedPeerID, _, _, _, joinError) = c.joinSync(test: self, + voucherData: voucherData!, + voucherSig: voucherSig!, + ckksKeys: [], + tlkShares: []) XCTAssertNil(joinError) XCTAssertEqual(joinedPeerID, peerID!) } @@ -299,13 +301,17 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb", "ccc"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aViewList, aPolicy, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + XCTAssertNotNil(aViewList, "Should have a view list coming back from a successful prepare") + XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare") + XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version") + do { let state = containerA.getStateSync(test: self) XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer") @@ -326,7 +332,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { } print("preparing B") - let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) = + let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -386,11 +392,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) print("B joins") - let (peerID, _, error) = containerB.joinSync(test: self, - voucherData: voucherData!, - voucherSig: voucherSig!, - ckksKeys: [], - tlkShares: []) + let (peerID, _, _, _, error) = containerB.joinSync(test: self, + voucherData: voucherData!, + voucherSig: voucherSig!, + ckksKeys: [], + tlkShares: []) XCTAssertNil(error) XCTAssertEqual(peerID, bPeerID!) } @@ -400,7 +406,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { _ = containerC.dumpSync(test: self) print("preparing C") - let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, error4) = + let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, _, error4) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerC.getStateSync(test: self) @@ -438,11 +444,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) print("C joins") - let (peerID, _, error2) = containerC.joinSync(test: self, - voucherData: voucherData!, - voucherSig: voucherSig!, - ckksKeys: [self.manateeKeySet, provisionalEngramKeySet], - tlkShares: []) + let (peerID, _, _, _, error2) = containerC.joinSync(test: self, + voucherData: voucherData!, + voucherSig: voucherSig!, + ckksKeys: [self.manateeKeySet, provisionalEngramKeySet], + tlkShares: []) XCTAssertNil(error2) XCTAssertEqual(peerID, cPeerID!) @@ -473,7 +479,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) - let (peerID, permanentInfo, permanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerA.getStateSync(test: self) XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer") @@ -486,14 +492,14 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertNotNil(permanentInfo, "Should have a permanent info after preparing A") XCTAssertNotNil(permanentInfoSig, "Should have a signature after preparing A") - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs") + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs") let (peerID2, _, error2) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: []) XCTAssertNotNil(peerID2, "Should get a peer when you establish a now allow-listed peer") XCTAssertNil(error2, "Should not get an error when you establish a now allow-listed peer") print("preparing B") - let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, errorPrepareB) = + let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, errorPrepareB) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerA.getStateSync(test: self) @@ -507,7 +513,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertNotNil(bPermanentInfo, "Should have a permanent info after preparing B") XCTAssertNotNil(bPermanentInfoSig, "Should have a signature after preparing B") - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs on container B") + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs on container B") do { print("A vouches for B") @@ -524,11 +530,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertNotNil(voucherSig, "Should have a signature from A") print("B joins") - let (peerID, _, error) = containerB.joinSync(test: self, - voucherData: voucherData!, - voucherSig: voucherSig!, - ckksKeys: [], - tlkShares: []) + let (peerID, _, _, _, error) = containerB.joinSync(test: self, + voucherData: voucherData!, + voucherSig: voucherSig!, + ckksKeys: [], + tlkShares: []) XCTAssertNotNil(error, "Should have an error joining with an unapproved machine ID") XCTAssertNil(peerID, "Should not receive a peer ID joining with an unapproved machine ID") } @@ -539,10 +545,10 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerA.getStateSync(test: self) XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer") @@ -573,7 +579,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { let (dict, error) = containerA.dumpSync(test: self) XCTAssertNil(error) XCTAssertNotNil(dict) - let peers: Array = dict!["peers"] as! Array + let peers: [Any] = dict!["peers"] as! [Any] XCTAssertEqual(0, peers.count) } } @@ -583,9 +589,9 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerA.getStateSync(test: self) XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer") @@ -634,26 +640,26 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]))) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]))) - XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]))) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), accountIsDemo: false)) + XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), accountIsDemo: false)) print("preparing") - let (peerID, _, _, _, _, _) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (peerID, _, _, _, _, _, _, _) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerA.getStateSync(test: self) XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer") let secret = containerA.loadSecretSync(test: self, label: peerID!) XCTAssertNotNil(secret, "secret should not be nil") } - let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, _) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer") let secret = containerB.loadSecretSync(test: self, label: bPeerID!) XCTAssertNotNil(secret, "secret should not be nil") } - let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, _, _) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerC.getStateSync(test: self) XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer") @@ -743,53 +749,62 @@ class TrustedPeersHelperUnitTests: XCTestCase { func testFetchPolicyDocuments() throws { // 1 is known locally via builtin, 3 is not but is known to cuttlefish - let policies = - [ - 1: ("SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=", - "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYS" + - "DgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0" + - "Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2"), - 3: ("SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=", - "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A="), - ] - let (request1, data1) = policies[1]! - let (request3, data3) = policies[3]! + + let missingTuple = TPPolicyVersion(version: 900, hash: "not a hash") + + let policy1Tuple = TPPolicyVersion(version: 1, hash: "SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=") + let policy1Data = Data(base64Encoded: "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYS" + + "DgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0" + + "Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2")! + + let policy3Tuple = TPPolicyVersion(version: 3, hash: "SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=") + let policy3Data = Data(base64Encoded: "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A=")! let description = tmpStoreDescription(name: "container.db") let container = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) // nothing - let (response1, error1) = container.fetchPolicyDocumentsSync(test: self, keys: [:]) + let (response1, error1) = container.fetchPolicyDocumentsSync(test: self, versions: []) XCTAssertNil(error1, "No error querying for an empty list") XCTAssertEqual(response1, [:], "Received empty dictionary") // local stuff - let (response2, error2) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1]) - XCTAssertNil(error2, "No error getting locally known policy document") - XCTAssertEqual(response2?.count, 1, "Got one response for request for one locally known policy") - XCTAssertEqual(response2?[1]?[0], request1, "retrieved hash matches request hash") - XCTAssertEqual(response2?[1]?[1], data1, "retrieved data matches known data") + do { + let (response2, error2) = container.fetchPolicyDocumentsSync(test: self, versions: Set([policy1Tuple])) + XCTAssertNil(error2, "No error getting locally known policy document") + XCTAssertEqual(response2?.count, 1, "Got one response for request for one locally known policy") + XCTAssert(response2?.keys.contains(policy1Tuple) ?? false, "Should have retrieved request for policy1") + XCTAssertEqual(response2?[policy1Tuple], policy1Data, "retrieved data matches known data") + } // fetch remote - let (response3, error3) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1, 3: request3]) - XCTAssertNil(error3, "No error fetching local + remote policy") - XCTAssertEqual(response3?.count, 2, "Got two responses for local+remote policy request") - XCTAssertEqual(response3?[1]?[0], request1, "retrieved hash matches local request hash") - XCTAssertEqual(response3?[1]?[1], data1, "retrieved data matches local known data") - XCTAssertEqual(response3?[3]?[0], request3, "retrieved hash matches remote request hash") - XCTAssertEqual(response3?[3]?[1], data3, "retrieved data matches remote known data") + do { + let (response3, error3) = container.fetchPolicyDocumentsSync(test: self, versions: [policy1Tuple, policy3Tuple]) + XCTAssertNil(error3, "No error fetching local + remote policy") + XCTAssertEqual(response3?.count, 2, "Got two responses for local+remote policy request") + + XCTAssert(response3?.keys.contains(policy1Tuple) ?? false, "Should have retrieved request for policy1") + XCTAssertEqual(response3?[policy1Tuple], policy1Data, "retrieved data matches known data") + + XCTAssert(response3?.keys.contains(policy3Tuple) ?? false, "Should have retrieved request for policy3") + XCTAssertEqual(response3?[policy3Tuple], policy3Data, "retrieved data matches known data") + } // invalid version - let (response4, error4) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash"]) - XCTAssertNil(response4, "No response for wrong [version: hash] combination") - XCTAssertNotNil(error4, "Expected error fetching invalid policy version") + do { + let (response4, error4) = container.fetchPolicyDocumentsSync(test: self, versions: Set([missingTuple])) + XCTAssertNil(response4, "No response for wrong [version: hash] combination") + XCTAssertNotNil(error4, "Expected error fetching invalid policy version") + } // valid + invalid - let (response5, error5) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash", - 1: request1, - 3: request3, ]) - XCTAssertNil(response5, "No response for valid + unknown [version: hash] combination") - XCTAssertNotNil(error5, "Expected error fetching valid + invalid policy version") + do { + let (response5, error5) = container.fetchPolicyDocumentsSync(test: self, versions: Set([missingTuple, + policy1Tuple, + policy3Tuple, ])) + XCTAssertNil(response5, "No response for valid + unknown [version: hash] combination") + XCTAssertNotNil(error5, "Expected error fetching valid + invalid policy version") + } } func testEscrowKeys() throws { @@ -834,25 +849,25 @@ class TrustedPeersHelperUnitTests: XCTestCase { let secret = secretString.data(using: .utf8) do { - let testv1 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: secret!, bottleSalt: testDSID) + let testv1 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySigning, masterSecret: secret!, bottleSalt: testDSID) XCTAssertEqual(testv1, signingKey_384, "signing keys should match") - let testv2 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret!, bottleSalt: testDSID) + let testv2 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret!, bottleSalt: testDSID) XCTAssertEqual(testv2, encryptionKey_384, "encryption keys should match") - let testv3 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret!, bottleSalt: testDSID) + let testv3 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret!, bottleSalt: testDSID) XCTAssertEqual(testv3, symmetricKey_384, "symmetric keys should match") let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret" let newSecret = newSecretString.data(using: .utf8) - let testv4 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: newSecret!, bottleSalt: testDSID) + let testv4 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySigning, masterSecret: newSecret!, bottleSalt: testDSID) XCTAssertNotEqual(testv4, signingKey_384, "signing keys should not match") - let testv5 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: newSecret!, bottleSalt: testDSID) + let testv5 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeyEncryption, masterSecret: newSecret!, bottleSalt: testDSID) XCTAssertNotEqual(testv5, encryptionKey_384, "encryption keys should not match") - let testv6 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: newSecret!, bottleSalt: testDSID) + let testv6 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySymmetric, masterSecret: newSecret!, bottleSalt: testDSID) XCTAssertNotEqual(testv6, symmetricKey_384, "symmetric keys should not match") } catch { XCTFail("error testing escrow key test vectors \(error)") @@ -865,19 +880,19 @@ class TrustedPeersHelperUnitTests: XCTestCase { let secret = secretString.data(using: .utf8) do { - let testv1 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret!, recoverySalt: testDSID) + let testv1 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret!, recoverySalt: testDSID) XCTAssertEqual(testv1, recovery_signingKey_384, "signing keys should match") - let testv2 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret!, recoverySalt: testDSID) + let testv2 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret!, recoverySalt: testDSID) XCTAssertEqual(testv2, recovery_encryptionKey_384, "encryption keys should match") let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret" let newSecret = newSecretString.data(using: .utf8) - let testv4 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeySigning, masterSecret: newSecret!, recoverySalt: testDSID) + let testv4 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeySigning, masterSecret: newSecret!, recoverySalt: testDSID) XCTAssertNotEqual(testv4, recovery_signingKey_384, "signing keys should not match") - let testv5 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: newSecret!, recoverySalt: testDSID) + let testv5 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: newSecret!, recoverySalt: testDSID) XCTAssertNotEqual(testv5, recovery_encryptionKey_384, "encryption keys should not match") } catch { XCTFail("error testing RecoveryKey test vectors \(error)") @@ -892,11 +907,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { var state = containerA.getStateSync(test: self) @@ -932,7 +947,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { _ = containerB.updateSync(test: self) print("preparing B") - let (bPeerID, _, _, _, _, error2) = + let (bPeerID, _, _, _, _, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -946,11 +961,13 @@ class TrustedPeersHelperUnitTests: XCTestCase { do { print("B prepares to join via bottle") - let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) + let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle") XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A") + XCTAssertNotNil(views, "Should have a set of views to restore") + XCTAssertNotNil(policy, "Should have a policy") - let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) + let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) XCTAssertNil(error3) XCTAssertNotNil(voucherData) @@ -960,7 +977,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) print("B joins") - let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: []) + let (peerID, _, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: []) XCTAssertNil(error) XCTAssertEqual(peerID, bPeerID!) @@ -977,11 +994,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { var state = containerA.getStateSync(test: self) @@ -1017,7 +1034,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { _ = containerB.updateSync(test: self) print("preparing B") - let (bPeerID, _, _, _, _, error2) = + let (bPeerID, _, _, _, _, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -1031,11 +1048,13 @@ class TrustedPeersHelperUnitTests: XCTestCase { do { print("B prepares to join via bottle") - let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) + let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle") XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A") + XCTAssertNotNil(views, "Should have a set of views to restore") + XCTAssertNotNil(policy, "Should have a policy") - let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) + let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) XCTAssertNil(error3) XCTAssertNotNil(voucherData) @@ -1045,7 +1064,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) print("B joins") - let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: []) + let (peerID, _, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: []) XCTAssertNil(error) XCTAssertEqual(peerID, bPeerID!) @@ -1061,11 +1080,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerA.getStateSync(test: self) @@ -1097,7 +1116,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { _ = containerB.updateSync(test: self) print("preparing B") - let (bPeerID, _, _, _, _, error2) = + let (bPeerID, _, _, _, _, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -1111,11 +1130,13 @@ class TrustedPeersHelperUnitTests: XCTestCase { do { print("B joins via bottle") - let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: "wrong escrow record") + let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: "wrong escrow record") XCTAssertNotNil(errorPreflight, "Should be an error preflighting bottle that doesn't exist") XCTAssertNil(bottlePeerID, "peerID should be nil for no bottle") + XCTAssertNil(views, "Should not have a set of views to restore") + XCTAssertNil(policy, "Should not have a policy") - let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: "wrong escrow record", entropy: entropy, bottleSalt: "123456789", tlkShares: []) + let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: "wrong escrow record", entropy: entropy, bottleSalt: "123456789", tlkShares: []) XCTAssertNotNil(error3) XCTAssertNil(voucherData) @@ -1131,11 +1152,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerA.getStateSync(test: self) @@ -1165,7 +1186,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { } print("preparing B") - let (bPeerID, _, _, _, _, error2) = + let (bPeerID, _, _, _, _, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { var state = containerB.getStateSync(test: self) @@ -1180,11 +1201,13 @@ class TrustedPeersHelperUnitTests: XCTestCase { do { print("B joins via bottle") - let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleB.bottleID!) - XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle") - XCTAssertEqual(bottlePeerID, bPeerID, "Bottle should be for peer B") + let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleB.bottleID!) + XCTAssertNotNil(errorPreflight, "Should be an error preflighting bottle that doesn't correspond to a peer") + XCTAssertNil(bottlePeerID, "Should have no peer for invalid bottle") + XCTAssertNil(views, "Should not have a set of views to restore") + XCTAssertNil(policy, "Should not have a policy") - let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleB.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) + let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleB.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) XCTAssertNotNil(error3) XCTAssertNil(voucherData) @@ -1200,11 +1223,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { var state = containerA.getStateSync(test: self) @@ -1237,7 +1260,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { _ = containerB.updateSync(test: self) print("preparing B") - let (bPeerID, _, _, _, _, error2) = + let (bPeerID, _, _, _, _, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -1251,11 +1274,13 @@ class TrustedPeersHelperUnitTests: XCTestCase { do { print("B joins via bottle") - let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) + let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle") XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A") + XCTAssertNotNil(views, "Should have a set of views to restore") + XCTAssertNotNil(policy, "Should have a policy") - let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "987654321", tlkShares: []) + let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "987654321", tlkShares: []) XCTAssertNotNil(error3) XCTAssertNil(voucherData) @@ -1270,11 +1295,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { var state = containerA.getStateSync(test: self) @@ -1306,7 +1331,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { _ = containerB.updateSync(test: self) print("preparing B") - let (bPeerID, _, _, _, _, error2) = + let (bPeerID, _, _, _, _, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -1320,11 +1345,13 @@ class TrustedPeersHelperUnitTests: XCTestCase { do { print("B joins via bottle") - let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) + let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle") XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A") + XCTAssertNotNil(views, "Should have a set of views to restore") + XCTAssertNotNil(policy, "Should have a policy") - let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: Data(count: Int(OTMasterSecretLength)), bottleSalt: "123456789", tlkShares: []) + let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: Data(count: Int(OTMasterSecretLength)), bottleSalt: "123456789", tlkShares: []) XCTAssertNotNil(error3) XCTAssertNil(voucherData) @@ -1340,11 +1367,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { var state = containerA.getStateSync(test: self) @@ -1375,7 +1402,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { } print("preparing B") - let (bPeerID, _, _, _, _, error2) = + let (bPeerID, _, _, _, _, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -1391,13 +1418,15 @@ class TrustedPeersHelperUnitTests: XCTestCase { self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired)) - let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) + let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) XCTAssertNotNil(errorPreflight, "Should be an error preflighting a vouch with bottle with a fetch error") XCTAssertNil(bottlePeerID, "peerID should be nil") + XCTAssertNil(views, "Should not have a set of views to restore") + XCTAssertNil(policy, "Should not have a policy") self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired)) - let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) + let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) XCTAssertNotNil(error3) XCTAssertNil(voucherData) @@ -1411,19 +1440,23 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aViewList, aPolicy, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") XCTAssertNil(error) XCTAssertNotNil(aPeerID) XCTAssertNotNil(aPermanentInfo) XCTAssertNotNil(aPermanentInfoSig) + XCTAssertNotNil(aViewList, "Should have a view list coming back from a successful prepare") + XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare") + XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version") + print("preparing B") - let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, error2) = + let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -1454,9 +1487,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) print("B joins by preapproval, and uploads all TLKShares that it has") - let (bJoinedPeerID, _, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: []) + let (bJoinedPeerID, _, views, policy, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: []) XCTAssertNil(bJoinedError, "Should be no error joining by preapproval") XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join") + XCTAssertNotNil(views, "should have a list of views to use") + XCTAssertNotNil(policy, "Should have a policy back from preapprovedjoin") assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) } @@ -1480,13 +1515,13 @@ class TrustedPeersHelperUnitTests: XCTestCase { let (c2, peerID2) = try joinByVoucher(sponsor: c, containerID: "second", machineID: "bbb", - machineIDs: ["aaa", "bbb", "ccc"], + machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, store: store) let (c3, peerID3) = try joinByVoucher(sponsor: c, containerID: "third", machineID: "ccc", - machineIDs: ["aaa", "bbb", "ccc"], + machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, store: store) let (_, cUpdateError) = c.updateSync(test: self) @@ -1519,7 +1554,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { // But all that goes away, and a new peer establishes self.cuttlefish.state = FakeCuttlefishServer.State() - let (_, peerID2) = try establish(reload: false, contextID: "second", store: tmpStoreDescription(name: "container-peer2.db")) + let (_, peerID2) = try establish(reload: false, contextID: "second", accountIsDemo: false, store: tmpStoreDescription(name: "container-peer2.db")) // And the first container fetches again, which should succeed self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired)) @@ -1680,10 +1715,10 @@ class TrustedPeersHelperUnitTests: XCTestCase { cuttlefish: self.cuttlefish) let machineIDs = Set(["aaa", "bbb", "ccc"]) - XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing peer A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = c.getStateSync(test: self) @@ -1736,10 +1771,10 @@ class TrustedPeersHelperUnitTests: XCTestCase { cuttlefish: self.cuttlefish) let machineIDs = Set(["aaa", "bbb", "ccc"]) - XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing peer A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = c.getStateSync(test: self) @@ -1759,10 +1794,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertNil(error) XCTAssertNotNil(peerID) } - let (repairAccount, repairEscrow, resetOctagon, healthError) = c.requestHealthCheckSync(requiresEscrowCheck: true, test: self) + let (repairAccount, repairEscrow, resetOctagon, leaveTrust, healthError) = c.requestHealthCheckSync(requiresEscrowCheck: true, test: self) XCTAssertEqual(repairAccount, false, "") XCTAssertEqual(repairEscrow, false, "") XCTAssertEqual(resetOctagon, false, "") + XCTAssertEqual(leaveTrust, false, "") XCTAssertNil(healthError) } @@ -1774,11 +1810,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { var state = containerA.getStateSync(test: self) @@ -1814,7 +1850,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { _ = containerB.updateSync(test: self) print("preparing B") - let (bPeerID, _, _, _, _, error2) = + let (bPeerID, _, _, _, _, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -1828,11 +1864,13 @@ class TrustedPeersHelperUnitTests: XCTestCase { do { print("B prepares to join via bottle") - let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) + let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!) XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle") XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A") + XCTAssertNotNil(views, "Should have a set of views to restore") + XCTAssertNotNil(policy, "Should have a policy") - let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) + let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: []) XCTAssertNil(error3) XCTAssertNotNil(voucherData) @@ -1844,7 +1882,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) print("B joins") - let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: []) + let (peerID, _, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: []) XCTAssertNotNil(error) XCTAssertNil(peerID) } @@ -1856,11 +1894,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) let machineIDs = Set(["aaa", "bbb"]) - XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) - XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs)) + XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) + XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false)) print("preparing peer A") - let (aPeerID, aPermanentInfo, aPermanentInfoSig, aStableInfo, aStableInfoSig, error) = + let (aPeerID, aPermanentInfo, aPermanentInfoSig, aStableInfo, aStableInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerA.getStateSync(test: self) @@ -1884,7 +1922,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { } print("preparing B") - let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) = + let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, error2) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") do { let state = containerB.getStateSync(test: self) @@ -1944,11 +1982,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) print("B joins") - let (peerID, _, error) = containerB.joinSync(test: self, - voucherData: voucherData!, - voucherSig: voucherSig!, - ckksKeys: [], - tlkShares: []) + let (peerID, _, _, _, error) = containerB.joinSync(test: self, + voucherData: voucherData!, + voucherSig: voucherSig!, + ckksKeys: [], + tlkShares: []) XCTAssertNil(error) XCTAssertEqual(peerID, bPeerID!) } @@ -2018,7 +2056,6 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertEqual(midList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs should match") XCTAssertEqual(midList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs should match") - let (fetchedAllowList, fetchErr) = container.fetchAllowedMachineIDsSync(test: self) XCTAssertNil(fetchErr, "Should be no error fetching the allowed list") XCTAssertEqual(fetchedAllowList, allowedMachineIDs, "A fetched list of allowed machine IDs should match the loaded list") @@ -2036,7 +2073,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { let description = tmpStoreDescription(name: "container.db") let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) - let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") XCTAssertNil(error) XCTAssertNotNil(peerID) @@ -2045,11 +2082,11 @@ class TrustedPeersHelperUnitTests: XCTestCase { try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish) - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"]), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"]), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") @@ -2063,22 +2100,22 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal") // once they're unknown, a full list set will make them disallowed - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"]), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], accountIsDemo: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // Resetting the list to what it is doesn't change the list - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], listDifference: false), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // But changing it to something completely new does - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm"]), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm"], accountIsDemo: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm"]), disallowedMachineIDs: Set(["aaa", "zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // And, readding a previously disallowed machine ID works too - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm", "aaa"]), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm", "aaa"], accountIsDemo: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") @@ -2105,7 +2142,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish) - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") @@ -2115,7 +2152,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // But, the next time we ask IDMS, they still haven't made it to the full list, and in fact, C has disappeared. - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd", "eee"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") @@ -2125,23 +2162,23 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal") // and a list set after the remove confirms the removal - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // Then a new list set includes D! Hurray IDMS. Note that this is not a "list change", because the list doesn't actually change - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], listDifference: false), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // And another list set no longer includes D, so it should now be disallowed - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // And just to check the 48 hour boundary... XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["xxx"]), "should be able to receive an add push") - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: false), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "xxx"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish) container.moc.performAndWait { @@ -2159,7 +2196,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // Setting the list again should kick out X, since it was 'added' too long ago - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee", "xxx"]), persistentStore: description, cuttlefish: self.cuttlefish) } @@ -2188,7 +2225,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { // Now TPH boots up with a preexisting model let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish) - let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") XCTAssertNil(error) XCTAssertNotNil(peerID) @@ -2198,7 +2235,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish) // Setting a new list should work fine - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"]), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], accountIsDemo: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertEqual(container.containerMO.allowedMachineIDs, Set() as NSSet, "Set of allowed machine IDs should now be empty") @@ -2242,7 +2279,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { try self.assert(container: container, allowedMachineIDs: Set(["aaa"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish) // Setting a new list should work fine - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "ddd"]), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "ddd"], accountIsDemo: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "ddd"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertEqual(container.containerMO.allowedMachineIDs, Set() as NSSet, "Set of allowed machine IDs should now be empty") @@ -2263,7 +2300,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { } // and set the machine ID list to something that doesn't include 'aaa' - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") @@ -2286,7 +2323,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // Setting it again is fine... - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish) // And doesn't reset the modified date on the record @@ -2304,53 +2341,80 @@ class TrustedPeersHelperUnitTests: XCTestCase { } // And can be promoted to 'allowed' - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") } func testMachineIDListSetDisallowedOldUnknownMachineIDs() throws { let description = tmpStoreDescription(name: "container.db") - let (container, _) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(), store: description) + var (container, peerID1) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(["aaa"]), accountIsDemo: false, store: description) - // and set the machine ID list to something that doesn't include 'aaa' - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs") - try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish) + // and set the machine ID list to something that doesn't include 'ddd' + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") + try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES") - // But an entry for "aaa" should exist, as a peer in the model claims it as their MID + let unknownMachineID = "ddd" + let (_, peerID3) = try self.joinByVoucher(sponsor: container, + containerID: "second", + machineID: unknownMachineID, + machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]), accountIsDemo: false, + store: description) + + // And the first container accepts the join... + let (_, cUpdateError) = container.updateSync(test: self) + XCTAssertNil(cUpdateError, "Should be able to update first container") + assertTrusts(context: container, peerIDs: [peerID1, peerID3]) + + try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish) + + XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES") + + // But an entry for "ddd" should exist, as a peer in the model claims it as their MID container.moc.performAndWait { let knownMachineMOs = container.containerMO.machines as? Set ?? Set() - let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" } - XCTAssertEqual(aaaMOs.count, 1, "Should have one machine MO for aaa") + let unknownMOs = knownMachineMOs.filter { $0.machineID == unknownMachineID } + XCTAssertEqual(unknownMOs.count, 1, "Should have one machine MO for ddd") - let aaaMO = aaaMOs.first! - XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'") - XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field") + let dddMO = unknownMOs.first! + XCTAssertEqual(dddMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of ddd MO should be 'unknown'") + XCTAssertFalse(dddMO.allowed, "allowed should no longer be a used field") - // Pretend that aaa was added 49 hours ago - aaaMO.modified = Date(timeIntervalSinceNow: -60 * 60 * 49) + // Pretend that ddd was added 49 hours ago + dddMO.modified = Date(timeIntervalSinceNow: -60 * 60 * 49) try! container.moc.save() } + //reload container + do { + container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES") + + } catch { + XCTFail("Creating container errored: \(error)") + } XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal") - // And, setting the list again should disallow aaa, since it is so old - // Note that this _should_ return a list difference, since A is promoted to disallowed - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs") - try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish) + // And, setting the list again should disallow ddd, since it is so old + // Note that this _should_ return a list difference, since D is promoted to disallowed + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") + try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [unknownMachineID], persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") // Setting ths list again has no change - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs") - try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish) + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs") + try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [unknownMachineID], persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") - // But A can appear again, no problem. - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs") - try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish) + // But D can appear again, no problem. + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc", "ddd"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs") + try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc", "ddd"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES") } func testMachineIDListHandlingWithPeers() throws { @@ -2363,7 +2427,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { let (_, peerID2) = try self.joinByVoucher(sponsor: container, containerID: "second", machineID: unknownMachineID, - machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]), + machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]), accountIsDemo: false, store: description) // And the first container accepts the join... @@ -2377,7 +2441,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { func testMachineIDListHandlingInDemoAccounts() throws { // Demo accounts have no machine IDs in their lists let description = tmpStoreDescription(name: "container.db") - let (container, peerID1) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(), store: description) + var (container, peerID1) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(), accountIsDemo: true, store: description) // And so we just don't write down any MIDs try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish) @@ -2388,9 +2452,12 @@ class TrustedPeersHelperUnitTests: XCTestCase { containerID: "second", machineID: unknownMachineID, machineIDs: Set(), + accountIsDemo: true, store: description) try self.assert(container: c2, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish) + c2.containerMO.honorIDMSListChanges = "NO" + // And the first container accepts the join... let (_, cUpdateError) = container.updateSync(test: self) XCTAssertNil(cUpdateError, "Should be able to update first container") @@ -2399,11 +2466,21 @@ class TrustedPeersHelperUnitTests: XCTestCase { // And still has nothing in its list... try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish) + //reload container + do { + container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "NO", "honorIDMSListChanges should be NO") + + } catch { + XCTFail("Creating container errored: \(error)") + } + // Even after a full list set - XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: [], listDifference: false), "should be able to set allowed machine IDs") + XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: [], accountIsDemo: true, listDifference: false), "should be able to set allowed machine IDs") try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish) XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set") + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "NO", "honorIDMSListChanges should be NO") } func testContainerAndModelConsistency() throws { @@ -2411,7 +2488,7 @@ class TrustedPeersHelperUnitTests: XCTestCase { let preTestContainerName = ContainerName(container: "testToCreatePrepareData", context: "context") let description = tmpStoreDescription(name: "container.db") let containerTest = try Container(name: preTestContainerName, persistentStoreDescription: description, cuttlefish: cuttlefish) - let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) = containerTest.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error) = containerTest.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") XCTAssertNil(error) XCTAssertNotNil(peerID) XCTAssertNotNil(permanentInfo) @@ -2443,8 +2520,8 @@ class TrustedPeersHelperUnitTests: XCTestCase { do { let peerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: containerMO.egoPeerID!) let info3 = TPPeerStableInfo(clock: containerEgoStableInfo!.clock + 2, - policyVersion: containerEgoStableInfo!.policyVersion, - policyHash: containerEgoStableInfo!.policyHash, + frozenPolicyVersion: containerEgoStableInfo!.frozenPolicyVersion, + flexiblePolicyVersion: containerEgoStableInfo!.flexiblePolicyVersion!, policySecrets: containerEgoStableInfo!.policySecrets, deviceName: containerEgoStableInfo!.deviceName, serialNumber: containerEgoStableInfo!.serialNumber, @@ -2518,4 +2595,328 @@ class TrustedPeersHelperUnitTests: XCTestCase { let e4 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int4]) XCTAssertTrue(RetryingInvocable.retryableError(error: e4)) } + + func testEstablishWithEnforceIDMSListNotSetBehavior() throws { + let contextID = "OTDefaultContext" + let description = tmpStoreDescription(name: "container.db") + + var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: description, cuttlefish: cuttlefish) + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + + let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + do { + let state = container.getStateSync(test: self) + XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer") + let secret = container.loadSecretSync(test: self, label: peerID!) + XCTAssertNotNil(secret, "secret should not be nil") + XCTAssertNil(error, "error should be nil") + } + XCTAssertNotNil(peerID) + XCTAssertNotNil(permanentInfo) + XCTAssertNotNil(permanentInfoSig) + XCTAssertNil(error) + + _ = container.dumpSync(test: self) + + do { + container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: description, cuttlefish: cuttlefish) + } catch { + XCTFail("Creating container errored: \(error)") + } + + let (peerID2, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: []) + XCTAssertNil(error2) + XCTAssertNotNil(peerID2) + + _ = container.dumpSync(test: self) + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + } + + func testJoinWithEnforceIDMSListNotSetBehavior() throws { + let description = tmpStoreDescription(name: "container.db") + let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) + let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) + let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) + + XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + XCTAssertEqual(containerC.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + + print("preparing A") + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aViewList, aPolicy, error) = + containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + XCTAssertNotNil(aViewList, "Should have a view list coming back from a successful prepare") + XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare") + XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version") + + do { + let state = containerA.getStateSync(test: self) + XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer") + let secret = containerA.loadSecretSync(test: self, label: aPeerID!) + XCTAssertNotNil(secret, "secret should not be nil") + XCTAssertNil(error, "error should be nil") + } + XCTAssertNil(error) + XCTAssertNotNil(aPeerID) + XCTAssertNotNil(aPermanentInfo) + XCTAssertNotNil(aPermanentInfoSig) + + print("establishing A") + do { + let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: []) + XCTAssertNil(error) + XCTAssertNotNil(peerID) + } + + print("preparing B") + let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, error2) = + containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + do { + let state = containerB.getStateSync(test: self) + XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer") + let secret = containerB.loadSecretSync(test: self, label: bPeerID!) + XCTAssertNotNil(secret, "secret should not be nil") + XCTAssertNil(error, "error should be nil") + } + XCTAssertNil(error2) + XCTAssertNotNil(bPeerID) + XCTAssertNotNil(bPermanentInfo) + XCTAssertNotNil(bPermanentInfoSig) + + do { + assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) + print("A vouches for B, but doesn't provide any TLKShares") + let (_, _, errorVouchingWithoutTLKs) = + containerA.vouchSync(test: self, + peerID: bPeerID!, + permanentInfo: bPermanentInfo!, + permanentInfoSig: bPermanentInfoSig!, + stableInfo: bStableInfo!, + stableInfoSig: bStableInfoSig!, + ckksKeys: []) + XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares") + assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) + + print("A vouches for B, but doesn't only has provisional TLKs at the time") + let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee")) + provisionalManateeKeySet.newUpload = true + + let (_, _, errorVouchingWithProvisionalTLKs) = + containerA.vouchSync(test: self, + peerID: bPeerID!, + permanentInfo: bPermanentInfo!, + permanentInfoSig: bPermanentInfoSig!, + stableInfo: bStableInfo!, + stableInfoSig: bStableInfoSig!, + ckksKeys: [provisionalManateeKeySet]) + XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key") + assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) + + print("A vouches for B") + let (voucherData, voucherSig, error3) = + containerA.vouchSync(test: self, + peerID: bPeerID!, + permanentInfo: bPermanentInfo!, + permanentInfoSig: bPermanentInfoSig!, + stableInfo: bStableInfo!, + stableInfoSig: bStableInfoSig!, + ckksKeys: [self.manateeKeySet]) + XCTAssertNil(error3) + XCTAssertNotNil(voucherData) + XCTAssertNotNil(voucherSig) + + // As part of the vouch, A should have uploaded a tlkshare for B + assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) + + print("B joins") + let (peerID, _, _, _, error) = containerB.joinSync(test: self, + voucherData: voucherData!, + voucherSig: voucherSig!, + ckksKeys: [], + tlkShares: []) + XCTAssertNil(error) + XCTAssertEqual(peerID, bPeerID!) + } + + _ = containerA.dumpSync(test: self) + _ = containerB.dumpSync(test: self) + _ = containerC.dumpSync(test: self) + + print("preparing C") + let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, _, error4) = + containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + do { + let state = containerC.getStateSync(test: self) + XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer") + let secret = containerC.loadSecretSync(test: self, label: cPeerID!) + XCTAssertNotNil(secret, "secret should not be nil") + XCTAssertNil(error, "error should be nil") + } + XCTAssertNil(error4) + XCTAssertNotNil(cPeerID) + XCTAssertNotNil(cPermanentInfo) + XCTAssertNotNil(cPermanentInfoSig) + + do { + // C, when it joins, will create a new CKKS zone. It should also upload TLKShares for A and B. + let provisionalEngramKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Engram")) + provisionalEngramKeySet.newUpload = true + + assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) + assertNoTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram")) + + print("B vouches for C") + let (voucherData, voucherSig, error) = + containerB.vouchSync(test: self, + peerID: cPeerID!, + permanentInfo: cPermanentInfo!, + permanentInfoSig: cPermanentInfoSig!, + stableInfo: cStableInfo!, + stableInfoSig: cStableInfoSig!, + ckksKeys: [self.manateeKeySet]) + XCTAssertNil(error) + XCTAssertNotNil(voucherData) + XCTAssertNotNil(voucherSig) + + assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) + + print("C joins") + let (peerID, _, _, _, error2) = containerC.joinSync(test: self, + voucherData: voucherData!, + voucherSig: voucherSig!, + ckksKeys: [self.manateeKeySet, provisionalEngramKeySet], + tlkShares: []) + XCTAssertNil(error2) + XCTAssertEqual(peerID, cPeerID!) + + assertTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram")) + assertTLKShareFor(peerID: aPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram")) + assertTLKShareFor(peerID: bPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram")) + } + + print("A updates") + do { + let (_, error) = containerA.updateSync(test: self) + XCTAssertNil(error) + } + + do { + let state = containerA.getStateSync(test: self) + let a = state.peers[aPeerID!]! + XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!)) + } + + _ = containerA.dumpSync(test: self) + _ = containerB.dumpSync(test: self) + _ = containerC.dumpSync(test: self) + + XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + XCTAssertEqual(containerC.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + } + + func testPreApprovedJoinWithEnforceIDMSListNotSetBehavior() throws { + let description = tmpStoreDescription(name: "container.db") + let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) + let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish) + + XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + + print("preparing A") + let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aViewList, aPolicy, error) = + containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + XCTAssertNil(error) + XCTAssertNotNil(aPeerID) + XCTAssertNotNil(aPermanentInfo) + XCTAssertNotNil(aPermanentInfoSig) + + XCTAssertNotNil(aViewList, "Should have a view list coming back from a successful prepare") + XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare") + XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version") + + print("preparing B") + let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, _, _, error2) = + containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + do { + let state = containerB.getStateSync(test: self) + XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer") + let secret = containerB.loadSecretSync(test: self, label: bPeerID!) + XCTAssertNotNil(secret, "secret should not be nil") + XCTAssertNil(error, "error should be nil") + } + XCTAssertNil(error2) + XCTAssertNotNil(bPeerID) + XCTAssertNotNil(bPermanentInfo) + XCTAssertNotNil(bPermanentInfoSig) + + // Now, A establishes preapproving B + // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares + + let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory()) + XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info") + + print("establishing A") + do { + let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki()]) + XCTAssertNil(error) + XCTAssertNotNil(peerID) + } + + do { + assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) + + print("B joins by preapproval, and uploads all TLKShares that it has") + let (bJoinedPeerID, _, views, policy, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: []) + XCTAssertNil(bJoinedError, "Should be no error joining by preapproval") + XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join") + XCTAssertNotNil(views, "should have a list of views to use") + XCTAssertNotNil(policy, "Should have a policy back from preapprovedjoin") + + assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee")) + } + + _ = containerA.dumpSync(test: self) + _ = containerB.dumpSync(test: self) + + XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + } + + func testReloadingContainerIDMSListVariable() throws { + let store = tmpStoreDescription(name: "container.db") + let contextID = "contextID" + var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish) + + let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1") + do { + let state = container.getStateSync(test: self) + XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer") + let secret = container.loadSecretSync(test: self, label: peerID!) + XCTAssertNotNil(secret, "secret should not be nil") + XCTAssertNil(error, "error should be nil") + } + XCTAssertNotNil(peerID) + XCTAssertNotNil(permanentInfo) + XCTAssertNotNil(permanentInfoSig) + XCTAssertNil(error) + + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + + _ = container.dumpSync(test: self) + + do { + container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish) + XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown") + + } catch { + XCTFail("Creating container errored: \(error)") + } + + let (peerID2, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: []) + XCTAssertNil(error2) + XCTAssertNotNil(peerID2) + + _ = container.dumpSync(test: self) + } } diff --git a/keychain/ckks/CKKS.m b/keychain/ckks/CKKS.m index 934ff6a2..3b35e838 100644 --- a/keychain/ckks/CKKS.m +++ b/keychain/ckks/CKKS.m @@ -146,7 +146,8 @@ bool SecCKKSDisable() { } bool SecCKKSResetSyncing(void) { - [CKKSViewManager resetManager: true setTo: nil]; + // The function name is a bit of a lie, but it does the thing. + [OTManager resetManager:true to:nil]; return SecCKKSIsEnabled(); } @@ -378,13 +379,15 @@ void CKKSRegisterSyncStatusCallback(CFStringRef cfuuid, SecBoolCFErrorCallback c void SecCKKSPerformLocalResync() { #if OCTAGON - secnotice("ckks", "Local keychain was reset; performing local resync"); - [[CKKSViewManager manager] rpcResyncLocal:nil reply:^(NSError *result) { - if(result) { - secnotice("ckks", "Local keychain reset resync finished with an error: %@", result); - } else { - secnotice("ckks", "Local keychain reset resync finished successfully"); - } - }]; + if(SecCKKSIsEnabled()) { + secnotice("ckks", "Local keychain was reset; performing local resync"); + [[CKKSViewManager manager] rpcResyncLocal:nil reply:^(NSError *result) { + if(result) { + secnotice("ckks", "Local keychain reset resync finished with an error: %@", result); + } else { + secnotice("ckks", "Local keychain reset resync finished successfully"); + } + }]; + } #endif } diff --git a/keychain/ckks/CKKSAnalytics.h b/keychain/ckks/CKKSAnalytics.h index 6c80b5c5..b90f4252 100644 --- a/keychain/ckks/CKKSAnalytics.h +++ b/keychain/ckks/CKKSAnalytics.h @@ -40,6 +40,7 @@ extern NSString* const CKKSAnalyticsLastInCircle; extern NSString* const OctagonAnalyticsStateMachineState; extern NSString* const OctagonAnalyticIcloudAccountState; +extern NSString* const OctagonAnalyticCDPBitStatus; extern NSString* const OctagonAnalyticsTrustState; extern NSString* const OctagonAnalyticsAttemptedJoin; extern NSString* const OctagonAnalyticsLastHealthCheck; @@ -66,6 +67,12 @@ extern NSString* const OctagonAnalyticsKeychainSyncEnabled; extern NSString* const OctagonAnalyticsCloudKitProvisioned; extern NSString* const OctagonAnalyticsCloudKitEnabled; +extern NSString* const OctagonAnalyticsBottledUniqueTLKsRecovered; +extern NSString* const OctagonAnalyticsBottledTotalTLKShares; +extern NSString* const OctagonAnalyticsBottledTotalTLKSharesRecovered; +extern NSString* const OctagonAnalyticsBottledUniqueTLKsWithSharesCount; +extern NSString* const OctagonAnalyticsBottledTLKUniqueViewCount; + @class CKKSKeychainView; @protocol CKKSAnalyticsFailableEvent @@ -129,6 +136,7 @@ extern CKKSAnalyticsFailableEvent* const OctagonEventPreflightVouchWithBottle; extern CKKSAnalyticsFailableEvent* const OctagonEventVoucherWithBottle; /* inner: join with recovery key */ +extern CKKSAnalyticsFailableEvent* const OctagonEventPreflightVouchWithRecoveryKey; extern CKKSAnalyticsFailableEvent* const OctagonEventVoucherWithRecoveryKey; extern CKKSAnalyticsFailableEvent* const OctagonEventJoinRecoveryKeyValidationFailed; extern CKKSAnalyticsFailableEvent* const OctagonEventJoinRecoveryKeyFailed; diff --git a/keychain/ckks/CKKSAnalytics.m b/keychain/ckks/CKKSAnalytics.m index dea0502b..6547366d 100644 --- a/keychain/ckks/CKKSAnalytics.m +++ b/keychain/ckks/CKKSAnalytics.m @@ -49,6 +49,8 @@ NSString* const CKKSAnalyticsLastInCircle = @"lastInCircle"; NSString* const OctagonAnalyticsStateMachineState = @"OASMState"; NSString* const OctagonAnalyticIcloudAccountState = @"OAiC"; +NSString* const OctagonAnalyticCDPBitStatus = @"OACDPStatus"; + NSString* const OctagonAnalyticsTrustState = @"OATrust"; NSString* const OctagonAnalyticsAttemptedJoin = @"OAAttemptedJoin"; NSString* const OctagonAnalyticsLastHealthCheck = @"OAHealthCheck"; @@ -62,6 +64,12 @@ NSString* const OctagonAnalyticsCoreFollowupLastFailureTime = @"OACFULastFailure NSString* const OctagonAnalyticsPrerecordPending = @"OAPrerecordPending"; NSString* const OctagonAnalyticsCDPStateRun = @"OACDPStateRun"; +NSString* const OctagonAnalyticsBottledUniqueTLKsRecovered = @"OABottledUniqueTLKsRecoveredCount"; +NSString* const OctagonAnalyticsBottledTotalTLKShares = @"OABottledTotalTLKSharesCount"; +NSString* const OctagonAnalyticsBottledTotalTLKSharesRecovered = @"OABottledTotalTLKSharesRecoveredCount"; +NSString* const OctagonAnalyticsBottledUniqueTLKsWithSharesCount = @"OABottledUniqueTLKsWithSharesCount"; +NSString* const OctagonAnalyticsBottledTLKUniqueViewCount = @"OABottledTLKUniqueViewCount"; + NSString* const OctagonAnalyticsHaveMachineID = @"OAMIDPresent"; NSString* const OctagonAnalyticsMIDOnMemoizedList = @"OAMIDOnList"; NSString* const OctagonAnalyticsPeersWithMID = @"OAPeersWithMID"; @@ -128,6 +136,7 @@ CKKSAnalyticsFailableEvent* const OctagonEventJoinWithVoucher = (CKKSAnalyticsFa CKKSAnalyticsFailableEvent* const OctagonEventPreflightVouchWithBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventPreflightVouchWithBottle"; CKKSAnalyticsFailableEvent* const OctagonEventVoucherWithBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventVoucherWithBottle"; +CKKSAnalyticsFailableEvent* const OctagonEventPreflightVouchWithRecoveryKey = (CKKSAnalyticsFailableEvent*)@"OctagonEventPreflightVouchWithRecoveryKey";; CKKSAnalyticsFailableEvent* const OctagonEventVoucherWithRecoveryKey = (CKKSAnalyticsFailableEvent*)@"OctagonEventVoucherWithRecoveryKey"; CKKSAnalyticsFailableEvent* const OctagonEventSetRecoveryKey = (CKKSAnalyticsFailableEvent*)@"OctagonEventSetRecoveryKey"; diff --git a/keychain/ckks/CKKSIncomingQueueEntry.m b/keychain/ckks/CKKSIncomingQueueEntry.m index 3bb5e78d..0aa13111 100644 --- a/keychain/ckks/CKKSIncomingQueueEntry.m +++ b/keychain/ckks/CKKSIncomingQueueEntry.m @@ -88,7 +88,7 @@ error: (NSError * __autoreleasing *) error { NSMutableDictionary* whereDict = [@{@"state": CKKSNilToNSNull(state), @"ckzone":CKKSNilToNSNull(zoneID.zoneName)} mutableCopy]; if(uuid) { - whereDict[@"UUID"] = [CKKSSQLWhereObject op:@">" stringValue:uuid]; + whereDict[@"UUID"] = [CKKSSQLWhereValue op:CKKSSQLWhereComparatorGreaterThan value:uuid]; } return [self fetch:n where:whereDict diff --git a/keychain/ckks/CKKSIncomingQueueOperation.m b/keychain/ckks/CKKSIncomingQueueOperation.m index f53425a1..e5c96b49 100644 --- a/keychain/ckks/CKKSIncomingQueueOperation.m +++ b/keychain/ckks/CKKSIncomingQueueOperation.m @@ -652,12 +652,10 @@ NSError* error = NULL; NSDictionary* queryAttributes = @{(__bridge NSString*) kSecClass: (__bridge NSString*) classP->name, (__bridge NSString*) kSecAttrUUID: iqe.uuid, - (__bridge NSString*) kSecAttrSyncViewHint: ckks.zoneID.zoneName, (__bridge NSString*) kSecAttrSynchronizable: @(YES)}; ckksnotice("ckksincoming", ckks, "trying to delete with query: %@", queryAttributes); Query *q = query_create_with_limit( (__bridge CFDictionaryRef) queryAttributes, NULL, kSecMatchUnlimited, &cferror); - if(cferror) { ckkserror("ckksincoming", ckks, "couldn't create query: %@", cferror); SecTranslateError(&error, cferror); diff --git a/keychain/ckks/CKKSKey.m b/keychain/ckks/CKKSKey.m index 10e88f36..4f497ee0 100644 --- a/keychain/ckks/CKKSKey.m +++ b/keychain/ckks/CKKSKey.m @@ -459,7 +459,11 @@ } + (NSArray*)selfWrappedKeys:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { - return [self allWhere: @{@"UUID": [CKKSSQLWhereObject op:@"=" string:@"parentKeyUUID"], @"state": SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error:error]; + return [self allWhere: @{@"UUID": [CKKSSQLWhereColumn op:CKKSSQLWhereComparatorEquals + column:CKKSSQLWhereColumnNameParentKeyUUID], + @"state": SecCKKSProcessedStateLocal, + @"ckzone":zoneID.zoneName} + error:error]; } + (instancetype _Nullable)currentKeyForClass:(CKKSKeyClass*)keyclass diff --git a/keychain/ckks/CKKSKeychainView.h b/keychain/ckks/CKKSKeychainView.h index ac6e0339..5e090e1b 100644 --- a/keychain/ckks/CKKSKeychainView.h +++ b/keychain/ckks/CKKSKeychainView.h @@ -175,10 +175,10 @@ NS_ASSUME_NONNULL_BEGIN /* Synchronous operations */ - (void)handleKeychainEventDbConnection:(SecDbConnectionRef)dbconn + source:(SecDbTransactionSource)txionSource added:(SecDbItemRef _Nullable)added deleted:(SecDbItemRef _Nullable)deleted - rateLimiter:(CKKSRateLimiter*)rateLimiter - syncCallback:(SecBoolNSErrorCallback)syncCallback; + rateLimiter:(CKKSRateLimiter*)rateLimiter; - (void)setCurrentItemForAccessGroup:(NSData*)newItemPersistentRef hash:(NSData*)newItemSHA1 @@ -208,8 +208,6 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)otherDevicesReportHavingTLKs:(CKKSCurrentKeySet*)keyset; -- (NSSet*)_onqueuePriorityOutgoingQueueUUIDs; - /* Asynchronous kickoffs */ - (CKKSOutgoingQueueOperation*)processOutgoingQueue:(CKOperationGroup* _Nullable)ckoperationGroup; @@ -304,6 +302,8 @@ NS_ASSUME_NONNULL_BEGIN // Please don't use these unless you're an Operation in this package @property NSHashTable* incomingQueueOperations; @property NSHashTable* outgoingQueueOperations; + +@property NSHashTable* scanLocalItemsOperations; @property CKKSScanLocalItemsOperation* initialScanOperation; // Returns the current state of this view, fastStatus is the same, but as name promise, no expensive calculations diff --git a/keychain/ckks/CKKSKeychainView.m b/keychain/ckks/CKKSKeychainView.m index 3e7a16fe..6a9589a1 100644 --- a/keychain/ckks/CKKSKeychainView.m +++ b/keychain/ckks/CKKSKeychainView.m @@ -115,8 +115,6 @@ @property CKKSResultOperation* processIncomingQueueAfterNextUnlockOperation; @property CKKSResultOperation* resultsOfNextIncomingQueueOperationOperation; -@property NSMutableDictionary* pendingSyncCallbacks; - // An extra queue for semaphore-waiting-based NSOperations @property NSOperationQueue* waitingQueue; @@ -161,6 +159,7 @@ _incomingQueueOperations = [NSHashTable weakObjectsHashTable]; _outgoingQueueOperations = [NSHashTable weakObjectsHashTable]; + _scanLocalItemsOperations = [NSHashTable weakObjectsHashTable]; _cloudkitDeleteZoneOperations = [NSHashTable weakObjectsHashTable]; _localResetOperations = [NSHashTable weakObjectsHashTable]; _keysetProviderOperations = [NSHashTable weakObjectsHashTable]; @@ -213,8 +212,6 @@ }]; - _pendingSyncCallbacks = [[NSMutableDictionary alloc] init]; - _lockStateTracker = lockStateTracker; _savedTLKNotifier = savedTLKNotifier; @@ -966,7 +963,11 @@ #if DEBUG // During testing, keep the developer honest: this function should always have the self identities, unless the account has lost trust - if(self.trustStatus == CKKSAccountStatusAvailable && ![state isEqualToString:SecCKKSZoneKeyStateLoggedOut]) { + // But, beginTrustedOperation is currently racy: if it acquires the queue and sets the trust bit between fetching the trust states and getting on the queue, + // this will fire. So, release SecCKKSZoneKeyStateInitialized from this check. + if(self.trustStatus == CKKSAccountStatusAvailable + && ![state isEqualToString:SecCKKSZoneKeyStateLoggedOut] + && ![state isEqualToString:SecCKKSZoneKeyStateInitialized]) { bool hasSelfIdentities = false; NSAssert(self.currentTrustStates.count > 0, @"Should have at least one trust state"); for(CKKSPeerProviderState* state in self.currentTrustStates) { @@ -2022,10 +2023,11 @@ } - (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn + source:(SecDbTransactionSource)txionSource added: (SecDbItemRef) added deleted: (SecDbItemRef) deleted rateLimiter: (CKKSRateLimiter*) rateLimiter - syncCallback: (SecBoolNSErrorCallback) syncCallback { +{ if(!SecCKKSIsEnabled()) { ckksnotice("ckks", self, "Skipping handleKeychainEventDbConnection due to disabled CKKS"); return; @@ -2080,6 +2082,10 @@ return; } + if(txionSource == kSecDbSOSTransaction) { + ckksnotice("ckks", self, "Received an incoming %@ from SOS", isAdd ? @"addition" : (isModify ? @"modification" : @"deletion")); + } + // Our caller gave us a database connection. We must get on the local queue to ensure atomicity // Note that we're at the mercy of the surrounding db transaction, so don't try to rollback here [self dispatchSyncWithConnection: dbconn block: ^bool { @@ -2091,21 +2097,15 @@ self.droppedItems = true; ckksnotice("ckks", self, "Dropping sync item modification due to CK account state; will scan to find changes later"); + // We're positively not logged into CloudKit, and therefore don't expect this item to be synced anytime particularly soon. + NSString* uuid = (__bridge NSString*)SecDbItemGetValue(added ? added : deleted, &v10itemuuid, NULL); + + SecBoolNSErrorCallback syncCallback = [[CKKSViewManager manager] claimCallbackForUUID:uuid]; if(syncCallback) { - // We're positively not logged into CloudKit, and therefore don't expect this item to be synced anytime particularly soon. - [self callSyncCallbackWithErrorNoAccount: syncCallback]; + [CKKSViewManager callSyncCallbackWithErrorNoAccount: syncCallback]; } - return true; - } - // Always record the callback, even if we can't encrypt the item right now. Maybe we'll get to it soon! - if(syncCallback) { - CFErrorRef cferror = NULL; - NSString* uuid = (__bridge_transfer NSString*) CFRetain(SecDbItemGetValue(added, &v10itemuuid, &cferror)); - if(!cferror && uuid) { - self.pendingSyncCallbacks[uuid] = syncCallback; - } - CFReleaseNull(cferror); + return true; } CKKSOutgoingQueueEntry* oqe = nil; @@ -2486,11 +2486,6 @@ } } -- (NSSet*)_onqueuePriorityOutgoingQueueUUIDs -{ - return [self.pendingSyncCallbacks.allKeys copy]; -} - - (CKKSOutgoingQueueOperation*)processOutgoingQueue:(CKOperationGroup*)ckoperationGroup { return [self processOutgoingQueueAfter:nil ckoperationGroup:ckoperationGroup]; } @@ -2533,6 +2528,8 @@ [self.outgoingQueueOperationScheduler triggerAt:requiredDelay]; + [op linearDependencies:self.outgoingQueueOperations]; + [self scheduleOperation: op]; ckksnotice("ckksoutgoing", self, "Scheduled %@", op); return op; @@ -2605,8 +2602,25 @@ return [self scanLocalItems:operationName ckoperationGroup:nil after:nil]; } -- (CKKSScanLocalItemsOperation*)scanLocalItems:(NSString*)operationName ckoperationGroup:(CKOperationGroup*)operationGroup after:(NSOperation*)after { - CKKSScanLocalItemsOperation* scanOperation = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:operationGroup]; +- (CKKSScanLocalItemsOperation*)scanLocalItems:(NSString*)operationName + ckoperationGroup:(CKOperationGroup*)operationGroup + after:(NSOperation*)after +{ + CKKSScanLocalItemsOperation* scanOperation = (CKKSScanLocalItemsOperation*)[self findFirstPendingOperation:self.scanLocalItemsOperations]; + + if(scanOperation) { + [scanOperation addNullableDependency:after]; + + // check (again) for race condition; if the op has started we need to add another (for the dependency) + if([scanOperation isPending]) { + scanOperation.ckoperationGroup = operationGroup; + + scanOperation.name = [NSString stringWithFormat:@"%@::%@", scanOperation.name, operationName]; + return scanOperation; + } + } + + scanOperation = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:operationGroup]; scanOperation.name = operationName; [scanOperation addNullableDependency:self.lastFixupOperation]; @@ -2614,7 +2628,9 @@ [scanOperation addNullableDependency:self.keyStateReadyDependency]; [scanOperation addNullableDependency:after]; - [self scheduleOperation: scanOperation]; + [scanOperation linearDependencies:self.scanLocalItemsOperations]; + + [self scheduleOperation:scanOperation]; return scanOperation; } @@ -3333,10 +3349,9 @@ if([state isEqualToString: SecCKKSStateDeleted]) { // Hurray, this must be a success - SecBoolNSErrorCallback callback = self.pendingSyncCallbacks[oqe.uuid]; - if(callback) { - callback(true, nil); - self.pendingSyncCallbacks[oqe.uuid] = nil; + SecBoolNSErrorCallback syncCallback = [[CKKSViewManager manager] claimCallbackForUUID:oqe.uuid]; + if(syncCallback) { + syncCallback(true, nil); } [oqe deleteFromDatabase: &localerror]; @@ -3387,10 +3402,9 @@ - (bool)_onqueueErrorOutgoingQueueEntry: (CKKSOutgoingQueueEntry*) oqe itemError: (NSError*) itemError error: (NSError* __autoreleasing*) error { dispatch_assert_queue(self.queue); - SecBoolNSErrorCallback callback = self.pendingSyncCallbacks[oqe.uuid]; + SecBoolNSErrorCallback callback = [[CKKSViewManager manager] claimCallbackForUUID:oqe.uuid]; if(callback) { callback(false, itemError); - self.pendingSyncCallbacks[oqe.uuid] = nil; } NSError* localerror = nil; @@ -3736,12 +3750,6 @@ [self.loggedOut fulfill]; [self.accountStateKnown fulfill]; - // Tell all pending sync clients that we don't expect to ever sync - for(NSString* callbackUUID in self.pendingSyncCallbacks.allKeys) { - [self callSyncCallbackWithErrorNoAccount:self.pendingSyncCallbacks[callbackUUID]]; - self.pendingSyncCallbacks[callbackUUID] = nil; - } - return true; }]; }]; @@ -3749,16 +3757,6 @@ [self scheduleAccountStatusOperation: logout]; } -- (void)callSyncCallbackWithErrorNoAccount:(SecBoolNSErrorCallback)syncCallback { - CKKSAccountStatus accountStatus = self.accountStatus; - dispatch_async(self.queue, ^{ - syncCallback(false, [NSError errorWithDomain:@"securityd" - code:errSecNotLoggedIn - userInfo:@{NSLocalizedDescriptionKey: - [NSString stringWithFormat: @"No iCloud account available(%d); item is not expected to sync", (int)accountStatus]}]); - }); -} - #pragma mark - Trust operations - (void)beginTrustedOperation:(NSArray>*)peerProviders @@ -3890,6 +3888,25 @@ { [self.launch addEvent:@"changes-fetched"]; + if(changedRecords.count == 0 && deletedRecords.count == 0 && !moreComing && !resync) { + // Early-exit, so we don't pick up the account keys or kick off an IncomingQueue operation for no changes + [self dispatchSync:^bool { + ckkserror("ckksfetch", self, "No record changes in this fetch"); + + NSError* error = nil; + CKKSZoneStateEntry* state = [CKKSZoneStateEntry state:self.zoneName]; + state.lastFetchTime = [NSDate date]; // The last fetch happened right now! + state.changeToken = newChangeToken; + state.moreRecordsInCloudKit = moreComing; + [state saveToDatabase:&error]; + if(error) { + ckkserror("ckksfetch", self, "Couldn't save new server change token: %@", error); + } + return true; + }]; + return; + } + [self dispatchSyncWithAccountKeys:^bool{ for (CKRecord* record in changedRecords) { [self _onqueueCKRecordChanged:record resync:resync]; @@ -4108,6 +4125,13 @@ [self.incomingQueueOperations removeAllObjects]; } + @synchronized(self.scanLocalItemsOperations) { + for(NSOperation* op in self.scanLocalItemsOperations) { + [op cancel]; + } + [self.scanLocalItemsOperations removeAllObjects]; + } + [super cancelAllOperations]; } @@ -4118,6 +4142,7 @@ [self.keyStateNonTransientDependency cancel]; [self.zoneChangeFetcher cancel]; [self.notifyViewChangedScheduler cancel]; + [self.pokeKeyStateMachineScheduler cancel]; [self cancelPendingOperations]; diff --git a/keychain/ckks/CKKSNewTLKOperation.m b/keychain/ckks/CKKSNewTLKOperation.m index 2cc46843..9cf42a1b 100644 --- a/keychain/ckks/CKKSNewTLKOperation.m +++ b/keychain/ckks/CKKSNewTLKOperation.m @@ -83,6 +83,7 @@ } // Synchronous, on some thread. Get back on the CKKS queue for SQL thread-safety. + __block bool enterWaitForTLKUpload = false; [ckks dispatchSyncWithAccountKeys: ^bool{ if(self.cancelled) { ckksnotice("ckkstlk", ckks, "CKKSNewTLKOperation cancelled, quitting"); @@ -246,10 +247,19 @@ self.keyset = keyset; - [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForTLKUpload withError:nil]; - + // Finish this transaction to cause a keychiain db commit + // This means that if we provide the new keys to another thread, they'll be able to immediately load them from the keychain + enterWaitForTLKUpload = true; return true; }]; + + if(enterWaitForTLKUpload) { + // And move the CKKS state machine: + [ckks dispatchSyncWithAccountKeys: ^bool{ + [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForTLKUpload withError:nil]; + return true; + }]; + } } - (void)cancel { diff --git a/keychain/ckks/CKKSOutgoingQueueOperation.m b/keychain/ckks/CKKSOutgoingQueueOperation.m index 940cf26a..087ad6a9 100644 --- a/keychain/ckks/CKKSOutgoingQueueOperation.m +++ b/keychain/ckks/CKKSOutgoingQueueOperation.m @@ -26,6 +26,7 @@ #import #import +#import "keychain/ckks/CKKSViewManager.h" #import "CKKSKeychainView.h" #import "CKKSCurrentKeyPointer.h" #import "CKKSOutgoingQueueOperation.h" @@ -90,7 +91,7 @@ NSError* error = nil; - NSSet* priorityUUIDs = [ckks _onqueuePriorityOutgoingQueueUUIDs]; + NSSet* priorityUUIDs = [[CKKSViewManager manager] pendingCallbackUUIDs]; NSMutableArray* priorityEntries = [NSMutableArray array]; NSMutableSet* priorityEntryUUIDs = [NSMutableSet set]; diff --git a/keychain/ckks/CKKSSQLDatabaseObject.h b/keychain/ckks/CKKSSQLDatabaseObject.h index 85f6ad5c..a4698b38 100644 --- a/keychain/ckks/CKKSSQLDatabaseObject.h +++ b/keychain/ckks/CKKSSQLDatabaseObject.h @@ -135,13 +135,41 @@ NS_ASSUME_NONNULL_BEGIN @end // Helper class to use with where clauses -// If you pass in one of these instead of a concrete value, its substring will be used directly, instead of binding the value as a named parameter -@interface CKKSSQLWhereObject : NSObject -@property NSString* sqlOp; -@property NSString* contents; -- (instancetype)initWithOperation:(NSString*)op string:(NSString*)str; -+ (instancetype)op:(NSString*)op string:(NSString*)str; -+ (instancetype)op:(NSString*)op stringValue:(NSString*)str; // Will add single quotes around your value. +// If you pass in one of these in a where dictionary instead of a concrete value, columnName will be +// used directly, instead of binding as a named parameter. Therefore, it's essential to use +// compile-time constants for both fields. + +typedef NS_ENUM(uint64_t, CKKSSQLWhereComparator) { + CKKSSQLWhereComparatorEquals = 1, + CKKSSQLWhereComparatorNotEquals = 2, + CKKSSQLWhereComparatorGreaterThan = 3, + CKKSSQLWhereComparatorLessThan = 4, +}; + +NSString* CKKSSQLWhereComparatorAsString(CKKSSQLWhereComparator comparator); + +// This typedef is to ensure that CKKSSQLWhereColumn can only ever produce static strings +typedef NS_ENUM(uint64_t, CKKSSQLWhereColumnName) { + CKKSSQLWhereColumnNameUUID = 1, + CKKSSQLWhereColumnNameParentKeyUUID = 2, +}; +NSString* CKKSSQLWhereColumnNameAsString(CKKSSQLWhereColumnName columnName); + +@interface CKKSSQLWhereColumn : NSObject +@property CKKSSQLWhereComparator sqlOp; +@property CKKSSQLWhereColumnName columnName; +- (instancetype)initWithOperation:(CKKSSQLWhereComparator)op columnName:(CKKSSQLWhereColumnName)column; ++ (instancetype)op:(CKKSSQLWhereComparator)op column:(CKKSSQLWhereColumnName)columnName; @end +// Unlike CKKSSQLWhereColumn, this will insert the value as a parameter in a prepared statement +// but gives you the flexbility to inject a sqlOp. sqlOp must be a compile-time constant. +@interface CKKSSQLWhereValue : NSObject +@property CKKSSQLWhereComparator sqlOp; +@property NSString* value; +- (instancetype)initWithOperation:(CKKSSQLWhereComparator)op value:(NSString*)value; ++ (instancetype)op:(CKKSSQLWhereComparator)op value:(NSString*)value; +@end + + NS_ASSUME_NONNULL_END diff --git a/keychain/ckks/CKKSSQLDatabaseObject.m b/keychain/ckks/CKKSSQLDatabaseObject.m index 356424cc..f8c70d96 100644 --- a/keychain/ckks/CKKSSQLDatabaseObject.m +++ b/keychain/ckks/CKKSSQLDatabaseObject.m @@ -140,10 +140,17 @@ [whereClause appendFormat: @" AND "]; } - if([value class] == [CKKSSQLWhereObject class]) { - // Use this string verbatim - CKKSSQLWhereObject* whereob = (CKKSSQLWhereObject*) value; - [whereClause appendFormat: @"%@%@%@", key, whereob.sqlOp, whereob.contents]; + if([value class] == [CKKSSQLWhereValue class]) { + CKKSSQLWhereValue* obj = (CKKSSQLWhereValue*)value; + [whereClause appendFormat: @"%@%@(?)", key, CKKSSQLWhereComparatorAsString(obj.sqlOp)]; + + } else if([value class] == [CKKSSQLWhereColumn class]) { + CKKSSQLWhereColumn* obj = (CKKSSQLWhereColumn*)value; + [whereClause appendFormat: @"%@%@%@", + key, + CKKSSQLWhereComparatorAsString(obj.sqlOp), + CKKSSQLWhereColumnNameAsString(obj.columnName)]; + } else { [whereClause appendFormat: @"%@=(?)", key]; } @@ -193,6 +200,22 @@ return orderByClause; } ++ (void)bindWhereClause:(sqlite3_stmt*)stmt whereDict:(NSDictionary*)whereDict cferror:(CFErrorRef*)cferror +{ + __block int whereObjectsSkipped = 0; + [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) { + if([whereDict[key] class] == [CKKSSQLWhereValue class]) { + CKKSSQLWhereValue* obj = (CKKSSQLWhereValue*)whereDict[key]; + SecDbBindObject(stmt, (int)(i+1-whereObjectsSkipped), (__bridge CFStringRef)obj.value, cferror); + } else if([whereDict[key] class] == [CKKSSQLWhereColumn class]) { + // skip + whereObjectsSkipped += 1; + } else { + SecDbBindObject(stmt, (int)(i+1-whereObjectsSkipped), (__bridge CFStringRef) whereDict[key], cferror); + } + }]; +} + + (bool) deleteFromTable: (NSString*) table where: (NSDictionary*) whereDict connection:(SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error { __block CFErrorRef cferror = NULL; @@ -201,15 +224,7 @@ NSString * sql = [[NSString alloc] initWithFormat: @"DELETE FROM %@%@;", table, whereClause]; SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) { - __block int whereObjectsSkipped = 0; - - [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) { - if([whereDict[key] class] != [CKKSSQLWhereObject class]) { - SecDbBindObject(stmt, (int)(i+1-whereObjectsSkipped), (__bridge CFStringRef) whereDict[key], &cferror); - } else { - whereObjectsSkipped += 1; - } - }]; + [self bindWhereClause:stmt whereDict:whereDict cferror:&cferror]; SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) { }); @@ -254,14 +269,7 @@ NSString * sql = [[NSString alloc] initWithFormat: @"SELECT %@ FROM %@%@%@%@%@;", columns, table, whereClause, groupByClause, orderByClause, limitClause]; SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) { - __block int whereObjectsSkipped = 0; - [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) { - if([whereDict[key] class] != [CKKSSQLWhereObject class]) { - SecDbBindObject(stmt, (int)(i+1-whereObjectsSkipped), (__bridge CFStringRef) whereDict[key], &cferror); - } else { - whereObjectsSkipped += 1; - } - }]; + [self bindWhereClause:stmt whereDict:whereDict cferror:&cferror]; SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) { __block NSMutableDictionary* row = [[NSMutableDictionary alloc] init]; @@ -315,11 +323,7 @@ NSString* sql = [[NSString alloc] initWithFormat:@"SELECT %@ FROM %@%@", columns, quotedTable, whereClause]; SecDbPrepare(dbconn, (__bridge CFStringRef)sql, &cferror, ^(sqlite3_stmt* stmt) { - [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL* _Nonnull stop) { - if ([whereDict[key] class] != [CKKSSQLWhereObject class]) { - SecDbBindObject(stmt, (int)(i+1), (__bridge CFStringRef) whereDict[key], &cferror); - } - }]; + [self bindWhereClause:stmt whereDict:whereDict cferror:&cferror]; SecDbStep(dbconn, stmt, &cferror, ^(bool*stop) { __block NSMutableDictionary* row = [[NSMutableDictionary alloc] init]; @@ -517,24 +521,61 @@ } @end -#pragma mark - CKKSSQLWhereObject +NSString* CKKSSQLWhereComparatorAsString(CKKSSQLWhereComparator comparator) +{ + switch(comparator) { + case CKKSSQLWhereComparatorEquals: + return @"="; + case CKKSSQLWhereComparatorNotEquals: + return @"<>"; + case CKKSSQLWhereComparatorGreaterThan: + return @">"; + case CKKSSQLWhereComparatorLessThan: + return @"<"; + } +} -@implementation CKKSSQLWhereObject -- (instancetype)initWithOperation:(NSString*)op string: (NSString*) str { - if(self = [super init]) { +NSString* CKKSSQLWhereColumnNameAsString(CKKSSQLWhereColumnName columnName) +{ + switch(columnName) { + case CKKSSQLWhereColumnNameUUID: + return @"uuid"; + case CKKSSQLWhereColumnNameParentKeyUUID: + return @"parentKeyUUID"; + } +} + +#pragma mark - CKKSSQLWhereColumn + +@implementation CKKSSQLWhereColumn +- (instancetype)initWithOperation:(CKKSSQLWhereComparator)op columnName:(CKKSSQLWhereColumnName)column +{ + if((self = [super init])) { _sqlOp = op; - _contents = str; + _columnName = column; } return self; } - -+ (instancetype)op:(NSString*) op string: (NSString*) str { - return [[CKKSSQLWhereObject alloc] initWithOperation:op string: str]; ++ (instancetype)op:(CKKSSQLWhereComparator)op column:(CKKSSQLWhereColumnName)columnName +{ + return [[CKKSSQLWhereColumn alloc] initWithOperation:op columnName:columnName]; } +@end -+ (instancetype)op:(NSString*) op stringValue: (NSString*) str { - return [[CKKSSQLWhereObject alloc] initWithOperation:op string:[NSString stringWithFormat:@"'%@'", str]]; -} +#pragma mark - CKKSSQLWhereObject +@implementation CKKSSQLWhereValue +- (instancetype)initWithOperation:(CKKSSQLWhereComparator)op value:(NSString*)value +{ + if((self = [super init])) { + _sqlOp = op; + _value = value; + } + return self; +} ++ (instancetype)op:(CKKSSQLWhereComparator)op value:(NSString*)value +{ + return [[CKKSSQLWhereValue alloc] initWithOperation:op value:value]; +} @end diff --git a/keychain/ckks/CKKSScanLocalItemsOperation.h b/keychain/ckks/CKKSScanLocalItemsOperation.h index a35a240d..0ad03e57 100644 --- a/keychain/ckks/CKKSScanLocalItemsOperation.h +++ b/keychain/ckks/CKKSScanLocalItemsOperation.h @@ -33,6 +33,8 @@ NS_ASSUME_NONNULL_BEGIN @class CKKSEgoManifest; @interface CKKSScanLocalItemsOperation : CKKSResultOperation +@property CKOperationGroup* ckoperationGroup; + @property (weak) CKKSKeychainView* ckks; @property size_t recordsFound; diff --git a/keychain/ckks/CKKSScanLocalItemsOperation.m b/keychain/ckks/CKKSScanLocalItemsOperation.m index 7d285ad6..3c3a6afe 100644 --- a/keychain/ckks/CKKSScanLocalItemsOperation.m +++ b/keychain/ckks/CKKSScanLocalItemsOperation.m @@ -22,6 +22,9 @@ */ #if OCTAGON +#import +#import +#import #import "keychain/ckks/CKKSAnalytics.h" #import "keychain/ckks/CKKSKeychainView.h" @@ -47,7 +50,6 @@ #import @interface CKKSScanLocalItemsOperation () -@property CKOperationGroup* ckoperationGroup; @property (assign) NSUInteger processedItems; @end @@ -66,6 +68,49 @@ return self; } +// returns true if the currently loaded TPPolicy in the view manager says that only items with a VewHint +// matching the viewName belong to this view. +- (BOOL)policyRecommendsOnlyViewHintItems:(CKKSKeychainView*)ckks +{ + // Early-exit for when feature is not on: + if(![[CKKSViewManager manager] useCKKSViewsFromPolicy]) { + return YES; + } + + TPPBPolicyKeyViewMapping* viewRule = nil; + + // If there's more than one rule matching this view, then exit with NO. + for(TPPBPolicyKeyViewMapping* mapping in [CKKSViewManager manager].policy.keyViewMapping) { + if([mapping.view isEqualToString:ckks.zoneName]) { + if(viewRule == nil) { + viewRule = mapping; + } else { + // Too many rules for this view! Don't perform optimization. + ckksnotice("ckksscan", ckks, "Too many policy rules for view %@", ckks.zoneName); + return NO; + } + } + } + + if(viewRule.hasMatchingRule && + viewRule.matchingRule.andsCount == 0 && + viewRule.matchingRule.orsCount == 0 && + !viewRule.matchingRule.hasNot && + !viewRule.matchingRule.hasExists && + viewRule.matchingRule.hasMatch) { + if([@"vwht" isEqualToString:viewRule.matchingRule.match.fieldName] && + [viewRule.matchingRule.match.regex isEqualToString:[NSString stringWithFormat:@"^%@$", ckks.zoneName]]) { + return YES; + } else { + ckksnotice("ckksscan", ckks, "Policy view rule is not a match against viewhint: %@", viewRule); + } + } else { + ckksnotice("ckksscan", ckks, "Policy view rule is of unknown type: %@", viewRule); + } + + return NO; +} + - (void) main { // Take a strong reference. CKKSKeychainView* ckks = self.ckks; @@ -103,15 +148,22 @@ continue; } - NSDictionary* queryAttributes = @{(__bridge NSString*) kSecClass: (__bridge NSString*) (*class)->name, - (__bridge NSString*) kSecReturnRef: @(YES), - (__bridge NSString*) kSecAttrSynchronizable: @(YES), - (__bridge NSString*) kSecAttrTombstone: @(NO), - // This works ~as long as~ item views are chosen by view hint only. It's a significant perf win, though. - // SpinTracer: CKKSScanLocalItemsOperation expensive on M8 machines - (__bridge NSString*) kSecAttrSyncViewHint: ckks.zoneName, - }; - ckksinfo("ckksscan", ckks, "Scanning all synchronizable items for: %@", queryAttributes); + // As a performance optimization, if the current policy says that this view only includes items by viewhint, + // add that to the query. + BOOL limitToViewHint = [self policyRecommendsOnlyViewHintItems:ckks]; + + NSMutableDictionary* queryAttributes = [ + @{(__bridge NSString*) kSecClass: (__bridge NSString*) (*class)->name, + (__bridge NSString*) kSecReturnRef: @(YES), + (__bridge NSString*) kSecAttrSynchronizable: @(YES), + (__bridge NSString*) kSecAttrTombstone: @(NO), + } mutableCopy]; + + if(limitToViewHint) { + queryAttributes[(__bridge NSString*)kSecAttrSyncViewHint] = ckks.zoneName; + } + + ckksnotice("ckksscan", ckks, "Scanning all synchronizable %@ items(%@) for: %@", (__bridge NSString*)(*class)->name, self.name, queryAttributes); Query *q = query_create_with_limit( (__bridge CFDictionaryRef) queryAttributes, NULL, kSecMatchUnlimited, &cferror); bool ok = false; diff --git a/keychain/ckks/CKKSViewManager.h b/keychain/ckks/CKKSViewManager.h index cd6ac8cd..f19cccab 100644 --- a/keychain/ckks/CKKSViewManager.h +++ b/keychain/ckks/CKKSViewManager.h @@ -65,14 +65,15 @@ NS_ASSUME_NONNULL_BEGIN @property id sosPeerAdapter; -@property (nullable) TPPolicy* policy; +@property (readonly, nullable) TPPolicy* policy; -@property NSMutableDictionary* views; +@property (readonly) NSMutableDictionary* views; -- (instancetype)initWithContainerName:(NSString*)containerName - usePCS:(bool)usePCS - sosAdapter:(id _Nullable)sosAdapter - cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies; +- (instancetype)initWithContainer:(CKContainer*)container + sosAdapter:(id _Nullable)sosAdapter + accountStateTracker:(CKKSAccountStateTracker*)accountTracker + lockStateTracker:(CKKSLockStateTracker*)lockStateTracker + cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies; - (CKKSKeychainView*)findView:(NSString*)viewName; - (CKKSKeychainView*)findOrCreateView:(NSString*)viewName; @@ -85,7 +86,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)setupAnalytics; -- (NSString*)viewNameForItem:(SecDbItemRef)item; +- (NSString* _Nullable)viewNameForItem:(SecDbItemRef)item; - (void)handleKeychainEventDbConnection:(SecDbConnectionRef)dbconn source:(SecDbTransactionSource)txionSource @@ -112,29 +113,30 @@ NS_ASSUME_NONNULL_BEGIN // Cancels pending operations owned by this view manager - (void)cancelPendingOperations; -// Use these to acquire (and set) the singleton + (instancetype)manager; -+ (instancetype _Nullable)resetManager:(bool)reset setTo:(CKKSViewManager* _Nullable)obj; // Called by XPC every 24 hours - (void)xpc24HrNotification; -/* White-box testing only */ -- (CKKSKeychainView*)restartZone:(NSString*)viewName; - -// Returns the viewList for a CKKSViewManager +// Returns the current set of views - (NSSet*)viewList; - (NSSet*)defaultViewList; -- (void)setViewList:(NSSet* _Nullable)newViewList; +// Call this to set the syncing views+policy that this manager will use. +// If beginCloudKitOperationOfAllViews has previously been called, then any new views created +// as a result of this call will begin CK operation. +- (void)setSyncingViews:(NSSet* _Nullable)viewNames sortingPolicy:(TPPolicy* _Nullable)policy; - (void)clearAllViews; // Create all views, but don't begin CK/network operations +// Remove as part of CKKS: ensure we collect keychain changes made before policy is loaded from disk - (void)createViews; // Call this to begin CK operation of all views +// This bit will be 'sticky', in that any new views created with also begin cloudkit operation immediately. +// (clearAllViews will reset this bit.) - (void)beginCloudKitOperationOfAllViews; // Notify sbd to re-backup. @@ -145,11 +147,29 @@ NS_ASSUME_NONNULL_BEGIN // first time after launch, only waits the the initial call - (BOOL)waitForTrustReady; -// For testing -- (void)setOverrideCKKSViewsFromPolicy:(BOOL)value; +// Helper function to make CK containers ++ (CKContainer*)makeCKContainer:(NSString*)containerName + usePCS:(bool)usePCS; + +// Checks featureflags to return whether we should use policy-based views, or use the hardcoded list - (BOOL)useCKKSViewsFromPolicy; + +// Interfaces to examine sync callbacks +- (SecBoolNSErrorCallback _Nullable)claimCallbackForUUID:(NSString*)uuid; +- (NSSet*)pendingCallbackUUIDs; ++ (void)callSyncCallbackWithErrorNoAccount:(SecBoolNSErrorCallback)syncCallback; +@end + +@interface CKKSViewManager (Testing) +- (void)setOverrideCKKSViewsFromPolicy:(BOOL)value; +- (void)resetSyncingPolicy; + - (void)haltAll; +- (CKKSKeychainView*)restartZone:(NSString*)viewName; +- (void)haltZone:(NSString*)viewName; +// If set, any set passed to setSyncingViews will be intersected with this set +- (void)setSyncingViewsAllowList:(NSSet* _Nullable)viewNames; @end NS_ASSUME_NONNULL_END diff --git a/keychain/ckks/CKKSViewManager.m b/keychain/ckks/CKKSViewManager.m index 8a226dfc..3838cc6b 100644 --- a/keychain/ckks/CKKSViewManager.m +++ b/keychain/ckks/CKKSViewManager.m @@ -23,6 +23,7 @@ #import +#import "keychain/ckks/CKKSAccountStateTracker.h" #import "keychain/ckks/CKKSViewManager.h" #import "keychain/ckks/CKKSKeychainView.h" #import "keychain/ckks/CKKSSynchronizeOperation.h" @@ -67,10 +68,16 @@ #import "CKKSAnalytics.h" #endif +#if !OCTAGON @interface CKKSViewManager () -#if OCTAGON +#else +@interface CKKSViewManager () + @property NSXPCListener *listener; +@property (nullable) NSSet* viewAllowList; + // Once you set these, all CKKSKeychainViews created will use them @property CKKSCloudKitClassDependencies* cloudKitClassDependencies; @@ -82,6 +89,12 @@ @property (nonatomic) BOOL overrideCKKSViewsFromPolicy; @property (nonatomic) BOOL valueCKKSViewsFromPolicy; +@property (nonatomic) BOOL startCKOperationAtViewCreation; + +@property BOOL itemModificationsBeforePolicyLoaded; + +// Make writable +@property (nullable) TPPolicy* policy; #endif @end @@ -94,23 +107,23 @@ @implementation CKKSViewManager #if OCTAGON -NSSet* _viewList; - -- (instancetype)initWithContainerName: (NSString*) containerName - usePCS: (bool)usePCS - sosAdapter:(id _Nullable)sosAdapter - cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies +- (instancetype)initWithContainer:(CKContainer*)container + sosAdapter:(id _Nullable)sosAdapter + accountStateTracker:(CKKSAccountStateTracker*)accountTracker + lockStateTracker:(CKKSLockStateTracker*)lockStateTracker + cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies { if(self = [super init]) { _cloudKitClassDependencies = cloudKitClassDependencies; _sosPeerAdapter = sosAdapter; - _viewList = nil; - _container = [self makeCKContainer: containerName usePCS:usePCS]; - _accountTracker = [[CKKSAccountStateTracker alloc] init:self.container nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass]; - _lockStateTracker = [[CKKSLockStateTracker alloc] init]; + _viewAllowList = nil; + _container = container; + _accountTracker = accountTracker; + _lockStateTracker = lockStateTracker; [_lockStateTracker addLockStateObserver:self]; _reachabilityTracker = [[CKKSReachabilityTracker alloc] init]; + _itemModificationsBeforePolicyLoaded = NO; _zoneChangeFetcher = [[CKKSZoneChangeFetcher alloc] initWithContainer:_container fetchClass:cloudKitClassDependencies.fetchRecordZoneChangesOperationClass @@ -127,6 +140,8 @@ NSSet* _viewList; _views = [[NSMutableDictionary alloc] init]; _pendingSyncCallbacks = [[NSMutableDictionary alloc] init]; + _startCKOperationAtViewCreation = NO; + _completedSecCKKSInitialize = [[CKKSCondition alloc] init]; WEAKIFY(self); @@ -139,14 +154,21 @@ NSSet* _viewList; [self notifyNewTLKsInKeychain]; }]; + _policy = nil; + _listener = [NSXPCListener anonymousListener]; _listener.delegate = self; [_listener resume]; + + // Start listening for CK account status (for sync callbacks) + [_accountTracker registerForNotificationsOfCloudKitAccountStatusChange:self]; } return self; } --(CKContainer*)makeCKContainer:(NSString*)containerName usePCS:(bool)usePCS { ++ (CKContainer*)makeCKContainer:(NSString*)containerName + usePCS:(bool)usePCS +{ CKContainer* container = [CKContainer containerWithIdentifier:containerName]; if(!usePCS) { CKContainerOptions* containerOptions = [[CKContainerOptions alloc] init]; @@ -312,6 +334,8 @@ dispatch_once_t globalZoneStateQueueOnce; } } +#pragma mark - View List handling + - (NSSet*)defaultViewList { NSSet* fullList = [OTSOSActualAdapter sosCKKSViewList]; @@ -338,18 +362,77 @@ dispatch_once_t globalZoneStateQueueOnce; return fullList; } --(NSSet*)viewList { - if (_viewList) { - return _viewList; - } else { - return [self defaultViewList]; +- (NSSet*)viewList { + return [self.views.allKeys copy]; +} + +- (void)setSyncingViews:(NSSet*)viewNames sortingPolicy:(TPPolicy*)policy +{ + secnotice("ckks-policy", "New syncing policy: %@ views: %@", policy, viewNames); + + if(![self useCKKSViewsFromPolicy]) { + // Thanks, but no thanks. + viewNames = [self defaultViewList]; + secnotice("ckks-policy", "Reverting to default view list: %@", viewNames); + } + + if(self.viewAllowList) { + secnotice("ckks-policy", "Intersecting view list with allow list: %@", self.viewAllowList); + NSMutableSet* set = [viewNames mutableCopy]; + [set intersectSet:self.viewAllowList]; + + viewNames = set; + secnotice("ckks-policy", "Final list: %@", viewNames); + } + + self.policy = policy; + + @synchronized(self.views) { + NSArray* previousViewNames = [self.views.allKeys copy]; + + // First, shut down any views that are no longer in the set + for(NSString* viewName in previousViewNames) { + if(![viewNames containsObject:viewName]) { + secnotice("ckks-policy", "Stopping old view %@", viewName); + [self clearView:viewName]; + } + } + + for(NSString* viewName in viewNames) { + if([previousViewNames containsObject:viewName]) { + CKKSKeychainView* view = [self findView:viewName]; + secnotice("ckks-policy", "Already have view %@", view); + } else { + CKKSKeychainView* view = [self findOrCreateView:viewName]; + secnotice("ckks-policy", "Created new view %@", view); + } + } + + if(self.itemModificationsBeforePolicyLoaded) { + secnotice("ckks-policy", "Issuing scan suggestions to handle missed items"); + for(CKKSKeychainView* view in [self.views allValues]) { + [view scanLocalItems:@"item-added-before-policy"]; + } + self.itemModificationsBeforePolicyLoaded = NO; + } } } -- (void)setViewList:(NSSet* _Nullable)newViewList { - _viewList = newViewList; +- (void)setSyncingViewsAllowList:(NSSet*)viewNames +{ + self.viewAllowList = viewNames; +} + +- (void)resetSyncingPolicy +{ + secnotice("ckks-policy", "Setting policy to nil"); + self.policy = nil; + + self.startCKOperationAtViewCreation = NO; } +#pragma mark - View Handling + - (void)setView: (CKKSKeychainView*) obj { CKKSKeychainView* kcv = nil; @@ -368,6 +451,8 @@ dispatch_once_t globalZoneStateQueueOnce; @synchronized(self.views) { tempviews = [self.views.allValues copy]; [self.views removeAllObjects]; + + self.startCKOperationAtViewCreation = NO; } for(CKKSKeychainView* view in tempviews) { @@ -412,6 +497,10 @@ dispatch_once_t globalZoneStateQueueOnce; zoneModifier:self.zoneModifier savedTLKNotifier: self.savedTLKNotifier cloudKitClassDependencies:self.cloudKitClassDependencies]; + + if(self.startCKOperationAtViewCreation) { + [self.views[viewName] beginCloudKitOperation]; + } return self.views[viewName]; } } @@ -429,18 +518,23 @@ dispatch_once_t globalZoneStateQueueOnce; - (void)createViews { - // In the future, the CKKSViewManager needs to persist its policy property through daemon restarts - // and load it here, before creating whatever views it was told to (in a previous daemon lifetime) - for (NSString* viewName in self.viewList) { - CKKSKeychainView* view = [self findOrCreateView:viewName]; - (void)view; + if(![self useCKKSViewsFromPolicy]) { + // In the future, the CKKSViewManager needs to persist its policy property through daemon restarts + // and load it here, before creating whatever views it was told to (in a previous daemon lifetime) + for (NSString* viewName in [self defaultViewList]) { + CKKSKeychainView* view = [self findOrCreateView:viewName]; + (void)view; + } + } else { + secnotice("ckks-views", "Not loading default view list due to enabled CKKS4All"); } } - (void)beginCloudKitOperationOfAllViews { - for (NSString* viewName in self.viewList) { - CKKSKeychainView* view = [self findView:viewName]; + self.startCKOperationAtViewCreation = YES; + + for (CKKSKeychainView* view in self.views.allValues) { [view beginCloudKitOperation]; } } @@ -460,11 +554,18 @@ dispatch_once_t globalZoneStateQueueOnce; return tlks; } -- (CKKSKeychainView*)restartZone:(NSString*)viewName { +- (void)haltZone:(NSString*)viewName +{ @synchronized(self.views) { - [self.views[viewName] halt]; + CKKSKeychainView* view = self.views[viewName]; + [view halt]; + [view cancelAllOperations]; self.views[viewName] = nil; } +} + +- (CKKSKeychainView*)restartZone:(NSString*)viewName { + [self haltZone:viewName]; return [self findOrCreateView: viewName]; } @@ -491,28 +592,42 @@ dispatch_once_t globalZoneStateQueueOnce; if (self.overrideCKKSViewsFromPolicy) { return self.valueCKKSViewsFromPolicy; } else { - return os_feature_enabled(Security, CKKSViewsFromPolicy); + BOOL viewsFromPolicy = os_feature_enabled(Security, CKKSViewsFromPolicy); + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + secnotice("ckks", "ViewsFromPolicy feature flag: %@", viewsFromPolicy ? @"on" : @"off"); + }); + return viewsFromPolicy; } } -- (NSString*)viewNameForItem: (SecDbItemRef) item { +- (NSString* _Nullable)viewNameForItem:(SecDbItemRef)item +{ if ([self useCKKSViewsFromPolicy]) { CFErrorRef cferror = NULL; - NSMutableDictionary *dict = (__bridge_transfer NSMutableDictionary*) SecDbItemCopyPListWithMask(item, ~0, &cferror); + NSMutableDictionary *dict = (__bridge_transfer NSMutableDictionary*) SecDbItemCopyPListWithMask(item, kSecDbSyncFlag, &cferror); if(cferror) { secerror("ckks: Couldn't fetch attributes from item: %@", cferror); CFReleaseNull(cferror); - return [self viewNameForViewHint: nil]; - } - TPPolicy* policy = self.policy; - if (policy == nil) { - return [self viewNameForViewHint: nil]; + return nil; } - NSString* view = [policy mapKeyToView:dict]; + + NSString* view = [self.policy mapKeyToView:dict]; if (view == nil) { - return [self viewNameForViewHint: nil]; + secerror("ckks: No view returned from policy (%@): %@", self.policy, dict); + return nil; } + + // Horrible hack until Cuttlefish: remove Safari prefix from view names + if([view isEqualToString:@"CreditCards"]) { + return @"SafariCreditCards"; + } + if([view isEqualToString:@"Passwords"]) { + return @"SafariPasswords"; + } + return view; } else { CFErrorRef cferror = NULL; @@ -531,15 +646,63 @@ dispatch_once_t globalZoneStateQueueOnce; - (void)registerSyncStatusCallback: (NSString*) uuid callback: (SecBoolNSErrorCallback) callback { // Someone is requesting future notification of this item. @synchronized(self.pendingSyncCallbacks) { + secnotice("ckkscallback", "registered callback for UUID: %@", uuid); self.pendingSyncCallbacks[uuid] = callback; } } +- (SecBoolNSErrorCallback _Nullable)claimCallbackForUUID:(NSString*)uuid +{ + @synchronized(self.pendingSyncCallbacks) { + SecBoolNSErrorCallback callback = self.pendingSyncCallbacks[uuid]; + + if(callback) { + secerror("ckkscallback : fetched UUID: %@", uuid); + } + + self.pendingSyncCallbacks[uuid] = nil; + return callback; + } +} + +- (NSSet*)pendingCallbackUUIDs +{ + @synchronized(self.pendingSyncCallbacks) { + return [[self.pendingSyncCallbacks allKeys] copy]; + } +} + +- (void)cloudkitAccountStateChange:(CKAccountInfo* _Nullable)oldAccountInfo to:(CKAccountInfo*)currentAccountInfo +{ + if(currentAccountInfo.accountStatus == CKAccountStatusAvailable && currentAccountInfo.hasValidCredentials) { + // Account is okay! + } else { + @synchronized(self.pendingSyncCallbacks) { + if(self.pendingSyncCallbacks.count > 0) { + secnotice("ckkscallback", "No CK account; failing all pending sync callbacks"); + + for(NSString* uuid in [self.pendingSyncCallbacks allKeys]) { + [CKKSViewManager callSyncCallbackWithErrorNoAccount:self.pendingSyncCallbacks[uuid]]; + } + + [self.pendingSyncCallbacks removeAllObjects]; + } + } + } +} + ++ (void)callSyncCallbackWithErrorNoAccount:(SecBoolNSErrorCallback)syncCallback +{ + // I don't love using this domain, but PCS depends on it + syncCallback(false, [NSError errorWithDomain:@"securityd" + code:errSecNotLoggedIn + description:@"No iCloud account available; item is not expected to sync"]); +} + - (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn source:(SecDbTransactionSource)txionSource added: (SecDbItemRef) added deleted: (SecDbItemRef) deleted { SecDbItemRef modified = added ? added : deleted; - NSString* viewName = [self viewNameForItem: modified]; NSString* keyViewName = [CKKSKey isItemKeyForKeychainView: modified]; if(keyViewName) { @@ -557,44 +720,56 @@ dispatch_once_t globalZoneStateQueueOnce; return; } - // When SOS is in charge of a view, CKKS is not. - // Since this isn't a CKKS key item, we don't care about it. - if(txionSource == kSecDbSOSTransaction) { - secinfo("ckks", "Ignoring new non-CKKS item in kSecDbSOSTransaction notification"); + bool addedSync = added && SecDbItemIsSyncable(added); + bool deletedSync = deleted && SecDbItemIsSyncable(deleted); + + if(!addedSync && !deletedSync) { + // Local-only change. Skip with prejudice. + secinfo("ckks", "skipping sync of non-sync item (%d, %d)", addedSync, deletedSync); + return; } - // Looks like a normal item. Proceed! - CKKSKeychainView* view = [self findView:viewName]; + NSString* viewName = nil; - NSString* uuid = (__bridge NSString*) SecDbItemGetValue(modified, &v10itemuuid, NULL); - SecBoolNSErrorCallback syncCallback = nil; - if(uuid) { - @synchronized(self.pendingSyncCallbacks) { - syncCallback = self.pendingSyncCallbacks[uuid]; - self.pendingSyncCallbacks[uuid] = nil; + @synchronized(self.views) { + if([self useCKKSViewsFromPolicy] && !self.policy) { + secerror("ckks: No policy configured(%@). Skipping item: %@", self.policy, modified); + self.itemModificationsBeforePolicyLoaded = YES; - if(syncCallback) { - secinfo("ckks", "Have a pending callback for %@; passing along", uuid); - } + return; } + + viewName = [self viewNameForItem:modified]; + } + + if(!viewName) { + secnotice("ckks", "No intended CKKS view for item; skipping: %@", modified); + return; } + // Looks like a normal item. Proceed! + CKKSKeychainView* view = [self findView:viewName]; + if(!view) { - if(viewName) { - secnotice("ckks", "No CKKS view for %@, skipping: %@", viewName, modified); - } else { - secinfo("ckks", "No CKKS view for %@, skipping: %@", viewName, modified); - } + secnotice("ckks", "No CKKS view for %@, skipping: %@", viewName, modified); + + NSString* uuid = (__bridge NSString*) SecDbItemGetValue(modified, &v10itemuuid, NULL); + SecBoolNSErrorCallback syncCallback = [self claimCallbackForUUID:uuid]; + if(syncCallback) { - syncCallback(false, [NSError errorWithDomain:@"securityd" - code:kSOSCCNoSuchView + syncCallback(false, [NSError errorWithDomain:CKKSErrorDomain + code:CKKSNoSuchView userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for '%@'", viewName]}]); } return; } ckksnotice("ckks", view, "Routing item to zone %@: %@", viewName, modified); - [view handleKeychainEventDbConnection: dbconn added:added deleted:deleted rateLimiter:self.globalRateLimiter syncCallback: syncCallback]; + [view handleKeychainEventDbConnection:dbconn + source:txionSource + added:added + deleted:deleted + rateLimiter:self.globalRateLimiter]; } -(void)setCurrentItemForAccessGroup:(NSData* _Nonnull)newItemPersistentRef @@ -646,40 +821,9 @@ dispatch_once_t globalZoneStateQueueOnce; complete:complete]; } - -+ (instancetype) manager { - return [self resetManager: false setTo: nil]; -} - -+ (instancetype) resetManager: (bool) reset setTo: (CKKSViewManager*) obj { - static CKKSViewManager* manager = nil; - - if([CKDatabase class] == nil) { - secerror("CKKS: CloudKit.framework appears to not be linked. Can't create CKKS objects."); - return nil; - } - - if(!manager || reset || obj) { - @synchronized([self class]) { - if(obj != nil) { - [manager clearAllViews]; - manager = obj; - } else { - if(reset) { - [manager clearAllViews]; - manager = nil; - } else if (manager == nil && SecCKKSIsEnabled()) { - // The CKKSViewManager doesn't do much with its adapter, so leave as nonessentialq - manager = [[CKKSViewManager alloc] initWithContainerName:SecCKKSContainerName - usePCS:SecCKKSContainerUsePCS - sosAdapter:[[OTSOSActualAdapter alloc] initAsEssential:NO] - cloudKitClassDependencies:[CKKSCloudKitClassDependencies forLiveCloudKit]]; - } - } - } - } - - return manager; ++ (instancetype)manager +{ + return [OTManager manager].viewManager; } - (void)cancelPendingOperations { @@ -907,6 +1051,8 @@ dispatch_once_t globalZoneStateQueueOnce; @"lockstatetracker": stringify(self.lockStateTracker), @"cloudkitRetryAfter": stringify(self.zoneModifier.cloudkitRetryAfter), @"lastCKKSPush": CKKSNilToNSNull(lastCKKSPush), + @"policy": stringify(self.policy), + @"viewsFromPolicy": [self useCKKSViewsFromPolicy] ? @"yes" : @"no", }; [a addObject: global]; } diff --git a/keychain/ckks/CKKSZone.m b/keychain/ckks/CKKSZone.m index b51bf477..c5f878b3 100644 --- a/keychain/ckks/CKKSZone.m +++ b/keychain/ckks/CKKSZone.m @@ -450,6 +450,13 @@ // Bring all operations down, too [self cancelAllOperations]; + + // And now, wait for all operations that are running + for(NSOperation* op in self.operationQueue.operations) { + if(op.isExecuting) { + [op waitUntilFinished]; + } + } } @end diff --git a/keychain/ckks/tests/CKKSCloudKitTests.m b/keychain/ckks/tests/CKKSCloudKitTests.m index 6add0637..82239815 100644 --- a/keychain/ckks/tests/CKKSCloudKitTests.m +++ b/keychain/ckks/tests/CKKSCloudKitTests.m @@ -41,6 +41,7 @@ #import "keychain/ckks/CloudKitCategories.h" #import "keychain/categories/NSError+UsefulConstructors.h" #import "keychain/ckks/tests/MockCloudKit.h" +#import "keychain/ot/OTManager.h" @interface CKKSCloudKitTests : XCTestCase @@ -95,11 +96,20 @@ nsdistributednotificationCenterClass:[NSDistributedNotificationCenter class] notifierClass:[FakeCKKSNotifier class]]; - CKKSViewManager* manager = [[CKKSViewManager alloc] initWithContainerName:containerName - usePCS:SecCKKSContainerUsePCS - sosAdapter:nil - cloudKitClassDependencies:cloudKitClassDependencies]; - [CKKSViewManager resetManager:false setTo:manager]; + CKContainer* container = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS]; + CKKSAccountStateTracker* accountStateTracker = [[CKKSAccountStateTracker alloc] init:container + nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass]; + + CKKSLockStateTracker* lockStateTracker = [[CKKSLockStateTracker alloc] init]; + + CKKSViewManager* manager = [[CKKSViewManager alloc] initWithContainer:container + sosAdapter:nil + accountStateTracker:accountStateTracker + lockStateTracker:lockStateTracker + cloudKitClassDependencies:cloudKitClassDependencies]; + // No longer a supported mechanism: + //[CKKSViewManager resetManager:false setTo:manager]; + (void)manager; // Make a new fake keychain NSString* smallName = [self.name componentsSeparatedByString:@" "][1]; diff --git a/keychain/ckks/tests/CKKSDispatchTests.m b/keychain/ckks/tests/CKKSDispatchTests.m deleted file mode 100644 index eff091fc..00000000 --- a/keychain/ckks/tests/CKKSDispatchTests.m +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (c) 2017 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#include -#import -#import "keychain/ckks/CKKSNearFutureScheduler.h" - -@interface CKKSNearFutureSchedulerTests : XCTestCase - -@end - -@implementation CKKSNearFutureSchedulerTests - -- (void)setUp { - [super setUp]; -} - -- (void)tearDown { - [super tearDown]; -} - -- (void)testOneShot { - XCTestExpectation *expectation = [self expectationWithDescription:@"FutureScheduler fired"]; - - CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay:50*NSEC_PER_MSEC keepProcessAlive:true block:^{ - [expectation fulfill]; - }]; - - [scheduler trigger]; - - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -- (void)testOneShotDelay { - XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"]; - toofastexpectation.inverted = YES; - - XCTestExpectation *expectation = [self expectationWithDescription:@"FutureScheduler fired"]; - - CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 200*NSEC_PER_MSEC keepProcessAlive:false block:^{ - [toofastexpectation fulfill]; - [expectation fulfill]; - }]; - - [scheduler trigger]; - - // Make sure it waits at least 0.1 seconds - [self waitForExpectations: @[toofastexpectation] timeout:0.1]; - - // But finishes within 1.1s (total) - [self waitForExpectations: @[expectation] timeout:1]; -} - -- (void)testOneShotManyTrigger { - XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"]; - toofastexpectation.inverted = YES; - - XCTestExpectation *expectation = [self expectationWithDescription:@"FutureScheduler fired"]; - expectation.assertForOverFulfill = YES; - - CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 200*NSEC_PER_MSEC keepProcessAlive:true block:^{ - [toofastexpectation fulfill]; - [expectation fulfill]; - }]; - - [scheduler trigger]; - [scheduler trigger]; - [scheduler trigger]; - [scheduler trigger]; - [scheduler trigger]; - [scheduler trigger]; - [scheduler trigger]; - [scheduler trigger]; - - // Make sure it waits at least 0.1 seconds - [self waitForExpectations: @[toofastexpectation] timeout:0.1]; - - // But finishes within .6s (total) - [self waitForExpectations: @[expectation] timeout:0.5]; - - // Ensure we don't get called again in the next 0.3 s - XCTestExpectation* waitmore = [self expectationWithDescription:@"waiting"]; - waitmore.inverted = YES; - [self waitForExpectations: @[waitmore] timeout: 0.3]; -} - - -- (void)testMultiShot { - XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"]; - first.assertForOverFulfill = NO; - - XCTestExpectation *second = [self expectationWithDescription:@"FutureScheduler fired (two)"]; - second.expectedFulfillmentCount = 2; - second.assertForOverFulfill = YES; - - CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 100*NSEC_PER_MSEC keepProcessAlive:false block:^{ - [first fulfill]; - [second fulfill]; - }]; - - [scheduler trigger]; - - [self waitForExpectations: @[first] timeout:0.2]; - - [scheduler trigger]; - [scheduler trigger]; - [scheduler trigger]; - - [self waitForExpectations: @[second] timeout:0.2]; - - XCTestExpectation* waitmore = [self expectationWithDescription:@"waiting"]; - waitmore.inverted = YES; - [self waitForExpectations: @[waitmore] timeout: 0.2]; -} - -- (void)testMultiShotDelays { - XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"]; - first.assertForOverFulfill = NO; - - XCTestExpectation *longdelay = [self expectationWithDescription:@"FutureScheduler fired (long delay expectation)"]; - longdelay.inverted = YES; - longdelay.expectedFulfillmentCount = 2; - - XCTestExpectation *second = [self expectationWithDescription:@"FutureScheduler fired (two)"]; - second.expectedFulfillmentCount = 2; - second.assertForOverFulfill = YES; - - CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" initialDelay: 50*NSEC_PER_MSEC continuingDelay:300*NSEC_PER_MSEC keepProcessAlive:false block:^{ - [first fulfill]; - [longdelay fulfill]; - [second fulfill]; - }]; - - [scheduler trigger]; - - [self waitForExpectations: @[first] timeout:0.2]; - - [scheduler trigger]; - [scheduler trigger]; - [scheduler trigger]; - - // longdelay should NOT be fulfilled twice in the first 0.3 seconds - [self waitForExpectations: @[longdelay] timeout:0.2]; - - // But second should be fulfilled in the first 0.8 seconds - [self waitForExpectations: @[second] timeout:0.5]; - - XCTestExpectation* waitmore = [self expectationWithDescription:@"waiting"]; - waitmore.inverted = YES; - [self waitForExpectations: @[waitmore] timeout: 0.2]; -} - -- (void)testCancel { - XCTestExpectation *cancelexpectation = [self expectationWithDescription:@"FutureScheduler fired (after cancel)"]; - cancelexpectation.inverted = YES; - - CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 100*NSEC_PER_MSEC keepProcessAlive:true block:^{ - [cancelexpectation fulfill]; - }]; - - [scheduler trigger]; - [scheduler cancel]; - - // Make sure it does not fire in 0.5 s - [self waitForExpectations: @[cancelexpectation] timeout:0.2]; -} - -- (void)testDelayedNoShot { - XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"]; - toofastexpectation.inverted = YES; - - CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 10*NSEC_PER_MSEC keepProcessAlive:false block:^{ - [toofastexpectation fulfill]; - }]; - - // Tell the scheduler to wait, but don't trigger it. It shouldn't fire. - [scheduler waitUntil: 50*NSEC_PER_MSEC]; - - [self waitForExpectations: @[toofastexpectation] timeout:0.1]; -} - -- (void)testDelayedOneShot { - XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"]; - first.assertForOverFulfill = NO; - - XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"]; - toofastexpectation.inverted = YES; - - CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 10*NSEC_PER_MSEC keepProcessAlive:false block:^{ - [first fulfill]; - [toofastexpectation fulfill]; - }]; - - [scheduler waitUntil: 150*NSEC_PER_MSEC]; - [scheduler trigger]; - - [self waitForExpectations: @[toofastexpectation] timeout:0.1]; - [self waitForExpectations: @[first] timeout:0.2]; -} - -- (void)testDelayedMultiShot { - XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"]; - first.assertForOverFulfill = NO; - - XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"]; - toofastexpectation.expectedFulfillmentCount = 2; - toofastexpectation.inverted = YES; - - XCTestExpectation *second = [self expectationWithDescription:@"FutureScheduler fired (two)"]; - second.expectedFulfillmentCount = 2; - second.assertForOverFulfill = YES; - - CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 10*NSEC_PER_MSEC keepProcessAlive:false block:^{ - [first fulfill]; - [second fulfill]; - [toofastexpectation fulfill]; - }]; - - [scheduler trigger]; - [self waitForExpectations: @[first] timeout:0.2]; - - [scheduler waitUntil: 150*NSEC_PER_MSEC]; - [scheduler trigger]; - - [self waitForExpectations: @[toofastexpectation] timeout:0.1]; - [self waitForExpectations: @[second] timeout:0.3]; -} - -@end diff --git a/keychain/ckks/tests/CKKSMockSOSPresentAdapter.h b/keychain/ckks/tests/CKKSMockSOSPresentAdapter.h index 7fcff165..4eb48520 100644 --- a/keychain/ckks/tests/CKKSMockSOSPresentAdapter.h +++ b/keychain/ckks/tests/CKKSMockSOSPresentAdapter.h @@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN @property bool excludeSelfPeerFromTrustSet; @property SOSCCStatus circleStatus; +@property (nullable) NSError* circleStatusError; + @property CKKSSOSSelfPeer* selfPeer; @property NSMutableSet>* trustedPeers; diff --git a/keychain/ckks/tests/CKKSMockSOSPresentAdapter.m b/keychain/ckks/tests/CKKSMockSOSPresentAdapter.m index ec18e233..7f8077ec 100644 --- a/keychain/ckks/tests/CKKSMockSOSPresentAdapter.m +++ b/keychain/ckks/tests/CKKSMockSOSPresentAdapter.m @@ -38,7 +38,8 @@ { if(!self.sosEnabled || self.circleStatus == kSOSCCError) { if(error && self.circleStatus == kSOSCCError) { - *error = [NSError errorWithDomain:(__bridge NSString*)kSOSErrorDomain code:self.circleStatus userInfo:nil]; + // I'm not at all sure that the second error here actually is any error in particular + *error = self.circleStatusError ?: [NSError errorWithDomain:(__bridge NSString*)kSOSErrorDomain code:self.circleStatus userInfo:nil]; } return kSOSCCError; } diff --git a/keychain/ckks/tests/CKKSSQLTests.m b/keychain/ckks/tests/CKKSSQLTests.m index 8bd89e46..827a5c83 100644 --- a/keychain/ckks/tests/CKKSSQLTests.m +++ b/keychain/ckks/tests/CKKSSQLTests.m @@ -269,7 +269,14 @@ XCTAssertEqual (zse.ckzonesubscribed, loaded.ckzonesubscribed, "ckzonesubscribed persisted through db save and load"); XCTAssertEqualObjects(zse.encodedChangeToken, loaded.encodedChangeToken, "encodedChangeToken persisted through db save and load"); - XCTAssert([[NSCalendar currentCalendar] isDate:zse.lastFetchTime equalToDate: loaded.lastFetchTime toUnitGranularity:NSCalendarUnitSecond], + secnotice("ckkstests", "zse.lastFetchTime: %@", zse.lastFetchTime); + secnotice("ckkstests", "loaded.lastFetchTime: %@", loaded.lastFetchTime); + + secnotice("ckkstests", "equal?: %d", [zse.lastFetchTime isEqualToDate:loaded.lastFetchTime]); + secnotice("ckkstests", "equal to seconds?: %d", [[NSCalendar currentCalendar] isDate:zse.lastFetchTime equalToDate: loaded.lastFetchTime toUnitGranularity:NSCalendarUnitSecond]); + + // We only compare to the minute level, as that's enough to test the save+load. + XCTAssert([[NSCalendar currentCalendar] isDate:zse.lastFetchTime equalToDate: loaded.lastFetchTime toUnitGranularity:NSCalendarUnitMinute], "lastFetchTime persisted through db save and load"); } @@ -411,23 +418,43 @@ XCTAssertTrue([wrappedKey saveToDatabase: &error], "key saved to database"); XCTAssertNil(error, "no error saving key to database"); + NSString* secondUUID = @"8b2aeb7f-0000-0000-0000-70d5c728ebf7"; + CKKSKey* secondtlk = [[CKKSKey alloc] initSelfWrappedWithAESKey:[[CKKSAESSIVKey alloc] initWithBase64: @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="] + uuid:secondUUID + keyclass:SecCKKSKeyClassTLK + state:SecCKKSProcessedStateLocal + zoneID:self.testZoneID + encodedCKRecord:testCKRecord + currentkey:true]; + XCTAssertTrue([secondtlk saveToDatabase: &error], "Second TLK saved to database"); + XCTAssertNil(error, "no error saving TLK to database"); + NSArray* tlks = [CKKSKey allWhere: @{@"UUID": @"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7"} error: &error]; XCTAssertNotNil(tlks, "Returned some array from allWhere"); XCTAssertNil(error, "no error back from allWhere"); XCTAssertEqual([tlks count], 1ul, "Received one row (and expected one row)"); - NSArray* selfWrapped = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereObject op:@"=" string:@"uuid"]} error: &error]; + NSArray* selfWrapped = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereColumn op:CKKSSQLWhereComparatorEquals column:CKKSSQLWhereColumnNameUUID]} error: &error]; XCTAssertNotNil(selfWrapped, "Returned some array from allWhere"); XCTAssertNil(error, "no error back from allWhere"); - XCTAssertEqual([selfWrapped count], 1ul, "Received one row (and expected one row)"); + XCTAssertEqual([selfWrapped count], 2ul, "Should have recievied two rows"); - // Try using CKKSSQLWhereObject alongside normal binds - NSArray* selfWrapped2 = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereObject op:@"=" string:@"uuid"], + // Try using CKKSSQLWhereColumn alongside normal binds + NSArray* selfWrapped2 = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereColumn op:CKKSSQLWhereComparatorEquals column:CKKSSQLWhereColumnNameUUID], @"uuid": @"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7"} error: &error]; XCTAssertNotNil(selfWrapped2, "Returned some array from allWhere"); XCTAssertNil(error, "no error back from allWhere"); XCTAssertEqual([selfWrapped2 count], 1ul, "Received one row (and expected one row)"); + XCTAssertEqualObjects([selfWrapped2[0] uuid], @"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7", "Should received first TLK UUID"); + + NSArray* selfWrapped3 = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereColumn op:CKKSSQLWhereComparatorEquals column:CKKSSQLWhereColumnNameUUID], + @"uuid": [CKKSSQLWhereValue op:CKKSSQLWhereComparatorNotEquals value:@"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7"]} + error: &error]; + XCTAssertNotNil(selfWrapped3, "Returned some array from allWhere"); + XCTAssertNil(error, "no error back from allWhere"); + XCTAssertEqual([selfWrapped3 count], 1ul, "Should have received one rows"); + XCTAssertEqualObjects([selfWrapped3[0] uuid], secondUUID, "Should received second TLK UUID"); } - (void)testGroupBy { @@ -507,7 +534,7 @@ while(count == 0 || uuid != nil) { uuid = nil; [CKKSSQLDatabaseObject queryDatabaseTable: [CKKSOutgoingQueueEntry sqlTable] - where: lastUUID ? @{@"UUID": [CKKSSQLWhereObject op:@">" stringValue:lastUUID]} : nil + where: lastUUID ? @{@"UUID": [CKKSSQLWhereValue op:CKKSSQLWhereComparatorGreaterThan value:lastUUID]} : nil columns: @[@"action", @"UUID"] groupBy:nil orderBy:@[@"uuid"] diff --git a/keychain/ckks/tests/CKKSTests+API.m b/keychain/ckks/tests/CKKSTests+API.m index 0b8ad45e..10235c42 100644 --- a/keychain/ckks/tests/CKKSTests+API.m +++ b/keychain/ckks/tests/CKKSTests+API.m @@ -69,6 +69,7 @@ (id)kSecAttrAccount : account, (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, (id)kSecValueData : data, + (id)kSecAttrSyncViewHint : self.keychainView.zoneName, (id)kSecAttrDeriveSyncIDFromItemAttributes : (id)kCFBooleanTrue, (id)kSecAttrPCSPlaintextServiceIdentifier : serviceIdentifier, (id)kSecAttrPCSPlaintextPublicKey : publicKey, @@ -180,6 +181,7 @@ (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccount : @"testaccount", (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, + (id)kSecAttrSyncViewHint : self.keychainView.zoneName, (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding], } mutableCopy]; @@ -192,6 +194,8 @@ [blockExpectation fulfill]; }), @"_SecItemAddAndNotifyOnSync succeeded"); + OCMVerifyAllWithDelay(self.mockDatabase, 10); + [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @@ -223,6 +227,7 @@ (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccount : @"testaccount", (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, + (id)kSecAttrSyncViewHint : self.keychainView.zoneName, (id)kSecAttrPCSPlaintextPublicKey : [@"asdf" dataUsingEncoding:NSUTF8StringEncoding], (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding], } mutableCopy]; @@ -275,6 +280,7 @@ (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccount : @"testaccount", (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, + (id)kSecAttrSyncViewHint : self.keychainView.zoneName, (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding], (id)kSecAttrSyncViewHint : self.keychainView.zoneName, // @ fake view hint for fake view } mutableCopy]; @@ -306,6 +312,7 @@ (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccount : @"testaccount", (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, + (id)kSecAttrSyncViewHint : self.keychainView.zoneName, (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding], } mutableCopy]; @@ -333,6 +340,7 @@ (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccount : @"testaccount", (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, + (id)kSecAttrSyncViewHint : self.keychainView.zoneName, (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding], } mutableCopy]; @@ -398,6 +406,59 @@ [self waitForExpectationsWithTimeout:5.0 handler:nil]; } +- (void)testAddAndNotifyOnSyncBeforePolicyLoaded { + [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID]; + [self saveTLKMaterialToKeychain:self.keychainZoneID]; + [self expectCKKSTLKSelfShareUpload:self.keychainZoneID]; + + [self startCKKSSubsystem]; + + // Allow CKKS to spin up fully + XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "CKKS entered ready"); + + [self.keychainView waitForOperationsOfClass:[CKKSScanLocalItemsOperation class]]; + [self.keychainView waitForOperationsOfClass:[CKKSOutgoingQueueOperation class]]; + + // Simulate a daemon restart + self.automaticallyBeginCKKSViewCloudKitOperation = false; + [self.injectedManager resetSyncingPolicy]; + [self.injectedManager haltZone:self.keychainZoneID.zoneName]; + + // Issue the query (to simulate the query starting securityd) + NSMutableDictionary* query = [@{ + (id)kSecClass : (id)kSecClassGenericPassword, + (id)kSecAttrAccessGroup : @"com.apple.security.ckks", + (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock, + (id)kSecAttrAccount : @"testaccount", + (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, + (id)kSecAttrSyncViewHint : self.keychainView.zoneName, + (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding], + } mutableCopy]; + + XCTestExpectation* blockExpectation = [self expectationWithDescription: @"callback occurs"]; + + XCTAssertEqual(errSecSuccess, _SecItemAddAndNotifyOnSync((__bridge CFDictionaryRef) query, NULL, ^(bool didSync, CFErrorRef error) { + XCTAssertTrue(didSync, "Item synced"); + XCTAssertNil((__bridge NSError*)error, "Shouldn't have received an error syncing item"); + + [blockExpectation fulfill]; + }), @"_SecItemAddAndNotifyOnSync succeeded"); + + // When the policy is loaded, the item should upload and the callback should fire + [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.keychainZoneID]; + + [self.injectedManager setSyncingViews:self.managedViewList sortingPolicy:self.viewSortingPolicyForManagedViewList]; + self.keychainView = [self.injectedManager findView:self.keychainZoneID.zoneName]; + + [self.injectedManager beginCloudKitOperationOfAllViews]; + [self beginSOSTrustedViewOperation:self.keychainView]; + + XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], @"Should have reached key state 'ready'"); + OCMVerifyAllWithDelay(self.mockDatabase, 20); + + [self waitForExpectations:@[blockExpectation] timeout:5]; +} + - (void)testPCSUnencryptedFieldsAdd { [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test. diff --git a/keychain/ckks/tests/CKKSTests+CurrentPointerAPI.m b/keychain/ckks/tests/CKKSTests+CurrentPointerAPI.m index c75ef74c..e2b7c753 100644 --- a/keychain/ckks/tests/CKKSTests+CurrentPointerAPI.m +++ b/keychain/ckks/tests/CKKSTests+CurrentPointerAPI.m @@ -142,7 +142,7 @@ // Check that the record is where we expect it in CloudKit [self waitForCKModifications]; - CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID]; + CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID]; CKRecord* record = self.keychainZone.currentDatabase[pcsItemRecordID]; XCTAssertNotNil(record, "Found record in CloudKit at expected UUID"); @@ -206,7 +206,7 @@ // Check that the record is where we expect it [self waitForCKModifications]; - CKRecordID* pcsOtherItemRecordID = [[CKRecordID alloc] initWithRecordName: @"878BEAA6-1EE9-1079-1025-E6832AC8F2F3" zoneID:self.keychainZoneID]; + CKRecordID* pcsOtherItemRecordID = [[CKRecordID alloc] initWithRecordName: @"2DEA6136-2505-6BFD-E3E8-B44A6E39C3B5" zoneID:self.keychainZoneID]; CKRecord* recordOther = self.keychainZone.currentDatabase[pcsOtherItemRecordID]; XCTAssertNotNil(recordOther, "Found other record in CloudKit at expected UUID"); @@ -344,7 +344,7 @@ // Check that the record is where we expect it in CloudKit [self waitForCKModifications]; - CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID]; + CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID]; CKRecord* record = self.keychainZone.currentDatabase[pcsItemRecordID]; XCTAssertNotNil(record, "Found record in CloudKit at expected UUID"); @@ -547,11 +547,11 @@ // Check that the records are where we expect them in CloudKit [self waitForCKModifications]; - CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID]; + CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID]; CKRecord* record = self.keychainZone.currentDatabase[pcsItemRecordID]; XCTAssertNotNil(record, "Found record in CloudKit at expected UUID"); - CKRecordID* pcsItemRecordID2 = [[CKRecordID alloc] initWithRecordName: @"3AB8E78D-75AF-CFEF-F833-FA3E3E90978A" zoneID:self.keychainZoneID]; + CKRecordID* pcsItemRecordID2 = [[CKRecordID alloc] initWithRecordName: @"10E76B80-CE1C-A52A-B0CB-462A2EBA05AF" zoneID:self.keychainZoneID]; CKRecord* record2 = self.keychainZone.currentDatabase[pcsItemRecordID2]; XCTAssertNotNil(record2, "Found 2nd record in CloudKit at expected UUID"); @@ -560,7 +560,7 @@ // Another machine comes along and updates the pointer! CKKSCurrentItemPointer* cip = [[CKKSCurrentItemPointer alloc] initForIdentifier:@"com.apple.security.ckks-pcsservice" - currentItemUUID:@"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" + currentItemUUID:@"50184A35-4480-E8BA-769B-567CF72F1EC0" state:SecCKKSProcessedStateRemote zoneID:self.keychainZoneID encodedCKRecord:nil]; @@ -638,7 +638,7 @@ // Check that the record is where we expect it in CloudKit [self waitForCKModifications]; - CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID]; + CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID]; CKRecord* record = self.keychainZone.currentDatabase[pcsItemRecordID]; XCTAssertNotNil(record, "Found record in CloudKit at expected UUID"); @@ -647,7 +647,7 @@ // Another machine comes along and updates the pointer! CKKSCurrentItemPointer* cip = [[CKKSCurrentItemPointer alloc] initForIdentifier:@"com.apple.security.ckks-pcsservice" - currentItemUUID:@"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" + currentItemUUID:@"50184A35-4480-E8BA-769B-567CF72F1EC0" state:SecCKKSProcessedStateRemote zoneID:self.keychainZoneID encodedCKRecord:nil]; @@ -712,7 +712,7 @@ // Check that the record is where we expect it in CloudKit [self waitForCKModifications]; - NSString* recordUUID = @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3"; + NSString* recordUUID = @"50184A35-4480-E8BA-769B-567CF72F1EC0"; CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName:recordUUID zoneID:self.keychainZoneID]; CKRecord* record = self.keychainZone.currentDatabase[pcsItemRecordID]; XCTAssertNotNil(record, "Found record in CloudKit at expected UUID"); @@ -792,7 +792,7 @@ // Check that the record is where we expect it in CloudKit [self waitForCKModifications]; - CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID]; + CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID]; CKRecord* record = self.keychainZone.currentDatabase[pcsItemRecordID]; XCTAssertNotNil(record, "Found record in CloudKit at expected UUID"); @@ -832,7 +832,7 @@ // Check that the number is on the CKKSMirrorEntry [self.keychainView dispatchSync: ^bool { NSError* error = nil; - CKKSMirrorEntry* ckme = [CKKSMirrorEntry fromDatabase:@"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID error:&error]; + CKKSMirrorEntry* ckme = [CKKSMirrorEntry fromDatabase:@"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID error:&error]; XCTAssertNil(error, "no error fetching ckme"); XCTAssertNotNil(ckme, "Received a ckme"); @@ -879,7 +879,7 @@ // Check that the record is where we expect it in CloudKit [self waitForCKModifications]; - CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID]; + CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID]; CKRecord* record = self.keychainZone.currentDatabase[pcsItemRecordID]; XCTAssertNotNil(record, "Found record in CloudKit at expected UUID"); @@ -943,7 +943,7 @@ // Check that the record is where we expect it in CloudKit [self waitForCKModifications]; - CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID]; + CKRecordID* pcsItemRecordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID]; CKRecord* record = self.keychainZone.currentDatabase[pcsItemRecordID]; XCTAssertNotNil(record, "Found record in CloudKit at expected UUID"); @@ -1061,7 +1061,8 @@ XCTAssertNil(error, "Error should be nil parsing base64 item"); item[@"v_Data"] = [@"conflictingdata" dataUsingEncoding:NSUTF8StringEncoding]; - CKRecordID* ckrid = [[CKRecordID alloc] initWithRecordName:@"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID]; + item[@"vwht"] = @"keychain"; + CKRecordID* ckrid = [[CKRecordID alloc] initWithRecordName:@"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID]; CKRecord* mismatchedRecord = [self newRecord:ckrid withNewItemData:item]; [self.keychainZone addToZone: mismatchedRecord]; diff --git a/keychain/ckks/tests/CKKSTests+MultiZone.h b/keychain/ckks/tests/CKKSTests+MultiZone.h index 1d0b619b..57207e33 100644 --- a/keychain/ckks/tests/CKKSTests+MultiZone.h +++ b/keychain/ckks/tests/CKKSTests+MultiZone.h @@ -44,6 +44,11 @@ @property FakeCKZone* limitedZone; @property (readonly) ZoneKeys* limitedZoneKeys; +@property CKRecordZoneID* passwordsZoneID; +@property CKKSKeychainView* passwordsView; +@property FakeCKZone* passwordsZone; +@property (readonly) ZoneKeys* passwordsZoneKeys; + - (void)saveFakeKeyHierarchiesToLocalDatabase; - (void)putFakeDeviceStatusesInCloudKit; - (void)putFakeKeyHierachiesInCloudKit; diff --git a/keychain/ckks/tests/CKKSTests+MultiZone.m b/keychain/ckks/tests/CKKSTests+MultiZone.m index f3179612..0823b71d 100644 --- a/keychain/ckks/tests/CKKSTests+MultiZone.m +++ b/keychain/ckks/tests/CKKSTests+MultiZone.m @@ -7,6 +7,9 @@ #import #include +#import +#import +#import #import "keychain/ckks/tests/CloudKitMockXCTest.h" #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h" @@ -22,6 +25,7 @@ #import "keychain/ckks/CKKSZoneStateEntry.h" #import "keychain/ckks/CKKSManifest.h" +#import "keychain/ckks/tests/CKKSTests+MultiZone.h" #import "keychain/ckks/tests/MockCloudKit.h" #pragma clang diagnostic push @@ -37,50 +41,51 @@ #include #pragma clang diagnostic pop -@interface CloudKitKeychainSyncingMultiZoneTestsBase : CloudKitKeychainSyncingMockXCTest - -@property CKRecordZoneID* engramZoneID; -@property CKKSKeychainView* engramView; -@property FakeCKZone* engramZone; -@property (readonly) ZoneKeys* engramZoneKeys; +@interface CloudKitKeychainSyncingMultiZoneTestsBase () +@end -@property CKRecordZoneID* manateeZoneID; -@property CKKSKeychainView* manateeView; -@property FakeCKZone* manateeZone; -@property (readonly) ZoneKeys* manateeZoneKeys; +@implementation CloudKitKeychainSyncingMultiZoneTestsBase ++ (void)setUp { + SecCKKSEnable(); + SecCKKSResetSyncing(); + [super setUp]; +} -@property CKRecordZoneID* autoUnlockZoneID; -@property CKKSKeychainView* autoUnlockView; -@property FakeCKZone* autoUnlockZone; -@property (readonly) ZoneKeys* autoUnlockZoneKeys; +- (NSSet*)managedViewList { + NSMutableSet* parentSet = [[super managedViewList] mutableCopy]; + [parentSet addObject:@"SafariPasswords"]; + return parentSet; +} -@property CKRecordZoneID* healthZoneID; -@property CKKSKeychainView* healthView; -@property FakeCKZone* healthZone; -@property (readonly) ZoneKeys* healthZoneKeys; +// Make a policy as normal for most views, but Passwords is special +- (TPPolicy*)viewSortingPolicyForManagedViewList +{ + NSMutableArray* rules = [NSMutableArray array]; -@property CKRecordZoneID* applepayZoneID; -@property CKKSKeychainView* applepayView; -@property FakeCKZone* applepayZone; -@property (readonly) ZoneKeys* applepayZoneKeys; + for(NSString* viewName in self.managedViewList) { + TPPBPolicyKeyViewMapping* mapping = [[TPPBPolicyKeyViewMapping alloc] init]; + mapping.view = viewName; -@property CKRecordZoneID* homeZoneID; -@property CKKSKeychainView* homeView; -@property FakeCKZone* homeZone; -@property (readonly) ZoneKeys* homeZoneKeys; + // The real passwords view is on com.appple.cfnetwork, but for these tests, let's just use the sbd agrp (because of how the entitlements are specified) + if([viewName isEqualToString:@"SafariPasswords"]) { + mapping.matchingRule = [TPDictionaryMatchingRule fieldMatch:@"agrp" + fieldRegex:[NSString stringWithFormat:@"^com\\.apple\\.sbd$"]]; + } else { + mapping.matchingRule = [TPDictionaryMatchingRule fieldMatch:@"vwht" + fieldRegex:[NSString stringWithFormat:@"^%@$", viewName]]; + } -@property CKRecordZoneID* limitedZoneID; -@property CKKSKeychainView* limitedView; -@property FakeCKZone* limitedZone; -@property (readonly) ZoneKeys* limitedZoneKeys; + [rules addObject:mapping]; + } -@end + TPPolicy* policy = [TPPolicy policyWithModelToCategory:@[] + categoriesByView:@{} + introducersByCategory:@{} + keyViewMapping:rules + unknownRedactions:NO + version:[[TPPolicyVersion alloc] initWithVersion:1 hash:@"fake-policy-for-views"]]; -@implementation CloudKitKeychainSyncingMultiZoneTestsBase -+ (void)setUp { - SecCKKSEnable(); - SecCKKSResetSyncing(); - [super setUp]; + return policy; } - (void)setUp { @@ -149,6 +154,19 @@ XCTAssertNotNil(self.limitedView, "CKKSViewManager created the LimitedPeersAllowed view"); [self.ckksViews addObject:self.limitedView]; [self.ckksZones addObject:self.limitedZoneID]; + + self.passwordsZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"SafariPasswords" ownerName:CKCurrentUserDefaultName]; + self.passwordsZone = [[FakeCKZone alloc] initZone: self.passwordsZoneID]; + self.zones[self.passwordsZoneID] = self.passwordsZone; + self.passwordsView = [[CKKSViewManager manager] findOrCreateView:@"SafariPasswords"]; + XCTAssertNotNil(self.passwordsView, "should have a passwords ckks view"); + XCTAssertNotNil(self.passwordsView, "CKKSViewManager created the Passwords view"); + [self.ckksViews addObject:self.passwordsView]; + [self.ckksZones addObject:self.passwordsZoneID]; + + // These tests, at least, will use the policy codepaths! + [self.injectedManager setOverrideCKKSViewsFromPolicy:YES]; + [self.injectedManager setSyncingViews:self.managedViewList sortingPolicy:self.viewSortingPolicyForManagedViewList]; } + (void)tearDown { @@ -186,6 +204,14 @@ [self.homeView waitUntilAllOperationsAreFinished]; self.homeView = nil; + [self.limitedView halt]; + [self.limitedView waitUntilAllOperationsAreFinished]; + self.limitedView = nil; + + [self.passwordsView halt]; + [self.passwordsView waitUntilAllOperationsAreFinished]; + self.passwordsView = nil; + [super tearDown]; } @@ -211,6 +237,7 @@ [self putFakeDeviceStatusInCloudKit: self.applepayZoneID]; [self putFakeDeviceStatusInCloudKit: self.homeZoneID]; [self putFakeDeviceStatusInCloudKit: self.limitedZoneID]; + [self putFakeDeviceStatusInCloudKit: self.passwordsZoneID]; } - (void)putFakeKeyHierachiesInCloudKit{ @@ -221,6 +248,7 @@ [self putFakeKeyHierarchyInCloudKit: self.applepayZoneID]; [self putFakeKeyHierarchyInCloudKit: self.homeZoneID]; [self putFakeKeyHierarchyInCloudKit: self.limitedZoneID]; + [self putFakeKeyHierarchyInCloudKit: self.passwordsZoneID]; } - (void)saveTLKsToKeychain{ @@ -231,6 +259,7 @@ [self saveTLKMaterialToKeychain:self.applepayZoneID]; [self saveTLKMaterialToKeychain:self.homeZoneID]; [self saveTLKMaterialToKeychain:self.limitedZoneID]; + [self saveTLKMaterialToKeychain:self.passwordsZoneID]; } - (void)deleteTLKMaterialsFromKeychain{ @@ -241,6 +270,7 @@ [self deleteTLKMaterialFromKeychain: self.applepayZoneID]; [self deleteTLKMaterialFromKeychain: self.homeZoneID]; [self deleteTLKMaterialFromKeychain:self.limitedZoneID]; + [self deleteTLKMaterialFromKeychain:self.passwordsZoneID]; } - (void)waitForKeyHierarchyReadinesses { @@ -251,6 +281,7 @@ [self.applepayView waitForKeyHierarchyReadiness]; [self.homeView waitForKeyHierarchyReadiness]; [self.limitedView waitForKeyHierarchyReadiness]; + [self.passwordsView waitForKeyHierarchyReadiness]; } - (void)expectCKKSTLKSelfShareUploads { @@ -424,6 +455,108 @@ [self waitForExpectations:@[pcsChanged] timeout:0.2]; } +- (void)testMultipleZoneAdd { + [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test. + + // Let the horses loose + [self startCKKSSubsystem]; + + // We expect a single record to be uploaded to the 'LimitedPeersAllowed' view + [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.limitedZoneID]; + [self addGenericPassword: @"data" account: @"account-delete-me-limited-peers" viewHint:(NSString*)kSecAttrViewHintLimitedPeersAllowed]; + OCMVerifyAllWithDelay(self.mockDatabase, 20); + + // We expect a single record to be uploaded to the 'atv' home + [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.homeZoneID]; + [self addGenericPassword: @"data" account: @"account-delete-me-home" viewHint:(NSString*)kSecAttrViewHintHome]; + + OCMVerifyAllWithDelay(self.mockDatabase, 20); + OCMVerifyAllWithDelay(self.mockCKKSViewManager, 10); +} + +- (void)testMultipleZoneDelete { + [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test. + + [self startCKKSSubsystem]; + + // We expect a single record to be uploaded. + [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.limitedZoneID]; + [self addGenericPassword: @"data" account: @"account-delete-me-limited-peers" viewHint:(NSString*)kSecAttrViewHintLimitedPeersAllowed]; + OCMVerifyAllWithDelay(self.mockDatabase, 20); + + [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.homeZoneID]; + [self addGenericPassword: @"data" account: @"account-delete-me-home" viewHint:(NSString*)kSecAttrViewHintHome]; + OCMVerifyAllWithDelay(self.mockDatabase, 20); + [self waitForCKModifications]; + + // We expect a single record to be deleted from the ATV zone + [self expectCKDeleteItemRecords:1 zoneID:self.homeZoneID]; + [self deleteGenericPassword:@"account-delete-me-home"]; + OCMVerifyAllWithDelay(self.mockDatabase, 20); + + // Now we expect a single record to be deleted from the test zone + [self expectCKDeleteItemRecords:1 zoneID:self.limitedZoneID]; + [self deleteGenericPassword:@"account-delete-me-limited-peers"]; + OCMVerifyAllWithDelay(self.mockDatabase, 20); +} + +- (void)testAddAndReceiveDeleteForSafariPasswordItem { + [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test. + + [self startCKKSSubsystem]; + + XCTestExpectation* passwordChanged = [self expectChangeForView:self.passwordsView.zoneName]; + + // We expect a single record to be uploaded to the Passwords view. + [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.passwordsZoneID]; + + [self addGenericPassword:@"data" + account:@"account-delete-me" + access:(id)kSecAttrAccessibleWhenUnlocked + viewHint:nil + accessGroup:@"com.apple.sbd" + expecting:errSecSuccess + message:@"Item for Password view"]; + + OCMVerifyAllWithDelay(self.mockDatabase, 20); + [self waitForExpectations:@[passwordChanged] timeout:1]; + [self waitForCKModifications]; + + [self waitForKeyHierarchyReadinesses]; + [self.passwordsView waitForOperationsOfClass:[CKKSIncomingQueueOperation class]]; + + // Now, the item is deleted. Do we properly remove it? + CKRecord* itemRecord = nil; + for(CKRecord* record in [self.passwordsZone.currentDatabase allValues]) { + if([record.recordType isEqualToString:SecCKRecordItemType]) { + itemRecord = record; + break; + } + } + XCTAssertNotNil(itemRecord, "Should have found the item in the password zone"); + + NSDictionary *query = @{(id)kSecClass : (id)kSecClassGenericPassword, + (id)kSecAttrAccessGroup : @"com.apple.sbd", + (id)kSecAttrAccount : @"account-delete-me", + (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, + (id)kSecMatchLimit : (id)kSecMatchLimitOne, + (id)kSecReturnData : (id)kCFBooleanTrue, + }; + + CFTypeRef item = NULL; + XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef) query, &item), "item should still exist"); + XCTAssertNotNil((__bridge id)item, "No item should have been found"); + CFReleaseNull(item); + + // Now, the item is deleted. The passwords view should delete the local item, even though it has the wrong 'vwht' on disk. + [self.passwordsZone deleteCKRecordIDFromZone:itemRecord.recordID]; + [self.passwordsView notifyZoneChange:nil]; + [self.passwordsView waitForFetchAndIncomingQueueProcessing]; + + XCTAssertEqual(errSecItemNotFound, SecItemCopyMatching((__bridge CFDictionaryRef) query, &item), "item should no longer exist"); + XCTAssertNil((__bridge id)item, "No item should have been found"); +} + - (void)testAddOtherViewHintItem { [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test. @@ -431,12 +564,49 @@ // We expect no uploads to CKKS. [self addGenericPassword: @"data" account: @"account-delete-me-no-viewhint"]; - [self addGenericPassword: @"data" account: @"account-delete-me-password" viewHint:(NSString*) kSOSViewAutofillPasswords]; + [self addGenericPassword: @"data" account: @"account-delete-me-password" viewHint:(NSString*) kSOSViewBackupBagV0]; sleep(1); OCMVerifyAllWithDelay(self.mockDatabase, 20); } +- (void)testUploadItemsAddedBeforeStart { + [self addGenericPassword:@"data" + account:@"account-delete-me" + access:(id)kSecAttrAccessibleAfterFirstUnlock + viewHint:nil + accessGroup:@"com.apple.sbd" + expecting:errSecSuccess + message:@"Item for Password view"]; + + [self addGenericPassword:@"data" + account:@"account-delete-me-2" + access:(id)kSecAttrAccessibleAfterFirstUnlock + viewHint:nil + accessGroup:@"com.apple.sbd" + expecting:errSecSuccess + message:@"Item for Password view"]; + + [self addGenericPassword:@"data" account:@"account-delete-me-limited-peers" viewHint:(NSString*)kSecAttrViewHintLimitedPeersAllowed]; + + NSError* error = nil; + NSDictionary* currentOQEs = [CKKSOutgoingQueueEntry countsByStateInZone:self.passwordsZoneID error:&error]; + XCTAssertNil(error, "Should be no error counting OQEs"); + XCTAssertEqual(0, currentOQEs.count, "Should be no OQEs"); + + [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test. + + // Now CKKS starts up + // Upon sign in, these items should be uploaded + [self expectCKModifyItemRecords:2 currentKeyPointerRecords:1 zoneID:self.passwordsZoneID + checkItem:[self checkClassCBlock:self.passwordsZoneID message:@"Object was encrypted under class C key in hierarchy"]]; + [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.limitedZoneID]; + [self startCKKSSubsystem]; + + XCTAssertEqual(0, [self.passwordsView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "CKKS entered ready"); + OCMVerifyAllWithDelay(self.mockDatabase, 20); +} + - (void)testReceiveItemInView { [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test. [self startCKKSSubsystem]; diff --git a/keychain/ckks/tests/CKKSTests.m b/keychain/ckks/tests/CKKSTests.m index 1aaf5cd8..8baf53aa 100644 --- a/keychain/ckks/tests/CKKSTests.m +++ b/keychain/ckks/tests/CKKSTests.m @@ -340,7 +340,7 @@ [self.keychainView dispatchSync:^bool { NSError* error = nil; - CKRecordID* ckrid = [[CKRecordID alloc] initWithRecordName:@"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID]; + CKRecordID* ckrid = [[CKRecordID alloc] initWithRecordName:@"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID]; CKKSItem* item = [self newItem:ckrid withNewItemData:[self fakeRecordDictionary:account zoneID:self.keychainZoneID] key:self.keychainZoneKeys.classC]; XCTAssertNotNil(item, "Should be able to create a new fake item"); @@ -736,6 +736,7 @@ XCTAssertNil(error, "No error loading IQEs"); XCTAssertNotNil(iqes, "Could load IQEs"); XCTAssertEqual(iqes.count, 0u, "Incoming queue is empty"); + return false; }]; } @@ -1228,6 +1229,7 @@ (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked, (id)kSecAttrAccount : @"account-class-A", (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, + (id)kSecAttrSyncViewHint : self.keychainView.zoneName, (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding], }, NULL), @"Adding class A item"); OCMVerifyAllWithDelay(self.mockDatabase, 20); @@ -2457,74 +2459,6 @@ [self checkGenericPassword: @"data" account: @"second"]; } -- (void)testMultipleZoneAdd { - // Bring up a new zone: we expect a key hierarchy upload. - CKKSKeychainView* atvView = [self.injectedManager findOrCreateView:(id)kSecAttrViewHintAppleTV]; - [self.ckksViews addObject:atvView]; - CKRecordZoneID* appleTVZoneID = [[CKRecordZoneID alloc] initWithZoneName:(__bridge NSString*) kSecAttrViewHintAppleTV ownerName:CKCurrentUserDefaultName]; - - // We also expect the view manager's notifyNewTLKsInKeychain call to fire once (after some delay) - OCMExpect([self.mockCKKSViewManager notifyNewTLKsInKeychain]); - - // Let the horses loose - [self startCKKSSubsystem]; - [self performOctagonTLKUpload:self.ckksViews]; - - // We expect a single record to be uploaded to the 'keychain' view - [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID]; - [self addGenericPassword: @"data" account: @"account-delete-me"]; - OCMVerifyAllWithDelay(self.mockDatabase, 20); - - // We expect a single record to be uploaded to the 'atv' view - [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:appleTVZoneID]; - [self addGenericPassword: @"atv" - account: @"tvaccount" - viewHint:(__bridge NSString*) kSecAttrViewHintAppleTV - access:(id)kSecAttrAccessibleAfterFirstUnlock - expecting:errSecSuccess message:@"AppleTV view-hinted object"]; - - OCMVerifyAllWithDelay(self.mockDatabase, 20); - - OCMVerifyAllWithDelay(self.mockCKKSViewManager, 10); -} - -- (void)testMultipleZoneDelete { - [self startCKKSSubsystem]; - - // Bring up a new zone: we expect a key hierarchy and an item. - CKKSKeychainView* atvView = [self.injectedManager findOrCreateView:(id)kSecAttrViewHintAppleTV]; - XCTAssertNotNil(atvView, "Should have a new ATV view"); - [self.ckksViews addObject:atvView]; - [self beginSOSTrustedViewOperation:atvView]; - CKRecordZoneID* appleTVZoneID = [[CKRecordZoneID alloc] initWithZoneName:(__bridge NSString*) kSecAttrViewHintAppleTV ownerName:CKCurrentUserDefaultName]; - - [self performOctagonTLKUpload:self.ckksViews]; - - // We expect a single record to be uploaded. - [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID]; - [self addGenericPassword: @"data" account: @"account-delete-me"]; - OCMVerifyAllWithDelay(self.mockDatabase, 20); - - [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:appleTVZoneID]; - [self addGenericPassword: @"atv" - account: @"tvaccount" - viewHint:(__bridge NSString*) kSecAttrViewHintAppleTV - access:(id)kSecAttrAccessibleAfterFirstUnlock - expecting:errSecSuccess - message:@"AppleTV view-hinted object"]; - OCMVerifyAllWithDelay(self.mockDatabase, 20); - - // We expect a single record to be deleted from the ATV zone - [self expectCKDeleteItemRecords: 1 zoneID:appleTVZoneID]; - [self deleteGenericPassword:@"tvaccount"]; - OCMVerifyAllWithDelay(self.mockDatabase, 20); - - // Now we expect a single record to be deleted from the test zone - [self expectCKDeleteItemRecords: 1 zoneID:self.keychainZoneID]; - [self deleteGenericPassword:@"account-delete-me"]; - OCMVerifyAllWithDelay(self.mockDatabase, 20); -} - - (void)testRestartWithoutRefetch { // Restarting the CKKS operation should check that it's been 15 minutes since the last fetch before it fetches again. Simulate this. [self startCKKSSubsystem]; @@ -4113,7 +4047,7 @@ NSError* error = nil; NSDictionary* currentOQEs = [CKKSOutgoingQueueEntry countsByStateInZone:self.keychainZoneID error:&error]; - XCTAssertNil(error, "Should be no error coutning OQEs"); + XCTAssertNil(error, "Should be no error counting OQEs"); XCTAssertEqual(0, currentOQEs.count, "Should be no OQEs"); // Now, insert a restart to simulate securityd restarting (and throwing away all pending operations), then a real sign in @@ -4134,6 +4068,42 @@ OCMVerifyAllWithDelay(self.mockDatabase, 20); } +- (void)testSyncableItemAddedOnDaemonRestartBeforePolicyLoaded { + [self createAndSaveFakeKeyHierarchy:self.keychainZoneID]; + [self startCKKSSubsystem]; + + XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "CKKS entered ready"); + + [self.keychainView waitForOperationsOfClass:[CKKSScanLocalItemsOperation class]]; + [self.keychainView waitForOperationsOfClass:[CKKSOutgoingQueueOperation class]]; + + // Daemon restarts + self.automaticallyBeginCKKSViewCloudKitOperation = false; + [self.injectedManager resetSyncingPolicy]; + [self.injectedManager haltZone:self.keychainZoneID.zoneName]; + + // This item addition shouldn't be uploaded yet, or in any queues + [self addGenericPassword:@"data" account:@"account-delete-me-2"]; + + NSError* error = nil; + NSDictionary* currentOQEs = [CKKSOutgoingQueueEntry countsByStateInZone:self.keychainZoneID error:&error]; + XCTAssertNil(error, "Should be no error counting OQEs"); + XCTAssertEqual(0, currentOQEs.count, "Should be no OQEs"); + + [self.injectedManager setSyncingViews:self.managedViewList sortingPolicy:self.viewSortingPolicyForManagedViewList]; + self.keychainView = [self.injectedManager findView:self.keychainZoneID.zoneName]; + // end of daemon restart + + [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.keychainZoneID + checkItem:[self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]]; + + [self.injectedManager beginCloudKitOperationOfAllViews]; + [self beginSOSTrustedViewOperation:self.keychainView]; + + XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:20*NSEC_PER_SEC], "CKKS entered ready"); + OCMVerifyAllWithDelay(self.mockDatabase, 20); +} + // Note that this test assumes that the keychainView object was created at daemon restart. // I don't really know how to write a test for that... - (void)testSyncableItemAddedOnDaemonRestartBeforeCloudKitAccountKnown { diff --git a/keychain/ckks/tests/CloudKitKeychainSyncingFixupTests.m b/keychain/ckks/tests/CloudKitKeychainSyncingFixupTests.m index fddf4ca6..e0701910 100644 --- a/keychain/ckks/tests/CloudKitKeychainSyncingFixupTests.m +++ b/keychain/ckks/tests/CloudKitKeychainSyncingFixupTests.m @@ -99,7 +99,7 @@ // Add some current item pointers. They don't necessarily need to point to anything... CKKSCurrentItemPointer* cip = [[CKKSCurrentItemPointer alloc] initForIdentifier:@"com.apple.security.ckks-pcsservice" - currentItemUUID:@"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" + currentItemUUID:@"50184A35-4480-E8BA-769B-567CF72F1EC0" state:SecCKKSProcessedStateRemote zoneID:self.keychainZoneID encodedCKRecord:nil]; @@ -109,7 +109,7 @@ XCTAssertNotNil(currentPointerRecord, "Found record in CloudKit at expected UUID"); CKKSCurrentItemPointer* cip2 = [[CKKSCurrentItemPointer alloc] initForIdentifier:@"com.apple.security.ckks-pcsservice2" - currentItemUUID:@"3AB8E78D-75AF-CFEF-F833-FA3E3E90978A" + currentItemUUID:@"10E76B80-CE1C-A52A-B0CB-462A2EBA05AF" state:SecCKKSProcessedStateRemote zoneID:self.keychainZoneID encodedCKRecord:nil]; @@ -138,7 +138,7 @@ // And add a garbage CIP CKKSCurrentItemPointer* cip3 = [[CKKSCurrentItemPointer alloc] initForIdentifier:@"garbage" - currentItemUUID:@"3AB8E78D-75AF-CFEF-F833-FA3E3E90978A" + currentItemUUID:@"10E76B80-CE1C-A52A-B0CB-462A2EBA05AF" state:SecCKKSProcessedStateLocal zoneID:self.keychainZoneID encodedCKRecord:nil]; diff --git a/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h b/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h index 933305d3..f64abed9 100644 --- a/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h +++ b/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h @@ -136,6 +136,15 @@ NS_ASSUME_NONNULL_BEGIN access:(NSString*)access expecting:(OSStatus)status message:(NSString*)message; + +- (BOOL)addGenericPassword:(NSString*)password + account:(NSString*)account + access:(NSString*)access + viewHint:(NSString* _Nullable)viewHint + accessGroup:(NSString* _Nullable)accessGroup + expecting:(OSStatus)status + message:(NSString*)message; + - (void)addGenericPassword:(NSString*)password account:(NSString*)account expecting:(OSStatus)status message:(NSString*)message; - (void)updateGenericPassword:(NSString*)newPassword account:(NSString*)account; diff --git a/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m b/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m index 3ea304a4..f7edec83 100644 --- a/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m +++ b/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m @@ -106,9 +106,10 @@ self.suggestTLKUpload = OCMClassMock([CKKSNearFutureScheduler class]); OCMStub([self.suggestTLKUpload trigger]); - self.ckksZones = [NSMutableSet set]; - self.ckksViews = [NSMutableSet set]; - self.keys = [[NSMutableDictionary alloc] init]; + // If a subclass wants to fill these in before calling setUp, fine. + self.ckksZones = self.ckksZones ?: [NSMutableSet set]; + self.ckksViews = self.ckksViews ?: [NSMutableSet set]; + self.keys = self.keys ?: [[NSMutableDictionary alloc] init]; [SecMockAKS reset]; @@ -203,6 +204,7 @@ - (void)verifyDatabaseMocks { OCMVerifyAllWithDelay(self.mockDatabase, 20); + [self waitForCKModifications]; } - (void)createClassCItemAndWaitForUpload:(CKRecordZoneID*)zoneID account:(NSString*)account { @@ -1046,16 +1048,24 @@ static CFDictionaryRef SOSCreatePeerGestaltFromName(CFStringRef name) return ret; } -- (void)addGenericPassword: (NSString*) password account: (NSString*) account viewHint: (NSString*) viewHint access: (NSString*) access expecting: (OSStatus) status message: (NSString*) message { +- (BOOL)addGenericPassword:(NSString*)password + account:(NSString*)account + access:(NSString*)access + viewHint:(NSString* _Nullable)viewHint + accessGroup:(NSString* _Nullable)accessGroup + expecting:(OSStatus)status + message:(NSString*)message +{ NSMutableDictionary* query = [@{ (id)kSecClass : (id)kSecClassGenericPassword, - (id)kSecAttrAccessGroup : @"com.apple.security.ckks", - (id)kSecAttrAccessible: access, + (id)kSecAttrAccessible: (id)access, (id)kSecAttrAccount : account, (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, (id)kSecValueData : (id) [password dataUsingEncoding:NSUTF8StringEncoding], } mutableCopy]; + query[(id)kSecAttrAccessGroup] = accessGroup ?: @"com.apple.security.ckks"; + if(viewHint) { query[(id)kSecAttrSyncViewHint] = viewHint; } else { @@ -1066,6 +1076,16 @@ static CFDictionaryRef SOSCreatePeerGestaltFromName(CFStringRef name) XCTAssertEqual(status, SecItemAdd((__bridge CFDictionaryRef) query, NULL), @"%@", message); } +- (void)addGenericPassword: (NSString*) password account: (NSString*) account viewHint: (NSString*) viewHint access: (NSString*) access expecting: (OSStatus) status message: (NSString*) message { + [self addGenericPassword:password + account:account + access:access + viewHint:viewHint + accessGroup:nil + expecting:status + message:message]; +} + - (void)addGenericPassword: (NSString*) password account: (NSString*) account expecting: (OSStatus) status message: (NSString*) message { [self addGenericPassword:password account:account viewHint:nil access:(id)kSecAttrAccessibleAfterFirstUnlock expecting:errSecSuccess message:message]; diff --git a/keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.h b/keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.h index 2c669584..0cb5406b 100644 --- a/keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.h +++ b/keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.h @@ -53,9 +53,6 @@ NS_ASSUME_NONNULL_BEGIN @property NSCalendar* utcCalendar; -- (NSSet*)managedViewList; - - - (ZoneKeys*)keychainZoneKeys; @end diff --git a/keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.m b/keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.m index 35640b3c..f122a1bd 100644 --- a/keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.m +++ b/keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.m @@ -62,7 +62,7 @@ // Wait for the ViewManager to be brought up XCTAssertEqual(0, [self.injectedManager.completedSecCKKSInitialize wait:20*NSEC_PER_SEC], "No timeout waiting for SecCKKSInitialize"); - self.keychainView = [[CKKSViewManager manager] findOrCreateView:@"keychain"]; + self.keychainView = [[CKKSViewManager manager] findView:@"keychain"]; XCTAssertNotNil(self.keychainView, "CKKSViewManager created the keychain view"); [self.ckksViews addObject:self.keychainView]; } @@ -70,10 +70,6 @@ // Check that your environment is set up correctly XCTAssertFalse([CKKSManifest shouldSyncManifests], "Manifests syncing is disabled"); XCTAssertFalse([CKKSManifest shouldEnforceManifests], "Manifests enforcement is disabled"); - - self.aksLockState = false; // Lie and say AKS is always unlocked - self.mockLockStateTracker = OCMClassMock([CKKSLockStateTracker class]); - OCMStub([self.mockLockStateTracker queryAKSLocked]).andCall(self, @selector(aksLockState)); } diff --git a/keychain/ckks/tests/CloudKitMockXCTest.h b/keychain/ckks/tests/CloudKitMockXCTest.h index 0e3573d6..4df9a3cb 100644 --- a/keychain/ckks/tests/CloudKitMockXCTest.h +++ b/keychain/ckks/tests/CloudKitMockXCTest.h @@ -35,6 +35,8 @@ #import "keychain/ckks/CKKSAccountStateTracker.h" #import "keychain/ckks/tests/MockCloudKit.h" +#import "keychain/ot/OTManager.h" + NS_ASSUME_NONNULL_BEGIN @class CKKSKey; @@ -103,10 +105,25 @@ NS_ASSUME_NONNULL_BEGIN @property CKKSMockSOSPresentAdapter* mockSOSAdapter; @property (nullable) CKKSMockOctagonAdapter *mockOctagonAdapter; --(NSSet*)managedViewList; +- (NSSet*)managedViewList; +- (TPPolicy*)viewSortingPolicyForManagedViewList; + @property (nullable) id mockCKKSViewManager; @property (nullable) CKKSViewManager* injectedManager; +// Injected into CKKSViewManager using OCMock +@property BOOL overrideUseCKKSViewsFromPolicy; + +// Set this to true before calling -setup if you're going to configure the ckks view manager yourself +// !disable format used because this will be initialized to false, and so will happen unless subclasses take positive action +@property BOOL disableConfigureCKKSViewManagerWithViews; + +// Set this to true to override CKKSViewsFromPolicy to NO instead of the default YES +// !disable format used because this will be initialized to false, and so will happen unless subclasses take positive action +@property BOOL setCKKSViewsFromPolicyToNo; + +@property (nullable) OTManager* injectedOTManager; + // Fill this in to fail the next modifyzones operation @property (nullable) NSError* nextModifyRecordZonesError; @@ -160,6 +177,11 @@ NS_ASSUME_NONNULL_BEGIN - (NSError* _Nullable)shouldFailModifyRecordZonesOperation; - (void)ensureZoneDeletionAllowed:(FakeCKZone*)zone; +// Override this to interpose on how an OTManager is created +// This will be called during setUp, after the CloudKit mocks are in-place +// This needs to fill in the injectedOTManager variable on this class +- (OTManager*)setUpOTManager:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies; + // Use this to assert that a fetch occurs (especially if silentFetchesAllowed = false) - (void)expectCKFetch; diff --git a/keychain/ckks/tests/CloudKitMockXCTest.m b/keychain/ckks/tests/CloudKitMockXCTest.m index 76916eab..f8e42b58 100644 --- a/keychain/ckks/tests/CloudKitMockXCTest.m +++ b/keychain/ckks/tests/CloudKitMockXCTest.m @@ -31,10 +31,14 @@ #import #import #import +#import +#import +#import #include "keychain/securityd/Regressions/SecdTestKeychainUtilities.h" #include #include "keychain/securityd/SecItemServer.h" +#include "keychain/securityd/SecItemDataSource.h" #if NO_SERVER #include "keychain/securityd/spi.h" @@ -54,6 +58,9 @@ #include "keychain/ckks/CKKSLockStateTracker.h" #include "keychain/ckks/CKKSReachabilityTracker.h" +#include "keychain/ot/OT.h" +#include "keychain/ot/OTManager.h" + #import "tests/secdmockaks/mockaks.h" #import "utilities/SecTapToRadar.h" @@ -120,7 +127,7 @@ self.operationQueue = [[NSOperationQueue alloc] init]; self.operationQueue.maxConcurrentOperationCount = 1; - self.zones = [[NSMutableDictionary alloc] init]; + self.zones = self.zones ?: [[NSMutableDictionary alloc] init]; self.apsEnvironment = @"fake APS push string"; @@ -340,19 +347,23 @@ nsdistributednotificationCenterClass:[FakeNSDistributedNotificationCenter class] notifierClass:[FakeCKKSNotifier class]]; - self.mockCKKSViewManager = OCMPartialMock( - [[CKKSViewManager alloc] initWithContainerName:SecCKKSContainerName - usePCS:SecCKKSContainerUsePCS - sosAdapter:self.mockSOSAdapter - cloudKitClassDependencies:cloudKitClassDependencies]); + self.injectedOTManager = [self setUpOTManager:cloudKitClassDependencies]; + [OTManager resetManager:false to:self.injectedOTManager]; + + self.mockCKKSViewManager = OCMPartialMock(self.injectedOTManager.viewManager); + self.injectedManager = self.mockCKKSViewManager; + + [self.mockCKKSViewManager setOverrideCKKSViewsFromPolicy:!self.setCKKSViewsFromPolicyToNo]; OCMStub([self.mockCKKSViewManager defaultViewList]).andCall(self, @selector(managedViewList)); OCMStub([self.mockCKKSViewManager syncBackupAndNotifyAboutSync]); OCMStub([self.mockCKKSViewManager waitForTrustReady]).andReturn(YES); - self.injectedManager = self.mockCKKSViewManager; - - [CKKSViewManager resetManager:false setTo:self.injectedManager]; + if(!self.disableConfigureCKKSViewManagerWithViews) { + // Normally, the Octagon state machine calls this. But, since we won't be running that, help it out. + [self.injectedManager setSyncingViews:[self managedViewList] + sortingPolicy:[self viewSortingPolicyForManagedViewList]]; + } // Lie and say network is available [self.reachabilityTracker setNetworkReachability:true]; @@ -368,6 +379,14 @@ kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) { return false; }); } +- (OTManager*)setUpOTManager:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies +{ + return [[OTManager alloc] initWithSOSAdapter:self.mockSOSAdapter + lockStateTracker:[[CKKSLockStateTracker alloc] init] + cloudKitClassDependencies:cloudKitClassDependencies]; + +} + - (SOSAccountStatus*)circleStatus { NSError* error = nil; SOSCCStatus status = [self.mockSOSAdapter circleStatus:&error]; @@ -423,12 +442,12 @@ XCTAssertTrue(self.silentZoneDeletesAllowed, "Should be allowing zone deletes"); } --(CKKSAccountStateTracker*)accountStateTracker { - return self.injectedManager.accountTracker; +- (CKKSAccountStateTracker*)accountStateTracker { + return self.injectedOTManager.accountStateTracker; } -(CKKSLockStateTracker*)lockStateTracker { - return self.injectedManager.lockStateTracker; + return self.injectedOTManager.lockStateTracker; } -(CKKSReachabilityTracker*)reachabilityTracker { @@ -439,6 +458,29 @@ return (NSSet*) CFBridgingRelease(SOSViewCopyViewSet(kViewSetCKKS)); } +- (TPPolicy*)viewSortingPolicyForManagedViewList +{ + NSMutableArray* rules = [NSMutableArray array]; + + for(NSString* viewName in self.managedViewList) { + TPPBPolicyKeyViewMapping* mapping = [[TPPBPolicyKeyViewMapping alloc] init]; + mapping.view = viewName; + mapping.matchingRule = [TPDictionaryMatchingRule fieldMatch:@"vwht" + fieldRegex:[NSString stringWithFormat:@"^%@$", viewName]]; + + [rules addObject:mapping]; + } + + TPPolicy* policy = [TPPolicy policyWithModelToCategory:@[] + categoriesByView:@{} + introducersByCategory:@{} + keyViewMapping:rules + unknownRedactions:NO + version:[[TPPolicyVersion alloc] initWithVersion:1 hash:@"fake-policy-for-views"]]; + + return policy; +} + -(void)expectCKFetch { [self expectCKFetchAndRunBeforeFinished: nil]; } @@ -1085,11 +1127,18 @@ [super tearDown]; [self.injectedManager cancelPendingOperations]; - [CKKSViewManager resetManager:true setTo:nil]; + [self.injectedManager clearAllViews]; self.injectedManager = nil; + [self.mockCKKSViewManager stopMocking]; self.mockCKKSViewManager = nil; + self.injectedOTManager.viewManager = nil; + + [self.injectedOTManager clearAllContexts]; + self.injectedOTManager = nil; + [OTManager resetManager:true to:nil]; + [self.mockAccountStateTracker stopMocking]; self.mockAccountStateTracker = nil; @@ -1130,6 +1179,26 @@ _mockSOSAdapter = nil; _mockOctagonAdapter = nil; + // Bring the database down and delete it + + NSURL* keychainDir = (NSURL*)CFBridgingRelease(SecCopyHomeURL()); + + SecItemDataSourceFactoryReleaseAll(); + SecKeychainDbForceClose(); + SecKeychainDbReset(NULL); + + // Only perform the desctructive step if the url matches what we expect! + if([keychainDir.path hasPrefix:[NSString stringWithFormat:@"/tmp/%@", testName]]) { + secnotice("ckkstest", "Removing test-specific keychain directory at %@", keychainDir); + + NSError* removeError = nil; + [[NSFileManager defaultManager] removeItemAtURL:keychainDir error:&removeError]; + + XCTAssertNil(removeError, "Should have been able to remove temporary files"); + } else { + XCTFail("Unsure what happened to the keychain directory URL: %@", keychainDir); + } + SecCKKSTestResetFlags(); } diff --git a/keychain/ckks/tests/gen_test_plist.py b/keychain/ckks/tests/gen_test_plist.py new file mode 100644 index 00000000..52065f61 --- /dev/null +++ b/keychain/ckks/tests/gen_test_plist.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# +# TODO(kirk or SEAR/QA) after radar 53867279 is fixed, please delete this script +# +# WARNING: if you add new tests to the swift octagon tests, it is possible that +# this script will not find them and then your new tests will not get executed +# in BATS! +# +import sys +import Foundation +from glob import glob +import re +import os +import pprint + +test_dir = sys.argv[1] +outfile = sys.argv[2] + +test_plist = Foundation.NSMutableDictionary.dictionary() +test_plist['BATSConfigVersion'] = '0.1.0' +test_plist['Project'] = 'Security' +test_list = Foundation.NSMutableArray.array() + +test_files = glob(test_dir + '/*.m') + glob(test_dir + '/*.h') + +def get_class_names(): + test_classes = ['CKKSLaunchSequenceTests'] + for filename in test_files: + f = open(filename, 'r') + for line in f: + match = re.search('@interface ([a-zA-Z0-9_]+) ?: ?CloudKitKeychain[a-zA-Z0-9_]*TestsBase', line) + #match = re.search('class (([a-zA-Z0-9_]+Tests)|(OctagonTests[a-zA-Z0-9_]*(? - - - - BATSConfigVersion - 0.1.0 - Project - Security - Tests - - - TestName - CKKSTests - WorkingDirectory - /AppleInternal/XCTests/com.apple.security/ - ShowSubtestResults - - Timeout - 1200 - Command - - BATS_XCTEST_CMD CKKSTests.xctest - - - - - - diff --git a/keychain/ckksctl/ckksctl.m b/keychain/ckksctl/ckksctl.m index bad4d21b..d27cf217 100644 --- a/keychain/ckksctl/ckksctl.m +++ b/keychain/ckksctl/ckksctl.m @@ -284,7 +284,7 @@ static void print_entry(id k, id v, int ind) return status; } -- (void)printHumanReadableStatus: (NSString*) view { +- (void)printHumanReadableStatus:(NSString*)view shortenOutput:(BOOL)shortenOutput { #if OCTAGON dispatch_semaphore_t sema = dispatch_semaphore_create(0); @@ -306,15 +306,25 @@ static void print_entry(id k, id v, int ind) NSString* lockStateTracker = pop(global,@"lockstatetracker", NSString); NSString* retry = pop(global,@"cloudkitRetryAfter", NSString); NSDate *lastCKKSPush = pop(global, @"lastCKKSPush", NSDate); + NSString *syncingPolicy = pop(global, @"policy", NSString); + NSString *viewsFromPolicy = pop(global, @"viewsFromPolicy", NSString); - printf("================================================================================\n\n"); - printf("Global state:\n\n"); - printf("Reachability: %s\n", [[reachability description] UTF8String]); - printf("Retry: %s\n", [[retry description] UTF8String]); - printf("CK DeviceID: %s\n", [[ckdeviceID description] UTF8String]); - printf("CK DeviceID Error: %s\n", [[ckdeviceIDError description] UTF8String]); - printf("Lock state: %s\n", [[lockStateTracker description] UTF8String]); - printf("Last CKKS push: %s\n", [[lastCKKSPush description] UTF8String]); + if(!shortenOutput) { + printf("================================================================================\n\n"); + printf("Global state:\n\n"); + } + + printf("Syncing Policy: %s\n", [[syncingPolicy description] UTF8String]); + printf("Views from policy: %s\n", [[viewsFromPolicy description] UTF8String]); + + if(!shortenOutput) { + printf("Reachability: %s\n", [[reachability description] UTF8String]); + printf("Retry: %s\n", [[retry description] UTF8String]); + printf("CK DeviceID: %s\n", [[ckdeviceID description] UTF8String]); + printf("CK DeviceID Error: %s\n", [[ckdeviceIDError description] UTF8String]); + printf("Lock state: %s\n", [[lockStateTracker description] UTF8String]); + printf("Last CKKS push: %s\n", [[lastCKKSPush description] UTF8String]); + } printf("\n"); } @@ -326,6 +336,16 @@ static void print_entry(id k, id v, int ind) } for(NSDictionary* viewStatus in remainingViews) { + if(shortenOutput) { + NSMutableDictionary* status = [viewStatus mutableCopy]; + + NSString* viewName = pop(status, @"view", NSString); + NSString* keystate = pop(status, @"keystate", NSString); + + printf("%-25s: %s\n", [viewName UTF8String], [keystate UTF8String]); + continue; + } + NSMutableDictionary* status = [viewStatus mutableCopy]; NSString* viewName = pop(status,@"view", NSString); @@ -525,6 +545,7 @@ static int resetCloudKit = false; static int fetch = false; static int push = false; static int json = false; +static int shortOutput = false; static int ckmetric = false; static char* viewArg = NULL; @@ -534,6 +555,7 @@ int main(int argc, char **argv) static struct argument options[] = { { .shortname='p', .longname="perfcounters", .flag=&perfCounters, .flagval=true, .description="Print CKKS performance counters"}, { .shortname='j', .longname="json", .flag=&json, .flagval=true, .description="Output in JSON format"}, + { .shortname='s', .longname="short", .flag=&shortOutput, .flagval=true, .description="Output a short format"}, { .shortname='v', .longname="view", .argument=&viewArg, .description="Operate on a single view"}, { .command="status", .flag=&status, .flagval=true, .description="Report status on CKKS views"}, @@ -585,7 +607,7 @@ int main(int argc, char **argv) } if(!json) { - [ctl printHumanReadableStatus:view]; + [ctl printHumanReadableStatus:view shortenOutput:shortOutput]; } return 0; } else if(perfCounters) { diff --git a/keychain/escrowrequest/generated_source/SecEscrowPendingRecord.m b/keychain/escrowrequest/generated_source/SecEscrowPendingRecord.m index 689908a9..6dd81f06 100644 --- a/keychain/escrowrequest/generated_source/SecEscrowPendingRecord.m +++ b/keychain/escrowrequest/generated_source/SecEscrowPendingRecord.m @@ -30,7 +30,7 @@ } - (BOOL)hasCertCached { - return _has.certCached; + return _has.certCached != 0; } - (BOOL)hasSerializedPrerecord { @@ -49,7 +49,7 @@ } - (BOOL)hasLastCloudServicesTriggerTime { - return _has.lastCloudServicesTriggerTime; + return _has.lastCloudServicesTriggerTime != 0; } @synthesize lastEscrowAttemptTime = _lastEscrowAttemptTime; - (void)setLastEscrowAttemptTime:(uint64_t)v @@ -63,7 +63,7 @@ } - (BOOL)hasLastEscrowAttemptTime { - return _has.lastEscrowAttemptTime; + return _has.lastEscrowAttemptTime != 0; } @synthesize uploadCompleted = _uploadCompleted; - (void)setUploadCompleted:(BOOL)v @@ -77,7 +77,7 @@ } - (BOOL)hasUploadCompleted { - return _has.uploadCompleted; + return _has.uploadCompleted != 0; } @synthesize uploadRetries = _uploadRetries; - (void)setUploadRetries:(uint64_t)v @@ -91,7 +91,7 @@ } - (BOOL)hasUploadRetries { - return _has.uploadRetries; + return _has.uploadRetries != 0; } - (BOOL)hasAltDSID { @@ -110,7 +110,7 @@ } - (BOOL)hasTriggerRequestTime { - return _has.triggerRequestTime; + return _has.triggerRequestTime != 0; } - (NSString *)description diff --git a/keychain/headers/SecItem.h b/keychain/headers/SecItem.h index f39dabbc..baef2e98 100644 --- a/keychain/headers/SecItem.h +++ b/keychain/headers/SecItem.h @@ -770,7 +770,8 @@ extern const CFStringRef kSecAttrKeyClassSymmetric @discussion Predefined item attribute constants used to get or set values in a dictionary. The kSecAttrKeyType constant is the key and its value is one of the constants defined here. - @constant kSecAttrKeyTypeECSECPrimeRandom. + @constant kSecAttrKeyTypeECSECPrimeRandom. The used curve is P-192, P-256, P-384 or P-521. + The size is specified by kSecAttrKeySizeInBits attribute. Curves are defined in FIPS PUB 186-4 standard. @constant kSecAttrKeyTypeEC This is the legacy name for kSecAttrKeyTypeECSECPrimeRandom, new applications should not use it. @constant kSecAttrKeyTypeDSA (OSX only) @constant kSecAttrKeyTypeAES (OSX only) diff --git a/keychain/headers/SecKey.h b/keychain/headers/SecKey.h index 021a672a..9b0a67b0 100644 --- a/keychain/headers/SecKey.h +++ b/keychain/headers/SecKey.h @@ -871,7 +871,7 @@ __OSX_AVAILABLE(10.12) __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AV * kSecAttrKeySizeInBits * kSecAttrTokenID * kSecAttrApplicationLabel - Other values returned in that dictionary are RFU. + The set of values is not fixed. Future versions may return more values in this dictionary. */ CFDictionaryRef _Nullable SecKeyCopyAttributes(SecKeyRef key) __OSX_AVAILABLE(10.12) __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0); diff --git a/keychain/ot/CuttlefishXPCWrapper.m b/keychain/ot/CuttlefishXPCWrapper.m index c67395bb..868f2bd8 100644 --- a/keychain/ot/CuttlefishXPCWrapper.m +++ b/keychain/ot/CuttlefishXPCWrapper.m @@ -189,6 +189,7 @@ enum {NUM_RETRIES = 5}; - (void)setAllowedMachineIDsWithContainer:(NSString *)container context:(NSString *)context allowedMachineIDs:(NSSet *)allowedMachineIDs + honorIDMSListChanges:(BOOL)accountIsDemo reply:(void (^)(BOOL listDifferences, NSError * _Nullable error))reply { __block int i = 0; @@ -204,7 +205,7 @@ enum {NUM_RETRIES = 5}; reply(NO, error); } ++i; - }] setAllowedMachineIDsWithContainer:container context:context allowedMachineIDs:allowedMachineIDs reply:reply]; + }] setAllowedMachineIDsWithContainer:container context:context allowedMachineIDs:allowedMachineIDs honorIDMSListChanges:accountIsDemo reply:reply]; } while (retry); } @@ -309,7 +310,7 @@ enum {NUM_RETRIES = 5}; deviceName:(nullable NSString*)deviceName serialNumber:(NSString *)serialNumber osVersion:(NSString *)osVersion - policyVersion:(nullable NSNumber *)policyVersion + policyVersion:(nullable TPPolicy *)policyVersion policySecrets:(nullable NSDictionary *)policySecrets signingPrivKeyPersistentRef:(nullable NSData *)spkPr encPrivKeyPersistentRef:(nullable NSData*)epkPr @@ -318,6 +319,8 @@ enum {NUM_RETRIES = 5}; NSData * _Nullable permanentInfoSig, NSData * _Nullable stableInfo, NSData * _Nullable stableInfoSig, + NSSet* syncingViews, + TPPolicy* _Nullable syncingPolicy, NSError * _Nullable error))reply { __block int i = 0; @@ -330,10 +333,24 @@ enum {NUM_RETRIES = 5}; retry = true; } else { secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); - reply(nil, nil, nil, nil, nil, error); + reply(nil, nil, nil, nil, nil, nil, nil, error); } ++i; - }] prepareWithContainer:container context:context epoch:epoch machineID:machineID bottleSalt:bottleSalt bottleID:bottleID modelID:modelID deviceName:deviceName serialNumber:serialNumber osVersion:osVersion policyVersion:policyVersion policySecrets:policySecrets signingPrivKeyPersistentRef:spkPr encPrivKeyPersistentRef:epkPr reply:reply]; + }] prepareWithContainer:container + context:context + epoch:epoch + machineID:machineID + bottleSalt:bottleSalt + bottleID:bottleID + modelID:modelID + deviceName:deviceName + serialNumber:serialNumber + osVersion:osVersion + policyVersion:policyVersion + policySecrets:policySecrets + signingPrivKeyPersistentRef:spkPr + encPrivKeyPersistentRef:epkPr + reply:reply]; } while (retry); } @@ -396,7 +413,10 @@ enum {NUM_RETRIES = 5}; - (void)preflightVouchWithBottleWithContainer:(nonnull NSString *)container context:(nonnull NSString *)context bottleID:(nonnull NSString *)bottleID - reply:(nonnull void (^)(NSString * _Nullable, NSError * _Nullable))reply { + reply:(nonnull void (^)(NSString * _Nullable, + NSSet* _Nullable peerSyncingViewList, + TPPolicy * _Nullable peerSyncingPolicy, + NSError * _Nullable))reply { __block int i = 0; __block bool retry; do { @@ -407,7 +427,7 @@ enum {NUM_RETRIES = 5}; retry = true; } else { secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); - reply(nil, error); + reply(nil, nil, nil, error); } ++i; }] preflightVouchWithBottleWithContainer:container @@ -425,6 +445,8 @@ enum {NUM_RETRIES = 5}; tlkShares:(NSArray *)tlkShares reply:(void (^)(NSData * _Nullable voucher, NSData * _Nullable voucherSig, + int64_t uniqueTLKsRecovered, + int64_t totalTLKSharesRecovered, NSError * _Nullable error))reply { __block int i = 0; @@ -437,13 +459,42 @@ enum {NUM_RETRIES = 5}; retry = true; } else { secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); - reply(nil, nil, error); + reply(nil, nil, 0, 0, error); } ++i; }] vouchWithBottleWithContainer:container context:context bottleID:bottleID entropy:entropy bottleSalt:bottleSalt tlkShares:tlkShares reply:reply]; } while (retry); } +- (void)preflightVouchWithRecoveryKeyWithContainer:(nonnull NSString *)container + context:(nonnull NSString *)context + recoveryKey:(NSString*)recoveryKey + salt:(NSString*)salt + reply:(nonnull void (^)(NSString * _Nullable, + NSSet* _Nullable peerSyncingViewList, + TPPolicy * _Nullable peerSyncingPolicy, + NSError * _Nullable))reply { + __block int i = 0; + __block bool retry; + do { + retry = false; + [[self.cuttlefishXPCConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError *_Nonnull error) { + if (i < NUM_RETRIES && [self.class retryable:error]) { + secnotice("octagon", "retrying cuttlefish XPC, (%d, %@)", i, error); + retry = true; + } else { + secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); + reply(nil, nil, nil, error); + } + ++i; + }] preflightVouchWithRecoveryKeyWithContainer:container + context:context + recoveryKey:recoveryKey + salt:salt + reply:reply]; + } while (retry); +} + - (void)vouchWithRecoveryKeyWithContainer:(NSString *)container context:(NSString *)context recoveryKey:(NSString*)recoveryKey @@ -479,6 +530,8 @@ enum {NUM_RETRIES = 5}; preapprovedKeys:(NSArray *)preapprovedKeys reply:(void (^)(NSString * _Nullable peerID, NSArray* _Nullable keyHierarchyRecords, + NSSet* _Nullable viewSet, + TPPolicy* _Nullable syncingPolicy, NSError * _Nullable error))reply { __block int i = 0; @@ -491,7 +544,7 @@ enum {NUM_RETRIES = 5}; retry = true; } else { secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); - reply(nil, nil, error); + reply(nil, nil, nil, nil, error); } ++i; }] joinWithContainer:container context:context voucherData:voucherData voucherSig:voucherSig ckksKeys:viewKeySets tlkShares:tlkShares preapprovedKeys:preapprovedKeys reply:reply]; @@ -527,6 +580,8 @@ enum {NUM_RETRIES = 5}; preapprovedKeys:(NSArray *)preapprovedKeys reply:(void (^)(NSString * _Nullable peerID, NSArray* _Nullable keyHierarchyRecords, + NSSet* _Nullable syncingViewList, + TPPolicy* _Nullable syncingPolicy, NSError * _Nullable error))reply { __block int i = 0; @@ -539,7 +594,7 @@ enum {NUM_RETRIES = 5}; retry = true; } else { secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); - reply(nil, nil, error); + reply(nil, nil, nil, nil, error); } ++i; }] attemptPreapprovedJoinWithContainer:container context:context ckksKeys:ckksKeys tlkShares:tlkShares preapprovedKeys:preapprovedKeys reply:reply]; @@ -575,7 +630,7 @@ enum {NUM_RETRIES = 5}; - (void)setPreapprovedKeysWithContainer:(NSString *)container context:(NSString *)context preapprovedKeys:(NSArray *)preapprovedKeys - reply:(void (^)(NSError * _Nullable error))reply + reply:(void (^)(TrustedPeersHelperPeerState* _Nullable peerState, NSError * _Nullable error))reply { __block int i = 0; __block bool retry; @@ -587,7 +642,7 @@ enum {NUM_RETRIES = 5}; retry = true; } else { secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); - reply(error); + reply(nil, error); } ++i; }] setPreapprovedKeysWithContainer:container context:context preapprovedKeys:preapprovedKeys reply:reply]; @@ -664,8 +719,8 @@ enum {NUM_RETRIES = 5}; - (void)fetchPolicyDocumentsWithContainer:(NSString*)container context:(NSString*)context - keys:(NSDictionary*)keys - reply:(void (^)(NSDictionary*>* _Nullable entries, + versions:(NSSet*)versions + reply:(void (^)(NSDictionary* _Nullable entries, NSError * _Nullable error))reply { __block int i = 0; @@ -681,14 +736,15 @@ enum {NUM_RETRIES = 5}; reply(nil, error); } ++i; - }] fetchPolicyDocumentsWithContainer:container context:context keys:keys reply:reply]; + }] fetchPolicyDocumentsWithContainer:container context:context versions:versions reply:reply]; } while (retry); } -- (void)fetchPolicyWithContainer:(NSString*)container - context:(NSString*)context - reply:(void (^)(TPPolicy * _Nullable policy, - NSError * _Nullable error))reply +- (void)fetchCurrentPolicyWithContainer:(NSString*)container + context:(NSString*)context + reply:(void (^)(NSSet* _Nullable viewList, + TPPolicy * _Nullable policy, + NSError * _Nullable error))reply { __block int i = 0; __block bool retry; @@ -700,10 +756,10 @@ enum {NUM_RETRIES = 5}; retry = true; } else { secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); - reply(nil, error); + reply(nil, nil, error); } ++i; - }] fetchPolicyWithContainer:container context:context reply:reply]; + }] fetchCurrentPolicyWithContainer:container context:context reply:reply]; } while (retry); } @@ -820,32 +876,10 @@ enum {NUM_RETRIES = 5}; } while (retry); } -- (void)getViewsWithContainer:(NSString *)container - context:(NSString *)context - inViews:(NSArray*)inViews - reply:(void (^)(NSArray* _Nullable, NSError* _Nullable))reply -{ - __block int i = 0; - __block bool retry; - do { - retry = false; - [[self.cuttlefishXPCConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError *_Nonnull error) { - if (i < NUM_RETRIES && [self.class retryable:error]) { - secnotice("octagon", "retrying cuttlefish XPC, (%d, %@)", i, error); - retry = true; - } else { - secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); - reply(nil, error); - } - ++i; - }] getViewsWithContainer:container context:context inViews:inViews reply:reply]; - } while (retry); -} - - (void)requestHealthCheckWithContainer:(NSString *)container context:(NSString *)context requiresEscrowCheck:(BOOL)requiresEscrowCheck - reply:(void (^)(BOOL postRepairCFU, BOOL postEscrowCFU, BOOL resetOctagon, NSError* _Nullable))reply + reply:(void (^)(BOOL postRepairCFU, BOOL postEscrowCFU, BOOL resetOctagon, BOOL leaveTrust, NSError* _Nullable))reply { __block int i = 0; __block bool retry; @@ -857,10 +891,10 @@ enum {NUM_RETRIES = 5}; retry = true; } else { secerror("octagon: Can't talk with TrustedPeersHelper: %@", error); - reply(NO, NO, NO, error); + reply(NO, NO, NO, NO, error); } ++i; - }] requestHealthCheckWithContainer:container context:context requiresEscrowCheck:requiresEscrowCheck reply:reply]; + }] requestHealthCheckWithContainer:container context:context requiresEscrowCheck:requiresEscrowCheck reply:reply]; } while (retry); } diff --git a/keychain/ot/OT.m b/keychain/ot/OT.m index c5246045..4e515885 100644 --- a/keychain/ot/OT.m +++ b/keychain/ot/OT.m @@ -31,6 +31,7 @@ void OctagonInitialize(void) { OTManager* manager = [OTManager manager]; [manager initializeOctagon]; + [manager setupAnalytics]; } // If you want octagon to be initialized in your daemon/tests, you must set this to be true @@ -48,11 +49,7 @@ void OctagonSetShouldPerformInitialization(bool value) void SecOctagon24hrNotification(void) { #if OCTAGON @autoreleasepool { - [[OTManager manager] xpc24HrNotification:OTCKContainerName context:OTDefaultContext skipRateLimitingCheck:NO reply: ^(NSError * error) { - if(error){ - secerror("error attempting to check octagon health: %@", error); - } - }]; + [[OTManager manager] xpc24HrNotification]; } #endif } diff --git a/keychain/ot/OTAuthKitAdapter.h b/keychain/ot/OTAuthKitAdapter.h index 5539aa1f..e0f4ec03 100644 --- a/keychain/ot/OTAuthKitAdapter.h +++ b/keychain/ot/OTAuthKitAdapter.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString* _Nullable)primaryiCloudAccountAltDSID:(NSError **)error; - (BOOL)accountIsHSA2ByAltDSID:(NSString*)altDSID; +- (BOOL)accountIsDemoAccount:(NSError**)error NS_SWIFT_NOTHROW; - (NSString* _Nullable)machineID:(NSError**)error; - (void)fetchCurrentDeviceList:(void (^)(NSSet* _Nullable machineIDs, NSError* _Nullable error))complete; diff --git a/keychain/ot/OTAuthKitAdapter.m b/keychain/ot/OTAuthKitAdapter.m index b9452d2e..80e1ff92 100644 --- a/keychain/ot/OTAuthKitAdapter.m +++ b/keychain/ot/OTAuthKitAdapter.m @@ -53,18 +53,43 @@ - (BOOL)accountIsHSA2ByAltDSID:(NSString*)altDSID { - bool hsa2 = false; + BOOL hsa2 = NO; AKAccountManager *manager = [AKAccountManager sharedInstance]; ACAccount *authKitAccount = [manager authKitAccountWithAltDSID:altDSID]; AKAppleIDSecurityLevel securityLevel = [manager securityLevelForAccount:authKitAccount]; if(securityLevel == AKAppleIDSecurityLevelHSA2) { - hsa2 = true; + hsa2 = YES; } secnotice("security-authkit", "Security level for altDSID %@ is %lu", altDSID, (unsigned long)securityLevel); return hsa2; } +- (BOOL)accountIsDemoAccount:(NSError**)error +{ + NSError* localError = nil; + NSString* altDSID = [self primaryiCloudAccountAltDSID:&localError]; + + if(altDSID == nil) { + secerror("octagon-authkit:could not retrieve altDSID"); + } + if (localError) { + secerror("octagon-authkit: hit an error retrieving altDSID: %@", localError); + if(error){ + *error = localError; + } + return NO; + } + + AKAccountManager *manager = [AKAccountManager sharedInstance]; + ACAccount *authKitAccount = [manager authKitAccountWithAltDSID:altDSID]; + BOOL isDemo = [manager demoAccountForAccount:authKitAccount]; + + secnotice("security-authkit", "Account with altDSID %@ is a demo account: %@", altDSID, isDemo ? @"true" : @"false"); + + return isDemo; +} + - (NSString* _Nullable)machineID:(NSError**)error { AKAnisetteProvisioningController* anisetteController = [[AKAnisetteProvisioningController alloc] init]; diff --git a/keychain/ot/OTCheckHealthOperation.h b/keychain/ot/OTCheckHealthOperation.h index e3463e6d..430fa43f 100644 --- a/keychain/ot/OTCheckHealthOperation.h +++ b/keychain/ot/OTCheckHealthOperation.h @@ -53,6 +53,7 @@ NS_ASSUME_NONNULL_BEGIN @property BOOL postRepairCFU; @property BOOL postEscrowCFU; @property BOOL resetOctagon; +@property BOOL leaveTrust; @end diff --git a/keychain/ot/OTCheckHealthOperation.m b/keychain/ot/OTCheckHealthOperation.m index b76ce462..8158222a 100644 --- a/keychain/ot/OTCheckHealthOperation.m +++ b/keychain/ot/OTCheckHealthOperation.m @@ -61,6 +61,7 @@ _postRepairCFU = NO; _postEscrowCFU = NO; _resetOctagon = NO; + _leaveTrust = NO; _skipRateLimitingCheck = skipRateLimitedCheck; } return self; @@ -99,7 +100,9 @@ self.error = nil; lastUpdate = [self.deps.stateHolder lastHealthCheckupDate:&accountLoadError]; - if([self.deps.viewManager.lockStateTracker isLockedError: accountLoadError]) { + + CKKSViewManager* viewManager = self.deps.viewManager; + if([viewManager.lockStateTracker isLockedError: accountLoadError]) { secnotice("octagon-health", "device is locked, not performing cuttlefish check"); [self runBeforeGroupFinished:self.finishOp]; return; @@ -132,7 +135,7 @@ NSError* persistedError = nil; BOOL persisted = [self.deps.stateHolder persistLastHealthCheck:now error:&persistedError]; - if([self.deps.viewManager.lockStateTracker isLockedError: persistedError]) { + if([viewManager.lockStateTracker isLockedError: persistedError]) { secnotice("octagon-health", "device is locked, not performing cuttlefish check"); [self runBeforeGroupFinished:self.finishOp]; return; @@ -150,7 +153,7 @@ [self.deps.cuttlefishXPCWrapper requestHealthCheckWithContainer:self.deps.containerName context:self.deps.contextID requiresEscrowCheck: [self checkIfPasscodeIsSetForDevice] - reply:^(BOOL postRepairCFU, BOOL postEscrowCFU, BOOL resetOctagon, NSError *error) { + reply:^(BOOL postRepairCFU, BOOL postEscrowCFU, BOOL resetOctagon, BOOL leaveTrust, NSError *error) { STRONGIFY(self); if(error) { secerror("octagon-health: error: %@", error); @@ -159,23 +162,28 @@ [self runBeforeGroupFinished:self.finishOp]; return; } else { - secnotice("octagon-health", "cuttlefish came back with these suggestions\n: post repair? %d\n, post escrow? %d\n, reset octagon? %d", postRepairCFU, postEscrowCFU, resetOctagon); + secnotice("octagon-health", "cuttlefish came back with these suggestions\n: post repair? %d\n, post escrow? %d\n, reset octagon? %d\n leave trust? %d\n", postRepairCFU, postEscrowCFU, resetOctagon, leaveTrust); [self handleRepairSuggestions:postRepairCFU postEscrowCFU:postEscrowCFU - resetOctagon:resetOctagon]; + resetOctagon:resetOctagon + leaveTrust:leaveTrust]; } }]; } -- (void)handleRepairSuggestions:(BOOL)postRepairCFU postEscrowCFU:(BOOL)postEscrowCFU resetOctagon:(BOOL)resetOctagon +- (void)handleRepairSuggestions:(BOOL)postRepairCFU postEscrowCFU:(BOOL)postEscrowCFU resetOctagon:(BOOL)resetOctagon leaveTrust:(BOOL)leaveTrust { self.postEscrowCFU = postEscrowCFU; self.postRepairCFU = postRepairCFU; self.resetOctagon = resetOctagon; + self.leaveTrust = leaveTrust; if (resetOctagon) { secnotice("octagon-health", "Resetting Octagon as per Cuttlefish request"); self.nextState = OctagonStateHealthCheckReset; + } else if(leaveTrust) { + secnotice("octagon-health", "Leaving clique as per Cuttlefish request"); + self.nextState = OctagonStateHealthCheckLeaveClique; } else { self.nextState = self.intendedState; } diff --git a/keychain/ot/OTClientStateMachine.m b/keychain/ot/OTClientStateMachine.m index b36a24aa..88f684f1 100644 --- a/keychain/ot/OTClientStateMachine.m +++ b/keychain/ot/OTClientStateMachine.m @@ -42,7 +42,6 @@ #import "keychain/ot/OTConstants.h" #import "keychain/ot/OTClientStateMachine.h" -#import "keychain/ot/OTPrepareOperation.h" #import "keychain/ot/OTSOSAdapter.h" #import "keychain/ot/OTEpochOperation.h" #import "keychain/ot/OTClientVoucherOperation.h" diff --git a/keychain/ot/OTClique.h b/keychain/ot/OTClique.h index 77217221..cfe080f1 100644 --- a/keychain/ot/OTClique.h +++ b/keychain/ot/OTClique.h @@ -44,27 +44,41 @@ typedef NS_ENUM(NSInteger, CliqueStatus) { #import #import +typedef NS_ENUM(NSInteger, OTCDPStatus) { + OTCDPStatusUnknown = 0, + OTCDPStatusDisabled = 1, + OTCDPStatusEnabled = 2, +}; + NS_ASSUME_NONNULL_BEGIN NSString* OTCliqueStatusToString(CliqueStatus status); CliqueStatus OTCliqueStatusFromString(NSString* str); +NSString* OTCDPStatusToString(OTCDPStatus status); @class KCPairingChannelContext; @class KCPairingChannel; @class OTPairingChannel; @class OTPairingChannelContext; @class OTControl; +@class CKKSControl; extern NSString* kSecEntitlementPrivateOctagonEscrow; @interface OTConfigurationContext : NSObject -@property (nonatomic, copy, nullable) NSString* context; -@property (nonatomic, copy) NSString* dsid; -@property (nonatomic, copy) NSString* altDSID; +@property (nonatomic, copy) NSString* context; +@property (nonatomic, copy, nullable) NSString* dsid; +@property (nonatomic, copy, nullable) NSString* altDSID; @property (nonatomic, strong, nullable) SFSignInAnalytics* analytics; +@property (nonatomic, copy, nullable) NSString* authenticationAppleID; +@property (nonatomic, copy, nullable) NSString* passwordEquivalentToken; // Use this to inject your own OTControl object. It must be configured as synchronous. @property (nullable, strong) OTControl* otControl; + +// Use this to inject your own CKKSControl object. It must be configured as synchronous. +@property (nullable, strong) CKKSControl* ckksControl; + // Use this to inject your own SecureBackup object. It must conform to the OctagonEscrowRecoverer protocol. @property (nullable, strong) id sbd; @@ -110,10 +124,15 @@ extern OTCliqueCDPContextType OTCliqueCDPContextTypeUpdatePasscode; /* * * @abstract, initializes a clique object given a context. A clique object enables octagon trust operations for a given context and dsid. - * @param ctx, a unique string that is used as a way to retrieve current trust state + * @param ctx, a collection of arguments describing the world * @return an instance of octagon trust */ -- (instancetype _Nullable)initWithContextData:(OTConfigurationContext *)ctx error:(NSError * __autoreleasing * _Nonnull)error; +- (instancetype)initWithContextData:(OTConfigurationContext *)ctx; + +/* + * Much like initWithContextData, but might fail. There are currently no failures possible. + */ +- (instancetype _Nullable)initWithContextData:(OTConfigurationContext *)ctx error:(NSError**)error __deprecated_msg("Use initWithContextData instead"); /* * * @abstract Establish a new clique, reset protected data @@ -155,10 +174,8 @@ extern OTCliqueCDPContextType OTCliqueCDPContextTypeUpdatePasscode; /* * * @abstract Create pairing channel with * - * @param ctx, context containing parameters to setup OTClique - * @param pairingChannelContext, context containing parameters to setup the pairing channel as the initiator - * @return clique, An instance of an OTClique - * @return error, error gets filled if something goes horribly wrong + * @param ctx, context containing parameters to setup the pairing channel as the initiator + * @return KCPairingChannel, An instance of a KCPairingCHannel */ - (KCPairingChannel *)setupPairingChannelAsInitiator:(KCPairingChannelContext *)ctx; @@ -167,10 +184,8 @@ extern OTCliqueCDPContextType OTCliqueCDPContextTypeUpdatePasscode; /* * * @abstract Configure this peer as the acceptor during piggybacking * - * @param ctx, context containing parameters to setup OTClique - * @param pairingChannelContext, context containing parameters to setup the pairing channel as the acceptor - * @param error, error gets filled if something goes horribly wrong - * @return KCPairingChannel, An instance of an OTClique + * @param ctx, context containing parameters to setup the pairing channel as the acceptor + * @return KCPairingChannel, An instance of a KCPairingChannel */ - (KCPairingChannel *)setupPairingChannelAsAcceptor:(KCPairingChannelContext *)ctx; @@ -233,6 +248,15 @@ extern OTCliqueCDPContextType OTCliqueCDPContextTypeUpdatePasscode; - (NSDictionary* _Nullable)peerDeviceNamesByPeerID:(NSError * __autoreleasing *)error; +/* + * CDP bit handling + */ + ++ (BOOL)setCDPEnabled:(OTConfigurationContext*)arguments + error:(NSError* __autoreleasing*)error; + ++ (OTCDPStatus)getCDPStatus:(OTConfigurationContext*)arguments + error:(NSError* __autoreleasing *)error; /* SOS glue */ @@ -321,6 +345,17 @@ extern OTCliqueCDPContextType OTCliqueCDPContextTypeUpdatePasscode; // CoreCDP will call this function when they are upgrading an account from SA to HSA2 - (BOOL)waitForOctagonUpgrade:(NSError** _Nullable)error; + +/* +* @abstract CoreCDP to call this function when they need to reset protected data. +* This routine resets all circles, creates a new octagon and sos circle, then puts this device into each circle. +* This routine does not create a new escrow record +* This routine will need ensure OTConfigurationContext contains appleID and passwordEquivalentToken to delete all CDP records +* @param data The OTClique configuration data +* @param error Reports any error along the process +* @return a new clique +*/ ++ (OTClique* _Nullable)resetProtectedData:(OTConfigurationContext*)data error:(NSError**)error; @end NS_ASSUME_NONNULL_END diff --git a/keychain/ot/OTClique.m b/keychain/ot/OTClique.m index 5bf39cee..fbf8e5da 100644 --- a/keychain/ot/OTClique.m +++ b/keychain/ot/OTClique.m @@ -47,6 +47,7 @@ const NSString* kSecEntitlementPrivateOctagonEscrow = @"com.apple.private.octago #import #import #import "keychain/ot/OTControl.h" +#import "keychain/ckks/CKKSControl.h" #import "keychain/ot/categories/OctagonEscrowRecoverer.h" SOFT_LINK_FRAMEWORK(PrivateFrameworks, KeychainCircle); @@ -58,6 +59,10 @@ SOFT_LINK_CLASS(KeychainCircle, KCPairingChannel); SOFT_LINK_CLASS(KeychainCircle, OTPairingChannel); SOFT_LINK_CLASS(CloudServices, SecureBackup); SOFT_LINK_CONSTANT(CloudServices, kSecureBackupErrorDomain, NSErrorDomain); +SOFT_LINK_CONSTANT(CloudServices, kSecureBackupAuthenticationAppleID, NSString*); +SOFT_LINK_CONSTANT(CloudServices, kSecureBackupAuthenticationPassword, NSString*); +SOFT_LINK_CONSTANT(CloudServices, kSecureBackupiCloudDataProtectionDeleteAllRecordsKey, NSString*); +SOFT_LINK_CONSTANT(CloudServices, kSecureBackupContainsiCDPDataKey, NSString*); #pragma clang diagnostic pop #endif @@ -106,6 +111,17 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) return CliqueStatusError; } +NSString* OTCDPStatusToString(OTCDPStatus status) { + switch(status) { + case OTCDPStatusUnknown: + return @"unknown"; + case OTCDPStatusDisabled: + return @"disabled"; + case OTCDPStatusEnabled: + return @"enabled"; + } +} + @implementation OTConfigurationContext - (OTControl* _Nullable)makeOTControl:(NSError**)error @@ -119,6 +135,26 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) return nil; #endif } + +- (CKKSControl* _Nullable)makeCKKSControl:(NSError**)error +{ +#if OCTAGON + if(self.ckksControl) { + return self.ckksControl; + } + return [CKKSControl CKKSControlObject:true error:error]; +#else + return nil; +#endif +} + +- (instancetype)init +{ + if((self = [super init])) { + _context = OTDefaultContext; + } + return self; +} @end @implementation OTBottleIDs @@ -190,6 +226,11 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) } - (instancetype)initWithContextData:(OTConfigurationContext *)ctx error:(NSError * __autoreleasing *)error +{ + return [self initWithContextData:ctx]; +} + +- (instancetype)initWithContextData:(OTConfigurationContext *)ctx { #if OCTAGON self = [super init]; @@ -200,13 +241,17 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) _ctx.altDSID = [ctx.altDSID copy]; _ctx.analytics = ctx.analytics; _ctx.otControl = ctx.otControl; + _ctx.ckksControl = ctx.ckksControl; self.defaults = [NSMutableDictionary dictionary]; } return self; #else NSAssert(false, @"OTClique is not implemented on this platform"); - return nil; + + // make the build analyzer happy + self = [super init]; + return self; #endif // OCTAGON } @@ -242,6 +287,7 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) SOSPeerInfoRef me = SOSCCCopyMyPeerInfo(&error); retPeerID = (NSString*)CFBridgingRelease(CFRetainSafe(SOSPeerInfoGetPeerID(me))); CFReleaseNull(me); + CFBridgingRelease(error); } secnotice("clique", "cliqueMemberIdentifier complete: %@", retPeerID); @@ -278,7 +324,7 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) if(operationError) { secnotice("clique-establish", "establish returned an error: %@", operationError); } - success = !!operationError; + success = operationError == nil; localError = operationError; }]; @@ -312,7 +358,7 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) if(operationError) { secnotice("clique-resetandestablish", "resetAndEstablish returned an error: %@", operationError); } - success = !!operationError; + success = operationError == nil; localError = operationError; }]; @@ -341,7 +387,7 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) bool subTaskSuccess = false; OctagonSignpost performEscrowRecoverySignpost = OctagonSignpostBegin(OctagonSignpostNameMakeNewFriends); - OTClique* clique = [[OTClique alloc] initWithContextData:data error:error]; + OTClique* clique = [[OTClique alloc] initWithContextData:data]; if(OctagonIsEnabled()) { NSError* localError = nil; @@ -361,22 +407,14 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) if([OTClique platformSupportsSOS]) { CFErrorRef resetError = NULL; - NSData* analyticsData = nil; - if(data.analytics) { - NSError* encodingError = nil; - analyticsData = [NSKeyedArchiver archivedDataWithRootObject:data.analytics requiringSecureCoding:YES error:&encodingError]; - - if(encodingError) { - secnotice("clique-newfriends", "newFriendsWithContextData: unable to serialize analytics: %@", encodingError); - } - } - result = SOSCCResetToOffering(&resetError); if(!result || resetError){ secnotice("clique-newfriends", "newFriendsWithContextData: resetToOffering failed: %@", resetError); if(error) { *error = CFBridgingRelease(resetError); + } else { + CFBridgingRelease(resetError); } OctagonSignpostEnd(performEscrowRecoverySignpost, OctagonSignpostNameMakeNewFriends, OctagonSignpostNumber1(OctagonSignpostNameMakeNewFriends), (int)subTaskSuccess); return nil; @@ -407,17 +445,8 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) OctagonSignpost performEscrowRecoverySignpost = OctagonSignpostBegin(OctagonSignpostNamePerformEscrowRecovery); bool subTaskSuccess = false; NSError* localError = nil; - OTClique* clique = [[OTClique alloc] initWithContextData:data - error:&localError]; - if(!clique || localError) { - secnotice("clique-recovery", "unable to create otclique: %@", localError); - if(error) { - *error = localError; - } - OctagonSignpostEnd(performEscrowRecoverySignpost, OctagonSignpostNamePerformEscrowRecovery, OctagonSignpostNumber1(OctagonSignpostNamePerformEscrowRecovery), (int)subTaskSuccess); - return nil; - } + OTClique* clique = [[OTClique alloc] initWithContextData:data]; // Attempt the recovery from sbd secnotice("clique-recovery", "attempting an escrow recovery for context:%@, altdsid:%@", data.context, data.altDSID); @@ -441,6 +470,7 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) } else { secnotice("clique-recovery", "resetting SOS circle successful"); } + CFBridgingRelease(blowItAwayError); } else { secnotice("clique-recovery", "Legacy restore failed on a non-SOS platform"); } @@ -1166,12 +1196,12 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) } BOOL setCredentialsResult = result ? YES : NO; + secnotice("clique-legacy", "setUserCredentialsAndDSID results: %d %@", setCredentialsResult, setCredentialsErrorRef); if (error) { *error = (NSError*)CFBridgingRelease(setCredentialsErrorRef); } else { CFBridgingRelease(setCredentialsErrorRef); } - secnotice("clique-legacy", "setUserCredentialsAndDSID results: %d %@", setCredentialsResult, setCredentialsErrorRef); subTaskSuccess = result; OctagonSignpostEnd(signPost, OctagonSignpostNameSetUserCredentialsAndDSID, OctagonSignpostNumber1(OctagonSignpostNameSetUserCredentialsAndDSID), (int)subTaskSuccess); @@ -1204,12 +1234,12 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) &tryCredentialsErrorRef); BOOL tryCredentialsResult = result ? YES : NO; + secnotice("clique-legacy", "tryUserCredentialsAndDSID results: %d %@", tryCredentialsResult, tryCredentialsErrorRef); if (error) { *error = (NSError*)CFBridgingRelease(tryCredentialsErrorRef); } else { CFBridgingRelease(tryCredentialsErrorRef); } - secnotice("clique-legacy", "tryUserCredentialsAndDSID results: %d %@", tryCredentialsResult, tryCredentialsErrorRef); subTaskSuccess = result; OctagonSignpostEnd(signPost, OctagonSignpostNameTryUserCredentialsAndDSID, OctagonSignpostNumber1(OctagonSignpostNameTryUserCredentialsAndDSID), (int)subTaskSuccess); return tryCredentialsResult; @@ -1238,12 +1268,12 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) NSArray* peerList = (result ? (NSArray*)(CFBridgingRelease(result)) : nil); + secnotice("clique-legacy", "copyPeerPeerInfo results: %@ (%@)", peerList, copyPeerErrorRef); if (error) { *error = (NSError*)CFBridgingRelease(copyPeerErrorRef); } else { CFBridgingRelease(copyPeerErrorRef); } - secnotice("clique-legacy", "copyPeerPeerInfo results: %@", peerList); subTaskSuccess = (peerList != nil) ? true : false; OctagonSignpostEnd(signPost, OctagonSignpostNameCopyPeerPeerInfo, OctagonSignpostNumber1(OctagonSignpostNameCopyPeerPeerInfo), (int)subTaskSuccess); return peerList; @@ -1273,12 +1303,13 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) if(result){ viewsEnabledResult = CFBooleanGetValue(result) ? YES : NO; } + secnotice("clique-legacy", "peersHaveViewsEnabled results: %@ (%@)", viewsEnabledResult ? @"YES" : @"NO", + viewsEnabledErrorRef); if (error) { *error = (NSError*)CFBridgingRelease(viewsEnabledErrorRef); } else { CFBridgingRelease(viewsEnabledErrorRef); } - secnotice("clique-legacy", "peersHaveViewsEnabled results: %@", viewsEnabledResult ? @"YES" : @"NO"); subTaskSuccess = viewsEnabledResult ? true : false; OctagonSignpostEnd(signPost, OctagonSignpostNamePeersHaveViewsEnabled, OctagonSignpostNumber1(OctagonSignpostNamePeersHaveViewsEnabled), (int)subTaskSuccess); return viewsEnabledResult; @@ -1297,7 +1328,6 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) - (BOOL)requestToJoinCircle:(NSError *__autoreleasing*)error { bool result = false; - CFErrorRef joinErrorRef = NULL; bool subTaskSuccess = false; OctagonSignpost signPost = OctagonSignpostBegin(OctagonSignpostNameRequestToJoinCircle); @@ -1340,7 +1370,7 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) // If we didn't early-exit, and we aren't going to invoke SOS below, we succeeded. if(!OctagonPlatformSupportsSOS()) { - secnotice("clique-legacy", "requestToJoinCircle results: %d %@", result, joinErrorRef); + secnotice("clique-legacy", "requestToJoinCircle platform does not support SOS"); subTaskSuccess = true; OctagonSignpostEnd(signPost, OctagonSignpostNameRequestToJoinCircle, OctagonSignpostNumber1(OctagonSignpostNameRequestToJoinCircle), (int)subTaskSuccess); return YES; @@ -1350,6 +1380,7 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) if([OTClique platformSupportsSOS]) { NSData* analyticsData = nil; + CFErrorRef joinErrorRef = NULL; if(self.ctx.analytics){ NSError* encodingError = nil; analyticsData = [NSKeyedArchiver archivedDataWithRootObject:self.ctx.analytics requiringSecureCoding:YES error:&encodingError]; @@ -1362,13 +1393,13 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) } secnotice("clique-legacy", "sos requestToJoinCircle complete: %d %@", result, joinErrorRef); + if (error) { + *error = (NSError*)CFBridgingRelease(joinErrorRef); + } else { + CFBridgingRelease(joinErrorRef); + } } - if (error) { - *error = (NSError*)CFBridgingRelease(joinErrorRef); - } else { - CFBridgingRelease(joinErrorRef); - } subTaskSuccess = result; OctagonSignpostEnd(signPost, OctagonSignpostNameRequestToJoinCircle, OctagonSignpostNumber1(OctagonSignpostNameRequestToJoinCircle), (int)subTaskSuccess); @@ -1741,6 +1772,184 @@ CliqueStatus OTCliqueStatusFromString(NSString* str) [self performedCDPStateMachineRun:type success:YES error:nil reply:reply]; } ++ (BOOL)setCDPEnabled:(OTConfigurationContext*)arguments + error:(NSError* __autoreleasing*)error +{ +#if OCTAGON + NSError *controlError = nil; + OTControl* control = [arguments makeOTControl:&controlError]; + if (!control) { + secerror("octagon-setcdpenabled: failed to fetch OTControl object: %@", controlError); + if (error) { + *error = controlError; + } + return NO; + } + + __block NSError* reterror = nil; + + [control setCDPEnabled:nil + contextID:arguments.context + reply:^(NSError * _Nullable resultError) { + if(resultError) { + secnotice("octagon-setcdpenabled", "failed to set CDP bit: %@", resultError); + reterror = resultError; + } else { + secnotice("octagon-setcdpenabled", "successfully set CDP bit"); + } + }]; + + if(reterror && error) { + *error = reterror; + } + + return (reterror == nil); +#else // !OCTAGON + if(error) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecUnimplemented userInfo:nil]; + } + return NO; +#endif +} + ++ (OTCDPStatus)getCDPStatus:(OTConfigurationContext*)arguments + error:(NSError* __autoreleasing *)error +{ +#if OCTAGON + NSError *controlError = nil; + OTControl* control = [arguments makeOTControl:&controlError]; + if (!control) { + secerror("octagon-cdp-status: failed to fetch OTControl object: %@", controlError); + if (error) { + *error = controlError; + } + return OTCDPStatusUnknown; + } + + __block NSError* reterror = nil; + __block OTCDPStatus retcdpstatus = OTCDPStatusUnknown; + + [control getCDPStatus:nil + contextID:arguments.context + reply:^(OTCDPStatus status, NSError * _Nullable resultError) { + if(resultError) { + secnotice("octagon-cdp-status", "failed to fetch CDP status: %@", resultError); + reterror = resultError; + + } else { + secnotice("octagon-cdp-status", "successfully fetched CDP status as %@", OTCDPStatusToString(status)); + retcdpstatus = status; + } + }]; + + if(reterror && error) { + *error = reterror; + } + + return retcdpstatus; +#else // !OCTAGON + if(error) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecUnimplemented userInfo:nil]; + } + return OTCDPStatusDisabled; +#endif +} + ++ (OTClique* _Nullable)resetProtectedData:(OTConfigurationContext*)data error:(NSError**)error +{ +#if OCTAGON + NSError *controlError = nil; + OTControl *control = [data makeOTControl:&controlError]; + if (!control) { + secerror("clique-reset-protected-data: unable to create otcontrol: %@", controlError); + if (error) { + *error = controlError; + } + return nil; + } + + __block NSError* localError = nil; + + //delete all records + id sb = data.sbd ?: [[getSecureBackupClass() alloc] init]; + + NSDictionary* deletionInformation = @{ getkSecureBackupAuthenticationAppleID() : data.authenticationAppleID, + getkSecureBackupAuthenticationPassword() : data.passwordEquivalentToken, + getkSecureBackupiCloudDataProtectionDeleteAllRecordsKey() : @YES, + getkSecureBackupContainsiCDPDataKey() : @YES}; + + NSError* sbError = [sb disableWithInfo:deletionInformation]; + if(sbError) { + secerror("clique-reset-protected-data: secure backup escrow record deletion failed: %@", sbError); + if(error) { + *error = sbError; + } + return nil; + } else { + secnotice("clique-reset-protected-data", "sbd disableWithInfo succeeded"); + } + + //reset sos + if(OctagonPlatformSupportsSOS()) { + //reset SOS + CFErrorRef sosError = NULL; + bool resetSuccess = SOSCCResetToOffering(&sosError); + + if(sosError || !resetSuccess) { + secerror("clique-reset-protected-data: sos reset failed: %@", sosError); + if(error) { + *error = (NSError*)CFBridgingRelease(sosError); + } + return nil; + } else { + secnotice("clique-reset-protected-data", "sos reset succeeded"); + } + } else { + secnotice("clique-reset-protected-data", "platform does not support sos"); + } + + //reset octagon + OTClique* clique = [[OTClique alloc] initWithContextData:data]; + if(OctagonIsEnabled()) { + [clique resetAndEstablish:CuttlefishResetReasonUserInitiatedReset error:&localError]; + + if(localError) { + secerror("clique-reset-protected-data: account reset failed: %@", localError); + if(error) { + *error = localError; + } + return nil; + } else { + secnotice("clique-reset-protected-data", "Octagon account reset succeeded"); + } + } + + //reset ckks + CKKSControl* ckksControl = [data makeCKKSControl:&localError]; + [ckksControl rpcResetCloudKit:nil reason:@"clique-reset-protected-data" reply:^(NSError* resetError){ + localError = resetError; + }]; + + if(localError) { + secerror("clique-reset-protected-data: ckks cloudkit reset failed: %@", localError); + if(error) { + *error = localError; + } + return nil; + } else { + secnotice("clique-reset-protected-data", "ckks cloudkit reset succeeded"); + } + + return clique; +#else // !OCTAGON + if(error) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecUnimplemented userInfo:nil]; + } + return nil; +#endif + +} + @end #endif /* OBJC2 */ diff --git a/keychain/ot/OTControl.h b/keychain/ot/OTControl.h index 6e8b48de..c279f62c 100644 --- a/keychain/ot/OTControl.h +++ b/keychain/ot/OTControl.h @@ -85,12 +85,12 @@ NS_ASSUME_NONNULL_BEGIN NSError * _Nullable error))reply; - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config - reply:(void (^)(NSString * _Nullable peerID, - NSData * _Nullable permanentInfo, - NSData * _Nullable permanentInfoSig, - NSData * _Nullable stableInfo, - NSData * _Nullable stableInfoSig, - NSError * _Nullable error))reply; + reply:(void (^)(NSString * _Nullable peerID, + NSData * _Nullable permanentInfo, + NSData * _Nullable permanentInfoSig, + NSData * _Nullable stableInfo, + NSData * _Nullable stableInfoSig, + NSError * _Nullable error))reply; - (void)rpcVoucherWithConfiguration:(OTJoiningConfiguration*)config peerID:(NSString*)peerID permanentInfo:(NSData *)permanentInfo @@ -102,7 +102,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config vouchData:(NSData*)vouchData vouchSig:(NSData*)vouchSig - preapprovedKeys:(NSArray* _Nullable)preapprovedKeys reply:(void (^)(NSError * _Nullable error))reply; @@ -235,6 +234,18 @@ skipRateLimitingCheck:(BOOL)skipRateLimitingCheck radar:(NSString *)radar reply:(void (^)(NSError* _Nullable error))reply; +- (void)setCDPEnabled:(NSString* _Nullable)containerName + contextID:(NSString*)contextID + reply:(void (^)(NSError* _Nullable error))reply; + +- (void)getCDPStatus:(NSString* _Nullable)containerName + contextID:(NSString*)contextID + reply:(void (^)(OTCDPStatus status, NSError* _Nullable error))reply; + +- (void)refetchCKKSPolicy:(NSString* _Nullable)containerName + contextID:(NSString*)contextID + reply:(void (^)(NSError* _Nullable error))reply; + @end NS_ASSUME_NONNULL_END diff --git a/keychain/ot/OTControl.m b/keychain/ot/OTControl.m index ce1d0965..e5669901 100644 --- a/keychain/ot/OTControl.m +++ b/keychain/ot/OTControl.m @@ -232,13 +232,12 @@ - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config vouchData:(NSData*)vouchData vouchSig:(NSData*)vouchSig - preapprovedKeys:(NSArray* _Nullable)preapprovedKeys reply:(void (^)(NSError * _Nullable error))reply { #if OCTAGON [[self getConnection: ^(NSError* error) { reply(error); - }] rpcJoinWithConfiguration:config vouchData:vouchData vouchSig:vouchSig preapprovedKeys:preapprovedKeys reply:^(NSError* e) { + }] rpcJoinWithConfiguration:config vouchData:vouchData vouchSig:vouchSig reply:^(NSError* e) { reply(e); }]; #else @@ -482,6 +481,33 @@ skipRateLimitingCheck:(BOOL)skipRateLimitingCheck }] tapToRadar:action description:description radar:radar reply:reply]; } +- (void)refetchCKKSPolicy:(NSString* _Nullable)container + contextID:(NSString*)contextID + reply:(void (^)(NSError* _Nullable error))reply +{ + [[self getConnection: ^(NSError* error) { + reply(error); + }] refetchCKKSPolicy:container contextID:contextID reply:reply]; +} + +- (void)setCDPEnabled:(NSString* _Nullable)containerName + contextID:(NSString*)contextID + reply:(void (^)(NSError* _Nullable error))reply +{ + [[self getConnection: ^(NSError* connectionError) { + reply(connectionError); + }] setCDPEnabled:containerName contextID:contextID reply:reply]; +} + +- (void)getCDPStatus:(NSString* _Nullable)containerName + contextID:(NSString*)contextID + reply:(void (^)(OTCDPStatus status, NSError* _Nullable error))reply +{ + [[self getConnection: ^(NSError* connectionError) { + reply(OTCDPStatusUnknown, connectionError); + }] getCDPStatus:containerName contextID:contextID reply:reply]; +} + + (OTControl*)controlObject:(NSError* __autoreleasing *)error { return [OTControl controlObject:false error:error]; } diff --git a/keychain/ot/OTControlProtocol.h b/keychain/ot/OTControlProtocol.h index 6af4bbef..48f99a77 100644 --- a/keychain/ot/OTControlProtocol.h +++ b/keychain/ot/OTControlProtocol.h @@ -86,7 +86,6 @@ typedef void (^OTNextJoinCompleteBlock)(BOOL finished, NSData* _Nullable message - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config vouchData:(NSData*)vouchData vouchSig:(NSData*)vouchSig - preapprovedKeys:(NSArray* _Nullable)preapprovedKeys reply:(void (^)(NSError * _Nullable error))reply; - (void)preflightBottledPeer:(NSString*)contextID @@ -206,6 +205,18 @@ skipRateLimitingCheck:(BOOL)skipRateLimitingCheck radar:(NSString *)radar reply:(void (^)(NSError* _Nullable error))reply; +- (void)refetchCKKSPolicy:(NSString* _Nullable)container + contextID:(NSString*)contextID + reply:(void (^)(NSError* _Nullable error))reply; + +- (void)setCDPEnabled:(NSString* _Nullable)containerName + contextID:(NSString*)contextID + reply:(void (^)(NSError* _Nullable error))reply; + +- (void)getCDPStatus:(NSString* _Nullable)containerName + contextID:(NSString*)contextID + reply:(void (^)(OTCDPStatus status, NSError* _Nullable error))reply; + @end NSXPCInterface* OTSetupControlProtocol(NSXPCInterface* interface); diff --git a/keychain/ot/OTControlProtocol.m b/keychain/ot/OTControlProtocol.m index f98ae502..9c0df198 100644 --- a/keychain/ot/OTControlProtocol.m +++ b/keychain/ot/OTControlProtocol.m @@ -83,7 +83,7 @@ NSXPCInterface* OTSetupControlProtocol(NSXPCInterface* interface) { argumentIndex:2 ofReply:YES]; [interface setClasses:errorClasses - forSelector:@selector(rpcJoinWithConfiguration:vouchData:vouchSig:preapprovedKeys:reply:) + forSelector:@selector(rpcJoinWithConfiguration:vouchData:vouchSig:reply:) argumentIndex:0 ofReply:YES]; #endif /* __OBJC2__ */ diff --git a/keychain/ot/OTCuttlefishAccountStateHolder.h b/keychain/ot/OTCuttlefishAccountStateHolder.h index 4630b7f9..abdb5e32 100644 --- a/keychain/ot/OTCuttlefishAccountStateHolder.h +++ b/keychain/ot/OTCuttlefishAccountStateHolder.h @@ -3,6 +3,7 @@ #import #import "keychain/ot/proto/generated_source/OTAccountMetadataClassC.h" +#import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h" extern NSString* _Nonnull OTCuttlefishContextErrorDomain; typedef NS_ENUM(uint32_t, OTCuttlefishContextErrors) { @@ -35,10 +36,10 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)persistNewEpoch:(uint64_t)epoch error:(NSError**)error; -- (BOOL)persistAccountChanges:(OTAccountMetadataClassC* (^)(OTAccountMetadataClassC* metadata))makeChanges +- (BOOL)persistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC* metadata))makeChanges error:(NSError**)error; -- (BOOL)_onqueuePersistAccountChanges:(OTAccountMetadataClassC* (^)(OTAccountMetadataClassC* metadata))makeChanges +- (BOOL)_onqueuePersistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC* metadata))makeChanges error:(NSError**)error; - (NSDate *)lastHealthCheckupDate:(NSError * _Nullable *)error; diff --git a/keychain/ot/OTCuttlefishAccountStateHolder.m b/keychain/ot/OTCuttlefishAccountStateHolder.m index 2455c543..b6d596d1 100644 --- a/keychain/ot/OTCuttlefishAccountStateHolder.m +++ b/keychain/ot/OTCuttlefishAccountStateHolder.m @@ -164,7 +164,7 @@ } error:error]; } -- (BOOL)persistAccountChanges:(OTAccountMetadataClassC* (^)(OTAccountMetadataClassC*))makeChanges +- (BOOL)persistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC*))makeChanges error:(NSError**)error { __block NSError* localError = nil; @@ -178,7 +178,7 @@ } newState = makeChanges([oldState copy]); - if(![newState saveToKeychainForContainer:self.containerName contextID:self.contextID error:&localError]) { + if(newState && ![newState saveToKeychainForContainer:self.containerName contextID:self.contextID error:&localError]) { newState = nil; } }); @@ -213,7 +213,7 @@ } error:error]; } -- (BOOL)_onqueuePersistAccountChanges:(OTAccountMetadataClassC* (^)(OTAccountMetadataClassC* metadata))makeChanges +- (BOOL)_onqueuePersistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC* metadata))makeChanges error:(NSError**)error { __block NSError* localError = nil; diff --git a/keychain/ot/OTCuttlefishContext.h b/keychain/ot/OTCuttlefishContext.h index 426c5dc8..714b7013 100644 --- a/keychain/ot/OTCuttlefishContext.h +++ b/keychain/ot/OTCuttlefishContext.h @@ -35,6 +35,7 @@ #import "keychain/ckks/CKKSCondition.h" #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h" #import "OTDeviceInformation.h" +#import "keychain/ot/OTConstants.h" #import "keychain/ot/OTDefines.h" #import "keychain/ot/OTClique.h" #import "keychain/ot/OTFollowup.h" @@ -76,15 +77,13 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) CKKSLockStateTracker *lockStateTracker; @property (nonatomic, readonly) OTCuttlefishAccountStateHolder* accountMetadataStore; @property (readonly) OctagonStateMachine* stateMachine; -@property (readonly) BOOL postedRepairCFU; -@property (readonly) BOOL postedEscrowRepairCFU; -@property (readonly) BOOL postedRecoveryKeyCFU; @property (nullable, nonatomic) CKKSNearFutureScheduler* apsRateLimiter; @property (nullable, nonatomic) CKKSNearFutureScheduler* sosConsistencyRateLimiter; @property (readonly, nullable) CKKSViewManager* viewManager; // Dependencies (for injection) +@property (readonly) id deviceAdapter; @property id authKitAdapter; @property dispatch_queue_t queue; @@ -107,6 +106,11 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)accountNoLongerAvailable:(NSError**)error; - (BOOL)idmsTrustLevelChanged:(NSError**)error; +// Call these to manipulate the "CDP-ness" of the account +// Note that there is no way to turn CDP back off again +- (OTCDPStatus)getCDPStatus:(NSError* __autoreleasing *)error; +- (BOOL)setCDPEnabled:(NSError* __autoreleasing *)error; + - (void)deviceNameUpdated; - (void)startOctagonStateMachine; @@ -122,7 +126,6 @@ NS_ASSUME_NONNULL_BEGIN NSError * _Nullable error))reply; - (void)rpcJoin:(NSData*)vouchData vouchSig:(NSData*)vouchSig -preapprovedKeys:(NSArray* _Nullable)preapprovedKeys reply:(void (^)(NSError * _Nullable error))reply; - (void)rpcResetAndEstablish:(CuttlefishResetReason)resetReason reply:(nonnull void (^)(NSError * _Nullable))reply; @@ -165,6 +168,8 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys NSError* _Nullable error))reply; - (void)rpcSetRecoveryKey:(NSString*)recoveryKey reply:(void (^)(NSError * _Nullable error))reply; +- (void)rpcRefetchCKKSPolicy:(void (^)(NSError * _Nullable error))reply; + - (void)requestTrustedDeviceListRefresh; - (OTDeviceInformation*)prepareInformation; @@ -178,24 +183,24 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys - (void)waitForOctagonUpgrade:(void (^)(NSError* error))reply NS_SWIFT_NAME(waitForOctagonUpgrade(reply:)); -- (void)clearPendingCFUFlags; - - (BOOL)waitForReady:(int64_t)timeOffset; // For testing. -- (void)setPostedBool:(BOOL)posted; - (OTAccountMetadataClassC_AccountState)currentMemoizedAccountState; - (OTAccountMetadataClassC_TrustState)currentMemoizedTrustState; - (NSDate* _Nullable) currentMemoizedLastHealthCheck; - (void) checkTrustStatusAndPostRepairCFUIfNecessary:(void (^ _Nullable)(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError * _Nullable error))reply; - (void) setAccountStateHolder:(OTCuttlefishAccountStateHolder*)accountMetadataStore; +- (void)clearCKKSViewManager; + +@property (nullable) TPPolicyVersion* policyOverride; + // Octagon Health Check Helpers - (void)checkOctagonHealth:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError * _Nullable error))reply; - (BOOL)postRepairCFU:(NSError**)error; - (void)postConfirmPasscodeCFU:(NSError**)error; -- (void)postRecoveryKeyCFU:(NSError**)error; // For reporting - (BOOL)machineIDOnMemoizedList:(NSString*)machineID error:(NSError**)error NS_SWIFT_NOTHROW; diff --git a/keychain/ot/OTCuttlefishContext.m b/keychain/ot/OTCuttlefishContext.m index 0eac866a..be6ecf96 100644 --- a/keychain/ot/OTCuttlefishContext.m +++ b/keychain/ot/OTCuttlefishContext.m @@ -22,81 +22,83 @@ */ #if OCTAGON -#include - +#import +#import #import - #import - -#include #include #import +#include +#import + #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h" -#import + +#import "keychain/analytics/CKKSLaunchSequence.h" +#import "keychain/categories/NSError+UsefulConstructors.h" #import "keychain/ckks/CKKS.h" +#import "keychain/ckks/CKKSAccountStateTracker.h" #import "keychain/ckks/CKKSAnalytics.h" +#import "keychain/ckks/CKKSKeychainView.h" #import "keychain/ckks/CKKSResultOperation.h" - +#import "keychain/ckks/CKKSViewManager.h" +#import "keychain/ckks/CloudKitCategories.h" #import "keychain/ckks/OctagonAPSReceiver.h" -#import "keychain/analytics/CKKSLaunchSequence.h" -#import "keychain/ot/OTDeviceInformationAdapter.h" - - -#import "keychain/ot/OctagonStateMachine.h" -#import "keychain/ot/OctagonStateMachineHelpers.h" -#import "keychain/ot/OctagonCKKSPeerAdapter.h" -#import "keychain/ot/OctagonCheckTrustStateOperation.h" -#import "keychain/ot/OTStates.h" -#import "keychain/ot/OTFollowup.h" +#import "keychain/escrowrequest/EscrowRequestServer.h" #import "keychain/ot/OTAuthKitAdapter.h" -#import "keychain/ot/OTConstants.h" -#import "keychain/ot/OTOperationDependencies.h" +#import "keychain/ot/OTCheckHealthOperation.h" +#import "keychain/ot/OTClientVoucherOperation.h" #import "keychain/ot/OTClique.h" +#import "keychain/ot/OTConstants.h" +#import "keychain/ot/OTCuttlefishAccountStateHolder.h" #import "keychain/ot/OTCuttlefishContext.h" -#import "keychain/ot/OTPrepareOperation.h" -#import "keychain/ot/OTSOSAdapter.h" -#import "keychain/ot/OTSOSUpgradeOperation.h" -#import "keychain/ot/OTUpdateTPHOperation.h" +#import "keychain/ot/OTDetermineCDPBitStatusOperation.h" +#import "keychain/ot/OTDetermineHSA2AccountStatusOperation.h" +#import "keychain/ot/OTDeviceInformationAdapter.h" +#import "keychain/ot/OTEnsureOctagonKeyConsistency.h" #import "keychain/ot/OTEpochOperation.h" -#import "keychain/ot/OTClientVoucherOperation.h" -#import "keychain/ot/OTLeaveCliqueOperation.h" -#import "keychain/ot/OTRemovePeersOperation.h" -#import "keychain/ot/OTJoinWithVoucherOperation.h" -#import "keychain/ot/OTVouchWithBottleOperation.h" -#import "keychain/ot/OTVouchWithRecoveryKeyOperation.h" #import "keychain/ot/OTEstablishOperation.h" +#import "keychain/ot/OTFetchViewsOperation.h" +#import "keychain/ot/OTFollowup.h" +#import "keychain/ot/OTJoinWithVoucherOperation.h" +#import "keychain/ot/OTLeaveCliqueOperation.h" #import "keychain/ot/OTLocalCKKSResetOperation.h" -#import "keychain/ot/OTUpdateTrustedDeviceListOperation.h" -#import "keychain/ot/OTSOSUpdatePreapprovalsOperation.h" -#import "keychain/ot/OTResetOperation.h" #import "keychain/ot/OTLocalCuttlefishReset.h" -#import "keychain/ot/OTSetRecoveryKeyOperation.h" +#import "keychain/ot/OTOperationDependencies.h" +#import "keychain/ot/OTPrepareOperation.h" +#import "keychain/ot/OTRemovePeersOperation.h" #import "keychain/ot/OTResetCKKSZonesLackingTLKsOperation.h" +#import "keychain/ot/OTResetOperation.h" +#import "keychain/ot/OTSOSAdapter.h" +#import "keychain/ot/OTSOSUpdatePreapprovalsOperation.h" +#import "keychain/ot/OTSOSUpgradeOperation.h" +#import "keychain/ot/OTSetCDPBitOperation.h" +#import "keychain/ot/OTSetRecoveryKeyOperation.h" +#import "keychain/ot/OTStates.h" +#import "keychain/ot/OTTriggerEscrowUpdateOperation.h" +#import "keychain/ot/OTUpdateTPHOperation.h" +#import "keychain/ot/OTUpdateTrustedDeviceListOperation.h" #import "keychain/ot/OTUploadNewCKKSTLKsOperation.h" -#import "keychain/ot/OTCuttlefishAccountStateHolder.h" +#import "keychain/ot/OTVouchWithBottleOperation.h" +#import "keychain/ot/OTVouchWithRecoveryKeyOperation.h" #import "keychain/ot/ObjCImprovements.h" +#import "keychain/ot/OctagonCKKSPeerAdapter.h" +#import "keychain/ot/OctagonCheckTrustStateOperation.h" +#import "keychain/ot/OctagonStateMachine.h" +#import "keychain/ot/OctagonStateMachineHelpers.h" +#import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h" #import "keychain/ot/proto/generated_source/OTAccountMetadataClassC.h" -#import "keychain/ot/OTTriggerEscrowUpdateOperation.h" -#import "keychain/ot/OTCheckHealthOperation.h" -#import "keychain/ot/OTEnsureOctagonKeyConsistency.h" -#import "keychain/ot/OTDetermineHSA2AccountStatusOperation.h" -#import "keychain/ckks/CKKSAccountStateTracker.h" -#import "keychain/ckks/CloudKitCategories.h" -#import "keychain/escrowrequest/EscrowRequestServer.h" +#import "keychain/securityd/SOSCloudCircleServer.h" + +#import "utilities/SecFileLocations.h" +#import "utilities/SecTapToRadar.h" #if TARGET_OS_WATCH #import "keychain/otpaird/OTPairingClient.h" #endif /* TARGET_OS_WATCH */ -#import "keychain/ckks/CKKSViewManager.h" -#import "keychain/ckks/CKKSKeychainView.h" -#import "utilities/SecTapToRadar.h" -#import "keychain/categories/NSError+UsefulConstructors.h" -#import -#import NSString* OTCuttlefishContextErrorDomain = @"otcuttlefish"; static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; @@ -110,7 +112,6 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; NSString* _bottleID; NSString* _bottleSalt; NSData* _entropy; - NSArray* _preapprovedKeys; NSString* _recoveryKey; CuttlefishResetReason _resetReason; BOOL _skipRateLimitingCheck; @@ -125,21 +126,17 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; @property CKAccountInfo* cloudKitAccountInfo; @property CKKSCondition *cloudKitAccountStateKnown; -@property BOOL getViewsSuccess; - @property CKKSNearFutureScheduler* suggestTLKUploadNotifier; +// Make writable +@property (nullable) CKKSViewManager* viewManager; + // Dependencies (for injection) @property id sosAdapter; @property id octagonAdapter; -@property id deviceAdapter; @property (readonly) Class apsConnectionClass; @property (readonly) Class escrowRequestClass; -@property (nonatomic) BOOL postedRepairCFU; -@property (nonatomic) BOOL postedEscrowRepairCFU; -@property (nonatomic) BOOL postedRecoveryKeyCFU; - @property (nonatomic) BOOL initialBecomeUntrustedPosted; @end @@ -166,9 +163,6 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; _contextID = contextID; _viewManager = viewManager; - _postedRepairCFU = NO; - _postedRecoveryKeyCFU = NO; - _postedEscrowRepairCFU = NO; _initialBecomeUntrustedPosted = NO; @@ -222,6 +216,11 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; return self; } +- (void)clearCKKSViewManager +{ + self.viewManager = nil; +} + - (void)dealloc { // TODO: how to invalidate this? @@ -444,6 +443,7 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT; metadata.altDSID = nil; metadata.trustState = OTAccountMetadataClassC_TrustState_UNKNOWN; + metadata.cdpState = OTAccountMetadataClassC_CDPState_UNKNOWN; return metadata; } error:&localError]; @@ -476,6 +476,59 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; return YES; } +- (OTCDPStatus)getCDPStatus:(NSError*__autoreleasing*)error +{ + NSError* localError = nil; + OTAccountMetadataClassC* accountState = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError]; + + if(localError) { + secnotice("octagon-cdp-status", "error fetching account metadata: %@", localError); + if(error) { + *error = localError; + } + + return OTCDPStatusUnknown; + } + + OTCDPStatus status = OTCDPStatusUnknown; + switch(accountState.cdpState) { + case OTAccountMetadataClassC_CDPState_UNKNOWN: + status = OTCDPStatusUnknown; + break; + case OTAccountMetadataClassC_CDPState_DISABLED: + status = OTCDPStatusDisabled; + break; + case OTAccountMetadataClassC_CDPState_ENABLED: + status = OTCDPStatusEnabled; + break; + } + + secnotice("octagon-cdp-status", "current cdp status is: %@", OTCDPStatusToString(status)); + return status; +} + +- (BOOL)setCDPEnabled:(NSError* __autoreleasing *)error +{ + NSError* localError = nil; + [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) { + metadata.cdpState = OTAccountMetadataClassC_CDPState_ENABLED; + return metadata; + } error:&localError]; + + [self.stateMachine handleFlag:OctagonFlagCDPEnabled]; + + if(localError) { + secerror("octagon-cdp-status: unable to persist CDP enablement: %@", localError); + if(error) { + *error = localError; + } + return NO; + } + + secnotice("octagon-cdp-status", "Successfully set CDP status bit to 'enabled''"); + return YES; +} + - (void)resetOctagonStateMachine { OctagonStateTransitionOperation* op = [OctagonStateTransitionOperation named:@"resetting-state-machine" @@ -507,21 +560,23 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; - (NSDictionary*)establishStatePathDictionary { return @{ - OctagonStateReEnactDeviceList: @{ - OctagonStateReEnactPrepare: @{ - OctagonStateReEnactReadyToEstablish: @{ - OctagonStateEscrowTriggerUpdate: @{ - OctagonStateBecomeReady: @{ - OctagonStateReady: [OctagonStateTransitionPathStep success], + OctagonStateEstablishEnableCDPBit: @{ + OctagonStateReEnactDeviceList: @{ + OctagonStateReEnactPrepare: @{ + OctagonStateReEnactReadyToEstablish: @{ + OctagonStateEscrowTriggerUpdate: @{ + OctagonStateBecomeReady: @{ + OctagonStateReady: [OctagonStateTransitionPathStep success], + }, }, - }, - // Error handling extra states: - OctagonStateEstablishCKKSReset: @{ - OctagonStateEstablishAfterCKKSReset: @{ - OctagonStateEscrowTriggerUpdate: @{ - OctagonStateBecomeReady: @{ - OctagonStateReady: [OctagonStateTransitionPathStep success], + // Error handling extra states: + OctagonStateEstablishCKKSReset: @{ + OctagonStateEstablishAfterCKKSReset: @{ + OctagonStateEscrowTriggerUpdate: @{ + OctagonStateBecomeReady: @{ + OctagonStateReady: [OctagonStateTransitionPathStep success], + }, }, }, }, @@ -697,6 +752,38 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; return [self cloudKitAccountNewlyAvailableOperation]; } + if([currentState isEqualToString:OctagonStateDetermineCDPState]) { + return [[OTDetermineCDPBitStatusOperation alloc] initWithDependencies:self.operationDependencies + intendedState:OctagonStateCheckTrustState + errorState:OctagonStateWaitForCDP]; + } + + if([currentState isEqualToString:OctagonStateWaitForCDP]) { + if([flags _onqueueContains:OctagonFlagCDPEnabled]) { + [flags _onqueueRemoveFlag:OctagonFlagCDPEnabled]; + secnotice("octagon", "CDP is newly available!"); + + return [OctagonStateTransitionOperation named:@"cdp_enabled" + entering:OctagonStateDetermineiCloudAccountState]; + + } else if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) { + [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification]; + return [OctagonStateTransitionOperation named:@"cdp_enabled_push_received" + entering:OctagonStateWaitForCDPUpdated]; + + } else { + return nil; + } + } + + if([currentState isEqualToString:OctagonStateWaitForCDPUpdated]) { + return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies + intendedState:OctagonStateDetermineCDPState + peerUnknownState:OctagonStateDetermineCDPState + errorState:OctagonStateError + retryFlag:OctagonFlagCuttlefishNotification]; + } + if([currentState isEqualToString:OctagonStateCheckTrustState]) { return [[OctagonCheckTrustStateOperation alloc] initWithDependencies:self.operationDependencies intendedState:OctagonStateBecomeUntrusted @@ -705,12 +792,18 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; #pragma mark --- Octagon Health Check States if([currentState isEqualToString:OctagonStateHSA2HealthCheck]) { return [[OTDetermineHSA2AccountStatusOperation alloc] initWithDependencies:self.operationDependencies - stateIfHSA2:OctagonStateSecurityTrustCheck + stateIfHSA2:OctagonStateCDPHealthCheck stateIfNotHSA2:OctagonStateWaitForHSA2 stateIfNoAccount:OctagonStateNoAccount errorState:OctagonStateError]; } + if([currentState isEqualToString:OctagonStateCDPHealthCheck]) { + return [[OTDetermineCDPBitStatusOperation alloc] initWithDependencies:self.operationDependencies + intendedState:OctagonStateSecurityTrustCheck + errorState:OctagonStateWaitForCDP]; + } + if([currentState isEqualToString:OctagonStateSecurityTrustCheck]) { return [self evaluateSecdOctagonTrust]; } @@ -749,6 +842,12 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; if([currentState isEqualToString:OctagonStateBecomeReady]) { return [self becomeReadyOperation]; } + + if([currentState isEqualToString:OctagonStateRefetchCKKSPolicy]) { + return [[OTFetchViewsOperation alloc] initWithDependencies:self.operationDependencies + intendedState:OctagonStateBecomeReady + errorState:OctagonStateError]; + } if([currentState isEqualToString:OctagonStateNoAccount]) { // We only want to move out of untrusted if something useful has happened! @@ -797,11 +896,18 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) { [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged]; } + + // We're untrusted; no need for the CDP level flag anymore + if([flags _onqueueContains:OctagonFlagCDPEnabled]) { + secnotice("octagon", "Removing 'CDP enabled' flag"); + [flags _onqueueRemoveFlag:OctagonFlagCDPEnabled]; + } } if([currentState isEqualToString:OctagonStateUntrustedUpdated]) { return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies intendedState:OctagonStateUntrusted + peerUnknownState:OctagonStateBecomeUntrusted errorState:OctagonStateError retryFlag:OctagonFlagCuttlefishNotification]; } @@ -901,7 +1007,8 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; intendedState:OctagonStateBecomeReady ckksConflictState:OctagonStateSOSUpgradeCKKSReset errorState:OctagonStateBecomeUntrusted - deviceInfo:self.prepareInformation]; + deviceInfo:self.prepareInformation + policyOverride:self.policyOverride]; } else if([currentState isEqualToString:OctagonStateSOSUpgradeCKKSReset]) { return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies @@ -913,28 +1020,32 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; intendedState:OctagonStateBecomeReady ckksConflictState:OctagonStateBecomeUntrusted errorState:OctagonStateBecomeUntrusted - deviceInfo:self.prepareInformation]; + deviceInfo:self.prepareInformation + policyOverride:self.policyOverride]; } else if([currentState isEqualToString:OctagonStateCreateIdentityForRecoveryKey]) { - OTPrepareOperation* op = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies - intendedState:OctagonStateVouchWithRecoveryKey - errorState:OctagonStateBecomeUntrusted - deviceInfo:[self prepareInformation] - epoch:1]; - return op; + return [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies + intendedState:OctagonStateVouchWithRecoveryKey + errorState:OctagonStateBecomeUntrusted + deviceInfo:[self prepareInformation] + policyOverride:self.policyOverride + epoch:1]; - } else if([currentState isEqualToString:OctagonStateInitiatorCreateIdentity]) { - OTPrepareOperation* op = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies - intendedState:OctagonStateInitiatorVouchWithBottle - errorState:OctagonStateBecomeUntrusted - deviceInfo:[self prepareInformation] - epoch:1]; - return op; + } else if([currentState isEqualToString:OctagonStateBottleJoinCreateIdentity]) { + return [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies + intendedState:OctagonStateBottleJoinVouchWithBottle + errorState:OctagonStateBecomeUntrusted + deviceInfo:[self prepareInformation] + policyOverride:self.policyOverride + epoch:1]; + + } else if([currentState isEqualToString:OctagonStateBottleJoinVouchWithBottle]) { + // Octagon: ensure we use appropriate CKKS policy when joining octagon with future policy + // When we join with a bottle, we need to be sure that we've found all the TLKShares that we can reasonably unpack via the bottle - } else if([currentState isEqualToString:OctagonStateInitiatorVouchWithBottle]) { OTVouchWithBottleOperation* pendingOp = [[OTVouchWithBottleOperation alloc] initWithDependencies:self.operationDependencies - intendedState:OctagonStateInitiatorUpdateDeviceList + intendedState:OctagonStateInitiatorSetCDPBit errorState:OctagonStateBecomeUntrusted bottleID:_bottleID entropy:_entropy @@ -953,7 +1064,7 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; } else if([currentState isEqualToString:OctagonStateVouchWithRecoveryKey]) { OTVouchWithRecoveryKeyOperation* pendingOp = [[OTVouchWithRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies - intendedState:OctagonStateInitiatorUpdateDeviceList + intendedState:OctagonStateInitiatorSetCDPBit errorState:OctagonStateBecomeUntrusted recoveryKey:_recoveryKey]; @@ -968,6 +1079,11 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; return pendingOp; + } else if([currentState isEqualToString:OctagonStateInitiatorSetCDPBit]) { + return [[OTSetCDPBitOperation alloc] initWithDependencies:self.operationDependencies + intendedState:OctagonStateInitiatorUpdateDeviceList + errorState:OctagonStateDetermineCDPState]; + } else if([currentState isEqualToString:OctagonStateInitiatorUpdateDeviceList]) { // As part of the 'initiate' flow, we need to update the trusted device list-you're probably on it already OTUpdateTrustedDeviceListOperation* op = [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies @@ -983,8 +1099,7 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; ckksConflictState:OctagonStateInitiatorJoinCKKSReset errorState:OctagonStateBecomeUntrusted voucherData:_vouchData - voucherSig:_vouchSig - preapprovedKeys:_preapprovedKeys]; + voucherSig:_vouchSig]; return op; } else if([currentState isEqualToString:OctagonStateInitiatorJoinCKKSReset]) { @@ -998,8 +1113,7 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; ckksConflictState:OctagonStateBecomeUntrusted errorState:OctagonStateBecomeUntrusted voucherData:_vouchData - voucherSig:_vouchSig - preapprovedKeys:_preapprovedKeys]; + voucherSig:_vouchSig]; } else if([currentState isEqualToString:OctagonStateResetBecomeUntrusted]) { return [self becomeUntrustedOperation:OctagonStateResetAndEstablish]; @@ -1014,22 +1128,29 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; } else if([currentState isEqualToString:OctagonStateResetAnyMissingTLKCKKSViews]) { return [[OTResetCKKSZonesLackingTLKsOperation alloc] initWithDependencies:self.operationDependencies - intendedState:OctagonStateReEnactDeviceList + intendedState:OctagonStateEstablishEnableCDPBit errorState:OctagonStateError]; + } else if([currentState isEqualToString:OctagonStateEstablishEnableCDPBit]) { + return [[OTSetCDPBitOperation alloc] initWithDependencies:self.operationDependencies + intendedState:OctagonStateReEnactDeviceList + errorState:OctagonStateError]; + } else if([currentState isEqualToString:OctagonStateReEnactDeviceList]) { return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies intendedState:OctagonStateReEnactPrepare listUpdatesState:OctagonStateReEnactPrepare - errorState:OctagonStateError + errorState:OctagonStateBecomeUntrusted retryFlag:nil]; } else if([currentState isEqualToString:OctagonStateReEnactPrepare]) { + // Octagon: use epoch transmitted across pairing channel // Note: Resetting the account returns epoch to 0. return [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies intendedState:OctagonStateReEnactReadyToEstablish errorState:OctagonStateError deviceInfo:[self prepareInformation] + policyOverride:self.policyOverride epoch:0]; } else if([currentState isEqualToString:OctagonStateReEnactReadyToEstablish]) { @@ -1051,11 +1172,15 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; errorState:OctagonStateBecomeUntrusted]; } else if ([currentState isEqualToString:OctagonStateEscrowTriggerUpdate]){ - return [[OTTriggerEscrowUpdateOperation alloc] initWithDependencies:self.operationDependencies intendedState:OctagonStateBecomeReady errorState:OctagonStateError]; + } else if ([currentState isEqualToString:OctagonStateHealthCheckLeaveClique]) { + return [[OTLeaveCliqueOperation alloc] initWithDependencies: self.operationDependencies + intendedState: OctagonStateBecomeUntrusted + errorState: OctagonStateBecomeUntrusted]; + } else if([currentState isEqualToString: OctagonStateWaitForUnlock]) { if([flags _onqueueContains:OctagonFlagUnlocked]) { [flags _onqueueRemoveFlag:OctagonFlagUnlocked]; @@ -1156,6 +1281,12 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged]; } + // We're ready; no need for the CDP level flag anymore + if([flags _onqueueContains:OctagonFlagCDPEnabled]) { + secnotice("octagon", "Removing 'CDP enabled' flag"); + [flags _onqueueRemoveFlag:OctagonFlagCDPEnabled]; + } + secnotice("octagon", "Entering state ready"); [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:OctagonAnalyticsLastKeystateReady]; [self.launchSequence launch]; @@ -1163,10 +1294,14 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; } else if([currentState isEqualToString:OctagonStateReadyUpdated]) { return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies intendedState:OctagonStateReady + peerUnknownState:OctagonStateBecomeUntrusted errorState:OctagonStateError retryFlag:OctagonFlagCuttlefishNotification]; - } else if ([currentState isEqualToString:OctagonStateError]) { + } + + if ([currentState isEqualToString:OctagonStateError]) { + return nil; } return nil; @@ -1237,7 +1372,7 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; } else { secnotice("octagon-health", "trust state (%@). checking in with TPH", [account trustStateAsString:account.trustState]); - op.nextState = [self repairAccountIfTrustedByTPHWithIntededState:OctagonStateTPHTrustCheck errorState:OctagonStatePostRepairCFU]; + op.nextState = [self repairAccountIfTrustedByTPHWithIntendedState:OctagonStateTPHTrustCheck]; } }]; } @@ -1284,8 +1419,8 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcHealthCheck" withBlock:^{ - secnotice("octagon-health", "Returning from cuttlefish trust check call: postRepairCFU(%d), postEscrowCFU(%d), resetOctagon(%d)", - op.postRepairCFU, op.postEscrowCFU, op.resetOctagon); + secnotice("octagon-health", "Returning from cuttlefish trust check call: postRepairCFU(%d), postEscrowCFU(%d), resetOctagon(%d), leaveTrust(%d)", + op.postRepairCFU, op.postEscrowCFU, op.resetOctagon, op.leaveTrust); if(op.postRepairCFU) { secnotice("octagon-health", "Posting Repair CFU"); NSError* postRepairCFUError = nil; @@ -1312,7 +1447,15 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; } else { secnotice("octagon-health", "Not posting confirm passcode CFU, already pending a prerecord upload"); } + } + if(op.leaveTrust){ + secnotice("octagon-health", "Leaving Octagon and SOS trust"); + NSError* leaveError = nil; + if(![self leaveTrust:&leaveError]) { + op.error = leaveError; + } + } }]; [callback addDependency:op]; [self.operationQueue addOperation: callback]; @@ -1343,7 +1486,7 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; { WEAKIFY(self); return [OctagonStateTransitionOperation named:@"octagon-icloud-account-available" - intending:OctagonStateCheckTrustState + intending:OctagonStateDetermineCDPState errorState:OctagonStateError withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) { STRONGIFY(self); @@ -1375,12 +1518,12 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; }]; } -- (OctagonState*) repairAccountIfTrustedByTPHWithIntededState:(OctagonState*)intendedState errorState:(OctagonState*)errorState +- (OctagonState*) repairAccountIfTrustedByTPHWithIntendedState:(OctagonState*)intendedState { __block OctagonState* nextState = intendedState; //let's check in with TPH real quick to make sure it agrees with our local assessment - secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntededState: calling into TPH for trust status"); + secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntendedState: calling into TPH for trust status"); OTOperationConfiguration *config = [[OTOperationConfiguration alloc]init]; @@ -1390,7 +1533,7 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; BOOL isExcluded, NSError * _Nullable error) { BOOL hasIdentity = egoPeerID != nil; - secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntededState status: %ld, peerID: %@, isExcluded: %d error: %@", (long)status, egoPeerID, isExcluded, error); + secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntendedState status: %ld, peerID: %@, isExcluded: %d error: %@", (long)status, egoPeerID, isExcluded, error); if (error) { secnotice("octagon-health", "got an error from tph, returning to become_ready state: %@", error); @@ -1399,66 +1542,31 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; } if(OctagonAuthoritativeTrustIsEnabled() && hasIdentity && status == CliqueStatusIn) { - dispatch_semaphore_t sema = dispatch_semaphore_create(0); - [self rpcStatus:^(NSDictionary *dump, NSError *dumpError) { - if(dumpError) { - secerror("octagon-health: error fetching ego peer id!: %@", dumpError); - nextState = errorState; - } else { - NSDictionary* egoInformation = dump[@"self"]; - NSString* peerID = egoInformation[@"peerID"]; - NSError* persistError = nil; - BOOL persisted = [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) { - metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED; - metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE; - metadata.peerID = peerID; - return metadata; - } error:&persistError]; - if(!persisted || persistError) { - secerror("octagon-health: couldn't persist results: %@", persistError); - nextState = errorState; - } else { - secnotice("octagon-health", "added trusted identity to account metadata"); - nextState = intendedState; - } - } - dispatch_semaphore_signal(sema); - }]; - if (0 != dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10))) { - secerror("octagon: Timed out checking trust status"); + secnotice("octagon-health", "TPH believes we're trusted, accepting ego peerID as %@", egoPeerID); + + NSError* persistError = nil; + BOOL persisted = [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) { + metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED; + metadata.peerID = egoPeerID; + return metadata; + } error:&persistError]; + if(!persisted || persistError) { + secerror("octagon-health: couldn't persist results: %@", persistError); + nextState = OctagonStateError; + } else { + secnotice("octagon-health", "added trusted identity to account metadata"); + nextState = intendedState; } - } else if (OctagonAuthoritativeTrustIsEnabled() && (self.postedRepairCFU == NO) && hasIdentity && status != CliqueStatusIn){ - nextState = errorState; + + } else if (OctagonAuthoritativeTrustIsEnabled() && hasIdentity && status != CliqueStatusIn){ + secnotice("octagon-health", "TPH believes we're not trusted, requesting CFU post"); + nextState = OctagonStatePostRepairCFU; } }]; return nextState; } -- (BOOL) didDeviceAttemptToJoinOctagon:(NSError**)error -{ - NSError* fetchAttemptError = nil; - OTAccountMetadataClassC_AttemptedAJoinState attemptedAJoin = [self.accountMetadataStore fetchPersistedJoinAttempt:&fetchAttemptError]; - if(fetchAttemptError) { - secerror("octagon: failed to fetch data indicating device attempted to join octagon, assuming it did: %@", fetchAttemptError); - if(error){ - *error = fetchAttemptError; - } - return YES; - } - BOOL attempted = YES; - switch (attemptedAJoin) { - case OTAccountMetadataClassC_AttemptedAJoinState_NOTATTEMPTED: - attempted = NO; - break; - case OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED: - case OTAccountMetadataClassC_AttemptedAJoinState_UNKNOWN: - default: - break; - } - return attempted; -} - - (void)checkTrustStatusAndPostRepairCFUIfNecessary:(void (^ _Nullable)(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError * _Nullable error))reply { WEAKIFY(self); @@ -1505,17 +1613,38 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; // On platforms with SOS, we only want to post a CFU if we've attempted to join at least once. // This prevents us from posting a CFU, then performing an SOS upgrade and succeeding. if(self.sosAdapter.sosEnabled) { - NSError* fetchAttemptError = nil; - BOOL attemptedToJoin = [self didDeviceAttemptToJoinOctagon:&fetchAttemptError]; - if(fetchAttemptError){ - secerror("octagon: failed to retrieve joining attempt information: %@", fetchAttemptError); - attemptedToJoin = YES; - } + NSError* fetchError = nil; + OTAccountMetadataClassC* accountState = [self.accountMetadataStore loadOrCreateAccountMetadata:&fetchError]; - if(!attemptedToJoin) { - secnotice("octagon", "SOS is enabled and we haven't attempted to join; not posting CFU"); - reply(status, NO, hasIdentity, nil); - return; + if(!accountState || fetchError){ + secerror("octagon: failed to retrieve joining attempt information: %@", fetchError); + // fall through to below: posting the CFU is better than a false negative + + } else if(accountState.attemptedJoin == OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED) { + // Normal flow, fall through to below + } else { + // Triple-check with SOS: if it's in a bad state, post the CFU anyway + secnotice("octagon", "SOS is enabled and we haven't attempted to join; checking with SOS"); + + NSError* circleError = nil; + SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&circleError]; + + if(circleError && [circleError.domain isEqualToString:(__bridge NSString*)kSOSErrorDomain] && circleError.code == kSOSErrorNotReady) { + secnotice("octagon", "SOS is not ready, not posting CFU until it becomes so"); + reply(status, NO, hasIdentity, nil); + return; + + } else if(circleError) { + // Any other error probably indicates that there is some circle, but we're not in it + secnotice("octagon", "SOS is in an unknown error state, posting CFU: %@", circleError); + + } else if(sosStatus == kSOSCCInCircle) { + secnotice("octagon", "SOS is InCircle, not posting CFU"); + reply(status, NO, hasIdentity, nil); + return; + } else { + secnotice("octagon", "SOS is %@, posting CFU", (__bridge NSString*)SOSCCGetStatusDescription(sosStatus)); + } } } @@ -1565,7 +1694,10 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; STRONGIFY(self); NSError* localError = nil; - [self.accountStateTracker triggerOctagonStatusFetch]; + // During testing, don't kick this off until it's needed + if([self.contextID isEqualToString:OTDefaultContext]) { + [self.accountStateTracker triggerOctagonStatusFetch]; + } [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError * _Nullable postError) { @@ -1616,59 +1748,89 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) { STRONGIFY(self); - // Note: we don't modify the account metadata store here; that will have been done - // by a join or upgrade operation, possibly long ago + if([self.contextID isEqualToString:OTDefaultContext]) { + [self.accountStateTracker triggerOctagonStatusFetch]; + } - [self.accountStateTracker triggerOctagonStatusFetch]; + // Note: we don't modify the account metadata trust state; that will have been done + // by a join or upgrade operation, possibly long ago - NSError* localError = nil; - NSString* peerID = [self.accountMetadataStore getEgoPeerID:&localError]; - if(!peerID || localError) { - secerror("octagon-ckks: No peer ID to pass to CKKS. Syncing will be disabled."); - } else { - OctagonCKKSPeerAdapter* octagonAdapter = [[OctagonCKKSPeerAdapter alloc] initWithPeerID:peerID operationDependencies:[self operationDependencies]]; + // but, we do set the 'attempted join' bit, just in case the device joined before we started setting this bit - // This octagon adapter must be able to load the self peer keys, or we're in trouble. - NSError* egoPeerKeysError = nil; - CKKSSelves* selves = [octagonAdapter fetchSelfPeers:&egoPeerKeysError]; - if(!selves || egoPeerKeysError) { - secerror("octagon-ckks: Unable to fetch self peers for %@: %@", octagonAdapter, egoPeerKeysError); + // Also, ensure that the CKKS policy is correctly present and set in the view manager + __block NSString* peerID = nil; + NSError* localError = nil; - if([self.lockStateTracker isLockedError:egoPeerKeysError]) { - secnotice("octagon-ckks", "Waiting for device unlock to proceed"); - op.nextState = OctagonStateWaitForUnlock; - } else { - secnotice("octagon-ckks", "Error is scary; becoming untrusted"); - op.nextState = OctagonStateBecomeUntrusted; - } - return; - } + __block NSSet* viewList = nil; + __block TPPolicy* policy = nil; - // stash a reference to the adapter so we can provided updates later - self.octagonAdapter = octagonAdapter; + [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) { + peerID = metadata.peerID; + viewList = metadata.syncingViews.count > 0 ? [NSSet setWithArray:metadata.syncingViews] : nil; + policy = metadata.hasSyncingPolicy ? [metadata getTPPolicy] : nil; - // Start all our CKKS views! - for (id key in self.viewManager.views) { - CKKSKeychainView* view = self.viewManager.views[key]; - secnotice("octagon-ckks", "Informing CKKS view '%@' of trusted operation with self peer %@", view.zoneName, peerID); + if(metadata.attemptedJoin == OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED) { + return nil; + } + metadata.attemptedJoin = OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED; + return metadata; + } error:&localError]; + + if(!peerID || localError) { + secerror("octagon-ckks: No peer ID to pass to CKKS. Syncing will be disabled."); + } else if(!viewList || !policy) { + secerror("octagon-ckks: No memoized CKKS policy, re-fetching"); + op.nextState = OctagonStateRefetchCKKSPolicy; + return; - NSArray>* peerProviders = nil; + } else { + secnotice("octagon-ckks", "Initializing CKKS views with view list %@ and policy %@", viewList, policy); + [self.viewManager setSyncingViews:viewList + sortingPolicy:policy]; - if(self.sosAdapter.sosEnabled) { - peerProviders = @[self.octagonAdapter, self.sosAdapter]; + OctagonCKKSPeerAdapter* octagonAdapter = [[OctagonCKKSPeerAdapter alloc] initWithPeerID:peerID operationDependencies:[self operationDependencies]]; - } else { - peerProviders = @[self.octagonAdapter]; - } + // This octagon adapter must be able to load the self peer keys, or we're in trouble. + NSError* egoPeerKeysError = nil; + CKKSSelves* selves = [octagonAdapter fetchSelfPeers:&egoPeerKeysError]; + if(!selves || egoPeerKeysError) { + secerror("octagon-ckks: Unable to fetch self peers for %@: %@", octagonAdapter, egoPeerKeysError); - [view beginTrustedOperation:peerProviders - suggestTLKUpload:self.suggestTLKUploadNotifier]; - } - } - [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_TRUSTED]; + if([self.lockStateTracker isLockedError:egoPeerKeysError]) { + secnotice("octagon-ckks", "Waiting for device unlock to proceed"); + op.nextState = OctagonStateWaitForUnlock; + } else { + secnotice("octagon-ckks", "Error is scary; becoming untrusted"); + op.nextState = OctagonStateBecomeUntrusted; + } + return; + } - op.nextState = op.intendedState; - }]; + // stash a reference to the adapter so we can provide updates later + self.octagonAdapter = octagonAdapter; + + // Start all our CKKS views! + for (id key in self.viewManager.views) { + CKKSKeychainView* view = self.viewManager.views[key]; + secnotice("octagon-ckks", "Informing CKKS view '%@' of trusted operation with self peer %@", view.zoneName, peerID); + + NSArray>* peerProviders = nil; + + if(self.sosAdapter.sosEnabled) { + peerProviders = @[self.octagonAdapter, self.sosAdapter]; + + } else { + peerProviders = @[self.octagonAdapter]; + } + + [view beginTrustedOperation:peerProviders + suggestTLKUpload:self.suggestTLKUploadNotifier]; + } + } + [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_TRUSTED]; + + op.nextState = op.intendedState; + }]; } #pragma mark --- Utilities to run at times @@ -1744,7 +1906,7 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; - (void)notifyContainerChangeWithUserInfo:(NSDictionary*)userInfo { - secerror("OTCuttlefishContext: received a cuttlefish push notification (%@): %@", + secnotice("octagonpush", "received a cuttlefish push notification (%@): %@", self.containerName, userInfo); NSDictionary *cfDictionary = userInfo[@"cf"]; @@ -1952,13 +2114,14 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; } secnotice("otrpc", "Preparing identity as applicant"); + OTPrepareOperation* pendingOp = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies intendedState:OctagonStateInitiatorAwaitingVoucher errorState:OctagonStateBecomeUntrusted deviceInfo:[self prepareInformation] + policyOverride:self.policyOverride epoch:epoch]; - dispatch_time_t timeOut = 0; if(config.timeout != 0) { timeOut = config.timeout; @@ -2010,8 +2173,8 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; } OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{ - OctagonStateInitiatorCreateIdentity: @{ - OctagonStateInitiatorVouchWithBottle: [self joinStatePathDictionary], + OctagonStateBottleJoinCreateIdentity: @{ + OctagonStateBottleJoinVouchWithBottle: [self joinStatePathDictionary], }, }]; @@ -2048,16 +2211,18 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; - (NSDictionary*)joinStatePathDictionary { return @{ - OctagonStateInitiatorUpdateDeviceList: @{ - OctagonStateInitiatorJoin: @{ - OctagonStateBecomeReady: @{ - OctagonStateReady: [OctagonStateTransitionPathStep success], - }, + OctagonStateInitiatorSetCDPBit: @{ + OctagonStateInitiatorUpdateDeviceList: @{ + OctagonStateInitiatorJoin: @{ + OctagonStateBecomeReady: @{ + OctagonStateReady: [OctagonStateTransitionPathStep success], + }, - OctagonStateInitiatorJoinCKKSReset: @{ - OctagonStateInitiatorJoinAfterCKKSReset: @{ - OctagonStateBecomeReady: @{ - OctagonStateReady: [OctagonStateTransitionPathStep success] + OctagonStateInitiatorJoinCKKSReset: @{ + OctagonStateInitiatorJoinAfterCKKSReset: @{ + OctagonStateBecomeReady: @{ + OctagonStateReady: [OctagonStateTransitionPathStep success] + }, }, }, }, @@ -2068,13 +2233,11 @@ static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC; - (void)rpcJoin:(NSData*)vouchData vouchSig:(NSData*)vouchSig -preapprovedKeys:(NSArray* _Nullable)preapprovedKeys reply:(void (^)(NSError * _Nullable error))reply { _vouchData = vouchData; _vouchSig = vouchSig; - _preapprovedKeys = preapprovedKeys; if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) { secnotice("octagon", "No cloudkit account present"); @@ -2181,10 +2344,19 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys result[@"statePendingFlags"] = [self.stateMachine dumpPendingFlags]; result[@"stateFlags"] = [self.stateMachine.flags dumpFlags]; - result[@"memoizedTrustState"] = @(self.currentMemoizedTrustState); - result[@"memoizedAccountState"] = @(self.currentMemoizedAccountState); + NSError* metadataError = nil; + OTAccountMetadataClassC* currentAccountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError]; + if(metadataError) { + secnotice("octagon", "Failed to load account metaada for container (%@) and context (%@): %@", self.containerName, self.contextID, metadataError); + } + + result[@"memoizedTrustState"] = @(currentAccountMetadata.trustState); + result[@"memoizedAccountState"] = @(currentAccountMetadata.icloudAccountState); + result[@"memoizedCDPStatus"] = @(currentAccountMetadata.cdpState); result[@"octagonLaunchSeqence"] = [self.launchSequence eventsByTime]; - result[@"memoizedlastHealthCheck"] = self.currentMemoizedLastHealthCheck ? self.currentMemoizedLastHealthCheck : @"Never checked"; + + NSDate* lastHealthCheck = self.currentMemoizedLastHealthCheck; + result[@"memoizedlastHealthCheck"] = lastHealthCheck ?: @"Never checked"; if (self.sosAdapter.sosEnabled) { result[@"sosTrustedPeersStatus"] = [self sosTrustedPeersStatus]; result[@"sosSelvesStatus"] = [self sosSelvesStatus]; @@ -2398,12 +2570,6 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys } }]; - if(trustStatus == CliqueStatusIn && self.postedRepairCFU == YES){ - NSError* clearError = nil; - [self.followupHandler clearFollowUp:OTFollowupContextTypeStateRepair error:&clearError]; - // TODO(caw): should we clear this flag if `clearFollowUpForContext` fails? - self.postedRepairCFU = NO; - } reply(trustStatus, peerID, peerModelCounts, excluded, localError); } @@ -2476,6 +2642,19 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys }]; } +- (void)rpcRefetchCKKSPolicy:(void (^)(NSError * _Nullable error))reply +{ + [self.stateMachine doWatchedStateMachineRPC:@"octagon-refetch-ckks-policy" + sourceStates:[NSMutableSet setWithArray: @[OctagonStateReady]] + path:[OctagonStateTransitionPath pathFromDictionary:@{ + OctagonStateRefetchCKKSPolicy: @{ + OctagonStateBecomeReady: @{ + OctagonStateReady: [OctagonStateTransitionPathStep success], + }, + }, + }] + reply:reply]; +} #pragma mark --- Testing - (void) setAccountStateHolder:(OTCuttlefishAccountStateHolder*)accountMetadataStore @@ -2483,32 +2662,22 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys self.accountMetadataStore = accountMetadataStore; } -- (void)setPostedBool:(BOOL)posted -{ - self.postedRepairCFU = posted; -} - #pragma mark --- Health Checker - (BOOL)postRepairCFU:(NSError**)error { NSError* localError = nil; BOOL postSuccess = NO; - if (self.postedRepairCFU == NO) { - [self.followupHandler postFollowUp:OTFollowupContextTypeStateRepair error:&localError]; - if(localError){ - secerror("octagon-health: CoreCDP repair failed: %@", localError); - if(error){ - *error = localError; - } - } - else{ - secnotice("octagon-health", "CoreCDP post repair success"); - self.postedRepairCFU = YES; - postSuccess = YES; + [self.followupHandler postFollowUp:OTFollowupContextTypeStateRepair error:&localError]; + if(localError){ + secerror("octagon-health: CoreCDP repair failed: %@", localError); + if(error){ + *error = localError; } - } else { - secnotice("octagon-health", "already posted a repair CFU!"); + } + else{ + secnotice("octagon-health", "CoreCDP post repair success"); + postSuccess = YES; } return postSuccess; } @@ -2543,38 +2712,36 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys } } -- (void)postConfirmPasscodeCFU:(NSError**)error +- (BOOL)leaveTrust:(NSError**)error { - NSError* localError = nil; - if (self.postedEscrowRepairCFU == NO) { - [self.followupHandler postFollowUp:OTFollowupContextTypeOfflinePasscodeChange error:&localError]; - if(localError){ - secerror("octagon-health: CoreCDP offline passcode change failed: %@", localError); - *error = localError; - } - else{ - secnotice("octagon-health", "CoreCDP offline passcode change success"); - self.postedEscrowRepairCFU = YES; + if (OctagonPlatformSupportsSOS()) { + CFErrorRef cfError = NULL; + bool left = SOSCCRemoveThisDeviceFromCircle_Server(&cfError); + + if(!left || cfError) { + secerror("failed to leave SOS circle: %@", cfError); + if(error) { + *error = (NSError*)CFBridgingRelease(cfError); + } else { + CFReleaseNull(cfError); + } + return NO; } - } else { - secnotice("octagon-health", "already posted escrow CFU"); } + secnotice("octagon-health", "Successfully left SOS"); + return YES; } -- (void)postRecoveryKeyCFU:(NSError**)error +- (void)postConfirmPasscodeCFU:(NSError**)error { NSError* localError = nil; - if (self.postedRecoveryKeyCFU == NO) { - [self.followupHandler postFollowUp:OTFollowupContextTypeRecoveryKeyRepair error:&localError]; - if(localError){ - secerror("octagon-health: CoreCDP recovery key cfu failed: %@", localError); - } - else{ - secnotice("octagon-health", "CoreCDP recovery key cfu success"); - self.postedRecoveryKeyCFU = YES; - } - } else { - secnotice("octagon-health", "already posted recovery key CFU"); + [self.followupHandler postFollowUp:OTFollowupContextTypeOfflinePasscodeChange error:&localError]; + if(localError){ + secerror("octagon-health: CoreCDP offline passcode change failed: %@", localError); + *error = localError; + } + else{ + secnotice("octagon-health", "CoreCDP offline passcode change success"); } } @@ -2589,18 +2756,22 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys sourceStates:OctagonHealthSourceStates() path:[OctagonStateTransitionPath pathFromDictionary:@{ OctagonStateHSA2HealthCheck: @{ - OctagonStateSecurityTrustCheck: @{ - OctagonStateTPHTrustCheck: @{ - OctagonStateCuttlefishTrustCheck: @{ - OctagonStateBecomeReady: @{ - OctagonStateReady: [OctagonStateTransitionPathStep success], - OctagonStateWaitForUnlock: [OctagonStateTransitionPathStep success], + OctagonStateCDPHealthCheck: @{ + OctagonStateSecurityTrustCheck: @{ + OctagonStateTPHTrustCheck: @{ + OctagonStateCuttlefishTrustCheck: @{ + OctagonStateBecomeReady: @{ + OctagonStateReady: [OctagonStateTransitionPathStep success], + OctagonStateWaitForUnlock: [OctagonStateTransitionPathStep success], + }, + // Cuttlefish can suggest we reset the world. Consider reaching here a success, + // instead of tracking the whole reset. + OctagonStateHealthCheckReset: [OctagonStateTransitionPathStep success], + OctagonStateHealthCheckLeaveClique: [OctagonStateTransitionPathStep success], }, - // Cuttlefish can suggest we reset the world. Consider reaching here a success, - // instead of tracking the whole reset. - OctagonStateHealthCheckReset: [OctagonStateTransitionPathStep success], }, }, + OctagonStateWaitForCDP: [OctagonStateTransitionPathStep success], }, OctagonStateWaitForHSA2: [OctagonStateTransitionPathStep success], } @@ -2624,7 +2795,8 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys intendedState:OctagonStateBecomeReady ckksConflictState:OctagonStateBecomeUntrusted errorState:OctagonStateBecomeUntrusted - deviceInfo:self.prepareInformation]; + deviceInfo:self.prepareInformation + policyOverride:self.policyOverride]; OctagonStateTransitionRequest* request = [[OctagonStateTransitionRequest alloc] init:@"attempt-sos-upgrade" sourceStates:sourceStates @@ -2687,13 +2859,6 @@ preapprovedKeys:(NSArray* _Nullable)preapprovedKeys reply:reply]; } -- (void)clearPendingCFUFlags -{ - self.postedRecoveryKeyCFU = NO; - self.postedEscrowRepairCFU = NO; - self.postedRepairCFU = NO; -} - // Metrics passthroughs - (BOOL)machineIDOnMemoizedList:(NSString*)machineID error:(NSError**)error diff --git a/keychain/ot/OTDefines.h b/keychain/ot/OTDefines.h index 8d5f6e71..d391c48f 100644 --- a/keychain/ot/OTDefines.h +++ b/keychain/ot/OTDefines.h @@ -90,6 +90,7 @@ typedef NS_ERROR_ENUM(OctagonErrorDomain, OctagonError) { OTAuthKitNoAuthenticationController = 45, OTAuthKitMachineIDMissing = 46, OTAuthKitPrimaryAccountHaveNoDSID = 47, + OTErrorFailedToLeaveClique = 48, }; #define OTMasterSecretLength 72 @@ -97,6 +98,7 @@ typedef NS_ERROR_ENUM(OctagonErrorDomain, OctagonError) { typedef NS_ENUM(NSInteger, TrustedPeersHelperErrorCode) { TrustedPeersHelperErrorNoPreparedIdentity = 1, TrustedPeersHelperErrorNoPeersPreapprovePreparedIdentity = 14, + TrustedPeersHelperErrorCodeUntrustedRecoveryKeys = 32, TrustedPeersHelperErrorCodeNotEnrolled = 34, }; diff --git a/keychain/SecureObjectSync/Tool/syncbackup.h b/keychain/ot/OTDetermineCDPBitStatusOperation.h similarity index 59% rename from keychain/SecureObjectSync/Tool/syncbackup.h rename to keychain/ot/OTDetermineCDPBitStatusOperation.h index ccacc3c3..f4b377cb 100644 --- a/keychain/SecureObjectSync/Tool/syncbackup.h +++ b/keychain/ot/OTDetermineCDPBitStatusOperation.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved. + * Copyright (c) 2019 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -21,21 +21,23 @@ * @APPLE_LICENSE_HEADER_END@ */ -// -// syncbackup.h -// sec -// -// -// +#if OCTAGON +#import +#import "keychain/ckks/CKKSGroupOperation.h" +#import "keychain/ot/OctagonStateMachineHelpers.h" +#import "keychain/ot/OTOperationDependencies.h" -#include "SecurityTool/sharedTool/security_tool_commands.h" +NS_ASSUME_NONNULL_BEGIN -SECURITY_COMMAND( - "syncbackup", syncbackup, - "[options]\n" - " -i info (current status)\n" - " -l list backup slice keybag membership and recovery status" - "\n", - "iCloud Circle Backup Information") +@interface OTDetermineCDPBitStatusOperation : CKKSGroupOperation +- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies + intendedState:(OctagonState*)intendedState + errorState:(OctagonState*)errorState; + +@end + +NS_ASSUME_NONNULL_END + +#endif // OCTAGON diff --git a/keychain/ot/OTDetermineCDPBitStatusOperation.m b/keychain/ot/OTDetermineCDPBitStatusOperation.m new file mode 100644 index 00000000..8ad69c12 --- /dev/null +++ b/keychain/ot/OTDetermineCDPBitStatusOperation.m @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2019 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#if OCTAGON + +#import "utilities/debugging.h" + +#import "keychain/ot/ObjCImprovements.h" +#import "keychain/ot/OTDetermineCDPBitStatusOperation.h" +#import "keychain/ot/OTStates.h" + +@interface OTDetermineCDPBitStatusOperation () +@property OTOperationDependencies* deps; +@end + +@implementation OTDetermineCDPBitStatusOperation +@synthesize intendedState = _intendedState; +@synthesize nextState = _nextState; + +- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies + intendedState:(OctagonState*)intendedState + errorState:(OctagonState*)errorState +{ + if((self = [super init])) { + _deps = dependencies; + _intendedState = intendedState; + _nextState = errorState; + } + return self; +} + +- (void)groupStart +{ + secnotice("octagon-cdp-status", "Checking CDP status"); + + NSError* localError = nil; + OTAccountMetadataClassC* account = [self.deps.stateHolder loadOrCreateAccountMetadata:&localError]; + if(localError && [self.deps.lockStateTracker isLockedError:localError]) { + secnotice("octagon-cdp-status", "Device is locked! restarting on unlock"); + self.nextState = OctagonStateWaitForUnlock; + return; + } + + if(localError) { + secnotice("octagon-cdp-status", "Failed to load account metadata: %@", localError); + self.error = localError; + return; + } + + secnotice("octagon-cdp-status", "CDP is %@", OTAccountMetadataClassC_CDPStateAsString(account.cdpState)); + + if(account.cdpState == OTAccountMetadataClassC_CDPState_ENABLED) { + self.nextState = self.intendedState; + } else { + // If the CDP status is unknown or disabled, double-check with TPH. + // If there are any peers, the CDP status really should be ENABLED. + __block OTAccountMetadataClassC_CDPState newState = OTAccountMetadataClassC_CDPState_UNKNOWN; + + WEAKIFY(self); + [self.deps.cuttlefishXPCWrapper trustStatusWithContainer:self.deps.containerName + context:self.deps.contextID + reply:^(TrustedPeersHelperEgoPeerStatus *egoStatus, + NSError *xpcError) { + STRONGIFY(self); + if(xpcError) { + secnotice("octagon-cdp-status", "Unable to talk with TPH; leaving CDP status as 'unknown': %@", xpcError); + return; + } + + secnotice("octagon-cdp-status", "Octagon reports %d peers", (int)egoStatus.numberOfPeersInOctagon); + if(egoStatus.numberOfPeersInOctagon > 0) { + newState = OTAccountMetadataClassC_CDPState_ENABLED; + } else { + // As a last gasp, check in with SOS (if enabled). If there's a circle (in or out), CDP is on + if(self.deps.sosAdapter.sosEnabled) { + secnotice("octagon-cdp-status", "Requesting SOS status..."); + + NSError* circleError = nil; + SOSCCStatus circleStatus = [self.deps.sosAdapter circleStatus:&circleError]; + + if(circleError || circleStatus == kSOSCCError) { + secnotice("octagon-cdp-status", "Error fetching circle status. Leaving CDP status as 'unknown': %@", circleError); + } else if(circleStatus == kSOSCCCircleAbsent) { + secnotice("octagon-cdp-status", "SOS reports circle absent. Setting CDP to 'disabled'"); + newState = OTAccountMetadataClassC_CDPState_DISABLED; + } else { + secnotice("octagon-cdp-status", "SOS reports some existing circle (%d). Setting CDP to 'enabled'", (int)circleStatus); + newState = OTAccountMetadataClassC_CDPState_ENABLED; + } + } else { + // No SOS? no CDP. + secnotice("octagon-cdp-status", "No SOS. CDP bit is off."); + newState = OTAccountMetadataClassC_CDPState_DISABLED; + } + } + }]; + + if(account.cdpState != newState) { + NSError* stateError = nil; + [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) { + if(metadata.cdpState == OTAccountMetadataClassC_CDPState_ENABLED) { + secnotice("octagon-cdp-status", "CDP bit is enabled on-disk, not modifying (would have been %@)", OTAccountMetadataClassC_CDPStateAsString(newState)); + + // Set this here to perform the right state choice later + newState = OTAccountMetadataClassC_CDPState_ENABLED; + return nil; + } else { + secnotice("octagon-cdp-status", "Writing CDP bit as %@", OTAccountMetadataClassC_CDPStateAsString(newState)); + metadata.cdpState = newState; + return metadata; + } + } error:&stateError]; + + if(stateError) { + secnotice("octagon-cdp-status", "Failed to load account metadata: %@", stateError); + } + } + + if(newState == OTAccountMetadataClassC_CDPState_ENABLED) { + self.nextState = self.intendedState; + } else { + self.nextState = OctagonStateWaitForCDP; + } + } +} + +@end + +#endif // OCTAGON diff --git a/keychain/ot/OTEstablishOperation.m b/keychain/ot/OTEstablishOperation.m index cfb4a621..0acbd9ff 100644 --- a/keychain/ot/OTEstablishOperation.m +++ b/keychain/ot/OTEstablishOperation.m @@ -31,6 +31,7 @@ #import "keychain/ckks/CloudKitCategories.h" #import "keychain/ckks/CKKSCurrentKeyPointer.h" #import "keychain/ckks/CKKSKeychainView.h" +#import "keychain/SecureObjectSync/SOSAccount.h" #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h" #import "keychain/ot/ObjCImprovements.h" @@ -93,19 +94,16 @@ WEAKIFY(self); NSArray* publicSigningSPKIs = nil; - if(self.operationDependencies.sosAdapter.sosEnabled) { - NSError* peerError = nil; - - secnotice("octagon-sos", "SOS not enabled; no preapproved keys"); - NSSet>* peerSet = [self.operationDependencies.sosAdapter fetchTrustedPeers:&peerError]; + NSError* sosPreapprovalError = nil; + publicSigningSPKIs = [OTSOSAdapterHelpers peerPublicSigningKeySPKIsForCircle:self.operationDependencies.sosAdapter error:&sosPreapprovalError]; - if(!peerSet || peerError) { - secerror("octagon-sos: Can't fetch trusted peers during establish: %@", peerError); + if(publicSigningSPKIs) { + secnotice("octagon-sos", "SOS preapproved keys are %@", publicSigningSPKIs); + } else { + secnotice("octagon-sos", "Unable to fetch SOS preapproved keys: %@", sosPreapprovalError); } - publicSigningSPKIs = [OTSOSActualAdapter peerPublicSigningKeySPKIs:peerSet]; - secnotice("octagon-sos", "SOS preapproved keys are %@", publicSigningSPKIs); } else { secnotice("octagon-sos", "SOS not enabled; no preapproved keys"); } diff --git a/keychain/ot/OTFetchCKKSKeysOperation.m b/keychain/ot/OTFetchCKKSKeysOperation.m index fc7fd5ed..13613b2e 100644 --- a/keychain/ot/OTFetchCKKSKeysOperation.m +++ b/keychain/ot/OTFetchCKKSKeysOperation.m @@ -101,7 +101,7 @@ self.tlkShares = tlkShares; self.pendingTLKShares = pendingTLKShares; - secnotice("octagon-ckks", "Fetched %d key sets, %d broken key set,s %d tlk shares, and %d pendingTLKShares", + secnotice("octagon-ckks", "Fetched %d key sets, %d broken key sets, %d tlk shares, and %d pendingTLKShares", (int)self.viewKeySets.count, (int)self.incompleteKeySets.count, (int)self.tlkShares.count, diff --git a/keychain/ot/OTFetchViewsOperation.h b/keychain/ot/OTFetchViewsOperation.h index b7fbb418..f866bd21 100644 --- a/keychain/ot/OTFetchViewsOperation.h +++ b/keychain/ot/OTFetchViewsOperation.h @@ -31,12 +31,11 @@ NS_ASSUME_NONNULL_BEGIN -@interface OTFetchViewsOperation : CKKSGroupOperation +@interface OTFetchViewsOperation : CKKSGroupOperation -- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies; - -@property (nonatomic) NSSet* viewList; -@property (nonatomic) TPPolicy* _Nullable policy; +- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies + intendedState:(OctagonState*)intendedState + errorState:(OctagonState*)errorState; @end diff --git a/keychain/ot/OTFetchViewsOperation.m b/keychain/ot/OTFetchViewsOperation.m index cbe4e56a..3b63b205 100644 --- a/keychain/ot/OTFetchViewsOperation.m +++ b/keychain/ot/OTFetchViewsOperation.m @@ -26,21 +26,26 @@ #import "keychain/ot/OTFetchViewsOperation.h" #import "keychain/ot/ObjCImprovements.h" #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h" +#import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h" #import "keychain/ckks/CKKSAnalytics.h" @interface OTFetchViewsOperation () @property OTOperationDependencies* deps; -@property NSOperation* finishedOp; -@property CKKSViewManager* ckm; @end @implementation OTFetchViewsOperation +@synthesize intendedState = _intendedState; +@synthesize nextState = _nextState; - (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies + intendedState:(OctagonState*)intendedState + errorState:(OctagonState*)errorState { if ((self = [super init])) { _deps = dependencies; - _ckm = dependencies.viewManager; + + _intendedState = intendedState; + _nextState = errorState; } return self; } @@ -49,60 +54,41 @@ { secnotice("octagon", "fetching views"); - self.finishedOp = [[NSOperation alloc] init]; - [self dependOnBeforeGroupFinished:self.finishedOp]; - - NSSet* sosViewList = [self.ckm viewList]; - self.policy = nil; - self.viewList = sosViewList; - - if ([self.ckm useCKKSViewsFromPolicy]) { - WEAKIFY(self); - - [self.deps.cuttlefishXPCWrapper fetchPolicyWithContainer:self.deps.containerName context:self.deps.contextID reply:^(TPPolicy* _Nullable policy, NSError* _Nullable error) { - STRONGIFY(self); - if (error) { - secerror("octagon: failed to retrieve policy: %@", error); - [[CKKSAnalytics logger] logResultForEvent:OctagonEventFetchViews hardFailure:true result:error]; - self.error = error; - [self runBeforeGroupFinished:self.finishedOp]; - } else { - if (policy == nil) { - secerror("octagon: no policy returned"); - } - self.policy = policy; - NSArray* sosViews = [sosViewList allObjects]; - [self.deps.cuttlefishXPCWrapper getViewsWithContainer:self.deps.containerName context:self.deps.contextID inViews:sosViews reply:^(NSArray* _Nullable outViews, NSError* _Nullable error) { - STRONGIFY(self); - if (error) { - secerror("octagon: failed to retrieve list of views: %@", error); - [[CKKSAnalytics logger] logResultForEvent:OctagonEventFetchViews hardFailure:true result:error]; - self.error = error; - [self runBeforeGroupFinished:self.finishedOp]; - } else { - if (outViews == nil) { - secerror("octagon: bad results from getviews"); - } else { - self.viewList = [NSSet setWithArray:outViews]; - } - [self complete]; - } - }]; - } - }]; - } else { - [self complete]; - } -} + WEAKIFY(self); + [self.deps.cuttlefishXPCWrapper fetchCurrentPolicyWithContainer:self.deps.containerName + context:self.deps.contextID + reply:^(NSSet* _Nullable viewList, + TPPolicy* _Nullable policy, + NSError* _Nullable error) { + STRONGIFY(self); + [[CKKSAnalytics logger] logResultForEvent:OctagonEventFetchViews hardFailure:true result:error]; + + if (error) { + secerror("octagon: failed to retrieve policy+views: %@", error); + self.error = error; + return; + } + + secnotice("octagon-ckks", "Received policy %@ with view list: %@", policy, viewList); + // Write them down before continuing + + NSError* stateError = nil; + [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) { + metadata.syncingViews = [viewList mutableCopy]; + [metadata setTPPolicy:policy]; + return metadata; + } error:&stateError]; + + if(stateError) { + secerror("octagon: failed to save policy+views: %@", stateError); + self.error = stateError; + return; + } -- (void)complete { - secnotice("octagon", "viewList: %@", self.viewList); - self.ckm.policy = self.policy; - self.ckm.viewList = self.viewList; + [self.deps.viewManager setSyncingViews:viewList sortingPolicy:policy]; - [self.ckm createViews]; - [self.ckm beginCloudKitOperationOfAllViews]; - [self runBeforeGroupFinished:self.finishedOp]; + self.nextState = self.intendedState; + }]; } @end diff --git a/keychain/ot/OTFollowup.h b/keychain/ot/OTFollowup.h index e6f738ca..a7095929 100644 --- a/keychain/ot/OTFollowup.h +++ b/keychain/ot/OTFollowup.h @@ -57,7 +57,12 @@ NSString* OTFollowupContextTypeToString(OTFollowupContextType contextType); - (NSDictionary *)sysdiagnoseStatus; - (NSDictionary *)sfaStatus; +@end +@interface OTFollowup (Testing) +// Reports on whether this individual OTFollowUp object has posted a CFU of this type. +- (BOOL)hasPosted:(OTFollowupContextType)contextType; +- (void)clearAllPostedFlags; @end NS_ASSUME_NONNULL_END diff --git a/keychain/ot/OTFollowup.m b/keychain/ot/OTFollowup.m index 419f399c..649dc59d 100644 --- a/keychain/ot/OTFollowup.m +++ b/keychain/ot/OTFollowup.m @@ -56,6 +56,8 @@ NSString* OTFollowupContextTypeToString(OTFollowupContextType contextType) @property NSTimeInterval previousFollowupEnd; @property NSTimeInterval followupStart; @property NSTimeInterval followupEnd; + +@property NSMutableSet* postedCFUTypes; @end @implementation OTFollowup : NSObject @@ -64,6 +66,8 @@ NSString* OTFollowupContextTypeToString(OTFollowupContextType contextType) { if (self = [super init]) { self.cdpd = cdpFollowupController; + + _postedCFUTypes = [NSMutableSet set]; } return self; } @@ -95,9 +99,16 @@ NSString* OTFollowupContextTypeToString(OTFollowupContextType contextType) } NSError *followupError = nil; + + secnotice("followup", "Posting a follow up (for Octagon) of type %@", OTFollowupContextTypeToString(contextType)); BOOL result = [self.cdpd postFollowUpWithContext:context error:&followupError]; - if (error) { - *error = followupError; + + if(result) { + [self.postedCFUTypes addObject:OTFollowupContextTypeToString(contextType)]; + } else { + if (error) { + *error = followupError; + } } return result; @@ -112,7 +123,13 @@ NSString* OTFollowupContextTypeToString(OTFollowupContextType contextType) return NO; } - return [self.cdpd clearFollowUpWithContext:context error:error]; + secnotice("followup", "Clearing follow ups (for Octagon) of type %@", OTFollowupContextTypeToString(contextType)); + BOOL result = [self.cdpd clearFollowUpWithContext:context error:error]; + if(result) { + [self.postedCFUTypes removeObject:OTFollowupContextTypeToString(contextType)]; + } + + return result; } @@ -168,7 +185,18 @@ NSString* OTFollowupContextTypeToString(OTFollowupContextType contextType) return values; } +@end +@implementation OTFollowup (Testing) +- (BOOL)hasPosted:(OTFollowupContextType)contextType +{ + return [self.postedCFUTypes containsObject:OTFollowupContextTypeToString(contextType)]; +} + +- (void)clearAllPostedFlags +{ + [self.postedCFUTypes removeAllObjects]; +} @end #endif // OCTAGON diff --git a/keychain/ot/OTJoinWithVoucherOperation.h b/keychain/ot/OTJoinWithVoucherOperation.h index 3668f4d1..c5a9addb 100644 --- a/keychain/ot/OTJoinWithVoucherOperation.h +++ b/keychain/ot/OTJoinWithVoucherOperation.h @@ -40,12 +40,10 @@ NS_ASSUME_NONNULL_BEGIN ckksConflictState:(OctagonState*)ckksConflictState errorState:(OctagonState*)errorState voucherData:(NSData*)voucherData - voucherSig:(NSData*)voucherSig - preapprovedKeys:(NSArray*)preapprovedKeys; + voucherSig:(NSData*)voucherSig; @property (nonatomic) NSData* voucherData; @property (nonatomic) NSData* voucherSig; -@property (nonatomic) NSArray* preapprovedKeys; @property (nonatomic) NSString* peerID; diff --git a/keychain/ot/OTJoinWithVoucherOperation.m b/keychain/ot/OTJoinWithVoucherOperation.m index f68765d5..6a7c5b97 100644 --- a/keychain/ot/OTJoinWithVoucherOperation.m +++ b/keychain/ot/OTJoinWithVoucherOperation.m @@ -55,7 +55,6 @@ errorState:(OctagonState*)errorState voucherData:(NSData*)voucherData voucherSig:(NSData*)voucherSig - preapprovedKeys:(NSArray*)preapprovedKeys { if((self = [super init])) { _deps = dependencies; @@ -66,7 +65,6 @@ _voucherData = voucherData; _voucherSig = voucherSig; - _preapprovedKeys = preapprovedKeys; } return self; } @@ -98,14 +96,33 @@ { WEAKIFY(self); + NSArray* publicSigningSPKIs = nil; + if(self.deps.sosAdapter.sosEnabled) { + NSError* sosPreapprovalError = nil; + publicSigningSPKIs = [OTSOSAdapterHelpers peerPublicSigningKeySPKIsForCircle:self.deps.sosAdapter error:&sosPreapprovalError]; + + if(publicSigningSPKIs) { + secnotice("octagon-sos", "SOS preapproved keys are %@", publicSigningSPKIs); + } else { + secnotice("octagon-sos", "Unable to fetch SOS preapproved keys: %@", sosPreapprovalError); + } + + } else { + secnotice("octagon-sos", "SOS not enabled; no preapproved keys"); + } + [self.deps.cuttlefishXPCWrapper joinWithContainer:self.deps.containerName context:self.deps.contextID voucherData:self.voucherData voucherSig:self.voucherSig ckksKeys:viewKeySets tlkShares:pendingTLKShares - preapprovedKeys:self.preapprovedKeys - reply:^(NSString * _Nullable peerID, NSArray* keyHierarchyRecords, NSError * _Nullable error) { + preapprovedKeys:publicSigningSPKIs + reply:^(NSString * _Nullable peerID, + NSArray* keyHierarchyRecords, + NSSet* _Nullable syncingViews, + TPPolicy* _Nullable syncingPolicy, + NSError * _Nullable error) { STRONGIFY(self); if(error){ secerror("octagon: Error joining with voucher: %@", error); @@ -127,12 +144,16 @@ [[CKKSAnalytics logger] logSuccessForEventNamed:OctagonEventJoinWithVoucher]; + [self.deps.viewManager setSyncingViews:syncingViews sortingPolicy:syncingPolicy]; + NSError* localError = nil; BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) { - metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED; - metadata.peerID = peerID; - return metadata; - } error:&localError]; + metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED; + metadata.peerID = peerID; + metadata.syncingViews = [syncingViews mutableCopy]; + [metadata setTPPolicy:syncingPolicy]; + return metadata; + } error:&localError]; if(!persisted || localError) { secnotice("octagon", "Couldn't persist results: %@", localError); self.error = localError; diff --git a/keychain/ot/OTJoiningConfiguration.h b/keychain/ot/OTJoiningConfiguration.h index af3b327a..9207a19f 100644 --- a/keychain/ot/OTJoiningConfiguration.h +++ b/keychain/ot/OTJoiningConfiguration.h @@ -44,14 +44,6 @@ NS_ASSUME_NONNULL_BEGIN // Set this to non-zero if you want to configure your timeouts @property int64_t timeout; -- (instancetype)initWithProtocolType:(NSString*)protocolType - uniqueDeviceID:(NSString*)uniqueDeviceID - uniqueClientID:(NSString*)uniqueClientID - containerName:(NSString* _Nullable)containerName - contextID:(NSString*)contextID - epoch:(uint64_t)epoch - isInitiator:(BOOL)isInitiator; - - (instancetype)initWithProtocolType:(NSString*)protocolType uniqueDeviceID:(NSString*)uniqueDeviceID uniqueClientID:(NSString*)uniqueClientID diff --git a/keychain/ot/OTJoiningConfiguration.m b/keychain/ot/OTJoiningConfiguration.m index 339311f5..f7442540 100644 --- a/keychain/ot/OTJoiningConfiguration.m +++ b/keychain/ot/OTJoiningConfiguration.m @@ -33,24 +33,6 @@ NS_ASSUME_NONNULL_BEGIN return YES; } -- (instancetype)initWithProtocolType:(NSString*)protocolType - uniqueDeviceID:(NSString*)uniqueDeviceID - uniqueClientID:(NSString*)uniqueClientID - containerName:(NSString* _Nullable)containerName - contextID:(NSString*)contextID - epoch:(uint64_t)epoch - isInitiator:(BOOL)isInitiator -{ - return [self initWithProtocolType:protocolType - uniqueDeviceID:uniqueDeviceID - uniqueClientID:uniqueClientID - pairingUUID:[NSUUID UUID].UUIDString - containerName:containerName - contextID:contextID - epoch:(uint64_t)epoch - isInitiator:isInitiator]; -} - - (instancetype)initWithProtocolType:(NSString*)protocolType uniqueDeviceID:(NSString*)uniqueDeviceID uniqueClientID:(NSString*)uniqueClientID diff --git a/keychain/ot/OTManager.h b/keychain/ot/OTManager.h index 88a1820f..d8fcd385 100644 --- a/keychain/ot/OTManager.h +++ b/keychain/ot/OTManager.h @@ -36,6 +36,7 @@ #import "keychain/ot/OTCuttlefishAccountStateHolder.h" #import "keychain/escrowrequest/Framework/SecEscrowRequest.h" #import "keychain/ckks/CKKSAccountStateTracker.h" +#import "keychain/ckks/CKKSViewManager.h" #include "keychain/securityd/SecDbItem.h" #import NS_ASSUME_NONNULL_BEGIN @@ -45,22 +46,27 @@ NS_ASSUME_NONNULL_BEGIN @class OTClientStateMachine; @class CKKSLockStateTracker; @class CKKSAccountStateTracker; +@class CloudKitClassDependencies; @interface OTManager : NSObject @property (nonatomic, readonly) CKKSLockStateTracker* lockStateTracker; -@property id accountStateTracker; +@property CKKSAccountStateTracker* accountStateTracker; -- (instancetype)init NS_UNAVAILABLE; +@property (readonly) CKContainer* cloudKitContainer; +@property (nullable) CKKSViewManager* viewManager; + +// Creates an OTManager ready for use with live external systems. +- (instancetype)init; - (instancetype)initWithSOSAdapter:(id)sosAdapter - authKitAdapter:(id)authKitAdapter + authKitAdapter:(id)authKitAdapter deviceInformationAdapter:(id)deviceInformationAdapter apsConnectionClass:(Class)apsConnectionClass escrowRequestClass:(Class)escrowRequestClass - loggerClass:(Class _Nullable)loggerClass - lockStateTracker:(CKKSLockStateTracker* _Nullable)lockStateTracker - accountStateTracker:(id)accountStateTracker + loggerClass:(Class)loggerClass + lockStateTracker:(CKKSLockStateTracker*)lockStateTracker + cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies cuttlefishXPCConnection:(id _Nullable)cuttlefishXPCConnection cdpd:(id)cdpd; @@ -69,9 +75,12 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)waitForReady:(NSString* _Nullable)containerName context:(NSString*)context wait:(int64_t)wait; - (void)moveToCheckTrustedStateForContainer:(NSString* _Nullable)containerName context:(NSString*)context; +// Call this to ensure SFA is ready +- (void)setupAnalytics; + + (instancetype _Nullable)manager; + (instancetype _Nullable)resetManager:(bool)reset to:(OTManager* _Nullable)obj; -- (void)xpc24HrNotification:(NSString* _Nullable)containerName context:(NSString*)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *error))reply; +- (void)xpc24HrNotification; - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName contextID:(NSString*)contextID @@ -127,10 +136,20 @@ NS_ASSUME_NONNULL_BEGIN containerName:(NSString* _Nullable)containerName contextName:(NSString *)contextName reply:(void (^)(NSError *error))reply; +@end -//test only +@interface OTManager (Testing) - (void)setSOSEnabledForPlatformFlag:(bool) value; + +- (void)clearAllContexts; + +// Note that the OTManager returned by this will not work particularly well, if you want to do Octagon things +// This should only be used for the CKKS tests +- (instancetype)initWithSOSAdapter:(id)sosAdapter + lockStateTracker:(CKKSLockStateTracker*)lockStateTracker + cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies; @end + NS_ASSUME_NONNULL_END #endif // OCTAGON diff --git a/keychain/ot/OTManager.m b/keychain/ot/OTManager.m index 92c0caae..79d43f93 100644 --- a/keychain/ot/OTManager.m +++ b/keychain/ot/OTManager.m @@ -49,6 +49,7 @@ #import "keychain/ckks/CKKS.h" #import "keychain/ckks/CKKSViewManager.h" #import "keychain/ckks/CKKSLockStateTracker.h" +#import "keychain/ckks/CKKSCloudKitClassDependencies.h" #import #import @@ -135,11 +136,11 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; - (instancetype)init { - // Under Octagon, the sos adatper is not considered essential. + // Under Octagon, the sos adapter is not considered essential. id sosAdapter = (OctagonPlatformSupportsSOS() ? [[OTSOSActualAdapter alloc] initAsEssential:NO] : [[OTSOSMissingAdapter alloc] init]); - + return [self initWithSOSAdapter:sosAdapter authKitAdapter:[[OTAuthKitActualAdapter alloc] init] deviceInformationAdapter:[[OTDeviceInformationActualAdapter alloc] init] @@ -147,23 +148,21 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; escrowRequestClass:[EscrowRequestServer class] // Use the server class here to skip the XPC layer loggerClass:[CKKSAnalytics class] lockStateTracker:[CKKSLockStateTracker globalTracker] - // The use of CKKS's account tracker here is an inversion, and will be fixed with CKKS-for-all when we - // have Octagon own the CKKS objects - accountStateTracker:[CKKSViewManager manager].accountTracker + cloudKitClassDependencies:[CKKSCloudKitClassDependencies forLiveCloudKit] cuttlefishXPCConnection:nil cdpd:[[CDPFollowUpController alloc] init]]; } --(instancetype)initWithSOSAdapter:(id)sosAdapter - authKitAdapter:(id)authKitAdapter - deviceInformationAdapter:(id)deviceInformationAdapter - apsConnectionClass:(Class)apsConnectionClass - escrowRequestClass:(Class)escrowRequestClass - loggerClass:(Class)loggerClass - lockStateTracker:(CKKSLockStateTracker*)lockStateTracker - accountStateTracker:(id)accountStateTracker - cuttlefishXPCConnection:(id)cuttlefishXPCConnection - cdpd:(id)cdpd +- (instancetype)initWithSOSAdapter:(id)sosAdapter + authKitAdapter:(id)authKitAdapter + deviceInformationAdapter:(id)deviceInformationAdapter + apsConnectionClass:(Class)apsConnectionClass + escrowRequestClass:(Class)escrowRequestClass + loggerClass:(Class)loggerClass + lockStateTracker:(CKKSLockStateTracker*)lockStateTracker + cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies + cuttlefishXPCConnection:(id)cuttlefishXPCConnection + cdpd:(id)cdpd { if((self = [super init])) { _sosAdapter = sosAdapter; @@ -171,10 +170,13 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; _deviceInformationAdapter = deviceInformationAdapter; _loggerClass = loggerClass; _lockStateTracker = lockStateTracker; - _accountStateTracker = accountStateTracker; _sosEnabledForPlatform = OctagonPlatformSupportsSOS(); _cuttlefishXPCConnection = cuttlefishXPCConnection; + _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS]; + _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer + nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass]; + self.contexts = [NSMutableDictionary dictionary]; self.clients = [NSMutableDictionary dictionary]; @@ -185,19 +187,41 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; _cdpd = cdpd; + _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer + sosAdapter:sosAdapter + accountStateTracker:_accountStateTracker + lockStateTracker:lockStateTracker + cloudKitClassDependencies:cloudKitClassDependencies]; + // The default CuttlefishContext always exists: (void) [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext]; - - // Tell SFA to expect us - if(OctagonIsEnabled()){ - [self setupAnalytics]; - } secnotice("octagon", "otmanager init"); } return self; } +- (instancetype)initWithSOSAdapter:(id)sosAdapter + lockStateTracker:(CKKSLockStateTracker*)lockStateTracker + cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies +{ + if((self = [super init])) { + _sosAdapter = sosAdapter; + _lockStateTracker = lockStateTracker; + + _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS]; + _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer + nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass]; + + _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer + sosAdapter:sosAdapter + accountStateTracker:_accountStateTracker + lockStateTracker:lockStateTracker + cloudKitClassDependencies:cloudKitClassDependencies]; + } + return self; +} + - (void)initializeOctagon { secnotice("octagon", "Initializing Octagon..."); @@ -584,7 +608,7 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; CKKSViewManager* viewManager = nil; if([containerName isEqualToString:SecCKKSContainerName] && [contextID isEqualToString:OTDefaultContext]) { - viewManager = [CKKSViewManager manager]; + viewManager = self.viewManager; } if(!context) { @@ -607,6 +631,15 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; return context; } +- (void)clearAllContexts +{ + if(self.contexts) { + dispatch_sync(self.queue, ^{ + [self.contexts removeAllObjects]; + }); + } +} + - (void)fetchEgoPeerID:(NSString* _Nullable)container context:(NSString*)context reply:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))reply @@ -842,7 +875,14 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID]; [cfshContext handlePairingRestart:config]; [cfshContext startOctagonStateMachine]; - [cfshContext rpcPrepareIdentityAsApplicantWithConfiguration:config epoch:config.epoch reply:^(NSString * _Nullable peerID, NSData * _Nullable permanentInfo, NSData * _Nullable permanentInfoSig, NSData * _Nullable stableInfo, NSData * _Nullable stableInfoSig, NSError * _Nullable error) { + [cfshContext rpcPrepareIdentityAsApplicantWithConfiguration:config + epoch:config.epoch + reply:^(NSString * _Nullable peerID, + NSData * _Nullable permanentInfo, + NSData * _Nullable permanentInfoSig, + NSData * _Nullable stableInfo, + NSData * _Nullable stableInfoSig, + NSError * _Nullable error) { reply(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error); }]; } @@ -850,13 +890,12 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config vouchData:(NSData*)vouchData vouchSig:(NSData*)vouchSig - preapprovedKeys:(NSArray* _Nullable)preapprovedKeys reply:(void (^)(NSError * _Nullable error))reply { OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID]; [cfshContext handlePairingRestart:config]; [cfshContext startOctagonStateMachine]; - [cfshContext rpcJoin:vouchData vouchSig:vouchSig preapprovedKeys:preapprovedKeys reply:^(NSError * _Nullable error) { + [cfshContext rpcJoin:vouchData vouchSig:vouchSig reply:^(NSError * _Nullable error) { reply(error); }]; } @@ -980,6 +1019,7 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; secnotice("octagon-analytics", "Error fetching Octagon metadata: %@", metadataError); } values[OctagonAnalyticIcloudAccountState] = metadata ? @(metadata.icloudAccountState) : nil; + values[OctagonAnalyticCDPBitStatus] = metadata? @(metadata.cdpState) : nil; values[OctagonAnalyticsTrustState] = metadata ? @(metadata.trustState) : nil; NSDate* healthCheck = [cuttlefishContext currentMemoizedLastHealthCheck]; @@ -1176,8 +1216,9 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; [cfshContext startOctagonStateMachine]; [cfshContext joinWithRecoveryKey:recoveryKey reply:^(NSError *error) { - if (error.code == TrustedPeersHelperErrorCodeNotEnrolled && [error.domain isEqualToString:TrustedPeersHelperErrorDomain]) { - // If we hit this error, let's reset and establish octagon then enroll this recovery key in the new circle + if ((error.code == TrustedPeersHelperErrorCodeNotEnrolled || error.code == TrustedPeersHelperErrorCodeUntrustedRecoveryKeys) + && [error.domain isEqualToString:TrustedPeersHelperErrorDomain]){ + // If we hit either of these errors, let's reset and establish octagon then enroll this recovery key in the new circle secerror("octagon, recovery key is not enrolled in octagon, resetting octagon circle"); [[self.loggerClass logger] logResultForEvent:OctagonEventJoinRecoveryKeyCircleReset hardFailure:NO result:error]; @@ -1219,17 +1260,22 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; }]; } -- (void)healthCheck:(NSString *)container context:(NSString *)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *_Nullable error))reply +- (void)xpc24HrNotification { - [self xpc24HrNotification:container context:context skipRateLimitingCheck:skipRateLimitingCheck reply:reply]; -} + secnotice("octagon-health", "Received 24hr xpc notification"); + [self healthCheck:OTCKContainerName context:OTDefaultContext skipRateLimitingCheck:NO reply:^(NSError * _Nullable error) { + if(error) { + secerror("octagon: error attempting to check octagon health: %@", error); + } else { + secnotice("octagon", "health check success"); + } + }]; +} -- (void)xpc24HrNotification:(NSString* _Nullable)containerName context:(NSString*)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *error))reply +- (void)healthCheck:(NSString *)container context:(NSString *)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *_Nullable error))reply { - secnotice("octagon-health", "Received 24 xpc notification"); - - OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context]; + OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context]; secnotice("octagon", "notifying container of change"); @@ -1253,6 +1299,9 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; { for(OTCuttlefishContext* context in self.contexts.allValues) { [context.stateMachine haltOperation]; + + // Also, clear the viewManager strong pointer + [context clearCKKSViewManager]; } } @@ -1349,9 +1398,6 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; [[CKKSAnalytics logger] setNumberProperty:NULL forKey:countName]; } - // Clear all CFU state variables, too - [cuttlefishContext clearPendingCFUFlags]; - // Always return without error reply(nil); } @@ -1366,6 +1412,79 @@ static NSString* const kOTRampZoneName = @"metadata_zone"; reply(NULL); } +- (void)refetchCKKSPolicy:(NSString * _Nullable)containerName + contextID:(nonnull NSString *)contextID + reply:(nonnull void (^)(NSError * _Nullable))reply { + secnotice("octagon-ckks", "refetch-ckks-policy"); + + if(!containerName) { + containerName = OTCKContainerName; + } + + OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID]; + + if(!cfshContext) { + reply([NSError errorWithDomain:OctagonErrorDomain + code:OTErrorNoSuchContext + description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]); + return; + } + + [cfshContext rpcRefetchCKKSPolicy:^(NSError* error) { + secnotice("octagon-ckks", "refetch-ckks-policy result: %@", error ?: @"no error"); + reply(error); + }]; +} + + +- (void)getCDPStatus:(NSString * _Nullable)containerName + contextID:(nonnull NSString *)contextID + reply:(nonnull void (^)(OTCDPStatus, NSError * _Nullable))reply { + secnotice("octagon-cdp", "get-cdp-status"); + + if(!containerName) { + containerName = OTCKContainerName; + } + + OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID]; + + if(!cfshContext) { + reply(OTCDPStatusUnknown, [NSError errorWithDomain:OctagonErrorDomain + code:OTErrorNoSuchContext + description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]); + return; + } + + NSError* error = nil; + OTCDPStatus status = [cfshContext getCDPStatus:&error]; + + reply(status, error); +} + + +- (void)setCDPEnabled:(NSString * _Nullable)containerName + contextID:(nonnull NSString *)contextID + reply:(nonnull void (^)(NSError * _Nullable))reply { + secnotice("octagon-cdp", "set-cdp-enabled"); + if(!containerName) { + containerName = OTCKContainerName; + } + + OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID]; + + if(!cfshContext) { + reply([NSError errorWithDomain:OctagonErrorDomain + code:OTErrorNoSuchContext + description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]); + return; + } + + NSError* localError = nil; + [cfshContext setCDPEnabled:&localError]; + + reply(localError); +} + @end #endif diff --git a/keychain/ot/OTOperationDependencies.h b/keychain/ot/OTOperationDependencies.h index e9e5aeb6..373177f6 100644 --- a/keychain/ot/OTOperationDependencies.h +++ b/keychain/ot/OTOperationDependencies.h @@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN @property id authKitAdapter; @property id deviceInformationAdapter; @property (readonly) CuttlefishXPCWrapper* cuttlefishXPCWrapper; -@property CKKSViewManager* viewManager; +@property (readonly, weak) CKKSViewManager* viewManager; @property CKKSLockStateTracker* lockStateTracker; @property Class escrowRequestClass; diff --git a/keychain/ot/OTPrepareOperation.h b/keychain/ot/OTPrepareOperation.h index 9f03512a..a18e23a0 100644 --- a/keychain/ot/OTPrepareOperation.h +++ b/keychain/ot/OTPrepareOperation.h @@ -24,6 +24,7 @@ #if OCTAGON #import +#import #import "keychain/ckks/CKKSGroupOperation.h" #import "keychain/ot/OctagonStateMachineHelpers.h" @@ -41,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN intendedState:(OctagonState*)intendedState errorState:(OctagonState*)errorState deviceInfo:(OTDeviceInformation*)deviceInfo + policyOverride:(TPPolicyVersion* _Nullable)policyOverride epoch:(uint64_t)epoch; @property (nonatomic) uint64_t epoch; @@ -52,6 +54,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable) NSData* stableInfo; @property (nullable) NSData* stableInfoSig; +@property (nullable) TPPolicyVersion* policyOverride; + @end NS_ASSUME_NONNULL_END diff --git a/keychain/ot/OTPrepareOperation.m b/keychain/ot/OTPrepareOperation.m index 2b34ec23..d0e0ca45 100644 --- a/keychain/ot/OTPrepareOperation.m +++ b/keychain/ot/OTPrepareOperation.m @@ -28,7 +28,6 @@ #import #import "keychain/ot/OTCuttlefishContext.h" -#import "keychain/ot/OTFetchViewsOperation.h" #import "keychain/ot/OTOperationDependencies.h" #import "keychain/ot/OTPrepareOperation.h" @@ -48,6 +47,7 @@ intendedState:(OctagonState*)intendedState errorState:(OctagonState*)errorState deviceInfo:(OTDeviceInformation*)deviceInfo + policyOverride:(TPPolicyVersion* _Nullable)policyOverride epoch:(uint64_t)epoch { if((self = [super init])) { @@ -58,6 +58,8 @@ _intendedState = intendedState; _nextState = errorState; + + _policyOverride = policyOverride; } return self; } @@ -143,11 +145,18 @@ deviceName:self.deviceInfo.deviceName serialNumber:self.deviceInfo.serialNumber osVersion:self.deviceInfo.osVersion - policyVersion:nil + policyVersion:self.policyOverride policySecrets:nil signingPrivKeyPersistentRef:signingKeyPersistRef encPrivKeyPersistentRef:encryptionKeyPersistRef - reply:^(NSString * _Nullable peerID, NSData * _Nullable permanentInfo, NSData * _Nullable permanentInfoSig, NSData * _Nullable stableInfo, NSData * _Nullable stableInfoSig, NSError * _Nullable error) { + reply:^(NSString * _Nullable peerID, + NSData * _Nullable permanentInfo, + NSData * _Nullable permanentInfoSig, + NSData * _Nullable stableInfo, + NSData * _Nullable stableInfoSig, + NSSet* _Nullable syncingViews, + TPPolicy* _Nullable syncingPolicy, + NSError * _Nullable error) { STRONGIFY(self); [[CKKSAnalytics logger] logResultForEvent:OctagonEventPrepareIdentity hardFailure:true result:error]; if(error) { @@ -163,27 +172,29 @@ self.stableInfoSig = stableInfoSig; NSError* localError = nil; - BOOL persisted = [self.deps.stateHolder persistNewEgoPeerID:peerID error:&localError]; + + secnotice("octagon-ckks", "New syncing policy: %@ views: %@", syncingPolicy, syncingViews); + + BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) { + metadata.peerID = peerID; + metadata.syncingViews = [syncingViews mutableCopy]; + [metadata setTPPolicy:syncingPolicy]; + + return metadata; + } error:&localError]; + if(!persisted || localError) { - secnotice("octagon", "Couldn't persist peer ID: %@", localError); + secnotice("octagon", "Couldn't persist metadata: %@", localError); self.error = localError; [self runBeforeGroupFinished:self.finishedOp]; - } else { - WEAKIFY(self); - - CKKSResultOperation *doneOp = [CKKSResultOperation named:@"ot-prepare" - withBlock:^{ - STRONGIFY(self); - self.nextState = self.intendedState; - }]; - - OTFetchViewsOperation *fetchViewsOp = [[OTFetchViewsOperation alloc] initWithDependencies:self.deps]; - [self runBeforeGroupFinished:fetchViewsOp]; - [doneOp addDependency:fetchViewsOp]; - [self runBeforeGroupFinished:doneOp]; - [self.finishedOp addDependency:doneOp]; - [self runBeforeGroupFinished:self.finishedOp]; + return; } + + // Let CKKS know of the new policy, so it can spin up + [self.deps.viewManager setSyncingViews:syncingViews sortingPolicy:syncingPolicy]; + + self.nextState = self.intendedState; + [self runBeforeGroupFinished:self.finishedOp]; } }]; } diff --git a/keychain/ot/OTResetCKKSZonesLackingTLKsOperation.m b/keychain/ot/OTResetCKKSZonesLackingTLKsOperation.m index 0a09dd2f..0f2dbc38 100644 --- a/keychain/ot/OTResetCKKSZonesLackingTLKsOperation.m +++ b/keychain/ot/OTResetCKKSZonesLackingTLKsOperation.m @@ -73,7 +73,8 @@ for(CKKSCurrentKeySet* incompleteKeySet in incompleteKeySets) { if(incompleteKeySet.error == nil) { - CKKSKeychainView* viewMatchingSet = [self.deps.viewManager findView:incompleteKeySet.viewName]; + CKKSViewManager* viewManager = self.deps.viewManager; + CKKSKeychainView* viewMatchingSet = [viewManager findView:incompleteKeySet.viewName]; if(!viewMatchingSet) { secnotice("octagon-ckks", "No view matching viewset %@?", incompleteKeySet); diff --git a/keychain/ot/OTSOSAdapter.h b/keychain/ot/OTSOSAdapter.h index dddda03b..976476e1 100644 --- a/keychain/ot/OTSOSAdapter.h +++ b/keychain/ot/OTSOSAdapter.h @@ -19,16 +19,20 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; - (instancetype)initAsEssential:(BOOL)essential; -// Helper methods -+ (NSArray*)peerPublicSigningKeySPKIs:(NSSet>* _Nullable)peers; - +// Helper methods. + (NSSet*)sosCKKSViewList; @end + // This adapter is for a platform which does not have SOS (e.g., aTV, Watch, HomePod) @interface OTSOSMissingAdapter : NSObject @end +// Helper code +@interface OTSOSAdapterHelpers : NSObject ++ (NSArray* _Nullable)peerPublicSigningKeySPKIsForCircle:(id)sosAdapter error:(NSError**)error; +@end + NS_ASSUME_NONNULL_END #endif // OCTAGON diff --git a/keychain/ot/OTSOSAdapter.m b/keychain/ot/OTSOSAdapter.m index 69ed211f..4513d980 100644 --- a/keychain/ot/OTSOSAdapter.m +++ b/keychain/ot/OTSOSAdapter.m @@ -98,7 +98,7 @@ code:CKKSNoPeersAvailable description:@"Not in SOS circle, but no error returned"]; } - secerror("octagon-sos: Not in circle : %d %@", circleStatus, localerror); + secerror("octagon-sos: Not in circle : %@ %@", SOSAccountGetSOSCCStatusString(circleStatus), localerror); if(error) { *error = localerror; } @@ -302,22 +302,6 @@ return result; } - -+ (NSArray*)peerPublicSigningKeySPKIs:(NSSet>* _Nullable)peerSet -{ - NSMutableArray* publicSigningSPKIs = [NSMutableArray array]; - - for(id peer in peerSet) { - NSData* spki = [peer.publicSigningKey encodeSubjectPublicKeyInfo]; - if(!spki) { - secerror("octagon-sos: Can't create SPKI for peer: %@", peer); - } else { - secerror("octagon-sos: Created SPKI for peer: %@", peer); - [publicSigningSPKIs addObject:spki]; - } - } - return publicSigningSPKIs; -} @end @implementation OTSOSMissingAdapter @@ -406,7 +390,53 @@ trustedPeersError:unimplementedError]; } +@end + +@implementation OTSOSAdapterHelpers + ++ (NSArray*)peerPublicSigningKeySPKIs:(NSSet>* _Nullable)peerSet +{ + NSMutableArray* publicSigningSPKIs = [NSMutableArray array]; + + for(id peer in peerSet) { + NSData* spki = [peer.publicSigningKey encodeSubjectPublicKeyInfo]; + if(!spki) { + secerror("octagon-sos: Can't create SPKI for peer: %@", peer); + } else { + secerror("octagon-sos: Created SPKI for peer: %@", peer); + [publicSigningSPKIs addObject:spki]; + } + } + return publicSigningSPKIs; +} + ++ (NSArray* _Nullable)peerPublicSigningKeySPKIsForCircle:(id)sosAdapter error:(NSError**)error +{ + NSError* peerError = nil; + SOSCCStatus sosStatus = [sosAdapter circleStatus:&peerError]; + + if(sosStatus != kSOSCCInCircle || peerError) { + secerror("octagon-sos: Not in circle; not preapproving keys: %@ (%@)", SOSAccountGetSOSCCStatusString(sosStatus), peerError); + if(error) { + *error = peerError; + } + return nil; + } else { + // We're in-circle: preapprove these peers + NSError* peerError = nil; + NSSet>* peerSet = [sosAdapter fetchTrustedPeers:&peerError]; + if(!peerSet || peerError) { + secerror("octagon-sos: Can't fetch trusted peer SPKIs: %@", peerError); + if(error) { + *error = peerError; + } + return nil; + } + + return [self peerPublicSigningKeySPKIs:peerSet]; + } +} @end #endif // OCTAGON diff --git a/keychain/ot/OTSOSUpdatePreapprovalsOperation.m b/keychain/ot/OTSOSUpdatePreapprovalsOperation.m index 7abf6522..096f1cdd 100644 --- a/keychain/ot/OTSOSUpdatePreapprovalsOperation.m +++ b/keychain/ot/OTSOSUpdatePreapprovalsOperation.m @@ -85,30 +85,35 @@ }]; [self dependOnBeforeGroupFinished:self.finishedOp]; - NSError* error = nil; - NSSet>* peerSet = [self.deps.sosAdapter fetchTrustedPeers:&error]; + NSError* sosPreapprovalError = nil; + NSArray* publicSigningSPKIs = [OTSOSAdapterHelpers peerPublicSigningKeySPKIsForCircle:self.deps.sosAdapter error:&sosPreapprovalError]; - if(!peerSet || error) { - secerror("octagon-sos: Can't fetch trusted peers; stopping preapproved key update: %@", error); - self.error = error; + if(!publicSigningSPKIs || sosPreapprovalError) { + secerror("octagon-sos: Can't fetch trusted peers; stopping preapproved key update: %@", sosPreapprovalError); + self.error = sosPreapprovalError; self.nextState = self.sosNotPresentState; [self runBeforeGroupFinished:self.finishedOp]; return; } - NSArray* publicSigningSPKIs = [OTSOSActualAdapter peerPublicSigningKeySPKIs:peerSet]; secnotice("octagon-sos", "Updating SOS preapproved keys to %@", publicSigningSPKIs); [self.deps.cuttlefishXPCWrapper setPreapprovedKeysWithContainer:self.deps.containerName context:self.deps.contextID preapprovedKeys:publicSigningSPKIs - reply:^(NSError* error) { + reply:^(TrustedPeersHelperPeerState* _Nullable peerState, NSError* error) { STRONGIFY(self); if(error) { secerror("octagon-sos: unable to update preapproved keys: %@", error); self.error = error; } else { secnotice("octagon-sos", "Updated SOS preapproved keys"); + + if (peerState.memberChanges) { + secnotice("octagon", "Member list changed"); + [self.deps.octagonAdapter sendTrustedPeerSetChangedUpdate]; + } + self.nextState = self.intendedState; } [self runBeforeGroupFinished:self.finishedOp]; diff --git a/keychain/ot/OTSOSUpgradeOperation.h b/keychain/ot/OTSOSUpgradeOperation.h index 04bbfec9..8c87b128 100644 --- a/keychain/ot/OTSOSUpgradeOperation.h +++ b/keychain/ot/OTSOSUpgradeOperation.h @@ -2,6 +2,7 @@ #if OCTAGON #import +#import #import "keychain/ckks/CKKSGroupOperation.h" #import "keychain/ot/OctagonStateMachineHelpers.h" #import "keychain/ot/OTStates.h" @@ -16,11 +17,14 @@ NS_ASSUME_NONNULL_BEGIN @interface OTSOSUpgradeOperation : CKKSGroupOperation +@property (readonly, nullable) TPPolicyVersion* policyOverride; + - (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies intendedState:(OctagonState*)intendedState ckksConflictState:(OctagonState*)ckksConflictState errorState:(OctagonState*)errorState - deviceInfo:(OTDeviceInformation*)deviceInfo; + deviceInfo:(OTDeviceInformation*)deviceInfo + policyOverride:(TPPolicyVersion* _Nullable)policyOverride; @end diff --git a/keychain/ot/OTSOSUpgradeOperation.m b/keychain/ot/OTSOSUpgradeOperation.m index 8f223984..4972beed 100644 --- a/keychain/ot/OTSOSUpgradeOperation.m +++ b/keychain/ot/OTSOSUpgradeOperation.m @@ -45,6 +45,7 @@ ckksConflictState:(OctagonState*)ckksConflictState errorState:(OctagonState*)errorState deviceInfo:(OTDeviceInformation*)deviceInfo + policyOverride:(TPPolicyVersion* _Nullable)policyOverride { if((self = [super init])) { _deps = dependencies; @@ -54,6 +55,7 @@ _ckksConflictState = ckksConflictState; _deviceInfo = deviceInfo; + _policyOverride = policyOverride; } return self; } @@ -97,9 +99,13 @@ return; } - // Now that we have some non-error SOS status, write down that we attempted an SOS Upgrade. + // Now that we have some non-error SOS status, write down that we attempted an SOS Upgrade (and make sure the CDP bit is on) NSError* persistError = nil; - BOOL persisted = [self.deps.stateHolder persistOctagonJoinAttempt:OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED error:&persistError]; + BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) { + metadata.attemptedJoin = OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED; + metadata.cdpState = OTAccountMetadataClassC_CDPState_ENABLED; + return metadata; + } error:&persistError]; if(!persisted || persistError) { secerror("octagon: failed to save 'attempted join' state: %@", persistError); } @@ -196,7 +202,7 @@ deviceName:self.deviceInfo.deviceName serialNumber:self.self.deviceInfo.serialNumber osVersion:self.deviceInfo.osVersion - policyVersion:nil + policyVersion:self.policyOverride policySecrets:nil signingPrivKeyPersistentRef:signingKeyPersistRef encPrivKeyPersistentRef:encryptionKeyPersistRef @@ -205,6 +211,8 @@ NSData * _Nullable permanentInfoSig, NSData * _Nullable stableInfo, NSData * _Nullable stableInfoSig, + NSSet* syncingViews, + TPPolicy* _Nullable syncingPolicy, NSError * _Nullable error) { STRONGIFY(self); @@ -216,12 +224,29 @@ [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted]; [self runBeforeGroupFinished:self.finishedOp]; - } else { - secnotice("octagon-sos", "Prepared: %@ %@ %@", peerID, permanentInfo, permanentInfoSig); + return; + } + + secnotice("octagon-sos", "Prepared: %@ %@ %@", peerID, permanentInfo, permanentInfoSig); + + NSError* localError = nil; + BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) { + metadata.syncingViews = [syncingViews mutableCopy]; + [metadata setTPPolicy:syncingPolicy]; + return metadata; + } error:&localError]; - [self afterPrepare]; + if(!persisted || localError) { + secerror("octagon-ckks: Error persisting new views and policy: %@", localError); + self.error = localError; + [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted]; + [self runBeforeGroupFinished:self.finishedOp]; + return; } + [self.deps.viewManager setSyncingViews:syncingViews sortingPolicy:syncingPolicy]; + + [self afterPrepare]; }]; } @@ -233,6 +258,9 @@ reply:^(BOOL launchOkay, NSError * _Nullable error) { STRONGIFY(self); + // Preflight preapprovedjoin should return the policy used by the peers we eventually will trust + // Octagon: ensure we use appropriate CKKS policy when joining octagon with future policy + [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreflightPreapprovedJoin hardFailure:true result:error]; if(error) { secerror("octagon-sos: preflightPreapprovedJoin failed: %@", error); @@ -321,11 +349,7 @@ { WEAKIFY(self); - OTFetchViewsOperation *fetchViews = [[OTFetchViewsOperation alloc] initWithDependencies:self.deps]; - [self runBeforeGroupFinished:fetchViews]; - OTFetchCKKSKeysOperation* fetchKeysOp = [[OTFetchCKKSKeysOperation alloc] initWithDependencies:self.deps]; - [fetchKeysOp addDependency:fetchViews]; [self runBeforeGroupFinished:fetchKeysOp]; secnotice("octagon-sos", "Fetching keys from CKKS"); @@ -344,75 +368,84 @@ secnotice("octagon-sos", "Fetching trusted peers from SOS"); - NSError* error = nil; - NSSet>* peerSet = [self.deps.sosAdapter fetchTrustedPeers:&error]; + NSArray* publicSigningSPKIs = nil; + if(self.deps.sosAdapter.sosEnabled) { + NSError* sosPreapprovalError = nil; + publicSigningSPKIs = [OTSOSAdapterHelpers peerPublicSigningKeySPKIsForCircle:self.deps.sosAdapter error:&sosPreapprovalError]; - if(!peerSet || error) { - secerror("octagon-sos: Can't fetch trusted peers; stopping upgrade: %@", error); - self.error = error; - self.nextState = OctagonStateBecomeUntrusted; - [self runBeforeGroupFinished:self.finishedOp]; - return; - } + if(publicSigningSPKIs) { + secnotice("octagon-sos", "SOS preapproved keys are %@", publicSigningSPKIs); + } else { + secnotice("octagon-sos", "Unable to fetch SOS preapproved keys: %@", sosPreapprovalError); + } - NSArray* publicSigningSPKIs = [OTSOSActualAdapter peerPublicSigningKeySPKIs:peerSet]; - secnotice("octagon-sos", "Creating SOS preapproved keys as %@", publicSigningSPKIs); + } else { + secnotice("octagon-sos", "SOS not enabled; no preapproved keys"); + } - secnotice("octagon-sos", "Beginning SOS upgrade with %d key sets and %d SOS peers", (int)viewKeySets.count, (int)peerSet.count); + secnotice("octagon-sos", "Beginning SOS upgrade with %d key sets and %d SOS peers", (int)viewKeySets.count, (int)publicSigningSPKIs.count); [self.deps.cuttlefishXPCWrapper attemptPreapprovedJoinWithContainer:self.deps.containerName context:self.deps.contextID ckksKeys:viewKeySets tlkShares:pendingTLKShares preapprovedKeys:publicSigningSPKIs - reply:^(NSString * _Nullable peerID, NSArray* keyHierarchyRecords, NSError * _Nullable error) { - STRONGIFY(self); + reply:^(NSString * _Nullable peerID, + NSArray* keyHierarchyRecords, + NSSet* _Nullable syncingViewList, + TPPolicy* _Nullable syncingPolicy, + NSError * _Nullable error) { + STRONGIFY(self); - [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreapprovedJoin hardFailure:true result:error]; - if(error) { - secerror("octagon-sos: attemptPreapprovedJoin failed: %@", error); - - if ([error isCuttlefishError:CuttlefishErrorKeyHierarchyAlreadyExists]) { - secnotice("octagon-ckks", "A CKKS key hierarchy is out of date; requesting reset"); - self.nextState = self.ckksConflictState; - } else { - self.error = error; - self.nextState = OctagonStateBecomeUntrusted; - } - [self runBeforeGroupFinished:self.finishedOp]; - return; + [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreapprovedJoin hardFailure:true result:error]; + if(error) { + secerror("octagon-sos: attemptPreapprovedJoin failed: %@", error); + + if ([error isCuttlefishError:CuttlefishErrorKeyHierarchyAlreadyExists]) { + secnotice("octagon-ckks", "A CKKS key hierarchy is out of date; requesting reset"); + self.nextState = self.ckksConflictState; + } else { + self.error = error; + self.nextState = OctagonStateBecomeUntrusted; } + [self runBeforeGroupFinished:self.finishedOp]; + return; + } - [self requestSilentEscrowUpdate]; + [self requestSilentEscrowUpdate]; - secerror("octagon-sos: attemptPreapprovedJoin succeded"); + secerror("octagon-sos: attemptPreapprovedJoin succeded"); + [self.deps.viewManager setSyncingViews:syncingViewList sortingPolicy:syncingPolicy]; - NSError* localError = nil; - BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) { - metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED; - metadata.peerID = peerID; - return metadata; - } error:&localError]; + NSError* localError = nil; + BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) { + metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED; + metadata.peerID = peerID; + metadata.syncingViews = [syncingViewList mutableCopy]; + [metadata setTPPolicy:syncingPolicy]; - if(!persisted || localError) { - secnotice("octagon-sos", "Couldn't persist results: %@", localError); - self.error = localError; - self.nextState = OctagonStateError; - [self runBeforeGroupFinished:self.finishedOp]; - return; - } + return metadata; + } error:&localError]; - self.nextState = self.intendedState; + if(!persisted || localError) { + secnotice("octagon-sos", "Couldn't persist results: %@", localError); + self.error = localError; + self.nextState = OctagonStateError; + [self runBeforeGroupFinished:self.finishedOp]; + return; + } - // Tell CKKS about our shiny new records! - for (id key in self.deps.viewManager.views) { - CKKSKeychainView* view = self.deps.viewManager.views[key]; - secnotice("octagon-ckks", "Providing ck records (from sos upgrade) to %@", view); - [view receiveTLKUploadRecords: keyHierarchyRecords]; - } + self.nextState = self.intendedState; - [self runBeforeGroupFinished:self.finishedOp]; - }]; + // Tell CKKS about our shiny new records! + for (id key in self.deps.viewManager.views) { + CKKSKeychainView* view = self.deps.viewManager.views[key]; + secnotice("octagon-ckks", "Providing ck records (from sos upgrade) to %@", view); + [view receiveTLKUploadRecords: keyHierarchyRecords]; + } + + [self runBeforeGroupFinished:self.finishedOp]; + }]; } @end diff --git a/keychain/SecureObjectSync/SOSBackupInformation.h b/keychain/ot/OTSetCDPBitOperation.h similarity index 59% rename from keychain/SecureObjectSync/SOSBackupInformation.h rename to keychain/ot/OTSetCDPBitOperation.h index b11d57b6..03a53964 100644 --- a/keychain/SecureObjectSync/SOSBackupInformation.h +++ b/keychain/ot/OTSetCDPBitOperation.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Apple Inc. All Rights Reserved. + * Copyright (c) 2019 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -21,29 +21,23 @@ * @APPLE_LICENSE_HEADER_END@ */ -// -// SOSBackupInformation.h -// Security -// +#if OCTAGON -#ifndef SOSBackupInformation_h -#define SOSBackupInformation_h +#import +#import "keychain/ckks/CKKSGroupOperation.h" +#import "keychain/ot/OctagonStateMachineHelpers.h" +#import "keychain/ot/OTOperationDependencies.h" -#include -#import "keychain/SecureObjectSync/SOSAccountTransaction.h" +NS_ASSUME_NONNULL_BEGIN -enum { - noError = 0, - noTxnorAcct, - noAlloc, - noTrustedPubKey, - noBSKBs, -}; +@interface OTSetCDPBitOperation : CKKSGroupOperation -extern const CFStringRef kSOSBkpInfoStatus; -extern const CFStringRef kSOSBkpInfoBSKB; -extern const CFStringRef kSOSBkpInfoRKBG; +- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies + intendedState:(OctagonState*)intendedState + errorState:(OctagonState*)errorState; -CFDictionaryRef SOSBackupInformation(SOSAccountTransaction* txn, CFErrorRef *error); +@end -#endif /* SOSBackupInformation_h */ +NS_ASSUME_NONNULL_END + +#endif // OCTAGON diff --git a/keychain/ot/OTSetCDPBitOperation.m b/keychain/ot/OTSetCDPBitOperation.m new file mode 100644 index 00000000..9b1b3592 --- /dev/null +++ b/keychain/ot/OTSetCDPBitOperation.m @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#if OCTAGON + +#import "utilities/debugging.h" + +#import "keychain/ot/OTSetCDPBitOperation.h" +#import "keychain/ot/ObjCImprovements.h" + +@interface OTSetCDPBitOperation () +@property OTOperationDependencies* deps; + +@property NSOperation* finishedOp; +@end + +@implementation OTSetCDPBitOperation +@synthesize intendedState = _intendedState; +@synthesize nextState = _nextState; + +- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies + intendedState:(OctagonState*)intendedState + errorState:(OctagonState*)errorState +{ + if((self = [super init])) { + _deps = dependencies; + _intendedState = intendedState; + _nextState = errorState; + } + return self; +} + +- (void)groupStart +{ + NSError* localError = nil; + [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) { + metadata.cdpState = OTAccountMetadataClassC_CDPState_ENABLED; + return metadata; + } error:&localError]; + + if(localError) { + secnotice("octagon-cdp-status", "Unable to set CDP bit: %@", localError); + } else { + secnotice("octagon-cdp-status", "Successfully set CDP bit"); + self.nextState = self.intendedState; + } +} + +@end + +#endif // OCTAGON diff --git a/keychain/ot/OTStates.h b/keychain/ot/OTStates.h index ede2cb05..52cee05b 100644 --- a/keychain/ot/OTStates.h +++ b/keychain/ot/OTStates.h @@ -33,9 +33,11 @@ NS_ASSUME_NONNULL_BEGIN // No iCloud Account (the state machine won't help at all) // Untrusted (user interaction is required to resolve) // WaitForHSA2 (there's some primary icloud account, but it's not HSA2 (yet)) +// WaitForCDP (there's some HSA2 primary icloud account, but it's not CDP-enabled (yet) extern OctagonState* const OctagonStateNoAccount; extern OctagonState* const OctagonStateUntrusted; extern OctagonState* const OctagonStateWaitForHSA2; +extern OctagonState* const OctagonStateWaitForCDP; // Entering this state will mark down that the device is untrusted, then go to OctagonStateUntrusted extern OctagonState* const OctagonStateBecomeUntrusted; @@ -50,6 +52,9 @@ extern OctagonState* const OctagonStateReady; // This state runs any final preparation to enter the Ready state extern OctagonState* const OctagonStateBecomeReady; +// BecomeReady might go here, if it's not actually ready +extern OctagonState* const OctagonStateRefetchCKKSPolicy; + // Enter this state if you'd like the state machine to double-check everything extern OctagonState* const OctagonStateEnsureConsistency; extern OctagonState* const OctagonStateEnsureOctagonKeysAreConsistent; @@ -59,23 +64,25 @@ extern OctagonState* const OctagonStateEnsureUpdatePreapprovals; extern OctagonState* const OctagonStateInitializing; extern OctagonState* const OctagonStateWaitingForCloudKitAccount; extern OctagonState* const OctagonStateCloudKitNewlyAvailable; +extern OctagonState* const OctagonStateDetermineCDPState; extern OctagonState* const OctagonStateCheckTrustState; /*Piggybacking and ProximitySetup as Initiator, Octagon only*/ extern OctagonState* const OctagonStateInitiatorAwaitingVoucher; +extern OctagonState* const OctagonStateInitiatorSetCDPBit; extern OctagonState* const OctagonStateInitiatorUpdateDeviceList; extern OctagonState* const OctagonStateInitiatorJoin; extern OctagonState* const OctagonStateInitiatorJoinCKKSReset; extern OctagonState* const OctagonStateInitiatorJoinAfterCKKSReset; -extern OctagonState* const OctagonStateInitiatorVouchWithBottle; +extern OctagonState* const OctagonStateBottleJoinVouchWithBottle; extern OctagonState* const OctagonStateIdentityPrepared; // OctagonStateIdentityPrepared leads directly to extern OctagonState* const OctagonStateDeviceListUpdated; /* used for join with bottle */ -extern OctagonState* const OctagonStateInitiatorCreateIdentity; +extern OctagonState* const OctagonStateBottleJoinCreateIdentity; /* used for join with recovery key */ extern OctagonState* const OctagonStateCreateIdentityForRecoveryKey; @@ -87,6 +94,7 @@ extern OctagonState* const OctagonStateVouchWithRecoveryKey; extern OctagonState* const OctagonStateResetBecomeUntrusted; extern OctagonState* const OctagonStateResetAndEstablish; extern OctagonState* const OctagonStateResetAnyMissingTLKCKKSViews; +extern OctagonState* const OctagonStateEstablishEnableCDPBit; extern OctagonState* const OctagonStateReEnactDeviceList; extern OctagonState* const OctagonStateReEnactPrepare; extern OctagonState* const OctagonStateReEnactReadyToEstablish; @@ -96,6 +104,7 @@ extern OctagonState* const OctagonStateEstablishAfterCKKSReset; /* used for trust health checks */ extern OctagonState* const OctagonStateHSA2HealthCheck; +extern OctagonState* const OctagonStateCDPHealthCheck; extern OctagonState* const OctagonStateSecurityTrustCheck; extern OctagonState* const OctagonStateTPHTrustCheck; extern OctagonState* const OctagonStateCuttlefishTrustCheck; @@ -104,6 +113,9 @@ extern OctagonState* const OctagonStateHealthCheckReset; // End of account reset state flow +//Leave Clique +extern OctagonState* const OctagonStateHealthCheckLeaveClique; + // Part of the signout flow extern OctagonState* const OctagonStateNoAccountDoReset; // @@ -131,6 +143,9 @@ extern OctagonState* const OctagonStateAssistCKKSTLKUploadAfterCKKSReset; // Call out to otpaird (KCPairing via IDS), then proceed to BecomeUntrusted extern OctagonState* const OctagonStateStartCompanionPairing; +// Cuttlefish notification while waiting for CDP +extern OctagonState* const OctagonStateWaitForCDPUpdated; + // Untrusted cuttlefish notification. extern OctagonState* const OctagonStateUntrustedUpdated; @@ -163,6 +178,7 @@ extern OctagonFlag* const OctagonFlagCuttlefishNotification NS_SWIFT_NAME(Octago extern OctagonFlag* const OctagonFlagFetchAuthKitMachineIDList; extern OctagonFlag* const OctagonFlagAccountIsAvailable; +extern OctagonFlag* const OctagonFlagCDPEnabled; extern OctagonFlag* const OctagonFlagAttemptSOSUpgrade; extern OctagonFlag* const OctagonFlagUnlocked; diff --git a/keychain/ot/OTStates.m b/keychain/ot/OTStates.m index 5af0ffb4..5db76711 100644 --- a/keychain/ot/OTStates.m +++ b/keychain/ot/OTStates.m @@ -33,6 +33,7 @@ OctagonState* const OctagonStateNoAccount = (OctagonState*) @"no_account"; OctagonState* const OctagonStateWaitForHSA2 = (OctagonState*) @"wait_for_hsa2"; +OctagonState* const OctagonStateWaitForCDP = (OctagonState*) @"wait_for_cdp_enable"; OctagonState* const OctagonStateUntrusted = (OctagonState*) @"untrusted"; OctagonState* const OctagonStateBecomeUntrusted = (OctagonState*) @"become_untrusted"; @@ -47,11 +48,14 @@ OctagonState* const OctagonStateEnsureUpdatePreapprovals = (OctagonState*)@"ensu OctagonState* const OctagonStateInitializing = (OctagonState*) @"initializing"; OctagonState* const OctagonStateWaitingForCloudKitAccount = (OctagonState*) @"waiting_for_cloudkit_account"; OctagonState* const OctagonStateCloudKitNewlyAvailable = (OctagonState*) @"account_newly_available"; +OctagonState* const OctagonStateRefetchCKKSPolicy = (OctagonState*) @"ckks_fetch_policy"; +OctagonState* const OctagonStateDetermineCDPState = (OctagonState*) @"check_cdp_state"; OctagonState* const OctagonStateCheckTrustState = (OctagonState*) @"check_trust_state"; OctagonState* const OctagonStateUpdateSOSPreapprovals = (OctagonState*) @"update_sos_preapprovals"; /*Piggybacking and ProximitySetup as Initiator Octagon only*/ +OctagonState* const OctagonStateInitiatorSetCDPBit = (OctagonState*) @"initiator_set_cdp"; OctagonState* const OctagonStateInitiatorUpdateDeviceList = (OctagonState*) @"initiator_device_list_update"; OctagonState* const OctagonStateInitiatorAwaitingVoucher = (OctagonState*)@"await_voucher"; OctagonState* const OctagonStateInitiatorJoin = (OctagonState*)@"join"; @@ -59,8 +63,8 @@ OctagonState* const OctagonStateInitiatorJoinCKKSReset = (OctagonState*)@"join_c OctagonState* const OctagonStateInitiatorJoinAfterCKKSReset = (OctagonState*)@"join_after_ckks_reset"; /* used in restore (join with bottle)*/ -OctagonState* const OctagonStateInitiatorCreateIdentity = (OctagonState*)@"create_identity"; -OctagonState* const OctagonStateInitiatorVouchWithBottle = (OctagonState*)@"vouchWithBottle"; +OctagonState* const OctagonStateBottleJoinCreateIdentity = (OctagonState*)@"bottle_join_create_identity"; +OctagonState* const OctagonStateBottleJoinVouchWithBottle = (OctagonState*)@"bottle_join_vouch_with_bottle"; OctagonState* const OctagonStateCreateIdentityForRecoveryKey = (OctagonState*)@"vouchWithRecovery"; /* used in resotre (join with recovery key)*/ @@ -68,6 +72,8 @@ OctagonState* const OctagonStateVouchWithRecoveryKey = (OctagonState*)@"vouchWit OctagonState* const OctagonStateStartCompanionPairing = (OctagonState*)@"start_companion_pairing"; +OctagonState* const OctagonStateWaitForCDPUpdated = (OctagonState*)@"wait_for_cdp_update"; + // Untrusted cuttlefish notification. OctagonState* const OctagonStateUntrustedUpdated = (OctagonState*)@"untrusted_update"; @@ -87,6 +93,7 @@ OctagonState* const OctagonStateUnimplemented = (OctagonState*) @"unimplemented" OctagonState* const OctagonStateResetBecomeUntrusted = (OctagonState*) @"reset_become_untrusted"; OctagonState* const OctagonStateResetAndEstablish = (OctagonState*) @"reset_and_establish"; OctagonState* const OctagonStateResetAnyMissingTLKCKKSViews = (OctagonState*) @"reset_ckks_missing_views"; +OctagonState* const OctagonStateEstablishEnableCDPBit = (OctagonState*) @"reenact_cdp_bit"; OctagonState* const OctagonStateReEnactDeviceList = (OctagonState*) @"reenact_device_list"; OctagonState* const OctagonStateReEnactPrepare = (OctagonState*) @"reenact_prepare"; OctagonState* const OctagonStateReEnactReadyToEstablish = (OctagonState*) @"reenact_ready_to_establish"; @@ -95,6 +102,7 @@ OctagonState* const OctagonStateEstablishAfterCKKSReset = (OctagonState*) @"reen /* used for trust health checks */ OctagonState* const OctagonStateHSA2HealthCheck = (OctagonState*) @"health_hsa2_check"; +OctagonState* const OctagonStateCDPHealthCheck = (OctagonState*) @"health_cdp_check"; OctagonState* const OctagonStateTPHTrustCheck = (OctagonState*) @"tph_trust_check"; OctagonState* const OctagonStateCuttlefishTrustCheck = (OctagonState*) @"cuttlefish_trust_check"; OctagonState* const OctagonStatePostRepairCFU = (OctagonState*) @"post_repair_cfu"; @@ -109,6 +117,8 @@ OctagonState* const OctagonStateAssistCKKSTLKUpload = (OctagonState*) @"assist_c OctagonState* const OctagonStateAssistCKKSTLKUploadCKKSReset = (OctagonState*) @"assist_ckks_tlk_upload_ckks_reset"; OctagonState* const OctagonStateAssistCKKSTLKUploadAfterCKKSReset = (OctagonState*) @"assist_ckks_tlk_upload_after_ckks_reset"; +OctagonState* const OctagonStateHealthCheckLeaveClique = (OctagonState*) @"leave_clique"; + /* escrow */ OctagonState* const OctagonStateEscrowTriggerUpdate = (OctagonState*) @"escrow-trigger-update"; @@ -145,8 +155,8 @@ NSDictionary* OctagonStateMap(void) { OctagonStateReEnactPrepare: @14U, OctagonStateReEnactReadyToEstablish: @15U, OctagonStateNoAccountDoReset: @16U, - OctagonStateInitiatorVouchWithBottle: @17U, - OctagonStateInitiatorCreateIdentity: @18U, + OctagonStateBottleJoinVouchWithBottle: @17U, + OctagonStateBottleJoinCreateIdentity: @18U, OctagonStateCloudKitNewlyAvailable: @19U, OctagonStateCheckTrustState: @20U, OctagonStateBecomeUntrusted: @21U, @@ -181,6 +191,14 @@ NSDictionary* OctagonStateMap(void) { OctagonStateHealthCheckReset: @50U, OctagonStateAssistCKKSTLKUploadCKKSReset: @51U, OctagonStateAssistCKKSTLKUploadAfterCKKSReset: @52U, + OctagonStateWaitForCDP: @53U, + OctagonStateDetermineCDPState: @54U, + OctagonStateWaitForCDPUpdated: @55U, + OctagonStateEstablishEnableCDPBit: @56U, + OctagonStateInitiatorSetCDPBit: @57U, + OctagonStateCDPHealthCheck: @58U, + OctagonStateHealthCheckLeaveClique: @59U, + OctagonStateRefetchCKKSPolicy: @60U, }; }); return map; @@ -230,6 +248,7 @@ NSSet* OctagonHealthSourceStates(void) [sourceStates addObject:OctagonStateUntrusted]; [sourceStates addObject:OctagonStateWaitForHSA2]; [sourceStates addObject:OctagonStateWaitForUnlock]; + [sourceStates addObject:OctagonStateWaitForCDP]; s = sourceStates; }); @@ -242,6 +261,7 @@ OctagonFlag* const OctagonFlagEgoPeerPreapproved = (OctagonFlag*) @"preapproved" OctagonFlag* const OctagonFlagCKKSRequestsTLKUpload = (OctagonFlag*) @"tlk_upload_needed"; OctagonFlag* const OctagonFlagCuttlefishNotification = (OctagonFlag*) @"recd_push"; OctagonFlag* const OctagonFlagAccountIsAvailable = (OctagonFlag*)@"account_available"; +OctagonFlag* const OctagonFlagCDPEnabled = (OctagonFlag*) @"cdp_enabled"; OctagonFlag* const OctagonFlagAttemptSOSUpgrade = (OctagonFlag*)@"attempt_sos_upgrade"; OctagonFlag* const OctagonFlagFetchAuthKitMachineIDList = (OctagonFlag*)@"attempt_machine_id_list"; OctagonFlag* const OctagonFlagUnlocked = (OctagonFlag*)@"unlocked"; @@ -261,6 +281,7 @@ NSSet* AllOctagonFlags(void) [flags addObject:OctagonFlagCKKSRequestsTLKUpload]; [flags addObject:OctagonFlagCuttlefishNotification]; [flags addObject:OctagonFlagAccountIsAvailable]; + [flags addObject:OctagonFlagCDPEnabled]; [flags addObject:OctagonFlagAttemptSOSUpgrade]; [flags addObject:OctagonFlagFetchAuthKitMachineIDList]; [flags addObject:OctagonFlagUnlocked]; diff --git a/keychain/ot/OTUpdateTPHOperation.h b/keychain/ot/OTUpdateTPHOperation.h index 6dbc7022..18999f22 100644 --- a/keychain/ot/OTUpdateTPHOperation.h +++ b/keychain/ot/OTUpdateTPHOperation.h @@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies intendedState:(OctagonState*)intendedState + peerUnknownState:(OctagonState*)peerUnknownState errorState:(OctagonState*)errorState retryFlag:(OctagonFlag* _Nullable)retryFlag; @end diff --git a/keychain/ot/OTUpdateTPHOperation.m b/keychain/ot/OTUpdateTPHOperation.m index 5a24b829..c31589f0 100644 --- a/keychain/ot/OTUpdateTPHOperation.m +++ b/keychain/ot/OTUpdateTPHOperation.m @@ -18,6 +18,8 @@ @interface OTUpdateTPHOperation () @property OTOperationDependencies* deps; +@property OctagonState* peerUnknownState; + @property NSOperation* finishedOp; @property (nullable) OctagonFlag* retryFlag; @@ -29,6 +31,7 @@ - (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies intendedState:(OctagonState*)intendedState + peerUnknownState:(OctagonState*)peerUnknownState errorState:(OctagonState*)errorState retryFlag:(OctagonFlag* _Nullable)retryFlag { @@ -37,6 +40,7 @@ _intendedState = intendedState; _nextState = errorState; + _peerUnknownState = peerUnknownState; _retryFlag = retryFlag; } @@ -135,8 +139,8 @@ self.nextState = OctagonStateBecomeUntrusted; } else if(peerState.peerStatus & TPPeerStatusUnknown) { - secnotice("octagon", "Self peer (%@) is unknown; moving to untrusted", peerState.peerID); - self.nextState = OctagonStateBecomeUntrusted; + secnotice("octagon", "Self peer (%@) is unknown; moving to '%@''", peerState.peerID, self.peerUnknownState); + self.nextState = self.peerUnknownState; } else { self.nextState = self.intendedState; diff --git a/keychain/ot/OTUpdateTrustedDeviceListOperation.m b/keychain/ot/OTUpdateTrustedDeviceListOperation.m index 5f8a8763..0e6cb278 100644 --- a/keychain/ot/OTUpdateTrustedDeviceListOperation.m +++ b/keychain/ot/OTUpdateTrustedDeviceListOperation.m @@ -87,6 +87,12 @@ }]; [self dependOnBeforeGroupFinished:self.finishedOp]; + NSError* localError = nil; + BOOL isAccountDemo = [self.deps.authKitAdapter accountIsDemoAccount:&localError]; + if(localError) { + secerror("octagon-authkit: failed to fetch demo account flag: %@", localError); + } + [self.deps.authKitAdapter fetchCurrentDeviceList:^(NSSet * _Nullable machineIDs, NSError * _Nullable error) { STRONGIFY(self); if(!machineIDs || error) { @@ -103,17 +109,20 @@ if (self.logForUpgrade) { [[CKKSAnalytics logger] logSuccessForEventNamed:OctagonEventUpgradeFetchDeviceIDs]; } - [self afterAuthKitFetch:machineIDs]; + [self afterAuthKitFetch:machineIDs accountIsDemo:isAccountDemo]; } }]; } -- (void)afterAuthKitFetch:(NSSet*)allowedMachineIDs +- (void)afterAuthKitFetch:(NSSet*)allowedMachineIDs accountIsDemo:(BOOL)accountIsDemo { WEAKIFY(self); + BOOL honorIDMSListChanges = accountIsDemo ? NO : YES; + [self.deps.cuttlefishXPCWrapper setAllowedMachineIDsWithContainer:self.deps.containerName context:self.deps.contextID allowedMachineIDs:allowedMachineIDs + honorIDMSListChanges:honorIDMSListChanges reply:^(BOOL listDifferences, NSError * _Nullable error) { STRONGIFY(self); diff --git a/keychain/ot/OTUploadNewCKKSTLKsOperation.m b/keychain/ot/OTUploadNewCKKSTLKsOperation.m index f5430804..5c74b845 100644 --- a/keychain/ot/OTUploadNewCKKSTLKsOperation.m +++ b/keychain/ot/OTUploadNewCKKSTLKsOperation.m @@ -51,7 +51,8 @@ NSMutableSet* viewsToUpload = [NSMutableSet set]; // One (or more) of our sub-CKKSes believes it needs to upload new TLKs. - for(CKKSKeychainView* view in [self.deps.viewManager currentViews]) { + CKKSViewManager* viewManager = self.deps.viewManager; + for(CKKSKeychainView* view in viewManager.currentViews) { if([view.keyHierarchyState isEqualToString:SecCKKSZoneKeyStateWaitForTLKUpload] || [view.keyHierarchyState isEqualToString:SecCKKSZoneKeyStateWaitForTLKCreation]) { secnotice("octagon-ckks", "CKKS view %@ needs TLK uploads!", view); diff --git a/keychain/ot/OTVouchWithBottleOperation.m b/keychain/ot/OTVouchWithBottleOperation.m index 48a381bc..d47c452d 100644 --- a/keychain/ot/OTVouchWithBottleOperation.m +++ b/keychain/ot/OTVouchWithBottleOperation.m @@ -94,35 +94,20 @@ } } - WEAKIFY(self); - - // After a vouch, we also want to acquire all TLKs that the bottled peer might have had - OTFetchCKKSKeysOperation* fetchKeysOp = [[OTFetchCKKSKeysOperation alloc] initWithDependencies:self.deps]; - [self runBeforeGroupFinished:fetchKeysOp]; - - CKKSResultOperation* proceedWithKeys = [CKKSResultOperation named:@"bottle-tlks" - withBlock:^{ - STRONGIFY(self); - [self proceedWithKeys:fetchKeysOp.viewKeySets tlkShares:fetchKeysOp.tlkShares]; - }]; - - [proceedWithKeys addDependency:fetchKeysOp]; - [self runBeforeGroupFinished:proceedWithKeys]; -} - -- (void)proceedWithKeys:(NSArray*)viewKeySets tlkShares:(NSArray*)tlkShares -{ // Preflight the vouch: this will tell us the peerID of the recovering peer. // Then, filter the tlkShares array to include only tlks sent to that peer. WEAKIFY(self); [self.deps.cuttlefishXPCWrapper preflightVouchWithBottleWithContainer:self.deps.containerName context:self.deps.contextID bottleID:self.bottleID - reply:^(NSString * _Nullable peerID, NSError * _Nullable error) { + reply:^(NSString * _Nullable peerID, + NSSet* peerSyncingViews, + TPPolicy* peerSyncingPolicy, + NSError * _Nullable error) { STRONGIFY(self); [[CKKSAnalytics logger] logResultForEvent:OctagonEventPreflightVouchWithBottle hardFailure:true result:error]; - if(error){ + if(error || !peerID) { secerror("octagon: Error preflighting voucher using bottle: %@", error); self.error = error; [self runBeforeGroupFinished:self.finishedOp]; @@ -131,16 +116,52 @@ secnotice("octagon", "Bottle %@ is for peerID %@", self.bottleID, peerID); + // Tell CKKS to spin up the new views and policy + // But, do not persist this view set! We'll do that when we actually manager to join + [self.deps.viewManager setSyncingViews:peerSyncingViews sortingPolicy:peerSyncingPolicy]; + + [self proceedWithPeerID:peerID]; + }]; +} + +- (void)proceedWithPeerID:(NSString*)peerID +{ + WEAKIFY(self); + + // After a vouch, we also want to acquire all TLKs that the bottled peer might have had + OTFetchCKKSKeysOperation* fetchKeysOp = [[OTFetchCKKSKeysOperation alloc] initWithDependencies:self.deps]; + [self runBeforeGroupFinished:fetchKeysOp]; + + CKKSResultOperation* proceedWithKeys = [CKKSResultOperation named:@"bottle-tlks" + withBlock:^{ + STRONGIFY(self); + NSMutableArray* filteredTLKShares = [NSMutableArray array]; - for(CKKSTLKShare* share in tlkShares) { + for(CKKSTLKShare* share in fetchKeysOp.tlkShares) { // If we didn't get a peerID, just pass every tlkshare and hope for the best if(peerID == nil || [share.receiverPeerID isEqualToString:peerID]) { [filteredTLKShares addObject:share]; } } - [self proceedWithKeys:viewKeySets filteredTLKShares:filteredTLKShares]; + [self proceedWithKeys:fetchKeysOp.viewKeySets filteredTLKShares:filteredTLKShares]; }]; + + [proceedWithKeys addDependency:fetchKeysOp]; + [self runBeforeGroupFinished:proceedWithKeys]; +} + + +- (void)noteMetric:(NSString*)metric count:(int64_t)count +{ + NSString* metricName = [NSString stringWithFormat:@"%@%lld", metric, count]; + + [[CKKSAnalytics logger] logResultForEvent:metricName + hardFailure:NO + result:nil]; + + [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:metricName]; + [[CKKSAnalytics logger] setNumberProperty:[[NSNumber alloc]initWithLong:count] forKey:metric]; } - (void)proceedWithKeys:(NSArray*)viewKeySets filteredTLKShares:(NSArray*)tlkShares @@ -153,7 +174,11 @@ entropy:self.entropy bottleSalt:self.bottleSalt tlkShares:tlkShares - reply:^(NSData * _Nullable voucher, NSData * _Nullable voucherSig, NSError * _Nullable error) { + reply:^(NSData * _Nullable voucher, + NSData * _Nullable voucherSig, + int64_t uniqueTLKsRecovered, + int64_t totalTLKSharesRecovered, + NSError * _Nullable error) { STRONGIFY(self); [[CKKSAnalytics logger] logResultForEvent:OctagonEventVoucherWithBottle hardFailure:true result:error]; @@ -164,6 +189,24 @@ return; } + //collect TLK count metrics + [self noteMetric:OctagonAnalyticsBottledUniqueTLKsRecovered count:uniqueTLKsRecovered]; + [self noteMetric:OctagonAnalyticsBottledTotalTLKSharesRecovered count:totalTLKSharesRecovered]; + [self noteMetric:OctagonAnalyticsBottledTotalTLKShares count:tlkShares.count]; + + NSMutableSet* uniqueTLKsWithShares = [NSMutableSet set]; + for (CKKSTLKShare* share in tlkShares) { + [uniqueTLKsWithShares addObject:share.tlkUUID]; + } + + [self noteMetric:OctagonAnalyticsBottledUniqueTLKsWithSharesCount count:uniqueTLKsWithShares.count]; + + NSMutableDictionary *views = [NSMutableDictionary dictionary]; + for (CKKSTLKShare *share in tlkShares) { + views[share.zoneID] = share.zoneID; + } + [self noteMetric:OctagonAnalyticsBottledTLKUniqueViewCount count:views.count]; + secnotice("octagon", "Received bottle voucher"); self.voucher = voucher; diff --git a/keychain/ot/OTVouchWithRecoveryKeyOperation.h b/keychain/ot/OTVouchWithRecoveryKeyOperation.h index 12712c0e..1203d715 100644 --- a/keychain/ot/OTVouchWithRecoveryKeyOperation.h +++ b/keychain/ot/OTVouchWithRecoveryKeyOperation.h @@ -42,8 +42,6 @@ NS_ASSUME_NONNULL_BEGIN recoveryKey:(NSString*)recoveryKey; @property (weak) OTCuttlefishContext* cuttlefishContext; -@property (nonatomic) NSString* salt; -@property (nonatomic) NSString* recoveryKey; @property (nonatomic) NSData* voucher; @property (nonatomic) NSData* voucherSig; diff --git a/keychain/ot/OTVouchWithRecoveryKeyOperation.m b/keychain/ot/OTVouchWithRecoveryKeyOperation.m index 0bb767fe..24809b5c 100644 --- a/keychain/ot/OTVouchWithRecoveryKeyOperation.m +++ b/keychain/ot/OTVouchWithRecoveryKeyOperation.m @@ -36,6 +36,9 @@ @interface OTVouchWithRecoveryKeyOperation () @property OTOperationDependencies* deps; +@property NSString* salt; +@property NSString* recoveryKey; + @property NSOperation* finishOp; @end @@ -64,31 +67,56 @@ self.finishOp = [[NSOperation alloc] init]; [self dependOnBeforeGroupFinished:self.finishOp]; - NSString* salt = nil; + NSString *altDSID = [self.deps.authKitAdapter primaryiCloudAccountAltDSID:nil]; + if(altDSID){ + secnotice("octagon", "using auth kit adapter, altdsid is: %@", altDSID); + self.salt = altDSID; + } + else { + NSError* accountError = nil; + OTAccountMetadataClassC* account = [self.deps.stateHolder loadOrCreateAccountMetadata:&accountError]; - if(self.salt != nil) { - secnotice("octagon", "using passed in altdsid, altdsid is: %@", self.salt); - salt = self.salt; - } else{ - NSString *altDSID = [self.deps.authKitAdapter primaryiCloudAccountAltDSID:nil]; - if(altDSID){ - secnotice("octagon", "using auth kit adapter, altdsid is: %@", altDSID); - salt = altDSID; + if(account && !accountError) { + secnotice("octagon", "retrieved account, altdsid is: %@", account.altDSID); + self.salt = account.altDSID; } - else { - NSError* accountError = nil; - OTAccountMetadataClassC* account = [self.deps.stateHolder loadOrCreateAccountMetadata:&accountError]; - - if(account && !accountError) { - secnotice("octagon", "retrieved account, altdsid is: %@", account.altDSID); - salt = account.altDSID; - } - if(accountError || !account){ - secerror("failed to rerieve account object: %@", accountError); - } + if(accountError || !account){ + secerror("failed to rerieve account object: %@", accountError); } } + // First, let's preflight the vouch (to receive a policy and view set to use for TLK fetching + WEAKIFY(self); + [self.deps.cuttlefishXPCWrapper preflightVouchWithRecoveryKeyWithContainer:self.deps.containerName + context:self.deps.contextID + recoveryKey:self.recoveryKey + salt:self.salt + reply:^(NSString * _Nullable recoveryKeyID, + NSSet* peerSyncingViews, + TPPolicy* peerSyncingPolicy, + NSError * _Nullable error) { + STRONGIFY(self); + [[CKKSAnalytics logger] logResultForEvent:OctagonEventPreflightVouchWithRecoveryKey hardFailure:true result:error]; + + if(error || !recoveryKeyID) { + secerror("octagon: Error preflighting voucher using recovery key: %@", error); + self.error = error; + [self runBeforeGroupFinished:self.finishOp]; + return; + } + + secnotice("octagon", "Recovery key ID %@ looks good to go", recoveryKeyID); + + // Tell CKKS to spin up the new views and policy + // But, do not persist this view set! We'll do that when we actually manage to join + [self.deps.viewManager setSyncingViews:peerSyncingViews sortingPolicy:peerSyncingPolicy]; + + [self proceedWithRecoveryKeyID:recoveryKeyID]; + }]; +} + +- (void)proceedWithRecoveryKeyID:(NSString*)recoveryKeyID +{ WEAKIFY(self); // After a vouch, we also want to acquire all TLKs that the bottled peer might have had @@ -97,9 +125,18 @@ CKKSResultOperation* proceedWithKeys = [CKKSResultOperation named:@"recovery-tlks" withBlock:^{ - STRONGIFY(self); - [self proceedWithKeys:fetchKeysOp.viewKeySets tlkShares:fetchKeysOp.tlkShares salt:salt]; - }]; + STRONGIFY(self); + + NSMutableArray* filteredTLKShares = [NSMutableArray array]; + for(CKKSTLKShare* share in fetchKeysOp.tlkShares) { + // If we didn't get a recoveryKeyID, just pass every tlkshare and hope for the best + if(recoveryKeyID == nil || [share.receiverPeerID isEqualToString:recoveryKeyID]) { + [filteredTLKShares addObject:share]; + } + } + + [self proceedWithKeys:fetchKeysOp.viewKeySets tlkShares:filteredTLKShares salt:self.salt]; + }]; [proceedWithKeys addDependency:fetchKeysOp]; [self runBeforeGroupFinished:proceedWithKeys]; diff --git a/keychain/ot/OctagonStateMachine.m b/keychain/ot/OctagonStateMachine.m index dcd693ad..3e4f4efe 100644 --- a/keychain/ot/OctagonStateMachine.m +++ b/keychain/ot/OctagonStateMachine.m @@ -303,8 +303,6 @@ format, // Overwrite any existing pending flag! self.pendingFlags[pendingFlag.flag] = pendingFlag; - self.currentFlags.flagConditions[pendingFlag.flag] = [[CKKSCondition alloc]init]; - // Do we need to recheck any conditions? Anything which is currently the state of the world needs checking OctagonPendingConditions recheck = pendingFlag.conditions & self.currentConditions; if(recheck != 0x0) { diff --git a/keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h b/keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h index 4278b499..b60e47ea 100644 --- a/keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h +++ b/keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h @@ -15,6 +15,12 @@ NS_ASSUME_NONNULL_BEGIN + (OTAccountMetadataClassC* _Nullable)loadFromKeychainForContainer:(NSString*)containerName contextID:(NSString*)contextID error:(NSError**)error; @end +@class TPPolicy; +@interface OTAccountMetadataClassC (NSSecureCodingSupport) +- (void)setTPPolicy:(TPPolicy* _Nullable)policy; +- (TPPolicy* _Nullable)getTPPolicy; +@end + NS_ASSUME_NONNULL_END #endif // OCTAGON diff --git a/keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.m b/keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.m index 68f33b21..ced8b4d2 100644 --- a/keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.m +++ b/keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.m @@ -12,6 +12,7 @@ #import "keychain/ot/OTDefines.h" #import "keychain/ot/OTConstants.h" +#import @implementation OTAccountMetadataClassC (KeychainSupport) @@ -158,6 +159,28 @@ return state; } +#pragma mark - Field Coding support + +- (void)setTPPolicy:(TPPolicy*)policy +{ + if(policy) { + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES]; + [policy encodeWithCoder:archiver]; + self.syncingPolicy = archiver.encodedData; + } else { + self.syncingPolicy = nil; + } +} + +- (TPPolicy* _Nullable)getTPPolicy +{ + NSKeyedUnarchiver *coder = [[NSKeyedUnarchiver alloc] initForReadingFromData:self.syncingPolicy error:nil]; + TPPolicy* policy = [[TPPolicy alloc] initWithCoder:coder]; + [coder finishDecoding]; + + return policy; +} + @end #endif // OCTAGON diff --git a/keychain/ot/categories/OctagonEscrowRecoverer.h b/keychain/ot/categories/OctagonEscrowRecoverer.h index 3943683f..204e720c 100644 --- a/keychain/ot/categories/OctagonEscrowRecoverer.h +++ b/keychain/ot/categories/OctagonEscrowRecoverer.h @@ -8,6 +8,7 @@ @protocol OctagonEscrowRecovererPrococol - (NSError*)recoverWithInfo:(NSDictionary*)info results:(NSDictionary**)results; +- (NSError *)disableWithInfo:(NSDictionary *)info; @end @interface SecureBackup (OctagonProtocolConformance) diff --git a/keychain/ot/proto/OTAccountMetadataClassC.proto b/keychain/ot/proto/OTAccountMetadataClassC.proto index a3f0379f..d706b6d9 100644 --- a/keychain/ot/proto/OTAccountMetadataClassC.proto +++ b/keychain/ot/proto/OTAccountMetadataClassC.proto @@ -13,6 +13,9 @@ message AccountMetadataClassC { // If there is an SA account, we will bt in NO_ACCOUNT and have an altDSID // If there is an HSA2 account, we will be in ACCOUNT_AVAILABLE and have an altDSID + // Once you're in an HSA2 account, CDP might be enabled or disabled. If it's not enabled, + // then Octagon shouldn't be active. + enum AccountState { UNKNOWN = 0; NO_ACCOUNT = 1; @@ -32,6 +35,12 @@ message AccountMetadataClassC { ATTEMPTED = 2; } + enum CDPState { + UNKNOWN = 0; + DISABLED = 1; + ENABLED = 2; + } + optional string peerID = 1; optional AccountState icloudAccountState = 2; optional int64 epoch = 3; @@ -43,4 +52,10 @@ message AccountMetadataClassC { optional uint64 lastHealthCheckup = 6; optional AttemptedAJoinState attemptedJoin = 7; + + optional CDPState cdpState = 8; + + // These store the current policy and view list, so that we don't need to re-ask TPH every time + optional bytes syncingPolicy = 9; + repeated string syncingView = 10; } diff --git a/keychain/ot/proto/OTPairingMessage.proto b/keychain/ot/proto/OTPairingMessage.proto index c6b68a56..edc46473 100644 --- a/keychain/ot/proto/OTPairingMessage.proto +++ b/keychain/ot/proto/OTPairingMessage.proto @@ -28,14 +28,8 @@ option objc_class_visibility = "hidden"; package OT; -message SOSMessage { - optional bytes credential = 1; - optional bytes peerInfo = 2; - optional bytes circleBlob = 3; - optional bytes initialSyncItems = 4; -} -message SponsorToApplicantRound1M2{ +message SponsorToApplicantRound1M2 { optional uint64 epoch = 1; } @@ -50,12 +44,14 @@ message ApplicantToSponsorRound2M1 { message SponsorToApplicantRound2M2{ optional bytes voucher = 1; optional bytes voucherSignature = 2; - repeated bytes preapprovedKeys = 3; + // Claimed for a field, but never used + //reserved 3; } message PairingMessage { - optional SponsorToApplicantRound1M2 epoch= 1; + optional SponsorToApplicantRound1M2 epoch = 1; optional ApplicantToSponsorRound2M1 prepare = 2; optional SponsorToApplicantRound2M2 voucher = 3; - optional SOSMessage sosPairingMessage = 4; + // reserved is not a keyword (it should be) + // reserved 4; } diff --git a/keychain/ot/proto/generated_source/OTAccountMetadataClassC.h b/keychain/ot/proto/generated_source/OTAccountMetadataClassC.h index aeef0ac0..295d83b6 100644 --- a/keychain/ot/proto/generated_source/OTAccountMetadataClassC.h +++ b/keychain/ot/proto/generated_source/OTAccountMetadataClassC.h @@ -10,6 +10,8 @@ * If there is no account, we will be in NO_ACCOUNT and have no altDSID * If there is an SA account, we will bt in NO_ACCOUNT and have an altDSID * If there is an HSA2 account, we will be in ACCOUNT_AVAILABLE and have an altDSID + * Once you're in an HSA2 account, CDP might be enabled or disabled. If it's not enabled, + * then Octagon shouldn't be active. */ typedef NS_ENUM(int32_t, OTAccountMetadataClassC_AccountState) { OTAccountMetadataClassC_AccountState_UNKNOWN = 0, @@ -92,6 +94,32 @@ NS_INLINE OTAccountMetadataClassC_AttemptedAJoinState StringAsOTAccountMetadataC return OTAccountMetadataClassC_AttemptedAJoinState_UNKNOWN; } #endif /* __OBJC__ */ +typedef NS_ENUM(int32_t, OTAccountMetadataClassC_CDPState) { + OTAccountMetadataClassC_CDPState_UNKNOWN = 0, + OTAccountMetadataClassC_CDPState_DISABLED = 1, + OTAccountMetadataClassC_CDPState_ENABLED = 2, +}; +#ifdef __OBJC__ +NS_INLINE NSString *OTAccountMetadataClassC_CDPStateAsString(OTAccountMetadataClassC_CDPState value) +{ + switch (value) + { + case OTAccountMetadataClassC_CDPState_UNKNOWN: return @"UNKNOWN"; + case OTAccountMetadataClassC_CDPState_DISABLED: return @"DISABLED"; + case OTAccountMetadataClassC_CDPState_ENABLED: return @"ENABLED"; + default: return [NSString stringWithFormat:@"(unknown: %i)", value]; + } +} +#endif /* __OBJC__ */ +#ifdef __OBJC__ +NS_INLINE OTAccountMetadataClassC_CDPState StringAsOTAccountMetadataClassC_CDPState(NSString *value) +{ + if ([value isEqualToString:@"UNKNOWN"]) return OTAccountMetadataClassC_CDPState_UNKNOWN; + if ([value isEqualToString:@"DISABLED"]) return OTAccountMetadataClassC_CDPState_DISABLED; + if ([value isEqualToString:@"ENABLED"]) return OTAccountMetadataClassC_CDPState_ENABLED; + return OTAccountMetadataClassC_CDPState_UNKNOWN; +} +#endif /* __OBJC__ */ #ifdef __cplusplus #define OTACCOUNTMETADATACLASSC_FUNCTION extern "C" __attribute__((visibility("hidden"))) @@ -106,13 +134,17 @@ __attribute__((visibility("hidden"))) uint64_t _lastHealthCheckup; NSString *_altDSID; OTAccountMetadataClassC_AttemptedAJoinState _attemptedJoin; + OTAccountMetadataClassC_CDPState _cdpState; OTAccountMetadataClassC_AccountState _icloudAccountState; NSString *_peerID; + NSData *_syncingPolicy; + NSMutableArray *_syncingViews; OTAccountMetadataClassC_TrustState _trustState; struct { int epoch:1; int lastHealthCheckup:1; int attemptedJoin:1; + int cdpState:1; int icloudAccountState:1; int trustState:1; } _has; @@ -147,6 +179,22 @@ __attribute__((visibility("hidden"))) - (NSString *)attemptedJoinAsString:(OTAccountMetadataClassC_AttemptedAJoinState)value; - (OTAccountMetadataClassC_AttemptedAJoinState)StringAsAttemptedJoin:(NSString *)str; +@property (nonatomic) BOOL hasCdpState; +@property (nonatomic) OTAccountMetadataClassC_CDPState cdpState; +- (NSString *)cdpStateAsString:(OTAccountMetadataClassC_CDPState)value; +- (OTAccountMetadataClassC_CDPState)StringAsCdpState:(NSString *)str; + +@property (nonatomic, readonly) BOOL hasSyncingPolicy; +/** These store the current policy and view list, so that we don't need to re-ask TPH every time */ +@property (nonatomic, retain) NSData *syncingPolicy; + +@property (nonatomic, retain) NSMutableArray *syncingViews; +- (void)clearSyncingViews; +- (void)addSyncingView:(NSString *)i; +- (NSUInteger)syncingViewsCount; +- (NSString *)syncingViewAtIndex:(NSUInteger)idx; ++ (Class)syncingViewType; + // Performs a shallow copy into other - (void)copyTo:(OTAccountMetadataClassC *)other; diff --git a/keychain/ot/proto/generated_source/OTAccountMetadataClassC.m b/keychain/ot/proto/generated_source/OTAccountMetadataClassC.m index c7e41deb..40c4de46 100644 --- a/keychain/ot/proto/generated_source/OTAccountMetadataClassC.m +++ b/keychain/ot/proto/generated_source/OTAccountMetadataClassC.m @@ -34,7 +34,7 @@ } - (BOOL)hasIcloudAccountState { - return _has.icloudAccountState; + return _has.icloudAccountState != 0; } - (NSString *)icloudAccountStateAsString:(OTAccountMetadataClassC_AccountState)value { @@ -56,7 +56,7 @@ } - (BOOL)hasEpoch { - return _has.epoch; + return _has.epoch != 0; } - (BOOL)hasAltDSID { @@ -79,7 +79,7 @@ } - (BOOL)hasTrustState { - return _has.trustState; + return _has.trustState != 0; } - (NSString *)trustStateAsString:(OTAccountMetadataClassC_TrustState)value { @@ -101,7 +101,7 @@ } - (BOOL)hasLastHealthCheckup { - return _has.lastHealthCheckup; + return _has.lastHealthCheckup != 0; } @synthesize attemptedJoin = _attemptedJoin; - (OTAccountMetadataClassC_AttemptedAJoinState)attemptedJoin @@ -119,7 +119,7 @@ } - (BOOL)hasAttemptedJoin { - return _has.attemptedJoin; + return _has.attemptedJoin != 0; } - (NSString *)attemptedJoinAsString:(OTAccountMetadataClassC_AttemptedAJoinState)value { @@ -129,6 +129,62 @@ { return StringAsOTAccountMetadataClassC_AttemptedAJoinState(str); } +@synthesize cdpState = _cdpState; +- (OTAccountMetadataClassC_CDPState)cdpState +{ + return _has.cdpState ? _cdpState : OTAccountMetadataClassC_CDPState_UNKNOWN; +} +- (void)setCdpState:(OTAccountMetadataClassC_CDPState)v +{ + _has.cdpState = YES; + _cdpState = v; +} +- (void)setHasCdpState:(BOOL)f +{ + _has.cdpState = f; +} +- (BOOL)hasCdpState +{ + return _has.cdpState != 0; +} +- (NSString *)cdpStateAsString:(OTAccountMetadataClassC_CDPState)value +{ + return OTAccountMetadataClassC_CDPStateAsString(value); +} +- (OTAccountMetadataClassC_CDPState)StringAsCdpState:(NSString *)str +{ + return StringAsOTAccountMetadataClassC_CDPState(str); +} +- (BOOL)hasSyncingPolicy +{ + return _syncingPolicy != nil; +} +@synthesize syncingPolicy = _syncingPolicy; +@synthesize syncingViews = _syncingViews; +- (void)clearSyncingViews +{ + [_syncingViews removeAllObjects]; +} +- (void)addSyncingView:(NSString *)i +{ + if (!_syncingViews) + { + _syncingViews = [[NSMutableArray alloc] init]; + } + [_syncingViews addObject:i]; +} +- (NSUInteger)syncingViewsCount +{ + return [_syncingViews count]; +} +- (NSString *)syncingViewAtIndex:(NSUInteger)idx +{ + return [_syncingViews objectAtIndex:idx]; +} ++ (Class)syncingViewType +{ + return [NSString class]; +} - (NSString *)description { @@ -166,6 +222,18 @@ { [dict setObject:OTAccountMetadataClassC_AttemptedAJoinStateAsString(self->_attemptedJoin) forKey:@"attemptedJoin"]; } + if (self->_has.cdpState) + { + [dict setObject:OTAccountMetadataClassC_CDPStateAsString(self->_cdpState) forKey:@"cdpState"]; + } + if (self->_syncingPolicy) + { + [dict setObject:self->_syncingPolicy forKey:@"syncingPolicy"]; + } + if (self->_syncingViews) + { + [dict setObject:self->_syncingViews forKey:@"syncingView"]; + } return dict; } @@ -227,6 +295,27 @@ BOOL OTAccountMetadataClassCReadFrom(__unsafe_unretained OTAccountMetadataClassC self->_attemptedJoin = PBReaderReadInt32(reader); } break; + case 8 /* cdpState */: + { + self->_has.cdpState = YES; + self->_cdpState = PBReaderReadInt32(reader); + } + break; + case 9 /* syncingPolicy */: + { + NSData *new_syncingPolicy = PBReaderReadData(reader); + self->_syncingPolicy = new_syncingPolicy; + } + break; + case 10 /* syncingViews */: + { + NSString *new_syncingViews = PBReaderReadString(reader); + if (new_syncingViews) + { + [self addSyncingView:new_syncingViews]; + } + } + break; default: if (!PBReaderSkipValueWithTag(reader, tag, aType)) return NO; @@ -291,6 +380,27 @@ BOOL OTAccountMetadataClassCReadFrom(__unsafe_unretained OTAccountMetadataClassC PBDataWriterWriteInt32Field(writer, self->_attemptedJoin, 7); } } + /* cdpState */ + { + if (self->_has.cdpState) + { + PBDataWriterWriteInt32Field(writer, self->_cdpState, 8); + } + } + /* syncingPolicy */ + { + if (self->_syncingPolicy) + { + PBDataWriterWriteDataField(writer, self->_syncingPolicy, 9); + } + } + /* syncingViews */ + { + for (NSString *s_syncingViews in self->_syncingViews) + { + PBDataWriterWriteStringField(writer, s_syncingViews, 10); + } + } } - (void)copyTo:(OTAccountMetadataClassC *)other @@ -328,6 +438,24 @@ BOOL OTAccountMetadataClassCReadFrom(__unsafe_unretained OTAccountMetadataClassC other->_attemptedJoin = _attemptedJoin; other->_has.attemptedJoin = YES; } + if (self->_has.cdpState) + { + other->_cdpState = _cdpState; + other->_has.cdpState = YES; + } + if (_syncingPolicy) + { + other.syncingPolicy = _syncingPolicy; + } + if ([self syncingViewsCount]) + { + [other clearSyncingViews]; + NSUInteger syncingViewsCnt = [self syncingViewsCount]; + for (NSUInteger i = 0; i < syncingViewsCnt; i++) + { + [other addSyncingView:[self syncingViewAtIndex:i]]; + } + } } - (id)copyWithZone:(NSZone *)zone @@ -360,6 +488,17 @@ BOOL OTAccountMetadataClassCReadFrom(__unsafe_unretained OTAccountMetadataClassC copy->_attemptedJoin = _attemptedJoin; copy->_has.attemptedJoin = YES; } + if (self->_has.cdpState) + { + copy->_cdpState = _cdpState; + copy->_has.cdpState = YES; + } + copy->_syncingPolicy = [_syncingPolicy copyWithZone:zone]; + for (NSString *v in _syncingViews) + { + NSString *vCopy = [v copyWithZone:zone]; + [copy addSyncingView:vCopy]; + } return copy; } @@ -381,6 +520,12 @@ BOOL OTAccountMetadataClassCReadFrom(__unsafe_unretained OTAccountMetadataClassC ((self->_has.lastHealthCheckup && other->_has.lastHealthCheckup && self->_lastHealthCheckup == other->_lastHealthCheckup) || (!self->_has.lastHealthCheckup && !other->_has.lastHealthCheckup)) && ((self->_has.attemptedJoin && other->_has.attemptedJoin && self->_attemptedJoin == other->_attemptedJoin) || (!self->_has.attemptedJoin && !other->_has.attemptedJoin)) + && + ((self->_has.cdpState && other->_has.cdpState && self->_cdpState == other->_cdpState) || (!self->_has.cdpState && !other->_has.cdpState)) + && + ((!self->_syncingPolicy && !other->_syncingPolicy) || [self->_syncingPolicy isEqual:other->_syncingPolicy]) + && + ((!self->_syncingViews && !other->_syncingViews) || [self->_syncingViews isEqual:other->_syncingViews]) ; } @@ -401,6 +546,12 @@ BOOL OTAccountMetadataClassCReadFrom(__unsafe_unretained OTAccountMetadataClassC (self->_has.lastHealthCheckup ? PBHashInt((NSUInteger)self->_lastHealthCheckup) : 0) ^ (self->_has.attemptedJoin ? PBHashInt((NSUInteger)self->_attemptedJoin) : 0) + ^ + (self->_has.cdpState ? PBHashInt((NSUInteger)self->_cdpState) : 0) + ^ + [self->_syncingPolicy hash] + ^ + [self->_syncingViews hash] ; } @@ -439,6 +590,19 @@ BOOL OTAccountMetadataClassCReadFrom(__unsafe_unretained OTAccountMetadataClassC self->_attemptedJoin = other->_attemptedJoin; self->_has.attemptedJoin = YES; } + if (other->_has.cdpState) + { + self->_cdpState = other->_cdpState; + self->_has.cdpState = YES; + } + if (other->_syncingPolicy) + { + [self setSyncingPolicy:other->_syncingPolicy]; + } + for (NSString *iter_syncingViews in other->_syncingViews) + { + [self addSyncingView:iter_syncingViews]; + } } @end diff --git a/keychain/ot/proto/generated_source/OTPairingMessage.h b/keychain/ot/proto/generated_source/OTPairingMessage.h index f2fad2f1..e8c27edd 100644 --- a/keychain/ot/proto/generated_source/OTPairingMessage.h +++ b/keychain/ot/proto/generated_source/OTPairingMessage.h @@ -8,7 +8,6 @@ @class OTSponsorToApplicantRound1M2; @class OTApplicantToSponsorRound2M1; @class OTSponsorToApplicantRound2M2; -@class OTSOSMessage; #ifdef __cplusplus #define OTPAIRINGMESSAGE_FUNCTION extern "C" __attribute__((visibility("hidden"))) @@ -16,12 +15,15 @@ #define OTPAIRINGMESSAGE_FUNCTION extern __attribute__((visibility("hidden"))) #endif +/** + * Claimed for a field, but never used + * reserved 3; + */ __attribute__((visibility("hidden"))) @interface OTPairingMessage : PBCodable { OTSponsorToApplicantRound1M2 *_epoch; OTApplicantToSponsorRound2M1 *_prepare; - OTSOSMessage *_sosPairingMessage; OTSponsorToApplicantRound2M2 *_voucher; } @@ -35,9 +37,6 @@ __attribute__((visibility("hidden"))) @property (nonatomic, readonly) BOOL hasVoucher; @property (nonatomic, retain) OTSponsorToApplicantRound2M2 *voucher; -@property (nonatomic, readonly) BOOL hasSosPairingMessage; -@property (nonatomic, retain) OTSOSMessage *sosPairingMessage; - // Performs a shallow copy into other - (void)copyTo:(OTPairingMessage *)other; diff --git a/keychain/ot/proto/generated_source/OTPairingMessage.m b/keychain/ot/proto/generated_source/OTPairingMessage.m index 3416a4f9..a7c8f89b 100644 --- a/keychain/ot/proto/generated_source/OTPairingMessage.m +++ b/keychain/ot/proto/generated_source/OTPairingMessage.m @@ -8,7 +8,6 @@ #import #import "OTApplicantToSponsorRound2M1.h" -#import "OTSOSMessage.h" #import "OTSponsorToApplicantRound1M2.h" #import "OTSponsorToApplicantRound2M2.h" @@ -33,11 +32,6 @@ return _voucher != nil; } @synthesize voucher = _voucher; -- (BOOL)hasSosPairingMessage -{ - return _sosPairingMessage != nil; -} -@synthesize sosPairingMessage = _sosPairingMessage; - (NSString *)description { @@ -59,10 +53,6 @@ { [dict setObject:[_voucher dictionaryRepresentation] forKey:@"voucher"]; } - if (self->_sosPairingMessage) - { - [dict setObject:[_sosPairingMessage dictionaryRepresentation] forKey:@"sosPairingMessage"]; - } return dict; } @@ -136,24 +126,6 @@ BOOL OTPairingMessageReadFrom(__unsafe_unretained OTPairingMessage *self, __unsa PBReaderRecallMark(reader, &mark_voucher); } break; - case 4 /* sosPairingMessage */: - { - OTSOSMessage *new_sosPairingMessage = [[OTSOSMessage alloc] init]; - self->_sosPairingMessage = new_sosPairingMessage; - PBDataReaderMark mark_sosPairingMessage; - BOOL markError = !PBReaderPlaceMark(reader, &mark_sosPairingMessage); - if (markError) - { - return NO; - } - BOOL inError = !OTSOSMessageReadFrom(new_sosPairingMessage, reader); - if (inError) - { - return NO; - } - PBReaderRecallMark(reader, &mark_sosPairingMessage); - } - break; default: if (!PBReaderSkipValueWithTag(reader, tag, aType)) return NO; @@ -190,13 +162,6 @@ BOOL OTPairingMessageReadFrom(__unsafe_unretained OTPairingMessage *self, __unsa PBDataWriterWriteSubmessage(writer, self->_voucher, 3); } } - /* sosPairingMessage */ - { - if (self->_sosPairingMessage != nil) - { - PBDataWriterWriteSubmessage(writer, self->_sosPairingMessage, 4); - } - } } - (void)copyTo:(OTPairingMessage *)other @@ -213,10 +178,6 @@ BOOL OTPairingMessageReadFrom(__unsafe_unretained OTPairingMessage *self, __unsa { other.voucher = _voucher; } - if (_sosPairingMessage) - { - other.sosPairingMessage = _sosPairingMessage; - } } - (id)copyWithZone:(NSZone *)zone @@ -225,7 +186,6 @@ BOOL OTPairingMessageReadFrom(__unsafe_unretained OTPairingMessage *self, __unsa copy->_epoch = [_epoch copyWithZone:zone]; copy->_prepare = [_prepare copyWithZone:zone]; copy->_voucher = [_voucher copyWithZone:zone]; - copy->_sosPairingMessage = [_sosPairingMessage copyWithZone:zone]; return copy; } @@ -239,8 +199,6 @@ BOOL OTPairingMessageReadFrom(__unsafe_unretained OTPairingMessage *self, __unsa ((!self->_prepare && !other->_prepare) || [self->_prepare isEqual:other->_prepare]) && ((!self->_voucher && !other->_voucher) || [self->_voucher isEqual:other->_voucher]) - && - ((!self->_sosPairingMessage && !other->_sosPairingMessage) || [self->_sosPairingMessage isEqual:other->_sosPairingMessage]) ; } @@ -253,8 +211,6 @@ BOOL OTPairingMessageReadFrom(__unsafe_unretained OTPairingMessage *self, __unsa [self->_prepare hash] ^ [self->_voucher hash] - ^ - [self->_sosPairingMessage hash] ; } @@ -284,14 +240,6 @@ BOOL OTPairingMessageReadFrom(__unsafe_unretained OTPairingMessage *self, __unsa { [self setVoucher:other->_voucher]; } - if (self->_sosPairingMessage && other->_sosPairingMessage) - { - [self->_sosPairingMessage mergeFrom:other->_sosPairingMessage]; - } - else if (!self->_sosPairingMessage && other->_sosPairingMessage) - { - [self setSosPairingMessage:other->_sosPairingMessage]; - } } @end diff --git a/keychain/ot/proto/generated_source/OTSOSMessage.h b/keychain/ot/proto/generated_source/OTSOSMessage.h deleted file mode 100644 index e515077e..00000000 --- a/keychain/ot/proto/generated_source/OTSOSMessage.h +++ /dev/null @@ -1,48 +0,0 @@ -// This file was automatically generated by protocompiler -// DO NOT EDIT! -// Compiled from OTPairingMessage.proto - -#import -#import - -#ifdef __cplusplus -#define OTSOSMESSAGE_FUNCTION extern "C" __attribute__((visibility("hidden"))) -#else -#define OTSOSMESSAGE_FUNCTION extern __attribute__((visibility("hidden"))) -#endif - -__attribute__((visibility("hidden"))) -@interface OTSOSMessage : PBCodable -{ - NSData *_circleBlob; - NSData *_credential; - NSData *_initialSyncItems; - NSData *_peerInfo; -} - - -@property (nonatomic, readonly) BOOL hasCredential; -@property (nonatomic, retain) NSData *credential; - -@property (nonatomic, readonly) BOOL hasPeerInfo; -@property (nonatomic, retain) NSData *peerInfo; - -@property (nonatomic, readonly) BOOL hasCircleBlob; -@property (nonatomic, retain) NSData *circleBlob; - -@property (nonatomic, readonly) BOOL hasInitialSyncItems; -@property (nonatomic, retain) NSData *initialSyncItems; - -// Performs a shallow copy into other -- (void)copyTo:(OTSOSMessage *)other; - -// Performs a deep merge from other into self -// If set in other, singular values in self are replaced in self -// Singular composite values are recursively merged -// Repeated values from other are appended to repeated values in self -- (void)mergeFrom:(OTSOSMessage *)other; - -OTSOSMESSAGE_FUNCTION BOOL OTSOSMessageReadFrom(__unsafe_unretained OTSOSMessage *self, __unsafe_unretained PBDataReader *reader); - -@end - diff --git a/keychain/ot/proto/generated_source/OTSOSMessage.m b/keychain/ot/proto/generated_source/OTSOSMessage.m deleted file mode 100644 index e4a1f2a5..00000000 --- a/keychain/ot/proto/generated_source/OTSOSMessage.m +++ /dev/null @@ -1,229 +0,0 @@ -// This file was automatically generated by protocompiler -// DO NOT EDIT! -// Compiled from OTPairingMessage.proto - -#import "OTSOSMessage.h" -#import -#import -#import - -#if !__has_feature(objc_arc) -# error This generated file depends on ARC but it is not enabled; turn on ARC, or use 'objc_use_arc' option to generate non-ARC code. -#endif - -@implementation OTSOSMessage - -- (BOOL)hasCredential -{ - return _credential != nil; -} -@synthesize credential = _credential; -- (BOOL)hasPeerInfo -{ - return _peerInfo != nil; -} -@synthesize peerInfo = _peerInfo; -- (BOOL)hasCircleBlob -{ - return _circleBlob != nil; -} -@synthesize circleBlob = _circleBlob; -- (BOOL)hasInitialSyncItems -{ - return _initialSyncItems != nil; -} -@synthesize initialSyncItems = _initialSyncItems; - -- (NSString *)description -{ - return [NSString stringWithFormat:@"%@ %@", [super description], [self dictionaryRepresentation]]; -} - -- (NSDictionary *)dictionaryRepresentation -{ - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - if (self->_credential) - { - [dict setObject:self->_credential forKey:@"credential"]; - } - if (self->_peerInfo) - { - [dict setObject:self->_peerInfo forKey:@"peerInfo"]; - } - if (self->_circleBlob) - { - [dict setObject:self->_circleBlob forKey:@"circleBlob"]; - } - if (self->_initialSyncItems) - { - [dict setObject:self->_initialSyncItems forKey:@"initialSyncItems"]; - } - return dict; -} - -BOOL OTSOSMessageReadFrom(__unsafe_unretained OTSOSMessage *self, __unsafe_unretained PBDataReader *reader) { - while (PBReaderHasMoreData(reader)) { - uint32_t tag = 0; - uint8_t aType = 0; - - PBReaderReadTag32AndType(reader, &tag, &aType); - - if (PBReaderHasError(reader)) - break; - - if (aType == TYPE_END_GROUP) { - break; - } - - switch (tag) { - - case 1 /* credential */: - { - NSData *new_credential = PBReaderReadData(reader); - self->_credential = new_credential; - } - break; - case 2 /* peerInfo */: - { - NSData *new_peerInfo = PBReaderReadData(reader); - self->_peerInfo = new_peerInfo; - } - break; - case 3 /* circleBlob */: - { - NSData *new_circleBlob = PBReaderReadData(reader); - self->_circleBlob = new_circleBlob; - } - break; - case 4 /* initialSyncItems */: - { - NSData *new_initialSyncItems = PBReaderReadData(reader); - self->_initialSyncItems = new_initialSyncItems; - } - break; - default: - if (!PBReaderSkipValueWithTag(reader, tag, aType)) - return NO; - break; - } - } - return !PBReaderHasError(reader); -} - -- (BOOL)readFrom:(PBDataReader *)reader -{ - return OTSOSMessageReadFrom(self, reader); -} -- (void)writeTo:(PBDataWriter *)writer -{ - /* credential */ - { - if (self->_credential) - { - PBDataWriterWriteDataField(writer, self->_credential, 1); - } - } - /* peerInfo */ - { - if (self->_peerInfo) - { - PBDataWriterWriteDataField(writer, self->_peerInfo, 2); - } - } - /* circleBlob */ - { - if (self->_circleBlob) - { - PBDataWriterWriteDataField(writer, self->_circleBlob, 3); - } - } - /* initialSyncItems */ - { - if (self->_initialSyncItems) - { - PBDataWriterWriteDataField(writer, self->_initialSyncItems, 4); - } - } -} - -- (void)copyTo:(OTSOSMessage *)other -{ - if (_credential) - { - other.credential = _credential; - } - if (_peerInfo) - { - other.peerInfo = _peerInfo; - } - if (_circleBlob) - { - other.circleBlob = _circleBlob; - } - if (_initialSyncItems) - { - other.initialSyncItems = _initialSyncItems; - } -} - -- (id)copyWithZone:(NSZone *)zone -{ - OTSOSMessage *copy = [[[self class] allocWithZone:zone] init]; - copy->_credential = [_credential copyWithZone:zone]; - copy->_peerInfo = [_peerInfo copyWithZone:zone]; - copy->_circleBlob = [_circleBlob copyWithZone:zone]; - copy->_initialSyncItems = [_initialSyncItems copyWithZone:zone]; - return copy; -} - -- (BOOL)isEqual:(id)object -{ - OTSOSMessage *other = (OTSOSMessage *)object; - return [other isMemberOfClass:[self class]] - && - ((!self->_credential && !other->_credential) || [self->_credential isEqual:other->_credential]) - && - ((!self->_peerInfo && !other->_peerInfo) || [self->_peerInfo isEqual:other->_peerInfo]) - && - ((!self->_circleBlob && !other->_circleBlob) || [self->_circleBlob isEqual:other->_circleBlob]) - && - ((!self->_initialSyncItems && !other->_initialSyncItems) || [self->_initialSyncItems isEqual:other->_initialSyncItems]) - ; -} - -- (NSUInteger)hash -{ - return 0 - ^ - [self->_credential hash] - ^ - [self->_peerInfo hash] - ^ - [self->_circleBlob hash] - ^ - [self->_initialSyncItems hash] - ; -} - -- (void)mergeFrom:(OTSOSMessage *)other -{ - if (other->_credential) - { - [self setCredential:other->_credential]; - } - if (other->_peerInfo) - { - [self setPeerInfo:other->_peerInfo]; - } - if (other->_circleBlob) - { - [self setCircleBlob:other->_circleBlob]; - } - if (other->_initialSyncItems) - { - [self setInitialSyncItems:other->_initialSyncItems]; - } -} - -@end - diff --git a/keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.h b/keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.h index b7495329..a0a32e48 100644 --- a/keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.h +++ b/keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.h @@ -14,7 +14,6 @@ __attribute__((visibility("hidden"))) @interface OTSponsorToApplicantRound2M2 : PBCodable { - NSMutableArray *_preapprovedKeys; NSData *_voucher; NSData *_voucherSignature; } @@ -26,13 +25,6 @@ __attribute__((visibility("hidden"))) @property (nonatomic, readonly) BOOL hasVoucherSignature; @property (nonatomic, retain) NSData *voucherSignature; -@property (nonatomic, retain) NSMutableArray *preapprovedKeys; -- (void)clearPreapprovedKeys; -- (void)addPreapprovedKeys:(NSData *)i; -- (NSUInteger)preapprovedKeysCount; -- (NSData *)preapprovedKeysAtIndex:(NSUInteger)idx; -+ (Class)preapprovedKeysType; - // Performs a shallow copy into other - (void)copyTo:(OTSponsorToApplicantRound2M2 *)other; diff --git a/keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.m b/keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.m index 6737b641..c51eaced 100644 --- a/keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.m +++ b/keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.m @@ -23,31 +23,6 @@ return _voucherSignature != nil; } @synthesize voucherSignature = _voucherSignature; -@synthesize preapprovedKeys = _preapprovedKeys; -- (void)clearPreapprovedKeys -{ - [_preapprovedKeys removeAllObjects]; -} -- (void)addPreapprovedKeys:(NSData *)i -{ - if (!_preapprovedKeys) - { - _preapprovedKeys = [[NSMutableArray alloc] init]; - } - [_preapprovedKeys addObject:i]; -} -- (NSUInteger)preapprovedKeysCount -{ - return [_preapprovedKeys count]; -} -- (NSData *)preapprovedKeysAtIndex:(NSUInteger)idx -{ - return [_preapprovedKeys objectAtIndex:idx]; -} -+ (Class)preapprovedKeysType -{ - return [NSData class]; -} - (NSString *)description { @@ -65,10 +40,6 @@ { [dict setObject:self->_voucherSignature forKey:@"voucherSignature"]; } - if (self->_preapprovedKeys) - { - [dict setObject:self->_preapprovedKeys forKey:@"preapprovedKeys"]; - } return dict; } @@ -100,15 +71,6 @@ BOOL OTSponsorToApplicantRound2M2ReadFrom(__unsafe_unretained OTSponsorToApplica self->_voucherSignature = new_voucherSignature; } break; - case 3 /* preapprovedKeys */: - { - NSData *new_preapprovedKeys = PBReaderReadData(reader); - if (new_preapprovedKeys) - { - [self addPreapprovedKeys:new_preapprovedKeys]; - } - } - break; default: if (!PBReaderSkipValueWithTag(reader, tag, aType)) return NO; @@ -138,13 +100,6 @@ BOOL OTSponsorToApplicantRound2M2ReadFrom(__unsafe_unretained OTSponsorToApplica PBDataWriterWriteDataField(writer, self->_voucherSignature, 2); } } - /* preapprovedKeys */ - { - for (NSData *s_preapprovedKeys in self->_preapprovedKeys) - { - PBDataWriterWriteDataField(writer, s_preapprovedKeys, 3); - } - } } - (void)copyTo:(OTSponsorToApplicantRound2M2 *)other @@ -157,15 +112,6 @@ BOOL OTSponsorToApplicantRound2M2ReadFrom(__unsafe_unretained OTSponsorToApplica { other.voucherSignature = _voucherSignature; } - if ([self preapprovedKeysCount]) - { - [other clearPreapprovedKeys]; - NSUInteger preapprovedKeysCnt = [self preapprovedKeysCount]; - for (NSUInteger i = 0; i < preapprovedKeysCnt; i++) - { - [other addPreapprovedKeys:[self preapprovedKeysAtIndex:i]]; - } - } } - (id)copyWithZone:(NSZone *)zone @@ -173,11 +119,6 @@ BOOL OTSponsorToApplicantRound2M2ReadFrom(__unsafe_unretained OTSponsorToApplica OTSponsorToApplicantRound2M2 *copy = [[[self class] allocWithZone:zone] init]; copy->_voucher = [_voucher copyWithZone:zone]; copy->_voucherSignature = [_voucherSignature copyWithZone:zone]; - for (NSData *v in _preapprovedKeys) - { - NSData *vCopy = [v copyWithZone:zone]; - [copy addPreapprovedKeys:vCopy]; - } return copy; } @@ -189,8 +130,6 @@ BOOL OTSponsorToApplicantRound2M2ReadFrom(__unsafe_unretained OTSponsorToApplica ((!self->_voucher && !other->_voucher) || [self->_voucher isEqual:other->_voucher]) && ((!self->_voucherSignature && !other->_voucherSignature) || [self->_voucherSignature isEqual:other->_voucherSignature]) - && - ((!self->_preapprovedKeys && !other->_preapprovedKeys) || [self->_preapprovedKeys isEqual:other->_preapprovedKeys]) ; } @@ -201,8 +140,6 @@ BOOL OTSponsorToApplicantRound2M2ReadFrom(__unsafe_unretained OTSponsorToApplica [self->_voucher hash] ^ [self->_voucherSignature hash] - ^ - [self->_preapprovedKeys hash] ; } @@ -216,10 +153,6 @@ BOOL OTSponsorToApplicantRound2M2ReadFrom(__unsafe_unretained OTSponsorToApplica { [self setVoucherSignature:other->_voucherSignature]; } - for (NSData *iter_preapprovedKeys in other->_preapprovedKeys) - { - [self addPreapprovedKeys:iter_preapprovedKeys]; - } } @end diff --git a/keychain/ot/proto/source/OTSOSMessage.m b/keychain/ot/proto/source/OTSOSMessage.m deleted file mode 100644 index e4a1f2a5..00000000 --- a/keychain/ot/proto/source/OTSOSMessage.m +++ /dev/null @@ -1,229 +0,0 @@ -// This file was automatically generated by protocompiler -// DO NOT EDIT! -// Compiled from OTPairingMessage.proto - -#import "OTSOSMessage.h" -#import -#import -#import - -#if !__has_feature(objc_arc) -# error This generated file depends on ARC but it is not enabled; turn on ARC, or use 'objc_use_arc' option to generate non-ARC code. -#endif - -@implementation OTSOSMessage - -- (BOOL)hasCredential -{ - return _credential != nil; -} -@synthesize credential = _credential; -- (BOOL)hasPeerInfo -{ - return _peerInfo != nil; -} -@synthesize peerInfo = _peerInfo; -- (BOOL)hasCircleBlob -{ - return _circleBlob != nil; -} -@synthesize circleBlob = _circleBlob; -- (BOOL)hasInitialSyncItems -{ - return _initialSyncItems != nil; -} -@synthesize initialSyncItems = _initialSyncItems; - -- (NSString *)description -{ - return [NSString stringWithFormat:@"%@ %@", [super description], [self dictionaryRepresentation]]; -} - -- (NSDictionary *)dictionaryRepresentation -{ - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - if (self->_credential) - { - [dict setObject:self->_credential forKey:@"credential"]; - } - if (self->_peerInfo) - { - [dict setObject:self->_peerInfo forKey:@"peerInfo"]; - } - if (self->_circleBlob) - { - [dict setObject:self->_circleBlob forKey:@"circleBlob"]; - } - if (self->_initialSyncItems) - { - [dict setObject:self->_initialSyncItems forKey:@"initialSyncItems"]; - } - return dict; -} - -BOOL OTSOSMessageReadFrom(__unsafe_unretained OTSOSMessage *self, __unsafe_unretained PBDataReader *reader) { - while (PBReaderHasMoreData(reader)) { - uint32_t tag = 0; - uint8_t aType = 0; - - PBReaderReadTag32AndType(reader, &tag, &aType); - - if (PBReaderHasError(reader)) - break; - - if (aType == TYPE_END_GROUP) { - break; - } - - switch (tag) { - - case 1 /* credential */: - { - NSData *new_credential = PBReaderReadData(reader); - self->_credential = new_credential; - } - break; - case 2 /* peerInfo */: - { - NSData *new_peerInfo = PBReaderReadData(reader); - self->_peerInfo = new_peerInfo; - } - break; - case 3 /* circleBlob */: - { - NSData *new_circleBlob = PBReaderReadData(reader); - self->_circleBlob = new_circleBlob; - } - break; - case 4 /* initialSyncItems */: - { - NSData *new_initialSyncItems = PBReaderReadData(reader); - self->_initialSyncItems = new_initialSyncItems; - } - break; - default: - if (!PBReaderSkipValueWithTag(reader, tag, aType)) - return NO; - break; - } - } - return !PBReaderHasError(reader); -} - -- (BOOL)readFrom:(PBDataReader *)reader -{ - return OTSOSMessageReadFrom(self, reader); -} -- (void)writeTo:(PBDataWriter *)writer -{ - /* credential */ - { - if (self->_credential) - { - PBDataWriterWriteDataField(writer, self->_credential, 1); - } - } - /* peerInfo */ - { - if (self->_peerInfo) - { - PBDataWriterWriteDataField(writer, self->_peerInfo, 2); - } - } - /* circleBlob */ - { - if (self->_circleBlob) - { - PBDataWriterWriteDataField(writer, self->_circleBlob, 3); - } - } - /* initialSyncItems */ - { - if (self->_initialSyncItems) - { - PBDataWriterWriteDataField(writer, self->_initialSyncItems, 4); - } - } -} - -- (void)copyTo:(OTSOSMessage *)other -{ - if (_credential) - { - other.credential = _credential; - } - if (_peerInfo) - { - other.peerInfo = _peerInfo; - } - if (_circleBlob) - { - other.circleBlob = _circleBlob; - } - if (_initialSyncItems) - { - other.initialSyncItems = _initialSyncItems; - } -} - -- (id)copyWithZone:(NSZone *)zone -{ - OTSOSMessage *copy = [[[self class] allocWithZone:zone] init]; - copy->_credential = [_credential copyWithZone:zone]; - copy->_peerInfo = [_peerInfo copyWithZone:zone]; - copy->_circleBlob = [_circleBlob copyWithZone:zone]; - copy->_initialSyncItems = [_initialSyncItems copyWithZone:zone]; - return copy; -} - -- (BOOL)isEqual:(id)object -{ - OTSOSMessage *other = (OTSOSMessage *)object; - return [other isMemberOfClass:[self class]] - && - ((!self->_credential && !other->_credential) || [self->_credential isEqual:other->_credential]) - && - ((!self->_peerInfo && !other->_peerInfo) || [self->_peerInfo isEqual:other->_peerInfo]) - && - ((!self->_circleBlob && !other->_circleBlob) || [self->_circleBlob isEqual:other->_circleBlob]) - && - ((!self->_initialSyncItems && !other->_initialSyncItems) || [self->_initialSyncItems isEqual:other->_initialSyncItems]) - ; -} - -- (NSUInteger)hash -{ - return 0 - ^ - [self->_credential hash] - ^ - [self->_peerInfo hash] - ^ - [self->_circleBlob hash] - ^ - [self->_initialSyncItems hash] - ; -} - -- (void)mergeFrom:(OTSOSMessage *)other -{ - if (other->_credential) - { - [self setCredential:other->_credential]; - } - if (other->_peerInfo) - { - [self setPeerInfo:other->_peerInfo]; - } - if (other->_circleBlob) - { - [self setCircleBlob:other->_circleBlob]; - } - if (other->_initialSyncItems) - { - [self setInitialSyncItems:other->_initialSyncItems]; - } -} - -@end - diff --git a/keychain/ot/tests/octagon/.swiftlint.yml b/keychain/ot/tests/octagon/.swiftlint.yml new file mode 100644 index 00000000..9ddd0d11 --- /dev/null +++ b/keychain/ot/tests/octagon/.swiftlint.yml @@ -0,0 +1,3 @@ +disabled_rules: + - force_cast + - force_try diff --git a/keychain/ot/tests/octagon/OctagonDataPersistenceTests.swift b/keychain/ot/tests/octagon/OctagonDataPersistenceTests.swift index 0b457d23..82e22519 100644 --- a/keychain/ot/tests/octagon/OctagonDataPersistenceTests.swift +++ b/keychain/ot/tests/octagon/OctagonDataPersistenceTests.swift @@ -19,6 +19,7 @@ class OctagonAccountMetadataClassCPersistenceTests: CloudKitKeychainSyncingMockX state.peerID = "asdf" state.icloudAccountState = .ACCOUNT_AVAILABLE state.trustState = .TRUSTED + state.cdpState = .ENABLED XCTAssertNoThrow(try state.saveToKeychain(forContainer: OTCKContainerName, contextID: OTDefaultContext), "saving to the keychain should work") @@ -28,6 +29,7 @@ class OctagonAccountMetadataClassCPersistenceTests: CloudKitKeychainSyncingMockX XCTAssertEqual(state2.peerID, state.peerID, "peer ID persists through keychain") XCTAssertEqual(state2.icloudAccountState, state.icloudAccountState, "account state persists through keychain") XCTAssertEqual(state2.trustState, state.trustState, "trust state persists through keychain") + XCTAssertEqual(state2.cdpState, state.cdpState, "cdp state persists through keychain") } catch { XCTFail("error loading from keychain: \(error)") } diff --git a/keychain/ot/tests/octagon/OctagonTestMocks.swift b/keychain/ot/tests/octagon/OctagonTestMocks.swift index a9d31759..ef94770e 100644 --- a/keychain/ot/tests/octagon/OctagonTestMocks.swift +++ b/keychain/ot/tests/octagon/OctagonTestMocks.swift @@ -37,6 +37,10 @@ class OTMockSecureBackup: NSObject, OctagonEscrowRecovererPrococol { } return nil } + + func disable(withInfo info: [AnyHashable: Any]!) -> Error? { + return nil + } } class OTMockFollowUpController: NSObject, OctagonFollowUpControllerProtocol { diff --git a/keychain/ot/tests/octagon/OctagonTests+Account.swift b/keychain/ot/tests/octagon/OctagonTests+Account.swift new file mode 100644 index 00000000..fa1e3879 --- /dev/null +++ b/keychain/ot/tests/octagon/OctagonTests+Account.swift @@ -0,0 +1,568 @@ +/* +* Copyright (c) 2019 Apple Inc. All Rights Reserved. +* +* @APPLE_LICENSE_HEADER_START@ +* +* This file contains Original Code and/or Modifications of Original Code +* as defined in and that are subject to the Apple Public Source License +* Version 2.0 (the 'License'). You may not use this file except in +* compliance with the License. Please obtain a copy of the License at +* http://www.opensource.apple.com/apsl/ and read it before using this +* file. +* +* The Original Code and all software distributed under the License are +* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +* Please see the License for the specific language governing rights and +* limitations under the License. +* +* @APPLE_LICENSE_HEADER_END@ +*/ + +import Foundation + +#if OCTAGON + +class OctagonAccountTests: OctagonTestsBase { + func testAccountSave() throws { + let contextName = OTDefaultContext + let containerName = OTCKContainerName + + self.startCKAccountStatusMock() + + // Before resetAndEstablish, there shouldn't be any stored account state + XCTAssertThrowsError(try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName), "Before doing anything, loading a non-existent account state should fail") + + let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablish callback occurs") + self.manager.resetAndEstablish(containerName, + context: contextName, + altDSID: "new altDSID", + resetReason: .testGenerated) { resetError in + XCTAssertNil(resetError, "Should be no error calling resetAndEstablish") + resetAndEstablishExpectation.fulfill() + } + + self.wait(for: [resetAndEstablishExpectation], timeout: 10) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + + let selfPeerID = try self.cuttlefishContext.accountMetadataStore.loadOrCreateAccountMetadata().peerID + + // After resetAndEstablish, you should be able to see the persisted account state + do { + let accountState = try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName) + XCTAssertEqual(selfPeerID, accountState.peerID, "Saved account state should have the same peer ID that prepare returned") + XCTAssertEqual(accountState.cdpState, .ENABLED, "Saved CDP status should be 'enabled' after a resetAndEstablish") + } catch { + XCTFail("error loading account state: \(error)") + } + } + + func testLoadToNoAccount() throws { + // No CloudKit account, either + self.accountStatus = .noAccount + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // With no identity and AuthKit reporting no iCloud account, Octagon should go directly into 'no account' + self.mockAuthKit.altDSID = nil + + let asyncExpectation = self.expectation(description: "dispatch works") + let quiescentExpectation = self.expectation(description: "quiescence has been determined") + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + asyncExpectation.fulfill() + + let c = self!.cuttlefishContext.stateMachine.paused + XCTAssertEqual(0, c.wait(10 * NSEC_PER_SEC), "State machine should become quiescent") + quiescentExpectation.fulfill() + } + // Wait for the block above to fire before continuing + self.wait(for: [asyncExpectation], timeout: 10) + + self.cuttlefishContext.startOctagonStateMachine() + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + XCTAssertTrue(self.cuttlefishContext.stateMachine.isPaused(), "State machine should be stopped") + self.assertNoAccount(context: self.cuttlefishContext) + + XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "State machine should be quiescent") + + self.wait(for: [quiescentExpectation], timeout: 10) + + // CKKS should also be logged out, since Octagon believes there's no account + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) + } + + func testNoAccountLeadsToInitialize() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // With no identity and AuthKit reporting no iCloud account, Octagon should go directly into 'no account' + self.mockAuthKit.altDSID = nil + + self.cuttlefishContext.startOctagonStateMachine() + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + + self.mockAuthKit.altDSID = "1234" + let signinExpectation = self.expectation(description: "sign in returns") + self.otControl.sign(in: "1234", container: nil, context: OTDefaultContext) { error in + XCTAssertNil(error, "error should be nil") + signinExpectation.fulfill() + } + self.wait(for: [signinExpectation], timeout: 10) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + + // And now the CDP bit is set... + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + } + + func testSignIn() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // Device is signed out + self.mockAuthKit.altDSID = nil + self.mockAuthKit.hsa2 = false + + // With no account, Octagon should go directly into 'NoAccount' + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + + // Sign in occurs + let newAltDSID = UUID().uuidString + self.mockAuthKit.altDSID = newAltDSID + self.mockAuthKit.hsa2 = true + XCTAssertNoThrow(try self.cuttlefishContext.accountAvailable(newAltDSID), "Sign-in shouldn't error") + + // Octagon should go into 'waitforcdp' + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .disabled, "CDP status should be 'disabled'") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have not posted a repair CFU while waiting for CDP") + + // And CDP is enabled: + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .enabled, "CDP status should be 'enabled'") + + #if !os(tvOS) + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have posted a repair CFU") + #else + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "posted should be false on tvOS; there aren't any devices around to repair it") + #endif + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + + // On sign-out, octagon should go back to 'no account' + self.mockAuthKit.altDSID = nil + XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "sign-out shouldn't error") + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + self.assertNoAccount(context: self.cuttlefishContext) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .unknown, "CDP status should be 'unknown'") + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + } + + func testSignInWithDelayedHSA2Status() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // Device is signed out + self.mockAuthKit.altDSID = nil + self.mockAuthKit.hsa2 = false + + // With no account, Octagon should go directly into 'NoAccount' + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + + // Sign in occurs, but HSA2 status isn't here yet + let newAltDSID = UUID().uuidString + self.mockAuthKit.altDSID = newAltDSID + XCTAssertNoThrow(try self.cuttlefishContext.accountAvailable(newAltDSID), "Sign-in shouldn't error") + + // Octagon should go into 'waitforhsa2' + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForHSA2, within: 10 * NSEC_PER_SEC) + + self.mockAuthKit.hsa2 = true + XCTAssertNoThrow(try self.cuttlefishContext.idmsTrustLevelChanged(), "Notification of IDMS trust level shouldn't error") + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) + + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + + // On sign-out, octagon should go back to 'no account' + self.mockAuthKit.altDSID = nil + XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "sign-out shouldn't error") + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + self.assertNoAccount(context: self.cuttlefishContext) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + } + + func testSignInWithCDPStateBeforeDelayedHSA2Status() throws { + self.startCKAccountStatusMock() + + // Device is signed out + self.mockAuthKit.altDSID = nil + self.mockAuthKit.hsa2 = false + + // With no account, Octagon should go directly into 'NoAccount' + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + + // CDP state is set. Cool? + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + + // Sign in occurs, but HSA2 status isn't here yet + let newAltDSID = UUID().uuidString + self.mockAuthKit.altDSID = newAltDSID + self.mockAuthKit.hsa2 = true + XCTAssertNoThrow(try self.cuttlefishContext.accountAvailable(newAltDSID), "Sign-in shouldn't error") + + // Octagon should go into 'untrusted', as everything is in place + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + + // On sign-out, octagon should go back to 'no account' + self.mockAuthKit.altDSID = nil + XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "sign-out shouldn't error") + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + self.assertNoAccount(context: self.cuttlefishContext) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + } + + func testSetCDPStateWithUnconfiguredArguments() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // Device is signed out + self.mockAuthKit.altDSID = nil + self.mockAuthKit.hsa2 = false + + // With no account, Octagon should go directly into 'NoAccount' + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + + // Now set the CDP state, but using the OTClique API (and not configuring the context) + let unconfigured = OTConfigurationContext() + unconfigured.otControl = self.otControl + XCTAssertNoThrow(try OTClique.setCDPEnabled(unconfigured)) + + // Sign in occurs, but HSA2 status isn't here yet + let newAltDSID = UUID().uuidString + self.mockAuthKit.altDSID = newAltDSID + XCTAssertNoThrow(try self.cuttlefishContext.accountAvailable(newAltDSID), "Sign-in shouldn't error") + + // Octagon should go into 'waitforhsa2' + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForHSA2, within: 10 * NSEC_PER_SEC) + + self.mockAuthKit.hsa2 = true + XCTAssertNoThrow(try self.cuttlefishContext.idmsTrustLevelChanged(), "Notification of IDMS trust level shouldn't error") + + // and we should skip waiting for CDP, as it's already set + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + + // On sign-out, octagon should go back to 'no account' + self.mockAuthKit.altDSID = nil + XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "sign-out shouldn't error") + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + self.assertNoAccount(context: self.cuttlefishContext) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + } + + func testSignInWithExistingCuttlefishRecordsSetsCDPStatus() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // signing in to an account with pre-existing Octagon data should turn on the CDP bit by default: + // no setting needed. + + let remote = self.makeInitiatorContext(contextID: "remote") + self.assertResetAndBecomeTrusted(context: remote) + + // when this context boots up, it should go straight into untrusted, and set its CDP bit + // since there's already CDP data in the account + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .enabled, "CDP status should be 'enabled'") + + #if !os(tvOS) + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have posted a repair CFU after the CDP bit was set") + #else + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should not have posted on tvOS; there aren't any iphones around to repair it") + #endif + } + + func testEnableCDPStatusIfNotificationArrives() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // default context comes up, but CDP is not enabled + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .disabled, "CDP status should be 'disabled'") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have not posted a repair CFU while waiting for CDP") + + // If a cuttlefish push occurs without any data existing, the CDP bit should stay off + self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateWaitForCDPUpdated, OctagonStateDetermineCDPState]) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .disabled, "CDP status should be 'disabled'") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have not posted a repair CFU while waiting for CDP") + + // Another peer comes along and performs Octagon operations + let remote = self.makeInitiatorContext(contextID: "remote") + self.assertResetAndBecomeTrusted(context: remote) + + // And some SOS operations. SOS now returns "error" when asked its state + self.mockSOSAdapter.circleStatusError = NSError(domain: kSOSErrorDomain as String, code: kSOSErrorPublicKeyAbsent, userInfo: nil) + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCError) + + // And after the update, the context should go into 'untrusted' + self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateWaitForCDPUpdated, OctagonStateDetermineCDPState]) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .enabled, "CDP status should be 'enabled'") + + #if !os(tvOS) + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have posted a repair CFU") + #else + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should not have posted on tvOS; there aren't any iphones around to repair it") + #endif + } + + func testEnableCDPStatusIfNotificationArrivesWithoutCreatingSOSCircle() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // default context comes up, but CDP is not enabled + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .disabled, "CDP status should be 'disabled'") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have not posted a repair CFU while waiting for CDP") + + // If a cuttlefish push occurs without any data existing, the CDP bit should stay off + self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateWaitForCDPUpdated, OctagonStateDetermineCDPState]) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .disabled, "CDP status should be 'disabled'") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have not posted a repair CFU while waiting for CDP") + + // Another peer comes along and performs Octagon operations + let remote = self.makeInitiatorContext(contextID: "remote") + self.assertResetAndBecomeTrusted(context: remote) + + // Unlike testEnableCDPStatusIfNotificationArrives, SOS remains in 'absent', simulating a non-sos platform creating Octagon + + // And after the update, the context should go into 'untrusted' + self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateWaitForCDPUpdated, OctagonStateDetermineCDPState]) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .enabled, "CDP status should be 'enabled'") + + #if !os(tvOS) + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have posted a repair CFU") + #else + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should not have posted on tvOS; there aren't any iphones around to repair it") + #endif + } + + func testCDPEnableAPIRaceWithCDPStateDetermination() throws { + // The API call to enable CDP might occur while Octagon is figuring out that it should be disabled + // Octagon should respect the API call + + // SOS is absent + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + self.startCKAccountStatusMock() + + // The initial CDP-enable API call happens while Octagon is fetching the world's state + let fetchExpectation = self.expectation(description: "fetchChanges called") + self.fakeCuttlefishServer.fetchChangesListener = { [unowned self] _ in + do { + try self.cuttlefishContext.setCDPEnabled() + } catch { + XCTFail("Expected to be able to set CDP status without error: \(error)") + } + fetchExpectation.fulfill() + return nil + } + + self.cuttlefishContext.startOctagonStateMachine() + + // Octagon should go into 'untrusted', as the API call said that CDP was there + self.wait(for: [fetchExpectation], timeout: 10) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + } + + func testSignOut() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + + do { + let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) + XCTAssertNotNil(clique, "Clique should not be nil") + } catch { + XCTFail("Shouldn't have errored making new friends: \(error)") + } + + // Now, we should be in 'ready' + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfTrusted(context: self.cuttlefishContext) + self.verifyDatabaseMocks() + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + + // And 'dump' should show some information + let dumpExpectation = self.expectation(description: "dump callback occurs") + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, error in + XCTAssertNil(error, "Should be no error dumping data") + XCTAssertNotNil(dump, "dump should not be nil") + let egoSelf = dump!["self"] as? [String: AnyObject] + XCTAssertNotNil(egoSelf, "egoSelf should not be nil") + let peerID = egoSelf!["peerID"] as? String + XCTAssertNotNil(peerID, "peerID should not be nil") + + dumpExpectation.fulfill() + } + self.wait(for: [dumpExpectation], timeout: 10) + + // Turn off the CK account too + self.accountStatus = .noAccount + self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() + + XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "Should be no issue signing out") + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + self.assertNoAccount(context: self.cuttlefishContext) + + // And 'dump' should show nothing + let signedOutDumpExpectation = self.expectation(description: "dump callback occurs") + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, error in + XCTAssertNil(error, "Should be no error dumping data") + XCTAssertNotNil(dump, "dump should not be nil") + let egoSelf = dump!["self"] as? [String: AnyObject] + XCTAssertNotNil(egoSelf, "egoSelf should not be nil") + XCTAssertEqual(egoSelf!.count, 0, "egoSelf should have zero elements") + + signedOutDumpExpectation.fulfill() + } + self.wait(for: [signedOutDumpExpectation], timeout: 10) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) + + //check trust status + let checkTrustExpectation = self.expectation(description: "checkTrustExpectation callback occurs") + let configuration = OTOperationConfiguration() + self.cuttlefishContext.rpcTrustStatus(configuration) { _, _, _, _, _ in + checkTrustExpectation.fulfill() + } + self.wait(for: [checkTrustExpectation], timeout: 10) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + + // And 'dump' should show nothing + let signedOutDumpExpectationAfterCheckTrustStatus = self.expectation(description: "dump callback occurs") + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, error in + XCTAssertNil(error, "Should be no error dumping data") + XCTAssertNotNil(dump, "dump should not be nil") + let egoSelf = dump!["self"] as? [String: AnyObject] + XCTAssertNotNil(egoSelf, "egoSelf should not be nil") + XCTAssertEqual(egoSelf!.count, 0, "egoSelf should have zero elements") + + signedOutDumpExpectationAfterCheckTrustStatus.fulfill() + } + self.wait(for: [signedOutDumpExpectationAfterCheckTrustStatus], timeout: 10) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + self.assertNoAccount(context: self.cuttlefishContext) + } + + func testSignOutFromWaitForCDP() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + + // Turn off the CK account too + self.accountStatus = .noAccount + self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() + + XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "Should be no issue signing out") + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + self.assertNoAccount(context: self.cuttlefishContext) + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .unknown, "CDP status should be 'unknown'") + } + + func testNoAccountTimeoutTransitionWatcher() throws { + self.startCKAccountStatusMock() + + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // With no identity and AuthKit reporting no iCloud account, Octagon should go directly into 'no account' + self.mockAuthKit.altDSID = nil + + self.cuttlefishContext.startOctagonStateMachine() + self.cuttlefishContext.stateMachine.setWatcherTimeout(2 * NSEC_PER_SEC) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + XCTAssertTrue(self.cuttlefishContext.stateMachine.isPaused(), "State machine should be stopped") + self.assertNoAccount(context: self.cuttlefishContext) + XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "State machine should be quiescent") + + let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") + self.cuttlefishContext.join(withBottle: "bottleID", entropy: Data(), bottleSalt: "peer2AltDSID") { error in + XCTAssertNotNil(error, "error should not be nil") + joinWithBottleExpectation.fulfill() + } + self.wait(for: [joinWithBottleExpectation], timeout: 3) + } + +} + +#endif diff --git a/keychain/ot/tests/octagon/OctagonTests+CKKS.swift b/keychain/ot/tests/octagon/OctagonTests+CKKS.swift index d088ab71..4888f6e1 100644 --- a/keychain/ot/tests/octagon/OctagonTests+CKKS.swift +++ b/keychain/ot/tests/octagon/OctagonTests+CKKS.swift @@ -6,11 +6,11 @@ class OctagonCKKSTests: OctagonTestsBase { // Right after CKKS fetches for the first time, insert a new key hierarchy into CloudKit self.silentFetchesAllowed = false - self.expectCKFetchAndRun(beforeFinished: { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.expectCKFetchAndRun { + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() self.silentFetchesAllowed = true - }) + } self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) @@ -37,12 +37,12 @@ class OctagonCKKSTests: OctagonTestsBase { // Right after CKKS fetches for the first time, insert a new key hierarchy into CloudKit self.silentFetchesAllowed = false - self.expectCKFetchAndRun(beforeFinished: { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.expectCKFetchAndRun { + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() + self.saveTLKMaterialToKeychain() self.silentFetchesAllowed = true - }) + } self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) diff --git a/keychain/ot/tests/octagon/OctagonTests+CKKSConfiguration.swift b/keychain/ot/tests/octagon/OctagonTests+CKKSConfiguration.swift new file mode 100644 index 00000000..67e9a821 --- /dev/null +++ b/keychain/ot/tests/octagon/OctagonTests+CKKSConfiguration.swift @@ -0,0 +1,149 @@ +import Foundation + +class OctagonCKKSConfigurationTestsPolicyDisabled: OctagonTestsBase { + // Pre-configure some things, so the OctagonTests will only operate on these views + override func setUp() { + if self.mockDeviceInfo == nil { + let actualDeviceAdapter = OTDeviceInformationActualAdapter() + self.mockDeviceInfo = OTMockDeviceInfoAdapter(modelID: actualDeviceAdapter.modelID(), + deviceName: actualDeviceAdapter.deviceName(), + serialNumber: NSUUID().uuidString, + osVersion: actualDeviceAdapter.osVersion()) + } + + // With the policy disabled, we only want to operate on a few zones + if self.mockDeviceInfo.mockModelID.contains("AppleTV") { + self.intendedCKKSZones = Set([ + CKRecordZone.ID(zoneName: "LimitedPeersAllowed"), + ]) + } else { + self.intendedCKKSZones = Set([ + CKRecordZone.ID(zoneName: "LimitedPeersAllowed"), + CKRecordZone.ID(zoneName: "Manatee"), + ]) + } + + self.setCKKSViewsFromPolicyToNo = true + + super.setUp() + + XCTAssertFalse(self.cuttlefishContext.viewManager!.useCKKSViewsFromPolicy(), "CKKS should not be configured to listen to policy-based views") + } + + func testMergedViewListOff() throws { + XCTAssertFalse(self.cuttlefishContext.viewManager!.useCKKSViewsFromPolicy(), "CKKS should not be configured to listen to policy-based views") + + self.startCKAccountStatusMock() + self.assertResetAndBecomeTrustedInDefaultContext() + + let viewList = self.cuttlefishContext.viewManager!.viewList() + #if !os(tvOS) + let expected = Set(["Manatee", "LimitedPeersAllowed"]) + #else + let expected = Set(["LimitedPeersAllowed"]) + #endif + XCTAssertEqual(expected, viewList) + } +} + +class OctagonCKKSConfigurationTestsPolicyEnabled: OctagonTestsBase { + override func setUp() { + + if self.mockDeviceInfo == nil { + let actualDeviceAdapter = OTDeviceInformationActualAdapter() + self.mockDeviceInfo = OTMockDeviceInfoAdapter(modelID: actualDeviceAdapter.modelID(), + deviceName: actualDeviceAdapter.deviceName(), + serialNumber: NSUUID().uuidString, + osVersion: actualDeviceAdapter.osVersion()) + } + + // Most tests will use a much smaller list of views. But not us! Go wild! + if self.mockDeviceInfo.mockModelID.contains("AppleTV") { + self.intendedCKKSZones = Set([ + CKRecordZone.ID(zoneName: "Home"), + self.limitedPeersAllowedZoneID!, + CKRecordZone.ID(zoneName: "WiFi"), + ]) + } else { + self.intendedCKKSZones = Set([ + CKRecordZone.ID(zoneName: "ApplePay"), + CKRecordZone.ID(zoneName: "Applications"), + CKRecordZone.ID(zoneName: "AutoUnlock"), + // Octagon: create final policy for CKKS4All + // CKRecordZone.ID(zoneName: "Backstop"), + // Cuttlefish: remove Safari prefix from view names + CKRecordZone.ID(zoneName: "SafariCreditCards"), + CKRecordZone.ID(zoneName: "DevicePairing"), + CKRecordZone.ID(zoneName: "Engram"), + CKRecordZone.ID(zoneName: "Health"), + CKRecordZone.ID(zoneName: "Home"), + CKRecordZone.ID(zoneName: "LimitedPeersAllowed"), + CKRecordZone.ID(zoneName: "Manatee"), + // Cuttlefish: remove Safari prefix from view names + CKRecordZone.ID(zoneName: "SafariPasswords"), + CKRecordZone.ID(zoneName: "ProtectedCloudStorage"), + CKRecordZone.ID(zoneName: "SecureObjectSync"), + CKRecordZone.ID(zoneName: "WiFi"), + ]) + } + + super.setUp() + } + + func testMergedViewListOn() throws { + XCTAssertTrue(self.cuttlefishContext.viewManager!.useCKKSViewsFromPolicy(), "CKKS should be configured to listen to policy-based views") + + self.startCKAccountStatusMock() + self.assertResetAndBecomeTrustedInDefaultContext() + + let viewList = self.cuttlefishContext.viewManager!.viewList() + + #if !os(tvOS) + let expected = Set([ + "ApplePay", + "Applications", + "AutoUnlock", + // Octagon: create final policy for CKKS4All + //"Backstop", + // Cuttlefish: remove Safari prefix from view names + "SafariCreditCards", + "DevicePairing", + "Engram", + "Health", + "Home", + "LimitedPeersAllowed", + "Manatee", + // Cuttlefish: remove Safari prefix from view names + "SafariPasswords", + "ProtectedCloudStorage", + "SecureObjectSync", + "WiFi", + ]) + #else + let expected = Set(["LimitedPeersAllowed", + "Home", + "WiFi", ]) + #endif + XCTAssertEqual(expected, viewList) + } + + func testPolicyResetRPC() throws { + XCTAssertTrue(self.cuttlefishContext.viewManager!.useCKKSViewsFromPolicy(), "CKKS should be configured to listen to policy-based views") + + self.startCKAccountStatusMock() + self.assertResetAndBecomeTrustedInDefaultContext() + + XCTAssertNotNil(self.injectedManager?.policy, "Should have given CKKS a TPPolicy during initialization") + XCTAssertEqual(self.injectedManager?.policy?.version, prevailingPolicyVersion, "Policy given to CKKS should be prevailing policy") + + self.injectedManager!.resetSyncingPolicy() + XCTAssertNil(self.injectedManager?.policy, "CKKS policy should be reset (by the test)") + + self.otControl.refetchCKKSPolicy(nil, contextID: self.cuttlefishContext.contextID) { error in + XCTAssertNil(error, "Should be no error refetching the CKKS policy") + } + + XCTAssertNotNil(self.injectedManager?.policy, "Should have given CKKS a TPPolicy during refetch") + XCTAssertEqual(self.injectedManager?.policy?.version, prevailingPolicyVersion, "Policy given to CKKS should be prevailing policy") + } +} diff --git a/keychain/ot/tests/octagon/OctagonTests+CloudKitAccount.swift b/keychain/ot/tests/octagon/OctagonTests+CloudKitAccount.swift index 5a43ca54..fd35bd09 100644 --- a/keychain/ot/tests/octagon/OctagonTests+CloudKitAccount.swift +++ b/keychain/ot/tests/octagon/OctagonTests+CloudKitAccount.swift @@ -10,6 +10,9 @@ class OctagonCloudKitAccountTests: OctagonTestsBase { self.startCKAccountStatusMock() self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + // With no account, Octagon should go directly into 'NoAccount' self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) @@ -19,9 +22,9 @@ class OctagonCloudKitAccountTests: OctagonTestsBase { self.mockAuthKit.altDSID = newAltDSID XCTAssertNoThrow(try self.cuttlefishContext.accountAvailable(newAltDSID), "Sign-in shouldn't error") - // We should reach 'untrusted', as we cached the CK value - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) + // We should reach 'waitforcdp', as we cached the CK value + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) } func testSignInPausesForCloudKit() throws { @@ -32,6 +35,9 @@ class OctagonCloudKitAccountTests: OctagonTestsBase { self.accountStatus = .noAccount self.startCKAccountStatusMock() + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + // With no account, Octagon should go directly into 'NoAccount' self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) @@ -48,10 +54,16 @@ class OctagonCloudKitAccountTests: OctagonTestsBase { self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitingForCloudKitAccount, within: 10 * NSEC_PER_SEC) assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) - // And when CK shows up, we should go into 'untrusted' + // And when CK shows up, we should go into 'waitforcdp' self.accountStatus = .available self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) + + // And then CDP is set to be on + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) @@ -108,6 +120,9 @@ class OctagonCloudKitAccountTests: OctagonTestsBase { self.accountStatus = .noAccount self.startCKAccountStatusMock() + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + // With no account, Octagon should go directly into 'NoAccount' self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) @@ -120,10 +135,16 @@ class OctagonCloudKitAccountTests: OctagonTestsBase { // Octagon should go into 'wait for cloudkit account' self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitingForCloudKitAccount, within: 10 * NSEC_PER_SEC) - // And when CK shows up, we should go into 'untrusted' + // And when CK shows up, we should go into 'waitforcdp' self.accountStatus = .available self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) + + // and then, when CDP tells us, we should go into 'untrusted' + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) @@ -194,6 +215,68 @@ class OctagonCloudKitAccountTests: OctagonTestsBase { self.accountStatus = .noAccount self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + // With no account, Octagon should go directly into 'NoAccount' + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + + // Account signs in as SA. + let newAltDSID = UUID().uuidString + self.mockAuthKit.altDSID = newAltDSID + + XCTAssertNoThrow(try self.cuttlefishContext.idmsTrustLevelChanged(), "Notification of IDMS trust level shouldn't error") + XCTAssertNoThrow(try self.cuttlefishContext.accountAvailable(newAltDSID), "Sign-in shouldn't error") + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForHSA2, within: 10 * NSEC_PER_SEC) + self.assertNoAccount(context: self.cuttlefishContext) + + // Account promotes to HSA2 + self.mockAuthKit.hsa2 = true + XCTAssertNoThrow(try self.cuttlefishContext.idmsTrustLevelChanged(), "Notification of IDMS trust level shouldn't error") + + // Octagon should go into 'waitforcloudkit' + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitingForCloudKitAccount, within: 10 * NSEC_PER_SEC) + self.assertAccountAvailable(context: self.cuttlefishContext) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) + + // On CK account sign-in, Octagon should race to 'waitforcdp' + self.accountStatus = .available + self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfWaitingForCDP(context: self.cuttlefishContext) + + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + + // On sign-out, octagon should go back to 'no account' + self.mockAuthKit.altDSID = nil + XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "sign-out shouldn't error") + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) + self.assertNoAccount(context: self.cuttlefishContext) + + // But CKKS is listening for the CK account removal, not the accountNoLongerAvailable call + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + + self.accountStatus = .noAccount + self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) + } + + func testSAtoHSA2PromotionandCDPWithoutCloudKit() throws { + self.startCKAccountStatusMock() + + // Device is signed out + self.mockAuthKit.altDSID = nil + self.mockAuthKit.hsa2 = false + + self.accountStatus = .noAccount + self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() + // With no account, Octagon should go directly into 'NoAccount' self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) @@ -218,6 +301,10 @@ class OctagonCloudKitAccountTests: OctagonTestsBase { self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) + // And setting CDP bit now should be successful, but not move the state machine + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitingForCloudKitAccount, within: 10 * NSEC_PER_SEC) + // On CK account sign-in, Octagon should race to 'untrusted' self.accountStatus = .available self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() @@ -274,6 +361,71 @@ class OctagonCloudKitAccountTests: OctagonTestsBase { self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) } + + func testStatusRPCsWithUnknownCloudKitAccount() throws { + // If CloudKit isn't returning our calls, we should still return something reasonable... + let statusexpectation = self.expectation(description: "trust status returns") + let configuration = OTOperationConfiguration() + configuration.timeoutWaitForCKAccount = 500 * NSEC_PER_MSEC + self.cuttlefishContext.rpcTrustStatus(configuration) { egoStatus, _, _, _, _ in + XCTAssertTrue([.absent].contains(egoStatus), "Self peer should be in the 'absent' state") + statusexpectation.fulfill() + } + self.wait(for: [statusexpectation], timeout: 10) + + // Now sign in to 'untrusted' + self.startCKAccountStatusMock() + + self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) + + // And restart, without any idea of the cloudkit state + self.ckaccountHoldOperation = BlockOperation() + self.manager.accountStateTracker = CKKSAccountStateTracker(self.manager.cloudKitContainer, + nsnotificationCenterClass: FakeNSNotificationCenter.self as CKKSNSNotificationCenter.Type) + self.injectedManager!.accountTracker = self.manager.accountStateTracker + + self.manager.removeContext(forContainerName: OTCKContainerName, contextID: OTDefaultContext) + self.restartCKKSViews() + self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) + + // Should know it's untrusted + self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) + self.startCKAccountStatusMock() + + // Now become ready + do { + let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) + XCTAssertNotNil(clique, "Clique should not be nil") + } catch { + XCTFail("Shouldn't have errored making new friends: \(error)") + } + + // Now, we should be in 'ready' + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfTrusted(context: self.cuttlefishContext) + self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext) + + // and all subCKKSes should enter ready... + assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + + // Restart one more time: + + self.ckaccountHoldOperation = BlockOperation() + self.manager.accountStateTracker = CKKSAccountStateTracker(self.manager.cloudKitContainer, + nsnotificationCenterClass: FakeNSNotificationCenter.self as CKKSNSNotificationCenter.Type) + self.injectedManager!.accountTracker = self.manager.accountStateTracker + + self.manager.removeContext(forContainerName: OTCKContainerName, contextID: OTDefaultContext) + self.restartCKKSViews() + self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) + + self.assertConsidersSelfTrusted(context: self.cuttlefishContext) + self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext) + } } #endif // OCTAGON diff --git a/keychain/ot/tests/octagon/OctagonTests+CoreFollowUp.swift b/keychain/ot/tests/octagon/OctagonTests+CoreFollowUp.swift index c2692037..b639f749 100644 --- a/keychain/ot/tests/octagon/OctagonTests+CoreFollowUp.swift +++ b/keychain/ot/tests/octagon/OctagonTests+CoreFollowUp.swift @@ -16,14 +16,14 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { machineID: "asdf", bottleSalt: "123456789", bottleID: UUID().uuidString, - modelID: "asdf", + modelID: "iPhone9,1", deviceName: "asdf", serialNumber: "1234", osVersion: "asdf", policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(peerID, "Should be a peer ID") XCTAssertNotNil(permanentInfo, "Should have a permenent info") @@ -44,8 +44,9 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { XCTAssertNoThrow(try account.saveToKeychain(forContainer: containerName, contextID: contextName), "Should be no error saving fake account metadata") - OctagonInitialize() + self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) @@ -53,10 +54,10 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) #if !os(tvOS) - XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU") + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "should have posted an repair CFU") #else // Apple TV should not post a CFU, as there's no peers to join - XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "appleTV should not have posted a repair CFU") #endif } @@ -78,14 +79,14 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { machineID: "asdf", bottleSalt: "123456789", bottleID: UUID().uuidString, - modelID: "asdf", + modelID: "iPhone9,1", deviceName: "asdf", serialNumber: "1234", osVersion: "asdf", policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(peerID, "Should be a peer ID") XCTAssertNotNil(permanentInfo, "Should have a permenent info") @@ -106,8 +107,9 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { XCTAssertNoThrow(try account.saveToKeychain(forContainer: containerName, contextID: contextName), "Should be no error saving fake account metadata") - OctagonInitialize() + self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) @@ -116,17 +118,19 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { // Since SOS isn't around to help, Octagon should post a CFU #if os(tvOS) - XCTAssertEqual(self.cuttlefishContext.postedRepairCFU, false, "Should not have posted a CFU on aTV (due to having no peers to join)") + XCTAssertEqual(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), false, "Should not have posted a CFU on aTV (due to having no peers to join)") #else - XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU, as SOS can't help") + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "should have posted an repair CFU, as SOS can't help") #endif } func testAttemptedJoinNotAttemptedStateSOSError() throws { self.startCKAccountStatusMock() + // Note that some errors mean "out of circle", so use NotReady here to avoid that self.mockSOSAdapter.sosEnabled = true self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCError) + self.mockSOSAdapter.circleStatusError = NSError(domain: kSOSErrorDomain as String, code: kSOSErrorNotReady, userInfo: nil) // Prepare an identity, then pretend like securityd thought it was in the right account let containerName = OTCKContainerName @@ -140,14 +144,14 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { machineID: "asdf", bottleSalt: "123456789", bottleID: UUID().uuidString, - modelID: "asdf", + modelID: "iPhone9,1", deviceName: "asdf", serialNumber: "1234", osVersion: "asdf", policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(peerID, "Should be a peer ID") XCTAssertNotNil(permanentInfo, "Should have a permenent info") @@ -168,8 +172,9 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { XCTAssertNoThrow(try account.saveToKeychain(forContainer: containerName, contextID: contextName), "Should be no error saving fake account metadata") - OctagonInitialize() + self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) @@ -177,7 +182,7 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) // Since SOS is in 'error', octagon shouldn't post until SOS can say y/n - XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "should NOT have posted an repair CFU") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "should NOT have posted an repair CFU") } func testAttemptedJoinNotAttemptedStateSOSDisabled() throws { @@ -188,6 +193,7 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { // No need to mock not joining; Octagon won't have attempted a join if we just start it self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) @@ -195,10 +201,10 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) #if !os(tvOS) - XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU, as SOS is disabled") + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "should have posted an repair CFU, as SOS is disabled") #else // Apple TV should not post a CFU, as there's no peers to join - XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "appleTV should not have posted a repair CFU") #endif } @@ -217,14 +223,14 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { machineID: "asdf", bottleSalt: "123456789", bottleID: UUID().uuidString, - modelID: "asdf", + modelID: "iPhone9,1", deviceName: "asdf", serialNumber: "1234", osVersion: "asdf", policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(peerID, "Should be a peer ID") XCTAssertNotNil(permanentInfo, "Should have a permenent info") @@ -245,8 +251,9 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { XCTAssertNoThrow(try account.saveToKeychain(forContainer: containerName, contextID: contextName), "Should be no error saving fake account metadata") - OctagonInitialize() + self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) @@ -254,10 +261,10 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) #if !os(tvOS) - XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU") + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "should have posted an repair CFU") #else // Apple TV should not post a CFU, as there's no peers to join - XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "appleTV should not have posted a repair CFU") #endif } @@ -268,12 +275,13 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { self.mockSOSAdapter.sosEnabled = false self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) // Apple TV should not post a CFU, as there's no peers to join - XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "appleTV should not have posted a repair CFU") // Now, an iphone appears! let iphone = self.manager.context(forContainerName: OTCKContainerName, @@ -296,7 +304,7 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { // The TV should now post a CFU, as there's an iphone that can repair it self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "appleTV should have posted a repair CFU") + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "appleTV should have posted a repair CFU") } func testDontPostCFUWhenApprovalIncapablePeerJoins() throws { @@ -305,12 +313,13 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { self.mockSOSAdapter.sosEnabled = false self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) // Apple TV should not post a CFU, as there's no peers to join - XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "appleTV should not have posted a repair CFU") // Now, a mac appears! macs cannot fix apple TVs. let mac = self.manager.context(forContainerName: OTCKContainerName, @@ -333,7 +342,7 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { // The TV should not post a CFU, as there's still no iPhone to repair it self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU; no devices present can repair it") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "appleTV should not have posted a repair CFU; no devices present can repair it") } func testDontPostCFUWhenCapablePeersAreUntrusted() throws { @@ -373,6 +382,7 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { "iphone should distrust itself") self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) // Ensure that the aTV has fetched properly @@ -382,7 +392,7 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) // Apple TV should not post a CFU, as the only iPhone around is untrusted - XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU") + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "appleTV should not have posted a repair CFU") // Another iPhone resets the world let iphone2 = self.manager.context(forContainerName: OTCKContainerName, @@ -403,7 +413,7 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { // The aTV is notified, and now posts a CFU self.sendContainerChangeWaitForUntrustedFetch(context: self.cuttlefishContext) - XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "appleTV should have posted a repair CFU") + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "appleTV should have posted a repair CFU") } #endif @@ -413,14 +423,15 @@ class OctagonCoreFollowUpTests: OctagonTestsBase { self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle) self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) #if os(tvOS) - XCTAssertEqual(self.cuttlefishContext.postedRepairCFU, false, "Should not have posted a CFU on aTV (due to having no peers to join)") + XCTAssertEqual(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), false, "Should not have posted a CFU on aTV (due to having no peers to join)") #else - XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU, as SOS can't help") + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "should have posted an repair CFU, as SOS can't help") #endif } } diff --git a/keychain/ot/tests/octagon/OctagonTests+DeviceList.swift b/keychain/ot/tests/octagon/OctagonTests+DeviceList.swift index 848bb213..110b7e30 100644 --- a/keychain/ot/tests/octagon/OctagonTests+DeviceList.swift +++ b/keychain/ot/tests/octagon/OctagonTests+DeviceList.swift @@ -35,6 +35,7 @@ class OctagonDeviceListTests: OctagonTestsBase { self.startCKAccountStatusMock() self.mockAuthKit.excludeDevices.formUnion(self.mockAuthKit.currentDeviceList()) + self.mockAuthKit.isDemoAccount = true XCTAssertTrue(self.mockAuthKit.currentDeviceList().isEmpty, "should have zero devices") self.cuttlefishContext.startOctagonStateMachine() @@ -450,14 +451,13 @@ class OctagonDeviceListTests: OctagonTestsBase { let container = try self.tphClient.getContainer(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) container.moc.performAndWait { var foundPeer2 = false - for machinemo in container.containerMO.machines as? Set ?? Set() { - if machinemo.machineID == self.mockAuthKit2.currentMachineID { + for machinemo in container.containerMO.machines as? Set ?? Set() + where machinemo.machineID == self.mockAuthKit2.currentMachineID { foundPeer2 = true // machinemo.modified = Date(timeIntervalSinceNow: -60 * 60 * TimeInterval(72)) XCTAssertEqual(machinemo.status, Int64(TPMachineIDStatus.unknown.rawValue), "peer2's MID entry should be 'unknown'") } - } XCTAssertTrue(foundPeer2, "Should have found an entry for peer2") try! container.moc.save() @@ -534,6 +534,103 @@ class OctagonDeviceListTests: OctagonTestsBase { XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") } + + func testDemoAccountBypassIDMSTrustedDeviceList() throws { + // Check that we can bypass IdMS trusted device list (needed for demo accounts) + + self.startCKAccountStatusMock() + + self.mockAuthKit.excludeDevices.formUnion(self.mockAuthKit.currentDeviceList()) + self.mockAuthKit.isDemoAccount = true + XCTAssertTrue(self.mockAuthKit.currentDeviceList().isEmpty, "should have zero devices") + + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + + do { + let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) + XCTAssertNotNil(clique, "Clique should not be nil") + } catch { + XCTFail("Shouldn't have errored making new friends: \(error)") + } + + // Now, we should be in 'ready' + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfTrusted(context: self.cuttlefishContext) + self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext) + + // and all subCKKSes should enter ready... + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + + self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext) + + // And we haven't helpfully added anything to the MID list + self.assertMIDList(context: self.cuttlefishContext, allowed: Set(), disallowed: Set(), unknown: Set()) + } + + func testDemoAccountTrustPeerWhenMissingFromDeviceList() throws { + self.startCKAccountStatusMock() + + self.mockAuthKit.otherDevices.removeAll() + self.mockAuthKit.isDemoAccount = true + self.mockAuthKit2.isDemoAccount = true + + XCTAssertEqual(self.mockAuthKit.currentDeviceList(), Set([self.mockAuthKit.currentMachineID]), "AuthKit should have exactly one device on the list") + XCTAssertFalse(self.mockAuthKit.currentDeviceList().contains(self.mockAuthKit2.currentMachineID), "AuthKit should not already have device 2 on the list") + + let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() + + let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2) + let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) + + // Now, tell peer1 about the change. It will trust the peer, despite it missing from the list, and upload some TLK shares + self.assertAllCKKSViewsUpload(tlkShares: 1) + self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + + XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), + "peer 1 should trust peer 2 after update") + self.assertMIDList(context: self.cuttlefishContext, + allowed: self.mockAuthKit.currentDeviceList(), + disallowed: Set(), + unknown: Set()) + + // On a follow-up update, peer1 should _not_ hit IDMS, even though there's an unknown peer ID in its DB + let currentCount = self.mockAuthKit.fetchInvocations + self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateReadyUpdated, OctagonStateReady]) + self.assertMIDList(context: self.cuttlefishContext, + allowed: self.mockAuthKit.currentDeviceList(), + disallowed: Set(), + unknown: Set()) + XCTAssertEqual(currentCount, self.mockAuthKit.fetchInvocations, "Receving a push while having an unknown peer MID should not cause an AuthKit fetch") + + //////// + + // Then peer2 arrives on the device list. Peer 1 should update its dynamic info to no longer have a disposition for peer2. + let updateTrustExpectation = self.expectation(description: "updateTrust") + self.fakeCuttlefishServer.updateListener = { request in + XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info") + let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, + sig: request.dynamicInfoAndSig.sig) + XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf") + + // TODO: swift refuses to see the dispositions object on newDynamicInfo; ah well + updateTrustExpectation.fulfill() + return nil + } + + self.mockAuthKit.addAndSendNotification(machineID: try! self.mockAuthKit2.machineID()) + + self.wait(for: [updateTrustExpectation], timeout: 10) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + + XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), + "peer 1 should trust peer 2 after update") + } + } #endif diff --git a/keychain/ot/tests/octagon/OctagonTests+ErrorHandling.swift b/keychain/ot/tests/octagon/OctagonTests+ErrorHandling.swift index b5069a73..da28130f 100644 --- a/keychain/ot/tests/octagon/OctagonTests+ErrorHandling.swift +++ b/keychain/ot/tests/octagon/OctagonTests+ErrorHandling.swift @@ -2,6 +2,30 @@ class OctagonErrorHandlingTests: OctagonTestsBase { + func testEstablishFailedError() throws { + self.startCKAccountStatusMock() + + let establishExpectation = self.expectation(description: "establishExpectation") + + self.fakeCuttlefishServer.establishListener = { [unowned self] request in + self.fakeCuttlefishServer.establishListener = nil + + self.fakeCuttlefishServer.establish(request) { + response, error in + XCTAssertNil(error, "should be no error from establish") + XCTAssertNotNil(response, "should get a response from establish") + // drop the response on the floor + } + + establishExpectation.fulfill() + + return FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .establishFailed) + } + + _ = self.assertResetAndBecomeTrustedInDefaultContext() + self.wait(for: [establishExpectation], timeout: 10) + } + func testRecoverFromImmediateTimeoutDuringEstablish() throws { self.startCKAccountStatusMock() @@ -75,6 +99,7 @@ class OctagonErrorHandlingTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.aksLockState = true @@ -90,6 +115,7 @@ class OctagonErrorHandlingTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -146,6 +172,7 @@ class OctagonErrorHandlingTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -178,6 +205,7 @@ class OctagonErrorHandlingTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -225,6 +253,7 @@ class OctagonErrorHandlingTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -258,9 +287,9 @@ class OctagonErrorHandlingTests: OctagonTestsBase { func testPreapprovedPushWhileLocked() throws { // Peer 1 becomes SOS+Octagon - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -277,7 +306,7 @@ class OctagonErrorHandlingTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - // Peer 2 attempts to join via preapprovalh + // Peer 2 attempts to join via preapproval let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID") let peer2contextID = "peer2" let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false) @@ -324,31 +353,36 @@ class OctagonErrorHandlingTests: OctagonTestsBase { self.wait(for: [updateTrustExpectation], timeout: 100) self.fakeCuttlefishServer.updateListener = nil + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + // Now, peer 2 should lock and receive an Octagon push self.aksLockState = true self.lockStateTracker.recheck() - // Now, peer2 should receive an Octagon push, try to realize it is preapproved, and get stuck + // Now, peer2 should receive an Octagon push, try to realize it is preapproved, and get stuck waiting for an unlock + let flagCondition = peer2.stateMachine.flags.condition(forFlag: OctagonFlagCuttlefishNotification) + self.sendContainerChange(context: peer2) self.assertEnters(context: peer2, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - sleep(1) - XCTAssertTrue(peer2.stateMachine.possiblePendingFlags().contains(OctagonFlagCuttlefishNotification), "Should have recd_push pending flag") + // The pending flag should become a real flag after the lock state changes + // But it should not fire for a bit + XCTAssertNotEqual(0, flagCondition.wait(1 * NSEC_PER_SEC), "Cuttlefish Notification flag should not be removed while locked") self.aksLockState = false self.lockStateTracker.recheck() - XCTAssertEqual(self.cuttlefishContext.stateMachine.possiblePendingFlags(), [], "Should have 0 pending flags") - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + XCTAssertEqual(0, flagCondition.wait(10 * NSEC_PER_SEC), "Cuttlefish Notification flag should be removed") + XCTAssertEqual(peer2.stateMachine.possiblePendingFlags(), [], "Should have 0 pending flags") self.assertEnters(context: peer2, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) } func testReceiveMachineListUpdateWhileReadyAndLocked() throws { // Peer 1 becomes SOS+Octagon - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -383,12 +417,12 @@ class OctagonErrorHandlingTests: OctagonTestsBase { bNewOTCliqueContext.otControl = self.otcliqueContext.otControl bNewOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottle.bottleID, entropy: entropy!) - let deviceBmockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID, + let deviceBmockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID!, machineID: "b-machine-id", otherDevices: [self.mockAuthKit.currentMachineID]) let bRestoreContext = self.manager.context(forContainerName: OTCKContainerName, - contextID: bNewOTCliqueContext.context!, + contextID: bNewOTCliqueContext.context, sosAdapter: OTSOSMissingAdapter(), authKitAdapter: deviceBmockAuthKit, lockStateTracker: self.lockStateTracker, @@ -447,13 +481,14 @@ class OctagonErrorHandlingTests: OctagonTestsBase { } func testCKKSResetRecoverFromCKKSConflict() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() // But do NOT add them to the keychain // CKKS should get stuck in waitfortlk self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -473,23 +508,18 @@ class OctagonErrorHandlingTests: OctagonTestsBase { var tlkUUIDs: [CKRecordZone.ID: String] = [:] self.silentFetchesAllowed = false - self.expectCKFetchAndRun(beforeFinished: { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.expectCKFetchAndRun { + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() self.silentFetchesAllowed = true - // Use the commented version below when multi-zone support is readded to the tets - tlkUUIDs[self.manateeZoneID!] = (self.keys![self.manateeZoneID!] as? ZoneKeys)?.tlk?.uuid - /* for zoneID in self.ckksZones { tlkUUIDs[zoneID as! CKRecordZone.ID] = (self.keys![zoneID] as? ZoneKeys)?.tlk?.uuid } - */ - }) + } let resetExepctation = self.expectation(description: "reset callback is called") - self.cuttlefishContext.viewManager!.rpcResetCloudKit(nil, reason: "unit-test") { - error in + self.cuttlefishContext.viewManager!.rpcResetCloudKit(nil, reason: "unit-test") { error in XCTAssertNil(error, "should be no error resetting cloudkit") resetExepctation.fulfill() } diff --git a/keychain/ot/tests/octagon/OctagonTests+EscrowRecovery.swift b/keychain/ot/tests/octagon/OctagonTests+EscrowRecovery.swift index 64a4006f..d79263f8 100644 --- a/keychain/ot/tests/octagon/OctagonTests+EscrowRecovery.swift +++ b/keychain/ot/tests/octagon/OctagonTests+EscrowRecovery.swift @@ -1,6 +1,7 @@ #if OCTAGON -@objcMembers class OctagonEscrowRecoveryTests: OctagonTestsBase { +@objcMembers +class OctagonEscrowRecoveryTests: OctagonTestsBase { override func setUp() { super.setUp() } @@ -16,6 +17,7 @@ ckacctinfo.accountPartition = .production bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo) + XCTAssertNoThrow(try bottlerContext.setCDPEnabled()) self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -40,8 +42,8 @@ XCTAssertNotNil(entropy, "entropy should not be nil") // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: bottlerContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: bottlerContext) let bottle = self.fakeCuttlefishServer.state.bottles[0] @@ -53,7 +55,7 @@ self.holdCloudKitFetches() // Note: CKKS will want to upload a TLKShare for its self - self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.manateeZoneID) + self.assertAllCKKSViewsUpload(tlkShares: 1) // Before you call joinWithBottle, you need to call fetchViableBottles. let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs") @@ -65,7 +67,7 @@ self.wait(for: [fetchViableExpectation], timeout: 10) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } @@ -76,14 +78,13 @@ self.wait(for: [joinWithBottleExpectation], timeout: 100) let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") dumpCallback.fulfill() @@ -100,6 +101,7 @@ let initiatorContextID = "initiator-context-id" self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -126,7 +128,7 @@ initiatorContext.startOctagonStateMachine() let restoreExpectation = self.expectation(description: "restore returns") - self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: bottle.bottleID) { error in + self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID!, entropy: entropy!, bottleID: bottle.bottleID) { error in XCTAssertNil(error, "error should be nil") restoreExpectation.fulfill() } @@ -135,14 +137,13 @@ self.assertEnters(context: initiatorContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -162,6 +163,7 @@ ckacctinfo.accountPartition = .production bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo) + XCTAssertNoThrow(try bottlerContext.setCDPEnabled()) self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -189,11 +191,11 @@ // During the join, there's a CKKS key race self.silentFetchesAllowed = false - self.expectCKFetchAndRun(beforeFinished: { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.expectCKFetchAndRun { + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() self.silentFetchesAllowed = true - }) + } self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() @@ -209,7 +211,7 @@ self.wait(for: [fetchViableExpectation], timeout: 10) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } @@ -225,6 +227,7 @@ self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -252,7 +255,7 @@ differentDevice.startOctagonStateMachine() differentDevice.join(withBottle: bottle.bottleID, entropy: entropy!, - bottleSalt: self.otcliqueContext.altDSID) { error in + bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") differentRestoreExpectation.fulfill() } @@ -276,7 +279,7 @@ let restoreExpectation = self.expectation(description: "restore returns") restoreContext.join(withBottle: bottle.bottleID, entropy: entropy!, - bottleSalt: self.otcliqueContext.altDSID) { error in + bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") restoreExpectation.fulfill() } @@ -303,6 +306,7 @@ let initiatorContextID = "initiator-context-id" self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -327,25 +331,25 @@ initiatorContext.startOctagonStateMachine() + XCTAssertNoThrow(try initiatorContext.setCDPEnabled()) self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let restoreExpectation = self.expectation(description: "restore returns") - self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: bottle.bottleID) { error in + self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID!, entropy: entropy!, bottleID: bottle.bottleID) { error in XCTAssertNil(error, "error should be nil") restoreExpectation.fulfill() } self.wait(for: [restoreExpectation], timeout: 10) let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -359,6 +363,7 @@ let initiatorContextID = "initiator-context-id" self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -386,7 +391,7 @@ let restoreExpectation = self.expectation(description: "restore returns") - self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: "bad escrow record ID") { error in + self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID!, entropy: entropy!, bottleID: "bad escrow record ID") { error in XCTAssertNotNil(error, "error should not be nil") restoreExpectation.fulfill() } @@ -394,14 +399,13 @@ self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 1, "should be 1 peer ids") @@ -537,7 +541,8 @@ // We will upload a new TLK for the new peer self.assertAllCKKSViewsUpload(tlkShares: 1) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() self.sendContainerChangeWaitForFetch(context: initiatorContext) @@ -547,11 +552,10 @@ XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty") let dumpExpectation = self.expectation(description: "dump callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, error in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, error in XCTAssertNil(error, "Should be no error dumping data") XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let peerID = egoSelf!["peerID"] as? String XCTAssertNotNil(peerID, "peerID should not be nil") @@ -561,7 +565,7 @@ self.wait(for: [dumpExpectation], timeout: 10) self.otControlCLI.status(OTCKContainerName, - context: newOTCliqueContext.context!, + context: newOTCliqueContext.context, json: false) } @@ -673,6 +677,7 @@ let initiatorContextID = "initiator-context-id" self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -699,7 +704,7 @@ initiatorContext.startOctagonStateMachine() let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNotNil(error, "error should not be nil") joinWithBottleExpectation.fulfill() } @@ -713,6 +718,7 @@ let initiatorContextID = "initiator-context-id" self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -739,7 +745,7 @@ initiatorContext.startOctagonStateMachine() let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - initiatorContext.join(withBottle: "sos peer id", entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + initiatorContext.join(withBottle: "sos peer id", entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNotNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } @@ -752,6 +758,7 @@ let initiatorContextID = "initiator-context-id" self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -779,7 +786,7 @@ self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - initiatorContext.join(withBottle: bottle.bottleID, entropy: Data(count: 72), bottleSalt: self.otcliqueContext.altDSID) { error in + initiatorContext.join(withBottle: bottle.bottleID, entropy: Data(count: 72), bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNotNil(error, "error should not be nil, when entropy is missing") joinWithBottleExpectation.fulfill() } @@ -792,6 +799,7 @@ let initiatorContextID = "initiator-context-id" self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -865,12 +873,12 @@ bNewOTCliqueContext.otControl = self.otcliqueContext.otControl bNewOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottle.bottleID, entropy: entropy!) - let deviceBmockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID, + let deviceBmockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID!, machineID: "b-machine-id", otherDevices: [self.mockAuthKit.currentMachineID]) let bRestoreContext = self.manager.context(forContainerName: OTCKContainerName, - contextID: bNewOTCliqueContext.context!, + contextID: bNewOTCliqueContext.context, sosAdapter: OTSOSMissingAdapter(), authKitAdapter: deviceBmockAuthKit, lockStateTracker: self.lockStateTracker, @@ -889,7 +897,7 @@ // And introduce C, which will kick out A // During the next sign in, the machine ID list has changed to just the new one - let restoremockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID, + let restoremockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID!, machineID: "c-machine-id", otherDevices: [self.mockAuthKit.currentMachineID, deviceBmockAuthKit.currentMachineID]) let restoreContext = self.manager.context(forContainerName: OTCKContainerName, @@ -922,14 +930,13 @@ self.assertConsidersSelfTrusted(context: restoreContext) let restoreDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: newOTCliqueContext.context!) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: newOTCliqueContext.context) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 3, "should be 3 peer id included") @@ -972,6 +979,7 @@ ckacctinfo.accountPartition = .production bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo) + XCTAssertNoThrow(try bottlerContext.setCDPEnabled()) self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -996,8 +1004,8 @@ XCTAssertNotNil(entropy, "entropy should not be nil") // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: bottlerContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: bottlerContext) let bottle = self.fakeCuttlefishServer.state.bottles[0] @@ -1009,10 +1017,10 @@ self.holdCloudKitFetches() // Note: CKKS will want to upload a TLKShare for its self - self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.manateeZoneID) + self.assertAllCKKSViewsUpload(tlkShares: 1) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } @@ -1023,14 +1031,13 @@ self.wait(for: [joinWithBottleExpectation], timeout: 100) let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") dumpCallback.fulfill() @@ -1098,6 +1105,7 @@ ckacctinfo.accountPartition = .production bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo) + XCTAssertNoThrow(try bottlerContext.setCDPEnabled()) self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -1122,23 +1130,24 @@ XCTAssertNotNil(entropy, "entropy should not be nil") // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: bottlerContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: bottlerContext) let bottle = self.fakeCuttlefishServer.state.bottles[0] self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) // Try to enforce that that CKKS doesn't know about the key hierarchy until Octagon asks it self.holdCloudKitFetches() // Note: CKKS will want to upload a TLKShare for its self - self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.manateeZoneID) + self.assertAllCKKSViewsUpload(tlkShares: 1) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } @@ -1149,14 +1158,13 @@ self.wait(for: [joinWithBottleExpectation], timeout: 100) let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") dumpCallback.fulfill() @@ -1224,6 +1232,7 @@ ckacctinfo.accountPartition = .production bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo) + XCTAssertNoThrow(try bottlerContext.setCDPEnabled()) self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -1248,8 +1257,8 @@ XCTAssertNotNil(entropy, "entropy should not be nil") // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: bottlerContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: bottlerContext) let bottle = self.fakeCuttlefishServer.state.bottles[0] @@ -1261,10 +1270,10 @@ self.holdCloudKitFetches() // Note: CKKS will want to upload a TLKShare for its self - self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.manateeZoneID) + self.assertAllCKKSViewsUpload(tlkShares: 1) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } @@ -1277,15 +1286,14 @@ var egoPeerID: String? let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") egoPeerID = egoSelf!["peerID"] as? String - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") dumpCallback.fulfill() @@ -1346,6 +1354,40 @@ self.wait(for: [FetchAllViableBottles], timeout: 10) self.wait(for: [fetchUnCachedViableBottlesExpectation], timeout: 10) } + + func testRecoverTLKSharesSendByPeers() throws { + // First, set up the world: two peers, one of which has sent TLKs tto itself and the other + let noSelfSharesContext = self.makeInitiatorContext(contextID: "noShares", authKitAdapter: self.mockAuthKit2) + let allSharesContext = self.makeInitiatorContext(contextID: "allShares", authKitAdapter: self.mockAuthKit3) + + self.startCKAccountStatusMock() + let noSelfSharesPeerID = self.assertResetAndBecomeTrusted(context: noSelfSharesContext) + let allSharesPeerID = self.assertJoinViaEscrowRecovery(joiningContext: allSharesContext, sponsor: noSelfSharesContext) + + self.sendContainerChangeWaitForFetch(context: noSelfSharesContext) + self.assertEnters(context: noSelfSharesContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + + XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: noSelfSharesPeerID, opinion: .trusts, target: allSharesPeerID)), + "noShares should trust allShares") + XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: noSelfSharesPeerID, opinion: .trusts, target: noSelfSharesPeerID)), + "No shares should trust itself") + + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: allSharesContext) + try self.putAllTLKSharesInCloudKit(to: noSelfSharesContext, from: allSharesContext) + + try self.ckksZones.forEach { zone in + XCTAssertFalse(try self.tlkShareInCloudKit(receiverPeerID: noSelfSharesPeerID, senderPeerID: noSelfSharesPeerID, zoneID: zone as! CKRecordZone.ID), "Should not have self shares for noSelfShares") + XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: noSelfSharesPeerID, senderPeerID: allSharesPeerID, zoneID: zone as! CKRecordZone.ID), "Should have a share for noSelfShares from allShares") + } + + // Now, recover from noSelfShares + self.assertAllCKKSViewsUpload(tlkShares: 1) + self.assertJoinViaEscrowRecovery(joiningContext: self.cuttlefishContext, sponsor: noSelfSharesContext) + // And CKKS should enter ready! + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + } } #endif diff --git a/keychain/ot/tests/octagon/OctagonTests+ForwardCompatibility.swift b/keychain/ot/tests/octagon/OctagonTests+ForwardCompatibility.swift new file mode 100644 index 00000000..67c3928f --- /dev/null +++ b/keychain/ot/tests/octagon/OctagonTests+ForwardCompatibility.swift @@ -0,0 +1,555 @@ +#if OCTAGON + +import Foundation + +class OctagonForwardCompatibilityTests: OctagonTestsBase { + func testApprovePeerWithNewPolicy() throws { + self.startCKAccountStatusMock() + let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() + + // Now, we'll approve a new peer with a new policy! First, make that new policy. + let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first + XCTAssertNotNil(currentPolicyOptional, "Should have one current policy") + let currentPolicy = currentPolicyOptional! + + let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)! + self.fakeCuttlefishServer.policyOverlay.append(newPolicy) + + let peer2ContextID = "asdf" + + // Assist the other client here: it'll likely already have the policy built-in + let fetchExpectation = self.expectation(description: "fetch callback occurs") + self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName, + context: peer2ContextID, + versions: Set([newPolicy.version])) { _, error in + XCTAssertNil(error, "Should have no error") + fetchExpectation.fulfill() + } + self.wait(for: [fetchExpectation], timeout: 10) + + var peer2ID: String = "not initialized" + + let prepareExpectation = self.expectation(description: "prepare callback occurs") + let vouchExpectation = self.expectation(description: "vouch callback occurs") + let joinExpectation = self.expectation(description: "join callback occurs") + let serverJoinExpectation = self.expectation(description: "joinWithVoucher is called") + + self.tphClient.prepare(withContainer: OTCKContainerName, + context: peer2ContextID, + epoch: 1, + machineID: self.mockAuthKit2.currentMachineID, + bottleSalt: self.mockAuthKit2.altDSID!, + bottleID: "why-is-this-nonnil", + modelID: self.mockDeviceInfo.modelID(), + deviceName: "new-policy-peer", + serialNumber: "1234", + osVersion: "something", + policyVersion: newPolicy.version, + policySecrets: nil, + signingPrivKeyPersistentRef: nil, + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in + XCTAssertNil(error, "Should be no error preparing the second peer") + XCTAssertNotNil(peerID, "Should have a peerID") + peer2ID = peerID! + + XCTAssertNotNil(stableInfo, "Should have a stable info") + XCTAssertNotNil(stableInfoSig, "Should have a stable info signature") + + let newStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!) + XCTAssertNotNil(newStableInfo, "should be able to make a stableInfo info from protobuf") + + XCTAssertEqual(newStableInfo?.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version") + XCTAssertEqual(newStableInfo?.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version") + + self.tphClient.vouch(withContainer: self.cuttlefishContext.containerName, + context: self.cuttlefishContext.contextID, + peerID: peerID!, + permanentInfo: permanentInfo!, + permanentInfoSig: permanentInfoSig!, + stableInfo: stableInfo!, + stableInfoSig: stableInfoSig!, + ckksKeys: []) { voucher, voucherSig, error in + XCTAssertNil(error, "Should be no error vouching") + XCTAssertNotNil(voucher, "Should have a voucher") + XCTAssertNotNil(voucherSig, "Should have a voucher signature") + + self.fakeCuttlefishServer.joinListener = { joinRequest in + XCTAssertEqual(peer2ID, joinRequest.peer.peerID, "joinWithVoucher request should be for peer 2") + XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info") + let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version (as provided by new peer)") + + serverJoinExpectation.fulfill() + return nil + } + + self.tphClient.join(withContainer: OTCKContainerName, + context: peer2ContextID, + voucherData: voucher!, + voucherSig: voucherSig!, + ckksKeys: [], + tlkShares: [], + preapprovedKeys: []) { peerID, _, _, _, error in + XCTAssertNil(error, "Should be no error joining") + XCTAssertNotNil(peerID, "Should have a peerID") + joinExpectation.fulfill() + } + vouchExpectation.fulfill() + } + prepareExpectation.fulfill() + } + self.wait(for: [prepareExpectation, vouchExpectation, joinExpectation, serverJoinExpectation], timeout: 10) + + // Then, after the remote peer joins, the original peer should realize it trusts the new peer and update its own stableinfo to use the new policy + let updateTrustExpectation = self.expectation(description: "updateTrust") + self.fakeCuttlefishServer.updateListener = { request in + XCTAssertEqual(peer1ID, request.peerID, "updateTrust request should be for peer 1") + let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo() + XCTAssert(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should trust peer2") + + let newStableInfo = request.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Prevailing policy version in peer should match new policy version") + + updateTrustExpectation.fulfill() + return nil + } + + self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) + self.wait(for: [updateTrustExpectation], timeout: 10) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + } + + func testRejectVouchingForPeerWithUnknownNewPolicy() throws { + self.startCKAccountStatusMock() + _ = self.assertResetAndBecomeTrustedInDefaultContext() + + // Now, a new peer joins with a policy we can't fetch + let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first + XCTAssertNotNil(currentPolicyOptional, "Should have one current policy") + let currentPolicy = currentPolicyOptional! + + let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)! + + let peer2ContextID = "asdf" + + // Assist the other client here: it'll likely already have this built-in + self.fakeCuttlefishServer.policyOverlay.append(newPolicy) + + let fetchExpectation = self.expectation(description: "fetch callback occurs") + self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName, + context: peer2ContextID, + versions: Set([newPolicy.version])) { _, error in + XCTAssertNil(error, "Should have no error") + fetchExpectation.fulfill() + } + self.wait(for: [fetchExpectation], timeout: 10) + + // Remove the policy, now that peer2 has it + self.fakeCuttlefishServer.policyOverlay.removeAll() + + let prepareExpectation = self.expectation(description: "prepare callback occurs") + let vouchExpectation = self.expectation(description: "vouch callback occurs") + + self.tphClient.prepare(withContainer: OTCKContainerName, + context: peer2ContextID, + epoch: 1, + machineID: self.mockAuthKit2.currentMachineID, + bottleSalt: self.mockAuthKit2.altDSID!, + bottleID: "why-is-this-nonnil", + modelID: self.mockDeviceInfo.modelID(), + deviceName: "new-policy-peer", + serialNumber: "1234", + osVersion: "something", + policyVersion: newPolicy.version, + policySecrets: nil, + signingPrivKeyPersistentRef: nil, + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in + XCTAssertNil(error, "Should be no error preparing the second peer") + XCTAssertNotNil(peerID, "Should have a peerID") + + XCTAssertNotNil(stableInfo, "Should have a stable info") + XCTAssertNotNil(stableInfoSig, "Should have a stable info signature") + + let newStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!) + XCTAssertNotNil(newStableInfo, "should be able to make a stableInfo info from protobuf") + + XCTAssertEqual(newStableInfo?.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version") + XCTAssertEqual(newStableInfo?.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version") + + self.tphClient.vouch(withContainer: self.cuttlefishContext.containerName, + context: self.cuttlefishContext.contextID, + peerID: peerID!, + permanentInfo: permanentInfo!, + permanentInfoSig: permanentInfoSig!, + stableInfo: stableInfo!, + stableInfoSig: stableInfoSig!, + ckksKeys: []) { voucher, voucherSig, error in + XCTAssertNotNil(error, "should be an error vouching for a peer with an unknown policy") + XCTAssertNil(voucher, "Should have no voucher") + XCTAssertNil(voucherSig, "Should have no voucher signature") + + vouchExpectation.fulfill() + } + prepareExpectation.fulfill() + } + self.wait(for: [prepareExpectation, vouchExpectation], timeout: 10) + } + + func testIgnoreAlreadyJoinedPeerWithUnknownNewPolicy() throws { + self.startCKAccountStatusMock() + let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() + + // Now, a new peer joins with a policy we can't fetch + let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first + XCTAssertNotNil(currentPolicyOptional, "Should have one current policy") + let currentPolicy = currentPolicyOptional! + + let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)! + + let peer2ContextID = "asdf" + + // Assist the other client here: it'll likely already have this built-in + self.fakeCuttlefishServer.policyOverlay.append(newPolicy) + + let fetchExpectation = self.expectation(description: "fetch callback occurs") + self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName, + context: peer2ContextID, + versions: Set([newPolicy.version])) { _, error in + XCTAssertNil(error, "Should have no error") + fetchExpectation.fulfill() + } + self.wait(for: [fetchExpectation], timeout: 10) + + // Remove the policy, now that peer2 has it + self.fakeCuttlefishServer.policyOverlay.removeAll() + + let joiningContext = self.makeInitiatorContext(contextID: peer2ContextID, authKitAdapter: self.mockAuthKit2) + joiningContext.policyOverride = newPolicy.version + + let serverJoinExpectation = self.expectation(description: "peer2 joins successfully") + self.fakeCuttlefishServer.joinListener = { joinRequest in + XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info") + let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version (as provided by new peer)") + + serverJoinExpectation.fulfill() + return nil + } + + _ = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) + self.wait(for: [serverJoinExpectation], timeout: 10) + + // Then, after the remote peer joins, the original peer should ignore it entirely: peer1 has no idea what this new policy is about + // That means it won't update its trust in response to the join + self.fakeCuttlefishServer.updateListener = { request in + XCTFail("Expected no updateTrust after peer1 joins") + XCTAssertEqual(peer1ID, request.peerID, "updateTrust request should be for peer 1") + /* + * But, if it did update its trust, here's what we would expect: + let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo() + XCTAssertFalse(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should not trust peer2") + XCTAssertFalse(newDynamicInfo.excludedPeerIDs.contains(peer2ID), "Peer1 should not distrust peer2") + + let newStableInfo = request.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, prevailingPolicyVersion, "Prevailing policy version in peer should match current prevailing policy version") + + updateTrustExpectation.fulfill() + */ + return nil + } + + self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + } + + func createOctagonAndCKKSUsingFuturePolicy() throws -> (TPPolicyDocument, CKRecordZone.ID) { + // We want to set up a world with a peer, in Octagon, with TLKs for zones that don't even exist in our current policy. + // First, make a new policy. + let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first + XCTAssertNotNil(currentPolicyOptional, "Should have one current policy") + let currentPolicyDocument = currentPolicyOptional! + + let futureViewName = "FutureView" + let futureViewMapping = TPPBPolicyKeyViewMapping()! + + let futureViewZoneID = CKRecordZone.ID(zoneName: futureViewName) + self.ckksZones.add(futureViewZoneID) + self.injectedManager!.setSyncingViewsAllowList(Set((self.intendedCKKSZones.union([futureViewZoneID])).map { $0.zoneName })) + + self.zones![futureViewZoneID] = FakeCKZone(zone: futureViewZoneID) + + XCTAssertFalse(currentPolicyDocument.categoriesByView.keys.contains(futureViewName), "Current policy should not include future view") + + let newPolicyDocument = try TPPolicyDocument(internalVersion: currentPolicyDocument.version.versionNumber + 1, + modelToCategory: currentPolicyDocument.modelToCategory, + categoriesByView: currentPolicyDocument.categoriesByView.merging([futureViewName: Set(["watch", "full", "tv"])]) { _, new in new }, + introducersByCategory: currentPolicyDocument.introducersByCategory, + redactions: [:], + keyViewMapping: [futureViewMapping] + currentPolicyDocument.keyViewMapping, + hashAlgo: .SHA256) + + self.fakeCuttlefishServer.policyOverlay.append(newPolicyDocument) + + return (newPolicyDocument, futureViewZoneID) + } + + func testRestoreBottledPeerUsingFuturePolicy() throws { + let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy() + + let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer") + futurePeerContext.policyOverride = newPolicyDocument.version + + self.startCKAccountStatusMock() + let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext) + + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: futurePeerContext) + XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID)) + + // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via escrow recovery. + // It should be able to recover the FutureView TLK + self.assertAllCKKSViewsUpload(tlkShares: 1) + + let serverJoinExpectation = self.expectation(description: "peer1 joins successfully") + self.fakeCuttlefishServer.joinListener = { joinRequest in + XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info") + let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version") + + serverJoinExpectation.fulfill() + return nil + } + + let peerID = self.assertJoinViaEscrowRecovery(joiningContext: self.cuttlefishContext, sponsor: futurePeerContext) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy") + self.verifyDatabaseMocks() + + self.wait(for: [serverJoinExpectation], timeout: 10) + + // And the joined peer should have recovered the TLK, and uploaded itself a share + XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: peerID, senderPeerID: peerID, zoneID: futureViewZoneID)) + } + + func testPairingJoinUsingFuturePolicy() throws { + let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy() + + let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer") + futurePeerContext.policyOverride = newPolicyDocument.version + + self.startCKAccountStatusMock() + let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext) + + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: futurePeerContext) + XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID)) + + // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via pairing + // It should be able to recover the FutureView TLK + self.assertAllCKKSViewsUpload(tlkShares: 1) + + let serverJoinExpectation = self.expectation(description: "peer1 joins successfully") + self.fakeCuttlefishServer.joinListener = { joinRequest in + XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info") + let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version") + + serverJoinExpectation.fulfill() + return nil + } + + let peerID = self.assertJoinViaProximitySetup(joiningContext: self.cuttlefishContext, sponsor: futurePeerContext) + + // And then fake like the other peer uploaded TLKShares after the join succeeded (it would normally happen during, but that's okay) + try self.putAllTLKSharesInCloudKit(to: self.cuttlefishContext, from: futurePeerContext) + self.sendAllCKKSViewsZoneChanged() + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy") + self.verifyDatabaseMocks() + + self.wait(for: [serverJoinExpectation], timeout: 10) + + // And the joined peer should have recovered the TLK, and uploaded itself a share + XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: peerID, senderPeerID: peerID, zoneID: futureViewZoneID)) + } + + func testRecoveryKeyJoinUsingFuturePolicy() throws { + let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy() + + let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer") + futurePeerContext.policyOverride = newPolicyDocument.version + + self.startCKAccountStatusMock() + let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext) + + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: futurePeerContext) + XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID)) + + // Create the recovery key + let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String + XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") + + self.manager.setSOSEnabledForPlatformFlag(true) + + let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns") + self.manager.createRecoveryKey(OTCKContainerName, contextID: futurePeerContext.contextID, recoveryKey: recoveryKey) { error in + XCTAssertNil(error, "error should be nil") + createRecoveryExpectation.fulfill() + } + self.wait(for: [createRecoveryExpectation], timeout: 10) + + // Setting the RK will make TLKShares to the RK, so help that out too + try self.putRecoveryKeyTLKSharesInCloudKit(recoveryKey: recoveryKey, salt: self.mockAuthKit.altDSID!) + + // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via RecoveryKey + let serverJoinExpectation = self.expectation(description: "peer1 joins successfully") + self.fakeCuttlefishServer.joinListener = { joinRequest in + XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info") + let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version") + + serverJoinExpectation.fulfill() + return nil + } + + // It should recover and upload the FutureView TLK + self.assertAllCKKSViewsUpload(tlkShares: 1) + + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + + let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs") + self.cuttlefishContext.join(withRecoveryKey: recoveryKey) { error in + XCTAssertNil(error, "error should be nil") + joinWithRecoveryKeyExpectation.fulfill() + } + self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy") + self.verifyDatabaseMocks() + + self.wait(for: [serverJoinExpectation], timeout: 10) + } + + func testPreapprovedJoinUsingFuturePolicy() throws { + let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: self.createSOSPeer(peerID: "peer2ID"), trustedPeers: self.mockSOSAdapter.allPeers(), essential: false) + print(peer2mockSOS.allPeers()) + self.mockSOSAdapter.trustedPeers.add(peer2mockSOS.selfPeer) + + let futurePeerContext = self.manager.context(forContainerName: OTCKContainerName, + contextID: "futurePeer", + sosAdapter: peer2mockSOS, + authKitAdapter: self.mockAuthKit2, + lockStateTracker: self.lockStateTracker, + accountStateTracker: self.accountStateTracker, + deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-SOS-iphone", serialNumber: "456", osVersion: "iOS (fake version)")) + + let (newPolicyDocument, _) = try self.createOctagonAndCKKSUsingFuturePolicy() + futurePeerContext.policyOverride = newPolicyDocument.version + + let serverEstablishExpectation = self.expectation(description: "futurePeer establishes successfully") + self.fakeCuttlefishServer.establishListener = { establishRequest in + XCTAssertTrue(establishRequest.peer.hasStableInfoAndSig, "Establishing peer should have a stable info") + let newStableInfo = establishRequest.peer.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version") + serverEstablishExpectation.fulfill() + return nil + } + + // Setup is complete. Join using a new peer + self.startCKAccountStatusMock() + futurePeerContext.startOctagonStateMachine() + self.assertEnters(context: futurePeerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.wait(for: [serverEstablishExpectation], timeout: 10) + + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: futurePeerContext) + + // Now, the default peer joins via SOS preapproval + let serverJoinExpectation = self.expectation(description: "peer1 joins successfully") + self.fakeCuttlefishServer.joinListener = { joinRequest in + XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info") + let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version") + serverJoinExpectation.fulfill() + return nil + } + + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle) + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfTrusted(context: self.cuttlefishContext) + self.wait(for: [serverJoinExpectation], timeout: 10) + + // But, since we're not mocking the remote peer sharing the TLKs, ckks should get stuck + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC) + XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy") + self.verifyDatabaseMocks() + } + + func testRespondToFuturePoliciesInPeerUpdates() throws { + self.startCKAccountStatusMock() + self.assertResetAndBecomeTrustedInDefaultContext() + + // Now, another peer comes along and joins via BP recovery, using a new policy + let (newPolicyDocument, _) = try self.createOctagonAndCKKSUsingFuturePolicy() + + let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer") + futurePeerContext.policyOverride = newPolicyDocument.version + + let serverJoinExpectation = self.expectation(description: "futurePeer joins successfully") + self.fakeCuttlefishServer.joinListener = { joinRequest in + XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info") + let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version") + serverJoinExpectation.fulfill() + return nil + } + + let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: futurePeerContext, sponsor: self.cuttlefishContext) + self.wait(for: [serverJoinExpectation], timeout: 10) + + // Now, tell our first peer about the new changes. It should trust the new peer, and update its policy + let updateTrustExpectation = self.expectation(description: "updateTrustExpectation successfully") + self.fakeCuttlefishServer.updateListener = { request in + let newStableInfo = request.stableInfoAndSig.stableInfo() + + XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version") + XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version") + + let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo() + XCTAssert(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should trust peer2") + + updateTrustExpectation.fulfill() + return nil + } + + self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) + self.wait(for: [updateTrustExpectation], timeout: 10) + } +} + +#endif diff --git a/keychain/ot/tests/octagon/OctagonTests+HealthCheck.swift b/keychain/ot/tests/octagon/OctagonTests+HealthCheck.swift index e1fd51bd..6591eb61 100644 --- a/keychain/ot/tests/octagon/OctagonTests+HealthCheck.swift +++ b/keychain/ot/tests/octagon/OctagonTests+HealthCheck.swift @@ -9,6 +9,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -39,10 +40,9 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.wait(for: [healthCheckCallback], timeout: 10) let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() } @@ -56,6 +56,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let healthCheckCallback = self.expectation(description: "healthCheckCallback callback occurs") @@ -69,16 +70,16 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) #if os(tvOS) - XCTAssertEqual(self.cuttlefishContext.postedRepairCFU, false, "Should not have posted a CFU on aTV") + XCTAssertEqual(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), false, "Should not have posted a CFU on aTV") #else - XCTAssertEqual(self.cuttlefishContext.postedRepairCFU, true, "Should have posted a CFU (due to being untrusted)") + XCTAssertEqual(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), true, "Should have posted a CFU (due to being untrusted)") #endif self.verifyDatabaseMocks() self.assertEnters(context: cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) // Reset flag for remainder of test - self.cuttlefishContext.setPostedBool(false) + self.cuttlefishContext.followupHandler.clearAllPostedFlags() // Set the "have I attempted to join" bit; TVs should still not CFU, but other devices should try! self.cuttlefishContext.accountMetadataStore.persistOctagonJoinAttempt(.ATTEMPTED) @@ -91,9 +92,9 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.wait(for: [healthCheckCallback2], timeout: 10) #if os(tvOS) - XCTAssertEqual(self.cuttlefishContext.postedRepairCFU, false, "Should not have posted a CFU on aTV") + XCTAssertEqual(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), false, "Should not have posted a CFU on aTV") #else - XCTAssertEqual(self.cuttlefishContext.postedRepairCFU, true, "Should have posted a CFU") + XCTAssertEqual(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), true, "Should have posted a CFU") #endif } @@ -104,6 +105,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -140,14 +142,13 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.assertConsidersSelfTrusted(context: cuttlefishContext) let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 1, "should be 1 peer ids") dumpCallback.fulfill() @@ -165,6 +166,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -210,19 +212,20 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.wait(for: [healthCheckCallback], timeout: 10) #if !os(tvOS) - XCTAssertEqual(cuttlefishContext.postedRepairCFU, true, "Should have posted a CFU") + XCTAssertTrue(cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Should have posted a CFU") #else - XCTAssertFalse(cuttlefishContext.postedRepairCFU, "aTV should not have posted a CFU, as there's no iphone to recover from") + XCTAssertFalse(cuttlefishContext.followupHandler.hasPosted(.stateRepair), "aTV should not have posted a CFU, as there's no iphone to recover from") #endif } - func responseTestsSetup() throws -> (OTCuttlefishContext, String) { + func responseTestsSetup(expectedState: String) throws -> (OTCuttlefishContext, String) { let containerName = OTCKContainerName let contextName = OTDefaultContext self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -249,6 +252,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { // Reset any CFUs we've done so far self.otFollowUpController.postedFollowUp = false + self.cuttlefishContext.followupHandler.clearAllPostedFlags() let healthCheckCallback = self.expectation(description: "healthCheckCallback callback occurs") self.manager.healthCheck(containerName, context: contextName, skipRateLimitingCheck: false) { error in @@ -258,57 +262,55 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.wait(for: [healthCheckCallback], timeout: 10) let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() } self.wait(for: [dumpCallback], timeout: 10) self.verifyDatabaseMocks() - self.assertEnters(context: cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertEnters(context: cuttlefishContext, state: expectedState, within: 10 * NSEC_PER_SEC) return (cuttlefishContext, originalCliqueIdentifier!) } func testCuttlefishResponseNoAction() throws { self.fakeCuttlefishServer.returnNoActionResponse = true - let (cuttlefishContext, _) = try responseTestsSetup() + let (cuttlefishContext, _) = try responseTestsSetup(expectedState: OctagonStateReady) XCTAssertFalse(self.otFollowUpController.postedFollowUp, "should not have posted a CFU") - XCTAssertEqual(cuttlefishContext.postedRepairCFU, false, "should not have posted a CFU") + XCTAssertFalse(cuttlefishContext.followupHandler.hasPosted(.stateRepair), "should not have posted a Repair CFU") } func testCuttlefishResponseRepairAccount() throws { self.fakeCuttlefishServer.returnRepairAccountResponse = true - let (_, _) = try responseTestsSetup() - XCTAssertTrue(self.otFollowUpController.postedFollowUp, "should have posted a CFU") + let (_, _) = try responseTestsSetup(expectedState: OctagonStateReady) + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "should have posted a Repair CFU") } func testCuttlefishResponseRepairEscrow() throws { self.fakeCuttlefishServer.returnRepairEscrowResponse = true OTMockSecEscrowRequest.self.populateStatuses = false - let (cuttlefishContext, _) = try responseTestsSetup() + let (cuttlefishContext, _) = try responseTestsSetup(expectedState: OctagonStateReady) XCTAssertTrue(self.otFollowUpController.postedFollowUp, "should have posted a CFU") - XCTAssertEqual(cuttlefishContext.postedEscrowRepairCFU, true, "should have posted an escrow CFU") + XCTAssertTrue(cuttlefishContext.followupHandler.hasPosted(.offlinePasscodeChange), "should have posted an escrow CFU") } func testCuttlefishResponseResetOctagon() throws { let contextName = OTDefaultContext let containerName = OTCKContainerName self.fakeCuttlefishServer.returnResetOctagonResponse = true - let (cuttlefishContext, cliqueIdentifier) = try responseTestsSetup() + let (cuttlefishContext, cliqueIdentifier) = try responseTestsSetup(expectedState: OctagonStateReady) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() var newCliqueIdentifier: String? let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] newCliqueIdentifier = egoSelf!["peerID"]! as? String XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() @@ -321,12 +323,22 @@ class OctagonHealthCheckTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) } + func testCuttlefishResponseLeaveTrust() throws { + OctagonSetSOSFeatureEnabled(false) + + self.fakeCuttlefishServer.returnLeaveTrustResponse = true + let (_, _) = try responseTestsSetup(expectedState: OctagonStateUntrusted) + + self.verifyDatabaseMocks() + assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC) + } + func testCuttlefishResponseError() throws { self.fakeCuttlefishServer.returnRepairErrorResponse = FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired) - let (cuttlefishContext, _) = try responseTestsSetup() - XCTAssertEqual(cuttlefishContext.postedRepairCFU, false, "should not have posted an account repair CFU") - XCTAssertEqual(cuttlefishContext.postedEscrowRepairCFU, false, "should not have posted an escrow repair CFU") + let (cuttlefishContext, _) = try responseTestsSetup(expectedState: OctagonStateReady) + XCTAssertFalse(cuttlefishContext.followupHandler.hasPosted(.stateRepair), "should not have posted an account repair CFU") + XCTAssertFalse(cuttlefishContext.followupHandler.hasPosted(.offlinePasscodeChange), "should not have posted an escrow repair CFU") } func testHealthCheckBeforeStateMachineStarts() throws { @@ -350,6 +362,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -378,10 +391,9 @@ class OctagonHealthCheckTests: OctagonTestsBase { } let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() } @@ -395,6 +407,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { let containerName = OTCKContainerName let contextName = OTDefaultContext + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() @@ -430,10 +443,9 @@ class OctagonHealthCheckTests: OctagonTestsBase { } let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() } @@ -450,6 +462,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let healthCheckCallback = self.expectation(description: "healthCheckCallback callback occurs") @@ -481,10 +494,9 @@ class OctagonHealthCheckTests: OctagonTestsBase { } let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() } @@ -523,6 +535,59 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) } + func testHealthCheckWaitingForCDP() throws { + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + self.cuttlefishContext.startOctagonStateMachine() + self.startCKAccountStatusMock() + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + + self.cuttlefishContext.stateMachine.setWatcherTimeout(2 * NSEC_PER_SEC) + + let healthCheckCallback = self.expectation(description: "healthCheckCallback callback occurs") + self.manager.healthCheck(OTCKContainerName, context: self.cuttlefishContext.contextID, skipRateLimitingCheck: false) { error in + XCTAssertNil(error, "error should be nil") + healthCheckCallback.fulfill() + } + self.wait(for: [healthCheckCallback], timeout: 10) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + } + + func testHealthCheckRecoversFromWrongWaitingForCDP() throws { + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + + self.cuttlefishContext.startOctagonStateMachine() + self.startCKAccountStatusMock() + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + + // Now, another client creates the circle, but we miss the push + let remote = self.makeInitiatorContext(contextID: "remote") + self.assertResetAndBecomeTrusted(context: remote) + + // Now, does the health check get us into Untrusted? + self.cuttlefishContext.stateMachine.setWatcherTimeout(2 * NSEC_PER_SEC) + + let healthCheckCallback = self.expectation(description: "healthCheckCallback callback occurs") + self.manager.healthCheck(OTCKContainerName, context: self.cuttlefishContext.contextID, skipRateLimitingCheck: false) { error in + XCTAssertNil(error, "error should be nil") + healthCheckCallback.fulfill() + } + self.wait(for: [healthCheckCallback], timeout: 10) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + + #if !os(tvOS) + XCTAssertTrue(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "Octagon should have posted a repair CFU after the health check") + #else + XCTAssertFalse(self.cuttlefishContext.followupHandler.hasPosted(.stateRepair), "posted should be false on tvOS; there aren't any iphones around to repair it") + #endif + } + func testHealthCheckWhenLocked() throws { let containerName = OTCKContainerName @@ -531,6 +596,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -564,10 +630,9 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.wait(for: [healthCheckCallback], timeout: 10) let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() } @@ -584,6 +649,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -623,10 +689,9 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.wait(for: [healthCheckCallback], timeout: 10) let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() } @@ -660,7 +725,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { //update the last health check to something way in the past do { let state = try OTAccountMetadataClassC.loadFromKeychain(forContainer: OTCKContainerName, contextID: OTDefaultContext) - state.lastHealthCheckup = state.lastHealthCheckup - 172800000 /* 2 days of seconds * 1000*/ + state.lastHealthCheckup -= 172800000 /* 2 days of seconds * 1000*/ healthCheckMinusTwoDays = state.lastHealthCheckup XCTAssertNoThrow(try state.saveToKeychain(forContainer: OTCKContainerName, contextID: OTDefaultContext), "saving to the keychain should work") } catch { @@ -705,6 +770,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { bottlerContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try bottlerContext.setCDPEnabled()) self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -729,13 +795,14 @@ class OctagonHealthCheckTests: OctagonTestsBase { XCTAssertNotNil(entropy, "entropy should not be nil") // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: bottlerContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: bottlerContext) let bottle = self.fakeCuttlefishServer.state.bottles[0] self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) // cheat: a bottle restore can only succeed after a fetch occurs @@ -766,7 +833,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { } let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNotNil(error, "error should not be nil") joinWithBottleExpectation.fulfill() } @@ -783,8 +850,8 @@ class OctagonHealthCheckTests: OctagonTestsBase { func testCuttlefishDontPostEscrowCFUDueToPendingPrecord() throws { self.fakeCuttlefishServer.returnRepairEscrowResponse = true OTMockSecEscrowRequest.self.populateStatuses = true - let (cuttlefishContext, _) = try responseTestsSetup() - XCTAssertEqual(cuttlefishContext.postedEscrowRepairCFU, false, "should NOT have posted an escrow CFU") + let (cuttlefishContext, _) = try responseTestsSetup(expectedState: OctagonStateReady) + XCTAssertFalse(cuttlefishContext.followupHandler.hasPosted(.offlinePasscodeChange), "should NOT have posted an escrow CFU") } func testHealthCheckWhileLocked() throws { @@ -794,6 +861,7 @@ class OctagonHealthCheckTests: OctagonTestsBase { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique diff --git a/keychain/ot/tests/octagon/OctagonTests+Helpers.swift b/keychain/ot/tests/octagon/OctagonTests+Helpers.swift new file mode 100644 index 00000000..70ed71e6 --- /dev/null +++ b/keychain/ot/tests/octagon/OctagonTests+Helpers.swift @@ -0,0 +1,33 @@ +#if OCTAGON +import Foundation + +extension SignedPeerStableInfo { + func stableInfo() -> TPPeerStableInfo { + let newStableInfo = TPPeerStableInfo(data: self.peerStableInfo, sig: self.sig) + XCTAssertNotNil(newStableInfo, "should be able to make a stableInfo from protobuf") + return newStableInfo! + } +} + +extension SignedPeerDynamicInfo { + func dynamicInfo() -> TPPeerDynamicInfo { + let newDynamicInfo = TPPeerDynamicInfo(data: self.peerDynamicInfo, sig: self.sig) + XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamicInfo from protobuf") + return newDynamicInfo! + } +} + +extension EstablishRequest { + func permanentInfo() -> TPPeerPermanentInfo { + XCTAssertTrue(self.hasPeer, "establish request should have a peer") + XCTAssertTrue(self.peer.hasPermanentInfoAndSig, "establish request should have a permanentInfo") + let newPermanentInfo = TPPeerPermanentInfo(peerID: self.peer.peerID, + data: self.peer.permanentInfoAndSig.peerPermanentInfo, + sig: self.peer.permanentInfoAndSig.sig, + keyFactory: TPECPublicKeyFactory()) + XCTAssertNotNil(newPermanentInfo, "should be able to make a permanantInfo from protobuf") + return newPermanentInfo! + } +} + +#endif diff --git a/keychain/ot/tests/octagon/OctagonTests+RecoveryKey.swift b/keychain/ot/tests/octagon/OctagonTests+RecoveryKey.swift index ad69de5b..10172e35 100644 --- a/keychain/ot/tests/octagon/OctagonTests+RecoveryKey.swift +++ b/keychain/ot/tests/octagon/OctagonTests+RecoveryKey.swift @@ -1,6 +1,18 @@ #if OCTAGON -@objcMembers class OctagonRecoveryKeyTests: OctagonTestsBase { +extension Container { + func removeRKFromContainer() { + self.moc.performAndWait { + self.containerMO.recoveryKeySigningSPKI = nil + self.containerMO.recoveryKeyEncryptionSPKI = nil + + try! self.moc.save() + } + } +} + +@objcMembers +class OctagonRecoveryKeyTests: OctagonTestsBase { override func setUp() { super.setUp() } @@ -10,6 +22,7 @@ self.manager.setSOSEnabledForPlatformFlag(false) self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) XCTAssertFalse(self.mockAuthKit.currentDeviceList().isEmpty, "should not have zero devices") @@ -34,7 +47,7 @@ self.manager.setSOSEnabledForPlatformFlag(true) let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") - self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in + self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context, recoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") createKeyExpectation.fulfill() } @@ -46,6 +59,7 @@ self.manager.setSOSEnabledForPlatformFlag(false) self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -66,7 +80,7 @@ self.manager.setSOSEnabledForPlatformFlag(true) let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") - self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in + self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context, recoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") createKeyExpectation.fulfill() } @@ -89,7 +103,7 @@ self.sendContainerChange(context: initiatorContext) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } @@ -102,20 +116,19 @@ self.verifyDatabaseMocks() let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -124,20 +137,19 @@ self.wait(for: [stableInfoCheckDumpCallback], timeout: 10) let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context ?? "defaultContext") { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -152,6 +164,7 @@ self.manager.setSOSEnabledForPlatformFlag(false) self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -172,7 +185,7 @@ self.manager.setSOSEnabledForPlatformFlag(true) let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") - self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in + self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context, recoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") createKeyExpectation.fulfill() } @@ -196,7 +209,7 @@ self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } @@ -213,20 +226,19 @@ self.verifyDatabaseMocks() let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -235,20 +247,19 @@ self.wait(for: [stableInfoCheckDumpCallback], timeout: 10) let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context ?? "defaultContext") { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -263,7 +274,7 @@ self.sendContainerChange(context: thirdPeerContext) let thirdPeerJoinWithBottleExpectation = self.expectation(description: "thirdPeerJoinWithBottleExpectation callback occurs") - thirdPeerContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in + thirdPeerContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in XCTAssertNil(error, "error should be nil") thirdPeerJoinWithBottleExpectation.fulfill() } @@ -273,20 +284,19 @@ self.sendContainerChangeWaitForFetch(context: thirdPeerContext) let thirdPeerStableInfoCheckDumpCallback = self.expectation(description: "thirdPeerStableInfoCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: thirdPeerContextID) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: thirdPeerContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 3, "should be 3df peer ids") @@ -315,6 +325,7 @@ let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() + XCTAssertNoThrow(try establishContext.setCDPEnabled()) self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -336,8 +347,8 @@ self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: establishContext) self.assertSelfTLKSharesInCloudKit(context: establishContext) @@ -372,20 +383,19 @@ self.sendContainerChangeWaitForFetch(context: recoveryContext) let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] @@ -397,20 +407,19 @@ self.sendContainerChangeWaitForFetch(context: establishContext) let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] @@ -418,7 +427,7 @@ stableInfoAcceptorCheckDumpCallback.fulfill() } self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10) - try self.putSelfTLKShareInCloudKit(context: recoveryContext, zoneID: self.manateeZoneID) + try self.putSelfTLKSharesInCloudKit(context: recoveryContext) self.assertSelfTLKSharesInCloudKit(context: recoveryContext) } @@ -431,6 +440,7 @@ let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() + XCTAssertNoThrow(try establishContext.setCDPEnabled()) self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -466,11 +476,11 @@ self.sendContainerChangeWaitForFetch(context: establishContext) self.silentFetchesAllowed = false - self.expectCKFetchAndRun(beforeFinished: { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.expectCKFetchAndRun { + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() self.silentFetchesAllowed = true - }) + } let recoveryContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) recoveryContext.startOctagonStateMachine() @@ -497,6 +507,7 @@ let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() + XCTAssertNoThrow(try establishContext.setCDPEnabled()) self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -532,6 +543,7 @@ let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() + XCTAssertNoThrow(try establishContext.setCDPEnabled()) self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -575,6 +587,7 @@ let initiatorContextID = "initiator-context-id" self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -607,7 +620,7 @@ self.sendContainerChange(context: initiatorContext) let restoreExpectation = self.expectation(description: "restore returns") - self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: bottle.bottleID) { error in + self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID!, entropy: entropy!, bottleID: bottle.bottleID) { error in XCTAssertNil(error, "error should be nil") restoreExpectation.fulfill() } @@ -616,14 +629,13 @@ self.assertEnters(context: initiatorContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) var initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -662,15 +674,14 @@ //now let's ensure recovery keys are set for both the first device and second device initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") @@ -678,7 +689,7 @@ initiatorRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data initiatorRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] @@ -688,15 +699,14 @@ self.wait(for: [initiatorDumpCallback], timeout: 10) let firstDeviceDumpCallback = self.expectation(description: "firstDeviceDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") @@ -704,7 +714,7 @@ firstDeviceRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data firstDeviceRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] @@ -726,6 +736,7 @@ let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() + XCTAssertNoThrow(try establishContext.setCDPEnabled()) self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -747,8 +758,8 @@ self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: establishContext) self.assertSelfTLKSharesInCloudKit(context: establishContext) @@ -787,20 +798,19 @@ self.sendContainerChangeWaitForFetch(context: newGuyContext) let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] @@ -810,26 +820,25 @@ self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10) self.assertEnters(context: newGuyContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: newGuyContext) - try self.putSelfTLKShareInCloudKit(context: newGuyContext, zoneID: self.manateeZoneID) + try self.putSelfTLKSharesInCloudKit(context: newGuyContext) self.assertSelfTLKSharesInCloudKit(context: newGuyContext) self.sendContainerChangeWaitForFetch(context: establishContext) let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] @@ -866,20 +875,19 @@ self.sendContainerChange(context: recoveryGuyContext) let newGuyCheckDumpCallback = self.expectation(description: "newGuyCheckDumpCallback callback occurs") - self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { - dump, _ in + self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let stableInfo = egoSelf!["stableInfo"] as? Dictionary + let stableInfo = egoSelf!["stableInfo"] as? [String: AnyObject] XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 1, "should be 1 peer ids") let vouchers = dump!["vouchers"] @@ -898,6 +906,7 @@ self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) XCTAssertFalse(self.mockAuthKit.currentDeviceList().isEmpty, "should not have zero devices") @@ -921,7 +930,7 @@ XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") - self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in + self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context, recoveryKey: recoveryKey) { error in XCTAssertNotNil(error, "error should not be nil") XCTAssertEqual((error! as NSError).code, OctagonError.OTErrorLimitedPeer.rawValue, "error code should be limited peer") createKeyExpectation.fulfill() @@ -938,6 +947,7 @@ let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() + XCTAssertNoThrow(try establishContext.setCDPEnabled()) self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -959,8 +969,8 @@ self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: establishContext) self.assertSelfTLKSharesInCloudKit(context: establishContext) @@ -1013,6 +1023,7 @@ let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() + XCTAssertNoThrow(try establishContext.setCDPEnabled()) self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -1034,8 +1045,8 @@ self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: establishContext) self.assertSelfTLKSharesInCloudKit(context: establishContext) @@ -1071,9 +1082,7 @@ XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in - XCTAssertNotNil(error, "error should NOT be nil") - XCTAssertEqual((error! as NSError).code, 32, "error code should be 32/untrusted recovery keys") - XCTAssertEqual((error! as NSError).domain, "com.apple.security.trustedpeers.container", "error code domain should be com.apple.security.trustedpeers.container") + XCTAssertNil(error, "error should be nil") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) @@ -1088,6 +1097,7 @@ let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() + XCTAssertNoThrow(try establishContext.setCDPEnabled()) self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -1109,8 +1119,8 @@ self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + try self.putSelfTLKSharesInCloudKit(context: establishContext) self.assertSelfTLKSharesInCloudKit(context: establishContext) @@ -1164,6 +1174,7 @@ let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() + XCTAssertNoThrow(try establishContext.setCDPEnabled()) self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -1185,9 +1196,8 @@ self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) - + self.putFakeKeyHierarchiesInCloudKit() + try! self.putSelfTLKSharesInCloudKit(context: establishContext) self.assertSelfTLKSharesInCloudKit(context: establishContext) let recoveryKey = "malformedRecoveryKey" @@ -1195,7 +1205,7 @@ self.manager.setSOSEnabledForPlatformFlag(true) let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") - self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in + self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context, recoveryKey: recoveryKey) { error in XCTAssertNotNil(error, "error should NOT be nil") XCTAssertEqual((error! as NSError).code, 41, "error code should be 41/malformed recovery key") XCTAssertEqual((error! as NSError).domain, "com.apple.security.octagon", "error code domain should be com.apple.security.octagon") @@ -1225,5 +1235,116 @@ } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) } + + @discardableResult + func createAndSetRecoveryKey(context: OTCuttlefishContext) -> String { + let cliqueConfiguration = OTConfigurationContext() + cliqueConfiguration.context = context.contextID + cliqueConfiguration.altDSID = try! context.authKitAdapter.primaryiCloudAccountAltDSID() + cliqueConfiguration.otControl = self.otControl + + let recoveryKey = SecRKCreateRecoveryKeyString(nil) + XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") + + let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs") + TestsObjectiveC.setNewRecoveryKeyWithData(cliqueConfiguration, recoveryKey: recoveryKey!) { _, error in + XCTAssertNil(error, "error should be nil") + setRecoveryKeyExpectation.fulfill() + } + self.wait(for: [setRecoveryKeyExpectation], timeout: 10) + + return recoveryKey! + } + + func testConcurWithTrustedPeer() throws { + self.startCKAccountStatusMock() + self.manager.setSOSEnabledForPlatformFlag(true) + + self.assertResetAndBecomeTrustedInDefaultContext() + + let peer2Context = self.makeInitiatorContext(contextID: "peer2") + let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: peer2Context, sponsor: self.cuttlefishContext) + + self.assertAllCKKSViewsUpload(tlkShares: 1) + self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + + // peer1 sets a recovery key + var rkSigningPubKey : Data? = nil + var rkEncryptionPubKey : Data? = nil + + let setRKExpectation = self.expectation(description: "setRecoveryKey") + self.fakeCuttlefishServer.setRecoveryKeyListener = { request in + XCTAssertNotNil(request.recoverySigningPubKey, "signing public key should be present") + XCTAssertNotNil(request.recoveryEncryptionPubKey, "encryption public key should be present") + + rkSigningPubKey = request.recoverySigningPubKey + rkEncryptionPubKey = request.recoveryEncryptionPubKey + + setRKExpectation.fulfill() + return nil + } + + self.createAndSetRecoveryKey(context: self.cuttlefishContext) + self.wait(for: [setRKExpectation], timeout: 10) + + // And peer2 concurs with it upon receiving a push + let updateTrustExpectation = self.expectation(description: "updateTrust") + self.fakeCuttlefishServer.updateListener = { [unowned self] request in + XCTAssertEqual(request.peerID, peer2ID, "Update should be for peer2") + + let newStableInfo = request.stableInfoAndSig.stableInfo() + XCTAssertEqual(newStableInfo.recoverySigningPublicKey, rkSigningPubKey, "Recovery signing key should match other peer") + XCTAssertEqual(newStableInfo.recoveryEncryptionPublicKey, rkEncryptionPubKey, "Recovery encryption key should match other peer") + self.fakeCuttlefishServer.updateListener = nil + updateTrustExpectation.fulfill() + + return nil + } + + self.sendContainerChangeWaitForFetch(context: peer2Context) + self.wait(for: [updateTrustExpectation], timeout: 10) + + // Restart TPH, and ensure that more updates succeed + self.tphClient.containerMap.removeAllContainers() + + self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) + self.sendContainerChangeWaitForFetch(context: peer2Context) + } + + func testRecoveryKeyLoadingOnContainerLoad() throws { + self.startCKAccountStatusMock() + self.manager.setSOSEnabledForPlatformFlag(true) + + let _ = self.assertResetAndBecomeTrustedInDefaultContext() + // peer1 sets a recovery key + self.createAndSetRecoveryKey(context: self.cuttlefishContext) + + // Restart TPH + self.tphClient.containerMap.removeAllContainers() + + self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) + } + + func testRecoveryKeyLoadingOnContainerLoadEvenIfMissing() throws { + self.startCKAccountStatusMock() + self.manager.setSOSEnabledForPlatformFlag(true) + + let _ = self.assertResetAndBecomeTrustedInDefaultContext() + // peer1 sets a recovery key + self.createAndSetRecoveryKey(context: self.cuttlefishContext) + + // Before restarting TPH, emulate a world in which the RK variables were not set on the container + + let containerName = ContainerName(container: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) + let container = try self.tphClient.containerMap.findOrCreate(name: containerName) + container.removeRKFromContainer() + + // Restart TPH + self.tphClient.containerMap.removeAllContainers() + + self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) + } } #endif diff --git a/keychain/ot/tests/octagon/OctagonTests+Reset.swift b/keychain/ot/tests/octagon/OctagonTests+Reset.swift index 00037248..3bc43ec7 100644 --- a/keychain/ot/tests/octagon/OctagonTests+Reset.swift +++ b/keychain/ot/tests/octagon/OctagonTests+Reset.swift @@ -5,6 +5,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") @@ -21,6 +22,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in @@ -37,6 +39,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in @@ -58,7 +61,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() // Before resetAndEstablish, there shouldn't be any stored account state - XCTAssertThrowsError(try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName), "Before doing anything, loading a non-existent account state should fail") + XCTAssertThrowsError(try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName), "Before doing anything, loading a non-existent account state should fail") let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablish callback occurs") let escrowRequestNotification = expectation(forNotification: OTMockEscrowRequestNotification, @@ -94,6 +97,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -116,7 +120,7 @@ class OctagonResetTests: OctagonTestsBase { let waitfortrusts = self.ckksViews.compactMap { view in (view as! CKKSKeychainView).keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] as? CKKSCondition } - XCTAssert(waitfortrusts.count > 0, "Should have at least one waitfortrust condition") + XCTAssert(!waitfortrusts.isEmpty, "Should have at least one waitfortrust condition") let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablish callback occurs") let escrowRequestNotification = expectation(forNotification: OTMockEscrowRequestNotification, @@ -143,14 +147,15 @@ class OctagonResetTests: OctagonTestsBase { } func testOctagonResetAlsoResetsCKKSViewsMissingTLKs() { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() - let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys + let zoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(zoneKeys, "Should have some zone keys") XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set") self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC) @@ -164,7 +169,7 @@ class OctagonResetTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys + let laterZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(laterZoneKeys, "Should have some zone keys") XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset") XCTAssertNotEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys") @@ -172,23 +177,39 @@ class OctagonResetTests: OctagonTestsBase { func testOctagonResetIgnoresOldRemoteDevicesWithKeysAndResetsCKKS() { // CKKS has no keys, and there's another device claiming to have them already, but it's old - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() + #if !os(tvOS) (self.zones![self.manateeZoneID!]! as! FakeCKZone).currentDatabase.allValues.forEach { record in let r = record as! CKRecord - if(r.recordType == SecCKRecordDeviceStateType) { + if r.recordType == SecCKRecordDeviceStateType { + r.creationDate = NSDate.distantPast + r.modificationDate = NSDate.distantPast + } + } + #endif + (self.zones![self.limitedPeersAllowedZoneID!]! as! FakeCKZone).currentDatabase.allValues.forEach { record in + let r = record as! CKRecord + if r.recordType == SecCKRecordDeviceStateType { r.creationDate = NSDate.distantPast r.modificationDate = NSDate.distantPast } } + #if !os(tvOS) let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys - XCTAssertNotNil(zoneKeys, "Should have some zone keys") - XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set") + XCTAssertNotNil(zoneKeys, "Should have some zone keys for Manatee") + XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set for Manatee") + #endif + + let lpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys + XCTAssertNotNil(lpZoneKeys, "Should have some zone keys for LimitedPeers") + XCTAssertNotNil(lpZoneKeys?.tlk, "Should have a tlk in the original key set for LimitedPeers") self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.silentZoneDeletesAllowed = true @@ -199,25 +220,42 @@ class OctagonResetTests: OctagonTestsBase { XCTFail("failed to make new friends: \(error)") } - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + #if !os(tvOS) let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys - XCTAssertNotNil(laterZoneKeys, "Should have some zone keys") - XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset") - XCTAssertNotEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys") + XCTAssertNotNil(laterZoneKeys, "Should have some zone keys for Manatee") + XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset for Manatee") + XCTAssertNotEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys for Manatee") + #else + let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys + XCTAssertNil(laterZoneKeys, "Should have no Manatee zone keys for aTV") + #endif + + let laterLpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys + XCTAssertNotNil(laterLpZoneKeys, "Should have some zone keys for LimitedPeers") + XCTAssertNotNil(laterLpZoneKeys?.tlk, "Should have a tlk in the newly created keyset for LimitedPeers") + XCTAssertNotEqual(lpZoneKeys?.tlk?.uuid, laterLpZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys for LimitedPeers") } func testOctagonResetWithRemoteDevicesWithKeysDoesNotResetCKKS() { // CKKS has no keys, and there's another device claiming to have them already, so CKKS won't immediately reset it - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() + #if !os(tvOS) let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(zoneKeys, "Should have some zone keys") XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set") + #endif + + let lpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys + XCTAssertNotNil(lpZoneKeys, "Should have some zone keys for LimitedPeers") + XCTAssertNotNil(lpZoneKeys?.tlk, "Should have a tlk in the original key set for LimitedPeers") self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.silentZoneDeletesAllowed = true @@ -230,23 +268,40 @@ class OctagonResetTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC) + #if !os(tvOS) let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(laterZoneKeys, "Should have some zone keys") XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset") XCTAssertEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys") + #else + let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys + XCTAssertNil(laterZoneKeys, "Should have no Manatee zone keys for aTV") + #endif + + let lpLaterZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys + XCTAssertNotNil(lpLaterZoneKeys, "Should have some zone keys for LimitedPeersAllowed") + XCTAssertNotNil(lpLaterZoneKeys?.tlk, "Should have a tlk in the newly created keyset for LimitedPeersAllowed") + XCTAssertEqual(lpZoneKeys?.tlk?.uuid, lpLaterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys for LimitedPeersAllowed") } func testOctagonResetWithTLKsDoesNotResetCKKS() { // CKKS has the keys keys - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.saveTLKMaterialToKeychain() + #if !os(tvOS) let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(zoneKeys, "Should have some zone keys") XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set") + #endif + + let lpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys + XCTAssertNotNil(lpZoneKeys, "Should have some zone keys for LimitedPeers") + XCTAssertNotNil(lpZoneKeys?.tlk, "Should have a tlk in the original key set for LimitedPeers") self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -258,10 +313,20 @@ class OctagonResetTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + #if !os(tvOS) let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(laterZoneKeys, "Should have some zone keys") XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset") XCTAssertEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys") + #else + let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys + XCTAssertNil(laterZoneKeys, "Should have no Manatee zone keys for aTV") + #endif + + let lpLaterZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys + XCTAssertNotNil(lpLaterZoneKeys, "Should have some zone keys for LimitedPeersAllowed") + XCTAssertNotNil(lpLaterZoneKeys?.tlk, "Should have a tlk in the newly created keyset for LimitedPeersAllowed") + XCTAssertEqual(lpZoneKeys?.tlk?.uuid, lpLaterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys for LimitedPeersAllowed") } func testOctagonResetAndEstablishFail() throws { @@ -269,6 +334,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") @@ -297,6 +363,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") @@ -322,13 +389,14 @@ class OctagonResetTests: OctagonTestsBase { } func testResetReasonUserInitiatedReset() throws { - // Make sure if establish fail we end up in untrusted instead of error - self.startCKAccountStatusMock() + // Make sure if establish fail we end up in untrusted instead of error + self.startCKAccountStatusMock() - self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - _ = try self.cuttlefishContext.accountAvailable("13453464") + _ = try self.cuttlefishContext.accountAvailable("13453464") let resetExpectation = self.expectation(description: "resetExpectation") @@ -366,6 +434,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") @@ -404,6 +473,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let initiatorContext = self.manager.context(forContainerName: OTCKContainerName, @@ -448,6 +518,7 @@ class OctagonResetTests: OctagonTestsBase { let contextName = OTDefaultContext self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.startCKAccountStatusMock() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) @@ -497,10 +568,9 @@ class OctagonResetTests: OctagonTestsBase { self.verifyDatabaseMocks() let dumpCallback = self.expectation(description: "dumpCallback callback occurs") - self.tphClient.dump(withContainer: containerName, context: contextName) { - dump, _ in + self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() } @@ -513,6 +583,7 @@ class OctagonResetTests: OctagonTestsBase { func testLegacyJoinCircleDoesNotReset() throws { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) @@ -556,6 +627,7 @@ class OctagonResetTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") @@ -579,6 +651,62 @@ class OctagonResetTests: OctagonTestsBase { self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } + + func testCliqueResetAllSPI() throws { + self.cuttlefishContext.startOctagonStateMachine() + self.startCKAccountStatusMock() + OctagonSetSOSFeatureEnabled(false) + + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + + let establishAndResetExpectation = self.expectation(description: "resetExpectation") + let clique: OTClique + let otcliqueContext = OTConfigurationContext() + var firstCliqueIdentifier: String? + + otcliqueContext.context = OTDefaultContext + otcliqueContext.dsid = "13453464" + otcliqueContext.altDSID = self.mockAuthKit.altDSID! + otcliqueContext.authenticationAppleID = "appleID" + otcliqueContext.passwordEquivalentToken = "petpetpetpetpet" + otcliqueContext.otControl = self.otControl + otcliqueContext.ckksControl = self.ckksControl + otcliqueContext.sbd = OTMockSecureBackup(bottleID: nil, entropy: nil) + + do { + clique = try OTClique.newFriends(withContextData: otcliqueContext, resetReason: .testGenerated) + XCTAssertNotNil(clique, "Clique should not be nil") + XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") + firstCliqueIdentifier = clique.cliqueMemberIdentifier + establishAndResetExpectation.fulfill() + } catch { + XCTFail("Shouldn't have errored making new friends everything: \(error)") + throw error + } + self.wait(for: [establishAndResetExpectation], timeout: 10) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + + self.silentZoneDeletesAllowed = true + + let newClique: OTClique + do { + newClique = try OTClique.resetProtectedData(otcliqueContext) + XCTAssertNotEqual(newClique.cliqueMemberIdentifier, firstCliqueIdentifier, "clique identifiers should be different") + } catch { + XCTFail("Shouldn't have errored resetting everything: \(error)") + throw error + } + XCTAssertNotNil(newClique, "newClique should not be nil") + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + } } #endif // OCTAGON diff --git a/keychain/ot/tests/octagon/OctagonTests+SOS.swift b/keychain/ot/tests/octagon/OctagonTests+SOS.swift index a239ad9b..11961ed3 100644 --- a/keychain/ot/tests/octagon/OctagonTests+SOS.swift +++ b/keychain/ot/tests/octagon/OctagonTests+SOS.swift @@ -3,9 +3,9 @@ class OctagonSOSTests: OctagonTestsBase { func testSOSOctagonKeyConsistency() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() self.startCKAccountStatusMock() @@ -53,9 +53,9 @@ class OctagonSOSTests: OctagonTestsBase { } func testSOSOctagonKeyConsistencyLocked() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() self.startCKAccountStatusMock() @@ -115,9 +115,9 @@ class OctagonSOSTests: OctagonTestsBase { } func testSOSOctagonKeyConsistencySucceedsAfterUpdatingSOS() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID!) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID!) - self.saveTLKMaterial(toKeychain: self.manateeZoneID!) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() self.startCKAccountStatusMock() @@ -292,6 +292,221 @@ class OctagonSOSTests: OctagonTestsBase { XCTAssertNotNil(error, "error should not be nil") } } + + func testPreapproveSOSPeersWhenInCircle() throws { + self.startCKAccountStatusMock() + + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle) + let peer1Preapproval = TPHashBuilder.hash(with: .SHA256, of: self.mockSOSAdapter.selfPeer.publicSigningKey.encodeSubjectPublicKeyInfo()) + + let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID") + self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer) + let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo()) + + let peer3SOSMockPeer = self.createSOSPeer(peerID: "peer3ID") + self.mockSOSAdapter.trustedPeers.add(peer3SOSMockPeer) + let peer3Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer3SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo()) + + let establishTwiceExpectation = self.expectation(description: "establish should be called twice") + establishTwiceExpectation.expectedFulfillmentCount = 2 + + self.fakeCuttlefishServer.establishListener = { request in + XCTAssertTrue(request.hasPeer, "establish request should have a peer") + + let newDynamicInfo = TPPeerDynamicInfo(data: request.peer.dynamicInfoAndSig.peerDynamicInfo, sig: request.peer.dynamicInfoAndSig.sig) + XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamicInfo from protobuf") + + XCTAssertTrue(newDynamicInfo?.preapprovals.contains(peer2Preapproval) ?? false, "Fake peer 2 should be preapproved") + XCTAssertTrue(newDynamicInfo?.preapprovals.contains(peer3Preapproval) ?? false, "Fake peer 3 should be preapproved") + + establishTwiceExpectation.fulfill() + return nil + } + + self.assertAllCKKSViewsUpload(tlkShares: 3) + + // Just starting the state machine is sufficient; it should perform an SOS upgrade + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + + // And a reset does the right thing with preapprovals as well + do { + let arguments = OTConfigurationContext() + arguments.altDSID = try self.cuttlefishContext.authKitAdapter.primaryiCloudAccountAltDSID() + arguments.context = self.cuttlefishContext.contextID + arguments.otControl = self.otControl + + let clique = try OTClique.newFriends(withContextData: arguments, resetReason: .testGenerated) + XCTAssertNotNil(clique, "Clique should not be nil") + } catch { + XCTFail("Shouldn't have errored making new friends: \(error)") + } + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + + self.wait(for: [establishTwiceExpectation], timeout: 1) + + // And do we do the right thing when joining via SOS preapproval? + let peer2JoinExpectation = self.expectation(description: "join called") + self.fakeCuttlefishServer.joinListener = { request in + XCTAssertTrue(request.hasPeer, "establish request should have a peer") + + let newDynamicInfo = TPPeerDynamicInfo(data: request.peer.dynamicInfoAndSig.peerDynamicInfo, sig: request.peer.dynamicInfoAndSig.sig) + XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamicInfo from protobuf") + + XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer1Preapproval) ?? false, "Fake peer 1 should NOT be preapproved by peer2 (as it's already in Octagon)") + XCTAssertTrue(newDynamicInfo?.preapprovals.contains(peer3Preapproval) ?? false, "Fake peer 3 should be preapproved by peer2") + + peer2JoinExpectation.fulfill() + + return nil + } + + let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false) + let peer2 = self.makeInitiatorContext(contextID: "peer2", authKitAdapter: self.mockAuthKit2, sosAdapter: peer2mockSOS) + + peer2.startOctagonStateMachine() + self.assertEnters(context: peer2, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfTrusted(context: peer2) + + self.wait(for: [peer2JoinExpectation], timeout: 1) + } + + func testDoNotPreapproveSOSPeerWhenOutOfCircle() throws { + self.startCKAccountStatusMock() + + // SOS returns 'trusted' peers without actually being in-circle + // We don't want to preapprove those peers + + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle) + let peer1Preapproval = TPHashBuilder.hash(with: .SHA256, of: self.mockSOSAdapter.selfPeer.publicSigningKey.encodeSubjectPublicKeyInfo()) + + let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID") + self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer) + let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo()) + + let peer3SOSMockPeer = self.createSOSPeer(peerID: "peer3ID") + self.mockSOSAdapter.trustedPeers.add(peer3SOSMockPeer) + let peer3Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer3SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo()) + + self.fakeCuttlefishServer.establishListener = { request in + XCTAssertTrue(request.hasPeer, "establish request should have a peer") + + let newDynamicInfo = TPPeerDynamicInfo(data: request.peer.dynamicInfoAndSig.peerDynamicInfo, sig: request.peer.dynamicInfoAndSig.sig) + XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamicInfo from protobuf") + + XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer2Preapproval) ?? false, "Fake peer 2 should not be preapproved") + XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer3Preapproval) ?? false, "Fake peer 3 should not be preapproved") + + return nil + } + + self.assertResetAndBecomeTrustedInDefaultContext() + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfTrusted(context: self.cuttlefishContext) + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + + self.verifyDatabaseMocks() + + // And do we do the right thing when joining via bottle? + let peer2JoinExpectation = self.expectation(description: "join called") + self.fakeCuttlefishServer.joinListener = { request in + XCTAssertTrue(request.hasPeer, "establish request should have a peer") + + let newDynamicInfo = TPPeerDynamicInfo(data: request.peer.dynamicInfoAndSig.peerDynamicInfo, sig: request.peer.dynamicInfoAndSig.sig) + XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamicInfo from protobuf") + + XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer1Preapproval) ?? false, "Fake peer 1 should NOT be preapproved by peer2 (as it's not in SOS)") + XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer3Preapproval) ?? false, "Fake peer 3 should not be preapproved by peer2 (as it's not in SOS)") + + peer2JoinExpectation.fulfill() + + return nil + } + + let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false) + peer2mockSOS.circleStatus = SOSCCStatus(kSOSCCNotInCircle) + let peer2 = self.makeInitiatorContext(contextID: "peer2", authKitAdapter: self.mockAuthKit2, sosAdapter: peer2mockSOS) + + peer2.startOctagonStateMachine() + + _ = self.assertJoinViaEscrowRecovery(joiningContext: peer2, sponsor: self.cuttlefishContext) + self.assertEnters(context: peer2, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfTrusted(context: peer2) + + self.wait(for: [peer2JoinExpectation], timeout: 1) + } + + func testRespondToNewOctagonPeerWhenUpdatingPreapprovedKeys() throws { + self.startCKAccountStatusMock() + + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle) + let peer1Preapproval = TPHashBuilder.hash(with: .SHA256, of: self.mockSOSAdapter.selfPeer.publicSigningKey.encodeSubjectPublicKeyInfo()) + + let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID") + self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer) + let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo()) + + self.assertAllCKKSViewsUpload(tlkShares: 2) + + // Just starting the state machine is sufficient; it should perform an SOS upgrade + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + let peer1ID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID() + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + + // Another peer arrives, but we miss the Octagon push + let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false) + let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2, sosAdapter: peer2mockSOS) + let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) + + // Now, SOS updates its key list: we should update our preapproved keys (and then also trust the newly-joined peer) + let peer3SOSMockPeer = self.createSOSPeer(peerID: "peer3ID") + self.mockSOSAdapter.trustedPeers.add(peer3SOSMockPeer) + let peer3Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer3SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo()) + + let updateKeysExpectation = self.expectation(description: "UpdateTrust should fire (once)") + self.fakeCuttlefishServer.updateListener = { [unowned self] request in + XCTAssertEqual(request.peerID, peer1ID, "UpdateTrust should be for peer1") + + let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo() + + XCTAssertFalse(newDynamicInfo.preapprovals.contains(peer1Preapproval), "Fake peer 1 should NOT be preapproved by peer1 (as it's its own keys)") + XCTAssertTrue(newDynamicInfo.preapprovals.contains(peer2Preapproval), "Fake peer 2 should be preapproved by original peer") + XCTAssertTrue(newDynamicInfo.preapprovals.contains(peer3Preapproval), "Fake peer 3 should be preapproved by original peer") + + self.fakeCuttlefishServer.updateListener = nil + updateKeysExpectation.fulfill() + + return nil + } + + // And we'll send TLKShares to the new SOS peer and the new Octagon peer + self.assertAllCKKSViewsUpload(tlkShares: 2) + + // to avoid CKKS race conditions (wherein it uploads each TLKShare in its own operation), send the SOS notification only to the Octagon context + //self.mockSOSAdapter.sendTrustedPeerSetChangedUpdate() + self.cuttlefishContext.trustedPeerSetChanged(self.mockSOSAdapter) + self.wait(for: [updateKeysExpectation], timeout: 10) + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + + XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer1ID)), + "peer 1 should trust peer 1") + XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), + "peer 1 should trust peer 2") + } } #endif diff --git a/keychain/ot/tests/octagon/OctagonTests+SOSUpgrade.swift b/keychain/ot/tests/octagon/OctagonTests+SOSUpgrade.swift index f1a10e25..86e5caf3 100644 --- a/keychain/ot/tests/octagon/OctagonTests+SOSUpgrade.swift +++ b/keychain/ot/tests/octagon/OctagonTests+SOSUpgrade.swift @@ -2,9 +2,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { func testSOSUpgrade() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -33,14 +33,18 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { self.verifyDatabaseMocks() self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext) - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + + // Also, CKKS should be configured with the prevailing policy version + XCTAssertNotNil(self.injectedManager?.policy, "Should have given CKKS a TPPolicy during SOS upgrade") + XCTAssertEqual(self.injectedManager?.policy?.version, prevailingPolicyVersion, "Policy given to CKKS should be prevailing policy") } // Verify that an SOS upgrade only does one establish (and no update trust). func testSOSUpgradeUpdateNoUpdateTrust() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -74,9 +78,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testSOSUpgradeAuthkitError() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID!) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID!) - self.saveTLKMaterial(toKeychain: self.manateeZoneID!) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -116,9 +120,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { // Test that we tries to perform SOS upgrade once we unlock device again // - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -148,9 +152,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { func testSOSUpgradeDuringNetworkOutage() throws { // Test that we tries to perform SOS upgrade after a bit after a failure - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -182,9 +186,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { func testSOSUpgradeStopsIfSplitGraph() throws { // Test that we tries to perform SOS upgrade after a bit after a failure - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -242,8 +246,8 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testSOSUpgradeWithNoTLKs() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() self.startCKAccountStatusMock() @@ -262,11 +266,11 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { func testsSOSUpgradeWithCKKSConflict() throws { // Right after CKKS fetches for the first time, insert a new key hierarchy into CloudKit self.silentFetchesAllowed = false - self.expectCKFetchAndRun(beforeFinished: { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.expectCKFetchAndRun { + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() self.silentFetchesAllowed = true - }) + } self.startCKAccountStatusMock() @@ -283,7 +287,7 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testSOSJoin() throws { - if(!OctagonPerformSOSUpgrade()) { + if !OctagonPerformSOSUpgrade() { return } self.startCKAccountStatusMock() @@ -360,9 +364,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { func testSOSJoinUponNotificationOfPreapproval() throws { // Peer 1 becomes SOS+Octagon - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -379,7 +383,7 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - // Peer 2 attempts to join via preapprovalh + // Peer 2 attempts to join via preapproval let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID") let peer2contextID = "peer2" let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false) @@ -433,9 +437,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { func testSOSJoinUponNotificationOfPreapprovalRetry() throws { // Peer 1 becomes SOS+Octagon - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID!) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID!) - self.saveTLKMaterial(toKeychain: self.manateeZoneID!) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -452,7 +456,7 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - // Peer 2 attempts to join via preapprovalh + // Peer 2 attempts to join via preapproval let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID") let peer2contextID = "peer2" let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false) @@ -517,9 +521,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { func testSOSJoinUponNotificationOfPreapprovalRetryFail() throws { // Peer 1 becomes SOS+Octagon - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID!) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID!) - self.saveTLKMaterial(toKeychain: self.manateeZoneID!) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -536,7 +540,7 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - // Peer 2 attempts to join via preapprovalh + // Peer 2 attempts to join via preapproval let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID") let peer2contextID = "peer2" let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false) @@ -658,6 +662,7 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { self.cuttlefishContext.incompleteNotificationOfMachineIDListChange() self.wait(for: [updateTrustExpectation], timeout: 10) + self.verifyDatabaseMocks() assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) } @@ -694,8 +699,12 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 40 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() + + // Also, CKKS should be configured with the prevailing policy version + XCTAssertNotNil(self.injectedManager?.policy, "Should have given CKKS a TPPolicy during SOS upgrade") + XCTAssertEqual(self.injectedManager?.policy?.version, prevailingPolicyVersion, "Policy given to CKKS should be prevailing policy") } func testSOSDoNotAttemptUpgradeWhenPlatformDoesntSupport() throws { @@ -707,6 +716,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { let everEnteredSOSUpgrade: CKKSCondition = self.cuttlefishContext.stateMachine.stateConditions[OctagonStateAttemptSOSUpgrade] as! CKKSCondition self.cuttlefishContext.startOctagonStateMachine() + + // Cheat and even turn on CDP for the account + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) @@ -727,9 +739,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testSosUpgradeAndReady() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() self.startCKAccountStatusMock() @@ -743,7 +755,7 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle) let upgradeExpectation = self.expectation(description: "waitForOctagonUpgrade") - self.manager.wait(forOctagonUpgrade: OTCKContainerName, context: self.otcliqueContext.context ?? "defaultContext") { error in + self.manager.wait(forOctagonUpgrade: OTCKContainerName, context: self.otcliqueContext.context) { error in XCTAssertNil(error, "operation should not fail") upgradeExpectation.fulfill() } @@ -803,7 +815,7 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testSOSJoinAndBottle() throws { - if(!OctagonPerformSOSUpgrade()) { + if !OctagonPerformSOSUpgrade() { return } self.startCKAccountStatusMock() @@ -931,9 +943,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testSOSPeerUpdatePreapprovesNewPeer() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() self.startCKAccountStatusMock() @@ -989,9 +1001,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testSOSPeerUpdateOnRestartAfterMissingNotification() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() self.startCKAccountStatusMock() @@ -1063,9 +1075,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testResetAndEstablishDoesNotReuploadSOSTLKShares() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -1125,9 +1137,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testResetAndEstablishReusesSOSKeys() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -1148,14 +1160,13 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { let dumpExpectation = self.expectation(description: "dump callback occurs") var encryptionPubKey = Data() var signingPubKey = Data() - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, error in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, error in XCTAssertNil(error, "Should be no error dumping data") XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let permanentInfo = egoSelf!["permanentInfo"] as? Dictionary + let permanentInfo = egoSelf!["permanentInfo"] as? [String: AnyObject] XCTAssertNotNil(permanentInfo, "should have a permanent info") let epk = permanentInfo!["encryption_pub_key"] as? Data @@ -1186,14 +1197,13 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { // And check that the pub keys are equivalent let dumpResetExpectation = self.expectation(description: "dump callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, error in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, error in XCTAssertNil(error, "Should be no error dumping data") XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let permanentInfo = egoSelf!["permanentInfo"] as? Dictionary + let permanentInfo = egoSelf!["permanentInfo"] as? [String: AnyObject] XCTAssertNotNil(permanentInfo, "should have a permanent info") let epk = permanentInfo!["encryption_pub_key"] as? Data @@ -1210,9 +1220,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testSOSUpgradeWithFailingAuthKit() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") @@ -1231,9 +1241,9 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { } func testCliqueOctagonUpgrade () throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() self.startCKAccountStatusMock() self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle) @@ -1241,29 +1251,27 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { OctagonSetPlatformSupportsSOS(true) - var clique: OTClique? - XCTAssertNoThrow(clique = try OTClique(contextData: self.otcliqueContext)) + let clique = OTClique(contextData: self.otcliqueContext) XCTAssertNotNil(clique, "Clique should not be nil") - XCTAssertNoThrow(try clique!.waitForOctagonUpgrade(), "Upgrading should pass") + XCTAssertNoThrow(try clique.waitForOctagonUpgrade(), "Upgrading should pass") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) } func testCliqueOctagonUpgradeFail () throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putSelfTLKShares(inCloudKit: self.manateeZoneID) - self.saveTLKMaterial(toKeychain: self.manateeZoneID) + self.putFakeKeyHierarchiesInCloudKit() + self.putSelfTLKSharesInCloudKit() + self.saveTLKMaterialToKeychain() self.startCKAccountStatusMock() XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on") OctagonSetPlatformSupportsSOS(true) - var clique: OTClique? - XCTAssertNoThrow(clique = try OTClique(contextData: self.otcliqueContext)) + let clique = OTClique(contextData: self.otcliqueContext) XCTAssertNotNil(clique, "Clique should not be nil") - XCTAssertThrowsError(try clique!.waitForOctagonUpgrade(), "Upgrading should fail") + XCTAssertThrowsError(try clique.waitForOctagonUpgrade(), "Upgrading should fail") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) @@ -1323,7 +1331,6 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trustsByPreapproval, target: peer2ID)), "peer 1 should trust peer 3 by preapproval") - XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer2ID, opinion: .trusts, target: peer1ID)), "peer 2 should trust peer 1") XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer2ID, opinion: .trusts, target: peer3ID)), @@ -1359,7 +1366,7 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { // And if peer3 decides to reupgrade, but it shouldn't: there's no potentially-trusted peer that preapproves it let upgradeExpectation = self.expectation(description: "sosUpgrade call returns") - peer3.attemptSOSUpgrade() { error in + peer3.attemptSOSUpgrade { error in XCTAssertNotNil(error, "should be an error performing an SOS upgrade (the second time)") upgradeExpectation.fulfill() } @@ -1371,7 +1378,7 @@ class OctagonSOSUpgradeTests: OctagonTestsBase { // And "wait for upgrade" does something reasonable too let upgradeWaitExpectation = self.expectation(description: "sosWaitForUpgrade call returns") - peer3.waitForOctagonUpgrade() { error in + peer3.waitForOctagonUpgrade { error in XCTAssertNotNil(error, "should be an error waiting for an SOS upgrade (the second time)") upgradeWaitExpectation.fulfill() } diff --git a/keychain/ot/tests/octagon/OctagonTests-BridgingHeader.h b/keychain/ot/tests/octagon/OctagonTests-BridgingHeader.h index 850a15b1..64bca2d7 100644 --- a/keychain/ot/tests/octagon/OctagonTests-BridgingHeader.h +++ b/keychain/ot/tests/octagon/OctagonTests-BridgingHeader.h @@ -12,6 +12,7 @@ #import "KeychainCircle/KCJoiningRequestSession+Internal.h" #import "KeychainCircle/KCJoiningAcceptSession+Internal.h" #import +#import #import #import @@ -59,6 +60,7 @@ #import "keychain/ckks/CKKSTLKShare.h" #import "keychain/ckks/CKKSAnalytics.h" #import "keychain/ckks/CloudKitCategories.h" +#import "keychain/ckks/CKKSCurrentKeyPointer.h" #import "keychain/ot/OctagonControlServer.h" @@ -66,6 +68,12 @@ #import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h" #import "keychain/ot/categories/OctagonEscrowRecoverer.h" +#import "KeychainCircle/generated_source/KCInitialMessageData.h" +#import "keychain/ot/proto/generated_source/OTPairingMessage.h" +#import "keychain/ot/proto/generated_source/OTSponsorToApplicantRound1M2.h" +#import "keychain/ot/proto/generated_source/OTApplicantToSponsorRound2M1.h" +#import "keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.h" + #import "keychain/otctl/OTControlCLI.h" // Also, we're going to need whatever TPH needs. diff --git a/keychain/ot/tests/octagon/OctagonTests.swift b/keychain/ot/tests/octagon/OctagonTests.swift index de42b910..5e17bae5 100644 --- a/keychain/ot/tests/octagon/OctagonTests.swift +++ b/keychain/ot/tests/octagon/OctagonTests.swift @@ -51,10 +51,12 @@ class OTMockDeviceInfoAdapter: OTDeviceInformationAdapter { } class OTMockAuthKitAdapter: OTAuthKitAdapter { + // A nil altDSID means 'no authkit account' var altDSID: String? var hsa2: Bool + var isDemoAccount: Bool let currentMachineID: String var otherDevices: Set @@ -74,6 +76,7 @@ class OTMockAuthKitAdapter: OTAuthKitAdapter { self.otherDevices = otherDevices self.excludeDevices = Set() self.hsa2 = true + self.isDemoAccount = false self.listeners = CKKSListenerCollection(name: "test-authkit") } @@ -90,6 +93,9 @@ class OTMockAuthKitAdapter: OTAuthKitAdapter { // TODO: do we need to examine altDSID here? return self.hsa2 } + func accountIsDemoAccount(_ error: NSErrorPointer) -> Bool { + return self.isDemoAccount + } func machineID() throws -> String { // TODO: throw if !accountPresent @@ -213,7 +219,7 @@ class OTMockSecEscrowRequest: NSObject, SecEscrowRequestable { } } -class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { +class OctagonTestsBase: CloudKitKeychainSyncingMockXCTest { var tmpPath: String! var tmpURL: URL! @@ -223,7 +229,10 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { var otcliqueContext: OTConfigurationContext! + var intendedCKKSZones: Set! var manateeZoneID: CKRecordZone.ID! + var limitedPeersAllowedZoneID: CKRecordZone.ID! + var fakeCuttlefishServer: FakeCuttlefishServer! var fakeCuttlefishCreator: FakeCuttlefishInvocableCreator! var tphClient: Client! @@ -240,6 +249,7 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { var otControl: OTControl! var otXPCProxy: ProxyXPCConnection! + var otControlEntitlementBearer: FakeOTControlEntitlementBearer! var otControlEntitlementChecker: OTControlProtocol! var otControlCLI: OTControlCLI! @@ -248,8 +258,6 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { override static func setUp() { UserDefaults.standard.register(defaults: ["com.apple.CoreData.ConcurrencyDebug": 1]) - OctagonSetShouldPerformInitialization(true) - SecCKKSEnable() super.setUp() @@ -261,38 +269,46 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { // Set the global bool to TRUE OctagonSetIsEnabled(true) + // Set the global CKKS bool to TRUE + SecCKKSEnable() + // Until we can reasonably run SOS in xctest, this must be off. Note that this makes our tests // not accurately reproduce what a real device would do. OctagonSetPlatformSupportsSOS(false) - // Tell SecDb not to initialize the manager (as we haven't made our fake one yet). - // Each test is responsible for initialization, to allow for pre-test setup - OctagonSetShouldPerformInitialization(false) - - let actualDeviceAdapter = OTDeviceInformationActualAdapter() - self.mockDeviceInfo = OTMockDeviceInfoAdapter(modelID: actualDeviceAdapter.modelID(), - deviceName: actualDeviceAdapter.deviceName(), - serialNumber: NSUUID().uuidString, - osVersion: actualDeviceAdapter.osVersion()) - - super.setUp() - - // Octagon must initialize the views - self.automaticallyBeginCKKSViewCloudKitOperation = false + if self.mockDeviceInfo == nil { + let actualDeviceAdapter = OTDeviceInformationActualAdapter() + self.mockDeviceInfo = OTMockDeviceInfoAdapter(modelID: actualDeviceAdapter.modelID(), + deviceName: actualDeviceAdapter.deviceName(), + serialNumber: NSUUID().uuidString, + osVersion: actualDeviceAdapter.osVersion()) + } - // The CKKStests use the "keychain" view heavily, but that causes issues in Octagon as it isn't in the Octagon policy. - // Replace it with the Manatee view, unless you're on an appleTV: in that case, make it the LimitedPeersAllowed view - self.injectedManager!.clearAllViews() - #if !os(tvOS) - self.ckksViews = NSMutableSet(array: [self.injectedManager!.findOrCreateView("Manatee")]) self.manateeZoneID = CKRecordZone.ID(zoneName: "Manatee") - #else - self.ckksViews = NSMutableSet(array: [self.injectedManager!.findOrCreateView("LimitedPeersAllowed")]) - self.manateeZoneID = CKRecordZone.ID(zoneName: "LimitedPeersAllowed") - #endif + self.limitedPeersAllowedZoneID = CKRecordZone.ID(zoneName: "LimitedPeersAllowed") + + // We'll use this set to limit the views that CKKS brings up in the tests (mostly for performance reasons) + if self.intendedCKKSZones == nil { + if self.mockDeviceInfo.mockModelID.contains("AppleTV") { + self.intendedCKKSZones = Set([ + self.limitedPeersAllowedZoneID!, + ]) + } else { + self.intendedCKKSZones = Set([ + self.limitedPeersAllowedZoneID!, + self.manateeZoneID!, + ]) + } + } + self.ckksZones = NSMutableSet(array: Array(self.intendedCKKSZones)) - self.zones!.removeAllObjects() - self.zones![self.manateeZoneID!] = FakeCKZone(zone: self.manateeZoneID!) + // Create the zones, so we can inject them into our fake cuttlefish server + self.zones = [:] + self.keys = [:] + self.ckksZones.forEach { obj in + let zoneID = obj as! CKRecordZone.ID + self.zones![zoneID] = FakeCKZone(zone: zoneID) + } // Asserting a type on self.zones seems to duplicate the dictionary, but not deep-copy the contents // We'll use them as NSMutableDictionaries, I guess @@ -303,34 +319,46 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { self.otFollowUpController = OTMockFollowUpController() - // Octagon requires the self peer keys to be persisted in the keychain - saveToKeychain(keyPair: self.mockSOSAdapter.selfPeer.signingKey, label: "com.apple.securityd.sossigningkey") - saveToKeychain(keyPair: self.mockSOSAdapter.selfPeer.encryptionKey, label: "com.apple.securityd.sosencryptionkey") - self.mockAuthKit = OTMockAuthKitAdapter(altDSID: UUID().uuidString, machineID: "MACHINE1", otherDevices: ["MACHINE2", "MACHINE3"]) self.mockAuthKit2 = OTMockAuthKitAdapter(altDSID: self.mockAuthKit.altDSID, machineID: "MACHINE2", otherDevices: ["MACHINE1", "MACHINE3"]) self.mockAuthKit3 = OTMockAuthKitAdapter(altDSID: self.mockAuthKit.altDSID, machineID: "MACHINE3", otherDevices: ["MACHINE1", "MACHINE2"]) - // By default, not in SOS when test starts - // And under octagon, SOS trust is not essential - self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle) - self.mockSOSAdapter.essential = false - let tphInterface = TrustedPeersHelperSetupProtocol(NSXPCInterface(with: TrustedPeersHelperProtocol.self)) self.tphXPCProxy = ProxyXPCConnection(self.tphClient!, interface: tphInterface) - self.manager = OTManager(sosAdapter: self.mockSOSAdapter, - authKitAdapter: self.mockAuthKit, - deviceInformationAdapter: self.mockDeviceInfo, - apsConnectionClass: FakeAPSConnection.self, - escrowRequestClass: OTMockSecEscrowRequest.self, - loggerClass: OTMockLogger.self, - lockStateTracker: self.lockStateTracker, - accountStateTracker: self.accountStateTracker, - cuttlefishXPCConnection: tphXPCProxy.connection(), - cdpd: self.otFollowUpController) + self.disableConfigureCKKSViewManagerWithViews = true + + // Now, perform further test initialization (including temporary keychain creation) + super.setUp() + + self.injectedManager!.setSyncingViewsAllowList(Set(self.intendedCKKSZones!.map { $0.zoneName })) + + // Ensure we've made the CKKSKeychainView objects we're interested in + self.ckksZones.forEach { obj in + let zoneID = obj as! CKRecordZone.ID + self.ckksViews.add(self.injectedManager!.findOrCreateView(zoneID.zoneName)) + } + + // Double-check that the world of zones and views looks like what we expect + XCTAssertEqual(self.ckksZones as? Set, self.intendedCKKSZones, "should still operate on our expected zones only") + XCTAssertEqual(self.ckksZones.count, self.ckksViews.count, "Should have the same number of views as expected zones") + XCTAssertEqual(self.ckksZones.count, self.zones!.count, "Should have the same number of fake zones as expected zones") + + XCTAssertEqual(Set(self.ckksViews.map { ($0 as! CKKSKeychainView).zoneName }), + Set(self.ckksZones.map { ($0 as! CKRecordZone.ID).zoneName }), + "ckksViews should match ckksZones") + + // Octagon must initialize the views + self.automaticallyBeginCKKSViewCloudKitOperation = false - OTManager.resetManager(true, to: self.manager) + // Octagon requires the self peer keys to be persisted in the keychain + saveToKeychain(keyPair: self.mockSOSAdapter.selfPeer.signingKey, label: "com.apple.securityd.sossigningkey") + saveToKeychain(keyPair: self.mockSOSAdapter.selfPeer.encryptionKey, label: "com.apple.securityd.sosencryptionkey") + + // By default, not in SOS when test starts + // And under octagon, SOS trust is not essential + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle) + self.mockSOSAdapter.essential = false self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) @@ -339,7 +367,6 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { self.otXPCProxy = ProxyXPCConnection(self.otControlEntitlementChecker!, interface: OTSetupControlProtocol(NSXPCInterface(with: OTControlProtocol.self))) self.otControl = OTControl(connection: self.otXPCProxy.connection(), sync: true) - self.otControlCLI = OTControlCLI(otControl: self.otControl) self.otcliqueContext = OTConfigurationContext() @@ -349,7 +376,24 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { self.otcliqueContext.otControl = self.otControl } + override func setUpOTManager(_ cloudKitClassDependencies: CKKSCloudKitClassDependencies) -> OTManager { + self.manager = OTManager(sosAdapter: self.mockSOSAdapter, + authKitAdapter: self.mockAuthKit, + deviceInformationAdapter: self.mockDeviceInfo, + apsConnectionClass: FakeAPSConnection.self, + escrowRequestClass: OTMockSecEscrowRequest.self, + loggerClass: OTMockLogger.self, + lockStateTracker: CKKSLockStateTracker(), + cloudKitClassDependencies: cloudKitClassDependencies, + cuttlefishXPCConnection: tphXPCProxy.connection(), + cdpd: self.otFollowUpController) + return self.manager + } + override func tearDown() { + // Just to be sure + self.verifyDatabaseMocks() + let statusExpectation = self.expectation(description: "status callback occurs") self.cuttlefishContext.rpcStatus { _, _ in statusExpectation.fulfill() @@ -368,15 +412,54 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { XCTAssertTrue(self.manager.allContextsPause(10 * NSEC_PER_SEC), "All cuttlefish contexts should pause") + self.tphClient.containerMap.removeAllContainers() + super.tearDown() + + self.cuttlefishContext = nil + self.manager = nil + + self.otcliqueContext = nil + + self.manateeZoneID = nil + self.limitedPeersAllowedZoneID = nil + + self.fakeCuttlefishServer = nil + self.fakeCuttlefishCreator = nil + self.tphClient = nil + self.tphXPCProxy = nil + + self.accountAltDSID = nil + + self.mockAuthKit = nil + self.mockAuthKit2 = nil + self.mockAuthKit3 = nil + + self.mockDeviceInfo = nil + + self.otControl = nil + self.otXPCProxy = nil + + self.otControlEntitlementBearer = nil + self.otControlEntitlementChecker = nil + self.otControlCLI = nil + + self.otFollowUpController = nil } override func managedViewList() -> Set { - #if !os(tvOS) - return Set(["Manatee"]) - #else - return Set(["LimitedPeersAllowed"]) - #endif + if(self.overrideUseCKKSViewsFromPolicy) { + let viewNames = self.ckksZones.map { ($0 as! CKRecordZone.ID).zoneName } + return Set(viewNames) + } else { + // We only want to return the 'base' set of views here; not the full set. + // This should go away when CKKS4A is enabled... + #if !os(tvOS) + return Set(["LimitedPeersAllowed", "Manatee"]) + #else + return Set(["LimitedPeersAllowed"]) + #endif + } } func fetchEgoPeerID() -> String { @@ -392,11 +475,12 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { return ret } - func setAllowListToCurrentAuthKit(container: String, context: String) { + func setAllowListToCurrentAuthKit(container: String, context: String, accountIsDemo: Bool) { let allowListExpectation = self.expectation(description: "set allow list callback occurs") + let honorIDMSListChanges = accountIsDemo ? false : true self.tphClient.setAllowedMachineIDsWithContainer(container, context: context, - allowedMachineIDs: self.mockAuthKit.currentDeviceList()) { _, error in + allowedMachineIDs: self.mockAuthKit.currentDeviceList(), honorIDMSListChanges: honorIDMSListChanges) { _, error in XCTAssertNil(error, "Should be no error setting allow list") allowListExpectation.fulfill() } @@ -418,7 +502,7 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { func assertEnters(context: OTCuttlefishContext, state: String, within: UInt64) { XCTAssertEqual(0, (context.stateMachine.stateConditions[state] as! CKKSCondition).wait(within), "State machine should enter '\(state)'") - if(state == OctagonStateReady || state == OctagonStateUntrusted) { + if state == OctagonStateReady || state == OctagonStateUntrusted { XCTAssertEqual(0, context.stateMachine.paused.wait(10 * NSEC_PER_SEC), "State machine should pause soon") } } @@ -430,6 +514,9 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { func assertConsidersSelfTrusted(context: OTCuttlefishContext, isLocked: Bool = false) { XCTAssertEqual(context.currentMemoizedTrustState(), .TRUSTED, "Trust state (for \(context)) should be trusted") + let accountMetadata = try! context.accountMetadataStore.loadOrCreateAccountMetadata() + XCTAssertEqual(accountMetadata.attemptedJoin, .ATTEMPTED, "Should have 'attempted a join'") + let statusexpectation = self.expectation(description: "trust status returns") let configuration = OTOperationConfiguration() configuration.timeoutWaitForCKAccount = 500 * NSEC_PER_MSEC @@ -462,7 +549,7 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { cliqueConfiguration.dsid = "1234" cliqueConfiguration.altDSID = self.mockAuthKit.altDSID! cliqueConfiguration.otControl = self.otControl - let otclique = try! OTClique(contextData: cliqueConfiguration) + let otclique = OTClique(contextData: cliqueConfiguration) let status = otclique.fetchStatus(nil) XCTAssertEqual(status, .in, "OTClique API should return (trusted)") @@ -490,6 +577,21 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { self.wait(for: [statusexpectation], timeout: 10) } + func assertConsidersSelfWaitingForCDP(context: OTCuttlefishContext) { + XCTAssertEqual(context.currentMemoizedTrustState(), .UNKNOWN, "Trust state (for \(context)) should be unknown") + let statusexpectation = self.expectation(description: "trust status returns") + let configuration = OTOperationConfiguration() + configuration.timeoutWaitForCKAccount = 500 * NSEC_PER_MSEC + context.rpcTrustStatus(configuration) { egoStatus, _, _, _, _ in + // TODO: separate 'untrusted' and 'no trusted peers for account yet' + XCTAssertTrue([.notIn, .absent].contains(egoStatus), "Self peer (for \(context)) should be distrusted or absent") + statusexpectation.fulfill() + } + self.wait(for: [statusexpectation], timeout: 10) + + XCTAssertEqual(self.fetchCDPStatus(context: context), .disabled, "CDP status should be 'disabled'") + } + func assertAccountAvailable(context: OTCuttlefishContext) { XCTAssertEqual(context.currentMemoizedAccountState(), .ACCOUNT_AVAILABLE, "Account state (for \(context)) should be 'available''") } @@ -498,6 +600,18 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { XCTAssertEqual(context.currentMemoizedAccountState(), .NO_ACCOUNT, "Account state (for \(context)) should be no account") } + func fetchCDPStatus(context: OTCuttlefishContext) -> OTCDPStatus { + let config = OTConfigurationContext() + config.context = context.contextID + config.otControl = self.otControl + + var error: NSError? + let cdpstatus = OTClique.getCDPStatus(config, error: &error) + XCTAssertNil(error, "Should have no error fetching CDP status") + + return cdpstatus + } + func assertTrusts(context: OTCuttlefishContext, includedPeerIDCount: Int, excludedPeerIDCount: Int) { let dumpCallback = self.expectation(description: "dump callback occurs") self.tphClient.dumpEgoPeer(withContainer: context.containerName, context: context.contextID) { _, _, _, dynamicInfo, error in @@ -514,6 +628,9 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { func restartCKKSViews() { let viewNames = self.ckksViews.map { ($0 as! CKKSKeychainView).zoneName } self.ckksViews.removeAllObjects() + + self.injectedManager!.resetSyncingPolicy() + for view in viewNames { self.ckksViews.add(self.injectedManager!.restartZone(view)) } @@ -525,25 +642,61 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { } } + func sendAllCKKSViewsZoneChanged() { + for expectedView in self.ckksZones { + let view = self.injectedManager?.findView((expectedView as! CKRecordZone.ID).zoneName) + XCTAssertNotNil(view, "Should have a view '\(expectedView)'") + view!.notifyZoneChange(nil) + } + } + func assert(ckks: CKKSKeychainView, enters: String, within: UInt64) { XCTAssertEqual(0, (ckks.keyHierarchyConditions[enters] as! CKKSCondition).wait(within), "CKKS state machine should enter '\(enters)' (currently '\(ckks.keyHierarchyState)')") } func assertAllCKKSViews(enter: String, within: UInt64) { - for view in self.ckksViews { - self.assert(ckks: view as! CKKSKeychainView, enters: enter, within: within) + for expectedView in self.ckksZones { + let view = self.injectedManager?.findView((expectedView as! CKRecordZone.ID).zoneName) + XCTAssertNotNil(view, "Should have a view '\(expectedView)'") + self.assert(ckks: view!, enters: enter, within: within) } } func assertAllCKKSViewsUploadKeyHierarchy(tlkShares: UInt) { - self.ckksViews.forEach { view in - self.expectCKModifyKeyRecords(3, currentKeyPointerRecords: 3, tlkShareRecords: tlkShares, zoneID: (view as! CKKSKeychainView).zoneID) + for expectedView in self.ckksZones { + let view = self.injectedManager?.findView((expectedView as! CKRecordZone.ID).zoneName) + XCTAssertNotNil(view, "Should have a view '\(expectedView)'") + self.expectCKModifyKeyRecords(3, currentKeyPointerRecords: 3, tlkShareRecords: tlkShares, zoneID: view!.zoneID) } } func assertAllCKKSViewsUpload(tlkShares: UInt) { - self.ckksViews.forEach { view in - self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: tlkShares, zoneID: (view as! CKKSKeychainView).zoneID) + for expectedView in self.ckksZones { + self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: tlkShares, zoneID: expectedView as! CKRecordZone.ID) + } + } + + func putFakeKeyHierarchiesInCloudKit() { + self.ckksZones.forEach { zone in + self.putFakeKeyHierarchy(inCloudKit: zone as! CKRecordZone.ID) + } + } + + func putSelfTLKSharesInCloudKit() { + self.ckksZones.forEach { zone in + self.putSelfTLKShares(inCloudKit: zone as! CKRecordZone.ID) + } + } + + func putFakeDeviceStatusesInCloudKit() { + self.ckksZones.forEach { zone in + self.putFakeDeviceStatus(inCloudKit: zone as! CKRecordZone.ID) + } + } + + func saveTLKMaterialToKeychain() { + self.ckksZones.forEach { zone in + self.saveTLKMaterial(toKeychain: zone as! CKRecordZone.ID) } } @@ -557,11 +710,40 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { self.wait(for: [resetExpectation], timeout: 30) } - func putSelfTLKShareInCloudKit(context: OTCuttlefishContext, zoneID: CKRecordZone.ID) throws { - let accountMetadata = try context.accountMetadataStore.loadOrCreateAccountMetadata() - let peerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: accountMetadata.peerID) + func putSelfTLKSharesInCloudKit(context: OTCuttlefishContext) throws { + try self.ckksZones.forEach { zone in + try self.putTLKShareInCloudKit(to: context, from: context, zoneID: zone as! CKRecordZone.ID) + } + } + + func putAllTLKSharesInCloudKit(to: OTCuttlefishContext, from: OTCuttlefishContext) throws { + try self.ckksZones.forEach { zone in + try self.putTLKShareInCloudKit(to: to, from: from, zoneID: zone as! CKRecordZone.ID) + } + } + + func putTLKShareInCloudKit(to: OTCuttlefishContext, from: OTCuttlefishContext, zoneID: CKRecordZone.ID) throws { + let fromAccountMetadata = try from.accountMetadataStore.loadOrCreateAccountMetadata() + let fromPeerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: fromAccountMetadata.peerID) + + let toAccountMetadata = try to.accountMetadataStore.loadOrCreateAccountMetadata() + let toPeerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: toAccountMetadata.peerID) + + let zoneKeys = self.keys![zoneID] as! ZoneKeys + self.putTLKShare(inCloudKit: zoneKeys.tlk!, from: fromPeerKeys, to: toPeerKeys, zoneID: zoneID) + } + + func putRecoveryKeyTLKSharesInCloudKit(recoveryKey: String, salt: String) throws { + try self.ckksZones.forEach { zone in + try self.putRecoveryKeyTLKShareInCloudKit(recoveryKey: recoveryKey, salt: salt, zoneID: zone as! CKRecordZone.ID) + } + } + + func putRecoveryKeyTLKShareInCloudKit(recoveryKey: String, salt: String, zoneID: CKRecordZone.ID) throws { + let recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt) + let zoneKeys = self.keys![zoneID] as! ZoneKeys - self.putTLKShare(inCloudKit: zoneKeys.tlk!, from: peerKeys, to: peerKeys, zoneID: zoneID) + self.putTLKShare(inCloudKit: zoneKeys.tlk!, from: recoveryKeys.peerKeys, to: recoveryKeys.peerKeys, zoneID: zoneID) } func assertSelfTLKSharesInCloudKit(context: OTCuttlefishContext) { @@ -587,19 +769,25 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { } } - func assertTLKShareInCloudKit(receiverPeerID: String, senderPeerID: String, zoneID: CKRecordZone.ID) throws { - let zone = self.zones![zoneID] as! FakeCKZone + func tlkShareInCloudKit(receiverPeerID: String, senderPeerID: String, zoneID: CKRecordZone.ID) throws -> Bool { + guard let zone = self.zones![zoneID] as? FakeCKZone else { + return false + } let tlkShares = zone.currentDatabase.allValues.filter { ($0 as? CKRecord)?.recordType == SecCKRecordTLKShareType }.map { CKKSTLKShareRecord(ckRecord: $0 as! CKRecord) } for share in tlkShares { if share.share.receiverPeerID == receiverPeerID && share.senderPeerID == senderPeerID { // Found one! - return + return true } } + return false + } - XCTFail("Unable to find a TLKShare for peer ID \(String(describing: receiverPeerID)) sent by \(String(describing: senderPeerID))") + func assertTLKShareInCloudKit(receiverPeerID: String, senderPeerID: String, zoneID: CKRecordZone.ID) throws { + XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: receiverPeerID, senderPeerID: senderPeerID, zoneID: zoneID), + "Should have found a TLKShare for peerID \(String(describing: receiverPeerID)) sent by \(String(describing: senderPeerID)) for \(zoneID)") } func assertMIDList(context: OTCuttlefishContext, @@ -615,7 +803,7 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { } for allowedMID in allowed { - var err : NSError? + var err: NSError? let onList = context.machineID(onMemoizedList: allowedMID, error: &err) XCTAssertNil(err, "Should not have failed determining memoized list state") @@ -630,7 +818,7 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { } for disallowedMID in disallowed { - var err : NSError? + var err: NSError? let onList = context.machineID(onMemoizedList: disallowedMID, error: &err) XCTAssertNil(err, "Should not have failed determining memoized list state") @@ -659,7 +847,7 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { } if result != nil { - if let dictionary = result as? Dictionary { + if let dictionary = result as? [CFString: Any] { secret = dictionary[kSecValueData] as? Data } else { throw ContainerError.failedToLoadSecretDueToType @@ -767,26 +955,43 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter()) } - @discardableResult func assertResetAndBecomeTrustedInDefaultContext() -> String { - self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + @discardableResult + func assertResetAndBecomeTrustedInDefaultContext() -> String { + let ret = self.assertResetAndBecomeTrusted(context: self.cuttlefishContext) + + // And, the default context runs CKKS: + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.verifyDatabaseMocks() + self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext) + + return ret + } + + @discardableResult + func assertResetAndBecomeTrusted(context: OTCuttlefishContext) -> String { + context.startOctagonStateMachine() + XCTAssertNoThrow(try context.setCDPEnabled()) + self.assertEnters(context: context, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { - let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) + let arguments = OTConfigurationContext() + arguments.altDSID = try context.authKitAdapter.primaryiCloudAccountAltDSID() + arguments.context = context.contextID + arguments.otControl = self.otControl + + let clique = try OTClique.newFriends(withContextData: arguments, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") } - // Now, we should be in 'ready' - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) - self.assertConsidersSelfTrusted(context: self.cuttlefishContext) - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - self.verifyDatabaseMocks() - self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext) - return try! self.cuttlefishContext.accountMetadataStore.getEgoPeerID() + self.assertEnters(context: context, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfTrusted(context: context) + + return try! context.accountMetadataStore.getEgoPeerID() } + @discardableResult func assertJoinViaEscrowRecovery(joiningContext: OTCuttlefishContext, sponsor: OTCuttlefishContext) -> String { do { joiningContext.startOctagonStateMachine() @@ -820,14 +1025,37 @@ class OctagonTestsBase: CloudKitKeychainSyncingTestsBase { } } + func assertJoinViaProximitySetup(joiningContext: OTCuttlefishContext, sponsor: OTCuttlefishContext) -> String { + do { + joiningContext.startOctagonStateMachine() + self.assertEnters(context: joiningContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + + let (sponsorPairingChannel, initiatorPairingChannel) = self.setupPairingChannels(initiator: joiningContext, sponsor: sponsor) + + let firstInitiatorPacket = self.sendPairingExpectingReply(channel: initiatorPairingChannel, packet: nil, reason: "session initialization") + let sponsorEpochPacket = self.sendPairingExpectingReply(channel: sponsorPairingChannel, packet: firstInitiatorPacket, reason: "sponsor epoch") + let initiatorIdentityPacket = self.sendPairingExpectingReply(channel: initiatorPairingChannel, packet: sponsorEpochPacket, reason: "initiator identity") + let sponsorVoucherPacket = self.sendPairingExpectingCompletionAndReply(channel: sponsorPairingChannel, packet: initiatorIdentityPacket, reason: "sponsor voucher") + self.sendPairingExpectingCompletion(channel: initiatorPairingChannel, packet: sponsorVoucherPacket, reason: "initiator completion") + + self.assertEnters(context: joiningContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertConsidersSelfTrusted(context: joiningContext) + + return try joiningContext.accountMetadataStore.getEgoPeerID() + } catch { + XCTFail("Expected no error: \(error)") + return "failed" + } + } + func assertSelfOSVersion(_ osVersion: String) { let statusExpectation = self.expectation(description: "status callback occurs") - self.tphClient.dumpEgoPeer(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID, reply: { _, _, stableInfo, _, error in + self.tphClient.dumpEgoPeer(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { _, _, stableInfo, _, error in XCTAssertNil(error, "should be no error dumping ego peer") XCTAssertEqual(stableInfo?.osVersion, osVersion, "os version should be as required") statusExpectation.fulfill() - }) + } self.wait(for: [statusExpectation], timeout: 2) } @@ -845,14 +1073,14 @@ class OctagonTests: OctagonTestsBase { machineID: "asdf", bottleSalt: "123456789", bottleID: UUID().uuidString, - modelID: "asdf", + modelID: "iPhone9,1", deviceName: "asdf", serialNumber: "1234", osVersion: "asdf", policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(peerID, "Should be a peer ID") XCTAssertNotNil(permanentInfo, "Should have a permenent info") @@ -879,14 +1107,14 @@ class OctagonTests: OctagonTestsBase { machineID: "asdf", bottleSalt: "123456789", bottleID: UUID().uuidString, - modelID: "asdf", + modelID: "iPhone9,1", deviceName: "asdf", serialNumber: "1234", osVersion: "asdf", policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(peerID, "Should be a peer ID") XCTAssertNotNil(permanentInfo, "Should have a permenent info") @@ -907,14 +1135,14 @@ class OctagonTests: OctagonTestsBase { machineID: "asdf", bottleSalt: "123456789", bottleID: UUID().uuidString, - modelID: "asdf", + modelID: "iPhone9,1", deviceName: "asdf", serialNumber: "1234", osVersion: "asdf", policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(peerID, "Should be a peer ID") XCTAssertNotNil(permanentInfo, "Should have a permenent info") @@ -930,88 +1158,30 @@ class OctagonTests: OctagonTestsBase { self.wait(for: [tphPrepareExpectation2], timeout: 10) } - func testAccountSave() throws { - let contextName = OTDefaultContext - let containerName = OTCKContainerName - - self.startCKAccountStatusMock() - - // Before resetAndEstablish, there shouldn't be any stored account state - XCTAssertThrowsError(try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName), "Before doing anything, loading a non-existent account state should fail") - - let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablish callback occurs") - self.manager.resetAndEstablish(containerName, - context: contextName, - altDSID: "new altDSID", - resetReason: .testGenerated) { resetError in - XCTAssertNil(resetError, "Should be no error calling resetAndEstablish") - resetAndEstablishExpectation.fulfill() - } - - self.wait(for: [resetAndEstablishExpectation], timeout: 10) - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) - - let selfPeerID = try self.cuttlefishContext.accountMetadataStore.loadOrCreateAccountMetadata().peerID - - // After resetAndEstablish, you should be able to see the persisted account state - do { - let accountState = try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName) - XCTAssertEqual(selfPeerID, accountState.peerID, "Saved account state should have the same peer ID that prepare returned") - } catch { - XCTFail("error loading account state: \(error)") - } - } - - func testLoadToNoAccount() throws { - // No CloudKit account, either - self.accountStatus = .noAccount - self.startCKAccountStatusMock() - - // With no identity and AuthKit reporting no iCloud account, Octagon should go directly into 'no account' - self.mockAuthKit.altDSID = nil - - let asyncExpectation = self.expectation(description: "dispatch works") - let quiescentExpectation = self.expectation(description: "quiescence has been determined") - DispatchQueue.global(qos: .userInitiated).async { [weak self] in - asyncExpectation.fulfill() - - let c = self!.cuttlefishContext.stateMachine.paused - XCTAssertEqual(0, c.wait(10 * NSEC_PER_SEC), "State machine should become quiescent") - quiescentExpectation.fulfill() - } - // Wait for the block above to fire before continuing - self.wait(for: [asyncExpectation], timeout: 10) - - // Run initialization, like the real secd will do - OctagonInitialize() - - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - XCTAssertTrue(self.cuttlefishContext.stateMachine.isPaused(), "State machine should be stopped") - self.assertNoAccount(context: self.cuttlefishContext) - - XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "State machine should be quiescent") - - self.wait(for: [quiescentExpectation], timeout: 10) - - // CKKS should also be logged out, since Octagon believes there's no account - assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) - } - func testLoadToUntrusted() throws { self.startCKAccountStatusMock() + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + // With no identity but AuthKit reporting an existing iCloud account, Octagon should go directly into 'untrusted' - OctagonInitialize() + self.cuttlefishContext.startOctagonStateMachine() + + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) - assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) } func testLoadToUntrustedIfTPHHasPreparedIdentityOnly() throws { self.startCKAccountStatusMock() + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + // Prepare an identity, then pretend like securityd thought it was in the right account let containerName = OTCKContainerName let contextName = OTDefaultContext @@ -1024,14 +1194,14 @@ class OctagonTests: OctagonTestsBase { machineID: "asdf", bottleSalt: "123456789", bottleID: UUID().uuidString, - modelID: "asdf", + modelID: "iPhone9,1", deviceName: "asdf", serialNumber: "1234", osVersion: "asdf", policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(peerID, "Should be a peer ID") XCTAssertNotNil(permanentInfo, "Should have a permenent info") @@ -1051,88 +1221,26 @@ class OctagonTests: OctagonTestsBase { XCTAssertNoThrow(try account.saveToKeychain(forContainer: containerName, contextID: contextName), "Should be no error saving fake account metadata") - OctagonInitialize() - - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) - - // CKKS should be waiting for assistance - assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) - } - - func testSignIn() throws { - self.startCKAccountStatusMock() - - // Device is signed out - self.mockAuthKit.altDSID = nil - self.mockAuthKit.hsa2 = false - - // With no account, Octagon should go directly into 'NoAccount' self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - // Sign in occurs - let newAltDSID = UUID().uuidString - self.mockAuthKit.altDSID = newAltDSID - self.mockAuthKit.hsa2 = true - XCTAssertNoThrow(try self.cuttlefishContext.accountAvailable(newAltDSID), "Sign-in shouldn't error") - - // Octagon should go into 'untrusted' - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 100 * NSEC_PER_SEC) - self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) - - assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) - - // On sign-out, octagon should go back to 'no account' - self.mockAuthKit.altDSID = nil - XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "sign-out shouldn't error") - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - self.assertNoAccount(context: self.cuttlefishContext) - - assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) - } - - func testSignInWithDelayedHSA2Status() throws { - self.startCKAccountStatusMock() - - // Device is signed out - self.mockAuthKit.altDSID = nil - self.mockAuthKit.hsa2 = false - - // With no account, Octagon should go directly into 'NoAccount' - self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - - // Sign in occurs, but HSA2 status isn't here yet - let newAltDSID = UUID().uuidString - self.mockAuthKit.altDSID = newAltDSID - XCTAssertNoThrow(try self.cuttlefishContext.accountAvailable(newAltDSID), "Sign-in shouldn't error") - - // Octagon should go into 'waitforhsa2' - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForHSA2, within: 10 * NSEC_PER_SEC) - - self.mockAuthKit.hsa2 = true - XCTAssertNoThrow(try self.cuttlefishContext.idmsTrustLevelChanged(), "Notification of IDMS trust level shouldn't error") + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) - assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) - - // On sign-out, octagon should go back to 'no account' - self.mockAuthKit.altDSID = nil - XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "sign-out shouldn't error") - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - self.assertNoAccount(context: self.cuttlefishContext) - + // CKKS should be waiting for assistance assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) } func testNewFriendsForEmptyAccount() throws { self.startCKAccountStatusMock() + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) do { let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) @@ -1146,12 +1254,19 @@ class OctagonTests: OctagonTestsBase { self.assertConsidersSelfTrusted(context: self.cuttlefishContext) self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext) + // and the act of calling newFriends should set the CDP bit + XCTAssertEqual(self.fetchCDPStatus(context: self.cuttlefishContext), .enabled, "CDP status should be 'enabled'") + // and all subCKKSes should enter ready... - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext) + // Also, CKKS should be configured with the prevailing policy version + XCTAssertNotNil(self.injectedManager?.policy, "Should have given CKKS a TPPolicy during initialization") + XCTAssertEqual(self.injectedManager?.policy?.version, prevailingPolicyVersion, "Policy given to CKKS should be prevailing policy") + // TODO: add a CKKS item } @@ -1196,7 +1311,7 @@ class OctagonTests: OctagonTestsBase { self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - let clique = try OTClique(contextData: self.otcliqueContext) + let clique = OTClique(contextData: self.otcliqueContext) // Now, call requestToJoin. It should cause an establish to happen try clique.requestToJoinCircle() @@ -1227,6 +1342,7 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -1255,6 +1371,7 @@ class OctagonTests: OctagonTestsBase { func testDeviceFetchRetry() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let ckError = FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .transactionalFailure) @@ -1281,36 +1398,22 @@ class OctagonTests: OctagonTestsBase { func testDeviceFetchRetryFail() throws { self.startCKAccountStatusMock() - self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + + self.assertResetAndBecomeTrustedInDefaultContext() let ckError = FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .transactionalFailure) self.fakeCuttlefishServer.nextFetchErrors.append(ckError) self.fakeCuttlefishServer.nextFetchErrors.append(ckError) - self.cuttlefishContext.notifyContainerChange(nil) - - do { - let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) - XCTAssertNotNil(clique, "Clique should not be nil") - } catch { - XCTFail("Shouldn't have errored making new friends: \(error)") - } - - // Now, we should be in 'ready' - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 20 * NSEC_PER_SEC) - self.assertConsidersSelfTrusted(context: self.cuttlefishContext) - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - self.verifyDatabaseMocks() - self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext) - self.cuttlefishContext.notifyContainerChange(nil) - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 20 * NSEC_PER_SEC) + self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateReadyUpdated]) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 30 * NSEC_PER_SEC) } func testNewFriendsForEmptyAccountReturnsMoreChanges() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.fakeCuttlefishServer.nextEstablishReturnsMoreChanges = true @@ -1335,7 +1438,7 @@ class OctagonTests: OctagonTestsBase { } func testNewFriendsForEmptyAccountWithoutTLKsResetsZones() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID!) + self.putFakeKeyHierarchiesInCloudKit() // But do NOT add them to the keychain // CKKS+Octagon should reset the zones and be ready @@ -1343,6 +1446,7 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) // CKKS should reset the zones after Octagon has entered @@ -1365,7 +1469,7 @@ class OctagonTests: OctagonTestsBase { } func testUploadTLKsRetry() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID!) + self.putFakeKeyHierarchiesInCloudKit() // But do NOT add them to the keychain // CKKS+Octagon should reset the zones and be ready @@ -1373,6 +1477,7 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) // CKKS should reset the zones after Octagon has entered @@ -1406,6 +1511,7 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -1444,8 +1550,8 @@ class OctagonTests: OctagonTestsBase { } func testNewFriendsForEmptyAccountWithTLKs() throws { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID!) - self.saveTLKMaterial(toKeychain: self.manateeZoneID!) + self.putFakeKeyHierarchiesInCloudKit() + self.saveTLKMaterialToKeychain() self.startCKAccountStatusMock() @@ -1453,6 +1559,7 @@ class OctagonTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC) @@ -1482,6 +1589,7 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -1496,6 +1604,10 @@ class OctagonTests: OctagonTestsBase { assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) + // Ensure CKKS has a policy after newFriends + XCTAssertNotNil(self.injectedManager?.policy, "Should have given CKKS a TPPolicy during initialization") + XCTAssertEqual(self.injectedManager?.policy?.version, prevailingPolicyVersion, "Policy given to CKKS should be prevailing policy") + let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID() XCTAssertNotNil(peerID, "Should have a peer ID after making new friends") @@ -1508,6 +1620,8 @@ class OctagonTests: OctagonTestsBase { self.restartCKKSViews() self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) + XCTAssertNil(self.injectedManager?.policy, "CKKS should not have a policy after 'restart'") + let restartDate = Date() self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) @@ -1520,14 +1634,40 @@ class OctagonTests: OctagonTestsBase { XCTAssertEqual(peerID, restartedPeerID, "Should have the same peer ID after restarting") assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + XCTAssertNotNil(self.injectedManager?.policy, "Should have given CKKS a TPPolicy after restart") + XCTAssertEqual(self.injectedManager?.policy?.version, prevailingPolicyVersion, "Policy given to CKKS after restart should be prevailing policy") + readyDate = CKKSAnalytics.logger().dateProperty(forKey: OctagonAnalyticsLastKeystateReady) XCTAssertNotNil(readyDate, "Should have a ready date") XCTAssert(readyDate! > restartDate, "ready date should be after re-startdate") } + func testFillInUnknownAttemptedJoinState() throws { + self.startCKAccountStatusMock() + + _ = self.assertResetAndBecomeTrustedInDefaultContext() + + try self.cuttlefishContext.accountMetadataStore.persistAccountChanges { metadata in + metadata.attemptedJoin = .UNKNOWN + return metadata + } + + self.manager.removeContext(forContainerName: OTCKContainerName, contextID: OTDefaultContext) + self.restartCKKSViews() + self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) + + self.cuttlefishContext.startOctagonStateMachine() + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + + // And check that the metadata is fixed: + let metadata = try self.cuttlefishContext.accountMetadataStore.loadOrCreateAccountMetadata() + XCTAssertEqual(metadata.attemptedJoin, .ATTEMPTED, "Should have attempted a join") + } + func testLoadToUntrustedOnRestartIfKeysGone() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -1567,6 +1707,7 @@ class OctagonTests: OctagonTestsBase { func testLoadToUntrustedOnRestartIfTPHLosesAllData() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -1606,6 +1747,7 @@ class OctagonTests: OctagonTestsBase { func testLoadToTrustedOnRestartIfMismatchedPeerIDs() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -1641,70 +1783,6 @@ class OctagonTests: OctagonTestsBase { XCTAssertEqual(peerID, newPeerID, "Should now have TPH's peer ID") } - func testStatusRPCsWithUnknownCloudKitAccount() throws { - // If CloudKit isn't returning our calls, we should still return something reasonable... - let statusexpectation = self.expectation(description: "trust status returns") - let configuration = OTOperationConfiguration() - configuration.timeoutWaitForCKAccount = 500 * NSEC_PER_MSEC - self.cuttlefishContext.rpcTrustStatus(configuration) { egoStatus, _, _, _, _ in - XCTAssertTrue([.absent].contains(egoStatus), "Self peer should be in the 'absent' state") - statusexpectation.fulfill() - } - self.wait(for: [statusexpectation], timeout: 10) - - // Now sign in to 'untrusted' - self.startCKAccountStatusMock() - - self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) - - // And restart, without any idea of the cloudkit state - self.ckaccountHoldOperation = BlockOperation() - self.injectedManager!.accountTracker = CKKSAccountStateTracker(self.injectedManager!.container, - nsnotificationCenterClass: FakeNSNotificationCenter.self as CKKSNSNotificationCenter.Type) - self.manager.accountStateTracker = self.injectedManager!.accountTracker - - self.manager.removeContext(forContainerName: OTCKContainerName, contextID: OTDefaultContext) - self.restartCKKSViews() - self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) - - // Should know it's untrusted - self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) - self.startCKAccountStatusMock() - - // Now become ready - do { - let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) - XCTAssertNotNil(clique, "Clique should not be nil") - } catch { - XCTFail("Shouldn't have errored making new friends: \(error)") - } - - // Now, we should be in 'ready' - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) - self.assertConsidersSelfTrusted(context: self.cuttlefishContext) - self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext) - - // and all subCKKSes should enter ready... - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - self.verifyDatabaseMocks() - - // Restart one more time: - - self.ckaccountHoldOperation = BlockOperation() - self.injectedManager!.accountTracker = CKKSAccountStateTracker(self.injectedManager!.container, - nsnotificationCenterClass: FakeNSNotificationCenter.self as CKKSNSNotificationCenter.Type) - self.manager.accountStateTracker = self.injectedManager!.accountTracker - - self.manager.removeContext(forContainerName: OTCKContainerName, contextID: OTDefaultContext) - self.restartCKKSViews() - self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) - - self.assertConsidersSelfTrusted(context: self.cuttlefishContext) - self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext) - } - func testRestoreToNewClique() throws { self.startCKAccountStatusMock() @@ -1737,11 +1815,10 @@ class OctagonTests: OctagonTestsBase { let memberIdentifier = clique!.cliqueMemberIdentifier let dumpExpectation = self.expectation(description: "dump callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, error in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, error in XCTAssertNil(error, "Should be no error dumping data") XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let peerID = egoSelf!["peerID"] as? String XCTAssertNotNil(peerID, "peerID should not be nil") @@ -1756,6 +1833,7 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -1783,94 +1861,11 @@ class OctagonTests: OctagonTestsBase { // TODO: an item added here shouldn't sync } - func testSignOut() throws { - self.startCKAccountStatusMock() - - self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - - do { - let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) - XCTAssertNotNil(clique, "Clique should not be nil") - } catch { - XCTFail("Shouldn't have errored making new friends: \(error)") - } - - // Now, we should be in 'ready' - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) - self.assertConsidersSelfTrusted(context: self.cuttlefishContext) - self.verifyDatabaseMocks() - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - - // And 'dump' should show some information - let dumpExpectation = self.expectation(description: "dump callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, error in - XCTAssertNil(error, "Should be no error dumping data") - XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary - XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let peerID = egoSelf!["peerID"] as? String - XCTAssertNotNil(peerID, "peerID should not be nil") - - dumpExpectation.fulfill() - } - self.wait(for: [dumpExpectation], timeout: 10) - - // Turn off the CK account too - self.accountStatus = .noAccount - self.accountStateTracker.notifyCKAccountStatusChangeAndWaitForSignal() - - XCTAssertNoThrow(try self.cuttlefishContext.accountNoLongerAvailable(), "Should be no issue signing out") - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - self.assertNoAccount(context: self.cuttlefishContext) - - // And 'dump' should show nothing - let signedOutDumpExpectation = self.expectation(description: "dump callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, error in - XCTAssertNil(error, "Should be no error dumping data") - XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary - XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - XCTAssertEqual(egoSelf!.count, 0, "egoSelf should have zero elements") - - signedOutDumpExpectation.fulfill() - } - self.wait(for: [signedOutDumpExpectation], timeout: 10) - assertAllCKKSViews(enter: SecCKKSZoneKeyStateLoggedOut, within: 10 * NSEC_PER_SEC) - - //check trust status - let checkTrustExpectation = self.expectation(description: "checkTrustExpectation callback occurs") - let configuration = OTOperationConfiguration() - self.cuttlefishContext.rpcTrustStatus(configuration) { _, _, _, _, _ in - checkTrustExpectation.fulfill() - } - self.wait(for: [checkTrustExpectation], timeout: 10) - - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - - // And 'dump' should show nothing - let signedOutDumpExpectationAfterCheckTrustStatus = self.expectation(description: "dump callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, error in - XCTAssertNil(error, "Should be no error dumping data") - XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary - XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - XCTAssertEqual(egoSelf!.count, 0, "egoSelf should have zero elements") - - signedOutDumpExpectationAfterCheckTrustStatus.fulfill() - } - self.wait(for: [signedOutDumpExpectationAfterCheckTrustStatus], timeout: 10) - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - self.assertNoAccount(context: self.cuttlefishContext) - } - func testCliqueFriendAPI() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) @@ -1915,7 +1910,7 @@ class OctagonTests: OctagonTestsBase { accountStateTracker: self.accountStateTracker, deviceInformationAdapter: peer2DeviceAdapter) - self.setAllowListToCurrentAuthKit(container: OTCKContainerName, context: peer2ContextID) + self.setAllowListToCurrentAuthKit(container: OTCKContainerName, context: peer2ContextID, accountIsDemo: false) var peer2ID: String! let joinExpectation = self.expectation(description: "join callback occurs") @@ -1932,7 +1927,7 @@ class OctagonTests: OctagonTestsBase { policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(permanentInfo, "Should have a permanent identity") XCTAssertNotNil(permanentInfoSig, "Should have a permanent identity signature") @@ -1955,7 +1950,7 @@ class OctagonTests: OctagonTestsBase { voucherSig: voucherSig!, ckksKeys: [], tlkShares: [], - preapprovedKeys: []) { peerID, _, error in + preapprovedKeys: []) { peerID, _, _, _, error in XCTAssertNil(error, "Should be no error joining") XCTAssertNotNil(peerID, "Should have a peerID") peer2ID = peerID @@ -1970,6 +1965,7 @@ class OctagonTests: OctagonTestsBase { account.peerID = peer2ID account.icloudAccountState = .ACCOUNT_AVAILABLE account.trustState = .TRUSTED + account.attemptedJoin = . ATTEMPTED XCTAssertNoThrow(try account.saveToKeychain(forContainer: OTCKContainerName, contextID: peer2ContextID), "Should be no error saving fake account metadata") peer2.startOctagonStateMachine() @@ -2013,43 +2009,16 @@ class OctagonTests: OctagonTestsBase { } } - func testNoAccountLeadsToInitialize() throws { - self.startCKAccountStatusMock() - - // With no identity and AuthKit reporting no iCloud account, Octagon should go directly into 'no account' - self.mockAuthKit.altDSID = nil - - // Run initialization, like the real secd will do - OctagonInitialize() - - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - - self.mockAuthKit.altDSID = "1234" - let signinExpectation = self.expectation(description: "sign in returns") - self.otControl.sign(in: "1234", container: nil, context: OTDefaultContext) { error in - XCTAssertNil(error, "error should be nil") - signinExpectation.fulfill() - } - self.wait(for: [signinExpectation], timeout: 10) - - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - - } - func testOTCliqueOctagonAuthoritativeTrustResponse() throws { self.startCKAccountStatusMock() OctagonAuthoritativeTrustSetIsEnabled(true) - do { - let absentClique = try OTClique(contextData: self.otcliqueContext) - let absentStatus = absentClique.fetchStatus(nil) - XCTAssertEqual(absentStatus, CliqueStatus.absent, "clique should return Absent") - } catch { - XCTFail("Shouldn't have errored making new friends: \(error)") - throw error - } + let absentClique = OTClique(contextData: self.otcliqueContext) + let absentStatus = absentClique.fetchStatus(nil) + XCTAssertEqual(absentStatus, CliqueStatus.absent, "clique should return Absent") self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -2094,7 +2063,7 @@ class OctagonTests: OctagonTestsBase { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } - let newContext = self.manager.context(forContainerName: OTCKContainerName, contextID: newOTCliqueContext.context!) + let newContext = self.manager.context(forContainerName: OTCKContainerName, contextID: newOTCliqueContext.context) self.assertEnters(context: newContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) } @@ -2119,39 +2088,17 @@ class OctagonTests: OctagonTestsBase { json: false) } - func testNoAccountTimeoutTransitionWatcher() throws { - self.startCKAccountStatusMock() - - // With no identity and AuthKit reporting no iCloud account, Octagon should go directly into 'no account' - self.mockAuthKit.altDSID = nil - - // Run initialization, like the real secd will do - OctagonInitialize() - self.cuttlefishContext.stateMachine.setWatcherTimeout(2 * NSEC_PER_SEC) - - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateNoAccount, within: 10 * NSEC_PER_SEC) - XCTAssertTrue(self.cuttlefishContext.stateMachine.isPaused(), "State machine should be stopped") - self.assertNoAccount(context: self.cuttlefishContext) - XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "State machine should be quiescent") - - let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") - self.cuttlefishContext.join(withBottle: "bottleID", entropy: Data(), bottleSalt: "peer2AltDSID") { error in - XCTAssertNotNil(error, "error should not be nil") - joinWithBottleExpectation.fulfill() - } - self.wait(for: [joinWithBottleExpectation], timeout: 3) - } - func testFailingStateTransitionWatcher() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) // Set up a watcher that we expect to fail... let path = OctagonStateTransitionPath(from: [ OctagonStateResetAndEstablish: [ - OctagonStateInitiatorVouchWithBottle: [ + OctagonStateBottleJoinVouchWithBottle: [ OctagonStateResetAndEstablish: OctagonStateTransitionPathStep.success(), ], ], @@ -2163,10 +2110,10 @@ class OctagonTests: OctagonTestsBase { self.cuttlefishContext.stateMachine.register(watcher) let watcherCompleteOperationExpectation = self.expectation(description: "watcherCompleteOperationExpectation returns") - let watcherFinishOp = CKKSResultOperation.named("should-fail-cleanup", with: { + let watcherFinishOp = CKKSResultOperation.named("should-fail-cleanup") { XCTAssertNotNil(watcher.result.error, "watcher should have errored") watcherCompleteOperationExpectation.fulfill() - }) + } watcherFinishOp.addDependency(watcher.result) self.operationQueue.addOperation(watcherFinishOp) @@ -2189,6 +2136,7 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let stateTransitionOp = OctagonStateTransitionOperation(name: "will-never-run", @@ -2202,7 +2150,7 @@ class OctagonTests: OctagonTestsBase { // Set up a watcher that we expect to fail due to its initial transition op timing out... let path = OctagonStateTransitionPath(from: [ OctagonStateResetAndEstablish: [ - OctagonStateInitiatorVouchWithBottle: [ + OctagonStateBottleJoinVouchWithBottle: [ OctagonStateResetAndEstablish: OctagonStateTransitionPathStep.success(), ], ], @@ -2214,10 +2162,10 @@ class OctagonTests: OctagonTestsBase { self.cuttlefishContext.stateMachine.register(watcher) let watcherCompleteOperationExpectation = self.expectation(description: "watcherCompleteOperationExpectation returns") - let watcherFinishOp = CKKSResultOperation.named("should-fail-cleanup", with: { + let watcherFinishOp = CKKSResultOperation.named("should-fail-cleanup") { XCTAssertNotNil(watcher.result.error, "watcher should have errored") watcherCompleteOperationExpectation.fulfill() - }) + } watcherFinishOp.addDependency(watcher.result) self.operationQueue.addOperation(watcherFinishOp) @@ -2233,6 +2181,7 @@ class OctagonTests: OctagonTestsBase { OctagonAuthoritativeTrustSetIsEnabled(true) self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -2260,7 +2209,7 @@ class OctagonTests: OctagonTestsBase { self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let cfuExpectation = self.expectation(description: "cfu callback occurs") - self.cuttlefishContext.setPostedBool(false) + self.cuttlefishContext.followupHandler.clearAllPostedFlags() self.cuttlefishContext.checkTrustStatusAndPostRepairCFUIfNecessary { _, posted, _, error in #if !os(tvOS) XCTAssertTrue(posted, "posted should be true") @@ -2274,6 +2223,9 @@ class OctagonTests: OctagonTestsBase { } func testDeviceLockedDuringAccountRetrieval() throws { + // Tell SOS that it is absent, so we don't enable CDP on bringup + self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCCircleAbsent) + self.startCKAccountStatusMock() self.aksLockState = true @@ -2298,6 +2250,9 @@ class OctagonTests: OctagonTestsBase { self.aksLockState = false self.lockStateTracker.recheck() + self.assertEnters(context: initiatorContext, state: OctagonStateWaitForCDP, within: 10 * NSEC_PER_SEC) + + XCTAssertNoThrow(try initiatorContext.setCDPEnabled()) self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) } @@ -2305,6 +2260,7 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -2333,6 +2289,7 @@ class OctagonTests: OctagonTestsBase { func testFetchViewList() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -2358,16 +2315,18 @@ class OctagonTests: OctagonTestsBase { "WiFi", "Health", "Manatee", - "CreditCards", - "Passwords", + // Cuttlefish: remove Safari prefix from view names + "SafariCreditCards", + "SafariPasswords", "ApplePay", ]) #else let expectedViews = Set(["LimitedPeersAllowed", + "Home", "WiFi", ]) #endif let getViewsExpectation = self.expectation(description: "getViews callback happens") - self.tphClient.getViewsWithContainer(OTCKContainerName, context: OTDefaultContext, inViews: []) { outViews, error in + self.tphClient.fetchCurrentPolicy(withContainer: OTCKContainerName, context: OTDefaultContext) { outViews, _, error in XCTAssertNil(error, "should not have failed") XCTAssertEqual(expectedViews, Set(outViews!)) getViewsExpectation.fulfill() @@ -2375,73 +2334,6 @@ class OctagonTests: OctagonTestsBase { self.wait(for: [getViewsExpectation], timeout: 10) } - func testMergedViewListOff() throws { - self.startCKAccountStatusMock() - self.cuttlefishContext.viewManager!.setOverrideCKKSViewsFromPolicy(false) - self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - - do { - let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) - XCTAssertNotNil(clique, "Clique should not be nil") - } catch { - XCTFail("Shouldn't have errored making new friends: \(error)") - } - - // Now, we should be in 'ready' - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) - self.assertConsidersSelfTrusted(context: self.cuttlefishContext) - - let viewList = self.cuttlefishContext.viewManager!.viewList() - #if !os(tvOS) - let expected = Set(["Manatee"]) - #else - let expected = Set(["LimitedPeersAllowed"]) - #endif - XCTAssertEqual(expected, viewList) - } - - func testMergedViewListOn() throws { - /* - self.startCKAccountStatusMock() - self.cuttlefishContext.viewManager!.setOverrideCKKSViewsFromPolicy(true) - self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - - do { - let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, .testGenerated) - XCTAssertNotNil(clique, "Clique should not be nil") - } catch { - XCTFail("Shouldn't have errored making new friends: \(error)") - } - - // Now, we should be in 'ready' - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) - self.assertConsidersSelfTrusted(context: self.cuttlefishContext) - - let viewList = self.cuttlefishContext.viewManager!.viewList() - let expected = Set([ - "ApplePay", - "Applications", - "AutoUnlock", - "Backstop", - "DevicePairing", - "Engram", - "Health", - "Home", - "LimitedPeersAllowed", - "Manatee", - "ProtectedCloudStorage", - "SafariCreditCards", - "SafariPasswords", - "SecureObjectSync", - "WiFi", - "keychain", - ]) - XCTAssertEqual(expected, viewList) - */ - } - let octagonNotificationName = "com.apple.security.octagon.trust-status-change" func testNotifications() throws { @@ -2452,8 +2344,9 @@ class OctagonTests: OctagonTestsBase { let untrustedNotification = XCTDarwinNotificationExpectation(notificationName: octagonNotificationName) self.startCKAccountStatusMock() - self.cuttlefishContext.startOctagonStateMachine() + self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.wait(for: [untrustedNotification], timeout: 2) @@ -2505,11 +2398,11 @@ class OctagonTests: OctagonTestsBase { self.assertConsidersSelfTrusted(context: self.cuttlefishContext) let statusExpectation = self.expectation(description: "status callback occurs") - self.tphClient.dumpEgoPeer(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID, reply: { _, _, stableInfo, _, error in + self.tphClient.dumpEgoPeer(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { _, _, stableInfo, _, error in XCTAssertNil(error, "should be no error dumping ego peer") XCTAssertEqual(stableInfo?.deviceName, newDeviceName, "device name should be updated") statusExpectation.fulfill() - }) + } self.wait(for: [statusExpectation], timeout: 2) // Receiving a push shouldn't cause another update to be sent @@ -2616,11 +2509,28 @@ class OctagonTests: OctagonTestsBase { let untrustedNotification = XCTDarwinNotificationExpectation(notificationName: octagonNotificationName) self.startCKAccountStatusMock() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.cuttlefishContext.startOctagonStateMachine() + // Octagon will fetch once to determine its trust state + let trustStateFetchExpectation = self.expectation(description: "trust state fetch occurs") + self.fakeCuttlefishServer.fetchChangesListener = { [unowned self] _ in + self.fakeCuttlefishServer.fetchChangesListener = nil + trustStateFetchExpectation.fulfill() + return nil + } + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.wait(for: [untrustedNotification], timeout: 2) + self.wait(for: [trustStateFetchExpectation], timeout: 10) + + let fetchExpectation = self.expectation(description: "fetch occurs") + self.fakeCuttlefishServer.fetchChangesListener = { _ in + fetchExpectation.fulfill() + return nil + } + self.cuttlefishContext.notifyContainerChange(nil) self.cuttlefishContext.notifyContainerChange(nil) @@ -2631,7 +2541,8 @@ class OctagonTests: OctagonTestsBase { self.cuttlefishContext.notifyContainerChange(nil) - XCTAssertEqual(self.fakeCuttlefishServer.fetchChangesCalledCount, 1, "fetchChanges should have been called 1 times") + self.wait(for: [fetchExpectation], timeout: 10) + XCTAssertEqual(self.fakeCuttlefishServer.fetchChangesCalledCount, 2, "fetchChanges should have been called 1 times") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) @@ -2641,20 +2552,38 @@ class OctagonTests: OctagonTestsBase { let untrustedNotification = XCTDarwinNotificationExpectation(notificationName: octagonNotificationName) self.startCKAccountStatusMock() + + // Set the CDP bit before the test begins, so we don't have to fetch to discover CDP status + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) + self.cuttlefishContext.startOctagonStateMachine() + // Octagon will fetch once to determine its trust state + let trustStateFetchExpectation = self.expectation(description: "trust state fetch occurs") + self.fakeCuttlefishServer.fetchChangesListener = { [unowned self] _ in + self.fakeCuttlefishServer.fetchChangesListener = nil + trustStateFetchExpectation.fulfill() + return nil + } + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.wait(for: [untrustedNotification], timeout: 2) - self.cuttlefishContext.notifyContainerChange(nil) + self.wait(for: [trustStateFetchExpectation], timeout: 10) - self.cuttlefishContext.notifyContainerChange(nil) + let fetchExpectation = self.expectation(description: "fetch occurs") + self.fakeCuttlefishServer.fetchChangesListener = { _ in + fetchExpectation.fulfill() + return nil + } self.cuttlefishContext.notifyContainerChange(nil) - + self.cuttlefishContext.notifyContainerChange(nil) + self.cuttlefishContext.notifyContainerChange(nil) self.cuttlefishContext.notifyContainerChange(nil) - XCTAssertEqual(self.fakeCuttlefishServer.fetchChangesCalledCount, 1, "fetchChanges should have been called 1 times") + self.wait(for: [fetchExpectation], timeout: 10) + XCTAssertEqual(self.fakeCuttlefishServer.fetchChangesCalledCount, 2, "fetchChanges should have been called 1 times") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC) @@ -2664,7 +2593,14 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() OctagonSetPlatformSupportsSOS(true) - let initiatorPiggybackingConfig = OTJoiningConfiguration(protocolType: OTProtocolPiggybacking, uniqueDeviceID: "initiator", uniqueClientID: "acceptor", containerName: OTCKContainerName, contextID: OTDefaultContext, epoch: 1, isInitiator: true) + let initiatorPiggybackingConfig = OTJoiningConfiguration(protocolType: OTProtocolPiggybacking, + uniqueDeviceID: "initiator", + uniqueClientID: "acceptor", + pairingUUID: UUID().uuidString, + containerName: OTCKContainerName, + contextID: OTDefaultContext, + epoch: 1, + isInitiator: true) let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablish callback occurs") self.manager.resetAndEstablish(OTCKContainerName, @@ -2707,6 +2643,7 @@ class OctagonTests: OctagonTestsBase { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { @@ -2740,12 +2677,27 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { struct TestCase { let model: String let success: Bool - let sendTLKs: Bool + let manateeTLKs: Bool + let limitedTLKs: Bool + } + + func assertTLKs(expectation: TestCase, receiverPeerID: String, senderPeerID: String) throws { + let haveManateeTLK = try self.tlkShareInCloudKit(receiverPeerID: receiverPeerID, + senderPeerID: senderPeerID, + zoneID: self.manateeZoneID) + let haveLimitedPeersAllowedTLK = try self.tlkShareInCloudKit(receiverPeerID: receiverPeerID, + senderPeerID: senderPeerID, + zoneID: self.limitedPeersAllowedZoneID) + + XCTAssertEqual(haveManateeTLK, expectation.manateeTLKs, "manatee should be what's expected: \(expectation)") + XCTAssertEqual(haveLimitedPeersAllowedTLK, expectation.limitedTLKs, "limited should be what's expected: \(expectation)") } func _testVouchers(expectations: [TestCase]) throws { self.startCKAccountStatusMock() + self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -2760,7 +2712,15 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) - _ = fetchEgoPeerID() + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + + let ckksKeys: [CKKSKeychainBackedKeySet] = self.ckksViews.compactMap { view in + let viewName = (view as! CKKSKeychainView).zoneName + let currentKeySet = CKKSCurrentKeySet.load(forZone: CKRecordZone.ID(zoneName: viewName)) + return try! currentKeySet.asKeychainBackedSet() + } + + let senderPeerID = fetchEgoPeerID() for testCase in expectations { let model = testCase.model @@ -2775,7 +2735,7 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { accountStateTracker: self.accountStateTracker, deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-SOS-iphone", serialNumber: "456", osVersion: "iOS (fake version)")) - self.setAllowListToCurrentAuthKit(container: OTCKContainerName, context: peer2ContextID) + self.setAllowListToCurrentAuthKit(container: OTCKContainerName, context: peer2ContextID, accountIsDemo: false) let peer2DeviceName = "peer2-asdf" let joinExpectation = self.expectation(description: "join callback occurs") @@ -2792,7 +2752,7 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(permanentInfo, "Should have a permanent identity") XCTAssertNotNil(permanentInfoSig, "Should have a permanent identity signature") @@ -2806,17 +2766,22 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { permanentInfoSig: permanentInfoSig!, stableInfo: stableInfo!, stableInfoSig: stableInfoSig!, - ckksKeys: []) { voucher, voucherSig, error in + ckksKeys: ckksKeys) { voucher, voucherSig, error in XCTAssertNil(error, "Should be no error vouching") XCTAssertNotNil(voucher, "Should have a voucher") XCTAssertNotNil(voucherSig, "Should have a voucher signature") + + try! self.assertTLKs(expectation: testCase, + receiverPeerID: peerID!, + senderPeerID: senderPeerID) + self.tphClient.join(withContainer: OTCKContainerName, context: peer2ContextID, voucherData: voucher!, voucherSig: voucherSig!, ckksKeys: [], tlkShares: [], - preapprovedKeys: []) { peerID, _, error in + preapprovedKeys: []) { peerID, _, _, _, error in XCTAssertNil(error, "Should be no error joining") XCTAssertNotNil(peerID, "Should have a peerID") joinExpectation.fulfill() @@ -2834,9 +2799,15 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { XCTAssertNil(voucher, "voucher should be nil") XCTAssertNil(voucherSig, "voucherSig should be nil") XCTAssertNotNil(error, "error should be non nil") + + try! self.assertTLKs(expectation: testCase, + receiverPeerID: peerID!, + senderPeerID: senderPeerID) + joinExpectation.fulfill() } } + } self.wait(for: [joinExpectation], timeout: 10) } @@ -2845,6 +2816,7 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { func _testJoin(expectations: [TestCase]) throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() + XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique @@ -2872,7 +2844,7 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { let peer2DeviceName = "peer2-device-name" var peer2ID: String! - self.setAllowListToCurrentAuthKit(container: OTCKContainerName, context: peer2ContextID) + self.setAllowListToCurrentAuthKit(container: OTCKContainerName, context: peer2ContextID, accountIsDemo: false) let joinExpectation = self.expectation(description: "join callback occurs") self.tphClient.prepare(withContainer: OTCKContainerName, @@ -2888,7 +2860,7 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { policyVersion: nil, policySecrets: nil, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in XCTAssertNil(error, "Should be no error preparing identity") XCTAssertNotNil(permanentInfo, "Should have a permanent identity") XCTAssertNotNil(permanentInfoSig, "Should have a permanent identity signature") @@ -2911,7 +2883,7 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { voucherSig: voucher!.sig, ckksKeys: [], tlkShares: [], - preapprovedKeys: []) { peerID, _, error in + preapprovedKeys: []) { peerID, _, _, _, error in if expectedSuccess { XCTAssertNil(error, "expected success") XCTAssertNotNil(peerID, "peerID should be set") @@ -2939,9 +2911,11 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { return nil } - // Maybe send TLKs? - if testCase.sendTLKs { - self.assertAllCKKSViewsUpload(tlkShares: 1) + if testCase.manateeTLKs { + self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.manateeZoneID) + } + if testCase.limitedTLKs { + self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.limitedPeersAllowedZoneID) } self.cuttlefishContext.notifyContainerChange(nil) @@ -2950,69 +2924,59 @@ class OctagonTestsOverrideModelBase: OctagonTestsBase { self.fakeCuttlefishServer.updateListener = nil self.sendAllCKKSTrustedPeersChanged() - self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) } + + try self.assertTLKs(expectation: testCase, + receiverPeerID: peer2ID, + senderPeerID: peer1ID) } } } -class OctagonTestsOverrideModletV: OctagonTestsOverrideModelBase { - // If this test is running on a TV, we will send TLKs, since we're using the LimitedPeersAllowed view - #if !os(tvOS) - let sendTLKsToAllPeers = false - #else - let sendTLKsToAllPeers = true - #endif - +class OctagonTestsOverrideModelTV: OctagonTestsOverrideModelBase { override func setUp() { - super.setUp() + self.mockDeviceInfo = OTMockDeviceInfoAdapter(modelID: "AppleTV5,3", + deviceName: "intro-TV", + serialNumber: "456", + osVersion: "tvOS (whatever TV version)") - self.mockDeviceInfo.mockModelID = "AppleTV5,3" - self.mockDeviceInfo.mockDeviceName = "intro-TV" - self.mockDeviceInfo.mockSerialNumber = "456" - self.mockDeviceInfo.mockOsVersion = "iOS (whatever TV version)" + super.setUp() } func testVoucherFromTV() throws { - try self._testVouchers(expectations: [TestCase(model: "AppleTV5,3", success: true, sendTLKs: sendTLKsToAllPeers), - TestCase(model: "MacFoo", success: false, sendTLKs: sendTLKsToAllPeers), - TestCase(model: "Watch17", success: false, sendTLKs: sendTLKsToAllPeers), ]) + try self._testVouchers(expectations: [TestCase(model: "AppleTV5,3", success: true, manateeTLKs: false, limitedTLKs: true), + TestCase(model: "MacFoo", success: false, manateeTLKs: false, limitedTLKs: false), + TestCase(model: "Watch17", success: false, manateeTLKs: false, limitedTLKs: false), ]) } func testJoinFromTV() throws { - try self._testJoin(expectations: [TestCase(model: "AppleTV5,3", success: true, sendTLKs: sendTLKsToAllPeers), - TestCase(model: "MacFoo", success: false, sendTLKs: sendTLKsToAllPeers), - TestCase(model: "Watch17", success: false, sendTLKs: sendTLKsToAllPeers), ]) + try self._testJoin(expectations: [TestCase(model: "AppleTV5,3", success: true, manateeTLKs: false, limitedTLKs: true), + TestCase(model: "MacFoo", success: false, manateeTLKs: false, limitedTLKs: false), + TestCase(model: "Watch17", success: false, manateeTLKs: false, limitedTLKs: false), ]) } } class OctagonTestsOverrideModelMac: OctagonTestsOverrideModelBase { - #if !os(tvOS) - let sendTLKsToAllPeers = false - #else - let sendTLKsToAllPeers = true - #endif - override func setUp() { + self.mockDeviceInfo = OTMockDeviceInfoAdapter(modelID: "Mac17", + deviceName: "macbook", + serialNumber: "456", + osVersion: "OSX 11") super.setUp() - - self.mockDeviceInfo.mockModelID = "Mac17" - self.mockDeviceInfo.mockDeviceName = "macbook" - self.mockDeviceInfo.mockSerialNumber = "456" - self.mockDeviceInfo.mockOsVersion = "OSX 11" } func testVoucherFromMac() throws { - try self._testVouchers(expectations: [TestCase(model: "AppleTV5,3", success: true, sendTLKs: sendTLKsToAllPeers), - TestCase(model: "MacFoo", success: true, sendTLKs: sendTLKsToAllPeers), - TestCase(model: "Watch17", success: true, sendTLKs: sendTLKsToAllPeers), ]) + try self._testVouchers(expectations: [TestCase(model: "AppleTV5,3", success: true, manateeTLKs: false, limitedTLKs: true), + TestCase(model: "MacFoo", success: true, manateeTLKs: true, limitedTLKs: true), + TestCase(model: "Watch17", success: true, manateeTLKs: true, limitedTLKs: true), ]) } func testJoinFromMac() throws { - try self._testJoin(expectations: [TestCase(model: "AppleTV5,3", success: true, sendTLKs: sendTLKsToAllPeers), - TestCase(model: "MacFoo", success: true, sendTLKs: true), - TestCase(model: "Watch17", success: true, sendTLKs: true), ]) + try self._testJoin(expectations: [TestCase(model: "AppleTV5,3", success: true, manateeTLKs: false, limitedTLKs: true), + TestCase(model: "MacFoo", success: true, manateeTLKs: true, limitedTLKs: true), + TestCase(model: "Watch17", success: true, manateeTLKs: true, limitedTLKs: true), ]) } } diff --git a/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+Piggybacking.swift b/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+Piggybacking.swift index 17901fea..2c76a5ec 100644 --- a/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+Piggybacking.swift +++ b/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+Piggybacking.swift @@ -92,12 +92,10 @@ extension OctagonPairingTests { } self.wait(for: [rpcEpochCallbackOccurs], timeout: 10) - let initiator1Context = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) - let clientStateMachine = self.manager.clientStateMachine(forContainerName: OTCKContainerName, contextID: self.contextForAcceptor, clientName: self.initiatorName) - initiator1Context.startOctagonStateMachine() + self.cuttlefishContext.startOctagonStateMachine() - self.assertEnters(context: initiator1Context, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) + self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) var peerID: String = "" var permanentInfo = Data(count: 0) @@ -140,15 +138,15 @@ extension OctagonPairingTests { self.wait(for: [firstMessageAcceptorCallback], timeout: 10) let rpcJoinCallback = self.expectation(description: "joining callback") - self.manager.rpcJoin(with: self.initiatorPiggybackingConfig, vouchData: voucher, vouchSig: voucherSig, preapprovedKeys: nil) { error in + self.manager.rpcJoin(with: self.initiatorPiggybackingConfig, vouchData: voucher, vouchSig: voucherSig) { error in XCTAssertNil(error, "error should be nil") rpcJoinCallback.fulfill() } self.wait(for: [rpcJoinCallback], timeout: 10) - assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) - self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) + self.assertEnters(context: self.cuttlefishContextForAcceptor, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) self.assertConsidersSelfTrusted(context: self.cuttlefishContextForAcceptor) @@ -156,14 +154,13 @@ extension OctagonPairingTests { clientStateMachine.notifyContainerChange() let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -172,14 +169,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -187,9 +183,11 @@ extension OctagonPairingTests { self.wait(for: [acceptorDumpCallback], timeout: 10) self.verifyDatabaseMocks() + + self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) } - func testVersion2ofPiggybacking() { + func testVersion2ofPiggybacking() throws { KCSetJoiningOctagonPiggybackingEnabled(true) OctagonSetIsEnabled(true) self.startCKAccountStatusMock() @@ -264,7 +262,7 @@ extension OctagonPairingTests { error: nil) XCTAssertNotNil(requestCircleSession, "No request secret session") - requestCircleSession.setJoiningConfigurationObject(self.initiatorPiggybackingConfig) + requestCircleSession.setContextIDOnJoiningConfiguration(self.initiatorPiggybackingConfig.contextID) requestCircleSession.setControlObject(self.otControl) var identityMessage: Data? @@ -276,6 +274,10 @@ extension OctagonPairingTests { XCTAssertNil(error, "error retrieving identityMessage message") } + // Double-check that there's an Octagon message in the packet + let initiatorIdentityMessage = try self.unpackPiggybackingInitialMessage(identityMessage: identityMessage!, session: acceptSession!.accessSession()) + XCTAssertTrue(initiatorIdentityMessage.hasPrepare, "Pairing message should contain prepared information") + var voucherMessage: Data? do { voucherMessage = try acceptSession!.processMessage(identityMessage!) @@ -312,14 +314,13 @@ extension OctagonPairingTests { self.verifyDatabaseMocks() let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -328,14 +329,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -545,7 +545,7 @@ extension OctagonPairingTests { self.wait(for: [firstMessageWithNewJoiningConfigCallback], timeout: 10) } - func testVersion2ofPiggybackingWithSOS() { + func testVersion2ofPiggybackingWithSOS() throws { KCSetJoiningOctagonPiggybackingEnabled(true) OctagonSetPlatformSupportsSOS(true) self.startCKAccountStatusMock() @@ -626,7 +626,7 @@ extension OctagonPairingTests { error: nil) XCTAssertNotNil(requestCircleSession, "No request secret session") - requestCircleSession.setJoiningConfigurationObject(self.initiatorPiggybackingConfig) + requestCircleSession.setContextIDOnJoiningConfiguration(self.initiatorPiggybackingConfig.contextID) requestCircleSession.setControlObject(self.otControl) var identityMessage: Data? @@ -641,6 +641,10 @@ extension OctagonPairingTests { XCTAssertNil(error, "error retrieving identityMessage message") } + // Double-check that there's an Octagon message in the packet + let initiatorIdentityMessage = try self.unpackPiggybackingInitialMessage(identityMessage: identityMessage!, session: acceptSession!.accessSession()) + XCTAssertTrue(initiatorIdentityMessage.hasPrepare, "Pairing message should contain prepared information") + var voucherMessage: Data? do { voucherMessage = try acceptSession!.processMessage(identityMessage!) @@ -650,7 +654,6 @@ extension OctagonPairingTests { XCTAssertNotNil(voucherMessage, "No voucherMessage message") } catch { XCTAssertNil(error, "error retrieving voucherMessage message") - } var nothing: Data? @@ -680,14 +683,13 @@ extension OctagonPairingTests { self.assertTLKSharesInCloudKit(receiver: self.cuttlefishContextForAcceptor, sender: self.cuttlefishContext) let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -696,14 +698,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -791,7 +792,7 @@ extension OctagonPairingTests { error: nil) XCTAssertNotNil(requestCircleSession, "No request secret session") - requestCircleSession.setJoiningConfigurationObject(self.initiatorPiggybackingConfig) + requestCircleSession.setContextIDOnJoiningConfiguration(self.initiatorPiggybackingConfig.contextID) requestCircleSession.setControlObject(self.otControl) var identityMessage: Data? diff --git a/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProxMultiClients.swift b/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProxMultiClients.swift index e6b28859..142b3410 100644 --- a/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProxMultiClients.swift +++ b/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProxMultiClients.swift @@ -145,14 +145,13 @@ extension OctagonPairingTests { clientStateMachine.notifyContainerChange() let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -161,14 +160,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -285,14 +283,13 @@ extension OctagonPairingTests { clientStateMachine2.notifyContainerChange() let pair2InitiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -301,14 +298,13 @@ extension OctagonPairingTests { self.wait(for: [pair2InitiatorDumpCallback], timeout: 10) let pair2AcceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 3, "should be 3 peer ids") pair2AcceptorDumpCallback.fulfill() @@ -437,14 +433,13 @@ extension OctagonPairingTests { clientStateMachine.notifyContainerChange() let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -453,14 +448,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -549,14 +543,13 @@ extension OctagonPairingTests { clientStateMachine2.notifyContainerChange() let pair2InitiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -565,14 +558,13 @@ extension OctagonPairingTests { self.wait(for: [pair2InitiatorDumpCallback], timeout: 10) let pair2AcceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 3, "should be 3 peer ids") pair2AcceptorDumpCallback.fulfill() @@ -973,14 +965,13 @@ extension OctagonPairingTests { clientStateMachine.notifyContainerChange() let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -989,14 +980,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -1036,14 +1026,13 @@ extension OctagonPairingTests { clientStateMachine.notifyContainerChange() let pair2InitiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -1052,14 +1041,13 @@ extension OctagonPairingTests { self.wait(for: [pair2InitiatorDumpCallback], timeout: 10) let pair2AcceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 3, "should be 3 peer ids") pair2AcceptorDumpCallback.fulfill() @@ -1263,14 +1251,13 @@ extension OctagonPairingTests { clientStateMachine.notifyContainerChange() let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -1279,14 +1266,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -1327,14 +1313,13 @@ extension OctagonPairingTests { clientStateMachine.notifyContainerChange() let pair2InitiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -1343,14 +1328,13 @@ extension OctagonPairingTests { self.wait(for: [pair2InitiatorDumpCallback], timeout: 10) let pair2AcceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 3, "should be 3 peer ids") pair2AcceptorDumpCallback.fulfill() diff --git a/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProximitySetup.swift b/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProximitySetup.swift index 1103b6a9..47786d25 100644 --- a/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProximitySetup.swift +++ b/keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProximitySetup.swift @@ -11,6 +11,28 @@ extension OctagonPairingTests { // XCTAssert(SOSCircleHasPeer(self.circle, self.fcAcceptor.peerInfo(), nil), "HasPeer 2") } + func tlkInPairingChannel(packet: Data) throws -> Bool { + let plist = try self.pairingPacketToPlist(packet: packet) + + guard let arrayOfItems = (plist["d"] as? [[String: Any]]) else { + return false + } + + var foundTLK = false + arrayOfItems.forEach { item in + guard let agrp = (item["agrp"] as? String) else { + return + } + guard let cls = (item["class"] as? String) else { + return + } + if cls == "inet" && agrp == "com.apple.security.ckks" { + foundTLK = true + } + } + return foundTLK + } + func testJoin() { self.startCKAccountStatusMock() @@ -99,7 +121,7 @@ extension OctagonPairingTests { /* calling Join */ let rpcJoinCallbackOccurs = self.expectation(description: "rpcJoin callback occurs") - self.cuttlefishContext.rpcJoin(v, vouchSig: vS, preapprovedKeys: nil) { error in + self.cuttlefishContext.rpcJoin(v, vouchSig: vS) { error in XCTAssertNil(error, "error should be nil") rpcJoinCallbackOccurs.fulfill() } @@ -214,7 +236,7 @@ extension OctagonPairingTests { let rpcJoinCallbackOccurs = self.expectation(description: "rpcJoin callback occurs") - self.cuttlefishContext.rpcJoin(v, vouchSig: vS, preapprovedKeys: nil) { error in + self.cuttlefishContext.rpcJoin(v, vouchSig: vS) { error in XCTAssertNil(error, "error should be nil") rpcJoinCallbackOccurs.fulfill() } @@ -333,7 +355,7 @@ extension OctagonPairingTests { let rpcJoinCallbackOccurs = self.expectation(description: "rpcJoin callback occurs") - self.cuttlefishContext.rpcJoin(v, vouchSig: vS, preapprovedKeys: nil) { error in + self.cuttlefishContext.rpcJoin(v, vouchSig: vS) { error in XCTAssertNotNil(error, "error should be set") rpcJoinCallbackOccurs.fulfill() } @@ -350,11 +372,11 @@ extension OctagonPairingTests { let clientStateMachine = self.manager.clientStateMachine(forContainerName: OTCKContainerName, contextID: self.contextForAcceptor, clientName: self.initiatorName) self.silentFetchesAllowed = false - self.expectCKFetchAndRun(beforeFinished: { - self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) - self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) + self.expectCKFetchAndRun { + self.putFakeKeyHierarchiesInCloudKit() + self.putFakeDeviceStatusesInCloudKit() self.silentFetchesAllowed = true - }) + } clientStateMachine.startOctagonStateMachine() self.cuttlefishContext.startOctagonStateMachine() @@ -433,7 +455,7 @@ extension OctagonPairingTests { /* calling Join */ let rpcJoinCallbackOccurs = self.expectation(description: "rpcJoin callback occurs") - self.cuttlefishContext.rpcJoin(v, vouchSig: vS, preapprovedKeys: nil) { error in + self.cuttlefishContext.rpcJoin(v, vouchSig: vS) { error in XCTAssertNil(error, "error should be nil") rpcJoinCallbackOccurs.fulfill() } @@ -519,7 +541,7 @@ extension OctagonPairingTests { self.wait(for: [voucherCallback], timeout: 10) let rpcJoinCallback = self.expectation(description: "joining octagon callback") - self.manager.rpcJoin(with: self.initiatorPairingConfig, vouchData: voucher, vouchSig: voucherSig, preapprovedKeys: nil) { error in + self.manager.rpcJoin(with: self.initiatorPairingConfig, vouchData: voucher, vouchSig: voucherSig) { error in XCTAssertNil(error, "error should be nil") rpcJoinCallback.fulfill() } @@ -689,73 +711,28 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") /* ACCEPTOR FIRST RTT EPOCH*/ - var acceptorFirstPacket = Data() - let firstAcceptorCallback = self.expectation(description: "firstAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorFirstPacket = packet! - firstAcceptorCallback.fulfill() - } - self.wait(for: [firstAcceptorCallback], timeout: 10) + let acceptorEpochPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorFirstPacket, reason: "epoch return") /* INITIATOR SECOND RTT PREPARE*/ - var initiatorSecondPacket = Data() - let secondInitiatorCallback = self.expectation(description: "secondInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorSecondPacket = packet! - secondInitiatorCallback.fulfill() - } - - self.wait(for: [secondInitiatorCallback], timeout: 10) + let initiatorPreparedIdentityPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorEpochPacket, reason: "prepared identity") /* ACCEPTOR SECOND RTT */ - var acceptorSecondPacket = Data() - let SecondAcceptorCallback = self.expectation(description: "SecondAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorSecondPacket = packet! - SecondAcceptorCallback.fulfill() - } - self.wait(for: [SecondAcceptorCallback], timeout: 10) - XCTAssertNotNil(acceptorSecondPacket, "acceptor second packet should not be nil") + let acceptorVoucherPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorPreparedIdentityPacket, reason: "epoch return") /* INITIATOR THIRD STEP*/ - var initiatorThirdPacket: Data? - let thirdInitiatorCallback = self.expectation(description: "thirdInitiatorCallback callback occurs") + let initiatorThirdPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorVoucherPacket, reason: "intitiator third packet") - initiator.exchangePacket(acceptorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorThirdPacket = packet! - thirdInitiatorCallback.fulfill() - } - self.wait(for: [thirdInitiatorCallback], timeout: 10) - XCTAssertNotNil(initiatorThirdPacket, "acceptor second packet should not be nil") + /* ACCEPTOR THIRD STEP */ + let acceptorThirdPacket = self.sendPairingExpectingCompletionAndReply(channel: acceptor, packet: initiatorThirdPacket, reason: "acceptor third packet") + XCTAssertFalse(try self.tlkInPairingChannel(packet: acceptorThirdPacket), "pairing channel should NOT transport TLKs for SOS+Octagon") + + /* INITIATOR FOURTH STEP*/ + self.sendPairingExpectingCompletion(channel: initiator, packet: acceptorThirdPacket, reason: "final packet receipt") + + // pairing completes here assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) @@ -767,14 +744,13 @@ extension OctagonPairingTests { self.verifyDatabaseMocks() let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -783,14 +759,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -802,7 +777,7 @@ extension OctagonPairingTests { self.assertSOSSuccess() } - func testProximitySetupUsingCliqueOctagonOnly() { + func testProximitySetupUsingCliqueOctagonOnly() throws { OctagonSetPlatformSupportsSOS(false) OctagonSetIsEnabled(true) self.startCKAccountStatusMock() @@ -831,73 +806,22 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") /* ACCEPTOR FIRST RTT EPOCH*/ - var acceptorFirstPacket = Data() - let firstAcceptorCallback = self.expectation(description: "firstAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorFirstPacket = packet! - firstAcceptorCallback.fulfill() - } - self.wait(for: [firstAcceptorCallback], timeout: 10) + let acceptorEpochPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorFirstPacket, reason: "epoch return") /* INITIATOR SECOND RTT PREPARE*/ - var initiatorSecondPacket = Data() - let secondInitiatorCallback = self.expectation(description: "secondInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorSecondPacket = packet! - secondInitiatorCallback.fulfill() - } - - self.wait(for: [secondInitiatorCallback], timeout: 10) + let initiatorPreparedIdentityPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorEpochPacket, reason: "prepared identity") /* ACCEPTOR SECOND RTT */ - var acceptorSecondPacket = Data() - let SecondAcceptorCallback = self.expectation(description: "SecondAcceptorCallback callback occurs") + let acceptorVoucherPacket = self.sendPairingExpectingCompletionAndReply(channel: acceptor, packet: initiatorPreparedIdentityPacket, reason: "acceptor third packet") - acceptor.exchangePacket(initiatorSecondPacket) { complete, packet, error in - XCTAssertTrue(complete, "should be true") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorSecondPacket = packet! - SecondAcceptorCallback.fulfill() - } - self.wait(for: [SecondAcceptorCallback], timeout: 10) - XCTAssertNotNil(acceptorSecondPacket, "acceptor second packet should not be nil") + // the tlks are in the 3rd roundtrip, but lets check here too + XCTAssertFalse(try self.tlkInPairingChannel(packet: acceptorVoucherPacket), "pairing channel should not transport TLKs for octagon") /* INITIATOR THIRD STEP*/ - var initiatorThirdPacket: Data? - let thirdInitiatorCallback = self.expectation(description: "thirdInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorSecondPacket) { complete, packet, error in - XCTAssertTrue(complete, "should be true") - XCTAssertNil(packet, "packet should be nil") - XCTAssertNil(error, "error should be nil") - initiatorThirdPacket = (packet) - thirdInitiatorCallback.fulfill() - } - self.wait(for: [thirdInitiatorCallback], timeout: 10) - XCTAssertNil(initiatorThirdPacket, "acceptor second packet should be nil") + self.sendPairingExpectingCompletion(channel: initiator, packet: acceptorVoucherPacket, reason: "final packet receipt") assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) @@ -909,14 +833,13 @@ extension OctagonPairingTests { self.verifyDatabaseMocks() let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -925,14 +848,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -958,7 +880,11 @@ extension OctagonPairingTests { self.assertEnters(context: initiator1Context, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) - let (acceptor, initiator) = self.setupPairingEndpoints(withPairNumber: "1", initiatorContextID: OTDefaultContext, acceptorContextID: self.contextForAcceptor, initiatorUniqueID: self.initiatorName, acceptorUniqueID: "acceptor-2") + let (acceptor, initiator) = self.setupPairingEndpoints(withPairNumber: "1", + initiatorContextID: OTDefaultContext, + acceptorContextID: self.contextForAcceptor, + initiatorUniqueID: self.initiatorName, + acceptorUniqueID: "acceptor-2") XCTAssertNotNil(acceptor, "acceptor should not be nil") XCTAssertNotNil(initiator, "initiator should not be nil") @@ -971,73 +897,30 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") /* ACCEPTOR FIRST RTT EPOCH*/ - var acceptorFirstPacket = Data() - let firstAcceptorCallback = self.expectation(description: "firstAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorFirstPacket = packet! - firstAcceptorCallback.fulfill() - } - self.wait(for: [firstAcceptorCallback], timeout: 10) + let acceptorEpochPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorFirstPacket, reason: "epoch return") /* INITIATOR SECOND RTT PREPARE*/ - var initiatorSecondPacket = Data() - let secondInitiatorCallback = self.expectation(description: "secondInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorSecondPacket = packet! - secondInitiatorCallback.fulfill() - } - - self.wait(for: [secondInitiatorCallback], timeout: 10) + let initiatorPreparedIdentityPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorEpochPacket, reason: "prepared identity") /* ACCEPTOR SECOND RTT */ - var acceptorSecondPacket = Data() - let SecondAcceptorCallback = self.expectation(description: "SecondAcceptorCallback callback occurs") + let acceptorVoucherPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorPreparedIdentityPacket, reason: "epoch return") - acceptor.exchangePacket(initiatorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorSecondPacket = packet! - SecondAcceptorCallback.fulfill() - } - self.wait(for: [SecondAcceptorCallback], timeout: 10) - XCTAssertNotNil(acceptorSecondPacket, "acceptor second packet should not be nil") + // the tlks are in the 3rd roundtrip, but lets check here too + XCTAssertFalse(try self.tlkInPairingChannel(packet: acceptorVoucherPacket), "pairing channel should transport TLKs for SOS not 2nd step though") /* INITIATOR THIRD STEP*/ - var initiatorThirdPacket: Data? - let thirdInitiatorCallback = self.expectation(description: "thirdInitiatorCallback callback occurs") + let initiatorThirdPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorVoucherPacket, reason: "intitiator third packet") - initiator.exchangePacket(acceptorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorThirdPacket = packet! - thirdInitiatorCallback.fulfill() - } - self.wait(for: [thirdInitiatorCallback], timeout: 10) - XCTAssertNotNil(initiatorThirdPacket, "acceptor second packet should not be nil") + /* ACCEPTOR THIRD STEP */ + let acceptorThirdPacket = self.sendPairingExpectingCompletionAndReply(channel: acceptor, packet: initiatorThirdPacket, reason: "acceptor third packet") + + XCTAssertTrue(try self.tlkInPairingChannel(packet: acceptorThirdPacket), "pairing channel should transport TLKs for SOS") + + /* INITIATOR FORTH STEP*/ + self.sendPairingExpectingCompletion(channel: initiator, packet: acceptorThirdPacket, reason: "final packet receipt") self.assertSOSSuccess() } @@ -1075,73 +958,19 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") /* ACCEPTOR FIRST RTT EPOCH*/ - var acceptorFirstPacket = Data() - let firstAcceptorCallback = self.expectation(description: "firstAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorFirstPacket = packet! - firstAcceptorCallback.fulfill() - } - self.wait(for: [firstAcceptorCallback], timeout: 10) + let acceptorEpochPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorFirstPacket, reason: "epoch return") /* INITIATOR SECOND RTT PREPARE*/ - var initiatorSecondPacket = Data() - let secondInitiatorCallback = self.expectation(description: "secondInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorSecondPacket = packet! - secondInitiatorCallback.fulfill() - } - - self.wait(for: [secondInitiatorCallback], timeout: 10) + let initiatorPreparedIdentityPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorEpochPacket, reason: "prepared identity") /* ACCEPTOR SECOND RTT */ - var acceptorSecondPacket = Data() - let SecondAcceptorCallback = self.expectation(description: "SecondAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorSecondPacket = packet! - SecondAcceptorCallback.fulfill() - } - self.wait(for: [SecondAcceptorCallback], timeout: 10) - XCTAssertNotNil(acceptorSecondPacket, "acceptor second packet should not be nil") + let acceptorVoucherPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorPreparedIdentityPacket, reason: "epoch return") /* INITIATOR THIRD STEP*/ - var initiatorThirdPacket: Data? - let thirdInitiatorCallback = self.expectation(description: "thirdInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorThirdPacket = packet! - thirdInitiatorCallback.fulfill() - } - self.wait(for: [thirdInitiatorCallback], timeout: 10) - XCTAssertNotNil(initiatorThirdPacket, "acceptor second packet should not be nil") + _ = self.sendPairingExpectingReply(channel: initiator, packet: acceptorVoucherPacket, reason: "intitiator third packet") assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) @@ -1153,14 +982,13 @@ extension OctagonPairingTests { self.verifyDatabaseMocks() let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") @@ -1169,14 +997,13 @@ extension OctagonPairingTests { self.wait(for: [initiatorDumpCallback], timeout: 10) let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs") - self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { - dump, _ in + self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.contextForAcceptor) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") - let egoSelf = dump!["self"] as? Dictionary + let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") - let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary + let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject] XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") - let included = dynamicInfo!["included"] as? Array + let included = dynamicInfo!["included"] as? [String] XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") acceptorDumpCallback.fulfill() @@ -1218,16 +1045,7 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertNil(error, "should be no error") - XCTAssertTrue(complete, "should be true") - XCTAssertNil(packet, "packet should be nil") - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + self.sendPairingExpectingCompletion(channel: initiator, packet: nil, reason: "error on first message") } func testProximitySetupOctagonAndSOSWithOctagonAcceptorMessage1Failure() { @@ -1260,18 +1078,7 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") acceptor.setOctagonMessageFailForTesting(true) @@ -1286,8 +1093,8 @@ extension OctagonPairingTests { } self.wait(for: [firstAcceptorCallback], timeout: 10) } - func testProximitySetupOctagonAndSOSWithOctagonInitiatorMessage2Failure() { + func testProximitySetupOctagonAndSOSWithOctagonInitiatorMessage2Failure() { OctagonSetPlatformSupportsSOS(true) OctagonSetIsEnabled(true) self.startCKAccountStatusMock() @@ -1316,31 +1123,10 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") /* ACCEPTOR FIRST RTT EPOCH*/ - var acceptorFirstPacket = Data() - let firstAcceptorCallback = self.expectation(description: "firstAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorFirstPacket = packet! - firstAcceptorCallback.fulfill() - } - self.wait(for: [firstAcceptorCallback], timeout: 10) + let acceptorEpochPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorFirstPacket, reason: "epoch return") /* INITIATOR SECOND RTT PREPARE*/ let secondInitiatorCallback = self.expectation(description: "secondInitiatorCallback callback occurs") @@ -1348,7 +1134,7 @@ extension OctagonPairingTests { //set up initiator's message 2 to fail initiator.setOctagonMessageFailForTesting(true) - initiator.exchangePacket(acceptorFirstPacket) { complete, packet, error in + initiator.exchangePacket(acceptorEpochPacket) { complete, packet, error in XCTAssertNil(error, "should be no error") XCTAssertTrue(complete, "should be true") XCTAssertNil(packet, "packet should not be nil") @@ -1387,52 +1173,20 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") /* ACCEPTOR FIRST RTT EPOCH*/ - var acceptorFirstPacket = Data() - let firstAcceptorCallback = self.expectation(description: "firstAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorFirstPacket = packet! - firstAcceptorCallback.fulfill() - } - self.wait(for: [firstAcceptorCallback], timeout: 10) + let acceptorEpochPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorFirstPacket, reason: "epoch return") /* INITIATOR SECOND RTT PREPARE*/ - var initiatorSecondPacket = Data() - let secondInitiatorCallback = self.expectation(description: "secondInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorSecondPacket = packet! - secondInitiatorCallback.fulfill() - } - - self.wait(for: [secondInitiatorCallback], timeout: 10) + let initiatorPreparedIdentityPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorEpochPacket, reason: "prepared identity") /* ACCEPTOR SECOND RTT */ let SecondAcceptorCallback = self.expectation(description: "SecondAcceptorCallback callback occurs") acceptor.setOctagonMessageFailForTesting(true) - acceptor.exchangePacket(initiatorSecondPacket) { complete, packet, error in + acceptor.exchangePacket(initiatorPreparedIdentityPacket) { complete, packet, error in XCTAssertNil(error, "should be no error") XCTAssertTrue(complete, "should be true") XCTAssertNil(packet, "packet should be nil") @@ -1470,66 +1224,23 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") /* ACCEPTOR FIRST RTT EPOCH*/ - var acceptorFirstPacket = Data() - let firstAcceptorCallback = self.expectation(description: "firstAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorFirstPacket = packet! - firstAcceptorCallback.fulfill() - } - self.wait(for: [firstAcceptorCallback], timeout: 10) + let acceptorEpochPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorFirstPacket, reason: "epoch return") /* INITIATOR SECOND RTT PREPARE*/ - var initiatorSecondPacket = Data() - let secondInitiatorCallback = self.expectation(description: "secondInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorSecondPacket = packet! - secondInitiatorCallback.fulfill() - } - - self.wait(for: [secondInitiatorCallback], timeout: 10) + let initiatorPreparedIdentityPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorEpochPacket, reason: "prepared identity") /* ACCEPTOR SECOND RTT */ - var acceptorSecondPacket = Data() - let SecondAcceptorCallback = self.expectation(description: "SecondAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorSecondPacket = packet! - SecondAcceptorCallback.fulfill() - } - self.wait(for: [SecondAcceptorCallback], timeout: 10) - XCTAssertNotNil(acceptorSecondPacket, "acceptor second packet should not be nil") + let acceptorVoucherPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorPreparedIdentityPacket, reason: "epoch return") /* INITIATOR THIRD STEP*/ let thirdInitiatorCallback = self.expectation(description: "thirdInitiatorCallback callback occurs") initiator.setOctagonMessageFailForTesting(true) - initiator.exchangePacket(acceptorSecondPacket) { complete, packet, error in + initiator.exchangePacket(acceptorVoucherPacket) { complete, packet, error in XCTAssertNil(error, "should be no error") XCTAssertTrue(complete, "should be true") XCTAssertNil(packet, "packet should be nil") @@ -1611,75 +1322,19 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") /* ACCEPTOR FIRST RTT EPOCH*/ - var acceptorFirstPacket = Data() - let firstAcceptorCallback = self.expectation(description: "firstAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorFirstPacket = packet! - firstAcceptorCallback.fulfill() - } - self.wait(for: [firstAcceptorCallback], timeout: 10) - - initiator.setSessionSupportsOctagonForTesting(false) + let acceptorEpochPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorFirstPacket, reason: "epoch return") /* INITIATOR SECOND RTT PREPARE*/ - var initiatorSecondPacket = Data() - let secondInitiatorCallback = self.expectation(description: "secondInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorSecondPacket = packet! - secondInitiatorCallback.fulfill() - } - - self.wait(for: [secondInitiatorCallback], timeout: 10) + let initiatorPreparedIdentityPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorEpochPacket, reason: "prepared identity") /* ACCEPTOR SECOND RTT */ - var acceptorSecondPacket = Data() - let SecondAcceptorCallback = self.expectation(description: "SecondAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorSecondPacket = packet! - SecondAcceptorCallback.fulfill() - } - self.wait(for: [SecondAcceptorCallback], timeout: 10) - XCTAssertNotNil(acceptorSecondPacket, "acceptor second packet should not be nil") + let acceptorVoucherPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorPreparedIdentityPacket, reason: "epoch return") /* INITIATOR THIRD STEP*/ - var initiatorThirdPacket: Data? - let thirdInitiatorCallback = self.expectation(description: "thirdInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorThirdPacket = packet! - thirdInitiatorCallback.fulfill() - } - self.wait(for: [thirdInitiatorCallback], timeout: 10) - XCTAssertNotNil(initiatorThirdPacket, "acceptor second packet should not be nil") + _ = self.sendPairingExpectingReply(channel: initiator, packet: acceptorVoucherPacket, reason: "intitiator third packet") /* need to fix attempting sos upgrade in the tests when pairing/piggybacking and then kicking off an upgrade let initiatorContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) @@ -1721,73 +1376,19 @@ extension OctagonPairingTests { self.wait(for: [signInCallback], timeout: 10) /* INITIATOR FIRST RTT JOINING MESSAGE*/ - var initiatorFirstPacket = Data() - let firstInitiatorCallback = self.expectation(description: "firstInitiatorCallback callback occurs") - - initiator.exchangePacket(nil) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorFirstPacket = packet! - firstInitiatorCallback.fulfill() - } - - self.wait(for: [firstInitiatorCallback], timeout: 10) + let initiatorFirstPacket = self.sendPairingExpectingReply(channel: initiator, packet: nil, reason: "session initiation") /* ACCEPTOR FIRST RTT EPOCH*/ - var acceptorFirstPacket = Data() - let firstAcceptorCallback = self.expectation(description: "firstAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorFirstPacket = packet! - firstAcceptorCallback.fulfill() - } - self.wait(for: [firstAcceptorCallback], timeout: 10) + let acceptorEpochPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorFirstPacket, reason: "epoch return") /* INITIATOR SECOND RTT PREPARE*/ - var initiatorSecondPacket = Data() - let secondInitiatorCallback = self.expectation(description: "secondInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorFirstPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorSecondPacket = packet! - secondInitiatorCallback.fulfill() - } - - self.wait(for: [secondInitiatorCallback], timeout: 10) + let initiatorPreparedIdentityPacket = self.sendPairingExpectingReply(channel: initiator, packet: acceptorEpochPacket, reason: "prepared identity") /* ACCEPTOR SECOND RTT */ - var acceptorSecondPacket = Data() - let SecondAcceptorCallback = self.expectation(description: "SecondAcceptorCallback callback occurs") - - acceptor.exchangePacket(initiatorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - acceptorSecondPacket = packet! - SecondAcceptorCallback.fulfill() - } - self.wait(for: [SecondAcceptorCallback], timeout: 10) - XCTAssertNotNil(acceptorSecondPacket, "acceptor second packet should not be nil") + let acceptorVoucherPacket = self.sendPairingExpectingReply(channel: acceptor, packet: initiatorPreparedIdentityPacket, reason: "epoch return") /* INITIATOR THIRD STEP*/ - var initiatorThirdPacket: Data? - let thirdInitiatorCallback = self.expectation(description: "thirdInitiatorCallback callback occurs") - - initiator.exchangePacket(acceptorSecondPacket) { complete, packet, error in - XCTAssertFalse(complete, "should be false") - XCTAssertNotNil(packet, "packet should not be nil") - XCTAssertNil(error, "error should be nil") - initiatorThirdPacket = packet! - thirdInitiatorCallback.fulfill() - } - self.wait(for: [thirdInitiatorCallback], timeout: 10) - XCTAssertNotNil(initiatorThirdPacket, "acceptor second packet should not be nil") + _ = self.sendPairingExpectingReply(channel: initiator, packet: acceptorVoucherPacket, reason: "intitiator third packet") /* need to fix attempting sos upgrade in the tests when pairing/piggybacking and then kicking off an upgrade diff --git a/keychain/ot/tests/octagon/Pairing/OctagonPairingTests.swift b/keychain/ot/tests/octagon/Pairing/OctagonPairingTests.swift index ea84fc76..5eda3686 100644 --- a/keychain/ot/tests/octagon/Pairing/OctagonPairingTests.swift +++ b/keychain/ot/tests/octagon/Pairing/OctagonPairingTests.swift @@ -4,7 +4,7 @@ func GenerateFullECKey(keySize: Int) -> (SecKey) { let keyPair = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))! - var keyAttributes: Dictionary = [:] + var keyAttributes: [String: String] = [:] keyAttributes[kSecAttrKeyClass as String] = kSecAttrKeyClassPrivate as String keyAttributes[kSecAttrKeyType as String] = kSecAttrKeyTypeEC as String @@ -35,7 +35,7 @@ class KCJoiningRequestTestDelegate: NSObject, KCJoiningRequestSecretDelegate, KC XCTAssertNotNil(octagonSigningKey, "signing key should not be nil") XCTAssertNotNil(octagonEncryptionKey, "encryption key should not be nil") - var gestalt: Dictionary = [:] + var gestalt: [String: String] = [:] gestalt[kPIUserDefinedDeviceNameKey as String] = "Fakey" let newPeerInfo = SOSPeerInfoCreate(nil, gestalt as CFDictionary, nil, signingKey, octagonSigningKey, octagonEncryptionKey, nil) @@ -47,7 +47,7 @@ class KCJoiningRequestTestDelegate: NSObject, KCJoiningRequestSecretDelegate, KC } func nextSecret() -> String { - if (self.incorrectTries > 0) { + if self.incorrectTries > 0 { self.incorrectTries -= 1 return self.incorrectSecret } @@ -79,7 +79,7 @@ class KCJoiningRequestTestDelegate: NSObject, KCJoiningRequestSecretDelegate, KC class KCJoiningAcceptTestDelegate: NSObject, KCJoiningAcceptSecretDelegate, KCJoiningAcceptCircleDelegate { - var secrets: Array = [] + var secrets: [String] = [] var currentSecret: Int = 0 var retriesLeft: Int = 0 var retriesPerSecret: Int = 0 @@ -87,7 +87,7 @@ class KCJoiningAcceptTestDelegate: NSObject, KCJoiningAcceptSecretDelegate, KCJo var circleJoinData = Data() var peerInfo: SOSPeerInfoRef? - class func acceptDelegateWithSecrets(secrets: Array, retries: Int, code: String) -> KCJoiningAcceptTestDelegate { + class func acceptDelegateWithSecrets(secrets: [String], retries: Int, code: String) -> KCJoiningAcceptTestDelegate { return KCJoiningAcceptTestDelegate(withSecrets: secrets, retries: retries, code: code) } @@ -96,12 +96,12 @@ class KCJoiningAcceptTestDelegate: NSObject, KCJoiningAcceptSecretDelegate, KCJo } class func initWithSecret(secret: String, code: String) -> KCJoiningAcceptTestDelegate { - var secretArray: Array = Array() + var secretArray: [String] = Array() secretArray.append(secret) return KCJoiningAcceptTestDelegate(withSecrets: secretArray, retries: 3, code: code) } - init(withSecrets secrets: Array, retries: Int, code: String) { + init(withSecrets secrets: [String], retries: Int, code: String) { self.secrets = secrets self.currentSecret = 0 @@ -119,9 +119,9 @@ class KCJoiningAcceptTestDelegate: NSObject, KCJoiningAcceptSecretDelegate, KCJo } func advanceSecret() -> KCRetryOrNot { - if (self.retriesLeft == 0) { + if self.retriesLeft == 0 { self.currentSecret += 1 - if (self.currentSecret >= self.secrets.count) { + if self.currentSecret >= self.secrets.count { self.currentSecret = self.secrets.count - 1 } self.retriesLeft = self.retriesPerSecret @@ -163,7 +163,203 @@ class KCJoiningAcceptTestDelegate: NSObject, KCJoiningAcceptSecretDelegate, KCJo } } -@objcMembers class OctagonPairingTests: OctagonTestsBase { +// Similar to the helpers below, but more accessible +extension OctagonTestsBase { + func joiningConfigurations(initiator: OTCuttlefishContext, initiatorDeviceID: String, sponsor: OTCuttlefishContext, sponsorDeviceID: String) -> (OTJoiningConfiguration, OTJoiningConfiguration) { + let acceptorConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, + uniqueDeviceID: sponsorDeviceID, + uniqueClientID: initiatorDeviceID, + pairingUUID: UUID().uuidString, + containerName: OTCKContainerName, + contextID: sponsor.contextID, + epoch: 1, + isInitiator: false) + + let initiatorConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, + uniqueDeviceID: initiatorDeviceID, + uniqueClientID: initiatorDeviceID, + pairingUUID: UUID().uuidString, + containerName: OTCKContainerName, + contextID: initiator.contextID, + epoch: 1, + isInitiator: true) + + return (acceptorConfig, initiatorConfig) + } + + func setupPairingChannels(initiator: OTCuttlefishContext, sponsor: OTCuttlefishContext) -> (KCPairingChannel, KCPairingChannel) { + let sponsorChannelContext = KCPairingChannelContext() + sponsorChannelContext.model = sponsor.deviceAdapter.modelID() + sponsorChannelContext.osVersion = sponsor.deviceAdapter.osVersion() + sponsorChannelContext.modelClass = "AcceptorModelClass" + + sponsorChannelContext.uniqueDeviceID = UUID().uuidString + sponsorChannelContext.uniqueClientID = UUID().uuidString + + let initiatorChannelContext = KCPairingChannelContext() + initiatorChannelContext.model = initiator.deviceAdapter.modelID() + initiatorChannelContext.osVersion = initiator.deviceAdapter.osVersion() + initiatorChannelContext.modelClass = "InitiatorModelClass" + + // The initiator's client ID is equivalent to the sponsor's client ID + initiatorChannelContext.uniqueDeviceID = sponsorChannelContext.uniqueClientID + + let sponsorPairingChannel = KCPairingChannel(acceptor: sponsorChannelContext) + let initiatorPairingChannel = KCPairingChannel(initiator: initiatorChannelContext) + + XCTAssertNotNil(sponsorPairingChannel, "Should have a sponsor pairing channel") + XCTAssertNotNil(initiatorPairingChannel, "Should have a initiator pairing channel") + + sponsorPairingChannel?.setControlObject(self.otControl) + initiatorPairingChannel?.setControlObject(self.otControl) + + let (acceptorPairingConfig, initiatorPairingConfig) = self.joiningConfigurations(initiator: initiator, + initiatorDeviceID: initiatorChannelContext.uniqueDeviceID, + sponsor: sponsor, + sponsorDeviceID: sponsorChannelContext.uniqueDeviceID) + + sponsorPairingChannel?.setConfiguration(acceptorPairingConfig) + initiatorPairingChannel?.setConfiguration(initiatorPairingConfig) + + let fakeCircle = SOSCircleCreate(kCFAllocatorDefault, "TEST DOMAIN" as CFString, nil) as SOSCircleRef + + sponsorPairingChannel?.setXPCConnectionObject(FakeNSXPCConnectionSOS(withSOSControl: FCPairingFakeSOSControl(randomAccountKey: true, circle: fakeCircle))) + initiatorPairingChannel?.setXPCConnectionObject(FakeNSXPCConnectionSOS(withSOSControl: FCPairingFakeSOSControl(randomAccountKey: true, circle: fakeCircle))) + + return (sponsorPairingChannel!, initiatorPairingChannel!) + } + + func setupPiggybackingSessions(initiator: OTCuttlefishContext, + sponsor: OTCuttlefishContext) throws -> (KCJoiningRequestTestDelegate, KCJoiningAcceptTestDelegate, KCJoiningAcceptSession, KCJoiningRequestSecretSession) { + let (acceptorJoiningConfig, _) = self.joiningConfigurations(initiator: initiator, + initiatorDeviceID: UUID().uuidString, + sponsor: sponsor, + sponsorDeviceID: UUID().uuidString) + + return try self.setupKCJoiningSessionObjects(dsid: 0x123456, + sponsorConfiguration: acceptorJoiningConfig) + } + + func setupKCJoiningSessionObjects(dsid: UInt64, + sponsorConfiguration: OTJoiningConfiguration) throws -> (KCJoiningRequestTestDelegate, KCJoiningAcceptTestDelegate, KCJoiningAcceptSession, KCJoiningRequestSecretSession) { + let secret = "123456" + let code = "987654" + + let requestDelegate = KCJoiningRequestTestDelegate.requestDelegate(withSecret: secret) + let acceptDelegate = KCJoiningAcceptTestDelegate.acceptDelegateWithSecret(secret: secret, code: code) + + let requestSession = try KCJoiningRequestSecretSession(secretDelegate: requestDelegate as KCJoiningRequestSecretDelegate, dsid: dsid, rng: ccDRBGGetRngState()) + + let acceptSession = try KCJoiningAcceptSession(secretDelegate: acceptDelegate as KCJoiningAcceptSecretDelegate, + circleDelegate: acceptDelegate as KCJoiningAcceptCircleDelegate, + dsid: dsid, + rng: ccDRBGGetRngState()) + requestSession.setControlObject(self.otControl) + acceptSession.setControlObject(self.otControl) + + // requestSessions don't need control objects + acceptSession.setConfiguration(sponsorConfiguration) + + return (requestDelegate, acceptDelegate, acceptSession, requestSession) + } + + func octagonPiggypackingMessage(in message: KCJoiningMessage) throws -> OTPairingMessage { + guard let octagonData = message.secondData else { + throw NSError(domain: "missing octagon data" as String, code: -1, userInfo: nil) + } + + return OTPairingMessage(data: octagonData) + } + + func unpackPiggybackingInitialMessage(identityMessage: Data, session: KCAESGCMDuplexSession) throws -> OTPairingMessage { + let encryptedJoiningIdentityMessage = try KCJoiningMessage(der: identityMessage) + let joiningIdentityMessage = try session.decryptAndVerify(encryptedJoiningIdentityMessage.firstData) + + let initialMessageProtobuf = KCInitialMessageData(data: joiningIdentityMessage) + XCTAssertNotNil(initialMessageProtobuf, "should have an initial message container") + let initiatorIdentityMessageOpt = OTPairingMessage(data: initialMessageProtobuf!.prepareMessage) + XCTAssertNotNil(initiatorIdentityMessageOpt, "should have an Octagon message container") + return initiatorIdentityMessageOpt! + } + + func makePiggybackingPacket(combining currentMessage: KCJoiningMessage, octagonMessage: OTPairingMessage) throws -> Data { + let newMessage = try KCJoiningMessage(type: currentMessage.type, + data: currentMessage.firstData, + payload: octagonMessage.data) + return newMessage.der + } + + func pairingPacketToPlist(packet: Data) throws -> [String: Any] { + let binaryPlist = KCPairingChannel.pairingChannelDecompressData(packet)! + let plist = (try PropertyListSerialization.propertyList(from: binaryPlist, options: [], format: nil)) as! [String: Any] + return plist + } + + func octagonPairingMessage(in packet: Data) throws -> OTPairingMessage { + let plist = try self.pairingPacketToPlist(packet: packet) + + guard let octagonData = plist["o"] as? Data else { + throw NSError(domain: "missing octagon data" as String, code: -1, userInfo: nil) + } + + return OTPairingMessage(data: octagonData) + } + + func makePairingPacket(combining currentPacket: Data, octagonMessage: OTPairingMessage) throws -> Data { + var plist = try self.pairingPacketToPlist(packet: currentPacket) + plist["o"] = octagonMessage.data + + return KCPairingChannel.pairingChannelCompressData(try PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0)) + } + + func sendPairingExpectingReply(channel: KCPairingChannel, packet: Data?, reason: String) -> Data { + var packetResponse = Data() + let callback = self.expectation(description: "callback occurs (\(reason))") + + channel.exchangePacket(packet) { complete, response, error in + XCTAssertNil(error, "error should be nil (\(reason))") + + XCTAssertFalse(complete, "Expected no completion (\(reason))") + XCTAssertNotNil(response, "packet should not be nil (\(reason))") + packetResponse = response! + callback.fulfill() + } + self.wait(for: [callback], timeout: 10) + return packetResponse + } + + func sendPairingExpectingCompletionAndReply(channel: KCPairingChannel, packet: Data?, reason: String) -> Data { + var packetResponse = Data() + let callback = self.expectation(description: "callback occurs (\(reason))") + + channel.exchangePacket(packet) { complete, response, error in + XCTAssertNil(error, "error should be nil (\(reason))") + + XCTAssertTrue(complete, "Expected channel completion (\(reason))") + XCTAssertNotNil(response, "response should be present (\(reason))") + packetResponse = response! + callback.fulfill() + } + self.wait(for: [callback], timeout: 10) + return packetResponse + } + + func sendPairingExpectingCompletion(channel: KCPairingChannel, packet: Data?, reason: String) { + let callback = self.expectation(description: "callback occurs (\(reason))") + + channel.exchangePacket(packet) { complete, response, error in + XCTAssertNil(error, "error should be nil (\(reason))") + + XCTAssertTrue(complete, "Expected channel completion (\(reason))") + XCTAssertNil(response, "response should be nil (\(reason))") + callback.fulfill() + } + self.wait(for: [callback], timeout: 10) + } +} + +@objcMembers +class OctagonPairingTests: OctagonTestsBase { var sosAdapterForAcceptor: CKKSMockSOSPresentAdapter! var cuttlefishContextForAcceptor: OTCuttlefishContext! @@ -197,11 +393,39 @@ class KCJoiningAcceptTestDelegate: NSObject, KCJoiningAcceptSecretDelegate, KCJo accountStateTracker: self.accountStateTracker, deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-SOS-iphone", serialNumber: "456", osVersion: "iOS (fake version)")) - self.acceptorPiggybackingConfig = OTJoiningConfiguration(protocolType: OTProtocolPiggybacking, uniqueDeviceID: "acceptor", uniqueClientID: self.initiatorName, containerName: OTCKContainerName, contextID: self.contextForAcceptor, epoch: 1, isInitiator: false) - self.initiatorPiggybackingConfig = OTJoiningConfiguration(protocolType: OTProtocolPiggybacking, uniqueDeviceID: "initiator", uniqueClientID: "acceptor", containerName: OTCKContainerName, contextID: OTDefaultContext, epoch: 1, isInitiator: true) - - self.acceptorPairingConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, uniqueDeviceID: "acceptor", uniqueClientID: self.initiatorName, containerName: OTCKContainerName, contextID: self.contextForAcceptor, epoch: 1, isInitiator: false) - self.initiatorPairingConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, uniqueDeviceID: "initiator", uniqueClientID: "acceptor", containerName: OTCKContainerName, contextID: OTDefaultContext, epoch: 1, isInitiator: true) + self.acceptorPiggybackingConfig = OTJoiningConfiguration(protocolType: OTProtocolPiggybacking, + uniqueDeviceID: "acceptor", + uniqueClientID: self.initiatorName, + pairingUUID: UUID().uuidString, + containerName: OTCKContainerName, + contextID: self.contextForAcceptor, + epoch: 1, + isInitiator: false) + self.initiatorPiggybackingConfig = OTJoiningConfiguration(protocolType: OTProtocolPiggybacking, + uniqueDeviceID: "initiator", + uniqueClientID: "acceptor", + pairingUUID: UUID().uuidString, + containerName: OTCKContainerName, + contextID: OTDefaultContext, + epoch: 1, + isInitiator: true) + + self.acceptorPairingConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, + uniqueDeviceID: "acceptor", + uniqueClientID: self.initiatorName, + pairingUUID: UUID().uuidString, + containerName: OTCKContainerName, + contextID: self.contextForAcceptor, + epoch: 1, + isInitiator: false) + self.initiatorPairingConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, + uniqueDeviceID: "initiator", + uniqueClientID: "acceptor", + pairingUUID: UUID().uuidString, + containerName: OTCKContainerName, + contextID: OTDefaultContext, + epoch: 1, + isInitiator: true) } func getAcceptorInCircle() { @@ -237,7 +461,6 @@ class KCJoiningAcceptTestDelegate: NSObject, KCJoiningAcceptSecretDelegate, KCJo initiatorContext.osVersion = "InitiatorOsVersion" initiatorContext.modelClass = "InitiatorModelClass" initiatorContext.uniqueDeviceID = initiatorUniqueID - initiatorContext.uniqueDeviceID = initiatorUniqueID let acceptor = acceptorClique!.setupPairingChannel(asAcceptor: acceptorContext) let initiator = initiatorClique!.setupPairingChannel(asInitiator: initiatorContext) @@ -248,8 +471,22 @@ class KCJoiningAcceptTestDelegate: NSObject, KCJoiningAcceptSecretDelegate, KCJo acceptor.setControlObject(self.otControl) initiator.setControlObject(self.otControl) - let acceptorPairingConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, uniqueDeviceID: acceptorUniqueID, uniqueClientID: initiatorUniqueID, containerName: OTCKContainerName, contextID: acceptorContextID, epoch: 1, isInitiator: false) - let initiatorPairingConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, uniqueDeviceID: initiatorUniqueID, uniqueClientID: initiatorUniqueID, containerName: OTCKContainerName, contextID: initiatorContextID, epoch: 1, isInitiator: true) + let acceptorPairingConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, + uniqueDeviceID: acceptorUniqueID, + uniqueClientID: initiatorUniqueID, + pairingUUID: UUID().uuidString, + containerName: OTCKContainerName, + contextID: acceptorContextID, + epoch: 1, + isInitiator: false) + let initiatorPairingConfig = OTJoiningConfiguration(protocolType: OTProtocolPairing, + uniqueDeviceID: initiatorUniqueID, + uniqueClientID: initiatorUniqueID, + pairingUUID: UUID().uuidString, + containerName: OTCKContainerName, + contextID: initiatorContextID, + epoch: 1, + isInitiator: true) acceptor.setConfiguration(acceptorPairingConfig) initiator.setConfiguration(initiatorPairingConfig) @@ -278,53 +515,30 @@ class KCJoiningAcceptTestDelegate: NSObject, KCJoiningAcceptSecretDelegate, KCJo XCTAssertNotNil(acceptorAnalytics, "acceptorAnalytics should not be nil") secondAcceptorData.analytics = acceptorAnalytics - do { - let acceptor = try OTClique(contextData: secondAcceptorData) - XCTAssertNotNil(acceptor, "Clique should not be nil") - acceptor.setPairingDefault(true) + let acceptor = OTClique(contextData: secondAcceptorData) + XCTAssertNotNil(acceptor, "Clique should not be nil") + acceptor.setPairingDefault(true) - let secondInitiatorData = OTConfigurationContext() - secondInitiatorData.context = "secondInitiator" - secondInitiatorData.dsid = "i-"+count - secondInitiatorData.altDSID = "alt-i-"+count + let secondInitiatorData = OTConfigurationContext() + secondInitiatorData.context = "secondInitiator" + secondInitiatorData.dsid = "i-"+count + secondInitiatorData.altDSID = "alt-i-"+count - let initiatorAnalytics = SFSignInAnalytics(signInUUID: "uuid", category: "com.apple.cdp", eventName: "signed in") - XCTAssertNotNil(initiatorAnalytics, "initiatorAnalytics should not be nil") - secondInitiatorData.analytics = initiatorAnalytics - let initiator = try OTClique(contextData: secondInitiatorData) - XCTAssertNotNil(initiator, "Clique should not be nil") - initiator.setPairingDefault(true) + let initiatorAnalytics = SFSignInAnalytics(signInUUID: "uuid", category: "com.apple.cdp", eventName: "signed in") + XCTAssertNotNil(initiatorAnalytics, "initiatorAnalytics should not be nil") + secondInitiatorData.analytics = initiatorAnalytics + let initiator = OTClique(contextData: secondInitiatorData) + XCTAssertNotNil(initiator, "Clique should not be nil") + initiator.setPairingDefault(true) - return (acceptor, initiator) - - } catch { - XCTFail("error creating test clique: \(error)") - } - return(nil, nil) + return (acceptor, initiator) } func setupKCJoiningSessionObjects() -> (KCJoiningRequestTestDelegate?, KCJoiningAcceptTestDelegate?, KCJoiningAcceptSession?, KCJoiningRequestSecretSession?) { - - let secret = "123456" - let code = "987654" let dsid: UInt64 = 0x1234567887654321 - let requestDelegate = KCJoiningRequestTestDelegate.requestDelegate(withSecret: secret) - let acceptDelegate = KCJoiningAcceptTestDelegate.acceptDelegateWithSecret(secret: secret, code: code) - do { - let requestSession = try KCJoiningRequestSecretSession(secretDelegate: requestDelegate as KCJoiningRequestSecretDelegate, dsid: dsid, rng: ccDRBGGetRngState()) - - let acceptSession = try KCJoiningAcceptSession(secretDelegate: acceptDelegate as KCJoiningAcceptSecretDelegate, - circleDelegate: acceptDelegate as KCJoiningAcceptCircleDelegate, - dsid: dsid, - rng: ccDRBGGetRngState()) - requestSession.setControlObject(self.otControl) - acceptSession.setControlObject(self.otControl) - requestSession.setConfiguration(self.initiatorPiggybackingConfig) - acceptSession.setConfiguration(self.acceptorPiggybackingConfig) - - return (requestDelegate, acceptDelegate, acceptSession, requestSession) + return try self.setupKCJoiningSessionObjects(dsid: dsid, sponsorConfiguration: self.acceptorPiggybackingConfig) } catch { XCTFail("error creating test clique: \(error)") return (nil, nil, nil, nil) diff --git a/keychain/otctl/OTControlCLI.h b/keychain/otctl/OTControlCLI.h index a62f3bce..38293335 100644 --- a/keychain/otctl/OTControlCLI.h +++ b/keychain/otctl/OTControlCLI.h @@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN - (long)resetOctagon:(NSString*)container context:(NSString*)contextID altDSID:(NSString*)altDSID; +- (long)resetProtectedData:(NSString*)container context:(NSString*)contextID altDSID:(NSString*)altDSID appleID:(NSString*)appleID dsid:(NSString*)dsid; + - (long)status:(NSString* _Nullable)container context:(NSString*)contextID json:(bool)json; - (long)recoverUsingBottleID:(NSString*)bottleID @@ -36,6 +38,7 @@ NS_ASSUME_NONNULL_BEGIN control:(OTControl*)control; - (long)healthCheck:(NSString* _Nullable)container context:(NSString*)contextID skipRateLimitingCheck:(BOOL)skipRateLimitingCheck; +- (long)refetchCKKSPolicy:(NSString*)container context:(NSString*)contextID; - (long)tapToRadar:(NSString *)action description:(NSString *)description radar:(NSString *)radar; diff --git a/keychain/otctl/OTControlCLI.m b/keychain/otctl/OTControlCLI.m index 5fa418c4..5637dec9 100644 --- a/keychain/otctl/OTControlCLI.m +++ b/keychain/otctl/OTControlCLI.m @@ -10,11 +10,50 @@ #include "utilities/SecInternalReleasePriv.h" #import "utilities/debugging.h" +#import "keychain/ot/OTClique.h" #import "keychain/ot/OT.h" #import "keychain/ot/OTConstants.h" #import "keychain/ot/OTControl.h" #import "keychain/otctl/OTControlCLI.h" + +#import +#import +#import + +static NSString* fetch_pet(NSString* appleID, NSString* dsid) +{ + if(!appleID && !dsid) { + NSLog(@"Must provide either an AppleID or a DSID to fetch a PET"); + exit(1); + } + + AKAppleIDAuthenticationContext* authContext = [[AKAppleIDAuthenticationContext alloc] init]; + authContext.username = appleID; + + authContext.authenticationType = AKAppleIDAuthenticationTypeSilent; + authContext.isUsernameEditable = NO; + + __block NSString* pet = nil; + + dispatch_semaphore_t s = dispatch_semaphore_create(0); + + AKAppleIDAuthenticationController *authenticationController = [[AKAppleIDAuthenticationController alloc] init]; + [authenticationController authenticateWithContext:authContext + completion:^(AKAuthenticationResults authenticationResults, NSError *error) { + if(error) { + NSLog(@"error fetching PET: %@", error); + exit(1); + } + + pet = authenticationResults[AKAuthenticationPasswordKey]; + dispatch_semaphore_signal(s); + }]; + dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER); + + return pet; +} + // Mutual recursion to set up an object for jsonification static NSDictionary* cleanDictionaryForJSON(NSDictionary* dict); @@ -201,6 +240,30 @@ static void print_json(NSDictionary* dict) #endif } + +- (long)resetProtectedData:(NSString*)container context:(NSString*)contextID altDSID:(NSString*)altDSID appleID:(NSString*)appleID dsid:(NSString*)dsid +{ +#if OCTAGON + __block long ret = -1; + + NSError* error = nil; + OTConfigurationContext *data = [[OTConfigurationContext alloc] init]; + data.passwordEquivalentToken = fetch_pet(appleID, dsid); + data.authenticationAppleID = appleID; + data.altDSID = altDSID; + data.context = contextID; + + OTClique* clique = [OTClique resetProtectedData:data error:&error]; + if(clique != nil && error == nil) { + ret = 0; + } + return ret; +#else + printf("Unimplemented.\n"); + return -1; +#endif +} + - (void)printPeer:(NSDictionary*)peerInformation prefix:(NSString* _Nullable)prefix { NSString* peerID = peerInformation[@"peerID"]; NSString* model = peerInformation[@"permanentInfo"][@"model_id"]; @@ -451,6 +514,28 @@ static void print_json(NSDictionary* dict) #endif } +- (long)refetchCKKSPolicy:(NSString*)container context:(NSString*)contextID +{ + #if OCTAGON + __block long ret = 1; + + [self.control refetchCKKSPolicy:container + contextID:contextID + reply:^(NSError * _Nullable error) { + if(error) { + printf("Error refetching CKKS policy: %s\n", [[error description] UTF8String]); + } else { + printf("CKKS refetch completed.\n"); + ret = 0; + } + }]; + return ret; + #else + printf("Unimplemented.\n"); + return 1; + #endif +} + - (long)tapToRadar:(NSString *)action description:(NSString *)description radar:(NSString *)radar { #if OCTAGON diff --git a/keychain/otctl/otctl-Entitlements.plist b/keychain/otctl/otctl-Entitlements.plist index 472e6b42..311579d3 100644 --- a/keychain/otctl/otctl-Entitlements.plist +++ b/keychain/otctl/otctl-Entitlements.plist @@ -2,6 +2,14 @@ + com.apple.securebackupd.access + + keychain-cloud-circle + + com.apple.private.ckks + + com.apple.authkit.client.private + com.apple.private.octagon com.apple.private.escrow-update diff --git a/keychain/otctl/otctl.m b/keychain/otctl/otctl.m index d569f7ea..dd0cb2cb 100644 --- a/keychain/otctl/otctl.m +++ b/keychain/otctl/otctl.m @@ -22,6 +22,7 @@ static int start = false; static int signIn = false; static int signOut = false; static int resetoctagon = false; +static int resetProtectedData = false; static int fetchAllBottles = false; static int recover = false; @@ -33,6 +34,7 @@ static int er_trigger = false; static int er_status = false; static int er_reset = false; static int er_store = false; +static int ckks_policy_flag = false; static int ttr_flag = false; @@ -51,6 +53,8 @@ static int json = false; static char* altDSIDArg = NULL; static char* containerStr = NULL; static char* radarNumber = NULL; +static char* appleIDArg = NULL; +static char* dsidArg = NULL; static void internalOnly(void) { @@ -71,6 +75,9 @@ int main(int argc, char** argv) {.longname = "altDSID", .argument = &altDSIDArg, .description = "altDSID (for sign-in/out)"}, {.longname = "entropy", .argument = &secretArg, .description = "escrowed entropy in JSON"}, + {.longname = "appleID", .argument = &appleIDArg, .description = "AppleID"}, + {.longname = "dsid", .argument = &dsidArg, .description = "DSID"}, + {.longname = "container", .argument = &containerStr, .description = "CloudKit container name"}, {.longname = "radar", .argument = &radarNumber, .description = "Radar number"}, @@ -78,7 +85,10 @@ int main(int argc, char** argv) {.command = "sign-in", .flag = &signIn, .flagval = true, .description = "Inform Cuttlefish container of sign in"}, {.command = "sign-out", .flag = &signOut, .flagval = true, .description = "Inform Cuttlefish container of sign out"}, {.command = "status", .flag = &status, .flagval = true, .description = "Report Octagon status"}, + {.command = "resetoctagon", .flag = &resetoctagon, .flagval = true, .description = "Reset and establish new Octagon trust"}, + {.command = "resetProtectedData", .flag = &resetProtectedData, .flagval = true, .description = "Reset ProtectedData"}, + {.command = "allBottles", .flag = &fetchAllBottles, .flagval = true, .description = "Fetch all viable bottles"}, {.command = "recover", .flag = &recover, .flagval = true, .description = "Recover using this bottle"}, {.command = "depart", .flag = &depart, .flagval = true, .description = "Depart from Octagon Trust"}, @@ -89,9 +99,11 @@ int main(int argc, char** argv) {.command = "er-store", .flag = &er_store, .flagval = true, .description = "Store any pending Escrow Request prerecords"}, {.command = "health", .flag = &health, .flagval = true, .description = "Check Octagon Health status"}, + {.command = "ckks-policy", .flag = &ckks_policy_flag, .flagval = true, .description = "Trigger a refetch of the CKKS policy"}, {.command = "taptoradar", .flag = &ttr_flag, .flagval = true, .description = "Trigger a TapToRadar"}, + #if TARGET_OS_WATCH {.command = "pairme", .flag = &pairme, .flagval = true, .description = "Perform pairing (watchOS only)"}, #endif /* TARGET_OS_WATCH */ @@ -121,6 +133,9 @@ int main(int argc, char** argv) NSString* context = contextNameArg ? [NSString stringWithCString:contextNameArg encoding:NSUTF8StringEncoding] : OTDefaultContext; NSString* container = containerStr ? [NSString stringWithCString:containerStr encoding:NSUTF8StringEncoding] : nil; NSString* altDSID = altDSIDArg ? [NSString stringWithCString:altDSIDArg encoding:NSUTF8StringEncoding] : nil; + NSString* dsid = dsidArg ? [NSString stringWithCString:dsidArg encoding:NSUTF8StringEncoding] : nil; + NSString* appleID = appleIDArg ? [NSString stringWithCString:appleIDArg encoding:NSUTF8StringEncoding] : nil; + NSString* skipRateLimitingCheck = skipRateLimitingCheckArg ? [NSString stringWithCString:skipRateLimitingCheckArg encoding:NSUTF8StringEncoding] : @"NO"; OTControlCLI* ctl = [[OTControlCLI alloc] initWithOTControl:rpc]; @@ -134,6 +149,11 @@ int main(int argc, char** argv) long ret = [ctl resetOctagon:container context:context altDSID:altDSID]; return (int)ret; } + if(resetProtectedData) { + internalOnly(); + long ret = [ctl resetProtectedData:container context:context altDSID:altDSID appleID:appleID dsid:dsid]; + return (int)ret; + } if(fetchAllBottles) { return (int)[ctl fetchAllBottles:altDSID containerName:container context:context control:rpc]; } @@ -188,12 +208,16 @@ int main(int argc, char** argv) } return (int)[ctl healthCheck:container context:context skipRateLimitingCheck:skip]; } + if(ckks_policy_flag) { + return (int)[ctl refetchCKKSPolicy:container context:context]; + } if (ttr_flag) { if (radarNumber == NULL) { radarNumber = "1"; } return (int)[ctl tapToRadar:@"action" description:@"description" radar:[NSString stringWithUTF8String:radarNumber]]; } + if(er_trigger) { internalOnly(); return (int)[escrowctl trigger]; diff --git a/keychain/securityd/Regressions/SOSAccountTesting.h b/keychain/securityd/Regressions/SOSAccountTesting.h index f2d431ee..413b75cb 100644 --- a/keychain/securityd/Regressions/SOSAccountTesting.h +++ b/keychain/securityd/Regressions/SOSAccountTesting.h @@ -116,6 +116,41 @@ static inline SOSViewResultCode SOSAccountUpdateView_wTxn(SOSAccount* acct, CFSt return result; } +static inline bool SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(SOSAccount *account, CFStringRef viewname) { + __block bool result = false; + [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) { + result = SOSAccountIsMyPeerInBackupAndCurrentInView(account, viewname); + }]; + return result; +} + +static inline bool SOSAccountIsPeerInBackupAndCurrentInView_wTxn(SOSAccount *account, SOSPeerInfoRef peerInfo, CFStringRef viewname) { + __block bool result = false; + [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) { + result = SOSAccountIsPeerInBackupAndCurrentInView(account, peerInfo, viewname); + }]; + return result; +} + +static inline bool SOSAccountRecoveryKeyIsInBackupAndCurrentInView_wTxn(SOSAccount *account, CFStringRef viewname) { + __block bool result = false; + [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) { + result = SOSAccountRecoveryKeyIsInBackupAndCurrentInView(account, viewname); + }]; + return result; +} + +static inline SOSBackupSliceKeyBagRef SOSAccountBackupSliceKeyBagForView_wTxn(SOSAccount *account, CFStringRef viewname, CFErrorRef *error) { + __block SOSBackupSliceKeyBagRef result = NULL; + [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) { + result = SOSAccountBackupSliceKeyBagForView(account, viewname, error); + }]; + return result; +} + + + + // // Account comparison // @@ -141,7 +176,9 @@ static void SOSAccountResetToTest(SOSAccount* a, CFStringRef accountName) { a.key_transport = nil; a.kvs_message_transport = nil; - SOSAccountEnsureFactoryCirclesTest(a, accountName); + [a performTransaction:^(SOSAccountTransaction * _Nonnull txn) { + SOSAccountEnsureFactoryCirclesTest(a, accountName); + }]; } @@ -810,7 +847,10 @@ static inline bool testAccountPersistence(SOSAccount* account) { SOSAccount* reinflatedAccount = NULL; NSError* error = nil; - require(retval, errOut); + if(!retval) { + error = nil; + return retval; + } // Re-inflate to "inflated" reinflatedAccount = [SOSAccount accountFromData:accountDER @@ -822,16 +862,20 @@ static inline bool testAccountPersistence(SOSAccount* account) { ok(CFEqualSafe((__bridge CFTypeRef)reinflatedAccount, (__bridge CFTypeRef)account), "Compares"); // Repeat through SOSAccountCopyEncodedData() interface - this is the normally called combined interface - accountDER = [account encodedData:&error]; + [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) { + NSError* error = nil; + accountDER = [account encodedData:&error]; + }]; + error = nil; - reinflatedAccount = [SOSAccount accountFromData:accountDER factory:test_factory error:&error]; - ok(reinflatedAccount, "inflated2: %@", error); - ok(CFEqual((__bridge CFTypeRef)account, (__bridge CFTypeRef)reinflatedAccount), "Compares"); + SOSAccount* reinflatedAccount2 = NULL; + + reinflatedAccount2 = [SOSAccount accountFromData:accountDER factory:test_factory error:&error]; + ok(reinflatedAccount2, "inflated2: %@", error); + ok(CFEqual((__bridge CFTypeRef)account, (__bridge CFTypeRef)reinflatedAccount2), "Compares"); retval = true; -errOut: error = nil; - return retval; } diff --git a/keychain/securityd/Regressions/secd-62-account-backup.m b/keychain/securityd/Regressions/secd-62-account-backup.m index f62cc022..41934fda 100644 --- a/keychain/securityd/Regressions/secd-62-account-backup.m +++ b/keychain/securityd/Regressions/secd-62-account-backup.m @@ -165,9 +165,6 @@ static void tests(void) ok(SOSAccountIsMyPeerInBackupAndCurrentInView(alice_account, kTestView1), "Is alice in backup after sync?"); ok(SOSAccountIsMyPeerInBackupAndCurrentInView(bob_account, kTestView1), "IS bob in the backup after sync"); - - ok(!SOSAccountIsLastBackupPeer(alice_account, &error), "Alice is not last backup peer"); - CFReleaseNull(error); // //Bob leaves the circle @@ -179,11 +176,6 @@ static void tests(void) is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); ok(SOSAccountIsMyPeerInBackupAndCurrentInView(alice_account, kTestView1), "Bob left the circle, Alice is not in the backup"); - - ok(SOSAccountIsLastBackupPeer(alice_account, &error), "Alice is last backup peer"); - CFReleaseNull(error); - ok(!SOSAccountIsLastBackupPeer(bob_account, &error), "Bob is not last backup peer"); - CFReleaseNull(error); ok(testAccountPersistence(alice_account), "Test Account->DER->Account Equivalence"); @@ -211,16 +203,10 @@ static void tests(void) ok(!SOSAccountIsMyPeerInBackupAndCurrentInView(bob_account, kTestView1), "Bob isn't in the backup yet"); - ok(!SOSAccountIsLastBackupPeer(alice_account, &error), "Alice is the not the last backup peer - Bob still registers as one"); - CFReleaseNull(error); - ok(SOSAccountSetBackupPublicKey_wTxn(bob_account, bob_backup_key, &error), "Set backup public key, bob (%@)", error); is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 3, "updates"); - ok(!SOSAccountIsLastBackupPeer(alice_account, &error), "Alice is not last backup peer"); - CFReleaseNull(error); - // //removing backup key for bob account // diff --git a/keychain/securityd/Regressions/secd-65-account-retirement-reset.m b/keychain/securityd/Regressions/secd-65-account-retirement-reset.m index a406f2ab..5db81a50 100644 --- a/keychain/securityd/Regressions/secd-65-account-retirement-reset.m +++ b/keychain/securityd/Regressions/secd-65-account-retirement-reset.m @@ -122,10 +122,12 @@ static void tests(void) return 2; }, ^{ - NSError *ns_error = nil; - frozen_alice = (CFDataRef) CFBridgingRetain([alice_account encodedData:&ns_error]); - ok(frozen_alice, "Copy encoded %@", ns_error); - ns_error = nil; + [alice_account performTransaction:^(SOSAccountTransaction * _Nonnull txn) { + NSError *ns_error = nil; + frozen_alice = (CFDataRef) CFBridgingRetain([alice_account encodedData:&ns_error]); + ok(frozen_alice, "Copy encoded %@", ns_error); + ns_error = nil; + }]; SOSAccountPurgePrivateCredential(alice_account); diff --git a/keychain/securityd/Regressions/secd-66-account-recovery.m b/keychain/securityd/Regressions/secd-66-account-recovery.m index 2a2fa0ba..57ffd281 100644 --- a/keychain/securityd/Regressions/secd-66-account-recovery.m +++ b/keychain/securityd/Regressions/secd-66-account-recovery.m @@ -51,7 +51,7 @@ #include #include "secd_regressions.h" -#include "SOSTestDataSource.h" +#include "keychain/SecureObjectSync/Regressions/SOSTestDataSource.h" #include "SOSRegressionUtilities.h" #include @@ -209,10 +209,10 @@ static void tests(bool recKeyFirst) ok([bob_account.trust checkForRings:&error], "Bob_account is good"); CFReleaseNull(error); - ok(SOSAccountIsMyPeerInBackupAndCurrentInView(alice_account, kTestView1), "Is alice is in backup before sync?"); + ok(SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(alice_account, kTestView1), "Is alice is in backup before sync?"); if(!recKeyFirst) { - SOSBackupSliceKeyBagRef bskb = SOSAccountBackupSliceKeyBagForView(alice_account, kTestView1, &error); + SOSBackupSliceKeyBagRef bskb = SOSAccountBackupSliceKeyBagForView_wTxn(alice_account, kTestView1, &error); CFReleaseNull(error); ok(!SOSBSKBHasRecoveryKey(bskb), "BSKB should not have recovery key"); CFReleaseNull(bskb); @@ -221,7 +221,7 @@ static void tests(bool recKeyFirst) ok([alice_account.trust checkForRings:&error], "Alice_account is good"); CFReleaseNull(error); - ok(SOSAccountIsMyPeerInBackupAndCurrentInView(bob_account, kTestView1), "Is bob in the backup after sync? - 1"); + ok(SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(bob_account, kTestView1), "Is bob in the backup after sync? - 1"); ok([bob_account.trust checkForRings:&error], "Alice_account is good"); CFReleaseNull(error); @@ -232,12 +232,9 @@ static void tests(bool recKeyFirst) ok([alice_account.trust checkForRings:&error], "Alice_account is good"); CFReleaseNull(error); - ok(SOSAccountIsMyPeerInBackupAndCurrentInView(alice_account, kTestView1), "Is alice is in backup after sync?"); + ok(SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(alice_account, kTestView1), "Is alice is in backup after sync?"); - ok(SOSAccountIsMyPeerInBackupAndCurrentInView(bob_account, kTestView1), "IS bob in the backup after sync"); - - ok(!SOSAccountIsLastBackupPeer(alice_account, &error), "Alice is not last backup peer"); - CFReleaseNull(error); + ok(SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(bob_account, kTestView1), "IS bob in the backup after sync"); // //Bob leaves the circle @@ -248,16 +245,11 @@ static void tests(bool recKeyFirst) //Alice should kick Bob out of the backup! is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - ok(SOSAccountIsMyPeerInBackupAndCurrentInView(alice_account, kTestView1), "Bob left the circle, Alice is in the backup"); - - ok(SOSAccountIsLastBackupPeer(alice_account, &error), "Alice is last backup peer"); - CFReleaseNull(error); - ok(!SOSAccountIsLastBackupPeer(bob_account, &error), "Bob is not last backup peer"); - CFReleaseNull(error); - + ok(SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(alice_account, kTestView1), "Bob left the circle, Alice is in the backup"); + //ok(testAccountPersistence(alice_account), "Test Account->DER->Account Equivalence"); SOSAccountTrustClassic* bobTrust = bob_account.trust; - ok(!SOSAccountIsPeerInBackupAndCurrentInView(alice_account, bobTrust.peerInfo, kTestView1), "Bob is still in the backup!"); + ok(!SOSAccountIsPeerInBackupAndCurrentInView_wTxn(alice_account, bobTrust.peerInfo, kTestView1), "Bob is still in the backup!"); //Bob gets back into the circle ok(SOSTestJoinWithApproval(cfpassword, cfaccount, changes, alice_account, bob_account, KEEP_USERKEY, 2, false), "Bob Re-Joins"); @@ -266,18 +258,12 @@ static void tests(bool recKeyFirst) is([bob_account.trust updateView:bob_account name:kTestView1 code:kSOSCCViewEnable err:&error], kSOSCCViewMember, "Enable view (%@)", error); CFReleaseNull(error); - ok(!SOSAccountIsMyPeerInBackupAndCurrentInView(bob_account, kTestView1), "Bob isn't in the backup yet"); - - ok(!SOSAccountIsLastBackupPeer(alice_account, &error), "Alice is the not the last backup peer - Bob still registers as one"); - CFReleaseNull(error); - + ok(!SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(bob_account, kTestView1), "Bob isn't in the backup yet"); + ok(SOSAccountSetBackupPublicKey_wTxn(bob_account, bob_backup_key, &error), "Set backup public key, alice (%@)", error); is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 3, "updates"); - ok(!SOSAccountIsLastBackupPeer(alice_account, &error), "Alice is not last backup peer"); - CFReleaseNull(error); - // //removing backup key for bob account // @@ -286,8 +272,8 @@ static void tests(bool recKeyFirst) int nchanges = (recKeyFirst) ? 2: 2; is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), nchanges, "updates"); - ok(!SOSAccountIsMyPeerInBackupAndCurrentInView(bob_account, kTestView1), "Bob's backup key is in the backup - should not be so!"); - ok(!SOSAccountIsPeerInBackupAndCurrentInView(alice_account, bobTrust.peerInfo, kTestView1), "Bob is up to date in the backup!"); + ok(!SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(bob_account, kTestView1), "Bob's backup key is in the backup - should not be so!"); + ok(!SOSAccountIsPeerInBackupAndCurrentInView_wTxn(alice_account, bobTrust.peerInfo, kTestView1), "Bob is up to date in the backup!"); // // Setting new backup public key for Bob @@ -300,21 +286,21 @@ static void tests(bool recKeyFirst) ok(SOSAccountNewBKSBForView(bob_account, kTestView1, &error), "Setting new backup public key for bob account failed: (%@)", error); //bob is in his own backup - ok(SOSAccountIsMyPeerInBackupAndCurrentInView(bob_account, kTestView1), "Bob's backup key is not in the backup"); + ok(SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(bob_account, kTestView1), "Bob's backup key is not in the backup"); //alice does not have bob in her backup - ok(!SOSAccountIsPeerInBackupAndCurrentInView(alice_account, bobTrust.peerInfo, kTestView1), "Bob is up to date in the backup - should not be so!"); + ok(!SOSAccountIsPeerInBackupAndCurrentInView_wTxn(alice_account, bobTrust.peerInfo, kTestView1), "Bob is up to date in the backup - should not be so!"); is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 5, "updates"); - ok(SOSAccountIsMyPeerInBackupAndCurrentInView(bob_account, kTestView1), "Bob's backup key should be in the backup"); - ok(SOSAccountIsMyPeerInBackupAndCurrentInView(alice_account, kTestView1), "Alice is in the backup"); + ok(SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(bob_account, kTestView1), "Bob's backup key should be in the backup"); + ok(SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(alice_account, kTestView1), "Alice is in the backup"); if(!recKeyFirst) registerRecoveryKeyNow(changes, alice_account, bob_account, pubKeyBytes, recKeyFirst); - ok(SOSAccountRecoveryKeyIsInBackupAndCurrentInView(alice_account, kTestView1), "Recovery Key is also in the backup"); - ok(SOSAccountRecoveryKeyIsInBackupAndCurrentInView(bob_account, kTestView1), "Recovery Key is also in the backup"); + ok(SOSAccountRecoveryKeyIsInBackupAndCurrentInView_wTxn(alice_account, kTestView1), "Recovery Key is also in the backup"); + ok(SOSAccountRecoveryKeyIsInBackupAndCurrentInView_wTxn(bob_account, kTestView1), "Recovery Key is also in the backup"); - SOSBackupSliceKeyBagRef bskb = SOSAccountBackupSliceKeyBagForView(alice_account, kTestView1, &error); + SOSBackupSliceKeyBagRef bskb = SOSAccountBackupSliceKeyBagForView_wTxn(alice_account, kTestView1, &error); CFReleaseNull(error); ok(SOSBSKBHasRecoveryKey(bskb), "BSKB should have recovery key"); @@ -332,10 +318,10 @@ static void tests(bool recKeyFirst) registerRecoveryKeyNow(changes, alice_account, bob_account, NULL, recKeyFirst); - ok(!SOSAccountRecoveryKeyIsInBackupAndCurrentInView(alice_account, kTestView1), "Recovery Key is not in the backup"); - ok(!SOSAccountRecoveryKeyIsInBackupAndCurrentInView(bob_account, kTestView1), "Recovery Key is not in the backup"); + ok(!SOSAccountRecoveryKeyIsInBackupAndCurrentInView_wTxn(alice_account, kTestView1), "Recovery Key is not in the backup"); + ok(!SOSAccountRecoveryKeyIsInBackupAndCurrentInView_wTxn(bob_account, kTestView1), "Recovery Key is not in the backup"); - bskb = SOSAccountBackupSliceKeyBagForView(alice_account, kTestView1, &error); + bskb = SOSAccountBackupSliceKeyBagForView_wTxn(alice_account, kTestView1, &error); CFReleaseNull(error); ok(!SOSBSKBHasRecoveryKey(bskb), "BSKB should not have recovery key"); diff --git a/keychain/securityd/Regressions/secd-76-idstransport.m b/keychain/securityd/Regressions/secd-76-idstransport.m deleted file mode 100644 index 164c56f0..00000000 --- a/keychain/securityd/Regressions/secd-76-idstransport.m +++ /dev/null @@ -1,315 +0,0 @@ -// -// secd-76-idstransport.c -// sec -// -// - -/* - * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "secd_regressions.h" -#include "SOSTestDataSource.h" - -#include "SOSRegressionUtilities.h" -#include - -#include -#include "SecdTestKeychainUtilities.h" -#import "SOSAccountTesting.h" -#import "SOSTransportTestTransports.h" -#include -#include -#include "SOSTestDevice.h" - - - -static int kTestTestCount = 73; - -static void tests() -{ - CFErrorRef error = NULL; - - CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10); - CFStringRef cfaccount = CFSTR("test@test.org"); - - SOSAccount* alice_account = CreateAccountForLocalChanges(CFSTR("Alice"), CFSTR("ak")); - SOSAccount* bob_account = CreateAccountForLocalChanges(CFSTR("Bob"), CFSTR("ak")); - SOSAccountTrustClassic *aliceTrust = alice_account.trust; - SOSAccountTrustClassic *bobTrust = bob_account.trust; - - ok(SOSAccountAssertUserCredentialsAndUpdate(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error); - - // Bob wins writing at this point, feed the changes back to alice. - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 1, "updates"); - - ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error); - CFReleaseNull(cfpassword); - CFReleaseNull(error); - - ok(NULL != alice_account, "Alice Created"); - ok(NULL != bob_account, "Bob Created"); - - ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Reset to offering (%@)", error); - CFReleaseNull(error); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob Applies (%@)", error); - CFReleaseNull(error); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - { - CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error); - - ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error); - ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Alice accepts (%@)", error); - CFReleaseNull(error); - CFReleaseNull(applicants); - } - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 3, "updates"); - - accounts_agree("bob&alice pair", bob_account, alice_account); - - CFArrayRef peers = SOSAccountCopyPeers(alice_account, &error); - ok(peers && CFArrayGetCount(peers) == 2, "See two peers %@ (%@)", peers, error); - CFReleaseNull(peers); - - //creating test devices - CFIndex version = 0; - - // Optionally prefix each peer with name to make them more unique. - CFArrayRef deviceIDs = CFArrayCreateForCFTypes(kCFAllocatorDefault,alice_account.peerID, bob_account.peerID, NULL); - CFSetRef views = SOSViewsCopyTestV2Default(); - CFMutableArrayRef peerMetas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); - CFStringRef deviceID; - CFArrayForEachC(deviceIDs, deviceID) { - SOSPeerMetaRef peerMeta = SOSPeerMetaCreateWithComponents(deviceID, views, NULL); - CFArrayAppendValue(peerMetas, peerMeta); - CFReleaseNull(peerMeta); - } - - CFReleaseNull(views); - CFArrayForEachC(deviceIDs, deviceID) { - SOSTestDeviceRef device = SOSTestDeviceCreateWithDbNamed(kCFAllocatorDefault, deviceID, deviceID); - SOSTestDeviceSetPeerIDs(device, peerMetas, version, NULL); - - if(CFEqualSafe(deviceID, (__bridge CFTypeRef)(alice_account.peerID))){ - alice_account.factory = device->dsf; - SOSTestDeviceAddGenericItem(device, CFSTR("Alice"), CFSTR("Alice-add")); - } - else{ - bob_account.factory = device->dsf; - SOSTestDeviceAddGenericItem(device, CFSTR("Bob"), CFSTR("Bob-add")); - } - CFReleaseNull(device); - } - CFReleaseNull(deviceIDs); - CFReleaseNull(peerMetas); - - SOSUnregisterAllTransportMessages(); - CFArrayRemoveAllValues(message_transports); - - alice_account.ids_message_transport = (SOSMessageIDS*)[[SOSMessageIDSTest alloc] initWithAccount:alice_account andAccountName:CFSTR("Alice") andCircleName:SOSCircleGetName(aliceTrust.trustedCircle) err:&error]; - - - bob_account.ids_message_transport = (SOSMessageIDS*)[[SOSMessageIDSTest alloc] initWithAccount:bob_account andAccountName:CFSTR("Bob") andCircleName:SOSCircleGetName(bobTrust.trustedCircle) err:&error]; - ok(alice_account.ids_message_transport != NULL, "Alice Account, Created IDS Test Transport"); - ok(bob_account.ids_message_transport != NULL, "Bob Account, Created IDS Test Transport"); - - bool result = [alice_account.trust modifyCircle:alice_account.circle_transport err:&error action:^(SOSCircleRef circle) { - CFErrorRef localError = NULL; - - SOSFullPeerInfoUpdateTransportType(aliceTrust.fullPeerInfo, SOSTransportMessageTypeIDSV2, &localError); - SOSFullPeerInfoUpdateTransportPreference(aliceTrust.fullPeerInfo, kCFBooleanFalse, &localError); - SOSFullPeerInfoUpdateTransportFragmentationPreference(aliceTrust.fullPeerInfo, kCFBooleanTrue, &localError); - SOSFullPeerInfoUpdateTransportAckModelPreference(aliceTrust.fullPeerInfo, kCFBooleanTrue, &localError); - - return SOSCircleHasPeer(circle, aliceTrust.peerInfo, NULL); - }]; - - ok(result, "Alice account update circle with transport type"); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - result &= [bob_account.trust modifyCircle:bob_account.circle_transport err:&error action:^(SOSCircleRef circle) { - CFErrorRef localError = NULL; - - SOSFullPeerInfoUpdateTransportType(bobTrust.fullPeerInfo, SOSTransportMessageTypeIDSV2, &localError); - SOSFullPeerInfoUpdateTransportPreference(bobTrust.fullPeerInfo, kCFBooleanFalse, &localError); - SOSFullPeerInfoUpdateTransportFragmentationPreference(bobTrust.fullPeerInfo, kCFBooleanTrue, &localError); - SOSFullPeerInfoUpdateTransportAckModelPreference(bobTrust.fullPeerInfo, kCFBooleanTrue, &localError); - - return SOSCircleHasPeer(circle, bobTrust.peerInfo, NULL); - }]; - - ok(result, "Bob account update circle with transport type"); - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - CFStringRef alice_transportType =SOSPeerInfoCopyTransportType(alice_account.peerInfo); - CFStringRef bob_accountTransportType = SOSPeerInfoCopyTransportType(bob_account.peerInfo); - ok(CFEqualSafe(alice_transportType, CFSTR("IDS2.0")), "Alice transport type not IDS"); - ok(CFEqualSafe(bob_accountTransportType, CFSTR("IDS2.0")), "Bob transport type not IDS"); - - CFReleaseNull(alice_transportType); - CFReleaseNull(bob_accountTransportType); - - SOSTransportMessageIDSTestSetName((SOSMessageIDSTest*)alice_account.ids_message_transport, CFSTR("Alice Account")); - ok(SOSTransportMessageIDSTestGetName((SOSMessageIDSTest*)alice_account.ids_message_transport) != NULL, "retrieved getting account name"); - ok(SOSAccountRetrieveDeviceIDFromKeychainSyncingOverIDSProxy(alice_account, &error) != false, "device ID from KeychainSyncingOverIDSProxy"); - - SOSTransportMessageIDSTestSetName((SOSMessageIDSTest*)bob_account.ids_message_transport, CFSTR("Bob Account")); - ok(SOSTransportMessageIDSTestGetName((SOSMessageIDSTest*)bob_account.ids_message_transport) != NULL, "retrieved getting account name"); - ok(SOSAccountRetrieveDeviceIDFromKeychainSyncingOverIDSProxy(bob_account, &error) != false, "device ID from KeychainSyncingOverIDSProxy"); - - ok(SOSAccountSetMyDSID_wTxn(alice_account, CFSTR("Alice"),&error), "Setting IDS device ID"); - CFStringRef alice_dsid = SOSAccountCopyDeviceID(alice_account, &error); - ok(CFEqualSafe(alice_dsid, CFSTR("Alice")), "Getting IDS device ID"); - - ok(SOSAccountSetMyDSID_wTxn(bob_account, CFSTR("Bob"),&error), "Setting IDS device ID"); - CFStringRef bob_dsid = SOSAccountCopyDeviceID(bob_account, &error); - ok(CFEqualSafe(bob_dsid, CFSTR("Bob")), "Getting IDS device ID"); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 3, "updates"); - - SOSTransportMessageIDSTestSetName((SOSMessageIDSTest*)alice_account.ids_message_transport, CFSTR("Alice Account")); - ok(SOSTransportMessageIDSTestGetName((SOSMessageIDSTest*)alice_account.ids_message_transport) != NULL, "retrieved getting account name"); - ok(SOSAccountRetrieveDeviceIDFromKeychainSyncingOverIDSProxy(alice_account, &error) != false, "device ID from KeychainSyncingOverIDSProxy"); - - ok(SOSAccountSetMyDSID_wTxn(alice_account, CFSTR("DSID"),&error), "Setting IDS device ID"); - CFStringRef dsid = SOSAccountCopyDeviceID(alice_account, &error); - ok(CFEqualSafe(dsid, CFSTR("DSID")), "Getting IDS device ID"); - CFReleaseNull(dsid); - - ok(SOSAccountStartPingTest(alice_account, CFSTR("hai there!"), &error), "Ping test"); - ok(CFDictionaryGetCount(SOSTransportMessageIDSTestGetChanges((SOSMessageIDSTest*)alice_account.ids_message_transport)) != 0, "ping message made it to transport"); - SOSTransportMessageIDSTestClearChanges((SOSMessageIDSTest*)alice_account.ids_message_transport); - - ok(SOSAccountSendIDSTestMessage(alice_account, CFSTR("hai again!"), &error), "Send Test Message"); - ok(CFDictionaryGetCount(SOSTransportMessageIDSTestGetChanges((SOSMessageIDSTest*)alice_account.ids_message_transport)) != 0, "ping message made it to transport"); - - CFStringRef dataKey = CFStringCreateWithCString(kCFAllocatorDefault, kMessageKeyIDSDataMessage, kCFStringEncodingASCII); - CFStringRef deviceIDKey = CFStringCreateWithCString(kCFAllocatorDefault, kMessageKeyDeviceID, kCFStringEncodingASCII); - CFStringRef sendersPeerIDKey = CFStringCreateWithCString(kCFAllocatorDefault, kMessageKeySendersPeerID, kCFStringEncodingASCII); - - //test IDS message handling - CFMutableDictionaryRef messageDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - - ok([alice_account.ids_message_transport SOSTransportMessageIDSHandleMessage:alice_account m:messageDict err:&error] - == kHandleIDSMessageDontHandle, "sending empty message dictionary"); - - CFDictionaryAddValue(messageDict, deviceIDKey, CFSTR("Alice Account")); - ok([alice_account.ids_message_transport SOSTransportMessageIDSHandleMessage:alice_account m:messageDict err:&error] == kHandleIDSMessageDontHandle, "sending device ID only"); - - CFReleaseNull(messageDict); - messageDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFDictionaryAddValue(messageDict, sendersPeerIDKey, CFSTR("Alice Account")); - ok([alice_account.ids_message_transport SOSTransportMessageIDSHandleMessage:alice_account m:messageDict err:&error] == kHandleIDSMessageDontHandle, "sending peer ID only"); - - CFReleaseNull(messageDict); - messageDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFDataRef data = CFDataCreate(kCFAllocatorDefault, 0, 0); - CFDictionaryAddValue(messageDict, dataKey, data); - ok( [alice_account.ids_message_transport SOSTransportMessageIDSHandleMessage:alice_account m:messageDict err:&error] == kHandleIDSMessageDontHandle, "sending data only"); - - CFReleaseNull(messageDict); - CFReleaseNull(data); - messageDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - data = CFDataCreate(kCFAllocatorDefault, 0, 0); - CFDictionaryAddValue(messageDict, dataKey, data); - CFDictionaryAddValue(messageDict, sendersPeerIDKey, CFSTR("Alice Account")); - ok([(SOSMessageIDS*)alice_account.ids_message_transport SOSTransportMessageIDSHandleMessage:alice_account m:messageDict err:&error]== kHandleIDSMessageDontHandle, "sending data and peerid only"); - - CFReleaseNull(messageDict); - CFReleaseNull(data); - messageDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - data = CFDataCreate(kCFAllocatorDefault, 0, 0); - CFDictionaryAddValue(messageDict, dataKey, data); - CFDictionaryAddValue(messageDict, deviceIDKey, CFSTR("Alice Account")); - ok([(SOSMessageIDS*)alice_account.ids_message_transport SOSTransportMessageIDSHandleMessage:alice_account m:messageDict err:&error] == kHandleIDSMessageDontHandle, "sending data and deviceid only"); - - CFReleaseNull(messageDict); - CFReleaseNull(data); - messageDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFDictionaryAddValue(messageDict, deviceIDKey, CFSTR("Alice Account")); - CFDictionaryAddValue(messageDict, sendersPeerIDKey, CFSTR("Alice Account")); - ok([(SOSMessageIDS*)alice_account.ids_message_transport SOSTransportMessageIDSHandleMessage:alice_account m:messageDict err:&error] == kHandleIDSMessageDontHandle, "sending peerid and deviceid only"); - - CFReleaseNull(messageDict); - CFReleaseNull(data); - messageDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - data = CFDataCreate(kCFAllocatorDefault, 0, 0); - CFDictionaryAddValue(messageDict, dataKey, data); - CFDictionaryAddValue(messageDict, deviceIDKey, CFSTR("Alice Account")); - CFDictionaryAddValue(messageDict, sendersPeerIDKey, SOSPeerInfoGetPeerID(bob_account.peerInfo)); - ok([(SOSMessageIDS*)alice_account.ids_message_transport SOSTransportMessageIDSHandleMessage:alice_account m:messageDict err:&error]== kHandleIDSMessageDontHandle, "sending peerid and deviceid and data"); - - CFReleaseNull(messageDict); - CFReleaseNull(data); - - messageDict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - data = CFDataCreate(kCFAllocatorDefault, 0, 0); - CFDictionaryAddValue(messageDict, dataKey, data); - CFStringRef BobDeviceID = SOSPeerInfoCopyDeviceID(bob_account.peerInfo); - CFDictionaryAddValue(messageDict, deviceIDKey, BobDeviceID); - CFReleaseNull(BobDeviceID); - CFDictionaryAddValue(messageDict, sendersPeerIDKey, CFSTR("Alice Account")); - ok([(SOSMessageIDS*)alice_account.ids_message_transport SOSTransportMessageIDSHandleMessage:alice_account m:messageDict err:&error]== kHandleIDSMessageDontHandle, "sending peerid and deviceid and data"); - - CFReleaseNull(data); - CFReleaseNull(dataKey); - CFReleaseNull(deviceIDKey); - CFReleaseNull(sendersPeerIDKey); - - CFReleaseNull(alice_dsid); - CFReleaseNull(bob_dsid); - CFReleaseNull(changes); - - SOSTestCleanup(); -} -int secd_76_idstransport(int argc, char *const *argv) -{ - plan_tests(kTestTestCount); - - secd_test_setup_temp_keychain(__FUNCTION__, NULL); - - tests(); - - return 0; -} diff --git a/keychain/securityd/Regressions/secd-95-escrow-persistence.m b/keychain/securityd/Regressions/secd-95-escrow-persistence.m deleted file mode 100644 index 80c23aea..00000000 --- a/keychain/securityd/Regressions/secd-95-escrow-persistence.m +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2014 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - - -#include -#include - -#include - -#include "keychain/SecureObjectSync/SOSAccount.h" -#include -#include "keychain/SecureObjectSync/SOSInternal.h" -#include "keychain/SecureObjectSync/SOSUserKeygen.h" -#include "keychain/SecureObjectSync/SOSTransport.h" - -#include -#include - -#include "secd_regressions.h" -#include "SOSTestDataSource.h" - -#include "SOSRegressionUtilities.h" -#include -#include - -#include "keychain/securityd/SOSCloudCircleServer.h" - -#include "SOSAccountTesting.h" - -#include "SecdTestKeychainUtilities.h" - -static void tests(void) -{ - CFErrorRef error = NULL; - - CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10); - CFStringRef cfaccount = CFSTR("test@test.org"); - - CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - - SOSAccount* alice_account = CreateAccountForLocalChanges(CFSTR("Alice"), CFSTR("TestSource")); - SOSAccount* bob_account = CreateAccountForLocalChanges(CFSTR("Bob"), CFSTR("TestSource")); - - ok(SOSAccountAssertUserCredentialsAndUpdate(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error); - - // Bob wins writing at this point, feed the changes back to alice. - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 1, "updates"); - - ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error); - CFReleaseNull(error); - - CFReleaseNull(cfpassword); - CFReleaseNull(error); - - ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Reset to offering (%@)", error); - CFReleaseNull(error); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob Applies (%@)", error); - CFReleaseNull(error); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - { - CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error); - - ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicants %@ (%@)", applicants, error); - CFReleaseNull(error); - CFReleaseSafe(applicants); - } - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 1, "updates"); - CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("[")); - CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent(); - - withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) { - CFStringAppend(timeDescription, decription); - }); - CFStringAppend(timeDescription, CFSTR("]")); - - int tries = 5; - - CFNumberRef attempts = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &tries); - - CFMutableArrayRef escrowTimeAndTries = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); - CFArrayAppendValue(escrowTimeAndTries, timeDescription); - CFArrayAppendValue(escrowTimeAndTries, attempts); - CFDictionaryRef escrowRecord = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, CFSTR("account label"), escrowTimeAndTries, NULL); - - CFMutableDictionaryRef record = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFDictionaryAddValue(record, CFSTR("12345"), escrowRecord); - - SOSFullPeerInfoRef alice_fpi = alice_account.fullPeerInfo; - ok(SOSFullPeerInfoAddEscrowRecord(alice_fpi, CFSTR("12345"), escrowRecord, &error), "Adding Escrow records to Alice FPI(%@)", error); - CFDictionaryRef fpi_escrow = SOSPeerInfoCopyEscrowRecord(SOSFullPeerInfoGetPeerInfo(alice_fpi)); - ok(CFEqualSafe(CFDictionaryGetValue(fpi_escrow, CFSTR("12345")), escrowRecord), "Alice's FPI has escrow (%@)", error); - - ok(SOSAccountAddEscrowRecords(bob_account, CFSTR("12345"), escrowRecord, &error), "Adding escrow to Bob's account (%@)", error); - CFReleaseNull(fpi_escrow); - - fpi_escrow = (CFDictionaryRef)SOSAccountGetValue(bob_account, kSOSEscrowRecord, NULL); - ok(CFEqualSafe(CFDictionaryGetValue(fpi_escrow, CFSTR("12345")), escrowRecord), "Bob has escrow records in account (%@)", error); - ok(SOSAccountHasPublicKey(alice_account, &error), "Has Public Key" ); - - ok([alice_account.trust resetAccountToEmpty:alice_account transport:alice_account.circle_transport err:&error], "Reset to offering (%@)", error); - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - ok(SOSAccountHasPublicKey(bob_account, &error), "Has Public Key" ); - - ok([bob_account.trust resetAccountToEmpty:bob_account transport:bob_account.circle_transport err:&error], "Reset to offering (%@)", error); - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - ok(SOSAccountAddEscrowRecords(bob_account, CFSTR("12345"), escrowRecord, &error), "Adding escrow to Bob's account (%@)", error); - - ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Reset to offering (%@)", error); - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - SOSAccountTrustClassic *bobTrust = bob_account.trust; - CFDictionaryRef bob_fpi_escrow = SOSPeerInfoCopyEscrowRecord(SOSFullPeerInfoGetPeerInfo(bobTrust.fullPeerInfo)); - ok(bob_fpi_escrow == NULL, "Bob's FPI escrow should be null"); - CFReleaseNull(bob_fpi_escrow); - - ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob Applies (%@)", error); - bob_fpi_escrow = SOSPeerInfoCopyEscrowRecord(SOSFullPeerInfoGetPeerInfo(bobTrust.fullPeerInfo)); - ok(bob_fpi_escrow && CFEqualSafe(CFDictionaryGetValue(bob_fpi_escrow, CFSTR("12345")), escrowRecord), "Bob has escrow records in account (%@)", error); - CFReleaseNull(bob_fpi_escrow); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - { - CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error); - ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicants %@ (%@)", applicants, error); - ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Accept bob into the fold"); - CFReleaseNull(error); - CFReleaseNull(applicants); - } - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 3, "updates"); - - fpi_escrow = (CFDictionaryRef)SOSAccountGetValue(bob_account, kSOSEscrowRecord, NULL); - ok(isNull(fpi_escrow), "Bob's escrow records in the account object should be gone"); - - CFReleaseNull(record); - CFReleaseNull(escrowRecord); - CFReleaseNull(timeDescription); - CFReleaseNull(attempts); - SOSTestCleanup(); - -} - -int secd_95_escrow_persistence(int argc, char *const *argv) -{ - plan_tests(41); - - secd_test_setup_temp_keychain(__FUNCTION__, NULL); - - tests(); - - return 0; -} diff --git a/keychain/securityd/Regressions/secd_77_ids_messaging.m b/keychain/securityd/Regressions/secd_77_ids_messaging.m deleted file mode 100644 index a23cdabb..00000000 --- a/keychain/securityd/Regressions/secd_77_ids_messaging.m +++ /dev/null @@ -1,296 +0,0 @@ -// -// secd_77_ids_messaging.c -// sec -// - -/* - * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "secd_regressions.h" -#include "SOSTestDataSource.h" - -#include "SOSRegressionUtilities.h" -#include - -#include -#include "SecdTestKeychainUtilities.h" -#include "SOSAccountTesting.h" -#import "SOSTransportTestTransports.h" -#include "SOSTestDevice.h" -#include "SOSTestDataSource.h" -#include -#include - -static bool SOSAccountIsThisPeerIDMe(SOSAccount* account, CFStringRef peerID) { - SOSAccountTrustClassic* trust = account.trust; - SOSPeerInfoRef mypi = trust.peerInfo; - CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi); - - return myPeerID && CFEqualSafe(myPeerID, peerID); -} - -__unused static void ids_test_sync(SOSAccount* alice_account, SOSAccount* bob_account){ - - CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - __block bool SyncingCompletedOverIDS = false; - __block CFErrorRef localError = NULL; - __block bool done = false; - SOSAccountTrustClassic* aliceTrust = alice_account.trust; - SOSAccountTrustClassic* bobTrust = bob_account.trust; - - do{ - SOSCircleForEachValidPeer(aliceTrust.trustedCircle, alice_account.accountKey, ^(SOSPeerInfoRef peer) { - if (!SOSAccountIsThisPeerIDMe(alice_account, SOSPeerInfoGetPeerID(peer))) { - if(SOSPeerInfoShouldUseIDSTransport(aliceTrust.peerInfo, peer) && - SOSPeerInfoShouldUseIDSMessageFragmentation(aliceTrust.peerInfo, peer)){ - secnotice("IDS Transport","Syncing with IDS capable peers using IDS!"); - - CFMutableSetRef ids = CFSetCreateMutableForCFTypes(kCFAllocatorDefault); - CFSetAddValue(ids, SOSPeerInfoGetPeerID(peer)); - - SyncingCompletedOverIDS = [alice_account.ids_message_transport SOSTransportMessageSyncWithPeers:alice_account.ids_message_transport p:ids err:&localError]; - CFReleaseNull(ids); - } - } - }); - - ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL); - - SOSCircleForEachValidPeer(bobTrust.trustedCircle, bob_account.accountKey, ^(SOSPeerInfoRef peer) { - if (!SOSAccountIsThisPeerIDMe(bob_account, SOSPeerInfoGetPeerID(peer))) { - if(SOSPeerInfoShouldUseIDSTransport(bobTrust.peerInfo, peer) && - SOSPeerInfoShouldUseIDSMessageFragmentation(bobTrust.peerInfo, peer)){ - secnotice("IDS Transport","Syncing with IDS capable peers using IDS!"); - - CFMutableSetRef ids = CFSetCreateMutableForCFTypes(kCFAllocatorDefault); - CFSetAddValue(ids, SOSPeerInfoGetPeerID(peer)); - - SyncingCompletedOverIDS = [(SOSMessageIDSTest*)bob_account.ids_message_transport SOSTransportMessageSyncWithPeers:(SOSMessageIDSTest*)bob_account.ids_message_transport p:ids err:&localError]; - CFReleaseNull(ids); - } - } - }); - - ok(SyncingCompletedOverIDS, "synced items over IDS"); - if(CFDictionaryGetCount(SOSTransportMessageIDSTestGetChanges((SOSMessageIDSTest*)alice_account.ids_message_transport)) == 0 && CFDictionaryGetCount(SOSTransportMessageIDSTestGetChanges((SOSMessageIDSTest*)bob_account.ids_message_transport)) == 0){ - done = true; - break; - } - - ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL); - - }while(done == false); - CFReleaseNull(changes); -} - -static void tests() -{ - CFErrorRef error = NULL; - - CFMutableDictionaryRef changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10); - CFStringRef cfaccount = CFSTR("test@test.org"); - CFStringRef dsName = CFSTR("Test"); - - SOSAccount* alice_account = CreateAccountForLocalChanges(CFSTR("Alice"), dsName); - SOSAccount* bob_account = CreateAccountForLocalChanges(CFSTR("Bob"), dsName); - - ok(SOSAccountAssertUserCredentialsAndUpdate(bob_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error); - - // Bob wins writing at this point, feed the changes back to alice. - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 1, "updates"); - - ok(SOSAccountAssertUserCredentialsAndUpdate(alice_account, cfaccount, cfpassword, &error), "Credential setting (%@)", error); - CFReleaseNull(cfpassword); - CFReleaseNull(error); - - ok(NULL != alice_account, "Alice Created"); - ok(NULL != bob_account, "Bob Created"); - - ok(SOSAccountResetToOffering_wTxn(alice_account, &error), "Reset to offering (%@)", error); - CFReleaseNull(error); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - ok(SOSAccountJoinCircles_wTxn(bob_account, &error), "Bob Applies (%@)", error); - CFReleaseNull(error); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - { - CFArrayRef applicants = SOSAccountCopyApplicants(alice_account, &error); - - ok(applicants && CFArrayGetCount(applicants) == 1, "See one applicant %@ (%@)", applicants, error); - ok(SOSAccountAcceptApplicants(alice_account, applicants, &error), "Alice accepts (%@)", error); - CFReleaseNull(error); - CFReleaseNull(applicants); - } - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 3, "updates"); - - accounts_agree("bob&alice pair", bob_account, alice_account); - - CFArrayRef peers = SOSAccountCopyPeers(alice_account, &error); - ok(peers && CFArrayGetCount(peers) == 2, "See two peers %@ (%@)", peers, error); - CFReleaseNull(peers); - - //creating test devices - CFIndex version = 0; - - // Optionally prefix each peer with name to make them more unique. - CFArrayRef deviceIDs = CFArrayCreateForCFTypes(kCFAllocatorDefault,alice_account.peerID, bob_account.peerID, NULL); - CFSetRef views = SOSViewsCopyTestV2Default(); - CFMutableArrayRef peerMetas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); - CFStringRef deviceID; - CFArrayForEachC(deviceIDs, deviceID) { - SOSPeerMetaRef peerMeta = SOSPeerMetaCreateWithComponents(deviceID, views, NULL); - CFArrayAppendValue(peerMetas, peerMeta); - CFReleaseNull(peerMeta); - } - - CFReleaseNull(views); - CFArrayForEachC(deviceIDs, deviceID) { - SOSTestDeviceRef device = SOSTestDeviceCreateWithDbNamed(kCFAllocatorDefault, deviceID, deviceID); - SOSTestDeviceSetPeerIDs(device, peerMetas, version, NULL); - - if([alice_account.peerID isEqual: (__bridge id)(deviceID)]){ - alice_account.factory = device->dsf; - SOSTestDeviceAddGenericItem(device, CFSTR("Alice"), CFSTR("Alice-add")); - } - else{ - bob_account.factory = device->dsf; - SOSTestDeviceAddGenericItem(device, CFSTR("Bob"), CFSTR("Bob-add")); - } - - CFReleaseNull(device); - } - CFReleaseNull(deviceIDs); - CFReleaseNull(peerMetas); - - SOSUnregisterAllTransportMessages(); - CFArrayRemoveAllValues(message_transports); - - SOSAccountTrustClassic* aliceTrust = alice_account.trust; - SOSAccountTrustClassic* bobTrust = bob_account.trust; - - alice_account.ids_message_transport = (SOSMessageIDS*)[[SOSMessageIDSTest alloc] initWithAccount:alice_account andAccountName:CFSTR("Alice") andCircleName:SOSCircleGetName(aliceTrust.trustedCircle) err:&error ]; - - bob_account.ids_message_transport = (SOSMessageIDS*)[[SOSMessageIDSTest alloc] initWithAccount:bob_account andAccountName:CFSTR("Bob") andCircleName:SOSCircleGetName(bobTrust.trustedCircle) err:&error]; - - ok(alice_account.ids_message_transport != NULL, "Alice Account, Created IDS Test Transport"); - ok(bob_account.ids_message_transport != NULL, "Bob Account, Created IDS Test Transport"); - - bool result = [alice_account.trust modifyCircle:alice_account.circle_transport err:&error action:^bool(SOSCircleRef circle) { - CFErrorRef localError = NULL; - - SOSFullPeerInfoUpdateTransportType(aliceTrust.fullPeerInfo, SOSTransportMessageTypeIDSV2, &localError); - SOSFullPeerInfoUpdateTransportPreference(aliceTrust.fullPeerInfo, kCFBooleanFalse, &localError); - SOSFullPeerInfoUpdateTransportFragmentationPreference(aliceTrust.fullPeerInfo, kCFBooleanTrue, &localError); - SOSFullPeerInfoUpdateTransportAckModelPreference(aliceTrust.fullPeerInfo, kCFBooleanTrue, &localError); - - return SOSCircleHasPeer(circle, aliceTrust.peerInfo, NULL); - }]; - - ok(result, "Alice account update circle with transport type"); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - result = [bob_account.trust modifyCircle:bob_account.circle_transport err:&error action:^bool(SOSCircleRef circle) { - CFErrorRef localError = NULL; - - SOSFullPeerInfoUpdateTransportType(bobTrust.fullPeerInfo, SOSTransportMessageTypeIDSV2, &localError); - SOSFullPeerInfoUpdateTransportPreference(bobTrust.fullPeerInfo, kCFBooleanFalse, &localError); - SOSFullPeerInfoUpdateTransportFragmentationPreference(bobTrust.fullPeerInfo, kCFBooleanTrue, &localError); - SOSFullPeerInfoUpdateTransportAckModelPreference(bobTrust.fullPeerInfo, kCFBooleanTrue, &localError); - - return SOSCircleHasPeer(circle, bobTrust.peerInfo, NULL); - }]; - - ok(result, "Bob account update circle with transport type"); - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 2, "updates"); - - CFStringRef alice_transportType =SOSPeerInfoCopyTransportType(alice_account.peerInfo); - CFStringRef bob_accountTransportType = SOSPeerInfoCopyTransportType(bob_account.peerInfo); - ok(CFEqualSafe(alice_transportType, CFSTR("IDS2.0")), "Alice transport type not IDS"); - ok(CFEqualSafe(bob_accountTransportType, CFSTR("IDS2.0")), "Bob transport type not IDS"); - - CFReleaseNull(alice_transportType); - CFReleaseNull(bob_accountTransportType); - - SOSTransportMessageIDSTestSetName((SOSMessageIDSTest*)alice_account.ids_message_transport, CFSTR("Alice Account")); - ok(SOSTransportMessageIDSTestGetName((SOSMessageIDSTest*)alice_account.ids_message_transport) != NULL, "retrieved getting account name"); - ok(SOSAccountRetrieveDeviceIDFromKeychainSyncingOverIDSProxy(alice_account, &error) != false, "device ID from KeychainSyncingOverIDSProxy"); - - SOSTransportMessageIDSTestSetName((SOSMessageIDSTest*)bob_account.ids_message_transport, CFSTR("Bob Account")); - ok(SOSTransportMessageIDSTestGetName((SOSMessageIDSTest*)bob_account.ids_message_transport) != NULL, "retrieved getting account name"); - ok(SOSAccountRetrieveDeviceIDFromKeychainSyncingOverIDSProxy(bob_account, &error) != false, "device ID from KeychainSyncingOverIDSProxy"); - - - ok(SOSAccountSetMyDSID_wTxn(alice_account, CFSTR("Alice"),&error), "Setting IDS device ID"); - CFStringRef alice_dsid = SOSAccountCopyDeviceID(alice_account, &error); - ok(CFEqualSafe(alice_dsid, CFSTR("Alice")), "Getting IDS device ID"); - - ok(SOSAccountSetMyDSID_wTxn(bob_account, CFSTR("Bob"),&error), "Setting IDS device ID"); - CFStringRef bob_dsid = SOSAccountCopyDeviceID(bob_account, &error); - ok(CFEqualSafe(bob_dsid, CFSTR("Bob")), "Getting IDS device ID"); - - is(ProcessChangesUntilNoChange(changes, alice_account, bob_account, NULL), 3, "updates"); - - - ok(SOSAccountEnsurePeerRegistration(alice_account, NULL), "ensure peer registration - alice"); - - ok(SOSAccountEnsurePeerRegistration(bob_account, NULL), "ensure peer registration - bob"); - - - //ids_test_sync(alice_account, bob_account); - - CFReleaseNull(bob_dsid); - CFReleaseNull(alice_dsid); - CFReleaseNull(changes); - - SOSTestCleanup(); -} - -int secd_77_ids_messaging(int argc, char *const *argv) -{ - plan_tests(100); - - secd_test_setup_temp_keychain(__FUNCTION__, NULL); - - tests(); - - return 0; -} diff --git a/keychain/securityd/Regressions/secd_regressions.h b/keychain/securityd/Regressions/secd_regressions.h index d3de4a06..422b0cde 100644 --- a/keychain/securityd/Regressions/secd_regressions.h +++ b/keychain/securityd/Regressions/secd_regressions.h @@ -86,7 +86,6 @@ ONE_TEST(secd_82_persistent_ref) ONE_TEST(secd_83_item_match_policy) ONE_TEST(secd_83_item_match_valid_on_date) ONE_TEST(secd_83_item_match_trusted) -ONE_TEST(secd_95_escrow_persistence) ONE_TEST(secd_154_engine_backoff) ONE_TEST(secd_100_initialsync) ONE_TEST(secd_130_other_peer_views) diff --git a/keychain/securityd/SOSCloudCircleServer.h b/keychain/securityd/SOSCloudCircleServer.h index 3adf34eb..0bb84b8b 100644 --- a/keychain/securityd/SOSCloudCircleServer.h +++ b/keychain/securityd/SOSCloudCircleServer.h @@ -55,15 +55,6 @@ bool SOSCCRemovePeersFromCircle_Server(CFArrayRef peers, CFErrorRef* error); bool SOSCCRemovePeersFromCircleWithAnalytics_Server(CFArrayRef peers, CFDataRef parentEvent, CFErrorRef* error); bool SOSCCLoggedOutOfAccount_Server(CFErrorRef *error); bool SOSCCBailFromCircle_Server(uint64_t limit_in_seconds, CFErrorRef* error); -bool SOSCCRequestEnsureFreshParameters_Server(CFErrorRef* error); - - -bool SOSCCApplyToARing_Server(CFStringRef ringName, CFErrorRef *error); -bool SOSCCWithdrawlFromARing_Server(CFStringRef ringName, CFErrorRef *error); -SOSRingStatus SOSCCRingStatus_Server(CFStringRef ringName, CFErrorRef *error); -CF_RETURNS_RETAINED CFStringRef SOSCCGetAllTheRings_Server(CFErrorRef *error); -bool SOSCCEnableRing_Server(CFStringRef ringName, CFErrorRef *error); - CFArrayRef SOSCCCopyGenerationPeerInfo_Server(CFErrorRef* error); CFArrayRef SOSCCCopyApplicantPeerInfo_Server(CFErrorRef* error); @@ -81,8 +72,6 @@ CFArrayRef SOSCCCopyEngineState_Server(CFErrorRef* error); CFArrayRef SOSCCCopyPeerPeerInfo_Server(CFErrorRef* error); CFArrayRef SOSCCCopyConcurringPeerPeerInfo_Server(CFErrorRef* error); -bool SOSCCkSecXPCOpIsThisDeviceLastBackup_Server(CFErrorRef *error); -bool SOSCCkSecXPCOpIsThisDeviceLastBackup_Server(CFErrorRef *error); bool SOSCCAccountSetToNew_Server(CFErrorRef *error); bool SOSCCResetToOffering_Server(CFErrorRef* error); bool SOSCCResetToEmpty_Server(CFErrorRef* error); @@ -94,7 +83,6 @@ SOSViewResultCode SOSCCView_Server(CFStringRef view, SOSViewActionCode action, C bool SOSCCViewSetWithAnalytics_Server(CFSetRef enabledViews, CFSetRef disabledViews, CFDataRef parentEvent); bool SOSCCViewSet_Server(CFSetRef enabledViews, CFSetRef disabledViews); -CFStringRef SOSCCCopyIncompatibilityInfo_Server(CFErrorRef* error); enum DepartureReason SOSCCGetLastDepartureReason_Server(CFErrorRef* error); bool SOSCCSetLastDepartureReason_Server(enum DepartureReason reason, CFErrorRef *error); @@ -108,12 +96,6 @@ bool SOSCCRegisterSingleRecoverySecret_Server(CFDataRef backupSlice, bool setupV bool SOSCCWaitForInitialSync_Server(CFErrorRef*); bool SOSCCWaitForInitialSyncWithAnalytics_Server(CFDataRef parentEvent, CFErrorRef* error); -CFArrayRef SOSCCCopyYetToSyncViewsList_Server(CFErrorRef*); - -bool SOSWrapToBackupSliceKeyBagForView_Server(CFStringRef viewName, CFDataRef input, CFDataRef* output, CFDataRef* bskbEncoded, CFErrorRef* error); - -SOSBackupSliceKeyBagRef SOSBackupSliceKeyBagForView(CFStringRef viewName, CFErrorRef* error); -CF_RETURNS_RETAINED CFDataRef SOSWrapToBackupSliceKeyBag(SOSBackupSliceKeyBagRef bskb, CFDataRef input, CFErrorRef* error); // // MARK: Internal kicks. @@ -147,11 +129,6 @@ CFTypeRef SOSKeychainAccountGetSharedAccount(void); void SOSCCSetGestalt_Server(CFStringRef name, CFStringRef version, CFStringRef model, CFStringRef serial); CFStringRef SOSCCCopyOSVersion(void); -CFDataRef SOSCCCopyAccountState_Server(CFErrorRef* error); -CFDataRef SOSCCCopyEngineData_Server(CFErrorRef* error); -bool SOSCCDeleteEngineState_Server(CFErrorRef* error); -bool SOSCCDeleteAccountState_Server(CFErrorRef* error); - // // MARK: Testing operations, dangerous to call in normal operation. @@ -168,13 +145,9 @@ extern CFStringRef kSOSPeerDataLabel; CFDataRef SOSItemCopy(CFStringRef label, CFErrorRef* error); bool SOSItemUpdateOrAdd(CFStringRef label, CFStringRef accessibility, CFDataRef data, CFErrorRef *error); -bool SOSCCSetEscrowRecord_Server(CFStringRef escrow_label, uint64_t tries, CFErrorRef *error); -CFDictionaryRef SOSCCCopyEscrowRecord_Server(CFErrorRef *error); bool SOSCCRegisterRecoveryPublicKey_Server(CFDataRef recovery_key, CFErrorRef *error); CFDataRef SOSCCCopyRecoveryPublicKey_Server(CFErrorRef *error); -CFDictionaryRef SOSCCCopyBackupInformation_Server(CFErrorRef *error); - SOSPeerInfoRef SOSCCCopyApplication_Server(CFErrorRef *error); CFDataRef SOSCCCopyCircleJoiningBlob_Server(SOSPeerInfoRef applicant, CFErrorRef *error); bool SOSCCJoinWithCircleJoiningBlob_Server(CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error); @@ -182,8 +155,6 @@ CFDataRef SOSCCCopyInitialSyncData_Server(uint32_t flags, CFErrorRef *error); bool SOSCCCleanupKVSKeys_Server(CFErrorRef *error); bool SOSCCAccountHasPublicKey_Server(CFErrorRef *error); -bool SOSCCAccountIsNew_Server(CFErrorRef *error); -bool SOSCCTestPopulateKVSWithBadKeys_Server(CFErrorRef *error); void sync_the_last_data_to_kvs(CFTypeRef account, bool waitForeverForSynchronization); diff --git a/keychain/securityd/SOSCloudCircleServer.m b/keychain/securityd/SOSCloudCircleServer.m index 51bccb51..71677a8a 100644 --- a/keychain/securityd/SOSCloudCircleServer.m +++ b/keychain/securityd/SOSCloudCircleServer.m @@ -45,7 +45,6 @@ #include "keychain/SecureObjectSync/SOSInternal.h" #include "keychain/SecureObjectSync/SOSUserKeygen.h" #include "keychain/SecureObjectSync/SOSMessage.h" -#include "keychain/SecureObjectSync/SOSBackupInformation.h" #include "keychain/SecureObjectSync/SOSDataSource.h" #include "keychain/SecureObjectSync/SOSKVSKeys.h" #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h" @@ -258,7 +257,7 @@ static SOSAccount* SOSKeychainAccountCreateSharedAccount(CFDictionaryRef our_ges secerror("Got NULL creating account"); } - [account startStateMachine]; + //[account startStateMachine]; done: CFReleaseNull(savedAccount); @@ -1152,21 +1151,6 @@ bool SOSCCAccountHasPublicKey_Server(CFErrorRef *error) return hasPublicKey; } -bool SOSCCAccountIsNew_Server(CFErrorRef *error) -{ - __block bool result = true; - __block CFErrorRef localError = NULL; - - (void) do_with_account_while_unlocked(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - result = SOSAccountIsNew(txn.account, &localError); - return result; - }); - - if(error != NULL && localError != NULL) - *error = localError; - - return result; -} bool SOSCCRequestToJoinCircleAfterRestore_Server(CFErrorRef* error) { __block bool result = true; @@ -1209,99 +1193,6 @@ bool SOSCCRequestToJoinCircleAfterRestoreWithAnalytics_Server(CFDataRef parentEv } -bool SOSCCRequestEnsureFreshParameters_Server(CFErrorRef* error) -{ - bool returned = false; - returned = do_with_account_while_unlocked(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - return SyncKVSAndWait(block_error); - }); - if (returned) { - returned = Flush(error); - } - return returned; -} - -bool SOSCCApplyToARing_Server(CFStringRef ringName, CFErrorRef *error){ - __block bool result = true; - bool returned = false; - returned = do_with_account_while_unlocked(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - SOSFullPeerInfoRef fpi = txn.account.fullPeerInfo; - SOSRingRef ring = [txn.account.trust copyRing:ringName err:error]; - - if(fpi && ring) { - result = SOSRingApply(ring, txn.account.accountKey, fpi , error); - } - CFReleaseNull(ring); - return result; - }); - return returned; -} - -bool SOSCCWithdrawlFromARing_Server(CFStringRef ringName, CFErrorRef *error){ - __block bool result = true; - bool returned = false; - returned = do_with_account_while_unlocked(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - SOSFullPeerInfoRef fpi = txn.account.fullPeerInfo; - SOSRingRef ring = [txn.account.trust copyRing:ringName err:error]; - if(fpi && ring) { - result = SOSRingWithdraw(ring, txn.account.accountKey, fpi , error); - } - CFReleaseNull(ring); - return result; - }); - return returned; -} - -bool SOSCCEnableRing_Server(CFStringRef ringName, CFErrorRef *error){ - __block bool result = true; - bool returned = false; - returned = do_with_account_while_unlocked(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - SOSFullPeerInfoRef fpi = txn.account.fullPeerInfo; - SOSRingRef ring = [txn.account.trust copyRing:ringName err:error]; - if(fpi && ring) { - result = SOSRingResetToOffering(ring, NULL, fpi, error); - } - CFReleaseNull(ring); - return result; - }); - return returned; -} - -CFStringRef SOSCCGetAllTheRings_Server(CFErrorRef *error){ - __block CFMutableDictionaryRef result = NULL; - __block CFMutableStringRef description = CFStringCreateMutable(kCFAllocatorDefault, 0); - - (void) do_with_account_while_unlocked(error, ^bool(SOSAccountTransaction* txn, CFErrorRef *error) { - SOSAccountForEachRing(txn.account, ^SOSRingRef(CFStringRef name, SOSRingRef ring) { - CFStringAppendFormat(description, NULL, CFSTR("%@\n"), ring); - return NULL; - }); - if(result) - return true; - return false; - }); - - return description; -} - -SOSRingStatus SOSCCRingStatus_Server(CFStringRef ringName, CFErrorRef *error){ - __block bool result = true; - SOSRingStatus returned; - returned = do_with_account_while_unlocked(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - SOSFullPeerInfoRef fpi = txn.account.fullPeerInfo; - SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi); - - SOSRingRef ring = [txn.account.trust copyRing:ringName err:error]; - if(myPeer && ring) { - result = SOSRingDeviceIsInRing(ring, SOSPeerInfoGetPeerID(myPeer)); - } - CFReleaseNull(ring); - - return result; - }); - return returned; -} - bool SOSCCAccountSetToNew_Server(CFErrorRef *error) { return do_with_account_if_after_first_unlock(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { @@ -1440,7 +1331,7 @@ bool SOSCCBailFromCircle_Server(uint64_t limit_in_seconds, CFErrorRef* error) { return do_with_account_while_unlocked(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { bool waitForeverForSynchronization = false; - + bool result = SOSAccountBail(txn.account, limit_in_seconds, block_error); [txn restart]; // Make sure this gets finished before we set to new. @@ -1764,220 +1655,6 @@ fail: return result; } - -static CFArrayRef SOSAccountCopyYetToSyncViews(SOSAccount* account, CFErrorRef *error) { - __block CFArrayRef result = NULL; - - CFTypeRef valueFetched = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, error); - if (valueFetched == kCFBooleanTrue) { - SOSPeerInfoRef myPI = account.peerInfo; - if (myPI) { - SOSPeerInfoWithEnabledViewSet(myPI, ^(CFSetRef enabled) { - result = CFSetCopyValues(enabled); - }); - } - } else if (isSet(valueFetched)) { - result = CFSetCopyValues((CFSetRef)valueFetched); - } - - if (result == NULL) { - result = CFArrayCreateForCFTypes(kCFAllocatorDefault, NULL); - } - - return result; -} - -CFArrayRef SOSCCCopyYetToSyncViewsList_Server(CFErrorRef* error) { - - __block CFArrayRef views = NULL; - - (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - views = SOSAccountCopyYetToSyncViews(txn.account, error); - - return true; - }); - - return views; -} - -bool SOSWrapToBackupSliceKeyBagForView_Server(CFStringRef viewName, CFDataRef input, CFDataRef* output, CFDataRef* bskbEncoded, CFErrorRef* error) { - CFErrorRef localerror = NULL; - SOSBackupSliceKeyBagRef bskb = SOSBackupSliceKeyBagForView(viewName, &localerror); - - if(bskbEncoded && bskb) { - *bskbEncoded = SOSBSKBCopyEncoded(bskb, &localerror); - } - - if(output) { - *output = SOSWrapToBackupSliceKeyBag(bskb, input, &localerror); - } - - if(error) { - *error = localerror; - } - return localerror == NULL; -} - -SOSBackupSliceKeyBagRef SOSBackupSliceKeyBagForView(CFStringRef viewName, CFErrorRef* error){ - __block SOSBackupSliceKeyBagRef bskb = NULL; - (void) do_with_account(^ (SOSAccountTransaction* txn) { - bskb = SOSAccountBackupSliceKeyBagForView(txn.account, viewName, error); - }); - return bskb; -} - -CFDataRef SOSWrapToBackupSliceKeyBag(SOSBackupSliceKeyBagRef bskb, CFDataRef input, CFErrorRef* error) { - CFDataRef encrypted = NULL; - bskb_keybag_handle_t bskb_handle = 0; - - require_quiet(bskb, exit); - - bskb_handle = SOSBSKBLoadLocked(bskb, error); - require_quiet(bskb_handle, exit); - - SecAccessControlRef access = NULL; - require_quiet(access = SecAccessControlCreate(kCFAllocatorDefault, error), exit); - require_quiet(SecAccessControlSetProtection(access, kSecAttrAccessibleWhenUnlocked, error), exit); - - // ks_encrypt_data takes a dictionary as its plaintext. - CFMutableDictionaryRef plaintext = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFDictionarySetValue(plaintext, CFSTR("data"), input); - - require_quiet(ks_encrypt_data_legacy(bskb_handle, access, NULL, plaintext, NULL, &encrypted, false, error), exit); - -exit: - CFReleaseNull(bskb); - if(bskb_handle != 0) { - ks_close_keybag(bskb_handle, error); - } - if(error && *error) { - secnotice("backup", "Failed to wrap to a BKSB: %@", *error); - } - return encrypted; - -} - -CFDictionaryRef SOSCCCopyEscrowRecord_Server(CFErrorRef *error){ - - __block CFDictionaryRef result = NULL; - - (void) do_with_account_if_after_first_unlock(error, ^bool(SOSAccountTransaction* txn, CFErrorRef *error) { - CFErrorRef localError = NULL; - SOSCCStatus status = [txn.account getCircleStatus:&localError]; - - CFStringRef dsid = SOSAccountGetValue(txn.account, kSOSDSIDKey, error); - CFDictionaryRef escrowRecords = NULL; - CFDictionaryRef record = NULL; - switch(status) { - case kSOSCCInCircle: - //get the escrow record in the peer info! - escrowRecords = SOSPeerInfoCopyEscrowRecord(txn.account.peerInfo); - if(escrowRecords){ - record = CFDictionaryGetValue(escrowRecords, dsid); - if(record) - result = CFRetainSafe(record); - } - CFReleaseNull(escrowRecords); - break; - case kSOSCCRequestPending: - //set the escrow record in the peer info/application? - break; - case kSOSCCNotInCircle: - case kSOSCCCircleAbsent: - //set the escrow record in the account expansion! - escrowRecords = SOSAccountGetValue(txn.account, kSOSEscrowRecord, error); - if(escrowRecords){ - record = CFDictionaryGetValue(escrowRecords, dsid); - if(record) - result = CFRetainSafe(record); - } - break; - default: - secdebug("account", "no circle status!"); - break; - } - return true; - }); - - return result; -} - -CFDictionaryRef SOSCCCopyBackupInformation_Server(CFErrorRef *error) { - __block CFDictionaryRef result = NULL; - - (void) do_with_account_if_after_first_unlock(error, ^bool(SOSAccountTransaction* txn, CFErrorRef *error) { - result = SOSBackupInformation(txn, error); - return true; - }); - return result; -} - -bool SOSCCSetEscrowRecord_Server(CFStringRef escrow_label, uint64_t tries, CFErrorRef *error){ - - if (escrow_label == NULL) { - return false; - } - - __block bool result = true; - __block CFErrorRef block_error = NULL; - - (void) do_with_account_if_after_first_unlock(error, ^bool(SOSAccountTransaction* txn, CFErrorRef *error) { - SOSCCStatus status = [txn.account getCircleStatus:&block_error]; - - CFStringRef dsid = SOSAccountGetValue(txn.account, kSOSDSIDKey, error); - - CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("[")); - CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent(); - - withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) { - CFStringAppend(timeDescription, decription); - }); - CFStringAppend(timeDescription, CFSTR("]")); - - CFNumberRef attempts = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, (const void*)&tries); - - CFMutableDictionaryRef escrowTimeAndTries = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFDictionaryAddValue(escrowTimeAndTries, kSOSBurnedRecoveryAttemptCount, attempts); - CFDictionaryAddValue(escrowTimeAndTries, kSOSBurnedRecoveryAttemptAttestationDate, timeDescription); - - CFMutableDictionaryRef escrowRecord = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); - CFDictionaryAddValue(escrowRecord, escrow_label, escrowTimeAndTries); - - switch(status) { - case kSOSCCInCircle: - //set the escrow record in the peer info! - if(!SOSFullPeerInfoAddEscrowRecord(txn.account.fullPeerInfo, dsid, escrowRecord, error)){ - secdebug("accout", "Could not set escrow record in the full peer info"); - result = false; - } - break; - case kSOSCCRequestPending: - //set the escrow record in the peer info/application? - break; - case kSOSCCNotInCircle: - case kSOSCCCircleAbsent: - //set the escrow record in the account expansion! - - if(!SOSAccountAddEscrowRecords(txn.account, dsid, escrowRecord, error)) { - secdebug("account", "Could not set escrow record in expansion data"); - result = false; - } - break; - default: - secdebug("account", "no circle status!"); - break; - } - CFReleaseNull(attempts); - CFReleaseNull(timeDescription); - CFReleaseNull(escrowTimeAndTries); - CFReleaseNull(escrowRecord); - - return true; - }); - - return result; -} - bool SOSCCAcceptApplicants_Server(CFArrayRef applicants, CFErrorRef* error) { OctagonSignpost signPost = OctagonSignpostBegin(SOSSignpostNameSOSCCAcceptApplicants); @@ -2043,60 +1720,6 @@ SOSPeerInfoRef SOSCCCopyMyPeerInfo_Server(CFErrorRef* error) return result; } -CFDataRef SOSCCCopyAccountState_Server(CFErrorRef* error) -{ - __block CFDataRef accountState = NULL; - - (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - // Copy account state from the keychain - accountState = SOSAccountCopyAccountStateFromKeychain(block_error); - return accountState != NULL; - }); - - return accountState; -} - -bool SOSCCDeleteAccountState_Server(CFErrorRef* error) -{ - __block bool result = NULL; - - (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - // Delete account state from the keychain - result = SOSAccountDeleteAccountStateFromKeychain(block_error); - return result; - }); - - return result; -} - -CFDataRef SOSCCCopyEngineData_Server(CFErrorRef* error) -{ - __block CFDataRef engineState = NULL; - - (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - // Copy engine state from the keychain - engineState = SOSAccountCopyEngineStateFromKeychain(block_error); - return engineState != NULL; - }); - - return engineState; -} - -bool SOSCCDeleteEngineState_Server(CFErrorRef* error) -{ - __block bool result = NULL; - - (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - // Delete engine state from the keychain - result = SOSAccountDeleteEngineStateFromKeychain(block_error); - return result; - }); - - return result; -} - - - SOSPeerInfoRef SOSCCSetNewPublicBackupKey_Server(CFDataRef newPublicBackup, CFErrorRef *error){ __block SOSPeerInfoRef result = NULL; OctagonSignpost signPost = OctagonSignpostBegin(SOSSignpostNameSOSCCSetNewPublicBackupKey); @@ -2134,25 +1757,6 @@ bool SOSCCRegisterSingleRecoverySecret_Server(CFDataRef aks_bag, bool setupV0Onl return registerResult; } -CFStringRef SOSCCCopyIncompatibilityInfo_Server(CFErrorRef* error) -{ - __block CFStringRef result = NULL; - - (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - result = SOSAccountCopyIncompatibilityInfo(txn.account, block_error); - return result != NULL; - }); - - return result; -} - -bool SOSCCkSecXPCOpIsThisDeviceLastBackup_Server(CFErrorRef *error) { - bool result = do_with_account_while_unlocked(error, ^bool (SOSAccountTransaction* txn, CFErrorRef* block_error) { - return SOSAccountIsLastBackupPeer(txn.account, block_error); - }); - return result; -} - enum DepartureReason SOSCCGetLastDepartureReason_Server(CFErrorRef* error) { __block enum DepartureReason result = kSOSDepartureReasonError; @@ -2351,14 +1955,6 @@ bool SOSCCCleanupKVSKeys_Server(CFErrorRef *error) { return result; } -bool SOSCCTestPopulateKVSWithBadKeys_Server(CFErrorRef *error) -{ - __block bool result = false; - do_with_account_while_unlocked(error, ^bool(SOSAccountTransaction* txn, CFErrorRef *error) { - return SOSAccountPopulateKVSWithBadKeys(txn.account, error); - }); - return result; -} CFDataRef SOSCCCopyCircleJoiningBlob_Server(SOSPeerInfoRef applicant, CFErrorRef *error) { __block CFDataRef pbblob = NULL; OctagonSignpost signPost = OctagonSignpostBegin(SOSSignpostNameSOSCCCopyCircleJoiningBlob); diff --git a/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupKeyClassSigningKey.m b/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupKeyClassSigningKey.m index b86f63cb..bfdd3c2c 100644 --- a/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupKeyClassSigningKey.m +++ b/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupKeyClassSigningKey.m @@ -25,7 +25,7 @@ } - (BOOL)hasKeyClass { - return _has.keyClass; + return _has.keyClass != 0; } - (BOOL)hasPublicKey { diff --git a/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupMetadataClassKey.m b/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupMetadataClassKey.m index eae21ba4..0da4f8ec 100644 --- a/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupMetadataClassKey.m +++ b/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupMetadataClassKey.m @@ -25,7 +25,7 @@ } - (BOOL)hasKeyClass { - return _has.keyClass; + return _has.keyClass != 0; } - (BOOL)hasBackupWrappedMetadataKey { diff --git a/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupRecoverySet.m b/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupRecoverySet.m index 1621b0d2..9dd58a5e 100644 --- a/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupRecoverySet.m +++ b/keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupRecoverySet.m @@ -27,7 +27,7 @@ } - (BOOL)hasRecoveryType { - return _has.recoveryType; + return _has.recoveryType != 0; } - (BOOL)hasBagIdentity { diff --git a/keychain/securityd/SecDbKeychainItem.m b/keychain/securityd/SecDbKeychainItem.m index 7e52fc36..4ad9c892 100644 --- a/keychain/securityd/SecDbKeychainItem.m +++ b/keychain/securityd/SecDbKeychainItem.m @@ -71,7 +71,7 @@ static CFDataRef kc_copy_protection_data(SecAccessControlRef access_control); static CFTypeRef kc_copy_protection_from(const uint8_t *der, const uint8_t *der_end); static CF_RETURNS_RETAINED CFMutableDictionaryRef s3dl_item_v2_decode(CFDataRef plain, CFErrorRef *error); static CF_RETURNS_RETAINED CFMutableDictionaryRef s3dl_item_v3_decode(CFDataRef plain, CFErrorRef *error); -#if USE_KEYSTORE +#if USE_KEYSTORE && !TARGET_OS_SIMULATOR static bool kc_attribs_key_encrypted_data_from_blob(keybag_handle_t keybag, const SecDbClass *class, const void *blob_data, size_t blob_data_len, SecAccessControlRef access_control, uint32_t version, CFMutableDictionaryRef *authenticated_attributes, aks_ref_key_t *ref_key, CFDataRef *encrypted_data, CFErrorRef *error); static CFDataRef kc_create_auth_data(SecAccessControlRef access_control, CFDictionaryRef auth_attributes); @@ -155,7 +155,7 @@ bool ks_encrypt_data_legacy(keybag_handle_t keybag, SecAccessControlRef access_c CFRelease(attributes_dict); } } else { -#if USE_KEYSTORE +#if USE_KEYSTORE && !TARGET_OS_SIMULATOR if (attributes) { plainText = CFPropertyListCreateDERData(kCFAllocatorDefault, attributes, error); } @@ -195,7 +195,7 @@ bool ks_encrypt_data_legacy(keybag_handle_t keybag, SecAccessControlRef access_c if (!keyclass) goto out; -#if USE_KEYSTORE +#if USE_KEYSTORE && !TARGET_OS_SIMULATOR if (version >= 4) { auth_data = kc_create_auth_data(access_control, authenticated_attributes); require_quiet(ok = ks_encrypt_acl(keybag, keyclass, bulkKeySize, bulkKey, bulkKeyWrapped, auth_data, acm_context, access_control, error), out); @@ -603,7 +603,7 @@ bool ks_decrypt_data(keybag_handle_t keybag, CFTypeRef cryptoOp, SecAccessContro } } -#if USE_KEYSTORE +#if USE_KEYSTORE && !TARGET_OS_SIMULATOR if (hasProtectionData) { if (caller_access_groups) { caller_access_groups_data = kc_copy_access_groups_data(caller_access_groups, error); @@ -777,7 +777,8 @@ static CFTypeRef kc_encode_keyclass(keyclass_t keyclass) { } } -#if USE_KEYSTORE +// Simulator fakes security, this code is unused on it +#if USE_KEYSTORE && !TARGET_OS_SIMULATOR static bool kc_attribs_key_encrypted_data_from_blob(keybag_handle_t keybag, const SecDbClass *class, const void *blob_data, size_t blob_data_len, SecAccessControlRef access_control, uint32_t version, CFMutableDictionaryRef *authenticated_attributes, aks_ref_key_t *ref_key, CFDataRef *encrypted_data, CFErrorRef *error) { diff --git a/keychain/securityd/SecItemDataSource.c b/keychain/securityd/SecItemDataSource.c index 1845b5bb..d5a3a283 100644 --- a/keychain/securityd/SecItemDataSource.c +++ b/keychain/securityd/SecItemDataSource.c @@ -894,10 +894,23 @@ static SOSDataSourceFactoryRef SecItemDataSourceFactoryCreate(SecDbRef db) { return &dsf->factory; } + +static dispatch_once_t sDSFQueueOnce; +static dispatch_queue_t sDSFQueue; +static CFMutableDictionaryRef sDSTable = NULL; + +void SecItemDataSourceFactoryReleaseAll() { + // Ensure that the queue is set up + (void) SecItemDataSourceFactoryGetShared(nil); + + dispatch_sync(sDSFQueue, ^{ + if(sDSTable) { + CFDictionaryRemoveAllValues(sDSTable); + } + }); +} + SOSDataSourceFactoryRef SecItemDataSourceFactoryGetShared(SecDbRef db) { - static dispatch_once_t sDSFQueueOnce; - static dispatch_queue_t sDSFQueue; - static CFMutableDictionaryRef sDSTable = NULL; dispatch_once(&sDSFQueueOnce, ^{ sDSFQueue = dispatch_queue_create("dataSourceFactory queue", DISPATCH_QUEUE_SERIAL); diff --git a/keychain/securityd/SecItemDataSource.h b/keychain/securityd/SecItemDataSource.h index fab2678e..3ba61dd7 100644 --- a/keychain/securityd/SecItemDataSource.h +++ b/keychain/securityd/SecItemDataSource.h @@ -44,6 +44,8 @@ SOSManifestRef SOSCreateManifestWithBackup(CFDictionaryRef backup, CFErrorRef *e // Hack to log objects from inside SOS code void SecItemServerAppendItemDescription(CFMutableStringRef desc, CFDictionaryRef object); +// Are you a test? Call this to drop all data sources. +void SecItemDataSourceFactoryReleaseAll(void); __END_DECLS diff --git a/keychain/securityd/SecItemServer.c b/keychain/securityd/SecItemServer.c index 039b8252..cd9c7b97 100644 --- a/keychain/securityd/SecItemServer.c +++ b/keychain/securityd/SecItemServer.c @@ -1157,19 +1157,16 @@ SecDbRef SecKeychainDbCreate(CFStringRef path, CFErrorRef* error) { SecDbRef SecKeychainDbInitialize(SecDbRef db) { #if OCTAGON - if(SecCKKSIsEnabled()) { - // This needs to be async, otherwise we get hangs between securityd, cloudd, and apsd - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - SecCKKSInitialize(db); - - }); - } - - if(OctagonIsEnabled() && OctagonShouldPerformInitialization()) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // This needs to be async, otherwise we get hangs between securityd, cloudd, and apsd + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + if(OctagonIsEnabled() && OctagonShouldPerformInitialization()) { OctagonInitialize(); - }); - } + } + + if(SecCKKSIsEnabled()) { + SecCKKSInitialize(db); + } + }); if(EscrowRequestServerIsEnabled()) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -1221,6 +1218,16 @@ static SecDbRef kc_dbhandle(CFErrorRef* error) return _kc_dbhandle; } +/* whitebox testing only, and I really hope you call DbReset soon */ +void SecKeychainDbForceClose(void) +{ + dispatch_sync(get_kc_dbhandle_dispatch(), ^{ + if(_kc_dbhandle) { + SecDbForceClose(_kc_dbhandle); + } + }); +} + /* For whitebox testing only */ void SecKeychainDbReset(dispatch_block_t inbetween) { diff --git a/keychain/securityd/SecItemServer.h b/keychain/securityd/SecItemServer.h index 91fcda27..0c163778 100644 --- a/keychain/securityd/SecItemServer.h +++ b/keychain/securityd/SecItemServer.h @@ -87,7 +87,9 @@ bool kc_with_dbt_non_item_tables(bool writeAndRead, CFErrorRef* error, bool (^pe bool kc_with_custom_db(bool writeAndRead, bool usesItemTables, SecDbRef db, CFErrorRef *error, bool (^perform)(SecDbConnectionRef dbt)); + /* For whitebox testing only */ +void SecKeychainDbForceClose(void); void SecKeychainDbReset(dispatch_block_t inbetween); diff --git a/keychain/securityd/spi.c b/keychain/securityd/spi.c index 80b10d3f..367f2274 100644 --- a/keychain/securityd/spi.c +++ b/keychain/securityd/spi.c @@ -87,12 +87,6 @@ static struct securityd securityd_spi = { .soscc_RequestToJoinCircle = SOSCCRequestToJoinCircle_Server, .soscc_RequestToJoinCircleAfterRestore = SOSCCRequestToJoinCircleAfterRestore_Server, .soscc_RequestToJoinCircleAfterRestoreWithAnalytics = SOSCCRequestToJoinCircleAfterRestoreWithAnalytics_Server, - .soscc_RequestEnsureFreshParameters = SOSCCRequestEnsureFreshParameters_Server, - .soscc_GetAllTheRings = SOSCCGetAllTheRings_Server, - .soscc_ApplyToARing = SOSCCApplyToARing_Server, - .soscc_WithdrawlFromARing = SOSCCWithdrawlFromARing_Server, - .soscc_EnableRing = SOSCCEnableRing_Server, - .soscc_RingStatus = SOSCCRingStatus_Server, .soscc_SetToNew = SOSCCAccountSetToNew_Server, .soscc_ResetToOffering = SOSCCResetToOffering_Server, .soscc_ResetToEmpty = SOSCCResetToEmpty_Server, @@ -131,21 +125,10 @@ static struct securityd securityd_spi = { .soscc_RegisterSingleRecoverySecret = SOSCCRegisterSingleRecoverySecret_Server, .soscc_WaitForInitialSync = SOSCCWaitForInitialSync_Server, .soscc_WaitForInitialSyncWithAnalytics = SOSCCWaitForInitialSyncWithAnalytics_Server, - .soscc_CopyYetToSyncViewsList = SOSCCCopyYetToSyncViewsList_Server, - .soscc_SetEscrowRecords = SOSCCSetEscrowRecord_Server, - .soscc_CopyEscrowRecords = SOSCCCopyEscrowRecord_Server, - .sosbskb_WrapToBackupSliceKeyBagForView = SOSWrapToBackupSliceKeyBagForView_Server, - .soscc_CopyAccountState = SOSCCCopyAccountState_Server, - .soscc_DeleteAccountState = SOSCCDeleteAccountState_Server, - .soscc_CopyEngineData = SOSCCCopyEngineData_Server, - .soscc_DeleteEngineState = SOSCCDeleteEngineState_Server, .soscc_AccountHasPublicKey = SOSCCAccountHasPublicKey_Server, - .soscc_AccountIsNew = SOSCCAccountIsNew_Server, - .soscc_IsThisDeviceLastBackup = SOSCCkSecXPCOpIsThisDeviceLastBackup_Server, .soscc_SOSCCPeersHaveViewsEnabled = SOSCCPeersHaveViewsEnabled_Server, .soscc_RegisterRecoveryPublicKey = SOSCCRegisterRecoveryPublicKey_Server, .soscc_CopyRecoveryPublicKey = SOSCCCopyRecoveryPublicKey_Server, - .soscc_CopyBackupInformation = SOSCCCopyBackupInformation_Server, .soscc_SOSCCMessageFromPeerIsPending = SOSCCMessageFromPeerIsPending_Server, .soscc_SOSCCSendToPeerIsPending = SOSCCSendToPeerIsPending_Server, #endif /* SECUREOBJECTSYNC */ diff --git a/keychain/tpctl/main.swift b/keychain/tpctl/main.swift index fd98b353..5ba4a914 100644 --- a/keychain/tpctl/main.swift +++ b/keychain/tpctl/main.swift @@ -24,7 +24,6 @@ var preapprovedKeys: [Data]? var deviceName: String? var serialNumber: String? var osVersion: String? -var policyVersion: NSNumber? var policySecrets: [String: Data]? enum Command { @@ -209,11 +208,11 @@ while let arg = argIterator.next() { osVersion = newOsVersion case "--policy-version": - guard let newPolicyVersion = UInt64(argIterator.next() ?? "") else { + guard let _ = UInt64(argIterator.next() ?? "") else { print("Error: --policy-version takes an integer argument") exitUsage(1) } - policyVersion = NSNumber(value: newPolicyVersion) + // Option ignored for now case "--policy-secret": guard let name = argIterator.next(), let dataBase64 = argIterator.next() else { @@ -451,7 +450,7 @@ while let arg = argIterator.next() { var machineIDs = Set() var performIDMS = false while let arg = argIterator.next() { - if(arg == "--idms") { + if arg == "--idms" { performIDMS = true } else { machineIDs.insert(arg) @@ -465,7 +464,7 @@ while let arg = argIterator.next() { } } -if commands.count == 0 { +if commands.isEmpty { exitUsage(0) } @@ -543,7 +542,7 @@ for command in commands { voucherSig: voucherSig, ckksKeys: [], tlkShares: [], - preapprovedKeys: preapprovedKeys ?? []) { peerID, _, error in + preapprovedKeys: preapprovedKeys ?? []) { peerID, _, _, _, error in guard error == nil else { print("Error joining:", error!) return @@ -634,11 +633,10 @@ for command in commands { deviceName: deviceName ?? deviceInfo.deviceName(), serialNumber: serialNumber ?? deviceInfo.serialNumber(), osVersion: osVersion ?? deviceInfo.osVersion(), - policyVersion: policyVersion, + policyVersion: nil, policySecrets: policySecrets, signingPrivKeyPersistentRef: nil, - encPrivKeyPersistentRef: nil) { - peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in + encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, views, _, error in guard error == nil else { print("Error preparing:", error!) return @@ -651,7 +649,8 @@ for command in commands { "stableInfo": stableInfo!.base64EncodedString(), "stableInfoSig": stableInfoSig!.base64EncodedString(), "machineID": machineID!, - ] + "views": Array(views ?? Set()), + ] as [String: Any] do { print(try TPCTLObjectiveC.jsonSerialize(cleanDictionaryForJSON(result))) } catch { @@ -666,7 +665,7 @@ for command in commands { deviceName: deviceName, serialNumber: serialNumber, osVersion: osVersion, - policyVersion: policyVersion, + policyVersion: nil, policySecrets: policySecrets) { _, error in guard error == nil else { print("Error updating:", error!) @@ -754,7 +753,7 @@ for command in commands { bottleID: bottleID, entropy: entropy, bottleSalt: salt, - tlkShares: []) { voucher, voucherSig, error in + tlkShares: []) { voucher, voucherSig, _, _, error in guard error == nil else { print("Error during vouchWithBottle", error!) return @@ -771,8 +770,9 @@ for command in commands { os_log("allow-listing (%@, %@)", log: tplogDebug, type: .default, container, context) var idmsDeviceIDs: Set = Set() + var accountIsDemo: Bool = false - if(performIDMS) { + if performIDMS { let store = ACAccountStore() guard let account = store.aa_primaryAppleAccount() else { print("Unable to fetch primary Apple account!") @@ -783,6 +783,12 @@ for command in commands { requestArguments.altDSID = account.aa_altDSID requestArguments.services = [AKServiceNameiCloud] + let akManager = AKAccountManager.sharedInstance + let authKitAccount = akManager.authKitAccount(withAltDSID: account.aa_altDSID) + if let account = authKitAccount { + accountIsDemo = akManager.demoAccount(for: account) + } + guard let controller = AKAppleIDAuthenticationController() else { print("Unable to create AKAppleIDAuthenticationController!") abort() @@ -804,10 +810,9 @@ for command in commands { } semaphore.wait() } - let allMachineIDs = machineIDs.union(idmsDeviceIDs) print("Setting allowed machineIDs to \(allMachineIDs)") - tpHelper.setAllowedMachineIDsWithContainer(container, context: context, allowedMachineIDs: allMachineIDs) { listChanged, error in + tpHelper.setAllowedMachineIDsWithContainer(container, context: context, allowedMachineIDs: allMachineIDs, honorIDMSListChanges: accountIsDemo) { listChanged, error in guard error == nil else { print("Error during allow:", error!) return diff --git a/libsecurity_smime/lib/cmsdigest.c b/libsecurity_smime/lib/cmsdigest.c index e31f88f6..9968af80 100644 --- a/libsecurity_smime/lib/cmsdigest.c +++ b/libsecurity_smime/lib/cmsdigest.c @@ -44,11 +44,7 @@ #include #include -#if USE_CDSA_CRYPTO -#include -#else #include -#endif #include @@ -60,11 +56,7 @@ struct SecCmsDigestContextStr { PLArenaPool * poolp; Boolean saw_contents; int digcnt; -#if USE_CDSA_CRYPTO - CSSM_CC_HANDLE * digobjs; -#else void ** digobjs; -#endif SECAlgorithmID ** digestalgs; }; @@ -77,17 +69,14 @@ SecCmsDigestContextStartMultiple(SECAlgorithmID **digestalgs) { PLArenaPool *poolp; SecCmsDigestContextRef cmsdigcx; -#if USE_CDSA_CRYPTO - CSSM_CC_HANDLE digobj; -#else void * digobj; -#endif int digcnt; int i; poolp = PORT_NewArena(1024); - if (poolp == NULL) - goto loser; + if (poolp == NULL) { + goto loser; + } digcnt = (digestalgs == NULL) ? 0 : SecCmsArrayCount((void **)digestalgs); @@ -98,27 +87,18 @@ SecCmsDigestContextStartMultiple(SECAlgorithmID **digestalgs) cmsdigcx->poolp = poolp; if (digcnt > 0) { -#if USE_CDSA_CRYPTO - /* Security check to prevent under-allocation */ - if (digcnt >= (int)((INT_MAX/(MAX(sizeof(CSSM_CC_HANDLE),sizeof(SECAlgorithmID *))))-1)) { - goto loser; - } - cmsdigcx->digobjs = (CSSM_CC_HANDLE *)PORT_ArenaAlloc(poolp, digcnt * sizeof(CSSM_CC_HANDLE)); - if (cmsdigcx->digobjs == NULL) - goto loser; -#else - /* Security check to prevent under-allocation */ - if (digcnt >= (int)((INT_MAX/(MAX(sizeof(void *),sizeof(SECAlgorithmID *))))-1)) { - goto loser; - } - cmsdigcx->digobjs = (void**)PORT_ArenaAlloc(poolp, digcnt * sizeof(void *)); - if (cmsdigcx->digobjs == NULL) - goto loser; -#endif - cmsdigcx->digestalgs = (SECAlgorithmID **)PORT_ArenaZAlloc(poolp, - (digcnt + 1) * sizeof(SECAlgorithmID *)); - if (cmsdigcx->digestalgs == NULL) - goto loser; + /* Security check to prevent under-allocation */ + if (digcnt >= (int)((INT_MAX/(MAX(sizeof(void *),sizeof(SECAlgorithmID *))))-1)) { + goto loser; + } + cmsdigcx->digobjs = (void**)PORT_ArenaAlloc(poolp, digcnt * sizeof(void *)); + if (cmsdigcx->digobjs == NULL) { + goto loser; + } + cmsdigcx->digestalgs = (SECAlgorithmID **)PORT_ArenaZAlloc(poolp, (digcnt + 1) * sizeof(SECAlgorithmID *)); + if (cmsdigcx->digestalgs == NULL) { + goto loser; + } } cmsdigcx->digcnt = 0; @@ -127,31 +107,27 @@ SecCmsDigestContextStartMultiple(SECAlgorithmID **digestalgs) * Create a digest object context for each algorithm. */ for (i = 0; i < digcnt; i++) { - digobj = SecCmsUtilGetHashObjByAlgID(digestalgs[i]); - /* - * Skip any algorithm we do not even recognize; obviously, - * this could be a problem, but if it is critical then the - * result will just be that the signature does not verify. - * We do not necessarily want to error out here, because - * the particular algorithm may not actually be important, - * but we cannot know that until later. - */ -#if USE_CDSA_CRYPTO - if (digobj) - if (CSSM_DigestDataInit(digobj)) - goto loser; -#endif - - cmsdigcx->digobjs[cmsdigcx->digcnt] = digobj; - cmsdigcx->digestalgs[cmsdigcx->digcnt] = PORT_ArenaAlloc(poolp, sizeof(SECAlgorithmID)); - if (SECITEM_CopyItem(poolp, - &(cmsdigcx->digestalgs[cmsdigcx->digcnt]->algorithm), - &(digestalgs[i]->algorithm)) - || SECITEM_CopyItem(poolp, - &(cmsdigcx->digestalgs[cmsdigcx->digcnt]->parameters), - &(digestalgs[i]->parameters))) - goto loser; - cmsdigcx->digcnt++; + digobj = SecCmsUtilGetHashObjByAlgID(digestalgs[i]); + /* + * Skip any algorithm we do not even recognize; obviously, + * this could be a problem, but if it is critical then the + * result will just be that the signature does not verify. + * We do not necessarily want to error out here, because + * the particular algorithm may not actually be important, + * but we cannot know that until later. + */ + + cmsdigcx->digobjs[cmsdigcx->digcnt] = digobj; + cmsdigcx->digestalgs[cmsdigcx->digcnt] = PORT_ArenaAlloc(poolp, sizeof(SECAlgorithmID)); + if (SECITEM_CopyItem(poolp, + &(cmsdigcx->digestalgs[cmsdigcx->digcnt]->algorithm), + &(digestalgs[i]->algorithm)) + || SECITEM_CopyItem(poolp, + &(cmsdigcx->digestalgs[cmsdigcx->digcnt]->parameters), + &(digestalgs[i]->parameters))) { + goto loser; + } + cmsdigcx->digcnt++; } cmsdigcx->saw_contents = PR_FALSE; @@ -159,8 +135,9 @@ SecCmsDigestContextStartMultiple(SECAlgorithmID **digestalgs) return cmsdigcx; loser: - if (poolp) - PORT_FreeArena(poolp, PR_FALSE); + if (poolp) { + PORT_FreeArena(poolp, PR_FALSE); + } return NULL; } @@ -191,35 +168,31 @@ SecCmsDigestContextUpdate(SecCmsDigestContextRef cmsdigcx, const unsigned char * dataBuf.Data = (uint8_t *)data; cmsdigcx->saw_contents = PR_TRUE; for (i = 0; i < cmsdigcx->digcnt; i++) { - if (cmsdigcx->digobjs[i]) { -#if USE_CDSA_CRYPTO - CSSM_DigestDataUpdate(cmsdigcx->digobjs[i], &dataBuf, 1); -#else + if (cmsdigcx->digobjs[i]) { /* 64 bits cast: worst case is we truncate the length and we dont hash all the data. - This may cause an invalid CMS blob larger than 4GB to be validated. Unlikely, but - possible security issue. There is no way to return an error here, but a check at - the upper level may happen. */ - /* - rdar://problem/20642513 - Let's just die a horrible death rather than have the security issue. - CMS blob over 4GB? Oh well. - */ - if (len > UINT32_MAX) { - /* Ugh. */ - abort(); - } + This may cause an invalid CMS blob larger than 4GB to be validated. Unlikely, but + possible security issue. There is no way to return an error here, but a check at + the upper level may happen. */ + /* + rdar://problem/20642513 + Let's just die a horrible death rather than have the security issue. + CMS blob over 4GB? Oh well. + */ + if (len > UINT32_MAX) { + /* Ugh. */ + abort(); + } assert(len<=UINT32_MAX); /* Debug check. Correct as long as CC_LONG is uint32_t */ switch (SECOID_GetAlgorithmTag(cmsdigcx->digestalgs[i])) { - case SEC_OID_SHA1: CC_SHA1_Update((CC_SHA1_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; - case SEC_OID_MD5: CC_MD5_Update((CC_MD5_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; - case SEC_OID_SHA224: CC_SHA224_Update((CC_SHA256_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; - case SEC_OID_SHA256: CC_SHA256_Update((CC_SHA256_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; - case SEC_OID_SHA384: CC_SHA384_Update((CC_SHA512_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; - case SEC_OID_SHA512: CC_SHA512_Update((CC_SHA512_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; - default: - break; + case SEC_OID_SHA1: CC_SHA1_Update((CC_SHA1_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_MD5: CC_MD5_Update((CC_MD5_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_SHA224: CC_SHA224_Update((CC_SHA256_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_SHA256: CC_SHA256_Update((CC_SHA256_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_SHA384: CC_SHA384_Update((CC_SHA512_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + case SEC_OID_SHA512: CC_SHA512_Update((CC_SHA512_CTX *)cmsdigcx->digobjs[i], data, (CC_LONG)len); break; + default: + break; } -#endif } } } @@ -232,13 +205,12 @@ SecCmsDigestContextCancel(SecCmsDigestContextRef cmsdigcx) { int i; - for (i = 0; i < cmsdigcx->digcnt; i++) - if (cmsdigcx->digobjs[i]) -#if USE_CDSA_CRYPTO - CSSM_DeleteContext(cmsdigcx->digobjs[i]); -#else + for (i = 0; i < cmsdigcx->digcnt; i++) { + if (cmsdigcx->digobjs && cmsdigcx->digobjs[i]) { free(cmsdigcx->digobjs[i]); -#endif + cmsdigcx->digobjs[i] = NULL; + } + } PORT_FreeArena(cmsdigcx->poolp, PR_TRUE); } @@ -254,17 +226,16 @@ SecCmsDigestContextDestroy(SecCmsDigestContextRef cmsdigcx) /* * SecCmsDigestContextFinishMultiple - finish the digests + * Note that on iOS, this call only frees the digest objects and requires a call to SecCmsDisgestContextDestroy + * or SecCmsDisgestContextCancel (because the digests are allocated out of the context's pool). + * The macOS version cancels and frees the digest context (because the digests are allocated from an input arena pool). */ OSStatus SecCmsDigestContextFinishMultiple(SecCmsDigestContextRef cmsdigcx, - SECAlgorithmID ***digestalgsp, - SecAsn1Item * **digestsp) + SECAlgorithmID ***digestalgsp, + SecAsn1Item * **digestsp) { -#if USE_CDSA_CRYPTO - CSSM_CC_HANDLE digboj; -#else void * digobj; -#endif SecAsn1Item **digests, *digest; SECAlgorithmID **digestalgs; int i; @@ -277,17 +248,16 @@ SecCmsDigestContextFinishMultiple(SecCmsDigestContextRef cmsdigcx, #if 0 /* no contents? do not update digests */ if (digestsp == NULL || !cmsdigcx->saw_contents) { - for (i = 0; i < cmsdigcx->digcnt; i++) - if (cmsdigcx->digobjs[i]) -#if USE_CDSA_CRYPTO - CSSM_DeleteContext(cmsdigcx->digobjs[i]); -#else + for (i = 0; i < cmsdigcx->digcnt; i++) { + if (cmsdigcx->digobjs[i]) { free(cmsdigcx->digobjs[i]); -#endif - rv = SECSuccess; - if (digestsp) - *digestsp = NULL; - goto cleanup; + } + } + rv = SECSuccess; + if (digestsp) { + *digestsp = NULL; + } + goto cleanup; } #endif @@ -305,14 +275,13 @@ SecCmsDigestContextFinishMultiple(SecCmsDigestContextRef cmsdigcx, digests = (SecAsn1Item * *)PORT_ArenaZAlloc(cmsdigcx->poolp, (cmsdigcx->digcnt+1) * sizeof(SecAsn1Item *)); digest = (SecAsn1Item *)PORT_ArenaZAlloc(cmsdigcx->poolp, cmsdigcx->digcnt * sizeof(SecAsn1Item)); if (digestalgs == NULL || digests == NULL || digest == NULL) { - goto loser; + goto loser; } for (i = 0; i < cmsdigcx->digcnt; i++, digest++) { - SECOidTag hash_alg = SECOID_GetAlgorithmTag(cmsdigcx->digestalgs[i]); - int diglength = 0; - + int diglength = 0; + switch (hash_alg) { case SEC_OID_SHA1: diglength = CC_SHA1_DIGEST_LENGTH; break; case SEC_OID_MD5: diglength = CC_MD5_DIGEST_LENGTH; break; @@ -323,17 +292,12 @@ SecCmsDigestContextFinishMultiple(SecCmsDigestContextRef cmsdigcx, default: goto loser; } - digobj = cmsdigcx->digobjs[i]; - if (digobj) - { - digest->Data = (unsigned char*)PORT_ArenaAlloc(cmsdigcx->poolp, diglength); - if (digest->Data == NULL) - goto loser; - digest->Length = diglength; -#if USE_CDSA_CRYPTO - CSSM_DigestDataFinal(digobj, digest); - CSSM_DeleteContext(digobj); -#else + digobj = cmsdigcx->digobjs[i]; + if (digobj) { + digest->Data = (unsigned char*)PORT_ArenaAlloc(cmsdigcx->poolp, diglength); + if (digest->Data == NULL) + goto loser; + digest->Length = diglength; switch (hash_alg) { case SEC_OID_SHA1: CC_SHA1_Final(digest->Data, digobj); break; case SEC_OID_MD5: CC_MD5_Final(digest->Data, digobj); break; @@ -345,15 +309,12 @@ SecCmsDigestContextFinishMultiple(SecCmsDigestContextRef cmsdigcx, } free(digobj); -#endif - digestalgs[i] = cmsdigcx->digestalgs[i]; - digests[i] = digest; - } - else - { - digest->Data = NULL; - digest->Length = 0; - } + digestalgs[i] = cmsdigcx->digestalgs[i]; + digests[i] = digest; + } else { + digest->Data = NULL; + digest->Length = 0; + } } digestalgs[i] = NULL; digests[i] = NULL; @@ -363,13 +324,14 @@ SecCmsDigestContextFinishMultiple(SecCmsDigestContextRef cmsdigcx, rv = SECSuccess; loser: - if (rv == SECSuccess) - PORT_ArenaUnmark(cmsdigcx->poolp, mark); - else - PORT_ArenaRelease(cmsdigcx->poolp, mark); + if (rv == SECSuccess) { + PORT_ArenaUnmark(cmsdigcx->poolp, mark); + } else { + PORT_ArenaRelease(cmsdigcx->poolp, mark); + } -/*cleanup:*/ - /* Set things up so SecCmsDigestContextDestroy won't call CSSM_DeleteContext again. */ + /*cleanup:*/ + /* Set things up so SecCmsDigestContextDestroy won't call CSSM_DeleteContext again. */ cmsdigcx->digcnt = 0; return rv; @@ -381,15 +343,16 @@ loser: */ OSStatus SecCmsDigestContextFinishSingle(SecCmsDigestContextRef cmsdigcx, - SecAsn1Item * digest) + SecAsn1Item * digest) { OSStatus rv = SECFailure; SecAsn1Item * *dp; SECAlgorithmID **ap; /* get the digests into arena, then copy the first digest into poolp */ - if (SecCmsDigestContextFinishMultiple(cmsdigcx, &ap, &dp) != SECSuccess) - goto loser; + if (SecCmsDigestContextFinishMultiple(cmsdigcx, &ap, &dp) != SECSuccess) { + goto loser; + } /* Return the first element in the digest array. */ if (digest) { diff --git a/libsecurity_smime/lib/cmspriv.h b/libsecurity_smime/lib/cmspriv.h index 1c309f03..9d2597a5 100644 --- a/libsecurity_smime/lib/cmspriv.h +++ b/libsecurity_smime/lib/cmspriv.h @@ -95,11 +95,8 @@ SecCmsAlgArrayGetIndexByAlgID(SECAlgorithmID **algorithmArray, SECAlgorithmID *a extern int SecCmsAlgArrayGetIndexByAlgTag(SECAlgorithmID **algorithmArray, SECOidTag algtag); -#if USE_CDSA_CRYPTO -extern CSSM_CC_HANDLE -#else + extern void * -#endif SecCmsUtilGetHashObjByAlgID(SECAlgorithmID *algid); /* diff --git a/libsecurity_smime/lib/cmsutil.c b/libsecurity_smime/lib/cmsutil.c index 9ed5d173..e462cab5 100644 --- a/libsecurity_smime/lib/cmsutil.c +++ b/libsecurity_smime/lib/cmsutil.c @@ -227,58 +227,42 @@ SecCmsAlgArrayGetIndexByAlgTag(SECAlgorithmID **algorithmArray, return i; } -#if USE_CDSA_CRYPTO -CSSM_CC_HANDLE -#else void * -#endif SecCmsUtilGetHashObjByAlgID(SECAlgorithmID *algid) { SECOidData *oidData = SECOID_FindOID(&(algid->algorithm)); if (oidData) { -#if USE_CDSA_CRYPTO - CSSM_ALGORITHMS alg = oidData->cssmAlgorithm; - if (alg) - { - CSSM_CC_HANDLE digobj; - CSSM_CSP_HANDLE cspHandle = SecCspHandleForAlgorithm(alg); - - if (!CSSM_CSP_CreateDigestContext(cspHandle, alg, &digobj)) - return digobj; - } -#else void *digobj = NULL; switch (oidData->offset) { - case SEC_OID_SHA1: - digobj = calloc(1, sizeof(CC_SHA1_CTX)); - CC_SHA1_Init(digobj); - break; - case SEC_OID_MD5: - digobj = calloc(1, sizeof(CC_MD5_CTX)); - CC_MD5_Init(digobj); - break; - case SEC_OID_SHA224: - digobj = calloc(1, sizeof(CC_SHA256_CTX)); - CC_SHA224_Init(digobj); - break; - case SEC_OID_SHA256: - digobj = calloc(1, sizeof(CC_SHA256_CTX)); - CC_SHA256_Init(digobj); - break; - case SEC_OID_SHA384: - digobj = calloc(1, sizeof(CC_SHA512_CTX)); - CC_SHA384_Init(digobj); - break; - case SEC_OID_SHA512: - digobj = calloc(1, sizeof(CC_SHA512_CTX)); - CC_SHA512_Init(digobj); - break; - default: - break; + case SEC_OID_SHA1: + digobj = calloc(1, sizeof(CC_SHA1_CTX)); + CC_SHA1_Init(digobj); + break; + case SEC_OID_MD5: + digobj = calloc(1, sizeof(CC_MD5_CTX)); + CC_MD5_Init(digobj); + break; + case SEC_OID_SHA224: + digobj = calloc(1, sizeof(CC_SHA256_CTX)); + CC_SHA224_Init(digobj); + break; + case SEC_OID_SHA256: + digobj = calloc(1, sizeof(CC_SHA256_CTX)); + CC_SHA256_Init(digobj); + break; + case SEC_OID_SHA384: + digobj = calloc(1, sizeof(CC_SHA512_CTX)); + CC_SHA384_Init(digobj); + break; + case SEC_OID_SHA512: + digobj = calloc(1, sizeof(CC_SHA512_CTX)); + CC_SHA512_Init(digobj); + break; + default: + break; } return digobj; -#endif } return 0; diff --git a/protocol/SecProtocolPriv.h b/protocol/SecProtocolPriv.h index b209bc6f..25395fec 100644 --- a/protocol/SecProtocolPriv.h +++ b/protocol/SecProtocolPriv.h @@ -690,7 +690,7 @@ sec_protocol_metadata_get_tls_negotiated_group(sec_protocol_metadata_t metadata) * * Note: this SPI is meant to be called by libnetcore. It should not be called in any other circumstances. * - * @param options + * @param metadata * A `sec_protocol_metadata_t` instance. * * @return The identifier for a secure connection experiment, or NULL if none was specified. diff --git a/rio.yml b/rio.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/securityd/etc/com.apple.securityd.sb b/securityd/etc/com.apple.securityd.sb index c59ca4d2..26672e55 100644 --- a/securityd/etc/com.apple.securityd.sb +++ b/securityd/etc/com.apple.securityd.sb @@ -49,7 +49,8 @@ (global-name "com.apple.ocspd") (global-name "com.apple.PowerManagement.control") (global-name "com.apple.security.syspolicy") - (global-name "com.apple.security.agent")) + (global-name "com.apple.security.agent") + (global-name "com.apple.security.agent.login")) (allow ipc-posix-shm (ipc-posix-name "com.apple.AppleDatabaseChanged") diff --git a/securityd/securityd_service/securityd_service.xcodeproj/project.pbxproj b/securityd/securityd_service/securityd_service.xcodeproj/project.pbxproj index ad6cc5e7..43ebb292 100644 --- a/securityd/securityd_service/securityd_service.xcodeproj/project.pbxproj +++ b/securityd/securityd_service/securityd_service.xcodeproj/project.pbxproj @@ -347,7 +347,7 @@ 189D462D166AC95C001D8533 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1000; + LastUpgradeCheck = 1120; ORGANIZATIONNAME = Apple; }; buildConfigurationList = 189D4630166AC95C001D8533 /* Build configuration list for PBXProject "securityd_service" */; @@ -355,6 +355,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 189D462C166AC95C001D8533; @@ -475,6 +476,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPRESSION = lossless; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -532,6 +534,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPRESSION = "respect-asset-catalog"; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; diff --git a/securityd/src/kckey.cpp b/securityd/src/kckey.cpp index fc35bbc1..943df371 100644 --- a/securityd/src/kckey.cpp +++ b/securityd/src/kckey.cpp @@ -40,7 +40,9 @@ KeychainKey::KeychainKey(Database &db, const KeyBlob *blob) : LocalKey(db, n2h(blob->header.attributes())) { // perform basic validation on the incoming blob - assert(blob); + if (blob == NULL) { + CssmError::throwMe(CSSMERR_APPLEDL_INVALID_KEY_BLOB); + } blob->validate(CSSMERR_APPLEDL_INVALID_KEY_BLOB); if (blob->startCryptoBlob > blob->totalLength) { CssmError::throwMe(CSSMERR_APPLEDL_INVALID_KEY_BLOB); diff --git a/securityd/src/securityd.entitlements b/securityd/src/securityd.entitlements index ec8b2bc0..f569603b 100644 --- a/securityd/src/securityd.entitlements +++ b/securityd/src/securityd.entitlements @@ -8,5 +8,7 @@ kTCCServiceSystemPolicyAllFiles + com.apple.private.security.storage.SystemKeychain + diff --git a/supd/Tests/SFAnalyticsTests.m b/supd/Tests/SFAnalyticsTests.m index fa4ce753..c73a8267 100644 --- a/supd/Tests/SFAnalyticsTests.m +++ b/supd/Tests/SFAnalyticsTests.m @@ -23,6 +23,7 @@ #import #import +#import "SFAnalytics+Internal.h" #import "SFAnalyticsDefines.h" #import "SFAnalyticsSQLiteStore.h" #import "NSDate+SFAnalytics.h" @@ -68,6 +69,7 @@ static NSString* _path; static NSInteger _testnum; static NSString* build = NULL; static NSString* product = NULL; +static NSString* modelID = nil; // MARK: Test helper methods @@ -153,6 +155,7 @@ static NSString* product = NULL; XCTAssertTrue([rowdata[SFAnalyticsEventClassKey] isKindOfClass:[NSNumber class]] && [rowdata[SFAnalyticsEventClassKey] intValue] == class, @"eventClass is %ld", (long)class); XCTAssertTrue([rowdata[@"build"] isEqualToString:build], @"event row includes build"); XCTAssertTrue([rowdata[@"product"] isEqualToString:product], @"event row includes product"); + XCTAssertTrue([rowdata[@"modelid"] isEqualToString:modelID], @"event row includes modelid"); XCTAssertTrue(rowdata[@"internal"], @"event row includes internal"); } @@ -222,6 +225,8 @@ static NSString* product = NULL; NSLog(@"could not get build version/product, tests should fail"); } + modelID = [SFAnalytics hwModelID]; + [TestResourceUsage monitorTestResourceUsage]; } diff --git a/supd/Tests/SupdTests.m b/supd/Tests/SupdTests.m index 0b869500..20c99ffb 100644 --- a/supd/Tests/SupdTests.m +++ b/supd/Tests/SupdTests.m @@ -294,7 +294,7 @@ static NSInteger _reporterWrites; { dispatch_semaphore_t sema = dispatch_semaphore_create(0); __block NSDictionary* data; - [_supd getLoggingJSON:YES topic:topic reply:^(NSData *json, NSError *error) { + [_supd createLoggingJSON:YES topic:topic reply:^(NSData *json, NSError *error) { XCTAssertNil(error); XCTAssertNotNil(json); if (!error) { @@ -448,7 +448,8 @@ static NSInteger _reporterWrites; [_sosAnalytics logHardFailureForEventNamed:@"unittestevent" withAttributes:utAttrs]; [_sosAnalytics logSoftFailureForEventNamed:@"unittestevent" withAttributes:utAttrs]; - NSDictionary* data = [self getJSONDataFromSupd]; + NSDictionary *data = [self getJSONDataFromSupd]; + [self inspectDataBlobStructure:data]; // TODO: inspect health summaries @@ -763,6 +764,106 @@ static NSInteger _reporterWrites; XCTAssertEqual(foundErrorEvents, 1); } +- (void)testUploadSizeLimits +{ + SFAnalyticsTopic *trustTopic = [self TrustTopic]; + XCTAssertEqual(1000000, trustTopic.uploadSizeLimit); + + SFAnalyticsTopic *keySyncTopic = [self keySyncTopic]; + XCTAssertEqual(1000000, keySyncTopic.uploadSizeLimit); +} + +- (NSArray *)createRandomEventList:(size_t)count +{ + NSMutableArray *eventSet = [[NSMutableArray alloc] init]; + + const size_t dataSize = 100; + uint8_t backingBuffer[dataSize] = {}; + for (size_t i = 0; i < count; i++) { + NSData *data = [[NSData alloc] initWithBytes:backingBuffer length:dataSize]; + NSDictionary *entry = @{@"key" : [data base64EncodedStringWithOptions:0]}; + [eventSet addObject:entry]; + } + + return eventSet; +} + +- (void)testCreateLoggingJSON +{ + NSArray *summaries = [self createRandomEventList:5]; + NSArray *failures = [self createRandomEventList:100]; + NSMutableArray *visitedEvents = [[NSMutableArray alloc] init]; + + SFAnalyticsTopic *topic = [self TrustTopic]; + const size_t sizeLimit = 10000; // total size of the encoded data + topic.uploadSizeLimit = sizeLimit; + + NSError *error = nil; + NSArray *eventSet = [topic createChunkedLoggingJSON:summaries failures:failures error:&error]; + XCTAssertNil(error); + + for (NSDictionary *event in eventSet) { + XCTAssertNotNil([event objectForKey:@"events"]); + XCTAssertNotNil([event objectForKey:SFAnalyticsPostTime]); + NSArray *events = [event objectForKey:@"events"]; + for (NSDictionary *summary in summaries) { + BOOL foundSummary = NO; + for (NSDictionary *innerEvent in events) { + if ([summary isEqualToDictionary:innerEvent]) { + foundSummary = YES; + break; + } + } + XCTAssertTrue(foundSummary); + } + + // Record the events we've seen so far + for (NSDictionary *innerEvent in events) { + [visitedEvents addObject:innerEvent]; + } + } + + // Check that each summary and failure is in the visitedEvents + for (NSDictionary *summary in summaries) { + BOOL foundSummary = NO; + for (NSDictionary *innerEvent in visitedEvents) { + if ([summary isEqualToDictionary:innerEvent]) { + foundSummary = YES; + break; + } + } + XCTAssertTrue(foundSummary); + } + for (NSDictionary *failure in failures) { + BOOL foundFailure = NO; + for (NSDictionary *innerEvent in visitedEvents) { + if ([failure isEqualToDictionary:innerEvent]) { + foundFailure = YES; + break; + } + } + XCTAssertTrue(foundFailure); + } +} + +- (void)testEventSetChunking +{ + NSArray *eventSet = [self createRandomEventList:100]; + SFAnalyticsTopic *topic = [self TrustTopic]; + + const size_t sizeLimit = 10000; // total size of the encoded data + size_t encodedEventSize = [topic serializedEventSize:eventSet[0] error:nil]; + topic.uploadSizeLimit = sizeLimit; // fix the upload limit + + // Chunk up the set, assuming that each chunk already has one event in it. + // In practice, this is the health summary. + NSError *error = nil; + NSArray *chunkedEvents = [topic chunkFailureSet:(sizeLimit - encodedEventSize) events:eventSet error:nil]; + XCTAssertNil(error); + + // There should be two resulting chunks, since the set of chunks overflows. + XCTAssertEqual(2, [chunkedEvents count]); +} // TODO - (void)testGetSysdiagnoseDump diff --git a/supd/supd.h b/supd/supd.h index f9fdce96..9e4bf941 100644 --- a/supd/supd.h +++ b/supd/supd.h @@ -35,6 +35,7 @@ @property NSString* splunkTopicName; @property NSURL* splunkBagURL; @property NSString *internalTopicName; +@property NSUInteger uploadSizeLimit; @property NSArray* topicClients; @@ -42,6 +43,9 @@ // Things below are for unit testing - (instancetype)initWithDictionary:(NSDictionary *)dictionary name:(NSString *)topicName samplingRates:(NSDictionary *)rates; - (BOOL)haveEligibleClients; +- (NSArray *)createChunkedLoggingJSON:(NSArray *)healthSummaries failures:(NSArray *)failures error:(NSError **)error; +- (NSArray *)chunkFailureSet:(size_t)sizeCapacity events:(NSArray *)events error:(NSError **)error; +- (size_t)serializedEventSize:(NSObject *)event error:(NSError**)error; + (NSString*)databasePathForCKKS; + (NSString*)databasePathForSOS; + (NSString*)databasePathForPCS; diff --git a/supd/supd.m b/supd/supd.m index 194e1591..9fe33d87 100644 --- a/supd/supd.m +++ b/supd/supd.m @@ -61,7 +61,6 @@ NSString* const SFAnalyticsSplunkTopic = @"topic"; -NSString* const SFAnalyticsSplunkPostTime = @"postTime"; NSString* const SFAnalyticsClientId = @"clientId"; NSString* const SFAnalyticsInternal = @"internal"; @@ -69,6 +68,8 @@ NSString* const SFAnalyticsMetricsBase = @"metricsBase"; NSString* const SFAnalyticsDeviceID = @"ckdeviceID"; NSString* const SFAnalyticsAltDSID = @"altDSID"; +NSString* const SFAnalyticsEventCorrelationID = @"eventLinkID"; + NSString* const SFAnalyticsSecondsCustomerKey = @"SecondsBetweenUploadsCustomer"; NSString* const SFAnalyticsSecondsInternalKey = @"SecondsBetweenUploadsInternal"; NSString* const SFAnalyticsSecondsSeedKey = @"SecondsBetweenUploadsSeed"; @@ -369,6 +370,8 @@ _isiCloudAnalyticsEnabled() __splunkUploadURL = [NSURL URLWithString:dictionary[@"splunk_uploadURL"]]; _splunkBagURL = [NSURL URLWithString:dictionary[@"splunk_bagURL"]]; _allowInsecureSplunkCert = [[dictionary valueForKey:@"splunk_allowInsecureCertificate"] boolValue]; + _uploadSizeLimit = [[dictionary valueForKey:@"uploadSizeLimit"] unsignedIntegerValue]; + NSString* splunkEndpoint = dictionary[@"splunk_endpointDomain"]; if (dictionary[@"disableClientId"]) { _disableClientId = YES; @@ -390,6 +393,11 @@ _isiCloudAnalyticsEnabled() _splunkBagURL = userDefaultsSplunkBagURL; } + NSInteger userDefaultsUploadSizeLimit = [defaults integerForKey:@"uploadSizeLimit"]; + if (userDefaultsUploadSizeLimit > 0) { + _uploadSizeLimit = userDefaultsUploadSizeLimit; + } + BOOL userDefaultsAllowInsecureSplunkCert = [defaults boolForKey:@"splunk_allowInsecureCertificate"]; _allowInsecureSplunkCert |= userDefaultsAllowInsecureSplunkCert; @@ -536,7 +544,8 @@ _isiCloudAnalyticsEnabled() }]; } -- (BOOL)prepareEventForUpload:(NSMutableDictionary*)event { +- (BOOL)prepareEventForUpload:(NSMutableDictionary*)event + linkedUUID:(NSUUID *)linkedUUID { if ([self eventIsBlacklisted:event]) { return NO; } @@ -547,10 +556,13 @@ _isiCloudAnalyticsEnabled() event[SFAnalyticsClientId] = @(0); } event[SFAnalyticsSplunkTopic] = self->_splunkTopicName ?: [NSNull null]; + if (linkedUUID) { + event[SFAnalyticsEventCorrelationID] = [linkedUUID UUIDString]; + } return YES; } -- (void)addFailures:(NSMutableArray*)failures toUploadRecords:(NSMutableArray*)records threshold:(NSUInteger)threshold +- (void)addFailures:(NSMutableArray*)failures toUploadRecords:(NSMutableArray*)records threshold:(NSUInteger)threshold linkedUUID:(NSUUID *)linkedUUID { // The first 0 through 'threshold' items are getting uploaded in any case (which might be 0 for lower priority data) @@ -561,7 +573,7 @@ _isiCloudAnalyticsEnabled() *stop = YES; return; } - if ([self prepareEventForUpload:event]) { + if ([self prepareEventForUpload:event linkedUUID:linkedUUID]) { if ([NSJSONSerialization isValidJSONObject:event]) { [records addObject:event]; } else { @@ -594,7 +606,7 @@ _isiCloudAnalyticsEnabled() NSRange range = NSMakeRange(threshold, (client.count - threshold) * scale); NSArray* sub = [client subarrayWithRange:range]; [sub enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - if ([self prepareEventForUpload:obj]) { + if ([self prepareEventForUpload:obj linkedUUID:linkedUUID]) { [records addObject:obj]; } }]; @@ -651,7 +663,7 @@ _isiCloudAnalyticsEnabled() return statistics; } -- (NSMutableDictionary*)healthSummaryWithName:(NSString*)name store:(SFAnalyticsSQLiteStore*)store +- (NSMutableDictionary*)healthSummaryWithName:(NSString*)name store:(SFAnalyticsSQLiteStore*)store uuid:(NSUUID *)uuid { __block NSMutableDictionary* summary = [NSMutableDictionary new]; @@ -703,7 +715,7 @@ _isiCloudAnalyticsEnabled() }]; // Should always return yes because we already checked for event blacklisting specifically (unless summary itself is blacklisted) - if (![self prepareEventForUpload:summary]) { + if (![self prepareEventForUpload:summary linkedUUID:uuid]) { secwarning("supd: health summary for %@ blacklisted", name); return nil; } @@ -731,19 +743,147 @@ _isiCloudAnalyticsEnabled() } } -- (NSData*)getLoggingJSON:(bool)pretty - forUpload:(BOOL)upload - participatingClients:(NSMutableArray**)clients - force:(BOOL)force // supdctl uploads ignore privacy settings and recency - error:(NSError**)error +- (size_t)serializedEventSize:(NSObject *)event + error:(NSError**)error +{ + if (![NSJSONSerialization isValidJSONObject:event]) { + secnotice("serializedEventSize", "invalid JSON object"); + return 0; + } + + NSData *json = [NSJSONSerialization dataWithJSONObject:event + options:0 + error:error]; + if (json) { + return [json length]; + } else { + secnotice("serializedEventSize", "failed to serialize event"); + return 0; + } +} + +- (NSArray *)chunkFailureSet:(size_t)sizeCapacity + events:(NSArray *)events + error:(NSError **)error +{ + const size_t postBodyLimit = 1000; // 1000 events in a single upload + size_t currentSize = 0; + size_t currentEventCount = 0; + + NSMutableArray *> *eventChunks = [[NSMutableArray *> alloc] init]; + NSMutableArray *currentEventChunk = [[NSMutableArray alloc] init]; + for (NSDictionary *event in events) { + NSError *localError = nil; + size_t eventSize = [self serializedEventSize:event error:&localError]; + if (localError != nil) { + if (error) { + *error = localError; + } + secemergency("Unable to serialize event JSON: %@", [localError localizedDescription]); + return nil; + } + + BOOL countLessThanLimit = currentEventCount < postBodyLimit; + BOOL sizeLessThanCapacity = (currentSize + eventSize) <= sizeCapacity; + if (!countLessThanLimit || !sizeLessThanCapacity) { + [eventChunks addObject:currentEventChunk]; + currentEventChunk = [[NSMutableArray alloc] init]; + currentEventCount = 0; + currentSize = 0; + } + + [currentEventChunk addObject:event]; + currentEventCount++; + currentSize += eventSize; + } + + if ([currentEventChunk count] > 0) { + [eventChunks addObject:currentEventChunk]; + } + + return eventChunks; +} + +- (NSDictionary *)createEventDictionary:(NSArray *)healthSummaries + failures:(NSArray *)failures + error:(NSError **)error +{ + NSMutableArray *events = [[NSMutableArray alloc] init]; + [events addObjectsFromArray:healthSummaries]; + if (failures) { + [events addObjectsFromArray:failures]; + } + + NSDictionary *eventDictionary = @{ + SFAnalyticsPostTime : @([[NSDate date] timeIntervalSince1970] * 1000), + @"events" : events, + }; + + if (![NSJSONSerialization isValidJSONObject:eventDictionary]) { + secemergency("json: final dictionary invalid JSON."); + if (error) { + *error = [NSError errorWithDomain:SupdErrorDomain code:SupdInvalidJSONError + userInfo:@{NSLocalizedDescriptionKey : [NSString localizedStringWithFormat:@"Final dictionary for upload is invalid JSON: %@", eventDictionary]}]; + } + return nil; + } + + return eventDictionary; +} + +- (NSArray *)createChunkedLoggingJSON:(NSArray *)healthSummaries + failures:(NSArray *)failures + error:(NSError **)error { - NSMutableArray* localClients = [NSMutableArray new]; - __block NSMutableArray* uploadRecords = [NSMutableArray arrayWithCapacity:_maxEventsToReport]; - __block NSError *localError; - __block NSMutableArray* hardFailures = [NSMutableArray new]; - __block NSMutableArray* softFailures = [NSMutableArray new]; - NSString* ckdeviceID = nil; - NSString* accountID = nil; + NSError *localError = nil; + size_t baseSize = [self serializedEventSize:healthSummaries error:&localError]; + if (localError != nil) { + secemergency("Unable to serialize health summary JSON"); + if (error) { + *error = localError; + } + return nil; + } + + NSArray *chunkedEvents = [self chunkFailureSet:(self.uploadSizeLimit - baseSize) events:failures error:&localError]; + + NSMutableArray *jsonResults = [[NSMutableArray alloc] init]; + for (NSArray *failureSet in chunkedEvents) { + NSDictionary *eventDictionary = [self createEventDictionary:healthSummaries failures:failureSet error:error]; + if (eventDictionary) { + [jsonResults addObject:eventDictionary]; + } else { + return nil; + } + } + + if ([jsonResults count] == 0) { + NSDictionary *eventDictionary = [self createEventDictionary:healthSummaries failures:nil error:error]; + if (eventDictionary) { + [jsonResults addObject:eventDictionary]; + } else { + return nil; + } + } + + return jsonResults; +} + +- (BOOL)copyEvents:(NSMutableArray **)healthSummaries + failures:(NSMutableArray **)failures + forUpload:(BOOL)upload +participatingClients:(NSMutableArray**)clients + force:(BOOL)force + linkedUUID:(NSUUID *)linkedUUID + error:(NSError**)error +{ + NSMutableArray *localClients = [[NSMutableArray alloc] init]; + NSMutableArray *localHealthSummaries = [[NSMutableArray alloc] init]; + NSMutableArray *localFailures = [[NSMutableArray alloc] init]; + NSMutableArray *hardFailures = [[NSMutableArray alloc] init]; + NSMutableArray *softFailures = [[NSMutableArray alloc] init]; + NSString *ckdeviceID = nil; + NSString *accountID = nil; if (os_variant_has_internal_diagnostics("com.apple.security") && [_internalTopicName isEqualToString:SFAnalyticsTopicKeySync]) { ckdeviceID = [self askSecurityForCKDeviceID]; @@ -751,12 +891,12 @@ _isiCloudAnalyticsEnabled() } for (SFAnalyticsClient* client in self->_topicClients) { if (!force && [client requireDeviceAnalytics] && !_isDeviceAnalyticsEnabled()) { - // Client required device analytics, yet the user did not opt in. + // Client required device analytics, yet the user did not opt in. secnotice("getLoggingJSON", "Client '%@' requires device analytics yet user did not opt in.", [client name]); continue; - } + } if (!force && [client requireiCloudAnalytics] && !_isiCloudAnalyticsEnabled()) { - // Client required iCloud analytics, yet the user did not opt in. + // Client required iCloud analytics, yet the user did not opt in. secnotice("getLoggingJSON", "Client '%@' requires iCloud analytics yet user did not opt in.", [client name]); continue; } @@ -779,7 +919,7 @@ _isiCloudAnalyticsEnabled() [localClients addObject:client]; } - NSMutableDictionary* healthSummary = [self healthSummaryWithName:client.name store:store]; + NSMutableDictionary* healthSummary = [self healthSummaryWithName:client.name store:store uuid:linkedUUID]; if (healthSummary) { if (ckdeviceID) { healthSummary[SFAnalyticsDeviceID] = ckdeviceID; @@ -787,7 +927,7 @@ _isiCloudAnalyticsEnabled() if (accountID) { healthSummary[SFAnalyticsAltDSID] = accountID; } - [uploadRecords addObject:healthSummary]; + [localHealthSummaries addObject:healthSummary]; } [hardFailures addObject:store.hardFailures]; @@ -801,40 +941,93 @@ _isiCloudAnalyticsEnabled() code:-10 userInfo:@{NSLocalizedDescriptionKey : description}]; } - return nil; + return NO; } if (clients) { *clients = localClients; } - [self addFailures:hardFailures toUploadRecords:uploadRecords threshold:_maxEventsToReport/10]; - [self addFailures:softFailures toUploadRecords:uploadRecords threshold:0]; + if (failures) { + [self addFailures:hardFailures toUploadRecords:localFailures threshold:_maxEventsToReport/10 linkedUUID:linkedUUID]; + [self addFailures:softFailures toUploadRecords:localFailures threshold:0 linkedUUID:linkedUUID]; + [*failures addObjectsFromArray:localFailures]; + } + + if (healthSummaries) { + [*healthSummaries addObjectsFromArray:localHealthSummaries]; + } - NSDictionary* jsonDict = @{ - SFAnalyticsSplunkPostTime : @([[NSDate date] timeIntervalSince1970] * 1000), - @"events" : uploadRecords - }; + return YES; +} - // This check is "belt and suspenders" because we already checked each event separately - if (![NSJSONSerialization isValidJSONObject:jsonDict]) { - secemergency("json: final dictionary invalid JSON. This is terrible!"); +- (NSArray *)createChunkedLoggingJSON:(bool)pretty + forUpload:(BOOL)upload + participatingClients:(NSMutableArray**)clients + force:(BOOL)force // supdctl uploads ignore privacy settings and recency + error:(NSError**)error +{ + NSUUID *linkedUUID = [NSUUID UUID]; + NSError *localError = nil; + NSMutableArray *failures = [[NSMutableArray alloc] init]; + NSMutableArray *healthSummaries = [[NSMutableArray alloc] init]; + BOOL copied = [self copyEvents:&healthSummaries + failures:&failures + forUpload:upload + participatingClients:clients + force:force + linkedUUID:linkedUUID + error:&localError]; + if (!copied || localError) { if (error) { - *error = [NSError errorWithDomain:SupdErrorDomain code:SupdInvalidJSONError - userInfo:@{NSLocalizedDescriptionKey : [NSString localizedStringWithFormat:@"Final dictionary for upload is invalid JSON: %@", jsonDict]}]; + *error = localError; } return nil; } - NSData *json = [NSJSONSerialization dataWithJSONObject:jsonDict - options:(pretty ? NSJSONWritingPrettyPrinted : 0) - error:&localError]; - - if (error) { - *error = localError; + // Trim failures to the max count, based on health summary count + if ([failures count] > (_maxEventsToReport - [healthSummaries count])) { + NSRange range; + range.location = 0; + range.length = _maxEventsToReport - [healthSummaries count]; + failures = [[failures subarrayWithRange:range] mutableCopy]; } - return json; + return [self createChunkedLoggingJSON:healthSummaries failures:failures error:error]; +} + +- (NSDictionary *)createLoggingJSON:(bool)pretty + forUpload:(BOOL)upload + participatingClients:(NSMutableArray**)clients + force:(BOOL)force // supdctl uploads ignore privacy settings and recency + error:(NSError**)error +{ + NSError *localError = nil; + NSMutableArray *failures = [[NSMutableArray alloc] init]; + NSMutableArray *healthSummaries = [[NSMutableArray alloc] init]; + BOOL copied = [self copyEvents:&healthSummaries + failures:&failures + forUpload:upload + participatingClients:clients + force:force + linkedUUID:nil + error:&localError]; + if (!copied || localError) { + if (error) { + *error = localError; + } + return nil; + } + + // Trim failures to the max count, based on health summary count + if ([failures count] > (_maxEventsToReport - [healthSummaries count])) { + NSRange range; + range.location = 0; + range.length = _maxEventsToReport - [healthSummaries count]; + failures = [[failures subarrayWithRange:range] mutableCopy]; + } + + return [self createEventDictionary:healthSummaries failures:failures error:error]; } // Is at least one client eligible for data collection based on user consent? Otherwise callers should NOT reach off-device. @@ -1252,6 +1445,30 @@ static bool ShouldInitializeWithAsset(NSBundle *trustStoreBundle, NSURL *directo } } +- (NSArray *)serializeLoggingEvents:(NSArray *)events + error:(NSError **)error +{ + if (!events) { + return nil; + } + + NSMutableArray *serializedEvents = [[NSMutableArray alloc] init]; + for (NSDictionary *event in events) { + NSError *serializationError = nil; + NSData* serializedEvent = [NSJSONSerialization dataWithJSONObject:event + options:0 + error:&serializationError]; + if (serializedEvent && !serializationError) { + [serializedEvents addObject:serializedEvent]; + } else if (error) { + *error = serializationError; + return nil; + } + } + + return serializedEvents; +} + - (BOOL)uploadAnalyticsWithError:(NSError**)error force:(BOOL)force { [self sendNotificationForOncePerReportSamplers]; @@ -1272,9 +1489,32 @@ static bool ShouldInitializeWithAsset(NSBundle *trustStoreBundle, NSURL *directo } NSMutableArray* clients = [NSMutableArray new]; - NSData* json = [topic getLoggingJSON:false forUpload:YES participatingClients:&clients force:force error:&localError]; - if (json) { - if ([topic isSampledUpload]) { + NSArray *jsonEvents = [topic createChunkedLoggingJSON:false forUpload:YES participatingClients:&clients force:force error:&localError]; + if (!jsonEvents || localError) { + if ([[localError domain] isEqualToString:SupdErrorDomain] && [localError code] == SupdInvalidJSONError) { + // Pretend this was a success because at least we'll get rid of bad data. + // If someone keeps logging bad data and we only catch it here then + // this causes sustained data loss for the entire topic. + [topic updateUploadDateForClients:clients date:[NSDate date] clearData:YES]; + } + secerror("upload: failed to create chunked log events for logging topic %@: %@", [topic internalTopicName], localError); + continue; + } + + NSArray *serializedEvents = [self serializeLoggingEvents:jsonEvents error:&localError]; + if (!serializedEvents || localError) { + if ([[localError domain] isEqualToString:SupdErrorDomain] && [localError code] == SupdInvalidJSONError) { + // Pretend this was a success because at least we'll get rid of bad data. + // If someone keeps logging bad data and we only catch it here then + // this causes sustained data loss for the entire topic. + [topic updateUploadDateForClients:clients date:[NSDate date] clearData:YES]; + } + secerror("upload: failed to serialized chunked log events for logging topic %@: %@", [topic internalTopicName], localError); + continue; + } + + if ([topic isSampledUpload]) { + for (NSData *json in serializedEvents) { if (![self->_reporter saveReport:json fileName:[topic internalTopicName]]) { secerror("upload: failed to write analytics data to log"); } @@ -1285,20 +1525,12 @@ static bool ShouldInitializeWithAsset(NSBundle *trustStoreBundle, NSURL *directo } else { secerror("upload: Failed to post JSON for %@: %@", [topic internalTopicName], localError); } - } else { - /* If we didn't sample this report, update date to prevent trying to upload again sooner - * than we should. Clear data so that per-day calculations remain consistent. */ - secnotice("upload", "skipping unsampled upload for %@ and clearing data", [topic internalTopicName]); - [topic updateUploadDateForClients:clients date:[NSDate date] clearData:YES]; } } else { - if ([[localError domain] isEqualToString:SupdErrorDomain] && [localError code] == SupdInvalidJSONError) { - // Pretend this was a success because at least we'll get rid of bad data. - // If someone keeps logging bad data and we only catch it here then - // this causes sustained data loss for the entire topic. - [topic updateUploadDateForClients:clients date:[NSDate date] clearData:YES]; - } - secerror("upload: failed to get logging JSON for topic %@: %@", [topic internalTopicName], localError); + /* If we didn't sample this report, update date to prevent trying to upload again sooner + * than we should. Clear data so that per-day calculations remain consistent. */ + secnotice("upload", "skipping unsampled upload for %@ and clearing data", [topic internalTopicName]); + [topic updateUploadDateForClients:clients date:[NSDate date] clearData:YES]; } } if (error && localError) { @@ -1382,8 +1614,6 @@ static bool ShouldInitializeWithAsset(NSBundle *trustStoreBundle, NSURL *directo reply(info, nil); } - - - (NSString*)stringForEventClass:(SFAnalyticsEventClass)eventClass { if (eventClass == SFAnalyticsEventClassNote) { @@ -1409,20 +1639,51 @@ static bool ShouldInitializeWithAsset(NSBundle *trustStoreBundle, NSURL *directo reply([self getSysdiagnoseDump]); } -- (void)getLoggingJSON:(bool)pretty topic:(NSString *)topicName reply:(void (^)(NSData*, NSError*))reply { - secnotice("rpcGetLoggingJSON", "Building a JSON blob resembling the one we would have uploaded"); +- (void)createLoggingJSON:(bool)pretty topic:(NSString *)topicName reply:(void (^)(NSData *, NSError*))reply { + secnotice("rpcCreateLoggingJSON", "Building a JSON blob resembling the one we would have uploaded"); NSError* error = nil; [self sendNotificationForOncePerReportSamplers]; - NSData* json = nil; + NSDictionary *eventDictionary = nil; for (SFAnalyticsTopic* topic in self->_analyticsTopics) { if ([topic.internalTopicName isEqualToString:topicName]) { - json = [topic getLoggingJSON:pretty forUpload:NO participatingClients:nil force:!runningTests error:&error]; + eventDictionary = [topic createLoggingJSON:pretty forUpload:NO participatingClients:nil force:!runningTests error:&error]; } } - if (!json) { + + NSData *data = nil; + if (!eventDictionary) { secerror("Unable to obtain JSON: %@", error); + } else { + data = [NSJSONSerialization dataWithJSONObject:eventDictionary + options:(pretty ? NSJSONWritingPrettyPrinted : 0) + error:&error]; + } + + reply(data, error); +} + +- (void)createChunkedLoggingJSON:(bool)pretty topic:(NSString *)topicName reply:(void (^)(NSData *, NSError*))reply +{ + secnotice("rpcCreateChunkedLoggingJSON", "Building an array of JSON blobs resembling the one we would have uploaded"); + NSError* error = nil; + [self sendNotificationForOncePerReportSamplers]; + NSArray *events = nil; + for (SFAnalyticsTopic* topic in self->_analyticsTopics) { + if ([topic.internalTopicName isEqualToString:topicName]) { + events = [topic createChunkedLoggingJSON:pretty forUpload:NO participatingClients:nil force:!runningTests error:&error]; + } + } + + NSData *data = nil; + if (!events) { + secerror("Unable to obtain JSON: %@", error); + } else { + data = [NSJSONSerialization dataWithJSONObject:events + options:(pretty ? NSJSONWritingPrettyPrinted : 0) + error:&error]; } - reply(json, error); + + reply(data, error); } - (void)forceUploadWithReply:(void (^)(BOOL, NSError*))reply { diff --git a/supd/supdProtocol.h b/supd/supdProtocol.h index 98371ac2..b190a34b 100644 --- a/supd/supdProtocol.h +++ b/supd/supdProtocol.h @@ -25,7 +25,8 @@ @protocol supdProtocol - (void)getSysdiagnoseDumpWithReply:(void (^)(NSString*))reply; -- (void)getLoggingJSON:(bool)pretty topic:(NSString *)topicName reply:(void (^)(NSData*, NSError*))reply; +- (void)createLoggingJSON:(bool)pretty topic:(NSString *)topicName reply:(void (^)(NSData *, NSError*))reply; +- (void)createChunkedLoggingJSON:(bool)pretty topic:(NSString *)topicName reply:(void (^)(NSData *, NSError*))reply; - (void)forceUploadWithReply:(void (^)(BOOL, NSError*))reply; - (void)setUploadDateWith:(NSDate *)date reply:(void (^)(BOOL, NSError*))reply; - (void)clientStatus:(void (^)(NSDictionary *, NSError *))reply; diff --git a/supdctl/main.m b/supdctl/main.m index 3c920bdf..2920fd4c 100644 --- a/supdctl/main.m +++ b/supdctl/main.m @@ -73,7 +73,7 @@ static void getSysdiagnoseDump(void) [connection invalidate]; } -static void getLoggingJSON(char *topicName) +static void createLoggingJSON(char *topicName) { NSString *topic = topicName ? [NSString stringWithUTF8String:topicName] : SFAnalyticsTopicKeySync; dispatch_semaphore_t sema = dispatch_semaphore_create(0); @@ -81,7 +81,31 @@ static void getLoggingJSON(char *topicName) [[connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) { nsprintf(@"Could not communicate with supd: %@", error); dispatch_semaphore_signal(sema); - }] getLoggingJSON:YES topic:topic reply:^(NSData* data, NSError* error) { + }] createLoggingJSON:YES topic:topic reply:^(NSData* data, NSError* error) { + if (data) { + // Success! Only print the JSON blob to make output easier to parse + nsprintf(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); + } else { + nsprintf(@"supd gave us an error: %@", error); + } + dispatch_semaphore_signal(sema); + }]; + + if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 20)) != 0) { + printf("\n\nError: timed out waiting for response from supd\n"); + } + [connection invalidate]; +} + +static void createChunkedLoggingJSON(char *topicName) +{ + NSString *topic = topicName ? [NSString stringWithUTF8String:topicName] : SFAnalyticsTopicKeySync; + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + NSXPCConnection* connection = getConnection(); + [[connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) { + nsprintf(@"Could not communicate with supd: %@", error); + dispatch_semaphore_signal(sema); + }] createChunkedLoggingJSON:YES topic:topic reply:^(NSData* data, NSError* error) { if (data) { // Success! Only print the JSON blob to make output easier to parse nsprintf(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); @@ -168,6 +192,7 @@ forceOldUploadDate(void) static int forceUpload = false; static int getJSON = false; +static int getChunkedJSON = false; static int getSysdiagnose = false; static int getInfo = false; static int setOldUploadDate = false; @@ -177,9 +202,9 @@ int main(int argc, char **argv) { static struct argument options[] = { { .shortname='t', .longname="topicName", .argument=&topicName, .description="Operate on a non-default topic"}, - { .command="sysdiagnose", .flag=&getSysdiagnose, .flagval=true, .description="Retrieve the current sysdiagnose dump for security analytics"}, { .command="get", .flag=&getJSON, .flagval=true, .description="Get the JSON blob we would upload to the server if an upload were due"}, + { .command="getChunked", .flag=&getChunkedJSON, .flagval=true, .description="Chunk the JSON blob"}, { .command="upload", .flag=&forceUpload, .flagval=true, .description="Force an upload of analytics data to server (ignoring privacy settings)"}, { .command="info", .flag=&getInfo, .flagval=true, .description="Request info about clients"}, { .command="set-old-upload-date", .flag=&setOldUploadDate, .flagval=true, .description="Clear last upload date"}, @@ -206,7 +231,9 @@ int main(int argc, char **argv) if (forceUpload) { forceUploadAnalytics(); } else if (getJSON) { - getLoggingJSON(topicName); + createLoggingJSON(topicName); + } else if (getChunkedJSON) { + createChunkedLoggingJSON(topicName); } else if (getSysdiagnose) { getSysdiagnoseDump(); } else if (getInfo) { diff --git a/tests/TrustTests/EvaluationTests/ExceptionTests.m b/tests/TrustTests/EvaluationTests/ExceptionTests.m new file mode 100644 index 00000000..3e2bb6a9 --- /dev/null +++ b/tests/TrustTests/EvaluationTests/ExceptionTests.m @@ -0,0 +1,331 @@ +/* +* Copyright (c) 2006-2010,2012-2019 Apple Inc. All Rights Reserved. +*/ + +#include +#import +#import +#include +#include +#include "OSX/utilities/array_size.h" +#include "OSX/utilities/SecCFWrappers.h" + +#import "../TestMacroConversions.h" +#import "TrustEvaluationTestCase.h" + +#import "ExceptionTests_data.h" + +@interface TrustExceptionTests : TrustEvaluationTestCase +@end + +@implementation TrustExceptionTests + +static NSArray *certs = nil; +static NSDate *date = nil; + ++ (void)setUp +{ + [super setUp]; + SecCertificateRef cert0 = SecCertificateCreateWithBytes(NULL, _exception_cert0, sizeof(_exception_cert0)); + SecCertificateRef cert1 = SecCertificateCreateWithBytes(NULL, _exception_cert1, sizeof(_exception_cert1)); + certs = @[ (__bridge id)cert0, (__bridge id)cert1 ]; + date = [NSDate dateWithTimeIntervalSinceReferenceDate:545000000.0]; /* April 9, 2018 at 1:53:20 PM PDT */ + + CFReleaseNull(cert0); + CFReleaseNull(cert1); +} + +#if !TARGET_OS_BRIDGE +// bridgeOS doesn't have a system root store +- (void)testPassingTrust +{ + SecTrustRef trust = NULL; + SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("store.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + + SecTrustResultType trustResult; + ok(SecTrustEvaluateWithError(trust, NULL), "evaluate trust"); + ok_status(SecTrustGetTrustResult(trust, &trustResult)); + is_status(trustResult, kSecTrustResultUnspecified, + "trust is kSecTrustResultUnspecified"); + CFDataRef exceptions; + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + CFReleaseNull(trust); + CFReleaseNull(policy); + CFReleaseNull(exceptions); +} +#endif + +- (void)testFailingTrust +{ + SecTrustRef trust = NULL; + SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + + SecTrustResultType trustResult; + XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust"); + ok_status(SecTrustGetTrustResult(trust, &trustResult)); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure); + CFDataRef exceptions; + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + CFReleaseNull(trust); + CFReleaseNull(policy); + CFReleaseNull(exceptions); +} + +- (void)testNewTrustObjectSameFailure +{ + SecTrustRef trust = NULL; + SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + + SecTrustResultType trustResult; + XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust"); + ok_status(SecTrustGetTrustResult(trust, &trustResult)); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure); + CFDataRef exceptions; + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + /* new trust with the same failing policy and certs should pass */ + CFReleaseNull(trust); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + CFReleaseNull(trust); + CFReleaseNull(policy); + CFReleaseNull(exceptions); +} + +#if !TARGET_OS_BRIDGE +// bridgeOS always has an AnchorTrusted error due to lack of a system root store +- (void)testIntroduceNewAnchorTrustedFailure +{ + SecTrustRef trust = NULL; + SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + + SecTrustResultType trustResult; + XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust"); + ok_status(SecTrustGetTrustResult(trust, &trustResult)); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure); + CFDataRef exceptions; + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + // new AnchorTrusted failure + ok_status(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)@[]), "set empty anchor list"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure"); + + // fix AnchorTrusted failure + ok_status(SecTrustSetAnchorCertificatesOnly(trust, false), "trust passed in anchors and system anchors"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + CFReleaseNull(trust); + CFReleaseNull(policy); + CFReleaseNull(exceptions); +} +#endif + +- (void)testIntroduceNewExpiredFailure +{ + SecTrustRef trust = NULL; + SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + + SecTrustResultType trustResult; + XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust"); + ok_status(SecTrustGetTrustResult(trust, &trustResult)); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure); + CFDataRef exceptions; + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + // new expiry failure + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)[NSDate dateWithTimeIntervalSinceReferenceDate:667680000.0]), + "set date to far future so certs are expired"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure"); + + CFReleaseNull(trust); + CFReleaseNull(policy); + CFReleaseNull(exceptions); +} + +- (void)testNewTrustObjectNewHostnameFailure +{ + SecTrustRef trust = NULL; + SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("store.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + CFDataRef exceptions; + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + + CFReleaseNull(trust); + CFReleaseNull(policy); + policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust with hostname mismatch"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + + /* exceptions from the old trust evaluation should fail */ + SecTrustResultType trustResult; + ok(SecTrustSetExceptions(trust, exceptions), "set old exceptions"); + XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust"); + ok_status(SecTrustGetTrustResult(trust, &trustResult)); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure); + + /* we should be able to get new exceptions and pass */ + CFReleaseNull(exceptions); + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + CFReleaseNull(trust); + CFReleaseNull(policy); + CFReleaseNull(exceptions); +} + +- (void)testClearExceptions +{ + SecTrustRef trust = NULL; + SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + + SecTrustResultType trustResult; + XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust"); + ok_status(SecTrustGetTrustResult(trust, &trustResult)); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure); + CFDataRef exceptions; + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + XCTAssertFalse(SecTrustSetExceptions(trust, NULL)); + ok_status(SecTrustGetTrustResult(trust, &trustResult)); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure); + + CFReleaseNull(trust); + CFReleaseNull(policy); + CFReleaseNull(exceptions); +} + +- (void)testWrongCertForExceptions +{ + SecTrustRef trust = NULL; + SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + + SecTrustResultType trustResult; + XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust"); + ok_status(SecTrustGetTrustResult(trust, &trustResult)); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure); + CFDataRef exceptions; + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + /* new trust with the same failing policy and certs should pass */ + CFReleaseNull(trust); + CFReleaseNull(policy); + SecCertificateRef sscert0 = SecCertificateCreateWithBytes(NULL, _exception_self_signed, sizeof(_exception_self_signed)); + policy = SecPolicyCreateSSL(false, CFSTR("self-signed.ssltest.apple.com")); + ok_status(SecTrustCreateWithCertificates(sscert0, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + XCTAssertFalse(SecTrustSetExceptions(trust, exceptions), "set exceptions fails for other cert"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure); + + CFReleaseNull(trust); + CFReleaseNull(policy); + CFReleaseNull(exceptions); +} + +#if TARGET_OS_IPHONE +- (void)testExtensionsEpoch +{ + SecTrustRef trust = NULL; + SecTrustResultType trustResult; + CFDataRef exceptions = NULL; + + SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com")); + ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date"); + ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); + + /* Test the uninitialized extensions epoch. */ + CFErrorRef exceptionResetCountError = NULL; + uint64_t exceptionResetCount = SecTrustGetExceptionResetCount(&exceptionResetCountError); + ok(exceptionResetCount == 0, "exception reset count is uninitialized"); + is(SecTrustGetExceptionResetCount(&exceptionResetCountError), exceptionResetCount, "SecTrustGetExceptionResetCount is idempotent"); + ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); + + /* Test increasing the extensions epoch. */ + exceptionResetCountError = NULL; + ok_status(SecTrustIncrementExceptionResetCount(&exceptionResetCountError), "increase exception reset count"); + exceptionResetCountError = NULL; + is(SecTrustGetExceptionResetCount(&exceptionResetCountError), 1 + exceptionResetCount, "exception reset count is 1 + previous count"); + + /* Test trust evaluation under a future extensions epoch. */ + ok(!SecTrustSetExceptions(trust, exceptions), "set exceptions"); + ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust"); + is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure"); + + CFReleaseNull(trust); + CFReleaseNull(policy); + CFReleaseNull(exceptions); +} +#endif + +#if !TARGET_OS_BRIDGE +// bridgeOS doesn't support Valid +- (void)testFatalResultsNonOverride +{ + id root = [self SecCertificateCreateFromPEMResource:@"ca-ki" subdirectory:@"si-88-sectrust-valid-data"]; + id revokedLeaf = [self SecCertificateCreateFromPEMResource:@"leaf-ki-revoked1" subdirectory:@"si-88-sectrust-valid-data"]; + TestTrustEvaluation *eval = [[TestTrustEvaluation alloc] initWithCertificates:@[revokedLeaf, root] policies:nil]; + [eval setVerifyDate:[NSDate dateWithTimeIntervalSinceReferenceDate:542400000.0]]; // March 10, 2018 at 10:40:00 AM PST + [eval setAnchors:@[root]]; + XCTAssertFalse([eval evaluate:nil]); + XCTAssertEqual(eval.trustResult, kSecTrustResultFatalTrustFailure); + + /* try to set exceptions on the trust and ensure it still fails */ + NSData *exceptions = CFBridgingRelease(SecTrustCopyExceptions(eval.trust)); + XCTAssertNotNil(exceptions); + XCTAssert(SecTrustSetExceptions(eval.trust, (__bridge CFDataRef)exceptions)); + XCTAssertFalse([eval evaluate:nil]); + XCTAssertEqual(eval.trustResult, kSecTrustResultFatalTrustFailure); +} +#endif + +@end diff --git a/OSX/sec/Security/Regressions/secitem/si-27-sectrust-exceptions.c b/tests/TrustTests/EvaluationTests/ExceptionTests_data.h similarity index 58% rename from OSX/sec/Security/Regressions/secitem/si-27-sectrust-exceptions.c rename to tests/TrustTests/EvaluationTests/ExceptionTests_data.h index fffb493d..e6dc309c 100644 --- a/OSX/sec/Security/Regressions/secitem/si-27-sectrust-exceptions.c +++ b/tests/TrustTests/EvaluationTests/ExceptionTests_data.h @@ -1,17 +1,9 @@ /* - * Copyright (c) 2006-2010,2012-2018 Apple Inc. All Rights Reserved. - */ - -#include -#include -#include -#include -#include -#include -#include -#include +* Copyright (c) 2019 Apple Inc. All Rights Reserved. +*/ -#include "shared_regressions.h" +#ifndef _TRUSTTESTS_EXCEPTION_TESTS_H_ +#define _TRUSTTESTS_EXCEPTION_TESTS_H_ /* Serial Number: @@ -23,7 +15,7 @@ Not After : Mar 23 12:00:00 2019 GMT Subject: businessCategory=Private Organization/jurisdictionC=US/jurisdictionST=California/serialNumber=C0806592, C=US, ST=California, L=Cupertino, O=Apple Inc., OU=Internet Services for Akamai, CN=store.apple.com */ -static unsigned char _c0[]={ +static unsigned char _exception_cert0[]={ 0x30,0x82,0x06,0xF7,0x30,0x82,0x05,0xDF,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x01, 0xF1,0xEB,0xDB,0xEE,0xD3,0x1D,0x7A,0xB5,0x72,0x54,0x7D,0x34,0x43,0x4B,0x87,0x30, 0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x75, @@ -140,7 +132,7 @@ static unsigned char _c0[]={ /* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 Extended Validation Server CA */ /* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA */ -static unsigned char _c1[]={ +static unsigned char _exception_cert1[]={ 0x30,0x82,0x04,0xB6,0x30,0x82,0x03,0x9E,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x0C, 0x79,0xA9,0x44,0xB0,0x8C,0x11,0x95,0x20,0x92,0x61,0x5F,0xE2,0x6B,0x1D,0x83,0x30, 0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x6C, @@ -221,211 +213,57 @@ static unsigned char _c1[]={ /* subject:/CN=self-signed.ssltest.apple.com/C=US */ /* issuer :/CN=self-signed.ssltest.apple.com/C=US */ - -static unsigned char _ss0[]={ - 0x30,0x82,0x03,0x0F,0x30,0x82,0x01,0xF7,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, - 0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, - 0x35,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x0C,0x1D,0x73,0x65,0x6C,0x66, - 0x2D,0x73,0x69,0x67,0x6E,0x65,0x64,0x2E,0x73,0x73,0x6C,0x74,0x65,0x73,0x74,0x2E, - 0x61,0x70,0x70,0x6C,0x65,0x2E,0x63,0x6F,0x6D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, - 0x04,0x06,0x13,0x02,0x55,0x53,0x30,0x1E,0x17,0x0D,0x31,0x36,0x30,0x36,0x30,0x37, - 0x32,0x31,0x35,0x33,0x30,0x38,0x5A,0x17,0x0D,0x31,0x37,0x30,0x36,0x30,0x37,0x32, - 0x31,0x35,0x33,0x30,0x38,0x5A,0x30,0x35,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04, - 0x03,0x0C,0x1D,0x73,0x65,0x6C,0x66,0x2D,0x73,0x69,0x67,0x6E,0x65,0x64,0x2E,0x73, - 0x73,0x6C,0x74,0x65,0x73,0x74,0x2E,0x61,0x70,0x70,0x6C,0x65,0x2E,0x63,0x6F,0x6D, - 0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x30,0x82,0x01, - 0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, - 0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xCC,0x72, - 0x7D,0x09,0x36,0x5A,0x6A,0xED,0xC1,0x7A,0x2C,0xF4,0x7C,0x58,0x63,0x05,0x3E,0x91, - 0x68,0x55,0xB1,0x2A,0x5D,0x57,0xF3,0xA4,0xA7,0x80,0x05,0x41,0x74,0xB2,0xAD,0x5A, - 0x7F,0x38,0xF6,0xF7,0xFD,0xF9,0x64,0x4D,0xDE,0xF9,0x7A,0xD3,0x8C,0x78,0xE9,0x71, - 0xCF,0x1D,0x3E,0xF0,0xDB,0x12,0x48,0x74,0x22,0xA8,0x1F,0x3F,0xB9,0xDD,0xB0,0xAD, - 0x8C,0x10,0x64,0x05,0x0E,0xE2,0x59,0x9A,0xEB,0x3F,0xBF,0xA9,0x48,0x07,0xD9,0x2C, - 0x07,0x44,0x70,0x14,0x16,0x56,0x9C,0x73,0x01,0x2E,0x0B,0xF1,0x2A,0x9F,0x1C,0xC6, - 0x78,0x56,0xB7,0x0B,0xDA,0xA6,0xE6,0x99,0x87,0x2D,0x49,0xFB,0xF0,0x47,0x22,0xA6, - 0x8B,0xF0,0x02,0x37,0x31,0xD0,0x34,0x9F,0x43,0xD1,0x24,0x49,0x94,0x7F,0xFD,0x48, - 0x9C,0xBA,0x5D,0x6B,0xD4,0xF9,0x9E,0xB5,0x18,0xE4,0xB2,0x06,0x46,0xC3,0xD9,0xE7, - 0x80,0xD8,0x61,0xA9,0x09,0x5E,0xBA,0x2E,0x58,0x56,0xAE,0x37,0x31,0x6E,0x87,0x98, - 0xD5,0xC9,0x2B,0x31,0x5C,0x40,0x01,0xDF,0xD5,0x63,0x9E,0x05,0x18,0x21,0x53,0x70, - 0x62,0x36,0x44,0xCD,0x02,0xC0,0xCC,0x6A,0x58,0xC6,0xF6,0xA4,0xDC,0x89,0x94,0xBD, - 0x4E,0xC4,0xEE,0xEE,0x40,0x31,0x59,0xC3,0x43,0xAD,0x34,0x30,0xDE,0xA9,0xA7,0x0D, - 0x85,0xF7,0x96,0x8C,0x45,0xC1,0x6E,0x85,0x39,0x97,0xA6,0x4F,0xEA,0xE8,0x2F,0x01, - 0x3D,0xC0,0x3B,0x34,0x9F,0x8F,0xCB,0xD6,0x22,0x79,0x2C,0x8C,0x8C,0xE6,0xBB,0x1F, - 0x89,0x87,0x93,0x3B,0x39,0x4E,0x64,0x7D,0xDA,0x4D,0x52,0x4C,0x97,0xE5,0x02,0x03, - 0x01,0x00,0x01,0xA3,0x2A,0x30,0x28,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01, - 0xFF,0x04,0x04,0x03,0x02,0x07,0x80,0x30,0x16,0x06,0x03,0x55,0x1D,0x25,0x01,0x01, - 0xFF,0x04,0x0C,0x30,0x0A,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x01,0x30, - 0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82, - 0x01,0x01,0x00,0x36,0x06,0xC9,0xE6,0x98,0xC2,0x84,0x1D,0x13,0x1E,0x54,0x35,0x6D, - 0xE5,0xCB,0xC5,0xFD,0xD9,0x54,0x45,0x83,0x53,0xB3,0x3B,0xE7,0x30,0x6F,0xAE,0xEA, - 0x63,0x3F,0xA8,0xFA,0xD9,0x6D,0x0F,0x7D,0xD4,0xB6,0x28,0x66,0xF9,0x57,0x87,0x3E, - 0x57,0x27,0xB6,0x9A,0x56,0xAE,0xD7,0xE0,0x11,0x20,0x71,0xC1,0xEA,0xF6,0xED,0x74, - 0x1A,0x5A,0xB1,0x74,0x6C,0xBE,0xAC,0x0E,0x3C,0xD9,0x3E,0xEC,0x17,0x6E,0xF0,0x69, - 0xC9,0x4D,0xD2,0x7E,0xAE,0x8B,0x01,0xCC,0x1A,0x23,0x7C,0x58,0x07,0x30,0xE4,0x2A, - 0x12,0xE8,0xA0,0x25,0x65,0x66,0xB5,0xC7,0x5D,0xD8,0x47,0xDF,0xD7,0x51,0xBC,0xA2, - 0xAA,0xF0,0x2F,0xB5,0x9E,0x20,0x6D,0x1F,0x84,0x00,0xF0,0xD0,0xB8,0x42,0x6A,0x9A, - 0xE7,0xCA,0x7B,0xE5,0x39,0x09,0x91,0xBF,0xCB,0x4D,0x7A,0x32,0x1E,0x00,0x6E,0xE5, - 0xF7,0x44,0x80,0x82,0x38,0x53,0x64,0xB7,0x26,0x81,0xCB,0xCE,0xA1,0xAF,0x0C,0x67, - 0x32,0xC6,0xE4,0x5D,0x09,0x7B,0x37,0xD7,0xC8,0x43,0x44,0xEF,0xC6,0xF8,0x72,0xFF, - 0x65,0xD4,0x39,0x3D,0xEC,0x72,0xA5,0x28,0xFF,0x70,0x47,0x38,0xA3,0xC7,0xCC,0x5E, - 0x0F,0xFF,0x43,0x83,0x78,0x49,0x68,0x90,0x48,0x89,0xAD,0xE1,0x2E,0xFA,0x8F,0x59, - 0xB6,0x08,0x2A,0x72,0x2F,0x52,0x3F,0x73,0x84,0xCA,0xD8,0x18,0x6C,0xDA,0xA3,0x2E, - 0xF2,0xD7,0x4C,0x21,0xD9,0xF8,0xB1,0x86,0xE9,0x35,0x78,0xE4,0x4F,0xD0,0x93,0x11, - 0x8F,0xF4,0xB1,0x17,0x4F,0xDE,0xAC,0xBD,0xA9,0xBC,0x94,0xFC,0x2E,0x7D,0xF9,0x05, - 0x26,0x90,0xF1, +static unsigned char _exception_self_signed[]={ + 0x30,0x82,0x03,0x0F,0x30,0x82,0x01,0xF7,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, + 0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, + 0x35,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x0C,0x1D,0x73,0x65,0x6C,0x66, + 0x2D,0x73,0x69,0x67,0x6E,0x65,0x64,0x2E,0x73,0x73,0x6C,0x74,0x65,0x73,0x74,0x2E, + 0x61,0x70,0x70,0x6C,0x65,0x2E,0x63,0x6F,0x6D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, + 0x04,0x06,0x13,0x02,0x55,0x53,0x30,0x1E,0x17,0x0D,0x31,0x36,0x30,0x36,0x30,0x37, + 0x32,0x31,0x35,0x33,0x30,0x38,0x5A,0x17,0x0D,0x31,0x37,0x30,0x36,0x30,0x37,0x32, + 0x31,0x35,0x33,0x30,0x38,0x5A,0x30,0x35,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04, + 0x03,0x0C,0x1D,0x73,0x65,0x6C,0x66,0x2D,0x73,0x69,0x67,0x6E,0x65,0x64,0x2E,0x73, + 0x73,0x6C,0x74,0x65,0x73,0x74,0x2E,0x61,0x70,0x70,0x6C,0x65,0x2E,0x63,0x6F,0x6D, + 0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x30,0x82,0x01, + 0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, + 0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xCC,0x72, + 0x7D,0x09,0x36,0x5A,0x6A,0xED,0xC1,0x7A,0x2C,0xF4,0x7C,0x58,0x63,0x05,0x3E,0x91, + 0x68,0x55,0xB1,0x2A,0x5D,0x57,0xF3,0xA4,0xA7,0x80,0x05,0x41,0x74,0xB2,0xAD,0x5A, + 0x7F,0x38,0xF6,0xF7,0xFD,0xF9,0x64,0x4D,0xDE,0xF9,0x7A,0xD3,0x8C,0x78,0xE9,0x71, + 0xCF,0x1D,0x3E,0xF0,0xDB,0x12,0x48,0x74,0x22,0xA8,0x1F,0x3F,0xB9,0xDD,0xB0,0xAD, + 0x8C,0x10,0x64,0x05,0x0E,0xE2,0x59,0x9A,0xEB,0x3F,0xBF,0xA9,0x48,0x07,0xD9,0x2C, + 0x07,0x44,0x70,0x14,0x16,0x56,0x9C,0x73,0x01,0x2E,0x0B,0xF1,0x2A,0x9F,0x1C,0xC6, + 0x78,0x56,0xB7,0x0B,0xDA,0xA6,0xE6,0x99,0x87,0x2D,0x49,0xFB,0xF0,0x47,0x22,0xA6, + 0x8B,0xF0,0x02,0x37,0x31,0xD0,0x34,0x9F,0x43,0xD1,0x24,0x49,0x94,0x7F,0xFD,0x48, + 0x9C,0xBA,0x5D,0x6B,0xD4,0xF9,0x9E,0xB5,0x18,0xE4,0xB2,0x06,0x46,0xC3,0xD9,0xE7, + 0x80,0xD8,0x61,0xA9,0x09,0x5E,0xBA,0x2E,0x58,0x56,0xAE,0x37,0x31,0x6E,0x87,0x98, + 0xD5,0xC9,0x2B,0x31,0x5C,0x40,0x01,0xDF,0xD5,0x63,0x9E,0x05,0x18,0x21,0x53,0x70, + 0x62,0x36,0x44,0xCD,0x02,0xC0,0xCC,0x6A,0x58,0xC6,0xF6,0xA4,0xDC,0x89,0x94,0xBD, + 0x4E,0xC4,0xEE,0xEE,0x40,0x31,0x59,0xC3,0x43,0xAD,0x34,0x30,0xDE,0xA9,0xA7,0x0D, + 0x85,0xF7,0x96,0x8C,0x45,0xC1,0x6E,0x85,0x39,0x97,0xA6,0x4F,0xEA,0xE8,0x2F,0x01, + 0x3D,0xC0,0x3B,0x34,0x9F,0x8F,0xCB,0xD6,0x22,0x79,0x2C,0x8C,0x8C,0xE6,0xBB,0x1F, + 0x89,0x87,0x93,0x3B,0x39,0x4E,0x64,0x7D,0xDA,0x4D,0x52,0x4C,0x97,0xE5,0x02,0x03, + 0x01,0x00,0x01,0xA3,0x2A,0x30,0x28,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01, + 0xFF,0x04,0x04,0x03,0x02,0x07,0x80,0x30,0x16,0x06,0x03,0x55,0x1D,0x25,0x01,0x01, + 0xFF,0x04,0x0C,0x30,0x0A,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x01,0x30, + 0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82, + 0x01,0x01,0x00,0x36,0x06,0xC9,0xE6,0x98,0xC2,0x84,0x1D,0x13,0x1E,0x54,0x35,0x6D, + 0xE5,0xCB,0xC5,0xFD,0xD9,0x54,0x45,0x83,0x53,0xB3,0x3B,0xE7,0x30,0x6F,0xAE,0xEA, + 0x63,0x3F,0xA8,0xFA,0xD9,0x6D,0x0F,0x7D,0xD4,0xB6,0x28,0x66,0xF9,0x57,0x87,0x3E, + 0x57,0x27,0xB6,0x9A,0x56,0xAE,0xD7,0xE0,0x11,0x20,0x71,0xC1,0xEA,0xF6,0xED,0x74, + 0x1A,0x5A,0xB1,0x74,0x6C,0xBE,0xAC,0x0E,0x3C,0xD9,0x3E,0xEC,0x17,0x6E,0xF0,0x69, + 0xC9,0x4D,0xD2,0x7E,0xAE,0x8B,0x01,0xCC,0x1A,0x23,0x7C,0x58,0x07,0x30,0xE4,0x2A, + 0x12,0xE8,0xA0,0x25,0x65,0x66,0xB5,0xC7,0x5D,0xD8,0x47,0xDF,0xD7,0x51,0xBC,0xA2, + 0xAA,0xF0,0x2F,0xB5,0x9E,0x20,0x6D,0x1F,0x84,0x00,0xF0,0xD0,0xB8,0x42,0x6A,0x9A, + 0xE7,0xCA,0x7B,0xE5,0x39,0x09,0x91,0xBF,0xCB,0x4D,0x7A,0x32,0x1E,0x00,0x6E,0xE5, + 0xF7,0x44,0x80,0x82,0x38,0x53,0x64,0xB7,0x26,0x81,0xCB,0xCE,0xA1,0xAF,0x0C,0x67, + 0x32,0xC6,0xE4,0x5D,0x09,0x7B,0x37,0xD7,0xC8,0x43,0x44,0xEF,0xC6,0xF8,0x72,0xFF, + 0x65,0xD4,0x39,0x3D,0xEC,0x72,0xA5,0x28,0xFF,0x70,0x47,0x38,0xA3,0xC7,0xCC,0x5E, + 0x0F,0xFF,0x43,0x83,0x78,0x49,0x68,0x90,0x48,0x89,0xAD,0xE1,0x2E,0xFA,0x8F,0x59, + 0xB6,0x08,0x2A,0x72,0x2F,0x52,0x3F,0x73,0x84,0xCA,0xD8,0x18,0x6C,0xDA,0xA3,0x2E, + 0xF2,0xD7,0x4C,0x21,0xD9,0xF8,0xB1,0x86,0xE9,0x35,0x78,0xE4,0x4F,0xD0,0x93,0x11, + 0x8F,0xF4,0xB1,0x17,0x4F,0xDE,0xAC,0xBD,0xA9,0xBC,0x94,0xFC,0x2E,0x7D,0xF9,0x05, + 0x26,0x90,0xF1, }; -#define CFReleaseSafe(CF) { CFTypeRef _cf = (CF); if (_cf) CFRelease(_cf); } -#define CFReleaseNull(CF) { CFTypeRef _cf = (CF); if (_cf) { (CF) = NULL; CFRelease(_cf); } } - -/* Test basic add delete update copy matching stuff. */ -static void tests(void) -{ - SecTrustRef trust; - SecCertificateRef cert0, cert1, sscert0; - CFArrayRef anchors = NULL; - isnt(cert0 = SecCertificateCreateWithBytes(NULL, _c0, sizeof(_c0)), - NULL, "create cert0"); - isnt(cert1 = SecCertificateCreateWithBytes(NULL, _c1, sizeof(_c1)), - NULL, "create cert1"); - isnt(sscert0 = SecCertificateCreateWithBytes(NULL, _ss0, sizeof(_ss0)), - NULL, "create sscert0"); - const void *v_certs[] = { - cert0, - cert1 - }; - SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("store.apple.com")); - CFArrayRef certs = CFArrayCreate(NULL, v_certs, - array_size(v_certs), NULL); - ok_status(SecTrustCreateWithCertificates(certs, policy, &trust), "create trust"); - CFDateRef date = CFDateCreate(NULL, 545000000.0); /* April 9, 2018 at 1:53:20 PM PDT */ - ok_status(SecTrustSetVerifyDate(trust, date), "set date"); - - SecTrustResultType trustResult; - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultUnspecified, - "trust is kSecTrustResultUnspecified"); - CFDataRef exceptions; - ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); - if (!exceptions) { goto errOut; } - ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); - - CFReleaseNull(trust); - CFReleaseNull(policy); - policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com")); - ok_status(SecTrustCreateWithCertificates(certs, policy, &trust), "create trust with hostname mismatch"); - ok_status(SecTrustSetVerifyDate(trust, date), "set date"); - ok(SecTrustSetExceptions(trust, exceptions), "set old exceptions"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure"); - CFReleaseNull(exceptions); - ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); - if (!exceptions) { goto errOut; } - ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); - - CFReleaseNull(trust); - ok_status(SecTrustCreateWithCertificates(certs, policy, &trust), "create trust"); - ok_status(SecTrustSetVerifyDate(trust, date), "set date"); - ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); - anchors = CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks); - ok_status(SecTrustSetAnchorCertificates(trust, anchors), "set empty anchor list"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure"); - - ok_status(SecTrustSetAnchorCertificatesOnly(trust, false), "trust passed in anchors and system anchors"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultProceed, "trust is now kSecTrustResultProceed"); - - ok_status(SecTrustSetAnchorCertificatesOnly(trust, true), "only trust passed in anchors (default)"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure again"); - - CFReleaseNull(exceptions); - ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); - if (!exceptions) { goto errOut; } - ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); - CFReleaseNull(date); - date = CFDateCreate(NULL, 667680000.0); - ok_status(SecTrustSetVerifyDate(trust, date), "set date to far future so certs are expired"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure"); - - CFReleaseNull(trust); - CFReleaseNull(policy); - policy = SecPolicyCreateSSL(true, CFSTR("self-signed.ssltest.apple.com")); - ok_status(SecTrustCreateWithCertificates(sscert0, policy, &trust), "create trust"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure"); - CFReleaseNull(exceptions); - ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); - if (!exceptions) { goto errOut; } - ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); - CFReleaseNull(exceptions); - ok(!SecTrustSetExceptions(trust, NULL), "clear exceptions"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure"); - -#if TARGET_OS_IPHONE - /* Test the extenions epoch feature. */ - CFReleaseNull(exceptions); - CFReleaseNull(trust); - CFReleaseNull(policy); - policy = SecPolicyCreateSSL(false, CFSTR("self-signed.ssltest.apple.com")); - ok_status(SecTrustCreateWithCertificates(sscert0, policy, &trust), "create trust"); - ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions"); - if (!exceptions) { goto errOut; } - - /* Test the uninitialized extensions epoch. */ - CFErrorRef exceptionResetCountError = NULL; - uint64_t exceptionResetCount = SecTrustGetExceptionResetCount(&exceptionResetCountError); - ok(exceptionResetCount == 0, "exception reset count is not set yet"); - ok(SecTrustSetExceptions(trust, exceptions), "set exceptions"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed"); - - /* Test increasing the extensions epoch. */ - exceptionResetCountError = NULL; - ok_status(SecTrustIncrementExceptionResetCount(&exceptionResetCountError), "increase exception reset count"); - exceptionResetCountError = NULL; - is(SecTrustGetExceptionResetCount(&exceptionResetCountError), 1 + exceptionResetCount, "exception reset count is 1 + previous count"); - - /* Test trust evaluation under a future extensions epoch. */ - ok(!SecTrustSetExceptions(trust, exceptions), "set exceptions"); - ok_status(SecTrustEvaluate(trust, &trustResult), "evaluate trust"); - is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure"); -#endif - -errOut: - CFReleaseSafe(anchors); - CFReleaseSafe(exceptions); - CFReleaseSafe(trust); - CFReleaseSafe(policy); - CFReleaseSafe(certs); - CFReleaseSafe(cert0); - CFReleaseSafe(cert1); - CFReleaseSafe(sscert0); - CFReleaseSafe(date); -} - -int si_27_sectrust_exceptions(int argc, char *const *argv) -{ -#if TARGET_OS_IPHONE - plan_tests(62); -#else - plan_tests(51); -#endif - - tests(); - - return 0; -} +#endif /* _TRUSTTESTS_EXCEPTION_TESTS_H_ */ diff --git a/tests/TrustTests/EvaluationTests/RevocationTests.m b/tests/TrustTests/EvaluationTests/RevocationTests.m index 1bdff251..e3b239b1 100644 --- a/tests/TrustTests/EvaluationTests/RevocationTests.m +++ b/tests/TrustTests/EvaluationTests/RevocationTests.m @@ -638,6 +638,70 @@ errOut: CFReleaseNull(error); } +- (void) test_revocation_checked_via_cache { + if (!ping_host("ocsp.digicert.com")) { + XCTAssert(false, "Unable to contact required network resource"); + return; + } + + SecCertificateRef leaf = NULL, subCA = NULL, root = NULL; + SecPolicyRef sslPolicy = NULL, ocspPolicy = NULL; + SecTrustRef trust = NULL; + CFArrayRef certs = NULL, anchors = NULL, policies = NULL; + CFDateRef verifyDate = NULL; + CFErrorRef error = NULL; + + leaf = SecCertificateCreateWithBytes(NULL, _ocsp_c0, sizeof(_ocsp_c0)); + subCA = SecCertificateCreateWithBytes(NULL, _ocsp_c1, sizeof(_ocsp_c1)); + root = SecCertificateCreateWithBytes(NULL, _ocsp_c2, sizeof(_ocsp_c2)); + + sslPolicy = SecPolicyCreateSSL(true, CFSTR("www.apple.com")); + ocspPolicy = SecPolicyCreateRevocation(kSecRevocationOCSPMethod); + + const void *v_certs[] = { leaf, subCA }; + const void *v_anchors[] = { root }; + const void *v_policies[] = { sslPolicy, ocspPolicy }; + + certs = CFArrayCreate(NULL, v_certs, 2, &kCFTypeArrayCallBacks); + policies = CFArrayCreate(NULL, v_policies, 2, &kCFTypeArrayCallBacks); + require_noerr_action(SecTrustCreateWithCertificates(certs, policies, &trust), errOut, fail("failed to create trust object")); + + anchors = CFArrayCreate(NULL, v_anchors, 1, &kCFTypeArrayCallBacks); + require_noerr_action(SecTrustSetAnchorCertificates(trust, anchors), errOut, fail("failed to set anchors")); + + verifyDate = CFDateCreate(NULL, 577000000.0); // April 14, 2019 at 10:46:40 PM PDT + require_noerr_action(SecTrustSetVerifyDate(trust, verifyDate), errOut, fail("failed to set verify date")); + + is(SecTrustEvaluateWithError(trust, &error), true, "valid cert failed"); + + /* Set no fetch allowed, so we're relying on the cached response from above */ + require_noerr_action(SecTrustSetNetworkFetchAllowed(trust, false), errOut, fail("failed to set network fetch disallowed")); + + /* Evaluate trust. Cached response should tell us that it's revoked. */ + is(SecTrustEvaluateWithError(trust, &error), true, "valid cert failed"); + + /* Verify that the results dictionary contains the kSecTrustRevocationChecked key for a valid cert where revocation checked */ + CFDictionaryRef result = SecTrustCopyResult(trust); + isnt(result, NULL, "failed to copy result dictionary"); + if (result) { + is(CFDictionaryGetValue(result, kSecTrustRevocationChecked), kCFBooleanTrue, "expected revocation checked flag"); + } + CFReleaseNull(result); + +errOut: + CFReleaseNull(leaf); + CFReleaseNull(subCA); + CFReleaseNull(root); + CFReleaseNull(ocspPolicy); + CFReleaseNull(sslPolicy); + CFReleaseNull(trust); + CFReleaseNull(certs); + CFReleaseNull(anchors); + CFReleaseNull(policies); + CFReleaseNull(verifyDate); + CFReleaseNull(error); +} + #else /* TARGET_OS_WATCH || TARGET_OS_BRIDGE */ - (void)testNoNetworking { @@ -680,7 +744,89 @@ errOut: CFReleaseNull(verifyDate); CFReleaseNull(error); } -#endif +#endif /* !TARGET_OS_WATCH && !TARGET_OS_BRIDGE */ + +#if !TARGET_OS_BRIDGE +/* bridgeOS doesn't use Valid */ +- (NSNumber *)runRevocationCheckNoNetwork:(SecCertificateRef)leaf + subCA:(SecCertificateRef)subCA +{ + CFArrayRef anchors = NULL; + SecPolicyRef smimePolicy = NULL, revocationPolicy = NULL; + CFArrayRef certs = NULL; + SecTrustRef trust = NULL; + CFDateRef date = NULL; + CFErrorRef error = NULL; + NSArray *policies = nil; + NSDictionary *result = nil; + NSNumber *revocationChecked = nil; + + const void *v_certs[] = { leaf }; + require_action(certs = CFArrayCreate(NULL, v_certs, array_size(v_certs), &kCFTypeArrayCallBacks), errOut, + fail("unable to create certificates array")); + require_action(anchors = CFArrayCreate(NULL, (const void **)&subCA, 1, &kCFTypeArrayCallBacks), errOut, + fail("unable to create anchors array")); + + require_action(smimePolicy = SecPolicyCreateSMIME(kSecSignSMIMEUsage, NULL), errOut, fail("unable to create policy")); + revocationPolicy = SecPolicyCreateRevocation(kSecRevocationUseAnyAvailableMethod | kSecRevocationCheckIfTrusted); + policies = @[(__bridge id)smimePolicy, (__bridge id)revocationPolicy]; + ok_status(SecTrustCreateWithCertificates(certs, (__bridge CFArrayRef)policies, &trust), "failed to create trust"); + ok_status(SecTrustSetNetworkFetchAllowed(trust, false), "SecTrustSetNetworkFetchAllowed failed"); + + require_noerr_action(SecTrustSetAnchorCertificates(trust, anchors), errOut, + fail("unable to set anchors")); + ok(SecTrustEvaluateWithError(trust, &error), "Not trusted"); + result = CFBridgingRelease(SecTrustCopyResult(trust)); + revocationChecked = result[(__bridge NSString *)kSecTrustRevocationChecked]; + +errOut: + CFReleaseNull(anchors); + CFReleaseNull(smimePolicy); + CFReleaseNull(revocationPolicy); + CFReleaseNull(certs); + CFReleaseNull(trust); + CFReleaseNull(date); + CFReleaseNull(error); + + return revocationChecked; +} + +- (void) test_revocation_checked_via_valid { + SecCertificateRef leaf = NULL, subCA = NULL; + NSNumber *revocationChecked = NULL; + + require_action(leaf = SecCertificateCreateWithBytes(NULL, _leaf_sha256_valid_cav2_complete_ok1, sizeof(_leaf_sha256_valid_cav2_complete_ok1)), errOut, + fail("unable to create cert")); + require_action(subCA = SecCertificateCreateWithBytes(NULL, _ca_sha256_valid_cav2_complete, sizeof(_ca_sha256_valid_cav2_complete)), errOut, fail("unable to create cert")); + + revocationChecked = [self runRevocationCheckNoNetwork:leaf + subCA:subCA]; + XCTAssert(revocationChecked != NULL, "kSecTrustRevocationChecked is not in the result dictionary"); + +errOut: + CFReleaseNull(leaf); + CFReleaseNull(subCA); +} + +- (void) test_revocation_not_checked_no_network { + /* The intermediate does not have the noCAv2 flag and is "probably not revoked,", so + kSecTrustRevocationChecked should not be in the results dictionary */ + SecCertificateRef leaf = NULL, subCA = NULL; + NSNumber *revocationChecked = NULL; + + require_action(leaf = SecCertificateCreateWithBytes(NULL, _leaf_serial_invalid_incomplete_ok1, sizeof(_leaf_serial_invalid_incomplete_ok1)), errOut, + fail("unable to create cert")); + require_action(subCA = SecCertificateCreateWithBytes(NULL, _ca_serial_invalid_incomplete, sizeof(_ca_serial_invalid_incomplete)), errOut, fail("unable to create cert")); + + revocationChecked = [self runRevocationCheckNoNetwork:leaf + subCA:subCA]; + XCTAssert(revocationChecked == NULL, "kSecTrustRevocationChecked is in the result dictionary"); + +errOut: + CFReleaseNull(leaf); + CFReleaseNull(subCA); +} +#endif /* !TARGET_OS_BRIDGE */ /* bridgeOS and watchOS do not support networked OCSP but do support stapling */ - (void) test_stapled_revoked_response { diff --git a/tests/TrustTests/EvaluationTests/RevocationTests_data.h b/tests/TrustTests/EvaluationTests/RevocationTests_data.h index 90d87845..53a48d99 100644 --- a/tests/TrustTests/EvaluationTests/RevocationTests_data.h +++ b/tests/TrustTests/EvaluationTests/RevocationTests_data.h @@ -1284,4 +1284,298 @@ uint8_t _devID_OCSPResponse[] = { 0x44,0xc9,0x27,0x73,0x07,0xee,0x82,0xe5,0x4e,0xf5,0x70 }; +uint8_t _leaf_sha256_valid_cav2_complete_ok1[] = { + 0x30,0x82,0x04,0x7b,0x30,0x82,0x03,0x63,0xa0,0x03,0x02,0x01,0x02,0x02,0x05,0x00, + 0xa4,0x37,0x10,0xc8,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01, + 0x0b,0x05,0x00,0x30,0x81,0x8f,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x0c, + 0x1d,0x43,0x41,0x20,0x73,0x68,0x61,0x32,0x35,0x36,0x20,0x76,0x61,0x6c,0x69,0x64, + 0x20,0x63,0x61,0x76,0x32,0x20,0x63,0x6f,0x6d,0x70,0x6c,0x65,0x74,0x65,0x31,0x22, + 0x30,0x20,0x06,0x03,0x55,0x04,0x0b,0x0c,0x19,0x56,0x61,0x6c,0x69,0x64,0x20,0x50, + 0x72,0x6f,0x6a,0x65,0x63,0x74,0x20,0x54,0x65,0x73,0x74,0x20,0x43,0x41,0x73,0x20, + 0x56,0x32,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0a,0x0c,0x0a,0x41,0x70,0x70, + 0x6c,0x65,0x20,0x49,0x6e,0x63,0x2e,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07, + 0x0c,0x09,0x43,0x75,0x70,0x65,0x72,0x74,0x69,0x6e,0x6f,0x31,0x0b,0x30,0x09,0x06, + 0x03,0x55,0x04,0x08,0x0c,0x02,0x43,0x41,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04, + 0x06,0x13,0x02,0x55,0x53,0x30,0x20,0x17,0x0d,0x31,0x38,0x30,0x36,0x31,0x32,0x32, + 0x33,0x32,0x38,0x32,0x32,0x5a,0x18,0x0f,0x32,0x31,0x31,0x38,0x30,0x35,0x31,0x39, + 0x32,0x33,0x32,0x38,0x32,0x32,0x5a,0x30,0x81,0x95,0x31,0x2c,0x30,0x2a,0x06,0x03, + 0x55,0x04,0x03,0x0c,0x23,0x4c,0x65,0x61,0x66,0x20,0x73,0x68,0x61,0x32,0x35,0x36, + 0x20,0x76,0x61,0x6c,0x69,0x64,0x20,0x63,0x61,0x76,0x32,0x20,0x63,0x6f,0x6d,0x70, + 0x6c,0x65,0x74,0x65,0x20,0x6f,0x6b,0x31,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04, + 0x0b,0x0c,0x19,0x56,0x61,0x6c,0x69,0x64,0x20,0x50,0x72,0x6f,0x6a,0x65,0x63,0x74, + 0x20,0x54,0x65,0x73,0x74,0x20,0x43,0x41,0x73,0x20,0x56,0x32,0x31,0x13,0x30,0x11, + 0x06,0x03,0x55,0x04,0x0a,0x0c,0x0a,0x41,0x70,0x70,0x6c,0x65,0x20,0x49,0x6e,0x63, + 0x2e,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x0c,0x09,0x43,0x75,0x70,0x65, + 0x72,0x74,0x69,0x6e,0x6f,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x0c,0x02, + 0x43,0x41,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x30, + 0x82,0x01,0x22,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x01, + 0x05,0x00,0x03,0x82,0x01,0x0f,0x00,0x30,0x82,0x01,0x0a,0x02,0x82,0x01,0x01,0x00, + 0xc3,0x8b,0xfa,0x6c,0xa7,0x18,0xd3,0xbc,0x41,0x58,0x47,0x9c,0xa0,0x5a,0x21,0xb0, + 0xaa,0x28,0xd3,0x2b,0xb3,0x57,0xdf,0x5b,0x0f,0x18,0xa8,0xa9,0x5a,0x01,0x9a,0x15, + 0x0b,0x62,0x7f,0x51,0x7d,0x85,0x98,0x48,0x74,0xdf,0xa4,0xb7,0xf2,0x4c,0x88,0x10, + 0xe0,0x41,0x31,0x77,0x64,0x2f,0xe2,0x47,0x3a,0x9b,0xf9,0xf4,0xf3,0xf9,0x3f,0x09, + 0xbc,0x33,0xbb,0xbc,0xf9,0x75,0x7c,0xc8,0x4c,0x95,0xd2,0xa2,0xc3,0xc0,0x0a,0x5f, + 0xf4,0x3b,0x50,0x95,0x53,0xf8,0xf2,0x77,0xce,0x02,0x06,0xbf,0x9e,0x74,0x2e,0x58, + 0x6e,0xff,0x29,0xb6,0x80,0x6a,0xe4,0x07,0xaf,0x41,0x5b,0x91,0x49,0x30,0x3f,0x5b, + 0x1b,0x2d,0xab,0x6a,0xf5,0x48,0x44,0x87,0xf0,0xaf,0xa4,0xb3,0x41,0x28,0x9f,0xf6, + 0xae,0x35,0x0c,0xf6,0x09,0x17,0x34,0x55,0x35,0x50,0xf1,0x57,0xe7,0x6d,0x18,0x24, + 0x35,0x40,0xc3,0xf5,0x41,0xe7,0x9d,0x76,0x33,0xcc,0xaa,0x85,0x3f,0x80,0x6e,0x7f, + 0x55,0x62,0xf3,0x6d,0x61,0x26,0x90,0x4f,0x7d,0x73,0xc1,0x0b,0xae,0xf5,0xf8,0x04, + 0x1a,0xbb,0x88,0x50,0x79,0xfd,0x59,0xe3,0xfc,0x58,0x4b,0xb6,0x20,0x5f,0x26,0xc6, + 0x50,0x31,0x06,0x84,0x8a,0xa9,0xd4,0x1d,0x9b,0x7a,0x55,0x2e,0x52,0xfe,0x5b,0x95, + 0xdf,0x0b,0x03,0xf3,0xcc,0xca,0x9b,0xb5,0x75,0x66,0x16,0x67,0x11,0x63,0x39,0xf9, + 0x77,0x95,0x30,0x0a,0x74,0x9c,0xd4,0xc8,0x81,0x75,0xd3,0x3f,0x59,0x8a,0xea,0x17, + 0x06,0xb3,0xc1,0x20,0x81,0xef,0xbe,0xde,0x91,0x44,0x7c,0x6a,0x49,0xae,0xf2,0x25, + 0x02,0x03,0x01,0x00,0x01,0xa3,0x81,0xd3,0x30,0x81,0xd0,0x30,0x0e,0x06,0x03,0x55, + 0x1d,0x0f,0x01,0x01,0xff,0x04,0x04,0x03,0x02,0x07,0x80,0x30,0x0c,0x06,0x03,0x55, + 0x1d,0x13,0x01,0x01,0xff,0x04,0x02,0x30,0x00,0x30,0x1f,0x06,0x03,0x55,0x1d,0x23, + 0x04,0x18,0x30,0x16,0x80,0x14,0x39,0xbb,0x91,0xb5,0x14,0x54,0x76,0x0c,0x1c,0x0c, + 0xf4,0x4b,0xbc,0xd9,0xf6,0x51,0xc8,0xe2,0x03,0xfc,0x30,0x54,0x06,0x03,0x55,0x1d, + 0x1f,0x04,0x4d,0x30,0x4b,0x30,0x49,0xa0,0x47,0xa0,0x45,0x86,0x43,0x68,0x74,0x74, + 0x70,0x3a,0x2f,0x2f,0x76,0x61,0x6c,0x69,0x64,0x74,0x65,0x73,0x74,0x2e,0x61,0x70, + 0x70,0x6c,0x65,0x2e,0x67,0x65,0x6f,0x66,0x66,0x6b,0x2e,0x6e,0x65,0x74,0x2f,0x76, + 0x32,0x2d,0x73,0x68,0x61,0x32,0x35,0x36,0x2d,0x76,0x61,0x6c,0x69,0x64,0x2d,0x63, + 0x61,0x76,0x32,0x2d,0x63,0x6f,0x6d,0x70,0x6c,0x65,0x74,0x65,0x2e,0x63,0x72,0x6c, + 0x30,0x39,0x06,0x08,0x2b,0x06,0x01,0x05,0x05,0x07,0x01,0x01,0x04,0x2d,0x30,0x2b, + 0x30,0x29,0x06,0x08,0x2b,0x06,0x01,0x05,0x05,0x07,0x30,0x01,0x86,0x1d,0x68,0x74, + 0x74,0x70,0x3a,0x2f,0x2f,0x6b,0x6e,0x6f,0x77,0x6e,0x2d,0x61,0x6e,0x73,0x77,0x65, + 0x72,0x2d,0x6f,0x63,0x73,0x70,0x2f,0x6f,0x63,0x73,0x70,0x30,0x0d,0x06,0x09,0x2a, + 0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x0b,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x02, + 0xc7,0x2c,0xec,0x13,0xcc,0x7c,0x54,0xc6,0xe5,0x00,0x83,0x9b,0xc4,0xe5,0x4e,0xc3, + 0xb1,0x3c,0x1d,0x95,0x2f,0x45,0xe7,0x75,0xaf,0x04,0x89,0xdb,0x05,0x21,0x13,0x12, + 0x75,0x1a,0x52,0xc1,0x31,0x74,0x89,0x87,0xd4,0xfa,0xea,0x6b,0x6f,0x61,0xb1,0xb1, + 0x21,0x49,0xd7,0x25,0xfd,0x31,0xc5,0xf7,0x31,0x1a,0xa5,0x9f,0x08,0xc3,0x16,0x84, + 0x3e,0x93,0x7a,0xdd,0xc4,0x28,0xe2,0xec,0x90,0x70,0x6b,0x58,0x8f,0xdf,0x1f,0x1d, + 0x46,0xf2,0x23,0xa2,0x2f,0x29,0xd4,0xe2,0xd1,0xa2,0x6e,0x20,0x94,0x91,0xa2,0x03, + 0x81,0x04,0xf9,0x4e,0x36,0x4b,0x75,0x2e,0x3a,0x13,0x0a,0x94,0xf8,0x29,0x79,0x0d, + 0xac,0x4a,0xff,0xb6,0x60,0x59,0xa4,0x10,0xea,0x39,0xdd,0x39,0x06,0x7c,0xbc,0x91, + 0x5f,0xbd,0xa7,0x8c,0x9b,0xf5,0xbd,0x17,0x80,0xde,0x8f,0x71,0x22,0x6d,0xef,0xfe, + 0x01,0x04,0xf2,0x7c,0xe1,0x35,0xd1,0x21,0x64,0x4d,0xd7,0xf0,0x43,0x26,0x0b,0x18, + 0x78,0xc0,0xe8,0xad,0x0e,0xc5,0x38,0xb5,0xcc,0xaf,0x04,0x7a,0x78,0xbc,0x1b,0x89, + 0x15,0x1f,0x1e,0x61,0x41,0xb6,0x46,0x8e,0x8b,0xd9,0xae,0x69,0x5c,0x9d,0x2b,0x83, + 0x71,0x25,0xd4,0x48,0x8d,0xfc,0x48,0x51,0x60,0xb3,0xa9,0xf7,0x53,0xf2,0x9b,0xa7, + 0x32,0x56,0x5f,0xea,0xd6,0x99,0xd0,0xef,0x2c,0x41,0x9d,0xe0,0x68,0x9f,0x8e,0xad, + 0x86,0x90,0xaa,0x24,0x5e,0xef,0xb0,0xc7,0x15,0xf2,0xce,0x2d,0x54,0x0c,0x8e,0x5e, + 0x3f,0x63,0xdc,0x74,0x35,0x0e,0x35,0x0d,0xfc,0x0b,0x45,0x56,0x5b,0xb1,0xe2 +}; + +uint8_t _ca_sha256_valid_cav2_complete[] = { + 0x30,0x82,0x04,0x41,0x30,0x82,0x03,0x29,0xa0,0x03,0x02,0x01,0x02,0x02,0x05,0x00, + 0xd3,0x18,0x1d,0xb8,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01, + 0x0b,0x05,0x00,0x30,0x81,0x87,0x31,0x1e,0x30,0x1c,0x06,0x03,0x55,0x04,0x03,0x0c, + 0x15,0x56,0x61,0x6c,0x69,0x64,0x20,0x54,0x65,0x73,0x74,0x20,0x43,0x41,0x20,0x52, + 0x6f,0x6f,0x74,0x20,0x56,0x32,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x0b,0x0c, + 0x19,0x56,0x61,0x6c,0x69,0x64,0x20,0x50,0x72,0x6f,0x6a,0x65,0x63,0x74,0x20,0x54, + 0x65,0x73,0x74,0x20,0x43,0x41,0x73,0x20,0x56,0x32,0x31,0x13,0x30,0x11,0x06,0x03, + 0x55,0x04,0x0a,0x0c,0x0a,0x41,0x70,0x70,0x6c,0x65,0x20,0x49,0x6e,0x63,0x2e,0x31, + 0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x0c,0x09,0x43,0x75,0x70,0x65,0x72,0x74, + 0x69,0x6e,0x6f,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x0c,0x02,0x43,0x41, + 0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x30,0x20,0x17, + 0x0d,0x31,0x38,0x30,0x36,0x31,0x32,0x32,0x33,0x32,0x38,0x32,0x31,0x5a,0x18,0x0f, + 0x32,0x31,0x31,0x38,0x30,0x35,0x31,0x39,0x32,0x33,0x32,0x38,0x32,0x31,0x5a,0x30, + 0x81,0x8f,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x0c,0x1d,0x43,0x41,0x20, + 0x73,0x68,0x61,0x32,0x35,0x36,0x20,0x76,0x61,0x6c,0x69,0x64,0x20,0x63,0x61,0x76, + 0x32,0x20,0x63,0x6f,0x6d,0x70,0x6c,0x65,0x74,0x65,0x31,0x22,0x30,0x20,0x06,0x03, + 0x55,0x04,0x0b,0x0c,0x19,0x56,0x61,0x6c,0x69,0x64,0x20,0x50,0x72,0x6f,0x6a,0x65, + 0x63,0x74,0x20,0x54,0x65,0x73,0x74,0x20,0x43,0x41,0x73,0x20,0x56,0x32,0x31,0x13, + 0x30,0x11,0x06,0x03,0x55,0x04,0x0a,0x0c,0x0a,0x41,0x70,0x70,0x6c,0x65,0x20,0x49, + 0x6e,0x63,0x2e,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x0c,0x09,0x43,0x75, + 0x70,0x65,0x72,0x74,0x69,0x6e,0x6f,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x08, + 0x0c,0x02,0x43,0x41,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, + 0x53,0x30,0x82,0x01,0x22,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01, + 0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0f,0x00,0x30,0x82,0x01,0x0a,0x02,0x82,0x01, + 0x01,0x00,0xb6,0xfb,0x13,0xd4,0x51,0x5a,0xe0,0x4e,0x35,0xb6,0xe0,0xc9,0xc3,0x78, + 0x5f,0xec,0x24,0x23,0xf2,0x40,0x0e,0xf8,0x6c,0x7f,0x9a,0xc8,0x71,0xd8,0x0a,0x3e, + 0xdb,0xf4,0x85,0xa5,0x11,0x0e,0x5e,0x89,0xd8,0xd9,0x86,0x5f,0xec,0x85,0xc1,0x36, + 0x4c,0xa5,0x40,0x8b,0x2a,0x66,0xe2,0x41,0x07,0x08,0x74,0xb2,0xe0,0x75,0x5e,0x7b, + 0xe0,0x10,0xf0,0x7f,0x69,0x44,0xa5,0xe5,0xfc,0xd4,0x00,0xc7,0x7e,0xe7,0xc5,0x50, + 0xcf,0xc0,0xe1,0xac,0x84,0xe9,0x08,0x2a,0xba,0x36,0xc4,0x60,0x37,0x07,0x91,0xe0, + 0xcd,0x55,0xef,0x3a,0xcf,0x73,0xb6,0xd6,0x60,0x46,0xa4,0x8a,0x65,0x6d,0xfc,0x77, + 0xa1,0xa0,0x12,0xef,0x20,0xc3,0xc3,0xf5,0x82,0x88,0x36,0x42,0x61,0xec,0x3d,0x3a, + 0xaf,0x73,0x55,0xda,0x4c,0xca,0x86,0x3d,0xbf,0x47,0x1d,0xdf,0x1f,0x41,0xbf,0x2a, + 0x97,0x84,0x85,0x13,0x1e,0x63,0xc5,0x7e,0x2a,0xb4,0xd4,0x56,0x2b,0xa1,0x93,0xc3, + 0xb8,0xb7,0x15,0x71,0xee,0xdc,0xe6,0x8b,0x08,0xe5,0xc9,0x04,0x9f,0xf6,0x87,0xd1, + 0xbf,0x78,0xf8,0x91,0x38,0x43,0x57,0x4b,0x8a,0x12,0x66,0xd6,0xae,0xf2,0xf1,0xa8, + 0x0b,0x1a,0x81,0xf6,0xfd,0x20,0x28,0x5f,0xaf,0xa8,0xe7,0xbc,0x90,0x09,0x85,0xd4, + 0xae,0x21,0x02,0x52,0x41,0xa8,0x03,0x61,0x89,0x11,0x86,0xf1,0xe7,0x49,0x3d,0x14, + 0x63,0x8a,0x69,0x5c,0x53,0x55,0xfe,0x25,0xbd,0xd0,0x2a,0xe8,0xe2,0x19,0xea,0x6f, + 0x20,0x6e,0xb3,0x85,0xa7,0x49,0xe7,0xcc,0x96,0xd8,0x3f,0x4d,0xad,0xe9,0x22,0x03, + 0x65,0x25,0x02,0x03,0x01,0x00,0x01,0xa3,0x81,0xa7,0x30,0x81,0xa4,0x30,0x0e,0x06, + 0x03,0x55,0x1d,0x0f,0x01,0x01,0xff,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x12,0x06, + 0x03,0x55,0x1d,0x13,0x01,0x01,0xff,0x04,0x08,0x30,0x06,0x01,0x01,0xff,0x02,0x01, + 0x00,0x30,0x1d,0x06,0x03,0x55,0x1d,0x0e,0x04,0x16,0x04,0x14,0x39,0xbb,0x91,0xb5, + 0x14,0x54,0x76,0x0c,0x1c,0x0c,0xf4,0x4b,0xbc,0xd9,0xf6,0x51,0xc8,0xe2,0x03,0xfc, + 0x30,0x1f,0x06,0x03,0x55,0x1d,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xba,0x56,0xd8, + 0x57,0x66,0xbd,0xfc,0x5b,0xe2,0x10,0xf2,0x39,0xb3,0xaf,0xb2,0x72,0xed,0x55,0x0f, + 0x1c,0x30,0x3e,0x06,0x03,0x55,0x1d,0x1f,0x04,0x37,0x30,0x35,0x30,0x33,0xa0,0x31, + 0xa0,0x2f,0x86,0x2d,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x76,0x61,0x6c,0x69,0x64, + 0x74,0x65,0x73,0x74,0x2e,0x61,0x70,0x70,0x6c,0x65,0x2e,0x67,0x65,0x6f,0x66,0x66, + 0x6b,0x2e,0x6e,0x65,0x74,0x2f,0x76,0x32,0x2d,0x72,0x6f,0x6f,0x74,0x2e,0x63,0x72, + 0x6c,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x0b,0x05,0x00, + 0x03,0x82,0x01,0x01,0x00,0x58,0xc7,0x67,0x96,0xb5,0x7a,0x36,0x26,0x63,0xe8,0xc1, + 0x7d,0xd9,0x0b,0x26,0xa4,0x2c,0x09,0xec,0x1e,0x67,0xfd,0x26,0xe4,0x5b,0x73,0x11, + 0x1d,0xfc,0xaf,0xa5,0x70,0x05,0x19,0xdb,0xb7,0x42,0x1b,0xd0,0x36,0x6f,0x8e,0x01, + 0xd3,0x19,0x72,0xff,0x36,0xa5,0x06,0x62,0x8b,0xd6,0x9b,0x40,0x24,0x54,0x48,0x4c, + 0x3f,0xeb,0x8d,0x36,0x9b,0x56,0x2e,0x27,0xf6,0x19,0xbf,0xae,0x07,0x20,0x7c,0x64, + 0x79,0xe3,0x4e,0x1d,0x44,0x26,0x79,0x4f,0x21,0x12,0x6e,0xe6,0x33,0xe3,0xd3,0x23, + 0x55,0xfb,0x49,0x8b,0x7b,0x5d,0xce,0xf5,0x58,0x3b,0x3f,0xb5,0xc6,0x33,0xc2,0x6f, + 0xf5,0xc5,0x0f,0x25,0xf9,0x65,0x79,0x67,0x5d,0xab,0xe8,0xe5,0x59,0x55,0xd3,0x62, + 0xf4,0xfa,0xee,0x65,0xfa,0x55,0x4e,0xc7,0x0e,0x5c,0xa9,0x48,0xc1,0x10,0x30,0x73, + 0xbd,0x5b,0xe3,0x75,0x2b,0xc3,0x20,0x10,0xc7,0x7d,0x3a,0x1d,0x8d,0x17,0x39,0xb5, + 0x58,0xae,0x91,0x32,0xe1,0xd2,0x98,0xfe,0x9a,0xdf,0x4b,0x0e,0x79,0x7b,0x8a,0x7a, + 0x32,0xc7,0x69,0xec,0xe4,0x8e,0xc5,0x4f,0x45,0x00,0x89,0xe6,0x53,0xa0,0x3d,0xee, + 0x1c,0x5a,0xee,0x4d,0x49,0xf2,0x54,0x3b,0xc0,0x4d,0xb4,0xa1,0x47,0x65,0x65,0x7a, + 0xbc,0x3d,0xda,0x54,0xee,0x56,0x74,0x22,0xa9,0x68,0x8e,0x70,0x48,0x2c,0x83,0x1e, + 0x35,0x08,0xb0,0xf2,0x9a,0xb1,0x43,0x88,0x07,0x5e,0xd6,0x94,0x0b,0x4a,0xa5,0x5b, + 0x17,0x47,0x8e,0xcc,0xfa,0xa0,0x24,0xd9,0x1a,0xad,0xad,0x23,0x4f,0xa3,0x59,0xf1, + 0x58,0x45,0x12,0x48,0xdc +}; + +uint8_t _leaf_serial_invalid_incomplete_ok1[] = { + 0x30,0x82,0x04,0x78,0x30,0x82,0x03,0x60,0xa0,0x03,0x02,0x01,0x02,0x02,0x05,0x00, + 0xcd,0xfb,0x60,0x2b,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01, + 0x0b,0x05,0x00,0x30,0x81,0x8e,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x03,0x0c, + 0x1c,0x43,0x41,0x20,0x73,0x65,0x72,0x69,0x61,0x6c,0x20,0x69,0x6e,0x76,0x61,0x6c, + 0x69,0x64,0x20,0x69,0x6e,0x63,0x6f,0x6d,0x70,0x6c,0x65,0x74,0x65,0x31,0x22,0x30, + 0x20,0x06,0x03,0x55,0x04,0x0b,0x0c,0x19,0x56,0x61,0x6c,0x69,0x64,0x20,0x50,0x72, + 0x6f,0x6a,0x65,0x63,0x74,0x20,0x54,0x65,0x73,0x74,0x20,0x43,0x41,0x73,0x20,0x56, + 0x32,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0a,0x0c,0x0a,0x41,0x70,0x70,0x6c, + 0x65,0x20,0x49,0x6e,0x63,0x2e,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x0c, + 0x09,0x43,0x75,0x70,0x65,0x72,0x74,0x69,0x6e,0x6f,0x31,0x0b,0x30,0x09,0x06,0x03, + 0x55,0x04,0x08,0x0c,0x02,0x43,0x41,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06, + 0x13,0x02,0x55,0x53,0x30,0x20,0x17,0x0d,0x31,0x37,0x30,0x35,0x30,0x31,0x32,0x32, + 0x34,0x30,0x30,0x37,0x5a,0x18,0x0f,0x32,0x31,0x31,0x37,0x30,0x34,0x30,0x37,0x32, + 0x32,0x34,0x30,0x30,0x37,0x5a,0x30,0x81,0x94,0x31,0x2b,0x30,0x29,0x06,0x03,0x55, + 0x04,0x03,0x0c,0x22,0x4c,0x65,0x61,0x66,0x20,0x73,0x65,0x72,0x69,0x61,0x6c,0x20, + 0x69,0x6e,0x76,0x61,0x6c,0x69,0x64,0x20,0x69,0x6e,0x63,0x6f,0x6d,0x70,0x6c,0x65, + 0x74,0x65,0x20,0x6f,0x6b,0x31,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x0b,0x0c, + 0x19,0x56,0x61,0x6c,0x69,0x64,0x20,0x50,0x72,0x6f,0x6a,0x65,0x63,0x74,0x20,0x54, + 0x65,0x73,0x74,0x20,0x43,0x41,0x73,0x20,0x56,0x32,0x31,0x13,0x30,0x11,0x06,0x03, + 0x55,0x04,0x0a,0x0c,0x0a,0x41,0x70,0x70,0x6c,0x65,0x20,0x49,0x6e,0x63,0x2e,0x31, + 0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x0c,0x09,0x43,0x75,0x70,0x65,0x72,0x74, + 0x69,0x6e,0x6f,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x0c,0x02,0x43,0x41, + 0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x30,0x82,0x01, + 0x22,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x01,0x05,0x00, + 0x03,0x82,0x01,0x0f,0x00,0x30,0x82,0x01,0x0a,0x02,0x82,0x01,0x01,0x00,0xc0,0x12, + 0xf8,0x4e,0x54,0xd5,0x0a,0xf6,0x43,0x16,0x4d,0xe2,0xe9,0x4c,0xb8,0x1c,0x05,0x6d, + 0x1b,0x0c,0xd5,0x7c,0x29,0x1e,0x82,0x5e,0x6a,0xb6,0x85,0x03,0x72,0x13,0x60,0x94, + 0xee,0x8d,0x56,0x03,0xd1,0x41,0x84,0x7c,0xf0,0xc1,0xd2,0x60,0xda,0x2e,0x56,0x8a, + 0xca,0x01,0xf6,0xbe,0x89,0x7a,0x27,0x44,0x6d,0x28,0x2c,0xfa,0x02,0x36,0xf7,0xa7, + 0x0d,0x4b,0x27,0x83,0x90,0x3c,0x14,0x1e,0x73,0xa5,0x61,0xc0,0xf4,0x05,0x43,0xed, + 0x36,0x34,0x14,0x7a,0xe0,0x9b,0x4e,0xbb,0x0f,0x12,0x97,0xcb,0x1b,0x99,0x46,0x41, + 0x3e,0xcb,0xa1,0xc3,0xab,0x43,0x27,0x45,0x37,0x61,0x94,0xe3,0xe8,0x60,0xd3,0x94, + 0x21,0x6f,0xd8,0x61,0x28,0x81,0xb5,0xe3,0x0f,0xfd,0x2f,0x61,0xe4,0xa4,0x5c,0x25, + 0x91,0x58,0xa0,0x55,0x5e,0xae,0x44,0x61,0x92,0x2b,0x99,0x13,0x1a,0xba,0x7c,0x1e, + 0xa1,0x2d,0xd2,0x02,0x78,0x9d,0xf2,0xfb,0x1a,0xd8,0xc2,0x01,0x19,0x76,0x30,0xf1, + 0x6a,0x65,0xd3,0x76,0xc7,0x67,0xb0,0xe8,0xa0,0x15,0xce,0x8d,0x90,0x45,0xe8,0x71, + 0x68,0x9f,0x56,0x91,0x22,0xe4,0xf4,0x16,0x69,0x2d,0x87,0x23,0x52,0x51,0x27,0x13, + 0x6e,0x7e,0xdf,0xf1,0x7e,0x67,0x5d,0x56,0xb8,0x8b,0x5d,0x75,0x09,0xb2,0x4a,0x3b, + 0x5e,0xac,0x8e,0x42,0xf4,0xb6,0xc2,0xf6,0x5c,0xde,0x9d,0xeb,0xf5,0x82,0xed,0x71, + 0xfa,0x23,0x94,0xd6,0x1c,0x19,0x65,0xa5,0xae,0x38,0xbd,0x57,0x27,0xfd,0x7c,0x2b, + 0x30,0x71,0xb1,0xa0,0x4a,0xac,0xbe,0xcb,0xe7,0x17,0x81,0x20,0xe9,0x8f,0x02,0x03, + 0x01,0x00,0x01,0xa3,0x81,0xd2,0x30,0x81,0xcf,0x30,0x0e,0x06,0x03,0x55,0x1d,0x0f, + 0x01,0x01,0xff,0x04,0x04,0x03,0x02,0x07,0x80,0x30,0x0c,0x06,0x03,0x55,0x1d,0x13, + 0x01,0x01,0xff,0x04,0x02,0x30,0x00,0x30,0x1f,0x06,0x03,0x55,0x1d,0x23,0x04,0x18, + 0x30,0x16,0x80,0x14,0xa2,0xee,0xae,0x1f,0x41,0x28,0x69,0x40,0x55,0xdf,0xc5,0x33, + 0x56,0xab,0xfc,0x23,0x68,0x17,0xcb,0x39,0x30,0x53,0x06,0x03,0x55,0x1d,0x1f,0x04, + 0x4c,0x30,0x4a,0x30,0x48,0xa0,0x46,0xa0,0x44,0x86,0x42,0x68,0x74,0x74,0x70,0x3a, + 0x2f,0x2f,0x76,0x61,0x6c,0x69,0x64,0x74,0x65,0x73,0x74,0x2e,0x61,0x70,0x70,0x6c, + 0x65,0x2e,0x67,0x65,0x6f,0x66,0x66,0x6b,0x2e,0x6e,0x65,0x74,0x2f,0x76,0x32,0x2d, + 0x73,0x65,0x72,0x69,0x61,0x6c,0x2d,0x69,0x6e,0x76,0x61,0x6c,0x69,0x64,0x2d,0x69, + 0x6e,0x63,0x6f,0x6d,0x70,0x6c,0x65,0x74,0x65,0x2e,0x63,0x72,0x6c,0x30,0x39,0x06, + 0x08,0x2b,0x06,0x01,0x05,0x05,0x07,0x01,0x01,0x04,0x2d,0x30,0x2b,0x30,0x29,0x06, + 0x08,0x2b,0x06,0x01,0x05,0x05,0x07,0x30,0x01,0x86,0x1d,0x68,0x74,0x74,0x70,0x3a, + 0x2f,0x2f,0x6b,0x6e,0x6f,0x77,0x6e,0x2d,0x61,0x6e,0x73,0x77,0x65,0x72,0x2d,0x6f, + 0x63,0x73,0x70,0x2f,0x6f,0x63,0x73,0x70,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86, + 0xf7,0x0d,0x01,0x01,0x0b,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x6c,0x93,0x89,0x44, + 0x96,0x48,0x63,0xa2,0x3c,0x68,0x26,0x3c,0xb5,0x36,0x4a,0x4c,0x3e,0xc4,0xd2,0x7c, + 0x2a,0x62,0xed,0x48,0x61,0x60,0x7f,0xe0,0x90,0xe9,0x31,0xf0,0x12,0xa3,0xdd,0x51, + 0x20,0xf2,0x4b,0xec,0x1c,0x0e,0x1c,0xd5,0xdc,0xbf,0x79,0x1a,0x4b,0xc4,0xd8,0x50, + 0x9d,0x9d,0x17,0xcb,0x0b,0x13,0x12,0x5f,0x54,0x20,0x05,0x5a,0x36,0x27,0x6b,0xef, + 0x89,0x34,0x96,0x73,0xae,0x9d,0xd2,0x1c,0x0a,0x98,0x39,0xec,0x83,0x2d,0xcd,0x47, + 0x50,0x41,0x5e,0xac,0x03,0x02,0x43,0x05,0xdd,0x34,0xbe,0x64,0x97,0xe3,0x2f,0xa0, + 0x70,0xc8,0xbd,0xc9,0x3e,0x6b,0x58,0x84,0x87,0xa5,0xea,0xbe,0xcc,0x7a,0xf8,0xe6, + 0xe4,0xa7,0x57,0x32,0xc0,0x3e,0x5a,0x38,0xdb,0xf9,0x95,0xdb,0xb2,0x18,0x09,0xab, + 0xbc,0x5c,0x22,0xe4,0x08,0xd7,0xd1,0xdf,0xd0,0xf6,0xef,0x02,0x0d,0xbc,0x9b,0xce, + 0x0e,0xcd,0x13,0x60,0x48,0xcc,0x5f,0x0e,0xef,0x38,0x13,0x3b,0x88,0x51,0xca,0xea, + 0x7e,0xb7,0xf9,0x53,0xa5,0xe2,0xe3,0x79,0xc5,0xe3,0x3f,0x3d,0x7b,0xfb,0x16,0xd8, + 0xcf,0xed,0x8e,0x02,0x4a,0x60,0xa2,0x2e,0x00,0x16,0xd6,0x1b,0x8a,0xbe,0x0b,0xb3, + 0x6b,0x91,0x10,0x97,0xf4,0xf0,0x39,0xa8,0x35,0xb8,0xc8,0x51,0x09,0x34,0x9e,0x0f, + 0xc8,0x52,0xad,0x72,0xb1,0xe8,0x39,0x64,0xa4,0xa2,0x96,0xa2,0x85,0xff,0xc9,0x63, + 0x45,0xe0,0xad,0xf0,0x15,0x91,0x66,0xd0,0x05,0x7b,0x39,0xbd,0x39,0x33,0xf8,0xd1, + 0x46,0x21,0x02,0xe7,0xc3,0x73,0xda,0x65,0xe8,0xa7,0x64,0xbd +}; + +uint8_t _ca_serial_invalid_incomplete[] = { + 0x30,0x82,0x04,0x40,0x30,0x82,0x03,0x28,0xa0,0x03,0x02,0x01,0x02,0x02,0x05,0x00, + 0x80,0x00,0x46,0x45,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01, + 0x0b,0x05,0x00,0x30,0x81,0x87,0x31,0x1e,0x30,0x1c,0x06,0x03,0x55,0x04,0x03,0x0c, + 0x15,0x56,0x61,0x6c,0x69,0x64,0x20,0x54,0x65,0x73,0x74,0x20,0x43,0x41,0x20,0x52, + 0x6f,0x6f,0x74,0x20,0x56,0x32,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x0b,0x0c, + 0x19,0x56,0x61,0x6c,0x69,0x64,0x20,0x50,0x72,0x6f,0x6a,0x65,0x63,0x74,0x20,0x54, + 0x65,0x73,0x74,0x20,0x43,0x41,0x73,0x20,0x56,0x32,0x31,0x13,0x30,0x11,0x06,0x03, + 0x55,0x04,0x0a,0x0c,0x0a,0x41,0x70,0x70,0x6c,0x65,0x20,0x49,0x6e,0x63,0x2e,0x31, + 0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x0c,0x09,0x43,0x75,0x70,0x65,0x72,0x74, + 0x69,0x6e,0x6f,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x0c,0x02,0x43,0x41, + 0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x30,0x20,0x17, + 0x0d,0x31,0x37,0x30,0x35,0x30,0x31,0x32,0x32,0x34,0x30,0x30,0x36,0x5a,0x18,0x0f, + 0x32,0x31,0x31,0x37,0x30,0x34,0x30,0x37,0x32,0x32,0x34,0x30,0x30,0x36,0x5a,0x30, + 0x81,0x8e,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x03,0x0c,0x1c,0x43,0x41,0x20, + 0x73,0x65,0x72,0x69,0x61,0x6c,0x20,0x69,0x6e,0x76,0x61,0x6c,0x69,0x64,0x20,0x69, + 0x6e,0x63,0x6f,0x6d,0x70,0x6c,0x65,0x74,0x65,0x31,0x22,0x30,0x20,0x06,0x03,0x55, + 0x04,0x0b,0x0c,0x19,0x56,0x61,0x6c,0x69,0x64,0x20,0x50,0x72,0x6f,0x6a,0x65,0x63, + 0x74,0x20,0x54,0x65,0x73,0x74,0x20,0x43,0x41,0x73,0x20,0x56,0x32,0x31,0x13,0x30, + 0x11,0x06,0x03,0x55,0x04,0x0a,0x0c,0x0a,0x41,0x70,0x70,0x6c,0x65,0x20,0x49,0x6e, + 0x63,0x2e,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x0c,0x09,0x43,0x75,0x70, + 0x65,0x72,0x74,0x69,0x6e,0x6f,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x0c, + 0x02,0x43,0x41,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53, + 0x30,0x82,0x01,0x22,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01, + 0x01,0x05,0x00,0x03,0x82,0x01,0x0f,0x00,0x30,0x82,0x01,0x0a,0x02,0x82,0x01,0x01, + 0x00,0xcd,0x06,0x79,0xc3,0xeb,0x86,0x31,0x75,0xe7,0xb5,0x9d,0xe3,0x20,0xd3,0x13, + 0x7c,0x90,0xfb,0xfe,0x51,0xd0,0x17,0x04,0x61,0x64,0x92,0xd2,0x63,0xe2,0x91,0x17, + 0x90,0x2e,0xb0,0xa6,0x03,0x8f,0xb2,0xc2,0x8c,0x32,0xf2,0xbe,0x51,0xad,0xee,0x72, + 0xf5,0x1f,0x15,0x94,0x72,0x8c,0x17,0xc1,0x47,0x3c,0x74,0xa2,0xb0,0x89,0x7d,0x96, + 0xa3,0x01,0xf7,0x14,0x21,0x80,0x0d,0x6e,0x5d,0x56,0x2a,0xcc,0xdc,0x7b,0xfc,0x83, + 0xe9,0xb4,0xbc,0x46,0x41,0x14,0xfb,0xb6,0xcc,0x4b,0x5a,0xa0,0x11,0x14,0x4c,0x92, + 0x2f,0x47,0xd5,0x3c,0xc9,0xe9,0xfa,0xb5,0x66,0x6b,0x3a,0xdf,0x1b,0x62,0x1a,0x44, + 0xa3,0x63,0xf3,0x1a,0x2f,0x46,0xdb,0xd0,0x9e,0x0d,0x9a,0xed,0xcd,0xf3,0xa0,0xb0, + 0xd9,0xf0,0xb0,0x58,0xe8,0xfe,0xb7,0x34,0x6c,0x87,0xfa,0xf6,0x96,0xe3,0x2d,0xc1, + 0xd5,0x54,0x56,0x88,0x61,0xa6,0xd6,0x58,0xbd,0x58,0x0d,0xc7,0x6f,0xe6,0x84,0xf8, + 0x2a,0x24,0xf2,0x97,0xca,0x8c,0xae,0xf9,0xa5,0xe2,0x5e,0x4a,0x9f,0xbf,0xd2,0x4b, + 0xe6,0x53,0x72,0xfc,0x05,0x93,0x72,0x40,0x0f,0xc5,0x26,0xda,0x00,0x35,0xe6,0x57, + 0xef,0xc2,0x28,0xcf,0x62,0xe9,0x27,0x05,0x1c,0x47,0x33,0x92,0x14,0x87,0xcb,0x8a, + 0x34,0xb3,0xb2,0xf8,0x88,0xe5,0x8c,0x44,0xdf,0x1b,0x62,0xbc,0xaa,0x9a,0x49,0x5b, + 0xff,0x4c,0x37,0x33,0xad,0x6f,0x98,0x61,0xce,0xc5,0xb5,0x02,0xe0,0x12,0x24,0xc1, + 0x59,0x28,0xd8,0x2b,0xea,0x94,0x82,0x2d,0x28,0x37,0xcc,0xf3,0x60,0x9f,0x08,0xe1, + 0x3f,0x02,0x03,0x01,0x00,0x01,0xa3,0x81,0xa7,0x30,0x81,0xa4,0x30,0x0e,0x06,0x03, + 0x55,0x1d,0x0f,0x01,0x01,0xff,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x12,0x06,0x03, + 0x55,0x1d,0x13,0x01,0x01,0xff,0x04,0x08,0x30,0x06,0x01,0x01,0xff,0x02,0x01,0x00, + 0x30,0x1d,0x06,0x03,0x55,0x1d,0x0e,0x04,0x16,0x04,0x14,0xa2,0xee,0xae,0x1f,0x41, + 0x28,0x69,0x40,0x55,0xdf,0xc5,0x33,0x56,0xab,0xfc,0x23,0x68,0x17,0xcb,0x39,0x30, + 0x1f,0x06,0x03,0x55,0x1d,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xba,0x56,0xd8,0x57, + 0x66,0xbd,0xfc,0x5b,0xe2,0x10,0xf2,0x39,0xb3,0xaf,0xb2,0x72,0xed,0x55,0x0f,0x1c, + 0x30,0x3e,0x06,0x03,0x55,0x1d,0x1f,0x04,0x37,0x30,0x35,0x30,0x33,0xa0,0x31,0xa0, + 0x2f,0x86,0x2d,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x76,0x61,0x6c,0x69,0x64,0x74, + 0x65,0x73,0x74,0x2e,0x61,0x70,0x70,0x6c,0x65,0x2e,0x67,0x65,0x6f,0x66,0x66,0x6b, + 0x2e,0x6e,0x65,0x74,0x2f,0x76,0x32,0x2d,0x72,0x6f,0x6f,0x74,0x2e,0x63,0x72,0x6c, + 0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x0b,0x05,0x00,0x03, + 0x82,0x01,0x01,0x00,0x43,0xa4,0x08,0x47,0x8c,0x5c,0x79,0x0b,0x5f,0xe2,0xe7,0xec, + 0x0e,0x70,0x69,0xee,0xd1,0xbb,0xda,0x02,0x06,0xcf,0x75,0xbe,0xe9,0xbf,0xfd,0xf7, + 0xa0,0x3b,0x8e,0xf0,0x01,0x6c,0x1b,0x82,0x7d,0xeb,0x6f,0xaa,0x7a,0x3c,0xef,0x7b, + 0xd9,0x9c,0x74,0x48,0x2a,0x2a,0xd1,0xa3,0x0d,0xee,0xe8,0xa3,0x55,0x83,0xd6,0xa4, + 0x3a,0xb1,0x2b,0xf1,0x44,0x8e,0x3d,0xec,0xc7,0x74,0x73,0x1b,0xaf,0xf1,0xee,0xea, + 0x69,0x8a,0xa2,0x88,0xf6,0x82,0x6a,0x2d,0x21,0xfd,0x0c,0xf2,0x50,0x6a,0x34,0x81, + 0x43,0x8e,0x81,0x0a,0xfa,0x26,0xc4,0x4a,0x10,0x60,0x85,0xb7,0xf3,0xdc,0xd2,0xc7, + 0xe2,0xe7,0x17,0x94,0x82,0x68,0x33,0xa3,0xd4,0x19,0xcb,0x10,0x16,0x65,0x4c,0x6b, + 0xe7,0x3f,0x3a,0xbe,0x1b,0x3b,0x08,0x0c,0x16,0x0e,0xe0,0x7f,0x75,0x44,0xa9,0xfa, + 0x6e,0x2f,0xd1,0x27,0x9b,0xc5,0x4e,0xec,0xac,0xb5,0xaf,0x2b,0xdf,0x10,0xbc,0x38, + 0x85,0x4c,0x00,0x18,0x86,0x30,0x40,0x2d,0xab,0xf4,0xc3,0x1e,0xfb,0x3b,0xdf,0xa6, + 0x72,0xb9,0x5e,0x24,0x81,0x39,0x92,0x02,0xf9,0xc9,0xc1,0xfc,0x35,0x1a,0x0a,0x0c, + 0x2c,0xd4,0x4e,0xae,0x03,0x79,0x22,0xd5,0x97,0x29,0xf6,0x13,0x2f,0xce,0xb6,0x69, + 0x5e,0x4f,0xc6,0x0c,0x06,0x00,0x28,0x7c,0x14,0x3e,0xab,0x32,0xaf,0xd6,0x02,0x04, + 0x18,0xc8,0xdc,0x9c,0xaf,0x23,0x08,0xc3,0xca,0x3e,0xbc,0xf1,0x44,0xc5,0x43,0x91, + 0x00,0x39,0x46,0x15,0x88,0xce,0xe9,0xe3,0xf5,0xae,0xdc,0x5b,0x63,0x77,0xd5,0xc8, + 0xf9,0x35,0x0b,0xe1 +}; + #endif /* _TRUSTTESTS_REVOCATION_TESTS_H_ */ diff --git a/tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.h b/tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.h index 14836b9f..021a4a16 100644 --- a/tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.h +++ b/tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.h @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)runCertificateTestForDirectory:(SecPolicyRef)policy subDirectory:(NSString *)resourceSubDirectory verifyDate:(NSDate*)date; - (id _Nullable) CF_RETURNS_RETAINED SecCertificateCreateFromResource:(NSString * )name subdirectory:(NSString *)dir; +- (id _Nullable) CF_RETURNS_RETAINED SecCertificateCreateFromPEMResource:(NSString *)name subdirectory:(NSString *)dir; @end /* Use this interface to get a SecCertificateRef that has the same CFTypeID diff --git a/tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.m b/tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.m index 7e8378c0..99f8383f 100644 --- a/tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.m +++ b/tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.m @@ -182,7 +182,24 @@ const CFStringRef kTestSystemRootKey = CFSTR("TestSystemRoot"); NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:name withExtension:@".cer" subdirectory:dir]; NSData *certData = [NSData dataWithContentsOfURL:url]; - SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)certData); + if (!certData) { + return nil; + } + SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)certData); + return (__bridge id)cert; +} + +- (id _Nullable) CF_RETURNS_RETAINED SecCertificateCreateFromPEMResource:(NSString *)name + subdirectory:(NSString *)dir +{ + NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:name withExtension:@".pem" + subdirectory:dir]; + NSData *certData = [NSData dataWithContentsOfURL:url]; + if (!certData) { + return nil; + } + + SecCertificateRef cert = SecCertificateCreateWithPEM(kCFAllocatorDefault, (__bridge CFDataRef)certData); return (__bridge id)cert; } diff --git a/tests/TrustTests/TrustEvaluationTestHelpers.h b/tests/TrustTests/TrustEvaluationTestHelpers.h index c02a959c..f4c27614 100644 --- a/tests/TrustTests/TrustEvaluationTestHelpers.h +++ b/tests/TrustTests/TrustEvaluationTestHelpers.h @@ -56,6 +56,7 @@ int ping_host(char *host_name); - (instancetype _Nullable) initWithTrustDictionary:(NSDictionary *)testDict; - (void)addAnchor:(SecCertificateRef)certificate; +- (void)setNeedsEvaluation; - (bool)evaluate:(out NSError * _Nullable __autoreleasing * _Nullable)outError; - (bool)evaluateForExpectedResults:(out NSError * _Nullable __autoreleasing *)outError; diff --git a/tests/TrustTests/TrustEvaluationTestHelpers.m b/tests/TrustTests/TrustEvaluationTestHelpers.m index b62f9112..a7ee45d9 100644 --- a/tests/TrustTests/TrustEvaluationTestHelpers.m +++ b/tests/TrustTests/TrustEvaluationTestHelpers.m @@ -106,6 +106,10 @@ } } +- (void)setNeedsEvaluation { + SecTrustSetNeedsEvaluation(_trust); +} + - (bool)evaluate:(out NSError * _Nullable __autoreleasing *)outError { CFErrorRef localError = nil; _trustResult = kSecTrustResultInvalid; diff --git a/tests/secdmockaks/mockaksKeychain.m b/tests/secdmockaks/mockaksKeychain.m index d69762fe..9a5280f3 100644 --- a/tests/secdmockaks/mockaksKeychain.m +++ b/tests/secdmockaks/mockaksKeychain.m @@ -31,6 +31,7 @@ #include "OSX/sec/Security/SecItemShim.h" #import "server_security_helpers.h" #import "spi.h" +#import "utilities/SecAKSWrappers.h" #import #import #import "utilities/der_plist.h" @@ -363,7 +364,8 @@ - (void)testCreateSampleDatabase { -#if USE_KEYSTORE + // The keychain code only does the right thing with generation count if TARGET_HAS_KEYSTORE +#if TARGET_HAS_KEYSTORE id mock = OCMClassMock([SecMockAKS class]); OCMStub([mock useGenerationCount]).andReturn(true); #endif @@ -383,16 +385,22 @@ */ [self findManyItems:50]; + +#if TARGET_HAS_KEYSTORE + [mock stopMocking]; +#endif } - (void)testTestAKSGenerationCount { -#if USE_KEYSTORE +#if TARGET_HAS_KEYSTORE id mock = OCMClassMock([SecMockAKS class]); OCMStub([mock useGenerationCount]).andReturn(true); [self createManyItems]; [self findManyItems:50]; + + [mock stopMocking]; #endif } diff --git a/trust/headers/SecPolicyPriv.h b/trust/headers/SecPolicyPriv.h index 781fb65f..15019c8e 100644 --- a/trust/headers/SecPolicyPriv.h +++ b/trust/headers/SecPolicyPriv.h @@ -187,6 +187,11 @@ extern const CFStringRef kSecPolicyAppleKeyTransparency API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0)); extern const CFStringRef kSecPolicyAppleLegacySSL API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0)); +extern const CFStringRef kSecPolicyAppleAlisha + API_AVAILABLE(macos(10.15.4), ios(13.4), watchos(6.2), tvos(13.4)); +extern const CFStringRef kSecPolicyAppleMeasuredBootPolicySigning + API_AVAILABLE(macos(10.15.4), ios(13.4), watchos(6.2), tvos(13.4)); + /*! @enum Policy Name Constants (Private) @@ -209,6 +214,7 @@ extern const CFStringRef kSecPolicyAppleLegacySSL @constant kSecPolicyNameAppleAMPService @constant kSecPolicyNameAppleSiriService @constant kSecPolicyNameAppleHomeAppClipUploadService + @constant kSecPolicyNameAppleUpdatesService */ extern const CFStringRef kSecPolicyNameAppleAST2Service __OSX_AVAILABLE(10.13) __IOS_AVAILABLE(11.0) __TVOS_AVAILABLE(11.0) __WATCHOS_AVAILABLE(4.0); @@ -244,6 +250,8 @@ extern const CFStringRef kSecPolicyNameAppleSiriService API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0)); extern const CFStringRef kSecPolicyNameAppleHomeAppClipUploadService API_AVAILABLE(macos(10.15.1), ios(13.2), watchos(6.1), tvos(13.1)); +extern const CFStringRef kSecPolicyNameAppleUpdatesService + API_AVAILABLE(macos(10.15.4), ios(13.4), watchos(6.2), tvos(13.4)); /*! @enum Policy Value Constants @@ -1790,7 +1798,7 @@ SecPolicyRef SecPolicyCreateAppleComponentCertificate(CFDataRef __nullable testR pinning options: * The chain is anchored to any of the production Apple Root CAs. * There are exactly 3 certs in the chain. - * The intermediate has a marker extension with OID TBD. + * The intermediate has a marker extension with OID 1.2.840.113635.100.6.2.3". * The leaf has a marker extension with OID 1.2.840.113635.100.6.69.1 and value matching the applicationId. * Revocation is checked via any available method. @@ -1821,6 +1829,37 @@ __nullable CF_RETURNS_RETAINED SecPolicyRef SecPolicyCreateLegacySSL(Boolean server, CFStringRef __nullable hostname) SPI_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0)); +/*! + @function SecPolicyCreateAlisha + @abstract Returns a policy object for verifying Alisha certificates. + @discussion The resulting policy uses the Basic X.509 policy with no validity check and + pinning options: + * EC key sizes are P-256 or larger. + @result A policy object. The caller is responsible for calling CFRelease on this when + it is no longer needed. + */ +__nullable CF_RETURNS_RETAINED +SecPolicyRef SecPolicyCreateAlisha(void) + API_AVAILABLE(macos(10.15.4), ios(13.4), watchos(6.2), tvos(13.4)); + +/*! + @function SecPolicyCreateMeasuredBootPolicySigning + @abstract Returns a policy object for verifying Measured Boot Policy Signing certificates. + @discussion The resulting policy uses the Basic X.509 policy with no validity check and + pinning options: + * There are exactly 3 certs in the chain. + * The intermediate has a marker extension with OID 1.2.840.113635.100.6.24.17. + * The leaf has a marker extension with OID 1.2.840.113635.100.6.26.6.1 + * RSA key sizes are 2048-bit or larger. EC key sizes are P-256 or larger. + Because this policy does not pin the anchors, the caller must use SecTrustSetAnchorCertificates with + the expected roots. + @result A policy object. The caller is responsible for calling CFRelease on this when + it is no longer needed. + */ +__nullable CF_RETURNS_RETAINED +SecPolicyRef SecPolicyCreateMeasuredBootPolicySigning(void) + API_AVAILABLE(macos(10.15.4), ios(13.4), watchos(6.2), tvos(13.4)); + /* * Legacy functions (OS X only) */ diff --git a/trust/trustd/SecCertificateServer.c b/trust/trustd/SecCertificateServer.c index 6ddca031..ebd9350f 100644 --- a/trust/trustd/SecCertificateServer.c +++ b/trust/trustd/SecCertificateServer.c @@ -891,6 +891,25 @@ CFAbsoluteTime SecCertificatePathVCGetEarliestNextUpdate(SecCertificatePathVCRef return enu; } +bool SecCertificatePathVCRevocationCheckedAllCerts(SecCertificatePathVCRef path) { + CFIndex certIX, certCount = path->count; + if (certCount <= 1 || !path->rvcs) { + /* If there is only one certificate, it's the root, so revocation checking is irrelevant. */ + return false; + } + + for (certIX = 0; certIX < path->rvcCount - 1; ++certIX) { + SecRVCRef rvc = &((SecRVCRef)path->rvcs)[certIX]; + if (!SecRVCRevocationChecked(rvc)) { + secdebug("rvc", "revocation has not been checked for all certs (not checked for cert %ld)", certIX); + return false; + } + } + + secdebug("rvc", "revocation has been checked for all certs"); + return true; +} + void SecCertificatePathVCSetRevocationReasonForCertificateAtIndex(SecCertificatePathVCRef certificatePath, CFIndex ix, CFNumberRef revocationReason) { if (ix > certificatePath->count - 1) { return; } diff --git a/trust/trustd/SecCertificateServer.h b/trust/trustd/SecCertificateServer.h index 91486de4..8a144340 100644 --- a/trust/trustd/SecCertificateServer.h +++ b/trust/trustd/SecCertificateServer.h @@ -129,6 +129,7 @@ bool SecCertificatePathVCIsRevocationDone(SecCertificatePathVCRef certificatePat void SecCertificatePathVCAllocateRVCs(SecCertificatePathVCRef certificatePath, CFIndex certCount); CFAbsoluteTime SecCertificatePathVCGetEarliestNextUpdate(SecCertificatePathVCRef path); void *SecCertificatePathVCGetRVCAtIndex(SecCertificatePathVCRef certificatePath, CFIndex ix); // Returns a SecRVCRef +bool SecCertificatePathVCRevocationCheckedAllCerts(SecCertificatePathVCRef path); bool SecCertificatePathVCIsRevocationRequiredForCertificateAtIndex(SecCertificatePathVCRef certificatePath, CFIndex ix); void SecCertificatePathVCSetRevocationRequiredForCertificateAtIndex(SecCertificatePathVCRef certificatePath, diff --git a/trust/trustd/SecOCSPCache.c b/trust/trustd/SecOCSPCache.c index 0363c6c3..2fc2a03f 100644 --- a/trust/trustd/SecOCSPCache.c +++ b/trust/trustd/SecOCSPCache.c @@ -246,7 +246,9 @@ static void SecOCSPCacheWith(void(^cacheJob)(SecOCSPCacheRef cache)) { CFRelease(dbPath); } } - cacheJob(kSecOCSPCache); + if (kSecOCSPCache) { + cacheJob(kSecOCSPCache); + } os_unfair_lock_unlock(&cacheLock); } diff --git a/trust/trustd/SecPolicyServer.c b/trust/trustd/SecPolicyServer.c index d17d3bbd..2cec6688 100644 --- a/trust/trustd/SecPolicyServer.c +++ b/trust/trustd/SecPolicyServer.c @@ -2783,8 +2783,8 @@ if (__PC_TYPE_MEMBER_##TRUSTRESULT && CFEqual(key,CFSTR(#NAME))) { \ policy->_options is a caller provided dictionary, only its cf type has been checked. */ -bool SecPVCSetResultForced(SecPVCRef pvc, - CFStringRef key, CFIndex ix, CFTypeRef result, bool force) { +bool SecPVCSetResultForcedWithTrustResult(SecPVCRef pvc, CFStringRef key, CFIndex ix, CFTypeRef result, bool force, + SecTrustResultType overrideDefaultTR) { /* If this is not something the current policy cares about ignore this error and return true so our caller continues evaluation. */ @@ -2797,17 +2797,27 @@ bool SecPVCSetResultForced(SecPVCRef pvc, } } - /* Check to see if the SecTrustSettings for the certificate in question - tell us to ignore this error. */ - if (SecPVCIsAllowedError(pvc, ix, key)) { - secinfo("policy", "cert[%d]: skipped allowed error %@", (int) ix, key); - return true; - } + /* Get the default trust result for this key and override it if the caller needs to + * set a different trust result than the default. */ + SecTrustResultType trustResult = trust_result_for_key(key); + if (overrideDefaultTR != kSecTrustResultInvalid) { + trustResult = overrideDefaultTR; + } - /* Check to see if exceptions tells us to ignore this error. */ - if (SecPVCIsExceptedError(pvc, ix, key, result)) { - secinfo("policy", "cert[%d]: skipped exception error %@", (int) ix, key); - return true; + /* only recoverable errors can be allowed/excepted */ + if (trustResult == kSecTrustResultRecoverableTrustFailure) { + /* Check to see if the SecTrustSettings for the certificate in question + tell us to ignore this error. */ + if (SecPVCIsAllowedError(pvc, ix, key)) { + secinfo("policy", "cert[%d]: skipped allowed error %@", (int) ix, key); + return true; + } + + /* Check to see if exceptions tells us to ignore this error. */ + if (SecPVCIsExceptedError(pvc, ix, key, result)) { + secinfo("policy", "cert[%d]: skipped exception error %@", (int) ix, key); + return true; + } } secnotice("policy", "cert[%d]: %@ =(%s)[%s]> %@", (int) ix, key, @@ -2817,7 +2827,6 @@ bool SecPVCSetResultForced(SecPVCRef pvc, (force ? "force" : ""), result); /* Avoid resetting deny or fatal to recoverable */ - SecTrustResultType trustResult = trust_result_for_key(key); if (SecPVCIsOkResult(pvc) || trustResult == kSecTrustResultFatalTrustFailure) { pvc->result = trustResult; } else if (trustResult == kSecTrustResultDeny && @@ -2843,6 +2852,10 @@ bool SecPVCSetResultForced(SecPVCRef pvc, return true; } +bool SecPVCSetResultForced(SecPVCRef pvc, CFStringRef key, CFIndex ix, CFTypeRef result, bool force) { + return SecPVCSetResultForcedWithTrustResult(pvc, key, ix, result, force, kSecTrustResultInvalid); +} + bool SecPVCSetResult(SecPVCRef pvc, CFStringRef key, CFIndex ix, CFTypeRef result) { return SecPVCSetResultForced(pvc, key, ix, result, false); @@ -3649,12 +3662,12 @@ static void SecPVCCheckRequireCTConstraints(SecPVCRef pvc) { if (!SecOTAPKIKillSwitchEnabled(otaref, kOTAPKIKillSwitchCT) && SecOTAPKIAssetStalenessLessThanSeconds(otaref, kSecOTAPKIAssetStalenessDisable)) { /* CT was required. Error is always set on leaf certificate. */ - SecPVCSetResultForced(pvc, kSecPolicyCheckCTRequired, - 0, kCFBooleanFalse, true); if (ctp != kSecPathCTRequiredOverridable) { - /* Normally kSecPolicyCheckCTRequired is recoverable, - so need to manually change trust result here. */ - pvc->result = kSecTrustResultFatalTrustFailure; + /* Normally kSecPolicyCheckCTRequired is recoverable */ + SecPVCSetResultForcedWithTrustResult(pvc, kSecPolicyCheckCTRequired, 0, kCFBooleanFalse, true, + kSecTrustResultFatalTrustFailure); + } else { + SecPVCSetResultForced(pvc, kSecPolicyCheckCTRequired, 0, kCFBooleanFalse, true); } } CFReleaseNull(otaref); diff --git a/trust/trustd/SecPolicyServer.h b/trust/trustd/SecPolicyServer.h index fbeebb82..44811a01 100644 --- a/trust/trustd/SecPolicyServer.h +++ b/trust/trustd/SecPolicyServer.h @@ -49,11 +49,10 @@ SecPolicyRef SecPVCGetPolicy(SecPVCRef pv); /* Set the string result as the reason for the sub policy check key failing. The policy check function should continue processing if this function returns true. */ -bool SecPVCSetResult(SecPVCRef pv, CFStringRef key, CFIndex ix, - CFTypeRef result); -bool SecPVCSetResultForced(SecPVCRef pvc, - CFStringRef key, CFIndex ix, CFTypeRef result, bool force); -bool SecPVCIsOkResult(SecPVCRef pvc); +bool SecPVCSetResult(SecPVCRef pv, CFStringRef key, CFIndex ix, CFTypeRef result); +bool SecPVCSetResultForced(SecPVCRef pvc, CFStringRef key, CFIndex ix, CFTypeRef result, bool force); +bool SecPVCSetResultForcedWithTrustResult(SecPVCRef pvc, CFStringRef key, CFIndex ix, CFTypeRef result, bool force, + SecTrustResultType overrideDefaultTR); /* Is the current result considered successful. */ bool SecPVCIsOkResult(SecPVCRef pvc); diff --git a/trust/trustd/SecRevocationServer.c b/trust/trustd/SecRevocationServer.c index 266bdba3..b0b5ac86 100644 --- a/trust/trustd/SecRevocationServer.c +++ b/trust/trustd/SecRevocationServer.c @@ -289,6 +289,9 @@ void SecORVCConsumeOCSPResponse(SecORVCRef rvc, SecOCSPResponseRef ocspResponse // but we haven't checked dates yet. bool sr_valid = SecOCSPSingleResponseCalculateValidity(sr, kSecDefaultOCSPResponseTTL, verifyTime); + if (sr_valid) { + rvc->rvc->revocation_checked = true; + } if (sr->certStatus == CS_Good) { // Side effect of SecOCSPResponseCalculateValidity sets ocspResponse->expireTime require_quiet(sr_valid && SecOCSPResponseCalculateValidity(ocspResponse, maxAge, kSecDefaultOCSPResponseTTL, verifyTime), errOut); @@ -479,12 +482,11 @@ static void SecRVCProcessValidPolicyConstraints(SecRVCRef rvc) { SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(policies, policyIX); if (!SecRVCPolicyConstraintsPermitPolicy(constraints, count, policy)) { policyDeniedByConstraints = true; - SecPVCSetResultForced(pvc, kSecPolicyCheckIssuerPolicyConstraints, rvc->certIX, - kCFBooleanFalse, true); - pvc->result = kSecTrustResultRecoverableTrustFailure; - if (!rvc->valid_info->overridable) { - /* error for this check should be non-recoverable */ - pvc->result = kSecTrustResultFatalTrustFailure; + if (rvc->valid_info->overridable) { + SecPVCSetResultForcedWithTrustResult(pvc, kSecPolicyCheckIssuerPolicyConstraints, rvc->certIX, + kCFBooleanFalse, true, kSecTrustResultRecoverableTrustFailure); + } else { + SecPVCSetResultForced(pvc, kSecPolicyCheckIssuerPolicyConstraints, rvc->certIX, kCFBooleanFalse, true); } } } @@ -614,6 +616,10 @@ void SecRVCSetValidDeterminedErrorResult(SecRVCRef rvc) { CFReleaseNull(cfreason); } +bool SecRVCRevocationChecked(SecRVCRef rvc) { + return rvc->revocation_checked; +} + static void SecRVCProcessValidInfoResults(SecRVCRef rvc) { if (!rvc || !rvc->valid_info || !rvc->builder) { return; @@ -645,6 +651,7 @@ static void SecRVCProcessValidInfoResults(SecRVCRef rvc) { } else if (allowed) { /* definitely not revoked (allowlisted) */ SecCertificatePathVCSetIsAllowlisted(path, true); + rvc->revocation_checked = true; } /* no-ca is definitive; no need to check further. */ secdebug("validupdate", "rvc: definitely %s cert %" PRIdCFIndex, diff --git a/trust/trustd/SecRevocationServer.h b/trust/trustd/SecRevocationServer.h index b3f7a090..ef41918f 100644 --- a/trust/trustd/SecRevocationServer.h +++ b/trust/trustd/SecRevocationServer.h @@ -52,6 +52,8 @@ struct OpaqueSecRVC { SecValidInfoRef valid_info; bool done; + + bool revocation_checked; }; typedef struct OpaqueSecRVC *SecRVCRef; @@ -91,6 +93,7 @@ void SecRVCDelete(SecRVCRef rvc); bool SecRVCHasDefinitiveValidInfo(SecRVCRef rvc); bool SecRVCHasRevokedValidInfo(SecRVCRef rvc); void SecRVCSetValidDeterminedErrorResult(SecRVCRef rvc); +bool SecRVCRevocationChecked(SecRVCRef rvc); /* OCSP verification callbacks */ void SecORVCConsumeOCSPResponse(SecORVCRef rvc, SecOCSPResponseRef ocspResponse /*CF_CONSUMED*/, diff --git a/trust/trustd/SecTrustServer.c b/trust/trustd/SecTrustServer.c index 06f56077..8e1eeee0 100644 --- a/trust/trustd/SecTrustServer.c +++ b/trust/trustd/SecTrustServer.c @@ -1210,6 +1210,11 @@ static bool SecPathBuilderReportResult(SecPathBuilderRef builder) { kCFBooleanFalse); /* iOS key */ CFDictionarySetValue(builder->info, kSecTrustRevocationChecked, kCFBooleanFalse); /* unified API key */ + } else if (SecCertificatePathVCRevocationCheckedAllCerts(builder->bestPath)) { + CFDictionarySetValue(builder->info, kSecTrustInfoRevocationKey, + kCFBooleanTrue); /* iOS key */ + CFDictionarySetValue(builder->info, kSecTrustRevocationChecked, + kCFBooleanTrue); /* unified API key */ } } -- 2.45.2