1/* 2 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24 25// 26// 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_keychain/SecCFTypes.h> 41 42#include "SecKeychainPriv.h" 43 44#include <Security/SecKeychainItemPriv.h> 45#include <CoreFoundation/CoreFoundation.h> 46#include "DLDbListCFPref.h" 47#include <fcntl.h> 48#include <sys/param.h> 49#include <syslog.h> 50#include <sys/stat.h> 51#include <sys/socket.h> 52#include <sys/un.h> 53#include <sys/types.h> 54#include <sys/time.h> 55 56static dispatch_once_t SecKeychainSystemKeychainChecked; 57 58OSStatus SecKeychainSystemKeychainCheckWouldDeadlock() 59{ 60 dispatch_once(&SecKeychainSystemKeychainChecked, ^{}); 61 return errSecSuccess; 62} 63 64using namespace KeychainCore; 65using namespace CssmClient; 66 67 68typedef struct EventItem 69{ 70 SecKeychainEvent kcEvent; 71 Item item; 72} EventItem; 73 74typedef std::list<EventItem> EventBufferSuper; 75class EventBuffer : public EventBufferSuper 76{ 77public: 78 EventBuffer () {} 79 virtual ~EventBuffer (); 80}; 81 82 83EventBuffer::~EventBuffer () 84{ 85} 86 87 88 89// 90// KeychainSchemaImpl 91// 92KeychainSchemaImpl::KeychainSchemaImpl(const Db &db) : mMutex(Mutex::recursive) 93{ 94 DbCursor relations(db); 95 relations->recordType(CSSM_DL_DB_SCHEMA_INFO); 96 DbAttributes relationRecord(db, 1); 97 relationRecord.add(Schema::RelationID); 98 DbUniqueRecord outerUniqueId(db); 99 100 while (relations->next(&relationRecord, NULL, outerUniqueId)) 101 { 102 DbUniqueRecord uniqueId(db); 103 104 uint32 relationID = relationRecord.at(0); 105 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID 106 && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END) 107 continue; 108 109 // Create a cursor on the SCHEMA_ATTRIBUTES table for records with 110 // RelationID == relationID 111 DbCursor attributes(db); 112 attributes->recordType(CSSM_DL_DB_SCHEMA_ATTRIBUTES); 113 attributes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID); 114 115 // Set up a record for retriving the SCHEMA_ATTRIBUTES 116 DbAttributes attributeRecord(db, 2); 117 attributeRecord.add(Schema::AttributeFormat); 118 attributeRecord.add(Schema::AttributeID); 119 120 RelationInfoMap &rim = mDatabaseInfoMap[relationID]; 121 while (attributes->next(&attributeRecord, NULL, uniqueId)) 122 rim[attributeRecord.at(1)] = attributeRecord.at(0); 123 124 // Create a cursor on the CSSM_DL_DB_SCHEMA_INDEXES table for records 125 // with RelationID == relationID 126 DbCursor indexes(db); 127 indexes->recordType(CSSM_DL_DB_SCHEMA_INDEXES); 128 indexes->conjunctive(CSSM_DB_AND); 129 indexes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID); 130 indexes->add(CSSM_DB_EQUAL, Schema::IndexType, 131 uint32(CSSM_DB_INDEX_UNIQUE)); 132 133 // Set up a record for retriving the SCHEMA_INDEXES 134 DbAttributes indexRecord(db, 1); 135 indexRecord.add(Schema::AttributeID); 136 137 CssmAutoDbRecordAttributeInfo &infos = 138 *new CssmAutoDbRecordAttributeInfo(); 139 mPrimaryKeyInfoMap. 140 insert(PrimaryKeyInfoMap::value_type(relationID, &infos)); 141 infos.DataRecordType = relationID; 142 while (indexes->next(&indexRecord, NULL, uniqueId)) 143 { 144 CssmDbAttributeInfo &info = infos.add(); 145 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; 146 info.Label.AttributeID = indexRecord.at(0); 147 // @@@ Might insert bogus value if DB is corrupt 148 info.AttributeFormat = rim[info.Label.AttributeID]; 149 } 150 } 151} 152 153KeychainSchemaImpl::~KeychainSchemaImpl() 154{ 155 try 156 { 157 map<CSSM_DB_RECORDTYPE, CssmAutoDbRecordAttributeInfo *>::iterator it = mPrimaryKeyInfoMap.begin(); 158 while (it != mPrimaryKeyInfoMap.end()) 159 { 160 delete it->second; 161 it++; 162 } 163 // for_each_map_delete(mPrimaryKeyInfoMap.begin(), mPrimaryKeyInfoMap.end()); 164 } 165 catch(...) 166 { 167 } 168} 169 170const KeychainSchemaImpl::RelationInfoMap & 171KeychainSchemaImpl::relationInfoMapFor(CSSM_DB_RECORDTYPE recordType) const 172{ 173 DatabaseInfoMap::const_iterator dit = mDatabaseInfoMap.find(recordType); 174 if (dit == mDatabaseInfoMap.end()) 175 MacOSError::throwMe(errSecNoSuchClass); 176 return dit->second; 177} 178 179bool KeychainSchemaImpl::hasRecordType (CSSM_DB_RECORDTYPE recordType) const 180{ 181 DatabaseInfoMap::const_iterator it = mDatabaseInfoMap.find(recordType); 182 return it != mDatabaseInfoMap.end(); 183} 184 185bool 186KeychainSchemaImpl::hasAttribute(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const 187{ 188 try 189 { 190 const RelationInfoMap &rmap = relationInfoMapFor(recordType); 191 RelationInfoMap::const_iterator rit = rmap.find(attributeId); 192 return rit != rmap.end(); 193 } 194 catch (MacOSError result) 195 { 196 if (result.osStatus () == errSecNoSuchClass) 197 { 198 return false; 199 } 200 else 201 { 202 throw; 203 } 204 } 205} 206 207CSSM_DB_ATTRIBUTE_FORMAT 208KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const 209{ 210 const RelationInfoMap &rmap = relationInfoMapFor(recordType); 211 RelationInfoMap::const_iterator rit = rmap.find(attributeId); 212 if (rit == rmap.end()) 213 MacOSError::throwMe(errSecNoSuchAttr); 214 215 return rit->second; 216} 217 218CssmDbAttributeInfo 219KeychainSchemaImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const 220{ 221 CSSM_DB_ATTRIBUTE_INFO info; 222 info.AttributeFormat = attributeFormatFor(recordType, attributeId); 223 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; 224 info.Label.AttributeID = attributeId; 225 226 return info; 227} 228 229void 230KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType, SecKeychainAttributeInfo **Info) const 231{ 232 const RelationInfoMap &rmap = relationInfoMapFor(recordType); 233 234 SecKeychainAttributeInfo *theList=reinterpret_cast<SecKeychainAttributeInfo *>(malloc(sizeof(SecKeychainAttributeInfo))); 235 236 size_t capacity=rmap.size(); 237 UInt32 *tagBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32))); 238 UInt32 *formatBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32))); 239 UInt32 i=0; 240 241 242 for (RelationInfoMap::const_iterator rit = rmap.begin(); rit != rmap.end(); ++rit) 243 { 244 if (i>=capacity) 245 { 246 capacity *= 2; 247 if (capacity <= i) capacity = i + 1; 248 tagBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32)))); 249 formatBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32)))); 250 } 251 tagBuf[i]=rit->first; 252 formatBuf[i++]=rit->second; 253 } 254 255 theList->count=i; 256 theList->tag=tagBuf; 257 theList->format=formatBuf; 258 *Info=theList; 259} 260 261 262const CssmAutoDbRecordAttributeInfo & 263KeychainSchemaImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) const 264{ 265 PrimaryKeyInfoMap::const_iterator it; 266 it = mPrimaryKeyInfoMap.find(recordType); 267 268 if (it == mPrimaryKeyInfoMap.end()) 269 MacOSError::throwMe(errSecNoSuchClass); // @@@ Not really but whatever. 270 271 return *it->second; 272} 273 274bool 275KeychainSchemaImpl::operator <(const KeychainSchemaImpl &other) const 276{ 277 return mDatabaseInfoMap < other.mDatabaseInfoMap; 278} 279 280bool 281KeychainSchemaImpl::operator ==(const KeychainSchemaImpl &other) const 282{ 283 return mDatabaseInfoMap == other.mDatabaseInfoMap; 284} 285 286void 287KeychainSchemaImpl::didCreateRelation(CSSM_DB_RECORDTYPE relationID, 288 const char *inRelationName, 289 uint32 inNumberOfAttributes, 290 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *pAttributeInfo, 291 uint32 inNumberOfIndexes, 292 const CSSM_DB_SCHEMA_INDEX_INFO *pIndexInfo) 293{ 294 StLock<Mutex>_(mMutex); 295 296 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID 297 && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END) 298 return; 299 300 // if our schema is already in the map, return 301 if (mPrimaryKeyInfoMap.find(relationID) != mPrimaryKeyInfoMap.end()) 302 { 303 return; 304 } 305 306 RelationInfoMap &rim = mDatabaseInfoMap[relationID]; 307 for (uint32 ix = 0; ix < inNumberOfAttributes; ++ix) 308 rim[pAttributeInfo[ix].AttributeId] = pAttributeInfo[ix].DataType; 309 310 CssmAutoDbRecordAttributeInfo *infos = new CssmAutoDbRecordAttributeInfo(); 311 312 mPrimaryKeyInfoMap. 313 insert(PrimaryKeyInfoMap::value_type(relationID, infos)); 314 infos->DataRecordType = relationID; 315 for (uint32 ix = 0; ix < inNumberOfIndexes; ++ix) 316 if (pIndexInfo[ix].IndexType == CSSM_DB_INDEX_UNIQUE) 317 { 318 CssmDbAttributeInfo &info = infos->add(); 319 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; 320 info.Label.AttributeID = pIndexInfo[ix].AttributeId; 321 info.AttributeFormat = rim[info.Label.AttributeID]; 322 } 323} 324 325 326 327KeychainSchema::~KeychainSchema() 328 329{ 330} 331 332 333 334struct Event 335{ 336 SecKeychainEvent eventCode; 337 PrimaryKey primaryKey; 338}; 339typedef std::list<Event> EventList; 340 341#define SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME "/var/run/systemkeychaincheck" 342#define SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".socket") 343#define SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".done") 344 345static void check_system_keychain() 346{ 347 // sadly we can't use XPC here, XPC_DOMAIN_TYPE_SYSTEM doesn't exist yet. Also xpc-helper uses the 348 // keychain API (I assume for checking codesign things). So we use Unix Domain Sockets. 349 350 // NOTE: if we hit a system error we attempt to log it, and then just don't check the system keychain. 351 // In theory a system might be able to recover from this state if we let it try to muddle along, and 352 // past behaviour didn't even try this hard to do the keychain check. In particular we could be in a 353 // sandbox'ed process. So we just do our best and let another process try again. 354 355 struct stat keycheck_file_info; 356 if (stat(SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME, &keycheck_file_info) < 0) { 357 int server_fd = socket(PF_UNIX, SOCK_STREAM, 0); 358 if (server_fd < 0) { 359 syslog(LOG_ERR, "Can't get socket (%m) system keychain may be unchecked"); 360 return; 361 } 362 363 struct sockaddr_un keychain_check_server_address; 364 keychain_check_server_address.sun_family = AF_UNIX; 365 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)) { 366 // It would be nice if we could compile time assert this 367 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)); 368 close(server_fd); 369 return; 370 } 371 keychain_check_server_address.sun_len = SUN_LEN(&keychain_check_server_address); 372 373 int rc = connect(server_fd, (struct sockaddr *)&keychain_check_server_address, keychain_check_server_address.sun_len); 374 if (rc < 0) { 375 syslog(LOG_ERR, "Can not connect to %s: %m", SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME); 376 close(server_fd); 377 return; 378 } 379 380 // 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) 381 char byte; 382 ssize_t read_size = read(server_fd, &byte, 1); 383 if (read_size < 0) { 384 syslog(LOG_ERR, "Error reading from system keychain checker: %m"); 385 } 386 387 close(server_fd); 388 return; 389 } 390} 391 392// 393// KeychainImpl 394// 395KeychainImpl::KeychainImpl(const Db &db) 396 : mInCache(false), mDb(db), mCustomUnlockCreds (this), mIsInBatchMode (false), mMutex(Mutex::recursive) 397{ 398 dispatch_once(&SecKeychainSystemKeychainChecked, ^{ 399 check_system_keychain(); 400 }); 401 mDb->defaultCredentials(this); // install activation hook 402 mEventBuffer = new EventBuffer; 403} 404 405KeychainImpl::~KeychainImpl() 406{ 407 try 408 { 409 // Remove ourselves from the cache if we are in it. 410 // fprintf(stderr, "Removing %p from storage manager cache.\n", handle(false)); 411 globals().storageManager.removeKeychain(dlDbIdentifier(), this); 412 delete mEventBuffer; 413 } 414 catch(...) 415 { 416 } 417} 418 419Mutex* 420KeychainImpl::getMutexForObject() 421{ 422 return globals().storageManager.getStorageManagerMutex(); 423} 424 425Mutex* 426KeychainImpl::getKeychainMutex() 427{ 428 return &mMutex; 429} 430 431void KeychainImpl::aboutToDestruct() 432{ 433 // remove me from the global cache, we are done 434 // fprintf(stderr, "Destructing keychain object\n"); 435 DLDbIdentifier identifier = dlDbIdentifier(); 436 globals().storageManager.removeKeychain(identifier, this); 437} 438 439bool 440KeychainImpl::operator ==(const KeychainImpl &keychain) const 441{ 442 return dlDbIdentifier() == keychain.dlDbIdentifier(); 443} 444 445KCCursor 446KeychainImpl::createCursor(SecItemClass itemClass, const SecKeychainAttributeList *attrList) 447{ 448 StLock<Mutex>_(mMutex); 449 450 StorageManager::KeychainList keychains; 451 keychains.push_back(Keychain(this)); 452 return KCCursor(keychains, itemClass, attrList); 453} 454 455KCCursor 456KeychainImpl::createCursor(const SecKeychainAttributeList *attrList) 457{ 458 StLock<Mutex>_(mMutex); 459 460 StorageManager::KeychainList keychains; 461 keychains.push_back(Keychain(this)); 462 return KCCursor(keychains, attrList); 463} 464 465void 466KeychainImpl::create(UInt32 passwordLength, const void *inPassword) 467{ 468 StLock<Mutex>_(mMutex); 469 470 if (!inPassword) 471 { 472 create(); 473 return; 474 } 475 476 Allocator &alloc = Allocator::standard(); 477 478 // @@@ Share this instance 479 480 const CssmData password(const_cast<void *>(inPassword), passwordLength); 481 AclFactory::PasswordChangeCredentials pCreds (password, alloc); 482 AclFactory::AnyResourceContext rcc(pCreds); 483 create(&rcc); 484} 485 486void KeychainImpl::create(ConstStringPtr inPassword) 487{ 488 StLock<Mutex>_(mMutex); 489 490 if ( inPassword ) 491 create(static_cast<UInt32>(inPassword[0]), &inPassword[1]); 492 else 493 create(); 494} 495 496void 497KeychainImpl::create() 498{ 499 StLock<Mutex>_(mMutex); 500 501 AclFactory aclFactory; 502 AclFactory::AnyResourceContext rcc(aclFactory.unlockCred()); 503 create(&rcc); 504} 505 506void KeychainImpl::createWithBlob(CssmData &blob) 507{ 508 StLock<Mutex>_(mMutex); 509 510 mDb->dbInfo(&Schema::DBInfo); 511 AclFactory aclFactory; 512 AclFactory::AnyResourceContext rcc(aclFactory.unlockCred()); 513 mDb->resourceControlContext (&rcc); 514 try 515 { 516 mDb->createWithBlob(blob); 517 } 518 catch (...) 519 { 520 mDb->resourceControlContext(NULL); 521 mDb->dbInfo(NULL); 522 throw; 523 } 524 mDb->resourceControlContext(NULL); 525 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later) 526 globals().storageManager.created(Keychain(this)); 527 528 KCEventNotifier::PostKeychainEvent (kSecKeychainListChangedEvent, this, NULL); 529} 530 531void 532KeychainImpl::create(const ResourceControlContext *rcc) 533{ 534 StLock<Mutex>_(mMutex); 535 536 mDb->dbInfo(&Schema::DBInfo); // Set the schema (to force a create) 537 mDb->resourceControlContext(rcc); 538 try 539 { 540 mDb->create(); 541 } 542 catch (...) 543 { 544 mDb->resourceControlContext(NULL); 545 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later) 546 throw; 547 } 548 mDb->resourceControlContext(NULL); 549 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later) 550 globals().storageManager.created(Keychain(this)); 551} 552 553void 554KeychainImpl::open() 555{ 556 StLock<Mutex>_(mMutex); 557 558 mDb->open(); 559} 560 561void 562KeychainImpl::lock() 563{ 564 StLock<Mutex>_(mMutex); 565 566 mDb->lock(); 567} 568 569void 570KeychainImpl::unlock() 571{ 572 StLock<Mutex>_(mMutex); 573 574 mDb->unlock(); 575} 576 577void 578KeychainImpl::unlock(const CssmData &password) 579{ 580 StLock<Mutex>_(mMutex); 581 582 mDb->unlock(password); 583} 584 585void 586KeychainImpl::unlock(ConstStringPtr password) 587{ 588 StLock<Mutex>_(mMutex); 589 590 if (password) 591 { 592 const CssmData data(const_cast<unsigned char *>(&password[1]), password[0]); 593 unlock(data); 594 } 595 else 596 unlock(); 597} 598 599void 600KeychainImpl::stash() 601{ 602 StLock<Mutex>_(mMutex); 603 604 mDb->stash(); 605} 606 607void 608KeychainImpl::stashCheck() 609{ 610 StLock<Mutex>_(mMutex); 611 612 mDb->stashCheck(); 613} 614 615void 616KeychainImpl::getSettings(uint32 &outIdleTimeOut, bool &outLockOnSleep) 617{ 618 StLock<Mutex>_(mMutex); 619 620 mDb->getSettings(outIdleTimeOut, outLockOnSleep); 621} 622 623void 624KeychainImpl::setSettings(uint32 inIdleTimeOut, bool inLockOnSleep) 625{ 626 StLock<Mutex>_(mMutex); 627 628 // The .Mac syncing code only makes sense for the AppleFile CSP/DL, 629 // but other DLs such as the OCSP and LDAP DLs do not expose a way to 630 // change settings or the password. To make a minimal change that only affects 631 // the smartcard case, we only look for that CSP/DL 632 633 bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL); 634 635 // get the old keychain blob so that we can tell .Mac to resync it 636 CssmAutoData oldBlob(mDb ->allocator()); 637 if (!isSmartcard) 638 mDb->copyBlob(oldBlob.get()); 639 640 mDb->setSettings(inIdleTimeOut, inLockOnSleep); 641} 642 643void 644KeychainImpl::changePassphrase(UInt32 oldPasswordLength, const void *oldPassword, 645 UInt32 newPasswordLength, const void *newPassword) 646{ 647 StLock<Mutex>_(mMutex); 648 649 bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL); 650 651 TrackingAllocator allocator(Allocator::standard()); 652 AutoCredentials cred = AutoCredentials(allocator); 653 if (oldPassword) 654 { 655 const CssmData &oldPass = *new(allocator) CssmData(const_cast<void *>(oldPassword), oldPasswordLength); 656 TypedList &oldList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK); 657 oldList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD)); 658 oldList.append(new(allocator) ListElement(oldPass)); 659 cred += oldList; 660 } 661 662 if (newPassword) 663 { 664 const CssmData &newPass = *new(allocator) CssmData(const_cast<void *>(newPassword), newPasswordLength); 665 TypedList &newList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK); 666 newList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD)); 667 newList.append(new(allocator) ListElement(newPass)); 668 cred += newList; 669 } 670 671 // get the old keychain blob so that we can tell .Mac to resync it 672 CssmAutoData oldBlob(mDb->allocator()); 673 if (!isSmartcard) 674 mDb->copyBlob(oldBlob.get()); 675 676 mDb->changePassphrase(&cred); 677} 678 679void 680KeychainImpl::changePassphrase(ConstStringPtr oldPassword, ConstStringPtr newPassword) 681{ 682 StLock<Mutex>_(mMutex); 683 684 const void *oldPtr, *newPtr; 685 UInt32 oldLen, newLen; 686 if (oldPassword) 687 { 688 oldLen = oldPassword[0]; 689 oldPtr = oldPassword + 1; 690 } 691 else 692 { 693 oldLen = 0; 694 oldPtr = NULL; 695 } 696 697 if (newPassword) 698 { 699 newLen = newPassword[0]; 700 newPtr = newPassword + 1; 701 } 702 else 703 { 704 newLen = 0; 705 newPtr = NULL; 706 } 707 708 changePassphrase(oldLen, oldPtr, newLen, newPtr); 709} 710 711void 712KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS *cred) 713{ 714 StLock<Mutex>_(mMutex); 715 716 if (!exists()) 717 MacOSError::throwMe(errSecNoSuchKeychain); 718 719 MacOSError::throwMe(errSecUnimplemented); 720} 721 722UInt32 723KeychainImpl::status() const 724{ 725 // @@@ We should figure out the read/write status though a DL passthrough 726 // or some other way. Also should locked be unlocked read only or just 727 // read-only? 728 return (mDb->isLocked() ? 0 : kSecUnlockStateStatus | kSecWritePermStatus) 729 | kSecReadPermStatus; 730} 731 732bool 733KeychainImpl::exists() 734{ 735 StLock<Mutex>_(mMutex); 736 737 bool exists = true; 738 try 739 { 740 open(); 741 // Ok to leave the mDb open since it will get closed when it goes away. 742 } 743 catch (const CssmError &e) 744 { 745 if (e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST) 746 throw; 747 exists = false; 748 } 749 750 return exists; 751} 752 753bool 754KeychainImpl::isActive() const 755{ 756 return mDb->isActive(); 757} 758 759void KeychainImpl::completeAdd(Item &inItem, PrimaryKey &primaryKey) 760{ 761 762 763 // The inItem shouldn't be in the cache yet 764 assert(!inItem->inCache()); 765 766 // Insert inItem into mDbItemMap with key primaryKey. p.second will be 767 // true if it got inserted. If not p.second will be false and p.first 768 // will point to the current entry with key primaryKey. 769 pair<DbItemMap::iterator, bool> p = 770 mDbItemMap.insert(DbItemMap::value_type(primaryKey, inItem.get())); 771 if (!p.second) 772 { 773 // There was already an ItemImpl * in mDbItemMap with key 774 // primaryKey. Get a ref to the pointer to it so we can assign a 775 // new value to it below. 776 ItemImpl *oldItem = p.first->second; 777 778 // @@@ If this happens we are breaking our API contract of 779 // uniquifying items. We really need to insert the item into the 780 // map before we start the add. And have the item be in an 781 // "is being added" state. 782 assert(oldItem->inCache()); 783 secdebug("keychain", "add of new item %p somehow replaced %p", 784 inItem.get(), oldItem); 785 oldItem->inCache(false); 786 oldItem = inItem.get(); 787 } 788 789 inItem->inCache(true); 790} 791 792void 793KeychainImpl::addCopy(Item &inItem) 794{ 795 Keychain keychain(this); 796 PrimaryKey primaryKey = inItem->addWithCopyInfo(keychain, true); 797 completeAdd(inItem, primaryKey); 798 postEvent(kSecAddEvent, inItem); 799} 800 801void 802KeychainImpl::add(Item &inItem) 803{ 804 Keychain keychain(this); 805 PrimaryKey primaryKey = inItem->add(keychain); 806 completeAdd(inItem, primaryKey); 807 postEvent(kSecAddEvent, inItem); 808} 809 810void 811KeychainImpl::didUpdate(const Item &inItem, PrimaryKey &oldPK, 812 PrimaryKey &newPK) 813{ 814 // If the primary key hasn't changed we don't need to update mDbItemMap. 815 if (oldPK != newPK) 816 { 817 // If inItem isn't in the cache we don't need to update mDbItemMap. 818 assert(inItem->inCache()); 819 if (inItem->inCache()) 820 { 821 // First remove the entry for inItem in mDbItemMap with key oldPK. 822 DbItemMap::iterator it = mDbItemMap.find(oldPK); 823 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItem.get()) 824 mDbItemMap.erase(it); 825 826 // Insert inItem into mDbItemMap with key newPK. p.second will be 827 // true if it got inserted. If not p.second will be false and 828 // p.first will point to the current entry with key newPK. 829 pair<DbItemMap::iterator, bool> p = 830 mDbItemMap.insert(DbItemMap::value_type(newPK, inItem.get())); 831 if (!p.second) 832 { 833 // There was already an ItemImpl * in mDbItemMap with key 834 // primaryKey. Get a ref to the pointer to it so we can assign 835 // a new value to it below. 836 ItemImpl *oldItem = p.first->second; 837 838 // @@@ If this happens we are breaking our API contract of 839 // uniquifying items. We really need to insert the item into 840 // the map with the new primary key before we start the update. 841 // And have the item be in an "is being updated" state. 842 assert(oldItem->inCache()); 843 secdebug("keychain", "update of item %p somehow replaced %p", 844 inItem.get(), oldItem); 845 oldItem->inCache(false); 846 oldItem = inItem.get(); 847 } 848 } 849 } 850 851 postEvent(kSecUpdateEvent, inItem); 852} 853 854void 855KeychainImpl::deleteItem(Item &inoutItem) 856{ 857 { 858 // We don't need to hold the DO mutex through event posting, and, in fact, doing so causes deadlock. 859 // Hold it only as long as needed, instead. 860 861 862 // item must be persistent 863 if (!inoutItem->isPersistent()) 864 MacOSError::throwMe(errSecInvalidItemRef); 865 866 DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord(); 867 PrimaryKey primaryKey = inoutItem->primaryKey(); 868 uniqueId->deleteRecord(); 869 870 // Don't remove the item from the mDbItemMap here since this would cause 871 // us to report a new item to our caller when we receive the 872 // kSecDeleteEvent notification. 873 // It will be removed before we post the notification, because 874 // CCallbackMgr will call didDeleteItem() 875 876 // Post the notification for the item deletion with 877 // the primaryKey obtained when the item still existed 878 } 879 880 postEvent(kSecDeleteEvent, inoutItem); 881} 882 883 884CssmClient::CSP 885KeychainImpl::csp() 886{ 887 StLock<Mutex>_(mMutex); 888 889 if (!mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP) 890 MacOSError::throwMe(errSecInvalidKeychain); 891 892 // Try to cast first to a CSPDL to handle case where we don't have an SSDb 893 try 894 { 895 CssmClient::CSPDL cspdl(dynamic_cast<CssmClient::CSPDLImpl *>(&*mDb->dl())); 896 return CSP(cspdl); 897 } 898 catch (...) 899 { 900 SSDbImpl* impl = dynamic_cast<SSDbImpl *>(&(*mDb)); 901 if (impl == NULL) 902 { 903 CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); 904 } 905 906 SSDb ssDb(impl); 907 return ssDb->csp(); 908 } 909} 910 911PrimaryKey 912KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId) 913{ 914 StLock<Mutex>_(mMutex); 915 916 DbAttributes primaryKeyAttrs(uniqueId->database()); 917 primaryKeyAttrs.recordType(recordType); 918 gatherPrimaryKeyAttributes(primaryKeyAttrs); 919 uniqueId->get(&primaryKeyAttrs, NULL); 920 return PrimaryKey(primaryKeyAttrs); 921} 922 923const CssmAutoDbRecordAttributeInfo & 924KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) 925{ 926 StLock<Mutex>_(mMutex); 927 928 try 929 { 930 return keychainSchema()->primaryKeyInfosFor(recordType); 931 } 932 catch (const CommonError &error) 933 { 934 switch (error.osStatus()) 935 { 936 case errSecNoSuchClass: 937 case CSSMERR_DL_INVALID_RECORDTYPE: 938 resetSchema(); 939 return keychainSchema()->primaryKeyInfosFor(recordType); 940 default: 941 throw; 942 } 943 } 944} 945 946void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes& primaryKeyAttrs) 947{ 948 StLock<Mutex>_(mMutex); 949 950 const CssmAutoDbRecordAttributeInfo &infos = 951 primaryKeyInfosFor(primaryKeyAttrs.recordType()); 952 953 // @@@ fix this to not copy info. 954 for (uint32 i = 0; i < infos.size(); i++) 955 primaryKeyAttrs.add(infos.at(i)); 956} 957 958ItemImpl * 959KeychainImpl::_lookupItem(const PrimaryKey &primaryKey) 960{ 961 DbItemMap::iterator it = mDbItemMap.find(primaryKey); 962 if (it != mDbItemMap.end()) 963 { 964 if (it->second == NULL) 965 { 966 // we've been weak released... 967 mDbItemMap.erase(it); 968 } 969 else 970 { 971 return it->second; 972 } 973 } 974 975 return NULL; 976} 977 978Item 979KeychainImpl::item(const PrimaryKey &primaryKey) 980{ 981 StLock<Mutex>_(mMutex); 982 983 // Lookup the item in the map while holding the apiLock. 984 ItemImpl *itemImpl = _lookupItem(primaryKey); 985 if (itemImpl) 986 return Item(itemImpl); 987 988 try 989 { 990 // We didn't find it so create a new item with just a keychain and 991 // a primary key. However since we aren't holding 992 // globals().apiLock anymore some other thread might have beaten 993 // us to creating this item and adding it to the cache. If that 994 // happens we retry the lookup. 995 return Item(this, primaryKey); 996 } 997 catch (const MacOSError &e) 998 { 999 // If the item creation failed because some other thread already 1000 // inserted this item into the cache we retry the lookup. 1001 if (e.osStatus() == errSecDuplicateItem) 1002 { 1003 // Lookup the item in the map while holding the apiLock. 1004 ItemImpl *itemImpl = _lookupItem(primaryKey); 1005 if (itemImpl) 1006 return Item(itemImpl); 1007 } 1008 throw; 1009 } 1010} 1011 1012 1013Item 1014KeychainImpl::item(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId) 1015{ 1016 StLock<Mutex>_(mMutex); 1017 1018 PrimaryKey primaryKey = makePrimaryKey(recordType, uniqueId); 1019 { 1020 // Lookup the item in the map while holding the apiLock. 1021 ItemImpl *itemImpl = _lookupItem(primaryKey); 1022 1023 if (itemImpl) 1024 { 1025 return Item(itemImpl); 1026 } 1027 } 1028 1029 try 1030 { 1031 // We didn't find it so create a new item with a keychain, a primary key 1032 // and a DbUniqueRecord. However since we aren't holding 1033 // globals().apiLock anymore some other thread might have beaten 1034 // us to creating this item and adding it to the cache. If that 1035 // happens we retry the lookup. 1036 return Item(this, primaryKey, uniqueId); 1037 } 1038 catch (const MacOSError &e) 1039 { 1040 // If the item creation failed because some other thread already 1041 // inserted this item into the cache we retry the lookup. 1042 if (e.osStatus() == errSecDuplicateItem) 1043 { 1044 // Lookup the item in the map while holding the apiLock. 1045 ItemImpl *itemImpl = _lookupItem(primaryKey); 1046 if (itemImpl) 1047 return Item(itemImpl); 1048 } 1049 throw; 1050 } 1051} 1052 1053KeychainSchema 1054KeychainImpl::keychainSchema() 1055{ 1056 StLock<Mutex>_(mMutex); 1057 if (!mKeychainSchema) 1058 mKeychainSchema = KeychainSchema(mDb); 1059 1060 return mKeychainSchema; 1061} 1062 1063void KeychainImpl::resetSchema() 1064{ 1065 mKeychainSchema = NULL; // re-fetch it from db next time 1066} 1067 1068 1069// Called from DbItemImpl's constructor (so it is only partially constructed), 1070// add it to the map. 1071void 1072KeychainImpl::addItem(const PrimaryKey &primaryKey, ItemImpl *dbItemImpl) 1073{ 1074 StLock<Mutex>_(mMutex); 1075 1076 // The dbItemImpl shouldn't be in the cache yet 1077 assert(!dbItemImpl->inCache()); 1078 1079 // Insert dbItemImpl into mDbItemMap with key primaryKey. p.second will 1080 // be true if it got inserted. If not p.second will be false and p.first 1081 // will point to the current entry with key primaryKey. 1082 pair<DbItemMap::iterator, bool> p = 1083 mDbItemMap.insert(DbItemMap::value_type(primaryKey, dbItemImpl)); 1084 1085 if (!p.second) 1086 { 1087 // There was already an ItemImpl * in mDbItemMap with key primaryKey. 1088 // There is a race condition here when being called in multiple threads 1089 // We might have added an item using add and received a notification at 1090 // the same time. 1091 MacOSError::throwMe(errSecDuplicateItem); 1092 } 1093 1094 dbItemImpl->inCache(true); 1095} 1096 1097void 1098KeychainImpl::didDeleteItem(ItemImpl *inItemImpl) 1099{ 1100 StLock<Mutex>_(mMutex); 1101 1102 // Called by CCallbackMgr 1103 secdebug("kcnotify", "%p notified that item %p was deleted", this, inItemImpl); 1104 removeItem(inItemImpl->primaryKey(), inItemImpl); 1105} 1106 1107void 1108KeychainImpl::removeItem(const PrimaryKey &primaryKey, ItemImpl *inItemImpl) 1109{ 1110 StLock<Mutex>_(mMutex); 1111 1112 // If inItemImpl isn't in the cache to begin with we are done. 1113 if (!inItemImpl->inCache()) 1114 return; 1115 1116 DbItemMap::iterator it = mDbItemMap.find(primaryKey); 1117 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItemImpl) 1118 mDbItemMap.erase(it); 1119 1120 inItemImpl->inCache(false); 1121} 1122 1123void 1124KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID, 1125 SecKeychainAttributeInfo **Info) 1126{ 1127 StLock<Mutex>_(mMutex); 1128 1129 try 1130 { 1131 keychainSchema()->getAttributeInfoForRecordType(itemID, Info); 1132 } 1133 catch (const CommonError &error) 1134 { 1135 switch (error.osStatus()) 1136 { 1137 case errSecNoSuchClass: 1138 case CSSMERR_DL_INVALID_RECORDTYPE: 1139 resetSchema(); 1140 keychainSchema()->getAttributeInfoForRecordType(itemID, Info); 1141 default: 1142 throw; 1143 } 1144 } 1145} 1146 1147void 1148KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo *Info) 1149{ 1150 free(Info->tag); 1151 free(Info->format); 1152 free(Info); 1153} 1154 1155CssmDbAttributeInfo 1156KeychainImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, UInt32 tag) 1157{ 1158 StLock<Mutex>_(mMutex); 1159 1160 try 1161 { 1162 return keychainSchema()->attributeInfoFor(recordType, tag); 1163 } 1164 catch (const CommonError &error) 1165 { 1166 switch (error.osStatus()) 1167 { 1168 case errSecNoSuchClass: 1169 case CSSMERR_DL_INVALID_RECORDTYPE: 1170 resetSchema(); 1171 return keychainSchema()->attributeInfoFor(recordType, tag); 1172 default: 1173 throw; 1174 } 1175 } 1176} 1177 1178void 1179KeychainImpl::recode(const CssmData &data, const CssmData &extraData) 1180{ 1181 StLock<Mutex>_(mMutex); 1182 1183 mDb->recode(data, extraData); 1184} 1185 1186void 1187KeychainImpl::copyBlob(CssmData &data) 1188{ 1189 StLock<Mutex>_(mMutex); 1190 1191 mDb->copyBlob(data); 1192} 1193 1194void 1195KeychainImpl::setBatchMode(Boolean mode, Boolean rollback) 1196{ 1197 StLock<Mutex>_(mMutex); 1198 1199 mDb->setBatchMode(mode, rollback); 1200 mIsInBatchMode = mode; 1201 if (!mode) 1202 { 1203 if (!rollback) // was batch mode being turned off without an abort? 1204 { 1205 // dump the buffer 1206 EventBuffer::iterator it = mEventBuffer->begin(); 1207 while (it != mEventBuffer->end()) 1208 { 1209 PrimaryKey primaryKey; 1210 if (it->item) 1211 { 1212 primaryKey = it->item->primaryKey(); 1213 } 1214 1215 KCEventNotifier::PostKeychainEvent(it->kcEvent, mDb->dlDbIdentifier(), primaryKey); 1216 1217 ++it; 1218 } 1219 1220 } 1221 1222 // notify that a keychain has changed in too many ways to count 1223 KCEventNotifier::PostKeychainEvent(kSecKeychainLeftBatchModeEvent); 1224 mEventBuffer->clear(); 1225 } 1226 else 1227 { 1228 KCEventNotifier::PostKeychainEvent(kSecKeychainEnteredBatchModeEvent); 1229 } 1230} 1231 1232void 1233KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item) 1234{ 1235 PrimaryKey primaryKey; 1236 1237 { 1238 StLock<Mutex>_(mMutex); 1239 1240 if (item != NULL) 1241 { 1242 primaryKey = item->primaryKey(); 1243 } 1244 } 1245 1246 if (!mIsInBatchMode) 1247 { 1248 KCEventNotifier::PostKeychainEvent(kcEvent, mDb->dlDbIdentifier(), primaryKey); 1249 } 1250 else 1251 { 1252 StLock<Mutex>_(mMutex); 1253 1254 EventItem it; 1255 it.kcEvent = kcEvent; 1256 if (item != NULL) 1257 { 1258 it.item = item; 1259 } 1260 1261 mEventBuffer->push_back (it); 1262 } 1263} 1264 1265Keychain::Keychain() 1266{ 1267 dispatch_once(&SecKeychainSystemKeychainChecked, ^{ 1268 check_system_keychain(); 1269 }); 1270} 1271 1272Keychain::~Keychain() 1273{ 1274} 1275 1276 1277 1278Keychain 1279Keychain::optional(SecKeychainRef handle) 1280{ 1281 if (handle) 1282 return KeychainImpl::required(handle); 1283 else 1284 return globals().storageManager.defaultKeychain(); 1285} 1286 1287 1288CFIndex KeychainCore::GetKeychainRetainCount(Keychain& kc) 1289{ 1290 CFTypeRef ref = kc->handle(false); 1291 return CFGetRetainCount(ref); 1292} 1293 1294 1295// 1296// Create default credentials for this keychain. 1297// This is triggered upon default open (i.e. a Db::activate() with no set credentials). 1298// 1299// This function embodies the "default credentials" logic for Keychain-layer databases. 1300// 1301const AccessCredentials * 1302KeychainImpl::makeCredentials() 1303{ 1304 return defaultCredentials(); 1305} 1306 1307 1308const AccessCredentials * 1309KeychainImpl::defaultCredentials() 1310{ 1311 StLock<Mutex>_(mMutex); 1312 1313 // Use custom unlock credentials for file keychains which have a referral 1314 // record and the standard credentials for all others. 1315 1316 if (mDb->dl()->guid() == gGuidAppleCSPDL && mCustomUnlockCreds(mDb)) 1317 return &mCustomUnlockCreds; 1318 else 1319 if (mDb->dl()->guid() == gGuidAppleSdCSPDL) 1320 return globals().smartcardCredentials(); 1321 else 1322 return globals().keychainCredentials(); 1323} 1324 1325 1326 1327bool KeychainImpl::mayDelete() 1328{ 1329 return true; 1330} 1331