]> git.saurik.com Git - apple/security.git/commitdiff
Security-59306.101.1.tar.gz macos-10154 v59306.101.1
authorApple <opensource@apple.com>
Fri, 1 May 2020 18:19:48 +0000 (18:19 +0000)
committerApple <opensource@apple.com>
Fri, 1 May 2020 18:19:48 +0000 (18:19 +0000)
319 files changed:
Analytics/SFAnalytics+Internal.h
Analytics/SFAnalytics.m
Analytics/SFAnalytics.plist
Analytics/SFAnalyticsDefines.h
CircleJoinRequested/CircleJoinRequested.m
KeychainCircle/KCJoiningAcceptSession+Internal.h
KeychainCircle/KCJoiningAcceptSession.m
KeychainCircle/KCJoiningMessages.h
KeychainCircle/KCJoiningRequestCircleSession.m
KeychainCircle/KCJoiningRequestSecretSession.m
KeychainCircle/KCJoiningRequestSession+Internal.h
KeychainCircle/PairingChannel.h
KeychainCircle/PairingChannel.m
KeychainCircle/Tests/FakeSOSControl.m
KeychainCircle/Tests/KCJoiningSessionTest.m
KeychainCircle/Tests/KCTLKRequestTest.m [new file with mode: 0644]
OSX/Keychain Circle Notification/KNAppDelegate.m
OSX/authd/authorization.plist
OSX/lib/en.lproj/authorization.prompts.strings
OSX/libsecurity_cdsa_utilities/lib/cssmdb.h
OSX/libsecurity_codesigning/lib/diskimagerep.cpp
OSX/libsecurity_codesigning/lib/diskimagerep.h
OSX/libsecurity_codesigning/lib/notarization.cpp
OSX/libsecurity_codesigning/lib/signer.cpp
OSX/libsecurity_keychain/lib/SecItem.cpp
OSX/libsecurity_smime/lib/cmsdigest.c
OSX/libsecurity_smime/lib/cmspriv.h
OSX/libsecurity_smime/lib/cmsutil.c
OSX/libsecurityd/lib/sstransit.cpp
OSX/sec/Security/Regressions/secitem/si-27-sectrust-exceptions.c [deleted file]
OSX/sec/Security/SecAccessControl.m
OSX/sec/Security/SecCTKKey.m
OSX/sec/Security/SecCertificate.c
OSX/sec/Security/SecExports.exp-in
OSX/sec/Security/SecItem.c
OSX/sec/Security/SecItemInternal.h
OSX/sec/Security/SecOTRPacketData.h
OSX/sec/Security/SecPolicy.c
OSX/sec/Security/SecPolicy.list
OSX/sec/Security/SecuritydXPC.c
OSX/sec/ipc/securityd_client.h
OSX/sec/ipc/server.c
OSX/shared_regressions/shared_regressions.h
OSX/shared_regressions/si-44-seckey-aks.m
OSX/utilities/SecCFWrappers.h
OSX/utilities/SecDb.c
OSX/utilities/SecDb.h
OSX/utilities/SecXPCHelper.h
OSX/utilities/SecXPCHelper.m
Security.exp-in
Security.xcodeproj/project.pbxproj
Security.xcodeproj/xcshareddata/xcschemes/CKKSTests.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/OctagonTests.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/TrustTests_ios.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/TrustTests_macos.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/TrustedPeers.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/ios - Debug.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/ios - Release.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/ios - secdtests.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/osx - World.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/osx - secdtests.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/osx - sectests.xcscheme
Security.xcodeproj/xcshareddata/xcschemes/secdmockaks.xcscheme
SecurityTool/sharedTool/sos.m
SecurityTool/sharedTool/sub_commands.h
experiment/SecExperimentInternal.h
keychain/ResetCloudKeychainAccount/reset_ick_account [new file with mode: 0644]
keychain/SecureObjectSync/Regressions/SOSCircle_regressions.h
keychain/SecureObjectSync/Regressions/sc-150-ring.m [deleted file]
keychain/SecureObjectSync/Regressions/sc-kvstool.m [deleted file]
keychain/SecureObjectSync/SOSAccount.h
keychain/SecureObjectSync/SOSAccount.m
keychain/SecureObjectSync/SOSAccountBackup.m
keychain/SecureObjectSync/SOSAccountConfiguration.proto
keychain/SecureObjectSync/SOSAccountCredentials.m
keychain/SecureObjectSync/SOSAccountPeers.m
keychain/SecureObjectSync/SOSAccountPersistence.m
keychain/SecureObjectSync/SOSAccountPriv.h
keychain/SecureObjectSync/SOSAccountRecovery.m
keychain/SecureObjectSync/SOSAccountRings.m
keychain/SecureObjectSync/SOSAccountTransaction.m
keychain/SecureObjectSync/SOSAccountUpdate.m
keychain/SecureObjectSync/SOSBackupInformation.h [deleted file]
keychain/SecureObjectSync/SOSBackupInformation.m [deleted file]
keychain/SecureObjectSync/SOSCircle.c
keychain/SecureObjectSync/SOSCloudCircle.h
keychain/SecureObjectSync/SOSCloudCircle.m
keychain/SecureObjectSync/SOSCloudCircleInternal.h
keychain/SecureObjectSync/SOSControlHelper.m
keychain/SecureObjectSync/SOSControlServer.m
keychain/SecureObjectSync/SOSExports.exp-in
keychain/SecureObjectSync/SOSFullPeerInfo.h
keychain/SecureObjectSync/SOSFullPeerInfo.m
keychain/SecureObjectSync/SOSPeerInfo.h
keychain/SecureObjectSync/SOSPeerInfo.m
keychain/SecureObjectSync/SOSPeerOTRTimer.m
keychain/SecureObjectSync/SOSRing.h
keychain/SecureObjectSync/SOSRingTypes.m
keychain/SecureObjectSync/SOSRingUtils.c
keychain/SecureObjectSync/SOSRingV0.m
keychain/SecureObjectSync/SOSTypes.h
keychain/SecureObjectSync/Tool/keychain_sync.h
keychain/SecureObjectSync/Tool/keychain_sync.m
keychain/SecureObjectSync/Tool/recovery_key.m
keychain/SecureObjectSync/Tool/syncbackup.h [deleted file]
keychain/SecureObjectSync/Tool/syncbackup.m [deleted file]
keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.h
keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.m
keychain/Trieste/.swiftlint.yml [new file with mode: 0644]
keychain/Trieste/OctagonTriesteTests/Tests/OctagonTriesteTests/OctagonTests.swift
keychain/TrustedPeersHelper/BottledPeer/BottledPeer.swift
keychain/TrustedPeersHelper/BottledPeer/EscrowKeys.swift
keychain/TrustedPeersHelper/Client.swift
keychain/TrustedPeersHelper/Container.swift
keychain/TrustedPeersHelper/ContainerMap.swift
keychain/TrustedPeersHelper/Container_BottledPeers.swift [new file with mode: 0644]
keychain/TrustedPeersHelper/Container_MachineIDs.swift
keychain/TrustedPeersHelper/Container_RecoveryKey.swift [new file with mode: 0644]
keychain/TrustedPeersHelper/CuttlefishErrors.swift
keychain/TrustedPeersHelper/Policy.swift
keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift
keychain/TrustedPeersHelper/SetValueTransformer.swift
keychain/TrustedPeersHelper/TrustedPeersHelper.xcdatamodeld/TrustedPeersHelper_2.xcdatamodel/contents
keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h
keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.m
keychain/TrustedPeersHelper/main.swift
keychain/TrustedPeersHelperUnitTests/.swiftlint.yml [new file with mode: 0644]
keychain/TrustedPeersHelperUnitTests/ContainerSync.swift
keychain/TrustedPeersHelperUnitTests/FakeCuttlefish.swift
keychain/TrustedPeersHelperUnitTests/MockCuttlefish.swift
keychain/TrustedPeersHelperUnitTests/TrustedPeersHelperUnitTests.swift
keychain/ckks/CKKS.m
keychain/ckks/CKKSAnalytics.h
keychain/ckks/CKKSAnalytics.m
keychain/ckks/CKKSIncomingQueueEntry.m
keychain/ckks/CKKSIncomingQueueOperation.m
keychain/ckks/CKKSKey.m
keychain/ckks/CKKSKeychainView.h
keychain/ckks/CKKSKeychainView.m
keychain/ckks/CKKSNewTLKOperation.m
keychain/ckks/CKKSOutgoingQueueOperation.m
keychain/ckks/CKKSSQLDatabaseObject.h
keychain/ckks/CKKSSQLDatabaseObject.m
keychain/ckks/CKKSScanLocalItemsOperation.h
keychain/ckks/CKKSScanLocalItemsOperation.m
keychain/ckks/CKKSViewManager.h
keychain/ckks/CKKSViewManager.m
keychain/ckks/CKKSZone.m
keychain/ckks/tests/CKKSCloudKitTests.m
keychain/ckks/tests/CKKSDispatchTests.m [deleted file]
keychain/ckks/tests/CKKSMockSOSPresentAdapter.h
keychain/ckks/tests/CKKSMockSOSPresentAdapter.m
keychain/ckks/tests/CKKSSQLTests.m
keychain/ckks/tests/CKKSTests+API.m
keychain/ckks/tests/CKKSTests+CurrentPointerAPI.m
keychain/ckks/tests/CKKSTests+MultiZone.h
keychain/ckks/tests/CKKSTests+MultiZone.m
keychain/ckks/tests/CKKSTests.m
keychain/ckks/tests/CloudKitKeychainSyncingFixupTests.m
keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h
keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m
keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.h
keychain/ckks/tests/CloudKitKeychainSyncingTestsBase.m
keychain/ckks/tests/CloudKitMockXCTest.h
keychain/ckks/tests/CloudKitMockXCTest.m
keychain/ckks/tests/gen_test_plist.py [new file with mode: 0644]
keychain/ckks/tests/testrunner/KeychainCKKS.plist [deleted file]
keychain/ckksctl/ckksctl.m
keychain/escrowrequest/generated_source/SecEscrowPendingRecord.m
keychain/headers/SecItem.h
keychain/headers/SecKey.h
keychain/ot/CuttlefishXPCWrapper.m
keychain/ot/OT.m
keychain/ot/OTAuthKitAdapter.h
keychain/ot/OTAuthKitAdapter.m
keychain/ot/OTCheckHealthOperation.h
keychain/ot/OTCheckHealthOperation.m
keychain/ot/OTClientStateMachine.m
keychain/ot/OTClique.h
keychain/ot/OTClique.m
keychain/ot/OTControl.h
keychain/ot/OTControl.m
keychain/ot/OTControlProtocol.h
keychain/ot/OTControlProtocol.m
keychain/ot/OTCuttlefishAccountStateHolder.h
keychain/ot/OTCuttlefishAccountStateHolder.m
keychain/ot/OTCuttlefishContext.h
keychain/ot/OTCuttlefishContext.m
keychain/ot/OTDefines.h
keychain/ot/OTDetermineCDPBitStatusOperation.h [new file with mode: 0644]
keychain/ot/OTDetermineCDPBitStatusOperation.m [new file with mode: 0644]
keychain/ot/OTEstablishOperation.m
keychain/ot/OTFetchCKKSKeysOperation.m
keychain/ot/OTFetchViewsOperation.h
keychain/ot/OTFetchViewsOperation.m
keychain/ot/OTFollowup.h
keychain/ot/OTFollowup.m
keychain/ot/OTJoinWithVoucherOperation.h
keychain/ot/OTJoinWithVoucherOperation.m
keychain/ot/OTJoiningConfiguration.h
keychain/ot/OTJoiningConfiguration.m
keychain/ot/OTManager.h
keychain/ot/OTManager.m
keychain/ot/OTOperationDependencies.h
keychain/ot/OTPrepareOperation.h
keychain/ot/OTPrepareOperation.m
keychain/ot/OTResetCKKSZonesLackingTLKsOperation.m
keychain/ot/OTSOSAdapter.h
keychain/ot/OTSOSAdapter.m
keychain/ot/OTSOSUpdatePreapprovalsOperation.m
keychain/ot/OTSOSUpgradeOperation.h
keychain/ot/OTSOSUpgradeOperation.m
keychain/ot/OTSetCDPBitOperation.h [new file with mode: 0644]
keychain/ot/OTSetCDPBitOperation.m [new file with mode: 0644]
keychain/ot/OTStates.h
keychain/ot/OTStates.m
keychain/ot/OTUpdateTPHOperation.h
keychain/ot/OTUpdateTPHOperation.m
keychain/ot/OTUpdateTrustedDeviceListOperation.m
keychain/ot/OTUploadNewCKKSTLKsOperation.m
keychain/ot/OTVouchWithBottleOperation.m
keychain/ot/OTVouchWithRecoveryKeyOperation.h
keychain/ot/OTVouchWithRecoveryKeyOperation.m
keychain/ot/OctagonStateMachine.m
keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h
keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.m
keychain/ot/categories/OctagonEscrowRecoverer.h
keychain/ot/proto/OTAccountMetadataClassC.proto
keychain/ot/proto/OTPairingMessage.proto
keychain/ot/proto/generated_source/OTAccountMetadataClassC.h
keychain/ot/proto/generated_source/OTAccountMetadataClassC.m
keychain/ot/proto/generated_source/OTPairingMessage.h
keychain/ot/proto/generated_source/OTPairingMessage.m
keychain/ot/proto/generated_source/OTSOSMessage.h [deleted file]
keychain/ot/proto/generated_source/OTSOSMessage.m [deleted file]
keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.h
keychain/ot/proto/generated_source/OTSponsorToApplicantRound2M2.m
keychain/ot/proto/source/OTSOSMessage.m [deleted file]
keychain/ot/tests/octagon/.swiftlint.yml [new file with mode: 0644]
keychain/ot/tests/octagon/OctagonDataPersistenceTests.swift
keychain/ot/tests/octagon/OctagonTestMocks.swift
keychain/ot/tests/octagon/OctagonTests+Account.swift [new file with mode: 0644]
keychain/ot/tests/octagon/OctagonTests+CKKS.swift
keychain/ot/tests/octagon/OctagonTests+CKKSConfiguration.swift [new file with mode: 0644]
keychain/ot/tests/octagon/OctagonTests+CloudKitAccount.swift
keychain/ot/tests/octagon/OctagonTests+CoreFollowUp.swift
keychain/ot/tests/octagon/OctagonTests+DeviceList.swift
keychain/ot/tests/octagon/OctagonTests+ErrorHandling.swift
keychain/ot/tests/octagon/OctagonTests+EscrowRecovery.swift
keychain/ot/tests/octagon/OctagonTests+ForwardCompatibility.swift [new file with mode: 0644]
keychain/ot/tests/octagon/OctagonTests+HealthCheck.swift
keychain/ot/tests/octagon/OctagonTests+Helpers.swift [new file with mode: 0644]
keychain/ot/tests/octagon/OctagonTests+RecoveryKey.swift
keychain/ot/tests/octagon/OctagonTests+Reset.swift
keychain/ot/tests/octagon/OctagonTests+SOS.swift
keychain/ot/tests/octagon/OctagonTests+SOSUpgrade.swift
keychain/ot/tests/octagon/OctagonTests-BridgingHeader.h
keychain/ot/tests/octagon/OctagonTests.swift
keychain/ot/tests/octagon/Pairing/OctagonPairingTests+Piggybacking.swift
keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProxMultiClients.swift
keychain/ot/tests/octagon/Pairing/OctagonPairingTests+ProximitySetup.swift
keychain/ot/tests/octagon/Pairing/OctagonPairingTests.swift
keychain/otctl/OTControlCLI.h
keychain/otctl/OTControlCLI.m
keychain/otctl/otctl-Entitlements.plist
keychain/otctl/otctl.m
keychain/securityd/Regressions/SOSAccountTesting.h
keychain/securityd/Regressions/secd-62-account-backup.m
keychain/securityd/Regressions/secd-65-account-retirement-reset.m
keychain/securityd/Regressions/secd-66-account-recovery.m
keychain/securityd/Regressions/secd-76-idstransport.m [deleted file]
keychain/securityd/Regressions/secd-95-escrow-persistence.m [deleted file]
keychain/securityd/Regressions/secd_77_ids_messaging.m [deleted file]
keychain/securityd/Regressions/secd_regressions.h
keychain/securityd/SOSCloudCircleServer.h
keychain/securityd/SOSCloudCircleServer.m
keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupKeyClassSigningKey.m
keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupMetadataClassKey.m
keychain/securityd/SecDbBackupManager-protobufs/generated_source/SecDbBackupRecoverySet.m
keychain/securityd/SecDbKeychainItem.m
keychain/securityd/SecItemDataSource.c
keychain/securityd/SecItemDataSource.h
keychain/securityd/SecItemServer.c
keychain/securityd/SecItemServer.h
keychain/securityd/spi.c
keychain/tpctl/main.swift
libsecurity_smime/lib/cmsdigest.c
libsecurity_smime/lib/cmspriv.h
libsecurity_smime/lib/cmsutil.c
protocol/SecProtocolPriv.h
rio.yml [deleted file]
securityd/etc/com.apple.securityd.sb
securityd/securityd_service/securityd_service.xcodeproj/project.pbxproj
securityd/src/kckey.cpp
securityd/src/securityd.entitlements
supd/Tests/SFAnalyticsTests.m
supd/Tests/SupdTests.m
supd/supd.h
supd/supd.m
supd/supdProtocol.h
supdctl/main.m
tests/TrustTests/EvaluationTests/ExceptionTests.m [new file with mode: 0644]
tests/TrustTests/EvaluationTests/ExceptionTests_data.h [new file with mode: 0644]
tests/TrustTests/EvaluationTests/RevocationTests.m
tests/TrustTests/EvaluationTests/RevocationTests_data.h
tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.h
tests/TrustTests/EvaluationTests/TrustEvaluationTestCase.m
tests/TrustTests/TrustEvaluationTestHelpers.h
tests/TrustTests/TrustEvaluationTestHelpers.m
tests/secdmockaks/mockaksKeychain.m
trust/headers/SecPolicyPriv.h
trust/trustd/SecCertificateServer.c
trust/trustd/SecCertificateServer.h
trust/trustd/SecOCSPCache.c
trust/trustd/SecPolicyServer.c
trust/trustd/SecPolicyServer.h
trust/trustd/SecRevocationServer.c
trust/trustd/SecRevocationServer.h
trust/trustd/SecTrustServer.c

index 05aa19a794d6514acbd6a231b880c9080abc5912..2d5554db6c155c74c82f227e113b33fc97fd8671 100644 (file)
@@ -31,6 +31,7 @@
 @interface SFAnalytics (Internal)
 
 - (void)logMetric:(NSNumber*)metric withName:(NSString*)metricName oncePerReport:(BOOL)once;
++ (NSString*)hwModelID;
 
 @end
 
index 4f6933a4bcdc3bc466054c32f8423f19a3f50126..a51eb5f90dafa0aa4d96589847e3d83ec1443d55 100644 (file)
 
 #import <utilities/SecCoreAnalytics.h>
 
+#if TARGET_OS_OSX
+#include <sys/sysctl.h>
+#else
+#import <sys/utsname.h>
+#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;
     }
index 76b0892bf60a163780307c048d6e6613349b28c9..ec44cc55ab946e30c508e8cb58c4739e0e8d6534 100644 (file)
@@ -4,6 +4,8 @@
 <dict>
        <key>KeySyncTopic</key>
        <dict>
+               <key>uploadSizeLimit</key>
+               <real>1000000</real>
                <key>splunk_allowInsecureCertificate</key>
                <false/>
                <key>splunk_topic</key>
@@ -13,6 +15,8 @@
        </dict>
        <key>CloudServicesTopic</key>
        <dict>
+               <key>uploadSizeLimit</key>
+               <real>1000000</real>
                <key>splunk_allowInsecureCertificate</key>
                <false/>
                <key>splunk_topic</key>
@@ -24,6 +28,8 @@
        </dict>
        <key>TrustTopic</key>
        <dict>
+               <key>uploadSizeLimit</key>
+               <real>1000000</real>
                <key>splunk_allowInsecureCertificate</key>
                <false/>
                <key>splunk_topic</key>
@@ -35,6 +41,8 @@
        </dict>
        <key>TransparencyTopic</key>
        <dict>
+               <key>uploadSizeLimit</key>
+               <real>10000</real>
                <key>splunk_allowInsecureCertificate</key>
                <false/>
                <key>splunk_topic</key>
index 2291ca02441734b97d5603c025186e53a6977d4b..abf89eec69046ddf08351bbdfbb10cd7069672e0 100644 (file)
@@ -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;
index 38e7c8eab8b38639fab47a0709ff3663e7b8fb40..2d9caabd1430b7f15908dfa2628d477638998407 100644 (file)
@@ -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);
index ea2c1be2f7215ac634ef707824178d9d23550f56..a0a9d584744277b62c6ae5ee036af071d97e77bc 100644 (file)
@@ -31,6 +31,9 @@
 #import "KCJoiningSession.h"
 
 @interface KCJoiningAcceptSession (Internal)
+
+- (KCAESGCMDuplexSession*)accessSession;
+
 -(void)setControlObject:(OTControl*)control;
 - (void)setConfiguration:(OTJoiningConfiguration *)config;
 @end
index 01285024d7b1539cc9d8e476a8bba9c79364c9fc..931144a8e065ca5d519dc1b7364305146cfba666 100644 (file)
@@ -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
index a6ad9e385fb611702e3b232b80882a53baa7edf3..5dc66c706f896b06ae2dd20e064f1329e66e35be 100644 (file)
@@ -84,6 +84,8 @@ typedef enum {
     kPeerInfo = 4,
     kCircleBlob = 5,
 
+    kTLKRequest = 6,
+    
     kError = 0,
 
     kUnknown = 255,
index ed9db95082fbb6f570b575024d521c393dafe276..a8778b2935352ffb87655caebd6954eb1e2e7e70 100644 (file)
@@ -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;
index 2cb52bcfe7aee3e80bb8b1f41b489d6b9b2c2f3a..81b9aac8acf62b64b4ea8157e9b8133d9d2238d1 100644 (file)
@@ -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
index 917529c334f607fd6c701c63ec641f33b07f6f57..340d23cb8f3311f423b811b86e0abf0f3f44327c 100644 (file)
 
 @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
index 6f65dd6fcbfa646e1d506caaa602f37508d1dc08..d6c190332f88ed1c3edee8e0eb08009cb552db6f 100644 (file)
@@ -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
 
index b65d3f6b399bba2de86282e3bf5bee515b36bce4..3627feae34c86598c4102e17db5c33de3c2f8766 100644 (file)
@@ -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",
index b8edbae990b3aa87dbe2a82e1adfdbe0eef4b20b..eafb80c6fa1802c1c631fa5e2e3e7a0f3f65ddf3 100644 (file)
 
 - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete
 {
-    complete(@[], NULL);
+    // Make up a fake TLK
+    NSMutableArray<NSDictionary *> *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<NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
+- (void)triggerSync:(NSArray<NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
 {
     complete(true, NULL);
 }
     complete(nil, nil);
 }
 
-- (void)rpcTriggerBackup:(NSArray<NSString *> *)backupPeers complete:(void (^)(NSError *))complete {
-    complete(nil);
-}
-
-- (void)rpcTriggerRingUpdate:(void (^)(NSError *))complete {
+- (void)triggerBackup:(NSArray<NSString *> *)backupPeers complete:(void (^)(NSError *))complete {
     complete(nil);
 }
 
index a3d71d0f2eedd54b63f76b937689063dc119ece1..2cd57264e3fdca48d33e23e072faf64a4f00029e 100644 (file)
 #include <CommonCrypto/CommonRandomSPI.h>
 
 
-__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 (file)
index 0000000..325f198
--- /dev/null
@@ -0,0 +1,323 @@
+#import <XCTest/XCTest.h>
+
+#import <Foundation/Foundation.h>
+
+#import <KeychainCircle/KCJoiningSession.h>
+#import <KeychainCircle/KCAccountKCCircleDelegate.h>
+#import <KeychainCircle/KCError.h>
+#import <KeychainCircle/KCJoiningMessages.h>
+#import <KeychainCircle/NSError+KCCreationHelpers.h>
+#import <KeychainCircle/KCAESGCMDuplexSession.h>
+
+#include <Security/SecBase.h>
+#include "keychain/SecureObjectSync/SOSFullPeerInfo.h"
+#include "keychain/SecureObjectSync/SOSPeerInfoInternal.h"
+
+#include <CommonCrypto/CommonRandomSPI.h>
+
+
+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 <KCJoiningRequestSecretDelegate, KCJoiningRequestCircleDelegate>
+@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 <KCJoiningAcceptSecretDelegate, KCJoiningAcceptCircleDelegate>
+@property (readonly) NSArray<NSString*>* 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<NSString*>*) secrets retries: (int) retries code: (NSString*) code;
+- (id) initWithSecrets: (NSArray<NSString*>*) 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<NSString*>*) 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<NSString*>*) 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
index 2c9138260812ee5f48bb34c026e91ab91ff0d14d..742abf4aba708bcbf376cc458c5184bc3ef36755 100644 (file)
@@ -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);
index 2a1930aa5195409319c0a2d2ebbcab1f2f7629df..4377483756bd3fc865206fa8273cf52c142ea889 100644 (file)
@@ -1670,7 +1670,18 @@ See remaining rules for examples.
                        <key>timeout</key>
                        <integer>300</integer>
                </dict>
-               <key>com.apple.system-extensions.admin</key>
+        <key>com.apple.tcc.util.admin</key>
+        <dict>
+             <key>comment</key>
+             <string>For modification of TCC settings.</string>
+             <key>class</key>
+             <string>rule</string>
+             <key>rule</key>
+             <string>authenticate-admin-nonshared</string>
+             <key>shared</key>
+             <false/>
+         </dict>
+        <key>com.apple.system-extensions.admin</key>
                <dict>
                        <key>comment</key>
                        <string>Authorize a 3rd party application which wants to manipulate system extensions.</string>
index 1686dbd63897d2832a56026ebbfc9c7b6bd88e09..43870767ceb289b2283145cf0f6871f8b3b24901 100644 (file)
 "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.";
index 186e766987171bbb381abc81b5993a44ed80ac1e..66c9bcb285e7d5d26fa3cfc585b1f8b58202cff3 100644 (file)
@@ -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); }
        
index be2443d12951d1125c209818c5ff508ca824f9ef..86b056c063f6ac11d54cc424a14ceaddad3de7ad 100644 (file)
@@ -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
index 91a0919334180bf63944e3128a4b56a459ad02db..06a556e8f0b46a197b2e5646261df783fc778d33 100644 (file)
@@ -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)
 };
 
 
index fa75c0d02c4678e2255cece8008421cadf0516ff..f645fd2eec9130864442b899b1d11ffdf9986e9e 100644 (file)
@@ -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<CFErrorRef> 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
index 8927923867b57a600c09081f2e6c09af6272c2ae..eff950abc01b261491e1989ea64330a5aa04b904 100644 (file)
@@ -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<CFMutableDictionaryRef> 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;
index b1b571c265085dfda78ae643f27d8109d271fe36..2cdb17f942f5d256d6dc24c426f987c6dfa1d390 100644 (file)
@@ -41,6 +41,7 @@
 #include "TrustSettingsSchema.h"
 #include <Security/SecTrustPriv.h>
 #include "utilities/array_size.h"
+#include "utilities/SecCFWrappers.h"
 
 #include <AssertMacros.h>
 #include <syslog.h>
@@ -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);
                }
index cbdc0912215424698cb8fdfb26e0c5dfa49ec7f3..102b8a4fb85aee7a9fa9a4370986f664bdb865de 100644 (file)
@@ -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
 /*
  * CMS digesting.
  */
+#include <assert.h>
 
 #include "cmslocal.h"
 
-#include "secitem.h"
+#include "SecAsn1Item.h"
 #include "secoid.h"
 
 #include <security_asn1/secerr.h>
-#include <Security/cssmapi.h>
+#include <security_asn1/secport.h>
+
+#include <CommonCrypto/CommonDigest.h>
 
 #include <Security/SecCmsDigestContext.h>
 
