/* * Copyright (c) 2002-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@ */ // // KeyItem.cpp // #include #include #include #include #include #include #include #include #include #include #include "KCEventNotifier.h" #include #include // @@@ This needs to be shared. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-const-variable" static CSSM_DB_NAME_ATTR(kInfoKeyPrintName, kSecKeyPrintName, (char*) "PrintName", 0, NULL, BLOB); static CSSM_DB_NAME_ATTR(kInfoKeyLabel, kSecKeyLabel, (char*) "Label", 0, NULL, BLOB); static CSSM_DB_NAME_ATTR(kInfoKeyApplicationTag, kSecKeyApplicationTag, (char*) "ApplicationTag", 0, NULL, BLOB); #pragma clang diagnostic pop using namespace KeychainCore; using namespace CssmClient; KeyItem::KeyItem(const Keychain &keychain, const PrimaryKey &primaryKey, const CssmClient::DbUniqueRecord &uniqueId) : ItemImpl(keychain, primaryKey, uniqueId), mKey(), algid(NULL), mPubKeyHash(Allocator::standard()) { } KeyItem::KeyItem(const Keychain &keychain, const PrimaryKey &primaryKey) : ItemImpl(keychain, primaryKey), mKey(), algid(NULL), mPubKeyHash(Allocator::standard()) { } KeyItem* KeyItem::make(const Keychain &keychain, const PrimaryKey &primaryKey, const CssmClient::DbUniqueRecord &uniqueId) { KeyItem* k = new KeyItem(keychain, primaryKey, uniqueId); keychain->addItem(primaryKey, k); return k; } KeyItem* KeyItem::make(const Keychain &keychain, const PrimaryKey &primaryKey) { KeyItem* k = new KeyItem(keychain, primaryKey); keychain->addItem(primaryKey, k); return k; } KeyItem::KeyItem(KeyItem &keyItem) : ItemImpl(keyItem), mKey(), algid(NULL), mPubKeyHash(Allocator::standard()) { // @@@ this doesn't work for keys that are not in a keychain. } KeyItem::KeyItem(const CssmClient::Key &key) : ItemImpl(key->keyClass() + CSSM_DL_DB_RECORD_PUBLIC_KEY, (OSType)0, (UInt32)0, (const void*)NULL), mKey(key), algid(NULL), mPubKeyHash(Allocator::standard()) { if (key->keyClass() > CSSM_KEYCLASS_SESSION_KEY) MacOSError::throwMe(errSecParam); } KeyItem::~KeyItem() { } void KeyItem::update() { ItemImpl::update(); } Item KeyItem::copyTo(const Keychain &keychain, Access *newAccess) { if (!(keychain->database()->dl()->subserviceMask() & CSSM_SERVICE_CSP)) MacOSError::throwMe(errSecInvalidKeychain); /* Get the destination keychain's db. */ SSDbImpl* dbImpl = dynamic_cast(&(*keychain->database())); if (dbImpl == NULL) { CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); } SSDb ssDb(dbImpl); /* Make sure mKey is valid. */ const CSSM_KEY *cssmKey = key(); if (cssmKey && (0==(cssmKey->KeyHeader.KeyAttr & CSSM_KEYATTR_EXTRACTABLE))) { MacOSError::throwMe(errSecDataNotAvailable); } // Generate a random label to use initially CssmClient::CSP appleCsp(gGuidAppleCSP); CssmClient::Random random(appleCsp, CSSM_ALGID_APPLE_YARROW); uint8 labelBytes[20]; CssmData label(labelBytes, sizeof(labelBytes)); random.generate(label, (uint32)label.Length); /* Set up the ACL for the new key. */ SecPointer access; if (newAccess) access = newAccess; else access = new Access(*mKey); /* Generate a random 3DES wrapping Key. */ CssmClient::GenerateKey genKey(csp(), CSSM_ALGID_3DES_3KEY, 192); CssmClient::Key wrappingKey(genKey(KeySpec(CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP, CSSM_KEYATTR_EXTRACTABLE /* | CSSM_KEYATTR_RETURN_DATA */))); /* make a random IV */ uint8 ivBytes[8]; CssmData iv(ivBytes, sizeof(ivBytes)); random.generate(iv, (uint32)iv.length()); /* Extract the key by wrapping it with the wrapping key. */ CssmClient::WrapKey wrap(csp(), CSSM_ALGID_3DES_3KEY_EDE); wrap.key(wrappingKey); wrap.cred(getCredentials(CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED, kSecCredentialTypeDefault)); wrap.mode(CSSM_ALGMODE_ECBPad); wrap.padding(CSSM_PADDING_PKCS7); wrap.initVector(iv); CssmClient::Key wrappedKey(wrap(mKey)); /* Unwrap the new key into the new Keychain. */ CssmClient::UnwrapKey unwrap(keychain->csp(), CSSM_ALGID_3DES_3KEY_EDE); unwrap.key(wrappingKey); unwrap.mode(CSSM_ALGMODE_ECBPad); unwrap.padding(CSSM_PADDING_PKCS7); unwrap.initVector(iv); /* Setup the dldbHandle in the context. */ unwrap.add(CSSM_ATTRIBUTE_DL_DB_HANDLE, ssDb->handle()); /* Set up an initial aclEntry so we can change it after the unwrap. */ Access::Maker maker(Allocator::standard(), Access::Maker::kAnyMakerType); ResourceControlContext rcc; maker.initialOwner(rcc, NULL); unwrap.owner(rcc.input()); /* Unwrap the key. */ uint32 usage = mKey->usage(); /* Work around csp brokeness where it sets all usage bits in the Keyheader when CSSM_KEYUSE_ANY is set. */ if (usage & CSSM_KEYUSE_ANY) usage = CSSM_KEYUSE_ANY; CssmClient::Key unwrappedKey(unwrap(wrappedKey, KeySpec(usage, (mKey->attributes() | CSSM_KEYATTR_PERMANENT) & ~(CSSM_KEYATTR_ALWAYS_SENSITIVE | CSSM_KEYATTR_NEVER_EXTRACTABLE), label))); /* Look up unwrapped key in the DLDB. */ DbUniqueRecord uniqueId; SSDbCursor dbCursor(ssDb, 1); dbCursor->recordType(recordType()); dbCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, label); CssmClient::Key copiedKey; if (!dbCursor->nextKey(NULL, copiedKey, uniqueId)) MacOSError::throwMe(errSecItemNotFound); /* Copy the Label, PrintName and ApplicationTag attributes from the old key to the new one. */ dbUniqueRecord(); DbAttributes oldDbAttributes(mUniqueId->database(), 3); oldDbAttributes.add(kInfoKeyLabel); oldDbAttributes.add(kInfoKeyPrintName); oldDbAttributes.add(kInfoKeyApplicationTag); mUniqueId->get(&oldDbAttributes, NULL); try { uniqueId->modify(recordType(), &oldDbAttributes, NULL, CSSM_DB_MODIFY_ATTRIBUTE_REPLACE); } catch (CssmError e) { // clean up after trying to insert a duplicate key uniqueId->deleteRecord (); throw; } /* Set the acl and owner on the unwrapped key. */ access->setAccess(*unwrappedKey, maker); /* Return a keychain item which represents the new key. */ Item item(keychain->item(recordType(), uniqueId)); KCEventNotifier::PostKeychainEvent(kSecAddEvent, keychain, item); return item; } Item KeyItem::importTo(const Keychain &keychain, Access *newAccess, SecKeychainAttributeList *attrList) { if (!(keychain->database()->dl()->subserviceMask() & CSSM_SERVICE_CSP)) MacOSError::throwMe(errSecInvalidKeychain); /* Get the destination keychain's db. */ SSDbImpl* dbImpl = dynamic_cast(&(*keychain->database())); if (dbImpl == NULL) CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); SSDb ssDb(dbImpl); /* Make sure mKey is valid. */ /* We can't call key() here, since we won't have a unique record id yet */ if (!mKey) CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); // Generate a random label to use initially CssmClient::CSP appleCsp(gGuidAppleCSP); CssmClient::Random random(appleCsp, CSSM_ALGID_APPLE_YARROW); uint8 labelBytes[20]; CssmData label(labelBytes, sizeof(labelBytes)); random.generate(label, (uint32)label.Length); /* Set up the ACL for the new key. */ SecPointer access; if (newAccess) access = newAccess; else access = new Access(*mKey); /* Generate a random 3DES wrapping Key. */ CssmClient::GenerateKey genKey(csp(), CSSM_ALGID_3DES_3KEY, 192); CssmClient::Key wrappingKey(genKey(KeySpec(CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP, CSSM_KEYATTR_EXTRACTABLE /* | CSSM_KEYATTR_RETURN_DATA */))); /* make a random IV */ uint8 ivBytes[8]; CssmData iv(ivBytes, sizeof(ivBytes)); random.generate(iv, (uint32)iv.length()); /* Extract the key by wrapping it with the wrapping key. */ CssmClient::WrapKey wrap(csp(), CSSM_ALGID_3DES_3KEY_EDE); wrap.key(wrappingKey); wrap.cred(getCredentials(CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED, kSecCredentialTypeDefault)); wrap.mode(CSSM_ALGMODE_ECBPad); wrap.padding(CSSM_PADDING_PKCS7); wrap.initVector(iv); CssmClient::Key wrappedKey(wrap(mKey)); /* Unwrap the new key into the new Keychain. */ CssmClient::UnwrapKey unwrap(keychain->csp(), CSSM_ALGID_3DES_3KEY_EDE); unwrap.key(wrappingKey); unwrap.mode(CSSM_ALGMODE_ECBPad); unwrap.padding(CSSM_PADDING_PKCS7); unwrap.initVector(iv); /* Setup the dldbHandle in the context. */ unwrap.add(CSSM_ATTRIBUTE_DL_DB_HANDLE, ssDb->handle()); /* Set up an initial aclEntry so we can change it after the unwrap. */ Access::Maker maker(Allocator::standard(), Access::Maker::kAnyMakerType); ResourceControlContext rcc; maker.initialOwner(rcc, NULL); unwrap.owner(rcc.input()); /* Unwrap the key. */ uint32 usage = mKey->usage(); /* Work around csp brokeness where it sets all usage bits in the Keyheader when CSSM_KEYUSE_ANY is set. */ if (usage & CSSM_KEYUSE_ANY) usage = CSSM_KEYUSE_ANY; CssmClient::Key unwrappedKey(unwrap(wrappedKey, KeySpec(usage, (mKey->attributes() | CSSM_KEYATTR_PERMANENT) & ~(CSSM_KEYATTR_ALWAYS_SENSITIVE | CSSM_KEYATTR_NEVER_EXTRACTABLE), label))); /* Look up unwrapped key in the DLDB. */ DbUniqueRecord uniqueId; SSDbCursor dbCursor(ssDb, 1); dbCursor->recordType(recordType()); dbCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, label); CssmClient::Key copiedKey; if (!dbCursor->nextKey(NULL, copiedKey, 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(recordType()); 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; try { if (checkForDuplicates && otherDbCursor->nextKey(&otherDbAttributes, otherKey, otherUniqueId)) MacOSError::throwMe(errSecDuplicateItem); uniqueId->modify(recordType(), &newDbAttributes, NULL, CSSM_DB_MODIFY_ATTRIBUTE_REPLACE); } catch (CssmError e) { // clean up after trying to insert a duplicate key uniqueId->deleteRecord (); throw; } } /* Set the acl and owner on the unwrapped key. */ access->setAccess(*unwrappedKey, maker); /* Return a keychain item which represents the new key. */ Item item(keychain->item(recordType(), uniqueId)); KCEventNotifier::PostKeychainEvent(kSecAddEvent, keychain, item); return item; } void KeyItem::didModify() { } PrimaryKey KeyItem::add(Keychain &keychain) { MacOSError::throwMe(errSecUnimplemented); } CssmClient::SSDbUniqueRecord KeyItem::ssDbUniqueRecord() { DbUniqueRecordImpl *impl = &*dbUniqueRecord(); Security::CssmClient::SSDbUniqueRecordImpl *simpl = dynamic_cast(impl); if (simpl == NULL) { CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); } return CssmClient::SSDbUniqueRecord(simpl); } CssmClient::Key & KeyItem::key() { if (!mKey) { CssmClient::SSDbUniqueRecord uniqueId(ssDbUniqueRecord()); CssmDataContainer dataBlob(uniqueId->allocator()); uniqueId->get(NULL, &dataBlob); mKey = CssmClient::Key(uniqueId->database()->csp(), *reinterpret_cast(dataBlob.Data)); } return mKey; } CssmClient::CSP KeyItem::csp() { return key()->csp(); } const CSSM_X509_ALGORITHM_IDENTIFIER& KeyItem::algorithmIdentifier() { #if 0 CssmKey *mKey; CSSM_KEY_TYPE algorithm CSSM_KEY_PTR cssmKey = (CSSM_KEY_PTR)thisData->Data; cssmKey->KeyHeader static void printKeyHeader( const CSSM_KEYHEADER &hdr) { printf(" Algorithm : "); switch(hdr.AlgorithmId) { CSSM_X509_ALGORITHM_IDENTIFIER algID; CSSM_OID *CL_algToOid( CSSM_ALGORITHMS algId) typedef struct cssm_x509_algorithm_identifier { CSSM_OID algorithm; CSSM_DATA parameters; } CSSM_X509_ALGORITHM_IDENTIFIER, *CSSM_X509_ALGORITHM_IDENTIFIER_PTR; #endif abort(); } /* * itemID, used to locate Extended Attributes, is the public key hash for keys. */ const CssmData &KeyItem::itemID() { if(mPubKeyHash.length() == 0) { /* * Fetch the attribute from disk. */ UInt32 tag = kSecKeyLabel; UInt32 format = 0; SecKeychainAttributeInfo attrInfo = {1, &tag, &format}; SecKeychainAttributeList *attrList = NULL; getAttributesAndData(&attrInfo, NULL, &attrList, NULL, NULL); if((attrList == NULL) || (attrList->count != 1)) { MacOSError::throwMe(errSecNoSuchAttr); } mPubKeyHash.copy(attrList->attr->data, attrList->attr->length); freeAttributesAndData(attrList, NULL); } return mPubKeyHash; } unsigned int KeyItem::strengthInBits(const CSSM_X509_ALGORITHM_IDENTIFIER *algid) { // @@@ Make a context with key based on algid and use that to get the effective keysize and not just the logical one. CSSM_KEY_SIZE keySize = {}; CSSM_RETURN rv = CSSM_QueryKeySizeInBits (csp()->handle(), CSSM_INVALID_HANDLE, key(), &keySize); if (rv) return 0; return keySize.LogicalKeySizeInBits; } const AccessCredentials * KeyItem::getCredentials( CSSM_ACL_AUTHORIZATION_TAG operation, SecCredentialType credentialType) { // @@@ Fix this to actually examine the ACL for this key and consider operation and do the right thing. //AutoAclEntryInfoList aclInfos; //key()->getAcl(aclInfos); bool smartcard = keychain() != NULL ? (keychain()->database()->dl()->guid() == gGuidAppleSdCSPDL) : false; AclFactory factory; switch (credentialType) { case kSecCredentialTypeDefault: return smartcard?globals().smartcardItemCredentials():globals().itemCredentials(); case kSecCredentialTypeWithUI: return smartcard?globals().smartcardItemCredentials():factory.promptCred(); case kSecCredentialTypeNoUI: return factory.nullCred(); default: MacOSError::throwMe(errSecParam); } } bool KeyItem::operator == (KeyItem &other) { if (mKey && *mKey) { // Pointer compare return this == &other; } // If keychains are different, then keys are different Keychain otherKeychain = other.keychain(); return (mKeychain && otherKeychain && (*mKeychain == *otherKeychain)); } 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 initialAccess, SecPointer &outPublicKey, SecPointer &outPrivateKey) { bool freeKeys = false; bool deleteContext = false; if (!(keychain->database()->dl()->subserviceMask() & CSSM_SERVICE_CSP)) MacOSError::throwMe(errSecInvalidKeychain); SSDbImpl* impl = dynamic_cast(&(*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, (uint32)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(outData); CssmData &pubKeyHash = *cssmData; auto_ptrprivDescription; auto_ptrpubDescription; 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 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(&(*publicKeyItem)); if (impl == NULL) { CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); } outPublicKey = impl; impl = dynamic_cast(&(*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); } } void KeyItem::importPair( Keychain keychain, const CSSM_KEY &publicWrappedKey, const CSSM_KEY &privateWrappedKey, SecPointer initialAccess, SecPointer &outPublicKey, SecPointer &outPrivateKey) { bool freePublicKey = false; bool freePrivateKey = false; bool deleteContext = false; if (!(keychain->database()->dl()->subserviceMask() & CSSM_SERVICE_CSP)) MacOSError::throwMe(errSecInvalidKeychain); SSDbImpl* impl = dynamic_cast(&(*keychain->database())); if (impl == NULL) { CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); } SSDb ssDb(impl); CssmClient::CSP csp(keychain->csp()); CssmClient::CSP appleCsp(gGuidAppleCSP); // Create a Access::Maker for the initial owner of the private key. ResourceControlContext rcc; memset(&rcc, 0, sizeof(rcc)); Access::Maker maker(Allocator::standard(), Access::Maker::kAnyMakerType); // @@@ Potentially provide a credential argument which allows us to unwrap 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. 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; // 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(&publicWrappedKey); passThrough(CSSM_APPLECSP_KEYDIGEST, NULL, &outData); cssmData = reinterpret_cast(outData); CssmData &pubKeyHash = *cssmData; status = CSSM_CSP_CreateSymmetricContext(csp->handle(), publicWrappedKey.KeyHeader.WrapAlgorithmId, CSSM_ALGMODE_NONE, NULL, NULL, NULL, CSSM_PADDING_NONE, 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); // Unwrap the the keys CSSM_DATA descriptiveData = {0, NULL}; status = CSSM_UnwrapKey( ccHandle, NULL, &publicWrappedKey, publicWrappedKey.KeyHeader.KeyUsage, publicWrappedKey.KeyHeader.KeyAttr | CSSM_KEYATTR_PERMANENT, &pubKeyHash, &rcc, &publicCssmKey, &descriptiveData); if (status) CssmError::throwMe(status); freePublicKey = true; if (descriptiveData.Data != NULL) free (descriptiveData.Data); status = CSSM_UnwrapKey( ccHandle, NULL, &privateWrappedKey, privateWrappedKey.KeyHeader.KeyUsage, privateWrappedKey.KeyHeader.KeyAttr | CSSM_KEYATTR_PERMANENT, &pubKeyHash, &rcc, &privateCssmKey, &descriptiveData); if (status) CssmError::throwMe(status); if (descriptiveData.Data != NULL) free (descriptiveData.Data); freePrivateKey = true; // Find the keys we just generated in the DL to get SecKeyRefs 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, pubKeyHash); 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, pubKeyHash); CssmClient::Key privateKey; if (!dbPrivCursor->nextKey(&privDbAttributes, privateKey, privUniqueId)) MacOSError::throwMe(errSecItemNotFound); // @@@ Not exception safe! csp.allocator().free(cssmData->Data); csp.allocator().free(cssmData); auto_ptrprivDescription; auto_ptrpubDescription; 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(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(kInfoKeyPrintName, *privDescription); privUniqueId->modify(CSSM_DL_DB_RECORD_PRIVATE_KEY, &privDbAttributes, NULL, CSSM_DB_MODIFY_ATTRIBUTE_REPLACE); // Finally fix the acl and owner of the private key to the specified access control settings. initialAccess->setAccess(*privateKey, maker); // Make the public key acl completely open SecPointer 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(&(*publicKeyItem)); if (impl == NULL) { CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); } outPublicKey = impl; impl = dynamic_cast(&(*privateKeyItem)); if (impl == NULL) { CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); } outPrivateKey = impl; } catch (...) { if (freePublicKey) CSSM_FreeKey(csp->handle(), cred, &publicCssmKey, TRUE); if (freePrivateKey) CSSM_FreeKey(csp->handle(), cred, &privateCssmKey, TRUE); if (deleteContext) CSSM_DeleteContext(ccHandle); throw; } if (freePublicKey) CSSM_FreeKey(csp->handle(), cred, &publicCssmKey, FALSE); if (freePrivateKey) CSSM_FreeKey(csp->handle(), cred, &privateCssmKey, FALSE); if (deleteContext) CSSM_DeleteContext(ccHandle); if (keychain && publicKeyItem && privateKeyItem) { KCEventNotifier::PostKeychainEvent(kSecAddEvent, keychain, publicKeyItem); KCEventNotifier::PostKeychainEvent(kSecAddEvent, keychain, privateKeyItem); } } SecPointer KeyItem::generateWithAttributes(const SecKeychainAttributeList *attrList, Keychain keychain, CSSM_ALGORITHMS algorithm, uint32 keySizeInBits, CSSM_CC_HANDLE contextHandle, CSSM_KEYUSE keyUsage, uint32 keyAttr, SecPointer 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(&(*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, (uint32)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); if (item == NULL) { CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); } return item; } SecPointer KeyItem::generate(Keychain keychain, CSSM_ALGORITHMS algorithm, uint32 keySizeInBits, CSSM_CC_HANDLE contextHandle, CSSM_KEYUSE keyUsage, uint32 keyAttr, SecPointer initialAccess) { return KeyItem::generateWithAttributes(NULL, keychain, algorithm, keySizeInBits, contextHandle, keyUsage, keyAttr, initialAccess); } void KeyItem::RawSign(SecPadding padding, CSSM_DATA dataToSign, const AccessCredentials *credentials, CSSM_DATA& signature) { CSSM_ALGORITHMS baseAlg = key()->header().algorithm(); if ((baseAlg != CSSM_ALGID_RSA) && (baseAlg != CSSM_ALGID_ECDSA)) { MacOSError::throwMe(errSecParam); } CSSM_ALGORITHMS paddingAlg = CSSM_PADDING_PKCS1; switch (padding) { case kSecPaddingPKCS1: { paddingAlg = CSSM_PADDING_PKCS1; break; } case kSecPaddingPKCS1MD2: { baseAlg = CSSM_ALGID_MD2WithRSA; break; } case kSecPaddingPKCS1MD5: { baseAlg = CSSM_ALGID_MD5WithRSA; break; } case kSecPaddingPKCS1SHA1: { baseAlg = CSSM_ALGID_SHA1WithRSA; break; } default: { paddingAlg = CSSM_PADDING_NONE; break; } } Sign signContext(csp(), baseAlg); signContext.key(key()); signContext.set(CSSM_ATTRIBUTE_PADDING, paddingAlg); signContext.cred(credentials); CssmData data(dataToSign.Data, dataToSign.Length); signContext.sign(data); CssmData sig(signature.Data, signature.Length); signContext(sig); // yes, this is an accessor. Believe it, or not. signature.Length = sig.length(); } void KeyItem::RawVerify(SecPadding padding, CSSM_DATA dataToVerify, const AccessCredentials *credentials, CSSM_DATA sig) { CSSM_ALGORITHMS baseAlg = key()->header().algorithm(); if ((baseAlg != CSSM_ALGID_RSA) && (baseAlg != CSSM_ALGID_ECDSA)) { MacOSError::throwMe(errSecParam); } CSSM_ALGORITHMS paddingAlg = CSSM_PADDING_PKCS1; switch (padding) { case kSecPaddingPKCS1: { paddingAlg = CSSM_PADDING_PKCS1; break; } case kSecPaddingPKCS1MD2: { baseAlg = CSSM_ALGID_MD2WithRSA; break; } case kSecPaddingPKCS1MD5: { baseAlg = CSSM_ALGID_MD5WithRSA; break; } case kSecPaddingPKCS1SHA1: { baseAlg = CSSM_ALGID_SHA1WithRSA; break; } default: { paddingAlg = CSSM_PADDING_NONE; break; } } Verify verifyContext(csp(), baseAlg); verifyContext.key(key()); verifyContext.set(CSSM_ATTRIBUTE_PADDING, paddingAlg); verifyContext.cred(credentials); CssmData data(dataToVerify.Data, dataToVerify.Length); CssmData signature(sig.Data, sig.Length); verifyContext.verify(data, signature); } void KeyItem::Encrypt(SecPadding padding, CSSM_DATA dataToEncrypt, const AccessCredentials *credentials, CSSM_DATA& encryptedData) { CSSM_ALGORITHMS baseAlg = key()->header().algorithm(); if (baseAlg != CSSM_ALGID_RSA) { MacOSError::throwMe(errSecParam); } CSSM_ALGORITHMS paddingAlg = CSSM_PADDING_PKCS1; switch (padding) { case kSecPaddingPKCS1: { paddingAlg = CSSM_PADDING_PKCS1; break; } default: { paddingAlg = CSSM_PADDING_NONE; break; } } CssmClient::Encrypt encryptContext(csp(), baseAlg); encryptContext.key(key()); encryptContext.padding(paddingAlg); encryptContext.cred(credentials); CssmData inData(dataToEncrypt.Data, dataToEncrypt.Length); CssmData outData(encryptedData.Data, encryptedData.Length); CssmData remData((void*) NULL, 0); encryptedData.Length = encryptContext.encrypt(inData, outData, remData); } void KeyItem::Decrypt(SecPadding padding, CSSM_DATA dataToDecrypt, const AccessCredentials *credentials, CSSM_DATA& decryptedData) { CSSM_ALGORITHMS baseAlg = key()->header().algorithm(); if (baseAlg != CSSM_ALGID_RSA) { MacOSError::throwMe(errSecParam); } CSSM_ALGORITHMS paddingAlg = CSSM_PADDING_PKCS1; switch (padding) { case kSecPaddingPKCS1: { paddingAlg = CSSM_PADDING_PKCS1; break; } default: { paddingAlg = CSSM_PADDING_NONE; break; } } CssmClient::Decrypt decryptContext(csp(), baseAlg); decryptContext.key(key()); decryptContext.padding(paddingAlg); decryptContext.cred(credentials); CssmData inData(dataToDecrypt.Data, dataToDecrypt.Length); CssmData outData(decryptedData.Data, decryptedData.Length); CssmData remData((void*) NULL, 0); decryptedData.Length = decryptContext.decrypt(inData, outData, remData); if (remData.Data != NULL) { free(remData.Data); } } CFHashCode KeyItem::hash() { CFHashCode result = 0; const CSSM_KEY *cssmKey = key(); if (NULL != cssmKey) { unsigned char digest[CC_SHA256_DIGEST_LENGTH]; CFIndex size_of_data = sizeof(CSSM_KEYHEADER) + cssmKey->KeyData.Length; CFMutableDataRef temp_cfdata = CFDataCreateMutable(kCFAllocatorDefault, size_of_data); if (NULL == temp_cfdata) { return result; } CFDataAppendBytes(temp_cfdata, (const UInt8 *)cssmKey, sizeof(CSSM_KEYHEADER)); CFDataAppendBytes(temp_cfdata, cssmKey->KeyData.Data, cssmKey->KeyData.Length); if (size_of_data < 80) { // If it is less than 80 bytes then CFData can be used result = CFHash(temp_cfdata); CFRelease(temp_cfdata); } // 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 *)CFDataGetBytePtr(temp_cfdata), (CC_LONG)CFDataGetLength(temp_cfdata), digest); CFDataRef data_to_hash = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)digest, CC_SHA256_DIGEST_LENGTH, kCFAllocatorNull); result = CFHash(data_to_hash); CFRelease(data_to_hash); CFRelease(temp_cfdata); } } return result; }