1/*
2 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 */
5
6/*
7 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
8 * Note, this file is cstyle and lint clean and should stay that way.
9 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10 */
11
12#include <k5-int.h>
13#include <enc_provider.h>
14
15#define	BLOCK_SIZE 16
16
17/*
18 * AES encrypt using CipherText Stealing mode built on top of CBC mode.  CBC is
19 * being used because the Solaris Cryptographic Framework/PKCS11 does not
20 * currently support CTS while CBC is supported.  CBC as compared to ECB that
21 * was previously used allows crypto providers to do the crypto more
22 * efficiently.  In addition there is a crypto card (SCA6000) that did not
23 * provide ECB mode so krb was unable to take advantage.  If CTS mode is ever
24 * supported by the Solaris Cryptographic Framework then this code should be
25 * changed to use that.
26 *
27 * CTS is based on what is described in Schneier's Applied Cryptography and RFC
28 * 3962.
29 */
30
31#ifdef _KERNEL
32/*ARGSUSED*/
33krb5_error_code
34krb5int_aes_encrypt(krb5_context context,
35	const krb5_keyblock *key, const krb5_data *ivec,
36	const krb5_data *input, krb5_data *output)
37{
38	int ret = 0;
39	int nblocks, partialamount;
40	crypto_mechanism_t mech;
41	/*
42	 * nlobp = next to last output block pointer, lobp = last output block
43	 * pointer
44	 */
45	char *nlobp, *lobp;
46	char local_iv_data[BLOCK_SIZE];
47	krb5_data local_iv;
48
49	KRB5_LOG0(KRB5_INFO, "In krb5int_aes_encrypt(kernel): start");
50
51	ASSERT(input != NULL);
52	if (input->length < BLOCK_SIZE)
53		return (KRB5_BAD_MSIZE);
54	ASSERT(output != NULL);
55	ASSERT(input->length == output->length);
56	ASSERT(key != NULL);
57	ASSERT(key->key_tmpl != NULL);
58	ASSERT(key->kef_mt == crypto_mech2id(SUN_CKM_AES_CBC));
59
60	if (ivec != NULL) {
61		/*
62		 * This function updates ivec->data if the ivec is passed in so
63		 * it better have a data pointer and a proper length.
64		 */
65		if (ivec->data == NULL || ivec->length != BLOCK_SIZE) {
66			ASSERT(ivec->data != NULL);
67			ASSERT(ivec->length == BLOCK_SIZE);
68			KRB5_LOG1(KRB5_ERR, "In krb5int_aes_encrypt: error "
69			    "ivec->data = %p ivec->length = %d",
70			    (void *)ivec->data, ivec->length);
71			ret = KRB5_CRYPTO_INTERNAL;
72			goto cleanup;
73		}
74	}
75
76	/* number of input blocks including partial block */
77	nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
78	KRB5_LOG(KRB5_INFO, "nblocks = %d", nblocks);
79	/* get # of bytes in partially filled block */
80	partialamount = input->length % BLOCK_SIZE;
81	KRB5_LOG(KRB5_INFO, "partialamount = %d", partialamount);
82
83	if (nblocks == 1 || (partialamount == 0)) {
84		/*
85		 * Simple case:
86		 *
87		 * Use CBC for all plaintext blocks, all must be full, then swap
88		 * last 2 ciphertext blocks to implement CTS.  Note, CBC needs a
89		 * non-NULL IV.
90		 */
91		if (ivec != NULL) {
92			local_iv.data = ivec->data;
93			local_iv.length = ivec->length;
94		} else {
95			bzero(local_iv_data, sizeof (local_iv_data));
96			local_iv.data = local_iv_data;
97			local_iv.length = sizeof (local_iv_data);
98		}
99
100		/* Note using TRUE here because encryption is desired */
101		ret = k5_ef_crypto((const char *)input->data,
102		    (char *)output->data,
103		    input->length, (krb5_keyblock *)key,
104		    &local_iv, TRUE);
105
106		if (ret != 0) {
107			KRB5_LOG(KRB5_ERR,
108			    "k5_ef_crypto: error: ret = 0x%08x", ret);
109			goto cleanup;
110		}
111
112		if (nblocks > 1) {
113			/*
114			 * swap last 2 ciphertext blocks to implement CTS
115			 */
116			char tmp[BLOCK_SIZE];
117
118			nlobp = (char *)(output->data +
119			    ((nblocks - 2) * BLOCK_SIZE));
120			lobp = (char *)(output->data +
121			    ((nblocks - 1) * BLOCK_SIZE));
122
123			bcopy(nlobp, tmp, BLOCK_SIZE);
124			bcopy(lobp, nlobp, BLOCK_SIZE);
125			bcopy(tmp, lobp, BLOCK_SIZE);
126		}
127	} else {
128		/*
129		 * Complex case:
130		 *
131		 * This implements CTS mode where there is > 1 block and the
132		 * last block is partially filled Uses CBC mode in the kCF, then
133		 * does some swapping.
134		 *
135		 * pt = plain text, ct = cipher text
136		 */
137		char tmp_pt[BLOCK_SIZE], tmp_ct[BLOCK_SIZE];
138		/* Note the iovec below is NOT the ivec in the crypto sense */
139		struct iovec iovarray_pt[2], iovarray_ct[2];
140		struct uio uio_pt, uio_ct;
141		/* ct = ciphertext, pt = plaintext */
142		crypto_data_t ct, pt;
143
144		/* tmp_pt will provide 0 padding for last parital pt block */
145		bzero(tmp_pt, sizeof (tmp_pt));
146
147		/*
148		 * Setup the uio/iovecs so only one call to crypto_encrypt() is
149		 * made.  Plaintext first.
150		 */
151		pt.cd_format = CRYPTO_DATA_UIO;
152		pt.cd_offset = 0;
153		pt.cd_length = nblocks * BLOCK_SIZE;
154		pt.cd_miscdata = NULL;
155		bzero(&uio_pt, sizeof (uio_pt));
156		pt.cd_uio = &uio_pt;
157		pt.cd_uio->uio_iov = iovarray_pt;
158		pt.cd_uio->uio_iovcnt = 2;
159		pt.cd_uio->uio_segflg = UIO_SYSSPACE;
160
161		/*
162		 * first iovec has all full blocks of pt.
163		 */
164		pt.cd_uio->uio_iov[0].iov_base = (char *)input->data;
165		/* use full block input */
166		pt.cd_uio->uio_iov[0].iov_len = input->length - partialamount;
167
168		KRB5_LOG(KRB5_INFO, "pt0 iov_len = %d",
169		    (int)pt.cd_uio->uio_iov[0].iov_len);
170
171		/*
172		 * second iovec has the parital pt and 0 padding
173		 */
174		pt.cd_uio->uio_iov[1].iov_base = tmp_pt;
175		/*
176		 * since the first iovec includes the last partial pt,
177		 * set length to enough bytes to pad out to a full block
178		 */
179		bcopy(input->data + (input->length - partialamount), tmp_pt,
180		    partialamount);
181		pt.cd_uio->uio_iov[1].iov_len = BLOCK_SIZE;
182
183		/* setup ciphertext iovecs */
184		ct.cd_format = CRYPTO_DATA_UIO;
185		ct.cd_offset = 0;
186		ct.cd_length = nblocks * BLOCK_SIZE;
187		ct.cd_miscdata = NULL;
188		bzero(&uio_ct, sizeof (uio_ct));
189		ct.cd_uio = &uio_ct;
190		ct.cd_uio->uio_iov = iovarray_ct;
191		ct.cd_uio->uio_iovcnt = 2;
192		ct.cd_uio->uio_segflg = UIO_SYSSPACE;
193
194		/*
195		 * First iovec has almost all the ct but not the ct for the last
196		 * partial pt with the padding.  That will be stored in the
197		 * secont ct iovec.
198		 */
199		ct.cd_uio->uio_iov[0].iov_base = (char *)output->data;
200		ct.cd_uio->uio_iov[0].iov_len = output->length - partialamount;
201		KRB5_LOG(KRB5_INFO, "ct0 iov_len = %d",
202		    (int)ct.cd_uio->uio_iov[0].iov_len);
203		/*
204		 * Second iovec has the last ciphertext block
205		 */
206		ct.cd_uio->uio_iov[1].iov_base = tmp_ct;
207		ct.cd_uio->uio_iov[1].iov_len = BLOCK_SIZE;
208
209		/* This had better be AES CBC mode! */
210		mech.cm_type = key->kef_mt;
211
212		if (ivec == NULL) {
213			bzero(local_iv_data, sizeof (local_iv_data));
214			mech.cm_param = local_iv_data;
215			mech.cm_param_len = sizeof (local_iv_data);
216		} else {
217			mech.cm_param = ivec->data;
218			mech.cm_param_len = ivec->length;
219		}
220
221		/* encrypt using AES CBC */
222		ret = crypto_encrypt(&mech, &pt, (crypto_key_t *)&key->kef_key,
223		    key->key_tmpl, &ct, NULL);
224
225		if (ret != CRYPTO_SUCCESS) {
226			KRB5_LOG(KRB5_ERR,
227			    "crypto_encrypt: error: ret = 0x%08x",
228			    ret);
229			goto cleanup;
230		}
231
232		/*
233		 * Swap:
234		 * copy the next to last ct to last partial output block (only
235		 * the partial amount is copied).
236		 */
237		nlobp = (char *)(output->data + ((nblocks - 2) * BLOCK_SIZE));
238		lobp = (char *)(output->data + ((nblocks - 1) * BLOCK_SIZE));
239
240		bcopy(nlobp, lobp, partialamount);
241		/*
242		 * copy the last ct output block to next to last output block
243		 */
244		bcopy(tmp_ct, nlobp, BLOCK_SIZE);
245
246	} /* end partial block processing */
247
248	/*
249	 * The ivec is updated to allow the caller to chain ivecs.  At this
250	 * point I don't think any kernel callers are using this however the
251	 * userland version of this function does it so this should be done in
252	 * kernel for consistency's sake.  This is not done for 1 block, got
253	 * this from MIT.  Note, the next to last output block is copied because
254	 * it contains the last full block of cipher text.
255	 */
256	if (nblocks > 1 && ivec)
257		(void) memcpy(ivec->data, nlobp, BLOCK_SIZE);
258
259cleanup:
260	if (ret)
261		bzero(output->data, output->length);
262	return (ret);
263}
264
265#else /* User Space */
266
267/*ARGSUSED*/
268krb5_error_code
269krb5int_aes_encrypt(krb5_context context,
270	const krb5_keyblock *key, const krb5_data *ivec,
271	const krb5_data *input, krb5_data *output)
272{
273	krb5_error_code ret = 0;
274	int nblocks, partialamount;
275	CK_RV rv;
276	KRB5_MECH_TO_PKCS algos;
277	CK_MECHANISM mechanism;
278	CK_ULONG outlen;
279	/*
280	 * nlobp = next to last output block pointer, lobp = last output block
281	 * pointer
282	 */
283	char *nlobp, *lobp;
284	char tmp_ivec[BLOCK_SIZE];
285
286	assert(input != NULL);
287	if (input->length < BLOCK_SIZE)
288		return (KRB5_BAD_MSIZE);
289	assert(output != NULL);
290	assert(input->length == output->length);
291	assert(key != NULL);
292
293	if (ivec != NULL) {
294		/*
295		 * This function updates ivec->data if the ivec is passed in so
296		 * it better have a data pointer and a proper length.
297		 */
298		if (ivec->data == NULL || ivec->length != BLOCK_SIZE) {
299			assert(ivec->data != NULL);
300			assert(ivec->length == BLOCK_SIZE);
301			KRB5_LOG1(KRB5_ERR, "In krb5int_aes_encrypt: error "
302			    "ivec->data = %p ivec->length = %d", ivec->data,
303			    ivec->length);
304			ret = KRB5_CRYPTO_INTERNAL;
305			goto cleanup;
306		}
307	}
308
309	/* number of input blocks including partial block */
310	nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
311	KRB5_LOG(KRB5_INFO, "nblocks = %d", nblocks);
312	/* get # of bytes in partially filled block */
313	partialamount = input->length % BLOCK_SIZE;
314	KRB5_LOG(KRB5_INFO, "partialamount = %d", partialamount);
315
316	rv = get_algo(key->enctype, &algos);
317	if (rv != CKR_OK)
318		goto cleanup;
319	assert(algos.enc_algo == CKM_AES_CBC);
320
321	rv = init_key_uef(krb_ctx_hSession(context), (krb5_keyblock *)key);
322	if (rv != CKR_OK)
323		goto cleanup;
324
325	mechanism.mechanism = algos.enc_algo;
326
327	if (ivec == NULL) {
328		bzero(tmp_ivec, sizeof (tmp_ivec));
329		mechanism.pParameter = tmp_ivec;
330		mechanism.ulParameterLen = sizeof (tmp_ivec);
331	} else {
332		mechanism.pParameter = ivec->data;
333		mechanism.ulParameterLen = ivec->length;
334	}
335	/*
336	 * Note, since CBC is assumed to be the underlying mode, this
337	 * call to C_EncryptInit is setting the IV.  The IV in use here
338	 * is either the ivec passed in or a block of 0's.
339	 */
340	rv = C_EncryptInit(krb_ctx_hSession(context), &mechanism, key->hKey);
341
342	if (rv != CKR_OK) {
343		KRB5_LOG(KRB5_ERR, "C_EncryptInit failed in "
344		    "krb5int_aes_encrypt: rv = 0x%x", rv);
345		goto cleanup;
346	}
347
348	if (nblocks == 1 || (partialamount == 0)) {
349		/*
350		 * Simple case:
351		 *
352		 * Use CBC for all plaintext blocks, all must be full, then swap
353		 * last 2 ciphertext blocks to implement CTS.
354		 */
355
356		/*
357		 * C_Encrypt/Decrypt requires a pointer to long, not a pointer
358		 * to int cast to pointer to long!!!
359		 */
360		outlen = output->length;
361
362		rv = C_Encrypt(krb_ctx_hSession(context),
363		    (CK_BYTE_PTR)input->data,
364		    input->length,
365		    (CK_BYTE_PTR)output->data,
366		    &outlen);
367
368		if (rv != CKR_OK) {
369			KRB5_LOG(KRB5_ERR, "C_Encrypt failed in "
370			    "krb5int_aes_encrypt: rv = 0x%x", rv);
371			goto cleanup;
372		}
373
374		assert(output->length == (unsigned int)outlen);
375
376		if (nblocks > 1) {
377			/*
378			 * swap last 2 ciphertext blocks to implement CTS
379			 */
380			char tmp[BLOCK_SIZE];
381
382			nlobp = (char *)(output->data +
383			    ((nblocks - 2) * BLOCK_SIZE));
384			lobp = (char *)(output->data +
385			    ((nblocks - 1) * BLOCK_SIZE));
386
387			bcopy(nlobp, tmp, BLOCK_SIZE);
388			bcopy(lobp, nlobp, BLOCK_SIZE);
389			bcopy(tmp, lobp, BLOCK_SIZE);
390		}
391	} else {
392		/*
393		 * Complex case:
394		 *
395		 * This implements CTS mode where there is > 1 block and the
396		 * last block is partially filled. Uses CBC mode in uCF/PKCS11,
397		 * then does some swapping.
398		 *
399		 * pt = plain text, ct = cipher text
400		 */
401		char tmp_pt[BLOCK_SIZE], tmp_ct[BLOCK_SIZE];
402
403		/*
404		 * encrypt from P0...Pn-1 using CBC, last block of output is Cn
405		 * & C'
406		 */
407		outlen = input->length - partialamount;
408
409		rv = C_EncryptUpdate(krb_ctx_hSession(context),
410		    (CK_BYTE_PTR)input->data,
411		    input->length - partialamount,
412		    (CK_BYTE_PTR)output->data,
413		    &outlen);
414
415		if (rv != CKR_OK) {
416			KRB5_LOG(KRB5_ERR, "C_EncryptUpdate failed in "
417			    "krb5int_aes_encrypt: rv = 0x%x", rv);
418			goto cleanup;
419		}
420
421		/* tmp_pt will provide 0 padding for last parital pt block */
422		bzero(tmp_pt, sizeof (tmp_pt));
423		/* copy Pn to tmp_pt which has 0 padding */
424		bcopy(input->data + (input->length - partialamount), tmp_pt,
425		    partialamount);
426
427		/* encrypt Pn with 0 padding, Cn & C' ivec, output is Cn-1 */
428		outlen = sizeof (tmp_ct);
429
430		rv = C_EncryptUpdate(krb_ctx_hSession(context),
431		    (CK_BYTE_PTR)tmp_pt,
432		    BLOCK_SIZE,
433		    (CK_BYTE_PTR)tmp_ct,
434		    &outlen);
435
436		if (rv != CKR_OK) {
437			KRB5_LOG(KRB5_ERR, "C_Encrypt failed in "
438			    "krb5int_aes_encrypt: rv = 0x%x", rv);
439			goto cleanup;
440		}
441
442		nlobp = (char *)(output->data + ((nblocks - 2) * BLOCK_SIZE));
443		lobp = (char *)(output->data + ((nblocks - 1) * BLOCK_SIZE));
444
445		/* copy Cn from next to last output block to last block */
446		bcopy(nlobp, lobp, partialamount);
447		/* copy Cn-1 from tmp_ct to next to last output block */
448		bcopy(tmp_ct, nlobp, BLOCK_SIZE);
449
450		/* Close the crypto session, ignore the output */
451		rv = C_EncryptFinal(krb_ctx_hSession(context),
452		    (CK_BYTE_PTR)tmp_ct, &outlen);
453
454		if (rv != CKR_OK)
455			goto cleanup;
456	}
457	/*
458	 * The ivec is updated to allow the caller to chain ivecs, done for the
459	 * kcmd (rsh/rcp/etc...).  Note this is not done for 1 block although I
460	 * am not sure why but I'm continuing the tradition from the MIT code.
461	 * Note, the next to last output block is copied because it contains the
462	 * last full block of cipher text.
463	 */
464	if (nblocks > 1 && ivec)
465		(void) memcpy(ivec->data, nlobp, BLOCK_SIZE);
466
467cleanup:
468	if (rv != CKR_OK)
469		ret = PKCS_ERR;
470
471	if (ret)
472		bzero(output->data, input->length);
473
474	return (ret);
475}
476#endif /* _KERNEL */
477
478/*
479 * AES Decrypt using CipherText Stealing mode built on top of CBC mode.  See the
480 * krb5int_aes_encrypt() comments for the reason CBC is being used.
481 */
482
483#ifdef _KERNEL
484/*ARGSUSED*/
485krb5_error_code
486krb5int_aes_decrypt(krb5_context context,
487	const krb5_keyblock *key, const krb5_data *ivec,
488	const krb5_data *input, krb5_data *output)
489{
490	krb5_error_code ret = 0;
491	int nblocks, partialamount;
492	char local_iv_data[BLOCK_SIZE];
493	krb5_data local_iv;
494
495	KRB5_LOG0(KRB5_INFO, "In krb5int_aes_decrypt: start");
496
497	ASSERT(input != NULL);
498	if (input->length < BLOCK_SIZE)
499		return (KRB5_BAD_MSIZE);
500	ASSERT(output != NULL);
501	ASSERT(input->length == output->length);
502	ASSERT(key != NULL);
503	ASSERT(key->kef_mt == crypto_mech2id(SUN_CKM_AES_CBC));
504
505	if (ivec != NULL) {
506		/*
507		 * This function updates ivec->data if the ivec is passed in so
508		 * it better have a data pointer and a proper length.
509		 */
510		if (ivec->data == NULL || ivec->length != BLOCK_SIZE) {
511			ASSERT(ivec->data != NULL);
512			ASSERT(ivec->length == BLOCK_SIZE);
513			KRB5_LOG1(KRB5_ERR, "In krb5int_aes_decrypt: error "
514			    "ivec->data = %p ivec->length = %d",
515			    (void *)ivec->data, ivec->length);
516			ret = KRB5_CRYPTO_INTERNAL;
517			goto cleanup;
518		}
519	}
520
521	/* number of input blocks including partial block */
522	nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
523	KRB5_LOG(KRB5_INFO, "nblocks = %d", nblocks);
524	/* get # of bytes in partially filled block */
525	partialamount = input->length % BLOCK_SIZE;
526	KRB5_LOG(KRB5_INFO, "partialamount = %d", partialamount);
527
528	if (ivec != NULL) {
529		local_iv.data = ivec->data;
530		local_iv.length = ivec->length;
531	} else {
532		bzero(local_iv_data, sizeof (local_iv_data));
533		local_iv.data = local_iv_data;
534		local_iv.length = sizeof (local_iv_data);
535	}
536
537	if (nblocks == 1 || (partialamount == 0)) {
538		char orig_input[BLOCK_SIZE * 2];
539		/*
540		 * nlibp = next to last input block pointer
541		 * libp = last input block pointer
542		 */
543		char *nlibp, *libp;
544
545		/*
546		 * Simple case:
547		 *
548		 * Swap last 2 ciphertext blocks (all must be full), then use
549		 * CBC to implement CTS.
550		 */
551
552		if (nblocks > 1) {
553			/*
554			 * swap last 2 ciphertext blocks to implement CTS
555			 */
556			char tmp[BLOCK_SIZE];
557
558			nlibp = input->data + ((nblocks - 2) * BLOCK_SIZE);
559			libp = input->data + ((nblocks - 1) * BLOCK_SIZE);
560
561			/* first save orig input data for later restore */
562			/* we know that partial amount is 0, because */
563			/* nblocks is > 1, so we copy the last two blocks */
564			bcopy(nlibp, orig_input, sizeof (orig_input));
565
566			/* swap */
567			bcopy(nlibp, tmp, BLOCK_SIZE);
568			bcopy(libp, nlibp, BLOCK_SIZE);
569			bcopy(tmp, libp, BLOCK_SIZE);
570		}
571
572		ret = k5_ef_crypto((const char *)input->data,
573		    (char *)output->data,
574		    input->length, (krb5_keyblock *)key,
575		    &local_iv, FALSE);
576
577		if (nblocks > 1) {
578			/* restore orig input data */
579			bcopy(orig_input, nlibp, sizeof (orig_input));
580		}
581
582		if (ret != 0) {
583			KRB5_LOG(KRB5_ERR,
584			    "k5_ef_crypto returned error: ret = 0x%08x",
585			    ret);
586			goto cleanup;
587		}
588
589	} else {
590		krb5_data tmp_ivec;
591		char tmp_ivec_data[BLOCK_SIZE], tmp_input_data[BLOCK_SIZE],
592		    tmp_output_data[BLOCK_SIZE];
593		/* pointers to Cn, Cn-1, Cn-2 CipherText */
594		char *Cn, *Cn_1, *Cn_2;
595		long length;
596
597		/*
598		 * Complex case:
599		 *
600		 * Decrypting in CTS where there is a partial block of
601		 * ciphertext.
602		 */
603
604		/* setting pointers to CipherText for later use */
605		Cn = input->data + (input->length - partialamount);
606		/* Cn - 1 */
607		Cn_1 = Cn - BLOCK_SIZE;
608		/* Cn - 2 */
609		Cn_2 = Cn_1 - BLOCK_SIZE;
610
611		if (nblocks > 2) {
612			/* set length to include blocks C0 thru Cn-2 */
613			length = input->length - (BLOCK_SIZE + partialamount);
614
615			/*
616			 * First decrypt C0 thru Cn-2 using CBC with the input
617			 * ivec.
618			 */
619			ret = k5_ef_crypto((const char *)input->data,
620			    output->data, length, (krb5_keyblock *)key,
621			    &local_iv, FALSE);
622
623			if (ret != 0) {
624				KRB5_LOG(KRB5_ERR,
625				    "k5_ef_crypto: error: ret = 0x%08x",
626				    ret);
627				goto cleanup;
628			}
629		}
630		/*
631		 * Prepare to decrypt Cn-1 using a ivec of Cn with 0 padding.
632		 */
633		bzero(tmp_ivec_data, sizeof (tmp_ivec_data));
634		/* the tmp ivec data holds Cn with 0 padding */
635		bcopy(Cn, tmp_ivec_data, partialamount);
636		tmp_ivec.data = tmp_ivec_data;
637		tmp_ivec.length = sizeof (tmp_ivec_data);
638
639		/* decrypt 1 block */
640		length = BLOCK_SIZE;
641
642		/*
643		 * Now decrypt using Cn-1 input, Cn + 0 padding for ivec, Pn &
644		 * C' output
645		 */
646		ret = k5_ef_crypto((const char *)Cn_1,
647		    tmp_output_data, length,
648		    (krb5_keyblock *)key, &tmp_ivec, FALSE);
649
650		if (ret != 0) {
651			KRB5_LOG(KRB5_ERR,
652			    "k5_ef_crypto: error: ret = 0x%08x",
653			    ret);
654			goto cleanup;
655		}
656		/*
657		 * tmp input data should hold Cn with C'
658		 * Note, tmp_output_data contains Pn + C',
659		 */
660		/* copy Cn */
661		bcopy(Cn, tmp_input_data, partialamount);
662		/* copy C' */
663		bcopy(tmp_output_data + partialamount,
664		    tmp_input_data + partialamount,
665		    (BLOCK_SIZE - partialamount));
666
667		/* copy Pn in tmp output to output->data */
668		bcopy(tmp_output_data,
669		    output->data + (input->length - partialamount),
670		    partialamount);
671
672		if (nblocks > 2) {
673			/* use Cn-2 as ivec */
674			tmp_ivec.data = Cn_2;
675		} else {
676			/* use 0 as ivec because Cn-2 does not exist */
677			bzero(tmp_ivec_data, sizeof (tmp_ivec_data));
678		}
679
680		/*
681		 * Now decrypt Cn + C' input, using either Cn-2 or 0 for ivec
682		 * (set above), Pn-1 output.
683		 */
684		ret = k5_ef_crypto((const char *)tmp_input_data,
685		    (char *)output->data +
686		    (input->length - (BLOCK_SIZE + partialamount)),
687		    length, (krb5_keyblock *)key,
688		    &tmp_ivec, FALSE);
689
690		if (ret != 0) {
691			KRB5_LOG(KRB5_ERR,
692			    "k5_ef_crypto: error: ret = 0x%08x", ret);
693			goto cleanup;
694		}
695
696	} /* end partial block processing */
697	/*
698	 * The ivec is updated to allow the caller to chain ivecs.  At this
699	 * point I don't think any kernel callers are using this however the
700	 * userland version of this function does it so this should be done in
701	 * kernel for consistency's sake.  This is not done for 1 block, got
702	 * this from MIT.
703	 */
704	if (nblocks > 1 && ivec) {
705		(void) memcpy(ivec->data,
706		    input->data + ((nblocks - 2) * BLOCK_SIZE),
707		    BLOCK_SIZE);
708	}
709
710cleanup:
711	if (ret)
712		bzero(output->data, output->length);
713
714	return (ret);
715}
716
717#else /* User Space */
718
719/*ARGSUSED*/
720krb5_error_code
721krb5int_aes_decrypt(krb5_context context,
722	const krb5_keyblock *key, const krb5_data *ivec,
723	const krb5_data *input, krb5_data *output)
724{
725	krb5_error_code ret = 0;
726	int nblocks, partialamount;
727	CK_RV rv;
728	KRB5_MECH_TO_PKCS algos;
729	CK_MECHANISM mechanism;
730	CK_ULONG outlen;
731	char tmp_ivec[BLOCK_SIZE];
732
733	assert(input != NULL);
734	if (input->length < BLOCK_SIZE)
735		return (KRB5_BAD_MSIZE);
736	assert(output != NULL);
737	assert(input->length == output->length);
738	assert(key != NULL);
739
740	if (ivec != NULL) {
741		/*
742		 * This function updates ivec->data if the ivec is passed in so
743		 * it better have a data pointer and a proper length.
744		 */
745		if (ivec->data == NULL || ivec->length != BLOCK_SIZE) {
746			assert(ivec->data != NULL);
747			assert(ivec->length == BLOCK_SIZE);
748			KRB5_LOG1(KRB5_ERR, "In krb5int_aes_decrypt: error "
749			    "ivec->data = %p ivec->length = %d", ivec->data,
750			    ivec->length);
751			ret = KRB5_CRYPTO_INTERNAL;
752			goto cleanup;
753		}
754	}
755
756	/* number of input blocks including partial block */
757	nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
758	KRB5_LOG(KRB5_INFO, "nblocks = %d", nblocks);
759	/* get # of bytes in partially filled block */
760	partialamount = input->length % BLOCK_SIZE;
761	KRB5_LOG(KRB5_INFO, "partialamount = %d", partialamount);
762
763	rv = get_algo(key->enctype, &algos);
764	if (rv != CKR_OK)
765		goto cleanup;
766	assert(algos.enc_algo == CKM_AES_CBC);
767
768	rv = init_key_uef(krb_ctx_hSession(context), (krb5_keyblock *)key);
769	if (rv != CKR_OK) {
770		goto cleanup;
771	}
772
773	mechanism.mechanism = algos.enc_algo;
774	if (ivec == NULL) {
775		bzero(tmp_ivec, sizeof (tmp_ivec));
776		mechanism.pParameter = tmp_ivec;
777		mechanism.ulParameterLen = sizeof (tmp_ivec);
778	} else {
779		mechanism.pParameter = ivec->data;
780		mechanism.ulParameterLen = ivec->length;
781	}
782
783	if (nblocks == 1 || (partialamount == 0)) {
784		char orig_input[BLOCK_SIZE * 2];
785		/*
786		 * nlibp = next to last input block pointer
787		 * libp = last input block pointer
788		 */
789		char *nlibp, *libp;
790
791		/*
792		 * Simple case:
793		 *
794		 * Swap last 2 ciphertext blocks (all must be full), then use
795		 * CBC to implement CTS.
796		 */
797		if (nblocks > 1) {
798			/*
799			 * swap last 2 ciphertext blocks to implement CTS
800			 */
801			char tmp[BLOCK_SIZE];
802
803			/*
804			 * Note, the side effect with this is that we are
805			 * modifying the input->data!
806			 */
807			nlibp = input->data + ((nblocks - 2) * BLOCK_SIZE);
808			libp = input->data + ((nblocks - 1) * BLOCK_SIZE);
809
810			/* first save orig input data for later restore */
811			/* we know that partial amount is 0, because */
812			/* nblocks is > 1, so we copy the last two blocks */
813			bcopy(nlibp, orig_input, sizeof (orig_input));
814
815			bcopy(nlibp, tmp, BLOCK_SIZE);
816			bcopy(libp, nlibp, BLOCK_SIZE);
817			bcopy(tmp, libp, BLOCK_SIZE);
818		}
819
820		/*
821		 * Note, since CBC is assumed to be the underlying mode, this
822		 * call to C_DecryptInit is setting the IV.  The IV in use here
823		 * is either the ivec passed in or a block of 0's.  All calls to
824		 * C_DecryptInit set the IV in this function.
825		 */
826		rv = C_DecryptInit(krb_ctx_hSession(context), &mechanism,
827		    key->hKey);
828		if (rv != CKR_OK) {
829			KRB5_LOG(KRB5_ERR, "C_DecryptInit failed in "
830			    "krb5int_aes_decrypt: rv = 0x%x", rv);
831			goto cleanup;
832		}
833
834		/*
835		 * C_Encrypt/Decrypt requires a pointer to long, not a pointer
836		 * to int cast to pointer to long!!!
837		 */
838		outlen = output->length;
839
840		rv = C_Decrypt(krb_ctx_hSession(context),
841		    (CK_BYTE_PTR)input->data,
842		    input->length,
843		    (CK_BYTE_PTR)output->data,
844		    &outlen);
845
846		if (nblocks > 1) {
847			/* restore orig input data */
848			bcopy(orig_input, nlibp, sizeof (orig_input));
849		}
850	} else {
851		char tmp_ivec_data[BLOCK_SIZE], tmp_input_data[BLOCK_SIZE],
852		    tmp_output_data[BLOCK_SIZE];
853		/* pointers to Cn, Cn-1, Cn-2 CipherText */
854		char *Cn, *Cn_1, *Cn_2;
855		CK_ULONG length;
856
857		/*
858		 * Complex case:
859		 *
860		 * Decrypting in CTS where there is a partial block of
861		 * ciphertext.
862		 */
863
864		/* setting pointers to CipherText for later use */
865		Cn = input->data + (input->length - partialamount);
866		/* Cn - 1 */
867		Cn_1 = Cn - BLOCK_SIZE;
868		/* Cn - 2 */
869		Cn_2 = Cn_1 - BLOCK_SIZE;
870
871		if (nblocks > 2) {
872			rv = C_DecryptInit(krb_ctx_hSession(context),
873			    &mechanism, key->hKey);
874			if (rv != CKR_OK) {
875				KRB5_LOG(KRB5_ERR, "C_DecryptInit failed in "
876				    "krb5int_aes_decrypt: rv = 0x%x", rv);
877				goto cleanup;
878			}
879			/* set length to include blocks C0 thru Cn-2 */
880			length = input->length - (BLOCK_SIZE + partialamount);
881			outlen = length;
882			/*
883			 * First decrypt C0 thru Cn-2 using CBC with the input
884			 * ivec.
885			 */
886			rv = C_Decrypt(krb_ctx_hSession(context),
887			    (CK_BYTE_PTR)input->data,
888			    length,
889			    (CK_BYTE_PTR)output->data,
890			    &outlen);
891			if (rv != CKR_OK)
892				goto cleanup;
893		}
894
895		/*
896		 * Prepare to decrypt Cn-1 using a ivec of Cn with 0 padding.
897		 */
898		bzero(tmp_ivec_data, sizeof (tmp_ivec_data));
899		/* the tmp ivec data holds Cn with 0 padding */
900		bcopy(Cn, tmp_ivec_data, partialamount);
901
902		/* decrypt 1 block */
903		length = BLOCK_SIZE;
904		outlen = length;
905
906		/* set ivec to Cn with 0 padding */
907		mechanism.pParameter = tmp_ivec_data;
908		mechanism.ulParameterLen = sizeof (tmp_ivec_data);
909
910		rv = C_DecryptInit(krb_ctx_hSession(context), &mechanism,
911		    key->hKey);
912		if (rv != CKR_OK) {
913			KRB5_LOG(KRB5_ERR, "C_DecryptInit failed in "
914			    "krb5int_aes_decrypt: rv = 0x%x", rv);
915			goto cleanup;
916		}
917
918		/*
919		 * Now decrypt using Cn-1 input, Cn + 0 padding for ivec, Pn &
920		 * C' output
921		 */
922		rv = C_Decrypt(krb_ctx_hSession(context),
923		    (CK_BYTE_PTR)Cn_1,
924		    length,
925		    (CK_BYTE_PTR)tmp_output_data,
926		    &outlen);
927
928		if (rv != CKR_OK)
929			goto cleanup;
930
931		/*
932		 * tmp input data should hold Cn with C'
933		 * Note, tmp_output_data contains Pn + C',
934		 */
935		/* copy Cn */
936		bcopy(Cn, tmp_input_data, partialamount);
937		/* copy C' */
938		bcopy(tmp_output_data + partialamount,
939		    tmp_input_data + partialamount,
940		    (BLOCK_SIZE - partialamount));
941
942		/* copy Pn in tmp output to output->data last block */
943		bcopy(tmp_output_data,
944		    output->data + (input->length - partialamount),
945		    partialamount);
946
947		if (nblocks > 2) {
948			/* use Cn-2 as ivec */
949			mechanism.pParameter = Cn_2;
950		} else {
951			/*
952			 * nblocks == 2
953			 *
954			 * Cn-2 does not exist so either use 0 if input ivec
955			 * does not exist or use the input ivec.
956			 */
957			if (ivec == NULL) {
958				bzero(tmp_ivec_data, sizeof (tmp_ivec_data));
959			} else {
960				/* use original input ivec */
961				mechanism.pParameter = ivec->data;
962				mechanism.ulParameterLen = ivec->length;
963			}
964		}
965
966		rv = C_DecryptInit(krb_ctx_hSession(context), &mechanism,
967		    key->hKey);
968		if (rv != CKR_OK) {
969			KRB5_LOG(KRB5_ERR, "C_DecryptInit failed in "
970			    "krb5int_aes_decrypt: rv = 0x%x", rv);
971			goto cleanup;
972		}
973
974		/*
975		 * Now decrypt Cn + C' input, using either Cn-2, original input
976		 * ivec or 0 for ivec (set above), Pn-1 output.
977		 */
978		rv = C_Decrypt(krb_ctx_hSession(context),
979		    (CK_BYTE_PTR)tmp_input_data,
980		    length,
981		    (CK_BYTE_PTR)output->data + (input->length -
982		    (BLOCK_SIZE + partialamount)),
983		    &outlen);
984		if (rv != CKR_OK)
985			goto cleanup;
986	} /* end partial block processing */
987
988	/*
989	 * The ivec is updated to allow the caller to chain ivecs, done for the
990	 * kcmd (rsh/rcp/etc...).  Note this is not done for 1 block although I
991	 * am not sure why but I'm continuing the tradition from the MIT code.
992	 */
993	if (nblocks > 1 && ivec) {
994		(void) memcpy(ivec->data,
995		    input->data + ((nblocks - 2) * BLOCK_SIZE),
996		    BLOCK_SIZE);
997	}
998
999cleanup:
1000	if (rv != CKR_OK)
1001		ret = PKCS_ERR;
1002
1003	if (ret)
1004		bzero(output->data, input->length);
1005
1006	return (ret);
1007}
1008
1009#endif /* _KERNEL */
1010
1011static krb5_error_code
1012k5_aes_make_key(krb5_context context,
1013	const krb5_data *randombits, krb5_keyblock *key)
1014{
1015	krb5_error_code ret = 0;
1016	if (key->length != 16 && key->length != 32)
1017		return (KRB5_BAD_KEYSIZE);
1018	if (randombits->length != key->length)
1019		return (KRB5_CRYPTO_INTERNAL);
1020
1021	key->magic = KV5M_KEYBLOCK;
1022	key->dk_list = NULL;
1023
1024#ifdef _KERNEL
1025	key->kef_key.ck_data = NULL;
1026	key->key_tmpl = NULL;
1027	(void) memcpy(key->contents, randombits->data, randombits->length);
1028	ret = init_key_kef(context->kef_cipher_mt, key);
1029#else
1030	key->hKey = CK_INVALID_HANDLE;
1031	(void) memcpy(key->contents, randombits->data, randombits->length);
1032	ret = init_key_uef(krb_ctx_hSession(context), key);
1033#endif /* _KERNEL */
1034
1035	KRB5_LOG0(KRB5_INFO, "k5_aes_make_key() end\n");
1036	return (ret);
1037}
1038
1039/*ARGSUSED*/
1040static krb5_error_code
1041krb5int_aes_init_state(krb5_context context, const krb5_keyblock *key,
1042	krb5_keyusage usage, krb5_data *state)
1043{
1044	if (!state)
1045		return (0);
1046
1047	if (state && state->data)
1048		FREE(state->data, state->length);
1049
1050	state->length = BLOCK_SIZE;
1051	state->data = (void *) MALLOC(BLOCK_SIZE);
1052
1053	if (state->data == NULL)
1054		return (ENOMEM);
1055
1056	(void) memset(state->data, 0, state->length);
1057	return (0);
1058}
1059
1060const struct krb5_enc_provider krb5int_enc_aes128 = {
1061    BLOCK_SIZE,
1062    16, 16,
1063    krb5int_aes_encrypt,
1064    krb5int_aes_decrypt,
1065    k5_aes_make_key,
1066    krb5int_aes_init_state,
1067    krb5int_default_free_state
1068};
1069
1070const struct krb5_enc_provider krb5int_enc_aes256 = {
1071    BLOCK_SIZE,
1072    32, 32,
1073    krb5int_aes_encrypt,
1074    krb5int_aes_decrypt,
1075    k5_aes_make_key,
1076    krb5int_aes_init_state,
1077    krb5int_default_free_state
1078};
1079