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