-/* 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;
 }
index 5e41d83ee98895e67e38b5d8b97127835761a32a..cd7220b67cec2af91027a8cd4981104b87432028 100644 (file)
@@ -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);
 
 /*
index c0f890547526e66c10e063d249964e263d0906f4..f07f98e4d9599bb78b7d1a1281d894de06a1e45a 100644 (file)
@@ -48,6 +48,7 @@
 #include <Security/cssmapi.h>
 #include <Security/cssmapple.h>
 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+#include <CommonCrypto/CommonDigest.h>
 
 
 /*
@@ -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;
index a59d7b3fd6b340c1fc8771b154aec3cb9844d8b7..cf209e6743bed6163ca69e420a51631e52eeba37 100644 (file)
@@ -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/Regressions/secitem/si-27-sectrust-exceptions.c b/OSX/sec/Security/Regressions/secitem/si-27-sectrust-exceptions.c
deleted file mode 100644 (file)
index fffb493..0000000
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright (c) 2006-2010,2012-2018 Apple Inc. All Rights Reserved.
- */
-
-#include <CoreFoundation/CoreFoundation.h>
-#include <Security/SecCertificate.h>
-#include <Security/SecCertificatePriv.h>
-#include <Security/SecPolicy.h>
-#include <Security/SecTrustPriv.h>
-#include <utilities/array_size.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include "shared_regressions.h"
-
-/*
- Serial Number:
- 01:f1:eb:db:ee:d3:1d:7a:b5:72:54:7d:34:43:4b:87
- Signature Algorithm: sha256WithRSAEncryption
- Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 Extended Validation Server CA
- Validity
- Not Before: Mar 22 00:00:00 2018 GMT
- 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[]={
-    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,
-    0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,
-    0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
-    0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,
-    0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,
-    0x34,0x30,0x32,0x06,0x03,0x55,0x04,0x03,0x13,0x2B,0x44,0x69,0x67,0x69,0x43,0x65,
-    0x72,0x74,0x20,0x53,0x48,0x41,0x32,0x20,0x45,0x78,0x74,0x65,0x6E,0x64,0x65,0x64,
-    0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,
-    0x65,0x72,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x31,0x38,0x30,0x33,0x32,0x32,0x30,
-    0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x31,0x39,0x30,0x33,0x32,0x33,0x31,0x32,
-    0x30,0x30,0x30,0x30,0x5A,0x30,0x81,0xF0,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,
-    0x0F,0x0C,0x14,0x50,0x72,0x69,0x76,0x61,0x74,0x65,0x20,0x4F,0x72,0x67,0x61,0x6E,
-    0x69,0x7A,0x61,0x74,0x69,0x6F,0x6E,0x31,0x13,0x30,0x11,0x06,0x0B,0x2B,0x06,0x01,
-    0x04,0x01,0x82,0x37,0x3C,0x02,0x01,0x03,0x13,0x02,0x55,0x53,0x31,0x1B,0x30,0x19,
-    0x06,0x0B,0x2B,0x06,0x01,0x04,0x01,0x82,0x37,0x3C,0x02,0x01,0x02,0x13,0x0A,0x43,
-    0x61,0x6C,0x69,0x66,0x6F,0x72,0x6E,0x69,0x61,0x31,0x11,0x30,0x0F,0x06,0x03,0x55,
-    0x04,0x05,0x13,0x08,0x43,0x30,0x38,0x30,0x36,0x35,0x39,0x32,0x31,0x0B,0x30,0x09,
-    0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x13,0x30,0x11,0x06,0x03,0x55,
-    0x04,0x08,0x13,0x0A,0x43,0x61,0x6C,0x69,0x66,0x6F,0x72,0x6E,0x69,0x61,0x31,0x12,
-    0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x13,0x09,0x43,0x75,0x70,0x65,0x72,0x74,0x69,
-    0x6E,0x6F,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0A,0x13,0x0A,0x41,0x70,0x70,
-    0x6C,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0B,
-    0x13,0x1C,0x49,0x6E,0x74,0x65,0x72,0x6E,0x65,0x74,0x20,0x53,0x65,0x72,0x76,0x69,
-    0x63,0x65,0x73,0x20,0x66,0x6F,0x72,0x20,0x41,0x6B,0x61,0x6D,0x61,0x69,0x31,0x18,
-    0x30,0x16,0x06,0x03,0x55,0x04,0x03,0x13,0x0F,0x73,0x74,0x6F,0x72,0x65,0x2E,0x61,
-    0x70,0x70,0x6C,0x65,0x2E,0x63,0x6F,0x6D,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,0xB5,0xDF,0x48,0x6A,0xE5,0x5F,0x0C,
-    0x42,0xBF,0x71,0x07,0xAB,0xE6,0x48,0xD2,0x2C,0x13,0x7C,0x29,0xF7,0x90,0xC2,0x45,
-    0x39,0x43,0x48,0x2E,0x16,0x22,0xBF,0xAB,0x48,0x20,0x14,0x27,0xE7,0x87,0x52,0x97,
-    0xF6,0x64,0x0C,0x62,0xF5,0x39,0x54,0x64,0x09,0xE8,0x29,0x56,0xF4,0x03,0x40,0x5A,
-    0xFA,0x9A,0x11,0x2B,0xD0,0x01,0x51,0x1E,0xED,0xB8,0x51,0xCB,0xAB,0x6B,0x7A,0x99,
-    0xF3,0xB0,0x6E,0xA8,0xC4,0xB6,0xAF,0x17,0xEC,0xA6,0xD2,0x4D,0xCA,0x63,0x3D,0x59,
-    0x30,0x25,0x30,0x54,0x86,0xDB,0x70,0x7A,0xEC,0x07,0x83,0x19,0xA0,0x44,0xE7,0x3C,
-    0x68,0xE0,0xFD,0xB9,0xEE,0x3F,0xAC,0xF3,0x32,0x5A,0x5F,0x42,0x31,0x94,0x70,0x2C,
-    0xC5,0x73,0xD2,0x79,0x23,0x5B,0x96,0x45,0xB1,0xB3,0x2A,0xA1,0x5A,0x69,0xFE,0xBE,
-    0x52,0x0D,0x5D,0x79,0x18,0xCA,0xF1,0x44,0x92,0xA0,0x27,0x1F,0xAA,0x6E,0x9D,0x6F,
-    0x1B,0x83,0x5B,0x73,0x28,0x1D,0x87,0xB5,0x70,0x0E,0x3D,0xED,0xE2,0xC2,0x34,0x8A,
-    0x81,0xB2,0x22,0x40,0x98,0x77,0x2F,0x34,0x1B,0x70,0xEC,0x96,0x3F,0x91,0xB9,0xFF,
-    0xC9,0xE5,0x7E,0xE7,0x25,0xEF,0xDB,0x9A,0x58,0x4E,0xB2,0x92,0x19,0xA5,0x8D,0xEB,
-    0x76,0xF8,0xA8,0x48,0x9F,0x3D,0x10,0x0C,0xE4,0x69,0x7B,0xE7,0xB7,0xCA,0xF6,0x14,
-    0x5E,0x93,0x1E,0x20,0x37,0x1B,0xB8,0xB1,0x2C,0xD1,0x46,0x5C,0xD7,0x85,0x4A,0x2E,
-    0x19,0xB2,0x3C,0x31,0xDC,0x3D,0xB5,0x3C,0xEC,0x49,0x8D,0x9C,0xCF,0x75,0x0B,0xFC,
-    0x03,0x31,0x37,0xE7,0xA5,0xD6,0x9D,0x19,0x0D,0x02,0x03,0x01,0x00,0x01,0xA3,0x82,
-    0x03,0x05,0x30,0x82,0x03,0x01,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,
-    0x16,0x80,0x14,0x3D,0xD3,0x50,0xA5,0xD6,0xA0,0xAD,0xEE,0xF3,0x4A,0x60,0x0A,0x65,
-    0xD3,0x21,0xD4,0xF8,0xF8,0xD6,0x0F,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,
-    0x04,0x14,0x6F,0x89,0xAE,0x9B,0xE4,0x8A,0x21,0x3A,0x3B,0xEA,0xAA,0xBE,0x99,0x90,
-    0xC8,0xDB,0x34,0x80,0x21,0xB9,0x30,0x2F,0x06,0x03,0x55,0x1D,0x11,0x04,0x28,0x30,
-    0x26,0x82,0x0F,0x73,0x74,0x6F,0x72,0x65,0x2E,0x61,0x70,0x70,0x6C,0x65,0x2E,0x63,
-    0x6F,0x6D,0x82,0x13,0x77,0x77,0x77,0x2E,0x73,0x74,0x6F,0x72,0x65,0x2E,0x61,0x70,
-    0x70,0x6C,0x65,0x2E,0x63,0x6F,0x6D,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,
-    0xFF,0x04,0x04,0x03,0x02,0x05,0xA0,0x30,0x1D,0x06,0x03,0x55,0x1D,0x25,0x04,0x16,
-    0x30,0x14,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x08,0x2B,0x06,
-    0x01,0x05,0x05,0x07,0x03,0x02,0x30,0x75,0x06,0x03,0x55,0x1D,0x1F,0x04,0x6E,0x30,
-    0x6C,0x30,0x34,0xA0,0x32,0xA0,0x30,0x86,0x2E,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,
-    0x63,0x72,0x6C,0x33,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,
-    0x6D,0x2F,0x73,0x68,0x61,0x32,0x2D,0x65,0x76,0x2D,0x73,0x65,0x72,0x76,0x65,0x72,
-    0x2D,0x67,0x32,0x2E,0x63,0x72,0x6C,0x30,0x34,0xA0,0x32,0xA0,0x30,0x86,0x2E,0x68,
-    0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x34,0x2E,0x64,0x69,0x67,0x69,0x63,
-    0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x73,0x68,0x61,0x32,0x2D,0x65,0x76,0x2D,
-    0x73,0x65,0x72,0x76,0x65,0x72,0x2D,0x67,0x32,0x2E,0x63,0x72,0x6C,0x30,0x4B,0x06,
-    0x03,0x55,0x1D,0x20,0x04,0x44,0x30,0x42,0x30,0x37,0x06,0x09,0x60,0x86,0x48,0x01,
-    0x86,0xFD,0x6C,0x02,0x01,0x30,0x2A,0x30,0x28,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,
-    0x07,0x02,0x01,0x16,0x1C,0x68,0x74,0x74,0x70,0x73,0x3A,0x2F,0x2F,0x77,0x77,0x77,
-    0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x43,0x50,
-    0x53,0x30,0x07,0x06,0x05,0x67,0x81,0x0C,0x01,0x01,0x30,0x81,0x88,0x06,0x08,0x2B,
-    0x06,0x01,0x05,0x05,0x07,0x01,0x01,0x04,0x7C,0x30,0x7A,0x30,0x24,0x06,0x08,0x2B,
-    0x06,0x01,0x05,0x05,0x07,0x30,0x01,0x86,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,
-    0x6F,0x63,0x73,0x70,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,
-    0x6D,0x30,0x52,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x30,0x02,0x86,0x46,0x68,
-    0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x61,0x63,0x65,0x72,0x74,0x73,0x2E,0x64,0x69,
-    0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x44,0x69,0x67,0x69,0x43,
-    0x65,0x72,0x74,0x53,0x48,0x41,0x32,0x45,0x78,0x74,0x65,0x6E,0x64,0x65,0x64,0x56,
-    0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x53,0x65,0x72,0x76,0x65,0x72,0x43,
-    0x41,0x2E,0x63,0x72,0x74,0x30,0x09,0x06,0x03,0x55,0x1D,0x13,0x04,0x02,0x30,0x00,
-    0x30,0x82,0x01,0x03,0x06,0x0A,0x2B,0x06,0x01,0x04,0x01,0xD6,0x79,0x02,0x04,0x02,
-    0x04,0x81,0xF4,0x04,0x81,0xF1,0x00,0xEF,0x00,0x75,0x00,0xA4,0xB9,0x09,0x90,0xB4,
-    0x18,0x58,0x14,0x87,0xBB,0x13,0xA2,0xCC,0x67,0x70,0x0A,0x3C,0x35,0x98,0x04,0xF9,
-    0x1B,0xDF,0xB8,0xE3,0x77,0xCD,0x0E,0xC8,0x0D,0xDC,0x10,0x00,0x00,0x01,0x62,0x4E,
-    0xBC,0xC7,0x43,0x00,0x00,0x04,0x03,0x00,0x46,0x30,0x44,0x02,0x20,0x3E,0xD3,0xFE,
-    0xB4,0x45,0x97,0xCE,0xA3,0x05,0xC9,0x29,0x70,0x55,0x3B,0x77,0x9E,0xCC,0x4B,0x06,
-    0xFD,0x76,0x21,0xD0,0x79,0x69,0xD6,0x60,0x01,0xBB,0xC7,0x43,0x5E,0x02,0x20,0x3D,
-    0xB7,0x73,0x91,0x51,0xB7,0xAC,0x40,0xFB,0xA7,0x36,0xCF,0x10,0xE8,0x63,0x79,0xE6,
-    0x06,0xEC,0xFA,0x60,0xFE,0x44,0x90,0x9A,0x53,0x26,0x04,0x27,0x1A,0x4B,0xD4,0x00,
-    0x76,0x00,0x56,0x14,0x06,0x9A,0x2F,0xD7,0xC2,0xEC,0xD3,0xF5,0xE1,0xBD,0x44,0xB2,
-    0x3E,0xC7,0x46,0x76,0xB9,0xBC,0x99,0x11,0x5C,0xC0,0xEF,0x94,0x98,0x55,0xD6,0x89,
-    0xD0,0xDD,0x00,0x00,0x01,0x62,0x4E,0xBC,0xC8,0x6A,0x00,0x00,0x04,0x03,0x00,0x47,
-    0x30,0x45,0x02,0x20,0x18,0xF4,0x40,0x99,0x83,0xA7,0x53,0x6D,0x11,0xBF,0x7C,0xA5,
-    0x64,0x21,0x52,0xD0,0x7C,0xF5,0x96,0xCD,0xB0,0x56,0xAE,0x1E,0x13,0x9C,0xFC,0x08,
-    0x3B,0x56,0x66,0x0F,0x02,0x21,0x00,0x98,0xBE,0xC7,0x01,0x8E,0x88,0xB7,0xB2,0xF9,
-    0xC7,0xE6,0x08,0x46,0x10,0xEA,0x8D,0x01,0x12,0x0D,0x7D,0xA6,0xBA,0xA6,0xD3,0x1F,
-    0xEC,0xA3,0xD6,0x7A,0xE4,0x85,0x89,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
-    0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x6A,0x52,0xE8,0xB3,0x70,
-    0x25,0xD9,0x95,0x87,0xC4,0xF3,0x14,0xB1,0x8C,0x29,0x6F,0x11,0xA0,0x6D,0xD7,0xE9,
-    0x96,0xFA,0x06,0x57,0x4D,0x86,0xE8,0xD6,0x95,0xC0,0x3A,0x33,0xEC,0x67,0x6C,0x98,
-    0xA2,0x99,0x09,0x28,0x16,0x18,0xEC,0x21,0xED,0x93,0xEF,0xAB,0x69,0x5F,0x98,0x9C,
-    0x62,0x21,0x10,0xEB,0x1C,0xC4,0x20,0x5E,0x3E,0x6F,0x80,0x03,0xC9,0xC5,0xD5,0x2F,
-    0xDA,0x8C,0x86,0x40,0x59,0x71,0x34,0xEE,0xCF,0x54,0x83,0xE1,0x3E,0x11,0x1D,0x12,
-    0x66,0x3D,0x16,0x7F,0xD8,0x1B,0x42,0x35,0x50,0xA6,0xD6,0xE6,0x6B,0x32,0xDE,0xE2,
-    0xD9,0x01,0xD7,0xB1,0xF5,0xE8,0x72,0x44,0x03,0xB5,0xFA,0x19,0x0D,0xF2,0x8D,0x0B,
-    0x75,0xBA,0xD9,0x39,0xFF,0x73,0xF2,0xA0,0xD6,0x49,0xEF,0x9E,0xE9,0x23,0x5D,0xED,
-    0xC5,0x08,0x69,0x10,0xF7,0xF0,0x0B,0x8D,0x80,0xB6,0x43,0xE4,0x2A,0xEC,0x92,0xCF,
-    0x22,0xCA,0xAF,0x1A,0x8A,0x16,0xC7,0xC5,0x88,0xB7,0xCA,0xC6,0x19,0x1A,0xD1,0xFF,
-    0x13,0x93,0x56,0x29,0x1A,0x48,0xA8,0x92,0x21,0xE5,0x7F,0x3E,0x4C,0xB4,0x89,0xD6,
-    0x08,0xF0,0xB0,0x4B,0x22,0x7C,0x1F,0xEC,0xE7,0x09,0x5D,0x23,0xE2,0xD1,0xB8,0xA3,
-    0xDF,0xEC,0x2D,0x87,0x1F,0xCD,0x58,0x1C,0xDD,0x09,0xE8,0xB5,0xD4,0x30,0x1E,0x2A,
-    0x4D,0xA3,0xA1,0x84,0x43,0xD7,0x4A,0x7B,0x15,0x8E,0xEB,0xE2,0x53,0x2B,0x12,0xCB,
-    0xFB,0xF2,0x61,0x7E,0x92,0x0B,0x87,0x07,0x1C,0x8D,0x0A,0xED,0x62,0x10,0x27,0xE3,
-    0xDB,0xC4,0x65,0xDE,0x57,0xFB,0x6D,0x49,0xC8,0x7A,0xF8,
-};
-
-/* 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[]={
-    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,
-    0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,
-    0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
-    0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,
-    0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,
-    0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x44,0x69,0x67,0x69,0x43,0x65,
-    0x72,0x74,0x20,0x48,0x69,0x67,0x68,0x20,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63,
-    0x65,0x20,0x45,0x56,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,
-    0x31,0x33,0x31,0x30,0x32,0x32,0x31,0x32,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,
-    0x38,0x31,0x30,0x32,0x32,0x31,0x32,0x30,0x30,0x30,0x30,0x5A,0x30,0x75,0x31,0x0B,
-    0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,
-    0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x49,
-    0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,0x77,0x77,
-    0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x34,0x30,
-    0x32,0x06,0x03,0x55,0x04,0x03,0x13,0x2B,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
-    0x20,0x53,0x48,0x41,0x32,0x20,0x45,0x78,0x74,0x65,0x6E,0x64,0x65,0x64,0x20,0x56,
-    0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x65,0x72,
-    0x20,0x43,0x41,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,0xD7,0x53,0xA4,0x04,0x51,0xF8,0x99,0xA6,0x16,0x48,0x4B,0x67,
-    0x27,0xAA,0x93,0x49,0xD0,0x39,0xED,0x0C,0xB0,0xB0,0x00,0x87,0xF1,0x67,0x28,0x86,
-    0x85,0x8C,0x8E,0x63,0xDA,0xBC,0xB1,0x40,0x38,0xE2,0xD3,0xF5,0xEC,0xA5,0x05,0x18,
-    0xB8,0x3D,0x3E,0xC5,0x99,0x17,0x32,0xEC,0x18,0x8C,0xFA,0xF1,0x0C,0xA6,0x64,0x21,
-    0x85,0xCB,0x07,0x10,0x34,0xB0,0x52,0x88,0x2B,0x1F,0x68,0x9B,0xD2,0xB1,0x8F,0x12,
-    0xB0,0xB3,0xD2,0xE7,0x88,0x1F,0x1F,0xEF,0x38,0x77,0x54,0x53,0x5F,0x80,0x79,0x3F,
-    0x2E,0x1A,0xAA,0xA8,0x1E,0x4B,0x2B,0x0D,0xAB,0xB7,0x63,0xB9,0x35,0xB7,0x7D,0x14,
-    0xBC,0x59,0x4B,0xDF,0x51,0x4A,0xD2,0xA1,0xE2,0x0C,0xE2,0x90,0x82,0x87,0x6A,0xAE,
-    0xEA,0xD7,0x64,0xD6,0x98,0x55,0xE8,0xFD,0xAF,0x1A,0x50,0x6C,0x54,0xBC,0x11,0xF2,
-    0xFD,0x4A,0xF2,0x9D,0xBB,0x7F,0x0E,0xF4,0xD5,0xBE,0x8E,0x16,0x89,0x12,0x55,0xD8,
-    0xC0,0x71,0x34,0xEE,0xF6,0xDC,0x2D,0xEC,0xC4,0x87,0x25,0x86,0x8D,0xD8,0x21,0xE4,
-    0xB0,0x4D,0x0C,0x89,0xDC,0x39,0x26,0x17,0xDD,0xF6,0xD7,0x94,0x85,0xD8,0x04,0x21,
-    0x70,0x9D,0x6F,0x6F,0xFF,0x5C,0xBA,0x19,0xE1,0x45,0xCB,0x56,0x57,0x28,0x7E,0x1C,
-    0x0D,0x41,0x57,0xAA,0xB7,0xB8,0x27,0xBB,0xB1,0xE4,0xFA,0x2A,0xEF,0x21,0x23,0x75,
-    0x1A,0xAD,0x2D,0x9B,0x86,0x35,0x8C,0x9C,0x77,0xB5,0x73,0xAD,0xD8,0x94,0x2D,0xE4,
-    0xF3,0x0C,0x9D,0xEE,0xC1,0x4E,0x62,0x7E,0x17,0xC0,0x71,0x9E,0x2C,0xDE,0xF1,0xF9,
-    0x10,0x28,0x19,0x33,0x02,0x03,0x01,0x00,0x01,0xA3,0x82,0x01,0x49,0x30,0x82,0x01,
-    0x45,0x30,0x12,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x08,0x30,0x06,0x01,
-    0x01,0xFF,0x02,0x01,0x00,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,
-    0x04,0x03,0x02,0x01,0x86,0x30,0x1D,0x06,0x03,0x55,0x1D,0x25,0x04,0x16,0x30,0x14,
-    0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x08,0x2B,0x06,0x01,0x05,
-    0x05,0x07,0x03,0x02,0x30,0x34,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x01,0x01,
-    0x04,0x28,0x30,0x26,0x30,0x24,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x30,0x01,
-    0x86,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6F,0x63,0x73,0x70,0x2E,0x64,0x69,
-    0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x4B,0x06,0x03,0x55,0x1D,
-    0x1F,0x04,0x44,0x30,0x42,0x30,0x40,0xA0,0x3E,0xA0,0x3C,0x86,0x3A,0x68,0x74,0x74,
-    0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x34,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,
-    0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x48,0x69,
-    0x67,0x68,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63,0x65,0x45,0x56,0x52,0x6F,0x6F,
-    0x74,0x43,0x41,0x2E,0x63,0x72,0x6C,0x30,0x3D,0x06,0x03,0x55,0x1D,0x20,0x04,0x36,
-    0x30,0x34,0x30,0x32,0x06,0x04,0x55,0x1D,0x20,0x00,0x30,0x2A,0x30,0x28,0x06,0x08,
-    0x2B,0x06,0x01,0x05,0x05,0x07,0x02,0x01,0x16,0x1C,0x68,0x74,0x74,0x70,0x73,0x3A,
-    0x2F,0x2F,0x77,0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,
-    0x6F,0x6D,0x2F,0x43,0x50,0x53,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,
-    0x14,0x3D,0xD3,0x50,0xA5,0xD6,0xA0,0xAD,0xEE,0xF3,0x4A,0x60,0x0A,0x65,0xD3,0x21,
-    0xD4,0xF8,0xF8,0xD6,0x0F,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,
-    0x80,0x14,0xB1,0x3E,0xC3,0x69,0x03,0xF8,0xBF,0x47,0x01,0xD4,0x98,0x26,0x1A,0x08,
-    0x02,0xEF,0x63,0x64,0x2B,0xC3,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
-    0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x9D,0xB6,0xD0,0x90,0x86,0xE1,
-    0x86,0x02,0xED,0xC5,0xA0,0xF0,0x34,0x1C,0x74,0xC1,0x8D,0x76,0xCC,0x86,0x0A,0xA8,
-    0xF0,0x4A,0x8A,0x42,0xD6,0x3F,0xC8,0xA9,0x4D,0xAD,0x7C,0x08,0xAD,0xE6,0xB6,0x50,
-    0xB8,0xA2,0x1A,0x4D,0x88,0x07,0xB1,0x29,0x21,0xDC,0xE7,0xDA,0xC6,0x3C,0x21,0xE0,
-    0xE3,0x11,0x49,0x70,0xAC,0x7A,0x1D,0x01,0xA4,0xCA,0x11,0x3A,0x57,0xAB,0x7D,0x57,
-    0x2A,0x40,0x74,0xFD,0xD3,0x1D,0x85,0x18,0x50,0xDF,0x57,0x47,0x75,0xA1,0x7D,0x55,
-    0x20,0x2E,0x47,0x37,0x50,0x72,0x8C,0x7F,0x82,0x1B,0xD2,0x62,0x8F,0x2D,0x03,0x5A,
-    0xDA,0xC3,0xC8,0xA1,0xCE,0x2C,0x52,0xA2,0x00,0x63,0xEB,0x73,0xBA,0x71,0xC8,0x49,
-    0x27,0x23,0x97,0x64,0x85,0x9E,0x38,0x0E,0xAD,0x63,0x68,0x3C,0xBA,0x52,0x81,0x58,
-    0x79,0xA3,0x2C,0x0C,0xDF,0xDE,0x6D,0xEB,0x31,0xF2,0xBA,0xA0,0x7C,0x6C,0xF1,0x2C,
-    0xD4,0xE1,0xBD,0x77,0x84,0x37,0x03,0xCE,0x32,0xB5,0xC8,0x9A,0x81,0x1A,0x4A,0x92,
-    0x4E,0x3B,0x46,0x9A,0x85,0xFE,0x83,0xA2,0xF9,0x9E,0x8C,0xA3,0xCC,0x0D,0x5E,0xB3,
-    0x3D,0xCF,0x04,0x78,0x8F,0x14,0x14,0x7B,0x32,0x9C,0xC7,0x00,0xA6,0x5C,0xC4,0xB5,
-    0xA1,0x55,0x8D,0x5A,0x56,0x68,0xA4,0x22,0x70,0xAA,0x3C,0x81,0x71,0xD9,0x9D,0xA8,
-    0x45,0x3B,0xF4,0xE5,0xF6,0xA2,0x51,0xDD,0xC7,0x7B,0x62,0xE8,0x6F,0x0C,0x74,0xEB,
-    0xB8,0xDA,0xF8,0xBF,0x87,0x0D,0x79,0x50,0x91,0x90,0x9B,0x18,0x3B,0x91,0x59,0x27,
-    0xF1,0x35,0x28,0x13,0xAB,0x26,0x7E,0xD5,0xF7,0x7A,
-};
-
-/* 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,
-};
-
-#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;
-}
index 849426be771072b38e538a42f1a95924513656d9..652e72e833702736069d5260c074dc113516dcba 100644 (file)
@@ -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];
index 5ebebb0655730efd6486c6c78423d92b38d9e12f..9d43e27a6d67e866ce29340223c0b0c489ce4b4a 100644 (file)
@@ -36,6 +36,7 @@
 #include <utilities/array_size.h>
 #include <ctkclient/ctkclient.h>
 #include <libaks_acl_cf_keys.h>
+#include <coreauthd_spi.h>
 #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;
index d9b3f39d992a107c1f9ef02155637d71a8d43757..204ec41a73f349199dfa8dda5bb1167fa4c21830 100644 (file)
@@ -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");
index 7067b56d2c2392641c3f8818b867c7b693d020d9..d5f821526ff8e006318b8bde7fafad43d28a093d 100644 (file)
@@ -90,6 +90,7 @@ _kSecPolicyNameAppleParsecService
 _kSecPolicyNameApplePPQService
 _kSecPolicyNameApplePushService
 _kSecPolicyNameAppleSiriService
+_kSecPolicyNameAppleUpdatesService
 _kSecPolicyNameEAPClient
 _kSecPolicyNameEAPServer
 _kSecPolicyNameIPSecClient
index b526d63292249ec136d96672c1002a165fc5b078..98fb9085770d6a5d540494df6247ef794a2cc999 100644 (file)
@@ -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);
         }
index c3b022791509afa6baacca2f157885addf209d56..50cc4c6aeac94b9568d1f005bf6f3fca2140903f 100644 (file)
@@ -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);
index 81dc4409fe71653deeffc0c877ede5ecf50cdee6..3fe809942e1b7c33661350486b4430d7b0ed8db4 100644 (file)
@@ -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))
index 98db0bf9a785f7b6b63bc3142e7371596bf88946..bc7bb235554a34b96afcc6d8dd6a351361f31203 100644 (file)
@@ -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;
+}
index 6dfb4452416e78c3c82f3c771184ee152380e6c9..c3ee5011e08dba75c7a3a5bfa3f2805dbfe28375 100644 (file)
@@ -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)
index 696d1f2c729e7fc32e42632f8a7a9e3eb6877464..698ae145def6ccec094cbe6ed2ef2c26a7cc5eee 100644 (file)
@@ -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:
index afbd42d6cfcf1566190f247c7dfcd74c54b5760f..ed945f01c12e189e372072f26c6bb864a7dfbcdd 100644 (file)
@@ -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);
index 917ac3e623fb0f6238c635bfb0d29a405486e11d..df744076dced74b6bff6c67132cf4a98ff158434 100644 (file)
@@ -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);
index 534f8cd58feec6228632bef5a905e94b8e8847f6..8378610461289e718f91f80c4c94a0722b322bf9 100644 (file)
@@ -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)
index 048929d5e55dfbf6796dad844107f11e680fa20a..e276aeb779d35f25f020881b321e192423a0f254 100644 (file)
@@ -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:@"<SecAccessControlRef: ak>"], "unexpected desc: %@", desc);
+
+    SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{});
+    desc = ac.description;
+    ok([desc isEqualToString:@"<SecAccessControlRef: ak>"], "unexpected desc: %@", desc);
+
+    SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": (__bridge id)kCFBooleanTrue});
+    desc = ac.description;
+    ok([desc isEqualToString:@"<SecAccessControlRef: ak;od(true)>"], "unexpected desc: %@", desc);
+
+    SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": (__bridge id)kCFBooleanTrue, @"oe": (__bridge id)kCFBooleanFalse});
+    desc = ac.description;
+    ok([desc isEqualToString:@"<SecAccessControlRef: ak;od(true);oe(false)>"], "unexpected desc: %@", desc);
+
+    SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": @"huh"});
+    desc = ac.description;
+    ok([desc isEqualToString:@"<SecAccessControlRef: ak;od(huh)>"], "unexpected desc: %@", desc);
+
+    SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": @2});
+    desc = ac.description;
+    ok([desc isEqualToString:@"<SecAccessControlRef: ak;od(2)>"], "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:@"<SecAccessControlRef: ak;od(010203)>"], "unexpected desc: %@", desc);
+
+    NSData *longData = [NSMutableData dataWithLength:128];
+    SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": longData});
+    desc = ac.description;
+    ok([desc isEqualToString:@"<SecAccessControlRef: ak;od(00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000...(128b))>"], "unexpected desc: %@", desc);
+
+    SecAccessControlSetConstraints((__bridge SecAccessControlRef)ac, (__bridge CFDictionaryRef)@{@"od": @{@"kofn": @2}});
+    desc = ac.description;
+    ok([desc isEqualToString:@"<SecAccessControlRef: ak;od(kofn(2))>"], "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);
index f1efbf3f366a3efa27b2232da3f743bc953e72cb..1cfb109bccf3bcb6761d7085cbc2c2211feb2dd8 100644 (file)
@@ -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
 //
index 729520a4e4f69db5e29a652101a1c755be38c56d..f09ef95f7031915a577446cbe06f8574a049b558 100644 (file)
@@ -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;
index b3c90402b2d6bec6e68f1ddad4a1b2e3bec0e425..06ea04e4fea3ae8a5a0fe1edbab82c29483c4802 100644 (file)
@@ -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: -
index 905eca6d11498a6867f151ff3d1d7bcadaaab56d..4b926795a624cf347bf11f72ae11af1fbf769598 100644 (file)
@@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface SecXPCHelper : NSObject
 + (NSSet<Class> *)safeErrorClasses;
-+ (NSError *)cleanseErrorForXPC:(NSError * _Nullable)error;
++ (NSError * _Nullable)cleanseErrorForXPC:(NSError * _Nullable)error;
 
 /*
  * Some NSError objects contain non-NSSecureCoding-compliant userInfo.
index d9d80e096783be5ff9262e87a3ff74fea8fffa7f..5980d1822292de9948dc21c7d0ae52e176da2d5e 100644 (file)
     return NSStringFromClass([object class]);
 }
 
-+ (NSError *)cleanseErrorForXPC:(NSError * _Nullable)error
++ (NSError * _Nullable)cleanseErrorForXPC:(NSError * _Nullable)error
 {
     if (!error) {
         return nil;
index a027eee76deae4ad53aa81801fc719fdb43e09d3..a3efacd01c583602fe021a28a8bb7bb5541ec757 100644 (file)
@@ -347,6 +347,7 @@ __OctagonSignpostLogSystem
 
 _OTCliqueStatusToString
 _OTCliqueStatusFromString
+_OTCDPStatusToString
 
 _OTCliqueCDPContextTypeNone
 _OTCliqueCDPContextTypeSignIn
@@ -2100,6 +2101,7 @@ _SFAnalyticsColumnHardFailureCount
 _SFAnalyticsColumnSoftFailureCount
 _SFAnalyticsColumnSampleValue
 _SFAnalyticsColumnSampleName
+_SFAnalyticsPostTime
 _SFAnalyticsEventTime
 _SFAnalyticsEventType
 _SFAnalyticsEventTypeErrorEvent
index fbfb182caf5d83c71c2e9be3162da2127621858e..d469fbf5f1a05a23ebe341bc7d128e7f310c6750 100644 (file)
                        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" */;
                        buildPhases = (
                        );
                        dependencies = (
+                               0CA378EB23876E1000090B7E /* PBXTargetDependency */,
                                D4E0E9BC2224E15500A802E0 /* PBXTargetDependency */,
                                D45D8F5C2224D9F100D6C124 /* PBXTargetDependency */,
                                D45D8F5A2224D8A100D6C124 /* PBXTargetDependency */,
                        buildPhases = (
                        );
                        dependencies = (
+                               0CA378E923876E0900090B7E /* PBXTargetDependency */,
                                D4EB53C9223C4AB5009101F8 /* PBXTargetDependency */,
                                D4E0E9BE2224E15E00A802E0 /* PBXTargetDependency */,
                                D45D8F902224DC9900D6C124 /* PBXTargetDependency */,
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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, ); }; };
                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, ); }; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                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 */; };
                        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 */;
                        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;
                        );
                        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;
                0C664AB2175926B20092D3D9 /* secdtests-entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "secdtests-entitlements.plist"; sourceTree = "<group>"; };
                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 = "<group>"; };
                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 = "<group>"; };
                0C78F1CA16A5E1BF00654E08 /* sectask-10-sectask.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sectask-10-sectask.c"; sourceTree = "<group>"; };
                0CE760531E13155100B4381E /* SOSAccountTrustClassic+Circle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SOSAccountTrustClassic+Circle.h"; path = "SecureObjectSync/SOSAccountTrustClassic+Circle.h"; sourceTree = "<group>"; };
                0CE760551E1316E900B4381E /* SOSAccountTrustClassic+Retirement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SOSAccountTrustClassic+Retirement.h"; path = "SecureObjectSync/SOSAccountTrustClassic+Retirement.h"; sourceTree = "<group>"; };
                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 = "<group>"; };
-               0CE9C98A21B8891A006BDD80 /* OTSOSMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OTSOSMessage.m; sourceTree = "<group>"; };
                0CF0E2E31F8EE3B000BD18E4 /* SFSignInAnalytics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SFSignInAnalytics.m; sourceTree = "<group>"; };
                0CF0E2E71F8EE40700BD18E4 /* SFSignInAnalytics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SFSignInAnalytics.h; sourceTree = "<group>"; };
                0CF405F42072E2BF003D6A7F /* SFSignInAnalyticsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SFSignInAnalyticsTests.m; sourceTree = "<group>"; };
                48C2F9321E4BCFC30093D70C /* accountCirclesViewsPrint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = accountCirclesViewsPrint.m; sourceTree = "<group>"; };
                48C2F9331E4BCFC30093D70C /* accountCirclesViewsPrint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = accountCirclesViewsPrint.h; sourceTree = "<group>"; };
                48CC58971DA5FF0B00EBD9DB /* secd-66-account-recovery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-66-account-recovery.m"; sourceTree = "<group>"; };
-               48E6171A1DBEC40D0098EAAD /* SOSBackupInformation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOSBackupInformation.m; sourceTree = "<group>"; };
-               48E6171B1DBEC40D0098EAAD /* SOSBackupInformation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOSBackupInformation.h; sourceTree = "<group>"; };
                48FE668F20E6E69B00FAEF17 /* SOSAuthKitHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOSAuthKitHelpers.m; sourceTree = "<group>"; };
                48FE669520E6E69C00FAEF17 /* SOSAuthKitHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOSAuthKitHelpers.h; sourceTree = "<group>"; };
                4AF7FFF315AFB73800B9D400 /* SecOTR.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SecOTR.h; sourceTree = "<group>"; };
                52AA92881E662A4A004301A6 /* SecBackupKeybagEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecBackupKeybagEntry.m; sourceTree = "<group>"; };
                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 = "<group>"; };
                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 = "<group>"; };
                5346480717331E1200FE9172 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
                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 = "<group>"; };
                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 = "<group>"; };
-               6CB5F4781E402E5700DBF3F0 /* KeychainCKKS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = KeychainCKKS.plist; path = testrunner/KeychainCKKS.plist; sourceTree = "<group>"; };
                6CB5F4791E402E5700DBF3F0 /* KeychainEntitledTestRunner-Entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "KeychainEntitledTestRunner-Entitlements.plist"; sourceTree = "<group>"; };
                6CB5F47A1E402E5700DBF3F0 /* KeychainEntitledTestRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KeychainEntitledTestRunner.m; sourceTree = "<group>"; };
                6CB6CC022198D4BC0080AD6F /* SecDbBackupRecoverySet.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SecDbBackupRecoverySet.proto; sourceTree = "<group>"; };
                D4707A282113ECA0005BCFDA /* SecCmsMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SecCmsMessage.h; path = CMS/SecCmsMessage.h; sourceTree = "<group>"; };
                D4707A2B2114B31A005BCFDA /* SecCmsContentInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SecCmsContentInfo.h; path = CMS/SecCmsContentInfo.h; sourceTree = "<group>"; };
                D4707A2E2114C30A005BCFDA /* SecCmsDigestContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SecCmsDigestContext.h; path = CMS/SecCmsDigestContext.h; sourceTree = "<group>"; };
+               D477CB76237E453C00C02355 /* si-88-sectrust-valid-data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "si-88-sectrust-valid-data"; path = "SecurityTests/si-88-sectrust-valid-data"; sourceTree = "<group>"; };
+               D477CB7A237E4BD700C02355 /* ExceptionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ExceptionTests.m; path = tests/TrustTests/EvaluationTests/ExceptionTests.m; sourceTree = "<group>"; };
+               D477CB7D237F321400C02355 /* ExceptionTests_data.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ExceptionTests_data.h; path = tests/TrustTests/EvaluationTests/ExceptionTests_data.h; sourceTree = "<group>"; };
                D479F6E01F980F8F00388D28 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = en.lproj/Trust.strings; sourceTree = "<group>"; };
                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 = "<group>"; };
                DC0BCDB41D8C6A5B00070CB0 /* not_on_this_platorm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = not_on_this_platorm.c; sourceTree = "<group>"; };
                DC0BD4EF21BB05F2006B9154 /* CKKSKeychainBackedKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CKKSKeychainBackedKey.h; sourceTree = "<group>"; };
                DC0BD4F021BB05F2006B9154 /* CKKSKeychainBackedKey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSKeychainBackedKey.m; sourceTree = "<group>"; };
+               DC0D15FF2363A1D6007F0951 /* OTSetCDPBitOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTSetCDPBitOperation.h; sourceTree = "<group>"; };
+               DC0D16002363A1D6007F0951 /* OTSetCDPBitOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OTSetCDPBitOperation.m; sourceTree = "<group>"; };
+               DC0D16042363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTDetermineCDPBitStatusOperation.h; sourceTree = "<group>"; };
+               DC0D16052363BAF4007F0951 /* OTDetermineCDPBitStatusOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OTDetermineCDPBitStatusOperation.m; sourceTree = "<group>"; };
                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 = "<group>"; };
                DC0FA6AE2291F63F00FE01C4 /* OctagonPendingFlag.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OctagonPendingFlag.h; sourceTree = "<group>"; };
                DC3A4B601D91EAC500E46D4A /* com.apple.CodeSigningHelper.sb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = com.apple.CodeSigningHelper.sb; sourceTree = "<group>"; };
                DC3A4B621D91EAC500E46D4A /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; 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 = "<group>"; };
                DC3AA27C2097DF94007CA68A /* security_tool_commands_table.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = security_tool_commands_table.h; sourceTree = "<group>"; };
                DC3AF52A2229E6C0006577E8 /* CKKSListenerCollection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CKKSListenerCollection.h; sourceTree = "<group>"; };
                DC3AF52B2229E6C0006577E8 /* CKKSListenerCollection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSListenerCollection.m; sourceTree = "<group>"; };
                DC4269031E82EDAC002B7110 /* SecItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecItem.m; sourceTree = "<group>"; };
                DC4269061E82FBDF002B7110 /* server_security_helpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = server_security_helpers.m; sourceTree = "<group>"; };
                DC4269071E82FBDF002B7110 /* server_security_helpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = server_security_helpers.h; sourceTree = "<group>"; };
+               DC4415B323610BF40087981C /* OctagonTests+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OctagonTests+Account.swift"; sourceTree = "<group>"; };
                DC45D43B22EB619D00CEB6B7 /* OctagonStateMachineObservers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OctagonStateMachineObservers.h; sourceTree = "<group>"; };
                DC45D43C22EB619D00CEB6B7 /* OctagonStateMachineObservers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OctagonStateMachineObservers.m; sourceTree = "<group>"; };
                DC4A76A2221267D4006F2D8F /* EscrowRequestServerHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EscrowRequestServerHelpers.m; sourceTree = "<group>"; };
                DC4A76A4221267FB006F2D8F /* EscrowRequestServerHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EscrowRequestServerHelpers.h; sourceTree = "<group>"; };
                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 = "<group>"; };
                DC4D49D81F857728007AF2B8 /* CKKSSerializedKey.proto */ = {isa = PBXFileReference; lastKnownFileType = text; path = CKKSSerializedKey.proto; sourceTree = "<group>"; };
                DC4DB14E1E24692100CD6769 /* CKKSKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKKSKey.h; sourceTree = "<group>"; };
                DC4DB14F1E24692100CD6769 /* CKKSKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKKSKey.m; sourceTree = "<group>"; };
                DC7EB920211E17E500516452 /* OTAccountMetadataClassC+KeychainSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OTAccountMetadataClassC+KeychainSupport.h"; sourceTree = "<group>"; };
                DC7EB921211E17E500516452 /* OTAccountMetadataClassC+KeychainSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OTAccountMetadataClassC+KeychainSupport.m"; sourceTree = "<group>"; };
                DC7EB928211E20DF00516452 /* OctagonDataPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OctagonDataPersistenceTests.swift; sourceTree = "<group>"; };
+               DC7F6A7C233D7FAC00DF5769 /* OctagonTests+ForwardCompatibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OctagonTests+ForwardCompatibility.swift"; sourceTree = "<group>"; };
                DC7F79B522EA4ED4001FB69A /* OctagonTests+CKKS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OctagonTests+CKKS.swift"; sourceTree = "<group>"; };
                DC7F79B822EA5C72001FB69A /* OTLocalCKKSResetOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTLocalCKKSResetOperation.h; sourceTree = "<group>"; };
                DC7F79B922EA5C72001FB69A /* OTLocalCKKSResetOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OTLocalCKKSResetOperation.m; sourceTree = "<group>"; };
                DC88466C2237407500738068 /* TPDictionaryMatchingRuleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TPDictionaryMatchingRuleTests.m; sourceTree = "<group>"; };
                DC88467F2237431400738068 /* TPDictionaryMatchingRules.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TPDictionaryMatchingRules.h; sourceTree = "<group>"; };
                DC8846802237431400738068 /* TPDictionaryMatchingRules.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TPDictionaryMatchingRules.m; sourceTree = "<group>"; };
+               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 = "<group>"; };
                DC8E04901D7F6780006D80EB /* lib_ios.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = lib_ios.xcconfig; path = xcconfig/lib_ios.xcconfig; sourceTree = "<group>"; };
                DC9061B822B02BA30071474D /* TPTypes.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TPTypes.m; sourceTree = "<group>"; };
                DCA9D84521FFE7CF00B27421 /* EscrowRequestXPCProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EscrowRequestXPCProtocol.m; sourceTree = "<group>"; };
                DCA9D84B21FFF04600B27421 /* EscrowRequestServer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EscrowRequestServer.h; sourceTree = "<group>"; };
                DCA9D84C21FFF04700B27421 /* EscrowRequestServer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EscrowRequestServer.m; sourceTree = "<group>"; };
+               DCAA209823AAF8BD00DCB594 /* Container_RecoveryKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container_RecoveryKey.swift; sourceTree = "<group>"; };
                DCAB17CB21FFF6C400E1DFCF /* MockSynchronousEscrowServer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockSynchronousEscrowServer.h; sourceTree = "<group>"; };
                DCAB17CC21FFF6C400E1DFCF /* MockSynchronousEscrowServer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockSynchronousEscrowServer.m; sourceTree = "<group>"; };
                DCAB17CF2200D26700E1DFCF /* SecEscrowPendingRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecEscrowPendingRecord.h; sourceTree = "<group>"; };
                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 = "<group>"; };
                DCC78C711D8085D800865A7C /* secd-83-item-match-trusted.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-83-item-match-trusted.m"; sourceTree = "<group>"; };
                DCC78C721D8085D800865A7C /* secd-83-item-match.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "secd-83-item-match.h"; sourceTree = "<group>"; };
-               DCC78C741D8085D800865A7C /* secd-95-escrow-persistence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-95-escrow-persistence.m"; sourceTree = "<group>"; };
                DCC78C751D8085D800865A7C /* secd-100-initialsync.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-100-initialsync.m"; sourceTree = "<group>"; };
                DCC78C761D8085D800865A7C /* secd-130-other-peer-views.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-130-other-peer-views.m"; sourceTree = "<group>"; };
                DCC78C771D8085D800865A7C /* secd-154-engine-backoff.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "secd-154-engine-backoff.m"; sourceTree = "<group>"; };
                DCC78D021D8085F200865A7C /* sc-42-circlegencount.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-42-circlegencount.c"; sourceTree = "<group>"; };
                DCC78D031D8085F200865A7C /* sc-45-digestvector.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-45-digestvector.c"; sourceTree = "<group>"; };
                DCC78D041D8085F200865A7C /* sc-130-resignationticket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-130-resignationticket.c"; sourceTree = "<group>"; };
-               DCC78D061D8085F200865A7C /* sc-150-ring.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "sc-150-ring.m"; sourceTree = "<group>"; };
                DCC78D071D8085F200865A7C /* sc-150-backupkeyderivation.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-150-backupkeyderivation.c"; sourceTree = "<group>"; };
                DCC78D081D8085F200865A7C /* sc-153-backupslicekeybag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sc-153-backupslicekeybag.c"; sourceTree = "<group>"; };
                DCC78D091D8085F200865A7C /* SOSCircle_regressions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SOSCircle_regressions.h; sourceTree = "<group>"; };
                DCC78D991D8085F200865A7C /* keychain_sync_test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = keychain_sync_test.m; sourceTree = "<group>"; };
                DCC78D9A1D8085F200865A7C /* keychain_log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = keychain_log.h; sourceTree = "<group>"; };
                DCC78D9B1D8085F200865A7C /* keychain_log.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = keychain_log.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
-               DCC78D9C1D8085F200865A7C /* syncbackup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = syncbackup.h; sourceTree = "<group>"; };
-               DCC78D9D1D8085F200865A7C /* syncbackup.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = syncbackup.m; sourceTree = "<group>"; };
                DCC78D9E1D8085F200865A7C /* secViewDisplay.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = secViewDisplay.c; sourceTree = "<group>"; };
                DCC78D9F1D8085F200865A7C /* secViewDisplay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = secViewDisplay.h; sourceTree = "<group>"; };
                DCC78DA21D8085FC00865A7C /* Security_regressions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Security_regressions.h; path = Regressions/Security_regressions.h; sourceTree = "<group>"; };
                DCC78DC31D8085FC00865A7C /* si-24-sectrust-itms.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-24-sectrust-itms.c"; sourceTree = "<group>"; };
                DCC78DC51D8085FC00865A7C /* si-24-sectrust-passbook.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-24-sectrust-passbook.c"; sourceTree = "<group>"; };
                DCC78DC61D8085FC00865A7C /* si-26-sectrust-copyproperties.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-26-sectrust-copyproperties.c"; sourceTree = "<group>"; };
-               DCC78DC71D8085FC00865A7C /* si-27-sectrust-exceptions.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-27-sectrust-exceptions.c"; sourceTree = "<group>"; };
                DCC78DC81D8085FC00865A7C /* si-28-sectrustsettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "si-28-sectrustsettings.m"; sourceTree = "<group>"; };
                DCC78DC91D8085FC00865A7C /* si-28-sectrustsettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "si-28-sectrustsettings.h"; sourceTree = "<group>"; };
                DCC78DCA1D8085FC00865A7C /* si-30-keychain-upgrade.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "si-30-keychain-upgrade.c"; sourceTree = "<group>"; };
                DCE278DC1ED789EF0083B485 /* CKKSCurrentItemPointer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSCurrentItemPointer.m; sourceTree = "<group>"; };
                DCE278E61ED7A5B40083B485 /* CKKSUpdateCurrentItemPointerOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CKKSUpdateCurrentItemPointerOperation.h; sourceTree = "<group>"; };
                DCE278E71ED7A5B40083B485 /* CKKSUpdateCurrentItemPointerOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSUpdateCurrentItemPointerOperation.m; sourceTree = "<group>"; };
+               DCE405C423A04A7F00C4343B /* OctagonTests+CKKSConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OctagonTests+CKKSConfiguration.swift"; sourceTree = "<group>"; };
                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 = "<group>"; };
                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 = "<group>"; };
                        buildActionMask = 2147483647;
                        files = (
                                0C84D83D1FCF449700B822E3 /* Security.framework in Frameworks */,
