/*
* 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@
*/
//
// IDSProxy.m
// ids-xpc
//
#import <Foundation/NSArray.h>
#import <Foundation/Foundation.h>
#import <Security/SecBasePriv.h>
#import <Security/SecItemPriv.h>
#import <utilities/debugging.h>
#import <notify.h>
#include <Security/CKBridge/SOSCloudKeychainConstants.h>
#include <Security/SecureObjectSync/SOSARCDefines.h>
#include <Security/SecureObjectSync/SOSCloudCircle.h>
#include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
#import <IDS/IDS.h>
#import <os/activity.h>
#include <utilities/SecAKSWrappers.h>
#include <utilities/SecCFRelease.h>
#include <AssertMacros.h>
#import "IDSProxy.h"
#import "IDSKeychainSyncingProxy+IDSProxyReceiveMessage.h"
#import "IDSKeychainSyncingProxy+IDSProxySendMessage.h"
#import "IDSKeychainSyncingProxy+IDSProxyThrottle.h"
#import "IDSPersistentState.h"
#define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
#define kSecServerPeerInfoAvailable "com.apple.security.fpiAvailable"
#define IDSServiceNameKeychainSync "com.apple.private.alloy.keychainsync"
static NSString *kMonitorState = @"MonitorState";
static NSString *kExportUnhandledMessages = @"UnhandledMessages";
static const char *kStreamName = "com.apple.notifyd.matching";
NSString *const IDSSendMessageOptionForceEncryptionOffKey = @"IDSSendMessageOptionForceEncryptionOff";
static const int64_t kRetryTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for handling unhandled messages.
static const int64_t kMinMessageRetryDelay = (NSEC_PER_SEC * 8);
CFStringRef kSOSErrorDomain = CFSTR("com.apple.security.sos.error");
CFIndex kSOSErrorPeerNotFound = 1032;
CFIndex SECD_RUN_AS_ROOT_ERROR = 1041;
#define IDSPROXYSCOPE "IDSProxy"
@implementation IDSKeychainSyncingProxy
+ (IDSKeychainSyncingProxy *) idsProxy
{
static IDSKeychainSyncingProxy *idsProxy;
if (!idsProxy) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
idsProxy = [[self alloc] init];
});
}
return idsProxy;
}
-(NSDictionary*) exportState
{
return @{ kMonitorState:_monitor,
kExportUnhandledMessages:_unhandledMessageBuffer
};
}
- (void)persistState
{
if([_unhandledMessageBuffer count] > 0){
[IDSKeychainSyncingProxyPersistentState setUnhandledMessages:[self exportState]];
}
}
- (void) importIDSState: (NSMutableDictionary*) state
{
_unhandledMessageBuffer = state[kExportUnhandledMessages];
if(!_unhandledMessageBuffer)
_unhandledMessageBuffer = [NSMutableDictionary dictionary];
_monitor = state[kMonitorState];
if(_monitor == nil)
_monitor = [NSMutableDictionary dictionary];
}
- (id)init
{
if (self = [super init])
{
secnotice("event", "
_isIDSInitDone = false;
_service = nil;
_calloutQueue = dispatch_queue_create("IDSCallout", DISPATCH_QUEUE_SERIAL);
_unhandledMessageBuffer = [ [NSMutableDictionary alloc] initWithCapacity: 0];
_pingTimers = [ [NSMutableDictionary alloc] initWithCapacity: 0];
_isSecDRunningAsRoot = false;
_doesSecDHavePeer = true;
secdebug(IDSPROXYSCOPE, "
[self doIDSInitialization];
if(_isIDSInitDone)
[self doSetIDSDeviceID];
// Register for lock state changes
xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
^(xpc_object_t notification){
[self streamEvent:notification];
});
_retryTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_retryTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
dispatch_source_set_event_handler(_retryTimer, ^{
[self timerFired];
});
dispatch_resume(_retryTimer);
[self importIDSState: [IDSKeychainSyncingProxyPersistentState idsState]];
int notificationToken;
notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, dispatch_get_main_queue(),
^ (int token __unused)
{
secinfo("backoff", "keychain changed, wiping backoff monitor state");
_monitor = [NSMutableDictionary dictionary];
});
int peerInfo;
notify_register_dispatch(kSecServerPeerInfoAvailable, &peerInfo, dispatch_get_main_queue(),
^ (int token __unused)
{
secinfo("IDS Transport", "secd has a peer info");
if(_doesSecDHavePeer == false){
_doesSecDHavePeer = true;
[self doSetIDSDeviceID];
}
});
[self updateUnlockedSinceBoot];
[self updateIsLocked];
if (!_isLocked)
[self keybagDidUnlock];
}
return self;
}
- (void)streamEvent:(xpc_object_t)notification
{
#if (!TARGET_IPHONE_SIMULATOR)
const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
if (!notificationName) {
} else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
return [self keybagStateChange];
}
const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
char *desc = xpc_copy_description(notification);
secnotice("event", " if (desc)
free((void *)desc);
#endif
}
- (void) keybagDidLock
{
secnotice("IDS Transport", "}
- (void) keybagDidUnlock
{
secnotice("IDS Transport", " [self handleAllPendingMessage];
}
- (BOOL) updateUnlockedSinceBoot
{
CFErrorRef aksError = NULL;
if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
secerror(" CFReleaseSafe(aksError);
return NO;
}
return YES;
}
- (BOOL) updateIsLocked
{
CFErrorRef aksError = NULL;
if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
secerror(" CFReleaseSafe(aksError);
return NO;
}
secerror("updateIsLocked: if (!_isLocked)
_unlockedSinceBoot = YES;
return YES;
}
- (void) keybagStateChange
{
os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
secerror("keybagStateChange! was locked: BOOL wasLocked = _isLocked;
if ([self updateIsLocked]) {
if (wasLocked == _isLocked)
secdebug("IDS Transport", " else if (_isLocked)
[self keybagDidLock];
else
[self keybagDidUnlock];
}
});
}
- (void)timerFired
{
if(_unhandledMessageBuffer)
secnotice("IDS Transport", "
if(_isLocked)
_retryTimerScheduled = NO;
else if([_unhandledMessageBuffer count] == 0)
_retryTimerScheduled = NO;
else if (_retryTimerScheduled && !_isLocked)
[self handleAllPendingMessage];
else
[[IDSKeychainSyncingProxy idsProxy] scheduleRetryRequestTimer];
}
- (void)scheduleRetryRequestTimer
{
secnotice("IDS Transport", "scheduling unhandled messages timer");
dispatch_source_set_timer(_retryTimer, dispatch_time(DISPATCH_TIME_NOW, kMinMessageRetryDelay), DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
_retryTimerScheduled = YES;
}
- (void)doIDSInitialization
{
secnotice("IDS Transport", "doIDSInitialization!");
_service = [[IDSService alloc] initWithService: @IDSServiceNameKeychainSync];
if( _service == nil ){
_isIDSInitDone = false;
secerror("Could not create ids service");
}
else{
secnotice("IDS Transport", "IDS Transport Successfully set up IDS!");
[_service addDelegate:self queue: dispatch_get_main_queue()];
_isIDSInitDone = true;
if(_isSecDRunningAsRoot == false)
[self doSetIDSDeviceID];
}
}
- (void) doSetIDSDeviceID
{
NSInteger code = 0;
NSString *errorMessage = nil;
__block NSString* deviceID;
if(!_isIDSInitDone){
[self doIDSInitialization];
}
require_action_quiet(_isSecDRunningAsRoot == false, fail, errorMessage = @"cannot set IDS device ID, secd is running as root"; code = SECD_RUN_AS_ROOT_ERROR;);
require_action_quiet(_doesSecDHavePeer == true, fail, errorMessage = @"cannot set IDS deviceID, secd does not have a full peer info for account"; code = kSOSErrorPeerNotFound);
require_action_quiet(_isIDSInitDone, fail, errorMessage = @"IDSKeychainSyncingProxy can't set up the IDS service"; code = kSecIDSErrorNotRegistered);
require_action_quiet(!_isLocked, fail, errorMessage = @"IDSKeychainSyncingProxy can't set device ID, device is locked"; code = kSecIDSErrorDeviceIsLocked);
deviceID = IDSCopyLocalDeviceUniqueID();
secdebug("IDS Transport", "This is our IDS device ID:
require_action_quiet(deviceID != nil, fail, errorMessage = @"IDSKeychainSyncingProxy could not retrieve device ID from keychain"; code = kSecIDSErrorNoDeviceID);
if(_inCallout && _isSecDRunningAsRoot == false){
_shadowDoSetIDSDeviceID = YES;
}
else{
_setIDSDeviceID = YES;
[self calloutWith:^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
CFErrorRef localError = NULL;
bool handledSettingID = false;
handledSettingID = SOSCCSetDeviceID((__bridge CFStringRef) deviceID, &localError);
if(!handledSettingID && localError != NULL){
if(CFErrorGetCode(localError) == SECD_RUN_AS_ROOT_ERROR){
secerror("SETTING RUN AS ROOT ERROR: _isSecDRunningAsRoot = true;
}
else if (CFErrorGetCode(localError) == -536870174 && CFErrorGetDomain(localError) == kSecKernDomain) {
secnotice("IDS Transport", "system is locked, cannot set device ID, error: _isLocked = true;
}
else if (CFErrorGetCode(localError) == kSOSErrorPeerNotFound && CFStringCompare(CFErrorGetDomain(localError), kSOSErrorDomain, 0) == 0){
secnotice("IDS Transport","securityd does not have a peer yet , error: _doesSecDHavePeer = false;
}
}
CFReleaseNull(localError);
dispatch_async(queue, ^{
done(nil, NO, handledSettingID);
});
}];
}
fail:
if(errorMessage != nil){
secerror("Setting device ID error: }
}
- (void) calloutWith: (void(^)(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSettingDeviceID))) callout
{
// In IDSKeychainSyncingProxy serial queue
dispatch_queue_t idsproxy_queue = dispatch_get_main_queue();
// dispatch_get_global_queue - well-known global concurrent queue
// dispatch_get_main_queue - default queue that is bound to the main thread
xpc_transaction_begin();
dispatch_async(_calloutQueue, ^{
__block NSMutableDictionary *myPending;
__block bool myHandlePendingMessage;
__block bool myDoSetDeviceID;
__block bool wasLocked;
dispatch_sync(idsproxy_queue, ^{
myPending = [_unhandledMessageBuffer copy];
myHandlePendingMessage = _handleAllPendingMessages;
myDoSetDeviceID = _setIDSDeviceID;
wasLocked = _isLocked;
_inCallout = YES;
_shadowHandleAllPendingMessages = NO;
});
callout(myPending, myHandlePendingMessage, myDoSetDeviceID, idsproxy_queue, ^(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSetDeviceID) {
secdebug("event", "
// In IDSKeychainSyncingProxy's serial queue
_inCallout = NO;
// Update setting device id
_setIDSDeviceID = ((myDoSetDeviceID && !handledSetDeviceID));
_shadowDoSetIDSDeviceID = NO;
if(_setIDSDeviceID && !_isLocked && _isSecDRunningAsRoot == false && _doesSecDHavePeer)
[self doSetIDSDeviceID];
xpc_transaction_end();
});
});
}
- (void) sendKeysCallout: (NSMutableDictionary*(^)(NSMutableDictionary* pending, NSError** error)) handleMessages {
[self calloutWith: ^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
NSError* error = NULL;
NSMutableDictionary* handled = handleMessages(pending, &error);
dispatch_async(queue, ^{
if (!handled && error) {
secerror(" }
done(handled, NO, NO);
});
}];
}
NSString* createErrorString(NSString* format, ...)
{
va_list va;
va_start(va, format);
NSString* errorString = ([[NSString alloc] initWithFormat:format arguments:va]);
va_end(va);
return errorString;
}
@end