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