+                               0CE902352395D0A3005E3F8C /* AuthKit.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                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 */,
                                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 */,
                        );
                        files = (
                                DC730E2922401F5E0051DD48 /* ProtocolBuffer.framework in Frameworks */,
                                DC730E2522401E310051DD48 /* TrustedPeers.framework in Frameworks */,
-                               1B916CD0223FFF25006657FD /* ProtocolBuffer.framework in Frameworks */,
                                BE72782C209D2C1400F0DA77 /* SecurityFoundation.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                        isa = PBXGroup;
                        children = (
                                0C9FB40120D8729A00864612 /* CoreCDP.framework */,
-                               DCD067E71D8CDF7E007602F1 /* SecCodeHostLib.h */,
-                               DCD067E81D8CDF7E007602F1 /* SecCodeHostLib.c */,
                        );
                        name = "Recovered References";
                        sourceTree = "<group>";
                };
+               0C7382E52386379E004F98CB /* ResetCloudKeychainAccount */ = {
+                       isa = PBXGroup;
+                       children = (
+                               0C7382F023863AD5004F98CB /* reset_ick_account */,
+                       );
+                       path = ResetCloudKeychainAccount;
+                       sourceTree = "<group>";
+               };
                0C78F1C816A5E13400654E08 /* regressions */ = {
                        isa = PBXGroup;
                        children = (
                                0C9AE28E214054F6003BFDB5 /* OTApplicantToSponsorRound2M1.m */,
                                0C9AE2A1214055CE003BFDB5 /* OTPairingMessage.h */,
                                0C9AE2A2214055CF003BFDB5 /* OTPairingMessage.m */,
-                               0CE9C98921B88919006BDD80 /* OTSOSMessage.h */,
-                               0CE9C98A21B8891A006BDD80 /* OTSOSMessage.m */,
                                0C9AE289214054F4003BFDB5 /* OTSponsorToApplicantRound1M2.h */,
                                0C9AE28D214054F6003BFDB5 /* OTSponsorToApplicantRound1M2.m */,
                                0C9AE28A214054F5003BFDB5 /* OTSponsorToApplicantRound2M2.h */,
                                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 */,
                                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 */,
                                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;
                                BEF88C451EAFFFED00357577 /* TrustedPeers */,
                                BEAA002C202A832500E51F45 /* TrustedPeersHelper */,
                                BED987D42099145300607A5F /* TrustedPeersHelperUnitTests */,
+                               0C7382E52386379E004F98CB /* ResetCloudKeychainAccount */,
                        );
                        path = keychain;
                        sourceTree = "<group>";
                                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 */,
                DCA4D2121E5651950056214F /* Tests (Live CloudKit) */ = {
                        isa = PBXGroup;
                        children = (
-                               6CB5F4781E402E5700DBF3F0 /* KeychainCKKS.plist */,
                                6CF4A0B51E45488B00ECD7B5 /* KeychainEntitledTestApp_mac */,
                                6CF4A0E11E4549F200ECD7B5 /* KeychainEntitledTestApp_ios */,
                                6CB5F4771E402D6D00DBF3F0 /* testrunner */,
                                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 */,
                                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 */,
                                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 */,
                                DCC78D2A1D8085F200865A7C /* SOSBackupSliceKeyBag.h */,
                                48776C731DA5BB4200CC09B9 /* SOSRecoveryKeyBag.m */,
                                48776C741DA5BB4200CC09B9 /* SOSRecoveryKeyBag.h */,
-                               48E6171A1DBEC40D0098EAAD /* SOSBackupInformation.m */,
-                               48E6171B1DBEC40D0098EAAD /* SOSBackupInformation.h */,
                                DCC78D2B1D8085F200865A7C /* SOSUserKeygen.m */,
                                DCC78D2C1D8085F200865A7C /* SOSUserKeygen.h */,
                                485B64081DC16E8300B771B9 /* SOSKeyedPubKeyIdentifier.c */,
                                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 */,
                                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 */,
                E7D848031C6BEFAB0025BB44 /* Tests */ = {
                        isa = PBXGroup;
                        children = (
+                               52DA3C6F23C7E63500FEEDFF /* KCTLKRequestTest.m */,
                                E7CFF7221C8660A000E3484E /* KeychainCircle.plist */,
                                E7D848061C6BEFFA0025BB44 /* Info.plist */,
                                E7D848041C6BEFC10025BB44 /* KCSRPTests.m */,
                E7FCBE401314471B000DE34E /* Frameworks */ = {
                        isa = PBXGroup;
                        children = (
+                               DC89608C2395C75500D339D9 /* CoreServices.framework */,
                                BEC6A9142331992800080069 /* Network.framework */,
                                D47AB2CA2356AD72005A3801 /* Network.framework */,
                                0C6C2B6C2258295D00C53C96 /* UIKitCore.framework */,
                                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 */,
                                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 */,
                                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 */,
                                DC3502B11E0208BE00BC0587 /* Sources */,
                                DC3502B21E0208BE00BC0587 /* Frameworks */,
                                DC9A2C791EB40A64008FAC27 /* Embed OCMock */,
-                               DC7162D41EB4154D000D2BB5 /* Copy BATS Test Discovery Plist */,
                                DC7162D61EB4157D000D2BB5 /* ShellScript */,
                        );
                        buildRules = (
                        isa = PBXProject;
                        attributes = {
                                LastSwiftUpdateCheck = 1000;
-                               LastUpgradeCheck = 1000;
+                               LastUpgradeCheck = 1120;
                                TargetAttributes = {
                                        4381690B1B4EDCBD00C54D58 = {
                                                CreatedOnToolsVersion = 7.0;
                                EB7E90F12193F90700B1FA21 /* Build C2 Metrics */,
                                3D58392D21890FFB000ACA44 /* SecExperimentTests */,
                                5A442F81233C330F00918373 /* experimentTool */,
+                               0CA378E123876DD100090B7E /* reset_account */,
                        );
                };
 /* End PBXProject section */
                                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;
                };
                                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;
                };
                        );
                        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;
                                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 */,
                                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 */,
                                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;
                                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 */,
                                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 */,
                        );
                                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 */,
                                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 */,
                        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 */,
                        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 */,
                                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 */,
                        files = (
                                DC52EDA01D80D4F700B0A59C /* sd-10-policytree.m in Sources */,
                                DC52ED9F1D80D4F200B0A59C /* SOSTransportTestTransports.m in Sources */,
-                               DC52ED9E1D80D4ED00B0A59C /* secd-95-escrow-persistence.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                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 */,
                                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 */,
                        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 */,
                                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 */,
                                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 */,
                                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 */,
                                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 */,
                                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 */,
                                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 */,
                                E7F4809C1C74E85200390FDB /* KCDerTest.m in Sources */,
                                E7D848051C6BEFCD0025BB44 /* KCSRPTests.m in Sources */,
                                E7F4809E1C74E86D00390FDB /* KCAESGCMTest.m in Sources */,
+                               52DA3C7123C7E63600FEEDFF /* KCTLKRequestTest.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                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 */,
                                EB49B2D1202DF15F003F34A0 /* SFAnalyticsActivityTracker.m in Sources */,
                                EB49B2D0202DF14D003F34A0 /* SFAnalytics.m in Sources */,
                                EBC73F2820993FDA00AE3350 /* SFAnalyticsSampler.m in Sources */,
-                               EBDCC001233DD3E000806566 /* MockAKSRefKey.proto in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        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 */;
                                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 = "";
                                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 = "";
                        };
                        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 = {
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                        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 = (
                        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 = (
                        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)";
                        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)";
                                        "$(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;
                                        "$(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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                        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;
                        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;
                        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;
                        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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                        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;
                        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;
                        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;
                        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;
                                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;
                                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;
                                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;
                                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;
                        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)";
                        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)";
                        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 = (
                        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 = (
                        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 = (
                        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 = (
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                        "-framework",
                                        Security,
                                        "$(OTHER_LDFLAGS_FOR_SECURITYD)",
+                                       "-framework",
+                                       TrustedPeers,
                                );
                                PRODUCT_BUNDLE_IDENTIFIER = com.apple.security.CKKSTests;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                        "-framework",
                                        Security,
                                        "$(OTHER_LDFLAGS_FOR_SECURITYD)",
+                                       "-framework",
+                                       TrustedPeers,
                                );
                                PRODUCT_BUNDLE_IDENTIFIER = com.apple.security.CKKSTests;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                        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;
                        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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                        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)";
                        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)";
                        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)";
                        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)";
                                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;
                                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;
                        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;
                        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;
                        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;
                        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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                        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 = (
                        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 = (
                        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 = (
                        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 = (
                        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;
                        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;
                        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;
                        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;
                        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;
                        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;
                                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;
                                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;
                        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;
                        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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                MTL_ENABLE_DEBUG_INFO = YES;
                                ONLY_ACTIVE_ARCH = YES;
                                OTHER_LDFLAGS = (
-                                       "$(OTHER_LDFLAGS_MOBILEGESTALT)",
                                        "$(OTHER_LDFLAGS_UPWARD_FOUNDATION)",
                                        "$(OTHER_LDFLAGS_UPWARD_PROTOCOLBUFFER)",
                                );
                                MODULEMAP_FILE = Modules/KeychainCircle.modulemap;
                                MTL_ENABLE_DEBUG_INFO = NO;
                                OTHER_LDFLAGS = (
-                                       "$(OTHER_LDFLAGS_MOBILEGESTALT)",
                                        "$(OTHER_LDFLAGS_UPWARD_FOUNDATION)",
                                        "$(OTHER_LDFLAGS_UPWARD_PROTOCOLBUFFER)",
                                );
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                                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;
                        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 = (
index c30a62b4dfab81ccdb6af86fd5d519802b3b2416..577b55c3ec2157a7abfbc876fc0f5f6dae384209 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
@@ -41,6 +41,9 @@
                <Test
                   Identifier = "CloudKitKeychainSyncingMockXCTest">
                </Test>
+               <Test
+                  Identifier = "CloudKitKeychainSyncingMultiZoneTestsBase">
+               </Test>
                <Test
                   Identifier = "CloudKitKeychainSyncingTestsBase">
                </Test>
@@ -50,8 +53,6 @@
             </SkippedTests>
          </TestableReference>
       </Testables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -66,8 +67,6 @@
       migratedStopOnEveryIssue = "YES"
       debugServiceExtension = "internal"
       allowLocationSimulation = "YES">
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
index 34a200c9a9467201981690e89073a66a242f0e02..0ecee1d0459596f53538a366d836ff1da55f10c7 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
@@ -39,8 +39,6 @@
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -52,8 +50,6 @@
       debugDocumentVersioning = "YES"
       debugServiceExtension = "internal"
       allowLocationSimulation = "YES">
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
index 3fbabf063001538d9af87c45d71938afdb77d4e5..51730e53983b9985a8c43ea2428c00671b8cdb83 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
       codeCoverageEnabled = "YES"
-      onlyGenerateCoverageForSpecifiedTargets = "YES"
-      shouldUseLaunchSchemeArgsEnv = "YES">
+      onlyGenerateCoverageForSpecifiedTargets = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "D4707A0421136E69005BCFDA"
+            BuildableName = "TrustTests.xctest"
+            BlueprintName = "TrustTests_ios"
+            ReferencedContainer = "container:Security.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
       <CodeCoverageTargets>
          <BuildableReference
             BuildableIdentifier = "primary"
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "D4707A0421136E69005BCFDA"
-            BuildableName = "TrustTests.xctest"
-            BlueprintName = "TrustTests_ios"
-            ReferencedContainer = "container:Security.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -88,8 +86,6 @@
             ReferencedContainer = "container:Security.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
index 42212060feeb9e6dd6fee04fae99df3b3c45dfd5..f14558e0d3b23a307fb502020f108cc5019fd2a7 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
       codeCoverageEnabled = "YES"
-      onlyGenerateCoverageForSpecifiedTargets = "YES"
-      shouldUseLaunchSchemeArgsEnv = "YES">
+      onlyGenerateCoverageForSpecifiedTargets = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "D453A4A42122236D00850A26"
+            BuildableName = "TrustTests.xctest"
+            BlueprintName = "TrustTests_macos"
+            ReferencedContainer = "container:Security.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
       <CodeCoverageTargets>
          <BuildableReference
             BuildableIdentifier = "primary"
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "D453A4A42122236D00850A26"
-            BuildableName = "TrustTests.xctest"
-            BlueprintName = "TrustTests_macos"
-            ReferencedContainer = "container:Security.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -88,8 +86,6 @@
             ReferencedContainer = "container:Security.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
index 2f0c83a9b6921a1bda0f5a6945ac40231a65e5c8..52c6d628822d5f1afbf46d8dd220771cf82c954a 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      codeCoverageEnabled = "YES"
-      shouldUseLaunchSchemeArgsEnv = "YES">
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      codeCoverageEnabled = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "BEF88C271EAFFC3F00357577"
+            BuildableName = "TrustedPeers.framework"
+            BlueprintName = "TrustedPeers"
+            ReferencedContainer = "container:Security.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
       <Testables>
          <TestableReference
             skipped = "NO">
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "BEF88C271EAFFC3F00357577"
-            BuildableName = "TrustedPeers.framework"
-            BlueprintName = "TrustedPeers"
-            ReferencedContainer = "container:Security.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -71,8 +69,6 @@
             ReferencedContainer = "container:Security.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
index 6a67afc5f3948f2eea82e0789847a5f163f17103..d5c67142667dbf17aa6c3eeab45273f9bbd2f50f 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.8">
    <BuildAction
       parallelizeBuildables = "NO"
             argument = "si_26_sectrust_copyproperties"
             isEnabled = "NO">
          </CommandLineArgument>
-         <CommandLineArgument
-            argument = "si_27_sectrust_exceptions"
-            isEnabled = "NO">
-         </CommandLineArgument>
          <CommandLineArgument
             argument = "si_28_sectrustsettings"
             isEnabled = "NO">
index 11eaea1e35841be59011ae2396ba1f26ef81c8e7..06344ef9ad7f0687967aa5345d6089ad92235139 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
             argument = "si_26_sectrust_copyproperties"
             isEnabled = "NO">
          </CommandLineArgument>
-         <CommandLineArgument
-            argument = "si_27_sectrust_exceptions"
-            isEnabled = "NO">
-         </CommandLineArgument>
          <CommandLineArgument
             argument = "si_28_sectrustsettings"
             isEnabled = "NO">
index d1fcb4cf5e20e00bd56962a18f3301d8034dedf8..cf5cb28f9ffa2bd44de96c89a0e5177b82a3b81b 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "2.0">
    <BuildAction
       parallelizeBuildables = "NO"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "0C0BDB2E175685B000BC1A7E"
+            BuildableName = "secdtests"
+            BlueprintName = "secdtests_ios"
+            ReferencedContainer = "container:Security.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
       <Testables>
          <TestableReference
             skipped = "NO">
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "0C0BDB2E175685B000BC1A7E"
-            BuildableName = "secdtests"
-            BlueprintName = "secdtests_ios"
-            ReferencedContainer = "container:Security.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
             isEnabled = "NO">
          </EnvironmentVariable>
       </EnvironmentVariables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Debug"
index 09059a5058385ecfcfe208a985066e1a1c0b820e..cb70464e3ab66128eb1c69764ee8977dce6b496e 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
             argument = "si_26_sectrust_copyproperties"
             isEnabled = "NO">
          </CommandLineArgument>
-         <CommandLineArgument
-            argument = "si_27_sectrust_exceptions"
-            isEnabled = "NO">
-         </CommandLineArgument>
          <CommandLineArgument
             argument = "si_28_sectrustsettings"
             isEnabled = "NO">
index 30a0789a8c80ba9d8cfc708596c3ef869ffcf4d3..034c0652363faa5987811bf6ca2881ea658b7d48 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
index 67510aa41ad6fae33a9c999a5f53682398cbdccc..a64e3fd148f8c68adab0b9cead97a11e535b72f4 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
@@ -41,8 +41,6 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
-      <Testables>
-      </Testables>
       <MacroExpansion>
          <BuildableReference
             BuildableIdentifier = "primary"
@@ -52,8 +50,8 @@
             ReferencedContainer = "container:Security.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
+      <Testables>
+      </Testables>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -75,8 +73,6 @@
             ReferencedContainer = "container:Security.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
index bca98ccebac2a150cee48b5e28af87d7abed0fd8..ac9beb1a97a88d2e0fa92ee2237780a234bba7c5 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1000"
+   LastUpgradeVersion = "1120"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "NO"
@@ -23,8 +23,6 @@
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -36,8 +34,6 @@
       debugDocumentVersioning = "YES"
       debugServiceExtension = "internal"
       allowLocationSimulation = "YES">
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
index e52156b70b50712fd3ed1195be4ee7afc3197950..1fae8a91028717474093470da2301457d4c8da3d 100644 (file)
@@ -184,14 +184,12 @@ command_sos_control(__unused int argc, __unused char * const * argv)
         bool gbinfo = false;
         bool gbtriggered = false;
         bool circleHash = false;
-        bool triggerRingUpdate = false;
 
         static struct option long_options[] =
         {
             /* These options set a flag. */
             {"assertStashAccountKey",   no_argument, NULL, 'a'},
             {"trigger-backup",   optional_argument, NULL, 'B'},
-            {"trigger-ring-update",   no_argument, NULL, 'R'},
             {"trigger-sync",   optional_argument, NULL, 's'},
             {"circle-hash", optional_argument, NULL, 'H'},
             {"ghostbustByMID",   optional_argument, NULL, 'M'},
@@ -203,7 +201,7 @@ command_sos_control(__unused int argc, __unused char * const * argv)
             {0, 0, 0, 0}
         };
 
-        while ((ch = getopt_long(argc, argv, "as:AB:GHIMRST", long_options, &option_index)) != -1) {
+        while ((ch = getopt_long(argc, argv, "as:AB:GHMSIT", long_options, &option_index)) != -1) {
             switch  (ch) {
                 case 'a': {
                     assertStashAccountKey = true;
@@ -241,9 +239,6 @@ command_sos_control(__unused int argc, __unused char * const * argv)
                     gboptions |= SOSGhostBustByMID;
                     break;
                 }
-                case 'R':
-                    triggerRingUpdate = true;
-                    break;
                 case 'S': {
                     gboptions |= SOSGhostBustBySerialNumber;
                     break;
@@ -305,7 +300,7 @@ command_sos_control(__unused int argc, __unused char * const * argv)
         } else if (triggerSync) {
             [[control.connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError *error) {
                 printControlFailureMessage(error);
-            }] rpcTriggerSync:syncingPeers complete:^(bool res, NSError *error) {
+            }] triggerSync:syncingPeers complete:^(bool res, NSError *error) {
                 if (res) {
                     printf("starting to sync was successful\n");
                 } else {
@@ -315,21 +310,11 @@ command_sos_control(__unused int argc, __unused char * const * argv)
         } else if (triggerBackup) {
             [[control.connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError *error) {
                 printControlFailureMessage(error);
-            }] rpcTriggerBackup:backupPeers complete:^(NSError *error) {
+            }] triggerBackup:backupPeers complete:^(NSError *error) {
                 if (error == NULL) {
                     printf("trigger backup was successful\n");
                 } else {
-                    printf("%s", [[NSString stringWithFormat:@"Failed to start backup: %@\n", error] UTF8String]);
-                }
-            }];
-        } else if (triggerRingUpdate) {
-            [[control.connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError *error) {
-                printControlFailureMessage(error);
-            }] rpcTriggerRingUpdate:^(NSError *error) {
-                if (error == NULL) {
-                    printf("trigger ring update was successful\n");
-                } else {
-                    printf("%s", [[NSString stringWithFormat:@"Failed to start ring update: %@\n", error] UTF8String]);
+                    printf("%s", [[NSString stringWithFormat:@"Failed to start sync: %@\n", error] UTF8String]);
                 }
             }];
 
index de949106a59330237126bf835548880b78d44fd1..61f5d813a2d66149385b33a0e006e4573c5e4649 100644 (file)
@@ -29,5 +29,4 @@
 #include "keychain/SecureObjectSync/Tool/keychain_sync.h"
 #include "keychain/SecureObjectSync/Tool/keychain_sync_test.h"
 #include "keychain/SecureObjectSync/Tool/keychain_log.h"
-#include "keychain/SecureObjectSync/Tool/syncbackup.h"
 #include "keychain/SecureObjectSync/Tool/recovery_key.h"
index e475dcdbd7ef22273e73a0bbcc739185e8400add..2e05a8139104f0c38ebb4645f5dd1f2ba46858ec 100644 (file)
@@ -50,7 +50,7 @@ extern const NSString *SecExperimentConfigurationKeyConfigurationData;
  * @abstract
  *      Create an ARC-able `sec_experiment_t` instance wrapping an internal `SecExperiment` object.
  *
- * @param experiment_name
+ * @param experiment
  *      Name of the experiment.
  *
  * @return a `sec_experiment_t` instance.
diff --git a/keychain/ResetCloudKeychainAccount/reset_ick_account b/keychain/ResetCloudKeychainAccount/reset_ick_account
new file mode 100644 (file)
index 0000000..79e748b
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/python
+#
+
+import sys
+from glob import glob
+import subprocess
+import re
+import os
+import argparse
+
+parser = argparse.ArgumentParser(description='Reset your iCloud Keychain account')
+parser.add_argument('icloudpassword', help='iCloud password')
+parser.add_argument('passcode', help='passcode or password of the local device')
+args = vars(parser.parse_args())
+
+
+iCloudPassword = args['icloudpassword']
+passcode = args['passcode']
+
+def set_security_mac_cmd():
+        return 'security2'
+
+def set_security_ios_cmd():
+        return 'security'
+
+def security_cmd_by_platform():
+        swVers = subprocess.check_output(["sw_vers"])
+        deviceInformation = str(swVers, 'utf-8')
+        if "Mac OS X" in deviceInformation:
+                print("using security2 command on macosx")
+                return set_security_mac_cmd()
+        elif "iPhone OS" in deviceInformation:
+                print("using security command on ios")
+                return set_security_ios_cmd()
+        else:
+                print("unsupported platform")
+                sys.exit(1)
+
+security_cmd = security_cmd_by_platform()
+
+print("resetting octagon")
+subprocess.check_output(["otctl", "resetoctagon"])
+
+print("resetting ckks")
+subprocess.check_output(["ckksctl", "reset-cloudkit"])
+
+print("resetting SOS")
+subprocess.check_output([security_cmd, "sync", "-C"])
+subprocess.check_output([security_cmd, "sync", "-P", "$iCloudPassword"])
+subprocess.check_output([security_cmd, "sync", "-O"])
+
+print("deleting all escrow records")
+subprocess.check_output(["stingrayutil", "--deleteAll", "ReallyDeleteAll"])
+
+print("creating new escrow record")
+subprocess.check_output(["sbdtool", "passcode_request_trigger"])
+subprocess.check_output(["sbdtool", "passcode_request_provide_passcode", "$passcode"])
+
index fb048fd43c76147ca047aa77eeaff1e055377c8d..3bdeb8b15c91939138362e76dcfa5f20897db14d 100644 (file)
@@ -13,7 +13,6 @@ ONE_TEST(sc_42_circlegencount)
 ONE_TEST(sc_45_digestvector)
 
 ONE_TEST(sc_130_resignationticket)
-ONE_TEST(sc_150_Ring)
 
 ONE_TEST(sc_150_backupkeyderivation)
 ONE_TEST(sc_153_backupslicekeybag)
diff --git a/keychain/SecureObjectSync/Regressions/sc-150-ring.m b/keychain/SecureObjectSync/Regressions/sc-150-ring.m
deleted file mode 100644 (file)
index c547a98..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-//
-//  sc-150-ring.c
-//  sec
-//
-//  Created by Richard Murphy on 3/3/15.
-//
-//
-
-#include <stdio.h>
-/*
- * 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 <Security/SecBase.h>
-#include <Security/SecItem.h>
-#include <Security/SecKeyPriv.h>
-
-#include "keychain/SecureObjectSync/SOSRing.h"
-#include "keychain/SecureObjectSync/SOSRingTypes.h"
-#include "keychain/SecureObjectSync/SOSRingUtils.h"
-#include <Security/SecureObjectSync/SOSPeerInfo.h>
-#include "keychain/SecureObjectSync/SOSInternal.h"
-#include "keychain/SecureObjectSync/SOSUserKeygen.h"
-
-#include <utilities/SecCFWrappers.h>
-
-#include <CoreFoundation/CoreFoundation.h>
-
-#include <stdlib.h>
-#include <unistd.h>
-
-#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 (file)
index 63660de..0000000
+++ /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 <Foundation/Foundation.h>
-#include <SecureObjectSync/SOSEngine.h>
-#include <SecureObjectSync/SOSPeer.h>
-#include <SecureObjectSync/SOSInternal.h>
-#include <SecureObjectSync/SOSCloudCircle.h>
-
-#include "SOSCircle_regressions.h"
-
-#include <corecrypto/ccsha2.h>
-
-#include <utilities/SecCFWrappers.h>
-#include <utilities/debugging.h>
-
-#include <stdint.h>
-
-#include <AssertMacros.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <CoreFoundation/CFDate.h>
-#include <getopt.h>
-
-#include <notify.h>
-#include <dispatch/dispatch.h>
-
-#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;
-}
index a50be3eee2512ffeed7a41a09262ca17692da56f..f7674f30d0ee44dd0fa43a4970a636e015c91c4c 100644 (file)
@@ -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
 //
index cd5951a2545f654c916d3861f1ad7025444e5582..b75b48cfbfe534b0ca3015369681b8e0ec966c83 100644 (file)
@@ -113,7 +113,6 @@ static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void);
 @property (readwrite) CKKSPBFileStorage<SOSAccountConfiguration*>* accountConfiguration;
 
 @property CKKSNearFutureScheduler *performBackups;
-@property CKKSNearFutureScheduler *performRingUpdates;
 @end
 #endif
 
@@ -124,11 +123,6 @@ static NSDictionary<OctagonState*, NSNumber*>* 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<OctagonState*, NSNumber*>* 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<OctagonState*, NSNumber*>* SOSStateMap(void);
 
         self.settings =  [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
 
-        [self ensureFactoryCircles];
-        SOSAccountEnsureUUID(self);
-
 #if OCTAGON
         [self setupStateMachine];
 #endif
@@ -248,13 +244,6 @@ static NSDictionary<OctagonState*, NSNumber*>* 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 <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
+- (void)triggerSync:(NSArray <NSString *> *)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<NSString *>* _Nullable)backupPeers complete:(void (^)(NSError *error))complete
+- (void)triggerBackup:(NSArray<NSString *>* _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<OctagonState*, NSNumber*>* SOSStateMap(void) {
     static NSDictionary<OctagonState*, NSNumber*>* map = nil;
@@ -2941,7 +2808,6 @@ static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void) {
             SOSStateReady:                              @0U,
             SOSStateError:                              @1U,
             SOSStatePerformBackup:                      @2U,
-            SOSStatePerformRingUpdate:                  @3U,
         };
     });
     return map;
@@ -2952,8 +2818,7 @@ static NSSet<OctagonFlag*>* SOSFlagsSet(void) {
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
         set = [NSSet setWithArray:@[
-            SOSFlagTriggerBackup,
-            SOSFlagTriggerRingUpdate,
+            SOSFlagTriggerBackup
         ]];
     });
     return set;
@@ -2995,31 +2860,15 @@ static NSSet<OctagonFlag*>* 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<OctagonFlag*>* 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<NSString*>*)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<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
                                                                                                         flags:(nonnull OctagonFlags *)flags
                                                                                                  pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)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<OctagonFlag*>* 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;
index 3a631f1f4a3c0d2e984c3f2185163dd35100cf68..17c724d5b63a762654f01034aefa5719fbc65196 100644 (file)
@@ -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;
-}
index bb26993a25e8223d2d13c1b64582d8bf06f00fd4..8bba993c0d7917696448e3ac20e1c24eafb7e787 100644 (file)
@@ -8,5 +8,4 @@ package SOS;
 
 message AccountConfiguration {
     repeated string pendingBackupPeers = 1;
-    optional bool ringUpdateFlag = 2;
 }
index a2d80366e3fda6d9787e0e9725460a6aa152be27..31f28a5cd5ca4e23f3cf5d43e7b506bd7c216218 100644 (file)
@@ -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;
index 474335b323614032e900f3f75daf29700b9c0a4d..cae09dfbbfb35c2192deb5b83eb8299dee44f91a 100644 (file)
@@ -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) {
index eb4b7a0f076ec329d1d4b293f9c85d17a5b98d40..5b63e744296ff4a3370da4840f7b429bd1fbe1d3 100644 (file)
@@ -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;
     }];
index 798d1fb17832ee9aa5c7023a9a386669548970f5..249a8e00061f829ff46619c11efdd67bd9eecceb 100644 (file)
@@ -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<NSString*>*)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<NSDictionary*>* SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks);
 #endif
 
 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error);
-bool SOSAccountPopulateKVSWithBadKeys(SOSAccount*  account, CFErrorRef* error);
 
 @end
 
index 39d749939239d09ff496beb2a9a5cd4f73316a53..cd272013ac04f9a12504e5306d4ab610891d012a 100644 (file)
@@ -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)) {
index 30e18ed5fd4ad7d13caa96286f2cec7a227bc0ca..2ebcd729985e0f6fc264387ccb4f76a98ae0b610 100644 (file)
@@ -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);
   
index 168d096d2f89625fb541b82dc62da012e9548474..1c014b29b2428662d3acfb5aecc156bd6e0d0c99 100644 (file)
@@ -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];
index f24e67135a20de79eea9a423530443e0385f63af..9c0cad82bce4d5ffd64c75fd6184e09efaf03c71 100644 (file)
@@ -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.h b/keychain/SecureObjectSync/SOSBackupInformation.h
deleted file mode 100644 (file)
index b11d57b..0000000
+++ /dev/null
@@ -1,49 +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.h
-//  Security
-//
-
-#ifndef SOSBackupInformation_h
-#define SOSBackupInformation_h
-
-#include <CoreFoundation/CoreFoundation.h>
-#import "keychain/SecureObjectSync/SOSAccountTransaction.h"
-
-enum {
-    noError = 0,
-    noTxnorAcct,
-    noAlloc,
-    noTrustedPubKey,
-    noBSKBs,
-};
-
-extern const CFStringRef kSOSBkpInfoStatus;
-extern const CFStringRef kSOSBkpInfoBSKB;
-extern const CFStringRef kSOSBkpInfoRKBG;
-
-CFDictionaryRef SOSBackupInformation(SOSAccountTransaction* txn, CFErrorRef *error);
-
-#endif /* SOSBackupInformation_h */
diff --git a/keychain/SecureObjectSync/SOSBackupInformation.m b/keychain/SecureObjectSync/SOSBackupInformation.m
deleted file mode 100644 (file)
index 8b9b917..0000000
+++ /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 <CoreFoundation/CFNumber.h>
-#include <utilities/SecCFWrappers.h>
-
-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;
-}
index 68d47e37568dede44dddaf17a18592ca6f64788d..f953e859d816c67fc1cf375e422807e8a5794924 100644 (file)
@@ -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;
 }
 
index 46bbae1e83b4a7d21289cb757339896efb85d912..0f3df9b60b6e81dc9d6bf2cc94d73632a889d5ee 100644 (file)
@@ -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>], 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.
index d1af2e1abcbfc71a8b23607c27518fd4f1117051..a186bb76c64809e481a912de45e45684dcde2ede 100644 (file)
@@ -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);
index 36e5dfbd6ab693da5e8d2f97e554ac675359ae38..b174896fb48b4dd5599911755a38b9046470fb81 100644 (file)
@@ -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));
index aaf58c28cb1f1aeeeba48602d18787f379029aec..d700b69e3749e035f3f8a872f61ebe4487c7b5a5 100644 (file)
@@ -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];
 }
