X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/Keychains.cpp?ds=inline diff --git a/Security/libsecurity_keychain/lib/Keychains.cpp b/Security/libsecurity_keychain/lib/Keychains.cpp new file mode 100644 index 00000000..1d94dee2 --- /dev/null +++ b/Security/libsecurity_keychain/lib/Keychains.cpp @@ -0,0 +1,1332 @@ +/* + * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// Keychains.cpp +// + +#include "KCEventNotifier.h" +#include "Keychains.h" + +#include "Item.h" +#include "KCCursor.h" +#include "Globals.h" +#include +#include +#include +#include +#include +#include + +#include "SecKeychainPriv.h" + +#include +#include +#include "DLDbListCFPref.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static dispatch_once_t SecKeychainSystemKeychainChecked; + +OSStatus SecKeychainSystemKeychainCheckWouldDeadlock() +{ + dispatch_once(&SecKeychainSystemKeychainChecked, ^{}); + return errSecSuccess; +} + +using namespace KeychainCore; +using namespace CssmClient; + + +typedef struct EventItem +{ + SecKeychainEvent kcEvent; + Item item; +} EventItem; + +typedef std::list EventBufferSuper; +class EventBuffer : public EventBufferSuper +{ +public: + EventBuffer () {} + virtual ~EventBuffer (); +}; + + +EventBuffer::~EventBuffer () +{ +} + + + +// +// KeychainSchemaImpl +// +KeychainSchemaImpl::KeychainSchemaImpl(const Db &db) : mMutex(Mutex::recursive) +{ + DbCursor relations(db); + relations->recordType(CSSM_DL_DB_SCHEMA_INFO); + DbAttributes relationRecord(db, 1); + relationRecord.add(Schema::RelationID); + DbUniqueRecord outerUniqueId(db); + + while (relations->next(&relationRecord, NULL, outerUniqueId)) + { + DbUniqueRecord uniqueId(db); + + uint32 relationID = relationRecord.at(0); + if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID + && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END) + continue; + + // Create a cursor on the SCHEMA_ATTRIBUTES table for records with + // RelationID == relationID + DbCursor attributes(db); + attributes->recordType(CSSM_DL_DB_SCHEMA_ATTRIBUTES); + attributes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID); + + // Set up a record for retriving the SCHEMA_ATTRIBUTES + DbAttributes attributeRecord(db, 2); + attributeRecord.add(Schema::AttributeFormat); + attributeRecord.add(Schema::AttributeID); + + RelationInfoMap &rim = mDatabaseInfoMap[relationID]; + while (attributes->next(&attributeRecord, NULL, uniqueId)) + rim[attributeRecord.at(1)] = attributeRecord.at(0); + + // Create a cursor on the CSSM_DL_DB_SCHEMA_INDEXES table for records + // with RelationID == relationID + DbCursor indexes(db); + indexes->recordType(CSSM_DL_DB_SCHEMA_INDEXES); + indexes->conjunctive(CSSM_DB_AND); + indexes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID); + indexes->add(CSSM_DB_EQUAL, Schema::IndexType, + uint32(CSSM_DB_INDEX_UNIQUE)); + + // Set up a record for retriving the SCHEMA_INDEXES + DbAttributes indexRecord(db, 1); + indexRecord.add(Schema::AttributeID); + + CssmAutoDbRecordAttributeInfo &infos = + *new CssmAutoDbRecordAttributeInfo(); + mPrimaryKeyInfoMap. + insert(PrimaryKeyInfoMap::value_type(relationID, &infos)); + infos.DataRecordType = relationID; + while (indexes->next(&indexRecord, NULL, uniqueId)) + { + CssmDbAttributeInfo &info = infos.add(); + info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; + info.Label.AttributeID = indexRecord.at(0); + // @@@ Might insert bogus value if DB is corrupt + info.AttributeFormat = rim[info.Label.AttributeID]; + } + } +} + +KeychainSchemaImpl::~KeychainSchemaImpl() +{ + try + { + map::iterator it = mPrimaryKeyInfoMap.begin(); + while (it != mPrimaryKeyInfoMap.end()) + { + delete it->second; + it++; + } + // for_each_map_delete(mPrimaryKeyInfoMap.begin(), mPrimaryKeyInfoMap.end()); + } + catch(...) + { + } +} + +const KeychainSchemaImpl::RelationInfoMap & +KeychainSchemaImpl::relationInfoMapFor(CSSM_DB_RECORDTYPE recordType) const +{ + DatabaseInfoMap::const_iterator dit = mDatabaseInfoMap.find(recordType); + if (dit == mDatabaseInfoMap.end()) + MacOSError::throwMe(errSecNoSuchClass); + return dit->second; +} + +bool KeychainSchemaImpl::hasRecordType (CSSM_DB_RECORDTYPE recordType) const +{ + DatabaseInfoMap::const_iterator it = mDatabaseInfoMap.find(recordType); + return it != mDatabaseInfoMap.end(); +} + +bool +KeychainSchemaImpl::hasAttribute(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const +{ + try + { + const RelationInfoMap &rmap = relationInfoMapFor(recordType); + RelationInfoMap::const_iterator rit = rmap.find(attributeId); + return rit != rmap.end(); + } + catch (MacOSError result) + { + if (result.osStatus () == errSecNoSuchClass) + { + return false; + } + else + { + throw; + } + } +} + +CSSM_DB_ATTRIBUTE_FORMAT +KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const +{ + const RelationInfoMap &rmap = relationInfoMapFor(recordType); + RelationInfoMap::const_iterator rit = rmap.find(attributeId); + if (rit == rmap.end()) + MacOSError::throwMe(errSecNoSuchAttr); + + return rit->second; +} + +CssmDbAttributeInfo +KeychainSchemaImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const +{ + CSSM_DB_ATTRIBUTE_INFO info; + info.AttributeFormat = attributeFormatFor(recordType, attributeId); + info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; + info.Label.AttributeID = attributeId; + + return info; +} + +void +KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType, SecKeychainAttributeInfo **Info) const +{ + const RelationInfoMap &rmap = relationInfoMapFor(recordType); + + SecKeychainAttributeInfo *theList=reinterpret_cast(malloc(sizeof(SecKeychainAttributeInfo))); + + size_t capacity=rmap.size(); + UInt32 *tagBuf=reinterpret_cast(malloc(capacity*sizeof(UInt32))); + UInt32 *formatBuf=reinterpret_cast(malloc(capacity*sizeof(UInt32))); + UInt32 i=0; + + + for (RelationInfoMap::const_iterator rit = rmap.begin(); rit != rmap.end(); ++rit) + { + if (i>=capacity) + { + capacity *= 2; + if (capacity <= i) capacity = i + 1; + tagBuf=reinterpret_cast(realloc(tagBuf, (capacity*sizeof(UInt32)))); + formatBuf=reinterpret_cast(realloc(tagBuf, (capacity*sizeof(UInt32)))); + } + tagBuf[i]=rit->first; + formatBuf[i++]=rit->second; + } + + theList->count=i; + theList->tag=tagBuf; + theList->format=formatBuf; + *Info=theList; +} + + +const CssmAutoDbRecordAttributeInfo & +KeychainSchemaImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) const +{ + PrimaryKeyInfoMap::const_iterator it; + it = mPrimaryKeyInfoMap.find(recordType); + + if (it == mPrimaryKeyInfoMap.end()) + MacOSError::throwMe(errSecNoSuchClass); // @@@ Not really but whatever. + + return *it->second; +} + +bool +KeychainSchemaImpl::operator <(const KeychainSchemaImpl &other) const +{ + return mDatabaseInfoMap < other.mDatabaseInfoMap; +} + +bool +KeychainSchemaImpl::operator ==(const KeychainSchemaImpl &other) const +{ + return mDatabaseInfoMap == other.mDatabaseInfoMap; +} + +void +KeychainSchemaImpl::didCreateRelation(CSSM_DB_RECORDTYPE relationID, + const char *inRelationName, + uint32 inNumberOfAttributes, + const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *pAttributeInfo, + uint32 inNumberOfIndexes, + const CSSM_DB_SCHEMA_INDEX_INFO *pIndexInfo) +{ + StLock_(mMutex); + + if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID + && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END) + return; + + // if our schema is already in the map, return + if (mPrimaryKeyInfoMap.find(relationID) != mPrimaryKeyInfoMap.end()) + { + return; + } + + RelationInfoMap &rim = mDatabaseInfoMap[relationID]; + for (uint32 ix = 0; ix < inNumberOfAttributes; ++ix) + rim[pAttributeInfo[ix].AttributeId] = pAttributeInfo[ix].DataType; + + CssmAutoDbRecordAttributeInfo *infos = new CssmAutoDbRecordAttributeInfo(); + + mPrimaryKeyInfoMap. + insert(PrimaryKeyInfoMap::value_type(relationID, infos)); + infos->DataRecordType = relationID; + for (uint32 ix = 0; ix < inNumberOfIndexes; ++ix) + if (pIndexInfo[ix].IndexType == CSSM_DB_INDEX_UNIQUE) + { + CssmDbAttributeInfo &info = infos->add(); + info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; + info.Label.AttributeID = pIndexInfo[ix].AttributeId; + info.AttributeFormat = rim[info.Label.AttributeID]; + } +} + + + +KeychainSchema::~KeychainSchema() + +{ +} + + + +struct Event +{ + SecKeychainEvent eventCode; + PrimaryKey primaryKey; +}; +typedef std::list EventList; + +#define SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME "/var/run/systemkeychaincheck" +#define SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".socket") +#define SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".done") + +static void check_system_keychain() +{ + // sadly we can't use XPC here, XPC_DOMAIN_TYPE_SYSTEM doesn't exist yet. Also xpc-helper uses the + // keychain API (I assume for checking codesign things). So we use Unix Domain Sockets. + + // NOTE: if we hit a system error we attempt to log it, and then just don't check the system keychain. + // In theory a system might be able to recover from this state if we let it try to muddle along, and + // past behaviour didn't even try this hard to do the keychain check. In particular we could be in a + // sandbox'ed process. So we just do our best and let another process try again. + + struct stat keycheck_file_info; + if (stat(SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME, &keycheck_file_info) < 0) { + int server_fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (server_fd < 0) { + syslog(LOG_ERR, "Can't get socket (%m) system keychain may be unchecked"); + return; + } + + struct sockaddr_un keychain_check_server_address; + keychain_check_server_address.sun_family = AF_UNIX; + 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)) { + // It would be nice if we could compile time assert this + 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)); + close(server_fd); + return; + } + keychain_check_server_address.sun_len = SUN_LEN(&keychain_check_server_address); + + int rc = connect(server_fd, (struct sockaddr *)&keychain_check_server_address, keychain_check_server_address.sun_len); + if (rc < 0) { + syslog(LOG_ERR, "Can not connect to %s: %m", SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME); + close(server_fd); + return; + } + + // 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) + char byte; + ssize_t read_size = read(server_fd, &byte, 1); + if (read_size < 0) { + syslog(LOG_ERR, "Error reading from system keychain checker: %m"); + } + + close(server_fd); + return; + } +} + +// +// KeychainImpl +// +KeychainImpl::KeychainImpl(const Db &db) + : mInCache(false), mDb(db), mCustomUnlockCreds (this), mIsInBatchMode (false), mMutex(Mutex::recursive) +{ + dispatch_once(&SecKeychainSystemKeychainChecked, ^{ + check_system_keychain(); + }); + mDb->defaultCredentials(this); // install activation hook + mEventBuffer = new EventBuffer; +} + +KeychainImpl::~KeychainImpl() +{ + try + { + // Remove ourselves from the cache if we are in it. + // fprintf(stderr, "Removing %p from storage manager cache.\n", handle(false)); + globals().storageManager.removeKeychain(dlDbIdentifier(), this); + delete mEventBuffer; + } + catch(...) + { + } +} + +Mutex* +KeychainImpl::getMutexForObject() +{ + return globals().storageManager.getStorageManagerMutex(); +} + +Mutex* +KeychainImpl::getKeychainMutex() +{ + return &mMutex; +} + +void KeychainImpl::aboutToDestruct() +{ + // remove me from the global cache, we are done + // fprintf(stderr, "Destructing keychain object\n"); + DLDbIdentifier identifier = dlDbIdentifier(); + globals().storageManager.removeKeychain(identifier, this); +} + +bool +KeychainImpl::operator ==(const KeychainImpl &keychain) const +{ + return dlDbIdentifier() == keychain.dlDbIdentifier(); +} + +KCCursor +KeychainImpl::createCursor(SecItemClass itemClass, const SecKeychainAttributeList *attrList) +{ + StLock_(mMutex); + + StorageManager::KeychainList keychains; + keychains.push_back(Keychain(this)); + return KCCursor(keychains, itemClass, attrList); +} + +KCCursor +KeychainImpl::createCursor(const SecKeychainAttributeList *attrList) +{ + StLock_(mMutex); + + StorageManager::KeychainList keychains; + keychains.push_back(Keychain(this)); + return KCCursor(keychains, attrList); +} + +void +KeychainImpl::create(UInt32 passwordLength, const void *inPassword) +{ + StLock_(mMutex); + + if (!inPassword) + { + create(); + return; + } + + Allocator &alloc = Allocator::standard(); + + // @@@ Share this instance + + const CssmData password(const_cast(inPassword), passwordLength); + AclFactory::PasswordChangeCredentials pCreds (password, alloc); + AclFactory::AnyResourceContext rcc(pCreds); + create(&rcc); +} + +void KeychainImpl::create(ConstStringPtr inPassword) +{ + StLock_(mMutex); + + if ( inPassword ) + create(static_cast(inPassword[0]), &inPassword[1]); + else + create(); +} + +void +KeychainImpl::create() +{ + StLock_(mMutex); + + AclFactory aclFactory; + AclFactory::AnyResourceContext rcc(aclFactory.unlockCred()); + create(&rcc); +} + +void KeychainImpl::createWithBlob(CssmData &blob) +{ + StLock_(mMutex); + + mDb->dbInfo(&Schema::DBInfo); + AclFactory aclFactory; + AclFactory::AnyResourceContext rcc(aclFactory.unlockCred()); + mDb->resourceControlContext (&rcc); + try + { + mDb->createWithBlob(blob); + } + catch (...) + { + mDb->resourceControlContext(NULL); + mDb->dbInfo(NULL); + throw; + } + mDb->resourceControlContext(NULL); + mDb->dbInfo(NULL); // Clear the schema (to not break an open call later) + globals().storageManager.created(Keychain(this)); + + KCEventNotifier::PostKeychainEvent (kSecKeychainListChangedEvent, this, NULL); +} + +void +KeychainImpl::create(const ResourceControlContext *rcc) +{ + StLock_(mMutex); + + mDb->dbInfo(&Schema::DBInfo); // Set the schema (to force a create) + mDb->resourceControlContext(rcc); + try + { + mDb->create(); + } + catch (...) + { + mDb->resourceControlContext(NULL); + mDb->dbInfo(NULL); // Clear the schema (to not break an open call later) + throw; + } + mDb->resourceControlContext(NULL); + mDb->dbInfo(NULL); // Clear the schema (to not break an open call later) + globals().storageManager.created(Keychain(this)); +} + +void +KeychainImpl::open() +{ + StLock_(mMutex); + + mDb->open(); +} + +void +KeychainImpl::lock() +{ + StLock_(mMutex); + + mDb->lock(); +} + +void +KeychainImpl::unlock() +{ + StLock_(mMutex); + + mDb->unlock(); +} + +void +KeychainImpl::unlock(const CssmData &password) +{ + StLock_(mMutex); + + mDb->unlock(password); +} + +void +KeychainImpl::unlock(ConstStringPtr password) +{ + StLock_(mMutex); + + if (password) + { + const CssmData data(const_cast(&password[1]), password[0]); + unlock(data); + } + else + unlock(); +} + +void +KeychainImpl::stash() +{ + StLock_(mMutex); + + mDb->stash(); +} + +void +KeychainImpl::stashCheck() +{ + StLock_(mMutex); + + mDb->stashCheck(); +} + +void +KeychainImpl::getSettings(uint32 &outIdleTimeOut, bool &outLockOnSleep) +{ + StLock_(mMutex); + + mDb->getSettings(outIdleTimeOut, outLockOnSleep); +} + +void +KeychainImpl::setSettings(uint32 inIdleTimeOut, bool inLockOnSleep) +{ + StLock_(mMutex); + + // The .Mac syncing code only makes sense for the AppleFile CSP/DL, + // but other DLs such as the OCSP and LDAP DLs do not expose a way to + // change settings or the password. To make a minimal change that only affects + // the smartcard case, we only look for that CSP/DL + + bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL); + + // get the old keychain blob so that we can tell .Mac to resync it + CssmAutoData oldBlob(mDb ->allocator()); + if (!isSmartcard) + mDb->copyBlob(oldBlob.get()); + + mDb->setSettings(inIdleTimeOut, inLockOnSleep); +} + +void +KeychainImpl::changePassphrase(UInt32 oldPasswordLength, const void *oldPassword, + UInt32 newPasswordLength, const void *newPassword) +{ + StLock_(mMutex); + + bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL); + + TrackingAllocator allocator(Allocator::standard()); + AutoCredentials cred = AutoCredentials(allocator); + if (oldPassword) + { + const CssmData &oldPass = *new(allocator) CssmData(const_cast(oldPassword), oldPasswordLength); + TypedList &oldList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK); + oldList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD)); + oldList.append(new(allocator) ListElement(oldPass)); + cred += oldList; + } + + if (newPassword) + { + const CssmData &newPass = *new(allocator) CssmData(const_cast(newPassword), newPasswordLength); + TypedList &newList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK); + newList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD)); + newList.append(new(allocator) ListElement(newPass)); + cred += newList; + } + + // get the old keychain blob so that we can tell .Mac to resync it + CssmAutoData oldBlob(mDb->allocator()); + if (!isSmartcard) + mDb->copyBlob(oldBlob.get()); + + mDb->changePassphrase(&cred); +} + +void +KeychainImpl::changePassphrase(ConstStringPtr oldPassword, ConstStringPtr newPassword) +{ + StLock_(mMutex); + + const void *oldPtr, *newPtr; + UInt32 oldLen, newLen; + if (oldPassword) + { + oldLen = oldPassword[0]; + oldPtr = oldPassword + 1; + } + else + { + oldLen = 0; + oldPtr = NULL; + } + + if (newPassword) + { + newLen = newPassword[0]; + newPtr = newPassword + 1; + } + else + { + newLen = 0; + newPtr = NULL; + } + + changePassphrase(oldLen, oldPtr, newLen, newPtr); +} + +void +KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS *cred) +{ + StLock_(mMutex); + + if (!exists()) + MacOSError::throwMe(errSecNoSuchKeychain); + + MacOSError::throwMe(errSecUnimplemented); +} + +UInt32 +KeychainImpl::status() const +{ + // @@@ We should figure out the read/write status though a DL passthrough + // or some other way. Also should locked be unlocked read only or just + // read-only? + return (mDb->isLocked() ? 0 : kSecUnlockStateStatus | kSecWritePermStatus) + | kSecReadPermStatus; +} + +bool +KeychainImpl::exists() +{ + StLock_(mMutex); + + bool exists = true; + try + { + open(); + // Ok to leave the mDb open since it will get closed when it goes away. + } + catch (const CssmError &e) + { + if (e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST) + throw; + exists = false; + } + + return exists; +} + +bool +KeychainImpl::isActive() const +{ + return mDb->isActive(); +} + +void KeychainImpl::completeAdd(Item &inItem, PrimaryKey &primaryKey) +{ + + + // The inItem shouldn't be in the cache yet + assert(!inItem->inCache()); + + // Insert inItem into mDbItemMap with key primaryKey. p.second will be + // true if it got inserted. If not p.second will be false and p.first + // will point to the current entry with key primaryKey. + pair p = + mDbItemMap.insert(DbItemMap::value_type(primaryKey, inItem.get())); + if (!p.second) + { + // There was already an ItemImpl * in mDbItemMap with key + // primaryKey. Get a ref to the pointer to it so we can assign a + // new value to it below. + ItemImpl *oldItem = p.first->second; + + // @@@ If this happens we are breaking our API contract of + // uniquifying items. We really need to insert the item into the + // map before we start the add. And have the item be in an + // "is being added" state. + assert(oldItem->inCache()); + secdebug("keychain", "add of new item %p somehow replaced %p", + inItem.get(), oldItem); + + // make sure that we both mark the item and remove the item from the cache + removeItem(oldItem->primaryKey(), oldItem); + oldItem = inItem.get(); + } + + inItem->inCache(true); +} + +void +KeychainImpl::addCopy(Item &inItem) +{ + Keychain keychain(this); + PrimaryKey primaryKey = inItem->addWithCopyInfo(keychain, true); + completeAdd(inItem, primaryKey); + postEvent(kSecAddEvent, inItem); +} + +void +KeychainImpl::add(Item &inItem) +{ + Keychain keychain(this); + PrimaryKey primaryKey = inItem->add(keychain); + completeAdd(inItem, primaryKey); + postEvent(kSecAddEvent, inItem); +} + +void +KeychainImpl::didUpdate(const Item &inItem, PrimaryKey &oldPK, + PrimaryKey &newPK) +{ + // If the primary key hasn't changed we don't need to update mDbItemMap. + if (oldPK != newPK) + { + // If inItem isn't in the cache we don't need to update mDbItemMap. + assert(inItem->inCache()); + if (inItem->inCache()) + { + // First remove the entry for inItem in mDbItemMap with key oldPK. + DbItemMap::iterator it = mDbItemMap.find(oldPK); + if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItem.get()) + mDbItemMap.erase(it); + + // Insert inItem into mDbItemMap with key newPK. p.second will be + // true if it got inserted. If not p.second will be false and + // p.first will point to the current entry with key newPK. + pair p = + mDbItemMap.insert(DbItemMap::value_type(newPK, inItem.get())); + if (!p.second) + { + // There was already an ItemImpl * in mDbItemMap with key + // primaryKey. Get a ref to the pointer to it so we can assign + // a new value to it below. + ItemImpl *oldItem = p.first->second; + + // @@@ If this happens we are breaking our API contract of + // uniquifying items. We really need to insert the item into + // the map with the new primary key before we start the update. + // And have the item be in an "is being updated" state. + assert(oldItem->inCache()); + secdebug("keychain", "update of item %p somehow replaced %p", + inItem.get(), oldItem); + oldItem->inCache(false); + oldItem = inItem.get(); + } + } + } + + postEvent(kSecUpdateEvent, inItem); +} + +void +KeychainImpl::deleteItem(Item &inoutItem) +{ + { + // We don't need to hold the DO mutex through event posting, and, in fact, doing so causes deadlock. + // Hold it only as long as needed, instead. + + + // item must be persistent + if (!inoutItem->isPersistent()) + MacOSError::throwMe(errSecInvalidItemRef); + + DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord(); + PrimaryKey primaryKey = inoutItem->primaryKey(); + uniqueId->deleteRecord(); + + // Don't remove the item from the mDbItemMap here since this would cause + // us to report a new item to our caller when we receive the + // kSecDeleteEvent notification. + // It will be removed before we post the notification, because + // CCallbackMgr will call didDeleteItem() + + // Post the notification for the item deletion with + // the primaryKey obtained when the item still existed + } + + postEvent(kSecDeleteEvent, inoutItem); +} + + +CssmClient::CSP +KeychainImpl::csp() +{ + StLock_(mMutex); + + if (!mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP) + MacOSError::throwMe(errSecInvalidKeychain); + + // Try to cast first to a CSPDL to handle case where we don't have an SSDb + try + { + CssmClient::CSPDL cspdl(dynamic_cast(&*mDb->dl())); + return CSP(cspdl); + } + catch (...) + { + SSDbImpl* impl = dynamic_cast(&(*mDb)); + if (impl == NULL) + { + CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); + } + + SSDb ssDb(impl); + return ssDb->csp(); + } +} + +PrimaryKey +KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId) +{ + StLock_(mMutex); + + DbAttributes primaryKeyAttrs(uniqueId->database()); + primaryKeyAttrs.recordType(recordType); + gatherPrimaryKeyAttributes(primaryKeyAttrs); + uniqueId->get(&primaryKeyAttrs, NULL); + return PrimaryKey(primaryKeyAttrs); +} + +const CssmAutoDbRecordAttributeInfo & +KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) +{ + StLock_(mMutex); + + try + { + return keychainSchema()->primaryKeyInfosFor(recordType); + } + catch (const CommonError &error) + { + switch (error.osStatus()) + { + case errSecNoSuchClass: + case CSSMERR_DL_INVALID_RECORDTYPE: + resetSchema(); + return keychainSchema()->primaryKeyInfosFor(recordType); + default: + throw; + } + } +} + +void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes& primaryKeyAttrs) +{ + StLock_(mMutex); + + const CssmAutoDbRecordAttributeInfo &infos = + primaryKeyInfosFor(primaryKeyAttrs.recordType()); + + // @@@ fix this to not copy info. + for (uint32 i = 0; i < infos.size(); i++) + primaryKeyAttrs.add(infos.at(i)); +} + +ItemImpl * +KeychainImpl::_lookupItem(const PrimaryKey &primaryKey) +{ + DbItemMap::iterator it = mDbItemMap.find(primaryKey); + if (it != mDbItemMap.end()) + { + if (it->second == NULL) + { + // we've been weak released... + mDbItemMap.erase(it); + } + else + { + return it->second; + } + } + + return NULL; +} + +Item +KeychainImpl::item(const PrimaryKey &primaryKey) +{ + StLock_(mMutex); + + // Lookup the item in the map while holding the apiLock. + ItemImpl *itemImpl = _lookupItem(primaryKey); + if (itemImpl) + return Item(itemImpl); + + try + { + // We didn't find it so create a new item with just a keychain and + // a primary key. However since we aren't holding + // globals().apiLock anymore some other thread might have beaten + // us to creating this item and adding it to the cache. If that + // happens we retry the lookup. + return Item(this, primaryKey); + } + catch (const MacOSError &e) + { + // If the item creation failed because some other thread already + // inserted this item into the cache we retry the lookup. + if (e.osStatus() == errSecDuplicateItem) + { + // Lookup the item in the map while holding the apiLock. + ItemImpl *itemImpl = _lookupItem(primaryKey); + if (itemImpl) + return Item(itemImpl); + } + throw; + } +} + + +Item +KeychainImpl::item(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId) +{ + StLock_(mMutex); + + PrimaryKey primaryKey = makePrimaryKey(recordType, uniqueId); + { + // Lookup the item in the map while holding the apiLock. + ItemImpl *itemImpl = _lookupItem(primaryKey); + + if (itemImpl) + { + return Item(itemImpl); + } + } + + try + { + // We didn't find it so create a new item with a keychain, a primary key + // and a DbUniqueRecord. However since we aren't holding + // globals().apiLock anymore some other thread might have beaten + // us to creating this item and adding it to the cache. If that + // happens we retry the lookup. + return Item(this, primaryKey, uniqueId); + } + catch (const MacOSError &e) + { + // If the item creation failed because some other thread already + // inserted this item into the cache we retry the lookup. + if (e.osStatus() == errSecDuplicateItem) + { + // Lookup the item in the map while holding the apiLock. + ItemImpl *itemImpl = _lookupItem(primaryKey); + if (itemImpl) + return Item(itemImpl); + } + throw; + } +} + +KeychainSchema +KeychainImpl::keychainSchema() +{ + StLock_(mMutex); + if (!mKeychainSchema) + mKeychainSchema = KeychainSchema(mDb); + + return mKeychainSchema; +} + +void KeychainImpl::resetSchema() +{ + mKeychainSchema = NULL; // re-fetch it from db next time +} + + +// Called from DbItemImpl's constructor (so it is only partially constructed), +// add it to the map. +void +KeychainImpl::addItem(const PrimaryKey &primaryKey, ItemImpl *dbItemImpl) +{ + StLock_(mMutex); + + // The dbItemImpl shouldn't be in the cache yet + assert(!dbItemImpl->inCache()); + + // Insert dbItemImpl into mDbItemMap with key primaryKey. p.second will + // be true if it got inserted. If not p.second will be false and p.first + // will point to the current entry with key primaryKey. + pair p = + mDbItemMap.insert(DbItemMap::value_type(primaryKey, dbItemImpl)); + + if (!p.second) + { + // There was already an ItemImpl * in mDbItemMap with key primaryKey. + // There is a race condition here when being called in multiple threads + // We might have added an item using add and received a notification at + // the same time. + MacOSError::throwMe(errSecDuplicateItem); + } + + dbItemImpl->inCache(true); +} + +void +KeychainImpl::didDeleteItem(ItemImpl *inItemImpl) +{ + StLock_(mMutex); + + // Called by CCallbackMgr + secdebug("kcnotify", "%p notified that item %p was deleted", this, inItemImpl); + removeItem(inItemImpl->primaryKey(), inItemImpl); +} + +void +KeychainImpl::removeItem(const PrimaryKey &primaryKey, ItemImpl *inItemImpl) +{ + StLock_(mMutex); + + // If inItemImpl isn't in the cache to begin with we are done. + if (!inItemImpl->inCache()) + return; + + DbItemMap::iterator it = mDbItemMap.find(primaryKey); + if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItemImpl) + mDbItemMap.erase(it); + + inItemImpl->inCache(false); +} + +void +KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID, + SecKeychainAttributeInfo **Info) +{ + StLock_(mMutex); + + try + { + keychainSchema()->getAttributeInfoForRecordType(itemID, Info); + } + catch (const CommonError &error) + { + switch (error.osStatus()) + { + case errSecNoSuchClass: + case CSSMERR_DL_INVALID_RECORDTYPE: + resetSchema(); + keychainSchema()->getAttributeInfoForRecordType(itemID, Info); + default: + throw; + } + } +} + +void +KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo *Info) +{ + free(Info->tag); + free(Info->format); + free(Info); +} + +CssmDbAttributeInfo +KeychainImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, UInt32 tag) +{ + StLock_(mMutex); + + try + { + return keychainSchema()->attributeInfoFor(recordType, tag); + } + catch (const CommonError &error) + { + switch (error.osStatus()) + { + case errSecNoSuchClass: + case CSSMERR_DL_INVALID_RECORDTYPE: + resetSchema(); + return keychainSchema()->attributeInfoFor(recordType, tag); + default: + throw; + } + } +} + +void +KeychainImpl::recode(const CssmData &data, const CssmData &extraData) +{ + StLock_(mMutex); + + mDb->recode(data, extraData); +} + +void +KeychainImpl::copyBlob(CssmData &data) +{ + StLock_(mMutex); + + mDb->copyBlob(data); +} + +void +KeychainImpl::setBatchMode(Boolean mode, Boolean rollback) +{ + StLock_(mMutex); + + mDb->setBatchMode(mode, rollback); + mIsInBatchMode = mode; + if (!mode) + { + if (!rollback) // was batch mode being turned off without an abort? + { + // dump the buffer + EventBuffer::iterator it = mEventBuffer->begin(); + while (it != mEventBuffer->end()) + { + PrimaryKey primaryKey; + if (it->item) + { + primaryKey = it->item->primaryKey(); + } + + KCEventNotifier::PostKeychainEvent(it->kcEvent, mDb->dlDbIdentifier(), primaryKey); + + ++it; + } + + } + + // notify that a keychain has changed in too many ways to count + KCEventNotifier::PostKeychainEvent(kSecKeychainLeftBatchModeEvent); + mEventBuffer->clear(); + } + else + { + KCEventNotifier::PostKeychainEvent(kSecKeychainEnteredBatchModeEvent); + } +} + +void +KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item) +{ + PrimaryKey primaryKey; + + { + StLock_(mMutex); + + if (item != NULL) + { + primaryKey = item->primaryKey(); + } + } + + if (!mIsInBatchMode) + { + KCEventNotifier::PostKeychainEvent(kcEvent, mDb->dlDbIdentifier(), primaryKey); + } + else + { + StLock_(mMutex); + + EventItem it; + it.kcEvent = kcEvent; + if (item != NULL) + { + it.item = item; + } + + mEventBuffer->push_back (it); + } +} + +Keychain::Keychain() +{ + dispatch_once(&SecKeychainSystemKeychainChecked, ^{ + check_system_keychain(); + }); +} + +Keychain::~Keychain() +{ +} + + + +Keychain +Keychain::optional(SecKeychainRef handle) +{ + if (handle) + return KeychainImpl::required(handle); + else + return globals().storageManager.defaultKeychain(); +} + + +CFIndex KeychainCore::GetKeychainRetainCount(Keychain& kc) +{ + CFTypeRef ref = kc->handle(false); + return CFGetRetainCount(ref); +} + + +// +// Create default credentials for this keychain. +// This is triggered upon default open (i.e. a Db::activate() with no set credentials). +// +// This function embodies the "default credentials" logic for Keychain-layer databases. +// +const AccessCredentials * +KeychainImpl::makeCredentials() +{ + return defaultCredentials(); +} + + +const AccessCredentials * +KeychainImpl::defaultCredentials() +{ + StLock_(mMutex); + + // Use custom unlock credentials for file keychains which have a referral + // record and the standard credentials for all others. + + if (mDb->dl()->guid() == gGuidAppleCSPDL && mCustomUnlockCreds(mDb)) + return &mCustomUnlockCreds; + else + if (mDb->dl()->guid() == gGuidAppleSdCSPDL) + return globals().smartcardCredentials(); + else + return globals().keychainCredentials(); +} + + + +bool KeychainImpl::mayDelete() +{ + return true; +}