--- /dev/null
+/*
+ * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
+ *
+ * The contents of this file constitute Original Code as defined in and are
+ * subject to the Apple Public Source License Version 1.2 (the 'License').
+ * You may not use this file except in compliance with the License. Please obtain
+ * a copy of the License at http://www.apple.com/publicsource and read it before
+ * using this file.
+ *
+ * This 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.
+ */
+
+
+/*
+ File: deriveKey.cpp
+
+ Contains: CSSM_DeriveKey functions
+
+ Copyright: (C) 2000 by Apple Computer, Inc., all rights reserved
+
+ Written by: Doug Mitchell <dmitch@apple.com>
+*/
+
+#include <HMACSHA1.h>
+#include <pbkdf2.h>
+#include <pbkdDigest.h>
+#include <pkcs12Derive.h>
+#include "AppleCSPSession.h"
+#include "AppleCSPUtils.h"
+#include "AppleCSPContext.h"
+#include "cspdebugging.h"
+#include <security_cdsa_utilities/context.h>
+#include <DH_exchange.h>
+#include "FEEAsymmetricContext.h"
+
+/* minimum legal values */
+#define PBKDF2_MIN_SALT 8 /* bytes */
+#define PBKDF2_MIN_ITER_CNT 1000 /* iteration count */
+
+#define ALLOW_ZERO_PASSWORD 1
+
+void AppleCSPSession::DeriveKey_PBKDF2(
+ const Context &context,
+ const CssmData &Param,
+ CSSM_DATA *keyData)
+{
+ /* validate algorithm-specific arguments */
+
+ /* Param must point to a CSSM_PKCS5_PBKDF2_PARAMS */
+ if(Param.Length != sizeof(CSSM_PKCS5_PBKDF2_PARAMS)) {
+ errorLog0("DeriveKey_PBKDF2: Param wrong size\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER);
+ }
+ const CSSM_PKCS5_PBKDF2_PARAMS *pbkdf2Params =
+ reinterpret_cast<const CSSM_PKCS5_PBKDF2_PARAMS *>(Param.Data);
+ if(pbkdf2Params == NULL) {
+ errorLog0("DeriveKey_PBKDF2: null Param.Data\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_DATA);
+ }
+
+ /* Get passphrase from either baseKey or from CSSM_PKCS5_PBKDF2_PARAMS */
+ CssmKey *passKey = context.get<CssmKey>(CSSM_ATTRIBUTE_KEY);
+ CSSM_SIZE passphraseLen = 0;
+ uint8 *passphrase = NULL;
+ if(passKey != NULL) {
+ AppleCSPContext::symmetricKeyBits(context, *this,
+ CSSM_ALGID_SECURE_PASSPHRASE, CSSM_KEYUSE_DERIVE,
+ passphrase, passphraseLen);
+ }
+ else {
+ passphraseLen = pbkdf2Params->Passphrase.Length;
+ passphrase = pbkdf2Params->Passphrase.Data;
+ }
+
+ #if !ALLOW_ZERO_PASSWORD
+ /* passphrase required */
+ if(passphrase == NULL) {
+ errorLog0("DeriveKey_PBKDF2: null Passphrase\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_DATA);
+ }
+ if(passphraseLen == 0) {
+ /* FIXME - enforce minimum length? */
+ errorLog0("DeriveKey_PBKDF2: zero length passphrase\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER);
+ }
+ #endif /* ALLOW_ZERO_PASSWORD */
+
+ if(pbkdf2Params->PseudoRandomFunction !=
+ CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1) {
+ errorLog0("DeriveKey_PBKDF2: invalid PRF\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
+ }
+
+ /* salt, from context, required */
+ CssmData salt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT,
+ CSSMERR_CSP_MISSING_ATTR_SALT);
+ if((salt.Data == NULL) || (salt.Length < PBKDF2_MIN_SALT)){
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_SALT);
+ }
+
+ /* iteration count, from context, required */
+ uint32 iterCount = context.getInt(CSSM_ATTRIBUTE_ITERATION_COUNT,
+ CSSMERR_CSP_MISSING_ATTR_ITERATION_COUNT);
+ if(iterCount < PBKDF2_MIN_ITER_CNT) {
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_ITERATION_COUNT);
+ }
+
+ /*
+ * allocate a temp buffer, length
+ * = MAX (hLen, saltLen + 4) + 2 * hLen
+ * = MAX (kSHA1DigestSize, saltLen + 4) + 2 * kSHA1DigestSize
+ */
+ uint32 tempLen = salt.Length + 4;
+ if(tempLen < kSHA1DigestSize) {
+ tempLen = kSHA1DigestSize;
+ }
+ tempLen += (2 * kSHA1DigestSize);
+ CSSM_DATA tempData = {0, NULL};
+ setUpData(tempData, tempLen, privAllocator);
+
+ /* go */
+ pbkdf2 (hmacsha1,
+ kSHA1DigestSize,
+ passphrase, passphraseLen,
+ salt.Data, salt.Length,
+ iterCount,
+ keyData->Data, keyData->Length,
+ tempData.Data);
+ freeData(&tempData, privAllocator, false);
+}
+
+/*
+ * PKCS5 v1.5 key derivation. Also used for traditional openssl key
+ * derivation, which is mighty similar to PKCS5 v1.5, with the addition
+ * of the ability to generate more than (keysize + ivsize) bytes.
+ */
+void AppleCSPSession::DeriveKey_PKCS5_V1_5(
+ const Context &context,
+ CSSM_ALGORITHMS algId,
+ const CssmData &Param, // IV optional, mallocd by app to indicate
+ // size
+ CSSM_DATA *keyData) // mallocd by caller to indicate size
+{
+ CSSM_DATA pwd = {0, NULL};
+
+ /* password from either Seed.Param or from base key */
+ CssmCryptoData *cryptData =
+ context.get<CssmCryptoData>(CSSM_ATTRIBUTE_SEED);
+ if((cryptData != NULL) && (cryptData->Param.Length != 0)) {
+ pwd = cryptData->Param;
+ }
+ else {
+ /* Get secure passphrase from base key */
+ CssmKey *passKey = context.get<CssmKey>(CSSM_ATTRIBUTE_KEY);
+ if (passKey != NULL) {
+ AppleCSPContext::symmetricKeyBits(context, *this,
+ CSSM_ALGID_SECURE_PASSPHRASE, CSSM_KEYUSE_DERIVE,
+ pwd.Data, pwd.Length);
+ }
+ }
+
+ if(pwd.Data == NULL) {
+ errorLog0("DeriveKey_PKCS5_V1_5: null Passphrase\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_DATA);
+ }
+ if(pwd.Length == 0) {
+ errorLog0("DeriveKey_PKCS5_V1_5: zero length passphrase\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER);
+ }
+
+ CSSM_ALGORITHMS hashAlg;
+ unsigned digestLen;
+ bool opensslAlg = false;
+ switch(algId) {
+ case CSSM_ALGID_PKCS5_PBKDF1_MD5:
+ hashAlg = CSSM_ALGID_MD5;
+ digestLen = kMD5DigestSize;
+ break;
+ case CSSM_ALGID_PKCS5_PBKDF1_MD2:
+ hashAlg = CSSM_ALGID_MD2;
+ digestLen = kMD2DigestSize;
+ break;
+ case CSSM_ALGID_PKCS5_PBKDF1_SHA1:
+ hashAlg = CSSM_ALGID_SHA1;
+ digestLen = kSHA1DigestSize;
+ break;
+ case CSSM_ALGID_PBE_OPENSSL_MD5:
+ hashAlg = CSSM_ALGID_MD5;
+ digestLen = kMD5DigestSize;
+ opensslAlg = true;
+ break;
+ default:
+ /* should not have been called */
+ assert(0);
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
+ }
+
+ /* IV optional */
+ CSSM_DATA iv = Param;
+
+ /* total requested length can't exceed digest size for struct PKCS5 v1.5*/
+ if(!opensslAlg && ((keyData->Length + iv.Length) > digestLen)) {
+ errorLog0("DeriveKey_PKCS5_V1_5: requested length larger than digest\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY_LENGTH);
+ }
+
+ /* salt, from context, required */
+ CssmData salt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT,
+ CSSMERR_CSP_MISSING_ATTR_SALT);
+ if(salt.Data == NULL) {
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_SALT);
+ }
+
+ /* iteration count, from context, required */
+ uint32 iterCount = context.getInt(CSSM_ATTRIBUTE_ITERATION_COUNT,
+ CSSMERR_CSP_MISSING_ATTR_ITERATION_COUNT);
+
+ /*
+ * Apply the underlying hash function Hash for c iterations to
+ * the concatenation of the password P and the salt S, then
+ * extract the first dkLen octets to produce a derived key DK:
+ *
+ * T1 = Hash (P || S) ,
+ * T2 = Hash (T1) ,
+ * ...
+ * Tc = Hash (Tc-1) ,
+ * DK = Tc<0..dkLen-1> .
+ */
+ DigestCtx ctx;
+ uint8 *keyDataP = keyData->Data;
+ uint32 keyBytesToGo = keyData->Length;
+ uint8 *ivDataP = iv.Data;
+ uint32 ivBytesToGo = iv.Length;
+ bool looping = false; // true for additional bytes for openssl
+ unsigned char digestOut[kMaxDigestSize];
+
+ for(;;) {
+ /* this loop guaranteed to only run once if !opensslAlg */
+ DigestCtxInit(&ctx, hashAlg);
+
+ if(looping) {
+ /* openssl addition: re-digest the digest here */
+ DigestCtxUpdate(&ctx, digestOut, digestLen);
+ }
+
+ /* digest password then salt */
+ DigestCtxUpdate(&ctx, pwd.Data, pwd.Length);
+ DigestCtxUpdate(&ctx, salt.Data, salt.Length);
+
+ DigestCtxFinal(&ctx, digestOut);
+
+ /* now iterCount-1 more iterations */
+ for(unsigned dex=1; dex<iterCount; dex++) {
+ DigestCtxInit(&ctx, hashAlg);
+ DigestCtxUpdate(&ctx, digestOut, digestLen);
+ DigestCtxFinal(&ctx, digestOut);
+ }
+
+ /* first n bytes to the key */
+ uint32 bytesAvail = digestLen;
+ uint32 toMove = (keyBytesToGo > bytesAvail) ? bytesAvail : keyBytesToGo;
+ memmove(keyDataP, digestOut, toMove);
+ uint8 *remainder = digestOut + toMove;
+ bytesAvail -= toMove;
+ keyDataP += toMove;
+ keyBytesToGo -= toMove;
+
+ /* then optionally some to IV */
+ if(ivBytesToGo && bytesAvail) {
+ toMove = (ivBytesToGo > bytesAvail) ? bytesAvail : ivBytesToGo;
+ memmove(ivDataP, remainder, toMove);
+ ivDataP += toMove;
+ ivBytesToGo -= toMove;
+ }
+ if((keyBytesToGo == 0) && (ivBytesToGo == 0)) {
+ /* guaranteed true for PKCS5 v1.5 */
+ break;
+ }
+
+ assert(opensslAlg == true);
+ looping = true;
+ }
+ DigestCtxFree(&ctx);
+}
+
+/*
+ * Member function initially declared for CSPAbstractPluginSession;
+ * we're overriding the null version in CSPFullPluginSession.
+ *
+ * We'll generate any type of key (for now).
+ */
+void AppleCSPSession::DeriveKey(
+ CSSM_CC_HANDLE CCHandle,
+ const Context &context,
+ CssmData &Param,
+ uint32 KeyUsage,
+ uint32 KeyAttr,
+ const CssmData *KeyLabel,
+ const CSSM_RESOURCE_CONTROL_CONTEXT *CredAndAclEntry,
+ CssmKey &DerivedKey)
+{
+ /* validate input args, common to all algorithms */
+ switch(context.algorithm()) {
+ case CSSM_ALGID_PKCS5_PBKDF2:
+ case CSSM_ALGID_DH:
+ case CSSM_ALGID_PKCS12_PBE_ENCR:
+ case CSSM_ALGID_PKCS12_PBE_MAC:
+ case CSSM_ALGID_PKCS5_PBKDF1_MD5:
+ case CSSM_ALGID_PKCS5_PBKDF1_MD2:
+ case CSSM_ALGID_PKCS5_PBKDF1_SHA1:
+ case CSSM_ALGID_PBE_OPENSSL_MD5:
+ case CSSM_ALGID_OPENSSH1:
+ #if CRYPTKIT_CSP_ENABLE
+ case CSSM_ALGID_ECDH:
+ case CSSM_ALGID_ECDH_X963_KDF:
+ #endif
+ break;
+ /* maybe more here, later */
+ default:
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
+ }
+ DerivedKey.KeyData.Data = NULL;
+ DerivedKey.KeyData.Length = 0;
+ cspKeyStorage keyStorage = cspParseKeyAttr(CKT_Session, KeyAttr);
+ cspValidateKeyUsageBits(CKT_Session, KeyUsage);
+
+ /* outgoing key type, required (though any algorithm is OK) */
+ uint32 keyType = context.getInt(CSSM_ATTRIBUTE_KEY_TYPE,
+ CSSMERR_CSP_MISSING_ATTR_KEY_TYPE);
+
+ /* outgoing key size, required - any nonzero value is OK */
+ uint32 reqKeySize = context.getInt(
+ CSSM_ATTRIBUTE_KEY_LENGTH,
+ CSSMERR_CSP_MISSING_ATTR_KEY_LENGTH);
+
+ /* cook up a place to put the key data */
+ uint32 keySizeInBytes = (reqKeySize + 7) / 8;
+ SymmetricBinaryKey *binKey = NULL;
+ CSSM_DATA_PTR keyData = NULL;
+
+ switch(keyStorage) {
+ case CKS_None:
+ /* no way */
+ CssmError::throwMe(CSSMERR_CSP_INVALID_KEYATTR_MASK);
+ case CKS_Ref:
+ /* cook up a symmetric binary key */
+ binKey = new SymmetricBinaryKey(reqKeySize);
+ keyData = &binKey->mKeyData;
+ break;
+ case CKS_Data:
+ /* key bytes --> caller's cssmKey */
+ keyData = &DerivedKey.KeyData;
+ setUpData(*keyData, keySizeInBytes,
+ normAllocator);
+ break;
+ }
+
+ /* break off to algorithm-specific code, whose job it is
+ * to fill in keyData->Data with keyData->Length bytes */
+ switch(context.algorithm()) {
+ case CSSM_ALGID_PKCS5_PBKDF2:
+ DeriveKey_PBKDF2(context,
+ Param,
+ keyData);
+ break;
+ case CSSM_ALGID_DH:
+ DeriveKey_DH(context,
+ Param,
+ keyData,
+ *this);
+ break;
+ case CSSM_ALGID_PKCS12_PBE_ENCR:
+ case CSSM_ALGID_PKCS12_PBE_MAC:
+ DeriveKey_PKCS12(context,
+ *this,
+ Param,
+ keyData);
+ break;
+ case CSSM_ALGID_PKCS5_PBKDF1_MD5:
+ case CSSM_ALGID_PKCS5_PBKDF1_MD2:
+ case CSSM_ALGID_PKCS5_PBKDF1_SHA1:
+ case CSSM_ALGID_PBE_OPENSSL_MD5:
+ DeriveKey_PKCS5_V1_5(context,
+ context.algorithm(),
+ Param,
+ keyData);
+ break;
+ case CSSM_ALGID_OPENSSH1:
+ DeriveKey_OpenSSH1(context,
+ context.algorithm(),
+ Param,
+ keyData);
+ break;
+ #if CRYPTKIT_CSP_ENABLE
+ case CSSM_ALGID_ECDH:
+ case CSSM_ALGID_ECDH_X963_KDF:
+ CryptKit::DeriveKey_ECDH(context,
+ context.algorithm(),
+ Param,
+ keyData,
+ *this);
+ break;
+ #endif
+ /* maybe more here, later */
+ default:
+ assert(0);
+ }
+
+ /* set up outgoing header */
+ KeyAttr &= ~KEY_ATTR_RETURN_MASK;
+ CSSM_KEYHEADER &hdr = DerivedKey.KeyHeader;
+ setKeyHeader(hdr,
+ plugin.myGuid(),
+ keyType,
+ CSSM_KEYCLASS_SESSION_KEY,
+ KeyAttr,
+ KeyUsage);
+ /* handle derived size < requested size, legal for Diffie-Hellman */
+ hdr.LogicalKeySizeInBits = keyData->Length * 8;
+
+ if(keyStorage == CKS_Ref) {
+ /* store and convert to ref key */
+ addRefKey(*binKey, DerivedKey);
+ }
+ else {
+ /* Raw data */
+ hdr.BlobType = CSSM_KEYBLOB_RAW;
+ hdr.Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
+ }
+}
+