#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 <utilities/SecDispatchRelease.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, CFMutableArrayRef outArray);
#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) {
#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;
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;
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)
{
char *result = NULL;
const char *str = NULL;
if (!inStr)
return strdup("");
if ((str = CFStringGetCStringPtr(inStr, kCFStringEncodingUTF8))) {
result = strdup(str);
} else {
CFIndex length = CFStringGetLength(inStr); CFIndex bytesToAllocate = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
result = malloc(bytesToAllocate);
if (!CFStringGetCString(inStr, result, bytesToAllocate, kCFStringEncodingUTF8))
result[0] = 0;
}
return result;
}
#define MAX_OID_SIZE 32
#ifndef NDEBUG
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);
uint32_t x = oid->Data[0] / 40;
uint32_t y = oid->Data[0] % 40;
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);
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; }
}
#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) {
printDataAsHex(" Algorithm:",&tstInfo->messageImprint.hashAlgorithm.algorithm, 0);
printDataAsHex(" Message :", &tstInfo->messageImprint.hashedMessage, 0); }
#endif
}
#pragma mark ----- TimeStamp Response using XPC -----
#include <xpc/private.h>
static OSStatus checkForNonDERResponse(const unsigned char *resp, size_t respLen)
{
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);
if ((respLen > 1) && (memcmp(resp, ader, 2)==0)) return noErr;
badResponseCount = sizeof(badResponses)/sizeof(char *);
int ix;
for (ix = 0; ix < badResponseCount; ++ix)
if (strlen(badResponses[ix]) > maxlen)
maxlen = strlen(badResponses[ix]);
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
#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)
{
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)
{
SecAsn1CoderRef coder = NULL;
uint8_t version = 1;
SecAsn1Item vers = {1, &version};
uint8_t creq = noCerts?0:1;
SecAsn1Item certReq = {1, &creq}; 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;
tsreq.reqPolicy = fakePolicyOID;
tsreq.nonce = nonceItem;
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)
{
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);
}
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);
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;
}
CFIndex length = CFStringGetLength(urlStr); tsaURL = malloc(6 * length + 1); 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)
{
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);
CFDateFormatterSetFormat(formatter, CFSTR("yyyyMMddHHmmss'Z'")); 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)
{
OSStatus result = paramErr;
CFAbsoluteTime genTime = 0;
char timeStr[32] = {0,};
SecCertificateRef signingCertificate = NULL;
require(tstInfo && signingCerts && (tstInfo->genTime.Length < 16), xit);
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)) 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);
if (tstInfo->nonce.Data && expectedNonce!=0)
{
uint64_t nonce = tsaDER_ToInt(&tstInfo->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;
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"; 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)
{
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;
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; case kSecTrustResultDeny: result = errSecTimestampNotTrusted; break;
case kSecTrustResultInvalid:
assert(false); result = errSecTimestampNotTrusted; break;
case kSecTrustResultConfirm:
case kSecTrustResultRecoverableTrustFailure:
case kSecTrustResultFatalTrustFailure:
case kSecTrustResultOtherError:
default:
{
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, CFMutableArrayRef outArray) {
OSStatus status = noErr;
SecCertificateRef certRef = NULL;
require_action(cdata, xit, status = errSecUnsupportedFormat);
CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)cdata->Data, (CFIndex)cdata->Length, kCFAllocatorNull);
require_action(data, xit, status = errSecUnsupportedFormat);
certRef = SecCertificateCreateWithData(kCFAllocatorDefault, data);
CFRelease(data);
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;
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);
signerinfo->tsaLeafNotAfter = SecCertificateNotValidAfter(tsaLeaf);
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);
}
xit:
return status;
}
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)
{
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);
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);
contentLevelCount = SecCmsMessageContentLevelCount(cmsMessage);
if (encDigest)
printDataAsHex("encDigest",encDigest, 0);
for (ix = 0; ix < contentLevelCount; ++ix)
{
dtprintf("\n----- Content Level %d -----\n", ix);
contentInfo = SecCmsMessageContentLevel (cmsMessage, ix);
contentTypeTag = SecCmsContentInfoGetContentTypeTag (contentInfo);
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");
}
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);
if (result) 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;
}