2016-02-23 21:19:11 +01:00
/ *
* 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 @
* /
2020-07-06 07:59:58 -04:00
# include "keychain/SecureObjectSync/SOSInternal.h"
# include "keychain/SecureObjectSync/SOSCircle.h"
2016-02-23 21:19:11 +01:00
# include < Security / SecureObjectSync / SOSCloudCircle . h >
2020-07-06 07:59:58 -04:00
# include "keychain/SecureObjectSync/SOSKVSKeys.h"
# include < Security / SecureObjectSync / SOSViews . h >
2016-02-23 21:19:11 +01:00
# include "utilities/SecCFError.h"
# include "utilities/SecCFRelease.h"
# include "utilities/SecCFWrappers.h"
# include "utilities/iOSforOSX.h"
# include < CoreFoundation / CoreFoundation . h >
# include < Security / SecKey . h >
# include < Security / SecKeyPriv . h >
# include < Security / SecItem . h >
2020-07-06 07:59:58 -04:00
# include "keychain/securityd/SecDbItem.h" // For SecError
2016-02-23 21:19:11 +01:00
# include "utilities/iOSforOSX.h"
# include < Security / SecBase64 . h >
# include < utilities / der_plist . h >
# include < utilities / der_plist _internal . h >
# include < corecrypto / ccder . h >
# include < utilities / der_date . h >
# include < corecrypto / ccrng . h >
2022-12-12 19:51:23 -08:00
# include < corecrypto / ccdigest . h >
# include < corecrypto / ccsha2 . h >
# include < corecrypto / ccpbkdf2 . h >
# if ! TARGET_OS _OSX
# import < SoftLinking / SoftLinking . h >
# import < ManagedConfiguration / ManagedConfiguration . h >
# import < ManagedConfiguration / MCProfileConnection_Misc . h >
# endif
2016-02-23 21:19:11 +01:00
2020-07-06 07:59:58 -04:00
# include < os / lock . h >
2016-02-23 21:19:11 +01:00
# include < AssertMacros . h >
2020-07-06 07:59:58 -04:00
const CFStringRef kSOSErrorDomain = CFSTR ( "com.apple.security.sos.error" ) ;
const CFStringRef kSOSDSIDKey = CFSTR ( "AccountDSID" ) ;
const CFStringRef SOSTransportMessageTypeIDSV2 = CFSTR ( "IDS2.0" ) ;
const CFStringRef SOSTransportMessageTypeKVS = CFSTR ( "KVS" ) ;
2022-12-12 19:51:23 -08:00
const CFStringRef kSOSCountKey = CFSTR ( "numberOfErrorsDeep" ) ;
2020-07-06 07:59:58 -04:00
bool SOSErrorCreate ( CFIndex errorCode , CFErrorRef * error , CFDictionaryRef formatOptions , CFStringRef format , . . . )
CF_FORMAT _FUNCTION ( 4 , 5 ) ;
2016-02-23 21:19:11 +01:00
bool SOSErrorCreate ( CFIndex errorCode , CFErrorRef * error , CFDictionaryRef formatOptions , CFStringRef format , . . . ) {
if ( ! errorCode ) return true ;
if ( error && ! * error ) {
va_list va ;
va_start ( va , format ) ;
SecCFCreateErrorWithFormatAndArguments ( errorCode , kSOSErrorDomain , NULL , error , formatOptions , format , va ) ;
va_end ( va ) ;
}
return false ;
}
bool SOSCreateError ( CFIndex errorCode , CFStringRef descriptionString , CFErrorRef previousError , CFErrorRef * newError ) {
SOSCreateErrorWithFormat ( errorCode , previousError , newError , NULL , CFSTR ( "%@" ) , descriptionString ) ;
2017-06-03 12:31:08 -07:00
return false ;
2016-02-23 21:19:11 +01:00
}
bool SOSCreateErrorWithFormat ( CFIndex errorCode , CFErrorRef previousError , CFErrorRef * newError ,
CFDictionaryRef formatOptions , CFStringRef format , . . . ) {
va_list va ;
va_start ( va , format ) ;
bool res = SOSCreateErrorWithFormatAndArguments ( errorCode , previousError , newError , formatOptions , format , va ) ;
va_end ( va ) ;
return res ;
}
bool SOSCreateErrorWithFormatAndArguments ( CFIndex errorCode , CFErrorRef previousError , CFErrorRef * newError ,
CFDictionaryRef formatOptions , CFStringRef format , va_list args ) {
2017-06-03 12:31:08 -07:00
return SecCFCreateErrorWithFormatAndArguments ( errorCode , kSOSErrorDomain , previousError , newError , formatOptions , format , args ) ;
2016-02-23 21:19:11 +01:00
}
//
// Utility Functions
//
static OSStatus GenerateECPairImp ( int keySize , CFBooleanRef permanent , SecKeyRef * public , SecKeyRef * full )
{
static const CFStringRef sTempNameToUse = CFSTR ( "GenerateECPair Temporary Key - Shouldn't be live" ) ;
CFNumberRef signing_bitsize = CFNumberCreate ( kCFAllocatorDefault , kCFNumberIntType , & keySize ) ;
CFDictionaryRef keygen_parameters = CFDictionaryCreateForCFTypes ( kCFAllocatorDefault ,
kSecAttrKeyType , kSecAttrKeyTypeEC ,
kSecAttrKeySizeInBits , signing_bitsize ,
kSecAttrIsPermanent , permanent ,
kSecAttrLabel , sTempNameToUse ,
NULL ) ;
CFReleaseNull ( signing_bitsize ) ;
OSStatus result = SecKeyGeneratePair ( keygen_parameters , public , full ) ;
CFReleaseNull ( keygen_parameters ) ;
return result ;
}
OSStatus GenerateECPair ( int keySize , SecKeyRef * public , SecKeyRef * full )
{
return GenerateECPairImp ( keySize , kCFBooleanFalse , public , full ) ;
}
OSStatus GeneratePermanentECPair ( int keySize , SecKeyRef * public , SecKeyRef * full )
{
return GenerateECPairImp ( keySize , kCFBooleanTrue , public , full ) ;
}
static CFStringRef SOSCircleCopyDescriptionFromData ( CFDataRef data )
{
CFErrorRef error = NULL ;
CFStringRef result = NULL ;
SOSCircleRef circle = SOSCircleCreateFromData ( kCFAllocatorDefault , data , & error ) ;
if ( circle )
result = CFStringCreateWithFormat ( kCFAllocatorDefault , NULL , CFSTR ( "%@" ) , circle ) ;
CFReleaseSafe ( circle ) ;
return result ;
}
CFStringRef SOSItemsChangedCopyDescription ( CFDictionaryRef changes , bool is_sender )
{
CFMutableStringRef string = CFStringCreateMutableCopy ( kCFAllocatorDefault , 0 , CFSTR ( "<Changes: {\n" ) ) ;
CFDictionaryForEach ( changes , ^ ( const void * key , const void * value ) {
CFStringRef value_description = NULL ;
if ( isString ( key ) && isData ( value ) ) {
CFDataRef value_data = ( CFDataRef ) value ;
switch ( SOSKVSKeyGetKeyType ( key ) ) {
case kCircleKey :
value_description = SOSCircleCopyDescriptionFromData ( value_data ) ;
break ;
case kMessageKey :
value_description = CFCopyDescription ( value_data ) ;
break ;
default :
break ;
}
}
CFStringAppendFormat ( string , NULL , CFSTR ( " '%@' %s %@\n" ) ,
key ,
is_sender ? "<=" : "=>" ,
value_description ? value_description : value ) ;
CFReleaseNull ( value_description ) ;
} ) ;
CFStringAppendFormat ( string , NULL , CFSTR ( "}" ) ) ;
return string ;
}
2020-07-06 07:59:58 -04:00
CFStringRef SOSCopyHashBufAsString ( uint8_t * digest , size_t len ) {
char encoded [ 2 * len + 1 ] ; // Big enough for base64 encoding .
2016-02-23 21:19:11 +01:00
2020-07-06 07:59:58 -04:00
size_t length = SecBase64Encode ( digest , len , encoded , sizeof ( encoded ) ) ;
2016-02-23 21:19:11 +01:00
assert ( length && length < sizeof ( encoded ) ) ;
2020-07-06 07:59:58 -04:00
if ( length > kSOSPeerIDLengthMax )
length = kSOSPeerIDLengthMax ;
2016-02-23 21:19:11 +01:00
encoded [ length ] = 0 ;
return CFStringCreateWithCString ( kCFAllocatorDefault , encoded , kCFStringEncodingASCII ) ;
2017-06-03 12:31:08 -07:00
}
2016-02-23 21:19:11 +01:00
2020-07-06 07:59:58 -04:00
CFStringRef SOSCopyIDOfDataBuffer ( CFDataRef data , CFErrorRef * error ) {
const struct ccdigest_info * di = ccsha1_di ( ) ;
uint8_t digest [ di -> output_size ] ;
ccdigest ( di , CFDataGetLength ( data ) , CFDataGetBytePtr ( data ) , digest ) ;
return SOSCopyHashBufAsString ( digest , sizeof ( digest ) ) ;
}
2017-06-03 12:31:08 -07:00
CFStringRef SOSCopyIDOfDataBufferWithLength ( CFDataRef data , CFIndex len , CFErrorRef * error ) {
CFStringRef retval = NULL ;
CFStringRef tmp = SOSCopyIDOfDataBuffer ( data , error ) ;
if ( tmp ) retval = CFStringCreateWithSubstring ( kCFAllocatorDefault , tmp , CFRangeMake ( 0 , len ) ) ;
CFReleaseNull ( tmp ) ;
return retval ;
}
CFStringRef SOSCopyIDOfKey ( SecKeyRef key , CFErrorRef * error ) {
CFDataRef publicBytes = NULL ;
CFStringRef result = NULL ;
2020-07-06 07:59:58 -04:00
require_action _quiet ( key , errOut , SOSErrorCreate ( kSOSErrorNoKey , error , NULL , CFSTR ( "NULL key passed to SOSCopyIDOfKey" ) ) ) ;
require_quiet ( SecError ( SecKeyCopyPublicBytes ( key , & publicBytes ) , error , CFSTR ( "Failed to export public bytes %@" ) , key ) , errOut ) ;
2017-06-03 12:31:08 -07:00
result = SOSCopyIDOfDataBuffer ( publicBytes , error ) ;
2020-07-06 07:59:58 -04:00
errOut :
2016-02-23 21:19:11 +01:00
CFReleaseNull ( publicBytes ) ;
return result ;
}
2017-06-03 12:31:08 -07:00
CFStringRef SOSCopyIDOfKeyWithLength ( SecKeyRef key , CFIndex len , CFErrorRef * error ) {
CFStringRef retval = NULL ;
CFStringRef tmp = SOSCopyIDOfKey ( key , error ) ;
if ( tmp ) retval = CFStringCreateWithSubstring ( kCFAllocatorDefault , tmp , CFRangeMake ( 0 , len ) ) ;
CFReleaseNull ( tmp ) ;
return retval ;
}
2016-02-23 21:19:11 +01:00
CFGiblisGetSingleton ( ccec_const _cp _t , SOSGetBackupKeyCurveParameters , sBackupKeyCurveParameters , ^ {
* sBackupKeyCurveParameters = ccec_cp _256 ( ) ;
} ) ;
//
// We ' re expecting full entropy here , so we just need to stretch
// via the PBKDF entropy rng . We ' ll choose a few iterations and no salt
// since we don ' t get sent any .
//
const int kBackupKeyIterations = 20 ;
const uint8_t sBackupKeySalt [ ] = { 0 } ;
bool SOSPerformWithDeviceBackupFullKey ( ccec_const _cp _t cp , CFDataRef entropy , CFErrorRef * error , void ( ^ operation ) ( ccec_full _ctx _t fullKey ) )
{
bool result = false ;
ccec_full _ctx _decl _cp ( cp , fullKey ) ;
require_quiet ( SOSGenerateDeviceBackupFullKey ( fullKey , cp , entropy , error ) , exit ) ;
operation ( fullKey ) ;
result = true ;
exit :
ccec_full _ctx _clear _cp ( cp , fullKey ) ;
return result ;
}
bool SOSGenerateDeviceBackupFullKey ( ccec_full _ctx _t generatedKey , ccec_const _cp _t cp , CFDataRef entropy , CFErrorRef * error )
{
bool result = false ;
int cc_result = 0 ;
2022-12-12 19:51:23 -08:00
# define drbg_output _size 1024
uint8_t drbg_output [ drbg_output _size ] ;
cc_result = ccpbkdf2_hmac ( ccsha256_di ( ) , CFDataGetLength ( entropy ) , CFDataGetBytePtr ( entropy ) ,
sizeof ( sBackupKeySalt ) , sBackupKeySalt ,
kBackupKeyIterations ,
drbg_output _size , drbg_output ) ;
require_action _quiet ( cc_result = = 0 , exit ,
SOSErrorCreate ( kSOSErrorProcessingFailure , error , NULL , CFSTR ( "ccpbkdf2_hmac failed: %d" ) , cc_result ) ) ;
cc_result = ccec_generate _key _deterministic ( cp ,
drbg_output _size ,
drbg_output ,
ccrng ( NULL ) ,
CCEC_GENKEY _DETERMINISTIC _SECBKP ,
generatedKey ) ;
require_action _quiet ( cc_result = = 0 , exit ,
SOSErrorCreate ( kSOSErrorProcessingFailure , error , NULL , CFSTR ( "ccec_generate_key_deterministic failed: %d" ) , cc_result ) ) ;
2016-02-23 21:19:11 +01:00
result = true ;
exit :
return result ;
}
CFDataRef SOSCopyDeviceBackupPublicKey ( CFDataRef entropy , CFErrorRef * error )
{
CFDataRef result = NULL ;
CFMutableDataRef publicKeyData = NULL ;
ccec_full _ctx _decl _cp ( SOSGetBackupKeyCurveParameters ( ) , fullKey ) ;
require_quiet ( SOSGenerateDeviceBackupFullKey ( fullKey , SOSGetBackupKeyCurveParameters ( ) , entropy , error ) , exit ) ;
2020-07-06 07:59:58 -04:00
size_t space = ccec_compact _export _size ( false , ccec_ctx _pub ( fullKey ) ) ;
2016-02-23 21:19:11 +01:00
publicKeyData = CFDataCreateMutableWithScratch ( kCFAllocatorDefault , space ) ;
require_quiet ( SecAllocationError ( publicKeyData , error , CFSTR ( "Mutable data allocation" ) ) , exit ) ;
ccec_compact _export ( false , CFDataGetMutableBytePtr ( publicKeyData ) , fullKey ) ;
CFTransferRetained ( result , publicKeyData ) ;
exit :
CFReleaseNull ( publicKeyData ) ;
return result ;
}
CFDataRef SOSDateCreate ( void ) {
CFDateRef now = CFDateCreate ( NULL , CFAbsoluteTimeGetCurrent ( ) ) ;
size_t bufsiz = der_sizeof _date ( now , NULL ) ;
uint8_t buf [ bufsiz ] ;
der_encode _date ( now , NULL , buf , buf + bufsiz ) ;
CFReleaseNull ( now ) ;
return CFDataCreate ( NULL , buf , bufsiz ) ;
}
2017-06-03 12:31:08 -07:00
CFDataRef CFDataCreateWithDER ( CFAllocatorRef allocator , CFIndex size , uint8_t * ( ^ operation ) ( size_t size , uint8_t * buffer ) ) {
__block CFMutableDataRef result = NULL ;
if ( ! size ) return NULL ;
if ( ( result = CFDataCreateMutableWithScratch ( allocator , size ) ) = = NULL ) return NULL ;
uint8_t * ptr = CFDataGetMutableBytePtr ( result ) ;
uint8_t * derptr = operation ( size , ptr ) ;
if ( derptr = = ptr ) return result ; // most probable case
if ( ! derptr || derptr < ptr ) { // DER op failed - or derptr ended up prior to allocated buffer
CFReleaseNull ( result ) ;
} else if ( derptr > ptr ) { // This is a possible case where we don ' t end up using the entire allocated buffer
size_t diff = derptr - ptr ; // The unused space ends up being the beginning of the allocation
CFDataDeleteBytes ( result , CFRangeMake ( 0 , diff ) ) ;
}
return result ;
}
2020-07-06 07:59:58 -04:00
@ implementation SOSCachedNotification
+ ( NSString * ) notificationName : ( const char * ) notificationString {
# if TARGET_OS _OSX
return [ NSString stringWithFormat : @ "user.uid.%d.%s" , getuid ( ) , notificationString ] ;
# else
return @ ( notificationString ) ;
# endif
}
@ end
bool SOSCachedNotificationOperation ( const char * notificationString , bool ( ^ operation ) ( int token , bool gtg ) ) {
static os_unfair _lock token_lock = OS_UNFAIR _LOCK _INIT ;
static NSMutableDictionary * tokenCache = NULL ;
int token = NOTIFY_TOKEN _INVALID ;
@ autoreleasepool {
os_unfair _lock _lock ( & token_lock ) ;
if ( tokenCache = = NULL ) {
tokenCache = [ NSMutableDictionary dictionary ] ;
}
NSString * notification = [ SOSCachedNotification notificationName : notificationString ] ;
if ( notification = = NULL ) {
os_unfair _lock _unlock ( & token_lock ) ;
return false ;
}
NSNumber * cachedToken = tokenCache [ notification ] ;
if ( cachedToken = = NULL ) {
uint32_t status ;
status = notify_register _check ( [ notification UTF8String ] , & token ) ;
if ( status = = NOTIFY_STATUS _OK ) {
tokenCache [ notification ] = @ ( token ) ;
} else {
secnotice ( "cachedStatus" , "Failed to retreive token for %@: error %d" ,
notification , status ) ;
}
} else {
token = [ cachedToken intValue ] ;
}
os_unfair _lock _unlock ( & token_lock ) ;
}
return operation ( token , ( token ! = NOTIFY_TOKEN _INVALID ) ) ;
}
uint64_t SOSGetCachedCircleBitmask ( void ) {
__block uint64_t retval = 0 ; // If the following call fails and we return 0 the caller , checking CC_STATISVALID will see we didn ' t get anything .
SOSCachedNotificationOperation ( kSOSCCCircleChangedNotification , ^ bool ( int token , bool gtg ) {
if ( gtg ) {
notify_get _state ( token , & retval ) ;
}
return false ;
} ) ;
return retval ;
}
const SOSCCStatus kSOSNoCachedValue = -99 ;
SOSCCStatus SOSGetCachedCircleStatus ( CFErrorRef * error ) {
uint64_t statusMask = SOSGetCachedCircleBitmask ( ) ;
SOSCCStatus retval = kSOSNoCachedValue ;
if ( statusMask & CC_STATISVALID ) {
if ( statusMask & CC_UKEY _TRUSTED ) {
retval = ( SOSCCStatus ) statusMask & CC_MASK ;
} else {
retval = kSOSCCError ;
if ( error ) {
CFReleaseNull ( * error ) ;
if ( statusMask & CC_PEER _IS _IN ) {
SOSCreateError ( kSOSErrorPublicKeyAbsent , CFSTR ( "Public Key isn't available, this peer is in the circle, but invalid. The iCloud Password must be provided to keychain syncing subsystem to repair this." ) , NULL , error ) ;
} else {
SOSCreateError ( kSOSErrorPublicKeyAbsent , CFSTR ( "Public Key isn't available. The iCloud Password must be provided to keychain syncing subsystem to repair this." ) , NULL , error ) ;
}
}
}
}
return retval ;
}
uint64_t SOSCachedViewBitmask ( void ) {
__block uint64_t retval = 0 ;
if ( SOSGetCachedCircleStatus ( NULL ) = = kSOSCCInCircle ) {
SOSCachedNotificationOperation ( kSOSCCViewMembershipChangedNotification , ^ bool ( int token , bool gtg ) {
if ( gtg ) {
notify_get _state ( token , & retval ) ;
return true ;
}
return false ;
} ) ;
}
return retval ;
}
CFSetRef SOSCreateCachedViewStatus ( void ) {
__block CFSetRef retval = NULL ;
uint64_t state = SOSCachedViewBitmask ( ) ;
if ( state ) {
retval = SOSViewCreateSetFromBitmask ( state ) ;
}
return retval ;
}
NSDate * SOSCreateRandomDateBetweenNowPlus ( NSTimeInterval starting , NSTimeInterval ending ) {
uint64_t randomTime ;
uint64_t span = ending - starting ;
NSTimeInterval resultInterval = ( span / 2 ) + starting ; // fallback in case call below fails .
if ( SecRandomCopyBytes ( NULL , sizeof ( randomTime ) , & randomTime ) = = 0 ) {
resultInterval = ( randomTime % span ) + starting ;
}
return [ [ NSDate alloc ] initWithTimeIntervalSinceNow : resultInterval ] ;
}
2022-12-12 19:51:23 -08:00
dispatch_queue _t SOSCCCredentialQueue ( void ) {
static dispatch_queue _t credQueue = NULL ;
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^ {
credQueue = dispatch_queue _create ( "com.apple.SOSCredentialsQueue" , DISPATCH_QUEUE _SERIAL ) ;
} ) ;
return credQueue ;
}
# if TARGET_OS _OSX
# define KEYCHAINSYNCDISABLE "DisableKeychainCloudSync"
# define ICLOUDMANAGEDENVIRONMENT "com.apple.icloud.managed"
# else
SOFT_LINK _FRAMEWORK ( PrivateFrameworks , ManagedConfiguration )
SOFT_LINK _CLASS ( ManagedConfiguration , MCProfileConnection )
# endif
bool SOSVisibleKeychainNotAllowed ( void ) {
bool notAllowed = false ;
# if TARGET_OS _OSX
notAllowed = CFPreferencesGetAppBooleanValue ( CFSTR ( KEYCHAINSYNCDISABLE ) , CFSTR ( ICLOUDMANAGEDENVIRONMENT ) , NULL ) ;
# else
Class mpc = getMCProfileConnectionClass ( ) ;
MCProfileConnection * sharedConnection = [ mpc sharedConnection ] ;
notAllowed = ! [ sharedConnection isCloudKeychainSyncAllowed ] ;
# endif
if ( notAllowed ) {
secnotice ( "views" , "V0 views disabled by Managed Preferences Profile" ) ;
}
return notAllowed ;
}