--- /dev/null
+/*
+ * Copyright (c) 2012-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@
+ *
+ * tsaSupport.c - ASN1 templates Time Stamping Authority requests and responses
+ */
+
+/*
+#include <Security/SecCmsDigestContext.h>
+#include <Security/SecCmsMessage.h>
+#include <security_asn1/secasn1.h>
+#include <security_asn1/secerr.h>
+*/
+
+#include <security_utilities/debugging.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+
+#include <Security/SecCmsDecoder.h>
+#include <Security/SecCmsMessage.h>
+#include <Security/SecCmsContentInfo.h>
+#include <Security/SecCmsSignedData.h>
+#include <Security/SecCmsSignerInfo.h>
+#include "tsaTemplates.h"
+#include <Security/SecAsn1Coder.h>
+#include <AssertMacros.h>
+#include <Security/SecPolicy.h>
+#include <Security/SecTrustPriv.h>
+#include <Security/SecImportExport.h>
+#include <Security/SecCertificatePriv.h>
+#include <security_keychain/SecCertificateP.h>
+#include <security_keychain/SecCertificatePrivP.h>
+#include <utilities/SecCFRelease.h>
+
+#include "tsaSupport.h"
+#include "tsaSupportPriv.h"
+#include "tsaTemplates.h"
+#include "cmslocal.h"
+
+#include "secoid.h"
+#include "secitem.h"
+#include <fcntl.h>
+
+const CFStringRef kTSAContextKeyURL = CFSTR("ServerURL");
+const CFStringRef kTSAContextKeyNoCerts = CFSTR("NoCerts");
+const CFStringRef kTSADebugContextKeyBadReq = CFSTR("DebugBadReq");
+const CFStringRef kTSADebugContextKeyBadNonce = CFSTR("DebugBadNonce");
+
+extern const SecAsn1Template kSecAsn1TSATSTInfoTemplate[];
+
+extern OSStatus impExpImportCertCommon(
+ const CSSM_DATA *cdata,
+ SecKeychainRef importKeychain, // optional
+ CFMutableArrayRef outArray); // optional, append here
+
+#pragma mark ----- Debug Logs -----
+
+#ifndef NDEBUG
+#define TSA_USE_SYSLOG 1
+#endif
+
+#if TSA_USE_SYSLOG
+#include <syslog.h>
+ #include <time.h>
+ #include <sys/time.h>
+ #define tsaDebug(fmt, ...) \
+ do { if (true) { \
+ char buf[64]; \
+ struct timeval time_now; \
+ gettimeofday(&time_now, NULL); \
+ struct tm* time_info = localtime(&time_now.tv_sec); \
+ strftime(buf, sizeof(buf), "[%Y-%m-%d %H:%M:%S]", time_info); \
+ fprintf(stderr, "%s " fmt, buf, ## __VA_ARGS__); \
+ syslog(LOG_ERR, " " fmt, ## __VA_ARGS__); \
+ } } while (0)
+ #define tsa_secdebug(scope, format...) \
+ { \
+ syslog(LOG_NOTICE, format); \
+ secdebug(scope, format); \
+ printf(format); \
+ }
+#else
+ #define tsaDebug(args...) tsa_secdebug("tsa", ## args)
+#define tsa_secdebug(scope, format...) \
+ secdebug(scope, format)
+#endif
+
+#ifndef NDEBUG
+#define TSTINFO_DEBUG 1 //jch
+#endif
+
+#if TSTINFO_DEBUG
+#define dtprintf(args...) tsaDebug(args)
+#else
+#define dtprintf(args...)
+#endif
+
+#define kHTTPResponseCodeContinue 100
+#define kHTTPResponseCodeOK 200
+#define kHTTPResponseCodeNoContent 204
+#define kHTTPResponseCodeBadRequest 400
+#define kHTTPResponseCodeUnauthorized 401
+#define kHTTPResponseCodeForbidden 403
+#define kHTTPResponseCodeNotFound 404
+#define kHTTPResponseCodeConflict 409
+#define kHTTPResponseCodeExpectationFailed 417
+#define kHTTPResponseCodeServFail 500
+#define kHTTPResponseCodeServiceUnavailable 503
+#define kHTTPResponseCodeInsufficientStorage 507
+
+#pragma mark ----- Debug/Utilities -----
+
+static OSStatus remapHTTPErrorCodes(OSStatus status)
+{
+ switch (status)
+ {
+ case kHTTPResponseCodeOK:
+ case kHTTPResponseCodeContinue:
+ return noErr;
+ case kHTTPResponseCodeBadRequest:
+ return errSecTimestampBadRequest;
+ case kHTTPResponseCodeNoContent:
+ case kHTTPResponseCodeUnauthorized:
+ case kHTTPResponseCodeForbidden:
+ case kHTTPResponseCodeNotFound:
+ case kHTTPResponseCodeConflict:
+ case kHTTPResponseCodeExpectationFailed:
+ case kHTTPResponseCodeServFail:
+ case kHTTPResponseCodeInsufficientStorage:
+ case kHTTPResponseCodeServiceUnavailable:
+ return errSecTimestampServiceNotAvailable;
+ default:
+ return status;
+ }
+ return status;
+
+}
+
+static void printDataAsHex(const char *title, const CSSM_DATA *d, unsigned maxToPrint) // 0 means print it all
+{
+#ifndef NDEBUG
+ unsigned i;
+ bool more = false;
+ uint32 len = (uint32)d->Length;
+ uint8 *cp = d->Data;
+ char *buffer = NULL;
+ size_t bufferSize;
+ int offset, sz = 0;
+ const int wrapwid = 24; // large enough so SHA-1 hashes fit on one line...
+
+ if ((maxToPrint != 0) && (len > maxToPrint))
+ {
+ len = maxToPrint;
+ more = true;
+ }
+
+ bufferSize = wrapwid+3*len;
+ buffer = (char *)malloc(bufferSize);
+
+ offset = sprintf(buffer, "%s [len = %u]\n", title, len);
+ dtprintf("%s", buffer);
+ offset = 0;
+
+ for (i=0; (i < len) && (offset+3 < bufferSize); i++, offset += sz)
+ {
+ sz = sprintf(buffer + offset, " %02x", (unsigned int)cp[i] & 0xff);
+ if ((i % wrapwid) == (wrapwid-1))
+ {
+ dtprintf("%s", buffer);
+ offset = 0;
+ sz = 0;
+ }
+ }
+
+ sz=sprintf(buffer + offset, more?" ...\n":"\n");
+ offset += sz;
+ buffer[offset+1]=0;
+
+// fprintf(stderr, "%s", buffer);
+ dtprintf("%s", buffer);
+#endif
+}
+
+#ifndef NDEBUG
+int tsaWriteFileX(const char *fileName, const unsigned char *bytes, size_t numBytes)
+{
+ int rtn;
+ int fd;
+
+ fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ if (fd <= 0)
+ return errno;
+
+ rtn = (int)write(fd, bytes, numBytes);
+ if(rtn != (int)numBytes)
+ {
+ if (rtn >= 0)
+ fprintf(stderr, "writeFile: short write\n");
+ rtn = EIO;
+ }
+ else
+ rtn = 0;
+
+ close(fd);
+ return rtn;
+}
+#endif
+
+char *cfStringToChar(CFStringRef inStr)
+{
+ // Caller must free
+ char *result = NULL;
+ const char *str = NULL;
+
+ if (!inStr)
+ return strdup(""); // return a null string
+
+ // quick path first
+ if ((str = CFStringGetCStringPtr(inStr, kCFStringEncodingUTF8))) {
+ result = strdup(str);
+ } else {
+ // need to extract into buffer
+ CFIndex length = CFStringGetLength(inStr); // in 16-bit character units
+ CFIndex bytesToAllocate = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
+ result = malloc(bytesToAllocate);
+ if (!CFStringGetCString(inStr, result, bytesToAllocate, kCFStringEncodingUTF8))
+ result[0] = 0;
+ }
+
+ return result;
+}
+
+/* Oids longer than this are considered invalid. */
+#define MAX_OID_SIZE 32
+
+#ifndef NDEBUG
+/* FIXME: There are other versions of this in SecCertifcate.c and SecCertificateP.c */
+static CFStringRef SecDERItemCopyOIDDecimalRepresentation(CFAllocatorRef allocator, const CSSM_OID *oid)
+{
+ if (oid->Length == 0)
+ return CFSTR("<NULL>");
+
+ if (oid->Length > MAX_OID_SIZE)
+ return CFSTR("Oid too long");
+
+ CFMutableStringRef result = CFStringCreateMutable(allocator, 0);
+
+ // The first two levels are encoded into one byte, since the root levelq
+ // has only 3 nodes (40*x + y). However if x = joint-iso-itu-t(2) then
+ // y may be > 39, so we have to add special-case handling for this.
+ uint32_t x = oid->Data[0] / 40;
+ uint32_t y = oid->Data[0] % 40;
+ if (x > 2)
+ {
+ // Handle special case for large y if x = 2
+ y += (x - 2) * 40;
+ x = 2;
+ }
+ CFStringAppendFormat(result, NULL, CFSTR("%u.%u"), x, y);
+
+ uint32_t value = 0;
+ for (x = 1; x < oid->Length; ++x)
+ {
+ value = (value << 7) | (oid->Data[x] & 0x7F);
+ /* @@@ value may not span more than 4 bytes. */
+ /* A max number of 20 values is allowed. */
+ if (!(oid->Data[x] & 0x80))
+ {
+ CFStringAppendFormat(result, NULL, CFSTR(".%lu"), (unsigned long)value);
+ value = 0;
+ }
+ }
+ return result;
+}
+#endif
+
+static void debugSaveCertificates(CSSM_DATA **outCerts)
+{
+#ifndef NDEBUG
+ if (outCerts)
+ {
+ CSSM_DATA_PTR *certp;
+ unsigned jx = 0;
+ const char *certNameBase = "/tmp/tsa-resp-cert-";
+ char fname[PATH_MAX];
+ unsigned certCount = SecCmsArrayCount((void **)outCerts);
+ dtprintf("Found %d certs\n",certCount);
+
+ for (certp=outCerts;*certp;certp++, ++jx)
+ {
+ char numstr[32];
+ strncpy(fname, certNameBase, strlen(certNameBase)+1);
+ sprintf(numstr,"%u", jx);
+ strcat(fname,numstr);
+ tsaWriteFileX(fname, (*certp)->Data, (*certp)->Length);
+ if (jx > 5)
+ break; //something wrong
+ }
+ }
+#endif
+}
+
+static void debugShowSignerInfo(SecCmsSignedDataRef signedData)
+{
+#ifndef NDEBUG
+ int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData);
+ dtprintf("numberOfSigners : %d\n", numberOfSigners);
+ int ix;
+ for (ix=0;ix < numberOfSigners;ix++)
+ {
+ SecCmsSignerInfoRef sigi = SecCmsSignedDataGetSignerInfo(signedData,ix);
+ if (sigi)
+ {
+ CFStringRef commonName = SecCmsSignerInfoGetSignerCommonName(sigi);
+ const char *signerhdr = " signer : ";
+ if (commonName)
+ {
+ char *cn = cfStringToChar(commonName);
+ dtprintf("%s%s\n", signerhdr, cn);
+ if (cn)
+ free(cn);
+ }
+ else
+ dtprintf("%s<NULL>\n", signerhdr);
+ }
+ }
+#endif
+}
+
+static void debugShowContentTypeOID(SecCmsContentInfoRef contentInfo)
+{
+#ifndef NDEBUG
+
+ CSSM_OID *typeOID = SecCmsContentInfoGetContentTypeOID(contentInfo);
+ if (typeOID)
+ {
+ CFStringRef oidCFStr = SecDERItemCopyOIDDecimalRepresentation(kCFAllocatorDefault, typeOID);
+ char *oidstr = cfStringToChar(oidCFStr);
+ printDataAsHex("oid:", typeOID, (unsigned int)typeOID->Length);
+ dtprintf("\toid: %s\n", oidstr);
+ if (oidCFStr)
+ CFRelease(oidCFStr);
+ if (oidstr)
+ free(oidstr);
+ }
+#endif
+}
+
+uint64_t tsaDER_ToInt(const CSSM_DATA *DER_Data)
+{
+ uint64_t rtn = 0;
+ unsigned i = 0;
+
+ while(i < DER_Data->Length) {
+ rtn |= DER_Data->Data[i];
+ if(++i == DER_Data->Length) {
+ break;
+ }
+ rtn <<= 8;
+ }
+ return rtn;
+}
+
+void displayTSTInfo(SecAsn1TSATSTInfo *tstInfo)
+{
+#ifndef NDEBUG
+ dtprintf("--- TSTInfo ---\n");
+ if (!tstInfo)
+ return;
+
+ if (tstInfo->version.Data)
+ {
+ uint64_t vers = tsaDER_ToInt(&tstInfo->version);
+ dtprintf("Version:\t\t%u\n", (int)vers);
+ }
+
+ if (tstInfo->serialNumber.Data)
+ {
+ uint64_t sn = tsaDER_ToInt(&tstInfo->serialNumber);
+ dtprintf("SerialNumber:\t%llu\n", sn);
+ }
+
+ if (tstInfo->ordering.Data)
+ {
+ uint64_t ord = tsaDER_ToInt(&tstInfo->ordering);
+ dtprintf("Ordering:\t\t%s\n", ord?"yes":"no");
+ }
+
+ if (tstInfo->nonce.Data)
+ {
+ uint64_t nonce = tsaDER_ToInt(&tstInfo->nonce);
+ dtprintf("Nonce:\t\t%llu\n", nonce);
+ }
+ else
+ dtprintf("Nonce:\t\tnot specified\n");
+
+ if (tstInfo->genTime.Data)
+ {
+ char buf[tstInfo->genTime.Length+1];
+ memcpy(buf, (const char *)tstInfo->genTime.Data, tstInfo->genTime.Length);
+ buf[tstInfo->genTime.Length]=0;
+ dtprintf("GenTime:\t\t%s\n", buf);
+ }
+
+ dtprintf("-- MessageImprint --\n");
+ if (true) // SecAsn1TSAMessageImprint
+ {
+ printDataAsHex(" Algorithm:",&tstInfo->messageImprint.hashAlgorithm.algorithm, 0);
+ printDataAsHex(" Message :", &tstInfo->messageImprint.hashedMessage, 0);//tstInfo->messageImprint.hashedMessage.Length);
+ }
+#endif
+}
+
+#pragma mark ----- TimeStamp Response using XPC -----
+
+#include <xpc/private.h>
+
+static OSStatus checkForNonDERResponse(const unsigned char *resp, size_t respLen)
+{
+ /*
+ Good start is something like 30 82 0c 03 30 15 02 01 00 30 10 0c 0e 4f 70 65
+
+ URL: http://timestamp-int.corp.apple.com/signserver/process?TimeStampSigner
+ Resp: Http/1.1 Service Unavailable
+
+ URL: http://timestamp-int.corp.apple.com/ts01
+ Resp: blank
+
+ URL: http://cutandtaste.com/404 (or other forced 404 site)
+ Resp: 404
+ */
+
+ OSStatus status = noErr;
+ const char ader[2] = { 0x30, 0x82 };
+ char *respStr = NULL;
+ size_t maxlen = 0;
+ size_t badResponseCount;
+
+ const char *badResponses[] =
+ {
+ "<!DOCTYPE html>",
+ "Http/1.1 Service Unavailable",
+ "blank"
+ };
+
+ require_action(resp && respLen, xit, status = errSecTimestampServiceNotAvailable);
+
+ // This is usual case
+ if ((respLen > 1) && (memcmp(resp, ader, 2)==0)) // might be good; pass on to DER decoder
+ return noErr;
+
+ badResponseCount = sizeof(badResponses)/sizeof(char *);
+ int ix;
+ for (ix = 0; ix < badResponseCount; ++ix)
+ if (strlen(badResponses[ix]) > maxlen)
+ maxlen = strlen(badResponses[ix]);
+
+ // Prevent a large response from allocating a ton of memory
+ if (respLen > maxlen)
+ respLen = maxlen;
+
+ respStr = (char *)malloc(respLen+1);
+ strlcpy(respStr, (const char *)resp, respLen);
+
+ for (ix = 0; ix < badResponseCount; ++ix)
+ if (strcmp(respStr, badResponses[ix])==0)
+ return errSecTimestampServiceNotAvailable;
+
+xit:
+ if (respStr)
+ free((void *)respStr);
+
+ return status;
+}
+
+static OSStatus sendTSARequestWithXPC(const unsigned char *tsaReq, size_t tsaReqLength, const unsigned char *tsaURL, unsigned char **tsaResp, size_t *tsaRespLength)
+{
+ __block OSStatus result = noErr;
+ int timeoutInSeconds = 15;
+ extern xpc_object_t xpc_create_with_format(const char * format, ...);
+
+ dispatch_queue_t xpc_queue = dispatch_queue_create("com.apple.security.XPCTimeStampingService", DISPATCH_QUEUE_SERIAL);
+ __block dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
+ dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, timeoutInSeconds * NSEC_PER_SEC);
+
+ xpc_connection_t con = xpc_connection_create("com.apple.security.XPCTimeStampingService", xpc_queue);
+
+ xpc_connection_set_event_handler(con, ^(xpc_object_t event) {
+ xpc_type_t xtype = xpc_get_type(event);
+ if (XPC_TYPE_ERROR == xtype)
+ { tsaDebug("default: connection error: %s\n", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); }
+ else
+ { tsaDebug("default: unexpected connection event %p\n", event); }
+ });
+
+ xpc_connection_resume(con);
+
+ xpc_object_t tsaReqData = xpc_data_create(tsaReq, tsaReqLength);
+ const char *urlstr = (tsaURL?(const char *)tsaURL:"");
+ xpc_object_t url_as_xpc_string = xpc_string_create(urlstr);
+
+ xpc_object_t message = xpc_create_with_format("{operation: TimeStampRequest, ServerURL: %value, TimeStampRequest: %value}", url_as_xpc_string, tsaReqData);
+
+ xpc_connection_send_message_with_reply(con, message, xpc_queue, ^(xpc_object_t reply)
+ {
+ tsaDebug("xpc_connection_send_message_with_reply handler called back\n");
+ dispatch_retain(waitSemaphore);
+
+ xpc_type_t xtype = xpc_get_type(reply);
+ if (XPC_TYPE_ERROR == xtype)
+ { tsaDebug("message error: %s\n", xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION)); }
+ else if (XPC_TYPE_CONNECTION == xtype)
+ { tsaDebug("received connection\n"); }
+ else if (XPC_TYPE_DICTIONARY == xtype)
+ {
+#ifndef NDEBUG
+ /*
+ // This is useful for debugging.
+ char *debug = xpc_copy_description(reply);
+ tsaDebug("DEBUG %s\n", debug);
+ free(debug);
+ */
+#endif
+
+ xpc_object_t xpcTimeStampReply = xpc_dictionary_get_value(reply, "TimeStampReply");
+ size_t xpcTSRLength = xpc_data_get_length(xpcTimeStampReply);
+ tsaDebug("xpcTSRLength: %ld bytes of response\n", xpcTSRLength);
+
+ xpc_object_t xpcTimeStampError = xpc_dictionary_get_value(reply, "TimeStampError");
+ xpc_object_t xpcTimeStampStatus = xpc_dictionary_get_value(reply, "TimeStampStatus");
+
+ if (xpcTimeStampError || xpcTimeStampStatus)
+ {
+#ifndef NDEBUG
+ if (xpcTimeStampError)
+ {
+ size_t len = xpc_string_get_length(xpcTimeStampError);
+ char *buf = (char *)malloc(len);
+ strlcpy(buf, xpc_string_get_string_ptr(xpcTimeStampError), len+1);
+ tsaDebug("xpcTimeStampError: %s\n", buf);
+ if (buf)
+ free(buf);
+ }
+#endif
+ if (xpcTimeStampStatus)
+ {
+ result = (OSStatus)xpc_int64_get_value(xpcTimeStampStatus);
+ tsaDebug("xpcTimeStampStatus: %d\n", (int)result);
+ }
+ }
+
+ result = remapHTTPErrorCodes(result);
+
+ if ((result == noErr) && tsaResp && tsaRespLength)
+ {
+ *tsaRespLength = xpcTSRLength;
+ *tsaResp = (unsigned char *)malloc(xpcTSRLength);
+
+ size_t bytesCopied = xpc_data_get_bytes(xpcTimeStampReply, *tsaResp, 0, xpcTSRLength);
+ if (bytesCopied != xpcTSRLength)
+ { tsaDebug("length mismatch: copied: %ld, xpc: %ld\n", bytesCopied, xpcTSRLength); }
+ else
+ if ((result = checkForNonDERResponse(*tsaResp,bytesCopied)))
+ {
+ tsaDebug("received non-DER response from timestamp server\n");
+ }
+ else
+ {
+ result = noErr;
+ tsaDebug("copied: %ld bytes of response\n", bytesCopied);
+ }
+ }
+ tsaDebug("releasing connection\n");
+ xpc_release(con);
+ }
+ else
+ { tsaDebug("unexpected message reply type %p\n", xtype); }
+
+ dispatch_semaphore_signal(waitSemaphore);
+ dispatch_release(waitSemaphore);
+ });
+
+ { tsaDebug("waiting up to %d seconds for response from XPC\n", timeoutInSeconds); }
+ dispatch_semaphore_wait(waitSemaphore, finishTime);
+
+ dispatch_release(waitSemaphore);
+ xpc_release(tsaReqData);
+ xpc_release(message);
+
+ { tsaDebug("sendTSARequestWithXPC exit\n"); }
+
+ return result;
+}
+
+#pragma mark ----- TimeStamp request -----
+
+#include "tsaTemplates.h"
+#include <security_asn1/SecAsn1Coder.h>
+#include <Security/oidsalg.h>
+#include <AssertMacros.h>
+#include <libkern/OSByteOrder.h>
+
+extern const SecAsn1Template kSecAsn1TSATimeStampReqTemplate;
+extern const SecAsn1Template kSecAsn1TSATimeStampRespTemplateDER;
+
+CFMutableDictionaryRef SecCmsTSAGetDefaultContext(CFErrorRef *error)
+{
+ // Caller responsible for retain/release
+ // <rdar://problem/11077440> Update SecCmsTSAGetDefaultContext with actual URL for Apple Timestamp server
+ // URL will be in TimeStampingPrefs.plist
+
+ CFBundleRef secFWbundle = NULL;
+ CFURLRef resourceURL = NULL;
+ CFDataRef resourceData = NULL;
+ CFPropertyListRef prefs = NULL;
+ CFMutableDictionaryRef contextDict = NULL;
+ SInt32 errorCode = 0;
+ CFOptionFlags options = 0;
+ CFPropertyListFormat format = 0;
+ OSStatus status = noErr;
+
+ require_action(secFWbundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")), xit, status = errSecInternalError);
+ require_action(resourceURL = CFBundleCopyResourceURL(secFWbundle, CFSTR("TimeStampingPrefs"), CFSTR("plist"), NULL),
+ xit, status = errSecInvalidPrefsDomain);
+
+ require(CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, resourceURL, &resourceData,
+ NULL, NULL, &errorCode), xit);
+ require_action(resourceData, xit, status = errSecDataNotAvailable);
+
+ prefs = CFPropertyListCreateWithData(kCFAllocatorDefault, resourceData, options, &format, error);
+ require_action(prefs && (CFGetTypeID(prefs)==CFDictionaryGetTypeID()), xit, status = errSecInvalidPrefsDomain);
+
+ contextDict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, prefs);
+
+ if (error)
+ *error = NULL;
+xit:
+ if (errorCode)
+ status = errorCode;
+ if (error && status)
+ *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
+ if (secFWbundle)
+ CFRelease(secFWbundle);
+ if (resourceURL)
+ CFRelease(resourceURL);
+ if (resourceData)
+ CFRelease(resourceData);
+ if (prefs)
+ CFRelease(prefs);
+
+ return contextDict;
+}
+
+static CFDataRef _SecTSARequestCopyDEREncoding(SecAsn1TSAMessageImprint *messageImprint, bool noCerts, uint64_t nonce)
+{
+ // Returns DER encoded TimeStampReq
+ // Modeled on _SecOCSPRequestCopyDEREncoding
+ // The Timestamp Authority supports 64 bit nonces (or more possibly)
+
+ SecAsn1CoderRef coder = NULL;
+ uint8_t version = 1;
+ SecAsn1Item vers = {1, &version};
+ uint8_t creq = noCerts?0:1;
+ SecAsn1Item certReq = {1, &creq}; //jch - to request or not?
+ SecAsn1TSATimeStampReq tsreq = {};
+ CFDataRef der = NULL;
+ uint64_t nonceVal = OSSwapHostToBigConstInt64(nonce);
+ SecAsn1Item nonceItem = {sizeof(uint64_t), (unsigned char *)&nonceVal};
+
+ uint8_t OID_FakePolicy_Data[] = { 0x2A, 0x03, 0x04, 0x05, 0x06};
+ const CSSM_OID fakePolicyOID = {sizeof(OID_FakePolicy_Data),OID_FakePolicy_Data};
+
+ tsreq.version = vers;
+
+ tsreq.messageImprint = *messageImprint;
+ tsreq.certReq = certReq;
+
+ // skip reqPolicy, extensions for now - FAKES - jch
+ tsreq.reqPolicy = fakePolicyOID; //policyID;
+
+ tsreq.nonce = nonceItem;
+
+ // Encode the request
+ require_noerr(SecAsn1CoderCreate(&coder), errOut);
+
+ SecAsn1Item encoded;
+ require_noerr(SecAsn1EncodeItem(coder, &tsreq,
+ &kSecAsn1TSATimeStampReqTemplate, &encoded), errOut);
+ der = CFDataCreate(kCFAllocatorDefault, encoded.Data,
+ encoded.Length);
+
+errOut:
+ if (coder)
+ SecAsn1CoderRelease(coder);
+
+ return der;
+}
+
+OSStatus SecTSAResponseCopyDEREncoding(SecAsn1CoderRef coder, const CSSM_DATA *tsaResponse, SecAsn1TimeStampRespDER *respDER)
+{
+ // Partially decode the response
+ OSStatus status = paramErr;
+
+ require(tsaResponse && respDER, errOut);
+ require_noerr(SecAsn1DecodeData(coder, tsaResponse,
+ &kSecAsn1TSATimeStampRespTemplateDER, respDER), errOut);
+ status = noErr;
+
+errOut:
+
+ return status;
+}
+
+#pragma mark ----- TS Callback -----
+
+OSStatus SecCmsTSADefaultCallback(CFTypeRef context, void *messageImprintV, uint64_t nonce, CSSM_DATA *signedDERBlob)
+{
+ OSStatus result = paramErr;
+ const unsigned char *tsaReq = NULL;
+ size_t tsaReqLength = 0;
+ CFDataRef cfreq = NULL;
+ unsigned char *tsaURL = NULL;
+ bool noCerts = false;
+
+ if (!context || CFGetTypeID(context)!=CFDictionaryGetTypeID())
+ return paramErr;
+
+ SecAsn1TSAMessageImprint *messageImprint = (SecAsn1TSAMessageImprint *)messageImprintV;
+ if (!messageImprint || !signedDERBlob)
+ return paramErr;
+
+ CFBooleanRef cfnocerts = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSAContextKeyNoCerts);
+ if (cfnocerts)
+ {
+ tsaDebug("[TSA] Request noCerts\n");
+ noCerts = CFBooleanGetValue(cfnocerts);
+ }
+
+ // We must spoof the nonce here, before sending the request.
+ // If we tried to alter the reply, then the signature would break instead.
+ CFBooleanRef cfBadNonce = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSADebugContextKeyBadNonce);
+ if (cfBadNonce && CFBooleanGetValue(cfBadNonce))
+ {
+ tsaDebug("[TSA] Forcing bad TS Request by changing nonce\n");
+ nonce++;
+ }
+
+ printDataAsHex("[TSA] hashToTimeStamp:", &messageImprint->hashedMessage,128);
+ cfreq = _SecTSARequestCopyDEREncoding(messageImprint, noCerts, nonce);
+ if (cfreq)
+ {
+ tsaReq = CFDataGetBytePtr(cfreq);
+ tsaReqLength = CFDataGetLength(cfreq);
+
+#ifndef NDEBUG
+ CFShow(cfreq);
+ tsaWriteFileX("/tmp/tsareq.req", tsaReq, tsaReqLength);
+#endif
+ }
+
+ CFStringRef url = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSAContextKeyURL);
+ if (!url)
+ {
+ tsaDebug("[TSA] missing URL for TSA (key: %s)\n", "kTSAContextKeyURL");
+ goto xit;
+ }
+
+ /*
+ If debugging, look at special values in the context to mess things up
+ */
+
+ CFBooleanRef cfBadReq = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSADebugContextKeyBadReq);
+ if (cfBadReq && CFBooleanGetValue(cfBadReq))
+ {
+ tsaDebug("[TSA] Forcing bad TS Request by truncating length from %ld to %ld\n", tsaReqLength, (tsaReqLength-4));
+ tsaReqLength -= 4;
+ }
+
+ // need to extract into buffer
+ CFIndex length = CFStringGetLength(url); // in 16-bit character units
+ tsaURL = malloc(6 * length + 1); // pessimistic
+ if (!CFStringGetCString(url, (char *)tsaURL, 6 * length + 1, kCFStringEncodingUTF8))
+ goto xit;
+
+ tsaDebug("[TSA] URL for timestamp server: %s\n", tsaURL);
+
+ unsigned char *tsaResp = NULL;
+ size_t tsaRespLength = 0;
+ tsaDebug("calling sendTSARequestWithXPC with %ld bytes of request\n", tsaReqLength);
+
+ require_noerr(result = sendTSARequestWithXPC(tsaReq, tsaReqLength, tsaURL, &tsaResp, &tsaRespLength), xit);
+
+ tsaDebug("sendTSARequestWithXPC copied: %ld bytes of response\n", tsaRespLength);
+
+ signedDERBlob->Data = tsaResp;
+ signedDERBlob->Length = tsaRespLength;
+
+ result = noErr;
+
+xit:
+ if (tsaURL)
+ free((void *)tsaURL);
+ if (cfreq)
+ CFRelease(cfreq);
+
+ return result;
+}
+
+#pragma mark ----- TimeStamp Verification -----
+
+static OSStatus convertGeneralizedTimeToCFAbsoluteTime(const char *timeStr, CFAbsoluteTime *ptime)
+{
+ /*
+ See http://userguide.icu-project.org/formatparse/datetime for date/time format.
+ The "Z" signal a GMT time, but CFDateFormatterGetAbsoluteTimeFromString returns
+ values based on local time.
+ */
+
+ OSStatus result = noErr;
+ CFDateFormatterRef formatter = NULL;
+ CFStringRef time_string = NULL;
+ CFTimeZoneRef gmt = NULL;
+ CFLocaleRef locale = NULL;
+ CFRange *rangep = NULL;
+
+ require(timeStr && timeStr[0] && ptime, xit);
+ require(formatter = CFDateFormatterCreate(kCFAllocatorDefault, locale, kCFDateFormatterNoStyle, kCFDateFormatterNoStyle), xit);
+// CFRetain(formatter);
+ CFDateFormatterSetFormat(formatter, CFSTR("yyyyMMddHHmmss'Z'")); // GeneralizedTime
+ gmt = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
+ CFDateFormatterSetProperty(formatter, kCFDateFormatterTimeZone, gmt);
+
+ time_string = CFStringCreateWithCString(kCFAllocatorDefault, timeStr, kCFStringEncodingUTF8);
+ if (!time_string || !CFDateFormatterGetAbsoluteTimeFromString(formatter, time_string, rangep, ptime))
+ {
+ dtprintf("%s is not a valid date\n", timeStr);
+ result = 1;
+ }
+
+xit:
+ if (formatter)
+ CFRelease(formatter);
+ if (time_string)
+ CFRelease(time_string);
+ if (gmt)
+ CFRelease(gmt);
+
+ return result;
+}
+
+static OSStatus SecTSAValidateTimestamp(const SecAsn1TSATSTInfo *tstInfo, CSSM_DATA **signingCerts, CFAbsoluteTime *timestampTime)
+{
+ // See <rdar://problem/11077708> Properly handle revocation information of timestamping certificate
+ OSStatus result = paramErr;
+ CFAbsoluteTime genTime = 0;
+ char timeStr[32] = {0,};
+ SecCertificateRef signingCertificate = NULL;
+
+ require(tstInfo && signingCerts && (tstInfo->genTime.Length < 16), xit);
+
+ // Find the leaf signingCert
+ require_noerr(result = SecCertificateCreateFromData(*signingCerts,
+ CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &signingCertificate), xit);
+
+ memcpy(timeStr, tstInfo->genTime.Data, tstInfo->genTime.Length);
+ timeStr[tstInfo->genTime.Length] = 0;
+ require_noerr(convertGeneralizedTimeToCFAbsoluteTime(timeStr, &genTime), xit);
+ if (SecCertificateIsValidX(signingCertificate, genTime)) // iOS?
+ result = noErr;
+ else
+ result = errSecTimestampInvalid;
+ if (timestampTime)
+ *timestampTime = genTime;
+xit:
+ return result;
+}
+
+static OSStatus verifyTSTInfo(const CSSM_DATA_PTR content, CSSM_DATA **signingCerts, SecAsn1TSATSTInfo *tstInfo, CFAbsoluteTime *timestampTime, uint64_t expectedNonce)
+{
+ OSStatus status = paramErr;
+ SecAsn1CoderRef coder = NULL;
+
+ if (!tstInfo)
+ return SECFailure;
+
+ require_noerr(SecAsn1CoderCreate(&coder), xit);
+ require_noerr(SecAsn1Decode(coder, content->Data, content->Length,
+ kSecAsn1TSATSTInfoTemplate, tstInfo), xit);
+ displayTSTInfo(tstInfo);
+
+ // Check the nonce
+ if (tstInfo->nonce.Data && expectedNonce!=0)
+ {
+ uint64_t nonce = tsaDER_ToInt(&tstInfo->nonce);
+ // if (expectedNonce!=nonce)
+ dtprintf("verifyTSTInfo nonce: actual: %lld, expected: %lld\n", nonce, expectedNonce);
+ require_action(expectedNonce==nonce, xit, status = errSecTimestampRejection);
+ }
+
+ status = SecTSAValidateTimestamp(tstInfo, signingCerts, timestampTime);
+ dtprintf("SecTSAValidateTimestamp result: %ld\n", (long)status);
+
+xit:
+ if (coder)
+ SecAsn1CoderRelease(coder);
+ return status;
+}
+
+static void debugShowExtendedTrustResult(int index, CFDictionaryRef extendedResult)
+{
+#ifndef NDEBUG
+ if (extendedResult)
+ {
+ CFStringRef xresStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
+ CFSTR("Extended trust result for signer #%d : %@"), index, extendedResult);
+ if (xresStr)
+ {
+ CFShow(xresStr);
+ CFRelease(xresStr);
+ }
+ }
+#endif
+}
+
+#ifndef NDEBUG
+extern const char *cssmErrorString(CSSM_RETURN error);
+
+static void statusBitTest(CSSM_TP_APPLE_CERT_STATUS certStatus, uint32 bit, const char *str)
+{
+ if (certStatus & bit)
+ dtprintf("%s ", str);
+}
+#endif
+
+static void debugShowCertEvidenceInfo(uint16_t certCount, const CSSM_TP_APPLE_EVIDENCE_INFO *info)
+{
+#ifndef NDEBUG
+ CSSM_TP_APPLE_CERT_STATUS cs;
+// const CSSM_TP_APPLE_EVIDENCE_INFO *pinfo = info;
+ uint16_t ix;
+ for (ix=0; info && (ix<certCount); ix++, ++info)
+ {
+ cs = info->StatusBits;
+ dtprintf(" cert %u:\n", ix);
+ dtprintf(" StatusBits : 0x%x", (unsigned)cs);
+ if (cs)
+ {
+ dtprintf(" ( ");
+ statusBitTest(cs, CSSM_CERT_STATUS_EXPIRED, "EXPIRED");
+ statusBitTest(cs, CSSM_CERT_STATUS_NOT_VALID_YET,
+ "NOT_VALID_YET");
+ statusBitTest(cs, CSSM_CERT_STATUS_IS_IN_INPUT_CERTS,
+ "IS_IN_INPUT_CERTS");
+ statusBitTest(cs, CSSM_CERT_STATUS_IS_IN_ANCHORS,
+ "IS_IN_ANCHORS");
+ statusBitTest(cs, CSSM_CERT_STATUS_IS_ROOT, "IS_ROOT");
+ statusBitTest(cs, CSSM_CERT_STATUS_IS_FROM_NET, "IS_FROM_NET");
+ dtprintf(")\n");
+ }
+ else
+ dtprintf("\n");
+
+ dtprintf(" NumStatusCodes : %u ", info->NumStatusCodes);
+ CSSM_RETURN *pstatuscode = info->StatusCodes;
+ uint16_t jx;
+ for (jx=0; pstatuscode && (jx<info->NumStatusCodes); jx++, ++pstatuscode)
+ dtprintf("%s ", cssmErrorString(*pstatuscode));
+
+ dtprintf("\n");
+ dtprintf(" Index: %u\n", info->Index);
+ }
+
+#endif
+}
+
+#ifndef NDEBUG
+static const char *trustResultTypeString(SecTrustResultType trustResultType)
+{
+ switch (trustResultType)
+ {
+ case kSecTrustResultProceed: return "TrustResultProceed";
+ case kSecTrustResultUnspecified: return "TrustResultUnspecified";
+ case kSecTrustResultDeny: return "TrustResultDeny"; // user reject
+ case kSecTrustResultInvalid: return "TrustResultInvalid";
+ case kSecTrustResultConfirm: return "TrustResultConfirm";
+ case kSecTrustResultRecoverableTrustFailure: return "TrustResultRecoverableTrustFailure";
+ case kSecTrustResultFatalTrustFailure: return "TrustResultUnspecified";
+ case kSecTrustResultOtherError: return "TrustResultOtherError";
+ default: return "TrustResultUnknown";
+ }
+ return "";
+}
+#endif
+
+static OSStatus verifySigners(SecCmsSignedDataRef signedData, int numberOfSigners, CFTypeRef timeStampPolicy)
+{
+ // See <rdar://problem/11077588> Bubble up SecTrustEvaluate of timestamp response to high level callers
+ // Also <rdar://problem/11077708> Properly handle revocation information of timestamping certificate
+
+ CFTypeRef policy = CFRetainSafe(timeStampPolicy);
+ int result=errSecInternalError;
+ int rx;
+
+ if (!policy) {
+ require(policy = SecPolicyCreateWithOID(kSecPolicyAppleTimeStamping), xit);
+ }
+
+ int jx;
+ for (jx = 0; jx < numberOfSigners; ++jx)
+ {
+ SecTrustResultType trustResultType;
+ SecTrustRef trustRef = NULL;
+ CFDictionaryRef extendedResult = NULL;
+ CFArrayRef certChain = NULL;
+ uint16_t certCount = 0;
+
+ CSSM_TP_APPLE_EVIDENCE_INFO *statusChain = NULL;
+
+ // SecCmsSignedDataVerifySignerInfo returns trustRef, which we can call SecTrustEvaluate on
+ // usually (always?) if result is noErr, the SecTrust*Result calls will return errSecTrustNotAvailable
+ result = SecCmsSignedDataVerifySignerInfo (signedData, jx, NULL, policy, &trustRef);
+ dtprintf("[%s] SecCmsSignedDataVerifySignerInfo: result: %d, signer: %d\n",
+ __FUNCTION__, result, jx);
+ require_noerr(result, xit);
+
+ result = SecTrustEvaluate (trustRef, &trustResultType);
+ dtprintf("[%s] SecTrustEvaluate: result: %d, trustResult: %s (%d)\n",
+ __FUNCTION__, result, trustResultTypeString(trustResultType), trustResultType);
+ if (result)
+ goto xit;
+ switch (trustResultType)
+ {
+ case kSecTrustResultProceed:
+ case kSecTrustResultUnspecified:
+ break; // success
+ case kSecTrustResultDeny: // user reject
+ result = errSecTimestampNotTrusted; // SecCmsVSTimestampNotTrusted ?
+ break;
+ case kSecTrustResultInvalid:
+ assert(false); // should never happen
+ result = errSecTimestampNotTrusted; // SecCmsVSTimestampNotTrusted ?
+ break;
+ case kSecTrustResultConfirm:
+ case kSecTrustResultRecoverableTrustFailure:
+ case kSecTrustResultFatalTrustFailure:
+ case kSecTrustResultOtherError:
+ default:
+ {
+ /*
+ There are two "errors" that need to be resolved externally:
+ CSSMERR_TP_CERT_EXPIRED can be OK if the timestamp was made
+ before the TSA chain expired; CSSMERR_TP_CERT_NOT_VALID_YET
+ can happen in the case where the user's clock was set to 0.
+ We don't want to prevent them using apps automatically, so
+ return noErr and let codesign or whover decide.
+ */
+ OSStatus resultCode;
+ require_action(SecTrustGetCssmResultCode(trustRef, &resultCode)==noErr, xit, result = errSecTimestampNotTrusted);
+ result = (resultCode == CSSMERR_TP_CERT_EXPIRED || resultCode == CSSMERR_TP_CERT_NOT_VALID_YET)?noErr:errSecTimestampNotTrusted;
+ }
+ break;
+ }
+
+ rx = SecTrustGetResult(trustRef, &trustResultType, &certChain, &statusChain);
+ dtprintf("[%s] SecTrustGetResult: result: %d, type: %d\n", __FUNCTION__,rx, trustResultType);
+ certCount = certChain?CFArrayGetCount(certChain):0;
+ debugShowCertEvidenceInfo(certCount, statusChain);
+
+ rx = SecTrustCopyExtendedResult(trustRef, &extendedResult);
+ dtprintf("[%s] SecTrustCopyExtendedResult: result: %d\n", __FUNCTION__, rx);
+ if (extendedResult)
+ {
+ debugShowExtendedTrustResult(jx, extendedResult);
+ CFRelease(extendedResult);
+ }
+
+ if (trustRef)
+ CFRelease (trustRef);
+ }
+
+xit:
+ if (policy)
+ CFRelease (policy);
+ return result;
+}
+
+static OSStatus impExpImportCertUnCommon(
+ const CSSM_DATA *cdata,
+ SecKeychainRef importKeychain, // optional
+ CFMutableArrayRef outArray) // optional, append here
+{
+ // The only difference between this and impExpImportCertCommon is that we append to outArray
+ // before attempting to add to the keychain
+ OSStatus status = noErr;
+ SecCertificateRef certRef = NULL;
+
+ require_action(cdata, xit, status = errSecUnsupportedFormat);
+
+ /* Pass kCFAllocatorNull as bytesDeallocator to assure the bytes aren't freed */
+ CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)cdata->Data, (CFIndex)cdata->Length, kCFAllocatorNull);
+ require_action(data, xit, status = errSecUnsupportedFormat);
+
+ certRef = SecCertificateCreateWithData(kCFAllocatorDefault, data);
+ CFRelease(data); /* certRef has its own copy of the data now */
+ if(!certRef) {
+ dtprintf("impExpHandleCert error\n");
+ return errSecUnsupportedFormat;
+ }
+
+ if (outArray)
+ CFArrayAppendValue(outArray, certRef);
+
+ if (importKeychain)
+ {
+ status = SecCertificateAddToKeychain(certRef, importKeychain);
+ if (status!=noErr && status!=errSecDuplicateItem)
+ { dtprintf("SecCertificateAddToKeychain error: %ld\n", (long)status); }
+ }
+
+xit:
+ if (certRef)
+ CFRelease(certRef);
+ return status;
+}
+
+static void saveTSACertificates(CSSM_DATA **signingCerts, CFMutableArrayRef outArray)
+{
+ SecKeychainRef defaultKeychain = NULL;
+ // Don't save certificates in keychain to avoid securityd issues
+// if (SecKeychainCopyDefault(&defaultKeychain))
+// defaultKeychain = NULL;
+
+ unsigned certCount = SecCmsArrayCount((void **)signingCerts);
+ unsigned dex;
+ for (dex=0; dex<certCount; dex++)
+ {
+ OSStatus rx = impExpImportCertUnCommon(signingCerts[dex], defaultKeychain, outArray);
+ if (rx!=noErr && rx!=errSecDuplicateItem)
+ dtprintf("impExpImportCertCommon failed: %ld\n", (long)rx);
+ }
+ if (defaultKeychain)
+ CFRelease(defaultKeychain);
+}
+
+static const char *cfabsoluteTimeToString(CFAbsoluteTime abstime)
+{
+ CFGregorianDate greg = CFAbsoluteTimeGetGregorianDate(abstime, NULL);
+ char str[20];
+ if (19 != snprintf(str, 20, "%4.4d-%2.2d-%2.2d_%2.2d:%2.2d:%2.2d",
+ (int)greg.year, greg.month, greg.day, greg.hour, greg.minute, (int)greg.second))
+ str[0]=0;
+ char *data = (char *)malloc(20);
+ strncpy(data, str, 20);
+ return data;
+}
+
+static OSStatus setTSALeafValidityDates(SecCmsSignerInfoRef signerinfo)
+{
+ OSStatus status = noErr;
+
+ if (!signerinfo->timestampCertList || (CFArrayGetCount(signerinfo->timestampCertList) == 0))
+ return SecCmsVSSigningCertNotFound;
+
+ SecCertificateRef tsaLeaf = (SecCertificateRef)CFArrayGetValueAtIndex(signerinfo->timestampCertList, 0);
+ require_action(tsaLeaf, xit, status = errSecCertificateCannotOperate);
+
+ signerinfo->tsaLeafNotBefore = SecCertificateNotValidBefore(tsaLeaf); /* Start date for Timestamp Authority leaf */
+ signerinfo->tsaLeafNotAfter = SecCertificateNotValidAfter(tsaLeaf); /* Expiration date for Timestamp Authority leaf */
+
+ const char *nbefore = cfabsoluteTimeToString(signerinfo->tsaLeafNotBefore);
+ const char *nafter = cfabsoluteTimeToString(signerinfo->tsaLeafNotAfter);
+ if (nbefore && nafter)
+ {
+ dtprintf("Timestamp Authority leaf valid from %s to %s\n", nbefore, nafter);
+ free((void *)nbefore);free((void *)nafter);
+ }
+
+/*
+ if(at < nb)
+ status = errSecCertificateNotValidYet;
+ else if (at > na)
+ status = errSecCertificateExpired;
+*/
+
+xit:
+ return status;
+}
+
+/*
+ From RFC 3161: Time-Stamp Protocol (TSP),August 2001, APPENDIX B:
+
+ B) The validity of the digital signature may then be verified in the
+ following way:
+
+ 1) The time-stamp token itself MUST be verified and it MUST be
+ verified that it applies to the signature of the signer.
+
+ 2) The date/time indicated by the TSA in the TimeStampToken
+ MUST be retrieved.
+
+ 3) The certificate used by the signer MUST be identified and
+ retrieved.
+
+ 4) The date/time indicated by the TSA MUST be within the
+ validity period of the signer's certificate.
+
+ 5) The revocation information about that certificate, at the
+ date/time of the Time-Stamping operation, MUST be retrieved.
+
+ 6) Should the certificate be revoked, then the date/time of
+ revocation shall be later than the date/time indicated by
+ the TSA.
+
+ If all these conditions are successful, then the digital signature
+ shall be declared as valid.
+
+*/
+
+OSStatus decodeTimeStampToken(SecCmsSignerInfoRef signerinfo, CSSM_DATA_PTR inData, CSSM_DATA_PTR encDigest, uint64_t expectedNonce)
+{
+ return decodeTimeStampTokenWithPolicy(signerinfo, NULL, inData, encDigest, expectedNonce);
+}
+
+OSStatus decodeTimeStampTokenWithPolicy(SecCmsSignerInfoRef signerinfo, CFTypeRef timeStampPolicy, CSSM_DATA_PTR inData, CSSM_DATA_PTR encDigest, uint64_t expectedNonce)
+{
+ /*
+ We update signerinfo with timestamp and tsa certificate chain.
+ encDigest is the original signed blob, which we must hash and compare.
+ inData comes from the unAuthAttr section of the CMS message
+
+ These are set in signerinfo as side effects:
+ timestampTime -
+ timestampCertList
+ */
+
+ SecCmsDecoderRef decoderContext = NULL;
+ SecCmsMessageRef cmsMessage = NULL;
+ SecCmsContentInfoRef contentInfo;
+ SecCmsSignedDataRef signedData;
+ SECOidTag contentTypeTag;
+ int contentLevelCount;
+ int ix;
+ OSStatus result = errSecUnknownFormat;
+ CSSM_DATA **signingCerts = NULL;
+
+ dtprintf("decodeTimeStampToken top: PORT_GetError() %d -----\n", PORT_GetError());
+ PORT_SetError(0);
+
+ /* decode the message */
+ require_noerr(result = SecCmsDecoderCreate (NULL, NULL, NULL, NULL, NULL, NULL, NULL, &decoderContext), xit);
+ result = SecCmsDecoderUpdate(decoderContext, inData->Data, inData->Length);
+ if (result)
+ {
+ result = errSecTimestampInvalid;
+ SecCmsDecoderDestroy(decoderContext);
+ goto xit;
+ }
+
+ require_noerr(result = SecCmsDecoderFinish(decoderContext, &cmsMessage), xit);
+
+ // process the results
+ contentLevelCount = SecCmsMessageContentLevelCount(cmsMessage);
+
+ if (encDigest)
+ printDataAsHex("encDigest",encDigest, 0);
+
+ for (ix = 0; ix < contentLevelCount; ++ix)
+ {
+ dtprintf("\n----- Content Level %d -----\n", ix);
+ // get content information
+ contentInfo = SecCmsMessageContentLevel (cmsMessage, ix);
+ contentTypeTag = SecCmsContentInfoGetContentTypeTag (contentInfo);
+
+ // After 2nd round, contentInfo.content.data is the TSTInfo
+
+ debugShowContentTypeOID(contentInfo);
+
+ switch (contentTypeTag)
+ {
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ {
+ require((signedData = (SecCmsSignedDataRef)SecCmsContentInfoGetContent(contentInfo)) != NULL, xit);
+
+ debugShowSignerInfo(signedData);
+
+ SECAlgorithmID **digestAlgorithms = SecCmsSignedDataGetDigestAlgs(signedData);
+ unsigned digestAlgCount = SecCmsArrayCount((void **)digestAlgorithms);
+ dtprintf("digestAlgCount: %d\n", digestAlgCount);
+ if (signedData->digests)
+ {
+ int jx;
+ char buffer[128];
+ for (jx=0;jx < digestAlgCount;jx++)
+ {
+ sprintf(buffer, " digest[%u]", jx);
+ printDataAsHex(buffer,signedData->digests[jx], 0);
+ }
+ }
+ else
+ {
+ dtprintf("No digests\n");
+ CSSM_DATA_PTR innerContent = SecCmsContentInfoGetInnerContent(contentInfo);
+ if (innerContent)
+ {
+ dtprintf("inner content length: %ld\n", innerContent->Length);
+ SecAsn1TSAMessageImprint fakeMessageImprint = {{{0}},};
+ OSStatus status = createTSAMessageImprint(signedData, innerContent, &fakeMessageImprint);
+ if (status)
+ { dtprintf("createTSAMessageImprint status: %d\n", (int)status); }
+ printDataAsHex("inner content hash",&fakeMessageImprint.hashedMessage, 0);
+ CSSM_DATA_PTR digestdata = &fakeMessageImprint.hashedMessage;
+ CSSM_DATA_PTR digests[2] = {digestdata, NULL};
+ SecCmsSignedDataSetDigests(signedData, digestAlgorithms, (CSSM_DATA_PTR *)&digests);
+ }
+ else
+ dtprintf("no inner content\n");
+ }
+
+ /*
+ Import the certificates. We leave this as a warning, since
+ there are configurations where the certificates are not returned.
+ */
+ signingCerts = SecCmsSignedDataGetCertificateList(signedData);
+ if (signingCerts == NULL)
+ { dtprintf("SecCmsSignedDataGetCertificateList returned NULL\n"); }
+ else
+ {
+ if (!signerinfo->timestampCertList)
+ signerinfo->timestampCertList = CFArrayCreateMutable(kCFAllocatorDefault, 10, &kCFTypeArrayCallBacks);
+ saveTSACertificates(signingCerts, signerinfo->timestampCertList);
+ require_noerr(result = setTSALeafValidityDates(signerinfo), xit);
+ debugSaveCertificates(signingCerts);
+ }
+
+ int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData);
+
+ result = verifySigners(signedData, numberOfSigners, timeStampPolicy);
+ if (result)
+ dtprintf("verifySigners failed: %ld\n", (long)result); // warning
+
+
+ if (result) // remap to SecCmsVSTimestampNotTrusted ?
+ goto xit;
+
+ break;
+ }
+ case SEC_OID_PKCS9_SIGNING_CERTIFICATE:
+ {
+ dtprintf("SEC_OID_PKCS9_SIGNING_CERTIFICATE seen\n");
+ break;
+ }
+
+ case SEC_OID_PKCS9_ID_CT_TSTInfo:
+ {
+ SecAsn1TSATSTInfo tstInfo = {{0},};
+ result = verifyTSTInfo(contentInfo->rawContent, signingCerts, &tstInfo, &signerinfo->timestampTime, expectedNonce);
+ if (signerinfo->timestampTime)
+ {
+ const char *tstamp = cfabsoluteTimeToString(signerinfo->timestampTime);
+ if (tstamp)
+ {
+ dtprintf("Timestamp Authority timestamp: %s\n", tstamp);
+ free((void *)tstamp);
+ }
+ }
+ break;
+ }
+ case SEC_OID_OTHER:
+ {
+ dtprintf("otherContent : %p\n", (char *)SecCmsContentInfoGetContent (contentInfo));
+ break;
+ }
+ default:
+ dtprintf("ContentTypeTag : %x\n", contentTypeTag);
+ break;
+ }
+ }
+xit:
+ if (cmsMessage)
+ SecCmsMessageDestroy(cmsMessage);
+
+ return result;
+}
+
+