--- /dev/null
+/*
+ * 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 <security_cdsa_utilities/Schema.h>
+#include <security_cdsa_client/keychainacl.h>
+#include <security_cdsa_utilities/cssmacl.h>
+#include <security_cdsa_utilities/cssmdb.h>
+#include <security_utilities/trackingallocator.h>
+#include <security_keychain/SecCFTypes.h>
+
+#include "SecKeychainPriv.h"
+
+#include <Security/SecKeychainItemPriv.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "DLDbListCFPref.h"
+#include <fcntl.h>
+#include <sys/param.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+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<EventItem> 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<CSSM_DB_RECORDTYPE, CssmAutoDbRecordAttributeInfo *>::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<SecKeychainAttributeInfo *>(malloc(sizeof(SecKeychainAttributeInfo)));
+
+ size_t capacity=rmap.size();
+ UInt32 *tagBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32)));
+ UInt32 *formatBuf=reinterpret_cast<UInt32 *>(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<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32))));
+ formatBuf=reinterpret_cast<UInt32 *>(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<Mutex>_(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<Event> 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<Mutex>_(mMutex);
+
+ StorageManager::KeychainList keychains;
+ keychains.push_back(Keychain(this));
+ return KCCursor(keychains, itemClass, attrList);
+}
+
+KCCursor
+KeychainImpl::createCursor(const SecKeychainAttributeList *attrList)
+{
+ StLock<Mutex>_(mMutex);
+
+ StorageManager::KeychainList keychains;
+ keychains.push_back(Keychain(this));
+ return KCCursor(keychains, attrList);
+}
+
+void
+KeychainImpl::create(UInt32 passwordLength, const void *inPassword)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (!inPassword)
+ {
+ create();
+ return;
+ }
+
+ Allocator &alloc = Allocator::standard();
+
+ // @@@ Share this instance
+
+ const CssmData password(const_cast<void *>(inPassword), passwordLength);
+ AclFactory::PasswordChangeCredentials pCreds (password, alloc);
+ AclFactory::AnyResourceContext rcc(pCreds);
+ create(&rcc);
+}
+
+void KeychainImpl::create(ConstStringPtr inPassword)
+{
+ StLock<Mutex>_(mMutex);
+
+ if ( inPassword )
+ create(static_cast<UInt32>(inPassword[0]), &inPassword[1]);
+ else
+ create();
+}
+
+void
+KeychainImpl::create()
+{
+ StLock<Mutex>_(mMutex);
+
+ AclFactory aclFactory;
+ AclFactory::AnyResourceContext rcc(aclFactory.unlockCred());
+ create(&rcc);
+}
+
+void KeychainImpl::createWithBlob(CssmData &blob)
+{
+ StLock<Mutex>_(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<Mutex>_(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<Mutex>_(mMutex);
+
+ mDb->open();
+}
+
+void
+KeychainImpl::lock()
+{
+ StLock<Mutex>_(mMutex);
+
+ mDb->lock();
+}
+
+void
+KeychainImpl::unlock()
+{
+ StLock<Mutex>_(mMutex);
+
+ mDb->unlock();
+}
+
+void
+KeychainImpl::unlock(const CssmData &password)
+{
+ StLock<Mutex>_(mMutex);
+
+ mDb->unlock(password);
+}
+
+void
+KeychainImpl::unlock(ConstStringPtr password)
+{
+ StLock<Mutex>_(mMutex);
+
+ if (password)
+ {
+ const CssmData data(const_cast<unsigned char *>(&password[1]), password[0]);
+ unlock(data);
+ }
+ else
+ unlock();
+}
+
+void
+KeychainImpl::stash()
+{
+ StLock<Mutex>_(mMutex);
+
+ mDb->stash();
+}
+
+void
+KeychainImpl::stashCheck()
+{
+ StLock<Mutex>_(mMutex);
+
+ mDb->stashCheck();
+}
+
+void
+KeychainImpl::getSettings(uint32 &outIdleTimeOut, bool &outLockOnSleep)
+{
+ StLock<Mutex>_(mMutex);
+
+ mDb->getSettings(outIdleTimeOut, outLockOnSleep);
+}
+
+void
+KeychainImpl::setSettings(uint32 inIdleTimeOut, bool inLockOnSleep)
+{
+ StLock<Mutex>_(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<Mutex>_(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<void *>(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<void *>(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<DbItemMap::iterator, bool> 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<DbItemMap::iterator, bool> 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<Mutex>_(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<CssmClient::CSPDLImpl *>(&*mDb->dl()));
+ return CSP(cspdl);
+ }
+ catch (...)
+ {
+ SSDbImpl* impl = dynamic_cast<SSDbImpl *>(&(*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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<DbItemMap::iterator, bool> 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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(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<Mutex>_(mMutex);
+
+ mDb->recode(data, extraData);
+}
+
+void
+KeychainImpl::copyBlob(CssmData &data)
+{
+ StLock<Mutex>_(mMutex);
+
+ mDb->copyBlob(data);
+}
+
+void
+KeychainImpl::setBatchMode(Boolean mode, Boolean rollback)
+{
+ StLock<Mutex>_(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<Mutex>_(mMutex);
+
+ if (item != NULL)
+ {
+ primaryKey = item->primaryKey();
+ }
+ }
+
+ if (!mIsInBatchMode)
+ {
+ KCEventNotifier::PostKeychainEvent(kcEvent, mDb->dlDbIdentifier(), primaryKey);
+ }
+ else
+ {
+ StLock<Mutex>_(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<Mutex>_(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;
+}