index 5f8a748d97d8af95d444b10dcb277382669b7a9b..46332efa14ac77568bf85cbb5c1899884efe4e4c 100644 (file)
     [self.account importInitialSyncCredentials:items complete:complete];
 }
 
-- (void)rpcTriggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
+- (void)triggerSync:(NSArray <NSString *> *)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
     [self.account ghostBustInfo:complete];
 }
 
-- (void)rpcTriggerBackup:(NSArray<NSString *>* _Nullable)backupPeers complete:(void (^)(NSError *error))complete
+- (void)triggerBackup:(NSArray<NSString *>* _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
 
index 66d25d5eba843f92ab7fc84c19e10c397510c7a8..4417dd34b64408179c64bc684efc73a49526f599 100644 (file)
@@ -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
index 2d265c6e0aab4c3d4a61d9a17b9d1340f7148f9b..dce3a3f8e954290a9d4a24788bc5d26db23ba3b3 100644 (file)
@@ -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);
index 7b2bee13609520cc2b6c78e1902265c7fa7c9fce..adea27880f366c513dd08c2895fadcff17820bb9 100644 (file)
@@ -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) {
index 253891782e0a0b248404e0970367d18083751250..0e6579b4ea8bb0bd909978acaefca600f5cd325e 100644 (file)
@@ -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
index f75c33216a9372f9e65632fe2921fb460fba6a68..915cc40376b2055ee4b5f0b45525232e6eff7647 100644 (file)
@@ -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;
index 5562db2882abe11dbb3b3c9d467d74dec9fef406..87c682fec597de7c1de0608bfde5b24dd2a0aad1 100644 (file)
@@ -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){
index 25dd226bc6431b9151ecbb72355a5ff56d6a4e9c..8c97f793a1c1bf171565c9ce9f36ea22f04b8f27 100644 (file)
@@ -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,
index 6a5ed6be29232f3b8e70dcda68081870c71866cf..e7b6e995aab0c1398cdfc0aa24d5fcfcd6fb1a98 100644 (file)
@@ -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);
index bb0d62215e15b48fbc9d8ea8fc2cfb1a55b84a63..ddff4c19b057f831bf8c9fc6049afd43059054c3 100644 (file)
@@ -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);
index 8457983684571c89db881dd2207d2b24e1171a3f..32e280564e6abe12ce45a86ff79096db7c6a57e5 100644 (file)
@@ -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,
index a9e748aef107662c4eec7d15f373e589bd2d60d0..65f2d9e645f8bf653b03227b3e742ee933e8ad5a 100644 (file)
@@ -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 <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete;
+- (void)triggerSync:(NSArray <NSString *> *)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<NSString *>*)backupPeers complete:(void (^)(NSError *error))complete;
-- (void)rpcTriggerRingUpdate:(void (^)(NSError *error))complete;
-
+- (void)triggerBackup:(NSArray<NSString *>*)backupPeers complete:(void (^)(NSError *error))complete;
 @end
 #endif
 
index 141de9d9f8a9d03be66440f4aff193534685cc26..6d7d9ec5670204167e5e31b3c0e1de676d227f00 100644 (file)
@@ -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." )
+
+
index 4d17e4e594c37557cb03075a2f8130159500bed0..5cb4adc9473cfc58115896917539282125239179 100644 (file)
@@ -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);
index f836de1f370de253c8781f311d534d7be6363233..beca9f352e04d62b5e3b5a80ad80f1bf42b15ef5 100644 (file)
@@ -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.h b/keychain/SecureObjectSync/Tool/syncbackup.h
deleted file mode 100644 (file)
index ccacc3c..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (c) 2013-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@
- */
-
-//
-//  syncbackup.h
-//  sec
-//
-//
-//
-
-
-#include "SecurityTool/sharedTool/security_tool_commands.h"
-
-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")
-
diff --git a/keychain/SecureObjectSync/Tool/syncbackup.m b/keychain/SecureObjectSync/Tool/syncbackup.m
deleted file mode 100644 (file)
index 5216ad1..0000000
+++ /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 <stdio.h>
-#include <CoreFoundation/CoreFoundation.h>
-
-#include <Security/SecureObjectSync/SOSCloudCircle.h>
-#include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
-#include "keychain/SecureObjectSync/SOSBackupInformation.h"
-#include "keychain/SecureObjectSync/SOSRecoveryKeyBag.h"
-#include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h>
-
-#include <utilities/SecCFWrappers.h>
-
-#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;
-}
index c381e08a2fea9ce62098e130f0d9e593a2e0de67..1930811b07c1ba3e61d94d468cc51646fa851a1e 100644 (file)
@@ -15,9 +15,9 @@ __attribute__((visibility("hidden")))
 @interface SOSAccountConfiguration : PBCodable <NSCopying>
 {
     NSMutableArray<NSString *> *_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;
index 0314479aa90696e61bf31ed1c66202df38c26294..f394355f408a4ed6132f3d591082b6ee8ed58d21 100644 (file)
 {
     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
 {
     {
         [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 (file)
index 0000000..9ddd0d1
--- /dev/null
@@ -0,0 +1,3 @@
+disabled_rules:
+    - force_cast
+    - force_try
index a97ab461bf2cdc44de99ab57380486e56ff23dc3..9689ecaea15664390f231b2ab3f6b21e347c04b9 100644 (file)
@@ -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))")
-                })
+                }
             }
         }
 
index ff8a89298ae6b5a86ced49656b8c32c975a050eb..d18c8a9f35b6dd165c40c0112b65738e7cf45edc 100644 (file)
@@ -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
         }
 
index 04bb5f276f352245d5f53e921c72dddc3567938b..4669d72f4e7175ef70103cdba88dd18b67f6cf5e 100644 (file)
@@ -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<CFString, Any>) 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<CFString, Any> = 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 <CFString, Any>]? {
-        var keySet: [Dictionary<CFString, Any>]?
+    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<CFString, Any>] {
+            if let dictionaryArray = result as? [[CFString: Any]] {
                 keySet = dictionaryArray
             } else {
-                if let dictionary = result as? Dictionary<CFString, Any> {
+                if let dictionary = result as? [CFString: Any] {
                     keySet = [dictionary]
                 } else {
                     keySet = nil
index 0a062b7910c361672deba6c2274855fedf7761b3..64dfe9f122a5044102f17423eb7588d27b7e136a 100644 (file)
@@ -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<String>,
+                                           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<String>?, 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<String>?, 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<String>?, 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<String>?, 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<String>?, 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<String>?, 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<String>?, 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<TPPolicyVersion>,
+                              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))
         }
 
index fffd68b15587078f20f49bb6814719b6545f45c6..b5ac7067ccdd4672124b24076aebab86180f0b03 100644 (file)
@@ -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<String> = 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<PolicyMO>
         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<String>?, 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<String>?, 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)
 
+            // <rdar://problem/56270219> 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<String>?, 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<String>?, 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<BottleMO> 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: <rdar://problem/47899980> 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<String>?, TPPolicy?, Error?) -> Void) {
         self.semaphore.wait()
-        let reply: (TPPolicy?, Error?) -> Void = {
-            os_log("fetchPolicy complete: %@", log: tplogTrace, type: .info, traceError($1))
+        let reply: (Set<String>?, 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<String>, 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<TPPolicyVersion>,
+                              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<TPPolicyVersion>,
+                                           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)] ?? "<nil>", 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<String>?, 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<String>?, 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<String>?, 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<String>?, 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 ?? "<NULL>")
+            os_log("getState complete: %{public}@", log: tplogTrace, type: .info, $0.egoPeerID ?? "<NULL>")
             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<T: Equatable>(_ 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
index 59f539dcabe1076472b0f437b3da67cfaa0bceec..73a8fdfa89491dbddc609a33a5f16c1cb6bac01a 100644 (file)
@@ -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 (file)
index 0000000..ff281da
--- /dev/null
@@ -0,0 +1,81 @@
+import CoreData
+import Foundation
+
+extension Container {
+    func preflightVouchWithBottle(bottleID: String,
+                                  reply: @escaping (String?, Set<String>?, TPPolicy?, Error?) -> Void) {
+        self.semaphore.wait()
+        let reply: (String?, Set<String>?, 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)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
index 0a8dd0bcaee916e7ef85ef186dc0af3656a4252f..19e76cbb217145d3b79545ab6c71323129a6e11e 100644 (file)
@@ -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<String>, reply: @escaping (Bool, Error?) -> Void) {
+    func enforceIDMSListChanges(knownMachines: Set<MachineMO>) -> 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<String>, 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<MachineMO> ?? 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<String>?, Error?) -> Void) {
         self.semaphore.wait()
         let reply: (Set<String>?, 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<MachineMO> ?? 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<MachineMO> ?? Set()
-        if machines.count == 0 {
-            os_log("machineID list is empty; allowing %@", log: tplogDebug, type: .debug, machineID)
+        let knownMachines = containerMO.machines as? Set<MachineMO> ?? 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<MachineMO> ?? 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 (file)
index 0000000..165764a
--- /dev/null
@@ -0,0 +1,91 @@
+import CoreData
+import Foundation
+
+extension Container {
+    func preflightVouchWithRecoveryKey(recoveryKey: String,
+                                       salt: String,
+                                       reply: @escaping (String?, Set<String>?, TPPolicy?, Error?) -> Void) {
+        self.semaphore.wait()
+        let reply: (String?, Set<String>?, 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
+                    }
+                }
+            }
+        }
+    }
+}
index 04d9b902ede10d63f54ac54b08d9ec10e3a6e3b5..c10415deceb3dc9925c9fbe0dc24f023733baa8b 100644 (file)
@@ -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
     }
index 2cbece09f47f305200c27a09f8d6bb60baddde46..ce1c3eef9350c84414451148f8ac8009b4693fff 100644 (file)
 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
