1// 2// SecCertificateTrace.c 3// utilities 4// 5// Created on 10/18/13. 6// Copyright (c) 2013 Apple Inc. All rights reserved. 7// 8 9#include "SecCertificateTrace.h" 10#include <TargetConditionals.h> 11#include <inttypes.h> 12#include "SecCFWrappers.h" 13#include <sys/time.h> 14#include <CoreFoundation/CoreFoundation.h> 15#include "debugging.h" 16 17#define SEC_CONST_DECL(k,v) CFTypeRef k = (CFTypeRef)(CFSTR(v)); 18 19SEC_CONST_DECL (kCertStatsPrefix, "com.apple.certstats"); 20SEC_CONST_DECL (kCertStatsCert, "cert"); 21SEC_CONST_DECL (kCertStatsPolicy, "id"); 22SEC_CONST_DECL (kCertStatsNotBefore, "nb"); 23SEC_CONST_DECL (kCertStatsNotAfter, "na"); 24SEC_CONST_DECL (kCertStatsSerialNumber, "sn"); 25SEC_CONST_DECL (kCertStatsSubjectSummary, "s"); 26SEC_CONST_DECL (kCertStatsIssuerSummary, "i"); 27 28static const CFStringRef kCertStatsFormat = CFSTR("%@/%@=%@/%@=%@/%@=%@/%@=%@/%@=%@/%@=%@"); 29static const CFStringRef kCertStatsDelimiters = CFSTR("/"); 30 31bool SetCertificateTraceValueForKey(CFStringRef key, int64_t value); 32void* BeginCertificateLoggingTransaction(void); 33bool AddKeyValuePairToCertificateLoggingTransaction(void* token, CFStringRef key, int64_t value); 34void CloseCertificateLoggingTransaction(void* token); 35CFStringRef SecCFStringCopyEncodingDelimiters(CFStringRef str, CFStringRef delimiters); 36 37#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR)) 38#include <asl.h> 39 40static const char* gTopLevelKeyForCertificateTracing = "com.apple.certstats"; 41 42static const char* gMessageTracerSetPrefix = "com.apple.message."; 43 44static const char* gMessageTracerDomainField = "com.apple.message.domain"; 45 46/* -------------------------------------------------------------------------- 47 Function: OSX_BeginCertificateLoggingTransaction 48 49 Description: For OSX, the message tracer back end wants its logging 50 done in "bunches". This function allows for beginning 51 a 'transaction' of logging which will allow for putting 52 all of the transaction's items into a single log making 53 the message tracer folks happy. 54 55 The work of this function is to create the aslmsg context 56 and set the domain field and then return the aslmsg 57 context as a void* result. 58 -------------------------------------------------------------------------- */ 59static void* OSX_BeginCertificateLoggingTransaction() 60{ 61 void* result = NULL; 62 aslmsg mAsl = NULL; 63 mAsl = asl_new(ASL_TYPE_MSG); 64 if (NULL == mAsl) 65 { 66 return result; 67 } 68 69 asl_set(mAsl, gMessageTracerDomainField, gTopLevelKeyForCertificateTracing); 70 71 result = (void *)mAsl; 72 return result; 73} 74 75/* -------------------------------------------------------------------------- 76 Function: OSX_AddKeyValuePairToCertificateLoggingTransaction 77 78 Description: Once a call to OSX_BeginCertificateLoggingTransaction 79 is done, this call will allow for adding items to the 80 "bunch" of items being logged. 81 82 NOTE: The key should be a simple key such as 83 "numberOfPeers". This is because this function will 84 append the required prefix of "com.apple.message." 85 -------------------------------------------------------------------------- */ 86static bool OSX_AddKeyValuePairToCertificateLoggingTransaction(void* token, CFStringRef key, int64_t value) 87{ 88 if (NULL == token || NULL == key) 89 { 90 return false; 91 } 92 93 aslmsg mAsl = (aslmsg)token; 94 95 // Fix up the key 96 CFStringRef real_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s%@"), gMessageTracerSetPrefix, key); 97 if (NULL == real_key) 98 { 99 return false; 100 } 101 102 CFIndex key_length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(real_key), kCFStringEncodingUTF8); 103 key_length += 1; // For null 104 char key_buffer[key_length]; 105 memset(key_buffer, 0,key_length); 106 if (!CFStringGetCString(real_key, key_buffer, key_length, kCFStringEncodingUTF8)) 107 { 108 return false; 109 } 110 CFRelease(real_key); 111 112 CFStringRef value_str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%lld"), value); 113 if (NULL == value_str) 114 { 115 return false; 116 } 117 118 CFIndex value_str_numBytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value_str), kCFStringEncodingUTF8); 119 value_str_numBytes += 1; // For null 120 char value_buffer[value_str_numBytes]; 121 memset(value_buffer, 0, value_str_numBytes); 122 if (!CFStringGetCString(value_str, value_buffer, value_str_numBytes, kCFStringEncodingUTF8)) 123 { 124 CFRelease(value_str); 125 return false; 126 } 127 CFRelease(value_str); 128 129 asl_set(mAsl, key_buffer, value_buffer); 130 return true; 131} 132 133/* -------------------------------------------------------------------------- 134 Function: OSX_CloseCertificateLoggingTransaction 135 136 Description: Once a call to OSX_BeginCertificateLoggingTransaction 137 is done, and all of the items that are to be in the 138 "bunch" of items being logged, this function will do the 139 real logging and free the aslmsg context. 140 -------------------------------------------------------------------------- */ 141static void OSX_CloseCertificateLoggingTransaction(void* token) 142{ 143 if (NULL != token) 144 { 145 aslmsg mAsl = (aslmsg)token; 146 asl_log(NULL, mAsl, ASL_LEVEL_NOTICE, ""); 147 asl_free(mAsl); 148 } 149} 150 151/* -------------------------------------------------------------------------- 152 Function: OSX_SetCertificateTraceValueForKey 153 154 Description: If "bunching" of items either cannot be done or is not 155 desired, then this 'single shot' function should be used. 156 It will create the aslmsg context, register the domain, 157 fix up the key and log the key value pair, and then 158 do the real logging and free the aslmsg context. 159 -------------------------------------------------------------------------- */ 160static bool OSX_SetCertificateTraceValueForKey(CFStringRef key, int64_t value) 161{ 162 bool result = false; 163 164 if (NULL == key) 165 { 166 return result; 167 } 168 169 aslmsg mAsl = NULL; 170 mAsl = asl_new(ASL_TYPE_MSG); 171 if (NULL == mAsl) 172 { 173 return result; 174 } 175 176 // Fix up the key 177 CFStringRef real_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s%@"), gMessageTracerSetPrefix, key); 178 if (NULL == real_key) 179 { 180 return false; 181 } 182 183 CFIndex key_length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(real_key), kCFStringEncodingUTF8); 184 key_length += 1; // For null 185 char key_buffer[key_length]; 186 memset(key_buffer, 0,key_length); 187 if (!CFStringGetCString(real_key, key_buffer, key_length, kCFStringEncodingUTF8)) 188 { 189 return false; 190 } 191 CFRelease(real_key); 192 193 194 CFStringRef value_str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%lld"), value); 195 if (NULL == value_str) 196 { 197 asl_free(mAsl); 198 return result; 199 } 200 201 CFIndex value_str_numBytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value_str), kCFStringEncodingUTF8); 202 value_str_numBytes += 1; // For null 203 char value_buffer[value_str_numBytes]; 204 memset(value_buffer, 0, value_str_numBytes); 205 if (!CFStringGetCString(value_str, value_buffer, value_str_numBytes, kCFStringEncodingUTF8)) 206 { 207 asl_free(mAsl); 208 CFRelease(value_str); 209 return result; 210 } 211 CFRelease(value_str); 212 213 /* Domain */ 214 asl_set(mAsl, gMessageTracerDomainField, gTopLevelKeyForCertificateTracing); 215 216 /* Our custom key (starts with com.apple.message.) and its value */ 217 asl_set(mAsl, key_buffer, value_buffer); 218 219 /* Log this ASL record (note actual log message is empty) */ 220 asl_log(NULL, mAsl, ASL_LEVEL_NOTICE, ""); 221 asl_free(mAsl); 222 return true; 223 224} 225#endif 226 227#if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) 228 229static const char* gTopLevelKeyForCertificateTracing = "com.apple.certstats"; 230 231typedef void (*type_ADClientClearScalarKey)(CFStringRef key); 232typedef void (*type_ADClientAddValueForScalarKey)(CFStringRef key, int64_t value); 233 234static type_ADClientClearScalarKey gADClientClearScalarKey = NULL; 235static type_ADClientAddValueForScalarKey gADClientAddValueForScalarKey = NULL; 236 237static dispatch_once_t gADFunctionPointersSet = 0; 238static CFBundleRef gAggdBundleRef = NULL; 239static bool gFunctionPointersAreLoaded = false; 240 241/* -------------------------------------------------------------------------- 242 Function: InitializeADFunctionPointers 243 244 Description: Linking to the Aggregate library causes a build cycle, 245 so this function dynamically loads the needed function 246 pointers. 247 -------------------------------------------------------------------------- */ 248static bool InitializeADFunctionPointers() 249{ 250 if (gFunctionPointersAreLoaded) 251 { 252 return gFunctionPointersAreLoaded; 253 } 254 255 dispatch_once(&gADFunctionPointersSet, 256 ^{ 257 CFStringRef path_to_aggd_framework = CFSTR("/System/Library/PrivateFrameworks/AggregateDictionary.framework"); 258 259 CFURLRef aggd_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_to_aggd_framework, kCFURLPOSIXPathStyle, true); 260 261 if (NULL != aggd_url) 262 { 263 gAggdBundleRef = CFBundleCreate(kCFAllocatorDefault, aggd_url); 264 if (NULL != gAggdBundleRef) 265 { 266 gADClientClearScalarKey = (type_ADClientClearScalarKey) 267 CFBundleGetFunctionPointerForName(gAggdBundleRef, CFSTR("ADClientClearScalarKey")); 268 269 gADClientAddValueForScalarKey = (type_ADClientAddValueForScalarKey) 270 CFBundleGetFunctionPointerForName(gAggdBundleRef, CFSTR("ADClientAddValueForScalarKey")); 271 } 272 CFRelease(aggd_url); 273 } 274 }); 275 276 gFunctionPointersAreLoaded = ((NULL != gADClientClearScalarKey) && (NULL != gADClientAddValueForScalarKey)); 277 return gFunctionPointersAreLoaded; 278} 279 280/* -------------------------------------------------------------------------- 281 Function: Internal_ADClientClearScalarKey 282 283 Description: This function is a wrapper around calling the 284 ADClientClearScalarKey function. 285 286 NOTE: The key should be a simple key such as 287 "numberOfPeers". This is because this function will 288 append the required prefix of "com.apple.certstats" 289 -------------------------------------------------------------------------- */ 290static void Internal_ADClientClearScalarKey(CFStringRef key) 291{ 292 if (InitializeADFunctionPointers()) 293 { 294 CFStringRef real_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s.%@"), gTopLevelKeyForCertificateTracing, key); 295 if (NULL == real_key) 296 { 297 return; 298 } 299 300 gADClientClearScalarKey(real_key); 301 CFRelease(real_key); 302 } 303} 304 305/* -------------------------------------------------------------------------- 306 Function: Internal_ADClientAddValueForScalarKey 307 308 Description: This function is a wrapper around calling the 309 ADClientAddValueForScalarKey function. 310 311 NOTE: The key should be a simple key such as 312 "numberOfPeers". This is because this function will 313 append the required prefix of "com.apple.certstats" 314 -------------------------------------------------------------------------- */ 315static void Internal_ADClientAddValueForScalarKey(CFStringRef key, int64_t value) 316{ 317 if (InitializeADFunctionPointers()) 318 { 319 CFStringRef real_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s.%@"), gTopLevelKeyForCertificateTracing, key); 320 if (NULL == real_key) 321 { 322 return; 323 } 324 325 gADClientAddValueForScalarKey(real_key, value); 326 CFRelease(real_key); 327 } 328} 329 330 331/* -------------------------------------------------------------------------- 332 Function: iOS_SetCertificateTraceValueForKey 333 334 Description: This function is a wrapper around calling either 335 ADClientAddValueForScalarKey or ADClientClearScalarKey, 336 depending on whether the value is 0. 337 338 NOTE: The key should be a simple key such as 339 "numberOfPeers". This is because this function will 340 append the required prefix of "com.apple.certstats" 341 -------------------------------------------------------------------------- */ 342static bool iOS_SetCertificateTraceValueForKey(CFStringRef key, int64_t value) 343{ 344 if (NULL == key) 345 { 346 return false; 347 } 348 349 if (0LL == value) 350 { 351 Internal_ADClientClearScalarKey(key); 352 } 353 else 354 { 355 Internal_ADClientAddValueForScalarKey(key, value); 356 } 357 return true; 358} 359 360/* -------------------------------------------------------------------------- 361 Function: iOS_AddKeyValuePairToCertificateLoggingTransaction 362 363 Description: For iOS there is no "bunching". This function will simply 364 call iOS_SetCloudKeychainTraceValueForKey to log the 365 key-value pair. 366 -------------------------------------------------------------------------- */ 367static bool iOS_AddKeyValuePairToCertificateLoggingTransaction(void* token, CFStringRef key, int64_t value) 368{ 369#pragma unused(token) 370 return iOS_SetCertificateTraceValueForKey(key, value); 371} 372#endif 373 374/* -------------------------------------------------------------------------- 375 Function: SetCertificateTraceValueForKey 376 377 Description: SPI to log a single key-value pair with the logging system 378 -------------------------------------------------------------------------- */ 379bool SetCertificateTraceValueForKey(CFStringRef key, int64_t value) 380{ 381#if (TARGET_IPHONE_SIMULATOR) 382 return false; 383#endif 384 385#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) 386 return OSX_SetCertificateTraceValueForKey(key, value); 387#endif 388 389#if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) 390 return iOS_SetCertificateTraceValueForKey(key, value); 391#endif 392} 393 394/* -------------------------------------------------------------------------- 395 Function: BeginCertificateLoggingTransaction 396 397 Description: SPI to begin a logging transaction 398 -------------------------------------------------------------------------- */ 399void* BeginCertificateLoggingTransaction(void) 400{ 401#if (TARGET_IPHONE_SIMULATOR) 402 return (void *)-1; 403#endif 404 405#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) 406 return OSX_BeginCertificateLoggingTransaction(); 407#endif 408 409#if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) 410 return NULL; 411#endif 412} 413 414/* -------------------------------------------------------------------------- 415 Function: AddKeyValuePairToCertificateLoggingTransaction 416 417 Description: SPI to add a key-value pair to an outstanding logging 418 transaction 419 -------------------------------------------------------------------------- */ 420bool AddKeyValuePairToCertificateLoggingTransaction(void* token, CFStringRef key, int64_t value) 421{ 422#if (TARGET_IPHONE_SIMULATOR) 423 return false; 424#endif 425 426#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) 427 return OSX_AddKeyValuePairToCertificateLoggingTransaction(token, key, value); 428#endif 429 430#if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) 431 return iOS_AddKeyValuePairToCertificateLoggingTransaction(token, key, value); 432#endif 433} 434 435/* -------------------------------------------------------------------------- 436 Function: CloseCertificateLoggingTransaction 437 438 Description: SPI to complete a logging transaction and clean up the 439 context 440 -------------------------------------------------------------------------- */ 441void CloseCertificateLoggingTransaction(void* token) 442{ 443#if (TARGET_IPHONE_SIMULATOR) 444 ; // nothing 445#endif 446 447#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) 448 OSX_CloseCertificateLoggingTransaction(token); 449#endif 450 451#if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) 452 ; // nothing 453#endif 454} 455 456/* -------------------------------------------------------------------------- 457 Function: SecCFStringCopyEncodingDelimiters 458 459 Description: Utility to replace all characters in the given string 460 which match characters in the delimiters string. This 461 should be expanded later; for now we are assuming the 462 delimiters string is a single character, and we replace 463 it with an underscore instead of percent encoding. 464 -------------------------------------------------------------------------- */ 465 466CFStringRef SecCFStringCopyEncodingDelimiters(CFStringRef str, CFStringRef delimiters) 467{ 468 CFMutableStringRef newStr = (str) ? CFStringCreateMutableCopy(kCFAllocatorDefault, 0, str) : NULL; 469 if (str && delimiters) 470 { 471 CFStringRef stringToFind = delimiters; 472 CFStringRef replacementString = CFSTR("_"); 473 CFStringFindAndReplace(newStr, stringToFind, replacementString, CFRangeMake(0, CFStringGetLength(newStr)), 0); 474 } 475 return (CFStringRef) newStr; 476} 477 478/* -------------------------------------------------------------------------- 479 Function: SecCertificateTraceAddRecord 480 481 Description: High-level SPI that logs a single certificate record, 482 given a dictionary containing key-value pairs. 483 -------------------------------------------------------------------------- */ 484bool SecCertificateTraceAddRecord(CFDictionaryRef certRecord) 485{ 486 bool result = false; 487 if (!certRecord) 488 return result; 489 490 /* encode delimiter characters in supplied strings */ 491 CFStringRef policy = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsPolicy), kCertStatsDelimiters); 492 CFStringRef notBefore = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsNotBefore), kCertStatsDelimiters); 493 CFStringRef notAfter = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsNotAfter), kCertStatsDelimiters); 494 CFStringRef serial = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsSerialNumber), kCertStatsDelimiters); 495 CFStringRef subject = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsSubjectSummary), kCertStatsDelimiters); 496 CFStringRef issuer = SecCFStringCopyEncodingDelimiters(CFDictionaryGetValue(certRecord, kCertStatsIssuerSummary), kCertStatsDelimiters); 497 498 /* this generates a key which includes delimited fields to identify the certificate */ 499 CFStringRef keyStr = CFStringCreateWithFormat(NULL, NULL, 500 kCertStatsFormat, /* format string */ 501 kCertStatsCert, /* certificate key */ 502 kCertStatsPolicy, policy, 503 kCertStatsNotBefore, notBefore, 504 kCertStatsNotAfter, notAfter, 505 kCertStatsSerialNumber, serial, 506 kCertStatsSubjectSummary, subject, 507 kCertStatsIssuerSummary, issuer 508 ); 509 CFReleaseSafe(policy); 510 CFReleaseSafe(notBefore); 511 CFReleaseSafe(notAfter); 512 CFReleaseSafe(serial); 513 CFReleaseSafe(subject); 514 CFReleaseSafe(issuer); 515 516 result = SetCertificateTraceValueForKey(keyStr, 1); 517#if DEBUG 518 secerror("%@.%@ (%d)", kCertStatsPrefix, keyStr, (result) ? 1 : 0); 519#endif 520 CFReleaseSafe(keyStr); 521 522 return result; 523} 524 525