/* * Copyright (c) 2002,2000 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * ocspdNetwork.cpp - Network support for ocspd and CRL/cert fetch */ #include #include "ocspdNetwork.h" #include #include #include #include #include #include #include #pragma mark ----- OCSP support ----- /* POST method has Content-Type header line equal to "application/ocsp-request" */ static CFStringRef kContentType = CFSTR("Content-Type"); static CFStringRef kAppOcspRequest = CFSTR("application/ocsp-request"); #ifndef NDEBUG #define DUMP_BLOBS 0 #else #define DUMP_BLOBS 0 #endif #define OCSP_GET_FILE "/tmp/ocspGet" #define OCSP_RESP_FILE "/tmp/ocspResp" #if DUMP_BLOBS #include static void writeBlob( const char *fileName, const char *whatIsIt, const unsigned char *data, unsigned dataLen) { if(writeFile(fileName, data, dataLen)) { printf("***Error writing %s to %s\n", whatIsIt, fileName); } else { printf("...wrote %u bytes of %s to %s\n", dataLen, whatIsIt, fileName); } } #else #define writeBlob(f,w,d,l) #endif /* DUMP_BLOBS */ #if ENABLE_OCSP_VIA_GET /* fetch via HTTP GET */ CSSM_RETURN ocspdHttpGet( SecAsn1CoderRef coder, const CSSM_DATA &url, const CSSM_DATA &ocspReq, // DER encoded CSSM_DATA &fetched) // mallocd in coder space and RETURNED { CSSM_RETURN ourRtn = CSSM_OK; CFDataRef urlData = NULL; SInt32 errorCode; unsigned char *endp = NULL; bool done = false; unsigned totalLen; unsigned char *fullUrl = NULL; CFURLRef cfUrl = NULL; Boolean brtn; CFIndex len; /* trim off possible NULL terminator */ uint32 urlLen = url.Length; if(url.Data[urlLen - 1] == '\0') { urlLen--; } #ifndef NDEBUG { char *ustr = (char *)malloc(urlLen + 1); memmove(ustr, url.Data, urlLen); ustr[urlLen] = '\0'; ocspdDebug("ocspdHttpGet: fetching from URI %s", ustr); free(ustr); } #endif /* base64 encode the OCSP request; that's used as a path */ unsigned char *req64 = NULL; unsigned req64Len = 0; req64 = cuEnc64(ocspReq.Data, ocspReq.Length, &req64Len); if(req64 == NULL) { ocspdErrorLog("httpFetch: error base64-encoding request\n"); return CSSMERR_TP_INTERNAL_ERROR; } /* subsequent errors to errOut: */ writeBlob(OCSP_GET_FILE, "OCSP Request as URL", req64, req64Len); /* trim off trailing NULL and newline */ endp = req64 + req64Len - 1; for(;;) { switch(*endp) { case '\0': case '\n': case '\r': endp--; req64Len--; break; default: done = true; break; } if(done) { break; } } /* concatenate: URL plus path */ totalLen = urlLen + 1 + req64Len; fullUrl = (unsigned char *)malloc(totalLen); memmove(fullUrl, url.Data, urlLen); fullUrl[urlLen] = '/'; memmove(fullUrl + urlLen + 1, req64, req64Len); cfUrl = CFURLCreateWithBytes(NULL, fullUrl, totalLen, kCFStringEncodingUTF8, // right? NULL); // this is absolute path if(cfUrl == NULL) { ocspdErrorLog("CFURLCreateWithBytes returned NULL\n"); /* FIXME..? */ ourRtn = CSSMERR_APPLETP_CRL_BAD_URI; goto errOut; } brtn = CFURLCreateDataAndPropertiesFromResource(NULL, cfUrl, &urlData, NULL, // no properties NULL, &errorCode); CFRelease(cfUrl); if(!brtn) { ocspdErrorLog("CFURLCreateDataAndPropertiesFromResource err: %d\n", (int)errorCode); ourRtn = CSSMERR_APPLETP_CRL_BAD_URI; goto errOut; } if(urlData == NULL) { ocspdErrorLog("CFURLCreateDataAndPropertiesFromResource: no data\n"); ourRtn = CSSMERR_APPLETP_CRL_BAD_URI; goto errOut; } len = CFDataGetLength(urlData); fetched.Data = (uint8 *)SecAsn1Malloc(coder, len); fetched.Length = len; memmove(fetched.Data, CFDataGetBytePtr(urlData), len); writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, fetched.Length); errOut: if(urlData) { CFRelease(urlData); } if(fullUrl) { free(fullUrl); } if(req64) { free(req64); } return CSSM_OK; } #endif /* ENABLE_OCSP_VIA_GET */ /* fetch via HTTP POST */ /* SPI to specify timeout on CFReadStream */ #define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout") /* the timeout we set */ #define READ_STREAM_TIMEOUT 15 #define POST_BUFSIZE 1024 CSSM_RETURN ocspdHttpPost( SecAsn1CoderRef coder, const CSSM_DATA &url, const CSSM_DATA &ocspReq, // DER encoded CSSM_DATA &fetched) // mallocd in coder space and RETURNED { CSSM_RETURN ourRtn = CSSM_OK; CFIndex thisMove; UInt8 inBuf[POST_BUFSIZE]; SInt32 ito; /* resources to release on exit */ CFMutableDataRef inData = NULL; CFReadStreamRef cfStream = NULL; CFHTTPMessageRef request = NULL; CFDataRef postData = NULL; CFURLRef cfUrl = NULL; CFNumberRef cfnTo = NULL; /* trim off possible NULL terminator from incoming URL */ uint32 urlLen = url.Length; if(url.Data[urlLen - 1] == '\0') { urlLen--; } cfUrl = CFURLCreateWithBytes(NULL, url.Data, urlLen, kCFStringEncodingUTF8, // right? NULL); // this is absolute path if(cfUrl == NULL) { ocspdErrorLog("CFURLCreateWithBytes returned NULL\n"); /* FIXME..? */ return CSSMERR_APPLETP_CRL_BAD_URI; } /* subsequent errors to errOut: */ #ifndef NDEBUG { char *ustr = (char *)malloc(urlLen + 1); memmove(ustr, url.Data, urlLen); ustr[urlLen] = '\0'; ocspdDebug("ocspdHttpPost: posting to URI %s", ustr); free(ustr); } #endif writeBlob(OCSP_GET_FILE, "OCSP Request as POST data", ocspReq.Data, ocspReq.Length); postData = CFDataCreate(NULL, ocspReq.Data, ocspReq.Length); /* Create a new HTTP request. */ request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("POST"), cfUrl, kCFHTTPVersion1_1); if (request == NULL) { ocspdErrorLog("ocspdHttpPost: error creating CFHTTPMessage\n"); ourRtn = CSSMERR_TP_INTERNAL_ERROR; goto errOut; } // Set the body and required header fields. CFHTTPMessageSetBody(request, postData); CFHTTPMessageSetHeaderFieldValue(request, kContentType, kAppOcspRequest); // Create the stream for the request. cfStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request); if (cfStream == NULL) { ocspdErrorLog("ocspdHttpPost: error creating CFReadStream\n"); ourRtn = CSSMERR_TP_INTERNAL_ERROR; goto errOut; } /* set a reasonable timeout */ ito = READ_STREAM_TIMEOUT; cfnTo = CFNumberCreate(NULL, kCFNumberSInt32Type, &ito); if(!CFReadStreamSetProperty(cfStream, _kCFStreamPropertyReadTimeout, cfnTo)) { ocspdErrorLog("ocspdHttpPost: error setting _kCFStreamPropertyReadTimeout\n"); /* but keep going */ } /* go, synchronously */ if(!CFReadStreamOpen(cfStream)) { ocspdErrorLog("ocspdHttpPost: error opening CFReadStream\n"); ourRtn = CSSMERR_TP_INTERNAL_ERROR; goto errOut; } inData = CFDataCreateMutable(NULL, 0); for(;;) { thisMove = CFReadStreamRead(cfStream, inBuf, POST_BUFSIZE); if(thisMove < 0) { CFStreamError error = CFReadStreamGetError(cfStream); ocspdErrorLog("ocspdHttpPost: error on CFReadStreamRead: domain " "%d error %ld\n", error.domain, error.error); ourRtn = CSSMERR_APPLETP_NETWORK_FAILURE; break; } else if(thisMove == 0) { ocspdDebug("ocspdHttpPost: transfer complete, moved %ld bytes", CFDataGetLength(inData)); ourRtn = CSSM_OK; break; } else { CFDataAppendBytes(inData, inBuf, thisMove); } } if(ourRtn == CSSM_OK) { SecAsn1AllocCopy(coder, CFDataGetBytePtr(inData), CFDataGetLength(inData), &fetched); writeBlob(OCSP_RESP_FILE, "OCSP Response", fetched.Data, fetched.Length); } errOut: CFRELEASE(inData); CFRELEASE(cfStream); CFRELEASE(request); CFRELEASE(postData); CFRELEASE(cfUrl); CFRELEASE(cfnTo); return ourRtn; } #pragma mark ----- LDAP fetch ----- /* * LDAP attribute names, used if not present in URI. */ #define LDAP_ATTR_CERT "cacertificate;binary" #define LDAP_ATTR_CRL "certificaterevocationlist;binary" /* * Default LDAP options. */ #define LDAP_REFERRAL_DEFAULT LDAP_OPT_ON static CSSM_RETURN ldapRtnToCssm( int rtn) { switch(rtn) { case LDAP_SERVER_DOWN: case LDAP_TIMEOUT: case LDAP_CONNECT_ERROR: return CSSMERR_APPLETP_CRL_SERVER_DOWN; case LDAP_PARAM_ERROR: case LDAP_FILTER_ERROR: return CSSMERR_APPLETP_CRL_BAD_URI; default: return CSSMERR_APPLETP_CRL_NOT_FOUND; } } static CSSM_RETURN ldapFetch( Allocator &alloc, const CSSM_DATA &url, LF_Type lfType, CSSM_DATA &fetched) // mallocd in alloc space and RETURNED { BerValue **value = NULL; LDAPURLDesc *urlDesc = NULL; int rtn; LDAPMessage *msg = NULL; LDAP *ldap = NULL; LDAPMessage *entry = NULL; bool mallocdString = false; char *urlStr; int numEntries; CSSM_RETURN ourRtn = CSSM_OK; /* attr input to ldap_search_s() */ char *attrArray[2]; char **attrArrayP = NULL; /* don't assume URL string is NULL terminated */ if(url.Data[url.Length - 1] == '\0') { urlStr = (char *)url.Data; } else { urlStr = (char *)malloc(url.Length + 1); memmove(urlStr, url.Data, url.Length); urlStr[url.Length] = '\0'; mallocdString = true; } /* break up the URL into something usable */ rtn = ldap_url_parse(urlStr, &urlDesc); if(rtn) { ocspdErrorLog("ldap_url_parse returned %d", rtn); return CSSMERR_APPLETP_CRL_BAD_URI; } /* * Determine what attr we're looking for. */ if((urlDesc->lud_attrs != NULL) && // attrs present in URL (urlDesc->lud_attrs[0] != NULL) && // at least one attr present (urlDesc->lud_attrs[1] == NULL)) { /* * Exactly one attr present in the caller-specified URL; * assume that this is exactly what we want. */ attrArrayP = &urlDesc->lud_attrs[0]; } else { /* use caller-specified attr */ switch(lfType) { case LT_Crl: attrArray[0] = (char *)LDAP_ATTR_CRL; break; case LT_Cert: attrArray[0] = (char *)LDAP_ATTR_CERT; break; default: printf("***ldapFetch screwup: bogus lfType (%d)\n", (int)lfType); return CSSMERR_CSSM_INTERNAL_ERROR; } attrArray[1] = NULL; attrArrayP = &attrArray[0]; } /* establish connection */ rtn = ldap_initialize(&ldap, urlStr); if(rtn) { ocspdErrorLog("ldap_initialize returned %d\n", rtn); return ldapRtnToCssm(rtn); } /* subsequent errors to cleanup: */ rtn = ldap_simple_bind_s(ldap, NULL, NULL); if(rtn) { ocspdErrorLog("ldap_simple_bind_s returned %d\n", rtn); ourRtn = ldapRtnToCssm(rtn); goto cleanup; } rtn = ldap_set_option(ldap, LDAP_OPT_REFERRALS, LDAP_REFERRAL_DEFAULT); if(rtn) { ocspdErrorLog("ldap_set_option(referrals) returned %d\n", rtn); ourRtn = ldapRtnToCssm(rtn); goto cleanup; } rtn = ldap_search_s( ldap, urlDesc->lud_dn, LDAP_SCOPE_SUBTREE, urlDesc->lud_filter, urlDesc->lud_attrs, 0, // attrsonly &msg); if(rtn) { ocspdErrorLog("ldap_search_s returned %d\n", rtn); ourRtn = ldapRtnToCssm(rtn); goto cleanup; } /* * We require exactly one entry (for now). */ numEntries = ldap_count_entries(ldap, msg); if(numEntries != 1) { ocspdErrorLog("tpCrlViaLdap: numEntries %d\n", numEntries); ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND; goto cleanup; } entry = ldap_first_entry(ldap, msg); value = ldap_get_values_len(ldap, msg, attrArrayP[0]); if(value == NULL) { ocspdErrorLog("Error on ldap_get_values_len\n"); ourRtn = CSSMERR_APPLETP_CRL_NOT_FOUND; goto cleanup; } fetched.Length = value[0]->bv_len; fetched.Data = (uint8 *)alloc.malloc(fetched.Length); memmove(fetched.Data, value[0]->bv_val, fetched.Length); ldap_value_free_len(value); ourRtn = CSSM_OK; cleanup: if(msg) { ldap_msgfree(msg); } if(mallocdString) { free(urlStr); } ldap_free_urldesc(urlDesc); rtn = ldap_unbind(ldap); if(rtn) { ocspdErrorLog("Error %d on ldap_unbind\n", rtn); /* oh well */ } return ourRtn; } #pragma mark ----- HTTP fetch via GET ----- /* fetch via HTTP */ static CSSM_RETURN httpFetch( Allocator &alloc, const CSSM_DATA &url, CSSM_DATA &fetched) // mallocd in alloc space and RETURNED { /* trim off possible NULL terminator */ CSSM_DATA theUrl = url; if(theUrl.Data[theUrl.Length - 1] == '\0') { theUrl.Length--; } CFURLRef cfUrl = CFURLCreateWithBytes(NULL, theUrl.Data, theUrl.Length, kCFStringEncodingUTF8, // right? //kCFStringEncodingASCII, // right? NULL); // this is absolute path if(cfUrl == NULL) { ocspdErrorLog("CFURLCreateWithBytes returned NULL\n"); return CSSMERR_APPLETP_CRL_BAD_URI; } CFDataRef urlData = NULL; SInt32 errorCode; Boolean brtn = CFURLCreateDataAndPropertiesFromResource(NULL, cfUrl, &urlData, NULL, // no properties NULL, &errorCode); CFRelease(cfUrl); if(!brtn) { ocspdErrorLog("CFURLCreateDataAndPropertiesFromResource err: %d\n", (int)errorCode); if(urlData) { return CSSMERR_APPLETP_NETWORK_FAILURE; } } if(urlData == NULL) { ocspdErrorLog("CFURLCreateDataAndPropertiesFromResource: no data\n"); return CSSMERR_APPLETP_NETWORK_FAILURE; } CFIndex len = CFDataGetLength(urlData); fetched.Data = (uint8 *)alloc.malloc(len); fetched.Length = len; memmove(fetched.Data, CFDataGetBytePtr(urlData), len); CFRelease(urlData); return CSSM_OK; } /* Fetch cert or CRL from net, we figure out the schema */ CSSM_RETURN ocspdNetFetch( Allocator &alloc, const CSSM_DATA &url, LF_Type lfType, CSSM_DATA &fetched) // mallocd in alloc space and RETURNED { if(url.Length < 5) { return CSSMERR_APPLETP_CRL_BAD_URI; } if(!strncmp((char *)url.Data, "ldap:", 5)) { return ldapFetch(alloc, url, lfType, fetched); } if(!strncmp((char *)url.Data, "http:", 5) || !strncmp((char *)url.Data, "https:", 6)) { return httpFetch(alloc, url, fetched); } return CSSMERR_APPLETP_CRL_BAD_URI; }