1/* 2 * Copyright (c) 2000-2001,2011-2012,2014 Apple Inc. All Rights Reserved. 3 * 4 * The contents of this file constitute Original Code as defined in and are 5 * subject to the Apple Public Source License Version 1.2 (the 'License'). 6 * You may not use this file except in compliance with the License. Please obtain 7 * a copy of the License at http://www.apple.com/publicsource and read it before 8 * using this file. 9 * 10 * This Original Code and all software distributed under the License are 11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS 12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT 13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the 15 * specific language governing rights and limitations under the License. 16 */ 17 18 19/* 20 * FEEAsymmetricContext.cpp - CSPContexts for FEE asymmetric encryption 21 * 22 */ 23 24#ifdef CRYPTKIT_CSP_ENABLE 25 26#include "FEEAsymmetricContext.h" 27#include "FEECSPUtils.h" 28#include <security_cryptkit/falloc.h> 29#include <CommonCrypto/CommonDigest.h> 30 31/* validate context for FEED and FEEDExp - no unexpected attributes allowed */ 32static void validateFeedContext( 33 const Context &context) 34{ 35 /* Note we cannot distinguish between zero and "not there" */ 36 uint32 blockSize = context.getInt(CSSM_ATTRIBUTE_BLOCK_SIZE); 37 if(blockSize != 0) { 38 CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_BLOCK_SIZE); 39 } 40 CSSM_ENCRYPT_MODE cssmMode = context.getInt(CSSM_ATTRIBUTE_MODE); 41 if(cssmMode != 0) { 42 CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_MODE); 43 } 44 #if 0 45 /* we allow this for CMS wrapping */ 46 CssmData *iv = context.get<CssmData>(CSSM_ATTRIBUTE_INIT_VECTOR); 47 if(iv != NULL) { 48 CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_INIT_VECTOR); 49 } 50 #endif 51 CSSM_PADDING padding = context.getInt(CSSM_ATTRIBUTE_PADDING); 52 if(padding != 0) { 53 CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_PADDING); 54 } 55} 56 57/*** 58 *** FEED - 1:1 FEED - encrypt n bytes of plaintext, get (roughly) n bytes 59 *** of ciphertext. Ciphertext is smaller than with FEED, but this is slower. 60 ***/ 61CryptKit::FEEDContext::~FEEDContext() 62{ 63 if(mFeeFeed) { 64 feeFEEDFree(mFeeFeed); 65 mFeeFeed = NULL; 66 } 67 if(mPrivKey && mAllocdPrivKey) { 68 feePubKeyFree(mPrivKey); 69 } 70 if(mPubKey && mAllocdPubKey) { 71 feePubKeyFree(mPubKey); 72 } 73 mPrivKey = NULL; 74 mPubKey = NULL; 75 mInitFlag = false; 76} 77 78// called by CSPFullPluginSession; reusable 79void CryptKit::FEEDContext::init( 80 const Context &context, 81 bool encoding) 82{ 83 if(mInitFlag && !opStarted()) { 84 /* reusing - e.g. query followed by encrypt */ 85 return; 86 } 87 88 /* 89 * Fetch FEE keys from context. This is an unusual algorithm - it requires 90 * two keys, one public and one private. The public key MUST be stored in 91 * the context with attribute type CSSM_ATTRIBUTE_PUBLIC_KEY, and the private 92 * key with CSSM_ATTRIBUTE_KEY. 93 * 94 * For now, we require CSSM_KEYUSE_ANY for FEE keys used for this algorithm. 95 * Otherwise we'd have to allow both KEYUSE_ENCRYPT and KEYUSE_DECRYPT for 96 * both keys, and that would require some algorithm-specific hack in 97 * cspValidateKeyUsageBits() which I really don't want to do. 98 */ 99 if(mPrivKey == NULL) { 100 assert(!opStarted()); 101 mPrivKey = contextToFeeKey(context, 102 session(), 103 CSSM_ATTRIBUTE_KEY, 104 CSSM_KEYCLASS_PRIVATE_KEY, 105 CSSM_KEYUSE_ANY, 106 mAllocdPrivKey); 107 } 108 else { 109 assert(opStarted()); 110 } 111 if(mPubKey == NULL) { 112 assert(!opStarted()); 113 mPubKey = contextToFeeKey(context, 114 session(), 115 CSSM_ATTRIBUTE_PUBLIC_KEY, 116 CSSM_KEYCLASS_PUBLIC_KEY, 117 CSSM_KEYUSE_ANY, 118 mAllocdPubKey); 119 } 120 else { 121 assert(opStarted()); 122 } 123 124 /* validate context - no other attributes allowed */ 125 validateFeedContext(context); 126 127 if(mFeeFeed != NULL) { 128 /* not reusable */ 129 assert(opStarted()); 130 feeFEEDFree(mFeeFeed); 131 mFeeFeed = NULL; 132 } 133 134 /* OK, looks good. Cook up a feeFEED object. */ 135 mFeeFeed = feeFEEDNewWithPubKey(mPrivKey, 136 mPubKey, 137 encoding ? 1 : 0, 138 feeRandCallback, 139 &session()); 140 if(mFeeFeed == NULL) { 141 CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY); 142 } 143 144 /* finally, have BlockCryptor set up its stuff. */ 145 unsigned plainBlockSize = feeFEEDPlainBlockSize(mFeeFeed); 146 unsigned cipherBlockSize = feeFEEDCipherBlockSize(mFeeFeed); 147 setup(encoding ? plainBlockSize : cipherBlockSize, // blockSizeIn 148 encoding ? cipherBlockSize : plainBlockSize, // blockSizeOut 149 false, // pkcsPad 150 true, // needsFinal 151 BCM_ECB, 152 NULL); // IV 153 mInitFlag = true; 154} 155 156// called by BlockCryptor 157void CryptKit::FEEDContext::encryptBlock( 158 const void *plainText, // length implied (one block) 159 size_t plainTextLen, 160 void *cipherText, 161 size_t &cipherTextLen, // in/out, throws on overflow 162 bool final) 163{ 164 feeReturn frtn; 165 unsigned actMoved; 166 167 assert(mFeeFeed != NULL); 168 frtn = feeFEEDEncryptBlock(mFeeFeed, 169 (unsigned char *)plainText, 170 (unsigned int)plainTextLen, 171 (unsigned char *)cipherText, 172 &actMoved, 173 final ? 1 : 0); 174 if(frtn) { 175 throwCryptKit(frtn, "feeFEEDEncryptBlock"); 176 } 177 if(actMoved > cipherTextLen) { 178 /* Overflow already occurred! */ 179 CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR); 180 } 181 cipherTextLen = actMoved; 182} 183 184void CryptKit::FEEDContext::decryptBlock( 185 const void *cipherText, // length implied (one cipher block) 186 size_t cipherTextLen, 187 void *plainText, 188 size_t &plainTextLen, // in/out, throws on overflow 189 bool final) 190{ 191 feeReturn frtn; 192 unsigned actMoved; 193 194 assert(mFeeFeed != NULL); 195 frtn = feeFEEDDecryptBlock(mFeeFeed, 196 (unsigned char *)cipherText, 197 (unsigned int)inBlockSize(), 198 (unsigned char *)plainText, 199 &actMoved, 200 final ? 1 : 0); 201 if(frtn) { 202 throwCryptKit(frtn, "feeFEEDDecryptBlock"); 203 } 204 if(actMoved > plainTextLen) { 205 /* Overflow already occurred! */ 206 CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR); 207 } 208 plainTextLen = actMoved; 209} 210 211/* 212 * Additional query size support, necessary because we don't conform to 213 * BlockCryptor's standard one-to-one block scheme 214 */ 215 216#define BUFFER_DEBUG 0 217#if BUFFER_DEBUG 218#define bprintf(s) printf s 219#else 220#define bprintf(s) 221#endif 222 223size_t CryptKit::FEEDContext::inputSize( 224 size_t outSize) // input for given output size 225{ 226 /* 227 * We've been assured that this is NOT called for the final() op... 228 */ 229 unsigned inSize; 230 if(encoding()) { 231 inSize = feeFEEDPlainTextSize(mFeeFeed, (unsigned int)outSize, 0); 232 } 233 else { 234 inSize = feeFEEDCipherTextSize(mFeeFeed, (unsigned int)outSize, 0); 235 } 236 237 /* account for possible pending buffered input */ 238 if(inSize >= inBufSize()) { 239 inSize -= inBufSize(); 240 } 241 242 /* round up to next block size, then lop off one...anything from 243 * blockSize*n to (blockSize*n)-1 has same effect */ 244 unsigned inBlocks = (unsigned int)((inSize + inBlockSize()) / inBlockSize()); 245 inSize = (unsigned int)(inBlocks * inBlockSize()) - 1; 246 bprintf(("--- FEEDContext::inputSize inSize 0x%x outSize 0x%x\n", 247 inSize, outSize)); 248 return inSize; 249} 250 251size_t CryptKit::FEEDContext::outputSize( 252 bool final, 253 size_t inSize) // output for given input size 254{ 255 size_t rtn; 256 if(encoding()) { 257 rtn = feeFEEDCipherTextSize(mFeeFeed, (unsigned int)(inSize + inBufSize()), final ? 1 : 0); 258 } 259 else { 260 rtn = feeFEEDPlainTextSize(mFeeFeed, (unsigned int)(inSize + inBufSize()), final ? 1 : 0); 261 } 262 bprintf(("--- FEEDContext::outputSize inSize 0x%x outSize 0x%x final %d\n", 263 inSize, rtn, final)); 264 return rtn; 265} 266 267void CryptKit::FEEDContext::minimumProgress( 268 size_t &in, 269 size_t &out) // minimum progress chunks 270{ 271 if(encoding()) { 272 /* 273 * -- in := one block plaintext 274 * -- out := current cipher size for one block plaintext 275 */ 276 in = inBlockSize(); 277 out = feeFEEDCipherBufSize(mFeeFeed, 0); 278 } 279 else { 280 /* 281 * -- in := current cipher size for one block plaintext 282 * -- out := one block plaintext 283 */ 284 in = feeFEEDCipherBufSize(mFeeFeed, 0); 285 out = outBlockSize(); 286 } 287 288 /* 289 * Either case - input adjusted for pending. Note inBufSize can be up to one 290 * input block size, leaving the temp result zero here.... 291 */ 292 assert(in >= inBufSize()); 293 in -= inBufSize(); 294 295 /* if it is zero, bump it up so caller can make something happen */ 296 if(in == 0) { 297 in++; 298 } 299 bprintf(("--- FEEDContext::minProgres inSize 0x%x outSize 0x%x\n", 300 in, out)); 301} 302 303/*** 304 *** FEEDExp - 2:1 FEED - encrypt n bytes of plaintext, get (roughly) 2n bytes 305 *** of ciphertext. Ciphertext is larger than with FEED, but this is faster. 306 ***/ 307CryptKit::FEEDExpContext::~FEEDExpContext() 308{ 309 if(mFeeFeedExp) { 310 feeFEEDExpFree(mFeeFeedExp); 311 mFeeFeedExp = NULL; 312 } 313 if(mFeeKey && mAllocdFeeKey) { 314 feePubKeyFree(mFeeKey); 315 } 316 mFeeKey = NULL; 317 mInitFlag = false; 318} 319 320// called by CSPFullPluginSession; reusable 321void CryptKit::FEEDExpContext::init( 322 const Context &context, 323 bool encoding) 324{ 325 if(mInitFlag && !opStarted()) { 326 /* reusing - e.g. query followed by encrypt */ 327 return; 328 } 329 330 /* fetch FEE key from context */ 331 CSSM_KEYCLASS keyClass; 332 CSSM_KEYUSE keyUse; 333 334 if(encoding) { 335 /* encrypting to public key */ 336 keyClass = CSSM_KEYCLASS_PUBLIC_KEY; 337 keyUse = CSSM_KEYUSE_ENCRYPT; 338 } 339 else { 340 /* decrypting with private key */ 341 keyClass = CSSM_KEYCLASS_PRIVATE_KEY; 342 keyUse = CSSM_KEYUSE_DECRYPT; 343 } 344 if(mFeeKey == NULL) { 345 assert(!opStarted()); 346 mFeeKey = contextToFeeKey(context, 347 session(), 348 CSSM_ATTRIBUTE_KEY, 349 keyClass, 350 keyUse, 351 mAllocdFeeKey); 352 } 353 else { 354 assert(opStarted()); 355 } 356 357 /* validate context - no other attributes allowed */ 358 validateFeedContext(context); 359 360 /* OK, looks good. Cook up a feeFEEDExp object. */ 361 if(mFeeFeedExp != NULL) { 362 /* not reusable */ 363 assert(opStarted()); 364 feeFEEDExpFree(mFeeFeedExp); 365 mFeeFeedExp = NULL; 366 } 367 mFeeFeedExp = feeFEEDExpNewWithPubKey(mFeeKey, 368 feeRandCallback, 369 &session()); 370 if(mFeeFeedExp == NULL) { 371 CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY); 372 } 373 374 /* finally, have BlockCryptor set up its stuff. */ 375 unsigned plainBlockSize = feeFEEDExpPlainBlockSize(mFeeFeedExp); 376 unsigned cipherBlockSize = feeFEEDExpCipherBlockSize(mFeeFeedExp); 377 setup(encoding ? plainBlockSize : cipherBlockSize, // blockSizeIn 378 encoding ? cipherBlockSize : plainBlockSize, // blockSizeOut 379 false, // pkcs5Pad 380 true, // needsFinal 381 BCM_ECB, 382 NULL); // IV 383 mInitFlag = true; 384} 385 386// called by BlockCryptor 387void CryptKit::FEEDExpContext::encryptBlock( 388 const void *plainText, // length implied (one block) 389 size_t plainTextLen, 390 void *cipherText, 391 size_t &cipherTextLen, // in/out, throws on overflow 392 bool final) 393{ 394 feeReturn frtn; 395 unsigned actMoved; 396 397 assert(mFeeFeedExp != NULL); 398 frtn = feeFEEDExpEncryptBlock(mFeeFeedExp, 399 (unsigned char *)plainText, 400 (unsigned int)plainTextLen, 401 (unsigned char *)cipherText, 402 &actMoved, 403 final ? 1 : 0); 404 if(frtn) { 405 throwCryptKit(frtn, "feeFEEDExpEncryptBlock"); 406 } 407 if(actMoved > cipherTextLen) { 408 /* Overflow already occurred! */ 409 CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR); 410 } 411 cipherTextLen = actMoved; 412} 413 414void CryptKit::FEEDExpContext::decryptBlock( 415 const void *cipherText, // length implied (one cipher block) 416 size_t cipherTextLen, 417 void *plainText, 418 size_t &plainTextLen, // in/out, throws on overflow 419 bool final) 420{ 421 feeReturn frtn; 422 unsigned actMoved; 423 424 assert(mFeeFeedExp != NULL); 425 frtn = feeFEEDExpDecryptBlock(mFeeFeedExp, 426 (unsigned char *)cipherText, 427 (unsigned int)inBlockSize(), 428 (unsigned char *)plainText, 429 &actMoved, 430 final ? 1 : 0); 431 if(frtn) { 432 throwCryptKit(frtn, "feeFEEDExpDecryptBlock"); 433 } 434 if(actMoved > plainTextLen) { 435 /* Overflow already occurred! */ 436 CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR); 437 } 438 plainTextLen = actMoved; 439} 440 441/* convert uint32 to big-endian 4 bytes */ 442static void int32ToBytes( 443 uint32_t i, 444 unsigned char *b) 445{ 446 for(int dex=3; dex>=0; dex--) { 447 b[dex] = i; 448 i >>= 8; 449 } 450} 451 452/* 453 * X9.63 key derivation with optional SharedInfo passed as 454 * context attribute CSSM_ATTRIBUTE_SALT. 455 */ 456static feeReturn ecdhKdf( 457 const Context &context, 458 const unsigned char *Z, /* shared secret, i.e., output of ECDH */ 459 unsigned ZLen, 460 CSSM_DATA *K) /* output RETURNED in K->Data, length K->Length bytes */ 461{ 462 /* SharedInfo via salt, from context, optional */ 463 const unsigned char *sharedInfo = NULL; 464 CSSM_SIZE sharedInfoLen = 0; 465 466 CssmData *salt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT); 467 if(salt != NULL) { 468 sharedInfo = (const unsigned char *)salt->Data; 469 sharedInfoLen = salt->Length; 470 } 471 472 unsigned char *outp = K->Data; 473 CSSM_SIZE bytesToGo = K->Length; 474 CC_SHA1_CTX sha1; 475 uint32_t counter = 1; 476 uint8 counterBytes[4]; 477 unsigned char digOut[CC_SHA1_DIGEST_LENGTH]; 478 479 do { 480 /* K[i] = Hash(Z || Counter || SharedInfo) */ 481 CC_SHA1_Init(&sha1); 482 CC_SHA1_Update(&sha1, Z, ZLen); 483 int32ToBytes(counter, counterBytes); 484 CC_SHA1_Update(&sha1, counterBytes, 4); 485 if(sharedInfoLen) { 486 CC_SHA1_Update(&sha1, sharedInfo, (CC_LONG)sharedInfoLen); 487 } 488 CC_SHA1_Final(digOut, &sha1); 489 490 /* digest --> output */ 491 unsigned toMove = CC_SHA1_DIGEST_LENGTH; 492 if(toMove > bytesToGo) { 493 toMove = (unsigned int)bytesToGo; 494 } 495 memmove(outp, digOut, toMove); 496 497 counter++; 498 outp += toMove; 499 bytesToGo -= toMove; 500 501 } while(bytesToGo); 502 503 return FR_Success; 504} 505 506/* 507 * Elliptic curve Diffie-Hellman key exchange. The public key is 508 * specified in one of two ways - a raw X9.62 format public key 509 * string in Param, or a CSSM_KEY in the Context. 510 * Requested size, in keyData->Length, must be the same size as 511 * the keys' modulus. Data is returned in keyData->Data, which is 512 * allocated by the caller. 513 * Optionally performs X9.63 key derivation if algId == 514 * CSSM_ALGID_ECDH_X963_KDF, with the optional SharedInfo passed 515 * as optional context attribute CSSM_ATTRIBUTE_SALT. 516 */ 517void CryptKit::DeriveKey_ECDH ( 518 const Context &context, 519 CSSM_ALGORITHMS algId, 520 const CssmData &Param, // other's public key. may be empty 521 CSSM_DATA *keyData, // mallocd by caller 522 // we fill in keyData->Length bytes 523 AppleCSPSession &session) 524{ 525 bool mallocdPrivKey; 526 size_t privSize; 527 528 /* private ECDH key from context - required */ 529 feePubKey privKey = contextToFeeKey(context, session, CSSM_ATTRIBUTE_KEY, 530 CSSM_KEYCLASS_PRIVATE_KEY, CSSM_KEYUSE_DERIVE, mallocdPrivKey); 531 if(privKey == NULL) { 532 CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_KEY); 533 } 534 privSize = (feePubKeyBitsize(privKey) + 7) / 8; 535 if((algId == CSSM_ALGID_ECDH) & (privSize != keyData->Length)) { 536 /* exact match required here */ 537 CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR); 538 } 539 540 /* 541 * Public key ("their" key) can come from two places: 542 * -- in the context as a CSSM_ATTRIBUTE_PUBLIC_KEY. This is how 543 * public keys in X509 format must be used in this function. 544 * -- in the incoming Param, the raw unformatted (ANSI X9.62) form 545 */ 546 bool mallocdPubKey = false; 547 feePubKey pubKey = NULL; 548 if(Param.Data == NULL) { 549 /* this throws if no key present */ 550 pubKey = contextToFeeKey(context, session, CSSM_ATTRIBUTE_PUBLIC_KEY, 551 CSSM_KEYCLASS_PUBLIC_KEY, CSSM_KEYUSE_DERIVE, mallocdPubKey); 552 } 553 if((pubKey == NULL) && (Param.Data == NULL)) { 554 errorLog0("DeriveKey_ECDH: no pub_key\n"); 555 CssmError::throwMe(CSSMERR_CSP_INVALID_KEY); 556 } 557 unsigned char *output = NULL; 558 unsigned outputLen = 0; 559 feeReturn frtn = feePubKeyECDH(privKey, pubKey, 560 (const unsigned char *)Param.Data, (unsigned)Param.Length, 561 &output, &outputLen); 562 if(frtn) { 563 goto errOut; 564 } 565 switch(algId) { 566 case CSSM_ALGID_ECDH: 567 /* 568 * Raw ECDH - requested length must match the generated size 569 * exactly. If so, return the result unmodified. 570 */ 571 if(outputLen != keyData->Length) { 572 errorLog0("DeriveKey_ECDH: length mismatch\n"); 573 frtn = FR_Internal; 574 break; 575 } 576 memmove(keyData->Data, output, outputLen); 577 break; 578 case CSSM_ALGID_ECDH_X963_KDF: 579 /* Further processing... */ 580 frtn = ecdhKdf(context, output, outputLen, keyData); 581 break; 582 default: 583 /* shouldn't be here */ 584 frtn = FR_Internal; 585 break; 586 } 587 588errOut: 589 if(mallocdPrivKey) { 590 feePubKeyFree(privKey); 591 } 592 if(mallocdPubKey) { 593 feePubKeyFree(pubKey); 594 } 595 if(output != NULL) { 596 ffree(output); 597 } 598 if(frtn) { 599 throwCryptKit(frtn, NULL); 600 } 601} 602 603#endif /* CRYPTKIT_CSP_ENABLE */ 604