]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_apple_csp/lib/FEEAsymmetricContext.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_apple_csp / lib / FEEAsymmetricContext.cpp
diff --git a/libsecurity_apple_csp/lib/FEEAsymmetricContext.cpp b/libsecurity_apple_csp/lib/FEEAsymmetricContext.cpp
new file mode 100644 (file)
index 0000000..60c5df6
--- /dev/null
@@ -0,0 +1,604 @@
+/*
+ * 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 */