1/* 2 * Copyright (c) 2004 Apple Computer, 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/* 26 * DotMacTpUtils.cpp 27 */ 28 29#include "AppleDotMacTPSession.h" 30#include "dotMacTpDebug.h" 31#include "dotMacTpUtils.h" 32#include "dotMacTpMutils.h" 33#include "dotMacTp.h" 34#include "dotMacTpAsn1Templates.h" 35#include <security_asn1/SecNssCoder.h> 36#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h> 37#include <security_cdsa_utils/cuPem.h> 38#include <CoreFoundation/CoreFoundation.h> 39#include <CoreServices/CoreServices.h> 40#include <SystemConfiguration/SCDynamicStoreCopySpecific.h> 41 42#define CFRELEASE(cf) if(cf != NULL) { CFRelease(cf); } 43 44/* 45 * Given an array of name/value pairs, cook up a CSSM_X509_NAME in specified 46 * SecNssCoder's address space. 47 */ 48void dotMacTpbuildX509Name( 49 SecNssCoder &coder, 50 uint32 numTypeValuePairs, // size of typeValuePairs[] 51 CSSM_X509_TYPE_VALUE_PAIR_PTR typeValuePairs, 52 CSSM_X509_NAME &x509Name) 53{ 54 memset(&x509Name, 0, sizeof(x509Name)); 55 56 /* 57 * One RDN per type/value pair per common usage out in the world 58 * This actual CSSM_X509_TYPE_VALUE_PAIR is NOT re-mallocd; it's copied 59 * directly into the outgoing CSSM_X509_NAME. 60 */ 61 x509Name.RelativeDistinguishedName = 62 coder.mallocn<CSSM_X509_RDN>(numTypeValuePairs); 63 for(unsigned nameDex=0; nameDex<numTypeValuePairs; nameDex++) { 64 CSSM_X509_RDN_PTR rdn = &x509Name.RelativeDistinguishedName[nameDex]; 65 rdn->numberOfPairs = 1; 66 rdn->AttributeTypeAndValue = &typeValuePairs[nameDex]; 67 } 68 x509Name.numberOfRDNs = numTypeValuePairs; 69} 70 71/* Convert a reference key to a raw key. */ 72void dotMacRefKeyToRaw( 73 CSSM_CSP_HANDLE cspHand, 74 const CSSM_KEY *refKey, 75 CSSM_KEY_PTR rawKey) // RETURNED 76{ 77 CSSM_CC_HANDLE ccHand; 78 CSSM_RETURN crtn; 79 CSSM_ACCESS_CREDENTIALS creds; 80 81 memset(rawKey, 0, sizeof(CSSM_KEY)); 82 memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS)); 83 crtn = CSSM_CSP_CreateSymmetricContext(cspHand, 84 CSSM_ALGID_NONE, 85 CSSM_ALGMODE_NONE, 86 &creds, // passPhrase 87 NULL, // wrapping key 88 NULL, // init vector 89 CSSM_PADDING_NONE, // Padding 90 0, // Params 91 &ccHand); 92 if(crtn) { 93 dotMacErrorLog("dotMacRefKeyToRaw: context err"); 94 CssmError::throwMe(crtn); 95 } 96 97 crtn = CSSM_WrapKey(ccHand, 98 &creds, 99 refKey, 100 NULL, // DescriptiveData 101 rawKey); 102 if(crtn != CSSM_OK) { 103 dotMacErrorLog("dotMacRefKeyToRaw: wrapKey err"); 104 CssmError::throwMe(crtn); 105 } 106 CSSM_DeleteContext(ccHand); 107} 108 109void dotMacTokenizeHostName( 110 const CSSM_DATA &inName, // UTF8, no NULL 111 CSSM_DATA &outName, // RETURNED 112 CSSM_DATA &outDomain) // RETURNED 113{ 114 int idx = 0; 115 int stopIdx = inName.Length; 116 uint8 *p = inName.Data; 117 outName = inName; 118 outDomain.Length = idx; 119 if (!p) return; 120 while (idx < stopIdx) { 121 if (*p++ == '.') { 122 outName.Length = idx; 123 outDomain.Length = (CSSM_SIZE)(stopIdx - (idx + 1)); 124 outDomain.Data = p; 125 break; 126 } 127 idx++; 128 } 129 if (outDomain.Length) { 130 /* continue scanning for a port specifier */ 131 idx = 0; 132 stopIdx = outDomain.Length; 133 while (idx < stopIdx) { 134 if (*p++ == ':') { 135 outDomain.Length = idx; 136 break; 137 } 138 idx++; 139 } 140 } 141} 142 143void dotMacTokenizeUserName( 144 const CSSM_DATA &inName, // UTF8, no NULL 145 CSSM_DATA &outName, // RETURNED 146 CSSM_DATA &outDomain) // RETURNED 147{ 148 int idx = 0; 149 int stopIdx = inName.Length; 150 uint8 *p = inName.Data; 151 outName = inName; 152 outDomain.Length = idx; 153 if (!p) return; 154 while (idx < stopIdx) { 155 if (*p++ == '@') { 156 outName.Length = idx; 157 outDomain.Length = (CSSM_SIZE)(stopIdx - (idx + 1)); 158 outDomain.Data = p; 159 break; 160 } 161 idx++; 162 } 163} 164 165/* 166 * Encode/decode ReferenceIdentitifiers for queued requests. 167 * We PEM encode/decode here to keep things orthogonal, since returned 168 * certs and URLs are also in PEM or at least UTF8 format. 169 */ 170OSStatus dotMacEncodeRefId( 171 const CSSM_DATA &userName, // UTF8, no NULL 172 const CSSM_DATA &domainName, // UTF8, no NULL 173 DotMacCertTypeTag certTypeTag, 174 SecNssCoder &coder, // results mallocd in this address space 175 CSSM_DATA &refId) // RETURNED, PEM encoded 176{ 177 DotMacTpPendingRequest req; 178 179 /* set up a DotMacTpPendingRequest */ 180 req.userName = userName; 181 req.domainName = domainName; 182 uint8 certType = certTypeTag; 183 req.certTypeTag.Data = &certType; 184 req.certTypeTag.Length = 1; 185 186 /* DER encode */ 187 CSSM_DATA tempData = {0, NULL}; 188 PRErrorCode prtn = coder.encodeItem(&req, DotMacTpPendingRequestTemplate, tempData); 189 if(prtn) { 190 dotMacErrorLog("dotMacEncodeRefId: encodeItem error"); 191 return internalComponentErr; 192 } 193 194 /* PEM encode */ 195 unsigned char *pem; 196 unsigned pemLen; 197 if(pemEncode(tempData.Data, tempData.Length, &pem, &pemLen, "REFERENCE ID")) { 198 dotMacErrorLog("dotMacEncodeRefId: pemEncode error"); 199 return internalComponentErr; 200 } 201 refId.Data = NULL; 202 refId.Length = 0; 203 coder.allocCopyItem(pem, pemLen, refId); 204 free(pem); 205 206 return noErr; 207} 208 209OSStatus dotMacDecodeRefId( 210 SecNssCoder &coder, // results mallocd in this address space 211 const CSSM_DATA &refId, // PEM encoded 212 CSSM_DATA &userName, // RETURNED, UTF8, no NULL 213 CSSM_DATA &domainName, // RETURNED, UTF8, no NULL 214 DotMacCertTypeTag *certTypeTag) // RETURNED 215{ 216 /* PEM decode */ 217 unsigned char *unPem; 218 unsigned unPemLen; 219 if(pemDecode(refId.Data, refId.Length, &unPem, &unPemLen)) { 220 dotMacErrorLog("dotMacDecodeRefId: pemDecode error"); 221 return internalComponentErr; 222 } 223 224 /* DER decode */ 225 CSSM_DATA tempData; 226 tempData.Data = unPem; 227 tempData.Length = unPemLen; 228 229 DotMacTpPendingRequest req; 230 memset(&req, 0, sizeof(req)); 231 232 PRErrorCode prtn = coder.decodeItem(tempData, DotMacTpPendingRequestTemplate, &req); 233 free(unPem); 234 if(prtn) { 235 dotMacErrorLog("dotMacDecodeRefId: decodeItem error"); 236 return paramErr; 237 } 238 239 /* decoded params back to caller */ 240 userName = req.userName; 241 domainName = req.domainName; 242 if(req.certTypeTag.Length != 1) { 243 dotMacErrorLog("dotMacDecodeRefId: reqType length (%lu) error", req.certTypeTag.Length); 244 return paramErr; 245 } 246 *certTypeTag = req.certTypeTag.Data[0]; 247 return noErr; 248} 249 250/* SPI to specify timeout on CFReadStream */ 251#define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout") 252 253/* the read timeout we set, in seconds */ 254#define READ_STREAM_TIMEOUT 15 255 256/* amount of data per CFReadStreamRead() */ 257#define READ_FRAGMENT_SIZE 512 258 259/* fetch cert via HTTP */ 260CSSM_RETURN dotMacTpCertFetch( 261 const CSSM_DATA &userName, // UTF8, no NULL 262 const CSSM_DATA &domainName, // UTF8, no NULL 263 DotMacCertTypeTag certType, 264 Allocator &alloc, // results mallocd here 265 CSSM_DATA &result) // RETURNED 266{ 267 unsigned rawUrlLen; 268 unsigned domainLen = (domainName.Length && domainName.Data) ? domainName.Length : strlen(DOT_MAC_DOMAIN); 269 uint8 *domain = (domainName.Length && domainName.Data) ? domainName.Data : (uint8 *) DOT_MAC_DOMAIN; 270 const char *typeArg; 271 CSSM_RETURN crtn = CSSM_OK; 272 273 switch(certType) { 274 case CSSM_DOT_MAC_TYPE_ICHAT: 275 case CSSM_DOT_MAC_TYPE_UNSPECIFIED: 276 typeArg = DOT_MAC_CERT_TYPE_ICHAT; 277 break; 278 case CSSM_DOT_MAC_TYPE_SHARED_SERVICES: 279 typeArg = DOT_MAC_CERT_TYPE_SHARED_SERVICES; 280 break; 281 case CSSM_DOT_MAC_TYPE_EMAIL_SIGNING: 282 typeArg = DOT_MAC_CERT_TYPE_EMAIL_SIGNING; 283 break; 284 case CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT: 285 typeArg = DOT_MAC_CERT_TYPE_EMAIL_ENCRYPT; 286 break; 287 default: 288 dotMacErrorLog("dotMacTpCertFetch: bad signType"); 289 return paramErr; 290 } 291 292 /* URL := http://certinfo.mac.com/locate?accountName&type=certTypeTag */ 293 rawUrlLen = strlen(DOT_MAC_LOOKUP_SCHEMA) + /* http:// */ 294 strlen(DOT_MAC_LOOKUP_HOST_NAME) + /* certinfo */ 295 domainLen + 1 + /* .mac.com */ 296 strlen(DOT_MAC_LOOKUP_PATH) + /* /locate? */ 297 userName.Length + /* joe */ 298 strlen(DOT_MAC_LOOKUP_TYPE) + /* &type= */ 299 strlen(typeArg) + /* dmSharedServices */ 300 1; /* NULL */ 301 unsigned char rawUrl[rawUrlLen]; 302 unsigned char *cp = rawUrl; 303 304 unsigned len = strlen(DOT_MAC_LOOKUP_SCHEMA); 305 memmove(cp, DOT_MAC_LOOKUP_SCHEMA, len); 306 cp += len; 307 308 len = strlen(DOT_MAC_LOOKUP_HOST_NAME); 309 memmove(cp, DOT_MAC_LOOKUP_HOST_NAME, len); 310 cp += len; 311 312 memmove(cp, ".", 1); 313 cp += 1; 314 memmove(cp, domain, domainLen); 315 cp += domainLen; 316 317 len = strlen(DOT_MAC_LOOKUP_PATH); 318 memmove(cp, DOT_MAC_LOOKUP_PATH, len); 319 cp += len; 320 321 memmove(cp, userName.Data, userName.Length); 322 cp += userName.Length; 323 324 len = strlen(DOT_MAC_LOOKUP_TYPE); 325 memmove(cp, DOT_MAC_LOOKUP_TYPE, len); 326 cp += len; 327 328 len = strlen(typeArg); 329 memmove(cp, typeArg, len); 330 cp += len; 331 332 *cp = '\0'; // for debugging only, actually 333 dotMacDebug("dotMacTpCertFetch: URL %s", rawUrl); 334 335 CFURLRef cfUrl = CFURLCreateWithBytes(NULL, 336 rawUrl, rawUrlLen - 1, // no NULL 337 kCFStringEncodingUTF8, 338 NULL); // absolute path 339 if(cfUrl == NULL) { 340 dotMacErrorLog("dotMacTpCertFetch: CFURLCreateWithBytes returned NULL\n"); 341 return paramErr; 342 } 343 /* subsequent errors to errOut: */ 344 345 CFHTTPMessageRef httpRequestRef = NULL; 346 CFReadStreamRef httpStreamRef = NULL; 347 CFNumberRef cfnTo = NULL; 348 CFDictionaryRef proxyDict = NULL; 349 SInt32 ito = READ_STREAM_TIMEOUT; 350 CFMutableDataRef fetchedData = CFDataCreateMutable(NULL, 0); 351 UInt8 readFrag[READ_FRAGMENT_SIZE]; 352 CFIndex bytesRead; 353 CFIndex resultLen; 354 355 httpRequestRef = CFHTTPMessageCreateRequest(NULL, 356 CFSTR("GET"), cfUrl, kCFHTTPVersion1_1); 357 if(!httpRequestRef) { 358 dotMacErrorLog("***Error creating HTTPMessage from '%s'\n", rawUrl); 359 crtn = ioErr; 360 goto errOut; 361 } 362 363 // open the stream 364 httpStreamRef = CFReadStreamCreateForHTTPRequest(NULL, httpRequestRef); 365 if(!httpStreamRef) { 366 dotMacErrorLog("***Error creating stream for '%s'\n", rawUrl); 367 crtn = ioErr; 368 goto errOut; 369 } 370 371 // set a reasonable timeout 372 cfnTo = CFNumberCreate(NULL, kCFNumberSInt32Type, &ito); 373 if(!CFReadStreamSetProperty(httpStreamRef, _kCFStreamPropertyReadTimeout, cfnTo)) { 374 // oh well - keep going 375 } 376 377 // set up possible proxy info 378 proxyDict = SCDynamicStoreCopyProxies(NULL); 379 if(proxyDict) { 380 CFReadStreamSetProperty(httpStreamRef, kCFStreamPropertyHTTPProxy, proxyDict); 381 } 382 383 if(CFReadStreamOpen(httpStreamRef) == false) { 384 dotMacErrorLog("***Error opening stream for '%s'\n", rawUrl); 385 crtn = ioErr; 386 goto errOut; 387 } 388 389 // read data from the stream 390 bytesRead = CFReadStreamRead(httpStreamRef, readFrag, sizeof(readFrag)); 391 while (bytesRead > 0) { 392 CFDataAppendBytes(fetchedData, readFrag, bytesRead); 393 bytesRead = CFReadStreamRead(httpStreamRef, readFrag, sizeof(readFrag)); 394 } 395 396 if (bytesRead < 0) { 397 dotMacErrorLog("***Error reading URL '%s'\n", rawUrl); 398 crtn = ioErr; 399 goto errOut; 400 } 401 402 resultLen = CFDataGetLength(fetchedData); 403 if(resultLen == 0) { 404 dotMacErrorLog("***No data available from URL '%s'\n", rawUrl); 405 /* but don't abort on this one - it means "no cert found" */ 406 goto errOut; 407 } 408 409 /* 410 * Only pass back good data. 411 * FIXME this is a back to workaround nonconforming .Mac server behavior. 412 * It currently sends HTML data that is *not* a cert when it wants to 413 * indicate "no certs found". It should just return empty data, which 414 * we'd detect above. For now we have to determine manually if the data 415 * contains some PEM-formated stuff. 416 */ 417 { 418 /* Scan for PEM armour */ 419 bool isPEM = false; 420 const char *srchStr = "-----BEGIN CERTIFICATE-----"; 421 unsigned srchStrLen = strlen(srchStr); 422 const char *p = (const char *)CFDataGetBytePtr(fetchedData); 423 if(resultLen > (int)srchStrLen) { 424 /* no sense checking if result is smaller than that search string */ 425 unsigned srchLen = resultLen - srchStrLen; 426 for(unsigned dex=0; dex< srchLen; dex++) { 427 if(!strncmp(p, srchStr, srchStrLen)) { 428 isPEM = true; 429 break; 430 } 431 p++; 432 } 433 } 434 if(isPEM) { 435 result.Data = (uint8 *)alloc.malloc(resultLen); 436 result.Length = resultLen; 437 memmove(result.Data, CFDataGetBytePtr(fetchedData), resultLen); 438 } 439 else { 440 result.Data = NULL; 441 result.Length = 0; 442 } 443 } 444errOut: 445 CFRELEASE(cfUrl); 446 CFRELEASE(httpRequestRef); 447 CFRELEASE(httpStreamRef); 448 CFRELEASE(cfnTo); 449 CFRELEASE(proxyDict); 450 451 return crtn; 452} 453 454