X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/libsecurity_keychain/Security/SecImportExportUtils.cpp diff --git a/OSX/libsecurity_keychain/Security/SecImportExportUtils.cpp b/OSX/libsecurity_keychain/Security/SecImportExportUtils.cpp new file mode 100644 index 00000000..e0f17768 --- /dev/null +++ b/OSX/libsecurity_keychain/Security/SecImportExportUtils.cpp @@ -0,0 +1,972 @@ +/* + * 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 +#include +#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; idxversion = 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; +} +