/*
* 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@
*/
#include <dispatch/dispatch.h>
#import <Foundation/Foundation.h>
#if OCTAGON
#import <CloudKit/CloudKit.h>
#endif
#include "keychain/securityd/SecItemServer.h"
#include <Security/SecItemPriv.h>
#import <Foundation/Foundation.h>
#import "keychain/ckks/CKKS.h"
#import "keychain/ckks/CKKSKeychainView.h"
#import "keychain/ckks/CKKSViewManager.h"
#import "keychain/ckks/CKKSKey.h"
#import "keychain/ot/OTManager.h"
#import "keychain/ot/OctagonStateMachineHelpers.h"
NSDictionary<CKKSZoneKeyState*, NSNumber*>* CKKSZoneKeyStateMap(void) {
static NSDictionary<CKKSZoneKeyState*, NSNumber*>* map = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
map = @{
SecCKKSZoneKeyStateReady: @0U,
SecCKKSZoneKeyStateError: @1U,
//SecCKKSZoneKeyStateCancelled: @2U,
SecCKKSZoneKeyStateInitializing: @3U,
SecCKKSZoneKeyStateInitialized: @4U,
SecCKKSZoneKeyStateFetchComplete: @5U,
SecCKKSZoneKeyStateWaitForTLK: @6U,
SecCKKSZoneKeyStateWaitForUnlock: @7U,
SecCKKSZoneKeyStateUnhealthy: @8U,
SecCKKSZoneKeyStateBadCurrentPointers: @9U,
SecCKKSZoneKeyStateNewTLKsFailed: @10U,
SecCKKSZoneKeyStateNeedFullRefetch: @11U,
SecCKKSZoneKeyStateHealTLKShares: @12U,
SecCKKSZoneKeyStateHealTLKSharesFailed:@13U,
SecCKKSZoneKeyStateWaitForFixupOperation:@14U,
SecCKKSZoneKeyStateReadyPendingUnlock: @15U,
SecCKKSZoneKeyStateFetch: @16U,
SecCKKSZoneKeyStateResettingZone: @17U,
SecCKKSZoneKeyStateResettingLocalData: @18U,
SecCKKSZoneKeyStateLoggedOut: @19U,
SecCKKSZoneKeyStateZoneCreationFailed: @20U,
SecCKKSZoneKeyStateWaitForTrust: @21U,
SecCKKSZoneKeyStateWaitForTLKUpload: @22U,
SecCKKSZoneKeyStateWaitForTLKCreation: @23U,
SecCKKSZoneKeyStateProcess: @24U,
SecCKKSZoneKeyStateBecomeReady: @25U,
SecCKKSZoneKeyStateLoseTrust: @26U,
SecCKKSZoneKeyStateTLKMissing: @27U,
SecCKKSZoneKeyStateWaitForCloudKitAccountStatus:@28U,
SecCKKSZoneKeyStateBeginFetch: @29U,
};
});
return map;
}
NSDictionary<NSNumber*, CKKSZoneKeyState*>* CKKSZoneKeyStateInverseMap(void) {
static NSDictionary<NSNumber*, CKKSZoneKeyState*>* backwardMap = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary<CKKSZoneKeyState*, NSNumber*>* forwardMap = CKKSZoneKeyStateMap();
backwardMap = [NSDictionary dictionaryWithObjects:[forwardMap allKeys] forKeys:[forwardMap allValues]];
});
return backwardMap;
}
NSNumber* CKKSZoneKeyToNumber(CKKSZoneKeyState* state) {
if(!state) {
return CKKSZoneKeyStateMap()[SecCKKSZoneKeyStateError];
}
NSNumber* result = CKKSZoneKeyStateMap()[state];
if(result) {
return result;
}
return CKKSZoneKeyStateMap()[SecCKKSZoneKeyStateError];
}
CKKSZoneKeyState* CKKSZoneKeyRecover(NSNumber* stateNumber) {
if(!stateNumber) {
return SecCKKSZoneKeyStateError;
}
CKKSZoneKeyState* result = CKKSZoneKeyStateInverseMap()[stateNumber];
if(result) {
return result;
}
return SecCKKSZoneKeyStateError;
}
NSSet<CKKSZoneKeyState*>* CKKSKeyStateNonTransientStates()
{
static NSSet<CKKSZoneKeyState*>* states = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
states = [NSSet setWithArray:@[
SecCKKSZoneKeyStateReady,
SecCKKSZoneKeyStateReadyPendingUnlock,
SecCKKSZoneKeyStateWaitForTrust,
SecCKKSZoneKeyStateWaitForTLK,
SecCKKSZoneKeyStateWaitForTLKCreation,
SecCKKSZoneKeyStateWaitForTLKUpload,
SecCKKSZoneKeyStateWaitForUnlock,
SecCKKSZoneKeyStateError,
SecCKKSZoneKeyStateLoggedOut,
#if OCTAGON
OctagonStateMachineHalted,
#endif
]];
});
return states;
}
#if OCTAGON
// If you want CKKS to run in your daemon/tests, you must call SecCKKSEnable before bringing up the keychain db
static bool enableCKKS = false;
static bool testCKKS = false;
bool SecCKKSIsEnabled(void) {
if([CKDatabase class] == nil) {
// CloudKit is not linked. We cannot bring CKKS up; disable it with prejudice.
ckkserror_global("ckks", "CloudKit.framework appears to not be linked. Cannot enable CKKS (on pain of crash).");
return false;
}
return enableCKKS;
}
bool SecCKKSEnable() {
enableCKKS = true;
return enableCKKS;
}
bool SecCKKSDisable() {
enableCKKS = false;
return enableCKKS;
}
bool SecCKKSResetSyncing(void) {
// The function name is a bit of a lie, but it does the thing.
[OTManager resetManager:true to:nil];
return SecCKKSIsEnabled();
}
bool SecCKKSTestsEnabled(void) {
return testCKKS;
}
bool SecCKKSTestsEnable(void) {
if([CKDatabase class] == nil) {
// CloudKit is not linked. We cannot bring CKKS up; disable it with prejudice.
ckkserror_global("ckks", "CloudKit.framework appears to not be linked. Cannot enable CKKS testing.");
testCKKS = false;
return false;
}
testCKKS = true;
return testCKKS;
}
bool SecCKKSTestsDisable(void) {
testCKKS = false;
return testCKKS;
}
// Feature flags to twiddle behavior
static bool CKKSSyncManifests = false;
bool SecCKKSSyncManifests(void) {
return CKKSSyncManifests;
}
bool SecCKKSEnableSyncManifests() {
CKKSSyncManifests = true;
return CKKSSyncManifests;
}
bool SecCKKSSetSyncManifests(bool value) {
CKKSSyncManifests = value;
return CKKSSyncManifests;
}
static bool CKKSEnforceManifests = false;
bool SecCKKSEnforceManifests(void) {
return CKKSEnforceManifests;
}
bool SecCKKSEnableEnforceManifests() {
CKKSEnforceManifests = true;
return CKKSEnforceManifests;
}
bool SecCKKSSetEnforceManifests(bool value) {
CKKSEnforceManifests = value;
return CKKSEnforceManifests;
}
// defaults write com.apple.security.ckks reduce-rate-limiting YES
static bool CKKSReduceRateLimiting = false;
bool SecCKKSReduceRateLimiting(void) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Use the default value as above, or apply the preferences value if it exists
NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SecCKKSUserDefaultsSuite];
NSString* key = @"reduce-rate-limiting";
[defaults registerDefaults: @{key: CKKSReduceRateLimiting ? @YES : @NO}];
CKKSReduceRateLimiting = !![defaults boolForKey:@"reduce-rate-limiting"];
ckksnotice_global("ratelimit", "reduce-rate-limiting is });
return CKKSReduceRateLimiting;
}
bool SecCKKSSetReduceRateLimiting(bool value) {
(void) SecCKKSReduceRateLimiting(); // Call this once to read the defaults write
CKKSReduceRateLimiting = value;
ckksnotice_global("ratelimit", "reduce-rate-limiting is now return CKKSReduceRateLimiting;
}
// Here's a mechanism for CKKS feature flags with default values from NSUserDefaults:
/*static bool CKKSShareTLKs = true;
bool SecCKKSShareTLKs(void) {
return true;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Use the default value as above, or apply the preferences value if it exists
NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SecCKKSUserDefaultsSuite];
[defaults registerDefaults: @{@"tlksharing": CKKSShareTLKs ? @YES : @NO}];
CKKSShareTLKs = !![defaults boolForKey:@"tlksharing"];
ckksnotice_global("ckksshare", "TLK sharing is });
return CKKSShareTLKs;
}*/
// Feature flags to twiddle behavior for tests
static bool CKKSDisableAutomaticUUID = false;
bool SecCKKSTestDisableAutomaticUUID(void) {
#if DEBUG
return CKKSDisableAutomaticUUID;
#else
return false;
#endif
}
void SecCKKSTestSetDisableAutomaticUUID(bool set) {
CKKSDisableAutomaticUUID = set;
}
static bool CKKSDisableSOS = false;
bool SecCKKSTestDisableSOS(void) {
#if DEBUG
return CKKSDisableSOS;
#else
return false;
#endif
}
void SecCKKSTestSetDisableSOS(bool set) {
CKKSDisableSOS = set;
}
static bool CKKSDisableKeyNotifications = false;
bool SecCKKSTestDisableKeyNotifications(void) {
#if DEBUG
return CKKSDisableKeyNotifications;
#else
return false;
#endif
}
void SecCKKSTestSetDisableKeyNotifications(bool set) {
CKKSDisableKeyNotifications = set;
}
static bool CKKSSkipScan = false;
bool SecCKKSTestSkipScan(void) {
return CKKSSkipScan;
}
bool SecCKKSSetTestSkipScan(bool value) {
CKKSSkipScan = value;
return CKKSSkipScan;
}
void SecCKKSTestResetFlags(void) {
SecCKKSTestSetDisableAutomaticUUID(false);
SecCKKSTestSetDisableSOS(false);
SecCKKSTestSetDisableKeyNotifications(false);
SecCKKSSetTestSkipScan(false);
}
#else /* NO OCTAGON */
bool SecCKKSIsEnabled(void) {
ckkserror_global("ckks", "CKKS was disabled at compile time.");
return false;
}
bool SecCKKSEnable() {
return false;
}
bool SecCKKSDisable() {
return false;
}
bool SecCKKSResetSyncing(void) {
return SecCKKSIsEnabled();
}
#endif /* OCTAGON */
void SecCKKSInitialize(SecDbRef db) {
#if OCTAGON
@autoreleasepool {
CKKSViewManager* manager = [CKKSViewManager manager];
[manager createViews];
[manager setupAnalytics];
SecDbAddNotifyPhaseBlock(db, ^(SecDbConnectionRef dbconn, SecDbTransactionPhase phase, SecDbTransactionSource source, CFArrayRef changes) {
SecCKKSNotifyBlock(dbconn, phase, source, changes);
});
[manager.completedSecCKKSInitialize fulfill];
if(!SecCKKSTestsEnabled()) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[OctagonAPSReceiver receiverForNamedDelegatePort:SecCKKSAPSNamedPort apsConnectionClass:[APSConnection class]] registerForEnvironment:APSEnvironmentProduction];
});
}
}
#endif
}
void SecCKKSNotifyBlock(SecDbConnectionRef dbconn, SecDbTransactionPhase phase, SecDbTransactionSource source, CFArrayRef changes) {
#if OCTAGON
if(phase == kSecDbTransactionDidRollback) {
return;
}
// Ignore our own changes, otherwise we'd infinite-loop.
if(source == kSecDbCKKSTransaction) {
ckksinfo_global("ckks", "Ignoring kSecDbCKKSTransaction notification");
return;
}
CFArrayForEach(changes, ^(CFTypeRef r) {
SecDbItemRef deleted = NULL;
SecDbItemRef added = NULL;
SecDbEventTranslateComponents(r, (CFTypeRef*) &deleted, (CFTypeRef*) &added);
if(!added && !deleted) {
ckkserror_global("ckks", "SecDbEvent gave us garbage: return;
}
[[CKKSViewManager manager] handleKeychainEventDbConnection: dbconn source:source added: added deleted: deleted];
});
#endif
}
void SecCKKS24hrNotification() {
#if OCTAGON
@autoreleasepool {
[[CKKSViewManager manager] xpc24HrNotification];
}
#endif
}
void CKKSRegisterSyncStatusCallback(CFStringRef cfuuid, SecBoolCFErrorCallback cfcallback) {
#if OCTAGON
// Keep plumbing, but transition to NS.
SecBoolNSErrorCallback nscallback = ^(bool result, NSError* err) {
cfcallback(result, (__bridge CFErrorRef) err);
};
[[CKKSViewManager manager] registerSyncStatusCallback: (__bridge NSString*) cfuuid callback:nscallback];
#endif
}
void SecCKKSPerformLocalResync() {
#if OCTAGON
if(SecCKKSIsEnabled()) {
ckksnotice_global("reset", "Local keychain was reset; performing local resync");
[[CKKSViewManager manager] rpcResyncLocal:nil reply:^(NSError *result) {
if(result) {
ckksnotice_global("reset", "Local keychain reset resync finished with an error: } else {
ckksnotice_global("reset", "Local keychain reset resync finished successfully");
}
}];
}
#endif
}