/* * Copyright (c) 2000-2004,2012-2013 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; } }