index 1ab70461d4d560995036f0b764b5e299eac806e6..5499b200bca7feb5ae8cd0e62f96ac8d9e4f18ed 100644 (file)
@@ -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<CFString, Any>) 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<CFString, Any> = 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 <CFString, Any>]? {
-        var keySet: [Dictionary<CFString, Any>]?
+    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<CFString, Any>] {
+            if let dictionaryArray = result as? [[CFString: Any]] {
                 keySet = dictionaryArray
             } else {
-                if let dictionary = result as? Dictionary<CFString, Any> {
+                if let dictionary = result as? [CFString: Any] {
                     keySet = [dictionary]
                 } else {
                     keySet = nil
index 2529be279598ecb954d066774da9b1ed56374e8f..e670dbde94098c247980bedb8da9484eee06cf0a 100644 (file)
@@ -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)
index 34e02638b8f9587eb2d1f4e8efea6e61e68709bf..83d8766c0dc8d42c737c88d8184031e90847be25 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14886.2" systemVersion="19A541" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17026" systemVersion="19E219" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
     <entity name="Bottle" representedClassName="BottleMO" syncable="YES" codeGenerationType="class">
         <attribute name="bottleID" optional="YES" attributeType="String"/>
         <attribute name="contents" optional="YES" attributeType="Binary"/>
         <attribute name="egoPeerPermanentInfoSig" optional="YES" attributeType="Binary"/>
         <attribute name="egoPeerStableInfo" optional="YES" attributeType="Binary"/>
         <attribute name="egoPeerStableInfoSig" optional="YES" attributeType="Binary"/>
+        <attribute name="honorIDMSListChanges" optional="YES" attributeType="String" defaultValueString="UNKNOWN"/>
         <attribute name="moreChanges" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
         <attribute name="name" optional="YES" attributeType="String"/>
+        <attribute name="recoveryKeyEncryptionSPKI" optional="YES" attributeType="Binary"/>
+        <attribute name="recoveryKeySigningSPKI" optional="YES" attributeType="Binary"/>
         <relationship name="bottles" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Bottle" inverseName="container" inverseEntity="Bottle"/>
         <relationship name="machines" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Machine" inverseName="container" inverseEntity="Machine"/>
         <relationship name="peers" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Peer" inverseName="container" inverseEntity="Peer"/>
     </entity>
     <elements>
         <element name="Bottle" positionX="-549" positionY="-234" width="128" height="150"/>
-        <element name="Container" positionX="-758" positionY="-261" width="128" height="253"/>
+        <element name="Container" positionX="-758" positionY="-261" width="128" height="268"/>
         <element name="Machine" positionX="-549" positionY="-234" width="128" height="133"/>
         <element name="Peer" positionX="-414" positionY="-387" width="128" height="210"/>
         <element name="Policy" positionX="-414" positionY="54" width="128" height="105"/>
         <element name="RecoveryVoucher" positionX="-65" positionY="-297" width="128" height="105"/>
         <element name="Voucher" positionX="-65" positionY="-171" width="128" height="90"/>
     </elements>
-</model>
\ No newline at end of file
+</model>
index 197d419b45a32cbddf45517a75437ebf904b0e53..0f37bed20f8510afabe30b86985894c73dc56069 100644 (file)
@@ -129,6 +129,7 @@ NS_ASSUME_NONNULL_BEGIN
 - (void)setAllowedMachineIDsWithContainer:(NSString *)container
                                   context:(NSString *)context
                         allowedMachineIDs:(NSSet<NSString*> *)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<NSString*,NSData*> *)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<NSString*>* _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<NSString*>* _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<CKKSTLKShare*> *)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<NSString*>* _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<NSData*> *)preapprovedKeys
                     reply:(void (^)(NSString * _Nullable peerID,
                                     NSArray<CKRecord*>* _Nullable keyHierarchyRecords,
+                                    NSSet<NSString*>* _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<NSData*> *)preapprovedKeys
                                       reply:(void (^)(NSString * _Nullable peerID,
                                                       NSArray<CKRecord*>* _Nullable keyHierarchyRecords,
+                                                      NSSet<NSString*>* _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<NSData*> *)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<NSNumber*,NSString*>*)keys
-                                    reply:(void (^)(NSDictionary<NSNumber*,NSArray<NSString*>*>* _Nullable entries,
+                                 versions:(NSSet<TPPolicyVersion*>*)versions
+                                    reply:(void (^)(NSDictionary<TPPolicyVersion*, NSData*>* _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<NSString*>* _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<NSString*>*)inViews
-                     reply:(void (^)(NSArray<NSString*>* _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
index 6603111379090d2d5cdcb5beacfff0826c49d64c..fb2f1557e1c331a54c1da269a496b85019ca8a4c 100644 (file)
@@ -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:
index 7111e5b917ae95e42fe125cee14b3793817daa93..9bfa5fb666d17d0897158c518473aa867b590487 100644 (file)
@@ -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 (file)
index 0000000..9ddd0d1
--- /dev/null
@@ -0,0 +1,3 @@
+disabled_rules:
+    - force_cast
+    - force_try
index f3b3eb19a5c82ba4a1f2dcaf7953690ee10a3f20..655bfdf8cab1bde89a6a3874315d47a7736d1246 100644 (file)
@@ -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<String>?, TPPolicy?, Error?) {
         let expectation = XCTestExpectation(description: "prepare replied")
         var reta: String?, retb: Data?, retc: Data?, retd: Data?, rete: Data?, reterr: Error?
+        var retviews: Set<String>?
+        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<String>?, TPPolicy?, Error?) {
         let expectation = XCTestExpectation(description: "preflightVouchWithBottle replied")
         var reta: String?, reterr: Error?
-        self.preflightVouchWithBottle(bottleID: bottleID) { a, err in
+        var retviews: Set<String>?, 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<String>?, TPPolicy?, Error?) {
         let expectation = XCTestExpectation(description: "join replied")
         var reta: String?, retkhr: [CKRecord]?, reterr: Error?
+        var retviews: Set<String>?, 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<String>?, TPPolicy?, Error?) {
         let expectation = XCTestExpectation(description: "preapprovedjoin replied")
         var reta: String?
         var retkhr: [CKRecord]?
+        var retviews: Set<String>?
+        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<String>, listDifference: Bool = true) -> (Error?) {
+    func setAllowedMachineIDsSync(test: XCTestCase, allowedMachineIDs: Set<String>, 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<String>?
         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>) -> ([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)
     }
 }
index fbef651a4af2372f25a0908b8344566e5ba51773..23267a012df547dd3028458aa653894ae9568019 100644 (file)
@@ -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<String, Any>] = [
+    @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)
     }
index 5d8d2c33ed0b72d4cec0c81478c0e0a7efe55c08..8289a40e72c175389b8198c45e34efaade23f85b 100644 (file)
@@ -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)
+    }
 }
index 7d7ecae1ac710c6bc43bb3bc34305d2d9d07b0be..c5f2b3f766198b5cbce28b824de734e3e275fb1b 100644 (file)
@@ -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<String> = 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<String>,
+                       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<Any> = dict!["peers"] as! Array<Any>
+            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<String>() 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<String>() 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<MachineMO> ?? 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)
+    }
 }
index 934ff6a2210244e84bc44ec9dd7000f7da03b0b4..3b35e8380ee4e81d4d952c2c108c303579b0d70b 100644 (file)
@@ -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
 }
index 6c80b5c5c59c099b8b69e68270fd18c0a5c76f4c..b90f425261d3b8cc58c9985797ac1eb0787be3c4 100644 (file)
@@ -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 <NSObject>
@@ -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;
index dea0502bbf4a6465fafa23426940dbadafd430dc..6547366d6c8f2b48d3a664fce7836ba7240ff09a 100644 (file)
@@ -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";
index 3bb5e78d5b0157c3b56e0512bef366afe59b62a8..0aa1311107a5c13ef1cc35ed6f6867002eead0b5 100644 (file)
@@ -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
index f53425a1fe81864886501e8664e6198db654c9f6..e5c96b49d46fb5b9fc86f3911ad0fd54cd0fcd0f 100644 (file)
     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);
index 10e88f36e4a4ffd03ec79e6400d5cb65f02641ce..4f497ee0a2cd7b977db79458946f501e537bc3ee 100644 (file)
 }
 
 + (NSArray<CKKSKey*>*)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
index ac6e0339223d10ae8d7d4d2514102e91dbbad9e4..5e090e1bb228091d967d13dd2dacfcab75f5a676 100644 (file)
@@ -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<NSString*>*)_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<CKKSIncomingQueueOperation*>* incomingQueueOperations;
 @property NSHashTable<CKKSOutgoingQueueOperation*>* outgoingQueueOperations;
+
+@property NSHashTable<CKKSScanLocalItemsOperation*>* scanLocalItemsOperations;
 @property CKKSScanLocalItemsOperation* initialScanOperation;
 
 // Returns the current state of this view, fastStatus is the same, but as name promise, no expensive calculations
index 3e7a16fed619cda9246d9291d43e7638d55b3407..6a9589a1e874d811af28bbfb5cab45d6d6129fd6 100644 (file)
 @property CKKSResultOperation* processIncomingQueueAfterNextUnlockOperation;
 @property CKKSResultOperation* resultsOfNextIncomingQueueOperationOperation;
 
-@property NSMutableDictionary<NSString*, SecBoolNSErrorCallback>* pendingSyncCallbacks;
-
 // An extra queue for semaphore-waiting-based NSOperations
 @property NSOperationQueue* waitingQueue;
 
 
         _incomingQueueOperations = [NSHashTable weakObjectsHashTable];
         _outgoingQueueOperations = [NSHashTable weakObjectsHashTable];
+        _scanLocalItemsOperations = [NSHashTable weakObjectsHashTable];
         _cloudkitDeleteZoneOperations = [NSHashTable weakObjectsHashTable];
         _localResetOperations = [NSHashTable weakObjectsHashTable];
         _keysetProviderOperations = [NSHashTable weakObjectsHashTable];
                                                                               }];
 
 
-        _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
-
         _lockStateTracker = lockStateTracker;
         _savedTLKNotifier = savedTLKNotifier;
 
 
 #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) {
 }
 
 - (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;
         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 {
             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;
     }
 }
 
-- (NSSet<NSString*>*)_onqueuePriorityOutgoingQueueUUIDs
-{
-    return [self.pendingSyncCallbacks.allKeys copy];
-}
-
 - (CKKSOutgoingQueueOperation*)processOutgoingQueue:(CKOperationGroup*)ckoperationGroup {
     return [self processOutgoingQueueAfter:nil ckoperationGroup:ckoperationGroup];
 }
 
     [self.outgoingQueueOperationScheduler triggerAt:requiredDelay];
 
+    [op linearDependencies:self.outgoingQueueOperations];
+
     [self scheduleOperation: op];
     ckksnotice("ckksoutgoing", self, "Scheduled %@", op);
     return op;
     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];
     [scanOperation addNullableDependency:self.keyStateReadyDependency];
     [scanOperation addNullableDependency:after];
 
-    [self scheduleOperation: scanOperation];
+    [scanOperation linearDependencies:self.scanLocalItemsOperations];
+
+    [self scheduleOperation:scanOperation];
     return scanOperation;
 }
 
 
     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];
 - (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;
 
             [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;
         }];
     }];
     [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<id<CKKSPeerProvider>>*)peerProviders
 {
     [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];
         [self.incomingQueueOperations removeAllObjects];
     }
 
+    @synchronized(self.scanLocalItemsOperations) {
+        for(NSOperation* op in self.scanLocalItemsOperations) {
+            [op cancel];
+        }
+        [self.scanLocalItemsOperations removeAllObjects];
+    }
+
     [super cancelAllOperations];
 }
 
     [self.keyStateNonTransientDependency cancel];
     [self.zoneChangeFetcher cancel];
     [self.notifyViewChangedScheduler cancel];
+    [self.pokeKeyStateMachineScheduler cancel];
 
     [self cancelPendingOperations];
 
index 2cc4684316433221d4d2cce9dae36dd48924e1e5..9cf42a1bd4a59df84ab117453bd54eab9df7c2d5 100644 (file)
@@ -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");
 
         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 {
index 940cf26a6baa494d9da4f383fb6405b3db3eea22..087ad6a9ff9f4431a59b3e041b826e304c689860 100644 (file)
@@ -26,6 +26,7 @@
 #import <CloudKit/CloudKit.h>
 #import <CloudKit/CloudKit_Private.h>
 
+#import "keychain/ckks/CKKSViewManager.h"
 #import "CKKSKeychainView.h"
 #import "CKKSCurrentKeyPointer.h"
 #import "CKKSOutgoingQueueOperation.h"
@@ -90,7 +91,7 @@
 
         NSError* error = nil;
 
-        NSSet<NSString*>* priorityUUIDs = [ckks _onqueuePriorityOutgoingQueueUUIDs];
+        NSSet<NSString*>* priorityUUIDs = [[CKKSViewManager manager] pendingCallbackUUIDs];
 
         NSMutableArray<CKKSOutgoingQueueEntry*>* priorityEntries = [NSMutableArray array];
         NSMutableSet<NSString*>* priorityEntryUUIDs = [NSMutableSet set];
index 85f6ad5cf3ba86ece24bff9d232873313a3800ec..a4698b38a08106cc922db6b3792464a8841a8a67 100644 (file)
@@ -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
index 356424cc1b21ea45e9188efc3b23c464643b1cbf..f8c70d96a7457b3b86c0c942d33767688e30a955 100644 (file)
             [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];
         }
     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;
 
 
         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) {
             });
 
         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<NSString*, CKKSSQLResult*>* row = [[NSMutableDictionary alloc] init];
         
         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<NSString*, CKKSSQLResult*>* row = [[NSMutableDictionary alloc] init];
 }
 @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
index a35a240d37a35fff9eb623055f65446290a0c979..0ad03e5789caf367258f6c5028f4d90fe152518a 100644 (file)
@@ -33,6 +33,8 @@ NS_ASSUME_NONNULL_BEGIN
 @class CKKSEgoManifest;
 
 @interface CKKSScanLocalItemsOperation : CKKSResultOperation
+@property CKOperationGroup* ckoperationGroup;
+
 @property (weak) CKKSKeychainView* ckks;
 
 @property size_t recordsFound;
index 7d285ad6fbe9b2f2a9bb030e2964921e36d14b1c..3c3a6afe4533ca1206a264488113c482aa20f853 100644 (file)
@@ -22,6 +22,9 @@
  */
 
 #if OCTAGON
+#import <TrustedPeers/TPPolicy.h>
+#import <TrustedPeers/TPPBPolicyKeyViewMapping.h>
+#import <TrustedPeers/TPDictionaryMatchingRules.h>
 
 #import "keychain/ckks/CKKSAnalytics.h"
 #import "keychain/ckks/CKKSKeychainView.h"
@@ -47,7 +50,6 @@
 #import <IMCore/IMCloudKitHooks.h>
 
 @interface CKKSScanLocalItemsOperation ()
-@property CKOperationGroup* ckoperationGroup;
 @property (assign) NSUInteger processedItems;
 @end
 
     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;
                 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.
-                                              // <rdar://problem/32269541> 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;
index cd6ac8cd6de3a7c04b47dd8e6f05127235ba9bda..f19cccabe43ae3b54517fece49ddb1f563181531 100644 (file)
@@ -65,14 +65,15 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property id<OTSOSAdapter> sosPeerAdapter;
 
-@property (nullable) TPPolicy* policy;
+@property (readonly, nullable) TPPolicy* policy;
 
-@property NSMutableDictionary<NSString*, CKKSKeychainView*>* views;
+@property (readonly) NSMutableDictionary<NSString*, CKKSKeychainView*>* views;
 
-- (instancetype)initWithContainerName:(NSString*)containerName
-                               usePCS:(bool)usePCS
-                           sosAdapter:(id<OTSOSAdapter> _Nullable)sosAdapter
-            cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies;
+- (instancetype)initWithContainer:(CKContainer*)container
+                       sosAdapter:(id<OTSOSAdapter> _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<NSString*>*)viewList;
 
 - (NSSet<NSString*>*)defaultViewList;
 
-- (void)setViewList:(NSSet<NSString*>* _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<NSString*>* _Nullable)viewNames sortingPolicy:(TPPolicy* _Nullable)policy;
 
 - (void)clearAllViews;
 
 // Create all views, but don't begin CK/network operations
+// Remove as part of <rdar://problem/57768740> 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<NSString*>*)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<NSString*>* _Nullable)viewNames;
 @end
 NS_ASSUME_NONNULL_END
 
index 8a226dfc0c0bac8f062d06605f35e437432a4cee..3838cc6b381ea1697dd9e0bde6a917da2337a6ba 100644 (file)
@@ -23,6 +23,7 @@
 
 #import <os/feature_private.h>
 
+#import "keychain/ckks/CKKSAccountStateTracker.h"
 #import "keychain/ckks/CKKSViewManager.h"
 #import "keychain/ckks/CKKSKeychainView.h"
 #import "keychain/ckks/CKKSSynchronizeOperation.h"
 #import "CKKSAnalytics.h"
 #endif
 
+#if !OCTAGON
 @interface CKKSViewManager () <NSXPCListenerDelegate>
-#if OCTAGON
+#else
+@interface CKKSViewManager () <NSXPCListenerDelegate,
+                               CKKSCloudKitAccountStateListener>
+
 @property NSXPCListener *listener;
 
+@property (nullable) NSSet<NSString*>* viewAllowList;
+
 // Once you set these, all CKKSKeychainViews created will use them
 @property CKKSCloudKitClassDependencies* cloudKitClassDependencies;
 
 
 @property (nonatomic) BOOL overrideCKKSViewsFromPolicy;
 @property (nonatomic) BOOL valueCKKSViewsFromPolicy;
+@property (nonatomic) BOOL startCKOperationAtViewCreation;
+
+@property BOOL itemModificationsBeforePolicyLoaded;
+
+// Make writable
+@property (nullable) TPPolicy* policy;
 
 #endif
 @end
 @implementation CKKSViewManager
 #if OCTAGON
 
-NSSet<NSString*>* _viewList;
-
-- (instancetype)initWithContainerName: (NSString*) containerName
-                               usePCS: (bool)usePCS
-                           sosAdapter:(id<OTSOSAdapter> _Nullable)sosAdapter
-            cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
+- (instancetype)initWithContainer:(CKContainer*)container
+                       sosAdapter:(id<OTSOSAdapter> _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<NSString*>* _viewList;
         _views = [[NSMutableDictionary alloc] init];
         _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
 
+        _startCKOperationAtViewCreation = NO;
+
         _completedSecCKKSInitialize = [[CKKSCondition alloc] init];
 
         WEAKIFY(self);
@@ -139,14 +154,21 @@ NSSet<NSString*>* _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<NSString*>*)defaultViewList {
     NSSet<NSString*>* fullList = [OTSOSActualAdapter sosCKKSViewList];
 
@@ -338,18 +362,77 @@ dispatch_once_t globalZoneStateQueueOnce;
     return fullList;
 }
 
--(NSSet<NSString*>*)viewList {
-    if (_viewList) {
-        return _viewList;
-    } else {
-        return [self defaultViewList];
+- (NSSet<NSString*>*)viewList {
+    return [self.views.allKeys copy];
+}
+
+- (void)setSyncingViews:(NSSet<NSString*>*)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<NSString*>* 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<NSString*>* _Nullable)newViewList {
-    _viewList = newViewList;
+- (void)setSyncingViewsAllowList:(NSSet<NSString*>*)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 <rdar://problem/57810109> 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<NSString*>*)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];
         }
index b51bf4774f114ff753d65dee4744118feda32cca..c5f878b30011a03d701108904b70b5a59bf7f7cd 100644 (file)
 
     // 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
index 6add06379a4f853969a68674fbead5ae45e8327a..82239815e4ad66348e41888d2ca298c536799319 100644 (file)
@@ -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
 
                                                                                                               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 (file)
index eff091f..0000000
+++ /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 <dispatch/dispatch.h>
-#import <XCTest/XCTest.h>
-#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
index 7fcff165e28aaa3f8adbdd8a40fab64df1eca835..4eb485201408c9dd5bff27714b3392b2025ab844 100644 (file)
@@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN
 @property bool excludeSelfPeerFromTrustSet;
 
 @property SOSCCStatus circleStatus;
+@property (nullable) NSError* circleStatusError;
+
 @property CKKSSOSSelfPeer* selfPeer;
 @property NSMutableSet<id<CKKSSOSPeerProtocol>>* trustedPeers;
 
index ec18e233052b58bd6d23b91d5928e084da0d7e90..7f8077ec55850dc90c1cbfd7be012c263e24c7e7 100644 (file)
@@ -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;
     }
index 8bd89e46aefcbe5cbe065a679cad8b2d6f926d17..827a5c83a13fd3d2ec78989b696ab5609d8bd6a7 100644 (file)
     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");
 }
 
     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<CKKSKey*>* 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<CKKSKey*>* selfWrapped = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereObject op:@"=" string:@"uuid"]} error: &error];
+    NSArray<CKKSKey*>* 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<CKKSKey*>* selfWrapped2 = [CKKSKey allWhere: @{@"parentKeyUUID": [CKKSSQLWhereObject op:@"=" string:@"uuid"],
+    // Try using CKKSSQLWhereColumn alongside normal binds
+    NSArray<CKKSKey*>* 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<CKKSKey*>* 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 {
     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"]
index 0b8ad45e155da424f01f12c35013722ab536f542..10235c423cdd612e8ec90ce389fc451210dab21d 100644 (file)
@@ -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,
                                     (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock,
                                     (id)kSecAttrAccount : @"testaccount",
                                     (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
+                                    (id)kSecAttrSyncViewHint : self.keychainView.zoneName,
                                     (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding],
                                     } mutableCopy];
 
         [blockExpectation fulfill];
     }), @"_SecItemAddAndNotifyOnSync succeeded");
 
+    OCMVerifyAllWithDelay(self.mockDatabase, 10);
+
     [self waitForExpectationsWithTimeout:5.0 handler:nil];
 }
 
         (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];
                                     (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];
                                     (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock,
                                     (id)kSecAttrAccount : @"testaccount",
                                     (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
+                                    (id)kSecAttrSyncViewHint : self.keychainView.zoneName,
                                     (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding],
                                     } mutableCopy];
 
                                     (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock,
                                     (id)kSecAttrAccount : @"testaccount",
                                     (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
+                                    (id)kSecAttrSyncViewHint : self.keychainView.zoneName,
                                     (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding],
                                     } mutableCopy];
 
     [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.
 
index c75ef74c027f7e6bd7928a94fdeafa7d4d0154f2..e2b7c7537c09847304e12eed0d7d72ed2e07cc55 100644 (file)
 
     // 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");
 
 
     // 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");
 
 
     // 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");
 
 
     // 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");
 
 
     // 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];
 
     // 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");
 
 
     // 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];
 
     // 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");
 
     // 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");
 
     // 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");
 
     // 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");
 
 
     // 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");
 
     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];
 
index 1d0b619b42f529511d2bfcc9277399b4d0905e33..57207e33a8486f466321ca3f1da738b71d72d1cb 100644 (file)
 @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;
index f3179612fe34181f51859b2c5d400975d2024fe3..0823b71d1cd41ee49c7b4e5e34a1e790652e5f51 100644 (file)
@@ -7,6 +7,9 @@
 #import <notify.h>
 
 #include <Security/SecItemPriv.h>
+#import <TrustedPeers/TrustedPeers.h>
+#import <TrustedPeers/TPPBPolicyKeyViewMapping.h>
+#import <TrustedPeers/TPDictionaryMatchingRules.h>
 
 #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
 #include <Security/SecKeyPriv.h>
 #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<TPPBPolicyKeyViewMapping*>* 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 {
     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 {
     [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];
 }
 
     [self putFakeDeviceStatusInCloudKit: self.applepayZoneID];
     [self putFakeDeviceStatusInCloudKit: self.homeZoneID];
     [self putFakeDeviceStatusInCloudKit: self.limitedZoneID];
+    [self putFakeDeviceStatusInCloudKit: self.passwordsZoneID];
 }
 
 - (void)putFakeKeyHierachiesInCloudKit{
     [self putFakeKeyHierarchyInCloudKit: self.applepayZoneID];
     [self putFakeKeyHierarchyInCloudKit: self.homeZoneID];
     [self putFakeKeyHierarchyInCloudKit: self.limitedZoneID];
+    [self putFakeKeyHierarchyInCloudKit: self.passwordsZoneID];
 }
 
 - (void)saveTLKsToKeychain{
     [self saveTLKMaterialToKeychain:self.applepayZoneID];
     [self saveTLKMaterialToKeychain:self.homeZoneID];
     [self saveTLKMaterialToKeychain:self.limitedZoneID];
+    [self saveTLKMaterialToKeychain:self.passwordsZoneID];
 }
 
 - (void)deleteTLKMaterialsFromKeychain{
     [self deleteTLKMaterialFromKeychain: self.applepayZoneID];
     [self deleteTLKMaterialFromKeychain: self.homeZoneID];
     [self deleteTLKMaterialFromKeychain:self.limitedZoneID];
+    [self deleteTLKMaterialFromKeychain:self.passwordsZoneID];
 }
 
 - (void)waitForKeyHierarchyReadinesses {
     [self.applepayView waitForKeyHierarchyReadiness];
     [self.homeView waitForKeyHierarchyReadiness];
     [self.limitedView waitForKeyHierarchyReadiness];
+    [self.passwordsView waitForKeyHierarchyReadiness];
 }
 
 - (void)expectCKKSTLKSelfShareUploads {
     [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.
 
 
     // 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];
index 1aaf5cd8204869cc6edf9ece5845faf9a78aa64e..8baf53aa9175d2e00e019c74fb12bf724f572c27 100644 (file)
     [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");
         XCTAssertNil(error, "No error loading IQEs");
         XCTAssertNotNil(iqes, "Could load IQEs");
         XCTAssertEqual(iqes.count, 0u, "Incoming queue is empty");
+        return false;
     }];
 }
 
                                                                          (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);
     [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];
 
     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
     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 {
index fddf4ca66f63eeaa49c83b98fb44e0fe558bd4aa..e0701910f012ba6dd04654f9804d66f802bdc900 100644 (file)
@@ -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];
     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];
 
         // 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];
index 933305d3c2d9e4b80feca4f9f9a124c63adf7ad9..f64abed9bf3ab1a68d222ad251f0e55430fc4da0 100644 (file)
@@ -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;
index 3ea304a42bb9d3e64f9c605c10d55eacc40547b0..f7edec830d5cc3902162ed3718adef9960fe24ab 100644 (file)
     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];
 
 
 - (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];
 
index 2c669584d47f24ef38d28d516b0568bdf6e05082..0cb5406bdba9171e995d676917ee56485f9d9692 100644 (file)
@@ -53,9 +53,6 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property NSCalendar* utcCalendar;
 
-- (NSSet<NSString*>*)managedViewList;
-
-
 - (ZoneKeys*)keychainZoneKeys;
 
 @end
index 35640b3c0f23eab57bee7317f3ceaf7a0188e224..f122a1bdb1140ba40c43d50b7bd14d2c8f2b634c 100644 (file)
@@ -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];
     }
     // 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));
 }
 
 
index 0e3573d6fd7ecee990daafd647cdc9a9821fd415..4df9a3cba9ccd3b977d926da11d453c6112fd0fc 100644 (file)
@@ -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<NSString*>*)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;
 
index 76916eabf1ee536c5ef3dda6fcec7e6fc3d137d0..f8e42b58f730724f91c1ad068ce4b7577f2efe6c 100644 (file)
 #import <CloudKit/CloudKit_Private.h>
 #import <CloudKit/CKContainer_Private.h>
 #import <OCMock/OCMock.h>
+#import <TrustedPeers/TrustedPeers.h>
+#import <TrustedPeers/TPPBPolicyKeyViewMapping.h>
+#import <TrustedPeers/TPDictionaryMatchingRules.h>
 
 #include "keychain/securityd/Regressions/SecdTestKeychainUtilities.h"
 #include <utilities/SecFileLocations.h>
 #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"
 
     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";
 
                                                                 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];
     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];
     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 {
     return (NSSet*) CFBridgingRelease(SOSViewCopyViewSet(kViewSetCKKS));
 }
 
+- (TPPolicy*)viewSortingPolicyForManagedViewList
+{
+    NSMutableArray<TPPBPolicyKeyViewMapping*>* 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];
 }
     [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;
 
     _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 (file)
index 0000000..52065f6
--- /dev/null
@@ -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_]*(?<!Base))): ', line) 
+            if match:
+                test_classes.append(match.group(1))
+
+            # And pick up everything that's a default xctest, too
+            match = re.search('@interface ([a-zA-Z0-9_]+) ?: ?XCTestCase', line)
+            if match:
+                test_classes.append(match.group(1))
+
+            # or based on CloudKitMockXCTest
+            match = re.search('@interface ([a-zA-Z0-9_]+) ?: ?CloudKitMockXCTest', line)
+            if match:
+                test_classes.append(match.group(1))
+        f.close()
+
+    # But we don't want any helper classes
+    base_classes = ['CloudKitMockXCTest', 'CloudKitKeychainSyncingMockXCTest', 'CKKSCloudKitTests']
+    for base_class in base_classes:
+        test_classes = [x for x in test_classes if base_class != x]
+    return test_classes
+
+for x in get_class_names():
+
+    test_dictionary = Foundation.NSMutableDictionary.dictionary()
+    test_dictionary['TestName'] = x
+    test_dictionary['Timeout']= 1200
+    test_dictionary['ShowSubtestResults']= True
+    test_dictionary['WorkingDirectory'] = '/AppleInternal/XCTests/com.apple.security/'
+
+    test_command = Foundation.NSMutableArray.array()
+    test_command.append('BATS_XCTEST_CMD -XCTest {} CKKSTests.xctest'.format(x))
+    test_dictionary['Command'] = test_command
+
+    test_list.append(test_dictionary)
+
+test_plist['Tests'] = test_list
+
+out_dir = os.path.dirname(outfile)
+if not os.path.exists(out_dir):
+    os.makedirs(out_dir)
+success = test_plist.writeToFile_atomically_(outfile, 1)
+if not success:
+    print "test plist failed to write, error!"
+    sys.exit(1)
diff --git a/keychain/ckks/tests/testrunner/KeychainCKKS.plist b/keychain/ckks/tests/testrunner/KeychainCKKS.plist
deleted file mode 100644 (file)
index 7e285bc..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-       <key>BATSConfigVersion</key>
-       <string>0.1.0</string>
-       <key>Project</key>
-       <string>Security</string>
-       <key>Tests</key>
-       <array>
-               <dict>
-                       <key>TestName</key>
-                       <string>CKKSTests</string>
-                       <key>WorkingDirectory</key>
-                       <string>/AppleInternal/XCTests/com.apple.security/</string>
-                       <key>ShowSubtestResults</key>
-                       <true/>
-                       <key>Timeout</key>
-                       <integer>1200</integer>
-                       <key>Command</key>
-                       <array>
-                               <string>BATS_XCTEST_CMD CKKSTests.xctest</string>
-                       </array>
-               </dict>
-               <!--dict>
-                       <key>TestName</key>
-                       <string>CKKSCloudKitTests</string>
-                       <key>WorkingDirectory</key>
-                       <string>/AppleInternal/XCTests/com.apple.security/</string>
-                       <key>Command</key>
-                       <array>
-                               <string>/AppleInternal/CoreOS/tests/Security/KeychainEntitledTestRunner</string>
-                       </array>
-               </dict-->
-       </array>
-</dict>
-</plist>
index bad4d21b63dd4f092a866e512f102d451f4565e3..d27cf2176312a017c413f59ed04d9b8963cee49a 100644 (file)
@@ -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) {
index 689908a9030fd6f9dbb5e85c133e1df4a2cdf297..6dd81f06394a32e5e3aa77ccf363fb57fc4c6ee9 100644 (file)
@@ -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
 {
 }
 - (BOOL)hasTriggerRequestTime
 {
-    return _has.triggerRequestTime;
+    return _has.triggerRequestTime != 0;
 }
 
 - (NSString *)description
index f39dabbc1c2cea529d958a7ce281e7c20f959a87..baef2e98404e80d6626311060a6ccfe245bbcf7c 100644 (file)
@@ -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)
index 021a672af6b7d8bc0a3e8adf733d6240cd326454..9b0a67b070e03d334b881b04e21edbf27718c7a0 100644 (file)
@@ -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);
index c67395bbd7da7b0e8f580288b13c7d19a7d4c943..868f2bd848362e135583c949c1bb2b7015483ea5 100644 (file)
@@ -189,6 +189,7 @@ enum {NUM_RETRIES = 5};
 - (void)setAllowedMachineIDsWithContainer:(NSString *)container
                                   context:(NSString *)context
                         allowedMachineIDs:(NSSet<NSString*> *)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<NSString*,NSData*> *)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<NSString*>* 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<NSString*>* _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<CKKSTLKShare*> *)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<NSString*>* _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<NSData*> *)preapprovedKeys
                     reply:(void (^)(NSString * _Nullable peerID,
                                     NSArray<CKRecord*>* _Nullable keyHierarchyRecords,
+                                    NSSet<NSString*>* _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<NSData*> *)preapprovedKeys
                                       reply:(void (^)(NSString * _Nullable peerID,
                                                       NSArray<CKRecord*>* _Nullable keyHierarchyRecords,
+                                                      NSSet<NSString*>* _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<NSData*> *)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<NSNumber*,NSString*>*)keys
-                                    reply:(void (^)(NSDictionary<NSNumber*,NSArray<NSString*>*>* _Nullable entries,
+                                 versions:(NSSet<TPPolicyVersion*>*)versions
+                                    reply:(void (^)(NSDictionary<TPPolicyVersion*, NSData*>* _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<NSString*>* _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<NSString*>*)inViews
-                        reply:(void (^)(NSArray<NSString*>* _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);
 }
 
index c5246045529e1a29b559e8206524647d948a3cc1..4e515885f9facd588824ec59711e2b2bdde451ab 100644 (file)
@@ -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
 }
index 5539aa1f5d28361ededa927b08326ce9c58d7b4c..e0f4ec032a7deccd9fe86d048e863c2494889c2d 100644 (file)
@@ -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<NSString*>* _Nullable machineIDs, NSError* _Nullable error))complete;
index b9452d2e6e4e17b313c68753fdc29a3a411f40ad..80e1ff92e99e81c25af243d3d8e9753958770715 100644 (file)
 
 - (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];
index e3463e6dfa9b6d4c72bd87ef062b38930c5c18c8..430fa43fb34ff640c29a400b1cf133970d7e236d 100644 (file)
@@ -53,6 +53,7 @@ NS_ASSUME_NONNULL_BEGIN
 @property BOOL postRepairCFU;
 @property BOOL postEscrowCFU;
 @property BOOL resetOctagon;
+@property BOOL leaveTrust;
 
 @end
 
index b76ce4621d554fb3c6f9c55830d3d997ad4c7c1f..8158222aa12ca97dc03121d85849fad40ab7f1ef 100644 (file)
@@ -61,6 +61,7 @@
         _postRepairCFU = NO;
         _postEscrowCFU = NO;
         _resetOctagon = NO;
+        _leaveTrust = NO;
         _skipRateLimitingCheck = skipRateLimitedCheck;
     }
     return self;
         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;
         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;
     [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);
                 [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;
     }
