#include "Keychains.h"
#include "KCEventNotifier.h"
#include "Item.h"
#include "KCCursor.h"
#include "Globals.h"
#include "Schema.h"
#include <Security/keychainacl.h>
#include <Security/cssmacl.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <Security/cssmdb.h>
using namespace KeychainCore;
using namespace CssmClient;
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;
DbCursor attributes(db);
attributes->recordType(CSSM_DL_DB_SCHEMA_ATTRIBUTES);
attributes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID);
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);
}
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));
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]; }
}
}
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<SecKeychainAttributeInfo *>(malloc(sizeof(SecKeychainAttributeInfo)));
UInt32 capacity=32;
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 = dit->second.begin(); rit != dit->second.end(); ++rit)
{
if(i>=capacity)
{
capacity*=2;
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)
{
PrimaryKeyInfoMap::iterator it;
it = mPrimaryKeyInfoMap.find(recordType);
if (it == mPrimaryKeyInfoMap.end())
MacOSError::throwMe(errSecNoSuchClass);
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(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();
KeychainAclFactory aclFactory(alloc);
const CssmData password(const_cast<void *>(inPassword), passwordLength);
const AccessCredentials *cred = aclFactory.passwordChangeCredentials(password);
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<AccessCredentials *>(cred));
create(&rcc);
}
void KeychainImpl::create(ConstStringPtr inPassword)
{
if ( inPassword )
create(static_cast<UInt32>(inPassword[0]), &inPassword[1]);
else
create();
}
void
KeychainImpl::create()
{
CssmAllocator &alloc = CssmAllocator::standard();
KeychainAclFactory aclFactory(alloc);
const AccessCredentials *cred = aclFactory.keychainPromptUnlockCredentials();
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<AccessCredentials *>(cred));
create(&rcc);
}
void
KeychainImpl::create(const ResourceControlContext *rcc)
{
mDb->dbInfo(&Schema::DBInfo); mDb->resourceControlContext(rcc);
try
{
mDb->create();
}
catch (...)
{
mDb->resourceControlContext(NULL);
mDb->dbInfo(NULL); throw;
}
mDb->resourceControlContext(NULL);
mDb->dbInfo(NULL); 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<unsigned char *>(&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)
{
TrackingAllocator allocator(CssmAllocator::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;
}
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)
{
if (!exists())
MacOSError::throwMe(errSecNoSuchKeychain);
MacOSError::throwMe(unimpErr);
}
UInt32
KeychainImpl::status() const
{
return (mDb->isLocked() ? 0 : kSecUnlockStateStatus | kSecWrPermStatus) | kSecRdPermStatus;
}
bool
KeychainImpl::exists()
{
bool exists = true;
try
{
open();
}
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<Mutex> _(mDbItemMapLock);
mDbItemMap[primaryKey] = &*inItem;
}
KCEventNotifier::PostKeychainEvent(kSecAddEvent, this, inItem);
}
void
KeychainImpl::didUpdate(ItemImpl *inItemImpl, PrimaryKey &oldPK,
PrimaryKey &newPK)
{
{
StLock<Mutex> _(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)
{
if (!inoutItem->isPersistant())
MacOSError::throwMe(errSecInvalidItemRef);
DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord();
PrimaryKey primaryKey = inoutItem->primaryKey();
uniqueId->deleteRecord();
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());
for (uint32 i = 0; i < infos.size(); i++)
primaryKeyAttrs.add(infos.at(i));
}
Item
KeychainImpl::item(const PrimaryKey& primaryKey)
{
{
StLock<Mutex> _(mDbItemMapLock);
DbItemMap::iterator it = mDbItemMap.find(primaryKey);
if (it != mDbItemMap.end())
{
return Item(it->second);
}
}
return Item(this, primaryKey);
}
Item
KeychainImpl::item(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId)
{
PrimaryKey primaryKey = makePrimaryKey(recordType, uniqueId);
{
StLock<Mutex> _(mDbItemMapLock);
DbItemMap::iterator it = mDbItemMap.find(primaryKey);
if (it != mDbItemMap.end())
{
return Item(it->second);
}
}
return Item(this, primaryKey, uniqueId);
}
KeychainSchema
KeychainImpl::keychainSchema()
{
if (!mKeychainSchema)
{
mKeychainSchema = KeychainSchema(mDb);
}
return mKeychainSchema;
}
void
KeychainImpl::addItem(const PrimaryKey &primaryKey, ItemImpl *dbItemImpl)
{
StLock<Mutex> _(mDbItemMapLock);
DbItemMap::iterator it = mDbItemMap.find(primaryKey);
if (it != mDbItemMap.end())
{
throw errSecDuplicateItem;
}
mDbItemMap.insert(DbItemMap::value_type(primaryKey, dbItemImpl));
}
void
KeychainImpl::removeItem(const PrimaryKey &primaryKey, const ItemImpl *inItemImpl)
{
StLock<Mutex> _(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;
}