/* * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. * * The contents of this file constitute Original Code as defined in and are * subject to the Apple Public Source License Version 1.2 (the 'License'). * You may not use this file except in compliance with the License. Please obtain * a copy of the License at http://www.apple.com/publicsource and read it before * using this file. * * This 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. */ // // Keychains.cpp // #include "Keychains.h" #include "KCEventNotifier.h" #include "Item.h" #include "KCCursor.h" #include "Globals.h" #include "Schema.h" #include #include #include #include using namespace KeychainCore; using namespace CssmClient; // // KeychainSchemaImpl // KeychainSchemaImpl::KeychainSchemaImpl(const Db &db) { 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); attributeRecord.add(Schema::AttributeNameFormat); RelationInfoMap &rim = mDatabaseInfoMap[relationID]; while (attributes->next(&attributeRecord, NULL, uniqueId)) { if(CSSM_DB_ATTRIBUTE_FORMAT(attributeRecord.at(2))==CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER) 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); info.AttributeFormat = rim[info.Label.AttributeID]; // @@@ Might insert bogus value if DB is corrupt } } } KeychainSchemaImpl::~KeychainSchemaImpl() { for_each_map_delete(mPrimaryKeyInfoMap.begin(), mPrimaryKeyInfoMap.end()); } CSSM_DB_ATTRIBUTE_FORMAT KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const { DatabaseInfoMap::const_iterator dit = mDatabaseInfoMap.find(recordType); if (dit == mDatabaseInfoMap.end()) MacOSError::throwMe(errSecNoSuchClass); RelationInfoMap::const_iterator rit = dit->second.find(attributeId); if (dit == dit->second.end()) MacOSError::throwMe(errSecNoSuchAttr); return rit->second; } CssmDbAttributeInfo KeychainSchemaImpl::attributeInfoForTag(UInt32 tag) { CSSM_DB_ATTRIBUTE_INFO info; for(DatabaseInfoMap::const_iterator dit = mDatabaseInfoMap.begin(); dit != mDatabaseInfoMap.end(); ++dit) { for(RelationInfoMap::const_iterator rit = dit->second.begin(); rit != dit->second.end(); ++rit) { if(rit->first==tag) { info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; info.Label.AttributeID = rit->first; info.AttributeFormat = rit->second; return info; } } } return info; } void KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType, SecKeychainAttributeInfo **Info) { DatabaseInfoMap::const_iterator dit = mDatabaseInfoMap.find(recordType); if (dit == mDatabaseInfoMap.end()) MacOSError::throwMe(errSecNoSuchClass); SecKeychainAttributeInfo *theList=reinterpret_cast(malloc(sizeof(SecKeychainAttributeInfo))); UInt32 capacity=32; UInt32 *tagBuf=reinterpret_cast(malloc(capacity*sizeof(UInt32))); UInt32 *formatBuf=reinterpret_cast(malloc(capacity*sizeof(UInt32))); UInt32 i=0; for(RelationInfoMap::const_iterator rit = dit->second.begin(); rit != dit->second.end(); ++rit) { if(i>=capacity) { capacity*=2; 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) { PrimaryKeyInfoMap::iterator it; it = mPrimaryKeyInfoMap.find(recordType); // if the primary key attributes have already been determined, // return the cached results 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; } // // KeychainImpl // KeychainImpl::KeychainImpl(const Db &db) : mDb(db) { } KeychainImpl::~KeychainImpl() { } KCCursor KeychainImpl::createCursor(SecItemClass itemClass, const SecKeychainAttributeList *attrList) { return KCCursor(DbCursor(mDb), itemClass, attrList); } KCCursor KeychainImpl::createCursor(const SecKeychainAttributeList *attrList) { return KCCursor(DbCursor(mDb), attrList); } void KeychainImpl::create(UInt32 passwordLength, const void *inPassword) { if (!inPassword) { create(); return; } CssmAllocator &alloc = CssmAllocator::standard(); // @@@ Share this instance KeychainAclFactory aclFactory(alloc); // @@@ This leaks the returned credentials const CssmData password(const_cast(inPassword), passwordLength); const AccessCredentials *cred = aclFactory.passwordChangeCredentials(password); // @@@ Create a nice wrapper for building the default AclEntryPrototype. TypedList subject(alloc, CSSM_ACL_SUBJECT_TYPE_ANY); AclEntryPrototype protoType(subject); AuthorizationGroup &authGroup = protoType.authorization(); CSSM_ACL_AUTHORIZATION_TAG tag = CSSM_ACL_AUTHORIZATION_ANY; authGroup.NumberOfAuthTags = 1; authGroup.AuthTags = &tag; const ResourceControlContext rcc(protoType, const_cast(cred)); create(&rcc); } void KeychainImpl::create(ConstStringPtr inPassword) { if ( inPassword ) create(static_cast(inPassword[0]), &inPassword[1]); else create(); } void KeychainImpl::create() { CssmAllocator &alloc = CssmAllocator::standard(); // @@@ Share this instance KeychainAclFactory aclFactory(alloc); const AccessCredentials *cred = aclFactory.keychainPromptUnlockCredentials(); // @@@ Create a nice wrapper for building the default AclEntryPrototype. TypedList subject(alloc, CSSM_ACL_SUBJECT_TYPE_ANY); AclEntryPrototype protoType(subject); AuthorizationGroup &authGroup = protoType.authorization(); CSSM_ACL_AUTHORIZATION_TAG tag = CSSM_ACL_AUTHORIZATION_ANY; authGroup.NumberOfAuthTags = 1; authGroup.AuthTags = &tag; const ResourceControlContext rcc(protoType, const_cast(cred)); create(&rcc); } void KeychainImpl::create(const ResourceControlContext *rcc) { 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() { mDb->open(); } void KeychainImpl::lock() { mDb->lock(); } void KeychainImpl::unlock() { mDb->unlock(); } void KeychainImpl::unlock(const CssmData &password) { mDb->unlock(password); } void KeychainImpl::unlock(ConstStringPtr password) { if (password) { const CssmData data(const_cast(&password[1]), password[0]); unlock(data); } else unlock(); } void KeychainImpl::getSettings(uint32 &outIdleTimeOut, bool &outLockOnSleep) { mDb->getSettings(outIdleTimeOut, outLockOnSleep); } void KeychainImpl::setSettings(uint32 inIdleTimeOut, bool inLockOnSleep) { mDb->setSettings(inIdleTimeOut, inLockOnSleep); } void KeychainImpl::changePassphrase(UInt32 oldPasswordLength, const void *oldPassword, UInt32 newPasswordLength, const void *newPassword) { // @@@ When AutoCredentials is actually finished we should no logner use a tracking allocator. TrackingAllocator allocator(CssmAllocator::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; } mDb->changePassphrase(&cred); } void KeychainImpl::changePassphrase(ConstStringPtr oldPassword, ConstStringPtr newPassword) { 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) { // @@@ This should do an authenticate which is not the same as unlock. if (!exists()) MacOSError::throwMe(errSecNoSuchKeychain); MacOSError::throwMe(unimpErr); } 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 | kSecWrPermStatus) | kSecRdPermStatus; } bool KeychainImpl::exists() { 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.cssmError() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST) throw; exists = false; } return exists; } bool KeychainImpl::isActive() const { return mDb->isActive(); } void KeychainImpl::add(Item &inItem) { PrimaryKey primaryKey = inItem->add(this); { StLock _(mDbItemMapLock); // Use &* to get the item's Impl. mDbItemMap[primaryKey] = &*inItem; } KCEventNotifier::PostKeychainEvent(kSecAddEvent, this, inItem); } void KeychainImpl::didUpdate(ItemImpl *inItemImpl, PrimaryKey &oldPK, PrimaryKey &newPK) { // Make sure we only hold mDbItemMapLock as long as we need to. { StLock _(mDbItemMapLock); DbItemMap::iterator it = mDbItemMap.find(oldPK); if (it != mDbItemMap.end() && it->second == inItemImpl) mDbItemMap.erase(it); mDbItemMap[newPK] = inItemImpl; } KCEventNotifier::PostKeychainEvent( kSecUpdateEvent, this, inItemImpl ); } void KeychainImpl::deleteItem(Item &inoutItem) { // item must be persistant. if (!inoutItem->isPersistant()) MacOSError::throwMe(errSecInvalidItemRef); DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord(); PrimaryKey primaryKey = inoutItem->primaryKey(); uniqueId->deleteRecord(); // Don't kill the ref or clear the Item() since this potentially // messes up things for the receiver of the kSecDeleteEvent notification. //inoutItem->killRef(); //inoutItem = Item(); // Post the notification for the item deletion with // the primaryKey obtained when the item still existed KCEventNotifier::PostKeychainEvent(kSecDeleteEvent, dLDbIdentifier(), primaryKey); } PrimaryKey KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId) { DbAttributes primaryKeyAttrs(uniqueId->database()); primaryKeyAttrs.recordType(recordType); gatherPrimaryKeyAttributes(primaryKeyAttrs); uniqueId->get(&primaryKeyAttrs, NULL); return PrimaryKey(primaryKeyAttrs); } const CssmAutoDbRecordAttributeInfo & KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) { return keychainSchema()->primaryKeyInfosFor(recordType); } void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes& primaryKeyAttrs) { 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)); } Item KeychainImpl::item(const PrimaryKey& primaryKey) { { StLock _(mDbItemMapLock); DbItemMap::iterator it = mDbItemMap.find(primaryKey); if (it != mDbItemMap.end()) { return Item(it->second); } } // Create an item with just a primary key return Item(this, primaryKey); } Item KeychainImpl::item(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId) { PrimaryKey primaryKey = makePrimaryKey(recordType, uniqueId); { StLock _(mDbItemMapLock); DbItemMap::iterator it = mDbItemMap.find(primaryKey); if (it != mDbItemMap.end()) { return Item(it->second); } } // Create a new item return Item(this, primaryKey, uniqueId); } KeychainSchema KeychainImpl::keychainSchema() { if (!mKeychainSchema) { // @@@ Use cache in storageManager mKeychainSchema = KeychainSchema(mDb); } return mKeychainSchema; } // Called from DbItemImpl's constructor (so it is only paritally constructed), add it to the map. void KeychainImpl::addItem(const PrimaryKey &primaryKey, ItemImpl *dbItemImpl) { StLock _(mDbItemMapLock); DbItemMap::iterator it = mDbItemMap.find(primaryKey); if (it != mDbItemMap.end()) { // @@@ 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 //assert(true); throw errSecDuplicateItem; //mDbItemMap.erase(it); // @@@ What to do here? } mDbItemMap.insert(DbItemMap::value_type(primaryKey, dbItemImpl)); } void KeychainImpl::removeItem(const PrimaryKey &primaryKey, const ItemImpl *inItemImpl) { // Sent from DbItemImpl's destructor, remove it from the map. StLock _(mDbItemMapLock); DbItemMap::iterator it = mDbItemMap.find(primaryKey); if (it != mDbItemMap.end() && it->second == inItemImpl) mDbItemMap.erase(it); } void KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID, SecKeychainAttributeInfo **Info) { keychainSchema()->getAttributeInfoForRecordType(itemID, Info); } void KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo *Info) { free(Info->tag); free(Info->format); free(Info); } CssmDbAttributeInfo KeychainImpl::attributeInfoForTag(UInt32 tag) { return keychainSchema()->attributeInfoForTag(tag); } Keychain Keychain::optional(SecKeychainRef handle) { if (handle) return KeychainRef::required(handle); else return globals().defaultKeychain; }