/* * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * DotMacTpUtils.cpp */ #include "AppleDotMacTPSession.h" #include "dotMacTpDebug.h" #include "dotMacTpUtils.h" #include "dotMacTpMutils.h" #include "dotMacTp.h" #include "dotMacTpAsn1Templates.h" #include #include #include #include #include #include #define CFRELEASE(cf) if(cf != NULL) { CFRelease(cf); } /* * Given an array of name/value pairs, cook up a CSSM_X509_NAME in specified * SecNssCoder's address space. */ void dotMacTpbuildX509Name( SecNssCoder &coder, uint32 numTypeValuePairs, // size of typeValuePairs[] CSSM_X509_TYPE_VALUE_PAIR_PTR typeValuePairs, CSSM_X509_NAME &x509Name) { memset(&x509Name, 0, sizeof(x509Name)); /* * One RDN per type/value pair per common usage out in the world * This actual CSSM_X509_TYPE_VALUE_PAIR is NOT re-mallocd; it's copied * directly into the outgoing CSSM_X509_NAME. */ x509Name.RelativeDistinguishedName = coder.mallocn(numTypeValuePairs); for(unsigned nameDex=0; nameDexnumberOfPairs = 1; rdn->AttributeTypeAndValue = &typeValuePairs[nameDex]; } x509Name.numberOfRDNs = numTypeValuePairs; } /* Convert a reference key to a raw key. */ void dotMacRefKeyToRaw( CSSM_CSP_HANDLE cspHand, const CSSM_KEY *refKey, CSSM_KEY_PTR rawKey) // RETURNED { CSSM_CC_HANDLE ccHand; CSSM_RETURN crtn; CSSM_ACCESS_CREDENTIALS creds; memset(rawKey, 0, sizeof(CSSM_KEY)); memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS)); crtn = CSSM_CSP_CreateSymmetricContext(cspHand, CSSM_ALGID_NONE, CSSM_ALGMODE_NONE, &creds, // passPhrase NULL, // wrapping key NULL, // init vector CSSM_PADDING_NONE, // Padding 0, // Params &ccHand); if(crtn) { dotMacErrorLog("dotMacRefKeyToRaw: context err"); CssmError::throwMe(crtn); } crtn = CSSM_WrapKey(ccHand, &creds, refKey, NULL, // DescriptiveData rawKey); if(crtn != CSSM_OK) { dotMacErrorLog("dotMacRefKeyToRaw: wrapKey err"); CssmError::throwMe(crtn); } CSSM_DeleteContext(ccHand); } void dotMacTokenizeHostName( const CSSM_DATA &inName, // UTF8, no NULL CSSM_DATA &outName, // RETURNED CSSM_DATA &outDomain) // RETURNED { int idx = 0; int stopIdx = inName.Length; uint8 *p = inName.Data; outName = inName; outDomain.Length = idx; if (!p) return; while (idx < stopIdx) { if (*p++ == '.') { outName.Length = idx; outDomain.Length = (CSSM_SIZE)(stopIdx - (idx + 1)); outDomain.Data = p; break; } idx++; } if (outDomain.Length) { /* continue scanning for a port specifier */ idx = 0; stopIdx = outDomain.Length; while (idx < stopIdx) { if (*p++ == ':') { outDomain.Length = idx; break; } idx++; } } } void dotMacTokenizeUserName( const CSSM_DATA &inName, // UTF8, no NULL CSSM_DATA &outName, // RETURNED CSSM_DATA &outDomain) // RETURNED { int idx = 0; int stopIdx = inName.Length; uint8 *p = inName.Data; outName = inName; outDomain.Length = idx; if (!p) return; while (idx < stopIdx) { if (*p++ == '@') { outName.Length = idx; outDomain.Length = (CSSM_SIZE)(stopIdx - (idx + 1)); outDomain.Data = p; break; } idx++; } } /* * Encode/decode ReferenceIdentitifiers for queued requests. * We PEM encode/decode here to keep things orthogonal, since returned * certs and URLs are also in PEM or at least UTF8 format. */ OSStatus dotMacEncodeRefId( const CSSM_DATA &userName, // UTF8, no NULL const CSSM_DATA &domainName, // UTF8, no NULL DotMacCertTypeTag certTypeTag, SecNssCoder &coder, // results mallocd in this address space CSSM_DATA &refId) // RETURNED, PEM encoded { DotMacTpPendingRequest req; /* set up a DotMacTpPendingRequest */ req.userName = userName; req.domainName = domainName; uint8 certType = certTypeTag; req.certTypeTag.Data = &certType; req.certTypeTag.Length = 1; /* DER encode */ CSSM_DATA tempData = {0, NULL}; PRErrorCode prtn = coder.encodeItem(&req, DotMacTpPendingRequestTemplate, tempData); if(prtn) { dotMacErrorLog("dotMacEncodeRefId: encodeItem error"); return internalComponentErr; } /* PEM encode */ unsigned char *pem; unsigned pemLen; if(pemEncode(tempData.Data, tempData.Length, &pem, &pemLen, "REFERENCE ID")) { dotMacErrorLog("dotMacEncodeRefId: pemEncode error"); return internalComponentErr; } refId.Data = NULL; refId.Length = 0; coder.allocCopyItem(pem, pemLen, refId); free(pem); return noErr; } OSStatus dotMacDecodeRefId( SecNssCoder &coder, // results mallocd in this address space const CSSM_DATA &refId, // PEM encoded CSSM_DATA &userName, // RETURNED, UTF8, no NULL CSSM_DATA &domainName, // RETURNED, UTF8, no NULL DotMacCertTypeTag *certTypeTag) // RETURNED { /* PEM decode */ unsigned char *unPem; unsigned unPemLen; if(pemDecode(refId.Data, refId.Length, &unPem, &unPemLen)) { dotMacErrorLog("dotMacDecodeRefId: pemDecode error"); return internalComponentErr; } /* DER decode */ CSSM_DATA tempData; tempData.Data = unPem; tempData.Length = unPemLen; DotMacTpPendingRequest req; memset(&req, 0, sizeof(req)); PRErrorCode prtn = coder.decodeItem(tempData, DotMacTpPendingRequestTemplate, &req); free(unPem); if(prtn) { dotMacErrorLog("dotMacDecodeRefId: decodeItem error"); return paramErr; } /* decoded params back to caller */ userName = req.userName; domainName = req.domainName; if(req.certTypeTag.Length != 1) { dotMacErrorLog("dotMacDecodeRefId: reqType length (%lu) error", req.certTypeTag.Length); return paramErr; } *certTypeTag = req.certTypeTag.Data[0]; return noErr; } /* SPI to specify timeout on CFReadStream */ #define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout") /* the read timeout we set, in seconds */ #define READ_STREAM_TIMEOUT 15 /* amount of data per CFReadStreamRead() */ #define READ_FRAGMENT_SIZE 512 /* fetch cert via HTTP */ CSSM_RETURN dotMacTpCertFetch( const CSSM_DATA &userName, // UTF8, no NULL const CSSM_DATA &domainName, // UTF8, no NULL DotMacCertTypeTag certType, Allocator &alloc, // results mallocd here CSSM_DATA &result) // RETURNED { unsigned rawUrlLen; unsigned domainLen = (domainName.Length && domainName.Data) ? domainName.Length : strlen(DOT_MAC_DOMAIN); uint8 *domain = (domainName.Length && domainName.Data) ? domainName.Data : (uint8 *) DOT_MAC_DOMAIN; char *typeArg; CSSM_RETURN crtn = CSSM_OK; switch(certType) { case CSSM_DOT_MAC_TYPE_ICHAT: case CSSM_DOT_MAC_TYPE_UNSPECIFIED: typeArg = DOT_MAC_CERT_TYPE_ICHAT; break; case CSSM_DOT_MAC_TYPE_SHARED_SERVICES: typeArg = DOT_MAC_CERT_TYPE_SHARED_SERVICES; break; case CSSM_DOT_MAC_TYPE_EMAIL_SIGNING: typeArg = DOT_MAC_CERT_TYPE_EMAIL_SIGNING; break; case CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT: typeArg = DOT_MAC_CERT_TYPE_EMAIL_ENCRYPT; break; default: dotMacErrorLog("dotMacTpCertFetch: bad signType"); return paramErr; } /* URL := http://certinfo.mac.com/locate?accountName&type=certTypeTag */ rawUrlLen = strlen(DOT_MAC_LOOKUP_SCHEMA) + /* http:// */ strlen(DOT_MAC_LOOKUP_HOST_NAME) + /* certinfo */ domainLen + 1 + /* .mac.com */ strlen(DOT_MAC_LOOKUP_PATH) + /* /locate? */ userName.Length + /* joe */ strlen(DOT_MAC_LOOKUP_TYPE) + /* &type= */ strlen(typeArg) + /* dmSharedServices */ 1; /* NULL */ unsigned char rawUrl[rawUrlLen]; unsigned char *cp = rawUrl; unsigned len = strlen(DOT_MAC_LOOKUP_SCHEMA); memmove(cp, DOT_MAC_LOOKUP_SCHEMA, len); cp += len; len = strlen(DOT_MAC_LOOKUP_HOST_NAME); memmove(cp, DOT_MAC_LOOKUP_HOST_NAME, len); cp += len; memmove(cp, ".", 1); cp += 1; memmove(cp, domain, domainLen); cp += domainLen; len = strlen(DOT_MAC_LOOKUP_PATH); memmove(cp, DOT_MAC_LOOKUP_PATH, len); cp += len; memmove(cp, userName.Data, userName.Length); cp += userName.Length; len = strlen(DOT_MAC_LOOKUP_TYPE); memmove(cp, DOT_MAC_LOOKUP_TYPE, len); cp += len; len = strlen(typeArg); memmove(cp, typeArg, len); cp += len; *cp = '\0'; // for debugging only, actually dotMacDebug("dotMacTpCertFetch: URL %s", rawUrl); CFURLRef cfUrl = CFURLCreateWithBytes(NULL, rawUrl, rawUrlLen - 1, // no NULL kCFStringEncodingUTF8, NULL); // absolute path if(cfUrl == NULL) { dotMacErrorLog("dotMacTpCertFetch: CFURLCreateWithBytes returned NULL\n"); return paramErr; } /* subsequent errors to errOut: */ CFHTTPMessageRef httpRequestRef = NULL; CFReadStreamRef httpStreamRef = NULL; CFNumberRef cfnTo = NULL; CFDictionaryRef proxyDict = NULL; SInt32 ito = READ_STREAM_TIMEOUT; CFMutableDataRef fetchedData = CFDataCreateMutable(NULL, 0); UInt8 readFrag[READ_FRAGMENT_SIZE]; CFIndex bytesRead; CFIndex resultLen; httpRequestRef = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), cfUrl, kCFHTTPVersion1_1); if(!httpRequestRef) { dotMacErrorLog("***Error creating HTTPMessage from '%s'\n", rawUrl); crtn = ioErr; goto errOut; } // open the stream httpStreamRef = CFReadStreamCreateForHTTPRequest(NULL, httpRequestRef); if(!httpStreamRef) { dotMacErrorLog("***Error creating stream for '%s'\n", rawUrl); crtn = ioErr; goto errOut; } // set a reasonable timeout cfnTo = CFNumberCreate(NULL, kCFNumberSInt32Type, &ito); if(!CFReadStreamSetProperty(httpStreamRef, _kCFStreamPropertyReadTimeout, cfnTo)) { // oh well - keep going } // set up possible proxy info proxyDict = SCDynamicStoreCopyProxies(NULL); if(proxyDict) { CFReadStreamSetProperty(httpStreamRef, kCFStreamPropertyHTTPProxy, proxyDict); } if(CFReadStreamOpen(httpStreamRef) == false) { dotMacErrorLog("***Error opening stream for '%s'\n", rawUrl); crtn = ioErr; goto errOut; } // read data from the stream bytesRead = CFReadStreamRead(httpStreamRef, readFrag, sizeof(readFrag)); while (bytesRead > 0) { CFDataAppendBytes(fetchedData, readFrag, bytesRead); bytesRead = CFReadStreamRead(httpStreamRef, readFrag, sizeof(readFrag)); } if (bytesRead < 0) { dotMacErrorLog("***Error reading URL '%s'\n", rawUrl); crtn = ioErr; goto errOut; } resultLen = CFDataGetLength(fetchedData); if(resultLen == 0) { dotMacErrorLog("***No data available from URL '%s'\n", rawUrl); /* but don't abort on this one - it means "no cert found" */ goto errOut; } /* * Only pass back good data. * FIXME this is a back to workaround nonconforming .Mac server behavior. * It currently sends HTML data that is *not* a cert when it wants to * indicate "no certs found". It should just return empty data, which * we'd detect above. For now we have to determine manually if the data * contains some PEM-formated stuff. */ { /* Scan for PEM armour */ bool isPEM = false; const char *srchStr = "-----BEGIN CERTIFICATE-----"; unsigned srchStrLen = strlen(srchStr); const char *p = (const char *)CFDataGetBytePtr(fetchedData); if(resultLen > (int)srchStrLen) { /* no sense checking if result is smaller than that search string */ unsigned srchLen = resultLen - srchStrLen; for(unsigned dex=0; dex< srchLen; dex++) { if(!strncmp(p, srchStr, srchStrLen)) { isPEM = true; break; } p++; } } if(isPEM) { result.Data = (uint8 *)alloc.malloc(resultLen); result.Length = resultLen; memmove(result.Data, CFDataGetBytePtr(fetchedData), resultLen); } else { result.Data = NULL; result.Length = 0; } } errOut: CFRELEASE(cfUrl); CFRELEASE(httpRequestRef); CFRELEASE(httpStreamRef); CFRELEASE(cfnTo); CFRELEASE(proxyDict); return crtn; }