1/*
2 * Copyright (c) 2000-2002,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 * DH_keys.cpp - Diffie-Hellman key pair support.
21 */
22
23#include "DH_keys.h"
24#include "DH_utils.h"
25#include <opensslUtils/opensslUtils.h>
26#include <opensslUtils/opensslAsn1.h>
27#include <security_cdsa_utilities/cssmdata.h>
28#include <AppleCSPSession.h>
29#include <AppleCSPUtils.h>
30#include <assert.h>
31#include <security_utilities/debugging.h>
32#include <Security/oidsalg.h>
33#include <YarrowConnection.h>
34
35#define dhKeyDebug(args...)	secdebug("dhKey", ## args)
36
37/*
38 * FIXME - the CDSA Algorithm Guide claims that the incoming params argument
39 * for a GenerateAlgorithmParameters call is ignored for D-H. This means
40 * that there is no way for the caller to  specify 'g' (typically 2, 3, or
41 * 5). This seems WAY bogus but we'll code to the spec for now, assuming
42 * a hard-coded default generator.
43 */
44#define DH_GENERATOR_DEFAULT	DH_GENERATOR_2
45
46
47/***
48 *** Diffie-Hellman-style BinaryKey
49 ***/
50
51/* constructor with optional existing DSA key */
52DHBinaryKey::DHBinaryKey(DH *dhKey)
53	: mDhKey(dhKey)
54{
55}
56
57DHBinaryKey::~DHBinaryKey()
58{
59	if(mDhKey) {
60		DH_free(mDhKey);
61		mDhKey = NULL;
62	}
63}
64
65void DHBinaryKey::generateKeyBlob(
66	Allocator 		&allocator,
67	CssmData			&blob,
68	CSSM_KEYBLOB_FORMAT	&format,
69	AppleCSPSession		&session,
70	const CssmKey		*paramKey,	/* optional, unused here */
71	CSSM_KEYATTR_FLAGS 	&attrFlags)	/* IN/OUT */
72{
73
74	switch(mKeyHeader.KeyClass) {
75		case CSSM_KEYCLASS_PUBLIC_KEY:
76		{
77			switch(format) {
78				case CSSM_KEYBLOB_RAW_FORMAT_NONE:
79					// take default
80					format = DH_PUB_KEY_FORMAT;
81					break;
82				case DH_PUB_KEY_FORMAT:
83				case CSSM_KEYBLOB_RAW_FORMAT_X509:
84					// proceed
85					break;
86				case CSSM_KEYBLOB_RAW_FORMAT_DIGEST:
87					/* use PKCS3 - caller won't care if we change this...right? */
88					format = DH_PUB_KEY_FORMAT;
89					break;
90				default:
91					CssmError::throwMe(CSSMERR_CSP_UNSUPPORTED_KEY_FORMAT);
92			}
93
94			assert(mDhKey != NULL);
95			CssmAutoData encodedKey(allocator);
96			CSSM_RETURN crtn = DHPublicKeyEncode(mDhKey, format,
97				encodedKey);
98			if(crtn) {
99				CssmError::throwMe(crtn);
100			}
101			blob = encodedKey.release();
102			break;
103		}
104		case CSSM_KEYCLASS_PRIVATE_KEY:
105		{
106			switch(format) {
107				case CSSM_KEYBLOB_RAW_FORMAT_NONE:
108					// i.e., use default
109					format = DH_PRIV_KEY_FORMAT;
110					break;
111				case DH_PRIV_KEY_FORMAT:
112				case CSSM_KEYBLOB_RAW_FORMAT_PKCS8:
113					// proceed
114					break;
115
116				case CSSM_KEYBLOB_RAW_FORMAT_DIGEST:
117				{
118					/*
119					 * Use public blob; calculate it if we
120					 * don't already have it.
121					 */
122					assert(mDhKey != NULL);
123					if(mDhKey->pub_key == NULL) {
124						int irtn = DH_generate_key(mDhKey);
125						if(!irtn) {
126							throwRsaDsa("DH_generate_key");
127						}
128					}
129					assert(mDhKey->pub_key != NULL);
130					setUpData(blob,
131						BN_num_bytes(mDhKey->pub_key),
132						*DH_Factory::privAllocator);
133					BN_bn2bin(mDhKey->pub_key, blob);
134					format = DH_PUB_KEY_FORMAT;
135					return;
136				}
137
138				default:
139					CssmError::throwMe(CSSMERR_CSP_UNSUPPORTED_KEY_FORMAT);
140			}
141			assert(mDhKey != NULL);
142			CssmAutoData encodedKey(allocator);
143			CSSM_RETURN crtn = DHPrivateKeyEncode(mDhKey, format,
144				encodedKey);
145			if(crtn) {
146				CssmError::throwMe(crtn);
147			}
148			blob = encodedKey.release();
149			break;
150		}
151		default:
152			CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
153	}
154}
155
156/***
157 *** Diffie-Hellman style AppleKeyPairGenContext
158 ***/
159
160/*
161 * This one is specified in, and called from, CSPFullPluginSession. Our
162 * only job is to prepare two subclass-specific BinaryKeys and call up to
163 * AppleKeyPairGenContext.
164 */
165void DHKeyPairGenContext::generate(
166	const Context 	&context,
167	CssmKey 		&pubKey,
168	CssmKey 		&privKey)
169{
170	DHBinaryKey *pubBinKey  = new DHBinaryKey();
171	DHBinaryKey *privBinKey = new DHBinaryKey();
172
173	try {
174		AppleKeyPairGenContext::generate(context,
175			session(),
176			pubKey,
177			pubBinKey,
178			privKey,
179			privBinKey);
180	}
181	catch (...) {
182		delete pubBinKey;
183		delete privBinKey;
184		throw;
185	}
186}
187
188/*
189 * This one is specified in, and called from, AppleKeyPairGenContext
190 */
191void DHKeyPairGenContext::generate(
192	const Context 	&context,
193	BinaryKey		&pubBinKey,
194	BinaryKey		&privBinKey,
195	uint32			&keyBits)
196{
197	/*
198	 * These casts throw exceptions if the keys are of the
199	 * wrong classes, which would be a major bogon, since we created
200	 * the keys in the above generate() function.
201	 */
202	DHBinaryKey &rPubBinKey =
203		dynamic_cast<DHBinaryKey &>(pubBinKey);
204	DHBinaryKey &rPrivBinKey =
205		dynamic_cast<DHBinaryKey &>(privBinKey);
206
207	/*
208	 * Parameters from context:
209	 *   Key size in bits, required;
210	 *   {p,g,privKeyLength} from generateParams, optional
211	 * NOTE: currently the openssl D-H imnplementation ignores the
212	 * privKeyLength field.
213	 */
214	keyBits = context.getInt(CSSM_ATTRIBUTE_KEY_LENGTH,
215				CSSMERR_CSP_MISSING_ATTR_KEY_LENGTH);
216	CssmData *paramData = context.get<CssmData>(CSSM_ATTRIBUTE_ALG_PARAMS);
217
218	NSS_DHParameterBlock algParamBlock;
219	NSS_DHParameter &algParams = algParamBlock.params;
220	uint32 privValueLen = 0;		// only nonzero from externally generated
221									//   params
222	SecNssCoder coder;				// for temp allocs of decoded parameters
223
224	if(paramData != NULL) {
225		/* this contains the DER encoding of a DHParameterBlock */
226		CSSM_RETURN crtn;
227		crtn = DHParamBlockDecode(*paramData, algParamBlock, coder);
228		if(crtn) {
229			CssmError::throwMe(crtn);
230		}
231
232		/* snag the optional private key length field */
233		if(algParams.privateValueLength.Data) {
234			privValueLen = cssmDataToInt(algParams.privateValueLength);
235		}
236
237		/* ensure caller's key size matches the incoming params */
238		size_t paramKeyBytes;
239		if(privValueLen) {
240			paramKeyBytes = (privValueLen + 7) / 8;
241		}
242		else {
243			paramKeyBytes = algParams.prime.Length;
244			/* trim off possible m.s. byte of zero */
245			const unsigned char *uo =
246				(const unsigned char *)algParams.prime.Data;
247			if(*uo == 0) {
248				paramKeyBytes--;
249			}
250		}
251		uint32 reqBytes = (keyBits + 7) / 8;
252		if(paramKeyBytes != reqBytes) {
253			dhKeyDebug("DH key size mismatch (req %d  param %d)",
254				(int)reqBytes, (int)paramKeyBytes);
255			CssmError::throwMe(CSSMERR_CSP_UNSUPPORTED_KEY_SIZE);
256		}
257	}
258	else {
259		/* no alg params specified; generate them now */
260		dhKeyDebug("DH implicit alg param calculation");
261		memset(&algParamBlock, 0, sizeof(algParamBlock));
262		dhGenParams(keyBits, DH_GENERATOR_DEFAULT, 0, algParams, coder);
263	}
264
265	/* create key, stuff params into it */
266	rPrivBinKey.mDhKey = DH_new();
267	if(rPrivBinKey.mDhKey == NULL) {
268		CssmError::throwMe(CSSMERR_CSP_MEMORY_ERROR);
269	}
270	DH *dhKey = rPrivBinKey.mDhKey;
271	dhKey->p = cssmDataToBn(algParams.prime);
272	dhKey->g = cssmDataToBn(algParams.base);
273	dhKey->length = privValueLen;
274	cspDhDebug("private DH binary key dhKey %p", dhKey);
275
276	/* generate the key (both public and private capabilities) */
277	int irtn = DH_generate_key(dhKey);
278	if(!irtn) {
279		throwRsaDsa("DH_generate_key");
280	}
281
282	/* public key is a subset */
283	rPubBinKey.mDhKey = DH_new();
284	if(rPubBinKey.mDhKey == NULL) {
285		CssmError::throwMe(CSSMERR_CSP_MEMORY_ERROR);
286	}
287	DH *pubDhKey = rPubBinKey.mDhKey;
288	pubDhKey->pub_key = BN_dup(dhKey->pub_key);
289	/* these params used for X509 style key blobs */
290	pubDhKey->p = BN_dup(dhKey->p);
291	pubDhKey->g = BN_dup(dhKey->g);
292	cspDhDebug("public DH binary key pubDhKey %p", pubDhKey);
293}
294
295
296
297/***
298 *** Diffie-Hellman CSPKeyInfoProvider.
299 ***/
300DHKeyInfoProvider::DHKeyInfoProvider(
301	const CssmKey 	&cssmKey,
302	AppleCSPSession	&session) :
303		CSPKeyInfoProvider(cssmKey, session)
304{
305}
306
307CSPKeyInfoProvider *DHKeyInfoProvider::provider(
308	const CssmKey 	&cssmKey,
309	AppleCSPSession	&session)
310{
311	switch(cssmKey.algorithm()) {
312		case CSSM_ALGID_DH:
313			break;
314		default:
315			CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
316	}
317	switch(cssmKey.keyClass()) {
318		case CSSM_KEYCLASS_PUBLIC_KEY:
319		case CSSM_KEYCLASS_PRIVATE_KEY:
320			break;
321		default:
322			CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
323	}
324	/* OK, we'll handle this one */
325	return new DHKeyInfoProvider(cssmKey, session);
326}
327
328/* Given a raw key, cook up a Binary key */
329void DHKeyInfoProvider::CssmKeyToBinary(
330	CssmKey				*paramKey,		// optional, ignored here
331	CSSM_KEYATTR_FLAGS	&attrFlags,		// IN/OUT
332	BinaryKey 			**binKey)
333{
334	*binKey = NULL;
335
336	assert(mKey.blobType() == CSSM_KEYBLOB_RAW);
337	switch(mKey.keyClass()) {
338		case CSSM_KEYCLASS_PUBLIC_KEY:
339		case CSSM_KEYCLASS_PRIVATE_KEY:
340			break;
341		default:
342			CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
343	}
344
345	/* first cook up an DH key, then drop that into a BinaryKey */
346	DH *dhKey = rawCssmKeyToDh(mKey);
347	DHBinaryKey *dhBinKey = new DHBinaryKey(dhKey);
348	*binKey = dhBinKey;
349	cspDhDebug("CssmKeyToBinary dhKey %p", dhKey);
350}
351
352/*
353 * Obtain key size in bits.
354 * FIXME - I doubt that this is, or can be, exactly accurate.....
355 */
356void DHKeyInfoProvider::QueryKeySizeInBits(
357	CSSM_KEY_SIZE &keySize)
358{
359	uint32 numBits = 0;
360
361	if(mKey.blobType() != CSSM_KEYBLOB_RAW) {
362		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_FORMAT);
363	}
364	DH *dhKey = rawCssmKeyToDh(mKey);
365
366	/* DH_size requires the p parameter, which some public keys don't have */
367	if(dhKey->p != NULL) {
368		numBits = DH_size(dhKey) * 8;
369	}
370	else {
371		assert(dhKey->pub_key != NULL);
372		numBits = BN_num_bytes(dhKey->pub_key) * 8;
373	}
374	DH_free(dhKey);
375	keySize.LogicalKeySizeInBits = numBits;
376	keySize.EffectiveKeySizeInBits = numBits;
377}
378
379/*
380 * Obtain blob suitable for hashing in CSSM_APPLECSP_KEYDIGEST
381 * passthrough.
382 */
383bool DHKeyInfoProvider::getHashableBlob(
384	Allocator 	&allocator,
385	CssmData		&blob)			// blob to hash goes here
386{
387	/*
388	 * The optimized case, a raw key in the "proper" format already.
389	 */
390	assert(mKey.blobType() == CSSM_KEYBLOB_RAW);
391	bool useAsIs = false;
392
393	switch(mKey.keyClass()) {
394		case CSSM_KEYCLASS_PUBLIC_KEY:
395			if(mKey.blobFormat() == CSSM_KEYBLOB_RAW_FORMAT_PKCS3) {
396				useAsIs = true;
397			}
398			break;
399		case CSSM_KEYCLASS_PRIVATE_KEY:
400			break;
401		default:
402			/* shouldn't be here */
403			assert(0);
404			CssmError::throwMe(CSSMERR_CSP_INTERNAL_ERROR);
405	}
406	if(useAsIs) {
407		const CssmData &keyBlob = CssmData::overlay(mKey.KeyData);
408		copyCssmData(keyBlob, blob, allocator);
409		return true;
410	}
411
412	/* caller converts to binary and proceeds */
413	return false;
414}
415
416/*
417 * Generate keygen parameters, stash them in a context attr array for later use
418 * when actually generating the keys.
419 */
420
421void DHKeyPairGenContext::generate(
422	const Context &context,
423	uint32 bitSize,
424    CssmData &params,		// RETURNED here,
425    uint32 &attrCount, 		// here,
426	Context::Attr * &attrs)	// and here
427{
428	/* generate the params */
429	NSS_DHParameterBlock algParamBlock;
430	SecNssCoder coder;
431	NSS_DHParameter &algParams = algParamBlock.params;
432	dhGenParams(bitSize, DH_GENERATOR_DEFAULT, 0, algParams, coder);
433
434	/* drop in the required OID */
435	algParamBlock.oid = CSSMOID_PKCS3;
436
437	/*
438	 * Here comes the fun part.
439	 * We "return" the DER encoding of these generated params in two ways:
440	 * 1. Copy out to app via the params argument, mallocing if Data ptr is NULL.
441	 *    The app must free this.
442	 * 2. Cook up a 1-element Context::attr array containing one ALG_PARAM attr,
443	 *    a CSSM_DATA_PTR containing the DER encoding. We have to save a ptr to
444	 *    this attr array and free it, the CSSM_DATA it points to, and the DER
445	 *    encoding *that* points to, in our destructor.
446	 *
447	 * First, DER encode.
448	 */
449	CssmAutoData aDerData(session());
450	PRErrorCode perr;
451	perr = SecNssEncodeItemOdata(&algParamBlock, kSecAsn1DHParameterBlockTemplate,
452		aDerData);
453	if(perr) {
454		/* only known error... */
455		CssmError::throwMe(CSSMERR_CSP_MEMORY_ERROR);
456	}
457
458	/* copy/release that into a mallocd CSSM_DATA. */
459	CSSM_DATA_PTR derData = (CSSM_DATA_PTR)session().malloc(sizeof(CSSM_DATA));
460	*derData = aDerData.release();
461
462	/* stuff that into a one-element Attr array which we keep after returning */
463	freeGenAttrs();
464	mGenAttrs = (Context::Attr *)session().malloc(sizeof(Context::Attr));
465	mGenAttrs->AttributeType   = CSSM_ATTRIBUTE_ALG_PARAMS;
466	mGenAttrs->AttributeLength = sizeof(CSSM_DATA);
467	mGenAttrs->Attribute.Data  = derData;
468
469	/* and "return" this stuff */
470	copyCssmData(CssmData::overlay(*derData), params, session());
471	attrCount = 1;
472	attrs = mGenAttrs;
473}
474
475/* free mGenAttrs and its referents if present */
476void DHKeyPairGenContext::freeGenAttrs()
477{
478	if(mGenAttrs == NULL) {
479		return;
480	}
481	if(mGenAttrs->Attribute.Data) {
482		if(mGenAttrs->Attribute.Data->Data) {
483			session().free(mGenAttrs->Attribute.Data->Data);
484		}
485		session().free(mGenAttrs->Attribute.Data);
486	}
487	session().free(mGenAttrs);
488}
489
490/*
491 * Generate DSA algorithm parameters returning result
492 * into DHParameter.{prime,base,privateValueLength].
493 * This is called from both GenerateParameters and from
494 * KeyPairGenerate (if no GenerateParameters has yet been called).
495 *
496 * FIXME - privateValueLength not implemented in openssl, not here
497 * either for now.
498 */
499
500void DHKeyPairGenContext::dhGenParams(
501	uint32			keySizeInBits,
502	unsigned		g,					// probably should be BIGNUM
503	int				privValueLength, 	// optional
504	NSS_DHParameter	&algParams,
505	SecNssCoder		&coder)				// temp contents of algParams
506										//    mallocd here
507{
508	/* validate key size */
509	if((keySizeInBits < DH_MIN_KEY_SIZE) ||
510	   (keySizeInBits > DH_MAX_KEY_SIZE)) {
511		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY_LENGTH);
512	}
513
514	/* create an openssl-style DH key with minimal setup */
515	DH *dhKey = DH_generate_parameters(keySizeInBits, g, NULL, NULL);
516	if(dhKey == NULL) {
517		throwRsaDsa("DSA_generate_parameters");
518	}
519
520	/* stuff dhKey->{p,g,length}] into a caller's NSS_DHParameter */
521	bnToCssmData(dhKey->p, algParams.prime, coder);
522	bnToCssmData(dhKey->g, algParams.base, coder);
523	CSSM_DATA &privValData = algParams.privateValueLength;
524	if(privValueLength) {
525		intToCssmData(privValueLength, privValData, coder);
526	}
527	else {
528		privValData.Data = NULL;
529		privValData.Length = 0;
530	}
531	DH_free(dhKey);
532}
533
534