1/* 2 * Copyright (c) 2000-2012 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 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. 12 * 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. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24/* 25 * ocspdNetwork.cpp - Network support for ocspd and CRL/cert fetch 26 */ 27#if OCSP_DEBUG 28#define OCSP_USE_SYSLOG 1 29#endif 30#include <security_ocspd/ocspdDebug.h> 31#include "appleCrlIssuers.h" 32#include "ocspdNetwork.h" 33#include <security_ocspd/ocspdUtils.h> 34#include <CoreFoundation/CoreFoundation.h> 35#include <security_cdsa_utils/cuEnc64.h> 36#include <security_cdsa_utils/cuFileIo.h> 37#include <stdlib.h> 38#include <sys/stat.h> 39#include <dispatch/dispatch.h> 40#include <Security/cssmapple.h> 41#include <security_utilities/cfutilities.h> 42#include <CoreServices/CoreServices.h> 43#include <SystemConfiguration/SCDynamicStoreCopySpecific.h> 44 45/* enable deprecated function declarations */ 46#ifndef LDAP_DEPRECATED 47#define LDAP_DEPRECATED 1 48#endif 49#include <LDAP/ldap.h> 50 51/* useful macros for CF */ 52#define CFReleaseSafe(CF) { CFTypeRef _cf = (CF); if (_cf) CFRelease(_cf); } 53#define CFReleaseNull(CF) { CFTypeRef _cf = (CF); \ 54 if (_cf) { (CF) = NULL; CFRelease(_cf); } } 55 56extern Mutex gParamsLock; 57extern Mutex gFileWriteLock; 58extern Mutex gListLock; 59extern CFMutableArrayRef gDownloadList; 60extern CFMutableDictionaryRef gIssuersDict; 61 62extern bool crlSignatureValid( 63 const char *crlFileName, 64 const char *issuersFileName, 65 const char *updateFileName, 66 const char *revokedFileName); 67 68extern int crlCheckCachePath(); 69 70#pragma mark ----- async HTTP ----- 71 72/* how long to wait for more data to come in before we give up */ 73#define READ_STREAM_TIMEOUT 7.0 74 75/* read buffer size */ 76#define READ_BUFFER_SIZE 4096 77 78/* post buffer size */ 79#define POST_BUFFER_SIZE 1024 80 81/* context for an asynchronous HTTP request */ 82typedef struct asynchttp_s { 83 CFHTTPMessageRef request; 84 CFHTTPMessageRef response; 85 CFMutableDataRef data; 86 CFIndex increment; 87 size_t responseLength; /* how much data we have currently read */ 88 size_t previousLength; /* how much data we had read when the timer was started */ 89 CFReadStreamRef stream; 90 CFRunLoopTimerRef timer; 91 int finished; 92} asynchttp_t; 93 94 95static void asynchttp_complete( 96 asynchttp_t *http) 97{ 98 /* Shut down stream and timer. */ 99 if (http->stream) { 100 CFReadStreamSetClient(http->stream, kCFStreamEventNone, NULL, NULL); 101 CFReadStreamUnscheduleFromRunLoop(http->stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 102 CFReadStreamClose(http->stream); 103 CFReleaseNull(http->stream); 104 } 105 if (http->timer) { 106 CFRunLoopTimerInvalidate(http->timer); 107 CFReleaseNull(http->timer); 108 } 109 /* We're done. */ 110 http->finished = 1; 111} 112 113static void asynchttp_free( 114 asynchttp_t *http) 115{ 116 if(http == NULL) return; 117 CFReleaseNull(http->request); 118 CFReleaseNull(http->response); 119 CFReleaseNull(http->data); 120 CFReleaseNull(http->stream); 121 CFReleaseNull(http->timer); 122} 123 124static void asynchttp_timer_proc( 125 CFRunLoopTimerRef timer, 126 void *info) 127{ 128 asynchttp_t *http = (asynchttp_t *)info; 129#if HTTP_DEBUG 130 CFStringRef req_meth = http->request ? CFHTTPMessageCopyRequestMethod(http->request) : NULL; 131 CFURLRef req_url = http->request ? CFHTTPMessageCopyRequestURL(http->request) : NULL; 132 //%%% Add logging of url that timed out. 133 //asl_log(NULL, NULL, ASL_LEVEL_NOTICE, "Timeout during %@ %@.", req_meth, req_url); 134 if(req_url) CFRelease(req_url); 135 if(req_meth) CFRelease(req_meth); 136#endif 137 bool hasNewData = (http->responseLength > http->previousLength); 138 http->previousLength = http->responseLength; 139 140 if(hasNewData) { 141 /* Still getting data, so restart the timer. */ 142 CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL }; 143 timerContext.info = http; 144 CFRunLoopTimerInvalidate(http->timer); 145 CFReleaseNull(http->timer); 146 http->timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 147 CFAbsoluteTimeGetCurrent() + READ_STREAM_TIMEOUT, 148 0, 0, 0, asynchttp_timer_proc, &timerContext); 149 if (http->timer) { 150 CFRunLoopAddTimer(CFRunLoopGetCurrent(), http->timer, kCFRunLoopDefaultMode); 151 return; 152 } 153 } 154 /* Haven't gotten any more data since last time (or failed to restart timer) */ 155 asynchttp_complete(http); 156} 157 158static void handle_server_response( 159 CFReadStreamRef stream, 160 CFStreamEventType type, 161 void *info) 162{ 163 asynchttp_t *http = (asynchttp_t *)info; 164 switch (type) 165 { 166 case kCFStreamEventHasBytesAvailable: 167 { 168 size_t len = (http->increment) ? http->increment : READ_BUFFER_SIZE; 169 UInt8 *buf = (UInt8 *) malloc(len); 170 if (!buf) { 171 ocspdErrorLog("http stream: failed to malloc %ld bytes\n", len); 172 asynchttp_complete(http); 173 break; 174 } 175 if (!http->data) { 176 http->data = CFDataCreateMutable(kCFAllocatorDefault, 0); 177 http->responseLength = 0; 178 } 179 do { 180 CFIndex bytesRead = CFReadStreamRead(stream, buf, len); 181 if (bytesRead < 0) { 182 /* Negative length == error */ 183 asynchttp_complete(http); 184 break; 185 } else if (bytesRead == 0) { 186 /* Read 0 bytes, we're done */ 187 ocspdDebug("http stream: transfer complete, moved %ld bytes\n", 188 http->responseLength); 189 asynchttp_complete(http); 190 break; 191 } else { 192 /* Read some number of bytes */ 193 CFDataAppendBytes(http->data, buf, bytesRead); 194 http->responseLength += bytesRead; 195 } 196 } while (CFReadStreamHasBytesAvailable(stream)); 197 free(buf); 198 break; 199 } 200 case kCFStreamEventErrorOccurred: 201 { 202 CFStreamError error = CFReadStreamGetError(stream); 203 ocspdErrorLog("http stream: %p kCFStreamEventErrorOccurred, domain: %ld error: %ld\n", 204 stream, error.domain, (long int)error.error); 205 if (error.domain == kCFStreamErrorDomainPOSIX) { 206 ocspdErrorLog("CFReadStream POSIX error: %s\n", strerror(error.error)); 207 } else if (error.domain == kCFStreamErrorDomainMacOSStatus) { 208 ocspdErrorLog("CFReadStream OSStatus error: %ld\n", (long int)error.error); 209 } else { 210 ocspdErrorLog("CFReadStream domain: %ld error: %ld\n", error.domain, (long int)error.error); 211 } 212 asynchttp_complete(http); 213 break; 214 } 215 case kCFStreamEventEndEncountered: 216 { 217 http->response = (CFHTTPMessageRef)CFReadStreamCopyProperty( 218 stream, kCFStreamPropertyHTTPResponseHeader); 219 ocspdErrorLog("http stream: %p kCFStreamEventEndEncountered hdr: %p\n", 220 stream, http->response); 221 CFHTTPMessageSetBody(http->response, http->data); 222 asynchttp_complete(http); 223 break; 224 } 225 default: 226 { 227 ocspdErrorLog("handle_server_response: unexpected event type: %lu\n", type); 228 break; 229 } 230 } 231} 232 233 234#pragma mark ----- OCSP support ----- 235 236/* POST method has Content-Type header line equal to "application/ocsp-request" */ 237static CFStringRef kContentType = CFSTR("Content-Type"); 238static CFStringRef kAppOcspRequest = CFSTR("application/ocsp-request"); 239static CFStringRef kUserAgent = CFSTR("User-Agent"); 240static CFStringRef kAppUserAgent = CFSTR("ocspd/1.0"); 241 242#if OCSP_DEBUG 243#define DUMP_BLOBS 1 244#endif 245 246#define OCSP_GET_FILE "/tmp/ocspGet" 247#define OCSP_RESP_FILE "/tmp/ocspResp" 248 249#if DUMP_BLOBS 250 251static void writeBlob( 252 const char *fileName, 253 const char *whatIsIt, 254 const unsigned char *data, 255 unsigned dataLen) 256{ 257 if(writeFile(fileName, data, dataLen)) { 258 printf("***Error writing %s to %s\n", whatIsIt, fileName); 259 } 260 else { 261 printf("...wrote %u bytes of %s to %s\n", dataLen, whatIsIt, fileName); 262 } 263} 264 265#else 266 267#define writeBlob(f,w,d,l) 268 269#endif /* DUMP_BLOBS */ 270 271#if ENABLE_OCSP_VIA_GET 272 273/* fetch via HTTP GET */ 274CSSM_RETURN ocspdHttpGet( 275 SecAsn1CoderRef coder, 276 const CSSM_DATA &url, 277 const CSSM_DATA &ocspReq, // DER encoded 278 CSSM_DATA &fetched) // mallocd in coder space and RETURNED 279{ 280 ocspdHttpDebug("ocspdHttpGet: start\n"); 281 282 CSSM_RETURN result = CSSM_OK; 283 CFDataRef urlData = NULL; 284 SInt32 errorCode; 285 unsigned char *endp = NULL; 286 bool done = false; 287 size_t totalLen; 288 unsigned char *fullUrl = NULL; 289 CFURLRef cfUrl = NULL; 290 Boolean brtn; 291 CFIndex len; 292 293 /* trim off possible NULL terminator from incoming URL */ 294 size_t urlLen = url.Length; 295 if(url.Data[urlLen - 1] == '\0') { 296 urlLen--; 297 } 298 299 #if OCSP_DEBUG 300 { 301 char *ustr = (char *)malloc(urlLen + 1); 302 memmove(ustr, url.Data, urlLen); 303 ustr[urlLen] = '\0'; 304 ocspdDebug("ocspdHttpGet: fetching from URI %s\n", ustr); 305 free(ustr); 306 } 307 #endif 308 309 /* base64 encode the OCSP request; that's used as a path */ 310 unsigned char *req64 = NULL; 311 unsigned req64Len = 0; 312 req64 = cuEnc64(ocspReq.Data, ocspReq.Length, &req64Len); 313 if(req64 == NULL) { 314 ocspdErrorLog("ocspdHttpGet: error base64-encoding request\n"); 315 result = CSSMERR_TP_INTERNAL_ERROR; 316 goto cleanup; 317 } 318 319 writeBlob(OCSP_GET_FILE, "OCSP Request as URL", req64, req64Len); 320 321 /* trim off trailing NULL and newline */ 322 endp = req64 + req64Len - 1; 323 for(;;) { 324 switch(*endp) { 325 case '\0': 326 case '\n': 327 case '\r': 328 endp--; 329 req64Len--; 330 break; 331 default: 332 done = true; 333 break; 334 } 335 if(done) { 336 break; 337 } 338 } 339 340 /* concatenate: URL plus path (see RFC 2616 3.2.2, 5.1.2) */ 341 if( (req64Len >= INT_MAX) || (urlLen > (INT_MAX - (1 + req64Len))) ) { 342 /* long URL is long; concatenating these components would overflow totalLen */ 343 result = CSSMERR_TP_INVALID_DATA; 344 goto cleanup; 345 } 346 totalLen = urlLen + 1 + req64Len; 347 fullUrl = (unsigned char *)malloc(totalLen); 348 memmove(fullUrl, url.Data, urlLen); 349 fullUrl[urlLen] = '/'; 350 memmove(fullUrl + urlLen + 1, req64, req64Len); 351 352 cfUrl = CFURLCreateWithBytes(NULL, 353 fullUrl, totalLen, 354 kCFStringEncodingUTF8, // right? 355 NULL); // this is absolute path 356 if(!cfUrl) { 357 ocspdErrorLog("ocspdHttpGet: CFURLCreateWithBytes returned NULL\n"); 358 result = CSSMERR_APPLETP_CRL_BAD_URI; 359 goto cleanup; 360 } 361 brtn = CFURLCreateDataAndPropertiesFromResource(NULL, 362 cfUrl, 363 &urlData, 364 NULL, // no properties 365 NULL, 366 &errorCode); 367 if(!brtn) { 368 ocspdErrorLog("ocspdHttpGet: CFURLCreateDataAndPropertiesFromResource err: %d\n", 369 (int)errorCode); 370 result = CSSMERR_APPLETP_CRL_BAD_URI; 371 goto cleanup; 372 } 373 if(urlData == NULL) { 374 ocspdErrorLog("ocspdHttpGet: CFURLCreateDataAndPropertiesFromResource: no data\n"); 375 result = CSSMERR_APPLETP_CRL_BAD_URI; 376 goto cleanup; 377 } 378 len = CFDataGetLength(urlData); 379 fetched.Data = (uint8 *)SecAsn1Malloc(coder, len); 380 fetched.Length = len; 381 memmove(fetched.Data, CFDataGetBytePtr(urlData), len); 382 writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, fetched.Length); 383cleanup: 384 CFReleaseSafe(cfUrl); 385 CFReleaseSafe(urlData); 386 if(fullUrl) { 387 free(fullUrl); 388 } 389 if(req64) { 390 free(req64); 391 } 392 return result; 393} 394 395#endif /* ENABLE_OCSP_VIA_GET */ 396 397/* fetch via HTTP POST */ 398 399CSSM_RETURN ocspdHttpPost( 400 SecAsn1CoderRef coder, 401 const CSSM_DATA &url, 402 const CSSM_DATA &ocspReq, // DER encoded 403 CSSM_DATA &fetched) // mallocd in coder space and RETURNED 404{ 405 ocspdHttpDebug("ocspdHttpPost: start\n"); 406 407 CSSM_RETURN result = CSSM_OK; 408 CFURLRef cfUrl = NULL; 409 CFDictionaryRef proxyDict = NULL; 410 CFStreamClientContext clientContext = { 0, NULL, NULL, NULL, NULL }; 411 CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL }; 412 CFAbsoluteTime startTime, stopTime; 413 asynchttp_t *httpContext = NULL; 414 415 CFDataRef postData = NULL; 416 417 /* trim off possible NULL terminator from incoming URL */ 418 uint32 urlLen = url.Length; 419 if(url.Data[urlLen - 1] == '\0') { 420 urlLen--; 421 } 422 423 /* create URL with explicit path (see RFC 2616 3.2.2, 5.1.2) */ 424 cfUrl = CFURLCreateWithBytes(NULL, 425 url.Data, urlLen, 426 kCFStringEncodingUTF8, // right? 427 NULL); // this is absolute path 428 if(cfUrl) { 429 CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl); 430 if(pathStr) { 431 if (CFStringGetLength(pathStr) == 0) { 432 CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL, 433 cfUrl, CFSTR(""), FALSE); 434 CFRelease(cfUrl); 435 cfUrl = tmpUrl; 436 } 437 CFRelease(pathStr); 438 } 439 } 440 if(!cfUrl) { 441 ocspdErrorLog("ocspdHttpPost: CFURLCreateWithBytes returned NULL\n"); 442 result = CSSMERR_APPLETP_CRL_BAD_URI; 443 goto cleanup; 444 } 445 446 #if OCSP_DEBUG 447 { 448 char *ustr = (char *)malloc(urlLen + 1); 449 memmove(ustr, url.Data, urlLen); 450 ustr[urlLen] = '\0'; 451 ocspdDebug("ocspdHttpPost: posting to URI %s\n", ustr); 452 free(ustr); 453 } 454 #endif 455 456 writeBlob(OCSP_GET_FILE, "OCSP Request as POST data", ocspReq.Data, ocspReq.Length); 457 postData = CFDataCreate(NULL, ocspReq.Data, ocspReq.Length); 458 459 /* allocate our http context */ 460 httpContext = (asynchttp_t *) malloc(sizeof(asynchttp_t)); 461 if(!httpContext) { 462 result = memFullErr; 463 goto cleanup; 464 } 465 memset(httpContext, 0, sizeof(asynchttp_t)); 466 467 /* read this many bytes at a time */ 468 httpContext->increment = POST_BUFFER_SIZE; 469 470 /* create the http POST request */ 471 httpContext->request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, 472 CFSTR("POST"), cfUrl, kCFHTTPVersion1_1); 473 if(!httpContext->request) { 474 ocspdErrorLog("ocspdHttpPost: error creating CFHTTPMessage\n"); 475 result = CSSMERR_TP_INTERNAL_ERROR; 476 goto cleanup; 477 } 478 479 /* set the body and required header fields */ 480 CFHTTPMessageSetBody(httpContext->request, postData); 481 CFHTTPMessageSetHeaderFieldValue(httpContext->request, 482 kContentType, kAppOcspRequest); 483 CFHTTPMessageSetHeaderFieldValue(httpContext->request, 484 kUserAgent, kAppUserAgent); 485 486 /* create the stream */ 487 httpContext->stream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, 488 httpContext->request); 489 if(!httpContext->stream) { 490 ocspdErrorLog("ocspdHttpPost: error creating CFReadStream\n"); 491 result = CSSMERR_TP_INTERNAL_ERROR; 492 goto cleanup; 493 } 494 495 /* specify automatic redirect handling */ 496 if(!CFReadStreamSetProperty(httpContext->stream, 497 kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue)) { 498 ocspdErrorLog("ocspdHttpPost: error setting autoredirect property\n"); 499 /* this error is non-fatal; keep going */ 500 } 501 502 /* set up possible proxy info */ 503 proxyDict = SCDynamicStoreCopyProxies(NULL); 504 if(proxyDict) { 505 ocspdDebug("ocspdHttpPost: setting proxy dict\n"); 506 CFReadStreamSetProperty(httpContext->stream, kCFStreamPropertyHTTPProxy, proxyDict); 507 CFReleaseNull(proxyDict); 508 } 509 510 /* set a reasonable timeout */ 511 timerContext.info = httpContext; 512 httpContext->timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 513 CFAbsoluteTimeGetCurrent() + READ_STREAM_TIMEOUT, 514 0, 0, 0, asynchttp_timer_proc, &timerContext); 515 if (httpContext->timer == NULL) { 516 ocspdErrorLog("ocspdHttpPost: error setting kCFStreamPropertyReadTimeout\n"); 517 /* but keep going */ 518 } else { 519 CFRunLoopAddTimer(CFRunLoopGetCurrent(), httpContext->timer, 520 kCFRunLoopDefaultMode); 521 } 522 523 /* set up our response callback and schedule it on the current run loop */ 524 httpContext->finished = 0; 525 clientContext.info = httpContext; 526 CFReadStreamSetClient(httpContext->stream, 527 (kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered), 528 handle_server_response, &clientContext); 529 CFReadStreamScheduleWithRunLoop(httpContext->stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 530 531 /* open the stream */ 532 if(CFReadStreamOpen(httpContext->stream) == false) { 533 ocspdErrorLog("ocspdHttpPost: error opening CFReadStream\n"); 534 result = CSSMERR_APPLETP_NETWORK_FAILURE; 535 goto cleanup; 536 } 537 538 /* cycle the run loop until we get a response or time out */ 539 startTime = stopTime = CFAbsoluteTimeGetCurrent(); 540 while (!httpContext->finished) { 541 (void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, TRUE); 542 CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent(); 543 if (curTime != stopTime) { 544 stopTime = curTime; 545 } 546 } 547 548 if(!httpContext->data || httpContext->responseLength == 0) { 549 ocspdErrorLog("CFReadStreamRead: no data was read after %f seconds\n", 550 stopTime-startTime); 551 result = CSSMERR_APPLETP_NETWORK_FAILURE; 552 goto cleanup; 553 } 554 ocspdDebug("ocspdHttpPost: total %lu bytes read in %f seconds\n", 555 (unsigned long)httpContext->responseLength, stopTime-startTime); 556 SecAsn1AllocCopy(coder, CFDataGetBytePtr(httpContext->data), 557 CFDataGetLength(httpContext->data), &fetched); 558 writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, fetched.Length); 559 result = CSSM_OK; 560cleanup: 561 CFReleaseSafe(postData); 562 CFReleaseSafe(cfUrl); 563 asynchttp_free(httpContext); 564 565 return result; 566} 567 568#pragma mark ----- LDAP fetch ----- 569 570/* 571 * LDAP attribute names, used if not present in URI. 572 */ 573#define LDAP_ATTR_CERT "cacertificate;binary" 574#define LDAP_ATTR_CRL "certificaterevocationlist;binary" 575 576/* 577 * Default LDAP options. 578 */ 579#define LDAP_REFERRAL_DEFAULT LDAP_OPT_ON 580 581static CSSM_RETURN ldapRtnToCssm( 582 int rtn) 583{ 584 switch(rtn) { 585 case LDAP_SERVER_DOWN: 586 case LDAP_TIMEOUT: 587 case LDAP_CONNECT_ERROR: 588 return CSSMERR_APPLETP_CRL_SERVER_DOWN; 589 case LDAP_PARAM_ERROR: 590 case LDAP_FILTER_ERROR: 591 return CSSMERR_APPLETP_CRL_BAD_URI; 592 default: 593 return CSSMERR_APPLETP_CRL_NOT_FOUND; 594 } 595} 596 597static CSSM_RETURN ldapFetch( 598 Allocator &alloc, 599 const CSSM_DATA &url, 600 LF_Type lfType, 601 CSSM_DATA &fetched) // mallocd in alloc space and RETURNED 602{ 603 BerValue **value = NULL; 604 LDAPURLDesc *urlDesc = NULL; 605 int rtn; 606 LDAPMessage *msg = NULL; 607 LDAP *ldap = NULL; 608 LDAPMessage *entry = NULL; 609 bool mallocdString = false; 610 char *urlStr; 611 int numEntries; 612 CSSM_RETURN ourRtn = CSSM_OK; 613 /* attr input to ldap_search_s() */ 614 char *attrArray[2]; 615 char **attrArrayP = NULL; 616 617 /* don't assume URL string is NULL terminated */ 618 if(url.Data[url.Length - 1] == '\0') { 619 urlStr = (char *)url.Data; 620 } 621 else { 622 urlStr = (char *)malloc(url.Length + 1); 623 memmove(urlStr, url.Data, url.Length); 624 urlStr[url.Length] = '\0'; 625 mallocdString = true; 626 } 627 628 /* break up the URL into something usable */ 629 rtn = ldap_url_parse(urlStr, &urlDesc); 630 if(rtn) { 631 ocspdErrorLog("ldap_url_parse returned %d", rtn); 632 return CSSMERR_APPLETP_CRL_BAD_URI; 633 } 634 635 /* 636 * Determine what attr we're looking for. 637 */ 638 if((urlDesc->lud_attrs != NULL) && // attrs present in URL 639 (urlDesc->lud_attrs[0] != NULL) && // at least one attr present 640 (urlDesc->lud_attrs[1] == NULL)) { 641 /* 642 * Exactly one attr present in the caller-specified URL; 643 * assume that this is exactly what we want. 644 */ 645 attrArrayP = &urlDesc->lud_attrs[0]; 646 } 647 else { 648 /* use caller-specified attr */ 649 switch(lfType) { 650 case LT_Crl: 651 attrArray[0] = (char *)LDAP_ATTR_CRL; 652 break; 653 case LT_Cert: 654 attrArray[0] = (char *)LDAP_ATTR_CERT; 655 break; 656 default: 657 printf("***ldapFetch screwup: bogus lfType (%d)\n", 658 (int)lfType); 659 return CSSMERR_CSSM_INTERNAL_ERROR; 660 } 661 attrArray[1] = NULL; 662 attrArrayP = &attrArray[0]; 663 } 664 665 /* establish connection */ 666 rtn = ldap_initialize(&ldap, urlStr); 667 if(rtn) { 668 ocspdErrorLog("ldap_initialize returned %d\n", rtn); 669 return ldapRtnToCssm(rtn); 670 } 671 /* subsequent errors to cleanup: */ 672 rtn = ldap_simple_bind_s(ldap, NULL, NULL); 673 if(rtn) { 674 ocspdErrorLog("ldap_simple_bind_s returned %d\n", rtn); 675 ourRtn = ldapRtnToCssm(rtn); 676 goto cleanup; 677 } 678 679 rtn = ldap_set_option(ldap, LDAP_OPT_REFERRALS, LDAP_REFERRAL_DEFAULT); 680 if(rtn) { 681 ocspdErrorLog("ldap_set_option(referrals) returned %d\n", rtn); 682 ourRtn = ldapRtnToCssm(rtn); 683 goto cleanup; 684 } 685 686 rtn = ldap_search_s( 687 ldap, 688 urlDesc->lud_dn, 689 LDAP_SCOPE_SUBTREE, 690 urlDesc->lud_filter, 691 urlDesc->lud_attrs, 692 0, // attrsonly 693 &msg); 694 if(rtn) { 695 ocspdErrorLog("ldap_search_s returned %d\n", rtn); 696 ourRtn = ldapRtnToCssm(rtn); 697 goto cleanup; 698 } 699 700 /* 701 * We require exactly one entry (for now). 702 */ 703 numEntries = ldap_count_entries(ldap, msg); 704 if(numEntries != 1) { 705 ocspdErrorLog("tpCrlViaLdap: numEntries %d\n", numEntries); 706 ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND; 707 goto cleanup; 708 } 709 710 entry = ldap_first_entry(ldap, msg); 711 value = ldap_get_values_len(ldap, msg, attrArrayP[0]); 712 if(value == NULL) { 713 ocspdErrorLog("Error on ldap_get_values_len\n"); 714 ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND; 715 goto cleanup; 716 } 717 718 fetched.Length = value[0]->bv_len; 719 fetched.Data = (uint8 *)alloc.malloc(fetched.Length); 720 memmove(fetched.Data, value[0]->bv_val, fetched.Length); 721 722 ldap_value_free_len(value); 723 ourRtn = CSSM_OK; 724cleanup: 725 if(msg) { 726 ldap_msgfree(msg); 727 } 728 if(mallocdString) { 729 free(urlStr); 730 } 731 ldap_free_urldesc(urlDesc); 732 rtn = ldap_unbind(ldap); 733 if(rtn) { 734 ocspdErrorLog("Error %d on ldap_unbind\n", rtn); 735 /* oh well */ 736 } 737 return ourRtn; 738} 739 740#pragma mark ----- HTTP fetch via GET ----- 741 742/* fetch via HTTP */ 743static CSSM_RETURN httpFetch( 744 Allocator &alloc, 745 const CSSM_DATA &url, 746 LF_Type lfType, 747 CSSM_DATA &fetched) // mallocd in alloc space and RETURNED 748{ 749 #pragma unused (lfType) 750 ocspdHttpDebug("httpFetch: start\n"); 751 752 CSSM_RETURN result = CSSM_OK; 753 CFURLRef cfUrl = NULL; 754 CFDictionaryRef proxyDict = NULL; 755 CFStreamClientContext clientContext = { 0, NULL, NULL, NULL, NULL }; 756 CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL }; 757 CFAbsoluteTime startTime, stopTime; 758 asynchttp_t *httpContext = NULL; 759 760 /* trim off possible NULL terminator from incoming URL */ 761 CSSM_DATA theUrl = url; 762 if(theUrl.Data[theUrl.Length - 1] == '\0') { 763 theUrl.Length--; 764 } 765 766 /* create URL with explicit path (see RFC 2616 3.2.2, 5.1.2) */ 767 cfUrl = CFURLCreateWithBytes(NULL, 768 theUrl.Data, theUrl.Length, 769 kCFStringEncodingUTF8, // right? 770 NULL); // this is absolute path 771 if(cfUrl) { 772 CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl); 773 if(pathStr) { 774 if (CFStringGetLength(pathStr) == 0) { 775 CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL, 776 cfUrl, CFSTR(""), FALSE); 777 CFRelease(cfUrl); 778 cfUrl = tmpUrl; 779 } 780 CFRelease(pathStr); 781 } 782 } 783 if(!cfUrl) { 784 ocspdErrorLog("httpFetch: CFURLCreateWithBytes returned NULL\n"); 785 result = CSSMERR_APPLETP_CRL_BAD_URI; 786 goto cleanup; 787 } 788 789 #if OCSP_DEBUG 790 { 791 char *ustr = (char *)malloc(theUrl.Length + 1); 792 memmove(ustr, theUrl.Data, theUrl.Length); 793 ustr[theUrl.Length] = '\0'; 794 ocspdDebug("httpFetch: GET URI %s\n", ustr); 795 free(ustr); 796 } 797 #endif 798 799 /* allocate our http context */ 800 httpContext = (asynchttp_t *) malloc(sizeof(asynchttp_t)); 801 if(!httpContext) { 802 result = memFullErr; 803 goto cleanup; 804 } 805 memset(httpContext, 0, sizeof(asynchttp_t)); 806 807 /* read this many bytes at a time */ 808 httpContext->increment = READ_BUFFER_SIZE; 809 810 /* create the http GET request */ 811 httpContext->request = CFHTTPMessageCreateRequest(NULL, 812 CFSTR("GET"), cfUrl, kCFHTTPVersion1_1); 813 if(!httpContext->request) { 814 ocspdErrorLog("httpFetch: error creating CFHTTPMessage\n"); 815 result = CSSMERR_TP_INTERNAL_ERROR; 816 goto cleanup; 817 } 818 CFHTTPMessageSetHeaderFieldValue(httpContext->request, 819 kUserAgent, kAppUserAgent); 820 821 /* create the stream */ 822 httpContext->stream = CFReadStreamCreateForHTTPRequest(NULL, 823 httpContext->request); 824 if(!httpContext->stream) { 825 ocspdErrorLog("httpFetch: error creating CFReadStream\n"); 826 result = CSSMERR_TP_INTERNAL_ERROR; 827 goto cleanup; 828 } 829 830 /* specify automatic redirect handling */ 831 if(!CFReadStreamSetProperty(httpContext->stream, 832 kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue)) { 833 ocspdErrorLog("httpFetch: error setting autoredirect property\n"); 834 /* this error is non-fatal; keep going */ 835 } 836 837 /* set up possible proxy info */ 838 proxyDict = SCDynamicStoreCopyProxies(NULL); 839 if(proxyDict) { 840 ocspdDebug("httpFetch: setting proxy dict\n"); 841 CFReadStreamSetProperty(httpContext->stream, kCFStreamPropertyHTTPProxy, proxyDict); 842 CFReleaseNull(proxyDict); 843 } 844 845 /* set a reasonable timeout */ 846 timerContext.info = httpContext; 847 httpContext->timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 848 CFAbsoluteTimeGetCurrent() + READ_STREAM_TIMEOUT, 849 0, 0, 0, asynchttp_timer_proc, &timerContext); 850 if (httpContext->timer == NULL) { 851 ocspdErrorLog("httpFetch: error creating timer\n"); 852 /* oh well - keep going */ 853 } else { 854 CFRunLoopAddTimer(CFRunLoopGetCurrent(), httpContext->timer, 855 kCFRunLoopDefaultMode); 856 } 857 858 /* set up our response callback and schedule it on the current run loop */ 859 httpContext->finished = 0; 860 clientContext.info = httpContext; 861 CFReadStreamSetClient(httpContext->stream, 862 (kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered), 863 handle_server_response, &clientContext); 864 CFReadStreamScheduleWithRunLoop(httpContext->stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 865 866 /* open the stream */ 867 if(CFReadStreamOpen(httpContext->stream) == false) { 868 ocspdErrorLog("httpFetch: error opening CFReadStream\n"); 869 result = CSSMERR_APPLETP_NETWORK_FAILURE; 870 goto cleanup; 871 } 872 873 /* cycle the run loop until we get a response or time out */ 874 startTime = stopTime = CFAbsoluteTimeGetCurrent(); 875 while (!httpContext->finished) { 876 (void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, TRUE); 877 CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent(); 878 if (curTime != stopTime) { 879 stopTime = curTime; 880 } 881 } 882 883 if(httpContext->responseLength == 0) { 884 ocspdErrorLog("httpFetch: CFReadStreamRead: no data was read after %f seconds\n", 885 stopTime-startTime); 886 result = CSSMERR_APPLETP_NETWORK_FAILURE; 887 goto cleanup; 888 } 889 ocspdDebug("httpFetch: total %lu bytes read in %f seconds\n", 890 (unsigned long)httpContext->responseLength, stopTime-startTime); 891 if(httpContext->data) { 892 CFIndex len = CFDataGetLength(httpContext->data); 893 fetched.Data = (uint8 *)alloc.malloc(len); 894 fetched.Length = len; 895 memmove(fetched.Data, CFDataGetBytePtr(httpContext->data), len); 896 } else { 897 result = CSSMERR_TP_INTERNAL_ERROR; 898 } 899cleanup: 900 CFReleaseSafe(cfUrl); 901 asynchttp_free(httpContext); 902 903 return result; 904} 905 906/* Fetch cert or CRL from net, we figure out the schema */ 907CSSM_RETURN ocspdNetFetch( 908 Allocator &alloc, 909 const CSSM_DATA &url, 910 LF_Type lfType, 911 CSSM_DATA &fetched) // mallocd in alloc space and RETURNED 912{ 913 #if OCSP_DEBUG 914 { 915 char *ustr = (char *)malloc(url.Length + 1); 916 memmove(ustr, url.Data, url.Length); 917 ustr[url.Length] = '\0'; 918 ocspdDebug("ocspdNetFetch: fetching from URI %s\n", ustr); 919 free(ustr); 920 } 921 #endif 922 923 if(url.Length < 5) { 924 return CSSMERR_APPLETP_CRL_BAD_URI; 925 } 926 if(!strncmp((char *)url.Data, "ldap:", 5)) { 927 return ldapFetch(alloc, url, lfType, fetched); 928 } 929 if(!strncmp((char *)url.Data, "http:", 5) || 930 !strncmp((char *)url.Data, "https:", 6)) { 931 return httpFetch(alloc, url, lfType, fetched); 932 } 933 return CSSMERR_APPLETP_CRL_BAD_URI; 934} 935 936/* Maximum CRL length to consider putting in the cache db (128KB) */ 937#define CRL_MAX_DATA_LENGTH (1024*128) 938 939/* Post-process network fetched data after finishing download. */ 940CSSM_RETURN ocspdFinishNetFetch( 941 async_fetch_t *fetchParams) 942{ 943 CSSM_RETURN crtn = CSSMERR_APPLETP_NETWORK_FAILURE; 944 if(!fetchParams) { 945 return crtn; 946 } 947 StLock<Mutex> _(gParamsLock); /* lock before accessing parameters */ 948 if(fetchParams->result != CSSM_OK) { 949 ocspdErrorLog("ocspdFinishNetFetch: CRL not found on net"); 950 crtn = fetchParams->result; 951 } 952 else if(fetchParams->fetched.Length == 0) { 953 ocspdErrorLog("ocspdFinishNetFetch: no CRL data found"); 954 crtn = CSSMERR_APPLETP_NETWORK_FAILURE; 955 } 956 else if(fetchParams->fetched.Length > CRL_MAX_DATA_LENGTH) { 957 if (fetchParams->fetched.Data) { 958 /* Write oversize CRL data to file */ 959 StLock<Mutex> w_(gFileWriteLock); 960 crlCheckCachePath(); 961 int rtn = writeFile(fetchParams->outFile, fetchParams->fetched.Data, 962 fetchParams->fetched.Length); 963 if(rtn) { 964 ocspdErrorLog("Error %d writing %s\n", rtn, fetchParams->outFile); 965 } 966 else { 967 ocspdCrlDebug("ocspdFinishNetFetch wrote %lu bytes to %s", 968 fetchParams->fetched.Length, fetchParams->outFile); 969 970 if(chmod(fetchParams->outFile, 0644)) { 971 ocspdErrorLog("ocspdFinishNetFetch: chmod error %d for %s", 972 errno, fetchParams->outFile); 973 } 974 } 975 (*(fetchParams->alloc)).free(fetchParams->fetched.Data); 976 fetchParams->fetched.Data = NULL; 977 } 978 crtn = CSSMERR_APPLETP_NETWORK_FAILURE; 979 } 980 return crtn; 981} 982 983/* Fetch cert or CRL from net asynchronously. */ 984void ocspdNetFetchAsync( 985 void *context) 986{ 987 async_fetch_t *params = (async_fetch_t *)context; 988 ocspdCrlDebug("ocspdNetFetchAsync with context %p", context); 989 CSSM_RETURN crtn = 0; 990 CFStringRef fileNameStr = NULL; 991 CFStringRef pemNameStr = NULL; 992 CFAbsoluteTime fetchTime, verifyTime; 993 CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); 994 Boolean downloadInProgress = false; 995 Boolean wroteFile = false; 996 Boolean isCRL = false; 997 998 if(params) { 999 StLock<Mutex> _(gParamsLock); /* lock before accessing parameters */ 1000 params->finished = 0; 1001 isCRL = (params->lfType == LT_Crl); 1002 if(params->crlNames.pemFile) { 1003 pemNameStr = CFStringCreateWithCString(kCFAllocatorDefault, 1004 params->crlNames.pemFile, kCFStringEncodingUTF8); 1005 } 1006 if(params->outFile) { 1007 fileNameStr = CFStringCreateWithCString(kCFAllocatorDefault, 1008 params->outFile, kCFStringEncodingUTF8); 1009 } 1010 if(fileNameStr) { 1011 /* make sure we aren't already downloading this file */ 1012 StLock<Mutex> _(gListLock); /* lock before examining list */ 1013 if(gDownloadList == NULL) { 1014 gDownloadList = CFArrayCreateMutable(kCFAllocatorDefault, 1015 0, &kCFTypeArrayCallBacks); 1016 crtn = (gDownloadList) ? crtn : CSSMERR_TP_INTERNAL_ERROR; 1017 params->result = crtn; 1018 } 1019 if(!crtn) { 1020 downloadInProgress = CFArrayContainsValue(gDownloadList, 1021 CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr); 1022 if(!downloadInProgress) { 1023 /* add this filename to the global list which tells other 1024 * callers of the crlStatus MIG function that we are 1025 * already downloading this file. 1026 */ 1027 CFArrayAppendValue(gDownloadList, fileNameStr); 1028 } else { 1029 /* already downloading; indicate "busy, try later" status */ 1030 crtn = CSSMERR_APPLETP_NETWORK_FAILURE; 1031 params->result = crtn; 1032 } 1033 } 1034 } 1035 } 1036 1037 if(params && !crtn && !downloadInProgress) { 1038 /* fetch data into buffer */ 1039 crtn = ocspdNetFetch(*(params->alloc), 1040 params->url, params->lfType, params->fetched); 1041 { 1042 StLock<Mutex> _(gParamsLock); 1043 params->result = crtn; 1044 } 1045 /* potentially write data to file */ 1046 crtn = ocspdFinishNetFetch(params); 1047 { 1048 StLock<Mutex> _(gParamsLock); 1049 params->result = crtn; 1050 wroteFile = (!params->fetched.Data && params->fetched.Length > CRL_MAX_DATA_LENGTH); 1051 } 1052 fetchTime = CFAbsoluteTimeGetCurrent() - startTime; 1053 ocspdCrlDebug("%f seconds to download file", fetchTime); 1054 1055 if(isCRL && wroteFile) { 1056 /* write issuers to .pem file */ 1057 StLock<Mutex> _(gListLock); /* lock before examining list */ 1058 CFDataRef issuersData = NULL; 1059 if(gIssuersDict) { 1060 issuersData = (CFDataRef)CFDictionaryGetValue(gIssuersDict, 1061 pemNameStr); 1062 } else { 1063 ocspdCrlDebug("No issuers available for %s", 1064 params->crlNames.pemFile); 1065 gIssuersDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, 1066 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1067 } 1068 if(!issuersData) { 1069 /* add the Apple issuers if we have nothing else */ 1070 issuersData = CFDataCreate(kCFAllocatorDefault, 1071 (const UInt8 *)Apple_CRL_Issuers, (CFIndex)Apple_CRL_Issuers_Length); 1072 if(issuersData) { 1073 CFDictionarySetValue(gIssuersDict, pemNameStr, issuersData); 1074 CFRelease(issuersData); 1075 } 1076 } 1077 if(issuersData) { 1078 StLock<Mutex> _(gFileWriteLock); /* obtain lock before writing */ 1079 crlCheckCachePath(); 1080 int rtn = writeFile(params->crlNames.pemFile, 1081 (const unsigned char *)CFDataGetBytePtr(issuersData), 1082 CFDataGetLength(issuersData)); 1083 if(rtn) { 1084 ocspdErrorLog("Error %d writing %s\n", 1085 rtn, params->crlNames.pemFile); 1086 } 1087 else if(chmod(params->crlNames.pemFile, 0644)) { 1088 ocspdErrorLog("ocsp_server_crlStatus: chmod error %d for %s", 1089 errno, params->crlNames.pemFile); 1090 } 1091 } 1092 } 1093 1094 if(isCRL && wroteFile) { 1095 /* validate .crl signature (creates .update and .revoked files) */ 1096 crlSignatureValid(params->crlNames.crlFile, 1097 params->crlNames.pemFile, 1098 params->crlNames.updateFile, 1099 params->crlNames.revokedFile); 1100 verifyTime = ( CFAbsoluteTimeGetCurrent() - startTime ) - fetchTime; 1101 ocspdCrlDebug("%f seconds to validate CRL", verifyTime); 1102 } 1103 1104 if(fileNameStr) { 1105 /* all finished downloading, so remove filename from global list */ 1106 StLock<Mutex> _(gListLock); 1107 CFIndex idx = CFArrayGetFirstIndexOfValue(gDownloadList, 1108 CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr); 1109 if(idx >= 0) { 1110 CFArrayRemoveValueAtIndex(gDownloadList, idx); 1111 } 1112 } 1113 } 1114 1115 if(params) { 1116 StLock<Mutex> _(gParamsLock); 1117 params->finished = 1; 1118 1119 if(params->freeOnDone) { 1120 /* caller does not expect a reply; we must clean up everything. */ 1121 if(params->url.Data) { 1122 free(params->url.Data); 1123 } 1124 if(params->outFile) { 1125 free(params->outFile); 1126 } 1127 if(params->crlNames.crlFile) { 1128 free(params->crlNames.crlFile); 1129 } 1130 if(params->crlNames.pemFile) { 1131 free(params->crlNames.pemFile); 1132 } 1133 if(params->crlNames.updateFile) { 1134 free(params->crlNames.updateFile); 1135 } 1136 if(params->crlNames.revokedFile) { 1137 free(params->crlNames.revokedFile); 1138 } 1139 if(params->fetched.Data) { 1140 (*(params->alloc)).free(params->fetched.Data); 1141 } 1142 free(params); 1143 } 1144 } 1145 1146 if(fileNameStr) { 1147 CFRelease(fileNameStr); 1148 } 1149 if(pemNameStr) { 1150 CFRelease(pemNameStr); 1151 } 1152} 1153 1154/* Kick off net fetch of a cert or a CRL and return immediately. */ 1155CSSM_RETURN ocspdStartNetFetch( 1156 async_fetch_t *fetchParams) 1157{ 1158 dispatch_queue_t queue = dispatch_get_global_queue( 1159 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 1160 1161 ocspdCrlDebug("ocspdStartNetFetch with context %p", (void*)fetchParams); 1162 1163 dispatch_async_f(queue, fetchParams, ocspdNetFetchAsync); 1164 1165 return 0; 1166} 1167 1168