/* * 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@ */ #include "SecCertificateTrace.h" #include #include #include "SecCFWrappers.h" #include #include #include "debugging.h" #define SEC_CONST_DECL(k,v) CFTypeRef k = (CFTypeRef)(CFSTR(v)); SEC_CONST_DECL (kCertStatsPrefix, "com.apple.certstats"); SEC_CONST_DECL (kCertStatsCert, "cert"); SEC_CONST_DECL (kCertStatsPolicy, "id"); SEC_CONST_DECL (kCertStatsNotBefore, "nb"); SEC_CONST_DECL (kCertStatsNotAfter, "na"); SEC_CONST_DECL (kCertStatsSerialNumber, "sn"); SEC_CONST_DECL (kCertStatsSubjectSummary, "s"); SEC_CONST_DECL (kCertStatsIssuerSummary, "i"); static const CFStringRef kCertStatsFormat = CFSTR("%@/%@=%@/%@=%@/%@=%@/%@=%@/%@=%@/%@=%@"); static const CFStringRef kCertStatsDelimiters = CFSTR("/"); bool SetCertificateTraceValueForKey(CFStringRef key, int64_t value); void* BeginCertificateLoggingTransaction(void); bool AddKeyValuePairToCertificateLoggingTransaction(void* token, CFStringRef key, int64_t value); void CloseCertificateLoggingTransaction(void* token); CFStringRef SecCFStringCopyEncodingDelimiters(CFStringRef str, CFStringRef delimiters); #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR)) #include static const char* gTopLevelKeyForCertificateTracing = "com.apple.certstats"; static const char* gMessageTracerSetPrefix = "com.apple.message."; static const char* gMessageTracerDomainField = "com.apple.message.domain"; /* -------------------------------------------------------------------------- Function: OSX_BeginCertificateLoggingTransaction Description: For OSX, the message tracer back end wants its logging done in "bunches". This function allows for beginning a 'transaction' of logging which will allow for putting all of the transaction's items into a single log making the message tracer folks happy. The work of this function is to create the aslmsg context and set the domain field and then return the aslmsg context as a void* result. -------------------------------------------------------------------------- */ static void* OSX_BeginCertificateLoggingTransaction() { void* result = NULL; aslmsg mAsl = NULL; mAsl = asl_new(ASL_TYPE_MSG); if (NULL == mAsl) { return result; } asl_set(mAsl, gMessageTracerDomainField, gTopLevelKeyForCertificateTracing); result = (void *)mAsl; return result; } /* -------------------------------------------------------------------------- Function: OSX_AddKeyValuePairToCertificateLoggingTransaction Description: Once a call to OSX_BeginCertificateLoggingTransaction is done, this call will allow for adding items to the "bunch" of items being logged. NOTE: The key should be a simple key such as "numberOfPeers". This is because this function will append the required prefix of "com.apple.message." -------------------------------------------------------------------------- */ static bool OSX_AddKeyValuePairToCertificateLoggingTransaction(void* token, CFStringRef key, int64_t value) { if (NULL == token || NULL == key) { return false; } aslmsg mAsl = (aslmsg)token; // Fix up the key CFStringRef real_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s%@"), gMessageTracerSetPrefix, key); if (NULL == real_key) { return false; } CFIndex key_length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(real_key), kCFStringEncodingUTF8); key_length += 1; // For null char key_buffer[key_length]; memset(key_buffer, 0,key_length); if (!CFStringGetCString(real_key, key_buffer, key_length, kCFStringEncodingUTF8)) { CFRelease(real_key); return false; } CFRelease(real_key); CFStringRef value_str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%lld"), value); if (NULL == value_str) { return false; } CFIndex value_str_numBytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value_str), kCFStringEncodingUTF8); value_str_numBytes += 1; // For null char value_buffer[value_str_numBytes]; memset(value_buffer, 0, value_str_numBytes); if (!CFStringGetCString(value_str, value_buffer, value_str_numBytes, kCFStringEncodingUTF8)) { CFRelease(value_str); return false; } CFRelease(value_str); asl_set(mAsl, key_buffer, value_buffer); return true; } /* -------------------------------------------------------------------------- Function: OSX_CloseCertificateLoggingTransaction Description: Once a call to OSX_BeginCertificateLoggingTransaction is done, and all of the items that are to be in the "bunch" of items being logged, this function will do the real logging and free the aslmsg context. -------------------------------------------------------------------------- */ static void OSX_CloseCertificateLoggingTransaction(void* token) { if (NULL != token) { aslmsg mAsl = (aslmsg)token; asl_log(NULL, mAsl, ASL_LEVEL_NOTICE, ""); asl_free(mAsl); } } /* -------------------------------------------------------------------------- Function: OSX_SetCertificateTraceValueForKey Description: If "bunching" of items either cannot be done or is not desired, then this 'single shot' function should be used. It will create the aslmsg context, register the domain, fix up the key and log the key value pair, and then do the real logging and free the aslmsg context. -------------------------------------------------------------------------- */ static bool OSX_SetCertificateTraceValueForKey(CFStringRef key, int64_t value) { bool result = false; if (NULL == key) { return result; } aslmsg mAsl = NULL; mAsl = asl_new(ASL_TYPE_MSG); if (NULL == mAsl) { return result; } // Fix up the key CFStringRef real_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s%@"), gMessageTracerSetPrefix, key); if (NULL == real_key) { return false; } CFIndex key_length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(real_key), kCFStringEncodingUTF8); key_length += 1; // For null char key_buffer[key_length]; memset(key_buffer, 0,key_length); if (!CFStringGetCString(real_key, key_buffer, key_length, kCFStringEncodingUTF8)) { CFRelease(real_key); return false; } CFRelease(real_key); CFStringRef value_str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%lld"), value); if (NULL == value_str) { asl_free(mAsl); return result; } CFIndex value_str_numBytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value_str), kCFStringEncodingUTF8); value_str_numBytes += 1; // For null char value_buffer[value_str_numBytes]; memset(value_buffer, 0, value_str_numBytes); if (!CFStringGetCString(value_str, value_buffer, value_str_numBytes, kCFStringEncodingUTF8)) { asl_free(mAsl); CFRelease(value_str); return result; } CFRelease(value_str); /* Domain */ asl_set(mAsl, gMessageTracerDomainField, gTopLevelKeyForCertificateTracing); /* Our custom key (starts with com.apple.message.) and its value */ asl_set(mAsl, key_buffer, value_buffer); /* Log this ASL record (note actual log message is empty) */ asl_log(NULL, mAsl, ASL_LEVEL_NOTICE, ""); asl_free(mAsl); return true; } #endif #if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) static const char* gTopLevelKeyForCertificateTracing = "com.apple.certstats"; typedef void (*type_ADClientClearScalarKey)(CFStringRef key); typedef void (*type_ADClientAddValueForScalarKey)(CFStringRef key, int64_t value); static type_ADClientClearScalarKey gADClientClearScalarKey = NULL; static type_ADClientAddValueForScalarKey gADClientAddValueForScalarKey = NULL; static dispatch_once_t gADFunctionPointersSet = 0; static CFBundleRef gAggdBundleRef = NULL; static bool gFunctionPointersAreLoaded = false; /* -------------------------------------------------------------------------- Function: InitializeADFunctionPointers Description: Linking to the Aggregate library causes a build cycle, so this function dynamically loads the needed function pointers. -------------------------------------------------------------------------- */ static bool InitializeADFunctionPointers() { if (gFunctionPointersAreLoaded) { return gFunctionPointersAreLoaded; } dispatch_once(&gADFunctionPointersSet, ^{ CFStringRef path_to_aggd_framework = CFSTR("/System/Library/PrivateFrameworks/AggregateDictionary.framework"); CFURLRef aggd_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_to_aggd_framework, kCFURLPOSIXPathStyle, true); if (NULL != aggd_url) { gAggdBundleRef = CFBundleCreate(kCFAllocatorDefault, aggd_url); if (NULL != gAggdBundleRef) { gADClientClearScalarKey = (type_ADClientClearScalarKey) CFBundleGetFunctionPointerForName(gAggdBundleRef, CFSTR("ADClientClearScalarKey")); gADClientAddValueForScalarKey = (type_ADClientAddValueForScalarKey) CFBundleGetFunctionPointerForName(gAggdBundleRef, CFSTR("ADClientAddValueForScalarKey")); } CFRelease(aggd_url); } }); gFunctionPointersAreLoaded = ((NULL != gADClientClearScalarKey) && (NULL != gADClientAddValueForScalarKey)); return gFunctionPointersAreLoaded; } /* -------------------------------------------------------------------------- Function: Internal_ADClientClearScalarKey Description: This function is a wrapper around calling the ADClientClearScalarKey function. NOTE: The key should be a simple key such as "numberOfPeers". This is because this function will append the required prefix of "com.apple.certstats" -------------------------------------------------------------------------- */ static void Internal_ADClientClearScalarKey(CFStringRef key) { if (InitializeADFunctionPointers()) { CFStringRef real_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s.%@"), gTopLevelKeyForCertificateTracing, key); if (NULL == real_key) { return; } gADClientClearScalarKey(real_key); CFRelease(real_key); } } /* -------------------------------------------------------------------------- Function: Internal_ADClientAddValueForScalarKey Description: This function is a wrapper around calling the ADClientAddValueForScalarKey function. NOTE: The key should be a simple key such as "numberOfPeers". This is because this function will append the required prefix of "com.apple.certstats" -------------------------------------------------------------------------- */ static void Internal_ADClientAddValueForScalarKey(CFStringRef key, int64_t value) { if (InitializeADFunctionPointers()) { CFStringRef real_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s.%@"), gTopLevelKeyForCertificateTracing, key); if (NULL == real_key) { return; } gADClientAddValueForScalarKey(real_key, value); CFRelease(real_key); } } /* -------------------------------------------------------------------------- Function: iOS_SetCertificateTraceValueForKey Description: This function is a wrapper around calling either ADClientAddValueForScalarKey or ADClientClearScalarKey, depending on whether the value is 0. NOTE: The key should be a simple key such as "numberOfPeers". This is because this function will append the required prefix of "com.apple.certstats" -------------------------------------------------------------------------- */ static bool iOS_SetCertificateTraceValueForKey(CFStringRef key, int64_t value) { if (NULL == key) { return false; } if (0LL == value) { Internal_ADClientClearScalarKey(key); } else { Internal_ADClientAddValueForScalarKey(key, value); } return true; } /* -------------------------------------------------------------------------- Function: iOS_AddKeyValuePairToCertificateLoggingTransaction Description: For iOS there is no "bunching". This function will simply call iOS_SetCloudKeychainTraceValueForKey to log the key-value pair. -------------------------------------------------------------------------- */ static bool iOS_AddKeyValuePairToCertificateLoggingTransaction(void* token, CFStringRef key, int64_t value) { #pragma unused(token) return iOS_SetCertificateTraceValueForKey(key, value); } #endif /* -------------------------------------------------------------------------- Function: SetCertificateTraceValueForKey Description: SPI to log a single key-value pair with the logging system -------------------------------------------------------------------------- */ bool SetCertificateTraceValueForKey(CFStringRef key, int64_t value) { #if (TARGET_IPHONE_SIMULATOR) return false; #endif #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) return OSX_SetCertificateTraceValueForKey(key, value); #endif #if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) return iOS_SetCertificateTraceValueForKey(key, value); #endif } /* -------------------------------------------------------------------------- Function: BeginCertificateLoggingTransaction Description: SPI to begin a logging transaction -------------------------------------------------------------------------- */ void* BeginCertificateLoggingTransaction(void) { #if (TARGET_IPHONE_SIMULATOR) return (void *)-1; #endif #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) return OSX_BeginCertificateLoggingTransaction(); #endif #if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) return NULL; #endif } /* -------------------------------------------------------------------------- Function: AddKeyValuePairToCertificateLoggingTransaction Description: SPI to add a key-value pair to an outstanding logging transaction -------------------------------------------------------------------------- */ bool AddKeyValuePairToCertificateLoggingTransaction(void* token, CFStringRef key, int64_t value) { #if (TARGET_IPHONE_SIMULATOR) return false; #endif #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) return OSX_AddKeyValuePairToCertificateLoggingTransaction(token, key, value); #endif #if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) return iOS_AddKeyValuePairToCertificateLoggingTransaction(token, key, value); #endif } /* -------------------------------------------------------------------------- Function: CloseCertificateLoggingTransaction Description: SPI to complete a logging transaction and clean up the context -------------------------------------------------------------------------- */ void CloseCertificateLoggingTransaction(void* token) { #if (TARGET_IPHONE_SIMULATOR) ; // nothing #endif #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) OSX_CloseCertificateLoggingTransaction(token); #endif #if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) ; // nothing #endif } /* -------------------------------------------------------------------------- Function: SecCFStringCopyEncodingDelimiters Description: Utility to replace all characters in the given string which match characters in the delimiters string. This should be expanded later; for now we are assuming the delimiters string is a single character, and we replace it with an underscore instead of percent encoding. -------------------------------------------------------------------------- */ CFStringRef SecCFStringCopyEncodingDelimiters(CFStringRef str, CFStringRef delimiters) { CFMutableStringRef newStr = (str) ? CFStringCreateMutableCopy(kCFAllocatorDefault, 0, str) : NULL; if (newStr && delimiters) { CFStringRef replacementString = CFSTR("_"); CFIndex idx, count = CFStringGetLength(delimiters); for (idx = 0; idx < count; idx++) { UniChar uc = CFStringGetCharacterAtIndex(delimiters, idx); CFStringRef stringToFind = CFStringCreateWithCharacters(kCFAllocatorDefault, &uc, 1); if (stringToFind) { CFStringFindAndReplace(newStr, stringToFind, replacementString, CFRangeMake(0, CFStringGetLength(newStr)), 0); CFRelease(stringToFind); } } } return (CFStringRef) newStr; } /* -------------------------------------------------------------------------- Function: SecCertificateTraceAddRecord Description: High-level SPI that logs a single certificate record, given a dictionary containing key-value pairs. -------------------------------------------------------------------------- */ bool SecCertificateTraceAddRecord(CFDictionaryRef certRecord) { bool result = false; if (!certRecord) return result; /* encode delimiter characters in supplied strings */ CFStringRef policy = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsPolicy), kCertStatsDelimiters); CFStringRef notBefore = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsNotBefore), kCertStatsDelimiters); CFStringRef notAfter = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsNotAfter), kCertStatsDelimiters); CFStringRef serial = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsSerialNumber), kCertStatsDelimiters); CFStringRef subject = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsSubjectSummary), kCertStatsDelimiters); CFStringRef issuer = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsIssuerSummary), kCertStatsDelimiters); /* this generates a key which includes delimited fields to identify the certificate */ CFStringRef keyStr = CFStringCreateWithFormat(NULL, NULL, kCertStatsFormat, /* format string */ kCertStatsCert, /* certificate key */ kCertStatsPolicy, policy, kCertStatsNotBefore, notBefore, kCertStatsNotAfter, notAfter, kCertStatsSerialNumber, serial, kCertStatsSubjectSummary, subject, kCertStatsIssuerSummary, issuer ); CFReleaseSafe(policy); CFReleaseSafe(notBefore); CFReleaseSafe(notAfter); CFReleaseSafe(serial); CFReleaseSafe(subject); CFReleaseSafe(issuer); result = SetCertificateTraceValueForKey(keyStr, 1); #if DEBUG secerror("%@.%@ (%d)", kCertStatsPrefix, keyStr, (result) ? 1 : 0); #endif CFReleaseSafe(keyStr); return result; }