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