--- /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@
+ *
+ * SecWrappedKeys.cpp - SecExportRep and SecImportRep methods dealing with
+ * wrapped private keys (other than PKCS8 format).
+ */
+
+#include "SecExternalRep.h"
+#include "SecImportExportUtils.h"
+#include "SecImportExportPem.h"
+#include "SecImportExportCrypto.h"
+#include <Security/cssmtype.h>
+#include <Security/cssmapi.h>
+#include <Security/SecKeyPriv.h>
+#include <security_asn1/SecNssCoder.h>
+#include <security_cdsa_utils/cuCdsaUtils.h>
+#include <security_utilities/devrandom.h>
+
+#include <assert.h>
+
+using namespace Security;
+using namespace KeychainCore;
+
+static int hexToDigit(
+ char digit,
+ uint8 *rtn) // RETURNED
+{
+ if((digit >= '0') && (digit <= '9')) {
+ *rtn = digit - '0';
+ return 0;
+ }
+ if((digit >= 'a') && (digit <= 'f')) {
+ *rtn = digit - 'a' + 10;
+ return 0;
+ }
+ if((digit >= 'A') && (digit <= 'F')) {
+ *rtn = digit - 'A' + 10;
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * Convert two ascii characters starting at cp to an unsigned char.
+ * Returns nonzero on error.
+ */
+static int hexToUchar(
+ const char *cp,
+ uint8 *rtn) // RETURNED
+{
+ uint8 rtnc = 0;
+ uint8 c;
+ if(hexToDigit(*cp++, &c)) {
+ return -1;
+ }
+ rtnc = c << 4;
+ if(hexToDigit(*cp, &c)) {
+ return -1;
+ }
+ rtnc |= c;
+ *rtn = rtnc;
+ return 0;
+}
+
+/*
+ * Given an array of PEM parameter lines, infer parameters for key derivation and
+ * encryption.
+ */
+static OSStatus opensslPbeParams(
+ CFArrayRef paramLines, // elements are CFStrings
+ SecNssCoder &coder, // IV allocd with this
+ /* remaining arguments RETURNED */
+ CSSM_ALGORITHMS &pbeAlg,
+ CSSM_ALGORITHMS &keyAlg,
+ CSSM_ALGORITHMS &encrAlg,
+ CSSM_ENCRYPT_MODE &encrMode,
+ CSSM_PADDING &encrPad,
+ uint32 &keySizeInBits,
+ unsigned &blockSizeInBytes,
+ CSSM_DATA &iv)
+{
+ /*
+ * This format requires PEM parameter lines. We could have gotten here
+ * without them if caller specified wrong format.
+ */
+ if(paramLines == NULL) {
+ SecImpExpDbg("importWrappedKeyOpenssl: no PEM parameter lines");
+ return errSecUnknownFormat;
+ }
+ CFStringRef dekInfo = NULL;
+ CFIndex numLines = CFArrayGetCount(paramLines);
+ for(CFIndex dex=0; dex<numLines; dex++) {
+ CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(paramLines, dex);
+ CFRange range;
+ range = CFStringFind(str, CFSTR("DEK-Info: "), 0);
+ if(range.length != 0) {
+ dekInfo = str;
+ break;
+ }
+ }
+ if(dekInfo == NULL) {
+ SecImpExpDbg("importWrappedKeyOpenssl: no DEK-Info lines");
+ return errSecUnknownFormat;
+ }
+
+ /* drop down to C strings for low level grunging */
+ char cstr[1024];
+ if(!CFStringGetCString(dekInfo, cstr, sizeof(cstr), kCFStringEncodingASCII)) {
+ SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (1)");
+ return errSecUnknownFormat;
+ }
+
+ /*
+ * This line looks like this:
+ * DEK-Info: DES-CBC,A22977A0A6A6F696
+ *
+ * Now parse, getting the cipher spec and the IV.
+ */
+ char *cp = strchr(cstr, ':');
+ if(cp == NULL) {
+ SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (2)");
+ return errSecUnknownFormat;
+ }
+ if((cp[1] == ' ') && (cp[2] != '\0')) {
+ /* as it normally does... */
+ cp += 2;
+ }
+
+ /* We only support DES and 3DES here */
+ if(!strncmp(cp, "DES-EDE3-CBC", 12)) {
+ keyAlg = CSSM_ALGID_3DES_3KEY;
+ encrAlg = CSSM_ALGID_3DES_3KEY_EDE;
+ keySizeInBits = 64 * 3;
+ blockSizeInBytes = 8;
+ }
+ else if(!strncmp(cp, "DES-CBC", 7)) {
+ keyAlg = CSSM_ALGID_DES;
+ encrAlg = CSSM_ALGID_DES;
+ keySizeInBits = 64;
+ blockSizeInBytes = 8;
+ }
+ else {
+ SecImpExpDbg("importWrappedKeyOpenssl: unrecognized wrap alg (%s)",
+ cp);
+ return errSecUnknownFormat;
+ }
+
+ /* these are more or less fixed */
+ pbeAlg = CSSM_ALGID_PBE_OPENSSL_MD5;
+ encrMode = CSSM_ALGMODE_CBCPadIV8;
+ encrPad = CSSM_PADDING_PKCS7;
+
+ /* now get the ASCII hex version of the IV */
+ cp = strchr(cp, ',');
+ if(cp == NULL) {
+ SecImpExpDbg("importWrappedKeyOpenssl: No IV in DEK-Info line");
+ return errSecUnknownFormat;
+ }
+ if(cp[1] != '\0') {
+ cp++;
+ }
+
+ /* remainder should be just the IV */
+ if(strlen(cp) != (blockSizeInBytes * 2)) {
+ SecImpExpDbg("importWrappedKeyOpenssl: bad IV in DEK-Info line (1)");
+ return errSecUnknownFormat;
+ }
+
+ coder.allocItem(iv, blockSizeInBytes);
+ for(unsigned dex=0; dex<blockSizeInBytes; dex++) {
+ if(hexToUchar(cp + (dex * 2), &iv.Data[dex])) {
+ SecImpExpDbg("importWrappedKeyOpenssl: bad IV in DEK-Info line (2)");
+ return errSecUnknownFormat;
+ }
+ }
+ return errSecSuccess;
+}
+
+/*
+ * Common code to derive an openssl-wrap style wrap/unwrap key.
+ */
+static OSStatus deriveKeyOpensslWrap(
+ const SecKeyImportExportParameters *keyParams, // required
+ CSSM_CSP_HANDLE cspHand, // required
+ impExpVerifyPhrase vp, // import/export
+ CSSM_ALGORITHMS pbeAlg,
+ CSSM_ALGORITHMS keyAlg,
+ uint32 keySizeInBits,
+ const CSSM_DATA &salt,
+ CSSM_KEY_PTR derivedKey)
+{
+ CFDataRef cfPhrase = NULL;
+ CSSM_KEY *passKey = NULL;
+ OSStatus ortn;
+
+ /* passphrase or passkey? */
+ ortn = impExpPassphraseCommon(keyParams, cspHand, SPF_Data, vp,
+ (CFTypeRef *)&cfPhrase, &passKey);
+ if(ortn) {
+ return ortn;
+ }
+ /* subsequent errors to errOut: */
+
+ CSSM_CRYPTO_DATA seed;
+ CSSM_CC_HANDLE ccHand = 0;
+ CSSM_ACCESS_CREDENTIALS creds;
+ SecNssCoder coder;
+ CSSM_DATA param = {0, NULL};
+ CSSM_DATA dummyLabel;
+
+ memset(&seed, 0, sizeof(seed));
+ if(cfPhrase != NULL) {
+ size_t len = CFDataGetLength(cfPhrase);
+ coder.allocItem(seed.Param, len);
+ memmove(seed.Param.Data, CFDataGetBytePtr(cfPhrase), len);
+ CFRelease(cfPhrase);
+ }
+
+ memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
+ ortn = CSSM_CSP_CreateDeriveKeyContext(cspHand,
+ pbeAlg,
+ keyAlg,
+ keySizeInBits,
+ &creds,
+ passKey, // BaseKey
+ 1, // iterCount - yup, this is what openssl does
+ &salt,
+ &seed,
+ &ccHand);
+ if(ortn) {
+ SecImpExpDbg("deriveKeyOpensslWrap: CSSM_CSP_CreateDeriveKeyContext error");
+ goto errOut;
+ }
+
+ memset(derivedKey, 0, sizeof(CSSM_KEY));
+
+ dummyLabel.Data = (uint8 *)"temp unwrap key";
+ dummyLabel.Length = strlen((char *)dummyLabel.Data);
+
+ ortn = CSSM_DeriveKey(ccHand,
+ ¶m, // i.e., derived IV - don't want one
+ CSSM_KEYUSE_ANY,
+ /* not extractable even for the short time this key lives */
+ CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE,
+ &dummyLabel,
+ NULL, // cred and acl
+ derivedKey);
+ if(ortn) {
+ SecImpExpDbg("importWrappedKeyOpenssl: PKCS5 v1.5 CSSM_DeriveKey failure");
+ }
+
+errOut:
+ if(ccHand != 0) {
+ CSSM_DeleteContext(ccHand);
+ }
+ if(passKey != NULL) {
+ CSSM_FreeKey(cspHand, NULL, passKey, CSSM_FALSE);
+ free(passKey);
+ }
+ return ortn;
+}
+
+OSStatus SecImportRep::importWrappedKeyOpenssl(
+ SecKeychainRef importKeychain, // optional
+ CSSM_CSP_HANDLE cspHand, // required
+ SecItemImportExportFlags flags,
+ const SecKeyImportExportParameters *keyParams, // optional
+ CFMutableArrayRef outArray) // optional, append here
+{
+ assert(mExternFormat == kSecFormatWrappedOpenSSL);
+
+ /* I think this is an assert - only private keys are wrapped in opensssl format */
+ assert(mExternType == kSecItemTypePrivateKey);
+ assert(cspHand != 0);
+
+ if(keyParams == NULL) {
+ return errSecParam;
+ }
+
+ OSStatus ortn;
+ SecNssCoder coder;
+ impExpKeyUnwrapParams unwrapParams;
+ CSSM_ALGORITHMS pbeAlg = CSSM_ALGID_NONE;
+ CSSM_ALGORITHMS keyAlg = CSSM_ALGID_NONE;
+ uint32 keySizeInBits;
+ unsigned blockSizeInBytes;
+
+ memset(&unwrapParams, 0, sizeof(unwrapParams));
+
+ /* parse PEM header lines */
+ ortn = opensslPbeParams(mPemParamLines, coder,
+ pbeAlg, keyAlg,
+ unwrapParams.encrAlg,
+ unwrapParams.encrMode,
+ unwrapParams.encrPad,
+ keySizeInBits,
+ blockSizeInBytes,
+ unwrapParams.iv);
+ if(ortn) {
+ return ortn;
+ }
+
+ /* derive unwrapping key */
+ CSSM_KEY unwrappingKey;
+
+ ortn = deriveKeyOpensslWrap(keyParams, cspHand, VP_Import, pbeAlg, keyAlg,
+ keySizeInBits,
+ unwrapParams.iv, /* salt = IV for these algs */
+ &unwrappingKey);
+ if(ortn) {
+ return ortn;
+ }
+
+ /* set up key to unwrap */
+ CSSM_KEY wrappedKey;
+ CSSM_KEYHEADER &hdr = wrappedKey.KeyHeader;
+ memset(&wrappedKey, 0, sizeof(CSSM_KEY));
+ hdr.HeaderVersion = CSSM_KEYHEADER_VERSION;
+ /* CspId : don't care */
+ hdr.BlobType = CSSM_KEYBLOB_WRAPPED;
+ hdr.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL;
+ hdr.AlgorithmId = mKeyAlg;
+ hdr.KeyClass = CSSM_KEYCLASS_PRIVATE_KEY;
+ /* LogicalKeySizeInBits : calculated by CSP during unwrap */
+ hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE;
+ hdr.KeyUsage = CSSM_KEYUSE_ANY;
+
+ wrappedKey.KeyData.Data = (uint8 *)CFDataGetBytePtr(mExternal);
+ wrappedKey.KeyData.Length = CFDataGetLength(mExternal);
+
+ unwrapParams.unwrappingKey = &unwrappingKey;
+
+ /* GO */
+ ortn = impExpImportKeyCommon(&wrappedKey, importKeychain, cspHand,
+ flags, keyParams, &unwrapParams, NULL, outArray);
+
+ if(unwrappingKey.KeyData.Data != NULL) {
+ CSSM_FreeKey(cspHand, NULL, &unwrappingKey, CSSM_FALSE);
+ }
+ return ortn;
+}
+
+/*
+ * Hard coded parameters for export, we only do one flavor.
+ */
+#define OPENSSL_WRAP_KEY_ALG CSSM_ALGID_3DES_3KEY
+#define OPENSSL_WRAP_PBE_ALG CSSM_ALGID_PBE_OPENSSL_MD5
+#define OPENSSL_WRAP_KEY_SIZE (64 * 3)
+#define OPENSSL_WRAP_ENCR_ALG CSSM_ALGID_3DES_3KEY_EDE
+#define OPENSSL_WRAP_ENCR_MODE CSSM_ALGMODE_CBCPadIV8
+#define OPENSSL_WRAP_ENCR_PAD CSSM_PADDING_PKCS7
+
+OSStatus impExpWrappedKeyOpenSslExport(
+ SecKeyRef secKey,
+ SecItemImportExportFlags flags,
+ const SecKeyImportExportParameters *keyParams, // optional
+ CFMutableDataRef outData, // output appended here
+ const char **pemHeader, // RETURNED
+ CFArrayRef *pemParamLines) // RETURNED
+{
+ DevRandomGenerator rng;
+ SecNssCoder coder;
+ CSSM_CSP_HANDLE cspHand = 0;
+ OSStatus ortn;
+ bool releaseCspHand = false;
+ CFMutableArrayRef paramLines;
+ CFStringRef cfStr;
+ char dekStr[100];
+ char ivStr[3];
+
+ if(keyParams == NULL) {
+ return errSecParam;
+ }
+
+ /* we need a CSPDL handle - try to get it from the key */
+ ortn = SecKeyGetCSPHandle(secKey, &cspHand);
+ if(ortn) {
+ cspHand = cuCspStartup(CSSM_FALSE);
+ if(cspHand == 0) {
+ return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
+ }
+ releaseCspHand = true;
+ }
+ /* subsequent errors to errOut: */
+
+ /* 8 bytes of random IV/salt */
+ uint8 saltIv[8];
+ CSSM_DATA saltIvData = { 8, saltIv} ;
+ rng.random(saltIv, 8);
+
+ /* derive wrapping key */
+ CSSM_KEY wrappingKey;
+ wrappingKey.KeyData.Data = NULL;
+ wrappingKey.KeyData.Length = 0;
+ ortn = deriveKeyOpensslWrap(keyParams, cspHand, VP_Export,
+ OPENSSL_WRAP_PBE_ALG, OPENSSL_WRAP_KEY_ALG,
+ OPENSSL_WRAP_KEY_SIZE,
+ saltIvData, // IV == salt for this wrapping alg
+ &wrappingKey);
+ if(ortn) {
+ goto errOut;
+ }
+
+ /* wrap the outgoing key */
+ CSSM_KEY wrappedKey;
+ memset(&wrappedKey, 0, sizeof(CSSM_KEY));
+
+ ortn = impExpExportKeyCommon(cspHand, secKey, &wrappingKey, &wrappedKey,
+ OPENSSL_WRAP_ENCR_ALG, OPENSSL_WRAP_ENCR_MODE, OPENSSL_WRAP_ENCR_PAD,
+ CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL,
+ CSSM_ATTRIBUTE_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE,
+ NULL, &saltIvData);
+ if(ortn) {
+ goto errOut;
+ }
+
+ /*
+ * That wrapped key's KeyData is our output
+ */
+ CFDataAppendBytes(outData, wrappedKey.KeyData.Data, wrappedKey.KeyData.Length);
+
+ /* PEM header depends on key algorithm */
+ switch(wrappedKey.KeyHeader.AlgorithmId) {
+ case CSSM_ALGID_RSA:
+ *pemHeader = PEM_STRING_RSA;
+ break;
+ case CSSM_ALGID_DH:
+ *pemHeader = PEM_STRING_DH_PRIVATE;
+ break;
+ case CSSM_ALGID_DSA:
+ *pemHeader = PEM_STRING_DSA;
+ break;
+ case CSSM_ALGID_ECDSA:
+ *pemHeader = PEM_STRING_ECDSA_PRIVATE;
+ break;
+ default:
+ SecImpExpDbg("impExpWrappedKeyOpenSslExport unknown private key alg "
+ "%lu", (unsigned long)wrappedKey.KeyHeader.AlgorithmId);
+ /* punt though I think something is seriously hosed */
+ *pemHeader = "Private Key";
+ }
+ CSSM_FreeKey(cspHand, NULL, &wrappedKey, CSSM_FALSE);
+
+ /*
+ * Last thing: set up outgoing PEM parameter lines
+ */
+ assert(pemParamLines != NULL);
+ paramLines = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ cfStr = CFStringCreateWithCString(NULL,
+ "Proc-Type: 4,ENCRYPTED", kCFStringEncodingASCII);
+ CFArrayAppendValue(paramLines, cfStr);
+ CFRelease(cfStr); // owned by array now */
+ strcpy(dekStr, "DEK-Info: DES-EDE3-CBC,");
+ /* next goes the IV */
+ for(unsigned dex=0; dex<8; dex++) {
+ sprintf(ivStr, "%02X", saltIv[dex]);
+ strcat(dekStr, ivStr);
+ }
+ cfStr = CFStringCreateWithCString(NULL, dekStr, kCFStringEncodingASCII);
+ CFArrayAppendValue(paramLines, cfStr);
+ CFRelease(cfStr); // owned by array now */
+ /* and an empty line */
+ cfStr = CFStringCreateWithCString(NULL, "", kCFStringEncodingASCII);
+ CFArrayAppendValue(paramLines, cfStr);
+ CFRelease(cfStr); // owned by array now */
+ *pemParamLines = paramLines;
+
+errOut:
+ if(wrappingKey.KeyData.Data != NULL) {
+ CSSM_FreeKey(cspHand, NULL, &wrappingKey, CSSM_FALSE);
+ }
+ return ortn;
+
+}
+