index b36a24aacaa260115f75b55fd00168101f3a8129..88f684f1b07bd16dc23b94ec311b6341f7f6f1f5 100644 (file)
@@ -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"
index 772172214e683f33bdbcf977cce9c49c8ae70aeb..cfe080f159ece349964878f71f355feb2ab91064 100644 (file)
@@ -44,27 +44,41 @@ typedef NS_ENUM(NSInteger, CliqueStatus) {
 #import <Security/SecureObjectSync/SOSTypes.h>
 #import <Security/OTConstants.h>
 
+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<NSString*,NSString*>* _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
index 5bf39ceee3605e50e3d072a024f868072d464a5e..fbf8e5dab09e8b3cfa4c2e7c2626c7a7144711f7 100644 (file)
@@ -47,6 +47,7 @@ const NSString* kSecEntitlementPrivateOctagonEscrow = @"com.apple.private.octago
 #import <CloudServices/SecureBackup.h>
 #import <CloudServices/SecureBackupConstants.h>
 #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<OctagonEscrowRecovererPrococol> 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 */
index 6e8b48def52127092f3fbb0681e9645f1a2618ac..c279f62c8070a64ccd3ff790bc795bc1763ad6c2 100644 (file)
@@ -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<NSData*>* _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
index ce1d0965a8514c325c7f44763dc450fec15bad3d..e5669901fb6269076970a86df86bf5ce4fd51fa7 100644 (file)
 - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config
                        vouchData:(NSData*)vouchData
                         vouchSig:(NSData*)vouchSig
-                 preapprovedKeys:(NSArray<NSData*>* _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];
 }
index 6af4bbefbb17298b180588773727aa3f0c099c43..48f99a779fe9f0cc941ddbe06044d88ea5761da1 100644 (file)
@@ -86,7 +86,6 @@ typedef void (^OTNextJoinCompleteBlock)(BOOL finished, NSData* _Nullable message
 - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config
                        vouchData:(NSData*)vouchData
                         vouchSig:(NSData*)vouchSig
-                 preapprovedKeys:(NSArray<NSData*>* _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);
index f98ae5026232346f78f001d35a8b92029e42a59d..9c0df1987c201f7d01d891b60c807e468acd4dfb 100644 (file)
@@ -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__ */
index 4630b7f9c6e896a785f989d38899e5a5016b99d8..abdb5e32f6a3cbbcc737e2c045de8434b9476b7a 100644 (file)
@@ -3,6 +3,7 @@
 #import <dispatch/dispatch.h>
 
 #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;
index 2455c54360cd88141899f1d1bb16e20ecd0702b0..b6d596d12e3923cc63593e5a57ac2a0a19409173 100644 (file)
     } error:error];
 }
 
-- (BOOL)persistAccountChanges:(OTAccountMetadataClassC* (^)(OTAccountMetadataClassC*))makeChanges
+- (BOOL)persistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC*))makeChanges
                         error:(NSError**)error
 {
     __block NSError* localError = nil;
         }
 
         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;
         }
     });
     } error:error];
 }
 
-- (BOOL)_onqueuePersistAccountChanges:(OTAccountMetadataClassC* (^)(OTAccountMetadataClassC* metadata))makeChanges
+- (BOOL)_onqueuePersistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC* metadata))makeChanges
                                 error:(NSError**)error
 {
     __block NSError* localError = nil;
index 426c5dc8669b6f600d05adfb3bbb505845c9405e..714b7013b4eeaf7010684d673e77f676d4510739 100644 (file)
@@ -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<OTDeviceInformationAdapter> deviceAdapter;
 @property id<OTAuthKitAdapter> 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<NSData*>* _Nullable)preapprovedKeys
           reply:(void (^)(NSError * _Nullable error))reply;
 
 - (void)rpcResetAndEstablish:(CuttlefishResetReason)resetReason reply:(nonnull void (^)(NSError * _Nullable))reply;
@@ -165,6 +168,8 @@ preapprovedKeys:(NSArray<NSData*>* _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<NSData*>* _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;
index 0eac866a536730399539168e9bbce49796b5cade..be6ecf968e25bdb8331bb8dfe4187657f8a5364b 100644 (file)
  */
 #if OCTAGON
 
-#include <sys/sysctl.h>
-
+#import <CoreCDP/CDPAccount.h>
+#import <notify.h>
 #import <os/feature_private.h>
-
 #import <Security/Security.h>
-
-#include <utilities/SecFileLocations.h>
 #include <Security/SecRandomP.h>
 #import <SecurityFoundation/SFKey_Private.h>
+#include <sys/sysctl.h>
+#import <TrustedPeers/TrustedPeers.h>
+
 
 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
-#import <TrustedPeers/TrustedPeers.h>
+
+#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 <CoreCDP/CDPAccount.h>
-#import <notify.h>
 
 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<NSData*>* _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<OTSOSAdapter> sosAdapter;
 @property id<CKKSPeerProvider> octagonAdapter;
-@property id<OTDeviceInformationAdapter> deviceAdapter;
 @property (readonly) Class<OctagonAPSConnection> apsConnectionClass;
 @property (readonly) Class<SecEscrowRequestable> 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]) {
+        // <rdar://problem/57768490> 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]) {
+        // <rdar://problem/56270219> 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<NSString*>* 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<id<CKKSPeerProvider>>* 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<id<CKKSPeerProvider>>* 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<NSData*>* _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<NSData*>* _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<NSData*>* _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<NSData*>* _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<NSData*>* _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<NSData*>* _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<NSData*>* _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<NSData*>* _Nullable)preapprovedKeys
                                                                              intendedState:OctagonStateBecomeReady
                                                                          ckksConflictState:OctagonStateBecomeUntrusted
                                                                                 errorState:OctagonStateBecomeUntrusted
-                                                                                deviceInfo:self.prepareInformation];
+                                                                                deviceInfo:self.prepareInformation
+                                                                            policyOverride:self.policyOverride];
 
     OctagonStateTransitionRequest<OTSOSUpgradeOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"attempt-sos-upgrade"
                                                                                                     sourceStates:sourceStates
@@ -2687,13 +2859,6 @@ preapprovedKeys:(NSArray<NSData*>* _Nullable)preapprovedKeys
                                           reply:reply];
 }
 
-- (void)clearPendingCFUFlags
-{
-    self.postedRecoveryKeyCFU = NO;
-    self.postedEscrowRepairCFU = NO;
-    self.postedRepairCFU = NO;
-}
-
 // Metrics passthroughs
 
 - (BOOL)machineIDOnMemoizedList:(NSString*)machineID error:(NSError**)error
index 8d5f6e71fdb7f46637bb51ff1b272cc1ac0e877d..d391c48fd71feaa2224885b9671c64c7c0ea1b24 100644 (file)
@@ -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/ot/OTDetermineCDPBitStatusOperation.h b/keychain/ot/OTDetermineCDPBitStatusOperation.h
new file mode 100644 (file)
index 0000000..f4b377c
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "keychain/ckks/CKKSGroupOperation.h"
+#import "keychain/ot/OctagonStateMachineHelpers.h"
+#import "keychain/ot/OTOperationDependencies.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface OTDetermineCDPBitStatusOperation : CKKSGroupOperation <OctagonStateTransitionOperationProtocol>
+
+- (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 (file)
index 0000000..8ad69c1
--- /dev/null
@@ -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
index cfb4a621b0df4a4b3d89eecdd9bce3068b1de945..0acbd9ffe885ed5f0cedc167c69dba5e01228ba6 100644 (file)
@@ -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"
     WEAKIFY(self);
 
     NSArray<NSData*>* publicSigningSPKIs = nil;
-
     if(self.operationDependencies.sosAdapter.sosEnabled) {
-        NSError* peerError = nil;
-
-        secnotice("octagon-sos", "SOS not enabled; no preapproved keys");
-        NSSet<id<CKKSRemotePeerProtocol>>* 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");
     }
index fc7fd5ed170978c96fb73d4a4aeec70a5d2d4566..13613b2e5d21ce86a0103bb4285d2d620df2f35e 100644 (file)
                                                                 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,
index b7fbb418dd46987c08d7e70b9d86cf1c10f74bdc..f866bd21a3e787c36f1e7179f1287b1d4f0f4c36 100644 (file)
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface OTFetchViewsOperation : CKKSGroupOperation
+@interface OTFetchViewsOperation : CKKSGroupOperation <OctagonStateTransitionOperationProtocol>
 
-- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies;
-
-@property (nonatomic) NSSet<NSString*>* viewList;
-@property (nonatomic) TPPolicy* _Nullable policy;
+- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies
+                       intendedState:(OctagonState*)intendedState
+                          errorState:(OctagonState*)errorState;
 
 @end
 
index cbe4e56aee154a1f3b7c4c5639c2dea2cf3d6002..3b63b205451684b52ebd62021a1afc788236163f 100644 (file)
 #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;
 }
 {
     secnotice("octagon", "fetching views");
 
-    self.finishedOp = [[NSOperation alloc] init];
-    [self dependOnBeforeGroupFinished:self.finishedOp];
-
-    NSSet<NSString*>* 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<NSString*>* sosViews = [sosViewList allObjects];
-                    [self.deps.cuttlefishXPCWrapper getViewsWithContainer:self.deps.containerName context:self.deps.contextID inViews:sosViews reply:^(NSArray<NSString*>* _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<NSString*>* _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
index e6f738ca058c90088d099f35c3b2a43a7edcd5cb..a70959290dc6543b64214e10eef38ec0d9c855bf 100644 (file)
@@ -57,7 +57,12 @@ NSString* OTFollowupContextTypeToString(OTFollowupContextType contextType);
 
 - (NSDictionary *)sysdiagnoseStatus;
 - (NSDictionary<NSString*,NSNumber*> *)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
index 419f399c126b6bad3c91f7d450ecf40cb319e8d2..649dc59d0437b7aa9e32b23295f77d46b0035f0e 100644 (file)
@@ -56,6 +56,8 @@ NSString* OTFollowupContextTypeToString(OTFollowupContextType contextType)
 @property NSTimeInterval previousFollowupEnd;
 @property NSTimeInterval followupStart;
 @property NSTimeInterval followupEnd;
+
+@property NSMutableSet<NSString*>* 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
index 3668f4d1e02b0e9dc310111f2dfcdff117f6577f..c5a9addba3b2b28c740c74c077a170df6e2f4538 100644 (file)
@@ -40,12 +40,10 @@ NS_ASSUME_NONNULL_BEGIN
                    ckksConflictState:(OctagonState*)ckksConflictState
                           errorState:(OctagonState*)errorState
                          voucherData:(NSData*)voucherData
-                          voucherSig:(NSData*)voucherSig
-                     preapprovedKeys:(NSArray<NSData *>*)preapprovedKeys;
+                          voucherSig:(NSData*)voucherSig;
 
 @property (nonatomic) NSData* voucherData;
 @property (nonatomic) NSData* voucherSig;
-@property (nonatomic) NSArray<NSData *>* preapprovedKeys;
 
 @property (nonatomic) NSString* peerID;
 
index f68765d5c70949c9846d9619682cff5427da153a..6a7c5b97970d8febebcda9e4c2be17c0294138e8 100644 (file)
@@ -55,7 +55,6 @@
                           errorState:(OctagonState*)errorState
                          voucherData:(NSData*)voucherData
                           voucherSig:(NSData*)voucherSig
-                     preapprovedKeys:(NSArray<NSData *>*)preapprovedKeys
 {
     if((self = [super init])) {
         _deps = dependencies;
@@ -66,7 +65,6 @@
 
         _voucherData = voucherData;
         _voucherSig = voucherSig;
-        _preapprovedKeys = preapprovedKeys;
     }
     return self;
 }
 {
     WEAKIFY(self);
 
+    NSArray<NSData*>* 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<CKRecord*>* keyHierarchyRecords, NSError * _Nullable error) {
+                                      preapprovedKeys:publicSigningSPKIs
+                                                reply:^(NSString * _Nullable peerID,
+                                                        NSArray<CKRecord*>* keyHierarchyRecords,
+                                                        NSSet<NSString*>* _Nullable syncingViews,
+                                                        TPPolicy* _Nullable syncingPolicy,
+                                                        NSError * _Nullable error) {
             STRONGIFY(self);
             if(error){
                 secerror("octagon: Error joining with voucher: %@", error);
 
                 [[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;
index af3b327ac89ab751311424a81849c9d00177d281..9207a19fc5be6da106b6bcb3ca7d66cb5ae6976b 100644 (file)
@@ -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
index 339311f531b598017e56e734e1f3838936cbe022..f744254011168cc32a951a6ed7f0c45485309cd2 100644 (file)
@@ -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
index 88a1820f84c94ea0c9cb0185e5e8935b000c1513..d8fcd385e5a205d74de85e5e4c407b5c6dd0e9c4 100644 (file)
@@ -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 <CoreCDP/CDPAccount.h>
 NS_ASSUME_NONNULL_BEGIN
@@ -45,22 +46,27 @@ NS_ASSUME_NONNULL_BEGIN
 @class OTClientStateMachine;
 @class CKKSLockStateTracker;
 @class CKKSAccountStateTracker;
+@class CloudKitClassDependencies;
 
 @interface OTManager : NSObject <OTControlProtocol>
 
 @property (nonatomic, readonly) CKKSLockStateTracker* lockStateTracker;
-@property id<CKKSCloudKitAccountStateTrackingProvider> 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<OTSOSAdapter>)sosAdapter
-                   authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
+                    authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
           deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
                 apsConnectionClass:(Class<OctagonAPSConnection>)apsConnectionClass
                 escrowRequestClass:(Class<SecEscrowRequestable>)escrowRequestClass
-                       loggerClass:(Class<SFAnalyticsProtocol> _Nullable)loggerClass
-                  lockStateTracker:(CKKSLockStateTracker* _Nullable)lockStateTracker
-               accountStateTracker:(id<CKKSCloudKitAccountStateTrackingProvider>)accountStateTracker
+                       loggerClass:(Class<SFAnalyticsProtocol>)loggerClass
+                  lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
+         cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
            cuttlefishXPCConnection:(id<NSXPCProxyCreating> _Nullable)cuttlefishXPCConnection
                               cdpd:(id<OctagonFollowUpControllerProtocol>)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<OTSOSAdapter>)sosAdapter
+                  lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
+         cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies;
 @end
+
 NS_ASSUME_NONNULL_END
 
 #endif  // OCTAGON
index 92c0caae60551be701dcfc56e07a19eb44adf0bc..79d43f93ed5f809909507c2de1a98175a90f1650 100644 (file)
@@ -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 <CloudKit/CloudKit.h>
 #import <CloudKit/CloudKit_Private.h>
@@ -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<OTSOSAdapter> 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<OTSOSAdapter>)sosAdapter
-                   authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
-         deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
-               apsConnectionClass:(Class<OctagonAPSConnection>)apsConnectionClass
-               escrowRequestClass:(Class<SecEscrowRequestable>)escrowRequestClass
-                      loggerClass:(Class<SFAnalyticsProtocol>)loggerClass
-                 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
-              accountStateTracker:(id<CKKSCloudKitAccountStateTrackingProvider>)accountStateTracker
-          cuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
-                             cdpd:(id<OctagonFollowUpControllerProtocol>)cdpd
+- (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
+                    authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
+          deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
+                apsConnectionClass:(Class<OctagonAPSConnection>)apsConnectionClass
+                escrowRequestClass:(Class<SecEscrowRequestable>)escrowRequestClass
+                       loggerClass:(Class<SFAnalyticsProtocol>)loggerClass
+                  lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
+         cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
+           cuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
+                              cdpd:(id<OctagonFollowUpControllerProtocol>)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<OTSOSAdapter>)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<NSData*>* _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
index e9e5aeb60868469070ffe225c79ecbfbae86753f..373177f6536eafafc0e601f18490b0ed77aff390 100644 (file)
@@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
 @property id<OTAuthKitAdapter> authKitAdapter;
 @property id<OTDeviceInformationAdapter> deviceInformationAdapter;
 @property (readonly) CuttlefishXPCWrapper* cuttlefishXPCWrapper;
-@property CKKSViewManager* viewManager;
+@property (readonly, weak) CKKSViewManager* viewManager;
 @property CKKSLockStateTracker* lockStateTracker;
 @property Class<SecEscrowRequestable> escrowRequestClass;
 
index 9f03512ae871550c1dbb612b87eed0f49f727b0a..a18e23a0760212c09910c019d056dcd80cd5d209 100644 (file)
@@ -24,6 +24,7 @@
 #if OCTAGON
 
 #import <Foundation/Foundation.h>
+#import <TrustedPeers/TrustedPeers.h>
 
 #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
index 2b34ec232179015ca8a0af644a8baa64560ed7db..d0e0ca45c8434360efa9f5778193b07b3d8e5e12 100644 (file)
@@ -28,7 +28,6 @@
 #import <Security/SecKeyPriv.h>
 
 #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;
 }
                                               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<NSString*>* _Nullable syncingViews,
+                                                           TPPolicy* _Nullable syncingPolicy,
+                                                           NSError * _Nullable error) {
             STRONGIFY(self);
             [[CKKSAnalytics logger] logResultForEvent:OctagonEventPrepareIdentity hardFailure:true result:error];
             if(error) {
                 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];
             }
         }];
 }
index 0a09dd2fe573c3b91d37b5fa59a857d9a8364288..0f2dbc3879e9d7c59255dac8459f5fca68cc6d64 100644 (file)
@@ -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);
index dddda03b65bf7811611f0a47a5b5b2135062e584..976476e1bdd3c3e745fa41b29315fea8279df4a1 100644 (file)
@@ -19,16 +19,20 @@ NS_ASSUME_NONNULL_BEGIN
 - (instancetype)init NS_UNAVAILABLE;
 - (instancetype)initAsEssential:(BOOL)essential;
 
-// Helper methods
-+ (NSArray<NSData*>*)peerPublicSigningKeySPKIs:(NSSet<id<CKKSPeer>>* _Nullable)peers;
-
+// Helper methods.
 + (NSSet<NSString*>*)sosCKKSViewList;
 @end
 
+
 // This adapter is for a platform which does not have SOS (e.g., aTV, Watch, HomePod)
 @interface OTSOSMissingAdapter : NSObject <OTSOSAdapter>
 @end
 
+// Helper code
+@interface OTSOSAdapterHelpers : NSObject
++ (NSArray<NSData*>* _Nullable)peerPublicSigningKeySPKIsForCircle:(id<OTSOSAdapter>)sosAdapter error:(NSError**)error;
+@end
+
 NS_ASSUME_NONNULL_END
 
 #endif // OCTAGON
index 69ed211f95b93f20e8c0b578a995f1c365bef87a..4513d980a57c6d650f62975f064fc138551a162d 100644 (file)
@@ -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;
         }
 
     return result;
 }
-
-+ (NSArray<NSData*>*)peerPublicSigningKeySPKIs:(NSSet<id<CKKSPeer>>* _Nullable)peerSet
-{
-    NSMutableArray<NSData*>* publicSigningSPKIs = [NSMutableArray array];
-
-    for(id<CKKSPeer> 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
                                                          trustedPeersError:unimplementedError];
 }
 
+@end
+
+@implementation OTSOSAdapterHelpers
+
++ (NSArray<NSData*>*)peerPublicSigningKeySPKIs:(NSSet<id<CKKSPeer>>* _Nullable)peerSet
+{
+    NSMutableArray<NSData*>* publicSigningSPKIs = [NSMutableArray array];
+
+    for(id<CKKSPeer> 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<NSData*>* _Nullable)peerPublicSigningKeySPKIsForCircle:(id<OTSOSAdapter>)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<id<CKKSRemotePeerProtocol>>* 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
index 7abf6522e49dafa8e87ae566d1c29951f9176a65..096f1cdd9c6ee7cf7e6d13a16008d5b72ae263c7 100644 (file)
     }];
     [self dependOnBeforeGroupFinished:self.finishedOp];
 
-    NSError* error = nil;
-    NSSet<id<CKKSRemotePeerProtocol>>* peerSet = [self.deps.sosAdapter fetchTrustedPeers:&error];
+    NSError* sosPreapprovalError = nil;
+    NSArray<NSData*>* 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<NSData*>* 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];
index 04bbfec95f6bdd7428f2fb4767871bd9f2ffcd37..8c87b128e53d7c9a015ef2b9975a79c50c5d4801 100644 (file)
@@ -2,6 +2,7 @@
 #if OCTAGON
 
 #import <Foundation/Foundation.h>
+#import <TrustedPeers/TrustedPeers.h>
 #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 <OctagonStateTransitionOperationProtocol>
 
+@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
 
index 8f22398473b08abf9e5e6c49aa4975ee19d81f21..4972beed2305332ca777cabbd32e316de5567f7e 100644 (file)
@@ -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;
 }
         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);
     }
                                               deviceName:self.deviceInfo.deviceName
                                             serialNumber:self.self.deviceInfo.serialNumber
                                                osVersion:self.deviceInfo.osVersion
-                                           policyVersion:nil
+                                           policyVersion:self.policyOverride
                                            policySecrets:nil
                              signingPrivKeyPersistentRef:signingKeyPersistRef
                                  encPrivKeyPersistentRef:encryptionKeyPersistRef
                                                            NSData * _Nullable permanentInfoSig,
                                                            NSData * _Nullable stableInfo,
                                                            NSData * _Nullable stableInfoSig,
+                                                           NSSet<NSString*>* syncingViews,
+                                                           TPPolicy* _Nullable syncingPolicy,
                                                            NSError * _Nullable error) {
             STRONGIFY(self);
 
                 [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];
         }];
 }
 
                                                                     reply:^(BOOL launchOkay, NSError * _Nullable error) {
             STRONGIFY(self);
 
+            // Preflight preapprovedjoin should return the policy used by the peers we eventually will trust
+            // <rdar://problem/57768490> 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);
 {
     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");
 
     secnotice("octagon-sos", "Fetching trusted peers from SOS");
 
-    NSError* error = nil;
-    NSSet<id<CKKSRemotePeerProtocol>>* peerSet = [self.deps.sosAdapter fetchTrustedPeers:&error];
+    NSArray<NSData*>* 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<NSData*>* 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<CKRecord*>* keyHierarchyRecords, NSError * _Nullable error) {
-            STRONGIFY(self);
+                                                                  reply:^(NSString * _Nullable peerID,
+                                                                          NSArray<CKRecord*>* keyHierarchyRecords,
+                                                                          NSSet<NSString*>* _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/ot/OTSetCDPBitOperation.h b/keychain/ot/OTSetCDPBitOperation.h
new file mode 100644 (file)
index 0000000..03a5396
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "keychain/ckks/CKKSGroupOperation.h"
+#import "keychain/ot/OctagonStateMachineHelpers.h"
+#import "keychain/ot/OTOperationDependencies.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface OTSetCDPBitOperation : CKKSGroupOperation <OctagonStateTransitionOperationProtocol>
+
+- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies
+                       intendedState:(OctagonState*)intendedState
+                          errorState:(OctagonState*)errorState;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // OCTAGON
diff --git a/keychain/ot/OTSetCDPBitOperation.m b/keychain/ot/OTSetCDPBitOperation.m
new file mode 100644 (file)
index 0000000..9b1b359
--- /dev/null
@@ -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
index ede2cb05312f7a19d94e21081341804dcdffcec9..52cee05b3794758fec6348b13a43ed860e5918b9 100644 (file)
@@ -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;
index 5af0ffb4ab082d9bee795558908de4178e5f3766..5db76711fa2a93c57ac4b4402e2c4fe9e5b2170c 100644 (file)
@@ -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<OctagonState*, NSNumber*>* 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<OctagonState*, NSNumber*>* 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<OctagonState *>* 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<OctagonFlag *>* AllOctagonFlags(void)
         [flags addObject:OctagonFlagCKKSRequestsTLKUpload];
         [flags addObject:OctagonFlagCuttlefishNotification];
         [flags addObject:OctagonFlagAccountIsAvailable];
+        [flags addObject:OctagonFlagCDPEnabled];
         [flags addObject:OctagonFlagAttemptSOSUpgrade];
         [flags addObject:OctagonFlagFetchAuthKitMachineIDList];
         [flags addObject:OctagonFlagUnlocked];
index 6dbc7022576317f9f08b2b34efd45861c81d18d2..18999f229ea267b27910757493dcc535480da97a 100644 (file)
@@ -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
index 5a24b829e845ba4191a5c13dad1c6c9f6380f634..c31589f0fa212ac22755494fcfbcfbe4b0aedf32 100644 (file)
@@ -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;
     }
                 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;
index 5f8a8763ee8a5d854d5899bf8f518b3fd8c8a66d..0e6cb278527e6f436e0270c8721765d3d888b44e 100644 (file)
     }];
     [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<NSString *> * _Nullable machineIDs, NSError * _Nullable error) {
         STRONGIFY(self);
         if(!machineIDs || error) {
             if (self.logForUpgrade) {
                 [[CKKSAnalytics logger] logSuccessForEventNamed:OctagonEventUpgradeFetchDeviceIDs];
             }
-            [self afterAuthKitFetch:machineIDs];
+            [self afterAuthKitFetch:machineIDs accountIsDemo:isAccountDemo];
         }
     }];
 }
 
-- (void)afterAuthKitFetch:(NSSet<NSString *>*)allowedMachineIDs
+- (void)afterAuthKitFetch:(NSSet<NSString *>*)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);
 
index f54308046aae625c1cdf1927e7b9fc7a81da0f73..5c74b845e008e71f619863b04cb38a006ca89850 100644 (file)
@@ -51,7 +51,8 @@
     NSMutableSet<CKKSKeychainView*>* 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);
index 48a381bcb1bc04cd999688d94eeda8247f075033..d47c452d62cef4191d68547b039fb3271ae4f8ce 100644 (file)
         }
     }
 
-    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<CKKSKeychainBackedKeySet*>*)viewKeySets tlkShares:(NSArray<CKKSTLKShare*>*)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<NSString*>* 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];
 
         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<CKKSTLKShare*>* 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<CKKSKeychainBackedKeySet*>*)viewKeySets filteredTLKShares:(NSArray<CKKSTLKShare*>*)tlkShares
                                                          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];
 
                 return;
             }
 
+            //collect TLK count metrics
+            [self noteMetric:OctagonAnalyticsBottledUniqueTLKsRecovered count:uniqueTLKsRecovered];
+            [self noteMetric:OctagonAnalyticsBottledTotalTLKSharesRecovered count:totalTLKSharesRecovered];
+            [self noteMetric:OctagonAnalyticsBottledTotalTLKShares count:tlkShares.count];
+
+            NSMutableSet<NSString*>* 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;
index 12712c0ee89d1baa8009435e78dc52f6d550e31f..1203d7156f61f819d0bf58aa51b380adc79cb4da 100644 (file)
@@ -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;
index 0bb767fefe56dbdf26df9574bf00aca4a0549330..24809b5c5a3d29609a5e27c050a83844c3d52104 100644 (file)
@@ -36,6 +36,9 @@
 @interface OTVouchWithRecoveryKeyOperation ()
 @property OTOperationDependencies* deps;
 
+@property NSString* salt;
+@property NSString* recoveryKey;
+
 @property NSOperation* finishOp;
 @end
 
     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<NSString*>* 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
 
     CKKSResultOperation* proceedWithKeys = [CKKSResultOperation named:@"recovery-tlks"
                                                             withBlock:^{
-                                                                STRONGIFY(self);
-                                                                [self proceedWithKeys:fetchKeysOp.viewKeySets tlkShares:fetchKeysOp.tlkShares salt:salt];
-                                                            }];
+        STRONGIFY(self);
+
+        NSMutableArray<CKKSTLKShare*>* 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];
index dcd693ad5f5ec0dbb8dfe8aed514e475b80c3067..3e4f4efe3381e06621d44846d392832b83b5425c 100644 (file)
@@ -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) {
index 4278b4996c94d97061777addd1f957342159ed88..b60e47eafb7d1606762febb8b9f2d15c4d9e74b3 100644 (file)
@@ -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
index 68f33b21562291ecaf169b6b397c5c42da5c55d8..ced8b4d28cacddbe96ea6f4540944e4e09aac4b4 100644 (file)
@@ -12,6 +12,7 @@
 
 #import "keychain/ot/OTDefines.h"
 #import "keychain/ot/OTConstants.h"
+#import <TrustedPeers/TPPolicy.h>
 
 @implementation OTAccountMetadataClassC (KeychainSupport)
 
     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
index 3943683f0feac9f5213da6b8973a1f1cee455820..204e720cd287b2a972024b4c61d5c792f2caa00f 100644 (file)
@@ -8,6 +8,7 @@
 
 @protocol OctagonEscrowRecovererPrococol <NSObject>
 - (NSError*)recoverWithInfo:(NSDictionary*)info results:(NSDictionary**)results;
+- (NSError *)disableWithInfo:(NSDictionary *)info;
 @end
 
 @interface SecureBackup (OctagonProtocolConformance) <OctagonEscrowRecovererPrococol>
index a3f0379fb7871ba435080f5fe4ad2503c73a1bdb..d706b6d9dde4d2574e9427c146f4e566c3d0d822 100644 (file)
@@ -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;
 }
