Keychains.cpp
1 /* 2 * Copyright (c) 2000-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 // 26 // Keychains.cpp 27 // 28 29 #include "KCEventNotifier.h" 30 #include "Keychains.h" 31 32 #include "Item.h" 33 #include "KCCursor.h" 34 #include "Globals.h" 35 #include <security_cdsa_utilities/Schema.h> 36 #include <security_cdsa_client/keychainacl.h> 37 #include <security_cdsa_utilities/cssmacl.h> 38 #include <security_cdsa_utilities/cssmdb.h> 39 #include <security_utilities/trackingallocator.h> 40 #include <security_utilities/FileLockTransaction.h> 41 #include <security_keychain/SecCFTypes.h> 42 #include <securityd_client/ssblob.h> 43 #include <Security/TrustSettingsSchema.h> 44 45 #include "SecKeychainPriv.h" 46 47 #include <Security/SecKeychainItemPriv.h> 48 #include <CoreFoundation/CoreFoundation.h> 49 #include "DLDBListCFPref.h" 50 #include <fcntl.h> 51 #include <glob.h> 52 #include <sys/param.h> 53 #include <syslog.h> 54 #include <sys/stat.h> 55 #include <sys/socket.h> 56 #include <sys/un.h> 57 #include <sys/types.h> 58 #include <sys/time.h> 59 60 static dispatch_once_t SecKeychainSystemKeychainChecked; 61 62 OSStatus SecKeychainSystemKeychainCheckWouldDeadlock() 63 { 64 dispatch_once(&SecKeychainSystemKeychainChecked, ^{}); 65 return errSecSuccess; 66 } 67 68 using namespace KeychainCore; 69 using namespace CssmClient; 70 71 72 typedef struct EventItem 73 { 74 SecKeychainEvent kcEvent; 75 Item item; 76 } EventItem; 77 78 typedef std::list<EventItem> EventBufferSuper; 79 class EventBuffer : public EventBufferSuper 80 { 81 public: 82 EventBuffer () {} 83 virtual ~EventBuffer (); 84 }; 85 86 87 EventBuffer::~EventBuffer () 88 { 89 } 90 91 92 93 // 94 // KeychainSchemaImpl 95 // 96 KeychainSchemaImpl::KeychainSchemaImpl(const Db &db) : mMutex(Mutex::recursive) 97 { 98 DbCursor relations(db); 99 relations->recordType(CSSM_DL_DB_SCHEMA_INFO); 100 DbAttributes relationRecord(db, 1); 101 relationRecord.add(Schema::RelationID); 102 DbUniqueRecord outerUniqueId(db); 103 104 while (relations->next(&relationRecord, NULL, outerUniqueId)) 105 { 106 DbUniqueRecord uniqueId(db); 107 108 uint32 relationID = relationRecord.at(0); 109 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID 110 && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END) 111 continue; 112 113 // Create a cursor on the SCHEMA_ATTRIBUTES table for records with 114 // RelationID == relationID 115 DbCursor attributes(db); 116 attributes->recordType(CSSM_DL_DB_SCHEMA_ATTRIBUTES); 117 attributes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID); 118 119 // Set up a record for retriving the SCHEMA_ATTRIBUTES 120 DbAttributes attributeRecord(db, 2); 121 attributeRecord.add(Schema::AttributeFormat); 122 attributeRecord.add(Schema::AttributeID); 123 124 RelationInfoMap &rim = mDatabaseInfoMap[relationID]; 125 while (attributes->next(&attributeRecord, NULL, uniqueId)) 126 rim[attributeRecord.at(1)] = attributeRecord.at(0); 127 128 // Create a cursor on the CSSM_DL_DB_SCHEMA_INDEXES table for records 129 // with RelationID == relationID 130 DbCursor indexes(db); 131 indexes->recordType(CSSM_DL_DB_SCHEMA_INDEXES); 132 indexes->conjunctive(CSSM_DB_AND); 133 indexes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID); 134 indexes->add(CSSM_DB_EQUAL, Schema::IndexType, 135 uint32(CSSM_DB_INDEX_UNIQUE)); 136 137 // Set up a record for retriving the SCHEMA_INDEXES 138 DbAttributes indexRecord(db, 1); 139 indexRecord.add(Schema::AttributeID); 140 141 CssmAutoDbRecordAttributeInfo &infos = 142 *new CssmAutoDbRecordAttributeInfo(); 143 mPrimaryKeyInfoMap. 144 insert(PrimaryKeyInfoMap::value_type(relationID, &infos)); 145 infos.DataRecordType = relationID; 146 while (indexes->next(&indexRecord, NULL, uniqueId)) 147 { 148 CssmDbAttributeInfo &info = infos.add(); 149 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; 150 info.Label.AttributeID = indexRecord.at(0); 151 // @@@ Might insert bogus value if DB is corrupt 152 info.AttributeFormat = rim[info.Label.AttributeID]; 153 } 154 } 155 } 156 157 KeychainSchemaImpl::~KeychainSchemaImpl() 158 { 159 try 160 { 161 map<CSSM_DB_RECORDTYPE, CssmAutoDbRecordAttributeInfo *>::iterator it = mPrimaryKeyInfoMap.begin(); 162 while (it != mPrimaryKeyInfoMap.end()) 163 { 164 delete it->second; 165 it++; 166 } 167 // for_each_map_delete(mPrimaryKeyInfoMap.begin(), mPrimaryKeyInfoMap.end()); 168 } 169 catch(...) 170 { 171 } 172 } 173 174 const KeychainSchemaImpl::RelationInfoMap & 175 KeychainSchemaImpl::relationInfoMapFor(CSSM_DB_RECORDTYPE recordType) const 176 { 177 DatabaseInfoMap::const_iterator dit = mDatabaseInfoMap.find(recordType); 178 if (dit == mDatabaseInfoMap.end()) 179 MacOSError::throwMe(errSecNoSuchClass); 180 return dit->second; 181 } 182 183 bool KeychainSchemaImpl::hasRecordType (CSSM_DB_RECORDTYPE recordType) const 184 { 185 DatabaseInfoMap::const_iterator it = mDatabaseInfoMap.find(recordType); 186 return it != mDatabaseInfoMap.end(); 187 } 188 189 bool 190 KeychainSchemaImpl::hasAttribute(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const 191 { 192 try 193 { 194 const RelationInfoMap &rmap = relationInfoMapFor(recordType); 195 RelationInfoMap::const_iterator rit = rmap.find(attributeId); 196 return rit != rmap.end(); 197 } 198 catch (MacOSError result) 199 { 200 if (result.osStatus () == errSecNoSuchClass) 201 { 202 return false; 203 } 204 else 205 { 206 throw; 207 } 208 } 209 } 210 211 CSSM_DB_ATTRIBUTE_FORMAT 212 KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const 213 { 214 const RelationInfoMap &rmap = relationInfoMapFor(recordType); 215 RelationInfoMap::const_iterator rit = rmap.find(attributeId); 216 if (rit == rmap.end()) 217 MacOSError::throwMe(errSecNoSuchAttr); 218 219 return rit->second; 220 } 221 222 CssmDbAttributeInfo 223 KeychainSchemaImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const 224 { 225 CSSM_DB_ATTRIBUTE_INFO info; 226 info.AttributeFormat = attributeFormatFor(recordType, attributeId); 227 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; 228 info.Label.AttributeID = attributeId; 229 230 return info; 231 } 232 233 void 234 KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType, SecKeychainAttributeInfo **Info) const 235 { 236 const RelationInfoMap &rmap = relationInfoMapFor(recordType); 237 238 SecKeychainAttributeInfo *theList=reinterpret_cast<SecKeychainAttributeInfo *>(malloc(sizeof(SecKeychainAttributeInfo))); 239 240 size_t capacity=rmap.size(); 241 UInt32 *tagBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32))); 242 UInt32 *formatBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32))); 243 UInt32 i=0; 244 245 246 for (RelationInfoMap::const_iterator rit = rmap.begin(); rit != rmap.end(); ++rit) 247 { 248 if (i>=capacity) 249 { 250 capacity *= 2; 251 if (capacity <= i) capacity = i + 1; 252 tagBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32)))); 253 formatBuf=reinterpret_cast<UInt32 *>(realloc(formatBuf, (capacity*sizeof(UInt32)))); 254 } 255 tagBuf[i]=rit->first; 256 formatBuf[i++]=rit->second; 257 } 258 259 theList->count=i; 260 theList->tag=tagBuf; 261 theList->format=formatBuf; 262 *Info=theList; 263 } 264 265 266 const CssmAutoDbRecordAttributeInfo & 267 KeychainSchemaImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) const 268 { 269 PrimaryKeyInfoMap::const_iterator it; 270 it = mPrimaryKeyInfoMap.find(recordType); 271 272 if (it == mPrimaryKeyInfoMap.end()) 273 MacOSError::throwMe(errSecNoSuchClass); // @@@ Not really but whatever. 274 275 return *it->second; 276 } 277 278 bool 279 KeychainSchemaImpl::operator <(const KeychainSchemaImpl &other) const 280 { 281 return mDatabaseInfoMap < other.mDatabaseInfoMap; 282 } 283 284 bool 285 KeychainSchemaImpl::operator ==(const KeychainSchemaImpl &other) const 286 { 287 return mDatabaseInfoMap == other.mDatabaseInfoMap; 288 } 289 290 void 291 KeychainSchemaImpl::didCreateRelation(CSSM_DB_RECORDTYPE relationID, 292 const char *inRelationName, 293 uint32 inNumberOfAttributes, 294 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *pAttributeInfo, 295 uint32 inNumberOfIndexes, 296 const CSSM_DB_SCHEMA_INDEX_INFO *pIndexInfo) 297 { 298 StLock<Mutex>_(mMutex); 299 300 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID 301 && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END) 302 return; 303 304 // if our schema is already in the map, return 305 if (mPrimaryKeyInfoMap.find(relationID) != mPrimaryKeyInfoMap.end()) 306 { 307 return; 308 } 309 310 RelationInfoMap &rim = mDatabaseInfoMap[relationID]; 311 for (uint32 ix = 0; ix < inNumberOfAttributes; ++ix) 312 rim[pAttributeInfo[ix].AttributeId] = pAttributeInfo[ix].DataType; 313 314 CssmAutoDbRecordAttributeInfo *infos = new CssmAutoDbRecordAttributeInfo(); 315 316 mPrimaryKeyInfoMap. 317 insert(PrimaryKeyInfoMap::value_type(relationID, infos)); 318 infos->DataRecordType = relationID; 319 for (uint32 ix = 0; ix < inNumberOfIndexes; ++ix) 320 if (pIndexInfo[ix].IndexType == CSSM_DB_INDEX_UNIQUE) 321 { 322 CssmDbAttributeInfo &info = infos->add(); 323 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; 324 info.Label.AttributeID = pIndexInfo[ix].AttributeId; 325 info.AttributeFormat = rim[info.Label.AttributeID]; 326 } 327 } 328 329 330 331 KeychainSchema::~KeychainSchema() 332 333 { 334 } 335 336 337 338 struct Event 339 { 340 SecKeychainEvent eventCode; 341 PrimaryKey primaryKey; 342 }; 343 typedef std::list<Event> EventList; 344 345 #define SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME "/var/run/systemkeychaincheck" 346 #define SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".socket") 347 #define SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".done") 348 349 static void check_system_keychain() 350 { 351 // sadly we can't use XPC here, XPC_DOMAIN_TYPE_SYSTEM doesn't exist yet. Also xpc-helper uses the 352 // keychain API (I assume for checking codesign things). So we use Unix Domain Sockets. 353 354 // NOTE: if we hit a system error we attempt to log it, and then just don't check the system keychain. 355 // In theory a system might be able to recover from this state if we let it try to muddle along, and 356 // past behaviour didn't even try this hard to do the keychain check. In particular we could be in a 357 // sandbox'ed process. So we just do our best and let another process try again. 358 359 struct stat keycheck_file_info; 360 if (stat(SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME, &keycheck_file_info) < 0) { 361 int server_fd = socket(PF_UNIX, SOCK_STREAM, 0); 362 if (server_fd < 0) { 363 syslog(LOG_ERR, "Can't get socket (%m) system keychain may be unchecked"); 364 return; 365 } 366 367 struct sockaddr_un keychain_check_server_address; 368 keychain_check_server_address.sun_family = AF_UNIX; 369 if (strlcpy(keychain_check_server_address.sun_path, SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME, sizeof(keychain_check_server_address.sun_path)) > sizeof(keychain_check_server_address.sun_path)) { 370 // It would be nice if we could compile time assert this 371 syslog(LOG_ERR, "Socket path too long, max length %lu, your length %lu", (unsigned long)sizeof(keychain_check_server_address.sun_path), (unsigned long)strlen(SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME)); 372 close(server_fd); 373 return; 374 } 375 keychain_check_server_address.sun_len = SUN_LEN(&keychain_check_server_address); 376 377 int rc = connect(server_fd, (struct sockaddr *)&keychain_check_server_address, keychain_check_server_address.sun_len); 378 if (rc < 0) { 379 syslog(LOG_ERR, "Can not connect to %s: %m", SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME); 380 close(server_fd); 381 return; 382 } 383 384 // this read lets us block until the EOF comes, we don't ever get a byte (and if we do, we don't care about it) 385 char byte; 386 ssize_t read_size = read(server_fd, &byte, 1); 387 if (read_size < 0) { 388 syslog(LOG_ERR, "Error reading from system keychain checker: %m"); 389 } 390 391 close(server_fd); 392 return; 393 } 394 } 395 396 // 397 // KeychainImpl 398 // 399 KeychainImpl::KeychainImpl(const Db &db) 400 : mCacheTimer(NULL), mSuppressTickle(false), mAttemptedUpgrade(false), mDbItemMapMutex(Mutex::recursive), mDbDeletedItemMapMutex(Mutex::recursive), 401 mInCache(false), mDb(db), mCustomUnlockCreds (this), mIsInBatchMode (false), mMutex(Mutex::recursive) 402 { 403 dispatch_once(&SecKeychainSystemKeychainChecked, ^{ 404 check_system_keychain(); 405 }); 406 mDb->defaultCredentials(this); // install activation hook 407 mEventBuffer = new EventBuffer; 408 } 409 410 KeychainImpl::~KeychainImpl() 411 { 412 try 413 { 414 // Remove ourselves from the cache if we are in it. 415 // fprintf(stderr, "Removing %p from storage manager cache.\n", handle(false)); 416 globals().storageManager.removeKeychain(dlDbIdentifier(), this); 417 delete mEventBuffer; 418 } 419 catch(...) 420 { 421 } 422 } 423 424 Mutex* 425 KeychainImpl::getMutexForObject() const 426 { 427 return globals().storageManager.getStorageManagerMutex(); 428 } 429 430 Mutex* 431 KeychainImpl::getKeychainMutex() 432 { 433 return &mMutex; 434 } 435 436 void KeychainImpl::aboutToDestruct() 437 { 438 // remove me from the global cache, we are done 439 // fprintf(stderr, "Destructing keychain object\n"); 440 DLDbIdentifier identifier = dlDbIdentifier(); 441 globals().storageManager.removeKeychain(identifier, this); 442 } 443 444 bool 445 KeychainImpl::operator ==(const KeychainImpl &keychain) const 446 { 447 return dlDbIdentifier() == keychain.dlDbIdentifier(); 448 } 449 450 KCCursor 451 KeychainImpl::createCursor(SecItemClass itemClass, const SecKeychainAttributeList *attrList) 452 { 453 StLock<Mutex>_(mMutex); 454 455 StorageManager::KeychainList keychains; 456 keychains.push_back(Keychain(this)); 457 return KCCursor(keychains, itemClass, attrList); 458 } 459 460 KCCursor 461 KeychainImpl::createCursor(const SecKeychainAttributeList *attrList) 462 { 463 StLock<Mutex>_(mMutex); 464 465 StorageManager::KeychainList keychains; 466 keychains.push_back(Keychain(this)); 467 return KCCursor(keychains, attrList); 468 } 469 470 void 471 KeychainImpl::create(UInt32 passwordLength, const void *inPassword) 472 { 473 StLock<Mutex>_(mMutex); 474 475 if (!inPassword) 476 { 477 create(); 478 return; 479 } 480 481 Allocator &alloc = Allocator::standard(); 482 483 // @@@ Share this instance 484 485 const CssmData password(const_cast<void *>(inPassword), passwordLength); 486 AclFactory::PasswordChangeCredentials pCreds (password, alloc); 487 AclFactory::AnyResourceContext rcc(pCreds); 488 create(&rcc); 489 490 // Now that we've created, trigger setting the defaultCredentials 491 mDb->open(); 492 } 493 494 void KeychainImpl::create(ConstStringPtr inPassword) 495 { 496 StLock<Mutex>_(mMutex); 497 498 if ( inPassword ) 499 create(static_cast<UInt32>(inPassword[0]), &inPassword[1]); 500 else 501 create(); 502 } 503 504 void 505 KeychainImpl::create() 506 { 507 StLock<Mutex>_(mMutex); 508 509 AclFactory aclFactory; 510 AclFactory::AnyResourceContext rcc(aclFactory.unlockCred()); 511 create(&rcc); 512 513 // Now that we've created, trigger setting the defaultCredentials 514 mDb->open(); 515 } 516 517 void KeychainImpl::createWithBlob(CssmData &blob) 518 { 519 StLock<Mutex>_(mMutex); 520 521 mDb->dbInfo(&Schema::DBInfo); 522 AclFactory aclFactory; 523 AclFactory::AnyResourceContext rcc(aclFactory.unlockCred()); 524 mDb->resourceControlContext (&rcc); 525 try 526 { 527 mDb->createWithBlob(blob); 528 } 529 catch (...) 530 { 531 mDb->resourceControlContext(NULL); 532 mDb->dbInfo(NULL); 533 throw; 534 } 535 mDb->resourceControlContext(NULL); 536 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later) 537 globals().storageManager.created(Keychain(this)); 538 539 KCEventNotifier::PostKeychainEvent (kSecKeychainListChangedEvent, this, NULL); 540 } 541 542 void 543 KeychainImpl::create(const ResourceControlContext *rcc) 544 { 545 StLock<Mutex>_(mMutex); 546 547 mDb->dbInfo(&Schema::DBInfo); // Set the schema (to force a create) 548 mDb->resourceControlContext(rcc); 549 try 550 { 551 mDb->create(); 552 } 553 catch (...) 554 { 555 mDb->resourceControlContext(NULL); 556 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later) 557 throw; 558 } 559 mDb->resourceControlContext(NULL); 560 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later) 561 globals().storageManager.created(Keychain(this)); 562 } 563 564 void 565 KeychainImpl::open() 566 { 567 StLock<Mutex>_(mMutex); 568 569 mDb->open(); 570 } 571 572 void 573 KeychainImpl::lock() 574 { 575 StLock<Mutex>_(mMutex); 576 577 mDb->lock(); 578 } 579 580 void 581 KeychainImpl::unlock() 582 { 583 StLock<Mutex>_(mMutex); 584 585 mDb->unlock(); 586 } 587 588 void 589 KeychainImpl::unlock(const CssmData &password) 590 { 591 StLock<Mutex>_(mMutex); 592 593 mDb->unlock(password); 594 } 595 596 void 597 KeychainImpl::unlock(ConstStringPtr password) 598 { 599 StLock<Mutex>_(mMutex); 600 601 if (password) 602 { 603 const CssmData data(const_cast<unsigned char *>(&password[1]), password[0]); 604 unlock(data); 605 } 606 else 607 unlock(); 608 } 609 610 void 611 KeychainImpl::stash() 612 { 613 StLock<Mutex>_(mMutex); 614 615 mDb->stash(); 616 } 617 618 void 619 KeychainImpl::stashCheck() 620 { 621 StLock<Mutex>_(mMutex); 622 623 mDb->stashCheck(); 624 } 625 626 void 627 KeychainImpl::getSettings(uint32 &outIdleTimeOut, bool &outLockOnSleep) 628 { 629 StLock<Mutex>_(mMutex); 630 631 mDb->getSettings(outIdleTimeOut, outLockOnSleep); 632 } 633 634 void 635 KeychainImpl::setSettings(uint32 inIdleTimeOut, bool inLockOnSleep) 636 { 637 StLock<Mutex>_(mMutex); 638 639 // The .Mac syncing code only makes sense for the AppleFile CSP/DL, 640 // but other DLs such as the OCSP and LDAP DLs do not expose a way to 641 // change settings or the password. To make a minimal change that only affects 642 // the smartcard case, we only look for that CSP/DL 643 644 bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL); 645 646 // get the old keychain blob so that we can tell .Mac to resync it 647 CssmAutoData oldBlob(mDb ->allocator()); 648 if (!isSmartcard) 649 mDb->copyBlob(oldBlob.get()); 650 651 mDb->setSettings(inIdleTimeOut, inLockOnSleep); 652 } 653 654 void 655 KeychainImpl::changePassphrase(UInt32 oldPasswordLength, const void *oldPassword, 656 UInt32 newPasswordLength, const void *newPassword) 657 { 658 StLock<Mutex>_(mMutex); 659 660 secnotice("KCspi", "Attempting to change passphrase for %s", mDb->name()); 661 662 bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL); 663 664 TrackingAllocator allocator(Allocator::standard()); 665 AutoCredentials cred = AutoCredentials(allocator); 666 667 if (oldPassword) 668 { 669 const CssmData &oldPass = *new(allocator) CssmData(const_cast<void *>(oldPassword), oldPasswordLength); 670 TypedList &oldList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK); 671 oldList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD)); 672 oldList.append(new(allocator) ListElement(oldPass)); 673 cred += oldList; 674 } 675 676 if (newPassword) 677 { 678 const CssmData &newPass = *new(allocator) CssmData(const_cast<void *>(newPassword), newPasswordLength); 679 TypedList &newList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK); 680 newList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD)); 681 newList.append(new(allocator) ListElement(newPass)); 682 cred += newList; 683 } 684 685 // get the old keychain blob so that we can tell .Mac to resync it 686 CssmAutoData oldBlob(mDb->allocator()); 687 if (!isSmartcard) 688 mDb->copyBlob(oldBlob.get()); 689 690 mDb->changePassphrase(&cred); 691 } 692 693 void 694 KeychainImpl::changePassphrase(ConstStringPtr oldPassword, ConstStringPtr newPassword) 695 { 696 StLock<Mutex>_(mMutex); 697 698 const void *oldPtr, *newPtr; 699 UInt32 oldLen, newLen; 700 if (oldPassword) 701 { 702 oldLen = oldPassword[0]; 703 oldPtr = oldPassword + 1; 704 } 705 else 706 { 707 oldLen = 0; 708 oldPtr = NULL; 709 } 710 711 if (newPassword) 712 { 713 newLen = newPassword[0]; 714 newPtr = newPassword + 1; 715 } 716 else 717 { 718 newLen = 0; 719 newPtr = NULL; 720 } 721 722 changePassphrase(oldLen, oldPtr, newLen, newPtr); 723 } 724 725 void 726 KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS *cred) 727 { 728 StLock<Mutex>_(mMutex); 729 730 if (!exists()) 731 MacOSError::throwMe(errSecNoSuchKeychain); 732 733 MacOSError::throwMe(errSecUnimplemented); 734 } 735 736 UInt32 737 KeychainImpl::status() const 738 { 739 StLock<Mutex>_(mMutex); 740 741 // @@@ We should figure out the read/write status though a DL passthrough 742 // or some other way. Also should locked be unlocked read only or just 743 // read-only? 744 return (mDb->isLocked() ? 0 : kSecUnlockStateStatus | kSecWritePermStatus) 745 | kSecReadPermStatus; 746 } 747 748 bool 749 KeychainImpl::exists() 750 { 751 StLock<Mutex>_(mMutex); 752 753 bool exists = true; 754 try 755 { 756 open(); 757 // Ok to leave the mDb open since it will get closed when it goes away. 758 } 759 catch (const CssmError &e) 760 { 761 if (e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST) 762 throw; 763 exists = false; 764 } 765 766 return exists; 767 } 768 769 bool 770 KeychainImpl::isActive() const 771 { 772 return mDb->isActive(); 773 } 774 775 void KeychainImpl::completeAdd(Item &inItem, PrimaryKey &primaryKey) 776 { 777 // The inItem shouldn't be in the cache yet 778 assert(!inItem->inCache()); 779 780 // Insert inItem into mDbItemMap with key primaryKey. p.second will be 781 // true if it got inserted. If not p.second will be false and p.first 782 // will point to the current entry with key primaryKey. 783 StLock<Mutex> _(mDbItemMapMutex); 784 pair<DbItemMap::iterator, bool> p = 785 mDbItemMap.insert(DbItemMap::value_type(primaryKey, inItem.get())); 786 if (!p.second) 787 { 788 // There was already an ItemImpl * in mDbItemMap with key 789 // primaryKey. Remove it, and try the add again. 790 ItemImpl *oldItem = p.first->second; 791 792 // @@@ If this happens we are breaking our API contract of 793 // uniquifying items. We really need to insert the item into the 794 // map before we start the add. And have the item be in an 795 // "is being added" state. 796 secnotice("keychain", "add of new item %p somehow replaced %p", 797 inItem.get(), oldItem); 798 799 mDbItemMap.erase(p.first); 800 oldItem->inCache(false); 801 forceRemoveFromCache(oldItem); 802 mDbItemMap.insert(DbItemMap::value_type(primaryKey, inItem.get())); 803 } 804 805 inItem->inCache(true); 806 } 807 808 void 809 KeychainImpl::addCopy(Item &inItem) 810 { 811 StLock<Mutex>_(mMutex); 812 813 Keychain keychain(this); 814 PrimaryKey primaryKey = inItem->addWithCopyInfo(keychain, true); 815 completeAdd(inItem, primaryKey); 816 postEvent(kSecAddEvent, inItem); 817 } 818 819 void 820 KeychainImpl::add(Item &inItem) 821 { 822 StLock<Mutex>_(mMutex); 823 824 Keychain keychain(this); 825 PrimaryKey primaryKey = inItem->add(keychain); 826 completeAdd(inItem, primaryKey); 827 postEvent(kSecAddEvent, inItem); 828 } 829 830 void 831 KeychainImpl::didUpdate(const Item &inItem, PrimaryKey &oldPK, 832 PrimaryKey &newPK) 833 { 834 // If the primary key hasn't changed we don't need to update mDbItemMap. 835 if (oldPK != newPK) 836 { 837 // If inItem isn't in the cache we don't need to update mDbItemMap. 838 assert(inItem->inCache()); 839 if (inItem->inCache()) 840 { 841 StLock<Mutex> _(mDbItemMapMutex); 842 // First remove the entry for inItem in mDbItemMap with key oldPK. 843 DbItemMap::iterator it = mDbItemMap.find(oldPK); 844 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItem.get()) 845 mDbItemMap.erase(it); 846 847 // Insert inItem into mDbItemMap with key newPK. p.second will be 848 // true if it got inserted. If not p.second will be false and 849 // p.first will point to the current entry with key newPK. 850 pair<DbItemMap::iterator, bool> p = 851 mDbItemMap.insert(DbItemMap::value_type(newPK, inItem.get())); 852 if (!p.second) 853 { 854 // There was already an ItemImpl * in mDbItemMap with key 855 // primaryKey. Remove it, and try the add again. 856 ItemImpl *oldItem = p.first->second; 857 858 // @@@ If this happens we are breaking our API contract of 859 // uniquifying items. We really need to insert the item into 860 // the map with the new primary key before we start the update. 861 // And have the item be in an "is being updated" state. 862 secnotice("keychain", "update of item %p somehow replaced %p", 863 inItem.get(), oldItem); 864 865 mDbItemMap.erase(p.first); 866 oldItem->inCache(false); 867 forceRemoveFromCache(oldItem); 868 mDbItemMap.insert(DbItemMap::value_type(newPK, inItem.get())); 869 } 870 } 871 } 872 873 // Item updates now are technically a delete and re-add, so post these events instead of kSecUpdateEvent 874 postEvent(kSecDeleteEvent, inItem, oldPK); 875 postEvent(kSecAddEvent, inItem); 876 } 877 878 void 879 KeychainImpl::deleteItem(Item &inoutItem) 880 { 881 StLock<Mutex>_(mMutex); 882 883 { 884 // item must be persistent 885 if (!inoutItem->isPersistent()) 886 MacOSError::throwMe(errSecInvalidItemRef); 887 888 secinfo("kcnotify", "starting deletion of item %p", inoutItem.get()); 889 890 DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord(); 891 PrimaryKey primaryKey = inoutItem->primaryKey(); 892 uniqueId->deleteRecord(); 893 894 // Move the item from mDbItemMap to mDbDeletedItemMap. We need the item 895 // to give to the client process when we receive the kSecDeleteEvent 896 // notification, but if that notification never arrives, we don't want 897 // the item hanging around. When didDeleteItem is called by CCallbackMgr, 898 // we'll remove all traces of the item. 899 900 if (inoutItem->inCache()) { 901 StLock<Mutex> _(mDbItemMapMutex); 902 StLock<Mutex> __(mDbDeletedItemMapMutex); 903 // Only look for it if it's in the cache 904 DbItemMap::iterator it = mDbItemMap.find(primaryKey); 905 906 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inoutItem.get()) { 907 mDbDeletedItemMap.insert(DbItemMap::value_type(primaryKey, it->second)); 908 mDbItemMap.erase(it); 909 } 910 } 911 912 // Post the notification for the item deletion with 913 // the primaryKey obtained when the item still existed 914 } 915 916 postEvent(kSecDeleteEvent, inoutItem); 917 } 918 919 void KeychainImpl::changeDatabase(CssmClient::Db db) 920 { 921 StLock<Mutex>_(mDbMutex); 922 mDb = db; 923 mDb->defaultCredentials(this); 924 } 925 926 927 CssmClient::CSP 928 KeychainImpl::csp() 929 { 930 StLock<Mutex>_(mMutex); 931 932 if (!(mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP)) 933 MacOSError::throwMe(errSecInvalidKeychain); 934 935 // Try to cast first to a CSPDL to handle case where we don't have an SSDb 936 try 937 { 938 CssmClient::CSPDL cspdl(dynamic_cast<CssmClient::CSPDLImpl *>(&*mDb->dl())); 939 return CSP(cspdl); 940 } 941 catch (...) 942 { 943 SSDbImpl* impl = dynamic_cast<SSDbImpl *>(&(*mDb)); 944 if (impl == NULL) 945 { 946 CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); 947 } 948 949 SSDb ssDb(impl); 950 return ssDb->csp(); 951 } 952 } 953 954 PrimaryKey 955 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId) 956 { 957 StLock<Mutex>_(mMutex); 958 959 DbAttributes primaryKeyAttrs(uniqueId->database()); 960 primaryKeyAttrs.recordType(recordType); 961 gatherPrimaryKeyAttributes(primaryKeyAttrs); 962 uniqueId->get(&primaryKeyAttrs, NULL); 963 return PrimaryKey(primaryKeyAttrs); 964 } 965 966 PrimaryKey 967 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbAttributes* currentAttributes) 968 { 969 StLock<Mutex>_(mMutex); 970 971 DbAttributes primaryKeyAttrs; 972 primaryKeyAttrs.recordType(recordType); 973 gatherPrimaryKeyAttributes(primaryKeyAttrs); 974 975 for(int i = 0; i < primaryKeyAttrs.size(); i++) { 976 CssmDbAttributeData& attr = primaryKeyAttrs[i]; 977 978 CssmDbAttributeData * actual = currentAttributes->find(attr.info()); 979 if(actual) { 980 attr.set(*actual, Allocator::standard()); 981 } 982 } 983 return PrimaryKey(primaryKeyAttrs); 984 } 985 986 const CssmAutoDbRecordAttributeInfo & 987 KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) 988 { 989 StLock<Mutex>_(mMutex); 990 991 try 992 { 993 return keychainSchema()->primaryKeyInfosFor(recordType); 994 } 995 catch (const CommonError &error) 996 { 997 switch (error.osStatus()) 998 { 999 case errSecNoSuchClass: 1000 case CSSMERR_DL_INVALID_RECORDTYPE: 1001 resetSchema(); 1002 return keychainSchema()->primaryKeyInfosFor(recordType); 1003 default: 1004 throw; 1005 } 1006 } 1007 } 1008 1009 void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes& primaryKeyAttrs) 1010 { 1011 StLock<Mutex> _(mMutex); 1012 1013 const CssmAutoDbRecordAttributeInfo &infos = 1014 primaryKeyInfosFor(primaryKeyAttrs.recordType()); 1015 1016 // @@@ fix this to not copy info. 1017 for (uint32 i = 0; i < infos.size(); i++) 1018 primaryKeyAttrs.add(infos.at(i)); 1019 } 1020 1021 ItemImpl * 1022 KeychainImpl::_lookupItem(const PrimaryKey &primaryKey) 1023 { 1024 StLock<Mutex> _(mDbItemMapMutex); 1025 DbItemMap::iterator it = mDbItemMap.find(primaryKey); 1026 if (it != mDbItemMap.end()) 1027 { 1028 return it->second; 1029 } 1030 1031 return NULL; 1032 } 1033 1034 ItemImpl * 1035 KeychainImpl::_lookupDeletedItemOnly(const PrimaryKey &primaryKey) 1036 { 1037 StLock<Mutex> _(mDbDeletedItemMapMutex); 1038 DbItemMap::iterator it = mDbDeletedItemMap.find(primaryKey); 1039 if (it != mDbDeletedItemMap.end()) 1040 { 1041 return it->second; 1042 } 1043 1044 return NULL; 1045 } 1046 1047 Item 1048 KeychainImpl::item(const PrimaryKey &primaryKey) 1049 { 1050 StLock<Mutex>_(mMutex); 1051 1052 // Lookup the item in the map while holding the apiLock. 1053 ItemImpl *itemImpl = _lookupItem(primaryKey); 1054 if (itemImpl) { 1055 return Item(itemImpl); 1056 } 1057 1058 try 1059 { 1060 // We didn't find it so create a new item with just a keychain and 1061 // a primary key. Some other thread might have beaten 1062 // us to creating this item and adding it to the cache. If that 1063 // happens we retry the lookup. 1064 return Item(this, primaryKey); 1065 } 1066 catch (const MacOSError &e) 1067 { 1068 // If the item creation failed because some other thread already 1069 // inserted this item into the cache we retry the lookup. 1070 if (e.osStatus() == errSecDuplicateItem) 1071 { 1072 // Lookup the item in the map while holding the apiLock. 1073 ItemImpl *itemImpl = _lookupItem(primaryKey); 1074 if (itemImpl) 1075 return Item(itemImpl); 1076 } 1077 throw; 1078 } 1079 } 1080 // Check for an item that may have been deleted. 1081 Item 1082 KeychainImpl::itemdeleted(const PrimaryKey& primaryKey) { 1083 StLock<Mutex>_(mMutex); 1084 1085 Item i = _lookupDeletedItemOnly(primaryKey); 1086 if(i.get()) { 1087 return i; 1088 } else { 1089 return item(primaryKey); 1090 } 1091 } 1092 1093 1094 Item 1095 KeychainImpl::item(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId) 1096 { 1097 StLock<Mutex>_(mMutex); 1098 1099 PrimaryKey primaryKey = makePrimaryKey(recordType, uniqueId); 1100 { 1101 // Lookup the item in the map while holding the apiLock. 1102 ItemImpl *itemImpl = _lookupItem(primaryKey); 1103 1104 if (itemImpl) 1105 { 1106 return Item(itemImpl); 1107 } 1108 } 1109 1110 try 1111 { 1112 // We didn't find it so create a new item with a keychain, a primary key 1113 // and a DbUniqueRecord. However since we aren't holding 1114 // globals().apiLock anymore some other thread might have beaten 1115 // us to creating this item and adding it to the cache. If that 1116 // happens we retry the lookup. 1117 return Item(this, primaryKey, uniqueId); 1118 } 1119 catch (const MacOSError &e) 1120 { 1121 // If the item creation failed because some other thread already 1122 // inserted this item into the cache we retry the lookup. 1123 if (e.osStatus() == errSecDuplicateItem) 1124 { 1125 // Lookup the item in the map while holding the apiLock. 1126 ItemImpl *itemImpl = _lookupItem(primaryKey); 1127 if (itemImpl) 1128 return Item(itemImpl); 1129 } 1130 throw; 1131 } 1132 } 1133 1134 KeychainSchema 1135 KeychainImpl::keychainSchema() 1136 { 1137 StLock<Mutex>_(mMutex); 1138 if (!mKeychainSchema) 1139 mKeychainSchema = KeychainSchema(mDb); 1140 1141 return mKeychainSchema; 1142 } 1143 1144 void KeychainImpl::resetSchema() 1145 { 1146 mKeychainSchema = NULL; // re-fetch it from db next time 1147 } 1148 1149 1150 // Called from DbItemImpl's constructor (so it is only partially constructed), 1151 // add it to the map. 1152 void 1153 KeychainImpl::addItem(const PrimaryKey &primaryKey, ItemImpl *dbItemImpl) 1154 { 1155 StLock<Mutex>_(mMutex); 1156 1157 // The dbItemImpl shouldn't be in the cache yet 1158 assert(!dbItemImpl->inCache()); 1159 1160 // Insert dbItemImpl into mDbItemMap with key primaryKey. p.second will 1161 // be true if it got inserted. If not p.second will be false and p.first 1162 // will point to the current entry with key primaryKey. 1163 StLock<Mutex> __(mDbItemMapMutex); 1164 pair<DbItemMap::iterator, bool> p = 1165 mDbItemMap.insert(DbItemMap::value_type(primaryKey, dbItemImpl)); 1166 1167 if (!p.second) 1168 { 1169 // There was already an ItemImpl * in mDbItemMap with key primaryKey. 1170 // There is a race condition here when being called in multiple threads 1171 // We might have added an item using add and received a notification at 1172 // the same time. 1173 MacOSError::throwMe(errSecDuplicateItem); 1174 } 1175 1176 dbItemImpl->inCache(true); 1177 } 1178 1179 void 1180 KeychainImpl::didDeleteItem(ItemImpl *inItemImpl) 1181 { 1182 StLock<Mutex>_(mMutex); 1183 1184 // Called by CCallbackMgr 1185 secinfo("kcnotify", "%p notified that item %p was deleted", this, inItemImpl); 1186 removeItem(inItemImpl->primaryKey(), inItemImpl); 1187 } 1188 1189 void 1190 KeychainImpl::removeItem(const PrimaryKey &primaryKey, ItemImpl *inItemImpl) 1191 { 1192 StLock<Mutex>_(mMutex); 1193 1194 // If inItemImpl isn't in the cache to begin with we are done. 1195 if (!inItemImpl->inCache()) 1196 return; 1197 1198 { 1199 StLock<Mutex> _(mDbItemMapMutex); 1200 DbItemMap::iterator it = mDbItemMap.find(primaryKey); 1201 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItemImpl) { 1202 mDbItemMap.erase(it); 1203 } 1204 } // drop mDbItemMapMutex 1205 1206 { 1207 StLock<Mutex> _(mDbDeletedItemMapMutex); 1208 DbItemMap::iterator it = mDbDeletedItemMap.find(primaryKey); 1209 if (it != mDbDeletedItemMap.end() && (ItemImpl*) it->second == inItemImpl) { 1210 mDbDeletedItemMap.erase(it); 1211 } 1212 } // drop mDbDeletedItemMapMutex 1213 1214 inItemImpl->inCache(false); 1215 } 1216 1217 void 1218 KeychainImpl::forceRemoveFromCache(ItemImpl* inItemImpl) { 1219 try { 1220 // Wrap all this in a try-block and ignore all errors - we're trying to clean up these maps 1221 { 1222 StLock<Mutex> _(mDbItemMapMutex); 1223 for(DbItemMap::iterator it = mDbItemMap.begin(); it != mDbItemMap.end(); ) { 1224 if(it->second == inItemImpl) { 1225 // Increment the iterator, but use its pre-increment value for the erase 1226 it->second->inCache(false); 1227 mDbItemMap.erase(it++); 1228 } else { 1229 it++; 1230 } 1231 } 1232 } // drop mDbItemMapMutex 1233 1234 { 1235 StLock<Mutex> _(mDbDeletedItemMapMutex); 1236 for(DbItemMap::iterator it = mDbDeletedItemMap.begin(); it != mDbDeletedItemMap.end(); ) { 1237 if(it->second == inItemImpl) { 1238 // Increment the iterator, but use its pre-increment value for the erase 1239 it->second->inCache(false); 1240 mDbDeletedItemMap.erase(it++); 1241 } else { 1242 it++; 1243 } 1244 } 1245 } // drop mDbDeletedItemMapMutex 1246 } catch(UnixError ue) { 1247 secnotice("keychain", "caught UnixError: %d %s", ue.unixError(), ue.what()); 1248 } catch (CssmError cssme) { 1249 const char* errStr = cssmErrorString(cssme.error); 1250 secnotice("keychain", "caught CssmError: %d %s", (int) cssme.error, errStr); 1251 } catch (MacOSError mose) { 1252 secnotice("keychain", "MacOSError: %d", (int)mose.osStatus()); 1253 } catch(...) { 1254 secnotice("keychain", "Unknown error"); 1255 } 1256 } 1257 1258 void 1259 KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID, 1260 SecKeychainAttributeInfo **Info) 1261 { 1262 StLock<Mutex>_(mMutex); 1263 1264 try 1265 { 1266 keychainSchema()->getAttributeInfoForRecordType(itemID, Info); 1267 } 1268 catch (const CommonError &error) 1269 { 1270 switch (error.osStatus()) 1271 { 1272 case errSecNoSuchClass: 1273 case CSSMERR_DL_INVALID_RECORDTYPE: 1274 resetSchema(); 1275 keychainSchema()->getAttributeInfoForRecordType(itemID, Info); 1276 default: 1277 throw; 1278 } 1279 } 1280 } 1281 1282 void 1283 KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo *Info) 1284 { 1285 free(Info->tag); 1286 free(Info->format); 1287 free(Info); 1288 } 1289 1290 CssmDbAttributeInfo 1291 KeychainImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, UInt32 tag) 1292 { 1293 StLock<Mutex>_(mMutex); 1294 1295 try 1296 { 1297 return keychainSchema()->attributeInfoFor(recordType, tag); 1298 } 1299 catch (const CommonError &error) 1300 { 1301 switch (error.osStatus()) 1302 { 1303 case errSecNoSuchClass: 1304 case CSSMERR_DL_INVALID_RECORDTYPE: 1305 resetSchema(); 1306 return keychainSchema()->attributeInfoFor(recordType, tag); 1307 default: 1308 throw; 1309 } 1310 } 1311 } 1312 1313 void 1314 KeychainImpl::recode(const CssmData &data, const CssmData &extraData) 1315 { 1316 StLock<Mutex>_(mMutex); 1317 1318 mDb->recode(data, extraData); 1319 } 1320 1321 void 1322 KeychainImpl::copyBlob(CssmData &data) 1323 { 1324 StLock<Mutex>_(mMutex); 1325 1326 mDb->copyBlob(data); 1327 } 1328 1329 void 1330 KeychainImpl::setBatchMode(Boolean mode, Boolean rollback) 1331 { 1332 StLock<Mutex>_(mMutex); 1333 1334 mDb->setBatchMode(mode, rollback); 1335 mIsInBatchMode = mode; 1336 if (!mode) 1337 { 1338 if (!rollback) // was batch mode being turned off without an abort? 1339 { 1340 // dump the buffer 1341 EventBuffer::iterator it = mEventBuffer->begin(); 1342 while (it != mEventBuffer->end()) 1343 { 1344 PrimaryKey primaryKey; 1345 if (it->item) 1346 { 1347 primaryKey = it->item->primaryKey(); 1348 } 1349 1350 KCEventNotifier::PostKeychainEvent(it->kcEvent, mDb->dlDbIdentifier(), primaryKey); 1351 1352 ++it; 1353 } 1354 1355 } 1356 1357 // notify that a keychain has changed in too many ways to count 1358 KCEventNotifier::PostKeychainEvent((SecKeychainEvent) kSecKeychainLeftBatchModeEvent); 1359 mEventBuffer->clear(); 1360 } 1361 else 1362 { 1363 KCEventNotifier::PostKeychainEvent((SecKeychainEvent) kSecKeychainEnteredBatchModeEvent); 1364 } 1365 } 1366 void 1367 KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item) 1368 { 1369 postEvent(kcEvent, item, NULL); 1370 } 1371 1372 void 1373 KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item, PrimaryKey pk) 1374 { 1375 PrimaryKey primaryKey; 1376 1377 if(pk.get()) { 1378 primaryKey = pk; 1379 } else { 1380 StLock<Mutex>_(mMutex); 1381 1382 if (item != NULL) 1383 { 1384 primaryKey = item->primaryKey(); 1385 } 1386 } 1387 1388 if (!mIsInBatchMode) 1389 { 1390 KCEventNotifier::PostKeychainEvent(kcEvent, mDb->dlDbIdentifier(), primaryKey); 1391 } 1392 else 1393 { 1394 StLock<Mutex>_(mMutex); 1395 1396 EventItem it; 1397 it.kcEvent = kcEvent; 1398 if (item != NULL) 1399 { 1400 it.item = item; 1401 } 1402 1403 mEventBuffer->push_back (it); 1404 } 1405 } 1406 1407 void KeychainImpl::tickle() { 1408 if(!mSuppressTickle) { 1409 globals().storageManager.tickleKeychain(this); 1410 } 1411 } 1412 1413 1414 bool KeychainImpl::performKeychainUpgradeIfNeeded() { 1415 // Grab this keychain's mutex. 1416 StLock<Mutex>_(mMutex); 1417 1418 if(!globals().integrityProtection()) { 1419 secnotice("integrity", "skipping upgrade for %s due to global integrity protection being disabled", mDb->name()); 1420 return false; 1421 } 1422 1423 // We need a CSP database for 'upgrade' to be meaningful 1424 if((mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP) == 0) { 1425 return false; 1426 } 1427 1428 // We only want to upgrade file-based Apple keychains. Check the GUID. 1429 if(mDb->dl()->guid() != gGuidAppleCSPDL) { 1430 secinfo("integrity", "skipping upgrade for %s due to guid mismatch\n", mDb->name()); 1431 return false; 1432 } 1433 1434 // If we've already attempted an upgrade on this keychain, don't bother again 1435 if(mAttemptedUpgrade) { 1436 return false; 1437 } 1438 1439 // Don't upgrade the System root certificate keychain (to make old tp code happy) 1440 if(strncmp(mDb->name(), SYSTEM_ROOT_STORE_PATH, strlen(SYSTEM_ROOT_STORE_PATH)) == 0) { 1441 secinfo("integrity", "skipping upgrade for %s\n", mDb->name()); 1442 return false; 1443 } 1444 1445 uint32 dbBlobVersion = SecurityServer::DbBlob::version_MacOS_10_0; 1446 1447 try { 1448 dbBlobVersion = mDb->dbBlobVersion(); 1449 } catch (CssmError cssme) { 1450 if(cssme.error == CSSMERR_DL_DATASTORE_DOESNOT_EXIST) { 1451 // oh well! We tried to get the blob version of a database 1452 // that doesn't exist. It doesn't need migration, so do nothing. 1453 secnotice("integrity", "dbBlobVersion() failed for a non-existent database"); 1454 return false; 1455 } else { 1456 // Some other error occurred. We can't upgrade this keychain, so fail. 1457 const char* errStr = cssmErrorString(cssme.error); 1458 secnotice("integrity", "dbBlobVersion() failed for a CssmError: %d %s", (int) cssme.error, errStr); 1459 return false; 1460 } 1461 } catch (...) { 1462 secnotice("integrity", "dbBlobVersion() failed for an unknown reason"); 1463 return false; 1464 } 1465 1466 1467 1468 // Check the location of this keychain 1469 string path = mDb->name(); 1470 string keychainDbPath = StorageManager::makeKeychainDbFilename(path); 1471 1472 bool inHomeLibraryKeychains = StorageManager::pathInHomeLibraryKeychains(path); 1473 1474 string keychainDbSuffix = "-db"; 1475 bool endsWithKeychainDb = (path.size() > keychainDbSuffix.size() && (0 == path.compare(path.size() - keychainDbSuffix.size(), keychainDbSuffix.size(), keychainDbSuffix))); 1476 1477 bool isSystemKeychain = (0 == path.compare("/Library/Keychains/System.keychain")); 1478 1479 bool result = false; 1480 1481 if(inHomeLibraryKeychains && endsWithKeychainDb && dbBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0) { 1482 // something has gone horribly wrong: an old-versioned keychain has a .keychain-db name. Rename it. 1483 string basePath = path; 1484 basePath.erase(basePath.end()-3, basePath.end()); 1485 1486 attemptKeychainRename(path, basePath, dbBlobVersion); 1487 1488 // If we moved to a good path, we might still want to perform the upgrade. Update our variables. 1489 path = mDb->name(); 1490 1491 try { 1492 dbBlobVersion = mDb->dbBlobVersion(); 1493 } catch (CssmError cssme) { 1494 const char* errStr = cssmErrorString(cssme.error); 1495 secnotice("integrity", "dbBlobVersion() after a rename failed for a CssmError: %d %s", (int) cssme.error, errStr); 1496 return false; 1497 } catch (...) { 1498 secnotice("integrity", "dbBlobVersion() failed for an unknown reason after a rename"); 1499 return false; 1500 } 1501 1502 endsWithKeychainDb = (path.size() > keychainDbSuffix.size() && (0 == path.compare(path.size() - keychainDbSuffix.size(), keychainDbSuffix.size(), keychainDbSuffix))); 1503 keychainDbPath = StorageManager::makeKeychainDbFilename(path); 1504 secnotice("integrity", "after rename, our database thinks that it is %s", path.c_str()); 1505 } 1506 1507 // Migrate an old keychain in ~/Library/Keychains 1508 if(inHomeLibraryKeychains && dbBlobVersion != SecurityServer::DbBlob::version_partition && !endsWithKeychainDb) { 1509 // We can only attempt to migrate an unlocked keychain. 1510 if(mDb->isLocked()) { 1511 // However, it's possible that while we weren't doing any keychain operations, someone upgraded the keychain, 1512 // and then locked it. No way around hitting the filesystem here: check for the existence of a new file and, 1513 // if no new file exists, quit. 1514 DLDbIdentifier mungedDLDbIdentifier = StorageManager::mungeDLDbIdentifier(mDb->dlDbIdentifier(), false); 1515 string mungedPath(mungedDLDbIdentifier.dbName()); 1516 1517 // If this matches the file we already have, skip the upgrade. Otherwise, continue. 1518 if(mungedPath == path) { 1519 secnotice("integrity", "skipping upgrade for locked keychain %s\n", mDb->name()); 1520 return false; 1521 } 1522 } 1523 1524 result = keychainMigration(path, dbBlobVersion, keychainDbPath, SecurityServer::DbBlob::version_partition); 1525 } else if(inHomeLibraryKeychains && dbBlobVersion == SecurityServer::DbBlob::version_partition && !endsWithKeychainDb) { 1526 // This is a new-style keychain with the wrong name, try to rename it 1527 attemptKeychainRename(path, keychainDbPath, dbBlobVersion); 1528 result = true; 1529 } else if(isSystemKeychain && dbBlobVersion == SecurityServer::DbBlob::version_partition) { 1530 // Try to "unupgrade" the system keychain, to clean up our old issues 1531 secnotice("integrity", "attempting downgrade for %s version %d (%d %d %d)", path.c_str(), dbBlobVersion, inHomeLibraryKeychains, endsWithKeychainDb, isSystemKeychain); 1532 1533 // First step: acquire the credentials to allow for ACL modification 1534 SecurityServer::SystemKeychainKey skk(kSystemUnlockFile); 1535 if(skk.valid()) { 1536 // We've managed to read the key; now, create credentials using it 1537 CssmClient::Key systemKeychainMasterKey(csp(), skk.key(), true); 1538 CssmClient::AclFactory::MasterKeyUnlockCredentials creds(systemKeychainMasterKey, Allocator::standard(Allocator::sensitive)); 1539 1540 // Attempt the downgrade, using our master key as the ACL override 1541 result = keychainMigration(path, dbBlobVersion, path, SecurityServer::DbBlob::version_MacOS_10_0, creds.getAccessCredentials()); 1542 } else { 1543 secnotice("integrity", "Couldn't read System.keychain key, skipping update"); 1544 } 1545 } else { 1546 secinfo("integrity", "not attempting migration for %s version %d (%d %d %d)", path.c_str(), dbBlobVersion, inHomeLibraryKeychains, endsWithKeychainDb, isSystemKeychain); 1547 1548 // Since we don't believe any migration needs to be done here, mark the 1549 // migration as "attempted" to short-circuit future checks. 1550 mAttemptedUpgrade = true; 1551 } 1552 1553 // We might have changed our location on disk. Let StorageManager know. 1554 globals().storageManager.registerKeychainImpl(this); 1555 1556 // if we attempted a migration, try to clean up leftover files from <rdar://problem/23950408> XARA backup have provided me with 12GB of login keychain copies 1557 if(result) { 1558 string pattern = path + "_*_backup"; 1559 glob_t pglob = {}; 1560 secnotice("integrity", "globbing for %s", pattern.c_str()); 1561 int globresult = glob(pattern.c_str(), GLOB_MARK, NULL, &pglob); 1562 if(globresult == 0) { 1563 secnotice("integrity", "glob: %lu results", pglob.gl_pathc); 1564 if(pglob.gl_pathc > 10) { 1565 // There are more than 10 backup files, indicating a problem. 1566 // Delete all but one of them. Under rdar://23950408, they should all be identical. 1567 secnotice("integrity", "saving backup file: %s", pglob.gl_pathv[0]); 1568 for(int i = 1; i < pglob.gl_pathc; i++) { 1569 secnotice("integrity", "cleaning up backup file: %s", pglob.gl_pathv[i]); 1570 // ignore return code; this is a best-effort cleanup 1571 unlink(pglob.gl_pathv[i]); 1572 } 1573 } 1574 1575 struct stat st; 1576 bool pathExists = (::stat(path.c_str(), &st) == 0); 1577 bool keychainDbPathExists = (::stat(keychainDbPath.c_str(), &st) == 0); 1578 1579 if(!pathExists && keychainDbPathExists && pglob.gl_pathc >= 1) { 1580 // We have a file at keychainDbPath, no file at path, and at least one backup keychain file. 1581 // 1582 // Move the backup file to path, to simulate the current "split-world" view, 1583 // which copies from path to keychainDbPath, then modifies keychainDbPath. 1584 secnotice("integrity", "moving backup file %s to %s", pglob.gl_pathv[0], path.c_str()); 1585 ::rename(pglob.gl_pathv[0], path.c_str()); 1586 } 1587 } 1588 1589 globfree(&pglob); 1590 } 1591 1592 return result; 1593 } 1594 1595 bool KeychainImpl::keychainMigration(const string oldPath, const uint32 dbBlobVersion, const string newPath, const uint32 newBlobVersion, const AccessCredentials *cred) { 1596 secnotice("integrity", "going to migrate %s at version %d to", oldPath.c_str(), dbBlobVersion); 1597 secnotice("integrity", " %s at version %d", newPath.c_str(), newBlobVersion); 1598 1599 // We need to opportunistically perform the upgrade/reload dance. 1600 // 1601 // If the keychain is unlocked, try to upgrade it. 1602 // In either case, reload the database from disk. 1603 1604 // Try to grab the keychain mutex (although we should already have it) 1605 StLock<Mutex>_(mMutex); 1606 1607 // Take the file lock on the existing database. We don't need to commit this txion, because we're not planning to 1608 // change the original keychain. 1609 FileLockTransaction fileLockmDb(mDb); 1610 1611 // Let's reload this keychain to see if someone changed it on disk 1612 globals().storageManager.reloadKeychain(this); 1613 1614 bool result = false; 1615 1616 try { 1617 // We can only attempt an upgrade if the keychain is currently unlocked 1618 // There's a TOCTTOU issue here, but it's going to be rare in practice, and the upgrade will simply fail. 1619 if(!mDb->isLocked()) { 1620 secnotice("integrity", "have a plan to migrate database %s", mDb->name()); 1621 // Database blob is out of date. Attempt a migration. 1622 uint32 convertedVersion = attemptKeychainMigration(oldPath, dbBlobVersion, newPath, newBlobVersion, cred); 1623 if(convertedVersion == newBlobVersion) { 1624 secnotice("integrity", "conversion succeeded"); 1625 result = true; 1626 } else { 1627 secnotice("integrity", "conversion failed, keychain is still %d", convertedVersion); 1628 } 1629 } else { 1630 secnotice("integrity", "keychain is locked, can't upgrade"); 1631 } 1632 } catch (CssmError cssme) { 1633 const char* errStr = cssmErrorString(cssme.error); 1634 secnotice("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr); 1635 } catch (...) { 1636 // Something went wrong, but don't worry about it. 1637 secnotice("integrity", "caught unknown error"); 1638 } 1639 1640 // No matter if the migrator succeeded, we need to reload this keychain from disk. 1641 secnotice("integrity", "reloading keychain after migration"); 1642 globals().storageManager.reloadKeychain(this); 1643 secnotice("integrity", "database %s is now version %d", mDb->name(), mDb->dbBlobVersion()); 1644 1645 return result; 1646 } 1647 1648 // Make sure you have this keychain's mutex and write lock when you call this function! 1649 uint32 KeychainImpl::attemptKeychainMigration(const string oldPath, const uint32 oldBlobVersion, const string newPath, const uint32 newBlobVersion, const AccessCredentials* cred) { 1650 if(mDb->dbBlobVersion() == newBlobVersion) { 1651 // Someone else upgraded this, hurray! 1652 secnotice("integrity", "reloaded keychain version %d, quitting", mDb->dbBlobVersion()); 1653 return newBlobVersion; 1654 } 1655 1656 mAttemptedUpgrade = true; 1657 uint32 newDbVersion = oldBlobVersion; 1658 1659 if( (oldBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0 && newBlobVersion == SecurityServer::DbBlob::version_partition) || 1660 (oldBlobVersion == SecurityServer::DbBlob::version_partition && newBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0 && cred != NULL)) { 1661 // Here's the upgrade outline: 1662 // 1663 // 1. Make a copy of the keychain with the new file path 1664 // 2. Open that keychain database. 1665 // 3. Recode it to use the new version. 1666 // 4. Notify the StorageManager that the DLDB identifier for this keychain has changed. 1667 // 1668 // If we're creating a new keychain file, on failure, try to delete the new file. Otherwise, 1669 // everyone will try to use it. 1670 1671 secnotice("integrity", "attempting migration from version %d to %d", oldBlobVersion, newBlobVersion); 1672 1673 Db db; 1674 bool newFile = (oldPath != newPath); 1675 1676 try { 1677 DLDbIdentifier dldbi(dlDbIdentifier().ssuid(), newPath.c_str(), dlDbIdentifier().dbLocation()); 1678 if(newFile) { 1679 secnotice("integrity", "creating a new keychain at %s", newPath.c_str()); 1680 db = mDb->cloneTo(dldbi); 1681 } else { 1682 secnotice("integrity", "using old keychain at %s", newPath.c_str()); 1683 db = mDb; 1684 } 1685 FileLockTransaction fileLockDb(db); 1686 1687 if(newFile) { 1688 // since we're creating a completely new file, if this migration fails, delete the new file 1689 fileLockDb.setDeleteOnFailure(); 1690 } 1691 1692 // Let the upgrade begin. 1693 newDbVersion = db->recodeDbToVersion(newBlobVersion); 1694 if(newDbVersion != newBlobVersion) { 1695 // Recoding failed. Don't proceed. 1696 secnotice("integrity", "recodeDbToVersion failed, version is still %d", newDbVersion); 1697 return newDbVersion; 1698 } 1699 1700 secnotice("integrity", "recoded db successfully, adding extra integrity"); 1701 1702 Keychain keychain(db); 1703 1704 // Breaking abstraction, but what're you going to do? 1705 // Don't upgrade this keychain, since we just upgraded the DB 1706 // But the DB won't return any new data until the txion commits 1707 keychain->mAttemptedUpgrade = true; 1708 keychain->mSuppressTickle = true; 1709 1710 SecItemClass classes[] = {kSecGenericPasswordItemClass, 1711 kSecInternetPasswordItemClass, 1712 kSecPublicKeyItemClass, 1713 kSecPrivateKeyItemClass, 1714 kSecSymmetricKeyItemClass}; 1715 1716 for(int i = 0; i < sizeof(classes) / sizeof(classes[0]); i++) { 1717 Item item; 1718 KCCursor kcc = keychain->createCursor(classes[i], NULL); 1719 1720 // During recoding, we might have deleted some corrupt keys. 1721 // Because of this, we might have zombie SSGroup records left in 1722 // the database that have no matching key. Tell the KCCursor to 1723 // delete these if found. 1724 // This will also try to suppress any other invalid items. 1725 kcc->setDeleteInvalidRecords(true); 1726 1727 while(kcc->next(item)) { 1728 try { 1729 if(newBlobVersion == SecurityServer::DbBlob::version_partition) { 1730 // Force the item to set integrity. The keychain is confused about its version because it hasn't written to disk yet, 1731 // but if we've reached this point, the keychain supports integrity. 1732 item->setIntegrity(true); 1733 } else if(newBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0) { 1734 // We're downgrading this keychain. Pass in whatever credentials our caller thinks will allow this ACL modification. 1735 item->removeIntegrity(cred); 1736 } 1737 } catch(CssmError cssme) { 1738 // During recoding, we might have deleted some corrupt keys. Because of this, we might have zombie SSGroup records left in 1739 // the database that have no matching key. If we get a DL_RECORD_NOT_FOUND error, delete the matching item record. 1740 if (cssme.osStatus() == CSSMERR_DL_RECORD_NOT_FOUND) { 1741 secnotice("integrity", "deleting corrupt (Not Found) record"); 1742 keychain->deleteItem(item); 1743 } else if(cssme.osStatus() == CSSMERR_CSP_INVALID_KEY) { 1744 secnotice("integrity", "deleting corrupt key record"); 1745 keychain->deleteItem(item); 1746 } else { 1747 throw; 1748 } 1749 } 1750 } 1751 } 1752 1753 // Tell securityd we're done with the upgrade, to re-enable all protections 1754 db->recodeFinished(); 1755 1756 // If we reach here, tell the file locks to commit the transaction and return the new blob version 1757 fileLockDb.success(); 1758 1759 secnotice("integrity", "success, returning version %d", newDbVersion); 1760 return newDbVersion; 1761 } catch(UnixError ue) { 1762 secnotice("integrity", "caught UnixError: %d %s", ue.unixError(), ue.what()); 1763 } catch (CssmError cssme) { 1764 const char* errStr = cssmErrorString(cssme.error); 1765 secnotice("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr); 1766 } catch (MacOSError mose) { 1767 secnotice("integrity", "MacOSError: %d", (int)mose.osStatus()); 1768 } catch (const std::bad_cast & e) { 1769 secnotice("integrity", "***** bad cast: %s", e.what()); 1770 } catch (...) { 1771 // We failed to migrate. We won't commit the transaction, so the blob on-disk stays the same. 1772 secnotice("integrity", "***** unknown error"); 1773 } 1774 } else { 1775 secnotice("integrity", "no migration path for %s at version %d to", oldPath.c_str(), oldBlobVersion); 1776 secnotice("integrity", " %s at version %d", newPath.c_str(), newBlobVersion); 1777 return oldBlobVersion; 1778 } 1779 1780 // If we reached here, the migration failed. Return the old version. 1781 return oldBlobVersion; 1782 } 1783 1784 void KeychainImpl::attemptKeychainRename(const string oldPath, const string newPath, uint32 blobVersion) { 1785 secnotice("integrity", "attempting to rename keychain (%d) from %s to %s", blobVersion, oldPath.c_str(), newPath.c_str()); 1786 1787 // Take the file lock on this database, so other people won't try to move it before we do 1788 // NOTE: during a migration from a v256 to a v512 keychain, the db is first copied from the .keychain to the 1789 // .keychain-db path. Other non-migrating processes, if they open the keychain, enter this function to 1790 // try to move it back. These will attempt to take the .keychain-db file lock, but they will not succeed 1791 // until the migration is finished. Once they acquire that, they might try to take the .keychain file lock. 1792 // This is technically lock inversion, but deadlocks will not happen since the migrating process creates the 1793 // .keychain-db file lock before creating the .keychain-db file, so other processes will not try to grab the 1794 // .keychain-db lock in this function before the migrating process already has it. 1795 FileLockTransaction fileLockmDb(mDb); 1796 1797 // first, check if someone renamed this keychain while we were grabbing the file lock 1798 globals().storageManager.reloadKeychain(this); 1799 1800 uint32 dbBlobVersion = SecurityServer::DbBlob::version_MacOS_10_0; 1801 1802 try { 1803 dbBlobVersion = mDb->dbBlobVersion(); 1804 } catch (...) { 1805 secnotice("integrity", "dbBlobVersion() failed for an unknown reason while renaming, aborting rename"); 1806 return; 1807 } 1808 1809 if(dbBlobVersion != blobVersion) { 1810 secnotice("integrity", "database version changed while we were grabbing the file lock; aborting rename"); 1811 return; 1812 } 1813 1814 if(oldPath != mDb->name()) { 1815 secnotice("integrity", "database location changed while we were grabbing the file lock; aborting rename"); 1816 return; 1817 } 1818 1819 // we're still at the original location and version; go ahead and do the move 1820 globals().storageManager.rename(this, newPath.c_str()); 1821 } 1822 1823 Keychain::Keychain() 1824 { 1825 dispatch_once(&SecKeychainSystemKeychainChecked, ^{ 1826 check_system_keychain(); 1827 }); 1828 } 1829 1830 Keychain::~Keychain() 1831 { 1832 } 1833 1834 1835 1836 Keychain 1837 Keychain::optional(SecKeychainRef handle) 1838 { 1839 if (handle) 1840 return KeychainImpl::required(handle); 1841 else 1842 return globals().storageManager.defaultKeychain(); 1843 } 1844 1845 1846 CFIndex KeychainCore::GetKeychainRetainCount(Keychain& kc) 1847 { 1848 CFTypeRef ref = kc->handle(false); 1849 return CFGetRetainCount(ref); 1850 } 1851 1852 1853 // 1854 // Create default credentials for this keychain. 1855 // This is triggered upon default open (i.e. a Db::activate() with no set credentials). 1856 // 1857 // This function embodies the "default credentials" logic for Keychain-layer databases. 1858 // 1859 const AccessCredentials * 1860 KeychainImpl::makeCredentials() 1861 { 1862 return defaultCredentials(); 1863 } 1864 1865 1866 const AccessCredentials * 1867 KeychainImpl::defaultCredentials() 1868 { 1869 StLock<Mutex>_(mMutex); 1870 1871 // Use custom unlock credentials for file keychains which have a referral 1872 // record and the standard credentials for all others. 1873 1874 if (mDb->dl()->guid() == gGuidAppleCSPDL && mCustomUnlockCreds(mDb)) 1875 return &mCustomUnlockCreds; 1876 else 1877 if (mDb->dl()->guid() == gGuidAppleSdCSPDL) 1878 return globals().smartcardCredentials(); 1879 else 1880 return globals().keychainCredentials(); 1881 } 1882 1883 1884 1885 bool KeychainImpl::mayDelete() 1886 { 1887 return true; 1888 } 1889 1890 bool KeychainImpl::hasIntegrityProtection() { 1891 StLock<Mutex>_(mMutex); 1892 1893 // This keychain only supports integrity if there's a database attached, that database is an Apple CSPDL, and the blob version is high enough 1894 if(mDb && (mDb->dl()->guid() == gGuidAppleCSPDL)) { 1895 if(mDb->dbBlobVersion() >= SecurityServer::DbBlob::version_partition) { 1896 return true; 1897 } else { 1898 secnotice("integrity", "keychain blob version does not support integrity"); 1899 return false; 1900 } 1901 } else { 1902 secnotice("integrity", "keychain guid does not support integrity"); 1903 return false; 1904 } 1905 } 1906