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 * dotMacTpRpcGlue.cpp - glue between CDSA and XMLRPC for .mac TP 26 */ 27 28#include "dotMacTpRpcGlue.h" 29#include "dotMacTpUtils.h" 30#include "dotMacTpDebug.h" 31#include "dotMacTpMutils.h" 32#include <stdint.h> 33#include <CoreFoundation/CoreFoundation.h> 34#include "dotMacXmlRpc.h" 35#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h> 36#include <Security/cssmapple.h> 37 38/* Dump XMLRPC Result dictionary */ 39#if DICTIONARY_DEBUG 40#define RESULTS_DICTIONARY_DEBUG 1 41#define REQUEST_DICTIONARY_DEBUG 1 42#else 43#define RESULTS_DICTIONARY_DEBUG 0 44#define REQUEST_DICTIONARY_DEBUG 0 45#endif 46 47/* 48 * Force/simulate SuccessQueued errors to test this module before the server can 49 * actually generate those errors. 50 */ 51#ifndef NDEBUG 52#define FORCE_SUCCESS_QUEUED 0 53#else 54#define FORCE_SUCCESS_QUEUED 0 55#endif 56#if FORCE_SUCCESS_QUEUED 57/* actual behavior is tweakable by debugger */ 58int forceQueued = 1; 59#endif /* FORCE_SUCCESS_QUEUED */ 60 61/* 62 * Force SuccessQueued even if server returns a really bad error 63 */ 64#ifndef NDEBUG 65#define FORCE_SUCCESS_QUEUED_ALWAYS 0 66#else 67#define FORCE_SUCCESS_QUEUED 0 68#endif 69 70#define CFRELEASE(cf) \ 71 if(cf != NULL) { \ 72 CFRelease(cf); \ 73 } 74 75/* 76 * Constant strings for Treadstone XMLRPC API. 77 */ 78 79/* 80 * Method names. 81 */ 82static CFStringRef kMethodSignIChatEncrypt = CFSTR("sign." DOT_MAC_CERT_TYPE_ICHAT); 83static CFStringRef kMethodSignSharedServices = CFSTR("sign." DOT_MAC_CERT_TYPE_SHARED_SERVICES); 84static CFStringRef kMethodSignEmailSigning = CFSTR("sign." DOT_MAC_CERT_TYPE_EMAIL_SIGNING); 85static CFStringRef kMethodSignEmailEncryption = CFSTR("sign." DOT_MAC_CERT_TYPE_EMAIL_ENCRYPT); 86static CFStringRef kMethodStatusIChatEncrypt = CFSTR("status." DOT_MAC_CERT_TYPE_ICHAT); 87static CFStringRef kMethodStatusSharedServices = CFSTR("status." DOT_MAC_CERT_TYPE_SHARED_SERVICES); 88static CFStringRef kMethodStatusEmailSigning = CFSTR("status." DOT_MAC_CERT_TYPE_EMAIL_SIGNING); 89static CFStringRef kMethodStatusEmailEncryption = CFSTR("status." DOT_MAC_CERT_TYPE_EMAIL_ENCRYPT); 90static CFStringRef kMethodArchiveList = CFSTR("archive.list"); 91static CFStringRef kMethodArchiveSave = CFSTR("archive.save"); 92static CFStringRef kMethodArchiveFetch = CFSTR("archive.fetch"); 93static CFStringRef kMethodArchiveRemove = CFSTR("archive.remove"); 94 95/* 96 * Fixed parameter names. 97 */ 98 99/* first param to sign */ 100static CFStringRef kParamSignIssue = CFSTR("issue"); 101/* 102 * CertTypeTag as parameter to archive.save 103 * Also used as one of the out params from archive.list 104 */ 105static CFStringRef kParamCertTypeIChat = CFSTR(DOT_MAC_CERT_TYPE_ICHAT); 106static CFStringRef kParamCertTypeSharedServices = CFSTR(DOT_MAC_CERT_TYPE_SHARED_SERVICES); 107static CFStringRef kParamCertTypeEmailEncryption = CFSTR(DOT_MAC_CERT_TYPE_EMAIL_SIGNING); 108static CFStringRef kParamCertTypeEmailSigning = CFSTR(DOT_MAC_CERT_TYPE_EMAIL_ENCRYPT); 109 110/* 111 * names of values in an XMLRPC response 112 */ 113static CFStringRef kResponseResultCode = CFSTR("resultCode"); 114/* 115 * FIXME: We don't use this: Should we? 116 */ 117// static CFStringRef kResponseTimestamp = CFSTR("timestamp"); 118static CFStringRef kResponseResultBody = CFSTR("resultBody"); 119 120/* 121 * names of values in an archive.list dictionary 122 */ 123static CFStringRef kArchiveListName = CFSTR("name"); 124static CFStringRef kArchiveListExpires = CFSTR("expires"); 125static CFStringRef kArchiveListType = CFSTR("type"); 126static CFStringRef kArchiveListSerialNumber = CFSTR("serial"); 127 128/* 129 * resultCode strings 130 */ 131/* OK, resultBody contains the cert */ 132static CFStringRef kResultSuccess = CFSTR("Success"); 133/* OK, resultBody contains seconds to availability of cert */ 134static CFStringRef kResultQueued = CFSTR("SuccessQueued"); 135/* not really OK, caller must visit URL specified in resultBody */ 136static CFStringRef kResultRedirected = CFSTR("SuccessRedirected"); 137static CFStringRef kResultFailed = CFSTR("Failed"); 138static CFStringRef kResultAlreadyExists = CFSTR("FailedAlreadyExists"); 139static CFStringRef kResultCertAlreadyExists = CFSTR("FailedCertAlreadyExists"); 140static CFStringRef kResultServiceError = CFSTR("FailedServiceError"); 141static CFStringRef kResultParameterError = CFSTR("FailedParameterError"); 142static CFStringRef kResultNotAllowed = CFSTR("FailedNotAllowed"); 143static CFStringRef kResultPendingCSR = CFSTR("FailedPendingCSR"); 144static CFStringRef kResultNoExistingCSR = CFSTR("FailedNoExistingCSR"); 145static CFStringRef kResultNotSupportForAccount = CFSTR("FailedNotSupportedForAccount"); 146static CFStringRef kResultCSRDidNotVerify = CFSTR("FailedCSRDidNotVerify"); 147static CFStringRef kResultNotImplemented = CFSTR("NotImplemented"); 148static CFStringRef kResultNotAuthorized = CFSTR("NotAuthorized"); 149static CFStringRef kResultNotAvailable = CFSTR("NotAvailable"); 150static CFStringRef kResultConsistencyCheck = CFSTR("FailedConsistencyCheck"); 151 152 153/* quickie parameter names which don't go over the wire, just for ordering */ 154static CFStringRef kP1 = CFSTR("p1"); 155static CFStringRef kP2 = CFSTR("p2"); 156static CFStringRef kP3 = CFSTR("p3"); 157static CFStringRef kP4 = CFSTR("p4"); 158static CFStringRef kP5 = CFSTR("p5"); 159 160/* 161 * Convert an XMLRPC resultCode string to an OSStatus/CSSM_RETURN. 162 */ 163static OSStatus dotMacParseResult( 164 CFStringRef resultCode) 165{ 166 if(CFEqual(resultCode, kResultSuccess)) { 167 #if FORCE_SUCCESS_QUEUED 168 if(forceQueued) { 169 printf("...Forcing REQ_QUEUED status\n"); 170 return CSSMERR_APPLE_DOTMAC_REQ_QUEUED; 171 } 172 #endif /* FORCE_SUCCESS_QUEUED */ 173 return noErr; 174 } 175 else if(CFEqual(resultCode, kResultQueued)) { 176 return CSSMERR_APPLE_DOTMAC_REQ_QUEUED; 177 } 178 else if(CFEqual(resultCode, kResultRedirected)) { 179 return CSSMERR_APPLE_DOTMAC_REQ_REDIRECT; 180 } 181 else if(CFEqual(resultCode, kResultFailed)) { 182 return CSSMERR_APPLE_DOTMAC_REQ_SERVER_ERR; 183 } 184 else if(CFEqual(resultCode, kResultAlreadyExists) || 185 CFEqual(resultCode, kResultCertAlreadyExists)) { 186 return CSSMERR_APPLE_DOTMAC_REQ_SERVER_ALREADY_EXIST; 187 } 188 else if(CFEqual(resultCode, kResultParameterError)) { 189 return CSSMERR_APPLE_DOTMAC_REQ_SERVER_PARAM; 190 } 191 else if(CFEqual(resultCode, kResultNotAllowed)) { 192 return CSSMERR_APPLE_DOTMAC_REQ_SERVER_AUTH; 193 } 194 else if(CFEqual(resultCode, kResultPendingCSR)) { 195 return CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING; 196 } 197 else if(CFEqual(resultCode, kResultNoExistingCSR)) { 198 return CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING; 199 } 200 else if(CFEqual(resultCode, kResultNotSupportForAccount)) { 201 /* FIXME might want a .mac TP-specific error for this */ 202 return CSSMERR_TP_REQUEST_REJECTED; 203 } 204 else if(CFEqual(resultCode, kResultCSRDidNotVerify)) { 205 return CSSMERR_APPLE_DOTMAC_CSR_VERIFY_FAIL; 206 } 207 else if(CFEqual(resultCode, kResultServiceError)) { 208 return CSSMERR_APPLE_DOTMAC_REQ_SERVER_SERVICE_ERROR; 209 } 210 else if(CFEqual(resultCode, kResultNotImplemented)) { 211 return CSSMERR_APPLE_DOTMAC_REQ_SERVER_UNIMPL; 212 } 213 else if(CFEqual(resultCode, kResultNotAuthorized)) { 214 return CSSMERR_APPLE_DOTMAC_REQ_SERVER_AUTH; 215 } 216 else if(CFEqual(resultCode, kResultNotAvailable)) { 217 return CSSMERR_APPLE_DOTMAC_REQ_SERVER_NOT_AVAIL; 218 } 219 else if(CFEqual(resultCode, kResultConsistencyCheck)) { 220 return CSSMERR_APPLE_DOTMAC_FAILED_CONSISTENCY_CHECK; 221 } 222 223 else { 224 dotMacErrorLog("dotMacParseResult: unknown error\n"); 225 return ioErr; 226 } 227} 228 229/* CSSM_DATA <--> CFStringRef */ 230static inline CFStringRef cDataToCfstr( 231 const CSSM_DATA &cdata) 232{ 233 return CFStringCreateWithBytes(NULL, 234 cdata.Data, (CFIndex)cdata.Length, 235 kCFStringEncodingASCII, false); 236} 237 238static void cfstrToCdata( 239 const CFStringRef cfstr, 240 CSSM_DATA &cdata, 241 Allocator &alloc) 242{ 243 CFDataRef cfData = CFStringCreateExternalRepresentation(NULL, 244 cfstr, kCFStringEncodingUTF8, 0); 245 if(cfData == NULL) { 246 dotMacErrorLog("dotMac cfstrToCdatafailure"); 247 CssmError::throwMe(internalComponentErr); 248 } 249 cdata.Length = CFDataGetLength(cfData); 250 cdata.Data = (uint8 *)alloc.malloc(cdata.Length); 251 memmove(cdata.Data, CFDataGetBytePtr(cfData), cdata.Length); 252 CFRelease(cfData); 253} 254 255/* 256 * Convert between binary data - e.g. a cert serial number - and a CFStringRef. 257 * We do this with the same logic as SecurityInterface: if the data is 8 bytes 258 * or less, encode it as a decimal number, else stringify it as ASCII hex. 259 */ 260static const char hexChars[] = "0123456789ABCDEF"; 261 262static CFStringRef cDataToCfAsciiStr( 263 const CSSM_DATA &cdata) 264{ 265 if(cdata.Length > sizeof(uint64)) { 266 char asciiData[(2 * cdata.Length) + 1]; 267 const unsigned char *inp = (const unsigned char *)cdata.Data; 268 char *outp = asciiData; 269 270 for(unsigned dex=0; dex<cdata.Length; dex++) { 271 unsigned c = *inp++; 272 outp[1] = hexChars[c & 0xf]; 273 c >>= 4; 274 outp[0] = hexChars[c]; 275 outp += 2; 276 } 277 *outp = 0; 278 return CFStringCreateWithCString(NULL, asciiData, kCFStringEncodingASCII); 279 } 280 else { 281 uint64 value = 0; 282 for(unsigned i=0; i<cdata.Length; i++) { 283 value <<= 8; 284 value += cdata.Data[i]; 285 } 286 char cStr[200]; 287 snprintf(cStr, sizeof(cStr), "%llu", value); 288 return CFStringCreateWithCString(NULL, cStr, kCFStringEncodingASCII); 289 } 290} 291 292/* 293 * Convert a serial number string as created by cDataToCfAsciiStr() to 294 * a CSSM_DATA. We have to assume it's in decimal here. 295 */ 296static void CfAsciiStrToCdata( 297 const CFStringRef cfStr, 298 CSSM_DATA &cdata, 299 Allocator &alloc) 300{ 301 /* the string MUST be in ASCII */ 302 CFDataRef strData = CFStringCreateExternalRepresentation(NULL, cfStr, 303 kCFStringEncodingASCII, 0); 304 if(strData == NULL) { 305 dotMacDebug("dotMac CfAsciiStrToCdata: ASCII conversion FAILED!"); 306 return; 307 } 308 unsigned len = (unsigned)CFDataGetLength(strData); 309 const char *inp = (const char *)CFDataGetBytePtr(strData); 310 char cStr[len + 1]; 311 memmove(cStr, inp, len); 312 cStr[len] = '\0'; 313 314 uint64 val = 0; 315 sscanf(cStr, "%llu", &val); 316 317 /* convert 64-bit val to byte array */ 318 int byteNum = 7; // byte within 64-bit val 319 unsigned shift = 7*8; // bits to shift right to move that into one byte 320 uint64 mask = 0xff00000000000000ULL; 321 322 /* skip over zeroes in m.s. bytes */ 323 while(val & mask) { 324 byteNum--; 325 shift -= 8; 326 mask >>= 8; 327 } 328 329 cdata.Data = (uint8 *)alloc.malloc(byteNum + 1); 330 cdata.Length = byteNum + 1; 331 uint8 *outp = cdata.Data; 332 333 do { 334 uint64 v = val >> shift; 335 *outp++ = v & 0xff; 336 shift -= 8; 337 byteNum--; 338 } while(byteNum >= 0); 339 340 CFRelease(strData); 341} 342 343static CFURLRef createUrl( 344 const char *schema, 345 const CSSM_DATA &hostName, 346 const char *path) 347{ 348 int schemaLen = strlen(schema); 349 int urlLength = schemaLen + hostName.Length + strlen(path) + 1; 350 char *urlStr = (char *)malloc(urlLength); 351 memmove(urlStr, schema, schemaLen); 352 memmove(urlStr + schemaLen, hostName.Data, hostName.Length); 353 urlStr[schemaLen + hostName.Length] = '\0'; 354 strcat(urlStr, path); 355 CFURLRef url = CFURLCreateWithBytes(NULL, (const UInt8 *)urlStr, urlLength-1, 356 kCFStringEncodingASCII, NULL); 357 dotMacDebug("dotMac createUrl: URL %s", urlStr); 358 free(urlStr); 359 return url; 360} 361 362OSStatus dotMacPostCertReq( 363 DotMacCertTypeTag certType, 364 const CSSM_DATA &userName, 365 const CSSM_DATA &password, 366 const CSSM_DATA &hostName, 367 bool renew, // this is obsolete; currently ignored 368 const CSSM_DATA &csr, // DER encoded 369 SecNssCoder &coder, 370 sint32 &estTime, // possibly returned 371 CSSM_DATA &resultBodyData) // possibly returned 372{ 373 resultBodyData.Data = NULL; 374 resultBodyData.Length = 0; 375 376 /* 377 * First gather arguments into CF form. 378 */ 379 CFURLRef url = createUrl(DOT_MAC_SIGN_SCHEMA, hostName, DOT_MAC_SIGN_PATH); 380 CFStringRef userStr = cDataToCfstr(userName); 381 CFStringRef pwdStr = cDataToCfstr(password); 382 CFStringRef csrStr = cDataToCfstr(csr); 383 384 /* 385 * Now cook up arguments for an XMLRPC. 386 */ 387 CFMutableDictionaryRef argDict; 388 CFMutableArrayRef argOrder; 389 CFStringRef method; 390 391 switch(certType) { 392 case CSSM_DOT_MAC_TYPE_ICHAT: 393 method = kMethodSignIChatEncrypt; 394 break; 395 case CSSM_DOT_MAC_TYPE_SHARED_SERVICES: 396 method = kMethodSignSharedServices; 397 break; 398 case CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT: 399 method = kMethodSignEmailEncryption; 400 break; 401 case CSSM_DOT_MAC_TYPE_EMAIL_SIGNING: 402 method = kMethodSignEmailSigning; 403 break; 404 default: 405 return paramErr; 406 } 407 408 argDict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, 409 &kCFTypeDictionaryValueCallBacks); 410 argOrder = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 411 412 /* this used to be "new" or "renew" */ 413 CFDictionaryAddValue(argDict, kP1, kParamSignIssue); 414 CFArrayAppendValue(argOrder, kP1); 415 416 CFDictionaryAddValue(argDict, kP2, csrStr); 417 CFArrayAppendValue(argOrder, kP2); 418 419 OSStatus ortn; 420 CFDictionaryRef resultDict = NULL; 421 uint32_t httpStat; 422 423 ortn = performAuthenticatedXMLRPC(url, method, argDict, argOrder, userStr, pwdStr, 424 &resultDict, &httpStat); 425 426 if(ortn) { 427 #if FORCE_SUCCESS_QUEUED_ALWAYS 428 dotMacErrorLog("dotMacPostCertReq: FORCING queued success\n"); 429 ortn = CSSMERR_APPLE_DOTMAC_REQ_QUEUED; 430 goto proceedQueued; 431 #endif 432 dotMacErrorLog("dotMacPostCertReq: XMLRPC returned %ld\n", ortn); 433 /* And we're dead - means RPC did not complete */ 434 goto errOut; 435 } 436 if(resultDict == NULL) { 437 dotMacErrorLog("dotMacPostCertReq: XMLRPC failed to return a dictionary\n"); 438 ortn = ioErr; 439 goto errOut; 440 } 441 #if RESULTS_DICTIONARY_DEBUG 442 dumpDictionary("Results dictionary", resultDict); 443 #endif 444 445 CFStringRef resultCode; 446 resultCode = (CFStringRef)CFDictionaryGetValue(resultDict, kResponseResultCode); 447 if((resultCode == NULL) || (CFGetTypeID(resultCode) != CFStringGetTypeID())) { 448 dotMacErrorLog("dotMacPostCertReq: resultCode is not a string\n"); 449 ortn = ioErr; 450 goto errOut; 451 } 452 ortn = dotMacParseResult(resultCode); 453 454#if FORCE_SUCCESS_QUEUED_ALWAYS 455proceedQueued: 456#endif 457 458 /* For some results, we have some data to give to caller */ 459 switch(ortn) { 460 case noErr: /* resultBody = PEM-encoded cert */ 461 case CSSMERR_APPLE_DOTMAC_REQ_REDIRECT: /* resultBody = URL */ 462 { 463 CFStringRef resultBody; 464 resultBody = (CFStringRef)CFDictionaryGetValue(resultDict, 465 kResponseResultBody); 466 if((resultBody == NULL) ||(CFGetTypeID(resultBody) != CFStringGetTypeID())) { 467 dotMacErrorLog("dotMacPostCertReq: resultBody is not a string\n"); 468 ortn = ioErr; 469 goto errOut; 470 } 471 CFIndex len = CFStringGetLength(resultBody); 472 coder.allocItem(resultBodyData, (size_t)len + 1); 473 if(!CFStringGetCString(resultBody, (char *)resultBodyData.Data, len+1, 474 kCFStringEncodingUTF8)) { 475 dotMacErrorLog("dotMacPostCertReq: resultBody is not convertible to " 476 "UTF8\n"); 477 ortn = ioErr; 478 } 479 break; 480 } 481 482 case CSSMERR_APPLE_DOTMAC_REQ_QUEUED: 483 { 484 /* 485 * Cook up an opaque reference ID which enables us to fetch the 486 * result of this queued request at a later time. 487 * 488 * The estimated availability time is returned in the 489 * resultsBody as a string value. 490 */ 491 CSSM_DATA host = { 0, NULL }; 492 CSSM_DATA domain = { 0, NULL }; 493 dotMacTokenizeHostName(hostName, host, domain); 494 ortn = dotMacEncodeRefId(userName, domain, certType, coder, resultBodyData); 495 if(ortn == noErr) { 496 ortn = CSSMERR_APPLE_DOTMAC_REQ_QUEUED; 497 } 498 499 /* Return the estimated time */ 500 CFStringRef resultBody; 501 resultBody = (CFStringRef)CFDictionaryGetValue(resultDict, 502 kResponseResultBody); 503 if((resultBody == NULL) ||(CFGetTypeID(resultBody) != CFStringGetTypeID())) { 504 dotMacErrorLog("dotMacPostCertReq: resultBody is not a string\n"); 505 ortn = ioErr; 506 goto errOut; 507 } 508 SInt32 timeValue = CFStringGetIntValue(resultBody); 509 estTime = (sint32) timeValue; 510 break; 511 } 512 default: 513 dotMacErrorLog("dotMacPostCertReq: unhandled result %d\n", (int)ortn); 514 break; 515 } 516 517errOut: 518 CFRelease(url); 519 CFRelease(userStr); 520 CFRelease(pwdStr); 521 CFRelease(csrStr); 522 CFRelease(argDict); 523 CFRelease(argOrder); 524 if(resultDict) { 525 CFRelease(resultDict); 526 } 527 return ortn; 528 529} 530 531/* 532 * Return archive list in one of two formats. 533 * Returned memory is allocated in provided allocator's space. 534 * 535 * This version is used when the app provided a v1 CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST, 536 * and it therefore expecting an array of DotMacArchive in return. This can be 537 * deleted when we've ensured that nobody is using the v1 request. 538 */ 539static OSStatus dotMacReturnArchiveList_v1( 540 CFDictionaryRef resultDict, 541 unsigned *numArchives, // RETURNED 542 DotMacArchive **archives, // RETURNED 543 Allocator &alloc) 544{ 545 CFArrayRef archList; 546 archList = (CFArrayRef)CFDictionaryGetValue(resultDict, 547 kResponseResultBody); 548 if((archList == NULL) || (CFGetTypeID(archList) != CFArrayGetTypeID())) { 549 dotMacErrorLog("archive(list): resultBody is not an array\n"); 550 return ioErr; 551 } 552 assert(numArchives != NULL); 553 assert(archives != NULL); 554 CFIndex numEntries = CFArrayGetCount(archList); 555 *archives = (DotMacArchive *)alloc.malloc(sizeof(DotMacArchive) * numEntries); 556 memset(*archives, 0, sizeof(DotMacArchive) * numEntries); 557 for(CFIndex dex=0; dex<numEntries; dex++) { 558 DotMacArchive *dmarc = &((*archives)[dex]); 559 CFDictionaryRef dict = 560 (CFDictionaryRef)CFArrayGetValueAtIndex(archList, dex); 561 if((dict == NULL) || (CFGetTypeID(dict) != CFDictionaryGetTypeID())) { 562 dotMacErrorLog("archive(list): result element is not a dict\n"); 563 return ioErr; 564 } 565 566 /* extract two fields from this dictionary: name and expiration date */ 567 CFStringRef cfstr = (CFStringRef)CFDictionaryGetValue(dict, 568 kArchiveListName); 569 if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) { 570 dotMacErrorLog("archive(list): archiveName is not a string\n"); 571 return ioErr; 572 } 573 cfstrToCdata(cfstr, dmarc->archiveName, alloc); 574 575 cfstr = (CFStringRef)CFDictionaryGetValue(dict, kArchiveListExpires); 576 if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) { 577 dotMacErrorLog("archive(list): expires is not a string\n"); 578 return ioErr; 579 } 580 cfstrToCdata(cfstr, dmarc->timeString, alloc); 581 } 582 *numArchives = numEntries; 583 return noErr; 584} 585 586/* V. 2, Treadstone style Archive list */ 587static OSStatus dotMacReturnArchiveList_v2( 588 CFDictionaryRef resultDict, 589 unsigned *numArchives, // RETURNED 590 DotMacArchive_v2 **archives, // RETURNED 591 Allocator &alloc) 592{ 593 CFArrayRef archList; 594 archList = (CFArrayRef)CFDictionaryGetValue(resultDict, 595 kResponseResultBody); 596 if((archList == NULL) || (CFGetTypeID(archList) != CFArrayGetTypeID())) { 597 dotMacErrorLog("archive(list): resultBody is not an array\n"); 598 return ioErr; 599 } 600 assert(numArchives != NULL); 601 assert(archives != NULL); 602 CFIndex numEntries = CFArrayGetCount(archList); 603 *archives = (DotMacArchive_v2 *)alloc.malloc(sizeof(DotMacArchive_v2) * numEntries); 604 memset(*archives, 0, sizeof(DotMacArchive_v2) * numEntries); 605 for(CFIndex dex=0; dex<numEntries; dex++) { 606 DotMacArchive_v2 *dmarc = &((*archives)[dex]); 607 CFDictionaryRef dict = 608 (CFDictionaryRef)CFArrayGetValueAtIndex(archList, dex); 609 if((dict == NULL) || (CFGetTypeID(dict) != CFDictionaryGetTypeID())) { 610 dotMacErrorLog("archive(list): result element is not a dict\n"); 611 return ioErr; 612 } 613 614 /* 615 * Extract 4 fields from this dictionary: 616 * name 617 * expiration date 618 * certTypeTag 619 * serial number 620 */ 621 CFStringRef cfstr = (CFStringRef)CFDictionaryGetValue(dict, 622 kArchiveListName); 623 if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) { 624 dotMacErrorLog("archive(list): archiveName is not a string\n"); 625 return ioErr; 626 } 627 cfstrToCdata(cfstr, dmarc->archiveName, alloc); 628 629 /* expiration date */ 630 cfstr = (CFStringRef)CFDictionaryGetValue(dict, kArchiveListExpires); 631 if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) { 632 dotMacErrorLog("archive(list): expires is not a string\n"); 633 return ioErr; 634 } 635 cfstrToCdata(cfstr, dmarc->timeString, alloc); 636 637 /* certTypeTag */ 638 cfstr = (CFStringRef)CFDictionaryGetValue(dict, kArchiveListType); 639 if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) { 640 dotMacErrorLog("archive(list): CertType is not a string\n"); 641 return ioErr; 642 } 643 if(CFEqual(cfstr, kParamCertTypeIChat)) { 644 dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_ICHAT; 645 } 646 else if(CFEqual(cfstr, kParamCertTypeSharedServices)) { 647 dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_SHARED_SERVICES; 648 } 649 else if(CFEqual(cfstr, kParamCertTypeEmailEncryption)) { 650 dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT; 651 } 652 else if(CFEqual(cfstr, kParamCertTypeEmailSigning)) { 653 dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_EMAIL_SIGNING; 654 } 655 else { 656 dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_UNSPECIFIED; 657 } 658 659 /* serial number */ 660 cfstr = (CFStringRef)CFDictionaryGetValue(dict, kArchiveListSerialNumber); 661 if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) { 662 dotMacErrorLog("archive(list): Serial Number is not a string\n"); 663 return ioErr; 664 } 665 CfAsciiStrToCdata(cfstr, dmarc->serialNumber, alloc); 666 } 667 *numArchives = numEntries; 668 return noErr; 669} 670 671/* post archive request */ 672OSStatus dotMacPostArchiveReq( 673 uint32 version, 674 DotMacCertTypeTag certTypeTag, 675 DotMacArchiveType archiveType, 676 const CSSM_DATA &userName, 677 const CSSM_DATA &password, 678 const CSSM_DATA &hostName, 679 const CSSM_DATA *archiveName, 680 const CSSM_DATA *pfxIn, // for store only 681 const CSSM_DATA *timeString, // for store only 682 const CSSM_DATA *serialNumber, // for store only 683 CSSM_DATA *pfxOut, // RETURNED for fetch, allocated via alloc 684 unsigned *numArchives, // RETURNED for list 685 // at most one of the following is returned, and for list only 686 DotMacArchive **archives_v1, // RETURNED for list, allocated via alloc 687 DotMacArchive_v2 **archives_v2, 688 Allocator &alloc) 689{ 690 /* init return values */ 691 if(pfxOut) { 692 pfxOut->Data = NULL; 693 pfxOut->Length = 0; 694 } 695 if(numArchives) { 696 *numArchives = 0; 697 } 698 if(archives_v1) { 699 *archives_v1 = NULL; 700 } 701 if(archives_v2) { 702 *archives_v2 = NULL; 703 } 704 /* 705 * gather arguments into CF form. 706 * NOTE: This implementation always does a Treadstone-style XMLRPC, regardless 707 * of what the caller (the app) is trying to do. If the app is doing 708 * a v1 (pre-Treadstone op), we use default/NULL values for params 709 * like serial number that we have to send to the server; in the case 710 * of a list op, we return one of two forms of archive (DotMacArchive 711 * or DotMacArchive_v2). 712 * NOTE WELL: we rely on caller to validate required inputs, it just works 713 * out a lot cleaner to verify there than here. 714 */ 715 CFURLRef url = createUrl(DOT_MAC_ARCHIVE_SCHEMA, hostName, DOT_MAC_ARCHIVE_PATH); 716 CFStringRef userStr = cDataToCfstr(userName); 717 CFStringRef pwdStr = cDataToCfstr(password); 718 CFStringRef pfxInStr = NULL; 719 if(pfxIn) { 720 pfxInStr = cDataToCfstr(*pfxIn); 721 } 722 CFStringRef archiveNameStr = NULL; 723 if(archiveName) { 724 archiveNameStr = cDataToCfstr(*archiveName); 725 } 726 CFStringRef timeStringStr = NULL; 727 if(timeString) { 728 timeStringStr = cDataToCfstr(*timeString); 729 } 730 731 uint8 zero = 0; 732 CSSM_DATA zeroData = {1, &zero}; 733 CFStringRef serialNumberStr = NULL; 734 CFStringRef certTypeStr = NULL; 735 if(archiveType == DMAT_Store) { 736 /* 737 * Need a serial number for this. If caller didn't provide one, 738 * make an empty one. 739 * WARNING this actually will result in a failure on the server side 740 * since the server requires the correct serial number. 741 */ 742 if(serialNumber == NULL) { 743 serialNumber = &zeroData; 744 } 745 serialNumberStr = cDataToCfAsciiStr(*serialNumber); 746 747 /* certTypeTag --> string */ 748 switch(certTypeTag) { 749 case CSSM_DOT_MAC_TYPE_ICHAT: 750 certTypeStr = kParamCertTypeIChat; 751 break; 752 case CSSM_DOT_MAC_TYPE_SHARED_SERVICES: 753 certTypeStr = kParamCertTypeSharedServices; 754 break; 755 case CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT: 756 certTypeStr = kParamCertTypeEmailEncryption; 757 break; 758 case CSSM_DOT_MAC_TYPE_EMAIL_SIGNING: 759 certTypeStr = kParamCertTypeEmailSigning; 760 break; 761 default: 762 dotMacErrorLog("dotMacPostArchiveReq: Bad cert type\n"); 763 return paramErr; 764 } 765 } 766 767 /* 768 * Now cook up arguments for an XMLRPC. 769 */ 770 CFMutableDictionaryRef argDict; 771 CFMutableArrayRef argOrder; 772 CFStringRef method; 773 argDict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, 774 &kCFTypeDictionaryValueCallBacks); 775 argOrder = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 776 777 switch(archiveType) { 778 case DMAT_List: 779 method = kMethodArchiveList; 780 /* no parameters */ 781 break; 782 case DMAT_Store: 783 method = kMethodArchiveSave; 784 /* parameters: name, pfx, certType, date, serialNumber */ 785 assert(archiveNameStr != NULL); 786 assert(timeStringStr != NULL); 787 assert(pfxInStr != NULL); 788 assert(serialNumberStr != NULL); 789 assert(certTypeStr != NULL); 790 CFDictionaryAddValue(argDict, kP1, archiveNameStr); 791 CFArrayAppendValue(argOrder, kP1); 792 CFDictionaryAddValue(argDict, kP2, pfxInStr); 793 CFArrayAppendValue(argOrder, kP2); 794 CFDictionaryAddValue(argDict, kP3, certTypeStr); 795 CFArrayAppendValue(argOrder, kP3); 796 CFDictionaryAddValue(argDict, kP4, timeStringStr); 797 CFArrayAppendValue(argOrder, kP4); 798 CFDictionaryAddValue(argDict, kP5, serialNumberStr); 799 CFArrayAppendValue(argOrder, kP5); 800 break; 801 case DMAT_Fetch: 802 method = kMethodArchiveFetch; 803 /* parameters: name */ 804 assert(archiveNameStr != NULL); 805 CFDictionaryAddValue(argDict, kP1, archiveNameStr); 806 CFArrayAppendValue(argOrder, kP1); 807 break; 808 case DMAT_Remove: 809 method = kMethodArchiveRemove; 810 /* parameters: name */ 811 assert(archiveNameStr != NULL); 812 CFDictionaryAddValue(argDict, kP1, archiveNameStr); 813 CFArrayAppendValue(argOrder, kP1); 814 break; 815 default: 816 return paramErr; 817 } 818 819 OSStatus ortn; 820 CFDictionaryRef resultDict = NULL; 821 uint32_t httpStat; 822 823 #if REQUEST_DICTIONARY_DEBUG 824 dumpDictionary("Arguments dictionary", argDict); 825 #endif 826 ortn = performAuthenticatedXMLRPC(url, method, argDict, argOrder, userStr, pwdStr, 827 &resultDict, &httpStat); 828 829 if(ortn) { 830 dotMacErrorLog("dotMacPostArchiveReq: XMLRPC returned %ld\n", ortn); 831 /* And we're dead - means RPC did not complete */ 832 goto errOut; 833 } 834 if(resultDict == NULL) { 835 dotMacErrorLog("dotMacPostArchiveReq: XMLRPC failed to return a dictionary\n"); 836 ortn = ioErr; 837 goto errOut; 838 } 839 #if RESULTS_DICTIONARY_DEBUG 840 dumpDictionary("Results dictionary", resultDict); 841 #endif 842 843 CFStringRef resultCode; 844 resultCode = (CFStringRef)CFDictionaryGetValue(resultDict, kResponseResultCode); 845 if((resultCode == NULL) || (CFGetTypeID(resultCode) != CFStringGetTypeID())) { 846 dotMacErrorLog("dotMacPostArchiveReq: resultCode is not a string\n"); 847 ortn = ioErr; 848 goto errOut; 849 } 850 851 /* no partial success on this one - it worked or it didn't */ 852 ortn = dotMacParseResult(resultCode); 853 if(ortn) { 854 goto errOut; 855 } 856 857 /* For some ops, we have some data to give to caller */ 858 switch(archiveType) { 859 case DMAT_List: 860 { 861 if(version == CSSM_DOT_MAC_TP_ARCHIVE_REQ_VERSION_v1) { 862 assert(archives_v1 != NULL); 863 ortn = dotMacReturnArchiveList_v1(resultDict, numArchives, archives_v1, alloc); 864 } 865 else { 866 assert(archives_v2 != NULL); 867 ortn = dotMacReturnArchiveList_v2(resultDict, numArchives, archives_v2, alloc); 868 } 869 break; 870 } 871 case DMAT_Store: 872 /* no returned data */ 873 break; 874 875 case DMAT_Fetch: 876 { 877 /* resultBody is a PKCS12 PFX */ 878 CFStringRef pfxStr; 879 pfxStr = (CFStringRef)CFDictionaryGetValue(resultDict, 880 kResponseResultBody); 881 if((pfxStr == NULL) ||(CFGetTypeID(pfxStr) != CFStringGetTypeID())) { 882 dotMacErrorLog("archive(fetch): resultBody is not a string\n"); 883 ortn = ioErr; 884 goto errOut; 885 } 886 assert(pfxOut != NULL); 887 cfstrToCdata(pfxStr, *pfxOut, alloc); 888 break; 889 } 890 case DMAT_Remove: 891 /* no returned data */ 892 break; 893 894 default: 895 return paramErr; 896 } 897 898errOut: 899 CFRELEASE(url); 900 CFRELEASE(userStr); 901 CFRELEASE(pwdStr); 902 CFRELEASE(pfxInStr); 903 CFRELEASE(archiveNameStr); 904 CFRELEASE(timeStringStr); 905 CFRELEASE(argDict); 906 CFRELEASE(argOrder); 907 CFRELEASE(resultDict); 908 909 return ortn; 910 911} 912 913/* 914 * Post "Is request pending?" request. 915 * Aside from gross network failures and so forth this returns one of two values: 916 * 917 * CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING -- a request is pending 918 * CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING -- *no* request pending 919 */ 920OSStatus dotMacPostReqPendingPing( 921 DotMacCertTypeTag certType, 922 const CSSM_DATA &userName, 923 const CSSM_DATA &password, 924 const CSSM_DATA &hostName) 925{ 926 /* 927 * First gather arguments into CF form. 928 */ 929 CFURLRef url = createUrl(DOT_MAC_SIGN_SCHEMA, hostName, DOT_MAC_SIGN_PATH); 930 CFStringRef userStr = cDataToCfstr(userName); 931 CFStringRef pwdStr = cDataToCfstr(password); 932 933 CFStringRef method; 934 935 switch(certType) { 936 case CSSM_DOT_MAC_TYPE_ICHAT: 937 method = kMethodStatusIChatEncrypt; 938 break; 939 case CSSM_DOT_MAC_TYPE_SHARED_SERVICES: 940 method = kMethodStatusSharedServices; 941 break; 942 case CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT: 943 method = kMethodStatusEmailEncryption; 944 break; 945 case CSSM_DOT_MAC_TYPE_EMAIL_SIGNING: 946 method = kMethodStatusEmailSigning; 947 break; 948 default: 949 return paramErr; 950 } 951 /* 952 * Cook up empty arguments dict for an XMLRPC. 953 */ 954 CFMutableDictionaryRef argDict = 955 CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, 956 &kCFTypeDictionaryValueCallBacks); 957 CFMutableArrayRef argOrder = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 958 959 OSStatus ortn; 960 CFDictionaryRef resultDict = NULL; 961 uint32_t httpStat; 962 963 ortn = performAuthenticatedXMLRPC(url, method, argDict, argOrder, 964 userStr, pwdStr, &resultDict, &httpStat); 965 966 if(ortn) { 967 dotMacErrorLog("dotMacPostReqPendingPing: XMLRPC returned %ld\n", ortn); 968 /* And we're dead - means RPC did not complete */ 969 goto errOut; 970 } 971 if(resultDict == NULL) { 972 dotMacErrorLog("dotMacPostReqPendingPing: XMLRPC failed to return a dictionary\n"); 973 ortn = ioErr; 974 goto errOut; 975 } 976 #if RESULTS_DICTIONARY_DEBUG 977 dumpDictionary("Results dictionary", resultDict); 978 #endif 979 980 CFStringRef resultCode; 981 resultCode = (CFStringRef)CFDictionaryGetValue(resultDict, kResponseResultCode); 982 if((resultCode == NULL) || (CFGetTypeID(resultCode) != CFStringGetTypeID())) { 983 dotMacErrorLog("dotMacPostCertReq: resultCode is not a string\n"); 984 ortn = ioErr; 985 goto errOut; 986 } 987 ortn = dotMacParseResult(resultCode); 988 /* should not return success */ 989 dotMacDebug("dotMacPostReqPendingPing: ortn %lu", ortn); 990 991errOut: 992 CFRELEASE(url); 993 CFRELEASE(userStr); 994 CFRELEASE(pwdStr); 995 CFRELEASE(argDict); 996 CFRELEASE(argOrder); 997 CFRELEASE(resultDict); 998 return ortn; 999} 1000