--- /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.
+ */
+
+
+/*
+ * FEEAsymmetricContext.cpp - CSPContexts for FEE asymmetric encryption
+ *
+ * Created March 8 2001 by dmitch.
+ */
+
+#ifdef CRYPTKIT_CSP_ENABLE
+
+#include "FEEAsymmetricContext.h"
+#include "FEECSPUtils.h"
+#include <security_cryptkit/falloc.h>
+#include <CommonCrypto/CommonDigest.h>
+
+/* validate context for FEED and FEEDExp - no unexpected attributes allowed */
+static void validateFeedContext(
+ const Context &context)
+{
+ /* Note we cannot distinguish between zero and "not there" */
+ uint32 blockSize = context.getInt(CSSM_ATTRIBUTE_BLOCK_SIZE);
+ if(blockSize != 0) {
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_BLOCK_SIZE);
+ }
+ CSSM_ENCRYPT_MODE cssmMode = context.getInt(CSSM_ATTRIBUTE_MODE);
+ if(cssmMode != 0) {
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_MODE);
+ }
+ #if 0
+ /* we allow this for CMS wrapping */
+ CssmData *iv = context.get<CssmData>(CSSM_ATTRIBUTE_INIT_VECTOR);
+ if(iv != NULL) {
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_INIT_VECTOR);
+ }
+ #endif
+ CSSM_PADDING padding = context.getInt(CSSM_ATTRIBUTE_PADDING);
+ if(padding != 0) {
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_PADDING);
+ }
+}
+
+/***
+ *** FEED - 1:1 FEED - encrypt n bytes of plaintext, get (roughly) n bytes
+ *** of ciphertext. Ciphertext is smaller than with FEED, but this is slower.
+ ***/
+CryptKit::FEEDContext::~FEEDContext()
+{
+ if(mFeeFeed) {
+ feeFEEDFree(mFeeFeed);
+ mFeeFeed = NULL;
+ }
+ if(mPrivKey && mAllocdPrivKey) {
+ feePubKeyFree(mPrivKey);
+ }
+ if(mPubKey && mAllocdPubKey) {
+ feePubKeyFree(mPubKey);
+ }
+ mPrivKey = NULL;
+ mPubKey = NULL;
+ mInitFlag = false;
+}
+
+// called by CSPFullPluginSession; reusable
+void CryptKit::FEEDContext::init(
+ const Context &context,
+ bool encoding)
+{
+ if(mInitFlag && !opStarted()) {
+ /* reusing - e.g. query followed by encrypt */
+ return;
+ }
+
+ /*
+ * Fetch FEE keys from context. This is an unusual algorithm - it requires
+ * two keys, one public and one private. The public key MUST be stored in
+ * the context with attribute type CSSM_ATTRIBUTE_PUBLIC_KEY, and the private
+ * key with CSSM_ATTRIBUTE_KEY.
+ *
+ * For now, we require CSSM_KEYUSE_ANY for FEE keys used for this algorithm.
+ * Otherwise we'd have to allow both KEYUSE_ENCRYPT and KEYUSE_DECRYPT for
+ * both keys, and that would require some algorithm-specific hack in
+ * cspValidateKeyUsageBits() which I really don't want to do.
+ */
+ if(mPrivKey == NULL) {
+ assert(!opStarted());
+ mPrivKey = contextToFeeKey(context,
+ session(),
+ CSSM_ATTRIBUTE_KEY,
+ CSSM_KEYCLASS_PRIVATE_KEY,
+ CSSM_KEYUSE_ANY,
+ mAllocdPrivKey);
+ }
+ else {
+ assert(opStarted());
+ }
+ if(mPubKey == NULL) {
+ assert(!opStarted());
+ mPubKey = contextToFeeKey(context,
+ session(),
+ CSSM_ATTRIBUTE_PUBLIC_KEY,
+ CSSM_KEYCLASS_PUBLIC_KEY,
+ CSSM_KEYUSE_ANY,
+ mAllocdPubKey);
+ }
+ else {
+ assert(opStarted());
+ }
+
+ /* validate context - no other attributes allowed */
+ validateFeedContext(context);
+
+ if(mFeeFeed != NULL) {
+ /* not reusable */
+ assert(opStarted());
+ feeFEEDFree(mFeeFeed);
+ mFeeFeed = NULL;
+ }
+
+ /* OK, looks good. Cook up a feeFEED object. */
+ mFeeFeed = feeFEEDNewWithPubKey(mPrivKey,
+ mPubKey,
+ encoding ? 1 : 0,
+ feeRandCallback,
+ &session());
+ if(mFeeFeed == NULL) {
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY);
+ }
+
+ /* finally, have BlockCryptor set up its stuff. */
+ unsigned plainBlockSize = feeFEEDPlainBlockSize(mFeeFeed);
+ unsigned cipherBlockSize = feeFEEDCipherBlockSize(mFeeFeed);
+ setup(encoding ? plainBlockSize : cipherBlockSize, // blockSizeIn
+ encoding ? cipherBlockSize : plainBlockSize, // blockSizeOut
+ false, // pkcsPad
+ true, // needsFinal
+ BCM_ECB,
+ NULL); // IV
+ mInitFlag = true;
+}
+
+// called by BlockCryptor
+void CryptKit::FEEDContext::encryptBlock(
+ const void *plainText, // length implied (one block)
+ size_t plainTextLen,
+ void *cipherText,
+ size_t &cipherTextLen, // in/out, throws on overflow
+ bool final)
+{
+ feeReturn frtn;
+ unsigned actMoved;
+
+ assert(mFeeFeed != NULL);
+ frtn = feeFEEDEncryptBlock(mFeeFeed,
+ (unsigned char *)plainText,
+ plainTextLen,
+ (unsigned char *)cipherText,
+ &actMoved,
+ final ? 1 : 0);
+ if(frtn) {
+ throwCryptKit(frtn, "feeFEEDEncryptBlock");
+ }
+ if(actMoved > cipherTextLen) {
+ /* Overflow already occurred! */
+ CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
+ }
+ cipherTextLen = actMoved;
+}
+
+void CryptKit::FEEDContext::decryptBlock(
+ const void *cipherText, // length implied (one cipher block)
+ size_t cipherTextLen,
+ void *plainText,
+ size_t &plainTextLen, // in/out, throws on overflow
+ bool final)
+{
+ feeReturn frtn;
+ unsigned actMoved;
+
+ assert(mFeeFeed != NULL);
+ frtn = feeFEEDDecryptBlock(mFeeFeed,
+ (unsigned char *)cipherText,
+ inBlockSize(),
+ (unsigned char *)plainText,
+ &actMoved,
+ final ? 1 : 0);
+ if(frtn) {
+ throwCryptKit(frtn, "feeFEEDDecryptBlock");
+ }
+ if(actMoved > plainTextLen) {
+ /* Overflow already occurred! */
+ CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
+ }
+ plainTextLen = actMoved;
+}
+
+/*
+ * Additional query size support, necessary because we don't conform to
+ * BlockCryptor's standard one-to-one block scheme
+ */
+
+#define BUFFER_DEBUG 0
+#if BUFFER_DEBUG
+#define bprintf(s) printf s
+#else
+#define bprintf(s)
+#endif
+
+size_t CryptKit::FEEDContext::inputSize(
+ size_t outSize) // input for given output size
+{
+ /*
+ * We've been assured that this is NOT called for the final() op...
+ */
+ unsigned inSize;
+ if(encoding()) {
+ inSize = feeFEEDPlainTextSize(mFeeFeed, outSize, 0);
+ }
+ else {
+ inSize = feeFEEDCipherTextSize(mFeeFeed, outSize, 0);
+ }
+
+ /* account for possible pending buffered input */
+ if(inSize >= inBufSize()) {
+ inSize -= inBufSize();
+ }
+
+ /* round up to next block size, then lop off one...anything from
+ * blockSize*n to (blockSize*n)-1 has same effect */
+ unsigned inBlocks = ((inSize + inBlockSize()) / inBlockSize());
+ inSize = (inBlocks * inBlockSize()) - 1;
+ bprintf(("--- FEEDContext::inputSize inSize 0x%x outSize 0x%x\n",
+ inSize, outSize));
+ return inSize;
+}
+
+size_t CryptKit::FEEDContext::outputSize(
+ bool final,
+ size_t inSize) // output for given input size
+{
+ size_t rtn;
+ if(encoding()) {
+ rtn = feeFEEDCipherTextSize(mFeeFeed, inSize + inBufSize(), final ? 1 : 0);
+ }
+ else {
+ rtn = feeFEEDPlainTextSize(mFeeFeed, inSize + inBufSize(), final ? 1 : 0);
+ }
+ bprintf(("--- FEEDContext::outputSize inSize 0x%x outSize 0x%x final %d\n",
+ inSize, rtn, final));
+ return rtn;
+}
+
+void CryptKit::FEEDContext::minimumProgress(
+ size_t &in,
+ size_t &out) // minimum progress chunks
+{
+ if(encoding()) {
+ /*
+ * -- in := one block plaintext
+ * -- out := current cipher size for one block plaintext
+ */
+ in = inBlockSize();
+ out = feeFEEDCipherBufSize(mFeeFeed, 0);
+ }
+ else {
+ /*
+ * -- in := current cipher size for one block plaintext
+ * -- out := one block plaintext
+ */
+ in = feeFEEDCipherBufSize(mFeeFeed, 0);
+ out = outBlockSize();
+ }
+
+ /*
+ * Either case - input adjusted for pending. Note inBufSize can be up to one
+ * input block size, leaving the temp result zero here....
+ */
+ assert(in >= inBufSize());
+ in -= inBufSize();
+
+ /* if it is zero, bump it up so caller can make something happen */
+ if(in == 0) {
+ in++;
+ }
+ bprintf(("--- FEEDContext::minProgres inSize 0x%x outSize 0x%x\n",
+ in, out));
+}
+
+/***
+ *** FEEDExp - 2:1 FEED - encrypt n bytes of plaintext, get (roughly) 2n bytes
+ *** of ciphertext. Ciphertext is larger than with FEED, but this is faster.
+ ***/
+CryptKit::FEEDExpContext::~FEEDExpContext()
+{
+ if(mFeeFeedExp) {
+ feeFEEDExpFree(mFeeFeedExp);
+ mFeeFeedExp = NULL;
+ }
+ if(mFeeKey && mAllocdFeeKey) {
+ feePubKeyFree(mFeeKey);
+ }
+ mFeeKey = NULL;
+ mInitFlag = false;
+}
+
+// called by CSPFullPluginSession; reusable
+void CryptKit::FEEDExpContext::init(
+ const Context &context,
+ bool encoding)
+{
+ if(mInitFlag && !opStarted()) {
+ /* reusing - e.g. query followed by encrypt */
+ return;
+ }
+
+ /* fetch FEE key from context */
+ CSSM_KEYCLASS keyClass;
+ CSSM_KEYUSE keyUse;
+
+ if(encoding) {
+ /* encrypting to public key */
+ keyClass = CSSM_KEYCLASS_PUBLIC_KEY;
+ keyUse = CSSM_KEYUSE_ENCRYPT;
+ }
+ else {
+ /* decrypting with private key */
+ keyClass = CSSM_KEYCLASS_PRIVATE_KEY;
+ keyUse = CSSM_KEYUSE_DECRYPT;
+ }
+ if(mFeeKey == NULL) {
+ assert(!opStarted());
+ mFeeKey = contextToFeeKey(context,
+ session(),
+ CSSM_ATTRIBUTE_KEY,
+ keyClass,
+ keyUse,
+ mAllocdFeeKey);
+ }
+ else {
+ assert(opStarted());
+ }
+
+ /* validate context - no other attributes allowed */
+ validateFeedContext(context);
+
+ /* OK, looks good. Cook up a feeFEEDExp object. */
+ if(mFeeFeedExp != NULL) {
+ /* not reusable */
+ assert(opStarted());
+ feeFEEDExpFree(mFeeFeedExp);
+ mFeeFeedExp = NULL;
+ }
+ mFeeFeedExp = feeFEEDExpNewWithPubKey(mFeeKey,
+ feeRandCallback,
+ &session());
+ if(mFeeFeedExp == NULL) {
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY);
+ }
+
+ /* finally, have BlockCryptor set up its stuff. */
+ unsigned plainBlockSize = feeFEEDExpPlainBlockSize(mFeeFeedExp);
+ unsigned cipherBlockSize = feeFEEDExpCipherBlockSize(mFeeFeedExp);
+ setup(encoding ? plainBlockSize : cipherBlockSize, // blockSizeIn
+ encoding ? cipherBlockSize : plainBlockSize, // blockSizeOut
+ false, // pkcs5Pad
+ true, // needsFinal
+ BCM_ECB,
+ NULL); // IV
+ mInitFlag = true;
+}
+
+// called by BlockCryptor
+void CryptKit::FEEDExpContext::encryptBlock(
+ const void *plainText, // length implied (one block)
+ size_t plainTextLen,
+ void *cipherText,
+ size_t &cipherTextLen, // in/out, throws on overflow
+ bool final)
+{
+ feeReturn frtn;
+ unsigned actMoved;
+
+ assert(mFeeFeedExp != NULL);
+ frtn = feeFEEDExpEncryptBlock(mFeeFeedExp,
+ (unsigned char *)plainText,
+ plainTextLen,
+ (unsigned char *)cipherText,
+ &actMoved,
+ final ? 1 : 0);
+ if(frtn) {
+ throwCryptKit(frtn, "feeFEEDExpEncryptBlock");
+ }
+ if(actMoved > cipherTextLen) {
+ /* Overflow already occurred! */
+ CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
+ }
+ cipherTextLen = actMoved;
+}
+
+void CryptKit::FEEDExpContext::decryptBlock(
+ const void *cipherText, // length implied (one cipher block)
+ size_t cipherTextLen,
+ void *plainText,
+ size_t &plainTextLen, // in/out, throws on overflow
+ bool final)
+{
+ feeReturn frtn;
+ unsigned actMoved;
+
+ assert(mFeeFeedExp != NULL);
+ frtn = feeFEEDExpDecryptBlock(mFeeFeedExp,
+ (unsigned char *)cipherText,
+ inBlockSize(),
+ (unsigned char *)plainText,
+ &actMoved,
+ final ? 1 : 0);
+ if(frtn) {
+ throwCryptKit(frtn, "feeFEEDExpDecryptBlock");
+ }
+ if(actMoved > plainTextLen) {
+ /* Overflow already occurred! */
+ CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
+ }
+ plainTextLen = actMoved;
+}
+
+/* convert uint32 to big-endian 4 bytes */
+static void int32ToBytes(
+ uint32_t i,
+ unsigned char *b)
+{
+ for(int dex=3; dex>=0; dex--) {
+ b[dex] = i;
+ i >>= 8;
+ }
+}
+
+/*
+ * X9.63 key derivation with optional SharedInfo passed as
+ * context attribute CSSM_ATTRIBUTE_SALT.
+ */
+static feeReturn ecdhKdf(
+ const Context &context,
+ const unsigned char *Z, /* shared secret, i.e., output of ECDH */
+ unsigned ZLen,
+ CSSM_DATA *K) /* output RETURNED in K->Data, length K->Length bytes */
+{
+ /* SharedInfo via salt, from context, optional */
+ const unsigned char *sharedInfo = NULL;
+ CSSM_SIZE sharedInfoLen = 0;
+
+ CssmData *salt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT);
+ if(salt != NULL) {
+ sharedInfo = (const unsigned char *)salt->Data;
+ sharedInfoLen = salt->Length;
+ }
+
+ unsigned char *outp = K->Data;
+ CSSM_SIZE bytesToGo = K->Length;
+ CC_SHA1_CTX sha1;
+ uint32_t counter = 1;
+ uint8 counterBytes[4];
+ unsigned char digOut[CC_SHA1_DIGEST_LENGTH];
+
+ do {
+ /* K[i] = Hash(Z || Counter || SharedInfo) */
+ CC_SHA1_Init(&sha1);
+ CC_SHA1_Update(&sha1, Z, ZLen);
+ int32ToBytes(counter, counterBytes);
+ CC_SHA1_Update(&sha1, counterBytes, 4);
+ if(sharedInfoLen) {
+ CC_SHA1_Update(&sha1, sharedInfo, sharedInfoLen);
+ }
+ CC_SHA1_Final(digOut, &sha1);
+
+ /* digest --> output */
+ unsigned toMove = CC_SHA1_DIGEST_LENGTH;
+ if(toMove > bytesToGo) {
+ toMove = bytesToGo;
+ }
+ memmove(outp, digOut, toMove);
+
+ counter++;
+ outp += toMove;
+ bytesToGo -= toMove;
+
+ } while(bytesToGo);
+
+ return FR_Success;
+}
+
+/*
+ * Elliptic curve Diffie-Hellman key exchange. The public key is
+ * specified in one of two ways - a raw X9.62 format public key
+ * string in Param, or a CSSM_KEY in the Context.
+ * Requested size, in keyData->Length, must be the same size as
+ * the keys' modulus. Data is returned in keyData->Data, which is
+ * allocated by the caller.
+ * Optionally performs X9.63 key derivation if algId ==
+ * CSSM_ALGID_ECDH_X963_KDF, with the optional SharedInfo passed
+ * as optional context attribute CSSM_ATTRIBUTE_SALT.
+ */
+void CryptKit::DeriveKey_ECDH (
+ const Context &context,
+ CSSM_ALGORITHMS algId,
+ const CssmData &Param, // other's public key. may be empty
+ CSSM_DATA *keyData, // mallocd by caller
+ // we fill in keyData->Length bytes
+ AppleCSPSession &session)
+{
+ bool mallocdPrivKey;
+ size_t privSize;
+
+ /* private ECDH key from context - required */
+ feePubKey privKey = contextToFeeKey(context, session, CSSM_ATTRIBUTE_KEY,
+ CSSM_KEYCLASS_PRIVATE_KEY, CSSM_KEYUSE_DERIVE, mallocdPrivKey);
+ if(privKey == NULL) {
+ CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_KEY);
+ }
+ privSize = (feePubKeyBitsize(privKey) + 7) / 8;
+ if((algId == CSSM_ALGID_ECDH) & (privSize != keyData->Length)) {
+ /* exact match required here */
+ CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
+ }
+
+ /*
+ * Public key ("their" key) can come from two places:
+ * -- in the context as a CSSM_ATTRIBUTE_PUBLIC_KEY. This is how
+ * public keys in X509 format must be used in this function.
+ * -- in the incoming Param, the raw unformatted (ANSI X9.62) form
+ */
+ bool mallocdPubKey = false;
+ feePubKey pubKey = NULL;
+ if(Param.Data == NULL) {
+ /* this throws if no key present */
+ pubKey = contextToFeeKey(context, session, CSSM_ATTRIBUTE_PUBLIC_KEY,
+ CSSM_KEYCLASS_PUBLIC_KEY, CSSM_KEYUSE_DERIVE, mallocdPubKey);
+ }
+ if((pubKey == NULL) && (Param.Data == NULL)) {
+ errorLog0("DeriveKey_ECDH: no pub_key\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
+ }
+ unsigned char *output = NULL;
+ unsigned outputLen = 0;
+ feeReturn frtn = feePubKeyECDH(privKey, pubKey,
+ (const unsigned char *)Param.Data, (unsigned)Param.Length,
+ &output, &outputLen);
+ if(frtn) {
+ goto errOut;
+ }
+ switch(algId) {
+ case CSSM_ALGID_ECDH:
+ /*
+ * Raw ECDH - requested length must match the generated size
+ * exactly. If so, return the result unmodified.
+ */
+ if(outputLen != keyData->Length) {
+ errorLog0("DeriveKey_ECDH: length mismatch\n");
+ frtn = FR_Internal;
+ break;
+ }
+ memmove(keyData->Data, output, outputLen);
+ break;
+ case CSSM_ALGID_ECDH_X963_KDF:
+ /* Further processing... */
+ frtn = ecdhKdf(context, output, outputLen, keyData);
+ break;
+ default:
+ /* shouldn't be here */
+ frtn = FR_Internal;
+ break;
+ }
+
+errOut:
+ if(mallocdPrivKey) {
+ feePubKeyFree(privKey);
+ }
+ if(mallocdPubKey) {
+ feePubKeyFree(pubKey);
+ }
+ if(output != NULL) {
+ ffree(output);
+ }
+ if(frtn) {
+ throwCryptKit(frtn, NULL);
+ }
+}
+
+#endif /* CRYPTKIT_CSP_ENABLE */