X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/67c7378dcb8de24c86b7fedff90b4b496f2e474c..5a719ac813caa6f2ceaa192274fad2e1c2cec162:/Keychain/KeyItem.cpp diff --git a/Keychain/KeyItem.cpp b/Keychain/KeyItem.cpp index bfb10ec0..6404a06c 100644 --- a/Keychain/KeyItem.cpp +++ b/Keychain/KeyItem.cpp @@ -20,6 +20,16 @@ // #include #include +#include +#include +#include +#include +#include +#include + +// @@@ This needs to be shared. +static CSSM_DB_NAME_ATTR(kSecKeyLabel, 6, "Label", 0, NULL, BLOB); +static CSSM_DB_NAME_ATTR(kSecKeyPrintName, 1, "PrintName", 0, NULL, BLOB); using namespace KeychainCore; @@ -96,3 +106,361 @@ KeyItem::cssmKey() return *mKey; } + +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. + AclFactory factory; + switch (credentialType) + { + case kSecCredentialTypeDefault: + return globals().credentials(); + case kSecCredentialTypeWithUI: + return factory.promptCred(); + case kSecCredentialTypeNoUI: + return factory.nullCred(); + default: + MacOSError::throwMe(paramErr); + } +} + +void +KeyItem::createPair( + Keychain keychain, + CSSM_ALGORITHMS algorithm, + uint32 keySizeInBits, + CSSM_CC_HANDLE contextHandle, + CSSM_KEYUSE publicKeyUsage, + uint32 publicKeyAttr, + CSSM_KEYUSE privateKeyUsage, + uint32 privateKeyAttr, + RefPointer initialAccess, + RefPointer &outPublicKey, + RefPointer &outPrivateKey) +{ + bool freeKeys = false; + bool deleteContext = false; + + if (!keychain->database()->dl()->subserviceMask() & CSSM_SERVICE_CSP) + MacOSError::throwMe(errSecInvalidKeychain); + + SSDb ssDb(safe_cast(&(*keychain->database()))); + 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; + + 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, kSecKeyLabel, 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, kSecKeyLabel, 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; + + std::string description(initialAccess->promptDescription()); + // 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(kSecKeyLabel, pubKeyHash); + pubDbAttributes.add(kSecKeyPrintName, description); + 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(kSecKeyLabel, pubKeyHash); + privDbAttributes.add(kSecKeyPrintName, description); + 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); + + // Make the public key acl completely open + Access pubKeyAccess; + pubKeyAccess.setAccess(*publicKey, maker); + + // Create keychain items which will represent the keys. + outPublicKey = safe_cast(&(*keychain->item(CSSM_DL_DB_RECORD_PUBLIC_KEY, pubUniqueId))); + outPrivateKey = safe_cast(&(*keychain->item(CSSM_DL_DB_RECORD_PRIVATE_KEY, privUniqueId))); + } + catch (...) + { + if (freeKeys) + { + // Delete the keys if something goes wrong so we don't end up with inaccesable 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); +} + +void +KeyItem::importPair( + Keychain keychain, + const CSSM_KEY &publicWrappedKey, + const CSSM_KEY &privateWrappedKey, + RefPointer initialAccess, + RefPointer &outPublicKey, + RefPointer &outPrivateKey) +{ + bool freePublicKey = false; + bool freePrivateKey = false; + bool deleteContext = false; + + if (!keychain->database()->dl()->subserviceMask() & CSSM_SERVICE_CSP) + MacOSError::throwMe(errSecInvalidKeychain); + + SSDb ssDb(safe_cast(&(*keychain->database()))); + 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; + // @@@ Potentially provide a credential argument which allows us to unwrap 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; + + 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.AlgorithmId, 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 + status = CSSM_UnwrapKey( + ccHandle, + NULL, + &publicWrappedKey, + publicWrappedKey.KeyHeader.KeyUsage, + publicWrappedKey.KeyHeader.KeyAttr, + &pubKeyHash, + &rcc, + &publicCssmKey, + NULL); + if (status) + CssmError::throwMe(status); + freePublicKey = true; + + status = CSSM_UnwrapKey( + ccHandle, + NULL, + &privateWrappedKey, + privateWrappedKey.KeyHeader.KeyUsage, + privateWrappedKey.KeyHeader.KeyAttr, + &pubKeyHash, + &rcc, + &privateCssmKey, + NULL); + + if (status) + CssmError::throwMe(status); + + freePrivateKey = 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, kSecKeyLabel, 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, kSecKeyLabel, 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); + + std::string description(initialAccess->promptDescription()); + // 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(kSecKeyPrintName, description); + 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(kSecKeyPrintName, description); + 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 + Access pubKeyAccess; + pubKeyAccess.setAccess(*publicKey, maker); + + // Create keychain items which will represent the keys. + outPublicKey = safe_cast(&(*keychain->item(CSSM_DL_DB_RECORD_PUBLIC_KEY, pubUniqueId))); + outPrivateKey = safe_cast(&(*keychain->item(CSSM_DL_DB_RECORD_PRIVATE_KEY, privUniqueId))); + } + 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); +}