]> git.saurik.com Git - apple/security.git/blobdiff - OSX/include/security_smime/tsaSupport.c
Security-57336.1.9.tar.gz
[apple/security.git] / OSX / include / security_smime / tsaSupport.c
diff --git a/OSX/include/security_smime/tsaSupport.c b/OSX/include/security_smime/tsaSupport.c
new file mode 100644 (file)
index 0000000..46c3eee
--- /dev/null
@@ -0,0 +1,1412 @@
+/*
+ * 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);
+
+    free(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);
+                CFReleaseNull(commonName);
+            }
+            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);
+    CFRetain(secFWbundle);
+
+    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:
+    if (signingCertificate)
+        CFReleaseNull(signingCertificate);
+    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
+
+    SecTrustRef trustRef = NULL;
+    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;
+        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);
+        CFReleaseNull(certChain);
+
+        rx = SecTrustCopyExtendedResult(trustRef, &extendedResult);
+        dtprintf("[%s] SecTrustCopyExtendedResult: result: %d\n", __FUNCTION__, rx);
+        if (extendedResult)
+        {
+            debugShowExtendedTrustResult(jx, extendedResult);
+            CFRelease(extendedResult);
+        }
+
+        if (trustRef)
+            CFReleaseNull(trustRef);
+     }
+
+xit:
+    if (trustRef)
+        CFReleaseNull(trustRef);
+    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;
+}
+
+