/* * 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 #include #include #include */ #include #include #include #include #include #include #include #include "tsaTemplates.h" #include #include #include #include #include #include #include #include #include #include "tsaSupport.h" #include "tsaSupportPriv.h" #include "tsaTemplates.h" #include "cmslocal.h" #include "secoid.h" #include "secitem.h" #include 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 #include #include #define tsaDebug(fmt, ...) \ do { if (true) { \ char buf[64]; \ struct timeval time_now; \ gettimeofday(&time_now, NULL); \ struct tm* time_info = localtime(&time_now.tv_sec); \ strftime(buf, sizeof(buf), "[%Y-%m-%d %H:%M:%S]", time_info); \ fprintf(stderr, "%s " fmt, buf, ## __VA_ARGS__); \ syslog(LOG_ERR, " " fmt, ## __VA_ARGS__); \ } } while (0) #define tsa_secdebug(scope, format...) \ { \ syslog(LOG_NOTICE, format); \ secdebug(scope, format); \ printf(format); \ } #else #define tsaDebug(args...) tsa_secdebug("tsa", ## args) #define tsa_secdebug(scope, format...) \ secdebug(scope, format) #endif #ifndef NDEBUG #define TSTINFO_DEBUG 1 //jch #endif #if TSTINFO_DEBUG #define dtprintf(args...) tsaDebug(args) #else #define dtprintf(args...) #endif #define kHTTPResponseCodeContinue 100 #define kHTTPResponseCodeOK 200 #define kHTTPResponseCodeNoContent 204 #define kHTTPResponseCodeBadRequest 400 #define kHTTPResponseCodeUnauthorized 401 #define kHTTPResponseCodeForbidden 403 #define kHTTPResponseCodeNotFound 404 #define kHTTPResponseCodeConflict 409 #define kHTTPResponseCodeExpectationFailed 417 #define kHTTPResponseCodeServFail 500 #define kHTTPResponseCodeServiceUnavailable 503 #define kHTTPResponseCodeInsufficientStorage 507 #pragma mark ----- Debug/Utilities ----- static OSStatus remapHTTPErrorCodes(OSStatus status) { switch (status) { case kHTTPResponseCodeOK: case kHTTPResponseCodeContinue: return noErr; case kHTTPResponseCodeBadRequest: return errSecTimestampBadRequest; case kHTTPResponseCodeNoContent: case kHTTPResponseCodeUnauthorized: case kHTTPResponseCodeForbidden: case kHTTPResponseCodeNotFound: case kHTTPResponseCodeConflict: case kHTTPResponseCodeExpectationFailed: case kHTTPResponseCodeServFail: case kHTTPResponseCodeInsufficientStorage: case kHTTPResponseCodeServiceUnavailable: return errSecTimestampServiceNotAvailable; default: return status; } return status; } static void printDataAsHex(const char *title, const CSSM_DATA *d, unsigned maxToPrint) // 0 means print it all { #ifndef NDEBUG unsigned i; bool more = false; uint32 len = (uint32)d->Length; uint8 *cp = d->Data; char *buffer = NULL; size_t bufferSize; int offset, sz = 0; const int wrapwid = 24; // large enough so SHA-1 hashes fit on one line... if ((maxToPrint != 0) && (len > maxToPrint)) { len = maxToPrint; more = true; } bufferSize = wrapwid+3*len; buffer = (char *)malloc(bufferSize); offset = sprintf(buffer, "%s [len = %u]\n", title, len); dtprintf("%s", buffer); offset = 0; for (i=0; (i < len) && (offset+3 < bufferSize); i++, offset += sz) { sz = sprintf(buffer + offset, " %02x", (unsigned int)cp[i] & 0xff); if ((i % wrapwid) == (wrapwid-1)) { dtprintf("%s", buffer); offset = 0; sz = 0; } } sz=sprintf(buffer + offset, more?" ...\n":"\n"); offset += sz; buffer[offset+1]=0; // fprintf(stderr, "%s", buffer); dtprintf("%s", buffer); #endif } #ifndef NDEBUG int tsaWriteFileX(const char *fileName, const unsigned char *bytes, size_t numBytes) { int rtn; int fd; fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd <= 0) return errno; rtn = (int)write(fd, bytes, numBytes); if(rtn != (int)numBytes) { if (rtn >= 0) fprintf(stderr, "writeFile: short write\n"); rtn = EIO; } else rtn = 0; close(fd); return rtn; } #endif char *cfStringToChar(CFStringRef inStr) { // Caller must free char *result = NULL; const char *str = NULL; if (!inStr) return strdup(""); // return a null string // quick path first if ((str = CFStringGetCStringPtr(inStr, kCFStringEncodingUTF8))) { result = strdup(str); } else { // need to extract into buffer CFIndex length = CFStringGetLength(inStr); // in 16-bit character units CFIndex bytesToAllocate = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; result = malloc(bytesToAllocate); if (!CFStringGetCString(inStr, result, bytesToAllocate, kCFStringEncodingUTF8)) result[0] = 0; } return result; } /* Oids longer than this are considered invalid. */ #define MAX_OID_SIZE 32 #ifndef NDEBUG /* FIXME: There are other versions of this in SecCertifcate.c and SecCertificateP.c */ static CFStringRef SecDERItemCopyOIDDecimalRepresentation(CFAllocatorRef allocator, const CSSM_OID *oid) { if (oid->Length == 0) return CFSTR(""); if (oid->Length > MAX_OID_SIZE) return CFSTR("Oid too long"); CFMutableStringRef result = CFStringCreateMutable(allocator, 0); // The first two levels are encoded into one byte, since the root levelq // has only 3 nodes (40*x + y). However if x = joint-iso-itu-t(2) then // y may be > 39, so we have to add special-case handling for this. uint32_t x = oid->Data[0] / 40; uint32_t y = oid->Data[0] % 40; if (x > 2) { // Handle special case for large y if x = 2 y += (x - 2) * 40; x = 2; } CFStringAppendFormat(result, NULL, CFSTR("%u.%u"), x, y); uint32_t value = 0; for (x = 1; x < oid->Length; ++x) { value = (value << 7) | (oid->Data[x] & 0x7F); /* @@@ value may not span more than 4 bytes. */ /* A max number of 20 values is allowed. */ if (!(oid->Data[x] & 0x80)) { CFStringAppendFormat(result, NULL, CFSTR(".%lu"), (unsigned long)value); value = 0; } } return result; } #endif static void debugSaveCertificates(CSSM_DATA **outCerts) { #ifndef NDEBUG if (outCerts) { CSSM_DATA_PTR *certp; unsigned jx = 0; const char *certNameBase = "/tmp/tsa-resp-cert-"; char fname[PATH_MAX]; unsigned certCount = SecCmsArrayCount((void **)outCerts); dtprintf("Found %d certs\n",certCount); for (certp=outCerts;*certp;certp++, ++jx) { char numstr[32]; strncpy(fname, certNameBase, strlen(certNameBase)+1); sprintf(numstr,"%u", jx); strcat(fname,numstr); tsaWriteFileX(fname, (*certp)->Data, (*certp)->Length); if (jx > 5) break; //something wrong } } #endif } static void debugShowSignerInfo(SecCmsSignedDataRef signedData) { #ifndef NDEBUG int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData); dtprintf("numberOfSigners : %d\n", numberOfSigners); int ix; for (ix=0;ix < numberOfSigners;ix++) { SecCmsSignerInfoRef sigi = SecCmsSignedDataGetSignerInfo(signedData,ix); if (sigi) { CFStringRef commonName = SecCmsSignerInfoGetSignerCommonName(sigi); const char *signerhdr = " signer : "; if (commonName) { char *cn = cfStringToChar(commonName); dtprintf("%s%s\n", signerhdr, cn); if (cn) free(cn); } else dtprintf("%s\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 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[] = { "", "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 #include #include #include extern const SecAsn1Template kSecAsn1TSATimeStampReqTemplate; extern const SecAsn1Template kSecAsn1TSATimeStampRespTemplateDER; CFMutableDictionaryRef SecCmsTSAGetDefaultContext(CFErrorRef *error) { // Caller responsible for retain/release // Update SecCmsTSAGetDefaultContext with actual URL for Apple Timestamp server // URL will be in TimeStampingPrefs.plist CFBundleRef secFWbundle = NULL; CFURLRef resourceURL = NULL; CFDataRef resourceData = NULL; CFPropertyListRef prefs = NULL; CFMutableDictionaryRef contextDict = NULL; SInt32 errorCode = 0; CFOptionFlags options = 0; CFPropertyListFormat format = 0; OSStatus status = noErr; require_action(secFWbundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")), xit, status = errSecInternalError); require_action(resourceURL = CFBundleCopyResourceURL(secFWbundle, CFSTR("TimeStampingPrefs"), CFSTR("plist"), NULL), xit, status = errSecInvalidPrefsDomain); require(CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, resourceURL, &resourceData, NULL, NULL, &errorCode), xit); require_action(resourceData, xit, status = errSecDataNotAvailable); prefs = CFPropertyListCreateWithData(kCFAllocatorDefault, resourceData, options, &format, error); require_action(prefs && (CFGetTypeID(prefs)==CFDictionaryGetTypeID()), xit, status = errSecInvalidPrefsDomain); contextDict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, prefs); if (error) *error = NULL; xit: if (errorCode) status = errorCode; if (error && status) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL); if (secFWbundle) CFRelease(secFWbundle); if (resourceURL) CFRelease(resourceURL); if (resourceData) CFRelease(resourceData); if (prefs) CFRelease(prefs); return contextDict; } static CFDataRef _SecTSARequestCopyDEREncoding(SecAsn1TSAMessageImprint *messageImprint, bool noCerts, uint64_t nonce) { // Returns DER encoded TimeStampReq // Modeled on _SecOCSPRequestCopyDEREncoding // The Timestamp Authority supports 64 bit nonces (or more possibly) SecAsn1CoderRef coder = NULL; uint8_t version = 1; SecAsn1Item vers = {1, &version}; uint8_t creq = noCerts?0:1; SecAsn1Item certReq = {1, &creq}; //jch - to request or not? SecAsn1TSATimeStampReq tsreq = {}; CFDataRef der = NULL; uint64_t nonceVal = OSSwapHostToBigConstInt64(nonce); SecAsn1Item nonceItem = {sizeof(uint64_t), (unsigned char *)&nonceVal}; uint8_t OID_FakePolicy_Data[] = { 0x2A, 0x03, 0x04, 0x05, 0x06}; const CSSM_OID fakePolicyOID = {sizeof(OID_FakePolicy_Data),OID_FakePolicy_Data}; tsreq.version = vers; tsreq.messageImprint = *messageImprint; tsreq.certReq = certReq; // skip reqPolicy, extensions for now - FAKES - jch tsreq.reqPolicy = fakePolicyOID; //policyID; tsreq.nonce = nonceItem; // Encode the request require_noerr(SecAsn1CoderCreate(&coder), errOut); SecAsn1Item encoded; require_noerr(SecAsn1EncodeItem(coder, &tsreq, &kSecAsn1TSATimeStampReqTemplate, &encoded), errOut); der = CFDataCreate(kCFAllocatorDefault, encoded.Data, encoded.Length); errOut: if (coder) SecAsn1CoderRelease(coder); return der; } OSStatus SecTSAResponseCopyDEREncoding(SecAsn1CoderRef coder, const CSSM_DATA *tsaResponse, SecAsn1TimeStampRespDER *respDER) { // Partially decode the response OSStatus status = paramErr; require(tsaResponse && respDER, errOut); require_noerr(SecAsn1DecodeData(coder, tsaResponse, &kSecAsn1TSATimeStampRespTemplateDER, respDER), errOut); status = noErr; errOut: return status; } #pragma mark ----- TS Callback ----- OSStatus SecCmsTSADefaultCallback(CFTypeRef context, void *messageImprintV, uint64_t nonce, CSSM_DATA *signedDERBlob) { OSStatus result = paramErr; const unsigned char *tsaReq = NULL; size_t tsaReqLength = 0; CFDataRef cfreq = NULL; unsigned char *tsaURL = NULL; bool noCerts = false; if (!context || CFGetTypeID(context)!=CFDictionaryGetTypeID()) return paramErr; SecAsn1TSAMessageImprint *messageImprint = (SecAsn1TSAMessageImprint *)messageImprintV; if (!messageImprint || !signedDERBlob) return paramErr; CFBooleanRef cfnocerts = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSAContextKeyNoCerts); if (cfnocerts) { tsaDebug("[TSA] Request noCerts\n"); noCerts = CFBooleanGetValue(cfnocerts); } // We must spoof the nonce here, before sending the request. // If we tried to alter the reply, then the signature would break instead. CFBooleanRef cfBadNonce = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSADebugContextKeyBadNonce); if (cfBadNonce && CFBooleanGetValue(cfBadNonce)) { tsaDebug("[TSA] Forcing bad TS Request by changing nonce\n"); nonce++; } printDataAsHex("[TSA] hashToTimeStamp:", &messageImprint->hashedMessage,128); cfreq = _SecTSARequestCopyDEREncoding(messageImprint, noCerts, nonce); if (cfreq) { tsaReq = CFDataGetBytePtr(cfreq); tsaReqLength = CFDataGetLength(cfreq); #ifndef NDEBUG CFShow(cfreq); tsaWriteFileX("/tmp/tsareq.req", tsaReq, tsaReqLength); #endif } CFStringRef url = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSAContextKeyURL); if (!url) { tsaDebug("[TSA] missing URL for TSA (key: %s)\n", "kTSAContextKeyURL"); goto xit; } /* If debugging, look at special values in the context to mess things up */ CFBooleanRef cfBadReq = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSADebugContextKeyBadReq); if (cfBadReq && CFBooleanGetValue(cfBadReq)) { tsaDebug("[TSA] Forcing bad TS Request by truncating length from %ld to %ld\n", tsaReqLength, (tsaReqLength-4)); tsaReqLength -= 4; } // need to extract into buffer CFIndex length = CFStringGetLength(url); // in 16-bit character units tsaURL = malloc(6 * length + 1); // pessimistic if (!CFStringGetCString(url, (char *)tsaURL, 6 * length + 1, kCFStringEncodingUTF8)) goto xit; tsaDebug("[TSA] URL for timestamp server: %s\n", tsaURL); unsigned char *tsaResp = NULL; size_t tsaRespLength = 0; tsaDebug("calling sendTSARequestWithXPC with %ld bytes of request\n", tsaReqLength); require_noerr(result = sendTSARequestWithXPC(tsaReq, tsaReqLength, tsaURL, &tsaResp, &tsaRespLength), xit); tsaDebug("sendTSARequestWithXPC copied: %ld bytes of response\n", tsaRespLength); signedDERBlob->Data = tsaResp; signedDERBlob->Length = tsaRespLength; result = noErr; xit: if (tsaURL) free((void *)tsaURL); if (cfreq) CFRelease(cfreq); return result; } #pragma mark ----- TimeStamp Verification ----- static OSStatus convertGeneralizedTimeToCFAbsoluteTime(const char *timeStr, CFAbsoluteTime *ptime) { /* See http://userguide.icu-project.org/formatparse/datetime for date/time format. The "Z" signal a GMT time, but CFDateFormatterGetAbsoluteTimeFromString returns values based on local time. */ OSStatus result = noErr; CFDateFormatterRef formatter = NULL; CFStringRef time_string = NULL; CFTimeZoneRef gmt = NULL; CFLocaleRef locale = NULL; CFRange *rangep = NULL; require(timeStr && timeStr[0] && ptime, xit); require(formatter = CFDateFormatterCreate(kCFAllocatorDefault, locale, kCFDateFormatterNoStyle, kCFDateFormatterNoStyle), xit); // CFRetain(formatter); CFDateFormatterSetFormat(formatter, CFSTR("yyyyMMddHHmmss'Z'")); // GeneralizedTime gmt = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0); CFDateFormatterSetProperty(formatter, kCFDateFormatterTimeZone, gmt); time_string = CFStringCreateWithCString(kCFAllocatorDefault, timeStr, kCFStringEncodingUTF8); if (!time_string || !CFDateFormatterGetAbsoluteTimeFromString(formatter, time_string, rangep, ptime)) { dtprintf("%s is not a valid date\n", timeStr); result = 1; } xit: if (formatter) CFRelease(formatter); if (time_string) CFRelease(time_string); if (gmt) CFRelease(gmt); return result; } static OSStatus SecTSAValidateTimestamp(const SecAsn1TSATSTInfo *tstInfo, CSSM_DATA **signingCerts, CFAbsoluteTime *timestampTime) { // See Properly handle revocation information of timestamping certificate OSStatus result = paramErr; CFAbsoluteTime genTime = 0; char timeStr[32] = {0,}; SecCertificateRef signingCertificate = NULL; require(tstInfo && signingCerts && (tstInfo->genTime.Length < 16), xit); // Find the leaf signingCert require_noerr(result = SecCertificateCreateFromData(*signingCerts, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &signingCertificate), xit); memcpy(timeStr, tstInfo->genTime.Data, tstInfo->genTime.Length); timeStr[tstInfo->genTime.Length] = 0; require_noerr(convertGeneralizedTimeToCFAbsoluteTime(timeStr, &genTime), xit); if (SecCertificateIsValidX(signingCertificate, genTime)) // iOS? result = noErr; else result = errSecTimestampInvalid; if (timestampTime) *timestampTime = genTime; xit: return result; } static OSStatus verifyTSTInfo(const CSSM_DATA_PTR content, CSSM_DATA **signingCerts, SecAsn1TSATSTInfo *tstInfo, CFAbsoluteTime *timestampTime, uint64_t expectedNonce) { OSStatus status = paramErr; SecAsn1CoderRef coder = NULL; if (!tstInfo) return SECFailure; require_noerr(SecAsn1CoderCreate(&coder), xit); require_noerr(SecAsn1Decode(coder, content->Data, content->Length, kSecAsn1TSATSTInfoTemplate, tstInfo), xit); displayTSTInfo(tstInfo); // Check the nonce if (tstInfo->nonce.Data && expectedNonce!=0) { uint64_t nonce = tsaDER_ToInt(&tstInfo->nonce); // if (expectedNonce!=nonce) dtprintf("verifyTSTInfo nonce: actual: %lld, expected: %lld\n", nonce, expectedNonce); require_action(expectedNonce==nonce, xit, status = errSecTimestampRejection); } status = SecTSAValidateTimestamp(tstInfo, signingCerts, timestampTime); dtprintf("SecTSAValidateTimestamp result: %ld\n", (long)status); xit: if (coder) SecAsn1CoderRelease(coder); return status; } static void debugShowExtendedTrustResult(int index, CFDictionaryRef extendedResult) { #ifndef NDEBUG if (extendedResult) { CFStringRef xresStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("Extended trust result for signer #%d : %@"), index, extendedResult); if (xresStr) { CFShow(xresStr); CFRelease(xresStr); } } #endif } #ifndef NDEBUG extern const char *cssmErrorString(CSSM_RETURN error); static void statusBitTest(CSSM_TP_APPLE_CERT_STATUS certStatus, uint32 bit, const char *str) { if (certStatus & bit) dtprintf("%s ", str); } #endif static void debugShowCertEvidenceInfo(uint16_t certCount, const CSSM_TP_APPLE_EVIDENCE_INFO *info) { #ifndef NDEBUG CSSM_TP_APPLE_CERT_STATUS cs; // const CSSM_TP_APPLE_EVIDENCE_INFO *pinfo = info; uint16_t ix; for (ix=0; info && (ixStatusBits; 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 && (jxNumStatusCodes); 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 Bubble up SecTrustEvaluate of timestamp response to high level callers // Also Properly handle revocation information of timestamping certificate CFTypeRef policy = CFRetainSafe(timeStampPolicy); int result=errSecInternalError; int rx; if (!policy) { require(policy = SecPolicyCreateWithOID(kSecPolicyAppleTimeStamping), xit); } int jx; for (jx = 0; jx < numberOfSigners; ++jx) { SecTrustResultType trustResultType; SecTrustRef trustRef = NULL; CFDictionaryRef extendedResult = NULL; CFArrayRef certChain = NULL; uint16_t certCount = 0; CSSM_TP_APPLE_EVIDENCE_INFO *statusChain = NULL; // SecCmsSignedDataVerifySignerInfo returns trustRef, which we can call SecTrustEvaluate on // usually (always?) if result is noErr, the SecTrust*Result calls will return errSecTrustNotAvailable result = SecCmsSignedDataVerifySignerInfo (signedData, jx, NULL, policy, &trustRef); dtprintf("[%s] SecCmsSignedDataVerifySignerInfo: result: %d, signer: %d\n", __FUNCTION__, result, jx); require_noerr(result, xit); result = SecTrustEvaluate (trustRef, &trustResultType); dtprintf("[%s] SecTrustEvaluate: result: %d, trustResult: %s (%d)\n", __FUNCTION__, result, trustResultTypeString(trustResultType), trustResultType); if (result) goto xit; switch (trustResultType) { case kSecTrustResultProceed: case kSecTrustResultUnspecified: break; // success case kSecTrustResultDeny: // user reject result = errSecTimestampNotTrusted; // SecCmsVSTimestampNotTrusted ? break; case kSecTrustResultInvalid: assert(false); // should never happen result = errSecTimestampNotTrusted; // SecCmsVSTimestampNotTrusted ? break; case kSecTrustResultConfirm: case kSecTrustResultRecoverableTrustFailure: case kSecTrustResultFatalTrustFailure: case kSecTrustResultOtherError: default: { /* There are two "errors" that need to be resolved externally: CSSMERR_TP_CERT_EXPIRED can be OK if the timestamp was made before the TSA chain expired; CSSMERR_TP_CERT_NOT_VALID_YET can happen in the case where the user's clock was set to 0. We don't want to prevent them using apps automatically, so return noErr and let codesign or whover decide. */ OSStatus resultCode; require_action(SecTrustGetCssmResultCode(trustRef, &resultCode)==noErr, xit, result = errSecTimestampNotTrusted); result = (resultCode == CSSMERR_TP_CERT_EXPIRED || resultCode == CSSMERR_TP_CERT_NOT_VALID_YET)?noErr:errSecTimestampNotTrusted; } break; } rx = SecTrustGetResult(trustRef, &trustResultType, &certChain, &statusChain); dtprintf("[%s] SecTrustGetResult: result: %d, type: %d\n", __FUNCTION__,rx, trustResultType); certCount = certChain?CFArrayGetCount(certChain):0; debugShowCertEvidenceInfo(certCount, statusChain); rx = SecTrustCopyExtendedResult(trustRef, &extendedResult); dtprintf("[%s] SecTrustCopyExtendedResult: result: %d\n", __FUNCTION__, rx); if (extendedResult) { debugShowExtendedTrustResult(jx, extendedResult); CFRelease(extendedResult); } if (trustRef) CFRelease (trustRef); } xit: if (policy) CFRelease (policy); return result; } static OSStatus impExpImportCertUnCommon( const CSSM_DATA *cdata, SecKeychainRef importKeychain, // optional CFMutableArrayRef outArray) // optional, append here { // The only difference between this and impExpImportCertCommon is that we append to outArray // before attempting to add to the keychain OSStatus status = noErr; SecCertificateRef certRef = NULL; require_action(cdata, xit, status = errSecUnsupportedFormat); /* Pass kCFAllocatorNull as bytesDeallocator to assure the bytes aren't freed */ CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)cdata->Data, (CFIndex)cdata->Length, kCFAllocatorNull); require_action(data, xit, status = errSecUnsupportedFormat); certRef = SecCertificateCreateWithData(kCFAllocatorDefault, data); CFRelease(data); /* certRef has its own copy of the data now */ if(!certRef) { dtprintf("impExpHandleCert error\n"); return errSecUnsupportedFormat; } if (outArray) CFArrayAppendValue(outArray, certRef); if (importKeychain) { status = SecCertificateAddToKeychain(certRef, importKeychain); if (status!=noErr && status!=errSecDuplicateItem) { dtprintf("SecCertificateAddToKeychain error: %ld\n", (long)status); } } xit: if (certRef) CFRelease(certRef); return status; } static void saveTSACertificates(CSSM_DATA **signingCerts, CFMutableArrayRef outArray) { SecKeychainRef defaultKeychain = NULL; // Don't save certificates in keychain to avoid securityd issues // if (SecKeychainCopyDefault(&defaultKeychain)) // defaultKeychain = NULL; unsigned certCount = SecCmsArrayCount((void **)signingCerts); unsigned dex; for (dex=0; dextimestampCertList || (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; }