tsaSupport.c   [plain text]


/*
 * Copyright (c) 2012-2016 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/SecBasePriv.h>
#include <Security/SecPolicy.h>
#include <Security/SecTrustPriv.h>
#include <Security/SecImportExport.h>
#include <Security/SecCertificatePriv.h>
#include <utilities/SecCFRelease.h>
#include <utilities/SecDispatchRelease.h>
#include <utilities/debugging.h>

#include "tsaSupport.h"
#include "tsaSupportPriv.h"
#include "tsaTemplates.h"
#include "cmslocal.h"
#include "cert.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_secinfo(scope, format...) \
    { \
        syslog(LOG_NOTICE, format); \
        secinfo(scope, format); \
        printf(format); \
    }
#else
    #define tsaDebug(args...)			tsa_secinfo("tsa", ## args)
#define tsa_secinfo(scope, format...) \
        secinfo(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\n", buffer);
            offset = 0;
            sz = 0;
        }
    }

    sz=sprintf(buffer + offset, more?" ...\n":"\n");
    offset += sz;
    buffer[offset+1]=0;

    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 == -1) {
        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_safe(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); }
        if (waitSemaphore) { dispatch_semaphore_signal(waitSemaphore); }
        dispatch_release_null(waitSemaphore);
    });
    { tsaDebug("waiting up to %d seconds for response from XPC\n", timeoutInSeconds); }
    if (waitSemaphore) { dispatch_semaphore_wait(waitSemaphore, finishTime); }

    dispatch_release_null(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
    }

    CFTypeRef url = CFDictionaryGetValue((CFDictionaryRef)context, kTSAContextKeyURL);
    if (!url)
    {
        tsaDebug("[TSA] missing URL for TSA (key: %s)\n", "kTSAContextKeyURL");
        goto xit;
    }

    CFStringRef urlStr = NULL;
    if (CFURLGetTypeID() == CFGetTypeID(url)) {
        urlStr = CFURLGetString(url);
    } else {
        require_quiet(CFStringGetTypeID() == CFGetTypeID(url), xit);
        urlStr = url;
    }
    require_quiet(urlStr, 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(urlStr);        // in 16-bit character units
    tsaURL = malloc(6 * length + 1);                // pessimistic
    if (!CFStringGetCString(urlStr, (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, SecCertificateRef signerCert, CFAbsoluteTime *timestampTime)
{
    // See <rdar://problem/11077708> Properly handle revocation information of timestamping certificate
    OSStatus result = paramErr;
    CFAbsoluteTime genTime = 0;
    char timeStr[32] = {0,};

    require(tstInfo && signerCert && (tstInfo->genTime.Length < 16), xit);;

    memcpy(timeStr, tstInfo->genTime.Data, tstInfo->genTime.Length);
    timeStr[tstInfo->genTime.Length] = 0;
    require_noerr(convertGeneralizedTimeToCFAbsoluteTime(timeStr, &genTime), xit);
    if (SecCertificateIsValidX(signerCert, genTime)) // iOS?
        result = noErr;
    else
        result = errSecTimestampInvalid;
    if (timestampTime)
        *timestampTime = genTime;
xit:
    return result;
}

static OSStatus verifyTSTInfo(const CSSM_DATA_PTR content, SecCmsSignerInfoRef signerinfo, SecAsn1TSATSTInfo *tstInfo, CFAbsoluteTime *timestampTime, uint64_t expectedNonce, CSSM_DATA_PTR encDigest)
{
    OSStatus status = paramErr;
    SecAsn1CoderRef coder = NULL;

    if (!tstInfo)
        return SECFailure;

    SecCertificateRef signerCert = SecCmsSignerInfoGetTimestampSigningCert(signerinfo);
    SecAsn1TSAMessageImprint expectedMessageImprint;

    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);
    }

    // Check the times in the timestamp
    require_noerr(status = SecTSAValidateTimestamp(tstInfo, signerCert, timestampTime), xit);
    dtprintf("SecTSAValidateTimestamp result: %ld\n", (long)status);

    // Check the message imprint against the encDigest from the signerInfo containing this timestamp
    SECOidTag hashAlg = SECOID_GetAlgorithmTag(&tstInfo->messageImprint.hashAlgorithm);
    require_action(hashAlg == SEC_OID_SHA256 || hashAlg == SEC_OID_SHA1, xit, status = errSecInvalidDigestAlgorithm);
    require_noerr(status = createTSAMessageImprint(signerinfo, &tstInfo->messageImprint.hashAlgorithm,
                                                   encDigest, &expectedMessageImprint), xit);
    require_action(CERT_CompareCssmData(&expectedMessageImprint.hashedMessage, &tstInfo->messageImprint.hashedMessage), xit,
                   status = errSecTimestampInvalid; secerror("Timestamp MessageImprint did not match the signature's hash"));

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 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 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)
{
    SecCertificateRef tsaLeaf = SecCmsSignerInfoGetSigningCertificate(signerinfo, NULL);

    if (!tsaLeaf)
        return SecCmsVSSigningCertNotFound;

    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);
    }

    return errSecSuccess;
}

/*
    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
        timestampCert
     */

    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("digests not yet computed\n");
                    CSSM_DATA_PTR innerContent = SecCmsContentInfoGetInnerContent(contentInfo);
                    if (innerContent)
                    {
                        dtprintf("inner content length: %ld\n", innerContent->Length);
                        SecAsn1TSAMessageImprint fakeMessageImprint = {{{0}},};
                        SecCmsSignerInfoRef tsaSigner = SecCmsSignedDataGetSignerInfo(signedData, 0);
                        OSStatus status = createTSAMessageImprint(tsaSigner, &tsaSigner->digestAlg, innerContent, &fakeMessageImprint);
                        require_noerr_action(status, xit, dtprintf("createTSAMessageImprint status: %d\n", (int)status); result = status);
                        printDataAsHex("inner content hash",&fakeMessageImprint.hashedMessage, 0);
                        CSSM_DATA_PTR digestdata = &fakeMessageImprint.hashedMessage;
                        CSSM_DATA_PTR digests[2] = {digestdata, NULL};
                        status = SecCmsSignedDataSetDigests(signedData, digestAlgorithms, (CSSM_DATA_PTR *)&digests);
                        require_noerr_action(status, xit, dtprintf("createTSAMessageImprint status: %d\n", (int)status); result = status);
                    } 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);

                if (numberOfSigners > 0) {
                    /* @@@ assume there's only one signer since SecCms can't handle multiple signers anyway */
                    signerinfo->timestampCert = CFRetainSafe(SecCmsSignerInfoGetSigningCertificate(signedData->signerInfos[0], NULL));
                }

                result = verifySigners(signedData, numberOfSigners, timeStampPolicy);
                if (result) {
                    dtprintf("verifySigners failed: %ld\n", (long)result);   // warning
                    goto xit; // remap to SecCmsVSTimestampNotTrusted ?
                }

                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, signerinfo, &tstInfo, &signerinfo->timestampTime, expectedNonce, encDigest);
                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;
}