1/* 2 * Copyright (c) 2002-2004,2011-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// CertificateRequest.cpp 26// 27#include <security_keychain/CertificateRequest.h> 28#include <Security/oidsalg.h> 29#include <Security/SecKey.h> 30#include <Security/SecKeyPriv.h> 31#include <Security/cssmapi.h> 32#include <string.h> 33#include <dotMacTp.h> 34#include <Security/oidsattr.h> 35#include <security_utilities/simpleprefs.h> 36#include <SecBase.h> 37 38/* one top-level prefs file for all of .mac cert requests */ 39#define DOT_MAC_REQ_PREFS "com.apple.security.certreq" 40 41/* 42 * Within that dictionary is a set of per-policy dictionaries; the key in the 43 * top-level prefs for these dictionaries is the raw policy OID data encoded 44 * as an ASCII string. 45 * 46 * Within one per-policy dictionary exists a number of per-user dictionaries, 47 * with the username key as a string. Note that this user name, the one passed to the 48 * .mac server, does NOT have to have any relation to the current Unix user name; one 49 * Unix user can have multiple .mac accounts. 50 * 51 * 52 * Within the per-policy, per user dictionary are these two values, both stored 53 * as raw data (CFData) blobs. 54 */ 55#define DOT_MAC_REF_ID_KEY "refId" 56#define DOT_MAC_CERT_KEY "certificate" 57 58/* Domain for .mac cert requests */ 59#define DOT_MAC_DOMAIN_KEY "domain" 60#define DOT_MAC_DOMAIN "mac.com" 61 62/* Hosts for .mac cert requests */ 63#define DOT_MAC_MGMT_HOST "certmgmt" 64#define DOT_MAC_INFO_HOST "certinfo" 65 66/* 67 * Compare two CSSM_DATAs (or two CSSM_OIDs), return true if identical. 68 */ 69static 70bool nssCompareCssmData( 71 const CSSM_DATA *data1, 72 const CSSM_DATA *data2) 73{ 74 if((data1 == NULL) || (data1->Data == NULL) || 75 (data2 == NULL) || (data2->Data == NULL) || 76 (data1->Length != data2->Length)) { 77 return false; 78 } 79 if(data1->Length != data2->Length) { 80 return false; 81 } 82 if(memcmp(data1->Data, data2->Data, data1->Length) == 0) { 83 return true; 84 } 85 else { 86 return false; 87 } 88} 89 90/* any nonzero value means true */ 91static bool attrBoolValue( 92 const SecCertificateRequestAttribute *attr) 93{ 94 if((attr->value.Data != NULL) && 95 (attr->value.Length != 0) && 96 (attr->value.Data[0] != 0)) { 97 return true; 98 } 99 else { 100 return false; 101 } 102} 103 104static void tokenizeName( 105 const CSSM_DATA *inName, /* required */ 106 CSSM_DATA *outName, /* required */ 107 CSSM_DATA *outDomain) /* optional */ 108{ 109 if (!inName || !outName) return; 110 CSSM_SIZE idx = 0; 111 CSSM_SIZE stopIdx = inName->Length; 112 uint8 *p = inName->Data; 113 *outName = *inName; 114 if (outDomain) { 115 outDomain->Length = idx; 116 outDomain->Data = p; 117 } 118 if (!p) return; 119 while (idx < stopIdx) { 120 if (*p++ == '@') { 121 outName->Length = idx; 122 if (outDomain) { 123 outDomain->Length = inName->Length - (idx + 1); 124 outDomain->Data = p; 125 } 126 break; 127 } 128 idx++; 129 } 130} 131 132using namespace KeychainCore; 133 134CertificateRequest::CertificateRequest(const CSSM_OID &policy, 135 CSSM_CERT_TYPE certificateType, 136 CSSM_TP_AUTHORITY_REQUEST_TYPE requestType, 137 SecKeyRef privateKeyItemRef, 138 SecKeyRef publicKeyItemRef, 139 const SecCertificateRequestAttributeList *attributeList, 140 bool isNew /* = true */) 141 : mAlloc(Allocator::standard()), 142 mTP(gGuidAppleDotMacTP), 143 mCL(gGuidAppleX509CL), 144 mPolicy(mAlloc, policy.Data, policy.Length), 145 mCertType(certificateType), 146 mReqType(requestType), 147 mPrivKey(NULL), 148 mPubKey(NULL), 149 mEstTime(0), 150 mRefId(mAlloc), 151 mCertState(isNew ? CRS_New : CRS_Reconstructed), 152 mCertData(mAlloc), 153 mUserName(mAlloc), 154 mPassword(mAlloc), 155 mHostName(mAlloc), 156 mDomain(mAlloc), 157 mDoRenew(false), 158 mIsAsync(false), 159 mMutex(Mutex::recursive) 160{ 161 StLock<Mutex>_(mMutex); 162 certReqDbg("CertificateRequest construct"); 163 164 /* Validate policy OID. */ 165 if(!(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) || 166 nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) || 167 nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) || 168 nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy))) { 169 certReqDbg("CertificateRequest(): unknown policy oid"); 170 MacOSError::throwMe(errSecParam); 171 } 172 if(privateKeyItemRef) { 173 mPrivKey = privateKeyItemRef; 174 CFRetain(mPrivKey); 175 } 176 if(publicKeyItemRef) { 177 mPubKey = publicKeyItemRef; 178 CFRetain(mPubKey); 179 } 180 181 /* parse attr array */ 182 if(attributeList == NULL) { 183 return; 184 } 185 186 bool doPendingRequest = false; 187 for(unsigned dex=0; dex<attributeList->count; dex++) { 188 const SecCertificateRequestAttribute *attr = &attributeList->attr[dex]; 189 190 if((attr->oid.Data == NULL) || (attr->value.Data == NULL)) { 191 MacOSError::throwMe(errSecParam); 192 } 193 if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_USERNAME, &attr->oid)) { 194 CSSM_DATA userName = { 0, NULL }; 195 CSSM_DATA domainName = { 0, NULL }; 196 tokenizeName(&attr->value, &userName, &domainName); 197 if (!domainName.Length || !domainName.Data) { 198 domainName.Length = strlen(DOT_MAC_DOMAIN); 199 domainName.Data = (uint8*) DOT_MAC_DOMAIN; 200 } 201 mUserName.copy(userName); 202 mDomain.copy(domainName); 203 } 204 else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_PASSWORD, &attr->oid)) { 205 mPassword.copy(attr->value); 206 } 207 else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_HOSTNAME, &attr->oid)) { 208 mHostName.copy(attr->value); 209 } 210 else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_RENEW, &attr->oid)) { 211 /* 212 * any nonzero value means true 213 * FIXME: this is deprecated, Treadstone doesn't allow this. Reject this 214 * request? Ignore? 215 */ 216 mDoRenew = attrBoolValue(attr); 217 } 218 else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_ASYNC, &attr->oid)) { 219 /* any nonzero value means true */ 220 mIsAsync = attrBoolValue(attr); 221 } 222 else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_IS_PENDING, &attr->oid)) { 223 /* any nonzero value means true */ 224 doPendingRequest = attrBoolValue(attr); 225 } 226 227 else { 228 certReqDbg("CertificateRequest(): unknown name/value oid"); 229 MacOSError::throwMe(errSecParam); 230 } 231 } 232 if(mCertState == CRS_Reconstructed) { 233 /* see if we have a refId or maybe even a cert in prefs */ 234 retrieveResults(); 235 if(mCertData.data() != NULL) { 236 mCertState = CRS_HaveCert; 237 } 238 else if(mRefId.data() != NULL) { 239 mCertState = CRS_HaveRefId; 240 } 241 else if(doPendingRequest) { 242 /* ask the server if there's a request pending */ 243 postPendingRequest(); 244 /* NOT REACHED - that always throws */ 245 } 246 else { 247 certReqDbg("CertificateRequest(): nothing in prefs"); 248 /* Nothing found in prefs; nothing to go by */ 249 MacOSError::throwMe(errSecItemNotFound); 250 } 251 } 252} 253 254CertificateRequest::~CertificateRequest() throw() 255{ 256 StLock<Mutex>_(mMutex); 257 certReqDbg("CertificateRequest destruct"); 258 259 if(mPrivKey) { 260 CFRelease(mPrivKey); 261 } 262 if(mPubKey) { 263 CFRelease(mPubKey); 264 } 265} 266 267#pragma mark ----- cert request submit ----- 268 269void CertificateRequest::submit( 270 sint32 *estimatedTime) 271{ 272 StLock<Mutex>_(mMutex); 273 CSSM_DATA &policy = mPolicy.get(); 274 if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) || 275 nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) || 276 nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) || 277 nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy)) { 278 return submitDotMac(estimatedTime); 279 } 280 else { 281 /* shouldn't be here, we already validated policy in constructor */ 282 assert(0); 283 certReqDbg("CertificateRequest::submit(): bad policy"); 284 MacOSError::throwMe(errSecParam); 285 } 286} 287 288void CertificateRequest::submitDotMac( 289 sint32 *estimatedTime) 290{ 291 StLock<Mutex>_(mMutex); 292 CSSM_RETURN crtn; 293 CSSM_TP_AUTHORITY_ID tpAuthority; 294 CSSM_TP_AUTHORITY_ID *tpAuthPtr = NULL; 295 CSSM_NET_ADDRESS tpNetAddrs; 296 CSSM_APPLE_DOTMAC_TP_CERT_REQUEST certReq; 297 CSSM_TP_REQUEST_SET reqSet; 298 CSSM_CSP_HANDLE cspHand = 0; 299 CSSM_X509_TYPE_VALUE_PAIR tvp; 300 CSSM_TP_CALLERAUTH_CONTEXT callerAuth; 301 CSSM_FIELD policyField; 302 CSSM_DATA refId = {0, NULL}; 303 const CSSM_KEY *privKey; 304 const CSSM_KEY *pubKey; 305 OSStatus ortn; 306 307 if(mCertState != CRS_New) { 308 certReqDbg("CertificateRequest: can only submit a new request"); 309 MacOSError::throwMe(errSecParam); 310 } 311 if((mUserName.data() == NULL) || (mPassword.data() == NULL)) { 312 certReqDbg("CertificateRequest: user name and password required"); 313 MacOSError::throwMe(errSecParam); 314 } 315 316 /* get keys and CSP handle in CSSM terms */ 317 if((mPrivKey == NULL) || (mPubKey == NULL)) { 318 certReqDbg("CertificateRequest: pub and priv keys required"); 319 MacOSError::throwMe(errSecParam); 320 } 321 ortn = SecKeyGetCSSMKey(mPrivKey, &privKey); 322 if(ortn) { 323 MacOSError::throwMe(ortn); 324 } 325 ortn = SecKeyGetCSSMKey(mPubKey, &pubKey); 326 if(ortn) { 327 MacOSError::throwMe(ortn); 328 } 329 ortn = SecKeyGetCSPHandle(mPrivKey, &cspHand); 330 if(ortn) { 331 MacOSError::throwMe(ortn); 332 } 333 334 /* 335 * CSSM_X509_TYPE_VALUE_PAIR_PTR - one pair for now. 336 * Caller passes in user name like "johnsmith"; in the CSR, 337 * we write "johnsmith@mac.com". 338 */ 339 tvp.type = CSSMOID_CommonName; 340 tvp.valueType = BER_TAG_PKIX_UTF8_STRING; 341 CssmAutoData fullUserName(mAlloc); 342 size_t nameLen = mUserName.length(); 343 size_t domainLen = mDomain.length(); 344 fullUserName.malloc(nameLen + 1 + domainLen); 345 tvp.value = fullUserName.get(); 346 memmove(tvp.value.Data, mUserName.data(), nameLen); 347 memmove(tvp.value.Data + nameLen, "@", 1); 348 memmove(tvp.value.Data + nameLen + 1, mDomain.data(), domainLen); 349 350 /* Fill in the CSSM_APPLE_DOTMAC_TP_CERT_REQUEST */ 351 memset(&certReq, 0, sizeof(certReq)); 352 certReq.version = CSSM_DOT_MAC_TP_REQ_VERSION; 353 certReq.cspHand = cspHand; 354 certReq.clHand = mCL->handle(); 355 certReq.numTypeValuePairs = 1; 356 certReq.typeValuePairs = &tvp; 357 certReq.publicKey = const_cast<CSSM_KEY_PTR>(pubKey); 358 certReq.privateKey = const_cast<CSSM_KEY_PTR>(privKey); 359 certReq.userName = mUserName.get(); 360 certReq.password = mPassword.get(); 361 if(mDoRenew) { 362 certReq.flags |= CSSM_DOTMAC_TP_SIGN_RENEW; 363 } 364 /* we don't deal with CSR here, input or output */ 365 366 /* now the rest of the args for CSSM_TP_SubmitCredRequest() */ 367 reqSet.Requests = &certReq; 368 reqSet.NumberOfRequests = 1; 369 policyField.FieldOid = mPolicy; 370 policyField.FieldValue.Data = NULL; 371 policyField.FieldValue.Length = 0; 372 memset(&callerAuth, 0, sizeof(callerAuth)); 373 callerAuth.Policy.NumberOfPolicyIds = 1; 374 callerAuth.Policy.PolicyIds = &policyField; 375 ortn = SecKeyGetCredentials(mPrivKey, 376 CSSM_ACL_AUTHORIZATION_SIGN, 377 kSecCredentialTypeDefault, 378 const_cast<const CSSM_ACCESS_CREDENTIALS **>(&callerAuth.CallerCredentials)); 379 if(ortn) { 380 certReqDbg("CertificateRequest: SecKeyGetCredentials error"); 381 MacOSError::throwMe(ortn); 382 } 383 384 CssmAutoData hostName(mAlloc); 385 tpAuthority.AuthorityCert = NULL; 386 tpAuthority.AuthorityLocation = &tpNetAddrs; 387 tpNetAddrs.AddressType = CSSM_ADDR_NAME; 388 if(mHostName.data() != NULL) { 389 tpNetAddrs.Address = mHostName.get(); 390 } else { 391 unsigned hostLen = strlen(DOT_MAC_MGMT_HOST); 392 hostName.malloc(hostLen + 1 + domainLen); 393 tpNetAddrs.Address = hostName.get(); 394 memmove(tpNetAddrs.Address.Data, DOT_MAC_MGMT_HOST, hostLen); 395 memmove(tpNetAddrs.Address.Data + hostLen, ".", 1); 396 memmove(tpNetAddrs.Address.Data + hostLen + 1, mDomain.data(), domainLen); 397 } 398 tpAuthPtr = &tpAuthority; 399 400 /* go */ 401 crtn = CSSM_TP_SubmitCredRequest(mTP->handle(), 402 tpAuthPtr, 403 CSSM_TP_AUTHORITY_REQUEST_CERTISSUE, 404 &reqSet, 405 &callerAuth, 406 &mEstTime, 407 &refId); // CSSM_DATA_PTR ReferenceIdentifier 408 409 /* handle return, store results */ 410 switch(crtn) { 411 case CSSM_OK: 412 /* refID is a cert, we have to store it in prefs for later retrieval. */ 413 certReqDbg("submitDotMac: full success, storing cert"); 414 if(!mIsAsync) { 415 /* store in prefs if not running in async mode */ 416 ortn = storeResults(NULL, &refId); 417 if(ortn) { 418 crtn = ortn; 419 } 420 } 421 /* but keep a local copy too */ 422 mCertData.copy(refId); 423 mCertState = CRS_HaveCert; 424 if(estimatedTime) { 425 /* it's ready right now */ 426 *estimatedTime = 0; 427 } 428 break; 429 430 case CSSMERR_APPLE_DOTMAC_REQ_QUEUED: 431 /* refID is the blob we use in CSSM_TP_RetrieveCredResult() */ 432 certReqDbg("submitDotMac: queued, storing refId"); 433 mRefId.copy(refId); 434 /* return success - this crtn is not visible at API */ 435 crtn = CSSM_OK; 436 if(!mIsAsync) { 437 /* store in prefs if not running in async mode */ 438 ortn = storeResults(&refId, NULL); 439 if(ortn) { 440 crtn = ortn; 441 } 442 } 443 mCertState = CRS_HaveRefId; 444 if(estimatedTime) { 445 *estimatedTime = mEstTime; 446 } 447 break; 448 449 case CSSMERR_APPLE_DOTMAC_REQ_REDIRECT: 450 /* refID is a URL, caller obtains via getReturnData() */ 451 certReqDbg("submitDotMac: redirect"); 452 mRefId.copy(refId); 453 mCertState = CRS_HaveOtherData; 454 break; 455 456 default: 457 /* all others are fatal errors, thrown below */ 458 break; 459 } 460 if(refId.Data) { 461 /* mallocd on our behalf by TP */ 462 free(refId.Data); 463 } 464 if(crtn) { 465 CssmError::throwMe(crtn); 466 } 467} 468 469#pragma mark ----- cert request get result ----- 470 471void CertificateRequest::getResult( 472 sint32 *estimatedTime, // optional 473 CssmData &certData) 474{ 475 StLock<Mutex>_(mMutex); 476 CSSM_DATA &policy = mPolicy.get(); 477 if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) || 478 nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) || 479 nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) || 480 nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy)) { 481 return getResultDotMac(estimatedTime, certData); 482 } 483 else { 484 /* shouldn't be here, we already validated policy in constructor */ 485 assert(0); 486 certReqDbg("CertificateRequest::getResult(): bad policy"); 487 MacOSError::throwMe(errSecParam); 488 } 489} 490 491void CertificateRequest::getResultDotMac( 492 sint32 *estimatedTime, // optional 493 CssmData &certData) 494{ 495 StLock<Mutex>_(mMutex); 496 switch(mCertState) { 497 case CRS_HaveCert: 498 /* trivial case, we already have what caller is looking for */ 499 certReqDbg("getResultDotMac: have the cert right now"); 500 assert(mCertData.data() != NULL); 501 certData = mCertData.get(); 502 if(estimatedTime) { 503 *estimatedTime = 0; 504 } 505 break; 506 case CRS_HaveRefId: 507 { 508 /* ping the server */ 509 certReqDbg("getResultDotMac: CRS_HaveRefId; polling server"); 510 assert(mRefId.data() != NULL); 511 CSSM_BOOL ConfirmationRequired; 512 CSSM_TP_RESULT_SET_PTR resultSet = NULL; 513 CSSM_RETURN crtn; 514 515 crtn = CSSM_TP_RetrieveCredResult(mTP->handle(), 516 &mRefId.get(), 517 NULL, // CallerAuthCredentials 518 &mEstTime, 519 &ConfirmationRequired, 520 &resultSet); 521 switch(crtn) { 522 case CSSM_OK: 523 break; 524 case CSSMERR_TP_CERT_NOT_VALID_YET: 525 /* 526 * By convention, this means "not ready yet". 527 * The dot mac server does not have a way of telling us the 528 * estimated time on a straight lookup like this (we only get 529 * an estimated completion time on the initial request), so we 530 * fake it. 531 */ 532 certReqDbg("getResultDotMac: polled server, not ready yet"); 533 if(estimatedTime) { 534 *estimatedTime = (mEstTime) ? mEstTime : 1; 535 } 536 MacOSError::throwMe(CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING); 537 default: 538 certReqDbg("CSSM_TP_RetrieveCredResult error"); 539 CssmError::throwMe(crtn); 540 } 541 if(resultSet == NULL) { 542 certReqDbg("***CSSM_TP_RetrieveCredResult OK, but no result set"); 543 MacOSError::throwMe(errSecInternalComponent); 544 } 545 if(resultSet->NumberOfResults != 1) { 546 certReqDbg("***CSSM_TP_RetrieveCredResult OK, NumberOfResults (%lu)", 547 (unsigned long)resultSet->NumberOfResults); 548 MacOSError::throwMe(errSecInternalComponent); 549 } 550 if(resultSet->Results == NULL) { 551 certReqDbg("***CSSM_TP_RetrieveCredResult OK, but empty result set"); 552 MacOSError::throwMe(errSecInternalComponent); 553 } 554 certReqDbg("getResultDotMac: polled server, SUCCESS"); 555 CSSM_DATA_PTR result = (CSSM_DATA_PTR)resultSet->Results; 556 if(result->Data == NULL) { 557 certReqDbg("***CSSM_TP_RetrieveCredResult OK, but empty result"); 558 MacOSError::throwMe(errSecInternalComponent); 559 } 560 mCertData.copy(*result); 561 certData = mCertData.get(); 562 mCertState = CRS_HaveCert; 563 if(estimatedTime) { 564 *estimatedTime = 0; 565 } 566 567 /* 568 * Free the stuff allocated on our behalf by TP. 569 * FIXME - are we sure CssmClient is using alloc, free, etc.? 570 */ 571 free(result->Data); 572 free(result); 573 free(resultSet); 574 break; 575 } 576 default: 577 /* what do we do with this? */ 578 certReqDbg("CertificateRequest::getResultDotMac(): bad state"); 579 MacOSError::throwMe(errSecInternalComponent); 580 } 581 582 /* 583 * One more thing: once we pass a cert back to caller, we erase 584 * the record of this transaction from prefs. 585 */ 586 assert(mCertData.data() != NULL); 587 assert(mCertData.data() == certData.Data); 588 removeResults(); 589} 590 591/* 592 * Obtain policy/error specific return data blob. We own the data, it's 593 * not copied. 594 */ 595void CertificateRequest::getReturnData( 596 CssmData &rtnData) 597{ 598 StLock<Mutex>_(mMutex); 599 rtnData = mRefId.get(); 600} 601 602#pragma mark ----- preferences support ----- 603 604/* Current user as CFString, for use as key in per-policy dictionary */ 605CFStringRef CertificateRequest::createUserKey() 606{ 607 StLock<Mutex>_(mMutex); 608 return CFStringCreateWithBytes(NULL, (UInt8 *)mUserName.data(), mUserName.length(), 609 kCFStringEncodingUTF8, false); 610} 611 612#define MAX_OID_LEN 2048 // way big... */ 613 614/* current policy as CFString, for use as key in prefs dictionary */ 615CFStringRef CertificateRequest::createPolicyKey() 616{ 617 StLock<Mutex>_(mMutex); 618 char oidstr[MAX_OID_LEN]; 619 unsigned char *inp = (unsigned char *)mPolicy.data(); 620 char *outp = oidstr; 621 CFIndex len = mPolicy.length(); 622 for(CFIndex dex=0; dex<len; dex++) { 623 sprintf(outp, "%02X ", *inp++); 624 outp += 3; 625 } 626 return CFStringCreateWithBytes(NULL, (UInt8 *)oidstr, len * 3, 627 kCFStringEncodingUTF8, false); 628} 629 630/* 631 * Store cert data or refId in prefs. If both are NULL, delete the 632 * user dictionary entry from the policy dictionary if there, and then 633 * delete the policy dictionary if it's empty. 634 */ 635OSStatus CertificateRequest::storeResults( 636 const CSSM_DATA *refId, // optional, for queued requests 637 const CSSM_DATA *certData) // optional, for immediate completion 638{ 639 StLock<Mutex>_(mMutex); 640 assert(mPolicy.data() != NULL); 641 assert(mUserName.data() != NULL); 642 assert(mDomain.data() != NULL); 643 644 bool deleteEntry = ((refId == NULL) && (certData == NULL)); 645 646 /* get a mutable copy of the existing prefs, or a fresh empty one */ 647 MutableDictionary *prefsDict = MutableDictionary::CreateMutableDictionary(DOT_MAC_REQ_PREFS, Dictionary::US_User); 648 if (prefsDict == NULL) 649 { 650 prefsDict = new MutableDictionary(); 651 } 652 653 /* get a mutable copy of the dictionary for this policy, or a fresh empty one */ 654 CFStringRef policyKey = createPolicyKey(); 655 MutableDictionary *policyDict = prefsDict->copyMutableDictValue(policyKey); 656 657 CFStringRef userKey = createUserKey(); 658 if(deleteEntry) { 659 /* remove user dictionary from this policy dictionary */ 660 policyDict->removeValue(userKey); 661 } 662 else { 663 /* get a mutable copy of the dictionary for this user, or a fresh empty one */ 664 MutableDictionary *userDict = policyDict->copyMutableDictValue(userKey); 665 666 CFStringRef domainKey = CFStringCreateWithBytes(NULL, (UInt8 *)mDomain.data(), mDomain.length(), kCFStringEncodingUTF8, false); 667 userDict->setValue(CFSTR(DOT_MAC_DOMAIN_KEY), domainKey); 668 CFRelease(domainKey); 669 670 /* write refId and/or cert --> user dictionary */ 671 if(refId) { 672 userDict->setDataValue(CFSTR(DOT_MAC_REF_ID_KEY), refId->Data, refId->Length); 673 } 674 if(certData) { 675 userDict->setDataValue(CFSTR(DOT_MAC_CERT_KEY), certData->Data, certData->Length); 676 } 677 678 /* new user dictionary --> policy dictionary */ 679 policyDict->setValue(userKey, userDict->dict()); 680 delete userDict; 681 } 682 CFRelease(userKey); 683 684 /* new policy dictionary to prefs dictionary, or nuke it */ 685 if(policyDict->count() == 0) { 686 prefsDict->removeValue(policyKey); 687 } 688 else { 689 prefsDict->setValue(policyKey, policyDict->dict()); 690 } 691 CFRelease(policyKey); 692 delete policyDict; 693 694 /* prefs --> disk */ 695 OSStatus ortn = errSecSuccess; 696 if(!prefsDict->writePlistToPrefs(DOT_MAC_REQ_PREFS, Dictionary::US_User)) { 697 certReqDbg("storeResults: error writing prefs to disk"); 698 ortn = errSecIO; 699 } 700 delete prefsDict; 701 return ortn; 702} 703 704/* 705 * Attempt to fetch mCertData or mRefId from preferences. 706 */ 707void CertificateRequest::retrieveResults() 708{ 709 StLock<Mutex>_(mMutex); 710 assert(mPolicy.data() != NULL); 711 assert(mUserName.data() != NULL); 712 713 /* get the .mac cert prefs as a dictionary */ 714 Dictionary *pd = Dictionary::CreateDictionary(DOT_MAC_REQ_PREFS, Dictionary::US_User); 715 if (pd == NULL) 716 { 717 certReqDbg("retrieveResults: no prefs found"); 718 return; 719 } 720 721 auto_ptr<Dictionary> prefsDict(pd); 722 723 /* get dictionary for current policy */ 724 CFStringRef policyKey = createPolicyKey(); 725 Dictionary *policyDict = prefsDict->copyDictValue(policyKey); 726 CFRelease(policyKey); 727 if(policyDict != NULL) { 728 /* dictionary for user */ 729 CFStringRef userKey = createUserKey(); 730 Dictionary *userDict = policyDict->copyDictValue(userKey); 731 if(userDict != NULL) { 732 /* is there a cert in there? */ 733 CFDataRef val = userDict->getDataValue(CFSTR(DOT_MAC_CERT_KEY)); 734 if(val) { 735 mCertData.copy(CFDataGetBytePtr(val), CFDataGetLength(val)); 736 } 737 738 /* how about refId? */ 739 val = userDict->getDataValue(CFSTR(DOT_MAC_REF_ID_KEY)); 740 if(val) { 741 mRefId.copy(CFDataGetBytePtr(val), CFDataGetLength(val)); 742 } 743 delete userDict; 744 } 745 CFRelease(userKey); 746 delete policyDict; 747 } 748} 749 750/* 751 * Remove all trace of current policy/user. Called when we successfully transferred 752 * the cert back to caller. 753 */ 754void CertificateRequest::removeResults() 755{ 756 StLock<Mutex>_(mMutex); 757 assert(mPolicy.data() != NULL); 758 assert(mUserName.data() != NULL); 759 storeResults(NULL, NULL); 760} 761 762/* 763 * Have the TP ping the server to see of there's a request pending for the current 764 * user. Always throws: either 765 * CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING -- request pending 766 * CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING -- no request pending 767 * errSecParam -- no user, no password 768 * other gross errors, e.g. errSecIO for server connection failure 769 * 770 * The distinguishing features about this TP request are: 771 * 772 * policy OID = CSSMOID_DOTMAC_CERT_REQ_{IDENTITY,EMAIL_SIGN,EMAIL_ENCRYPT,SHARED_SERVICES} 773 * CSSM_TP_AUTHORITY_REQUEST_TYPE = CSSM_TP_AUTHORITY_REQUEST_CERTLOOKUP 774 * CSSM_APPLE_DOTMAC_TP_CERT_REQUEST.flags = CSSM_DOTMAC_TP_IS_REQ_PENDING 775 * must have userName and password 776 * hostname optional as usual 777 */ 778void CertificateRequest::postPendingRequest() 779{ 780 StLock<Mutex>_(mMutex); 781 CSSM_RETURN crtn; 782 CSSM_TP_AUTHORITY_ID tpAuthority; 783 CSSM_TP_AUTHORITY_ID *tpAuthPtr = NULL; 784 CSSM_NET_ADDRESS tpNetAddrs; 785 CSSM_APPLE_DOTMAC_TP_CERT_REQUEST certReq; 786 CSSM_TP_REQUEST_SET reqSet; 787 CSSM_TP_CALLERAUTH_CONTEXT callerAuth; 788 CSSM_FIELD policyField; 789 CSSM_DATA refId = {0, NULL}; 790 791 assert(mCertState == CRS_Reconstructed); 792 if((mUserName.data() == NULL) || (mPassword.data() == NULL)) { 793 certReqDbg("postPendingRequest: user name and password required"); 794 MacOSError::throwMe(errSecParam); 795 } 796 797 /* Fill in the CSSM_APPLE_DOTMAC_TP_CERT_REQUEST */ 798 memset(&certReq, 0, sizeof(certReq)); 799 certReq.version = CSSM_DOT_MAC_TP_REQ_VERSION; 800 certReq.userName = mUserName.get(); 801 certReq.password = mPassword.get(); 802 certReq.flags = CSSM_DOTMAC_TP_IS_REQ_PENDING; 803 804 /* now the rest of the args for CSSM_TP_SubmitCredRequest() */ 805 reqSet.Requests = &certReq; 806 reqSet.NumberOfRequests = 1; 807 /* 808 * This OID actually doesn't matter - right? This RPC doesn't know about 809 * which request we seek... 810 */ 811 policyField.FieldOid = mPolicy; 812 policyField.FieldValue.Data = NULL; 813 policyField.FieldValue.Length = 0; 814 memset(&callerAuth, 0, sizeof(callerAuth)); 815 callerAuth.Policy.NumberOfPolicyIds = 1; 816 callerAuth.Policy.PolicyIds = &policyField; 817 /* no other creds here */ 818 819 if(mHostName.data() != NULL) { 820 tpAuthority.AuthorityCert = NULL; 821 tpAuthority.AuthorityLocation = &tpNetAddrs; 822 tpNetAddrs.AddressType = CSSM_ADDR_NAME; 823 tpNetAddrs.Address = mHostName.get(); 824 tpAuthPtr = &tpAuthority; 825 } 826 827 /* go */ 828 crtn = CSSM_TP_SubmitCredRequest(mTP->handle(), 829 tpAuthPtr, 830 CSSM_TP_AUTHORITY_REQUEST_CERTLOOKUP, 831 &reqSet, 832 &callerAuth, 833 &mEstTime, 834 &refId); // CSSM_DATA_PTR ReferenceIdentifier 835 836 if(refId.Data) { 837 /* shouldn't be any but just in case.... */ 838 free(refId.Data); 839 } 840 switch(crtn) { 841 case CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING: 842 certReqDbg("postPendingRequest: REQ_IS_PENDING"); 843 break; 844 case CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING: 845 certReqDbg("postPendingRequest: NO_REQ_PENDING"); 846 break; 847 case CSSM_OK: 848 /* should never happen */ 849 certReqDbg("postPendingRequest: unexpected success!"); 850 crtn = errSecInternalComponent; 851 break; 852 default: 853 certReqDbg("postPendingRequest: unexpected rtn %lu", (unsigned long)crtn); 854 break; 855 } 856 CssmError::throwMe(crtn); 857} 858 859