KCCursor.cpp   [plain text]


/*
 * Copyright (c) 2000-2004 Apple Computer, 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@
 */


//
// KCCursor.cpp
//

#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 <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <Security/SecKeychainItemPriv.h>

using namespace KeychainCore;
using namespace CssmClient;
using namespace CSSMDateTimeUtils;

using namespace KeySchema;

// define a table of our attributes for easy lookup
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::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),
	mMutex(Mutex::recursive)
{
    recordType(Schema::recordTypeFor(itemClass));

	if (!attrList) // No additional selectionPredicates: we are done
		return;

	conjunctive(dbConjunctive);
	const SecKeychainAttribute *end=&attrList->attr[attrList->count];
	// Add all the attrs in attrs list to the cursor.
	for (const SecKeychainAttribute *attr=attrList->attr; attr != end; ++attr)
	{
		const CSSM_DB_ATTRIBUTE_INFO *temp;
		
		if (attr->tag <'    ') // ok, is this a key schema?  Handle differently, just because we can...
		{
			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];
    
        // XXX This code is duplicated in NewItemImpl::setAttribute()
        // 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<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),
	mMutex(Mutex::recursive)
{
	if (!attrList) // No additional selectionPredicates: we are done
		return;

	conjunctive(CSSM_DB_AND);
	bool foundClassAttribute=false;
	const SecKeychainAttribute *end=&attrList->attr[attrList->count];
	// Add all the attrs in attrs list to the cursor.
	for (const SecKeychainAttribute *attr=attrList->attr; attr != end; ++attr)
	{
		if (attr->tag!=kSecClassItemAttr)	// a regular attribute
		{
            const CssmDbAttributeInfo &info = Schema::attributeInfo(attr->tag);
            void *buf = attr->data;
            UInt32 length = attr->length;
            uint8 timeString[16];
        
            // XXX This code is duplicated in NewItemImpl::setAttribute()
            // 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<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;
		}
		
		// the class attribute
		if (foundClassAttribute || attr->length != sizeof(SecItemClass))
			MacOSError::throwMe(paramErr); // We have 2 different 'clas' attributes

		recordType(Schema::recordTypeFor(*reinterpret_cast<SecItemClass *>(attr->data)));
		foundClassAttribute=true;
	}
}

KCCursorImpl::~KCCursorImpl() throw()
{
}

static ModuleNexus<Mutex> gActivationMutex;

bool
KCCursorImpl::next(Item &item)
{
	StLock<Mutex>_(mMutex);
	DbAttributes dbAttributes;
	DbUniqueRecord uniqueId;
	OSStatus status = 0;

	for (;;)
	{
		while (!mDbCursor)
		{
			if (mCurrent == mSearchList.end())
			{
				// If we got always failed when calling mDbCursor->next return the error from
				// the last call to mDbCursor->next now
				if (mAllFailed && status)
					CssmError::throwMe(status);
				
				// No more keychains to search so we are done.
				return false;
			}
			
			try
			{
            /*  WARNING:  THIS SECTION IS CRITICAL.
                If multiple threads are attempting to activate
                the same database at the same time, the global
                system database queue will reflect the most recent
                activation rather than the activation which was
                created by this thread.
                
                We could protect the database structures, but that's not the
                right answer because it's perfectly legal to have
                more than one context.  That would basically just
                make a big lock around CDSA, which is a performance
                nightmare.  Instead, we protect the PROCESS under
                which databases are activated.
            */
            
                StLock<Mutex> _(gActivationMutex()); // force serialization of cursor creation
				(*mCurrent)->database()->activate();
				mDbCursor = DbCursor((*mCurrent)->database(), *this);
			}
			catch(const CommonError &err)
			{
				++mCurrent;
			}
		}

		bool gotRecord;
		try
		{
			// Clear out existing attributes first!
			// (the previous iteration may have left attributes from a different schema)
			dbAttributes.clear();

			gotRecord = mDbCursor->next(&dbAttributes, NULL, uniqueId);
			mAllFailed = false;
		}
		catch(const CommonError &err)
		{
			// Catch the last error we get and move on to the next keychain
			// This error will be returned when we reach the end of our keychain list
			// iff all calls to KCCursorImpl::next failed
			status = err.osStatus();
			gotRecord = false;
            dbAttributes.invalidate();
		}
		catch(...)
		{
			// Catch all other errors
			status = errSecItemNotFound;
			gotRecord = false;
		}

		// If we did not get a record from the current keychain or the current
		// keychain did not exist skip to the next keychain in the list.
		if (!gotRecord)
		{
			++mCurrent;
			mDbCursor = DbCursor();
			continue;
		}

        // If doing a search for all records, skip the db blob added by the CSPDL
        if (dbAttributes.recordType() == CSSM_DL_DB_RECORD_METADATA &&
            mDbCursor->recordType() == CSSM_DL_DB_RECORD_ANY)
                continue;
        
        // Filter out group keys at this layer
        if (dbAttributes.recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY)
        {
			bool groupKey = false;
			try
			{
				// fetch the key label attribute, if it exists
				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;
        }

		break;
	}

	// Go though Keychain since item might already exist.
	item = (*mCurrent)->item(dbAttributes.recordType(), uniqueId);
	return true;
}