X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/Item.cpp?ds=inline diff --git a/Security/libsecurity_keychain/lib/Item.cpp b/Security/libsecurity_keychain/lib/Item.cpp new file mode 100644 index 00000000..057f0618 --- /dev/null +++ b/Security/libsecurity_keychain/lib/Item.cpp @@ -0,0 +1,1548 @@ +/* + * 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@ + */ + + +// +// Item.cpp +// + +#include "Item.h" + +#include "Certificate.h" +#include "KeyItem.h" +#include "ExtendedAttribute.h" + +#include "Globals.h" +#include +#include "KCEventNotifier.h" +#include "KCExceptions.h" +#include "cssmdatetime.h" +#include +#include +#include +#include +#include +#include + +#define SENDACCESSNOTIFICATIONS 1 + +//%%% schema indexes should be defined in Schema.h +#define _kSecAppleSharePasswordItemClass 'ashp' +#define APPLEDB_CSSM_PRINTNAME_ATTRIBUTE 1 /* schema index for label attribute of keys or certificates */ +#define APPLEDB_GENERIC_PRINTNAME_ATTRIBUTE 7 /* schema index for label attribute of password items */ +#define IS_PASSWORD_ITEM_CLASS(X) ( (X) == kSecInternetPasswordItemClass || \ + (X) == kSecGenericPasswordItemClass || \ + (X) == _kSecAppleSharePasswordItemClass ) ? 1 : 0 + +using namespace KeychainCore; +using namespace CSSMDateTimeUtils; + +// +// ItemImpl +// + +// NewItemImpl constructor +ItemImpl::ItemImpl(SecItemClass itemClass, OSType itemCreator, UInt32 length, const void* data, bool dontDoAttributes) + : mDbAttributes(new DbAttributes()), + mKeychain(NULL), + secd_PersistentRef(NULL), + mDoNotEncrypt(false), + mInCache(false), + mMutex(Mutex::recursive) +{ + if (length && data) + mData = new CssmDataContainer(data, length); + + mDbAttributes->recordType(Schema::recordTypeFor(itemClass)); + + if (itemCreator) + mDbAttributes->add(Schema::attributeInfo(kSecCreatorItemAttr), itemCreator); +} + +ItemImpl::ItemImpl(SecItemClass itemClass, SecKeychainAttributeList *attrList, UInt32 length, const void* data) + : mDbAttributes(new DbAttributes()), + mKeychain(NULL), + secd_PersistentRef(NULL), + mDoNotEncrypt(false), + mInCache(false), + mMutex(Mutex::recursive) +{ + if (length && data) + mData = new CssmDataContainer(data, length); + + + mDbAttributes->recordType(Schema::recordTypeFor(itemClass)); + + if(attrList) + { + for(UInt32 i=0; i < attrList->count; i++) + { + mDbAttributes->add(Schema::attributeInfo(attrList->attr[i].tag), CssmData(attrList->attr[i].data, attrList->attr[i].length)); + } + } +} + +// DbItemImpl constructor +ItemImpl::ItemImpl(const Keychain &keychain, const PrimaryKey &primaryKey, const DbUniqueRecord &uniqueId) + : mUniqueId(uniqueId), mKeychain(keychain), mPrimaryKey(primaryKey), + secd_PersistentRef(NULL), mDoNotEncrypt(false), mInCache(false), + mMutex(Mutex::recursive) +{ +} + +// PrimaryKey ItemImpl constructor +ItemImpl::ItemImpl(const Keychain &keychain, const PrimaryKey &primaryKey) +: mKeychain(keychain), mPrimaryKey(primaryKey), secd_PersistentRef(NULL), mDoNotEncrypt(false), + mInCache(false), + mMutex(Mutex::recursive) +{ +} + +ItemImpl* ItemImpl::make(const Keychain &keychain, const PrimaryKey &primaryKey, const CssmClient::DbUniqueRecord &uniqueId) +{ + ItemImpl* ii = new ItemImpl(keychain, primaryKey, uniqueId); + keychain->addItem(primaryKey, ii); + return ii; +} + + + +ItemImpl* ItemImpl::make(const Keychain &keychain, const PrimaryKey &primaryKey) +{ + ItemImpl* ii = new ItemImpl(keychain, primaryKey); + keychain->addItem(primaryKey, ii); + return ii; +} + + + +// Constructor used when copying an item to a keychain. + +ItemImpl::ItemImpl(ItemImpl &item) : + mData(item.modifiedData() ? NULL : new CssmDataContainer()), + mDbAttributes(new DbAttributes()), + mKeychain(NULL), + secd_PersistentRef(NULL), + mDoNotEncrypt(false), + mInCache(false), + mMutex(Mutex::recursive) +{ + mDbAttributes->recordType(item.recordType()); + CSSM_DB_RECORD_ATTRIBUTE_INFO *schemaAttributes = NULL; + + if (item.mKeychain) { + // get the entire source item from its keychain. This requires figuring + // out the schema for the item based on its record type. + + for (uint32 i = 0; i < Schema::DBInfo.NumberOfRecordTypes; i++) + if (item.recordType() == Schema::DBInfo.RecordAttributeNames[i].DataRecordType) { + schemaAttributes = &Schema::DBInfo.RecordAttributeNames[i]; + break; + } + + if (schemaAttributes == NULL) + // the source item is invalid + MacOSError::throwMe(errSecInvalidItemRef); + + for (uint32 i = 0; i < schemaAttributes->NumberOfAttributes; i++) + mDbAttributes->add(schemaAttributes->AttributeInfo[i]); + + item.getContent(mDbAttributes.get(), mData.get()); + } + + // @@@ We don't deal with modified attributes. + + if (item.modifiedData()) + // the copied data comes from the source item + mData = new CssmDataContainer(item.modifiedData()->Data, + item.modifiedData()->Length); +} + +ItemImpl::~ItemImpl() +{ + if (secd_PersistentRef) { + CFRelease(secd_PersistentRef); + } +} + + + +Mutex* +ItemImpl::getMutexForObject() +{ + if (mKeychain.get()) + { + return mKeychain->getKeychainMutex(); + } + + return NULL; +} + + + +void +ItemImpl::aboutToDestruct() +{ + if (mKeychain && *mPrimaryKey) + { + mKeychain->removeItem(mPrimaryKey, this); + } +} + + + +void +ItemImpl::didModify() +{ + StLock_(mMutex); + mData = NULL; + mDbAttributes.reset(NULL); +} + +const CSSM_DATA & +ItemImpl::defaultAttributeValue(const CSSM_DB_ATTRIBUTE_INFO &info) +{ + static const uint32 zeroInt = 0; + static const double zeroDouble = 0.0; + static const char timeBytes[] = "20010101000000Z"; + + static const CSSM_DATA defaultFourBytes = { 4, (uint8 *) &zeroInt }; + static const CSSM_DATA defaultEightBytes = { 8, (uint8 *) &zeroDouble }; + static const CSSM_DATA defaultTime = { 16, (uint8 *) timeBytes }; + static const CSSM_DATA defaultZeroBytes = { 0, NULL }; + + switch (info.AttributeFormat) + { + case CSSM_DB_ATTRIBUTE_FORMAT_SINT32: + case CSSM_DB_ATTRIBUTE_FORMAT_UINT32: + return defaultFourBytes; + + case CSSM_DB_ATTRIBUTE_FORMAT_REAL: + return defaultEightBytes; + + case CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE: + return defaultTime; + + default: + return defaultZeroBytes; + } +} + + + +PrimaryKey ItemImpl::addWithCopyInfo (Keychain &keychain, bool isCopy) +{ + StLock_(mMutex); + // If we already have a Keychain we can't be added. + if (mKeychain) + MacOSError::throwMe(errSecDuplicateItem); + + // If we don't have any attributes we can't be added. + // (this might occur if attempting to add the item twice, since our attributes + // and data are set to NULL at the end of this function.) + if (!mDbAttributes.get()) + MacOSError::throwMe(errSecDuplicateItem); + + CSSM_DB_RECORDTYPE recordType = mDbAttributes->recordType(); + + // update the creation and update dates on the new item + if (!isCopy) + { + KeychainSchema schema = keychain->keychainSchema(); + SInt64 date; + GetCurrentMacLongDateTime(date); + if (schema->hasAttribute(recordType, kSecCreationDateItemAttr)) + { + setAttribute(schema->attributeInfoFor(recordType, kSecCreationDateItemAttr), date); + } + + if (schema->hasAttribute(recordType, kSecModDateItemAttr)) + { + setAttribute(schema->attributeInfoFor(recordType, kSecModDateItemAttr), date); + } + } + + // If the label (PrintName) attribute isn't specified, set a default label. + if (!mDoNotEncrypt && !mDbAttributes->find(Schema::attributeInfo(kSecLabelItemAttr))) + { + // if doNotEncrypt was set all of the attributes are wrapped in the data blob. Don't calculate here. + CssmDbAttributeData *label = NULL; + switch (recordType) + { + case CSSM_DL_DB_RECORD_GENERIC_PASSWORD: + label = mDbAttributes->find(Schema::attributeInfo(kSecServiceItemAttr)); + break; + + case CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD: + case CSSM_DL_DB_RECORD_INTERNET_PASSWORD: + label = mDbAttributes->find(Schema::attributeInfo(kSecServerItemAttr)); + // if AppleShare server name wasn't specified, try the server address + if (!label) label = mDbAttributes->find(Schema::attributeInfo(kSecAddressItemAttr)); + break; + + default: + break; + } + // if all else fails, use the account name. + if (!label) + label = mDbAttributes->find(Schema::attributeInfo(kSecAccountItemAttr)); + + if (label && label->size()) + setAttribute (Schema::attributeInfo(kSecLabelItemAttr), label->at(0)); + } + + // get the attributes that are part of the primary key + const CssmAutoDbRecordAttributeInfo &primaryKeyInfos = + keychain->primaryKeyInfosFor(recordType); + + // make sure each primary key element has a value in the item, otherwise + // the database will complain. we make a set of the provided attribute infos + // to avoid O(N^2) behavior. + + DbAttributes *attributes = mDbAttributes.get(); + typedef set InfoSet; + InfoSet infoSet; + + if (!mDoNotEncrypt) + { + // make a set of all the attributes in the key + for (uint32 i = 0; i < attributes->size(); i++) + infoSet.insert(attributes->at(i).Info); + + for (uint32 i = 0; i < primaryKeyInfos.size(); i++) { // check to make sure all required attributes are in the key + InfoSet::const_iterator it = infoSet.find(primaryKeyInfos.at(i)); + + if (it == infoSet.end()) { // not in the key? add the default + // we need to add a default value to the item attributes + attributes->add(primaryKeyInfos.at(i), defaultAttributeValue(primaryKeyInfos.at(i))); + } + } + } + + Db db(keychain->database()); + if (mDoNotEncrypt) + { + mUniqueId = db->insertWithoutEncryption (recordType, NULL, mData.get()); + } + else if (useSecureStorage(db)) + { + // Add the item to the secure storage db + SSDbImpl* impl = dynamic_cast(&(*db)); + if (impl == NULL) + { + CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); + } + + SSDb ssDb(impl); + + TrackingAllocator allocator(Allocator::standard()); + + // hhs replaced with the new aclFactory class + AclFactory aclFactory; + const AccessCredentials *nullCred = aclFactory.nullCred(); + + SecPointer access = mAccess; + if (!access) { + // create default access controls for the new item + CssmDbAttributeData *data = mDbAttributes->find(Schema::attributeInfo(kSecLabelItemAttr)); + string printName = data ? CssmData::overlay(data->Value[0]).toString() : "keychain item"; + access = new Access(printName); + + // special case for "iTools" password - allow anyone to decrypt the item + if (recordType == CSSM_DL_DB_RECORD_GENERIC_PASSWORD) + { + CssmDbAttributeData *data = mDbAttributes->find(Schema::attributeInfo(kSecServiceItemAttr)); + if (data && data->Value[0].Length == 6 && !memcmp("iTools", data->Value[0].Data, 6)) + { + typedef vector > AclSet; + AclSet acls; + access->findAclsForRight(CSSM_ACL_AUTHORIZATION_DECRYPT, acls); + for (AclSet::const_iterator it = acls.begin(); it != acls.end(); it++) + (*it)->form(ACL::allowAllForm); + } + } + } + + // Get the handle of the DL underlying this CSPDL. + CSSM_DL_DB_HANDLE dldbh; + db->passThrough(CSSM_APPLECSPDL_DB_GET_HANDLE, NULL, + reinterpret_cast(&dldbh)); + + // Turn off autocommit on the underlying DL and remember the old state. + CSSM_BOOL autoCommit = CSSM_TRUE; + ObjectImpl::check(CSSM_DL_PassThrough(dldbh, + CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, + 0, reinterpret_cast(&autoCommit))); + + try + { + // Create a new SSGroup with temporary access controls + Access::Maker maker; + ResourceControlContext prototype; + maker.initialOwner(prototype, nullCred); + SSGroup ssGroup(ssDb, &prototype); + + try + { + // Insert the record using the newly created group. + mUniqueId = ssDb->insert(recordType, mDbAttributes.get(), + mData.get(), ssGroup, maker.cred()); + } + catch(...) + { + ssGroup->deleteKey(nullCred); + throw; + } + + // now finalize the access controls on the group + access->setAccess(*ssGroup, maker); + mAccess = NULL; // use them and lose them + if (autoCommit) + { + // autoCommit was on so commit now that we are done and turn + // it back on. + ObjectImpl::check(CSSM_DL_PassThrough(dldbh, + CSSM_APPLEFILEDL_COMMIT, NULL, NULL)); + CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, + reinterpret_cast(autoCommit), NULL); + } + } + catch (...) + { + if (autoCommit) + { + // autoCommit was off so rollback since we failed and turn + // autoCommit back on. + CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_ROLLBACK, NULL, NULL); + CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, + reinterpret_cast(autoCommit), NULL); + } + throw; + } + } + else + { + // add the item to the (regular) db + mUniqueId = db->insert(recordType, mDbAttributes.get(), mData.get()); + } + + mPrimaryKey = keychain->makePrimaryKey(recordType, mUniqueId); + mKeychain = keychain; + + // Forget our data and attributes. + mData = NULL; + mDbAttributes.reset(NULL); + + return mPrimaryKey; +} + + + +PrimaryKey +ItemImpl::add (Keychain &keychain) +{ + return addWithCopyInfo (keychain, false); +} + + + +Item +ItemImpl::copyTo(const Keychain &keychain, Access *newAccess) +{ + StLock_(mMutex); + Item item(*this); + if (newAccess) + item->setAccess(newAccess); + else + { + /* Attempt to copy the access from the current item to the newly created one. */ + SSGroup myGroup = group(); + if (myGroup) + { + SecPointer access = new Access(*myGroup); + item->setAccess(access); + } + } + + keychain->addCopy(item); + return item; +} + +void +ItemImpl::update() +{ + StLock_(mMutex); + if (!mKeychain) + MacOSError::throwMe(errSecNoSuchKeychain); + + // Don't update if nothing changed. + if (!isModified()) + return; + + CSSM_DB_RECORDTYPE aRecordType = recordType(); + KeychainSchema schema = mKeychain->keychainSchema(); + + // Update the modification date on the item if there is a mod date attribute. + if (schema->hasAttribute(aRecordType, kSecModDateItemAttr)) + { + SInt64 date; + GetCurrentMacLongDateTime(date); + setAttribute(schema->attributeInfoFor(aRecordType, kSecModDateItemAttr), date); + } + + // Make sure that we have mUniqueId + dbUniqueRecord(); + Db db(mUniqueId->database()); + if (mDoNotEncrypt) + { + CSSM_DB_RECORD_ATTRIBUTE_DATA attrData; + memset (&attrData, 0, sizeof (attrData)); + attrData.DataRecordType = aRecordType; + + mUniqueId->modifyWithoutEncryption(aRecordType, + &attrData, + mData.get(), + CSSM_DB_MODIFY_ATTRIBUTE_REPLACE); + } + else if (useSecureStorage(db)) + { + // Add the item to the secure storage db + SSDbUniqueRecordImpl * impl = dynamic_cast(&(*mUniqueId)); + if (impl == NULL) + { + CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); + } + + SSDbUniqueRecord ssUniqueId(impl); + + // @@@ Share this instance + const AccessCredentials *autoPrompt = globals().itemCredentials(); + + + // Only call this is user interaction is enabled. + ssUniqueId->modify(aRecordType, + mDbAttributes.get(), + mData.get(), + CSSM_DB_MODIFY_ATTRIBUTE_REPLACE, + autoPrompt); + } + else + { + mUniqueId->modify(aRecordType, + mDbAttributes.get(), + mData.get(), + CSSM_DB_MODIFY_ATTRIBUTE_REPLACE); + } + + if (!mDoNotEncrypt) + { + PrimaryKey oldPK = mPrimaryKey; + mPrimaryKey = mKeychain->makePrimaryKey(aRecordType, mUniqueId); + + // Forget our data and attributes. + mData = NULL; + mDbAttributes.reset(NULL); + + // Let the Keychain update what it needs to. + mKeychain->didUpdate(this, oldPK, mPrimaryKey); + } +} + +void +ItemImpl::getClass(SecKeychainAttribute &attr, UInt32 *actualLength) +{ + StLock_(mMutex); + if (actualLength) + *actualLength = sizeof(SecItemClass); + + if (attr.length < sizeof(SecItemClass)) + MacOSError::throwMe(errSecBufferTooSmall); + + SecItemClass aClass = Schema::itemClassFor(recordType()); + memcpy(attr.data, &aClass, sizeof(SecItemClass)); +} + +void +ItemImpl::setAttribute(SecKeychainAttribute& attr) +{ + StLock_(mMutex); + setAttribute(Schema::attributeInfo(attr.tag), CssmData(attr.data, attr.length)); +} + +CSSM_DB_RECORDTYPE +ItemImpl::recordType() +{ + StLock_(mMutex); + if (mDbAttributes.get()) + return mDbAttributes->recordType(); + + return mPrimaryKey->recordType(); +} + +const DbAttributes * +ItemImpl::modifiedAttributes() +{ + StLock_(mMutex); + return mDbAttributes.get(); +} + +const CssmData * +ItemImpl::modifiedData() +{ + StLock_(mMutex); + return mData.get(); +} + +void +ItemImpl::setData(UInt32 length,const void *data) +{ + StLock_(mMutex); + mData = new CssmDataContainer(data, length); +} + +void +ItemImpl::setAccess(Access *newAccess) +{ + StLock_(mMutex); + mAccess = newAccess; +} + +CssmClient::DbUniqueRecord +ItemImpl::dbUniqueRecord() +{ + StLock_(mMutex); + if (!isPersistent()) // is there no database attached? + { + MacOSError::throwMe(errSecNotAvailable); + } + + if (!mUniqueId) + { + DbCursor cursor(mPrimaryKey->createCursor(mKeychain)); + if (!cursor->next(NULL, NULL, mUniqueId)) + MacOSError::throwMe(errSecInvalidItemRef); + } + + return mUniqueId; +} + +PrimaryKey +ItemImpl::primaryKey() +{ + return mPrimaryKey; +} + +bool +ItemImpl::isPersistent() +{ + return mKeychain; +} + +bool +ItemImpl::isModified() +{ + StLock_(mMutex); + return mData.get() || mDbAttributes.get(); +} + +Keychain +ItemImpl::keychain() +{ + return mKeychain; +} + +bool +ItemImpl::operator < (const ItemImpl &other) +{ + if (mData && *mData) + { + // Pointer compare + return this < &other; + } + + return mPrimaryKey < other.mPrimaryKey; +} + +void +ItemImpl::setAttribute(const CssmDbAttributeInfo &info, const CssmPolyData &data) +{ + StLock_(mMutex); + if (!mDbAttributes.get()) + { + mDbAttributes.reset(new DbAttributes()); + mDbAttributes->recordType(mPrimaryKey->recordType()); + } + + size_t length = data.Length; + const void *buf = reinterpret_cast(data.Data); + uint8 timeString[16]; + + // XXX This code is duplicated in KCCursorImpl::KCCursorImpl() + // Convert a 4 or 8 byte TIME_DATE to a CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE + // style attribute value. + if (info.format() == CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE) + { + if (length == sizeof(UInt32)) + { + MacSecondsToTimeString(*reinterpret_cast(buf), 16, &timeString); + buf = &timeString; + length = 16; + } + else if (length == sizeof(SInt64)) + { + MacLongDateTimeToTimeString(*reinterpret_cast(buf), 16, &timeString); + buf = &timeString; + length = 16; + } + } + + mDbAttributes->add(info, CssmData(const_cast(buf), length)); +} + +void +ItemImpl::modifyContent(const SecKeychainAttributeList *attrList, UInt32 dataLength, const void *inData) +{ + StLock_(mMutex); + if (!mDbAttributes.get()) + { + mDbAttributes.reset(new DbAttributes()); + mDbAttributes->recordType(mPrimaryKey->recordType()); + } + + if(attrList) // optional + { + for(UInt32 ix=0; ix < attrList->count; ix++) + { + SecKeychainAttrType attrTag = attrList->attr[ix].tag; + + if (attrTag == APPLEDB_CSSM_PRINTNAME_ATTRIBUTE) + { + // must remap a caller-supplied kSecKeyPrintName attribute tag for key items, since it isn't in the schema + // (note that this will ultimately match kGenericPrintName in Schema.cpp) + attrTag = kSecLabelItemAttr; + } + + mDbAttributes->add(Schema::attributeInfo(attrTag), CssmData(attrList->attr[ix].data, attrList->attr[ix].length)); + } + } + + if(inData) + { + mData = new CssmDataContainer(inData, dataLength); + } + + update(); +} + +void +ItemImpl::getContent(SecItemClass *itemClass, SecKeychainAttributeList *attrList, UInt32 *length, void **outData) +{ + StLock_(mMutex); + // If the data hasn't been set we can't return it. + if (!mKeychain && outData) + { + CssmData *data = mData.get(); + if (!data) + MacOSError::throwMe(errSecDataNotAvailable); + } + // TODO: need to check and make sure attrs are valid and handle error condition + + + if (itemClass) + *itemClass = Schema::itemClassFor(recordType()); + + bool getDataFromDatabase = mKeychain && mPrimaryKey; + if (getDataFromDatabase) // are we attached to a database? + { + dbUniqueRecord(); + + // get the number of attributes requested by the caller + UInt32 attrCount = attrList ? attrList->count : 0; + + // make a DBAttributes structure and populate it + DbAttributes dbAttributes(mUniqueId->database(), attrCount); + for (UInt32 ix = 0; ix < attrCount; ++ix) + { + dbAttributes.add(Schema::attributeInfo(attrList->attr[ix].tag)); + } + + // request the data from the database (since we are a reference "item" and the data is really stored there) + CssmDataContainer itemData; + getContent(&dbAttributes, outData ? &itemData : NULL); + + // retrieve the data from result + for (UInt32 ix = 0; ix < attrCount; ++ix) + { + if (dbAttributes.at(ix).NumberOfValues > 0) + { + attrList->attr[ix].data = dbAttributes.at(ix).Value[0].Data; + attrList->attr[ix].length = (UInt32)dbAttributes.at(ix).Value[0].Length; + + // We don't want the data released, it is up the client + dbAttributes.at(ix).Value[0].Data = NULL; + dbAttributes.at(ix).Value[0].Length = 0; + } + else + { + attrList->attr[ix].data = NULL; + attrList->attr[ix].length = 0; + } + } + + // clean up + if (outData) + { + *outData=itemData.data(); + itemData.Data = NULL; + + if (length) + *length=(UInt32)itemData.length(); + itemData.Length = 0; + } + } + else + { + getLocalContent(attrList, length, outData); + } + + // Inform anyone interested that we are doing this +#if SENDACCESSNOTIFICATIONS + if (outData) + { + secdebug("kcnotify", "ItemImpl::getContent(%p, %p, %p, %p) retrieved content", + itemClass, attrList, length, outData); + + KCEventNotifier::PostKeychainEvent(kSecDataAccessEvent, mKeychain, this); + } +#endif +} + +void +ItemImpl::freeContent(SecKeychainAttributeList *attrList, void *data) +{ + Allocator &allocator = Allocator::standard(); // @@@ This might not match the one used originally + if (data) + allocator.free(data); + + UInt32 attrCount = attrList ? attrList->count : 0; + for (UInt32 ix = 0; ix < attrCount; ++ix) + { + allocator.free(attrList->attr[ix].data); + attrList->attr[ix].data = NULL; + } +} + +void +ItemImpl::modifyAttributesAndData(const SecKeychainAttributeList *attrList, UInt32 dataLength, const void *inData) +{ + StLock_(mMutex); + if (!mKeychain) + MacOSError::throwMe(errSecNoSuchKeychain); + + if (!mDoNotEncrypt) + { + if (!mDbAttributes.get()) + { + mDbAttributes.reset(new DbAttributes()); + mDbAttributes->recordType(mPrimaryKey->recordType()); + } + + CSSM_DB_RECORDTYPE recordType = mDbAttributes->recordType(); + UInt32 attrCount = attrList ? attrList->count : 0; + for (UInt32 ix = 0; ix < attrCount; ix++) + { + SecKeychainAttrType attrTag = attrList->attr[ix].tag; + + if (attrTag == kSecLabelItemAttr) + { + // must remap a caller-supplied label attribute tag for password items, since it isn't in the schema + // (note that this will ultimately match kGenericPrintName in Schema.cpp) + if (IS_PASSWORD_ITEM_CLASS( Schema::itemClassFor(recordType) )) + attrTag = APPLEDB_GENERIC_PRINTNAME_ATTRIBUTE; + } + + CssmDbAttributeInfo info=mKeychain->attributeInfoFor(recordType, attrTag); + + if (attrList->attr[ix].length || info.AttributeFormat==CSSM_DB_ATTRIBUTE_FORMAT_STRING || info.AttributeFormat==CSSM_DB_ATTRIBUTE_FORMAT_BLOB + || info.AttributeFormat==CSSM_DB_ATTRIBUTE_FORMAT_STRING || info.AttributeFormat==CSSM_DB_ATTRIBUTE_FORMAT_BIG_NUM + || info.AttributeFormat==CSSM_DB_ATTRIBUTE_FORMAT_MULTI_UINT32) + mDbAttributes->add(info, CssmData(attrList->attr[ix].data, attrList->attr[ix].length)); + else + mDbAttributes->add(info); + } + } + + if(inData) + { + mData = new CssmDataContainer(inData, dataLength); + } + + update(); +} + +void +ItemImpl::getAttributesAndData(SecKeychainAttributeInfo *info, SecItemClass *itemClass, + SecKeychainAttributeList **attrList, UInt32 *length, void **outData) +{ + StLock_(mMutex); + // If the data hasn't been set we can't return it. + if (!mKeychain && outData) + { + CssmData *data = mData.get(); + if (!data) + MacOSError::throwMe(errSecDataNotAvailable); + } + // TODO: need to check and make sure attrs are valid and handle error condition + + SecItemClass myItemClass = Schema::itemClassFor(recordType()); + if (itemClass) + *itemClass = myItemClass; + + // @@@ This call won't work for floating items (like certificates). + dbUniqueRecord(); + + UInt32 attrCount = info ? info->count : 0; + DbAttributes dbAttributes(mUniqueId->database(), attrCount); + for (UInt32 ix = 0; ix < attrCount; ix++) + { + CssmDbAttributeData &record = dbAttributes.add(); + record.Info.AttributeNameFormat=CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER; + record.Info.Label.AttributeID=info->tag[ix]; + + if (record.Info.Label.AttributeID == kSecLabelItemAttr) + { + // must remap a caller-supplied label attribute tag for password items, since it isn't in the schema + if (IS_PASSWORD_ITEM_CLASS( myItemClass )) + record.Info.Label.AttributeID = APPLEDB_GENERIC_PRINTNAME_ATTRIBUTE; + } + } + + CssmDataContainer itemData; + getContent(&dbAttributes, outData ? &itemData : NULL); + + if (info && attrList) + { + SecKeychainAttributeList *theList=reinterpret_cast(malloc(sizeof(SecKeychainAttributeList))); + SecKeychainAttribute *attr=reinterpret_cast(malloc(sizeof(SecKeychainAttribute)*attrCount)); + theList->count=attrCount; + theList->attr=attr; + + for (UInt32 ix = 0; ix < attrCount; ++ix) + { + attr[ix].tag=info->tag[ix]; + + if (dbAttributes.at(ix).NumberOfValues > 0) + { + attr[ix].data = dbAttributes.at(ix).Value[0].Data; + attr[ix].length = (UInt32)dbAttributes.at(ix).Value[0].Length; + + // We don't want the data released, it is up the client + dbAttributes.at(ix).Value[0].Data = NULL; + dbAttributes.at(ix).Value[0].Length = 0; + } + else + { + attr[ix].data = NULL; + attr[ix].length = 0; + } + } + *attrList=theList; + } + + if (outData) + { + *outData=itemData.data(); + itemData.Data=NULL; + + if (length) *length=(UInt32)itemData.length(); + itemData.Length=0; + +#if SENDACCESSNOTIFICATIONS + secdebug("kcnotify", "ItemImpl::getAttributesAndData(%p, %p, %p, %p, %p) retrieved data", + info, itemClass, attrList, length, outData); + + KCEventNotifier::PostKeychainEvent(kSecDataAccessEvent, mKeychain, this); +#endif + } + +} + +void +ItemImpl::freeAttributesAndData(SecKeychainAttributeList *attrList, void *data) +{ + Allocator &allocator = Allocator::standard(); // @@@ This might not match the one used originally + + if (data) + allocator.free(data); + + if (attrList) + { + for (UInt32 ix = 0; ix < attrList->count; ++ix) + { + allocator.free(attrList->attr[ix].data); + } + free(attrList->attr); + free(attrList); + } +} + +void +ItemImpl::getAttribute(SecKeychainAttribute& attr, UInt32 *actualLength) +{ + StLock_(mMutex); + if (attr.tag == kSecClassItemAttr) + return getClass(attr, actualLength); + + if (mDbAttributes.get()) + { + CssmDbAttributeData *data = mDbAttributes->find(Schema::attributeInfo(attr.tag)); + if (data) + { + getAttributeFrom(data, attr, actualLength); + return; + } + } + + if (!mKeychain) + MacOSError::throwMe(errSecNoSuchAttr); + + dbUniqueRecord(); + DbAttributes dbAttributes(mUniqueId->database(), 1); + dbAttributes.add(Schema::attributeInfo(attr.tag)); + mUniqueId->get(&dbAttributes, NULL); + getAttributeFrom(&dbAttributes.at(0), attr, actualLength); +} + +void +ItemImpl::getAttributeFrom(CssmDbAttributeData *data, SecKeychainAttribute &attr, UInt32 *actualLength) +{ + StLock_(mMutex); + static const uint32 zero = 0; + UInt32 length; + const void *buf = NULL; + + // Temporary storage for buf. + sint64 macLDT; + uint32 macSeconds; + sint16 svalue16; + uint16 uvalue16; + sint8 svalue8; + uint8 uvalue8; + + if (!data) + length = 0; + else if (data->size() < 1) // Attribute has no values. + { + if (data->format() == CSSM_DB_ATTRIBUTE_FORMAT_SINT32 + || data->format() == CSSM_DB_ATTRIBUTE_FORMAT_UINT32) + { + length = sizeof(zero); + buf = &zero; + } + else if (CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE) + length = 0; // Should we throw here? + else // All other formats + length = 0; + } + else // Get the first value + { + length = (UInt32)data->Value[0].Length; + buf = data->Value[0].Data; + + if (data->format() == CSSM_DB_ATTRIBUTE_FORMAT_SINT32) + { + if (attr.length == sizeof(sint8)) + { + length = attr.length; + svalue8 = sint8(*reinterpret_cast(buf)); + buf = &svalue8; + } + else if (attr.length == sizeof(sint16)) + { + length = attr.length; + svalue16 = sint16(*reinterpret_cast(buf)); + buf = &svalue16; + } + } + else if (data->format() == CSSM_DB_ATTRIBUTE_FORMAT_UINT32) + { + if (attr.length == sizeof(uint8)) + { + length = attr.length; + uvalue8 = uint8(*reinterpret_cast(buf)); + buf = &uvalue8; + } + else if (attr.length == sizeof(uint16)) + { + length = attr.length; + uvalue16 = uint16(*reinterpret_cast(buf)); + buf = &uvalue16; + } + } + else if (data->format() == CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE) + { + if (attr.length == sizeof(uint32)) + { + TimeStringToMacSeconds(data->Value[0], macSeconds); + buf = &macSeconds; + length = attr.length; + } + else if (attr.length == sizeof(sint64)) + { + TimeStringToMacLongDateTime(data->Value[0], macLDT); + buf = &macLDT; + length = attr.length; + } + } + } + + if (actualLength) + *actualLength = length; + + if (length) + { + if (attr.length < length) + MacOSError::throwMe(errSecBufferTooSmall); + + memcpy(attr.data, buf, length); + } +} + +void +ItemImpl::getData(CssmDataContainer& outData) +{ + StLock_(mMutex); + if (!mKeychain) + { + CssmData *data = mData.get(); + // If the data hasn't been set we can't return it. + if (!data) + MacOSError::throwMe(errSecDataNotAvailable); + + outData = *data; + return; + } + + getContent(NULL, &outData); + +#if SENDACCESSNOTIFICATIONS + secdebug("kcnotify", "ItemImpl::getData retrieved data"); + + //%%% be done elsewhere, but here is good for now + KCEventNotifier::PostKeychainEvent(kSecDataAccessEvent, mKeychain, this); +#endif +} + +SSGroup +ItemImpl::group() +{ + StLock_(mMutex); + SSGroup group; + if (!!mUniqueId) + { + Db db(mKeychain->database()); + if (useSecureStorage(db)) + { + group = safer_cast(*mUniqueId).group(); + } + } + + return group; +} + +void ItemImpl::getLocalContent(SecKeychainAttributeList *attributeList, UInt32 *outLength, void **outData) +{ + StLock_(mMutex); + willRead(); + Allocator &allocator = Allocator::standard(); // @@@ This might not match the one used originally + if (outData) + { + CssmData *data = mData.get(); + if (!data) + MacOSError::throwMe(errSecDataNotAvailable); + + // Copy the data out of our internal cached copy. + UInt32 length = (UInt32)data->Length; + *outData = allocator.malloc(length); + memcpy(*outData, data->Data, length); + if (outLength) + *outLength = length; + } + + if (attributeList) + { + if (!mDbAttributes.get()) + MacOSError::throwMe(errSecDataNotAvailable); + + // Pull attributes out of a "floating" item, i.e. one that isn't attached to a database + for (UInt32 ix = 0; ix < attributeList->count; ++ix) + { + SecKeychainAttribute &attribute = attributeList->attr[ix]; + CssmDbAttributeData *data = mDbAttributes->find(Schema::attributeInfo(attribute.tag)); + if (data && data->NumberOfValues > 0) + { + // Copy the data out of our internal cached copy. + UInt32 length = (UInt32)data->Value[0].Length; + attribute.data = allocator.malloc(length); + memcpy(attribute.data, data->Value[0].Data, length); + attribute.length = length; + } + else + { + attribute.length = 0; + attribute.data = NULL; + } + } + } +} + +void +ItemImpl::getContent(DbAttributes *dbAttributes, CssmDataContainer *itemData) +{ + StLock_(mMutex); + // Make sure mUniqueId is set. + dbUniqueRecord(); + if (itemData) + { + Db db(mUniqueId->database()); + if (mDoNotEncrypt) + { + mUniqueId->getWithoutEncryption (dbAttributes, itemData); + return; + } + if (useSecureStorage(db)) + { + SSDbUniqueRecordImpl* impl = dynamic_cast(&(*mUniqueId)); + if (impl == NULL) + { + CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); + } + + SSDbUniqueRecord ssUniqueId(impl); + const AccessCredentials *autoPrompt = globals().itemCredentials(); + ssUniqueId->get(dbAttributes, itemData, autoPrompt); + return; + } + } + + mUniqueId->get(dbAttributes, itemData); +} + +bool +ItemImpl::useSecureStorage(const Db &db) +{ + StLock_(mMutex); + switch (recordType()) + { + case CSSM_DL_DB_RECORD_GENERIC_PASSWORD: + case CSSM_DL_DB_RECORD_INTERNET_PASSWORD: + case CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD: + if (db->dl()->subserviceMask() & CSSM_SERVICE_CSP) + return true; + break; + default: + break; + } + return false; +} + +void ItemImpl::willRead() +{ +} + +Item ItemImpl::makeFromPersistentReference(const CFDataRef persistentRef, bool *isIdentityRef) +{ + CssmData dictData((void*)::CFDataGetBytePtr(persistentRef), ::CFDataGetLength(persistentRef)); + NameValueDictionary dict(dictData); + + Keychain keychain; + Item item = (ItemImpl *) NULL; + + if (isIdentityRef) { + *isIdentityRef = (dict.FindByName(IDENTITY_KEY) != 0) ? true : false; + } + + // make sure we have a database identifier + if (dict.FindByName(SSUID_KEY) != 0) + { + DLDbIdentifier dlDbIdentifier = NameValueDictionary::MakeDLDbIdentifierFromNameValueDictionary(dict); + DLDbIdentifier newDlDbIdentifier(dlDbIdentifier.ssuid(), + DLDbListCFPref::ExpandTildesInPath(dlDbIdentifier.dbName()).c_str(), + dlDbIdentifier.dbLocation()); + + keychain = globals().storageManager.keychain(newDlDbIdentifier); + + const NameValuePair* aDictItem = dict.FindByName(ITEM_KEY); + if (aDictItem && keychain) + { + PrimaryKey primaryKey(aDictItem->Value()); + item = keychain->item(primaryKey); + } + } + KCThrowIf_( !item, errSecItemNotFound ); + return item; +} + +void ItemImpl::copyPersistentReference(CFDataRef &outDataRef, bool isSecIdentityRef) +{ + if (secd_PersistentRef) { + outDataRef = secd_PersistentRef; + return; + } + StLock_(mMutex); + // item must be in a keychain and have a primary key to be persistent + if (!mKeychain || !mPrimaryKey) { + MacOSError::throwMe(errSecItemNotFound); + } + DLDbIdentifier dlDbIdentifier = mKeychain->dlDbIdentifier(); + DLDbIdentifier newDlDbIdentifier(dlDbIdentifier.ssuid(), + DLDbListCFPref::AbbreviatedPath(mKeychain->name()).c_str(), + dlDbIdentifier.dbLocation()); + NameValueDictionary dict; + NameValueDictionary::MakeNameValueDictionaryFromDLDbIdentifier(newDlDbIdentifier, dict); + + CssmData* pKey = mPrimaryKey; + dict.Insert (new NameValuePair(ITEM_KEY, *pKey)); + + if (isSecIdentityRef) { + uint32_t value = -1; + CssmData valueData((void*)&value, sizeof(value)); + dict.Insert (new NameValuePair(IDENTITY_KEY, valueData)); + } + + // flatten the NameValueDictionary + CssmData dictData; + dict.Export(dictData); + outDataRef = ::CFDataCreate(kCFAllocatorDefault, dictData.Data, dictData.Length); + free (dictData.Data); +} + +void ItemImpl::copyRecordIdentifier(CSSM_DATA &data) +{ + StLock_(mMutex); + CssmClient::DbUniqueRecord uniqueRecord = dbUniqueRecord (); + uniqueRecord->getRecordIdentifier(data); +} + +/* + * Obtain blob used to bind a keychain item to an Extended Attribute record. + * We just use the PrimaryKey blob as the default. Note that for standard Items, + * this can cause the loss of extended attribute bindings if a Primary Key + * attribute changes. + */ +const CssmData &ItemImpl::itemID() +{ + StLock_(mMutex); + if(mPrimaryKey->length() == 0) { + /* not in a keychain; we don't have a primary key */ + MacOSError::throwMe(errSecNoSuchAttr); + } + return *mPrimaryKey; +} + +bool ItemImpl::equal(SecCFObject &other) +{ + // First check to see if both items have a primary key and + // if the primary key is the same. If so then these + // items must be equal + ItemImpl& other_item = (ItemImpl&)other; + if (mPrimaryKey != NULL && mPrimaryKey == other_item.mPrimaryKey) + { + return true; + } + + // The primary keys do not match so do a CFHash of the + // data of the item and compare those for equality + CFHashCode this_hash = hash(); + CFHashCode other_hash = other.hash(); + return (this_hash == other_hash); +} + +CFHashCode ItemImpl::hash() +{ + CFHashCode result = SecCFObject::hash(); + + StLock_(mMutex); + RefPointer data_to_hash; + + // Use the item data for the hash + if (mData && *mData) + { + data_to_hash = mData; + } + + // If there is no primary key AND not data ???? + // just return the 'old' hash value which is the + // object pointer. + if (NULL != data_to_hash.get()) + { + CFDataRef temp_data = NULL; + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + + if (data_to_hash->length() < 80) + { + // If it is less than 80 bytes then CFData can be used + temp_data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, + (const UInt8 *)data_to_hash->data(), data_to_hash->length(), kCFAllocatorNull); + + } + // CFData truncates its hash value to 80 bytes. ???? + // In order to do the 'right thing' a SHA 256 hash will be used to + // include all of the data + else + { + memset(digest, 0, CC_SHA256_DIGEST_LENGTH); + + CC_SHA256((const void *)data_to_hash->data(), (CC_LONG)data_to_hash->length(), digest); + + temp_data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, + (const UInt8 *)digest, CC_SHA256_DIGEST_LENGTH, kCFAllocatorNull); + } + + if (NULL != temp_data) + { + result = CFHash(temp_data); + CFRelease(temp_data); + } + + } + + return result; +} + + +void ItemImpl::postItemEvent(SecKeychainEvent theEvent) +{ + mKeychain->postEvent(theEvent, this); +} + + + +// +// Item -- This class is here to magically create the right subclass of ItemImpl +// when constructing new items. +// +Item::Item() +{ +} + +Item::Item(ItemImpl *impl) : SecPointer(impl) +{ +} + +Item::Item(SecItemClass itemClass, OSType itemCreator, UInt32 length, const void* data, bool inhibitCheck) +{ + if (!inhibitCheck) + { + if (itemClass == CSSM_DL_DB_RECORD_X509_CERTIFICATE + || itemClass == CSSM_DL_DB_RECORD_PUBLIC_KEY + || itemClass == CSSM_DL_DB_RECORD_PRIVATE_KEY + || itemClass == CSSM_DL_DB_RECORD_SYMMETRIC_KEY) + MacOSError::throwMe(errSecNoSuchClass); /* @@@ errSecInvalidClass */ + } + + *this = new ItemImpl(itemClass, itemCreator, length, data, inhibitCheck); +} + +Item::Item(SecItemClass itemClass, SecKeychainAttributeList *attrList, UInt32 length, const void* data) +{ + *this = new ItemImpl(itemClass, attrList, length, data); +} + +Item::Item(const Keychain &keychain, const PrimaryKey &primaryKey, const CssmClient::DbUniqueRecord &uniqueId) + : SecPointer( + primaryKey->recordType() == CSSM_DL_DB_RECORD_X509_CERTIFICATE + ? Certificate::make(keychain, primaryKey, uniqueId) + : (primaryKey->recordType() == CSSM_DL_DB_RECORD_PUBLIC_KEY + || primaryKey->recordType() == CSSM_DL_DB_RECORD_PRIVATE_KEY + || primaryKey->recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY) + ? KeyItem::make(keychain, primaryKey, uniqueId) + : primaryKey->recordType() == CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE + ? ExtendedAttribute::make(keychain, primaryKey, uniqueId) + : ItemImpl::make(keychain, primaryKey, uniqueId)) +{ +} + +Item::Item(const Keychain &keychain, const PrimaryKey &primaryKey) + : SecPointer( + primaryKey->recordType() == CSSM_DL_DB_RECORD_X509_CERTIFICATE + ? Certificate::make(keychain, primaryKey) + : (primaryKey->recordType() == CSSM_DL_DB_RECORD_PUBLIC_KEY + || primaryKey->recordType() == CSSM_DL_DB_RECORD_PRIVATE_KEY + || primaryKey->recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY) + ? KeyItem::make(keychain, primaryKey) + : primaryKey->recordType() == CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE + ? ExtendedAttribute::make(keychain, primaryKey) + : ItemImpl::make(keychain, primaryKey)) +{ +} + +Item::Item(ItemImpl &item) + : SecPointer( + item.recordType() == CSSM_DL_DB_RECORD_X509_CERTIFICATE + ? new Certificate(safer_cast(item)) + : (item.recordType() == CSSM_DL_DB_RECORD_PUBLIC_KEY + || item.recordType() == CSSM_DL_DB_RECORD_PRIVATE_KEY + || item.recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY) + ? new KeyItem(safer_cast(item)) + : item.recordType() == CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE + ? new ExtendedAttribute(safer_cast(item)) + : new ItemImpl(item)) +{ +} + +CFIndex KeychainCore::GetItemRetainCount(Item& item) +{ + return CFGetRetainCount(item->handle(false)); +} + +void ItemImpl::setPersistentRef(CFDataRef ref) +{ + if (secd_PersistentRef) { + CFRelease(secd_PersistentRef); + } + secd_PersistentRef = ref; + CFRetain(ref); +} + +CFDataRef ItemImpl::getPersistentRef() +{ + return secd_PersistentRef; +} + + + +bool ItemImpl::mayDelete() +{ + ObjectImpl* uniqueIDImpl = mUniqueId.get(); + + if (uniqueIDImpl != NULL) + { + bool result = mUniqueId->isIdle(); + return result; + } + else + { + return true; + } +}