--- /dev/null
+/*
+ * Copyright (c) 2003-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@
+ */
+
+/*
+ * pkcs12Keychain.h - P12Coder keychain-related functions.
+ */
+
+#include "pkcs12Coder.h"
+#include "pkcs12Templates.h"
+#include "pkcs12Utils.h"
+#include "pkcs12Debug.h"
+#include "pkcs12Crypto.h"
+#include <Security/cssmerr.h>
+#include <security_cdsa_utils/cuDbUtils.h> // cuAddCrlToDb()
+#include <security_asn1/nssUtils.h>
+#include <security_cdsa_utilities/KeySchema.h> /* private API */
+#include <security_keychain/SecImportExportCrypto.h> /* private API */
+
+/*
+ * Store the results of a successful decode in app-specified
+ * keychain per mImportFlags. Also assign public key hash attributes to any
+ * private keys found.
+ */
+void P12Coder::storeDecodeResults()
+{
+ assert(mKeychain != NULL);
+ assert(mDlDbHand.DLHandle != 0);
+ if(mImportFlags & kSecImportKeys) {
+ setPrivateKeyHashes();
+ }
+ if(mImportFlags & kSecImportCertificates) {
+ for(unsigned dex=0; dex<numCerts(); dex++) {
+ P12CertBag *certBag = mCerts[dex];
+ SecCertificateRef secCert = certBag->getSecCert();
+ OSStatus ortn = SecCertificateAddToKeychain(secCert, mKeychain);
+ CFRelease(secCert);
+ switch(ortn) {
+ case errSecSuccess: // normal
+ p12DecodeLog("cert added to keychain");
+ break;
+ case errSecDuplicateItem: // dup cert, OK< skip
+ p12DecodeLog("skipping dup cert");
+ break;
+ default:
+ p12ErrorLog("SecCertificateAddToKeychain failure\n");
+ MacOSError::throwMe(ortn);
+ }
+ }
+ }
+
+ if(mImportFlags & kSecImportCRLs) {
+ for(unsigned dex=0; dex<numCrls(); dex++) {
+ P12CrlBag *crlBag = mCrls[dex];
+ CSSM_RETURN crtn = cuAddCrlToDb(mDlDbHand,
+ clHand(),
+ &crlBag->crlData(),
+ NULL); // no URI known
+ switch(crtn) {
+ case CSSM_OK: // normal
+ p12DecodeLog("CRL added to keychain");
+ break;
+ case CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA: // dup, ignore
+ p12DecodeLog("skipping dup CRL");
+ break;
+ default:
+ p12LogCssmError("Error adding CRL to keychain", crtn);
+ CssmError::throwMe(crtn);
+ }
+ }
+ }
+
+ /* If all of that succeeded, post notification for imported keys */
+ if(mImportFlags & kSecImportKeys) {
+ notifyKeyImport();
+ }
+}
+
+/*
+ * Assign appropriate public key hash attribute to each
+ * private key.
+ */
+void P12Coder::setPrivateKeyHashes()
+{
+ CSSM_KEY_PTR newKey;
+
+ for(unsigned dex=0; dex<numKeys(); dex++) {
+ P12KeyBag *keyBag = mKeys[dex];
+
+ CSSM_DATA newLabel = {0, NULL};
+ CFStringRef friendlyName = keyBag->friendlyName();
+ newKey = NULL;
+ CSSM_RETURN crtn = p12SetPubKeyHash(mCspHand,
+ mDlDbHand,
+ keyBag->label(),
+ p12StringToUtf8(friendlyName, mCoder),
+ mCoder,
+ newLabel,
+ newKey);
+ if(friendlyName) {
+ CFRelease(friendlyName);
+ }
+ switch(crtn) {
+ case CSSM_OK:
+ /* update key's label in case we have to delete on error */
+ keyBag->setLabel(newLabel);
+ p12DecodeLog("set pub key hash for private key");
+ break;
+ case CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA:
+ /*
+ * Special case: update keyBag's CSSM_KEY and proceed without error
+ */
+ p12DecodeLog("ignoring dup private key");
+ assert(newKey != NULL);
+ keyBag->setKey(newKey);
+ keyBag->dupKey(true);
+ /* update key's label in case we have to delete on error */
+ keyBag->setLabel(newLabel);
+ break;
+ default:
+ p12ErrorLog("p12SetPubKeyHash failure\n");
+ CssmError::throwMe(crtn);
+ }
+ }
+}
+
+/*
+ * Post keychain notification for imported keys.
+ */
+void P12Coder::notifyKeyImport()
+{
+ if(mKeychain == NULL) {
+ /* Can't notify if user only gave us DLDB */
+ return;
+ }
+ for(unsigned dex=0; dex<numKeys(); dex++) {
+ P12KeyBag *keyBag = mKeys[dex];
+ if(keyBag->dupKey()) {
+ /* no notification for keys we merely looked up */
+ continue;
+ }
+ CssmData &labelData = CssmData::overlay(keyBag->label());
+ OSStatus ortn = impExpKeyNotify(mKeychain, labelData, *keyBag->key());
+ if(ortn) {
+ p12ErrorLog("notifyKeyImport: impExpKeyNotify returned %ld\n", (unsigned long)ortn);
+ MacOSError::throwMe(ortn);
+ }
+ }
+}
+
+/*
+ * Given a P12KeyBag, find a matching P12CertBag. Keys and certs
+ * "match" if their localKeyIds match. Returns NULL if not found.
+ */
+P12CertBag *P12Coder::findCertForKey(
+ P12KeyBag *keyBag)
+{
+ assert(keyBag != NULL);
+ CSSM_DATA &keyKeyId = keyBag->localKeyIdCssm();
+
+ for(unsigned dex=0; dex<numCerts(); dex++) {
+ P12CertBag *certBag = mCerts[dex];
+ CSSM_DATA &certKeyId = certBag->localKeyIdCssm();
+ if(nssCompareCssmData(&keyKeyId, &certKeyId)) {
+ p12DecodeLog("findCertForKey SUCCESS");
+ return certBag;
+ }
+ }
+ p12DecodeLog("findCertForKey FAILURE");
+ return NULL;
+}
+
+/*
+ * Export items specified as SecKeychainItemRefs.
+ */
+void P12Coder::exportKeychainItems(
+ CFArrayRef items)
+{
+ assert(items != NULL);
+ CFIndex numItems = CFArrayGetCount(items);
+ for(CFIndex dex=0; dex<numItems; dex++) {
+ const void *item = CFArrayGetValueAtIndex(items, dex);
+ if(item == NULL) {
+ p12ErrorLog("exportKeychainItems: NULL item\n");
+ MacOSError::throwMe(errSecParam);
+ }
+ CFTypeID itemType = CFGetTypeID(item);
+ if(itemType == SecCertificateGetTypeID()) {
+ addSecCert((SecCertificateRef)item);
+ }
+ else if(itemType == SecKeyGetTypeID()) {
+ addSecKey((SecKeyRef)item);
+ }
+ else {
+ p12ErrorLog("exportKeychainItems: unknown item\n");
+ MacOSError::throwMe(errSecParam);
+ }
+ }
+}
+
+/*
+ * Gross kludge to work around the fact that SecKeyRefs have no attributes which
+ * are visible at the Sec layer. Not only are the attribute names we happen
+ * to know about (Label, PrintName) not publically visible anywhere in the
+ * system, but the *format* of the attr names for SecKeyRefs differs from
+ * the format of all other SecKeychainItems (NAME_AS_STRING for SecKeys,
+ * NAME_AS_INTEGER for everything else).
+ *
+ * So. We use the privately accessible schema definition table for
+ * keys to map from the attr name strings we happen to know about to a
+ * totally private name-as-int index which we can then use in the
+ * SecKeychainItemCopyAttributesAndData mechanism.
+ *
+ * This will go away if SecKeyRef defines its actual attrs as strings, AND
+ * the SecKeychainSearch mechanism knows to specify attr names for SecKeyRefs
+ * as strings rather than integers.
+ */
+static OSStatus attrNameToInt(
+ const char *name,
+ UInt32 *attrInt)
+{
+ const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *attrList =
+ KeySchema::KeySchemaAttributeList;
+ unsigned numAttrs = KeySchema::KeySchemaAttributeCount;
+ for(unsigned dex=0; dex<numAttrs; dex++) {
+ const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *info = &attrList[dex];
+ if(!strcmp(name, info->AttributeName)) {
+ *attrInt = info->AttributeId;
+ return errSecSuccess;
+ }
+ }
+ return errSecParam;
+}
+
+void P12Coder::addSecKey(
+ SecKeyRef keyRef)
+{
+ /* get the cert's attrs (not data) */
+
+ /*
+ * Convert the attr name strings we happen to know about to
+ * unknowable name-as-int values.
+ */
+ UInt32 printNameTag;
+ OSStatus ortn = attrNameToInt(P12_KEY_ATTR_PRINT_NAME, &printNameTag);
+ if(ortn) {
+ p12ErrorLog("addSecKey: problem looking up key attr name\n");
+ MacOSError::throwMe(ortn);
+ }
+ UInt32 labelHashTag;
+ ortn = attrNameToInt(P12_KEY_ATTR_LABEL_AND_HASH, &labelHashTag);
+ if(ortn) {
+ p12ErrorLog("addSecKey: problem looking up key attr name\n");
+ MacOSError::throwMe(ortn);
+ }
+
+ UInt32 tags[2];
+ tags[0] = printNameTag;
+ tags[1] = labelHashTag;
+
+ /* I don't know what the format field is for */
+ SecKeychainAttributeInfo attrInfo;
+ attrInfo.count = 2;
+ attrInfo.tag = tags;
+ attrInfo.format = NULL; // ???
+
+ /* FIXME header says this is an IN/OUT param, but it's not */
+ SecKeychainAttributeList *attrList = NULL;
+
+ ortn = SecKeychainItemCopyAttributesAndData(
+ (SecKeychainItemRef)keyRef,
+ &attrInfo,
+ NULL, // itemClass
+ &attrList,
+ NULL, // don't need the data
+ NULL);
+ if(ortn) {
+ p12ErrorLog("addSecKey: SecKeychainItemCopyAttributesAndData "
+ "error\n");
+ MacOSError::throwMe(ortn);
+ }
+
+ /* Snag the attrs, convert to something useful */
+ CFStringRef friendName = NULL;
+ CFDataRef localKeyId = NULL;
+ for(unsigned i=0; i<attrList->count; i++) {
+ SecKeychainAttribute *attr = &attrList->attr[i];
+ if(attr->tag == printNameTag) {
+ friendName = CFStringCreateWithBytes(NULL,
+ (UInt8 *)attr->data, attr->length,
+ kCFStringEncodingUTF8, false);
+ }
+ else if(attr->tag == labelHashTag) {
+ localKeyId = CFDataCreate(NULL, (UInt8 *)attr->data, attr->length);
+ }
+ else {
+ p12ErrorLog("addSecKey: unexpected attr tag\n");
+ MacOSError::throwMe(errSecParam);
+
+ }
+ }
+
+ /*
+ * Infer the CSP associated with this key.
+ * FIXME: this should be an attribute of the SecKeyRef itself,
+ * not inferred from the keychain it happens to be living on
+ * (SecKeyRefs should not have to be attached to Keychains at
+ * this point).
+ */
+ SecKeychainRef kcRef;
+ ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)keyRef, &kcRef);
+ if(ortn) {
+ p12ErrorLog("addSecKey: SecKeychainItemCopyKeychain returned %d\n", (int)ortn);
+ MacOSError::throwMe(ortn);
+ }
+ CSSM_CSP_HANDLE cspHand;
+ ortn = SecKeychainGetCSPHandle(kcRef, &cspHand);
+ if(ortn) {
+ p12ErrorLog("addSecKey: SecKeychainGetCSPHandle returned %d\n", (int)ortn);
+ MacOSError::throwMe(ortn);
+ }
+ CFRelease(kcRef);
+
+ /* and the CSSM_KEY itself */
+ const CSSM_KEY *cssmKey;
+ ortn = SecKeyGetCSSMKey(keyRef, &cssmKey);
+ if(ortn) {
+ p12ErrorLog("addSecKey: SecKeyGetCSSMKey returned %d\n", (int)ortn);
+ MacOSError::throwMe(ortn);
+ }
+
+ /* Cook up a key bag and save it */
+ P12KeyBag *keyBag = new P12KeyBag(cssmKey,
+ cspHand,
+ friendName, localKeyId,
+ NULL, // other attrs
+ mCoder,
+ keyRef);
+ addKey(keyBag);
+ SecKeychainItemFreeAttributesAndData(attrList, NULL);
+ if(friendName) {
+ CFRelease(friendName);
+ }
+ if(localKeyId) {
+ CFRelease(localKeyId);
+ }
+}
+
+void P12Coder::addSecCert(
+ SecCertificateRef certRef)
+{
+ /* get the cert's attrs and data */
+ /* I don't know what the format field is for */
+ SecKeychainAttributeInfo attrInfo;
+ attrInfo.count = 2;
+ UInt32 tags[2] = {kSecLabelItemAttr, kSecPublicKeyHashItemAttr};
+ attrInfo.tag = tags;
+ attrInfo.format = NULL; // ???
+
+ /* FIXME header says this is an IN/OUT param, but it's not */
+ SecKeychainAttributeList *attrList = NULL;
+ UInt32 certLen;
+ void *certData;
+
+ OSStatus ortn = SecKeychainItemCopyAttributesAndData(
+ (SecKeychainItemRef)certRef,
+ &attrInfo,
+ NULL, // itemClass
+ &attrList,
+ &certLen,
+ &certData);
+ if(ortn) {
+ p12ErrorLog("addSecCert: SecKeychainItemCopyAttributesAndData "
+ "error\n");
+ MacOSError::throwMe(ortn);
+ }
+
+ /* Snag the attrs, convert to something useful */
+ CFStringRef friendName = NULL;
+ CFDataRef localKeyId = NULL;
+ for(unsigned i=0; i<attrList->count; i++) {
+ SecKeychainAttribute *attr = &attrList->attr[i];
+ switch(attr->tag) {
+ case kSecPublicKeyHashItemAttr:
+ localKeyId = CFDataCreate(NULL, (UInt8 *)attr->data, attr->length);
+ break;
+ case kSecLabelItemAttr:
+ /* FIXME: always in UTF8? */
+ friendName = CFStringCreateWithBytes(NULL,
+ (UInt8 *)attr->data, attr->length, kCFStringEncodingUTF8,
+ false);
+ break;
+ default:
+ p12ErrorLog("addSecCert: unexpected attr tag\n");
+ MacOSError::throwMe(errSecParam);
+
+ }
+ }
+
+ /* Cook up a cert bag and save it */
+ CSSM_DATA cData = {certLen, (uint8 *)certData};
+ P12CertBag *certBag = new P12CertBag(CT_X509, cData, friendName,
+ localKeyId, NULL, mCoder);
+ addCert(certBag);
+ SecKeychainItemFreeAttributesAndData(attrList, certData);
+ if(friendName) {
+ CFRelease(friendName);
+ }
+ if(localKeyId) {
+ CFRelease(localKeyId);
+ }
+}
+
+/*
+ * Delete anything stored in a keychain during decode, called on
+ * decode error.
+ * Currently the only thing we have to deal with is private keys,
+ * since certs and CRLs don't get stored until the end of a successful
+ * decode.
+ */
+void P12Coder::deleteDecodedItems()
+{
+ if(!(mImportFlags & kSecImportKeys)) {
+ /* no keys stored, done */
+ return;
+ }
+ if(mDlDbHand.DLHandle == 0) {
+ /* no keychain, done */
+ return;
+ }
+
+ unsigned nKeys = numKeys();
+ for(unsigned dex=0; dex<nKeys; dex++) {
+ P12KeyBag *keyBag = mKeys[dex];
+ p12DeleteKey(mDlDbHand, keyBag->label());
+ }
+
+}
+