Beispiel #1
0
void
KeyItem::createPair(
	Keychain keychain,
	CSSM_ALGORITHMS algorithm,
	uint32 keySizeInBits,
	CSSM_CC_HANDLE contextHandle,
	CSSM_KEYUSE publicKeyUsage,
	uint32 publicKeyAttr,
	CSSM_KEYUSE privateKeyUsage,
	uint32 privateKeyAttr,
	SecPointer<Access> initialAccess,
	SecPointer<KeyItem> &outPublicKey,
	SecPointer<KeyItem> &outPrivateKey)
{
	bool freeKeys = false;
	bool deleteContext = false;

	if (!(keychain->database()->dl()->subserviceMask() & CSSM_SERVICE_CSP))
		MacOSError::throwMe(errSecInvalidKeychain);

	SSDbImpl* impl = dynamic_cast<SSDbImpl*>(&(*keychain->database()));
	if (impl == NULL)
	{
		CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
	}

	SSDb ssDb(impl);
	CssmClient::CSP csp(keychain->csp());
	CssmClient::CSP appleCsp(gGuidAppleCSP);

	// Generate a random label to use initially
	CssmClient::Random random(appleCsp, CSSM_ALGID_APPLE_YARROW);
	uint8 labelBytes[20];
	CssmData label(labelBytes, sizeof(labelBytes));
	random.generate(label, label.Length);

	// Create a Access::Maker for the initial owner of the private key.
	ResourceControlContext rcc;
	memset(&rcc, 0, sizeof(rcc));
	Access::Maker maker;
	// @@@ Potentially provide a credential argument which allows us to generate keys in the csp.  Currently the CSP let's anyone do this, but we might restrict this in the future, f.e. a smartcard could require out of band pin entry before a key can be generated.
	maker.initialOwner(rcc);
	// Create the cred we need to manipulate the keys until we actually set a new access control for them.
	const AccessCredentials *cred = maker.cred();

	CSSM_KEY publicCssmKey, privateCssmKey;
	memset(&publicCssmKey, 0, sizeof(publicCssmKey));
	memset(&privateCssmKey, 0, sizeof(privateCssmKey));

	CSSM_CC_HANDLE ccHandle = 0;

	Item publicKeyItem, privateKeyItem;
	try
	{
		CSSM_RETURN status;
		if (contextHandle)
				ccHandle = contextHandle;
		else
		{
			status = CSSM_CSP_CreateKeyGenContext(csp->handle(), algorithm, keySizeInBits, NULL, NULL, NULL, NULL, NULL, &ccHandle);
			if (status)
				CssmError::throwMe(status);
			deleteContext = true;
		}

		CSSM_DL_DB_HANDLE dldbHandle = ssDb->handle();
		CSSM_DL_DB_HANDLE_PTR dldbHandlePtr = &dldbHandle;
		CSSM_CONTEXT_ATTRIBUTE contextAttributes = { CSSM_ATTRIBUTE_DL_DB_HANDLE, sizeof(dldbHandle), { (char *)dldbHandlePtr } };
		status = CSSM_UpdateContextAttributes(ccHandle, 1, &contextAttributes);
		if (status)
			CssmError::throwMe(status);

		// Generate the keypair
		status = CSSM_GenerateKeyPair(ccHandle, publicKeyUsage, publicKeyAttr, &label, &publicCssmKey, privateKeyUsage, privateKeyAttr, &label, &rcc, &privateCssmKey);
		if (status)
			CssmError::throwMe(status);
		freeKeys = true;

		// Find the keys we just generated in the DL to get SecKeyRef's to them
		// so we can change the label to be the hash of the public key, and
		// fix up other attributes.

		// Look up public key in the DLDB.
		DbAttributes pubDbAttributes;
		DbUniqueRecord pubUniqueId;
		SSDbCursor dbPubCursor(ssDb, 1);
		dbPubCursor->recordType(CSSM_DL_DB_RECORD_PUBLIC_KEY);
		dbPubCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, label);
		CssmClient::Key publicKey;
		if (!dbPubCursor->nextKey(&pubDbAttributes, publicKey, pubUniqueId))
			MacOSError::throwMe(errSecItemNotFound);

		// Look up private key in the DLDB.
		DbAttributes privDbAttributes;
		DbUniqueRecord privUniqueId;
		SSDbCursor dbPrivCursor(ssDb, 1);
		dbPrivCursor->recordType(CSSM_DL_DB_RECORD_PRIVATE_KEY);
		dbPrivCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, label);
		CssmClient::Key privateKey;
		if (!dbPrivCursor->nextKey(&privDbAttributes, privateKey, privUniqueId))
			MacOSError::throwMe(errSecItemNotFound);

		// Convert reference public key to a raw key so we can use it
		// in the appleCsp.
		CssmClient::WrapKey wrap(csp, CSSM_ALGID_NONE);
		wrap.cred(cred);
		CssmClient::Key rawPubKey = wrap(publicKey);

		// Calculate the hash of the public key using the appleCSP.
		CssmClient::PassThrough passThrough(appleCsp);
		void *outData;
		CssmData *cssmData;

		/* Given a CSSM_KEY_PTR in any format, obtain the SHA-1 hash of the
		* associated key blob.
		* Key is specified in CSSM_CSP_CreatePassThroughContext.
		* Hash is allocated bythe CSP, in the App's memory, and returned
		* in *outData. */
		passThrough.key(rawPubKey);
		passThrough(CSSM_APPLECSP_KEYDIGEST, NULL, &outData);
		cssmData = reinterpret_cast<CssmData *>(outData);
		CssmData &pubKeyHash = *cssmData;

		auto_ptr<string>privDescription;
		auto_ptr<string>pubDescription;
		try {
			privDescription.reset(new string(initialAccess->promptDescription()));
			pubDescription.reset(new string(initialAccess->promptDescription()));
		}
		catch(...) {
			/* this path taken if no promptDescription available, e.g., for complex ACLs */
			privDescription.reset(new string("Private key"));
			pubDescription.reset(new string("Public key"));
		}

		// Set the label of the public key to the public key hash.
		// Set the PrintName of the public key to the description in the acl.
		pubDbAttributes.add(kInfoKeyLabel, pubKeyHash);
		pubDbAttributes.add(kInfoKeyPrintName, *pubDescription);
		pubUniqueId->modify(CSSM_DL_DB_RECORD_PUBLIC_KEY, &pubDbAttributes, NULL, CSSM_DB_MODIFY_ATTRIBUTE_REPLACE);

		// Set the label of the private key to the public key hash.
		// Set the PrintName of the private key to the description in the acl.
		privDbAttributes.add(kInfoKeyLabel, pubKeyHash);
		privDbAttributes.add(kInfoKeyPrintName, *privDescription);
		privUniqueId->modify(CSSM_DL_DB_RECORD_PRIVATE_KEY, &privDbAttributes, NULL, CSSM_DB_MODIFY_ATTRIBUTE_REPLACE);

		// @@@ Not exception safe!
		csp.allocator().free(cssmData->Data);
		csp.allocator().free(cssmData);

		// Finally fix the acl and owner of the private key to the specified access control settings.
		initialAccess->setAccess(*privateKey, maker);

		if(publicKeyAttr & CSSM_KEYATTR_PUBLIC_KEY_ENCRYPT) {
			/*
			 * Make the public key acl completely open.
			 * If the key was not encrypted, it already has a wide-open
			 * ACL (though that is a feature of securityd; it's not
			 * CDSA-specified behavior).
			 */
			SecPointer<Access> pubKeyAccess(new Access());
			pubKeyAccess->setAccess(*publicKey, maker);
		}

		// Create keychain items which will represent the keys.
		publicKeyItem = keychain->item(CSSM_DL_DB_RECORD_PUBLIC_KEY, pubUniqueId);
		privateKeyItem = keychain->item(CSSM_DL_DB_RECORD_PRIVATE_KEY, privUniqueId);

		KeyItem* impl = dynamic_cast<KeyItem*>(&(*publicKeyItem));
		if (impl == NULL)
		{
			CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
		}

		outPublicKey = impl;

		impl = dynamic_cast<KeyItem*>(&(*privateKeyItem));
		if (impl == NULL)
		{
			CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
		}

		outPrivateKey = impl;
	}
	catch (...)
	{
		if (freeKeys)
		{
			// Delete the keys if something goes wrong so we don't end up with inaccessible keys in the database.
			CSSM_FreeKey(csp->handle(), cred, &publicCssmKey, TRUE);
			CSSM_FreeKey(csp->handle(), cred, &privateCssmKey, TRUE);
		}

		if (deleteContext)
			CSSM_DeleteContext(ccHandle);

		throw;
	}

	if (freeKeys)
	{
		CSSM_FreeKey(csp->handle(), NULL, &publicCssmKey, FALSE);
		CSSM_FreeKey(csp->handle(), NULL, &privateCssmKey, FALSE);
	}

	if (deleteContext)
		CSSM_DeleteContext(ccHandle);

	if (keychain && publicKeyItem && privateKeyItem)
	{
		keychain->postEvent(kSecAddEvent, publicKeyItem);
		keychain->postEvent(kSecAddEvent, privateKeyItem);
	}
}
Beispiel #2
0
SecPointer<KeyItem>
KeyItem::generateWithAttributes(const SecKeychainAttributeList *attrList,
	Keychain keychain,
	CSSM_ALGORITHMS algorithm,
	uint32 keySizeInBits,
	CSSM_CC_HANDLE contextHandle,
	CSSM_KEYUSE keyUsage,
	uint32 keyAttr,
	SecPointer<Access> initialAccess)
{
	CssmClient::CSP appleCsp(gGuidAppleCSP);
	CssmClient::CSP csp(NULL);
	SSDb ssDb(NULL);
	uint8 labelBytes[20];
	CssmData label(labelBytes, sizeof(labelBytes));
	bool freeKey = false;
	bool deleteContext = false;
	const CSSM_DATA *plabel = NULL;

	if (keychain)
	{
		if (!(keychain->database()->dl()->subserviceMask() & CSSM_SERVICE_CSP))
			MacOSError::throwMe(errSecInvalidKeychain);

		SSDbImpl* impl = dynamic_cast<SSDbImpl *>(&(*keychain->database()));
		if (impl == NULL)
		{
			CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
		}

		ssDb = SSDb(impl);
		csp = keychain->csp();

		// Generate a random label to use initially
		CssmClient::Random random(appleCsp, CSSM_ALGID_APPLE_YARROW);
		random.generate(label, label.Length);
		plabel = &label;
	}
	else
	{
		// Not a persistent key so create it in the regular csp
		csp = appleCsp;
	}

	// Create a Access::Maker for the initial owner of the private key.
	ResourceControlContext *prcc = NULL, rcc;
	const AccessCredentials *cred = NULL;
	Access::Maker maker;
	if (keychain && initialAccess)
	{
		memset(&rcc, 0, sizeof(rcc));
		// @@@ Potentially provide a credential argument which allows us to generate keys in the csp.
		// Currently the CSP lets anyone do this, but we might restrict this in the future, e.g. a smartcard
		// could require out-of-band pin entry before a key can be generated.
		maker.initialOwner(rcc);
		// Create the cred we need to manipulate the keys until we actually set a new access control for them.
		cred = maker.cred();
		prcc = &rcc;
	}

	CSSM_KEY cssmKey;

	CSSM_CC_HANDLE ccHandle = 0;

	Item keyItem;
	try
	{
		CSSM_RETURN status;
		if (contextHandle)
			ccHandle = contextHandle;
		else
		{
			status = CSSM_CSP_CreateKeyGenContext(csp->handle(), algorithm, keySizeInBits, NULL, NULL, NULL, NULL, NULL, &ccHandle);
			if (status)
				CssmError::throwMe(status);
			deleteContext = true;
		}

		if (ssDb)
		{
			CSSM_DL_DB_HANDLE dldbHandle = ssDb->handle();
			CSSM_DL_DB_HANDLE_PTR dldbHandlePtr = &dldbHandle;
			CSSM_CONTEXT_ATTRIBUTE contextAttributes = { CSSM_ATTRIBUTE_DL_DB_HANDLE, sizeof(dldbHandle), { (char *)dldbHandlePtr } };
			status = CSSM_UpdateContextAttributes(ccHandle, 1, &contextAttributes);
			if (status)
				CssmError::throwMe(status);

			keyAttr |= CSSM_KEYATTR_PERMANENT;
		}

		// Generate the key
		status = CSSM_GenerateKey(ccHandle, keyUsage, keyAttr, plabel, prcc, &cssmKey);
		if (status)
			CssmError::throwMe(status);

		if (ssDb)
		{
			freeKey = true;
			// Find the key we just generated in the DL and get a SecKeyRef
			// so we can specify the label attribute(s) and initial ACL.

			// Look up key in the DLDB.
			DbAttributes dbAttributes;
			DbUniqueRecord uniqueId;
			SSDbCursor dbCursor(ssDb, 1);
			dbCursor->recordType(CSSM_DL_DB_RECORD_SYMMETRIC_KEY);
			dbCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, label);
			CssmClient::Key key;
			if (!dbCursor->nextKey(&dbAttributes, key, uniqueId))
				MacOSError::throwMe(errSecItemNotFound);

			// Set the initial label, application label, and application tag (if provided)
			if (attrList) {
				DbAttributes newDbAttributes;
				SSDbCursor otherDbCursor(ssDb, 1);
				otherDbCursor->recordType(CSSM_DL_DB_RECORD_SYMMETRIC_KEY);
				bool checkForDuplicates = false;

				for (UInt32 index=0; index < attrList->count; index++) {
					SecKeychainAttribute attr = attrList->attr[index];
					CssmData attrData(attr.data, attr.length);
					if (attr.tag == kSecKeyPrintName) {
						newDbAttributes.add(kInfoKeyPrintName, attrData);
					}
					if (attr.tag == kSecKeyLabel) {
						newDbAttributes.add(kInfoKeyLabel, attrData);
						otherDbCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, attrData);
						checkForDuplicates = true;
					}
					if (attr.tag == kSecKeyApplicationTag) {
						newDbAttributes.add(kInfoKeyApplicationTag, attrData);
						otherDbCursor->add(CSSM_DB_EQUAL, kInfoKeyApplicationTag, attrData);
						checkForDuplicates = true;
					}
				}

				DbAttributes otherDbAttributes;
				DbUniqueRecord otherUniqueId;
				CssmClient::Key otherKey;
				if (checkForDuplicates && otherDbCursor->nextKey(&otherDbAttributes, otherKey, otherUniqueId))
					MacOSError::throwMe(errSecDuplicateItem);

				uniqueId->modify(CSSM_DL_DB_RECORD_SYMMETRIC_KEY, &newDbAttributes, NULL, CSSM_DB_MODIFY_ATTRIBUTE_REPLACE);
			}

			// Finally, fix the acl and owner of the key to the specified access control settings.
			if (initialAccess)
				initialAccess->setAccess(*key, maker);

			// Create keychain item which will represent the key.
			keyItem = keychain->item(CSSM_DL_DB_RECORD_SYMMETRIC_KEY, uniqueId);
		}
		else
		{
			CssmClient::Key tempKey(csp, cssmKey);
			keyItem = new KeyItem(tempKey);
		}
	}
	catch (...)
	{
		if (freeKey)
		{
			// Delete the key if something goes wrong so we don't end up with inaccessible keys in the database.
			CSSM_FreeKey(csp->handle(), cred, &cssmKey, TRUE);
		}

		if (deleteContext)
			CSSM_DeleteContext(ccHandle);

		throw;
	}

	if (freeKey)
	{
		CSSM_FreeKey(csp->handle(), NULL, &cssmKey, FALSE);
	}

	if (deleteContext)
		CSSM_DeleteContext(ccHandle);

	if (keychain && keyItem)
		keychain->postEvent(kSecAddEvent, keyItem);

	KeyItem* item = dynamic_cast<KeyItem*>(&*keyItem);
	if (item == NULL)
	{
		CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
	}

	return item;
}
PrimaryKey ItemImpl::addWithCopyInfo (Keychain &keychain, bool isCopy)
{
	StLock<Mutex>_(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<CssmData>(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<CssmDbAttributeInfo> 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<SSDbImpl *>(&(*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> 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<SecPointer<ACL> > 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<void **>(&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<void **>(&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<const void *>(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<const void *>(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;
}