#include <security_keychain/KeyItem.h>
#include <Security/cssmtype.h>
#include <security_keychain/Access.h>
#include <security_keychain/Keychains.h>
#include <security_keychain/KeyItem.h>
#include <security_cdsa_client/wrapkey.h>
#include <security_cdsa_client/genkey.h>
#include <security_cdsa_client/signclient.h>
#include <security_cdsa_client/cryptoclient.h>
#include <security_utilities/CSPDLTransaction.h>
#include <security_keychain/Globals.h>
#include "KCEventNotifier.h"
#include <CommonCrypto/CommonDigest.h>
#include <Security/SecBase.h>
#include <Security/SecBasePriv.h>
#include <CoreFoundation/CFPriv.h>
#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::required(SecKeyRef ptr)
{
if (KeyItem *p = optional(ptr)) {
return p;
} else {
MacOSError::throwMe(errSecInvalidItemRef);
}
}
KeyItem *KeyItem::optional(SecKeyRef ptr)
{
if (ptr != NULL) {
if (KeyItem *pp = dynamic_cast<KeyItem *>(fromSecKeyRef(ptr))) {
return pp;
} else {
MacOSError::throwMe(errSecInvalidItemRef);
}
} else {
return NULL;
}
}
KeyItem::operator CFTypeRef() const _NOEXCEPT
{
StMaybeLock<Mutex> _(this->getMutexForObject());
if (mWeakSecKeyRef != NULL) {
if (_CFTryRetain(mWeakSecKeyRef) == NULL) {
StMaybeLock<Mutex> secKeyCDSAMutex(static_cast<CDSASecKey *>(mWeakSecKeyRef)->cdsaKeyMutex);
mWeakSecKeyRef->key = NULL;
mWeakSecKeyRef = NULL;
} else {
CFRelease(mWeakSecKeyRef);
}
}
if (mWeakSecKeyRef == NULL) {
attachSecKeyRef();
}
return mWeakSecKeyRef;
}
void KeyItem::initializeWithSecKeyRef(SecKeyRef ref)
{
isNew();
mWeakSecKeyRef = ref;
}
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())
{
}
KeyItem::KeyItem(const CssmClient::Key &key) :
ItemImpl((SecItemClass) (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()
{
Db db(mKeychain->database());
CSPDLTransaction transaction(db);
ItemImpl::update();
setIntegrity();
transaction.commit();
}
Item
KeyItem::copyTo(const Keychain &keychain, Access *newAccess)
{
if (!(keychain->database()->dl()->subserviceMask() & CSSM_SERVICE_CSP))
MacOSError::throwMe(errSecInvalidKeychain);
SSDbImpl* dbImpl = dynamic_cast<SSDbImpl*>(&(*keychain->database()));
if (dbImpl == NULL)
{
CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
}
SSDb ssDb(dbImpl);
const CSSM_KEY *cssmKey = key();
if (cssmKey && (0==(cssmKey->KeyHeader.KeyAttr & CSSM_KEYATTR_EXTRACTABLE)))
{
MacOSError::throwMe(errSecDataNotAvailable);
}
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);
SecPointer<Access> access;
if (newAccess)
access = newAccess;
else
access = new Access(*mKey);
CssmClient::GenerateKey genKey(csp(), CSSM_ALGID_3DES_3KEY, 192);
CssmClient::Key wrappingKey(genKey(KeySpec(CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP,
CSSM_KEYATTR_EXTRACTABLE )));
uint8 ivBytes[8];
CssmData iv(ivBytes, sizeof(ivBytes));
random.generate(iv, (uint32)iv.length());
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));
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);
unwrap.add(CSSM_ATTRIBUTE_DL_DB_HANDLE, ssDb->handle());
Access::Maker maker(Allocator::standard(), Access::Maker::kAnyMakerType);
ResourceControlContext rcc;
maker.initialOwner(rcc, NULL);
unwrap.owner(rcc.input());
uint32 usage = mKey->usage();
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)));
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);
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)
{
uniqueId->deleteRecord ();
throw;
}
access->removeAclsForRight(CSSM_ACL_AUTHORIZATION_PARTITION_ID);
access->removeAclsForRight(CSSM_ACL_AUTHORIZATION_INTEGRITY);
access->setAccess(*unwrappedKey, maker);
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);
SSDbImpl* dbImpl = dynamic_cast<SSDbImpl*>(&(*keychain->database()));
if (dbImpl == NULL)
CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
SSDb ssDb(dbImpl);
if (!mKey)
CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
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);
SecPointer<Access> access;
if (newAccess)
access = newAccess;
else
access = new Access(*mKey);
CssmClient::GenerateKey genKey(csp(), CSSM_ALGID_3DES_3KEY, 192);
CssmClient::Key wrappingKey(genKey(KeySpec(CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP,
CSSM_KEYATTR_EXTRACTABLE )));
uint8 ivBytes[8];
CssmData iv(ivBytes, sizeof(ivBytes));
random.generate(iv, (uint32)iv.length());
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));
CssmClient::UnwrapKey unwrap(keychain->csp(), CSSM_ALGID_3DES_3KEY_EDE);
if (csp()->guid() != keychain->csp()->guid()) {
CssmClient::WrapKey exportWrapKey(csp(), CSSM_ALGID_NONE);
CssmClient::Key exportedWrappingKey(exportWrapKey(wrappingKey));
CssmClient::UnwrapKey importUnwrapKey(keychain->csp(), CSSM_ALGID_NONE);
CssmClient::Key importedWrappingKey(importUnwrapKey(exportedWrappingKey, KeySpec(CSSM_KEYUSE_UNWRAP, 0)));
unwrap.key(importedWrappingKey);
} else {
unwrap.key(wrappingKey);
}
unwrap.mode(CSSM_ALGMODE_ECBPad);
unwrap.padding(CSSM_PADDING_PKCS7);
unwrap.initVector(iv);
unwrap.add(CSSM_ATTRIBUTE_DL_DB_HANDLE, ssDb->handle());
Access::Maker maker(Allocator::standard(), Access::Maker::kAnyMakerType);
ResourceControlContext rcc;
maker.initialOwner(rcc, NULL);
unwrap.owner(rcc.input());
uint32 usage = mKey->usage();
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)));
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);
if (attrList) {
DbAttributes newDbAttributes;
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);
}
if (attr.tag == kSecKeyApplicationTag) {
newDbAttributes.add(kInfoKeyApplicationTag, attrData);
}
}
modifyUniqueId(keychain, ssDb, uniqueId, newDbAttributes, recordType());
}
addIntegrity(*access);
access->setAccess(*unwrappedKey, maker);
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<Security::CssmClient::SSDbUniqueRecordImpl *>(impl);
if (simpl == NULL)
{
CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
}
return CssmClient::SSDbUniqueRecord(simpl);
}
CssmKey::Header
KeyItem::unverifiedKeyHeader() {
return unverifiedKey()->header();
}
CssmClient::Key
KeyItem::unverifiedKey()
{
StLock<Mutex>_(mMutex);
if (!mKey)
{
CssmClient::SSDbUniqueRecord uniqueId(ssDbUniqueRecord());
CssmDataContainer dataBlob(uniqueId->allocator());
uniqueId->get(NULL, &dataBlob);
return CssmClient::Key(uniqueId->database()->csp(), *reinterpret_cast<CssmKey *>(dataBlob.Data));
}
return mKey;
}
CssmClient::Key &
KeyItem::key()
{
StLock<Mutex>_(mMutex);
if (!mKey)
{
mKey = unverifiedKey();
try {
if(!ItemImpl::checkIntegrity(*mKey)) {
secnotice("integrity", "key has no integrity, denying access");
mKey.release();
CssmError::throwMe(errSecInvalidItemRef);
}
} catch(CssmError cssme) {
mKey.release();
secnotice("integrity", "error while checking integrity, denying access: %s", cssme.what());
throw;
}
}
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();
}
const CssmData &KeyItem::itemID()
{
if(mPubKeyHash.length() == 0) {
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)
{
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)
{
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);
}
}
CssmClient::Key
KeyItem::publicKey() {
return mPublicKey;
}
bool
KeyItem::operator == (KeyItem &other)
{
if (mKey && *mKey)
{
return this == &other;
}
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<Access> initialAccess,
SecPointer<KeyItem> &outPublicKey,
SecPointer<KeyItem> &outPrivateKey)
{
SSDb ssDb(NULL);
Access::Maker maker;
const AccessCredentials *cred = NULL;
CssmClient::CSP appleCsp(gGuidAppleCSP);
CssmClient::CSP csp = appleCsp;
ResourceControlContext rcc;
memset(&rcc, 0, sizeof(rcc));
CssmData label;
uint8 labelBytes[20];
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();
CssmClient::Random random(appleCsp, CSSM_ALGID_APPLE_YARROW);
label = CssmData(labelBytes, sizeof(labelBytes));
random.generate(label, (uint32)label.length());
maker.initialOwner(rcc);
cred = maker.cred();
}
CssmKey publicCssmKey, privateCssmKey;
CSSM_CC_HANDLE ccHandle = 0;
bool freePublicKey = false;
bool freePrivateKey = false;
bool deleteContext = false;
bool permanentPubKey = false;
bool permanentPrivKey = false;
SecPointer<KeyItem> 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;
}
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);
}
status = CSSM_GenerateKeyPair(ccHandle, publicKeyUsage, publicKeyAttr, &label, &publicCssmKey, privateKeyUsage, privateKeyAttr, &label, &rcc, &privateCssmKey);
if (status)
CssmError::throwMe(status);
if ((publicKeyAttr & CSSM_KEYATTR_PERMANENT) != 0) {
permanentPubKey = true;
freePublicKey = true;
}
if ((privateKeyAttr & CSSM_KEYATTR_PERMANENT) != 0) {
permanentPrivKey = true;
freePrivateKey = true;
}
CssmClient::Key publicKey;
DbAttributes pubDbAttributes;
DbUniqueRecord pubUniqueId;
if (permanentPubKey && ssDb) {
SSDbCursor dbPubCursor(ssDb, 1);
dbPubCursor->recordType(CSSM_DL_DB_RECORD_PUBLIC_KEY);
dbPubCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, label);
if (!dbPubCursor->nextKey(&pubDbAttributes, publicKey, pubUniqueId))
MacOSError::throwMe(errSecItemNotFound);
} else {
publicKey = CssmClient::Key(csp, publicCssmKey);
outPublicKey = new KeyItem(publicKey);
freePublicKey = false;
}
CssmClient::Key privateKey;
DbAttributes privDbAttributes;
DbUniqueRecord privUniqueId;
if (permanentPrivKey && ssDb) {
SSDbCursor dbPrivCursor(ssDb, 1);
dbPrivCursor->recordType(CSSM_DL_DB_RECORD_PRIVATE_KEY);
dbPrivCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, label);
if (!dbPrivCursor->nextKey(&privDbAttributes, privateKey, privUniqueId))
MacOSError::throwMe(errSecItemNotFound);
} else {
privateKey = CssmClient::Key(csp, privateCssmKey);
outPrivateKey = new KeyItem(privateKey);
freePrivateKey = false;
}
if (ssDb) {
CssmClient::WrapKey wrap(csp, CSSM_ALGID_NONE);
wrap.cred(cred);
CssmClient::Key rawPubKey = wrap(publicKey);
CssmClient::PassThrough passThrough(appleCsp);
passThrough.key(rawPubKey);
CssmData *pubKeyHashData;
passThrough(CSSM_APPLECSP_KEYDIGEST, (const void *)NULL, &pubKeyHashData);
CssmAutoData pubKeyHash(passThrough.allocator());
pubKeyHash.set(*pubKeyHashData);
passThrough.allocator().free(pubKeyHashData);
unique_ptr<string> privDescription;
unique_ptr<string> pubDescription;
try {
privDescription.reset(new string(initialAccess->promptDescription()));
pubDescription.reset(new string(initialAccess->promptDescription()));
}
catch (...) {
privDescription.reset(new string("Private key"));
pubDescription.reset(new string("Public key"));
}
if (permanentPubKey) {
pubDbAttributes.add(kInfoKeyLabel, pubKeyHash.get());
pubDbAttributes.add(kInfoKeyPrintName, *pubDescription);
modifyUniqueId(keychain, ssDb, pubUniqueId, pubDbAttributes, CSSM_DL_DB_RECORD_PUBLIC_KEY);
publicKeyItem = dynamic_cast<KeyItem*>(keychain->item(CSSM_DL_DB_RECORD_PUBLIC_KEY, pubUniqueId).get());
if (!publicKeyItem) {
CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
}
if (publicKeyAttr & CSSM_KEYATTR_PUBLIC_KEY_ENCRYPT) {
SecPointer<Access> pubKeyAccess(new Access());
publicKeyItem->addIntegrity(*pubKeyAccess);
pubKeyAccess->setAccess(*publicKey, maker);
}
outPublicKey = publicKeyItem;
}
if (permanentPrivKey) {
privDbAttributes.add(kInfoKeyLabel, pubKeyHash.get());
privDbAttributes.add(kInfoKeyPrintName, *privDescription);
modifyUniqueId(keychain, ssDb, privUniqueId, privDbAttributes, CSSM_DL_DB_RECORD_PRIVATE_KEY);
privateKeyItem = dynamic_cast<KeyItem*>(keychain->item(CSSM_DL_DB_RECORD_PRIVATE_KEY, privUniqueId).get());
if (!privateKeyItem) {
CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
}
privateKeyItem->addIntegrity(*initialAccess);
initialAccess->setAccess(*privateKey, maker);
outPrivateKey = privateKeyItem;
}
}
outPrivateKey->mPublicKey = publicKey;
}
catch (...)
{
if (freePublicKey) {
CSSM_FreeKey(csp->handle(), cred, &publicCssmKey, permanentPubKey);
}
if (freePrivateKey) {
CSSM_FreeKey(csp->handle(), cred, &privateCssmKey, permanentPrivKey);
}
if (deleteContext)
CSSM_DeleteContext(ccHandle);
throw;
}
if (freePublicKey) {
CSSM_FreeKey(csp->handle(), NULL, &publicCssmKey, FALSE);
}
if (freePrivateKey) {
CSSM_FreeKey(csp->handle(), NULL, &privateCssmKey, FALSE);
}
if (deleteContext)
CSSM_DeleteContext(ccHandle);
if (keychain) {
if (permanentPubKey) {
keychain->postEvent(kSecAddEvent, publicKeyItem);
}
if (permanentPrivKey) {
keychain->postEvent(kSecAddEvent, privateKeyItem);
}
}
}
void
KeyItem::importPair(
Keychain keychain,
const CSSM_KEY &publicWrappedKey,
const CSSM_KEY &privateWrappedKey,
SecPointer<Access> initialAccess,
SecPointer<KeyItem> &outPublicKey,
SecPointer<KeyItem> &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<SSDbImpl *>(&(*keychain->database()));
if (impl == NULL)
{
CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
}
SSDb ssDb(impl);
CssmClient::CSP csp(keychain->csp());
CssmClient::CSP appleCsp(gGuidAppleCSP);
ResourceControlContext rcc;
memset(&rcc, 0, sizeof(rcc));
Access::Maker maker(Allocator::standard(), Access::Maker::kAnyMakerType);
maker.initialOwner(rcc);
const AccessCredentials *cred = maker.cred();
CSSM_KEY publicCssmKey, privateCssmKey;
memset(&publicCssmKey, 0, sizeof(publicCssmKey));
memset(&privateCssmKey, 0, sizeof(privateCssmKey));
CSSM_CC_HANDLE ccHandle = 0;
SecPointer<KeyItem> publicKeyItem, privateKeyItem;
try
{
CSSM_RETURN status;
CssmClient::PassThrough passThrough(appleCsp);
void *outData;
CssmData *cssmData;
passThrough.key(&publicWrappedKey);
passThrough(CSSM_APPLECSP_KEYDIGEST, NULL, &outData);
cssmData = reinterpret_cast<CssmData *>(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);
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;
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);
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);
csp.allocator().free(cssmData->Data);
csp.allocator().free(cssmData);
unique_ptr<string>privDescription;
unique_ptr<string>pubDescription;
try {
privDescription.reset(new string(initialAccess->promptDescription()));
pubDescription.reset(new string(initialAccess->promptDescription()));
}
catch(...) {
privDescription.reset(new string("Private key"));
pubDescription.reset(new string("Public key"));
}
pubDbAttributes.add(kInfoKeyPrintName, *pubDescription);
modifyUniqueId(keychain, ssDb, pubUniqueId, pubDbAttributes, CSSM_DL_DB_RECORD_PUBLIC_KEY);
privDbAttributes.add(kInfoKeyPrintName, *privDescription);
modifyUniqueId(keychain, ssDb, privUniqueId, privDbAttributes, CSSM_DL_DB_RECORD_PRIVATE_KEY);
publicKeyItem = dynamic_cast<KeyItem*>(keychain->item(CSSM_DL_DB_RECORD_PUBLIC_KEY, pubUniqueId).get());
privateKeyItem = dynamic_cast<KeyItem*>(keychain->item(CSSM_DL_DB_RECORD_PRIVATE_KEY, privUniqueId).get());
if (!publicKeyItem || !privateKeyItem)
{
CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
}
privateKeyItem->addIntegrity(*initialAccess);
initialAccess->setAccess(*privateKey, maker);
SecPointer<Access> pubKeyAccess(new Access());
publicKeyItem->addIntegrity(*pubKeyAccess);
pubKeyAccess->setAccess(*publicKey, maker);
outPublicKey = publicKeyItem;
outPrivateKey = privateKeyItem;
}
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, Item(publicKeyItem));
KCEventNotifier::PostKeychainEvent(kSecAddEvent, keychain, Item(privateKeyItem));
}
}
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();
CssmClient::Random random(appleCsp, CSSM_ALGID_APPLE_YARROW);
random.generate(label, (uint32)label.Length);
plabel = &label;
}
else
{
csp = appleCsp;
}
ResourceControlContext *prcc = NULL, rcc;
const AccessCredentials *cred = NULL;
Access::Maker maker;
if (keychain)
{
memset(&rcc, 0, sizeof(rcc));
maker.initialOwner(rcc);
cred = maker.cred();
prcc = &rcc;
if (!initialAccess) {
initialAccess = new Access(label.toString());
}
}
CSSM_KEY cssmKey;
CSSM_CC_HANDLE ccHandle = 0;
SecPointer<KeyItem> 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;
}
status = CSSM_GenerateKey(ccHandle, keyUsage, keyAttr, plabel, prcc, &cssmKey);
if (status)
CssmError::throwMe(status);
if (ssDb)
{
freeKey = true;
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);
if (attrList) {
DbAttributes newDbAttributes;
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);
}
if (attr.tag == kSecKeyApplicationTag) {
newDbAttributes.add(kInfoKeyApplicationTag, attrData);
}
}
modifyUniqueId(keychain, ssDb, uniqueId, newDbAttributes, CSSM_DL_DB_RECORD_SYMMETRIC_KEY);
}
keyItem = dynamic_cast<KeyItem *>(keychain->item(CSSM_DL_DB_RECORD_SYMMETRIC_KEY, uniqueId).get());
keyItem->addIntegrity(*initialAccess);
initialAccess->setAccess(*key, maker);
}
else
{
CssmClient::Key tempKey(csp, cssmKey);
keyItem = new KeyItem(tempKey);
}
}
catch (...)
{
if (freeKey)
{
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;
}
SecPointer<KeyItem>
KeyItem::generate(Keychain keychain,
CSSM_ALGORITHMS algorithm,
uint32 keySizeInBits,
CSSM_CC_HANDLE contextHandle,
CSSM_KEYUSE keyUsage,
uint32 keyAttr,
SecPointer<Access> initialAccess)
{
return KeyItem::generateWithAttributes(NULL, keychain,
algorithm, keySizeInBits, contextHandle,
keyUsage, keyAttr, initialAccess);
}
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)
{
result = CFHash(temp_cfdata);
CFRelease(temp_cfdata);
}
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;
}
void KeyItem::setIntegrity(bool force) {
ItemImpl::setIntegrity(*unverifiedKey(), force);
}
bool KeyItem::checkIntegrity() {
if(!isPersistent()) {
return true;
}
try {
key();
return true;
} catch (CssmError cssme) {
return false;
}
}
void KeyItem::removeIntegrity(const AccessCredentials *cred) {
ItemImpl::removeIntegrity(*key(), cred);
}
void KeyItem::modifyUniqueId(Keychain keychain, SSDb ssDb, DbUniqueRecord& uniqueId, DbAttributes& newDbAttributes, CSSM_DB_RECORDTYPE recordType) {
SSDbCursor otherDbCursor(ssDb, 1);
otherDbCursor->recordType(recordType);
bool checkForDuplicates = false;
CssmDbAttributeData* label = newDbAttributes.findAttribute(kInfoKeyLabel);
if(label) {
otherDbCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, label->at(0));
checkForDuplicates = true;
}
CssmDbAttributeData* apptag = newDbAttributes.findAttribute(kInfoKeyApplicationTag);
if(apptag) {
otherDbCursor->add(CSSM_DB_EQUAL, kInfoKeyApplicationTag, apptag->at(0));
checkForDuplicates = true;
}
if((!keychain) || !keychain->hasIntegrityProtection()) {
secnotice("integrity", "key skipping duplicate integrity check due to keychain version");
checkForDuplicates = false;
}
if (checkForDuplicates) {
secnotice("integrity", "looking for duplicates");
DbAttributes otherDbAttributes;
DbUniqueRecord otherUniqueId;
CssmClient::Key otherKey;
while(otherDbCursor->nextKey(&otherDbAttributes, otherKey, otherUniqueId)) {
secnotice("integrity", "found a duplicate, checking integrity");
PrimaryKey pk = keychain->makePrimaryKey(recordType, otherUniqueId);
ItemImpl* maybeItem = keychain->_lookupItem(pk);
if(maybeItem) {
if(maybeItem->checkIntegrity()) {
secnotice("integrity", "duplicate is real, throwing error");
MacOSError::throwMe(errSecDuplicateItem);
} else {
secnotice("integrity", "existing duplicate item is invalid, removing...");
Item item(maybeItem);
keychain->deleteItem(item);
}
} else {
KeyItem temp(keychain, pk, otherUniqueId);
if(temp.checkIntegrity()) {
secnotice("integrity", "duplicate is real, throwing error");
MacOSError::throwMe(errSecDuplicateItem);
} else {
secnotice("integrity", "duplicate is invalid, removing");
otherUniqueId->deleteRecord();
keychain->removeItem(temp.primaryKey(), &temp);
}
}
}
}
try {
secinfo("integrity", "modifying unique id");
uniqueId->modify(recordType, &newDbAttributes, NULL, CSSM_DB_MODIFY_ATTRIBUTE_REPLACE);
secinfo("integrity", "done modifying unique id");
} catch(CssmError e) {
uniqueId->deleteRecord();
throw;
}
}