#include "KCCursor.h"
#include "Item.h"
#include <security_cdsa_utilities/Schema.h>
#include <security_cdsa_utilities/KeySchema.h>
#include "cssmdatetime.h"
#include "Globals.h"
#include "StorageManager.h"
#include <Security/SecKeychainItemPriv.h>
#include <Security/SecBase.h>
#include <Security/SecBasePriv.h>
#include <utilities/array_size.h>
using namespace KeychainCore;
using namespace CssmClient;
using namespace CSSMDateTimeUtils;
using namespace KeySchema;
static const CSSM_DB_ATTRIBUTE_INFO* gKeyAttributeLookupTable[] =
{
&KeyClass, &PrintName, &Alias, &Permanent, &Private, &Modifiable, &Label, &ApplicationTag, &KeyCreator,
&KeyType, &KeySizeInBits, &EffectiveKeySize, &StartDate, &EndDate, &Sensitive, &AlwaysSensitive, &Extractable,
&NeverExtractable, &Encrypt, &Decrypt, &Derive, &Sign, &Verify, &SignRecover, &VerifyRecover, &Wrap, &Unwrap
};
KCCursorImpl::KCCursorImpl(const StorageManager::KeychainList &searchList, SecItemClass itemClass, const SecKeychainAttributeList *attrList, CSSM_DB_CONJUNCTIVE dbConjunctive, CSSM_DB_OPERATOR dbOperator) :
mSearchList(searchList),
mCurrent(mSearchList.begin()),
mAllFailed(true),
mDeleteInvalidRecords(false),
mIsNewKeychain(true),
mMutex(Mutex::recursive)
{
recordType(Schema::recordTypeFor(itemClass));
if (!attrList) return;
conjunctive(dbConjunctive);
const SecKeychainAttribute *end=&attrList->attr[attrList->count];
for (const SecKeychainAttribute *attr=attrList->attr; attr != end; ++attr)
{
const CSSM_DB_ATTRIBUTE_INFO *temp;
if (attr->tag <' ' && attr->tag < array_size(gKeyAttributeLookupTable))
{
temp = gKeyAttributeLookupTable[attr->tag];
}
else
{
temp = &Schema::attributeInfo(attr->tag);
}
const CssmDbAttributeInfo &info = *temp;
void *buf = attr->data;
UInt32 length = attr->length;
uint8 timeString[16];
if (info.format() == CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE)
{
if (length == sizeof(UInt32))
{
MacSecondsToTimeString(*reinterpret_cast<const UInt32 *>(buf),
16, &timeString);
buf = &timeString;
length = 16;
}
else if (length == sizeof(SInt64))
{
MacLongDateTimeToTimeString(*reinterpret_cast<const SInt64 *>(buf),
16, &timeString);
buf = &timeString;
length = 16;
}
}
add(dbOperator ,info, CssmData(buf,length));
}
}
KCCursorImpl::KCCursorImpl(const StorageManager::KeychainList &searchList, const SecKeychainAttributeList *attrList) :
mSearchList(searchList),
mCurrent(mSearchList.begin()),
mAllFailed(true),
mDeleteInvalidRecords(false),
mIsNewKeychain(true),
mMutex(Mutex::recursive)
{
if (!attrList) return;
conjunctive(CSSM_DB_AND);
bool foundClassAttribute=false;
const SecKeychainAttribute *end=&attrList->attr[attrList->count];
for (const SecKeychainAttribute *attr=attrList->attr; attr != end; ++attr)
{
if (attr->tag!=kSecClassItemAttr) {
const CssmDbAttributeInfo &info = Schema::attributeInfo(attr->tag);
void *buf = attr->data;
UInt32 length = attr->length;
uint8 timeString[16];
if (info.format() == CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE)
{
if (length == sizeof(UInt32))
{
MacSecondsToTimeString(*reinterpret_cast<const UInt32 *>(buf),
16, &timeString);
buf = &timeString;
length = 16;
}
else if (length == sizeof(SInt64))
{
MacLongDateTimeToTimeString(*reinterpret_cast<const SInt64 *>(buf),
16, &timeString);
buf = &timeString;
length = 16;
}
}
add(CSSM_DB_EQUAL,info, CssmData(buf,length));
continue;
}
if (foundClassAttribute || attr->length != sizeof(SecItemClass))
MacOSError::throwMe(errSecParam);
recordType(Schema::recordTypeFor(*reinterpret_cast<SecItemClass *>(attr->data)));
foundClassAttribute=true;
}
}
KCCursorImpl::~KCCursorImpl() _NOEXCEPT
{
}
bool
KCCursorImpl::next(Item &item)
{
StLock<Mutex>_(mMutex);
DbAttributes dbAttributes;
DbUniqueRecord uniqueId;
OSStatus status = 0;
for (;;)
{
Item tempItem = NULL;
{
while (!mDbCursor)
{
newKeychain(mCurrent);
if (mCurrent == mSearchList.end())
{
if (mAllFailed && status)
CssmError::throwMe(status);
return false;
}
try
{
Keychain &kc = *mCurrent;
Mutex* mutex = kc->getKeychainMutex();
StLock<Mutex> _(*mutex);
(*mCurrent)->database()->activate();
mDbCursor = DbCursor((*mCurrent)->database(), *this);
}
catch(const CommonError &err)
{
++mCurrent;
mIsNewKeychain = true;
}
}
Keychain &kc = *mCurrent;
Mutex* mutex = kc->getKeychainMutex();
StLock<Mutex> _(*mutex);
bool gotRecord;
try
{
dbAttributes.clear();
gotRecord = mDbCursor->next(&dbAttributes, NULL, uniqueId);
mAllFailed = false;
}
catch(const CommonError &err)
{
status = err.osStatus();
gotRecord = false;
dbAttributes.invalidate();
}
catch(...)
{
status = errSecItemNotFound;
gotRecord = false;
}
if (!gotRecord)
{
++mCurrent;
mDbCursor = DbCursor();
mIsNewKeychain = true;
continue;
}
if (dbAttributes.recordType() == CSSM_DL_DB_RECORD_METADATA &&
mDbCursor->recordType() == CSSM_DL_DB_RECORD_ANY)
continue;
if (dbAttributes.recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY)
{
bool groupKey = false;
try
{
dbAttributes.add(KeySchema::Label);
Db db((*mCurrent)->database());
CSSM_RETURN getattr_result = CSSM_DL_DataGetFromUniqueRecordId(db->handle(), uniqueId, &dbAttributes, NULL);
if (getattr_result == CSSM_OK)
{
CssmDbAttributeData *label = dbAttributes.find(KeySchema::Label);
CssmData attrData;
if (label)
attrData = *label;
if (attrData.length() > 4 && !memcmp(attrData.data(), "ssgp", 4))
groupKey = true;
}
else
{
dbAttributes.invalidate();
}
}
catch (...) {}
if (groupKey)
continue;
}
try {
tempItem = (*mCurrent)->item(dbAttributes.recordType(), uniqueId);
} catch(CssmError cssme) {
if (mDeleteInvalidRecords) {
const char* errStr = cssmErrorString(cssme.error);
secnotice("integrity", "deleting corrupt record because: %d %s", (int) cssme.error, errStr);
deleteInvalidRecord(uniqueId);
continue;
} else {
throw;
}
}
}
item = tempItem;
break;
}
return true;
}
void KCCursorImpl::deleteInvalidRecord(DbUniqueRecord& uniqueId) {
try {
uniqueId->deleteRecord();
} catch(CssmError delcssme) {
if (delcssme.osStatus() == CSSMERR_DL_RECORD_NOT_FOUND) {
secnotice("integrity", "couldn't delete nonexistent record (this is okay)");
} else {
throw;
}
}
}
bool KCCursorImpl::mayDelete()
{
if (mDbCursor.get() != NULL)
{
return mDbCursor->isIdle();
}
else
{
return true;
}
}
void KCCursorImpl::setDeleteInvalidRecords(bool deleteRecord) {
mDeleteInvalidRecords = deleteRecord;
}
void KCCursorImpl::newKeychain(StorageManager::KeychainList::iterator kcIter) {
if(!mIsNewKeychain) {
return;
}
if(kcIter != mSearchList.end()) {
(*kcIter)->performKeychainUpgradeIfNeeded();
(*kcIter)->tickle();
}
mIsNewKeychain = false;
}