]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_apple_csp/lib/FEEAsymmetricContext.cpp
Security-59754.41.1.tar.gz
[apple/security.git] / OSX / libsecurity_apple_csp / lib / FEEAsymmetricContext.cpp
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 */
32 static 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 ***/
61 CryptKit::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
79 void 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
157 void 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
184 void 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
223 size_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
251 size_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
267 void 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 ***/
307 CryptKit::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
321 void 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
387 void 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
414 void 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 */
442 static 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 */
456 static 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 */
517 void 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
588 errOut:
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 */