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