X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/libsecurity_apple_csp/lib/FEEAsymmetricContext.cpp diff --git a/OSX/libsecurity_apple_csp/lib/FEEAsymmetricContext.cpp b/OSX/libsecurity_apple_csp/lib/FEEAsymmetricContext.cpp new file mode 100644 index 00000000..080d555d --- /dev/null +++ b/OSX/libsecurity_apple_csp/lib/FEEAsymmetricContext.cpp @@ -0,0 +1,603 @@ +/* + * Copyright (c) 2000-2001,2011-2012,2014 Apple 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 + * + */ + +#ifdef CRYPTKIT_CSP_ENABLE + +#include "FEEAsymmetricContext.h" +#include "FEECSPUtils.h" +#include +#include + +/* 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(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, + (unsigned int)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, + (unsigned int)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, (unsigned int)outSize, 0); + } + else { + inSize = feeFEEDCipherTextSize(mFeeFeed, (unsigned int)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 = (unsigned int)((inSize + inBlockSize()) / inBlockSize()); + inSize = (unsigned int)(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, (unsigned int)(inSize + inBufSize()), final ? 1 : 0); + } + else { + rtn = feeFEEDPlainTextSize(mFeeFeed, (unsigned int)(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, + (unsigned int)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, + (unsigned int)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(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, (CC_LONG)sharedInfoLen); + } + CC_SHA1_Final(digOut, &sha1); + + /* digest --> output */ + unsigned toMove = CC_SHA1_DIGEST_LENGTH; + if(toMove > bytesToGo) { + toMove = (unsigned int)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 */