1/* 2 * Copyright (c) 2012-2014 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.mm - Network support for ocspd and CRL/cert fetch 26 */ 27 28#if OCSP_DEBUG 29#define OCSP_USE_SYSLOG 1 30#endif 31#include <security_ocspd/ocspdDebug.h> 32#include "appleCrlIssuers.h" 33#include "ocspdNetwork.h" 34#include <security_ocspd/ocspdUtils.h> 35#include <CoreFoundation/CoreFoundation.h> 36#include <security_cdsa_utils/cuEnc64.h> 37#include <security_cdsa_utils/cuFileIo.h> 38#include <stdlib.h> 39#include <netinet/in.h> 40#include <sys/socket.h> 41#include <sys/stat.h> 42#include <dispatch/dispatch.h> 43#include <Security/cssmapple.h> 44#include <security_utilities/cfutilities.h> 45#include <CoreServices/CoreServices.h> 46#include <SystemConfiguration/SCDynamicStoreCopySpecific.h> 47#include <SystemConfiguration/SCNetworkReachability.h> 48 49#include <Foundation/Foundation.h> 50 51/* enable deprecated function declarations */ 52#ifndef LDAP_DEPRECATED 53#define LDAP_DEPRECATED 1 54#endif 55#include <LDAP/ldap.h> 56 57/* useful macros for CF */ 58#define CFReleaseSafe(CF) { CFTypeRef _cf = (CF); if (_cf) CFRelease(_cf); } 59#define CFReleaseNull(CF) { CFTypeRef _cf = (CF); \ 60 if (_cf) { (CF) = NULL; CFRelease(_cf); } } 61 62extern Mutex gParamsLock; 63extern Mutex gFileWriteLock; 64extern Mutex gListLock; 65extern CFMutableArrayRef gDownloadList; 66extern CFMutableDictionaryRef gIssuersDict; 67extern CFAbsoluteTime gLastActivity; 68 69extern bool crlSignatureValid( 70 const char *crlFileName, 71 const char *issuersFileName, 72 const char *updateFileName, 73 const char *revokedFileName); 74 75extern int crlCheckCachePath(); 76 77static const char* SYSTEM_KC_PATH = "/Library/Keychains/System.keychain"; 78 79/* how long to wait for more data to come in before we give up */ 80#define READ_STREAM_TIMEOUT 7.0 81 82/* read buffer size */ 83#define READ_BUFFER_SIZE 4096 84 85/* post buffer size */ 86#define POST_BUFFER_SIZE 1024 87 88/* max age of upstream cached response */ 89#define CACHED_MAX_AGE 300 90 91 92#pragma mark -- SecURLLoader -- 93 94@interface SecURLLoader : NSObject 95{ 96 NSURL *_url; 97 NSMutableURLRequest *_request; 98 NSURLConnection *_connection; 99 NSMutableData *_receivedData; 100 CFAbsoluteTime _timeToGiveUp; 101 NSData *_data; 102 NSTimer *_timer; 103 NSError *_error; 104 BOOL _finished; 105} 106 107- (id)initWithURL:(NSURL *)theURL; 108- (NSMutableURLRequest *)request; 109- (void)startLoad; 110- (void)syncLoad; 111- (void)cancelLoad; 112- (void)resetTimeout; 113- (BOOL)finished; 114- (NSData *)data; 115- (NSError*)error; 116@end 117 118@implementation SecURLLoader 119 120- (id)initWithURL:(NSURL *)theURL 121{ 122 if (self = [super init]) { 123 _url = [theURL copy]; 124 } 125 return self; 126} 127 128- (void) dealloc 129{ 130 [self cancelLoad]; 131 [_url release]; 132 [_request release]; 133 [_data release]; 134 [_error release]; 135 [super dealloc]; 136} 137 138- (NSMutableURLRequest *)request 139{ 140 if (!_request) { 141 _request = [[NSMutableURLRequest alloc] initWithURL:_url]; 142 } 143 144 // Set cache policy to always load from origin and not the local cache 145 [_request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; 146 147 return _request; 148} 149 150- (void)startLoad 151{ 152 // Cancel any load currently in progress and clear instance variables 153 [self cancelLoad]; 154 155 _finished = NO; 156 157 // Create an empty data to receive bytes from the download 158 _receivedData = [[NSMutableData alloc] init]; 159 160 // Start the download 161 _connection = [[NSURLConnection alloc] initWithRequest:[self request] delegate:self]; 162 if (!_connection) { 163 ocspdDebug("Failed to open connection (is network available?)"); 164 [self timeout]; 165 return; 166 } 167 168 // Start the timer 169 [self resetTimeout]; 170 _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self 171 selector:@selector(timeoutCheck) 172 userInfo:nil repeats:YES]; 173 [_timer retain]; 174} 175 176- (void)syncLoad 177{ 178 [self startLoad]; 179 180 // cycle the run loop until we get a response or time out 181 CFAbsoluteTime stopTime = CFAbsoluteTimeGetCurrent(); 182 while (![self finished]) { 183 (void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, TRUE); 184 CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent(); 185 if (curTime != stopTime) { 186 stopTime = curTime; 187 } 188 } 189} 190 191- (void)cancelLoad 192{ 193 if (_timer) { 194 [_timer invalidate]; 195 [_timer release]; 196 _timer = nil; 197 } 198 if (_connection) { 199 [_connection cancel]; 200 [_connection release]; 201 _connection = nil; 202 } 203 if (_receivedData) { 204 // Hold onto the last data we received 205 if (_data) [_data release]; 206 _data = _receivedData; 207 _receivedData = nil; 208 } 209} 210 211- (void)timeoutCheck 212{ 213 if (_finished) { 214 return; // already completed 215 } 216 if (_timeToGiveUp > CFAbsoluteTimeGetCurrent()) { 217 return; // not time yet... 218 } 219 // give up and cancel the download 220 [self cancelLoad]; 221 OSStatus err = CSSMERR_APPLETP_NETWORK_FAILURE; 222 _error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; 223 _finished = YES; 224} 225 226- (void)resetTimeout 227{ 228 gLastActivity = CFAbsoluteTimeGetCurrent(); 229 _timeToGiveUp = gLastActivity + READ_STREAM_TIMEOUT; 230} 231 232- (BOOL)finished 233{ 234 return _finished; 235} 236 237- (NSData *)data 238{ 239 return _data; 240} 241 242- (NSError *)error 243{ 244 return _error; 245} 246 247/* NSURLConnection delegate methods */ 248- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)newData 249{ 250 if (![newData length]) 251 return; 252 253 [_receivedData appendData:newData]; 254 [self resetTimeout]; 255} 256 257- (void)connectionDidFinishLoading:(NSURLConnection *)connection 258{ 259 [self cancelLoad]; 260 if (_error) { 261 [_error release]; 262 _error = nil; 263 } 264 _finished = YES; 265} 266 267- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 268{ 269 //CFNetDiagnosticRef diagnostics = CFNetDiagnosticCreateWithURL(NULL, (CFURLRef)url); 270 //%%% add debug code to print diagnostics 271 272 [self cancelLoad]; 273 _error = [error retain]; 274 _finished = YES; 275} 276 277- (BOOL)connection:(NSURLConnection *)connection 278 canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace 279{ 280 return ([protectionSpace isProxy]); 281} 282 283- (void)connection:(NSURLConnection *)connection 284 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 285{ 286 NSInteger failCount = [challenge previousFailureCount]; 287 NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; 288 if (failCount > 0) { 289 #if OCSP_DEBUG 290 NSLog(@"Cancelling authentication challenge (failure count = %ld). Is proxy password correct?", (long)failCount); 291 #endif 292 [[challenge sender] cancelAuthenticationChallenge:challenge]; 293 return; 294 } 295 // Try to look up our proxy credential in the System keychain. 296 // Don't specify protocol (kSecProtocolTypeHTTPProxy, etc.) in our item since that 297 // can cause it to be found by AuthBrokerAgent, and since it's running as the user, 298 // it won't have access to the System keychain. 299 // 300 NSURLCredential *credential = NULL; 301 SecKeychainRef keychain = NULL; 302 NSString *host = [protectionSpace host]; 303 NSInteger port = [protectionSpace port]; 304 OSStatus status = SecKeychainOpen(SYSTEM_KC_PATH, &keychain); 305 if (status == noErr) { 306 // ask for attributes as well, so we can get the account name 307 NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: 308 (id)keychain, kSecUseKeychain, 309 (id)kSecClassInternetPassword, kSecClass, 310 (id)host, kSecAttrServer, 311 (id)[NSNumber numberWithInteger:port], kSecAttrPort, 312 (id)kSecMatchLimitOne, kSecMatchLimit, 313 (id)[NSNumber numberWithBool:YES], kSecReturnAttributes, 314 (id)[NSNumber numberWithBool:YES], kSecReturnData, 315 (id)nil, (id)nil]; 316 317 CFDictionaryRef result = NULL; 318 status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result); 319 #if OCSP_DEBUG 320 NSLog(@"SecItemCopyMatching returned %d looking up host=%@, port=%d", 321 (int)status, host, (int)port); 322 #endif 323 if (!status && result) { 324 if (CFDictionaryGetTypeID() == CFGetTypeID(result)) { 325 NSString *account = [(NSDictionary *)result objectForKey:(id)kSecAttrAccount]; 326 NSData *passwordData = [(NSDictionary *)result objectForKey:(id)kSecValueData]; 327 NSString *password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding]; 328 if (account && password) { 329 #if OCSP_DEBUG 330 NSLog(@"Found credential for %@:%d (length=%ld)", host, (int)port, (long)[password length]); 331 #endif 332 credential = [NSURLCredential credentialWithUser:account password:password persistence:NSURLCredentialPersistenceForSession]; 333 } 334 [password release]; 335 } 336 CFRelease(result); 337 } 338 } 339 else { 340 #if OCSP_DEBUG 341 NSLog(@"SecKeychainOpen error %d", (int)status); 342 #endif 343 } 344 if (keychain) { 345 CFRelease(keychain); 346 } 347 if (credential) { 348 #if OCSP_DEBUG 349 NSLog(@"Authentication challenge received, setting proxy credential"); 350 #endif 351 [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; 352 return; 353 } 354 355 NSLog(@"Authentication challenge received for \"%@:%d\", unable to obtain proxy server credential from System keychain.", host, (int)port); 356 static bool printedHint = false; 357 if (!printedHint) { 358 NSLog(@"You can specify the username and password for a proxy server with /usr/bin/security. Example: sudo security add-internet-password -a squiduser -l \"HTTP Proxy\" -P 3128 -r 'http' -s localhost -w squidpass -U -T /usr/sbin/ocspd /Library/Keychains/System.keychain"); 359 printedHint = true; 360 } 361 // Cancel the challenge since we do not have a credential to present (14761252) 362 [[challenge sender] cancelAuthenticationChallenge:challenge]; 363} 364 365/* NSURLConnectionDataDelegate methods */ 366- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse 367{ 368 // Returning nil prevents the loader from passing the response to the URL cache sub-system for caching. 369 return (NSCachedURLResponse *)nil; 370} 371 372@end 373 374void enableAutoreleasePool(int enable); 375void enableAutoreleasePool(int enable) 376{ 377 static NSAutoreleasePool *_pool = NULL; 378 if (!_pool) { 379 _pool = [[NSAutoreleasePool alloc] init]; 380 } 381 if (!enable && _pool) { 382 [_pool drain]; 383 } 384} 385 386#pragma mark ----- OCSP fetch ----- 387 388/* POST method has Content-Type header line equal to "application/ocsp-request" */ 389static NSString* kContentType = @"Content-Type"; 390static NSString* kAppOcspRequest = @"application/ocsp-request"; 391static NSString* kContentLength = @"Content-Length"; 392static NSString* kUserAgent = @"User-Agent"; 393static NSString* kAppUserAgent = @"ocspd/1.0.3"; 394static NSString* kCacheControl = @"Cache-Control"; 395 396#if OCSP_DEBUG 397#define DUMP_BLOBS 1 398#endif 399 400#define OCSP_GET_FILE "/tmp/ocspGet" 401#define OCSP_RESP_FILE "/tmp/ocspResp" 402 403#if DUMP_BLOBS 404 405static void writeBlob( 406 const char *fileName, 407 const char *whatIsIt, 408 const unsigned char *data, 409 unsigned dataLen) 410{ 411 if(writeFile(fileName, data, dataLen)) { 412 printf("***Error writing %s to %s\n", whatIsIt, fileName); 413 } 414 else { 415 printf("...wrote %u bytes of %s to %s\n", dataLen, whatIsIt, fileName); 416 } 417} 418 419#else 420 421#define writeBlob(f,w,d,l) 422 423#endif /* DUMP_BLOBS */ 424 425/* OCSP fetch via HTTP using GET (preferred) or POST (if required) */ 426 427CSSM_RETURN ocspdHttpFetch( 428 SecAsn1CoderRef coder, 429 const CSSM_DATA &url, 430 const CSSM_DATA &ocspReq, // DER encoded 431 CSSM_DATA &fetched) // mallocd in coder space and RETURNED 432{ 433 CSSM_RETURN result = CSSM_OK; 434 unsigned char *fullUrl = NULL; 435 CFURLRef cfUrl = NULL; 436 CFStringRef urlStr = NULL; 437 CFDataRef postData = NULL; 438 SecURLLoader *urlLoader = nil; 439 bool done = false; 440 size_t totalLen; 441 CFIndex len; 442 443 /* trim off possible NULL terminator from incoming URL */ 444 size_t urlLen = url.Length; 445 if(url.Data[urlLen - 1] == '\0') { 446 urlLen--; 447 } 448 449 /* base64 encode the OCSP request; that's used as a path */ 450 unsigned char *endp, *req64 = NULL; 451 unsigned req64Len = 0; 452 req64 = cuEnc64(ocspReq.Data, (unsigned)ocspReq.Length, &req64Len); 453 if(req64 == NULL) { 454 ocspdErrorLog("ocspdHttpFetch: error base64-encoding request\n"); 455 result = CSSMERR_TP_INTERNAL_ERROR; 456 goto cleanup; 457 } 458 459 /* trim off trailing NULL and newline */ 460 endp = req64 + req64Len - 1; 461 for(;;) { 462 switch(*endp) { 463 case '\0': 464 case '\n': 465 case '\r': 466 endp--; 467 req64Len--; 468 break; 469 default: 470 done = true; 471 break; 472 } 473 if(done) { 474 break; 475 } 476 } 477 478 /* sanity check length of request */ 479 if( (req64Len >= INT_MAX) || (urlLen > (INT_MAX - (1 + req64Len))) ) { 480 /* long URL is long; concatenating these components would overflow totalLen */ 481 result = CSSMERR_TP_INVALID_DATA; 482 goto cleanup; 483 } 484 485 if(urlLen && req64Len) { 486 CFStringRef incomingURLStr = CFStringCreateWithBytes(NULL, (const UInt8 *)url.Data, urlLen, kCFStringEncodingUTF8, false); 487 CFStringRef requestPathStr = CFStringCreateWithBytes(NULL, (const UInt8 *)req64, req64Len, kCFStringEncodingUTF8, false); 488 if(incomingURLStr && requestPathStr) { 489 /* percent-encode all reserved characters from RFC 3986 [2.2] */ 490 CFStringRef encodedRequestStr = CFURLCreateStringByAddingPercentEscapes(NULL, 491 requestPathStr, NULL, CFSTR(":/?#[]@!$&'()*+,;="), kCFStringEncodingUTF8); 492 if(encodedRequestStr) { 493 CFMutableStringRef tempStr = CFStringCreateMutable(NULL, 0); 494 if (tempStr) { 495 CFStringAppend(tempStr, incomingURLStr); 496 CFStringAppend(tempStr, CFSTR("/")); 497 CFStringAppend(tempStr, encodedRequestStr); 498 urlStr = (CFStringRef)tempStr; 499 } 500 } 501 CFReleaseSafe(encodedRequestStr); 502 } 503 CFReleaseSafe(incomingURLStr); 504 CFReleaseSafe(requestPathStr); 505 } 506 if(urlStr == NULL) { 507 ocspdErrorLog("ocspdHttpFetch: error percent-encoding request\n"); 508 result = CSSMERR_TP_INTERNAL_ERROR; 509 goto cleanup; 510 } 511 512 /* RFC 5019 says we MUST use the GET method if the URI is less than 256 bytes 513 * (to enable response caching), otherwise we SHOULD use the POST method. 514 */ 515 totalLen = CFStringGetLength(urlStr); 516 if (totalLen < 256) { 517 /* we can safely use GET */ 518 cfUrl = CFURLCreateWithString(NULL, urlStr, NULL); 519 } 520 else { 521 /* request too big for GET; use POST instead */ 522 writeBlob(OCSP_GET_FILE, "OCSP Request as POST data", ocspReq.Data, (unsigned int)ocspReq.Length); 523 postData = CFDataCreate(NULL, ocspReq.Data, ocspReq.Length); 524 cfUrl = CFURLCreateWithBytes(NULL, url.Data, urlLen, 525 kCFStringEncodingUTF8, 526 NULL); 527 } 528 529 if(cfUrl) { 530 /* create URL with explicit path (see RFC 2616 3.2.2, 5.1.2) */ 531 CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl); 532 if(pathStr) { 533 if (CFStringGetLength(pathStr) == 0) { 534 CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL, cfUrl, CFSTR(""), FALSE); 535 CFRelease(cfUrl); 536 cfUrl = tmpUrl; 537 } 538 CFRelease(pathStr); 539 } 540 } 541 if(!cfUrl) { 542 ocspdErrorLog("ocspdHttpFetch: CFURLCreateWithBytes returned NULL\n"); 543 result = CSSMERR_APPLETP_CRL_BAD_URI; 544 goto cleanup; 545 } 546 547#if OCSP_DEBUG 548 { 549 size_t len = (fullUrl) ? totalLen : urlLen; 550 char *ubuf = (char *)((fullUrl) ? fullUrl : url.Data); 551 char *ustr = (char *)malloc(len + 1); 552 memmove(ustr, ubuf, len); 553 ustr[len] = '\0'; 554 ocspdDebug("ocspdHttpFetch via %s to URI %s\n", 555 (fullUrl) ? "GET" : "POST", ustr); 556 free(ustr); 557 } 558#endif 559 560@autoreleasepool { 561 /* set up the URL request */ 562 urlLoader = [[SecURLLoader alloc] initWithURL:(NSURL*)cfUrl]; 563 if (urlLoader) { 564 NSMutableURLRequest *request = [urlLoader request]; 565 if (postData) { 566 [request setHTTPMethod:@"POST"]; 567 [request setHTTPBody:(NSData*)postData]; 568 // Content-Type header 569 [request setValue:kAppOcspRequest forHTTPHeaderField:kContentType]; 570 // Content-Length header 571 NSString *postLength = [NSString stringWithFormat:@"%lu",(unsigned long)[(NSData*)postData length]]; 572 [request setValue:postLength forHTTPHeaderField:kContentLength]; 573 } 574 else { 575 [request setHTTPMethod:@"GET"]; 576 // Cache-Control header 577 NSString *maxAge = [NSString stringWithFormat:@"max-age=%d", CACHED_MAX_AGE]; 578 [request setValue:maxAge forHTTPHeaderField:kCacheControl]; 579 } 580 // User-Agent header 581 [request setValue:kAppUserAgent forHTTPHeaderField:kUserAgent]; 582 } 583 584 /* load it! */ 585 [urlLoader syncLoad]; 586 587 /* return the response data */ 588 len = [[urlLoader data] length]; 589 if (!len || [urlLoader error]) { 590 result = CSSMERR_APPLETP_NETWORK_FAILURE; 591 } 592 else { 593 fetched.Data = (uint8 *)SecAsn1Malloc(coder, len); 594 fetched.Length = len; 595 memmove(fetched.Data, [[urlLoader data] bytes], len); 596 writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, (unsigned int)fetched.Length); 597 result = CSSM_OK; 598 } 599 [urlLoader release]; 600} /* end autorelease scope */ 601cleanup: 602 CFReleaseSafe(postData); 603 CFReleaseSafe(urlStr); 604 CFReleaseSafe(cfUrl); 605 if(fullUrl) { 606 free(fullUrl); 607 } 608 if(req64) { 609 free(req64); 610 } 611 return result; 612} 613 614 615#pragma mark ----- LDAP fetch ----- 616 617/* 618 * LDAP attribute names, used if not present in URI. 619 */ 620#define LDAP_ATTR_CERT "cacertificate;binary" 621#define LDAP_ATTR_CRL "certificaterevocationlist;binary" 622 623/* 624 * Default LDAP options. 625 */ 626#define LDAP_REFERRAL_DEFAULT LDAP_OPT_ON 627 628static CSSM_RETURN ldapRtnToCssm( 629 int rtn) 630{ 631 switch(rtn) { 632 case LDAP_SERVER_DOWN: 633 case LDAP_TIMEOUT: 634 case LDAP_CONNECT_ERROR: 635 return CSSMERR_APPLETP_CRL_SERVER_DOWN; 636 case LDAP_PARAM_ERROR: 637 case LDAP_FILTER_ERROR: 638 return CSSMERR_APPLETP_CRL_BAD_URI; 639 default: 640 return CSSMERR_APPLETP_CRL_NOT_FOUND; 641 } 642} 643 644static CSSM_RETURN ldapFetch( 645 Allocator &alloc, 646 const CSSM_DATA &url, 647 LF_Type lfType, 648 CSSM_DATA &fetched) // mallocd in alloc space and RETURNED 649{ 650 BerValue **value = NULL; 651 LDAPURLDesc *urlDesc = NULL; 652 int rtn; 653 LDAPMessage *msg = NULL; 654 LDAP *ldap = NULL; 655 LDAPMessage *entry = NULL; 656 bool mallocdString = false; 657 char *urlStr; 658 int numEntries; 659 CSSM_RETURN ourRtn = CSSM_OK; 660 /* attr input to ldap_search_s() */ 661 char *attrArray[2]; 662 char **attrArrayP = NULL; 663 664 /* don't assume URL string is NULL terminated */ 665 if(url.Data[url.Length - 1] == '\0') { 666 urlStr = (char *)url.Data; 667 } 668 else { 669 urlStr = (char *)malloc(url.Length + 1); 670 memmove(urlStr, url.Data, url.Length); 671 urlStr[url.Length] = '\0'; 672 mallocdString = true; 673 } 674 675 /* break up the URL into something usable */ 676 rtn = ldap_url_parse(urlStr, &urlDesc); 677 if(rtn) { 678 ocspdErrorLog("ldap_url_parse returned %d", rtn); 679 if(mallocdString) { 680 free(urlStr); 681 } 682 return CSSMERR_APPLETP_CRL_BAD_URI; 683 } 684 685 /* 686 * Determine what attr we're looking for. 687 */ 688 if((urlDesc->lud_attrs != NULL) && // attrs present in URL 689 (urlDesc->lud_attrs[0] != NULL) && // at least one attr present 690 (urlDesc->lud_attrs[1] == NULL)) { 691 /* 692 * Exactly one attr present in the caller-specified URL; 693 * assume that this is exactly what we want. 694 */ 695 attrArrayP = &urlDesc->lud_attrs[0]; 696 } 697 else { 698 /* use caller-specified attr */ 699 switch(lfType) { 700 case LT_Crl: 701 attrArray[0] = (char *)LDAP_ATTR_CRL; 702 break; 703 case LT_Cert: 704 attrArray[0] = (char *)LDAP_ATTR_CERT; 705 break; 706 default: 707 printf("***ldapFetch screwup: bogus lfType (%d)\n", 708 (int)lfType); 709 return CSSMERR_CSSM_INTERNAL_ERROR; 710 } 711 attrArray[1] = NULL; 712 attrArrayP = &attrArray[0]; 713 } 714 715 /* establish connection */ 716 rtn = ldap_initialize(&ldap, urlStr); 717 if(rtn) { 718 ocspdErrorLog("ldap_initialize returned %d\n", rtn); 719 if(mallocdString) { 720 free(urlStr); 721 } 722 return ldapRtnToCssm(rtn); 723 } 724 /* subsequent errors to cleanup: */ 725 rtn = ldap_simple_bind_s(ldap, NULL, NULL); 726 if(rtn) { 727 ocspdErrorLog("ldap_simple_bind_s returned %d\n", rtn); 728 ourRtn = ldapRtnToCssm(rtn); 729 goto cleanup; 730 } 731 732 rtn = ldap_set_option(ldap, LDAP_OPT_REFERRALS, LDAP_REFERRAL_DEFAULT); 733 if(rtn) { 734 ocspdErrorLog("ldap_set_option(referrals) returned %d\n", rtn); 735 ourRtn = ldapRtnToCssm(rtn); 736 goto cleanup; 737 } 738 739 rtn = ldap_search_s( 740 ldap, 741 urlDesc->lud_dn, 742 LDAP_SCOPE_SUBTREE, 743 urlDesc->lud_filter, 744 urlDesc->lud_attrs, 745 0, // attrsonly 746 &msg); 747 if(rtn) { 748 ocspdErrorLog("ldap_search_s returned %d\n", rtn); 749 ourRtn = ldapRtnToCssm(rtn); 750 goto cleanup; 751 } 752 753 /* 754 * We require exactly one entry (for now). 755 */ 756 numEntries = ldap_count_entries(ldap, msg); 757 if(numEntries != 1) { 758 ocspdErrorLog("tpCrlViaLdap: numEntries %d\n", numEntries); 759 ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND; 760 goto cleanup; 761 } 762 763 entry = ldap_first_entry(ldap, msg); 764 if(entry) { 765 ocspdErrorLog("ldapFetch: first entry %p\n", entry); 766 } 767 value = ldap_get_values_len(ldap, msg, attrArrayP[0]); 768 if(value == NULL) { 769 ocspdErrorLog("Error on ldap_get_values_len\n"); 770 ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND; 771 goto cleanup; 772 } 773 774 fetched.Length = value[0]->bv_len; 775 fetched.Data = (uint8 *)alloc.malloc(fetched.Length); 776 memmove(fetched.Data, value[0]->bv_val, fetched.Length); 777 778 ldap_value_free_len(value); 779 ourRtn = CSSM_OK; 780cleanup: 781 if(msg) { 782 ldap_msgfree(msg); 783 } 784 if(mallocdString) { 785 free(urlStr); 786 } 787 ldap_free_urldesc(urlDesc); 788 rtn = ldap_unbind(ldap); 789 if(rtn) { 790 ocspdErrorLog("Error %d on ldap_unbind\n", rtn); 791 /* oh well */ 792 } 793 return ourRtn; 794} 795 796#pragma mark ----- HTTP fetch via GET ----- 797 798/* fetch via HTTP */ 799static CSSM_RETURN httpFetch( 800 Allocator &alloc, 801 const CSSM_DATA &url, 802 LF_Type lfType, 803 CSSM_DATA &fetched) // mallocd in alloc space and RETURNED 804{ 805 #pragma unused (lfType) 806 ocspdHttpDebug("httpFetch: start\n"); 807 808 CSSM_RETURN result = CSSM_OK; 809 CFURLRef cfUrl = NULL; 810 SecURLLoader *urlLoader = nil; 811 CFAbsoluteTime startTime, stopTime; 812 CFIndex len; 813 814 /* trim off possible NULL terminator from incoming URL */ 815 CSSM_DATA theUrl = url; 816 if(theUrl.Data[theUrl.Length - 1] == '\0') { 817 theUrl.Length--; 818 } 819 820 /* create URL with explicit path (see RFC 2616 3.2.2, 5.1.2) */ 821 cfUrl = CFURLCreateWithBytes(NULL, 822 theUrl.Data, theUrl.Length, 823 kCFStringEncodingUTF8, 824 NULL); 825 if(cfUrl) { 826 CFStringRef pathStr = CFURLCopyLastPathComponent(cfUrl); 827 if(pathStr) { 828 if (CFStringGetLength(pathStr) == 0) { 829 CFURLRef tmpUrl = CFURLCreateCopyAppendingPathComponent(NULL, 830 cfUrl, CFSTR(""), FALSE); 831 CFRelease(cfUrl); 832 cfUrl = tmpUrl; 833 } 834 CFRelease(pathStr); 835 } 836 } 837 if(!cfUrl) { 838 ocspdErrorLog("httpFetch: CFURLCreateWithBytes returned NULL\n"); 839 result = CSSMERR_APPLETP_CRL_BAD_URI; 840 goto cleanup; 841 } 842 843 #if OCSP_DEBUG 844 { 845 char *ustr = (char *)malloc(theUrl.Length + 1); 846 memmove(ustr, theUrl.Data, theUrl.Length); 847 ustr[theUrl.Length] = '\0'; 848 ocspdDebug("httpFetch: GET URI %s\n", ustr); 849 free(ustr); 850 } 851 #endif 852 853@autoreleasepool { 854 /* set up the URL request */ 855 urlLoader = [[SecURLLoader alloc] initWithURL:(NSURL*)cfUrl]; 856 if (urlLoader) { 857 NSMutableURLRequest *request = [urlLoader request]; 858 [request setHTTPMethod:@"GET"]; 859 // User-Agent header 860 [request setValue:kAppUserAgent forHTTPHeaderField:kUserAgent]; 861 } 862 863 /* load it! */ 864 startTime = stopTime = CFAbsoluteTimeGetCurrent(); 865 [urlLoader syncLoad]; 866 stopTime = CFAbsoluteTimeGetCurrent(); 867 868 /* return the data */ 869 len = [[urlLoader data] length]; 870 if (!len || [urlLoader error]) { 871 result = CSSMERR_APPLETP_NETWORK_FAILURE; 872 } 873 else { 874 ocspdDebug("httpFetch: total %lu bytes read in %f seconds\n", 875 (unsigned long)len, stopTime-startTime); 876 fetched.Data = (uint8 *)alloc.malloc(len); 877 fetched.Length = len; 878 memmove(fetched.Data, [[urlLoader data] bytes], len); 879 result = CSSM_OK; 880 #if OCSP_DEBUG 881 writeBlob("/tmp/httpGetFile", "HTTP Fetch", fetched.Data, (unsigned int)fetched.Length); 882 #endif 883 } 884 [urlLoader release]; 885} /* end autorelease scope */ 886cleanup: 887 CFReleaseSafe(cfUrl); 888 889 return result; 890} 891 892/* Fetch cert or CRL from net, we figure out the schema */ 893CSSM_RETURN ocspdNetFetch( 894 Allocator &alloc, 895 const CSSM_DATA &url, 896 LF_Type lfType, 897 CSSM_DATA &fetched) // mallocd in alloc space and RETURNED 898{ 899 #if OCSP_DEBUG 900 { 901 char *ustr = (char *)malloc(url.Length + 1); 902 memmove(ustr, url.Data, url.Length); 903 ustr[url.Length] = '\0'; 904 ocspdDebug("ocspdNetFetch: fetching from URI %s\n", ustr); 905 free(ustr); 906 } 907 #endif 908 909 if(url.Length < 5) { 910 return CSSMERR_APPLETP_CRL_BAD_URI; 911 } 912 if(!strncmp((char *)url.Data, "ldap:", 5)) { 913 return ldapFetch(alloc, url, lfType, fetched); 914 } 915 if(!strncmp((char *)url.Data, "http:", 5) || 916 !strncmp((char *)url.Data, "https:", 6)) { 917 return httpFetch(alloc, url, lfType, fetched); 918 } 919 return CSSMERR_APPLETP_CRL_BAD_URI; 920} 921 922/* Maximum CRL length to consider putting in the cache db (128KB) */ 923#define CRL_MAX_DATA_LENGTH (1024*128) 924 925/* Post-process network fetched data after finishing download. */ 926CSSM_RETURN ocspdFinishNetFetch( 927 async_fetch_t *fetchParams) 928{ 929 CSSM_RETURN crtn = CSSMERR_APPLETP_NETWORK_FAILURE; 930 if(!fetchParams) { 931 return crtn; 932 } 933 StLock<Mutex> _(gParamsLock); /* lock before accessing parameters */ 934 if(fetchParams->result != CSSM_OK) { 935 ocspdErrorLog("ocspdFinishNetFetch: CRL not found on net"); 936 crtn = fetchParams->result; 937 } 938 else if(fetchParams->fetched.Length == 0) { 939 ocspdErrorLog("ocspdFinishNetFetch: no CRL data found"); 940 crtn = CSSMERR_APPLETP_NETWORK_FAILURE; 941 } 942 else if(fetchParams->fetched.Length > CRL_MAX_DATA_LENGTH) { 943 if (fetchParams->fetched.Data) { 944 /* Write oversize CRL data to file */ 945 StLock<Mutex> w_(gFileWriteLock); 946 crlCheckCachePath(); 947 int rtn = writeFile(fetchParams->outFile, fetchParams->fetched.Data, 948 (unsigned int)fetchParams->fetched.Length); 949 if(rtn) { 950 ocspdErrorLog("Error %d writing %s\n", rtn, fetchParams->outFile); 951 } 952 else { 953 ocspdCrlDebug("ocspdFinishNetFetch wrote %lu bytes to %s", 954 fetchParams->fetched.Length, fetchParams->outFile); 955 956 if(chmod(fetchParams->outFile, 0644)) { 957 ocspdErrorLog("ocspdFinishNetFetch: chmod error %d for %s", 958 errno, fetchParams->outFile); 959 } 960 } 961 (*(fetchParams->alloc)).free(fetchParams->fetched.Data); 962 fetchParams->fetched.Data = NULL; 963 } 964 crtn = CSSMERR_APPLETP_NETWORK_FAILURE; 965 } 966 return crtn; 967} 968 969/* Fetch cert or CRL from net asynchronously. */ 970static void ocspdNetFetchAsync( 971 void *context) 972{ 973 async_fetch_t *params = (async_fetch_t *)context; 974 ocspdCrlDebug("ocspdNetFetchAsync with context %p", context); 975 CSSM_RETURN crtn = 0; 976 CFStringRef fileNameStr = NULL; 977 CFStringRef pemNameStr = NULL; 978 CFAbsoluteTime fetchTime, verifyTime; 979 CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); 980 Boolean downloadInProgress = false; 981 Boolean wroteFile = false; 982 Boolean isCRL = false; 983 984 if(params) { 985 StLock<Mutex> _(gParamsLock); /* lock before accessing parameters */ 986 params->finished = 0; 987 isCRL = (params->lfType == LT_Crl); 988 if(params->crlNames.pemFile) { 989 pemNameStr = CFStringCreateWithCString(kCFAllocatorDefault, 990 params->crlNames.pemFile, kCFStringEncodingUTF8); 991 } 992 if(params->outFile) { 993 fileNameStr = CFStringCreateWithCString(kCFAllocatorDefault, 994 params->outFile, kCFStringEncodingUTF8); 995 } 996 if(fileNameStr) { 997 /* make sure we aren't already downloading this file */ 998 StLock<Mutex> _(gListLock); /* lock before examining list */ 999 if(gDownloadList == NULL) { 1000 gDownloadList = CFArrayCreateMutable(kCFAllocatorDefault, 1001 0, &kCFTypeArrayCallBacks); 1002 crtn = (gDownloadList) ? crtn : CSSMERR_TP_INTERNAL_ERROR; 1003 params->result = crtn; 1004 } 1005 if(!crtn) { 1006 downloadInProgress = CFArrayContainsValue(gDownloadList, 1007 CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr); 1008 if(!downloadInProgress) { 1009 /* add this filename to the global list which tells other 1010 * callers of the crlStatus MIG function that we are 1011 * already downloading this file. 1012 */ 1013 CFArrayAppendValue(gDownloadList, fileNameStr); 1014 } else { 1015 /* already downloading; indicate "busy, try later" status */ 1016 crtn = CSSMERR_APPLETP_NETWORK_FAILURE; 1017 params->result = crtn; 1018 } 1019 } 1020 } 1021 } 1022 1023 if(params && !crtn && !downloadInProgress) { 1024 /* fetch data into buffer */ 1025 crtn = ocspdNetFetch(*(params->alloc), 1026 params->url, params->lfType, params->fetched); 1027 { 1028 StLock<Mutex> _(gParamsLock); 1029 params->result = crtn; 1030 } 1031 /* potentially write data to file */ 1032 crtn = ocspdFinishNetFetch(params); 1033 { 1034 StLock<Mutex> _(gParamsLock); 1035 params->result = crtn; 1036 wroteFile = (!params->fetched.Data && params->fetched.Length > CRL_MAX_DATA_LENGTH); 1037 } 1038 fetchTime = CFAbsoluteTimeGetCurrent() - startTime; 1039 ocspdCrlDebug("%f seconds to download file", fetchTime); 1040 1041 if(isCRL && wroteFile) { 1042 /* write issuers to .pem file */ 1043 StLock<Mutex> _(gListLock); /* lock before examining list */ 1044 CFDataRef issuersData = NULL; 1045 if(gIssuersDict) { 1046 issuersData = (CFDataRef)CFDictionaryGetValue(gIssuersDict, 1047 pemNameStr); 1048 } else { 1049 ocspdCrlDebug("No issuers available for %s", 1050 params->crlNames.pemFile); 1051 gIssuersDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, 1052 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1053 } 1054 if(!issuersData) { 1055 /* add the Apple issuers if we have nothing else */ 1056 issuersData = CFDataCreate(kCFAllocatorDefault, 1057 (const UInt8 *)Apple_CRL_Issuers, (CFIndex)Apple_CRL_Issuers_Length); 1058 if(issuersData) { 1059 CFDictionarySetValue(gIssuersDict, pemNameStr, issuersData); 1060 CFRelease(issuersData); 1061 } 1062 } 1063 if(issuersData) { 1064 StLock<Mutex> _(gFileWriteLock); /* obtain lock before writing */ 1065 crlCheckCachePath(); 1066 int rtn = writeFile(params->crlNames.pemFile, 1067 (const unsigned char *)CFDataGetBytePtr(issuersData), 1068 (unsigned int)CFDataGetLength(issuersData)); 1069 if(rtn) { 1070 ocspdErrorLog("Error %d writing %s\n", 1071 rtn, params->crlNames.pemFile); 1072 } 1073 else if(chmod(params->crlNames.pemFile, 0644)) { 1074 ocspdErrorLog("ocsp_server_crlStatus: chmod error %d for %s", 1075 errno, params->crlNames.pemFile); 1076 } 1077 } 1078 } 1079 1080 if(isCRL && wroteFile) { 1081 /* validate .crl signature (creates .update and .revoked files) */ 1082 crlSignatureValid(params->crlNames.crlFile, 1083 params->crlNames.pemFile, 1084 params->crlNames.updateFile, 1085 params->crlNames.revokedFile); 1086 verifyTime = ( CFAbsoluteTimeGetCurrent() - startTime ) - fetchTime; 1087 ocspdCrlDebug("%f seconds to validate CRL", verifyTime); 1088 } 1089 1090 if(fileNameStr) { 1091 /* all finished downloading, so remove filename from global list */ 1092 StLock<Mutex> _(gListLock); 1093 CFIndex idx = CFArrayGetFirstIndexOfValue(gDownloadList, 1094 CFRangeMake(0, CFArrayGetCount(gDownloadList)), fileNameStr); 1095 if(idx >= 0) { 1096 CFArrayRemoveValueAtIndex(gDownloadList, idx); 1097 } 1098 } 1099 } 1100 1101 if(params) { 1102 StLock<Mutex> _(gParamsLock); 1103 params->finished = 1; 1104 1105 if(params->freeOnDone) { 1106 /* caller does not expect a reply; we must clean up everything. */ 1107 if(params->url.Data) { 1108 free(params->url.Data); 1109 } 1110 if(params->outFile) { 1111 free(params->outFile); 1112 } 1113 if(params->crlNames.crlFile) { 1114 free(params->crlNames.crlFile); 1115 } 1116 if(params->crlNames.pemFile) { 1117 free(params->crlNames.pemFile); 1118 } 1119 if(params->crlNames.updateFile) { 1120 free(params->crlNames.updateFile); 1121 } 1122 if(params->crlNames.revokedFile) { 1123 free(params->crlNames.revokedFile); 1124 } 1125 if(params->fetched.Data) { 1126 (*(params->alloc)).free(params->fetched.Data); 1127 } 1128 free(params); 1129 } 1130 } 1131 1132 if(fileNameStr) { 1133 CFRelease(fileNameStr); 1134 } 1135 if(pemNameStr) { 1136 CFRelease(pemNameStr); 1137 } 1138} 1139 1140/* 1141 * This is a basic "we have an internet connection" check. 1142 * It tests whether IP 0.0.0.0 is routable. 1143 */ 1144bool shouldAttemptNetFetch() 1145{ 1146 bool result = true; 1147 SCNetworkReachabilityRef scnr; 1148 struct sockaddr_in addr; 1149 bzero(&addr, sizeof(addr)); 1150 addr.sin_len = sizeof(addr); 1151 addr.sin_family = AF_INET; 1152 1153 scnr = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&addr); 1154 if (scnr) { 1155 SCNetworkReachabilityFlags flags = 0; 1156 if (SCNetworkReachabilityGetFlags(scnr, &flags)) { 1157 if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) 1158 result = false; 1159 } 1160 CFRelease(scnr); 1161 } 1162 else { ocspdDebug("Failed to create reachability reference"); } 1163 ocspdDebug("Finished reachability check, result=%s", (result) ? "YES" :"NO"); 1164 1165 return result; 1166} 1167 1168/* Kick off net fetch of a cert or a CRL and return immediately. */ 1169CSSM_RETURN ocspdStartNetFetch( 1170 async_fetch_t *fetchParams) 1171{ 1172 if (!shouldAttemptNetFetch()) 1173 return CSSMERR_APPLETP_NETWORK_FAILURE; 1174 1175 dispatch_queue_t queue = dispatch_get_global_queue( 1176 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 1177 1178 ocspdCrlDebug("ocspdStartNetFetch with context %p", (void*)fetchParams); 1179 1180 dispatch_async_f(queue, fetchParams, ocspdNetFetchAsync); 1181 1182 return 0; 1183} 1184 1185