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