index c6b68a5630399824cb3af66f250cbb42dac2189c..edc46473cb5b85b2d0dff5c37c6055e138e9aadb 100644 (file)
@@ -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;
+    // <rdar://problem/55465193> reserved is not a keyword (it should be)
+    // reserved 4;
 }
index aeef0ac0aef1d8deba620dd9d79b8c10e426b03a..295d83b6cf0ea23239965667a2df898a5226569a 100644 (file)
@@ -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<NSString *> *_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<NSString *> *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;
 
index c7e41deba9eed62f8871dd0078fee9d4e1194a83..40c4de4635ecb300cc3c5dfa2ff7f031cf254e15 100644 (file)
@@ -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
 {
 }
 - (BOOL)hasLastHealthCheckup
 {
-    return _has.lastHealthCheckup;
+    return _has.lastHealthCheckup != 0;
 }
 @synthesize attemptedJoin = _attemptedJoin;
 - (OTAccountMetadataClassC_AttemptedAJoinState)attemptedJoin
 }
 - (BOOL)hasAttemptedJoin
 {
-    return _has.attemptedJoin;
+    return _has.attemptedJoin != 0;
 }
 - (NSString *)attemptedJoinAsString:(OTAccountMetadataClassC_AttemptedAJoinState)value
 {
 {
     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
 {
     {
         [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
index f2fad2f14446c23de9666771b96de10955ff073c..e8c27edd467aa87ceb37f9c272a60bb8775b1c4f 100644 (file)
@@ -8,7 +8,6 @@
 @class OTSponsorToApplicantRound1M2;
 @class OTApplicantToSponsorRound2M1;
 @class OTSponsorToApplicantRound2M2;
-@class OTSOSMessage;
 
 #ifdef __cplusplus
 #define OTPAIRINGMESSAGE_FUNCTION extern "C" __attribute__((visibility("hidden")))
 #define OTPAIRINGMESSAGE_FUNCTION extern __attribute__((visibility("hidden")))
 #endif
 
+/**
+ * Claimed for a field, but never used
+ * reserved 3;
+ */
 __attribute__((visibility("hidden")))
 @interface OTPairingMessage : PBCodable <NSCopying>
 {
     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;
 
index 3416a4f952bdf5a78ac2b944d4faf1f315f1952c..a7c8f89b28faf48b41b3fb712869f50476e1e2cf 100644 (file)
@@ -8,7 +8,6 @@
 #import <ProtocolBuffer/PBDataReader.h>
 
 #import "OTApplicantToSponsorRound2M1.h"
-#import "OTSOSMessage.h"
 #import "OTSponsorToApplicantRound1M2.h"
 #import "OTSponsorToApplicantRound2M2.h"
 
     return _voucher != nil;
 }
 @synthesize voucher = _voucher;
-- (BOOL)hasSosPairingMessage
-{
-    return _sosPairingMessage != nil;
-}
-@synthesize sosPairingMessage = _sosPairingMessage;
 
 - (NSString *)description
 {
     {
         [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 (file)
index e515077..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-// This file was automatically generated by protocompiler
-// DO NOT EDIT!
-// Compiled from OTPairingMessage.proto
-
-#import <Foundation/Foundation.h>
-#import <ProtocolBuffer/PBCodable.h>
-
-#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 <NSCopying>
-{
-    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 (file)
index e4a1f2a..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-// This file was automatically generated by protocompiler
-// DO NOT EDIT!
-// Compiled from OTPairingMessage.proto
-
-#import "OTSOSMessage.h"
-#import <ProtocolBuffer/PBConstants.h>
-#import <ProtocolBuffer/PBHashUtil.h>
-#import <ProtocolBuffer/PBDataReader.h>
-
-#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
-
index b7495329a312f53c3e193b3b897705b480375b38..a0a32e482abb718f37248b60299f0ba3c0d49167 100644 (file)
@@ -14,7 +14,6 @@
 __attribute__((visibility("hidden")))
 @interface OTSponsorToApplicantRound2M2 : PBCodable <NSCopying>
 {
-    NSMutableArray<NSData *> *_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<NSData *> *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;
 
index 6737b64112abe9a90e41a54a00cad4f78cd30879..c51eaced45326b167af2349d11bc56baa418d13b 100644 (file)
     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
 {
     {
         [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 (file)
index e4a1f2a..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-// This file was automatically generated by protocompiler
-// DO NOT EDIT!
-// Compiled from OTPairingMessage.proto
-
-#import "OTSOSMessage.h"
-#import <ProtocolBuffer/PBConstants.h>
-#import <ProtocolBuffer/PBHashUtil.h>
-#import <ProtocolBuffer/PBDataReader.h>
-
-#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 (file)
index 0000000..9ddd0d1
--- /dev/null
@@ -0,0 +1,3 @@
+disabled_rules:
+    - force_cast
+    - force_try
index 0b457d231af78a482a1dfa84d2632a1c95e78405..82e22519249dd8ef5c5b7f984622fa264c1278b5 100644 (file)
@@ -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)")
         }
index a9d31759e0329062f7620451df526ca5bf5c30e6..ef94770e4f37aeb4bee228d1344c3cf83bb94c67 100644 (file)
@@ -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 (file)
index 0000000..fa1e387
--- /dev/null
@@ -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
index d088ab714e80c1a8d4b9a242207eadc828633bb6..4888f6e160c442137e0f6159e8a40ad7d5e9ebe9 100644 (file)
@@ -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 (file)
index 0000000..67e9a82
--- /dev/null
@@ -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<String>(["Manatee", "LimitedPeersAllowed"])
+        #else
+        let expected = Set<String>(["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"),
+                // <rdar://problem/57771098> Octagon: create final policy for CKKS4All
+                // CKRecordZone.ID(zoneName: "Backstop"),
+                // <rdar://problem/57810109> 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"),
+                // <rdar://problem/57810109> 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<String>([
+                                    "ApplePay",
+                                    "Applications",
+                                    "AutoUnlock",
+                                    // <rdar://problem/57771098> Octagon: create final policy for CKKS4All
+                                    //"Backstop",
+                                    // <rdar://problem/57810109> Cuttlefish: remove Safari prefix from view names
+                                    "SafariCreditCards",
+                                    "DevicePairing",
+                                    "Engram",
+                                    "Health",
+                                    "Home",
+                                    "LimitedPeersAllowed",
+                                    "Manatee",
+                                    // <rdar://problem/57810109> Cuttlefish: remove Safari prefix from view names
+                                    "SafariPasswords",
+                                    "ProtectedCloudStorage",
+                                    "SecureObjectSync",
+                                    "WiFi",
+                                    ])
+        #else
+        let expected = Set<String>(["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")
+    }
+}
index 5a43ca5487c8a180343e3139ff3f81b76f9565d6..fd35bd09f36b23d740b4a785d8ff42950ab179f9 100644 (file)
@@ -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
index c26920374b6dc49453b1c5c3b0a2ceb72c60bf71..b639f749d35506a411d62da1b3857dab2cefe389 100644 (file)
@@ -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
     }
 }
index 848bb213e12bff37a75f00b400971ece52f46c9c..110b7e30ab4007d36afcd01a76189bbd8dd5ad2f 100644 (file)
@@ -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<MachineMO> ?? Set() {
-                if machinemo.machineID == self.mockAuthKit2.currentMachineID {
+            for machinemo in container.containerMO.machines as? Set<MachineMO> ?? 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
index b5069a737c11ff9fafc3451bcbf540a62581aa60..da28130fdf630882a853b68bf43987f93c098eaf 100644 (file)
@@ -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()
         }
index 64a4006fb7df4359ab2a6869c681bc31af835ffd..d79263f8e3ed491b255b914f5e89ffd72a2091f7 100644 (file)
@@ -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()
         }
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
             dumpCallback.fulfill()
 
         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
         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()
         }
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
 
         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
 
         // 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()
         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()
         }
         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
         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()
         }
         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()
         }
 
         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
 
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
 
 
         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
 
         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()
         }
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 1, "should be 1 peer ids")
 
         // 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)
 
         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<String, AnyObject>
+            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")
         self.wait(for: [dumpExpectation], timeout: 10)
 
         self.otControlCLI.status(OTCKContainerName,
-                                 context: newOTCliqueContext.context!,
+                                 context: newOTCliqueContext.context,
                                  json: false)
     }
 
 
         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
         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()
         }
         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
         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()
         }
 
         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
         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()
         }
 
         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
         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,
 
         // 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,
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 3, "should be 3 peer id included")
 
         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
         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.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()
         }
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
             dumpCallback.fulfill()
         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
         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()
         }
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
             dumpCallback.fulfill()
         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
         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.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()
         }
         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<String, AnyObject>
+            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<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
             dumpCallback.fulfill()
         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 (file)
index 0000000..67c3928
--- /dev/null
@@ -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
index e1fd51bd0ddab33142e68a79f91ecbc565d8c6ee..6591eb61e3c18076d7b3eff847d308fccade6fa3 100644 (file)
@@ -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<String, AnyObject>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            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<String, AnyObject>
+            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<String, AnyObject>
+            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<String, AnyObject>
+            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<String, AnyObject>
+            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<String, AnyObject>
+            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<String, AnyObject>
+            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 (file)
index 0000000..70ed71e
--- /dev/null
@@ -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
index ad69de5b3b09a010995c1e411e6a3d5fd65c1cee..10172e3514cd8b8282f02fe43b70079f3d603cbd 100644 (file)
@@ -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()
         }
         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()
         }
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
 
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
 
         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
         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()
         }
         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()
         }
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
 
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
 
 
         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()
         }
 
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 3, "should be 3df peer ids")
 
         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
         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)
 
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            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"]
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            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"]
             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)
     }
 
         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
         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()
         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
         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
 
         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
         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()
         }
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
 
 
         //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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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")
             initiatorRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data
             initiatorRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data
 
-            let included = dynamicInfo!["included"] as? Array<String>
+            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"]
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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")
             firstDeviceRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data
             firstDeviceRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data
 
-            let included = dynamicInfo!["included"] as? Array<String>
+            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"]
         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
         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)
 
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            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"]
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            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"]
         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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
 
-            let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
+            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<String>
+            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"]
         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")
         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()
         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
         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 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
         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)
 
         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)
         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
         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 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
         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"
         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")
         }
         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
index 00037248579768448709953afb8e778a63de59b6..3bc43ec728c0c144dbc3f3e68b7dac8f12f8cd00 100644 (file)
@@ -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<String, AnyObject>
+            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
index a239ad9b6ffbc74879096c40d5121ee7d49a3a27..11961ed33c1ae90a75f38cc7c8a169b2a85920aa 100644 (file)
@@ -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
index f1a10e25d0fcc9f17bf79757fb330803c4b89258..86e5caf39f3b916dda8660eb7b0f9ae42b5055a8 100644 (file)
@@ -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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
 
-            let permanentInfo = egoSelf!["permanentInfo"] as? Dictionary<String, AnyObject>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
 
-            let permanentInfo = egoSelf!["permanentInfo"] as? Dictionary<String, AnyObject>
+            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()
         }
index 850a15b1291ee53fcebd410eaa65b594b5a94492..64bca2d79edfe29885692672f940a78b0bf17679 100644 (file)
@@ -12,6 +12,7 @@
 #import "KeychainCircle/KCJoiningRequestSession+Internal.h"
 #import "KeychainCircle/KCJoiningAcceptSession+Internal.h"
 #import <KeychainCircle/KCJoiningMessages.h>
+#import <KeychainCircle/PairingChannel.h>
 
 #import <TrustedPeers/TrustedPeers.h>
 #import <TrustedPeers/TPHash.h>
@@ -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"
 
 #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.
index de42b91099047aecba03756fc516cb1927254cd5..5e17bae512d9c7b13dca050c78ec377017a671ba 100644 (file)
@@ -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<String>
@@ -74,6 +76,7 @@ class OTMockAuthKitAdapter: OTAuthKitAdapter {
         self.otherDevices = otherDevices
         self.excludeDevices = Set()
         self.hsa2 = true
+        self.isDemoAccount = false
         self.listeners = CKKSListenerCollection<OTAuthKitAdapterNotifier>(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<CKRecordZone.ID>!
     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<CKRecordZone.ID>, 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<String> {
-        #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<CFString, Any> {
+            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<String, AnyObject>
+            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<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? Dictionary<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)
-        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<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 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",
+                                 // <rdar://problem/57810109> 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<String>(["Manatee"])
-        #else
-        let expected = Set<String>(["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<String>([
-                                    "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), ])
     }
 }
 
index 17901fea6c705af240e512ae90352f2794210112..2c76a5ecaf11e963735ab61ca54974ce61e96600 100644 (file)
@@ -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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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?
index e6b28859b0a3c2d44cd63bb910df2ea181d43391..142b3410018c85d54a31e527cf4184fa753ef6bc 100644 (file)
@@ -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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            let included = dynamicInfo!["included"] as? [String]
             XCTAssertNotNil(included, "included should not be nil")
             XCTAssertEqual(included!.count, 3, "should be 3 peer ids")
             pair2AcceptorDumpCallback.fulfill()
index 1103b6a9af242262bdafd03480526dea28a6c21b..47786d254236188189324ab9c37ad67ccec88550 100644 (file)
@@ -11,6 +11,28 @@ extension OctagonPairingTests {
 //        XCTAssert(SOSCircleHasPeer(self.circle, self.fcAcceptor.peerInfo(), nil), "HasPeer 2") <rdar://problem/54040068>
     }
 
+    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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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<String, AnyObject>
+            let egoSelf = dump!["self"] as? [String: AnyObject]
             XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
-            let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
+            let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
             XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
-            let included = dynamicInfo!["included"] as? Array<String>
+            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
index ea84fc767014117a67adf94d4231949e8429f016..5eda3686a79587a1813978fc076de3f908be68a7 100644 (file)
@@ -4,7 +4,7 @@ func GenerateFullECKey(keySize: Int) -> (SecKey) {
 
     let keyPair = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
 
-    var keyAttributes: Dictionary<String, String> = [:]
+    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<String, String> = [:]
+        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<String> = []
+    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<String>, 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<String> = Array()
+        var secretArray: [String] = Array()
         secretArray.append(secret)
         return KCJoiningAcceptTestDelegate(withSecrets: secretArray, retries: 3, code: code)
     }
 
-    init(withSecrets secrets: Array<String>, 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)
index a62f3bce6f49e71b6034b95114903c9f68a96fb4..382933355e65a5770bf3dfc67bdb7cf1a818d386 100644 (file)
@@ -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;
 
index 5fa418c48739eb76cbb944f458bfe02c3fc7e20f..5637dec991ad9725437afd0bcabc7b3f7e1aa3f3 100644 (file)
 #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 <AuthKit/AKAppleIDAuthenticationController.h>
+#import <AuthKit/AKAppleIDAuthenticationContext.h>
+#import <AuthKit/AKAppleIDAuthenticationContext_Private.h>
+
+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
index 472e6b4237caa0b8dd7151886418a88edb999df7..311579d3c7c888805ca9cf810650e752a3933c20 100644 (file)
@@ -2,6 +2,14 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+       <key>com.apple.securebackupd.access</key>
+       <true/>
+       <key>keychain-cloud-circle</key>
+       <true/>
+       <key>com.apple.private.ckks</key>
+       <true/>
+       <key>com.apple.authkit.client.private</key>
+       <true/>
        <key>com.apple.private.octagon</key>
        <true/>
        <key>com.apple.private.escrow-update</key>
index d569f7eaa0cb128861fd73a2faa6d9ee3e9d3365..dd0cb2cb9bfab04957429028acd8a17bffaefff6 100644 (file)
@@ -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];
index f2d431ee6895e272e03ea2cabd3d8e7f2b4b8657..413b75cb37cdc4af2de446b13f760cfd080bbfc7 100644 (file)
@@ -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;
 }
 
index f62cc0224b6338e7e5be432070e3f16b349d7d31..41934fda203dafd88a0fbc7c1e70688cf98b01ec 100644 (file)
@@ -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
     //
index a406f2ab77d8d208a3fd971991f63783d7038e66..5db81a50677b06b01d9eb68416acd13904073dbd 100644 (file)
@@ -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);
 
index 2a2fa0baf6aa61781972491e73431bf309f1b35c..57ffd281c6c24ff43c4844365ffa7149f4f5cd8a 100644 (file)
@@ -51,7 +51,7 @@
 #include <unistd.h>
 
 #include "secd_regressions.h"
-#include "SOSTestDataSource.h"
+#include "keychain/SecureObjectSync/Regressions/SOSTestDataSource.h"
 
 #include "SOSRegressionUtilities.h"
 #include <Security/SecRecoveryKey.h>
@@ -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 (file)
index 164c56f..0000000
+++ /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 <stdio.h>
-#include <Security/SecBase.h>
-#include <Security/SecItem.h>
-
-#include <Security/SecureObjectSync/SOSAccount.h>
-#include <Security/SecureObjectSync/SOSCloudCircle.h>
-#include <Security/SecureObjectSync/SOSInternal.h>
-#include <Security/SecureObjectSync/SOSFullPeerInfo.h>
-#include <Security/SecureObjectSync/SOSUserKeygen.h>
-#include <Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include "secd_regressions.h"
-#include "SOSTestDataSource.h"
-
-#include "SOSRegressionUtilities.h"
-#include <utilities/SecCFWrappers.h>
-
-#include <securityd/SOSCloudCircleServer.h>
-#include "SecdTestKeychainUtilities.h"
-#import "SOSAccountTesting.h"
-#import "SOSTransportTestTransports.h"
-#include <Security/SecureObjectSync/SOSTransportMessageIDS.h>
-#include <SOSCircle/CKBridge/SOSCloudKeychainConstants.h>
-#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 (file)
index 80c23ae..0000000
+++ /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 <Security/SecBase.h>
-#include <Security/SecItem.h>
-
-#include <CoreFoundation/CFDictionary.h>
-
-#include "keychain/SecureObjectSync/SOSAccount.h"
-#include <Security/SecureObjectSync/SOSCloudCircle.h>
-#include "keychain/SecureObjectSync/SOSInternal.h"
-#include "keychain/SecureObjectSync/SOSUserKeygen.h"
-#include "keychain/SecureObjectSync/SOSTransport.h"
-
-#include <stdlib.h>
-#include <unistd.h>
-
-#include "secd_regressions.h"
-#include "SOSTestDataSource.h"
-
-#include "SOSRegressionUtilities.h"
-#include <utilities/SecCFWrappers.h>
-#include <Security/SecKeyPriv.h>
-
-#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 (file)
index a23cdab..0000000
+++ /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 <stdio.h>
-#include <Security/SecBase.h>
-#include <Security/SecItem.h>
-
-#include <Security/SecureObjectSync/SOSAccount.h>
-#include <Security/SecureObjectSync/SOSCloudCircle.h>
-#include <Security/SecureObjectSync/SOSInternal.h>
-#include <Security/SecureObjectSync/SOSFullPeerInfo.h>
-#include <Security/SecureObjectSync/SOSUserKeygen.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include "secd_regressions.h"
-#include "SOSTestDataSource.h"
-
-#include "SOSRegressionUtilities.h"
-#include <utilities/SecCFWrappers.h>
-
-#include <securityd/SOSCloudCircleServer.h>
-#include "SecdTestKeychainUtilities.h"
-#include "SOSAccountTesting.h"
-#import "SOSTransportTestTransports.h"
-#include "SOSTestDevice.h"
-#include "SOSTestDataSource.h"
-#include <Security/SecureObjectSync/SOSTransportMessageIDS.h>
-#include <Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h>
-
-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;
-}
index d3de4a06e643234586d41e9821d37db58c9ba6d4..422b0cdeb118471e9dfd4821067589b991737d5d 100644 (file)
@@ -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)
index 3adf34ebc69c7af61e931250709b342603828062..0bb84b8be754b8f7c7ddb3f2d57e7380b3b0bf49 100644 (file)
@@ -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);
 
index 51bccb51f6b70718e03235c520c9440d1b31c693..71677a8a45a352d8f592044fed59d8d0548d1dec 100644 (file)
@@ -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);
index b86f63cbcd28da97b45a91ec5f0ea01145caf49b..bfdd3c2ca36c9088c37f2eb62020ef9d0742c61e 100644 (file)
@@ -25,7 +25,7 @@
 }
 - (BOOL)hasKeyClass
 {
-    return _has.keyClass;
+    return _has.keyClass != 0;
 }
 - (BOOL)hasPublicKey
 {
index eae21ba43807dc20ff2014e0748be3de08b5cec4..0da4f8ecb747808fb51c6f55bed8527456bae5dc 100644 (file)
@@ -25,7 +25,7 @@
 }
 - (BOOL)hasKeyClass
 {
-    return _has.keyClass;
+    return _has.keyClass != 0;
 }
 - (BOOL)hasBackupWrappedMetadataKey
 {
index 1621b0d2c89e9b864537cafad7f8c8e745a136b2..9dd58a5ec6ab0b79f1c587c60e716052574b5644 100644 (file)
@@ -27,7 +27,7 @@
 }
 - (BOOL)hasRecoveryType
 {
-    return _has.recoveryType;
+    return _has.recoveryType != 0;
 }
 - (BOOL)hasBagIdentity
 {
index 7e52fc36d9e66ab2e8586dc5c11297351819eb11..4ad9c89248f34a45ec1e24fb775502ef9542c263 100644 (file)
@@ -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)
 {
index 1845b5bb1afafa6345e30b2d38006ee82ca2407d..d5a3a283ab01216c5e38b0373d7f334c36d3796e 100644 (file)
@@ -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);
index fab2678e38248d4453f941ab190fb45cd413f156..3ba61dd777a7eddafd8dd9a2975006264de47af3 100644 (file)
@@ -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
 
index 039b82528c03426c299a18c3eb67d785f5ee20d1..cd9c7b9707074a970d08cfd8a7531346272a1af8 100644 (file)
@@ -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)
 {
index 91fcda27733b6494e7bcd956754a5e2ebd60a60e..0c1637786929acaa26201c90d85d4acfc26b3295 100644 (file)
@@ -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);
 
 
index 80b10d3fda5ce9430cfbef7199309972d85508b9..367f2274d741c7c1a7ff4f87df00e9d5a91ad4d8 100644 (file)
@@ -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 */
index fd98b35381f8db56c1f5c23478d5f3e67bdf7f4a..5ba4a914a470cb6a9769e28646df022be2e6e3ae 100644 (file)
@@ -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<String>()
         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<String> = 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
index e31f88f6210b8525dd2f038e275cce3e1b391dc5..9968af8068009cda8554a4b3e5c47ee4437f6fcc 100644 (file)
 #include <security_asn1/secerr.h>
 #include <security_asn1/secport.h>
 
-#if USE_CDSA_CRYPTO
-#include <Security/cssmapi.h>
-#else
 #include <CommonCrypto/CommonDigest.h>
-#endif
 
 #include <Security/SecCmsDigestContext.h>
 
@@ -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) {
index 1c309f03f745b2b255472334e2f2457f9f8387b6..9d2597a59badd659698b9c94546cdf539f8a9cb5 100644 (file)
@@ -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);
 
 /*
index 9ed5d17332c27001d640945c8cc51b6e285450c2..e462cab5a97503d42df362d6bf7132198c6c3151 100644 (file)
@@ -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;
index b209bc6f383611643897a284997594a3dd28ec62..25395fecaa4daaa4a7da637facd53bcde2a7b30e 100644 (file)
@@ -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 (file)
index e69de29..0000000
index c59ca4d27d92f20e111fd8d904c02a8ac0462161..26672e55bcd076d468ba62bb60a1a5298db8bdc8 100644 (file)
@@ -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")
index ad6cc5e7c63f48171a4c1e01b8610aabe7273fd0..43ebb292be4a918ff0043342ff76e1ff4d2d2556 100644 (file)
                189D462D166AC95C001D8533 /* Project object */ = {
                        isa = PBXProject;
                        attributes = {
-                               LastUpgradeCheck = 1000;
+                               LastUpgradeCheck = 1120;
                                ORGANIZATIONNAME = Apple;
                        };
                        buildConfigurationList = 189D4630166AC95C001D8533 /* Build configuration list for PBXProject "securityd_service" */;
                        developmentRegion = English;
                        hasScannedForEncodings = 0;
                        knownRegions = (
+                               English,
                                en,
                        );
                        mainGroup = 189D462C166AC95C001D8533;
                        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;
                        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;
index fc35bbc1a28c8000940c9f5e4849cc4a37f4c860..943df37195fb2123d1727d2cc085c59939f2a43a 100644 (file)
@@ -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);
index ec8b2bc0c4df45b548394be540953c5ab3ee2d8a..f569603b32cf419c85d547ab7b9269399e5f9f80 100644 (file)
@@ -8,5 +8,7 @@
        <array>
                <string>kTCCServiceSystemPolicyAllFiles</string>
        </array>
+       <key>com.apple.private.security.storage.SystemKeychain</key>
+       <true/>
 </dict>
 </plist>
index fa4ce7533efb2e07f8ea1230b82f64cde99376c2..c73a8267a202be29c169c3475512aa77f1b56f5d 100644 (file)
@@ -23,6 +23,7 @@
 
 #import <XCTest/XCTest.h>
 #import <Security/SFAnalytics.h>
+#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];
 }
 
