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; }
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); } }