--- /dev/null
+/*
+ * Copyright (c) 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@
+ *
+ * SecImportExportUtils.cpp - misc. utilities for import/export module
+ */
+
+#include "SecImportExportUtils.h"
+#include "SecImportExportAgg.h"
+#include "SecImportExportCrypto.h"
+#include "SecIdentityPriv.h"
+#include "SecItem.h"
+#include <security_cdsa_utils/cuCdsaUtils.h>
+#include <Security/SecBase.h>
+#pragma mark --- Debug support ---
+
+#ifndef NDEBUG
+
+const char *impExpExtFormatStr(
+ SecExternalFormat format)
+{
+ switch(format) {
+ case kSecFormatUnknown: return "kSecFormatUnknown";
+ case kSecFormatOpenSSL: return "kSecFormatOpenSSL";
+ case kSecFormatSSH: return "kSecFormatSSH";
+ case kSecFormatBSAFE: return "kSecFormatBSAFE";
+ case kSecFormatRawKey: return "kSecFormatRawKey";
+ case kSecFormatWrappedPKCS8: return "kSecFormatWrappedPKCS8";
+ case kSecFormatWrappedOpenSSL: return "kSecFormatWrappedOpenSSL";
+ case kSecFormatWrappedSSH: return "kSecFormatWrappedSSH";
+ case kSecFormatWrappedLSH: return "kSecFormatWrappedLSH";
+ case kSecFormatX509Cert: return "kSecFormatX509Cert";
+ case kSecFormatPEMSequence: return "kSecFormatPEMSequence";
+ case kSecFormatPKCS7: return "kSecFormatPKCS7";
+ case kSecFormatPKCS12: return "kSecFormatPKCS12";
+ case kSecFormatNetscapeCertSequence: return "kSecFormatNetscapeCertSequence";
+ default: return "UNKNOWN FORMAT ENUM";
+ }
+}
+
+const char *impExpExtItemTypeStr(
+ SecExternalItemType itemType)
+{
+ switch(itemType) {
+ case kSecItemTypeUnknown: return "kSecItemTypeUnknown";
+ case kSecItemTypePrivateKey: return "kSecItemTypePrivateKey";
+ case kSecItemTypePublicKey: return "kSecItemTypePublicKey";
+ case kSecItemTypeSessionKey: return "kSecItemTypeSessionKey";
+ case kSecItemTypeCertificate: return "kSecItemTypeCertificate";
+ case kSecItemTypeAggregate: return "kSecItemTypeAggregate";
+ default: return "UNKNOWN ITEM TYPE ENUM";
+ }
+}
+#endif /* NDEBUG */
+
+/*
+ * Parse file extension and attempt to map it to format and type. Returns true
+ * on success.
+ */
+bool impExpImportParseFileExten(
+ CFStringRef fstr,
+ SecExternalFormat *inputFormat, // RETURNED
+ SecExternalItemType *itemType) // RETURNED
+{
+ if(fstr == NULL) {
+ /* nothing to work with */
+ return false;
+ }
+ if(CFStringHasSuffix(fstr, CFSTR(".cer")) ||
+ CFStringHasSuffix(fstr, CFSTR(".crt"))) {
+ *inputFormat = kSecFormatX509Cert;
+ *itemType = kSecItemTypeCertificate;
+ SecImpInferDbg("Inferring kSecFormatX509Cert from file name");
+ return true;
+ }
+ if(CFStringHasSuffix(fstr, CFSTR(".p12")) ||
+ CFStringHasSuffix(fstr, CFSTR(".pfx"))) {
+ *inputFormat = kSecFormatPKCS12;
+ *itemType = kSecItemTypeAggregate;
+ SecImpInferDbg("Inferring kSecFormatPKCS12 from file name");
+ return true;
+ }
+
+ /* Get extension, look for key indicators as substrings */
+ CFURLRef url = CFURLCreateWithString(NULL, fstr, NULL);
+ if(url == NULL) {
+ SecImpInferDbg("impExpImportParseFileExten: error creating URL");
+ return false;
+ }
+ CFStringRef exten = CFURLCopyPathExtension(url);
+ CFRelease(url);
+ if(exten == NULL) {
+ /* no extension, app probably passed in only an extension */
+ exten = fstr;
+ CFRetain(exten);
+ }
+ bool ortn = false;
+ CFRange cfr;
+ cfr = CFStringFind(exten, CFSTR("p7"), kCFCompareCaseInsensitive);
+ if(cfr.length != 0) {
+ *inputFormat = kSecFormatPKCS7;
+ *itemType = kSecItemTypeAggregate;
+ SecImpInferDbg("Inferring kSecFormatPKCS7 from file name");
+ ortn = true;
+ }
+ if(!ortn) {
+ cfr = CFStringFind(exten, CFSTR("p8"), kCFCompareCaseInsensitive);
+ if(cfr.length != 0) {
+ *inputFormat = kSecFormatWrappedPKCS8;
+ *itemType = kSecItemTypePrivateKey;
+ SecImpInferDbg("Inferring kSecFormatPKCS8 from file name");
+ ortn = true;
+ }
+ }
+ CFRelease(exten);
+ return ortn;
+}
+
+/* do a [NSString stringByDeletingPathExtension] equivalent */
+CFStringRef impExpImportDeleteExtension(
+ CFStringRef fileStr)
+{
+ CFDataRef fileStrData = CFStringCreateExternalRepresentation(NULL, fileStr,
+ kCFStringEncodingUTF8, 0);
+ if(fileStrData == NULL) {
+ return NULL;
+ }
+
+ CFURLRef urlRef = CFURLCreateFromFileSystemRepresentation(NULL,
+ CFDataGetBytePtr(fileStrData), CFDataGetLength(fileStrData), false);
+ if(urlRef == NULL) {
+ CFRelease(fileStrData);
+ return NULL;
+ }
+ CFURLRef rtnUrl = CFURLCreateCopyDeletingPathExtension(NULL, urlRef);
+ CFStringRef rtnStr = NULL;
+ CFRelease(urlRef);
+ if(rtnUrl) {
+ rtnStr = CFURLGetString(rtnUrl);
+ CFRetain(rtnStr);
+ CFRelease(rtnUrl);
+ }
+ CFRelease(fileStrData);
+ return rtnStr;
+}
+
+#pragma mark --- mapping of external format to CDSA formats ---
+
+/*
+ * For the record, here is the mapping of SecExternalFormat, algorithm, and key
+ * class to CSSM-style key format (CSSM_KEYBLOB_FORMAT -
+ * CSSM_KEYBLOB_RAW_FORMAT_X509, etc). The entries in the table are the
+ * last component of a CSSM_KEYBLOB_FORMAT. Format kSecFormatUnknown means
+ * "default for specified class and algorithm", which is currently the
+ * same as kSecFormatOpenSSL.
+ *
+ * algorithm/class
+ * RSA DSA DH
+ * ---------------- ---------------- ----------------
+ * SecExternalFormat priv pub priv pub priv pub
+ * ----------------- ------- ------- ------- ------- ------- -------
+ * kSecFormatOpenSSL PKCS1 X509 OPENSSL X509 PKCS3 X509
+ * kSecFormatBSAFE PKCS8 PKCS1 FIPS186 FIPS186 PKCS8 not supported
+ * kSecFormatUnknown PKCS1 X509 OPENSSL X509 PKCS3 X509
+ * kSecFormatSSH SSH SSH n/s n/s n/s n/s
+ * kSecFormatSSHv2 n/s SSH2 n/s SSH2 n/s n/s
+ *
+ * The only external format supported for ECDSA and ECDH keys is kSecFormatOpenSSL,
+ * which translates to OPENSSL for private keys and X509 for public keys.
+ */
+
+/* Arrays expressing the above table. */
+
+/* l.s. dimension is pub/priv for one alg */
+typedef struct {
+ CSSM_KEYBLOB_FORMAT priv;
+ CSSM_KEYBLOB_FORMAT pub;
+} algForms;
+
+/*
+ * indices into array of algForms defining all algs' formats for a given
+ * SecExternalFormat
+ */
+#define SIE_ALG_RSA 0
+#define SIE_ALG_DSA 1
+#define SIE_ALG_DH 2
+#define SIE_ALG_ECDSA 3
+#define SIE_ALG_LAST SIE_ALG_ECDSA
+#define SIE_NUM_ALGS (SIE_ALG_LAST + 1)
+
+/* kSecFormatOpenSSL */
+static algForms opensslAlgForms[SIE_NUM_ALGS] =
+{
+ { CSSM_KEYBLOB_RAW_FORMAT_PKCS1, CSSM_KEYBLOB_RAW_FORMAT_X509 }, // RSA
+ { CSSM_KEYBLOB_RAW_FORMAT_OPENSSL, CSSM_KEYBLOB_RAW_FORMAT_X509 }, // DSA
+ { CSSM_KEYBLOB_RAW_FORMAT_PKCS3, CSSM_KEYBLOB_RAW_FORMAT_X509 }, // D-H
+ { CSSM_KEYBLOB_RAW_FORMAT_OPENSSL, CSSM_KEYBLOB_RAW_FORMAT_X509 }, // ECDSA
+};
+
+/* kSecFormatBSAFE */
+static algForms bsafeAlgForms[SIE_NUM_ALGS] =
+{
+ { CSSM_KEYBLOB_RAW_FORMAT_PKCS8, CSSM_KEYBLOB_RAW_FORMAT_PKCS1 }, // RSA
+ { CSSM_KEYBLOB_RAW_FORMAT_FIPS186, CSSM_KEYBLOB_RAW_FORMAT_FIPS186 }, // DSA
+ { CSSM_KEYBLOB_RAW_FORMAT_PKCS8, CSSM_KEYBLOB_RAW_FORMAT_NONE }, // D-H
+ { CSSM_KEYBLOB_RAW_FORMAT_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE }, // ECDSA not supported
+};
+
+/* kSecFormatSSH (v1) */
+static algForms ssh1AlgForms[SIE_NUM_ALGS] =
+{
+ { CSSM_KEYBLOB_RAW_FORMAT_OPENSSH, CSSM_KEYBLOB_RAW_FORMAT_OPENSSH }, // RSA only
+ { CSSM_KEYBLOB_RAW_FORMAT_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE }, // DSA not supported
+ { CSSM_KEYBLOB_RAW_FORMAT_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE }, // D-H not supported
+ { CSSM_KEYBLOB_RAW_FORMAT_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE }, // ECDSA not supported
+};
+
+/* kSecFormatSSHv2 */
+static algForms ssh2AlgForms[SIE_NUM_ALGS] =
+{
+ { CSSM_KEYBLOB_RAW_FORMAT_NONE, CSSM_KEYBLOB_RAW_FORMAT_OPENSSH2 }, // RSA - public only
+ { CSSM_KEYBLOB_RAW_FORMAT_NONE, CSSM_KEYBLOB_RAW_FORMAT_OPENSSH2 }, // DSA - public only
+ { CSSM_KEYBLOB_RAW_FORMAT_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE }, // D-H not supported
+ { CSSM_KEYBLOB_RAW_FORMAT_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE }, // ECDSA not supported
+};
+
+/*
+ * This routine performs a lookup into the above 3-dimensional array to
+ * map {algorithm, class, SecExternalFormat} to a CSSM_KEYBLOB_FORMAT.
+ * Returns errSecUnsupportedFormat in the rare appropriate case.
+ */
+OSStatus impExpKeyForm(
+ SecExternalFormat externForm,
+ SecExternalItemType itemType,
+ CSSM_ALGORITHMS alg,
+ CSSM_KEYBLOB_FORMAT *cssmForm, // RETURNED
+ CSSM_KEYCLASS *cssmClass) // RETRUNED
+{
+ if(itemType == kSecItemTypeSessionKey) {
+ /* special trivial case */
+ /* FIXME ensure caller hasn't specified bogus format */
+ *cssmForm = CSSM_KEYBLOB_RAW_FORMAT_NONE;
+ *cssmClass = CSSM_KEYCLASS_SESSION_KEY;
+ return errSecSuccess;
+ }
+ if(externForm == kSecFormatUnknown) {
+ /* default is openssl */
+ externForm = kSecFormatOpenSSL;
+ }
+
+ unsigned algDex;
+ switch(alg) {
+ case CSSM_ALGID_RSA:
+ algDex = SIE_ALG_RSA;
+ break;
+ case CSSM_ALGID_DSA:
+ algDex = SIE_ALG_DSA;
+ break;
+ case CSSM_ALGID_DH:
+ algDex = SIE_ALG_DH;
+ break;
+ case CSSM_ALGID_ECDSA:
+ algDex = SIE_ALG_ECDSA;
+ break;
+ default:
+ return CSSMERR_CSP_INVALID_ALGORITHM;
+ }
+ const algForms *forms;
+ switch(externForm) {
+ case kSecFormatOpenSSL:
+ forms = opensslAlgForms;
+ break;
+ case kSecFormatBSAFE:
+ forms = bsafeAlgForms;
+ break;
+ case kSecFormatSSH:
+ forms = ssh1AlgForms;
+ break;
+ case kSecFormatSSHv2:
+ forms = ssh2AlgForms;
+ break;
+ default:
+ return errSecUnsupportedFormat;
+ }
+ CSSM_KEYBLOB_FORMAT form = CSSM_KEYBLOB_RAW_FORMAT_NONE;
+ switch(itemType) {
+ case kSecItemTypePrivateKey:
+ form = forms[algDex].priv;
+ *cssmClass = CSSM_KEYCLASS_PRIVATE_KEY;
+ break;
+ case kSecItemTypePublicKey:
+ form = forms[algDex].pub;
+ *cssmClass = CSSM_KEYCLASS_PUBLIC_KEY;
+ break;
+ default:
+ return errSecUnsupportedFormat;
+ }
+ if(form == CSSM_KEYBLOB_RAW_FORMAT_NONE) {
+ /* not in the tables - abort */
+ return errSecUnsupportedFormat;
+ }
+ else {
+ *cssmForm = form;
+ return errSecSuccess;
+ }
+}
+
+/*
+ * Given a raw key blob and zero to three known parameters (type, format,
+ * algorithm), figure out all parameters. Used for private and public keys.
+ */
+static bool impExpGuessKeyParams(
+ CFDataRef keyData,
+ SecExternalFormat *externForm, // IN/OUT
+ SecExternalItemType *itemType, // IN/OUT
+ CSSM_ALGORITHMS *keyAlg) // IN/OUT
+{
+ /* CSSM alg list: RSA, DSA, DH, ECDSA */
+ CSSM_ALGORITHMS minAlg = CSSM_ALGID_RSA;
+ CSSM_ALGORITHMS maxAlg = CSSM_ALGID_ECDSA;
+ SecExternalFormat minForm = kSecFormatOpenSSL; // then SSH, BSAFE, then...
+ SecExternalFormat maxForm = kSecFormatSSHv2;
+ SecExternalItemType minType = kSecItemTypePrivateKey; // just two
+ SecExternalItemType maxType = kSecItemTypePublicKey;
+
+
+ if(keyData == NULL || CFDataGetLength(keyData) == 0){
+ MacOSError::throwMe(errSecUnsupportedKeySize);
+ }
+
+ switch(*externForm) {
+ case kSecFormatUnknown:
+ break; // run through all formats
+ case kSecFormatOpenSSL:
+ case kSecFormatSSH:
+ case kSecFormatSSHv2:
+ case kSecFormatBSAFE:
+ minForm = maxForm = *externForm; // just test this one
+ break;
+ default:
+ return false;
+ }
+ switch(*itemType) {
+ case kSecItemTypeUnknown:
+ break;
+ case kSecItemTypePrivateKey:
+ case kSecItemTypePublicKey:
+ minType = maxType = *itemType;
+ break;
+ default:
+ return false;
+ }
+ switch(*keyAlg) {
+ case CSSM_ALGID_NONE:
+ break;
+ case CSSM_ALGID_RSA:
+ case CSSM_ALGID_DSA:
+ case CSSM_ALGID_DH:
+ case CSSM_ALGID_ECDSA:
+ minAlg = maxAlg = *keyAlg;
+ break;
+ default:
+ return false;
+ }
+
+ CSSM_ALGORITHMS theAlg;
+ SecExternalFormat theForm;
+ SecExternalItemType theType;
+ CSSM_CSP_HANDLE cspHand = cuCspStartup(CSSM_TRUE);
+ if(cspHand == 0) {
+ return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
+ }
+
+ /*
+ * Iterate through all set of enabled {alg, type, format}.
+ * We do not assume that any of the enums are sequential hence this
+ * odd iteration algorithm....
+ */
+ bool ourRtn = false;
+ for(theAlg=minAlg; ; ) {
+ for(theForm=minForm; ; ) {
+ for(theType=minType; ; ) {
+
+ /* do super lightweight null unwrap to parse */
+ OSStatus ortn = impExpImportRawKey(keyData,
+ theForm, theType, theAlg,
+ NULL, // no keychain
+ cspHand,
+ 0, // no flags
+ NULL, // no key params
+ NULL, // no printName
+ NULL); // no returned items
+ if(ortn == errSecSuccess) {
+ *externForm = theForm;
+ *itemType = theType;
+ *keyAlg = theAlg;
+ ourRtn = true;
+ goto done;
+ }
+
+ /* next type or break if we're done */
+ if(theType == maxType) {
+ break;
+ }
+ else switch(theType) {
+ case kSecItemTypePrivateKey:
+ theType = kSecItemTypePublicKey;
+ break;
+ default:
+ assert(0);
+ ourRtn = false;
+ goto done;
+ }
+ } /* for each class/type */
+
+ /* next format or break if we're done */
+ if(theForm == maxForm) {
+ break;
+ }
+ else switch(theForm) {
+ case kSecFormatOpenSSL:
+ theForm = kSecFormatSSH;
+ break;
+ case kSecFormatSSH:
+ theForm = kSecFormatBSAFE;
+ break;
+ case kSecFormatBSAFE:
+ theForm = kSecFormatSSHv2;
+ break;
+ default:
+ assert(0);
+ ourRtn = false;
+ goto done;
+ }
+ } /* for each format */
+
+ /* next alg or break if we're done */
+ if(theAlg == maxAlg) {
+ break;
+ }
+ else switch(theAlg) {
+ case CSSM_ALGID_RSA:
+ theAlg = CSSM_ALGID_DSA;
+ break;
+ case CSSM_ALGID_DSA:
+ theAlg = CSSM_ALGID_DH;
+ break;
+ case CSSM_ALGID_DH:
+ theAlg = CSSM_ALGID_ECDSA;
+ break;
+ default:
+ /* i.e. CSSM_ALGID_ECDSA, we already broke at theAlg == maxAlg */
+ assert(0);
+ ourRtn = false;
+ goto done;
+ }
+ } /* for each alg */
+done:
+ cuCspDetachUnload(cspHand, CSSM_TRUE);
+ return ourRtn;
+}
+
+/*
+ * Guess an incoming blob's type, format and (for keys only) algorithm
+ * by examining its contents. Returns true on success, in which case
+ * *inputFormat, *itemType, and *keyAlg are all valid. Caller optionally
+ * passes in valid values any number of these as a clue.
+ */
+bool impExpImportGuessByExamination(
+ CFDataRef inData,
+ SecExternalFormat *inputFormat, // may be kSecFormatUnknown on entry
+ SecExternalItemType *itemType, // may be kSecItemTypeUnknown on entry
+ CSSM_ALGORITHMS *keyAlg) // CSSM_ALGID_NONE - unknown
+{
+ if( ( (*inputFormat == kSecFormatUnknown) ||
+ (*inputFormat == kSecFormatX509Cert)
+ ) &&
+ ( (*itemType == kSecItemTypeUnknown) ||
+ (*itemType == kSecItemTypeCertificate) ) ) {
+ /*
+ * See if it parses as a cert
+ */
+ CSSM_CL_HANDLE clHand = cuClStartup();
+ if(clHand == 0) {
+ return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
+ }
+ CSSM_HANDLE cacheHand;
+ CSSM_RETURN crtn;
+ CSSM_DATA cdata = { CFDataGetLength(inData),
+ (uint8 *)CFDataGetBytePtr(inData) };
+ crtn = CSSM_CL_CertCache(clHand, &cdata, &cacheHand);
+ bool brtn = false;
+ if(crtn == CSSM_OK) {
+ *inputFormat = kSecFormatX509Cert;
+ *itemType = kSecItemTypeCertificate;
+ SecImpInferDbg("Inferred kSecFormatX509Cert via CL");
+ CSSM_CL_CertAbortCache(clHand, cacheHand);
+ brtn = true;
+ }
+ cuClDetachUnload(clHand);
+ if(brtn) {
+ return true;
+ }
+ }
+ /* TBD: need way to inquire of P12 lib if this is a valid-looking PFX */
+
+ if( ( (*inputFormat == kSecFormatUnknown) ||
+ (*inputFormat == kSecFormatNetscapeCertSequence)
+ ) &&
+ ( (*itemType == kSecItemTypeUnknown) ||
+ (*itemType == kSecItemTypeAggregate) ) ) {
+ /* See if it's a netscape cert sequence */
+ CSSM_RETURN crtn = impExpNetscapeCertImport(inData, 0, NULL, NULL, NULL);
+ if(crtn == CSSM_OK) {
+ *inputFormat = kSecFormatNetscapeCertSequence;
+ *itemType = kSecItemTypeAggregate;
+ SecImpInferDbg("Inferred netscape-cert-sequence by decoding");
+ return true;
+ }
+ }
+
+ /* See if it's a key */
+ return impExpGuessKeyParams(inData, inputFormat, itemType, keyAlg);
+}
+
+#pragma mark --- Key Import support ---
+
+/*
+ * Given a context specified via a CSSM_CC_HANDLE, add a new
+ * CSSM_CONTEXT_ATTRIBUTE to the context as specified by AttributeType,
+ * AttributeLength, and an untyped pointer.
+ */
+CSSM_RETURN impExpAddContextAttribute(CSSM_CC_HANDLE CCHandle,
+ uint32 AttributeType,
+ uint32 AttributeLength,
+ const void *AttributePtr)
+{
+ CSSM_CONTEXT_ATTRIBUTE newAttr;
+
+ newAttr.AttributeType = AttributeType;
+ newAttr.AttributeLength = AttributeLength;
+ newAttr.Attribute.Data = (CSSM_DATA_PTR)AttributePtr;
+ return CSSM_UpdateContextAttributes(CCHandle, 1, &newAttr);
+}
+
+/*
+ * Free memory via specified plugin's app-level allocator
+ */
+void impExpFreeCssmMemory(
+ CSSM_HANDLE hand,
+ void *p)
+{
+ CSSM_API_MEMORY_FUNCS memFuncs;
+ CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(hand, &memFuncs);
+ if(crtn) {
+ return;
+ }
+ memFuncs.free_func(p, memFuncs.AllocRef);
+}
+
+/*
+ * Calculate digest of any CSSM_KEY. Unlike older implementations
+ * of this logic, you can actually calculate the public key hash
+ * on any class of key, any format, raw CSP or CSPDL (though if
+ * you're using the CSPDL, the key has to be a reference key
+ * in that CSPDL session).
+ *
+ * Caller must free keyDigest->Data using impExpFreeCssmMemory() since
+ * this is allocated by the CSP's app-specified allocator.
+ */
+CSSM_RETURN impExpKeyDigest(
+ CSSM_CSP_HANDLE cspHand,
+ CSSM_KEY_PTR key,
+ CSSM_DATA_PTR keyDigest) // contents allocd and RETURNED
+{
+ CSSM_DATA_PTR localDigest;
+ CSSM_CC_HANDLE ccHand;
+
+ CSSM_RETURN crtn = CSSM_CSP_CreatePassThroughContext(cspHand,
+ key,
+ &ccHand);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = CSSM_CSP_PassThrough(ccHand,
+ CSSM_APPLECSP_KEYDIGEST,
+ NULL,
+ (void **)&localDigest);
+ if(crtn) {
+ SecImpExpDbg("CSSM_CSP_PassThrough(KEY_DIGEST) failure");
+ }
+ else {
+ /*
+ * Give caller the Data referent and we'll free the
+ * CSSM_DATA struct itswelf.
+ */
+ *keyDigest = *localDigest;
+ impExpFreeCssmMemory(cspHand, localDigest);
+ }
+ CSSM_DeleteContext(ccHand);
+ return crtn;
+}
+
+
+/*
+ * Given a CFTypeRef passphrase which may be a CFDataRef or a CFStringRef,
+ * return a refcounted CFStringRef suitable for use with the PKCS12 library.
+ * PKCS12 passphrases in CFData format must be UTF8 encoded.
+ */
+OSStatus impExpPassphraseToCFString(
+ CFTypeRef passin,
+ CFStringRef *passout) // may be the same as passin, but refcounted
+{
+ if(CFGetTypeID(passin) == CFStringGetTypeID()) {
+ CFStringRef passInStr = (CFStringRef)passin;
+ CFRetain(passInStr);
+ *passout = passInStr;
+ return errSecSuccess;
+ }
+ else if(CFGetTypeID(passin) == CFDataGetTypeID()) {
+ CFDataRef cfData = (CFDataRef)passin;
+ CFIndex len = CFDataGetLength(cfData);
+ CFStringRef cfStr = CFStringCreateWithBytes(NULL,
+ CFDataGetBytePtr(cfData), len, kCFStringEncodingUTF8, true);
+ if(cfStr == NULL) {
+ SecImpExpDbg("Passphrase not in UTF8 format");
+ return errSecParam;
+ }
+ *passout = cfStr;
+ return errSecSuccess;
+ }
+ else {
+ SecImpExpDbg("Passphrase not CFData or CFString");
+ return errSecParam;
+ }
+}
+
+/*
+ * Given a CFTypeRef passphrase which may be a CFDataRef or a CFStringRef,
+ * return a refcounted CFDataRef whose bytes are suitable for use with
+ * PKCS5 (v1.5 and v2.0) key derivation.
+ */
+OSStatus impExpPassphraseToCFData(
+ CFTypeRef passin,
+ CFDataRef *passout) // may be the same as passin, but refcounted
+{
+ if(CFGetTypeID(passin) == CFDataGetTypeID()) {
+ CFDataRef passInData = (CFDataRef)passin;
+ CFRetain(passInData);
+ *passout = passInData;
+ return errSecSuccess;
+ }
+ else if(CFGetTypeID(passin) == CFStringGetTypeID()) {
+ CFStringRef passInStr = (CFStringRef)passin;
+ CFDataRef outData;
+ outData = CFStringCreateExternalRepresentation(NULL,
+ passInStr,
+ kCFStringEncodingUTF8,
+ 0); // lossByte 0 ==> no loss allowed
+ if(outData == NULL) {
+ /* Well try with lossy conversion */
+ SecImpExpDbg("Trying lossy conversion of CFString passphrase to UTF8");
+ outData = CFStringCreateExternalRepresentation(NULL,
+ passInStr,
+ kCFStringEncodingUTF8,
+ 1);
+ if(outData == NULL) {
+ SecImpExpDbg("Failure on conversion of CFString passphrase to UTF8");
+ /* what do we do now, Batman? */
+ return errSecParam;
+ }
+ }
+ *passout = outData;
+ return errSecSuccess;
+ }
+ else {
+ SecImpExpDbg("Passphrase not CFData or CFString");
+ return errSecParam;
+ }
+}
+
+/*
+* Add a CFString to a crypto context handle.
+*/
+static CSSM_RETURN impExpAddStringAttr(
+ CSSM_CC_HANDLE ccHand,
+ CFStringRef str,
+ CSSM_ATTRIBUTE_TYPE attrType)
+{
+ /* CFStrings are passed as external rep in UTF8 encoding by convention */
+ CFDataRef outData;
+ outData = CFStringCreateExternalRepresentation(NULL,
+ str, kCFStringEncodingUTF8, 0); // lossByte 0 ==> no loss allowed
+ if(outData == NULL) {
+ SecImpExpDbg("impExpAddStringAttr: bad string format");
+ return errSecParam;
+ }
+
+ CSSM_DATA attrData;
+ attrData.Data = (uint8 *)CFDataGetBytePtr(outData);
+ attrData.Length = CFDataGetLength(outData);
+ CSSM_RETURN crtn = impExpAddContextAttribute(ccHand, attrType, sizeof(CSSM_DATA),
+ &attrData);
+ CFRelease(outData);
+ if(crtn) {
+ SecImpExpDbg("impExpAddStringAttr: CSSM_UpdateContextAttributes error");
+ }
+ return crtn;
+}
+
+/*
+ * Generate a secure passphrase key. Caller must eventually CSSM_FreeKey the result.
+ */
+static CSSM_RETURN impExpCreatePassKey(
+ const SecKeyImportExportParameters *keyParams, // required
+ CSSM_CSP_HANDLE cspHand, // MUST be CSPDL
+ impExpVerifyPhrase verifyPhrase, // for secure passphrase
+ CSSM_KEY_PTR *passKey) // mallocd and RETURNED
+{
+ CSSM_RETURN crtn;
+ CSSM_CC_HANDLE ccHand;
+ uint32 verifyAttr;
+ CSSM_DATA dummyLabel;
+ CSSM_KEY_PTR ourKey = NULL;
+
+ SecImpExpDbg("Generating secure passphrase key");
+ ourKey = (CSSM_KEY_PTR)malloc(sizeof(CSSM_KEY));
+ if(ourKey == NULL) {
+ return errSecAllocate;
+ }
+ memset(ourKey, 0, sizeof(CSSM_KEY));
+
+ crtn = CSSM_CSP_CreateKeyGenContext(cspHand,
+ CSSM_ALGID_SECURE_PASSPHRASE,
+ 4, // keySizeInBits must be non zero
+ NULL, // Seed
+ NULL, // Salt
+ NULL, // StartDate
+ NULL, // EndDate
+ NULL, // Params
+ &ccHand);
+ if(crtn) {
+ SecImpExpDbg("impExpCreatePassKey: CSSM_CSP_CreateKeyGenContext error");
+ return crtn;
+ }
+ /* subsequent errors to errOut: */
+
+ /* additional context attributes specific to this type of key gen */
+ assert(keyParams != NULL); // or we wouldn't be here
+ if(keyParams->alertTitle != NULL) {
+ crtn = impExpAddStringAttr(ccHand, keyParams->alertTitle,
+ CSSM_ATTRIBUTE_ALERT_TITLE);
+ if(crtn) {
+ goto errOut;
+ }
+ }
+ if(keyParams->alertPrompt != NULL) {
+ crtn = impExpAddStringAttr(ccHand, keyParams->alertPrompt,
+ CSSM_ATTRIBUTE_PROMPT);
+ if(crtn) {
+ goto errOut;
+ }
+ }
+ verifyAttr = (verifyPhrase == VP_Export) ? 1 : 0;
+ crtn = impExpAddContextAttribute(ccHand, CSSM_ATTRIBUTE_VERIFY_PASSPHRASE,
+ sizeof(uint32), (const void *)((size_t) verifyAttr));
+ if(crtn) {
+ SecImpExpDbg("impExpCreatePassKey: CSSM_UpdateContextAttributes error");
+ goto errOut;
+ }
+
+ dummyLabel.Data = (uint8 *)"Secure Passphrase";
+ dummyLabel.Length = strlen((char *)dummyLabel.Data);
+
+ crtn = CSSM_GenerateKey(ccHand,
+ CSSM_KEYUSE_ANY,
+ CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE,
+ &dummyLabel,
+ NULL, // ACL
+ ourKey);
+ if(crtn) {
+ SecImpExpDbg("impExpCreatePassKey: CSSM_GenerateKey error");
+ }
+errOut:
+ CSSM_DeleteContext(ccHand);
+ if(crtn == CSSM_OK) {
+ *passKey = ourKey;
+ }
+ else if(ourKey != NULL) {
+ free(ourKey);
+ }
+ return crtn;
+}
+
+/*
+ * Obtain passphrase, given a SecKeyImportExportParameters.
+ *
+ * Passphrase comes from one of two places: app-specified, in
+ * SecKeyImportExportParameters.passphrase (either as CFStringRef
+ * or CFDataRef); or via the secure passphrase mechanism.
+ *
+ * Passphrase is returned in AT MOST one of two forms:
+ *
+ * -- Secure passphrase is returned as a CSSM_KEY_PTR, which the
+ * caller must CSSM_FreeKey later as well as free()ing the actual
+ * CSSM_KEY_PTR.
+ * -- CFTypeRef for app-supplied passphrases. This can be one of
+ * two types:
+ *
+ * -- CFStringRef, for use with P12
+ * -- CFDataRef, for more general use (e.g. for PKCS5).
+ *
+ * In either case the caller must CFRelease the result.
+ */
+OSStatus impExpPassphraseCommon(
+ const SecKeyImportExportParameters *keyParams,
+ CSSM_CSP_HANDLE cspHand, // MUST be CSPDL, for passKey generation
+ impExpPassphraseForm phraseForm,
+ impExpVerifyPhrase verifyPhrase, // for secure passphrase
+ CFTypeRef *phrase, // RETURNED, or
+ CSSM_KEY_PTR *passKey) // mallocd and RETURNED
+{
+ assert(keyParams != NULL);
+
+ /* Give precedence to secure passphrase */
+ if(keyParams->flags & kSecKeySecurePassphrase) {
+ assert(passKey != NULL);
+ return impExpCreatePassKey(keyParams, cspHand, verifyPhrase, passKey);
+ }
+ else if(keyParams->passphrase != NULL) {
+ CFTypeRef phraseOut;
+ OSStatus ortn;
+ assert(phrase != NULL);
+ switch(phraseForm) {
+ case SPF_String:
+ ortn = impExpPassphraseToCFString(keyParams->passphrase,
+ (CFStringRef *)&phraseOut);
+ break;
+ case SPF_Data:
+ ortn = impExpPassphraseToCFData(keyParams->passphrase,
+ (CFDataRef *)&phraseOut);
+ break;
+ default:
+ /* only called internally */
+ assert(0);
+ ortn = errSecParam;
+ }
+ if(ortn == errSecSuccess) {
+ *phrase = phraseOut;
+ }
+ return ortn;
+ }
+ else {
+ return errSecPassphraseRequired;
+ }
+}
+
+static void ToggleKeyAttribute(
+ CFArrayRef keyAttrs,
+ CFTypeRef attr,
+ CSSM_KEYATTR_FLAGS mask,
+ CSSM_KEYATTR_FLAGS &result)
+{
+ // If the keyAttrs array contains the given attribute,
+ // set the corresponding keyattr flags, otherwise clear them.
+ // (Note: caller verifies that keyAttrs is not NULL.)
+ CFIndex numItems = CFArrayGetCount(keyAttrs);
+ result &= ~(mask);
+ if (numItems > 0) {
+ CFRange range = CFRangeMake(0, numItems);
+ if (CFArrayContainsValue(keyAttrs, range, attr))
+ result |= mask;
+ }
+}
+
+CSSM_KEYATTR_FLAGS ConvertArrayToKeyAttributes(SecKeyRef aKey, CFArrayRef keyAttrs)
+{
+ CSSM_KEYATTR_FLAGS result = CSSM_KEYATTR_RETURN_DEFAULT;
+
+ if (aKey) {
+ const CSSM_KEY* cssmKey = NULL;
+ if (errSecSuccess == SecKeyGetCSSMKey(aKey, &cssmKey))
+ result = cssmKey->KeyHeader.KeyAttr;
+ }
+
+ if (!keyAttrs)
+ return result;
+
+ CFMutableArrayRef attrs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFIndex idx, count = CFArrayGetCount(keyAttrs);
+ for (idx=0; idx<count; idx++) {
+ CFTypeRef attr = (CFTypeRef) CFArrayGetValueAtIndex(keyAttrs, idx);
+ if (attr && (CFNumberGetTypeID() == CFGetTypeID(attr))) {
+ // Convert numeric CSSM keyattr values to equivalent attribute constants
+ uint32 value;
+ if (CFNumberGetValue((CFNumberRef)attr, kCFNumberSInt32Type, &value)) {
+ switch (value) {
+ case CSSM_KEYATTR_SENSITIVE:
+ attr = kSecAttrIsSensitive;
+ break;
+ case CSSM_KEYATTR_EXTRACTABLE:
+ attr = kSecAttrIsExtractable;
+ break;
+ case CSSM_KEYATTR_PERMANENT:
+ attr = kSecAttrIsPermanent;
+ break;
+ default:
+ attr = NULL;
+ break;
+ }
+ }
+ }
+ if (attr)
+ CFArrayAppendValue(attrs, attr);
+ }
+
+ // Set key attribute flag in result if present in the array, otherwise clear
+ ToggleKeyAttribute(attrs, kSecAttrIsSensitive, CSSM_KEYATTR_SENSITIVE, result);
+ ToggleKeyAttribute(attrs, kSecAttrIsExtractable, CSSM_KEYATTR_EXTRACTABLE, result);
+ ToggleKeyAttribute(attrs, kSecAttrIsPermanent, CSSM_KEYATTR_PERMANENT, result);
+
+ // if caller specified an attributes array which omitted kSecAttrIsExtractable,
+ // this implies the sensitive attribute; force it on so that at least one bit
+ // is set. If our result is 0, this is indistinguishable from the case where
+ // no key attributes were specified, causing a default bitmask to be used
+ // in subsequent import code.
+
+ if (0==(result & CSSM_KEYATTR_EXTRACTABLE))
+ result |= CSSM_KEYATTR_SENSITIVE;
+
+ CFRelease(attrs);
+ return result;
+}
+
+Boolean ConvertSecKeyImportExportParametersToSecImportExportKeyParameters(SecKeyRef aKey,
+ const SecItemImportExportKeyParameters* newPtr, SecKeyImportExportParameters* oldPtr)
+{
+ Boolean result = false;
+
+ if (NULL != oldPtr && NULL != newPtr)
+ {
+ oldPtr->version = newPtr->version;
+ oldPtr->flags = newPtr->flags;
+ oldPtr->passphrase = newPtr->passphrase;
+ oldPtr->alertTitle = newPtr->alertTitle;
+ oldPtr->alertPrompt = newPtr->alertPrompt;
+ oldPtr->accessRef = newPtr->accessRef;
+ oldPtr->keyUsage = ConvertArrayToKeyUsage(newPtr->keyUsage);
+ oldPtr->keyAttributes = ConvertArrayToKeyAttributes(aKey, newPtr->keyAttributes);
+ result = true;
+ }
+ return result;
+}
+