]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
8a50f688 | 2 | * Copyright (c) 2012-2016 Apple Inc. All Rights Reserved. |
427c49bc | 3 | * |
b1ab9ed8 | 4 | * @APPLE_LICENSE_HEADER_START@ |
d8f41ccd | 5 | * |
b1ab9ed8 A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
d8f41ccd | 12 | * |
b1ab9ed8 A |
13 | * The Original Code and all software distributed under the License are |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
d8f41ccd | 20 | * |
b1ab9ed8 A |
21 | * @APPLE_LICENSE_HEADER_END@ |
22 | * | |
23 | * tsaSupport.c - ASN1 templates Time Stamping Authority requests and responses | |
24 | */ | |
25 | ||
26 | /* | |
27 | #include <Security/SecCmsDigestContext.h> | |
28 | #include <Security/SecCmsMessage.h> | |
29 | #include <security_asn1/secasn1.h> | |
30 | #include <security_asn1/secerr.h> | |
31 | */ | |
32 | ||
33 | #include <security_utilities/debugging.h> | |
34 | #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h> | |
35 | ||
36 | #include <Security/SecCmsDecoder.h> | |
37 | #include <Security/SecCmsMessage.h> | |
38 | #include <Security/SecCmsContentInfo.h> | |
39 | #include <Security/SecCmsSignedData.h> | |
40 | #include <Security/SecCmsSignerInfo.h> | |
41 | #include "tsaTemplates.h" | |
42 | #include <Security/SecAsn1Coder.h> | |
43 | #include <AssertMacros.h> | |
8a50f688 | 44 | #include <Security/SecBasePriv.h> |
b1ab9ed8 A |
45 | #include <Security/SecPolicy.h> |
46 | #include <Security/SecTrustPriv.h> | |
47 | #include <Security/SecImportExport.h> | |
48 | #include <Security/SecCertificatePriv.h> | |
d8f41ccd | 49 | #include <utilities/SecCFRelease.h> |
fa7225c8 | 50 | #include <utilities/SecDispatchRelease.h> |
79b9da22 | 51 | #include <utilities/debugging.h> |
b1ab9ed8 A |
52 | |
53 | #include "tsaSupport.h" | |
54 | #include "tsaSupportPriv.h" | |
55 | #include "tsaTemplates.h" | |
56 | #include "cmslocal.h" | |
79b9da22 | 57 | #include "cert.h" |
b1ab9ed8 A |
58 | |
59 | #include "secoid.h" | |
60 | #include "secitem.h" | |
61 | #include <fcntl.h> | |
62 | ||
63 | const CFStringRef kTSAContextKeyURL = CFSTR("ServerURL"); | |
64 | const CFStringRef kTSAContextKeyNoCerts = CFSTR("NoCerts"); | |
65 | const CFStringRef kTSADebugContextKeyBadReq = CFSTR("DebugBadReq"); | |
66 | const CFStringRef kTSADebugContextKeyBadNonce = CFSTR("DebugBadNonce"); | |
67 | ||
68 | extern const SecAsn1Template kSecAsn1TSATSTInfoTemplate[]; | |
69 | ||
70 | extern OSStatus impExpImportCertCommon( | |
71 | const CSSM_DATA *cdata, | |
72 | SecKeychainRef importKeychain, // optional | |
427c49bc | 73 | CFMutableArrayRef outArray); // optional, append here |
b1ab9ed8 A |
74 | |
75 | #pragma mark ----- Debug Logs ----- | |
76 | ||
77 | #ifndef NDEBUG | |
78 | #define TSA_USE_SYSLOG 1 | |
79 | #endif | |
80 | ||
81 | #if TSA_USE_SYSLOG | |
82 | #include <syslog.h> | |
83 | #include <time.h> | |
84 | #include <sys/time.h> | |
85 | #define tsaDebug(fmt, ...) \ | |
86 | do { if (true) { \ | |
87 | char buf[64]; \ | |
88 | struct timeval time_now; \ | |
89 | gettimeofday(&time_now, NULL); \ | |
90 | struct tm* time_info = localtime(&time_now.tv_sec); \ | |
91 | strftime(buf, sizeof(buf), "[%Y-%m-%d %H:%M:%S]", time_info); \ | |
92 | fprintf(stderr, "%s " fmt, buf, ## __VA_ARGS__); \ | |
93 | syslog(LOG_ERR, " " fmt, ## __VA_ARGS__); \ | |
94 | } } while (0) | |
fa7225c8 | 95 | #define tsa_secinfo(scope, format...) \ |
b1ab9ed8 A |
96 | { \ |
97 | syslog(LOG_NOTICE, format); \ | |
fa7225c8 | 98 | secinfo(scope, format); \ |
b1ab9ed8 A |
99 | printf(format); \ |
100 | } | |
101 | #else | |
fa7225c8 A |
102 | #define tsaDebug(args...) tsa_secinfo("tsa", ## args) |
103 | #define tsa_secinfo(scope, format...) \ | |
104 | secinfo(scope, format) | |
b1ab9ed8 A |
105 | #endif |
106 | ||
107 | #ifndef NDEBUG | |
108 | #define TSTINFO_DEBUG 1 //jch | |
109 | #endif | |
110 | ||
111 | #if TSTINFO_DEBUG | |
112 | #define dtprintf(args...) tsaDebug(args) | |
113 | #else | |
114 | #define dtprintf(args...) | |
115 | #endif | |
116 | ||
117 | #define kHTTPResponseCodeContinue 100 | |
118 | #define kHTTPResponseCodeOK 200 | |
119 | #define kHTTPResponseCodeNoContent 204 | |
120 | #define kHTTPResponseCodeBadRequest 400 | |
121 | #define kHTTPResponseCodeUnauthorized 401 | |
122 | #define kHTTPResponseCodeForbidden 403 | |
123 | #define kHTTPResponseCodeNotFound 404 | |
124 | #define kHTTPResponseCodeConflict 409 | |
125 | #define kHTTPResponseCodeExpectationFailed 417 | |
126 | #define kHTTPResponseCodeServFail 500 | |
127 | #define kHTTPResponseCodeServiceUnavailable 503 | |
128 | #define kHTTPResponseCodeInsufficientStorage 507 | |
129 | ||
130 | #pragma mark ----- Debug/Utilities ----- | |
131 | ||
132 | static OSStatus remapHTTPErrorCodes(OSStatus status) | |
133 | { | |
134 | switch (status) | |
135 | { | |
136 | case kHTTPResponseCodeOK: | |
137 | case kHTTPResponseCodeContinue: | |
138 | return noErr; | |
139 | case kHTTPResponseCodeBadRequest: | |
140 | return errSecTimestampBadRequest; | |
141 | case kHTTPResponseCodeNoContent: | |
142 | case kHTTPResponseCodeUnauthorized: | |
143 | case kHTTPResponseCodeForbidden: | |
144 | case kHTTPResponseCodeNotFound: | |
145 | case kHTTPResponseCodeConflict: | |
146 | case kHTTPResponseCodeExpectationFailed: | |
147 | case kHTTPResponseCodeServFail: | |
148 | case kHTTPResponseCodeInsufficientStorage: | |
149 | case kHTTPResponseCodeServiceUnavailable: | |
150 | return errSecTimestampServiceNotAvailable; | |
151 | default: | |
152 | return status; | |
153 | } | |
154 | return status; | |
427c49bc | 155 | |
b1ab9ed8 A |
156 | } |
157 | ||
158 | static void printDataAsHex(const char *title, const CSSM_DATA *d, unsigned maxToPrint) // 0 means print it all | |
159 | { | |
160 | #ifndef NDEBUG | |
161 | unsigned i; | |
162 | bool more = false; | |
163 | uint32 len = (uint32)d->Length; | |
164 | uint8 *cp = d->Data; | |
165 | char *buffer = NULL; | |
166 | size_t bufferSize; | |
167 | int offset, sz = 0; | |
168 | const int wrapwid = 24; // large enough so SHA-1 hashes fit on one line... | |
427c49bc | 169 | |
ecaf5866 | 170 | if ((maxToPrint != 0) && (len > maxToPrint)) { |
b1ab9ed8 A |
171 | len = maxToPrint; |
172 | more = true; | |
173 | } | |
174 | ||
175 | bufferSize = wrapwid+3*len; | |
176 | buffer = (char *)malloc(bufferSize); | |
427c49bc | 177 | |
b1ab9ed8 A |
178 | offset = sprintf(buffer, "%s [len = %u]\n", title, len); |
179 | dtprintf("%s", buffer); | |
180 | offset = 0; | |
181 | ||
ecaf5866 | 182 | for (i=0; (i < len) && (offset+3 < bufferSize); i++, offset += sz) { |
b1ab9ed8 | 183 | sz = sprintf(buffer + offset, " %02x", (unsigned int)cp[i] & 0xff); |
ecaf5866 A |
184 | if ((i % wrapwid) == (wrapwid-1)) { |
185 | dtprintf("%s\n", buffer); | |
b1ab9ed8 A |
186 | offset = 0; |
187 | sz = 0; | |
188 | } | |
189 | } | |
427c49bc | 190 | |
b1ab9ed8 | 191 | sz=sprintf(buffer + offset, more?" ...\n":"\n"); |
ecaf5866 | 192 | offset += sz; |
b1ab9ed8 | 193 | buffer[offset+1]=0; |
427c49bc | 194 | |
b1ab9ed8 | 195 | dtprintf("%s", buffer); |
d87e1158 A |
196 | |
197 | free(buffer); | |
b1ab9ed8 A |
198 | #endif |
199 | } | |
200 | ||
201 | #ifndef NDEBUG | |
202 | int tsaWriteFileX(const char *fileName, const unsigned char *bytes, size_t numBytes) | |
203 | { | |
204 | int rtn; | |
205 | int fd; | |
206 | ||
207 | fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0600); | |
866f8763 | 208 | if (fd == -1) { |
b1ab9ed8 | 209 | return errno; |
866f8763 | 210 | } |
b1ab9ed8 A |
211 | |
212 | rtn = (int)write(fd, bytes, numBytes); | |
213 | if(rtn != (int)numBytes) | |
214 | { | |
215 | if (rtn >= 0) | |
216 | fprintf(stderr, "writeFile: short write\n"); | |
217 | rtn = EIO; | |
218 | } | |
219 | else | |
220 | rtn = 0; | |
221 | ||
222 | close(fd); | |
223 | return rtn; | |
224 | } | |
225 | #endif | |
226 | ||
227 | char *cfStringToChar(CFStringRef inStr) | |
228 | { | |
229 | // Caller must free | |
230 | char *result = NULL; | |
231 | const char *str = NULL; | |
b1ab9ed8 | 232 | |
427c49bc | 233 | if (!inStr) |
c2a06e24 | 234 | return strdup(""); // return a null string |
b1ab9ed8 | 235 | |
427c49bc A |
236 | // quick path first |
237 | if ((str = CFStringGetCStringPtr(inStr, kCFStringEncodingUTF8))) { | |
c2a06e24 | 238 | result = strdup(str); |
427c49bc | 239 | } else { |
c2a06e24 A |
240 | // need to extract into buffer |
241 | CFIndex length = CFStringGetLength(inStr); // in 16-bit character units | |
242 | CFIndex bytesToAllocate = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; | |
243 | result = malloc(bytesToAllocate); | |
244 | if (!CFStringGetCString(inStr, result, bytesToAllocate, kCFStringEncodingUTF8)) | |
245 | result[0] = 0; | |
246 | } | |
427c49bc A |
247 | |
248 | return result; | |
b1ab9ed8 A |
249 | } |
250 | ||
251 | /* Oids longer than this are considered invalid. */ | |
252 | #define MAX_OID_SIZE 32 | |
253 | ||
427c49bc A |
254 | #ifndef NDEBUG |
255 | /* FIXME: There are other versions of this in SecCertifcate.c and SecCertificateP.c */ | |
b1ab9ed8 | 256 | static CFStringRef SecDERItemCopyOIDDecimalRepresentation(CFAllocatorRef allocator, const CSSM_OID *oid) |
427c49bc | 257 | { |
b1ab9ed8 A |
258 | if (oid->Length == 0) |
259 | return CFSTR("<NULL>"); | |
260 | ||
261 | if (oid->Length > MAX_OID_SIZE) | |
262 | return CFSTR("Oid too long"); | |
263 | ||
264 | CFMutableStringRef result = CFStringCreateMutable(allocator, 0); | |
265 | ||
266 | // The first two levels are encoded into one byte, since the root levelq | |
267 | // has only 3 nodes (40*x + y). However if x = joint-iso-itu-t(2) then | |
268 | // y may be > 39, so we have to add special-case handling for this. | |
269 | uint32_t x = oid->Data[0] / 40; | |
270 | uint32_t y = oid->Data[0] % 40; | |
271 | if (x > 2) | |
272 | { | |
273 | // Handle special case for large y if x = 2 | |
274 | y += (x - 2) * 40; | |
275 | x = 2; | |
276 | } | |
277 | CFStringAppendFormat(result, NULL, CFSTR("%u.%u"), x, y); | |
278 | ||
279 | uint32_t value = 0; | |
280 | for (x = 1; x < oid->Length; ++x) | |
281 | { | |
282 | value = (value << 7) | (oid->Data[x] & 0x7F); | |
283 | /* @@@ value may not span more than 4 bytes. */ | |
284 | /* A max number of 20 values is allowed. */ | |
285 | if (!(oid->Data[x] & 0x80)) | |
286 | { | |
427c49bc | 287 | CFStringAppendFormat(result, NULL, CFSTR(".%lu"), (unsigned long)value); |
b1ab9ed8 A |
288 | value = 0; |
289 | } | |
290 | } | |
291 | return result; | |
292 | } | |
427c49bc | 293 | #endif |
b1ab9ed8 A |
294 | |
295 | static void debugSaveCertificates(CSSM_DATA **outCerts) | |
296 | { | |
297 | #ifndef NDEBUG | |
298 | if (outCerts) | |
299 | { | |
300 | CSSM_DATA_PTR *certp; | |
301 | unsigned jx = 0; | |
302 | const char *certNameBase = "/tmp/tsa-resp-cert-"; | |
303 | char fname[PATH_MAX]; | |
304 | unsigned certCount = SecCmsArrayCount((void **)outCerts); | |
305 | dtprintf("Found %d certs\n",certCount); | |
306 | ||
307 | for (certp=outCerts;*certp;certp++, ++jx) | |
308 | { | |
309 | char numstr[32]; | |
310 | strncpy(fname, certNameBase, strlen(certNameBase)+1); | |
311 | sprintf(numstr,"%u", jx); | |
312 | strcat(fname,numstr); | |
313 | tsaWriteFileX(fname, (*certp)->Data, (*certp)->Length); | |
314 | if (jx > 5) | |
315 | break; //something wrong | |
316 | } | |
317 | } | |
318 | #endif | |
319 | } | |
320 | ||
321 | static void debugShowSignerInfo(SecCmsSignedDataRef signedData) | |
322 | { | |
323 | #ifndef NDEBUG | |
324 | int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData); | |
325 | dtprintf("numberOfSigners : %d\n", numberOfSigners); | |
326 | int ix; | |
327 | for (ix=0;ix < numberOfSigners;ix++) | |
328 | { | |
329 | SecCmsSignerInfoRef sigi = SecCmsSignedDataGetSignerInfo(signedData,ix); | |
330 | if (sigi) | |
331 | { | |
332 | CFStringRef commonName = SecCmsSignerInfoGetSignerCommonName(sigi); | |
333 | const char *signerhdr = " signer : "; | |
334 | if (commonName) | |
335 | { | |
336 | char *cn = cfStringToChar(commonName); | |
337 | dtprintf("%s%s\n", signerhdr, cn); | |
338 | if (cn) | |
339 | free(cn); | |
d87e1158 | 340 | CFReleaseNull(commonName); |
b1ab9ed8 A |
341 | } |
342 | else | |
343 | dtprintf("%s<NULL>\n", signerhdr); | |
344 | } | |
345 | } | |
346 | #endif | |
347 | } | |
348 | ||
349 | static void debugShowContentTypeOID(SecCmsContentInfoRef contentInfo) | |
350 | { | |
351 | #ifndef NDEBUG | |
352 | ||
353 | CSSM_OID *typeOID = SecCmsContentInfoGetContentTypeOID(contentInfo); | |
354 | if (typeOID) | |
355 | { | |
356 | CFStringRef oidCFStr = SecDERItemCopyOIDDecimalRepresentation(kCFAllocatorDefault, typeOID); | |
357 | char *oidstr = cfStringToChar(oidCFStr); | |
358 | printDataAsHex("oid:", typeOID, (unsigned int)typeOID->Length); | |
359 | dtprintf("\toid: %s\n", oidstr); | |
360 | if (oidCFStr) | |
361 | CFRelease(oidCFStr); | |
362 | if (oidstr) | |
363 | free(oidstr); | |
364 | } | |
365 | #endif | |
366 | } | |
367 | ||
368 | uint64_t tsaDER_ToInt(const CSSM_DATA *DER_Data) | |
369 | { | |
370 | uint64_t rtn = 0; | |
371 | unsigned i = 0; | |
372 | ||
373 | while(i < DER_Data->Length) { | |
374 | rtn |= DER_Data->Data[i]; | |
375 | if(++i == DER_Data->Length) { | |
376 | break; | |
377 | } | |
378 | rtn <<= 8; | |
379 | } | |
380 | return rtn; | |
381 | } | |
382 | ||
383 | void displayTSTInfo(SecAsn1TSATSTInfo *tstInfo) | |
384 | { | |
385 | #ifndef NDEBUG | |
386 | dtprintf("--- TSTInfo ---\n"); | |
387 | if (!tstInfo) | |
388 | return; | |
389 | ||
390 | if (tstInfo->version.Data) | |
391 | { | |
392 | uint64_t vers = tsaDER_ToInt(&tstInfo->version); | |
393 | dtprintf("Version:\t\t%u\n", (int)vers); | |
394 | } | |
395 | ||
396 | if (tstInfo->serialNumber.Data) | |
397 | { | |
398 | uint64_t sn = tsaDER_ToInt(&tstInfo->serialNumber); | |
399 | dtprintf("SerialNumber:\t%llu\n", sn); | |
400 | } | |
401 | ||
402 | if (tstInfo->ordering.Data) | |
403 | { | |
404 | uint64_t ord = tsaDER_ToInt(&tstInfo->ordering); | |
405 | dtprintf("Ordering:\t\t%s\n", ord?"yes":"no"); | |
406 | } | |
407 | ||
408 | if (tstInfo->nonce.Data) | |
409 | { | |
410 | uint64_t nonce = tsaDER_ToInt(&tstInfo->nonce); | |
411 | dtprintf("Nonce:\t\t%llu\n", nonce); | |
412 | } | |
413 | else | |
414 | dtprintf("Nonce:\t\tnot specified\n"); | |
415 | ||
416 | if (tstInfo->genTime.Data) | |
417 | { | |
418 | char buf[tstInfo->genTime.Length+1]; | |
419 | memcpy(buf, (const char *)tstInfo->genTime.Data, tstInfo->genTime.Length); | |
420 | buf[tstInfo->genTime.Length]=0; | |
421 | dtprintf("GenTime:\t\t%s\n", buf); | |
422 | } | |
423 | ||
424 | dtprintf("-- MessageImprint --\n"); | |
425 | if (true) // SecAsn1TSAMessageImprint | |
426 | { | |
427 | printDataAsHex(" Algorithm:",&tstInfo->messageImprint.hashAlgorithm.algorithm, 0); | |
428 | printDataAsHex(" Message :", &tstInfo->messageImprint.hashedMessage, 0);//tstInfo->messageImprint.hashedMessage.Length); | |
429 | } | |
430 | #endif | |
431 | } | |
432 | ||
433 | #pragma mark ----- TimeStamp Response using XPC ----- | |
434 | ||
435 | #include <xpc/private.h> | |
436 | ||
437 | static OSStatus checkForNonDERResponse(const unsigned char *resp, size_t respLen) | |
438 | { | |
439 | /* | |
440 | Good start is something like 30 82 0c 03 30 15 02 01 00 30 10 0c 0e 4f 70 65 | |
427c49bc | 441 | |
b1ab9ed8 A |
442 | URL: http://timestamp-int.corp.apple.com/signserver/process?TimeStampSigner |
443 | Resp: Http/1.1 Service Unavailable | |
427c49bc | 444 | |
b1ab9ed8 A |
445 | URL: http://timestamp-int.corp.apple.com/ts01 |
446 | Resp: blank | |
427c49bc | 447 | |
b1ab9ed8 A |
448 | URL: http://cutandtaste.com/404 (or other forced 404 site) |
449 | Resp: 404 | |
450 | */ | |
427c49bc | 451 | |
b1ab9ed8 A |
452 | OSStatus status = noErr; |
453 | const char ader[2] = { 0x30, 0x82 }; | |
454 | char *respStr = NULL; | |
455 | size_t maxlen = 0; | |
456 | size_t badResponseCount; | |
427c49bc | 457 | |
b1ab9ed8 A |
458 | const char *badResponses[] = |
459 | { | |
460 | "<!DOCTYPE html>", | |
461 | "Http/1.1 Service Unavailable", | |
462 | "blank" | |
463 | }; | |
427c49bc | 464 | |
b1ab9ed8 | 465 | require_action(resp && respLen, xit, status = errSecTimestampServiceNotAvailable); |
427c49bc | 466 | |
b1ab9ed8 A |
467 | // This is usual case |
468 | if ((respLen > 1) && (memcmp(resp, ader, 2)==0)) // might be good; pass on to DER decoder | |
469 | return noErr; | |
470 | ||
471 | badResponseCount = sizeof(badResponses)/sizeof(char *); | |
472 | int ix; | |
473 | for (ix = 0; ix < badResponseCount; ++ix) | |
474 | if (strlen(badResponses[ix]) > maxlen) | |
475 | maxlen = strlen(badResponses[ix]); | |
476 | ||
477 | // Prevent a large response from allocating a ton of memory | |
478 | if (respLen > maxlen) | |
479 | respLen = maxlen; | |
427c49bc | 480 | |
b1ab9ed8 A |
481 | respStr = (char *)malloc(respLen+1); |
482 | strlcpy(respStr, (const char *)resp, respLen); | |
427c49bc | 483 | |
b1ab9ed8 | 484 | for (ix = 0; ix < badResponseCount; ++ix) |
b54c578e A |
485 | if (strcmp(respStr, badResponses[ix])==0) { |
486 | if(respStr) { | |
487 | free(respStr); | |
488 | } | |
b1ab9ed8 | 489 | return errSecTimestampServiceNotAvailable; |
b54c578e | 490 | } |
427c49bc | 491 | |
b1ab9ed8 A |
492 | xit: |
493 | if (respStr) | |
494 | free((void *)respStr); | |
495 | ||
496 | return status; | |
497 | } | |
498 | ||
499 | static OSStatus sendTSARequestWithXPC(const unsigned char *tsaReq, size_t tsaReqLength, const unsigned char *tsaURL, unsigned char **tsaResp, size_t *tsaRespLength) | |
427c49bc | 500 | { |
b1ab9ed8 A |
501 | __block OSStatus result = noErr; |
502 | int timeoutInSeconds = 15; | |
503 | extern xpc_object_t xpc_create_with_format(const char * format, ...); | |
504 | ||
505 | dispatch_queue_t xpc_queue = dispatch_queue_create("com.apple.security.XPCTimeStampingService", DISPATCH_QUEUE_SERIAL); | |
506 | __block dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0); | |
507 | dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, timeoutInSeconds * NSEC_PER_SEC); | |
508 | ||
509 | xpc_connection_t con = xpc_connection_create("com.apple.security.XPCTimeStampingService", xpc_queue); | |
510 | ||
511 | xpc_connection_set_event_handler(con, ^(xpc_object_t event) { | |
512 | xpc_type_t xtype = xpc_get_type(event); | |
513 | if (XPC_TYPE_ERROR == xtype) | |
514 | { tsaDebug("default: connection error: %s\n", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); } | |
515 | else | |
427c49bc | 516 | { tsaDebug("default: unexpected connection event %p\n", event); } |
b1ab9ed8 A |
517 | }); |
518 | ||
519 | xpc_connection_resume(con); | |
520 | ||
521 | xpc_object_t tsaReqData = xpc_data_create(tsaReq, tsaReqLength); | |
522 | const char *urlstr = (tsaURL?(const char *)tsaURL:""); | |
523 | xpc_object_t url_as_xpc_string = xpc_string_create(urlstr); | |
524 | ||
525 | xpc_object_t message = xpc_create_with_format("{operation: TimeStampRequest, ServerURL: %value, TimeStampRequest: %value}", url_as_xpc_string, tsaReqData); | |
526 | ||
527 | xpc_connection_send_message_with_reply(con, message, xpc_queue, ^(xpc_object_t reply) | |
528 | { | |
529 | tsaDebug("xpc_connection_send_message_with_reply handler called back\n"); | |
fa7225c8 | 530 | dispatch_retain_safe(waitSemaphore); |
b1ab9ed8 | 531 | |
fa7225c8 | 532 | xpc_type_t xtype = xpc_get_type(reply); |
b1ab9ed8 A |
533 | if (XPC_TYPE_ERROR == xtype) |
534 | { tsaDebug("message error: %s\n", xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION)); } | |
535 | else if (XPC_TYPE_CONNECTION == xtype) | |
536 | { tsaDebug("received connection\n"); } | |
537 | else if (XPC_TYPE_DICTIONARY == xtype) | |
538 | { | |
539 | #ifndef NDEBUG | |
540 | /* | |
541 | // This is useful for debugging. | |
542 | char *debug = xpc_copy_description(reply); | |
543 | tsaDebug("DEBUG %s\n", debug); | |
544 | free(debug); | |
545 | */ | |
546 | #endif | |
427c49bc | 547 | |
b1ab9ed8 A |
548 | xpc_object_t xpcTimeStampReply = xpc_dictionary_get_value(reply, "TimeStampReply"); |
549 | size_t xpcTSRLength = xpc_data_get_length(xpcTimeStampReply); | |
550 | tsaDebug("xpcTSRLength: %ld bytes of response\n", xpcTSRLength); | |
551 | ||
552 | xpc_object_t xpcTimeStampError = xpc_dictionary_get_value(reply, "TimeStampError"); | |
553 | xpc_object_t xpcTimeStampStatus = xpc_dictionary_get_value(reply, "TimeStampStatus"); | |
427c49bc | 554 | |
b1ab9ed8 A |
555 | if (xpcTimeStampError || xpcTimeStampStatus) |
556 | { | |
557 | #ifndef NDEBUG | |
558 | if (xpcTimeStampError) | |
559 | { | |
560 | size_t len = xpc_string_get_length(xpcTimeStampError); | |
561 | char *buf = (char *)malloc(len); | |
562 | strlcpy(buf, xpc_string_get_string_ptr(xpcTimeStampError), len+1); | |
563 | tsaDebug("xpcTimeStampError: %s\n", buf); | |
564 | if (buf) | |
565 | free(buf); | |
566 | } | |
567 | #endif | |
568 | if (xpcTimeStampStatus) | |
569 | { | |
570 | result = (OSStatus)xpc_int64_get_value(xpcTimeStampStatus); | |
571 | tsaDebug("xpcTimeStampStatus: %d\n", (int)result); | |
572 | } | |
573 | } | |
427c49bc | 574 | |
b1ab9ed8 A |
575 | result = remapHTTPErrorCodes(result); |
576 | ||
577 | if ((result == noErr) && tsaResp && tsaRespLength) | |
578 | { | |
579 | *tsaRespLength = xpcTSRLength; | |
580 | *tsaResp = (unsigned char *)malloc(xpcTSRLength); | |
581 | ||
582 | size_t bytesCopied = xpc_data_get_bytes(xpcTimeStampReply, *tsaResp, 0, xpcTSRLength); | |
583 | if (bytesCopied != xpcTSRLength) | |
584 | { tsaDebug("length mismatch: copied: %ld, xpc: %ld\n", bytesCopied, xpcTSRLength); } | |
585 | else | |
586 | if ((result = checkForNonDERResponse(*tsaResp,bytesCopied))) | |
587 | { | |
588 | tsaDebug("received non-DER response from timestamp server\n"); | |
589 | } | |
590 | else | |
591 | { | |
592 | result = noErr; | |
593 | tsaDebug("copied: %ld bytes of response\n", bytesCopied); | |
594 | } | |
595 | } | |
596 | tsaDebug("releasing connection\n"); | |
597 | xpc_release(con); | |
598 | } | |
599 | else | |
600 | { tsaDebug("unexpected message reply type %p\n", xtype); } | |
fa7225c8 A |
601 | if (waitSemaphore) { dispatch_semaphore_signal(waitSemaphore); } |
602 | dispatch_release_null(waitSemaphore); | |
b1ab9ed8 | 603 | }); |
b1ab9ed8 | 604 | { tsaDebug("waiting up to %d seconds for response from XPC\n", timeoutInSeconds); } |
fa7225c8 | 605 | if (waitSemaphore) { dispatch_semaphore_wait(waitSemaphore, finishTime); } |
b1ab9ed8 | 606 | |
fa7225c8 | 607 | dispatch_release_null(waitSemaphore); |
b1ab9ed8 A |
608 | xpc_release(tsaReqData); |
609 | xpc_release(message); | |
427c49bc | 610 | |
b1ab9ed8 A |
611 | { tsaDebug("sendTSARequestWithXPC exit\n"); } |
612 | ||
613 | return result; | |
614 | } | |
615 | ||
616 | #pragma mark ----- TimeStamp request ----- | |
617 | ||
618 | #include "tsaTemplates.h" | |
619 | #include <security_asn1/SecAsn1Coder.h> | |
620 | #include <Security/oidsalg.h> | |
621 | #include <AssertMacros.h> | |
622 | #include <libkern/OSByteOrder.h> | |
623 | ||
624 | extern const SecAsn1Template kSecAsn1TSATimeStampReqTemplate; | |
625 | extern const SecAsn1Template kSecAsn1TSATimeStampRespTemplateDER; | |
626 | ||
627 | CFMutableDictionaryRef SecCmsTSAGetDefaultContext(CFErrorRef *error) | |
628 | { | |
629 | // Caller responsible for retain/release | |
630 | // <rdar://problem/11077440> Update SecCmsTSAGetDefaultContext with actual URL for Apple Timestamp server | |
631 | // URL will be in TimeStampingPrefs.plist | |
427c49bc | 632 | |
b1ab9ed8 A |
633 | CFBundleRef secFWbundle = NULL; |
634 | CFURLRef resourceURL = NULL; | |
635 | CFDataRef resourceData = NULL; | |
636 | CFPropertyListRef prefs = NULL; | |
637 | CFMutableDictionaryRef contextDict = NULL; | |
638 | SInt32 errorCode = 0; | |
639 | CFOptionFlags options = 0; | |
640 | CFPropertyListFormat format = 0; | |
641 | OSStatus status = noErr; | |
427c49bc | 642 | |
b1ab9ed8 | 643 | require_action(secFWbundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")), xit, status = errSecInternalError); |
5c19dc3a A |
644 | CFRetain(secFWbundle); |
645 | ||
b1ab9ed8 A |
646 | require_action(resourceURL = CFBundleCopyResourceURL(secFWbundle, CFSTR("TimeStampingPrefs"), CFSTR("plist"), NULL), |
647 | xit, status = errSecInvalidPrefsDomain); | |
648 | ||
649 | require(CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, resourceURL, &resourceData, | |
650 | NULL, NULL, &errorCode), xit); | |
651 | require_action(resourceData, xit, status = errSecDataNotAvailable); | |
652 | ||
653 | prefs = CFPropertyListCreateWithData(kCFAllocatorDefault, resourceData, options, &format, error); | |
654 | require_action(prefs && (CFGetTypeID(prefs)==CFDictionaryGetTypeID()), xit, status = errSecInvalidPrefsDomain); | |
427c49bc | 655 | |
b1ab9ed8 A |
656 | contextDict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, prefs); |
657 | ||
658 | if (error) | |
659 | *error = NULL; | |
660 | xit: | |
661 | if (errorCode) | |
662 | status = errorCode; | |
663 | if (error && status) | |
664 | *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL); | |
665 | if (secFWbundle) | |
666 | CFRelease(secFWbundle); | |
667 | if (resourceURL) | |
668 | CFRelease(resourceURL); | |
669 | if (resourceData) | |
670 | CFRelease(resourceData); | |
671 | if (prefs) | |
672 | CFRelease(prefs); | |
673 | ||
674 | return contextDict; | |
675 | } | |
676 | ||
677 | static CFDataRef _SecTSARequestCopyDEREncoding(SecAsn1TSAMessageImprint *messageImprint, bool noCerts, uint64_t nonce) | |
678 | { | |
679 | // Returns DER encoded TimeStampReq | |
680 | // Modeled on _SecOCSPRequestCopyDEREncoding | |
681 | // The Timestamp Authority supports 64 bit nonces (or more possibly) | |
427c49bc | 682 | |
b1ab9ed8 A |
683 | SecAsn1CoderRef coder = NULL; |
684 | uint8_t version = 1; | |
685 | SecAsn1Item vers = {1, &version}; | |
686 | uint8_t creq = noCerts?0:1; | |
687 | SecAsn1Item certReq = {1, &creq}; //jch - to request or not? | |
688 | SecAsn1TSATimeStampReq tsreq = {}; | |
689 | CFDataRef der = NULL; | |
690 | uint64_t nonceVal = OSSwapHostToBigConstInt64(nonce); | |
691 | SecAsn1Item nonceItem = {sizeof(uint64_t), (unsigned char *)&nonceVal}; | |
692 | ||
427c49bc | 693 | uint8_t OID_FakePolicy_Data[] = { 0x2A, 0x03, 0x04, 0x05, 0x06}; |
b1ab9ed8 A |
694 | const CSSM_OID fakePolicyOID = {sizeof(OID_FakePolicy_Data),OID_FakePolicy_Data}; |
695 | ||
696 | tsreq.version = vers; | |
697 | ||
698 | tsreq.messageImprint = *messageImprint; | |
699 | tsreq.certReq = certReq; | |
427c49bc | 700 | |
b1ab9ed8 A |
701 | // skip reqPolicy, extensions for now - FAKES - jch |
702 | tsreq.reqPolicy = fakePolicyOID; //policyID; | |
703 | ||
704 | tsreq.nonce = nonceItem; | |
705 | ||
706 | // Encode the request | |
707 | require_noerr(SecAsn1CoderCreate(&coder), errOut); | |
427c49bc | 708 | |
b1ab9ed8 A |
709 | SecAsn1Item encoded; |
710 | require_noerr(SecAsn1EncodeItem(coder, &tsreq, | |
711 | &kSecAsn1TSATimeStampReqTemplate, &encoded), errOut); | |
712 | der = CFDataCreate(kCFAllocatorDefault, encoded.Data, | |
713 | encoded.Length); | |
714 | ||
715 | errOut: | |
716 | if (coder) | |
717 | SecAsn1CoderRelease(coder); | |
718 | ||
719 | return der; | |
720 | } | |
721 | ||
722 | OSStatus SecTSAResponseCopyDEREncoding(SecAsn1CoderRef coder, const CSSM_DATA *tsaResponse, SecAsn1TimeStampRespDER *respDER) | |
723 | { | |
724 | // Partially decode the response | |
725 | OSStatus status = paramErr; | |
427c49bc | 726 | |
b1ab9ed8 A |
727 | require(tsaResponse && respDER, errOut); |
728 | require_noerr(SecAsn1DecodeData(coder, tsaResponse, | |
729 | &kSecAsn1TSATimeStampRespTemplateDER, respDER), errOut); | |
730 | status = noErr; | |
731 | ||
732 | errOut: | |
733 | ||
734 | return status; | |
735 | } | |
736 | ||
737 | #pragma mark ----- TS Callback ----- | |
738 | ||
739 | OSStatus SecCmsTSADefaultCallback(CFTypeRef context, void *messageImprintV, uint64_t nonce, CSSM_DATA *signedDERBlob) | |
740 | { | |
741 | OSStatus result = paramErr; | |
742 | const unsigned char *tsaReq = NULL; | |
743 | size_t tsaReqLength = 0; | |
744 | CFDataRef cfreq = NULL; | |
745 | unsigned char *tsaURL = NULL; | |
746 | bool noCerts = false; | |
427c49bc | 747 | |
b1ab9ed8 A |
748 | if (!context || CFGetTypeID(context)!=CFDictionaryGetTypeID()) |
749 | return paramErr; | |
427c49bc | 750 | |
b1ab9ed8 A |
751 | SecAsn1TSAMessageImprint *messageImprint = (SecAsn1TSAMessageImprint *)messageImprintV; |
752 | if (!messageImprint || !signedDERBlob) | |
753 | return paramErr; | |
754 | ||
755 | CFBooleanRef cfnocerts = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSAContextKeyNoCerts); | |
b54c578e | 756 | if (cfnocerts != NULL) |
b1ab9ed8 A |
757 | { |
758 | tsaDebug("[TSA] Request noCerts\n"); | |
759 | noCerts = CFBooleanGetValue(cfnocerts); | |
760 | } | |
761 | ||
762 | // We must spoof the nonce here, before sending the request. | |
763 | // If we tried to alter the reply, then the signature would break instead. | |
764 | CFBooleanRef cfBadNonce = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSADebugContextKeyBadNonce); | |
765 | if (cfBadNonce && CFBooleanGetValue(cfBadNonce)) | |
766 | { | |
767 | tsaDebug("[TSA] Forcing bad TS Request by changing nonce\n"); | |
768 | nonce++; | |
769 | } | |
770 | ||
771 | printDataAsHex("[TSA] hashToTimeStamp:", &messageImprint->hashedMessage,128); | |
772 | cfreq = _SecTSARequestCopyDEREncoding(messageImprint, noCerts, nonce); | |
773 | if (cfreq) | |
774 | { | |
775 | tsaReq = CFDataGetBytePtr(cfreq); | |
776 | tsaReqLength = CFDataGetLength(cfreq); | |
427c49bc | 777 | |
b1ab9ed8 A |
778 | #ifndef NDEBUG |
779 | CFShow(cfreq); | |
780 | tsaWriteFileX("/tmp/tsareq.req", tsaReq, tsaReqLength); | |
781 | #endif | |
782 | } | |
427c49bc | 783 | |
fa7225c8 | 784 | CFTypeRef url = CFDictionaryGetValue((CFDictionaryRef)context, kTSAContextKeyURL); |
b1ab9ed8 A |
785 | if (!url) |
786 | { | |
787 | tsaDebug("[TSA] missing URL for TSA (key: %s)\n", "kTSAContextKeyURL"); | |
788 | goto xit; | |
789 | } | |
427c49bc | 790 | |
fa7225c8 A |
791 | CFStringRef urlStr = NULL; |
792 | if (CFURLGetTypeID() == CFGetTypeID(url)) { | |
793 | urlStr = CFURLGetString(url); | |
794 | } else { | |
795 | require_quiet(CFStringGetTypeID() == CFGetTypeID(url), xit); | |
796 | urlStr = url; | |
797 | } | |
798 | require_quiet(urlStr, xit); | |
799 | ||
b1ab9ed8 A |
800 | /* |
801 | If debugging, look at special values in the context to mess things up | |
802 | */ | |
427c49bc | 803 | |
b1ab9ed8 A |
804 | CFBooleanRef cfBadReq = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)context, kTSADebugContextKeyBadReq); |
805 | if (cfBadReq && CFBooleanGetValue(cfBadReq)) | |
806 | { | |
807 | tsaDebug("[TSA] Forcing bad TS Request by truncating length from %ld to %ld\n", tsaReqLength, (tsaReqLength-4)); | |
808 | tsaReqLength -= 4; | |
809 | } | |
427c49bc | 810 | |
b1ab9ed8 | 811 | // need to extract into buffer |
fa7225c8 | 812 | CFIndex length = CFStringGetLength(urlStr); // in 16-bit character units |
b1ab9ed8 | 813 | tsaURL = malloc(6 * length + 1); // pessimistic |
fa7225c8 | 814 | if (!CFStringGetCString(urlStr, (char *)tsaURL, 6 * length + 1, kCFStringEncodingUTF8)) |
b1ab9ed8 A |
815 | goto xit; |
816 | ||
817 | tsaDebug("[TSA] URL for timestamp server: %s\n", tsaURL); | |
818 | ||
819 | unsigned char *tsaResp = NULL; | |
820 | size_t tsaRespLength = 0; | |
821 | tsaDebug("calling sendTSARequestWithXPC with %ld bytes of request\n", tsaReqLength); | |
822 | ||
823 | require_noerr(result = sendTSARequestWithXPC(tsaReq, tsaReqLength, tsaURL, &tsaResp, &tsaRespLength), xit); | |
824 | ||
825 | tsaDebug("sendTSARequestWithXPC copied: %ld bytes of response\n", tsaRespLength); | |
826 | ||
827 | signedDERBlob->Data = tsaResp; | |
828 | signedDERBlob->Length = tsaRespLength; | |
427c49bc | 829 | |
b1ab9ed8 | 830 | result = noErr; |
427c49bc | 831 | |
b1ab9ed8 A |
832 | xit: |
833 | if (tsaURL) | |
834 | free((void *)tsaURL); | |
835 | if (cfreq) | |
836 | CFRelease(cfreq); | |
837 | ||
838 | return result; | |
839 | } | |
840 | ||
841 | #pragma mark ----- TimeStamp Verification ----- | |
842 | ||
843 | static OSStatus convertGeneralizedTimeToCFAbsoluteTime(const char *timeStr, CFAbsoluteTime *ptime) | |
844 | { | |
845 | /* | |
846 | See http://userguide.icu-project.org/formatparse/datetime for date/time format. | |
847 | The "Z" signal a GMT time, but CFDateFormatterGetAbsoluteTimeFromString returns | |
427c49bc | 848 | values based on local time. |
b1ab9ed8 A |
849 | */ |
850 | ||
851 | OSStatus result = noErr; | |
852 | CFDateFormatterRef formatter = NULL; | |
853 | CFStringRef time_string = NULL; | |
854 | CFTimeZoneRef gmt = NULL; | |
855 | CFLocaleRef locale = NULL; | |
856 | CFRange *rangep = NULL; | |
857 | ||
858 | require(timeStr && timeStr[0] && ptime, xit); | |
859 | require(formatter = CFDateFormatterCreate(kCFAllocatorDefault, locale, kCFDateFormatterNoStyle, kCFDateFormatterNoStyle), xit); | |
860 | // CFRetain(formatter); | |
861 | CFDateFormatterSetFormat(formatter, CFSTR("yyyyMMddHHmmss'Z'")); // GeneralizedTime | |
862 | gmt = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0); | |
863 | CFDateFormatterSetProperty(formatter, kCFDateFormatterTimeZone, gmt); | |
864 | ||
865 | time_string = CFStringCreateWithCString(kCFAllocatorDefault, timeStr, kCFStringEncodingUTF8); | |
866 | if (!time_string || !CFDateFormatterGetAbsoluteTimeFromString(formatter, time_string, rangep, ptime)) | |
867 | { | |
868 | dtprintf("%s is not a valid date\n", timeStr); | |
869 | result = 1; | |
870 | } | |
871 | ||
872 | xit: | |
427c49bc | 873 | if (formatter) |
b1ab9ed8 | 874 | CFRelease(formatter); |
427c49bc | 875 | if (time_string) |
b1ab9ed8 | 876 | CFRelease(time_string); |
427c49bc | 877 | if (gmt) |
b1ab9ed8 | 878 | CFRelease(gmt); |
427c49bc | 879 | |
b1ab9ed8 A |
880 | return result; |
881 | } | |
882 | ||
8a50f688 | 883 | static OSStatus SecTSAValidateTimestamp(const SecAsn1TSATSTInfo *tstInfo, SecCertificateRef signerCert, CFAbsoluteTime *timestampTime) |
b1ab9ed8 A |
884 | { |
885 | // See <rdar://problem/11077708> Properly handle revocation information of timestamping certificate | |
886 | OSStatus result = paramErr; | |
887 | CFAbsoluteTime genTime = 0; | |
888 | char timeStr[32] = {0,}; | |
b1ab9ed8 | 889 | |
8a50f688 | 890 | require(tstInfo && signerCert && (tstInfo->genTime.Length < 16), xit);; |
427c49bc | 891 | |
b1ab9ed8 A |
892 | memcpy(timeStr, tstInfo->genTime.Data, tstInfo->genTime.Length); |
893 | timeStr[tstInfo->genTime.Length] = 0; | |
894 | require_noerr(convertGeneralizedTimeToCFAbsoluteTime(timeStr, &genTime), xit); | |
8a50f688 | 895 | if (SecCertificateIsValidX(signerCert, genTime)) // iOS? |
b1ab9ed8 A |
896 | result = noErr; |
897 | else | |
898 | result = errSecTimestampInvalid; | |
899 | if (timestampTime) | |
900 | *timestampTime = genTime; | |
901 | xit: | |
902 | return result; | |
903 | } | |
904 | ||
79b9da22 | 905 | static OSStatus verifyTSTInfo(const CSSM_DATA_PTR content, SecCmsSignerInfoRef signerinfo, SecAsn1TSATSTInfo *tstInfo, CFAbsoluteTime *timestampTime, uint64_t expectedNonce, CSSM_DATA_PTR encDigest) |
b1ab9ed8 A |
906 | { |
907 | OSStatus status = paramErr; | |
908 | SecAsn1CoderRef coder = NULL; | |
909 | ||
910 | if (!tstInfo) | |
911 | return SECFailure; | |
427c49bc | 912 | |
79b9da22 A |
913 | SecCertificateRef signerCert = SecCmsSignerInfoGetTimestampSigningCert(signerinfo); |
914 | SecAsn1TSAMessageImprint expectedMessageImprint; | |
915 | ||
b1ab9ed8 | 916 | require_noerr(SecAsn1CoderCreate(&coder), xit); |
427c49bc | 917 | require_noerr(SecAsn1Decode(coder, content->Data, content->Length, |
b1ab9ed8 A |
918 | kSecAsn1TSATSTInfoTemplate, tstInfo), xit); |
919 | displayTSTInfo(tstInfo); | |
427c49bc | 920 | |
b1ab9ed8 A |
921 | // Check the nonce |
922 | if (tstInfo->nonce.Data && expectedNonce!=0) | |
923 | { | |
924 | uint64_t nonce = tsaDER_ToInt(&tstInfo->nonce); | |
925 | // if (expectedNonce!=nonce) | |
926 | dtprintf("verifyTSTInfo nonce: actual: %lld, expected: %lld\n", nonce, expectedNonce); | |
927 | require_action(expectedNonce==nonce, xit, status = errSecTimestampRejection); | |
928 | } | |
427c49bc | 929 | |
79b9da22 A |
930 | // Check the times in the timestamp |
931 | require_noerr(status = SecTSAValidateTimestamp(tstInfo, signerCert, timestampTime), xit); | |
b1ab9ed8 A |
932 | dtprintf("SecTSAValidateTimestamp result: %ld\n", (long)status); |
933 | ||
79b9da22 A |
934 | // Check the message imprint against the encDigest from the signerInfo containing this timestamp |
935 | SECOidTag hashAlg = SECOID_GetAlgorithmTag(&tstInfo->messageImprint.hashAlgorithm); | |
936 | require_action(hashAlg == SEC_OID_SHA256 || hashAlg == SEC_OID_SHA1, xit, status = errSecInvalidDigestAlgorithm); | |
937 | require_noerr(status = createTSAMessageImprint(signerinfo, &tstInfo->messageImprint.hashAlgorithm, | |
938 | encDigest, &expectedMessageImprint), xit); | |
939 | require_action(CERT_CompareCssmData(&expectedMessageImprint.hashedMessage, &tstInfo->messageImprint.hashedMessage), xit, | |
940 | status = errSecTimestampInvalid; secerror("Timestamp MessageImprint did not match the signature's hash")); | |
941 | ||
b1ab9ed8 A |
942 | xit: |
943 | if (coder) | |
944 | SecAsn1CoderRelease(coder); | |
945 | return status; | |
946 | } | |
947 | ||
948 | static void debugShowExtendedTrustResult(int index, CFDictionaryRef extendedResult) | |
949 | { | |
950 | #ifndef NDEBUG | |
951 | if (extendedResult) | |
952 | { | |
953 | CFStringRef xresStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, | |
954 | CFSTR("Extended trust result for signer #%d : %@"), index, extendedResult); | |
955 | if (xresStr) | |
956 | { | |
957 | CFShow(xresStr); | |
958 | CFRelease(xresStr); | |
959 | } | |
960 | } | |
961 | #endif | |
962 | } | |
963 | ||
964 | #ifndef NDEBUG | |
965 | extern const char *cssmErrorString(CSSM_RETURN error); | |
966 | ||
967 | static void statusBitTest(CSSM_TP_APPLE_CERT_STATUS certStatus, uint32 bit, const char *str) | |
968 | { | |
969 | if (certStatus & bit) | |
970 | dtprintf("%s ", str); | |
971 | } | |
972 | #endif | |
973 | ||
974 | static void debugShowCertEvidenceInfo(uint16_t certCount, const CSSM_TP_APPLE_EVIDENCE_INFO *info) | |
975 | { | |
976 | #ifndef NDEBUG | |
977 | CSSM_TP_APPLE_CERT_STATUS cs; | |
978 | // const CSSM_TP_APPLE_EVIDENCE_INFO *pinfo = info; | |
979 | uint16_t ix; | |
980 | for (ix=0; info && (ix<certCount); ix++, ++info) | |
981 | { | |
982 | cs = info->StatusBits; | |
983 | dtprintf(" cert %u:\n", ix); | |
984 | dtprintf(" StatusBits : 0x%x", (unsigned)cs); | |
985 | if (cs) | |
986 | { | |
987 | dtprintf(" ( "); | |
988 | statusBitTest(cs, CSSM_CERT_STATUS_EXPIRED, "EXPIRED"); | |
427c49bc | 989 | statusBitTest(cs, CSSM_CERT_STATUS_NOT_VALID_YET, |
b1ab9ed8 | 990 | "NOT_VALID_YET"); |
427c49bc | 991 | statusBitTest(cs, CSSM_CERT_STATUS_IS_IN_INPUT_CERTS, |
b1ab9ed8 | 992 | "IS_IN_INPUT_CERTS"); |
427c49bc | 993 | statusBitTest(cs, CSSM_CERT_STATUS_IS_IN_ANCHORS, |
b1ab9ed8 A |
994 | "IS_IN_ANCHORS"); |
995 | statusBitTest(cs, CSSM_CERT_STATUS_IS_ROOT, "IS_ROOT"); | |
996 | statusBitTest(cs, CSSM_CERT_STATUS_IS_FROM_NET, "IS_FROM_NET"); | |
997 | dtprintf(")\n"); | |
998 | } | |
999 | else | |
1000 | dtprintf("\n"); | |
1001 | ||
1002 | dtprintf(" NumStatusCodes : %u ", info->NumStatusCodes); | |
1003 | CSSM_RETURN *pstatuscode = info->StatusCodes; | |
1004 | uint16_t jx; | |
1005 | for (jx=0; pstatuscode && (jx<info->NumStatusCodes); jx++, ++pstatuscode) | |
1006 | dtprintf("%s ", cssmErrorString(*pstatuscode)); | |
427c49bc | 1007 | |
b1ab9ed8 A |
1008 | dtprintf("\n"); |
1009 | dtprintf(" Index: %u\n", info->Index); | |
1010 | } | |
1011 | ||
1012 | #endif | |
1013 | } | |
1014 | ||
427c49bc | 1015 | #ifndef NDEBUG |
b1ab9ed8 A |
1016 | static const char *trustResultTypeString(SecTrustResultType trustResultType) |
1017 | { | |
b1ab9ed8 A |
1018 | switch (trustResultType) |
1019 | { | |
1020 | case kSecTrustResultProceed: return "TrustResultProceed"; | |
1021 | case kSecTrustResultUnspecified: return "TrustResultUnspecified"; | |
1022 | case kSecTrustResultDeny: return "TrustResultDeny"; // user reject | |
1023 | case kSecTrustResultInvalid: return "TrustResultInvalid"; | |
b1ab9ed8 A |
1024 | case kSecTrustResultRecoverableTrustFailure: return "TrustResultRecoverableTrustFailure"; |
1025 | case kSecTrustResultFatalTrustFailure: return "TrustResultUnspecified"; | |
1026 | case kSecTrustResultOtherError: return "TrustResultOtherError"; | |
1027 | default: return "TrustResultUnknown"; | |
1028 | } | |
b1ab9ed8 A |
1029 | return ""; |
1030 | } | |
427c49bc | 1031 | #endif |
b1ab9ed8 | 1032 | |
d8f41ccd | 1033 | static OSStatus verifySigners(SecCmsSignedDataRef signedData, int numberOfSigners, CFTypeRef timeStampPolicy) |
b1ab9ed8 A |
1034 | { |
1035 | // See <rdar://problem/11077588> Bubble up SecTrustEvaluate of timestamp response to high level callers | |
1036 | // Also <rdar://problem/11077708> Properly handle revocation information of timestamping certificate | |
1037 | ||
d87e1158 | 1038 | SecTrustRef trustRef = NULL; |
d8f41ccd | 1039 | CFTypeRef policy = CFRetainSafe(timeStampPolicy); |
427c49bc A |
1040 | int result=errSecInternalError; |
1041 | int rx; | |
1042 | ||
d8f41ccd A |
1043 | if (!policy) { |
1044 | require(policy = SecPolicyCreateWithOID(kSecPolicyAppleTimeStamping), xit); | |
1045 | } | |
1046 | ||
b1ab9ed8 A |
1047 | int jx; |
1048 | for (jx = 0; jx < numberOfSigners; ++jx) | |
1049 | { | |
1050 | SecTrustResultType trustResultType; | |
b1ab9ed8 A |
1051 | CFDictionaryRef extendedResult = NULL; |
1052 | CFArrayRef certChain = NULL; | |
1053 | uint16_t certCount = 0; | |
427c49bc | 1054 | |
b1ab9ed8 A |
1055 | CSSM_TP_APPLE_EVIDENCE_INFO *statusChain = NULL; |
1056 | ||
1057 | // SecCmsSignedDataVerifySignerInfo returns trustRef, which we can call SecTrustEvaluate on | |
1058 | // usually (always?) if result is noErr, the SecTrust*Result calls will return errSecTrustNotAvailable | |
1059 | result = SecCmsSignedDataVerifySignerInfo (signedData, jx, NULL, policy, &trustRef); | |
1060 | dtprintf("[%s] SecCmsSignedDataVerifySignerInfo: result: %d, signer: %d\n", | |
1061 | __FUNCTION__, result, jx); | |
1062 | require_noerr(result, xit); | |
8e292c99 | 1063 | |
b1ab9ed8 A |
1064 | result = SecTrustEvaluate (trustRef, &trustResultType); |
1065 | dtprintf("[%s] SecTrustEvaluate: result: %d, trustResult: %s (%d)\n", | |
1066 | __FUNCTION__, result, trustResultTypeString(trustResultType), trustResultType); | |
1067 | if (result) | |
1068 | goto xit; | |
1069 | switch (trustResultType) | |
1070 | { | |
1071 | case kSecTrustResultProceed: | |
1072 | case kSecTrustResultUnspecified: | |
1073 | break; // success | |
1074 | case kSecTrustResultDeny: // user reject | |
1075 | result = errSecTimestampNotTrusted; // SecCmsVSTimestampNotTrusted ? | |
1076 | break; | |
1077 | case kSecTrustResultInvalid: | |
1078 | assert(false); // should never happen | |
1079 | result = errSecTimestampNotTrusted; // SecCmsVSTimestampNotTrusted ? | |
1080 | break; | |
b1ab9ed8 A |
1081 | case kSecTrustResultRecoverableTrustFailure: |
1082 | case kSecTrustResultFatalTrustFailure: | |
1083 | case kSecTrustResultOtherError: | |
1084 | default: | |
1085 | { | |
1086 | /* | |
1087 | There are two "errors" that need to be resolved externally: | |
1088 | CSSMERR_TP_CERT_EXPIRED can be OK if the timestamp was made | |
1089 | before the TSA chain expired; CSSMERR_TP_CERT_NOT_VALID_YET | |
1090 | can happen in the case where the user's clock was set to 0. | |
1091 | We don't want to prevent them using apps automatically, so | |
1092 | return noErr and let codesign or whover decide. | |
1093 | */ | |
1094 | OSStatus resultCode; | |
1095 | require_action(SecTrustGetCssmResultCode(trustRef, &resultCode)==noErr, xit, result = errSecTimestampNotTrusted); | |
1096 | result = (resultCode == CSSMERR_TP_CERT_EXPIRED || resultCode == CSSMERR_TP_CERT_NOT_VALID_YET)?noErr:errSecTimestampNotTrusted; | |
1097 | } | |
1098 | break; | |
1099 | } | |
427c49bc | 1100 | |
b1ab9ed8 A |
1101 | rx = SecTrustGetResult(trustRef, &trustResultType, &certChain, &statusChain); |
1102 | dtprintf("[%s] SecTrustGetResult: result: %d, type: %d\n", __FUNCTION__,rx, trustResultType); | |
1103 | certCount = certChain?CFArrayGetCount(certChain):0; | |
1104 | debugShowCertEvidenceInfo(certCount, statusChain); | |
d87e1158 | 1105 | CFReleaseNull(certChain); |
427c49bc | 1106 | |
b1ab9ed8 A |
1107 | rx = SecTrustCopyExtendedResult(trustRef, &extendedResult); |
1108 | dtprintf("[%s] SecTrustCopyExtendedResult: result: %d\n", __FUNCTION__, rx); | |
1109 | if (extendedResult) | |
1110 | { | |
1111 | debugShowExtendedTrustResult(jx, extendedResult); | |
1112 | CFRelease(extendedResult); | |
1113 | } | |
427c49bc | 1114 | |
b1ab9ed8 | 1115 | if (trustRef) |
d87e1158 | 1116 | CFReleaseNull(trustRef); |
b1ab9ed8 A |
1117 | } |
1118 | ||
1119 | xit: | |
d87e1158 A |
1120 | if (trustRef) |
1121 | CFReleaseNull(trustRef); | |
b1ab9ed8 | 1122 | if (policy) |
d87e1158 | 1123 | CFRelease(policy); |
b1ab9ed8 A |
1124 | return result; |
1125 | } | |
1126 | ||
1127 | static OSStatus impExpImportCertUnCommon( | |
1128 | const CSSM_DATA *cdata, | |
1129 | SecKeychainRef importKeychain, // optional | |
427c49bc | 1130 | CFMutableArrayRef outArray) // optional, append here |
b1ab9ed8 A |
1131 | { |
1132 | // The only difference between this and impExpImportCertCommon is that we append to outArray | |
1133 | // before attempting to add to the keychain | |
1134 | OSStatus status = noErr; | |
1135 | SecCertificateRef certRef = NULL; | |
427c49bc | 1136 | |
b1ab9ed8 A |
1137 | require_action(cdata, xit, status = errSecUnsupportedFormat); |
1138 | ||
1139 | /* Pass kCFAllocatorNull as bytesDeallocator to assure the bytes aren't freed */ | |
1140 | CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)cdata->Data, (CFIndex)cdata->Length, kCFAllocatorNull); | |
1141 | require_action(data, xit, status = errSecUnsupportedFormat); | |
1142 | ||
1143 | certRef = SecCertificateCreateWithData(kCFAllocatorDefault, data); | |
1144 | CFRelease(data); /* certRef has its own copy of the data now */ | |
1145 | if(!certRef) { | |
1146 | dtprintf("impExpHandleCert error\n"); | |
1147 | return errSecUnsupportedFormat; | |
1148 | } | |
1149 | ||
1150 | if (outArray) | |
1151 | CFArrayAppendValue(outArray, certRef); | |
1152 | ||
1153 | if (importKeychain) | |
1154 | { | |
1155 | status = SecCertificateAddToKeychain(certRef, importKeychain); | |
1156 | if (status!=noErr && status!=errSecDuplicateItem) | |
1157 | { dtprintf("SecCertificateAddToKeychain error: %ld\n", (long)status); } | |
1158 | } | |
1159 | ||
1160 | xit: | |
d64be36e | 1161 | if (certRef) { |
b1ab9ed8 | 1162 | CFRelease(certRef); |
d64be36e A |
1163 | } |
1164 | return status; | |
b1ab9ed8 A |
1165 | } |
1166 | ||
1167 | static void saveTSACertificates(CSSM_DATA **signingCerts, CFMutableArrayRef outArray) | |
1168 | { | |
1169 | SecKeychainRef defaultKeychain = NULL; | |
8e292c99 A |
1170 | // Don't save certificates in keychain to avoid securityd issues |
1171 | // if (SecKeychainCopyDefault(&defaultKeychain)) | |
1172 | // defaultKeychain = NULL; | |
b1ab9ed8 A |
1173 | |
1174 | unsigned certCount = SecCmsArrayCount((void **)signingCerts); | |
1175 | unsigned dex; | |
1176 | for (dex=0; dex<certCount; dex++) | |
1177 | { | |
1178 | OSStatus rx = impExpImportCertUnCommon(signingCerts[dex], defaultKeychain, outArray); | |
1179 | if (rx!=noErr && rx!=errSecDuplicateItem) | |
1180 | dtprintf("impExpImportCertCommon failed: %ld\n", (long)rx); | |
1181 | } | |
1182 | if (defaultKeychain) | |
1183 | CFRelease(defaultKeychain); | |
1184 | } | |
1185 | ||
1186 | static const char *cfabsoluteTimeToString(CFAbsoluteTime abstime) | |
1187 | { | |
1188 | CFGregorianDate greg = CFAbsoluteTimeGetGregorianDate(abstime, NULL); | |
1189 | char str[20]; | |
1190 | if (19 != snprintf(str, 20, "%4.4d-%2.2d-%2.2d_%2.2d:%2.2d:%2.2d", | |
1191 | (int)greg.year, greg.month, greg.day, greg.hour, greg.minute, (int)greg.second)) | |
1192 | str[0]=0; | |
1193 | char *data = (char *)malloc(20); | |
1194 | strncpy(data, str, 20); | |
1195 | return data; | |
1196 | } | |
1197 | ||
1198 | static OSStatus setTSALeafValidityDates(SecCmsSignerInfoRef signerinfo) | |
1199 | { | |
8a50f688 | 1200 | SecCertificateRef tsaLeaf = SecCmsSignerInfoGetSigningCertificate(signerinfo, NULL); |
427c49bc | 1201 | |
8a50f688 | 1202 | if (!tsaLeaf) |
b1ab9ed8 | 1203 | return SecCmsVSSigningCertNotFound; |
427c49bc | 1204 | |
427c49bc A |
1205 | signerinfo->tsaLeafNotBefore = SecCertificateNotValidBefore(tsaLeaf); /* Start date for Timestamp Authority leaf */ |
1206 | signerinfo->tsaLeafNotAfter = SecCertificateNotValidAfter(tsaLeaf); /* Expiration date for Timestamp Authority leaf */ | |
b1ab9ed8 A |
1207 | |
1208 | const char *nbefore = cfabsoluteTimeToString(signerinfo->tsaLeafNotBefore); | |
1209 | const char *nafter = cfabsoluteTimeToString(signerinfo->tsaLeafNotAfter); | |
1210 | if (nbefore && nafter) | |
1211 | { | |
1212 | dtprintf("Timestamp Authority leaf valid from %s to %s\n", nbefore, nafter); | |
1213 | free((void *)nbefore);free((void *)nafter); | |
1214 | } | |
1215 | ||
8a50f688 | 1216 | return errSecSuccess; |
b1ab9ed8 A |
1217 | } |
1218 | ||
1219 | /* | |
1220 | From RFC 3161: Time-Stamp Protocol (TSP),August 2001, APPENDIX B: | |
427c49bc | 1221 | |
b1ab9ed8 A |
1222 | B) The validity of the digital signature may then be verified in the |
1223 | following way: | |
1224 | ||
1225 | 1) The time-stamp token itself MUST be verified and it MUST be | |
1226 | verified that it applies to the signature of the signer. | |
1227 | ||
1228 | 2) The date/time indicated by the TSA in the TimeStampToken | |
1229 | MUST be retrieved. | |
1230 | ||
1231 | 3) The certificate used by the signer MUST be identified and | |
1232 | retrieved. | |
1233 | ||
1234 | 4) The date/time indicated by the TSA MUST be within the | |
1235 | validity period of the signer's certificate. | |
1236 | ||
1237 | 5) The revocation information about that certificate, at the | |
1238 | date/time of the Time-Stamping operation, MUST be retrieved. | |
1239 | ||
1240 | 6) Should the certificate be revoked, then the date/time of | |
1241 | revocation shall be later than the date/time indicated by | |
1242 | the TSA. | |
1243 | ||
1244 | If all these conditions are successful, then the digital signature | |
1245 | shall be declared as valid. | |
1246 | ||
1247 | */ | |
1248 | ||
1249 | OSStatus decodeTimeStampToken(SecCmsSignerInfoRef signerinfo, CSSM_DATA_PTR inData, CSSM_DATA_PTR encDigest, uint64_t expectedNonce) | |
d8f41ccd A |
1250 | { |
1251 | return decodeTimeStampTokenWithPolicy(signerinfo, NULL, inData, encDigest, expectedNonce); | |
1252 | } | |
1253 | ||
1254 | OSStatus decodeTimeStampTokenWithPolicy(SecCmsSignerInfoRef signerinfo, CFTypeRef timeStampPolicy, CSSM_DATA_PTR inData, CSSM_DATA_PTR encDigest, uint64_t expectedNonce) | |
b1ab9ed8 A |
1255 | { |
1256 | /* | |
79b9da22 A |
1257 | We update signerinfo with timestamp and tsa certificate chain. |
1258 | encDigest is the original signed blob, which we must hash and compare. | |
1259 | inData comes from the unAuthAttr section of the CMS message | |
1260 | ||
1261 | These are set in signerinfo as side effects: | |
1262 | timestampTime | |
1263 | timestampCertList | |
1264 | timestampCert | |
1265 | */ | |
b1ab9ed8 A |
1266 | |
1267 | SecCmsDecoderRef decoderContext = NULL; | |
1268 | SecCmsMessageRef cmsMessage = NULL; | |
1269 | SecCmsContentInfoRef contentInfo; | |
1270 | SecCmsSignedDataRef signedData; | |
1271 | SECOidTag contentTypeTag; | |
1272 | int contentLevelCount; | |
1273 | int ix; | |
1274 | OSStatus result = errSecUnknownFormat; | |
1275 | CSSM_DATA **signingCerts = NULL; | |
1276 | ||
427c49bc | 1277 | dtprintf("decodeTimeStampToken top: PORT_GetError() %d -----\n", PORT_GetError()); |
8e292c99 | 1278 | PORT_SetError(0); |
427c49bc | 1279 | |
b1ab9ed8 A |
1280 | /* decode the message */ |
1281 | require_noerr(result = SecCmsDecoderCreate (NULL, NULL, NULL, NULL, NULL, NULL, NULL, &decoderContext), xit); | |
1282 | result = SecCmsDecoderUpdate(decoderContext, inData->Data, inData->Length); | |
79b9da22 | 1283 | if (result) { |
b1ab9ed8 A |
1284 | result = errSecTimestampInvalid; |
1285 | SecCmsDecoderDestroy(decoderContext); | |
1286 | goto xit; | |
79b9da22 | 1287 | } |
427c49bc | 1288 | |
b1ab9ed8 A |
1289 | require_noerr(result = SecCmsDecoderFinish(decoderContext, &cmsMessage), xit); |
1290 | ||
1291 | // process the results | |
1292 | contentLevelCount = SecCmsMessageContentLevelCount(cmsMessage); | |
427c49bc | 1293 | |
79b9da22 | 1294 | if (encDigest) { |
b1ab9ed8 | 1295 | printDataAsHex("encDigest",encDigest, 0); |
79b9da22 | 1296 | } |
b1ab9ed8 | 1297 | |
79b9da22 | 1298 | for (ix = 0; ix < contentLevelCount; ++ix) { |
b1ab9ed8 A |
1299 | dtprintf("\n----- Content Level %d -----\n", ix); |
1300 | // get content information | |
1301 | contentInfo = SecCmsMessageContentLevel (cmsMessage, ix); | |
1302 | contentTypeTag = SecCmsContentInfoGetContentTypeTag (contentInfo); | |
427c49bc | 1303 | |
b1ab9ed8 A |
1304 | // After 2nd round, contentInfo.content.data is the TSTInfo |
1305 | ||
1306 | debugShowContentTypeOID(contentInfo); | |
427c49bc | 1307 | |
79b9da22 A |
1308 | switch (contentTypeTag) { |
1309 | case SEC_OID_PKCS7_SIGNED_DATA: { | |
1310 | require((signedData = (SecCmsSignedDataRef)SecCmsContentInfoGetContent(contentInfo)) != NULL, xit); | |
1311 | ||
1312 | debugShowSignerInfo(signedData); | |
1313 | ||
1314 | SECAlgorithmID **digestAlgorithms = SecCmsSignedDataGetDigestAlgs(signedData); | |
1315 | unsigned digestAlgCount = SecCmsArrayCount((void **)digestAlgorithms); | |
1316 | dtprintf("digestAlgCount: %d\n", digestAlgCount); | |
1317 | if (signedData->digests) { | |
1318 | int jx; | |
1319 | char buffer[128]; | |
1320 | for (jx=0;jx < digestAlgCount;jx++) { | |
1321 | sprintf(buffer, " digest[%u]", jx); | |
1322 | printDataAsHex(buffer,signedData->digests[jx], 0); | |
1323 | } | |
1324 | } else { | |
1325 | dtprintf("digests not yet computed\n"); | |
1326 | CSSM_DATA_PTR innerContent = SecCmsContentInfoGetInnerContent(contentInfo); | |
1327 | if (innerContent) | |
1328 | { | |
1329 | dtprintf("inner content length: %ld\n", innerContent->Length); | |
1330 | SecAsn1TSAMessageImprint fakeMessageImprint = {{{0}},}; | |
1331 | SecCmsSignerInfoRef tsaSigner = SecCmsSignedDataGetSignerInfo(signedData, 0); | |
1332 | OSStatus status = createTSAMessageImprint(tsaSigner, &tsaSigner->digestAlg, innerContent, &fakeMessageImprint); | |
1333 | require_noerr_action(status, xit, dtprintf("createTSAMessageImprint status: %d\n", (int)status); result = status); | |
1334 | printDataAsHex("inner content hash",&fakeMessageImprint.hashedMessage, 0); | |
1335 | CSSM_DATA_PTR digestdata = &fakeMessageImprint.hashedMessage; | |
1336 | CSSM_DATA_PTR digests[2] = {digestdata, NULL}; | |
1337 | status = SecCmsSignedDataSetDigests(signedData, digestAlgorithms, (CSSM_DATA_PTR *)&digests); | |
1338 | require_noerr_action(status, xit, dtprintf("createTSAMessageImprint status: %d\n", (int)status); result = status); | |
1339 | } else { | |
1340 | dtprintf("no inner content\n"); | |
1341 | } | |
1342 | } | |
427c49bc | 1343 | |
79b9da22 A |
1344 | /* |
1345 | Import the certificates. We leave this as a warning, since | |
1346 | there are configurations where the certificates are not returned. | |
1347 | */ | |
1348 | signingCerts = SecCmsSignedDataGetCertificateList(signedData); | |
1349 | if (signingCerts == NULL) { | |
1350 | dtprintf("SecCmsSignedDataGetCertificateList returned NULL\n"); | |
1351 | } else { | |
1352 | if (!signerinfo->timestampCertList) { | |
1353 | signerinfo->timestampCertList = CFArrayCreateMutable(kCFAllocatorDefault, 10, &kCFTypeArrayCallBacks); | |
1354 | } | |
1355 | saveTSACertificates(signingCerts, signerinfo->timestampCertList); | |
1356 | require_noerr(result = setTSALeafValidityDates(signerinfo), xit); | |
1357 | debugSaveCertificates(signingCerts); | |
1358 | } | |
b1ab9ed8 | 1359 | |
79b9da22 A |
1360 | int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData); |
1361 | ||
1362 | if (numberOfSigners > 0) { | |
1363 | /* @@@ assume there's only one signer since SecCms can't handle multiple signers anyway */ | |
1364 | signerinfo->timestampCert = CFRetainSafe(SecCmsSignerInfoGetSigningCertificate(signedData->signerInfos[0], NULL)); | |
b1ab9ed8 | 1365 | } |
79b9da22 A |
1366 | |
1367 | result = verifySigners(signedData, numberOfSigners, timeStampPolicy); | |
1368 | if (result) { | |
1369 | dtprintf("verifySigners failed: %ld\n", (long)result); // warning | |
1370 | goto xit; // remap to SecCmsVSTimestampNotTrusted ? | |
b1ab9ed8 | 1371 | } |
b1ab9ed8 | 1372 | |
79b9da22 | 1373 | break; |
b1ab9ed8 | 1374 | } |
79b9da22 A |
1375 | case SEC_OID_PKCS9_SIGNING_CERTIFICATE: { |
1376 | dtprintf("SEC_OID_PKCS9_SIGNING_CERTIFICATE seen\n"); | |
1377 | break; | |
ecaf5866 | 1378 | } |
79b9da22 A |
1379 | case SEC_OID_PKCS9_ID_CT_TSTInfo: { |
1380 | SecAsn1TSATSTInfo tstInfo = {{0},}; | |
1381 | result = verifyTSTInfo(contentInfo->rawContent, signerinfo, &tstInfo, &signerinfo->timestampTime, expectedNonce, encDigest); | |
1382 | if (signerinfo->timestampTime) { | |
1383 | const char *tstamp = cfabsoluteTimeToString(signerinfo->timestampTime); | |
1384 | if (tstamp) { | |
1385 | dtprintf("Timestamp Authority timestamp: %s\n", tstamp); | |
1386 | free((void *)tstamp); | |
1387 | } | |
b1ab9ed8 | 1388 | } |
79b9da22 | 1389 | break; |
b1ab9ed8 | 1390 | } |
79b9da22 A |
1391 | case SEC_OID_OTHER: { |
1392 | dtprintf("otherContent : %p\n", (char *)SecCmsContentInfoGetContent (contentInfo)); | |
1393 | break; | |
1394 | } | |
1395 | default: | |
1396 | dtprintf("ContentTypeTag : %x\n", contentTypeTag); | |
1397 | break; | |
b1ab9ed8 A |
1398 | } |
1399 | } | |
1400 | xit: | |
79b9da22 A |
1401 | if (cmsMessage) { |
1402 | SecCmsMessageDestroy(cmsMessage); | |
1403 | } | |
b1ab9ed8 A |
1404 | |
1405 | return result; | |
1406 | } | |
1407 | ||
1408 |