index 0b8695009c49890eb8e5253f2d68653e750c1e77..20c99ffbe4712887e23472d5ea95fe309b6dee85 100644 (file)
@@ -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<NSDictionary *> *)createRandomEventList:(size_t)count
+{
+    NSMutableArray<NSDictionary *> *eventSet = [[NSMutableArray<NSDictionary *> 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<NSDictionary *> *summaries = [self createRandomEventList:5];
+    NSArray<NSDictionary *> *failures = [self createRandomEventList:100];
+    NSMutableArray<NSDictionary *> *visitedEvents = [[NSMutableArray<NSDictionary *> alloc] init];
+
+    SFAnalyticsTopic *topic = [self TrustTopic];
+    const size_t sizeLimit = 10000; // total size of the encoded data
+    topic.uploadSizeLimit = sizeLimit;
+
+    NSError *error = nil;
+    NSArray<NSDictionary *> *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<NSDictionary *> *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<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
index f9fdce960d58c610231a1e9a863631d770256c99..9e4bf941a0a092dcb530b39263edbe5f64b44a6a 100644 (file)
@@ -35,6 +35,7 @@
 @property NSString* splunkTopicName;
 @property NSURL* splunkBagURL;
 @property NSString *internalTopicName;
+@property NSUInteger uploadSizeLimit;
 
 @property NSArray<SFAnalyticsClient*>* topicClients;
 
@@ -42,6 +43,9 @@
 // Things below are for unit testing
 - (instancetype)initWithDictionary:(NSDictionary *)dictionary name:(NSString *)topicName samplingRates:(NSDictionary *)rates;
 - (BOOL)haveEligibleClients;
+- (NSArray<NSDictionary *> *)createChunkedLoggingJSON:(NSArray<NSDictionary *> *)healthSummaries failures:(NSArray<NSDictionary *> *)failures error:(NSError **)error;
+- (NSArray<NSArray *> *)chunkFailureSet:(size_t)sizeCapacity events:(NSArray<NSDictionary *> *)events error:(NSError **)error;
+- (size_t)serializedEventSize:(NSObject *)event error:(NSError**)error;
 + (NSString*)databasePathForCKKS;
 + (NSString*)databasePathForSOS;
 + (NSString*)databasePathForPCS;
index 194e1591bb2664b7af18590b52d416fed2b89c9b..9fe33d8773009a4e8a7852819cac6692877eea59 100644 (file)
@@ -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<NSArray*>*)failures toUploadRecords:(NSMutableArray*)records threshold:(NSUInteger)threshold
+- (void)addFailures:(NSMutableArray<NSArray*>*)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<SFAnalyticsClient*>**)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<NSArray *> *)chunkFailureSet:(size_t)sizeCapacity
+                                 events:(NSArray<NSDictionary *> *)events
+                                  error:(NSError **)error
+{
+    const size_t postBodyLimit = 1000; // 1000 events in a single upload
+    size_t currentSize = 0;
+    size_t currentEventCount = 0;
+
+    NSMutableArray<NSArray<NSDictionary *> *> *eventChunks = [[NSMutableArray<NSArray<NSDictionary *> *> alloc] init];
+    NSMutableArray<NSDictionary *> *currentEventChunk = [[NSMutableArray<NSDictionary *> 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<NSDictionary *> 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<NSDictionary *> *)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<NSDictionary *> *)createChunkedLoggingJSON:(NSArray<NSDictionary *> *)healthSummaries
+                                             failures:(NSArray<NSDictionary *> *)failures
+                                                error:(NSError **)error
 {
-    NSMutableArray<SFAnalyticsClient*>* localClients = [NSMutableArray new];
-    __block NSMutableArray* uploadRecords = [NSMutableArray arrayWithCapacity:_maxEventsToReport];
-    __block NSError *localError;
-    __block NSMutableArray<NSArray*>* hardFailures = [NSMutableArray new];
-    __block NSMutableArray<NSArray*>* 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<NSArray *> *chunkedEvents = [self chunkFailureSet:(self.uploadSizeLimit - baseSize) events:failures error:&localError];
+
+    NSMutableArray<NSDictionary *> *jsonResults = [[NSMutableArray<NSDictionary *> alloc] init];
+    for (NSArray<NSDictionary *> *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<NSDictionary *> **)healthSummaries
+          failures:(NSMutableArray<NSDictionary *> **)failures
+         forUpload:(BOOL)upload
+participatingClients:(NSMutableArray<SFAnalyticsClient*>**)clients
+             force:(BOOL)force
+        linkedUUID:(NSUUID *)linkedUUID
+             error:(NSError**)error
+{
+    NSMutableArray<SFAnalyticsClient*> *localClients = [[NSMutableArray alloc] init];
+    NSMutableArray<NSDictionary *> *localHealthSummaries = [[NSMutableArray<NSDictionary *> alloc] init];
+    NSMutableArray<NSDictionary *> *localFailures = [[NSMutableArray<NSDictionary *> alloc] init];
+    NSMutableArray<NSArray*> *hardFailures = [[NSMutableArray alloc] init];
+    NSMutableArray<NSArray*> *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<NSDictionary *> *)createChunkedLoggingJSON:(bool)pretty
+                                            forUpload:(BOOL)upload
+                                 participatingClients:(NSMutableArray<SFAnalyticsClient*>**)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<SFAnalyticsClient*>**)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<NSData *> *)serializeLoggingEvents:(NSArray<NSDictionary *> *)events
+                                        error:(NSError **)error
+{
+    if (!events) {
+        return nil;
+    }
+
+    NSMutableArray<NSData *> *serializedEvents = [[NSMutableArray<NSData *> 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<SFAnalyticsClient*>* clients = [NSMutableArray new];
-            NSData* json = [topic getLoggingJSON:false forUpload:YES participatingClients:&clients force:force error:&localError];
-            if (json) {
-                if ([topic isSampledUpload]) {
+            NSArray<NSDictionary *> *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<NSData *> *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<NSDictionary *> *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 {
index 98371ac245034a7ae68fc14e27c1baf37d014c32..b190a34bc2086469f4da075101e7ab4bf2b86294 100644 (file)
@@ -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<NSString *, id> *, NSError *))reply;
index 3c920bdf8c0101e767a49c49ad88806cba1750cc..2920fd4c988d38f93c1ab7a58dc44e4d527bb3b9 100644 (file)
@@ -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 (file)
index 0000000..3e2bb6a
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+* Copyright (c) 2006-2010,2012-2019 Apple Inc. All Rights Reserved.
+*/
+
+#include <AssertMacros.h>
+#import <XCTest/XCTest.h>
+#import <Security/SecCertificatePriv.h>
+#include <Security/SecTrustPriv.h>
+#include <Security/SecPolicyPriv.h>
+#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/tests/TrustTests/EvaluationTests/ExceptionTests_data.h b/tests/TrustTests/EvaluationTests/ExceptionTests_data.h
new file mode 100644 (file)
index 0000000..e6dc309
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+* Copyright (c) 2019 Apple Inc. All Rights Reserved.
+*/
+
+#ifndef _TRUSTTESTS_EXCEPTION_TESTS_H_
+#define _TRUSTTESTS_EXCEPTION_TESTS_H_
+
+/*
+ Serial Number:
+ 01:f1:eb:db:ee:d3:1d:7a:b5:72:54:7d:34:43:4b:87
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 Extended Validation Server CA
+ Validity
+ Not Before: Mar 22 00:00:00 2018 GMT
+ 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 _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,
+    0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,
+    0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
+    0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,
+    0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,
+    0x34,0x30,0x32,0x06,0x03,0x55,0x04,0x03,0x13,0x2B,0x44,0x69,0x67,0x69,0x43,0x65,
+    0x72,0x74,0x20,0x53,0x48,0x41,0x32,0x20,0x45,0x78,0x74,0x65,0x6E,0x64,0x65,0x64,
+    0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,
+    0x65,0x72,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x31,0x38,0x30,0x33,0x32,0x32,0x30,
+    0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x31,0x39,0x30,0x33,0x32,0x33,0x31,0x32,
+    0x30,0x30,0x30,0x30,0x5A,0x30,0x81,0xF0,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,
+    0x0F,0x0C,0x14,0x50,0x72,0x69,0x76,0x61,0x74,0x65,0x20,0x4F,0x72,0x67,0x61,0x6E,
+    0x69,0x7A,0x61,0x74,0x69,0x6F,0x6E,0x31,0x13,0x30,0x11,0x06,0x0B,0x2B,0x06,0x01,
+    0x04,0x01,0x82,0x37,0x3C,0x02,0x01,0x03,0x13,0x02,0x55,0x53,0x31,0x1B,0x30,0x19,
+    0x06,0x0B,0x2B,0x06,0x01,0x04,0x01,0x82,0x37,0x3C,0x02,0x01,0x02,0x13,0x0A,0x43,
+    0x61,0x6C,0x69,0x66,0x6F,0x72,0x6E,0x69,0x61,0x31,0x11,0x30,0x0F,0x06,0x03,0x55,
+    0x04,0x05,0x13,0x08,0x43,0x30,0x38,0x30,0x36,0x35,0x39,0x32,0x31,0x0B,0x30,0x09,
+    0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x13,0x30,0x11,0x06,0x03,0x55,
+    0x04,0x08,0x13,0x0A,0x43,0x61,0x6C,0x69,0x66,0x6F,0x72,0x6E,0x69,0x61,0x31,0x12,
+    0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x13,0x09,0x43,0x75,0x70,0x65,0x72,0x74,0x69,
+    0x6E,0x6F,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0A,0x13,0x0A,0x41,0x70,0x70,
+    0x6C,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0B,
+    0x13,0x1C,0x49,0x6E,0x74,0x65,0x72,0x6E,0x65,0x74,0x20,0x53,0x65,0x72,0x76,0x69,
+    0x63,0x65,0x73,0x20,0x66,0x6F,0x72,0x20,0x41,0x6B,0x61,0x6D,0x61,0x69,0x31,0x18,
+    0x30,0x16,0x06,0x03,0x55,0x04,0x03,0x13,0x0F,0x73,0x74,0x6F,0x72,0x65,0x2E,0x61,
+    0x70,0x70,0x6C,0x65,0x2E,0x63,0x6F,0x6D,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,0xB5,0xDF,0x48,0x6A,0xE5,0x5F,0x0C,
+    0x42,0xBF,0x71,0x07,0xAB,0xE6,0x48,0xD2,0x2C,0x13,0x7C,0x29,0xF7,0x90,0xC2,0x45,
+    0x39,0x43,0x48,0x2E,0x16,0x22,0xBF,0xAB,0x48,0x20,0x14,0x27,0xE7,0x87,0x52,0x97,
+    0xF6,0x64,0x0C,0x62,0xF5,0x39,0x54,0x64,0x09,0xE8,0x29,0x56,0xF4,0x03,0x40,0x5A,
+    0xFA,0x9A,0x11,0x2B,0xD0,0x01,0x51,0x1E,0xED,0xB8,0x51,0xCB,0xAB,0x6B,0x7A,0x99,
+    0xF3,0xB0,0x6E,0xA8,0xC4,0xB6,0xAF,0x17,0xEC,0xA6,0xD2,0x4D,0xCA,0x63,0x3D,0x59,
+    0x30,0x25,0x30,0x54,0x86,0xDB,0x70,0x7A,0xEC,0x07,0x83,0x19,0xA0,0x44,0xE7,0x3C,
+    0x68,0xE0,0xFD,0xB9,0xEE,0x3F,0xAC,0xF3,0x32,0x5A,0x5F,0x42,0x31,0x94,0x70,0x2C,
+    0xC5,0x73,0xD2,0x79,0x23,0x5B,0x96,0x45,0xB1,0xB3,0x2A,0xA1,0x5A,0x69,0xFE,0xBE,
+    0x52,0x0D,0x5D,0x79,0x18,0xCA,0xF1,0x44,0x92,0xA0,0x27,0x1F,0xAA,0x6E,0x9D,0x6F,
+    0x1B,0x83,0x5B,0x73,0x28,0x1D,0x87,0xB5,0x70,0x0E,0x3D,0xED,0xE2,0xC2,0x34,0x8A,
+    0x81,0xB2,0x22,0x40,0x98,0x77,0x2F,0x34,0x1B,0x70,0xEC,0x96,0x3F,0x91,0xB9,0xFF,
+    0xC9,0xE5,0x7E,0xE7,0x25,0xEF,0xDB,0x9A,0x58,0x4E,0xB2,0x92,0x19,0xA5,0x8D,0xEB,
+    0x76,0xF8,0xA8,0x48,0x9F,0x3D,0x10,0x0C,0xE4,0x69,0x7B,0xE7,0xB7,0xCA,0xF6,0x14,
+    0x5E,0x93,0x1E,0x20,0x37,0x1B,0xB8,0xB1,0x2C,0xD1,0x46,0x5C,0xD7,0x85,0x4A,0x2E,
+    0x19,0xB2,0x3C,0x31,0xDC,0x3D,0xB5,0x3C,0xEC,0x49,0x8D,0x9C,0xCF,0x75,0x0B,0xFC,
+    0x03,0x31,0x37,0xE7,0xA5,0xD6,0x9D,0x19,0x0D,0x02,0x03,0x01,0x00,0x01,0xA3,0x82,
+    0x03,0x05,0x30,0x82,0x03,0x01,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,
+    0x16,0x80,0x14,0x3D,0xD3,0x50,0xA5,0xD6,0xA0,0xAD,0xEE,0xF3,0x4A,0x60,0x0A,0x65,
+    0xD3,0x21,0xD4,0xF8,0xF8,0xD6,0x0F,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,
+    0x04,0x14,0x6F,0x89,0xAE,0x9B,0xE4,0x8A,0x21,0x3A,0x3B,0xEA,0xAA,0xBE,0x99,0x90,
+    0xC8,0xDB,0x34,0x80,0x21,0xB9,0x30,0x2F,0x06,0x03,0x55,0x1D,0x11,0x04,0x28,0x30,
+    0x26,0x82,0x0F,0x73,0x74,0x6F,0x72,0x65,0x2E,0x61,0x70,0x70,0x6C,0x65,0x2E,0x63,
+    0x6F,0x6D,0x82,0x13,0x77,0x77,0x77,0x2E,0x73,0x74,0x6F,0x72,0x65,0x2E,0x61,0x70,
+    0x70,0x6C,0x65,0x2E,0x63,0x6F,0x6D,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,
+    0xFF,0x04,0x04,0x03,0x02,0x05,0xA0,0x30,0x1D,0x06,0x03,0x55,0x1D,0x25,0x04,0x16,
+    0x30,0x14,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x08,0x2B,0x06,
+    0x01,0x05,0x05,0x07,0x03,0x02,0x30,0x75,0x06,0x03,0x55,0x1D,0x1F,0x04,0x6E,0x30,
+    0x6C,0x30,0x34,0xA0,0x32,0xA0,0x30,0x86,0x2E,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,
+    0x63,0x72,0x6C,0x33,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,
+    0x6D,0x2F,0x73,0x68,0x61,0x32,0x2D,0x65,0x76,0x2D,0x73,0x65,0x72,0x76,0x65,0x72,
+    0x2D,0x67,0x32,0x2E,0x63,0x72,0x6C,0x30,0x34,0xA0,0x32,0xA0,0x30,0x86,0x2E,0x68,
+    0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x34,0x2E,0x64,0x69,0x67,0x69,0x63,
+    0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x73,0x68,0x61,0x32,0x2D,0x65,0x76,0x2D,
+    0x73,0x65,0x72,0x76,0x65,0x72,0x2D,0x67,0x32,0x2E,0x63,0x72,0x6C,0x30,0x4B,0x06,
+    0x03,0x55,0x1D,0x20,0x04,0x44,0x30,0x42,0x30,0x37,0x06,0x09,0x60,0x86,0x48,0x01,
+    0x86,0xFD,0x6C,0x02,0x01,0x30,0x2A,0x30,0x28,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,
+    0x07,0x02,0x01,0x16,0x1C,0x68,0x74,0x74,0x70,0x73,0x3A,0x2F,0x2F,0x77,0x77,0x77,
+    0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x43,0x50,
+    0x53,0x30,0x07,0x06,0x05,0x67,0x81,0x0C,0x01,0x01,0x30,0x81,0x88,0x06,0x08,0x2B,
+    0x06,0x01,0x05,0x05,0x07,0x01,0x01,0x04,0x7C,0x30,0x7A,0x30,0x24,0x06,0x08,0x2B,
+    0x06,0x01,0x05,0x05,0x07,0x30,0x01,0x86,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,
+    0x6F,0x63,0x73,0x70,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,
+    0x6D,0x30,0x52,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x30,0x02,0x86,0x46,0x68,
+    0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x61,0x63,0x65,0x72,0x74,0x73,0x2E,0x64,0x69,
+    0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x44,0x69,0x67,0x69,0x43,
+    0x65,0x72,0x74,0x53,0x48,0x41,0x32,0x45,0x78,0x74,0x65,0x6E,0x64,0x65,0x64,0x56,
+    0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x53,0x65,0x72,0x76,0x65,0x72,0x43,
+    0x41,0x2E,0x63,0x72,0x74,0x30,0x09,0x06,0x03,0x55,0x1D,0x13,0x04,0x02,0x30,0x00,
+    0x30,0x82,0x01,0x03,0x06,0x0A,0x2B,0x06,0x01,0x04,0x01,0xD6,0x79,0x02,0x04,0x02,
+    0x04,0x81,0xF4,0x04,0x81,0xF1,0x00,0xEF,0x00,0x75,0x00,0xA4,0xB9,0x09,0x90,0xB4,
+    0x18,0x58,0x14,0x87,0xBB,0x13,0xA2,0xCC,0x67,0x70,0x0A,0x3C,0x35,0x98,0x04,0xF9,
+    0x1B,0xDF,0xB8,0xE3,0x77,0xCD,0x0E,0xC8,0x0D,0xDC,0x10,0x00,0x00,0x01,0x62,0x4E,
+    0xBC,0xC7,0x43,0x00,0x00,0x04,0x03,0x00,0x46,0x30,0x44,0x02,0x20,0x3E,0xD3,0xFE,
+    0xB4,0x45,0x97,0xCE,0xA3,0x05,0xC9,0x29,0x70,0x55,0x3B,0x77,0x9E,0xCC,0x4B,0x06,
+    0xFD,0x76,0x21,0xD0,0x79,0x69,0xD6,0x60,0x01,0xBB,0xC7,0x43,0x5E,0x02,0x20,0x3D,
+    0xB7,0x73,0x91,0x51,0xB7,0xAC,0x40,0xFB,0xA7,0x36,0xCF,0x10,0xE8,0x63,0x79,0xE6,
+    0x06,0xEC,0xFA,0x60,0xFE,0x44,0x90,0x9A,0x53,0x26,0x04,0x27,0x1A,0x4B,0xD4,0x00,
+    0x76,0x00,0x56,0x14,0x06,0x9A,0x2F,0xD7,0xC2,0xEC,0xD3,0xF5,0xE1,0xBD,0x44,0xB2,
+    0x3E,0xC7,0x46,0x76,0xB9,0xBC,0x99,0x11,0x5C,0xC0,0xEF,0x94,0x98,0x55,0xD6,0x89,
+    0xD0,0xDD,0x00,0x00,0x01,0x62,0x4E,0xBC,0xC8,0x6A,0x00,0x00,0x04,0x03,0x00,0x47,
+    0x30,0x45,0x02,0x20,0x18,0xF4,0x40,0x99,0x83,0xA7,0x53,0x6D,0x11,0xBF,0x7C,0xA5,
+    0x64,0x21,0x52,0xD0,0x7C,0xF5,0x96,0xCD,0xB0,0x56,0xAE,0x1E,0x13,0x9C,0xFC,0x08,
+    0x3B,0x56,0x66,0x0F,0x02,0x21,0x00,0x98,0xBE,0xC7,0x01,0x8E,0x88,0xB7,0xB2,0xF9,
+    0xC7,0xE6,0x08,0x46,0x10,0xEA,0x8D,0x01,0x12,0x0D,0x7D,0xA6,0xBA,0xA6,0xD3,0x1F,
+    0xEC,0xA3,0xD6,0x7A,0xE4,0x85,0x89,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+    0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x6A,0x52,0xE8,0xB3,0x70,
+    0x25,0xD9,0x95,0x87,0xC4,0xF3,0x14,0xB1,0x8C,0x29,0x6F,0x11,0xA0,0x6D,0xD7,0xE9,
+    0x96,0xFA,0x06,0x57,0x4D,0x86,0xE8,0xD6,0x95,0xC0,0x3A,0x33,0xEC,0x67,0x6C,0x98,
+    0xA2,0x99,0x09,0x28,0x16,0x18,0xEC,0x21,0xED,0x93,0xEF,0xAB,0x69,0x5F,0x98,0x9C,
+    0x62,0x21,0x10,0xEB,0x1C,0xC4,0x20,0x5E,0x3E,0x6F,0x80,0x03,0xC9,0xC5,0xD5,0x2F,
+    0xDA,0x8C,0x86,0x40,0x59,0x71,0x34,0xEE,0xCF,0x54,0x83,0xE1,0x3E,0x11,0x1D,0x12,
+    0x66,0x3D,0x16,0x7F,0xD8,0x1B,0x42,0x35,0x50,0xA6,0xD6,0xE6,0x6B,0x32,0xDE,0xE2,
+    0xD9,0x01,0xD7,0xB1,0xF5,0xE8,0x72,0x44,0x03,0xB5,0xFA,0x19,0x0D,0xF2,0x8D,0x0B,
+    0x75,0xBA,0xD9,0x39,0xFF,0x73,0xF2,0xA0,0xD6,0x49,0xEF,0x9E,0xE9,0x23,0x5D,0xED,
+    0xC5,0x08,0x69,0x10,0xF7,0xF0,0x0B,0x8D,0x80,0xB6,0x43,0xE4,0x2A,0xEC,0x92,0xCF,
+    0x22,0xCA,0xAF,0x1A,0x8A,0x16,0xC7,0xC5,0x88,0xB7,0xCA,0xC6,0x19,0x1A,0xD1,0xFF,
+    0x13,0x93,0x56,0x29,0x1A,0x48,0xA8,0x92,0x21,0xE5,0x7F,0x3E,0x4C,0xB4,0x89,0xD6,
+    0x08,0xF0,0xB0,0x4B,0x22,0x7C,0x1F,0xEC,0xE7,0x09,0x5D,0x23,0xE2,0xD1,0xB8,0xA3,
+    0xDF,0xEC,0x2D,0x87,0x1F,0xCD,0x58,0x1C,0xDD,0x09,0xE8,0xB5,0xD4,0x30,0x1E,0x2A,
+    0x4D,0xA3,0xA1,0x84,0x43,0xD7,0x4A,0x7B,0x15,0x8E,0xEB,0xE2,0x53,0x2B,0x12,0xCB,
+    0xFB,0xF2,0x61,0x7E,0x92,0x0B,0x87,0x07,0x1C,0x8D,0x0A,0xED,0x62,0x10,0x27,0xE3,
+    0xDB,0xC4,0x65,0xDE,0x57,0xFB,0x6D,0x49,0xC8,0x7A,0xF8,
+};
+
+/* 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 _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,
+    0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,
+    0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
+    0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,
+    0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,
+    0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x44,0x69,0x67,0x69,0x43,0x65,
+    0x72,0x74,0x20,0x48,0x69,0x67,0x68,0x20,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63,
+    0x65,0x20,0x45,0x56,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,
+    0x31,0x33,0x31,0x30,0x32,0x32,0x31,0x32,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,
+    0x38,0x31,0x30,0x32,0x32,0x31,0x32,0x30,0x30,0x30,0x30,0x5A,0x30,0x75,0x31,0x0B,
+    0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,
+    0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x49,
+    0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,0x77,0x77,
+    0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x34,0x30,
+    0x32,0x06,0x03,0x55,0x04,0x03,0x13,0x2B,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
+    0x20,0x53,0x48,0x41,0x32,0x20,0x45,0x78,0x74,0x65,0x6E,0x64,0x65,0x64,0x20,0x56,
+    0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x65,0x72,
+    0x20,0x43,0x41,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,0xD7,0x53,0xA4,0x04,0x51,0xF8,0x99,0xA6,0x16,0x48,0x4B,0x67,
+    0x27,0xAA,0x93,0x49,0xD0,0x39,0xED,0x0C,0xB0,0xB0,0x00,0x87,0xF1,0x67,0x28,0x86,
+    0x85,0x8C,0x8E,0x63,0xDA,0xBC,0xB1,0x40,0x38,0xE2,0xD3,0xF5,0xEC,0xA5,0x05,0x18,
+    0xB8,0x3D,0x3E,0xC5,0x99,0x17,0x32,0xEC,0x18,0x8C,0xFA,0xF1,0x0C,0xA6,0x64,0x21,
+    0x85,0xCB,0x07,0x10,0x34,0xB0,0x52,0x88,0x2B,0x1F,0x68,0x9B,0xD2,0xB1,0x8F,0x12,
+    0xB0,0xB3,0xD2,0xE7,0x88,0x1F,0x1F,0xEF,0x38,0x77,0x54,0x53,0x5F,0x80,0x79,0x3F,
+    0x2E,0x1A,0xAA,0xA8,0x1E,0x4B,0x2B,0x0D,0xAB,0xB7,0x63,0xB9,0x35,0xB7,0x7D,0x14,
+    0xBC,0x59,0x4B,0xDF,0x51,0x4A,0xD2,0xA1,0xE2,0x0C,0xE2,0x90,0x82,0x87,0x6A,0xAE,
+    0xEA,0xD7,0x64,0xD6,0x98,0x55,0xE8,0xFD,0xAF,0x1A,0x50,0x6C,0x54,0xBC,0x11,0xF2,
+    0xFD,0x4A,0xF2,0x9D,0xBB,0x7F,0x0E,0xF4,0xD5,0xBE,0x8E,0x16,0x89,0x12,0x55,0xD8,
+    0xC0,0x71,0x34,0xEE,0xF6,0xDC,0x2D,0xEC,0xC4,0x87,0x25,0x86,0x8D,0xD8,0x21,0xE4,
+    0xB0,0x4D,0x0C,0x89,0xDC,0x39,0x26,0x17,0xDD,0xF6,0xD7,0x94,0x85,0xD8,0x04,0x21,
+    0x70,0x9D,0x6F,0x6F,0xFF,0x5C,0xBA,0x19,0xE1,0x45,0xCB,0x56,0x57,0x28,0x7E,0x1C,
+    0x0D,0x41,0x57,0xAA,0xB7,0xB8,0x27,0xBB,0xB1,0xE4,0xFA,0x2A,0xEF,0x21,0x23,0x75,
+    0x1A,0xAD,0x2D,0x9B,0x86,0x35,0x8C,0x9C,0x77,0xB5,0x73,0xAD,0xD8,0x94,0x2D,0xE4,
+    0xF3,0x0C,0x9D,0xEE,0xC1,0x4E,0x62,0x7E,0x17,0xC0,0x71,0x9E,0x2C,0xDE,0xF1,0xF9,
+    0x10,0x28,0x19,0x33,0x02,0x03,0x01,0x00,0x01,0xA3,0x82,0x01,0x49,0x30,0x82,0x01,
+    0x45,0x30,0x12,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x08,0x30,0x06,0x01,
+    0x01,0xFF,0x02,0x01,0x00,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,
+    0x04,0x03,0x02,0x01,0x86,0x30,0x1D,0x06,0x03,0x55,0x1D,0x25,0x04,0x16,0x30,0x14,
+    0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x08,0x2B,0x06,0x01,0x05,
+    0x05,0x07,0x03,0x02,0x30,0x34,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x01,0x01,
+    0x04,0x28,0x30,0x26,0x30,0x24,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x30,0x01,
+    0x86,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6F,0x63,0x73,0x70,0x2E,0x64,0x69,
+    0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x4B,0x06,0x03,0x55,0x1D,
+    0x1F,0x04,0x44,0x30,0x42,0x30,0x40,0xA0,0x3E,0xA0,0x3C,0x86,0x3A,0x68,0x74,0x74,
+    0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x34,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,
+    0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x48,0x69,
+    0x67,0x68,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63,0x65,0x45,0x56,0x52,0x6F,0x6F,
+    0x74,0x43,0x41,0x2E,0x63,0x72,0x6C,0x30,0x3D,0x06,0x03,0x55,0x1D,0x20,0x04,0x36,
+    0x30,0x34,0x30,0x32,0x06,0x04,0x55,0x1D,0x20,0x00,0x30,0x2A,0x30,0x28,0x06,0x08,
+    0x2B,0x06,0x01,0x05,0x05,0x07,0x02,0x01,0x16,0x1C,0x68,0x74,0x74,0x70,0x73,0x3A,
+    0x2F,0x2F,0x77,0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,
+    0x6F,0x6D,0x2F,0x43,0x50,0x53,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,
+    0x14,0x3D,0xD3,0x50,0xA5,0xD6,0xA0,0xAD,0xEE,0xF3,0x4A,0x60,0x0A,0x65,0xD3,0x21,
+    0xD4,0xF8,0xF8,0xD6,0x0F,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,
+    0x80,0x14,0xB1,0x3E,0xC3,0x69,0x03,0xF8,0xBF,0x47,0x01,0xD4,0x98,0x26,0x1A,0x08,
+    0x02,0xEF,0x63,0x64,0x2B,0xC3,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+    0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x9D,0xB6,0xD0,0x90,0x86,0xE1,
+    0x86,0x02,0xED,0xC5,0xA0,0xF0,0x34,0x1C,0x74,0xC1,0x8D,0x76,0xCC,0x86,0x0A,0xA8,
+    0xF0,0x4A,0x8A,0x42,0xD6,0x3F,0xC8,0xA9,0x4D,0xAD,0x7C,0x08,0xAD,0xE6,0xB6,0x50,
+    0xB8,0xA2,0x1A,0x4D,0x88,0x07,0xB1,0x29,0x21,0xDC,0xE7,0xDA,0xC6,0x3C,0x21,0xE0,
+    0xE3,0x11,0x49,0x70,0xAC,0x7A,0x1D,0x01,0xA4,0xCA,0x11,0x3A,0x57,0xAB,0x7D,0x57,
+    0x2A,0x40,0x74,0xFD,0xD3,0x1D,0x85,0x18,0x50,0xDF,0x57,0x47,0x75,0xA1,0x7D,0x55,
+    0x20,0x2E,0x47,0x37,0x50,0x72,0x8C,0x7F,0x82,0x1B,0xD2,0x62,0x8F,0x2D,0x03,0x5A,
+    0xDA,0xC3,0xC8,0xA1,0xCE,0x2C,0x52,0xA2,0x00,0x63,0xEB,0x73,0xBA,0x71,0xC8,0x49,
+    0x27,0x23,0x97,0x64,0x85,0x9E,0x38,0x0E,0xAD,0x63,0x68,0x3C,0xBA,0x52,0x81,0x58,
+    0x79,0xA3,0x2C,0x0C,0xDF,0xDE,0x6D,0xEB,0x31,0xF2,0xBA,0xA0,0x7C,0x6C,0xF1,0x2C,
+    0xD4,0xE1,0xBD,0x77,0x84,0x37,0x03,0xCE,0x32,0xB5,0xC8,0x9A,0x81,0x1A,0x4A,0x92,
+    0x4E,0x3B,0x46,0x9A,0x85,0xFE,0x83,0xA2,0xF9,0x9E,0x8C,0xA3,0xCC,0x0D,0x5E,0xB3,
+    0x3D,0xCF,0x04,0x78,0x8F,0x14,0x14,0x7B,0x32,0x9C,0xC7,0x00,0xA6,0x5C,0xC4,0xB5,
+    0xA1,0x55,0x8D,0x5A,0x56,0x68,0xA4,0x22,0x70,0xAA,0x3C,0x81,0x71,0xD9,0x9D,0xA8,
+    0x45,0x3B,0xF4,0xE5,0xF6,0xA2,0x51,0xDD,0xC7,0x7B,0x62,0xE8,0x6F,0x0C,0x74,0xEB,
+    0xB8,0xDA,0xF8,0xBF,0x87,0x0D,0x79,0x50,0x91,0x90,0x9B,0x18,0x3B,0x91,0x59,0x27,
+    0xF1,0x35,0x28,0x13,0xAB,0x26,0x7E,0xD5,0xF7,0x7A,
+};
+
+/* subject:/CN=self-signed.ssltest.apple.com/C=US */
+/* issuer :/CN=self-signed.ssltest.apple.com/C=US */
+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,
+};
+
+#endif /* _TRUSTTESTS_EXCEPTION_TESTS_H_ */
index 1bdff251052b289534c67404b830bc4ad07cccbb..e3b239b102434b6d82423753e62c20a9b0d082f7 100644 (file)
@@ -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 {
index 90d878450e678935f1e97b73b3c51b212803d793..53a48d99b6ef130d52ed17961bce7a95a546378a 100644 (file)
@@ -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_ */
index 14836b9f45caeda6b8d3620b6bfffee034d8e470..021a4a1619f7aaeaf0cea52ec6fd55b801b629e0 100644 (file)
@@ -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
index 7e8378c03c4c88a538b65f58d4bb1eadc5a2705d..99f8383fc341ba0ff0568a076097421a9d063901 100644 (file)
@@ -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;
 }
 
index c02a959cdb372b71181396d0ff9f877488d0a7f3..f4c276148e76848a2629f37187c9163d76f92912 100644 (file)
@@ -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;
index b62f91126fb1a6dcd087adf51cab628765b834e4..a7ee45d9d96d092eaf43f50e50828e9930411b3e 100644 (file)
     }
 }
 
+- (void)setNeedsEvaluation {
+    SecTrustSetNeedsEvaluation(_trust);
+}
+
 - (bool)evaluate:(out NSError * _Nullable __autoreleasing *)outError {
     CFErrorRef localError = nil;
     _trustResult = kSecTrustResultInvalid;
index d69762fe3be1ae37694e190dbadf44cfc9ac78e6..9a5280f3a5d453cccd28edef6f24da8caecce3f0 100644 (file)
@@ -31,6 +31,7 @@
 #include "OSX/sec/Security/SecItemShim.h"
 #import "server_security_helpers.h"
 #import "spi.h"
+#import "utilities/SecAKSWrappers.h"
 #import <utilities/SecCFWrappers.h>
 #import <utilities/SecFileLocations.h>
 #import "utilities/der_plist.h"
 
 - (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
     */
 
     [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
 }
 
index 781fb65ff663d1afed0d0ff8736dc2642fe5bd39..15019c8eb57a6fcfb852325735e42aabe29aca16 100644 (file)
@@ -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)
  */
index 6ddca031f069c808bb95eae0638667ecfc00702c..ebd9350f5aff080237b937af876bd63d049b5f10 100644 (file)
@@ -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; }
index 91486de48e8bb455fbb7acb517c00eca828907a7..8a14434001d146800b55fb5a14129aa438b395c0 100644 (file)
@@ -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,
index 0363c6c3194e4c9cae52bff9646d40a157b18c66..2fc2a03fa5f94f2e7e9e03a39b3ddb890b2fc0e3 100644 (file)
@@ -246,7 +246,9 @@ static void SecOCSPCacheWith(void(^cacheJob)(SecOCSPCacheRef cache)) {
             CFRelease(dbPath);
         }
     }
-    cacheJob(kSecOCSPCache);
+    if (kSecOCSPCache) {
+        cacheJob(kSecOCSPCache);
+    }
     os_unfair_lock_unlock(&cacheLock);
 }
 
index d17d3bbde27fbbfaf3c6c88c57bd700f1ebfb70a..2cec6688655e3948622fc3e6817490b89a3a3e52 100644 (file)
@@ -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);
index fbeebb822e1f58ba91bfd457d8bba48ff64ca6cf..44811a01d20cb67ce57f734a4d6a7f0be471a189 100644 (file)
@@ -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);
index 266bdba3214770c195355e510979db1f25d6b821..b0b5ac8699a183eb867508e481633198a3b172d1 100644 (file)
@@ -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,
index b3f7a090d77aff6088aab90d1b0ce13b9230f655..ef41918f1f7b99b41cf4a7b305e0dd35c5d1eb70 100644 (file)
@@ -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*/,
index 06f56077ab55d47803fb714313f1cd4c335409de..8e1eeee02aab8a799f72ae90eb35d531e69aaed3 100644 (file)
@@ -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 */
         }
     }