#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_utilities/FileLockTransaction.h>
#include <security_keychain/SecCFTypes.h>
#include <securityd_client/ssblob.h>
#include <Security/TrustSettingsSchema.h>
#include "SecKeychainPriv.h"
#include <Security/SecKeychainItemPriv.h>
#include <CoreFoundation/CoreFoundation.h>
#include "DLDbListCFPref.h"
#include <fcntl.h>
#include <glob.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(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;
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);
RelationInfoMap &rim = mDatabaseInfoMap[relationID];
while (attributes->next(&attributeRecord, NULL, uniqueId))
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()
{
try
{
map<CSSM_DB_RECORDTYPE, CssmAutoDbRecordAttributeInfo *>::iterator it = mPrimaryKeyInfoMap.begin();
while (it != mPrimaryKeyInfoMap.end())
{
delete it->second;
it++;
}
}
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);
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 (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()
{
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)) {
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;
}
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(const Db &db)
: mCacheTimer(NULL), mSuppressTickle(false), mAttemptedUpgrade(false), mDbItemMapMutex(Mutex::recursive), mDbDeletedItemMapMutex(Mutex::recursive),
mInCache(false), mDb(db), mCustomUnlockCreds (this), mIsInBatchMode (false), mMutex(Mutex::recursive)
{
dispatch_once(&SecKeychainSystemKeychainChecked, ^{
check_system_keychain();
});
mDb->defaultCredentials(this); mEventBuffer = new EventBuffer;
}
KeychainImpl::~KeychainImpl()
{
try
{
globals().storageManager.removeKeychain(dlDbIdentifier(), this);
delete mEventBuffer;
}
catch(...)
{
}
}
Mutex*
KeychainImpl::getMutexForObject() const
{
return globals().storageManager.getStorageManagerMutex();
}
Mutex*
KeychainImpl::getKeychainMutex()
{
return &mMutex;
}
ReadWriteLock*
KeychainImpl::getKeychainReadWriteLock()
{
return &mRWLock;
}
void KeychainImpl::aboutToDestruct()
{
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();
const CssmData password(const_cast<void *>(inPassword), passwordLength);
AclFactory::PasswordChangeCredentials pCreds (password, alloc);
AclFactory::AnyResourceContext rcc(pCreds);
create(&rcc);
mDb->open();
}
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);
mDb->open();
}
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); globals().storageManager.created(Keychain(this));
KCEventNotifier::PostKeychainEvent (kSecKeychainListChangedEvent, this, NULL);
}
void
KeychainImpl::create(const ResourceControlContext *rcc)
{
StLock<Mutex>_(mMutex);
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()
{
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);
bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL);
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;
}
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
{
return (mDb->isLocked() ? 0 : kSecUnlockStateStatus | kSecWritePermStatus)
| kSecReadPermStatus;
}
bool
KeychainImpl::exists()
{
StLock<Mutex>_(mMutex);
bool exists = true;
try
{
open();
}
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)
{
assert(!inItem->inCache());
StLock<Mutex> _(mDbItemMapMutex);
pair<DbItemMap::iterator, bool> p =
mDbItemMap.insert(DbItemMap::value_type(primaryKey, inItem.get()));
if (!p.second)
{
ItemImpl *oldItem = p.first->second;
secnotice("keychain", "add of new item %p somehow replaced %p",
inItem.get(), oldItem);
mDbItemMap.erase(p.first);
oldItem->inCache(false);
forceRemoveFromCache(oldItem);
mDbItemMap.insert(DbItemMap::value_type(primaryKey, 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 (oldPK != newPK)
{
assert(inItem->inCache());
if (inItem->inCache())
{
StLock<Mutex> _(mDbItemMapMutex);
DbItemMap::iterator it = mDbItemMap.find(oldPK);
if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItem.get())
mDbItemMap.erase(it);
pair<DbItemMap::iterator, bool> p =
mDbItemMap.insert(DbItemMap::value_type(newPK, inItem.get()));
if (!p.second)
{
ItemImpl *oldItem = p.first->second;
secnotice("keychain", "update of item %p somehow replaced %p",
inItem.get(), oldItem);
mDbItemMap.erase(p.first);
oldItem->inCache(false);
forceRemoveFromCache(oldItem);
mDbItemMap.insert(DbItemMap::value_type(newPK, inItem.get()));
}
}
}
postEvent(kSecDeleteEvent, inItem, oldPK);
postEvent(kSecAddEvent, inItem);
}
void
KeychainImpl::deleteItem(Item &inoutItem)
{
{
if (!inoutItem->isPersistent())
MacOSError::throwMe(errSecInvalidItemRef);
secinfo("kcnotify", "starting deletion of item %p", inoutItem.get());
DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord();
PrimaryKey primaryKey = inoutItem->primaryKey();
uniqueId->deleteRecord();
if (inoutItem->inCache()) {
StLock<Mutex> _(mDbItemMapMutex);
StLock<Mutex> __(mDbDeletedItemMapMutex);
DbItemMap::iterator it = mDbItemMap.find(primaryKey);
if (it != mDbItemMap.end() && (ItemImpl*) it->second == inoutItem.get()) {
mDbDeletedItemMap.insert(DbItemMap::value_type(primaryKey, it->second));
mDbItemMap.erase(it);
}
}
}
postEvent(kSecDeleteEvent, inoutItem);
}
void KeychainImpl::changeDatabase(CssmClient::Db db)
{
StLock<Mutex>_(mDbMutex);
mDb = db;
mDb->defaultCredentials(this);
}
CssmClient::CSP
KeychainImpl::csp()
{
StLock<Mutex>_(mMutex);
if (!mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP)
MacOSError::throwMe(errSecInvalidKeychain);
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);
}
PrimaryKey
KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbAttributes* currentAttributes)
{
StLock<Mutex>_(mMutex);
DbAttributes primaryKeyAttrs;
primaryKeyAttrs.recordType(recordType);
gatherPrimaryKeyAttributes(primaryKeyAttrs);
for(int i = 0; i < primaryKeyAttrs.size(); i++) {
CssmDbAttributeData& attr = primaryKeyAttrs[i];
CssmDbAttributeData * actual = currentAttributes->find(attr.info());
if(actual) {
attr.set(*actual, Allocator::standard());
}
}
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());
for (uint32 i = 0; i < infos.size(); i++)
primaryKeyAttrs.add(infos.at(i));
}
ItemImpl *
KeychainImpl::_lookupItem(const PrimaryKey &primaryKey)
{
StLock<Mutex> _(mDbItemMapMutex);
DbItemMap::iterator it = mDbItemMap.find(primaryKey);
if (it != mDbItemMap.end())
{
return it->second;
}
return NULL;
}
ItemImpl *
KeychainImpl::_lookupDeletedItemOnly(const PrimaryKey &primaryKey)
{
StLock<Mutex> _(mDbDeletedItemMapMutex);
DbItemMap::iterator it = mDbDeletedItemMap.find(primaryKey);
if (it != mDbDeletedItemMap.end())
{
return it->second;
}
return NULL;
}
Item
KeychainImpl::item(const PrimaryKey &primaryKey)
{
StLock<Mutex>_(mMutex);
ItemImpl *itemImpl = _lookupItem(primaryKey);
if (itemImpl) {
return Item(itemImpl);
}
try
{
return Item(this, primaryKey);
}
catch (const MacOSError &e)
{
if (e.osStatus() == errSecDuplicateItem)
{
ItemImpl *itemImpl = _lookupItem(primaryKey);
if (itemImpl)
return Item(itemImpl);
}
throw;
}
}
Item
KeychainImpl::itemdeleted(const PrimaryKey& primaryKey) {
StLock<Mutex>_(mMutex);
Item i = _lookupDeletedItemOnly(primaryKey);
if(i.get()) {
return i;
} else {
return item(primaryKey);
}
}
Item
KeychainImpl::item(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId)
{
StLock<Mutex>_(mMutex);
PrimaryKey primaryKey = makePrimaryKey(recordType, uniqueId);
{
ItemImpl *itemImpl = _lookupItem(primaryKey);
if (itemImpl)
{
return Item(itemImpl);
}
}
try
{
return Item(this, primaryKey, uniqueId);
}
catch (const MacOSError &e)
{
if (e.osStatus() == errSecDuplicateItem)
{
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; }
void
KeychainImpl::addItem(const PrimaryKey &primaryKey, ItemImpl *dbItemImpl)
{
StLock<Mutex>_(mMutex);
assert(!dbItemImpl->inCache());
StLock<Mutex> __(mDbItemMapMutex);
pair<DbItemMap::iterator, bool> p =
mDbItemMap.insert(DbItemMap::value_type(primaryKey, dbItemImpl));
if (!p.second)
{
MacOSError::throwMe(errSecDuplicateItem);
}
dbItemImpl->inCache(true);
}
void
KeychainImpl::didDeleteItem(ItemImpl *inItemImpl)
{
StLock<Mutex>_(mMutex);
secinfo("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->inCache())
return;
{
StLock<Mutex> _(mDbItemMapMutex);
DbItemMap::iterator it = mDbItemMap.find(primaryKey);
if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItemImpl) {
mDbItemMap.erase(it);
}
}
{
StLock<Mutex> _(mDbDeletedItemMapMutex);
DbItemMap::iterator it = mDbDeletedItemMap.find(primaryKey);
if (it != mDbDeletedItemMap.end() && (ItemImpl*) it->second == inItemImpl) {
mDbDeletedItemMap.erase(it);
}
}
inItemImpl->inCache(false);
}
void
KeychainImpl::forceRemoveFromCache(ItemImpl* inItemImpl) {
try {
{
StLock<Mutex> _(mDbItemMapMutex);
for(DbItemMap::iterator it = mDbItemMap.begin(); it != mDbItemMap.end(); ) {
if(it->second == inItemImpl) {
it->second->inCache(false);
mDbItemMap.erase(it++);
} else {
it++;
}
}
}
{
StLock<Mutex> _(mDbDeletedItemMapMutex);
for(DbItemMap::iterator it = mDbDeletedItemMap.begin(); it != mDbDeletedItemMap.end(); ) {
if(it->second == inItemImpl) {
it->second->inCache(false);
mDbDeletedItemMap.erase(it++);
} else {
it++;
}
}
} } catch(UnixError ue) {
secnotice("keychain", "caught UnixError: %d %s", ue.unixError(), ue.what());
} catch (CssmError cssme) {
const char* errStr = cssmErrorString(cssme.error);
secnotice("keychain", "caught CssmError: %d %s", (int) cssme.error, errStr);
} catch (MacOSError mose) {
secnotice("keychain", "MacOSError: %d", (int)mose.osStatus());
} catch(...) {
secnotice("keychain", "Unknown error");
}
}
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) {
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;
}
}
KCEventNotifier::PostKeychainEvent(kSecKeychainLeftBatchModeEvent);
mEventBuffer->clear();
}
else
{
KCEventNotifier::PostKeychainEvent(kSecKeychainEnteredBatchModeEvent);
}
}
void
KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item)
{
postEvent(kcEvent, item, NULL);
}
void
KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item, PrimaryKey pk)
{
PrimaryKey primaryKey;
if(pk.get()) {
primaryKey = pk;
} else {
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);
}
}
void KeychainImpl::tickle() {
if(!mSuppressTickle) {
globals().storageManager.tickleKeychain(this);
}
}
bool KeychainImpl::performKeychainUpgradeIfNeeded() {
StLock<Mutex>_(mMutex);
if(!globals().integrityProtection()) {
secnotice("integrity", "skipping upgrade for %s due to global integrity protection being disabled", mDb->name());
return false;
}
if((mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP) == 0) {
return false;
}
if(mDb->dl()->guid() != gGuidAppleCSPDL) {
secnotice("integrity", "skipping upgrade for %s due to guid mismatch\n", mDb->name());
return false;
}
if(mAttemptedUpgrade) {
return false;
}
if(strncmp(mDb->name(), SYSTEM_ROOT_STORE_PATH, strlen(SYSTEM_ROOT_STORE_PATH)) == 0) {
secnotice("integrity", "skipping upgrade for %s\n", mDb->name());
return false;
}
uint32 dbBlobVersion = SecurityServer::DbBlob::version_MacOS_10_0;
try {
dbBlobVersion = mDb->dbBlobVersion();
} catch (CssmError cssme) {
if(cssme.error == CSSMERR_DL_DATASTORE_DOESNOT_EXIST) {
secnotice("integrity", "dbBlobVersion() failed for a non-existent database");
return false;
} else {
const char* errStr = cssmErrorString(cssme.error);
secnotice("integrity", "dbBlobVersion() failed for a CssmError: %d %s", (int) cssme.error, errStr);
return false;
}
} catch (...) {
secnotice("integrity", "dbBlobVersion() failed for an unknown reason");
return false;
}
string path = mDb->name();
string keychainDbPath = StorageManager::makeKeychainDbFilename(path);
bool inHomeLibraryKeychains = StorageManager::pathInHomeLibraryKeychains(path);
string keychainDbSuffix = "-db";
bool endsWithKeychainDb = (path.size() > keychainDbSuffix.size() && (0 == path.compare(path.size() - keychainDbSuffix.size(), keychainDbSuffix.size(), keychainDbSuffix)));
bool isSystemKeychain = (0 == path.compare("/Library/Keychains/System.keychain"));
bool result = false;
if(inHomeLibraryKeychains && endsWithKeychainDb && dbBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0) {
string basePath = path;
basePath.erase(basePath.end()-3, basePath.end());
attemptKeychainRename(path, basePath, dbBlobVersion);
path = mDb->name();
try {
dbBlobVersion = mDb->dbBlobVersion();
} catch (CssmError cssme) {
const char* errStr = cssmErrorString(cssme.error);
secnotice("integrity", "dbBlobVersion() after a rename failed for a CssmError: %d %s", (int) cssme.error, errStr);
return false;
} catch (...) {
secnotice("integrity", "dbBlobVersion() failed for an unknown reason after a rename");
return false;
}
endsWithKeychainDb = (path.size() > keychainDbSuffix.size() && (0 == path.compare(path.size() - keychainDbSuffix.size(), keychainDbSuffix.size(), keychainDbSuffix)));
keychainDbPath = StorageManager::makeKeychainDbFilename(path);
secnotice("integrity", "after rename, our database thinks that it is %s", path.c_str());
}
if(inHomeLibraryKeychains && dbBlobVersion != SecurityServer::DbBlob::version_partition && !endsWithKeychainDb) {
if(mDb->isLocked()) {
DLDbIdentifier mungedDLDbIdentifier = StorageManager::mungeDLDbIdentifier(mDb->dlDbIdentifier(), false);
string mungedPath(mungedDLDbIdentifier.dbName());
if(mungedPath == path) {
secnotice("integrity", "skipping upgrade for locked keychain %s\n", mDb->name());
return false;
}
}
result = keychainMigration(path, dbBlobVersion, keychainDbPath, SecurityServer::DbBlob::version_partition);
} else if(inHomeLibraryKeychains && dbBlobVersion == SecurityServer::DbBlob::version_partition && !endsWithKeychainDb) {
attemptKeychainRename(path, keychainDbPath, dbBlobVersion);
result = true;
} else if(isSystemKeychain && dbBlobVersion == SecurityServer::DbBlob::version_partition) {
secnotice("integrity", "attempting downgrade for %s version %d (%d %d %d)", path.c_str(), dbBlobVersion, inHomeLibraryKeychains, endsWithKeychainDb, isSystemKeychain);
SecurityServer::SystemKeychainKey skk(kSystemUnlockFile);
if(skk.valid()) {
CssmClient::Key systemKeychainMasterKey(csp(), skk.key(), true);
CssmClient::AclFactory::MasterKeyUnlockCredentials creds(systemKeychainMasterKey, Allocator::standard(Allocator::sensitive));
result = keychainMigration(path, dbBlobVersion, path, SecurityServer::DbBlob::version_MacOS_10_0, creds.getAccessCredentials());
} else {
secnotice("integrity", "Couldn't read System.keychain key, skipping update");
}
} else {
secnotice("integrity", "not attempting migration for %s version %d (%d %d %d)", path.c_str(), dbBlobVersion, inHomeLibraryKeychains, endsWithKeychainDb, isSystemKeychain);
mAttemptedUpgrade = true;
}
globals().storageManager.registerKeychainImpl(this);
if(result) {
string pattern = path + "_*_backup";
glob_t pglob = {};
secnotice("integrity", "globbing for %s", pattern.c_str());
int globresult = glob(pattern.c_str(), GLOB_MARK, NULL, &pglob);
if(globresult == 0) {
secnotice("integrity", "glob: %lu results", pglob.gl_pathc);
if(pglob.gl_pathc > 10) {
secnotice("integrity", "saving backup file: %s", pglob.gl_pathv[0]);
for(int i = 1; i < pglob.gl_pathc; i++) {
secnotice("integrity", "cleaning up backup file: %s", pglob.gl_pathv[i]);
unlink(pglob.gl_pathv[i]);
}
}
struct stat st;
bool pathExists = (::stat(path.c_str(), &st) == 0);
bool keychainDbPathExists = (::stat(keychainDbPath.c_str(), &st) == 0);
if(!pathExists && keychainDbPathExists && pglob.gl_pathc >= 1) {
secnotice("integrity", "moving backup file %s to %s", pglob.gl_pathv[0], path.c_str());
::rename(pglob.gl_pathv[0], path.c_str());
}
}
globfree(&pglob);
}
return result;
}
bool KeychainImpl::keychainMigration(const string oldPath, const uint32 dbBlobVersion, const string newPath, const uint32 newBlobVersion, const AccessCredentials *cred) {
secnotice("integrity", "going to migrate %s at version %d to", oldPath.c_str(), dbBlobVersion);
secnotice("integrity", " %s at version %d", newPath.c_str(), newBlobVersion);
StReadWriteLock lock(mRWLock, StReadWriteLock::TryWrite);
if(!lock.isLocked()) {
secnotice("integrity", "couldn't get read-write lock, aborting upgrade");
return false;
}
FileLockTransaction fileLockmDb(mDb);
globals().storageManager.reloadKeychain(this);
bool result = false;
try {
if(!mDb->isLocked()) {
secnotice("integrity", "have a plan to migrate database %s", mDb->name());
uint32 convertedVersion = attemptKeychainMigration(oldPath, dbBlobVersion, newPath, newBlobVersion, cred);
if(convertedVersion == newBlobVersion) {
secnotice("integrity", "conversion succeeded");
result = true;
} else {
secnotice("integrity", "conversion failed, keychain is still %d", convertedVersion);
}
} else {
secnotice("integrity", "keychain is locked, can't upgrade");
}
} catch (CssmError cssme) {
const char* errStr = cssmErrorString(cssme.error);
secnotice("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr);
} catch (...) {
secnotice("integrity", "caught unknown error");
}
secnotice("integrity", "reloading keychain after migration");
globals().storageManager.reloadKeychain(this);
secnotice("integrity", "database %s is now version %d", mDb->name(), mDb->dbBlobVersion());
return result;
}
uint32 KeychainImpl::attemptKeychainMigration(const string oldPath, const uint32 oldBlobVersion, const string newPath, const uint32 newBlobVersion, const AccessCredentials* cred) {
if(mDb->dbBlobVersion() == newBlobVersion) {
secnotice("integrity", "reloaded keychain version %d, quitting", mDb->dbBlobVersion());
return newBlobVersion;
}
mAttemptedUpgrade = true;
uint32 newDbVersion = oldBlobVersion;
if( (oldBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0 && newBlobVersion == SecurityServer::DbBlob::version_partition) ||
(oldBlobVersion == SecurityServer::DbBlob::version_partition && newBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0 && cred != NULL)) {
secnotice("integrity", "attempting migration from version %d to %d", oldBlobVersion, newBlobVersion);
Db db;
bool newFile = (oldPath != newPath);
try {
DLDbIdentifier dldbi(dlDbIdentifier().ssuid(), newPath.c_str(), dlDbIdentifier().dbLocation());
if(newFile) {
secnotice("integrity", "creating a new keychain at %s", newPath.c_str());
db = mDb->cloneTo(dldbi);
} else {
secnotice("integrity", "using old keychain at %s", newPath.c_str());
db = mDb;
}
FileLockTransaction fileLockDb(db);
if(newFile) {
fileLockDb.setDeleteOnFailure();
}
newDbVersion = db->recodeDbToVersion(newBlobVersion);
if(newDbVersion != newBlobVersion) {
secnotice("integrity", "recodeDbToVersion failed, version is still %d", newDbVersion);
return newDbVersion;
}
secnotice("integrity", "recoded db successfully, adding extra integrity");
Keychain keychain(db);
keychain->mAttemptedUpgrade = true;
keychain->mSuppressTickle = true;
SecItemClass classes[] = {kSecGenericPasswordItemClass,
kSecInternetPasswordItemClass,
kSecPublicKeyItemClass,
kSecPrivateKeyItemClass,
kSecSymmetricKeyItemClass};
for(int i = 0; i < sizeof(classes) / sizeof(classes[0]); i++) {
Item item;
KCCursor kcc = keychain->createCursor(classes[i], NULL);
kcc->setDeleteInvalidRecords(true);
while(kcc->next(item)) {
try {
if(newBlobVersion == SecurityServer::DbBlob::version_partition) {
item->setIntegrity(true);
} else if(newBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0) {
item->removeIntegrity(cred);
}
} catch(CssmError cssme) {
if (cssme.osStatus() == CSSMERR_DL_RECORD_NOT_FOUND) {
secnotice("integrity", "deleting corrupt (Not Found) record");
keychain->deleteItem(item);
} else {
throw;
}
}
}
}
db->recodeFinished();
fileLockDb.success();
secnotice("integrity", "success, returning version %d", newDbVersion);
return newDbVersion;
} catch(UnixError ue) {
secnotice("integrity", "caught UnixError: %d %s", ue.unixError(), ue.what());
} catch (CssmError cssme) {
const char* errStr = cssmErrorString(cssme.error);
secnotice("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr);
} catch (MacOSError mose) {
secnotice("integrity", "MacOSError: %d", (int)mose.osStatus());
} catch (const std::bad_cast & e) {
secnotice("integrity", "***** bad cast: %s", e.what());
} catch (...) {
secnotice("integrity", "***** unknown error");
}
} else {
secnotice("integrity", "no migration path for %s at version %d to", oldPath.c_str(), oldBlobVersion);
secnotice("integrity", " %s at version %d", newPath.c_str(), newBlobVersion);
return oldBlobVersion;
}
return oldBlobVersion;
}
void KeychainImpl::attemptKeychainRename(const string oldPath, const string newPath, uint32 blobVersion) {
secnotice("integrity", "attempting to rename keychain (%d) from %s to %s", blobVersion, oldPath.c_str(), newPath.c_str());
FileLockTransaction fileLockmDb(mDb);
globals().storageManager.reloadKeychain(this);
uint32 dbBlobVersion = SecurityServer::DbBlob::version_MacOS_10_0;
try {
dbBlobVersion = mDb->dbBlobVersion();
} catch (...) {
secnotice("integrity", "dbBlobVersion() failed for an unknown reason while renaming, aborting rename");
return;
}
if(dbBlobVersion != blobVersion) {
secnotice("integrity", "database version changed while we were grabbing the file lock; aborting rename");
return;
}
if(oldPath != mDb->name()) {
secnotice("integrity", "database location changed while we were grabbing the file lock; aborting rename");
return;
}
globals().storageManager.rename(this, newPath.c_str());
}
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);
}
const AccessCredentials *
KeychainImpl::makeCredentials()
{
return defaultCredentials();
}
const AccessCredentials *
KeychainImpl::defaultCredentials()
{
StLock<Mutex>_(mMutex);
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;
}
bool KeychainImpl::hasIntegrityProtection() {
if(mDb && (mDb->dl()->guid() == gGuidAppleCSPDL)) {
if(mDb->dbBlobVersion() >= SecurityServer::DbBlob::version_partition) {
return true;
} else {
secnotice("integrity", "keychain blob version does not support integrity");
return false;
}
} else {
secnotice("integrity", "keychain guid does not support integrity");
return false;
}
return false;
}