summaryrefslogtreecommitdiff
path: root/usr/src/lib/gss_mechs/mech_dh/backend/mech/crypto.c
blob: 3bbc2ab4e6727933d58182a806c9783b1505dd8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 *	crypto.c
 *
 *	Copyright (c) 1997, by Sun Microsystems, Inc.
 *	All rights reserved.
 *
 */

#include <sys/note.h>
#include "dh_gssapi.h"
#include "crypto.h"

/* Release the storage for a signature */
void
__free_signature(dh_signature_t sig)
{
	Free(sig->dh_signature_val);
	sig->dh_signature_val = NULL;
	sig->dh_signature_len = 0;
}

/* Release the storage for a gss_buffer */
void
__dh_release_buffer(gss_buffer_t b)
{
	Free(b->value);
	b->length = 0;
	b->value = NULL;
}

typedef struct cipher_entry {
	cipher_proc cipher;	/* Routine to en/decrypt with */
	unsigned int pad;	/* Padding need for the routine */
} cipher_entry, *cipher_t;

typedef struct verifer_entry {
	verifier_proc msg;	/* Routine to calculate the check sum */
	unsigned int size;	/* Size of check sum */
	cipher_t signer;	/* Cipher entry to sign the check sum */
} verifier_entry, *verifier_t;

typedef struct QOP_entry {
	int export_level;	/* Not currentlyt used */
	verifier_t verifier;	/* Verifier entry to use for integrity */
} QOP_entry;

/*
 * Return the length produced by using cipher entry c given the supplied len
 */
static unsigned int
cipher_pad(cipher_t c, unsigned int len)
{
	unsigned int pad;

	pad = c ? c->pad : 1;

	return (((len + pad - 1)/pad)*pad);
}


/*
 * Des [en/de]crypt buffer, buf of length, len for each key provided using
 * an CBC initialization vector ivec.
 * If the mode is encrypt we will use the following pattern if the number
 * of keys is odd
 * encrypt(buf, k[0]), decrypt(buf, k[1]), encrypt(buf, k[2])
 *	decrypt(buf, k[4]) ... encrypt(buf, k[keynum - 1])
 * If we have an even number of keys and additional encryption will be
 * done with the first key, i.e., ecrypt(buf, k[0]);
 * In each [en/de]cription above we will used the passed in CBC initialization
 * vector. The new initialization vector will be the vector return from the
 * last encryption.
 *
 * In the decryption case we reverse the proccess. Note in this case
 * the return ivec will be from the first decryption.
 */

static int
__desN_crypt(des_block keys[], int keynum, char *buf, unsigned int len,
    unsigned int mode, char *ivec)
{
	/* Get the direction of ciphering */
	unsigned int m = mode & (DES_ENCRYPT | DES_DECRYPT);
	/* Get the remaining flags from mode */
	unsigned int flags = mode & ~(DES_ENCRYPT | DES_DECRYPT);
	des_block svec, dvec;
	int i, j, stat;

	/* Do we have at least one key */
	if (keynum < 1)
		return (DESERR_BADPARAM);

	/* Save the passed in ivec */
	memcpy(svec.c, ivec, sizeof (des_block));

	/* For  each key do the appropriate cipher */
	for (i = 0; i < keynum; i++) {
		j = (mode & DES_DECRYPT) ? keynum - 1 - i : i;
		stat = cbc_crypt(keys[j].c, buf, len, m | flags, ivec);
		if (mode & DES_DECRYPT && i == 0)
			memcpy(dvec.c, ivec, sizeof (des_block));

		if (DES_FAILED(stat))
			return (stat);

		m = (m == DES_ENCRYPT ? DES_DECRYPT : DES_ENCRYPT);

		if ((mode & DES_DECRYPT) || i != keynum - 1 || i%2)
			memcpy(ivec, svec.c, sizeof (des_block));
	}

	/*
	 * If we have an even number of keys then do an extra round of
	 * [en/de]cryption with the first key.
	 */
	if (keynum % 2 == 0)
		stat = cbc_crypt(keys[0].c, buf, len, mode, ivec);

	/* If were decrypting ivec is set from first decryption */
	if (mode & DES_DECRYPT)
		memcpy(ivec, dvec.c, sizeof (des_block));

	return (stat);
}


/*
 * DesN crypt packaged for use as a cipher entry
 */
static OM_uint32
__dh_desN_crypt(gss_buffer_t buf, dh_key_set_t keys, cipher_mode_t cipher_mode)
{
	int stat = DESERR_BADPARAM;
	int encrypt_flag = (cipher_mode == ENCIPHER);
	unsigned mode = (encrypt_flag ? DES_ENCRYPT : DES_DECRYPT) | DES_HW;
	des_block ivec;

	if (keys->dh_key_set_len < 1)
		return (DH_BADARG_FAILURE);

	/*
	 * We all ways start of with ivec set to zeros. There is no
	 * good way to maintain ivecs since packets could be out of sequence
	 * duplicated or worst of all lost. Under these conditions the
	 * higher level protocol would have to some how resync the ivecs
	 * on both sides and start again. Theres no mechanism for this in
	 * GSS.
	 */
	memset(&ivec, 0, sizeof (ivec));

	/* Do the encryption/decryption */
	stat = __desN_crypt(keys->dh_key_set_val, keys->dh_key_set_len,
			    (char *)buf->value, buf->length, mode, ivec.c);

	if (DES_FAILED(stat))
		return (DH_CIPHER_FAILURE);

	return (DH_SUCCESS);
}

/*
 * Package up plain des cbc crypt for use as a cipher entry.
 */
static OM_uint32
__dh_des_crypt(gss_buffer_t buf, dh_key_set_t keys, cipher_mode_t cipher_mode)
{
	int stat = DESERR_BADPARAM;
	int encrypt_flag = (cipher_mode == ENCIPHER);
	unsigned mode = (encrypt_flag ? DES_ENCRYPT : DES_DECRYPT) | DES_HW;
	des_block ivec;

	if (keys->dh_key_set_len < 1)
		return (DH_BADARG_FAILURE);

	/*  Set the ivec to zeros and then cbc crypt the result */
	memset(&ivec, 0, sizeof (ivec));
	stat = cbc_crypt(keys->dh_key_set_val[0].c, (char *)buf->value,
			buf->length, mode, ivec.c);

	if (DES_FAILED(stat))
		return (DH_CIPHER_FAILURE);

	return (DH_SUCCESS);
}

/*
 * MD5_verifier: This is a verifier routine suitable for use in a
 * verifier entry. It calculates the MD5 check sum over an optional
 * msg and a token. It signs it using the supplied cipher_proc and stores
 * the result in signature.
 *
 * Note signature should already be allocated and be large enough to
 * hold the signature after its been encrypted. If keys is null, then
 * we will just return the unencrypted check sum.
 */
static OM_uint32
MD5_verifier(gss_buffer_t tok, /* The buffer to sign */
	    gss_buffer_t msg, /* Optional buffer to include */
	    cipher_proc signer, /* Routine to encrypt the integrity check */
	    dh_key_set_t keys, /* Optiona keys to be used with the above */
	    dh_signature_t signature /* The resulting MIC */)
{
	MD5_CTX md5_ctx;	/* MD5 context */
	gss_buffer_desc buf;	/* GSS buffer to hold keys for cipher routine */

	/* Initialize the MD5 context */
	MD5Init(&md5_ctx);
	/* If we have a message to digest, digest it */
	if (msg)
	    MD5Update(&md5_ctx, (unsigned char *)msg->value, msg->length);
	/* Digest the supplied token */
	MD5Update(&md5_ctx, (unsigned char *)tok->value, tok->length);
	/* Finalize the sum. The MD5 context contains the digets */
	MD5Final(&md5_ctx);

	/* Copy the digest to the signature */
	memcpy(signature->dh_signature_val, (void *)md5_ctx.digest, 16);

	buf.length = signature->dh_signature_len;
	buf.value = signature->dh_signature_val;

	/* If we have keys encrypt it */
	if (keys != NULL)
		return (signer(&buf, keys, ENCIPHER));

	return (DH_SUCCESS);
}

/* Cipher table */
static
cipher_entry cipher_tab[] = {
	{ NULL, 1},
	{ __dh_desN_crypt, 8},
	{ __dh_des_crypt, 8}
};


#define	__NO_CRYPT	&cipher_tab[0]
#define	__DES_N_CRYPT	&cipher_tab[1]
#define	__DES_CRYPT	&cipher_tab[2]

/* Verifier table */
static
verifier_entry verifier_tab[] = {
	{ MD5_verifier, 16, __DES_N_CRYPT },
	{ MD5_verifier, 16, __DES_CRYPT }
};

/* QOP table */
static
QOP_entry QOP_table[] = {
	{ 0, &verifier_tab[0] },
	{ 0, &verifier_tab[1] }
};

#define	QOP_ENTRIES (sizeof (QOP_table) / sizeof (QOP_entry))

/*
 * __dh_is_valid_QOP: Return true if qop is valid entry into the QOP
 * table, else return false.
 */
bool_t
__dh_is_valid_QOP(dh_qop_t qop)
{
	bool_t is_valid = FALSE;

	is_valid = qop < QOP_ENTRIES;

	return (is_valid);
}

/*
 * __alloc_sig: Allocate a signature for a given QOP. This takes into
 * account the size of the signature after padding for the encryption
 * routine.
 */
OM_uint32
__alloc_sig(dh_qop_t qop, dh_signature_t sig)
{
	OM_uint32 stat = DH_VERIFIER_FAILURE;
	verifier_entry *v;

	/* Check that the QOP is valid */
	if (!__dh_is_valid_QOP(qop))
		return (DH_UNKNOWN_QOP);

	/* Get the verifier entry from the QOP entry */
	v = QOP_table[qop].verifier;

	/* Calulate the length needed for the signature */
	sig->dh_signature_len = cipher_pad(v->signer, v->size);

	/* Allocate the signature */
	sig->dh_signature_val = (void*)New(char, sig->dh_signature_len);
	if (sig->dh_signature_val == NULL) {
		sig->dh_signature_len = 0;
		return (DH_NOMEM_FAILURE);
	}

	stat = DH_SUCCESS;

	return (stat);
}

/*
 * __get_sig_size: Return the total size needed for a signature given a QOP.
 */
OM_uint32
__get_sig_size(dh_qop_t qop, unsigned int *size)
{
	/* Check for valid QOP */
	if (__dh_is_valid_QOP(qop)) {
		/* Get the verifier entry */
		verifier_t v = QOP_table[qop].verifier;

		/* Return the size include the padding needed for encryption */
		*size = v ? cipher_pad(v->signer, v->size) : 0;

		return (DH_SUCCESS);
	}
	*size = 0;

	return (DH_UNKNOWN_QOP);
}

/*
 * __mk_sig: Generate a signature using a given qop over a token of a
 * given length and an optional message. We use the supplied keys to
 * encrypt the check sum if they are available. The output is place
 * in a preallocate signature, that was allocated using __alloc_sig.
 */
OM_uint32
__mk_sig(dh_qop_t qop, /* The QOP to use */
	char *tok, /* The token to sign */
	long len, /* The tokens length */
	gss_buffer_t mesg,	/* An optional message to be included */
	dh_key_set_t keys, /* The optional encryption keys */
	dh_signature_t sig /* The resulting MIC */)
{
	OM_uint32 stat = DH_VERIFIER_FAILURE;


	verifier_entry *v;	/* Verifier entry */
	gss_buffer_desc buf;	/* Buffer to package tok */

	/* Make sure the QOP is valid */
	if (!__dh_is_valid_QOP(qop))
		return (DH_UNKNOWN_QOP);

	/* Grab the verifier entry for the qop */
	v = QOP_table[qop].verifier;

	/* Package the token for use in a verifier_proc */
	buf.length = len;
	buf.value = tok;

	/*
	 * Calculate the signature using the supplied keys. If keys
	 * is null, the the v->signer->cipher routine will not be called
	 * and sig will not be encrypted.
	 */
	stat = (*v->msg)(&buf, mesg, v->signer->cipher, keys, sig);

	return (stat);
}

/*
 * __verify_sig: Verify that the supplied signature, sig, is the same
 * as the token verifier
 */
OM_uint32
__verify_sig(dh_token_t token, /* The token to be verified */
	    dh_qop_t qop, /* The QOP to use */
	    dh_key_set_t keys, /* The context session keys */
	    dh_signature_t sig /* The signature from the serialized token */)
{
	OM_uint32 stat = DH_VERIFIER_FAILURE;

	cipher_proc cipher;	/* cipher routine to use */
	gss_buffer_desc buf;	/* Packaging for sig */

	/* Check the QOP */
	if (!__dh_is_valid_QOP(qop))
		return (DH_UNKNOWN_QOP);

	/* Package up the supplied signature */
	buf.length = sig->dh_signature_len;
	buf.value = sig->dh_signature_val;

	/* Get the cipher proc to use from the verifier entry for qop */
	cipher = QOP_table[qop].verifier->signer->cipher;

	/* Encrypt the check sum using the supplied set of keys */
	if ((stat = (*cipher)(&buf, keys, ENCIPHER)) != DH_SUCCESS)
		return (stat);

	/* Compare the signatures */
	if (__cmpsig(sig, &token->verifier))
		return (DH_SUCCESS);

	stat = DH_VERIFIER_MISMATCH;

	return (stat);
}

/*
 * __cmpsig: Return true if two signatures are the same, else false.
 */
bool_t
__cmpsig(dh_signature_t s1, dh_signature_t s2)
{
	return (s1->dh_signature_len == s2->dh_signature_len &&
	    memcmp(s1->dh_signature_val,
		s2->dh_signature_val, s1->dh_signature_len) == 0);
}

/*
 * wrap_msg_body: Wrap the message pointed to be in into a
 * message pointed to by out that has ben padded out by pad bytes.
 *
 * The output message looks like:
 * out->length = total length of out->value including any padding
 * out->value points to memory as follows:
 * +------------+-------------------------+---------|
 * | in->length | in->value               | XDR PAD |
 * +------------+-------------------------+---------|
 *    4 bytes      in->length bytes         0 - 3
 */
static OM_uint32
wrap_msg_body(gss_buffer_t in, gss_buffer_t out)
{
	XDR xdrs;			/* xdrs to wrap with */
	unsigned int len, out_len;	/* length  */
	size_t size;

	out->length = 0;
	out->value = 0;

	/* Make sure the address of len points to a 32 bit word */
	len = (unsigned int)in->length;
	if (len != in->length)
		return (DH_ENCODE_FAILURE);

	size = ((in->length + sizeof (OM_uint32) + 3)/4) * 4;
	out_len = size;
	if (out_len != size)
		return (DH_ENCODE_FAILURE);

	/* Allocate the output buffer and set the length */
	if ((out->value = (void *)New(char, len)) == NULL)
		return (DH_NOMEM_FAILURE);
	out->length = out_len;


	/* Create xdr stream to wrap into */
	xdrmem_create(&xdrs, out->value, out->length, XDR_ENCODE);

	/* Wrap the bytes in value */
	if (!xdr_bytes(&xdrs, (char **)&in->value, &len, len)) {
		__dh_release_buffer(out);
		return (DH_ENCODE_FAILURE);
	}

	return (DH_SUCCESS);
}

/*
 * __QOPSeal: Wrap the input message placing the output in output given
 * a valid QOP. If confidentialiy is requested it is ignored. We can't
 * support privacy. The return flag will always be zero.
 */
OM_uint32
__QOPSeal(dh_qop_t qop, /* The QOP to use */
	gss_buffer_t input, /* The buffer to wrap */
	int conf_req, /* Do we want privacy ? */
	dh_key_set_t keys, /* The session keys */
	gss_buffer_t output, /* The wraped message */
	int *conf_ret /* Did we encrypt it? */)
{
_NOTE(ARGUNUSED(conf_req,keys))
	OM_uint32 stat = DH_CIPHER_FAILURE;

	*conf_ret = FALSE;	/* No encryption allowed */

	/* Check for valid QOP */
	if (!__dh_is_valid_QOP(qop))
		return (DH_UNKNOWN_QOP);

	/* Wrap the message */
	if ((stat = wrap_msg_body(input, output))
	    != DH_SUCCESS)
		return (stat);

	return (stat);
}

/*
 * unwrap_msg_body: Unwrap the message, that was wrapped from above
 */
static OM_uint32
unwrap_msg_body(gss_buffer_t in, gss_buffer_t out)
{
	XDR xdrs;
	unsigned int len;	/* sizeof (len) == 32bits */

	/* Create an xdr stream to on wrap in */
	xdrmem_create(&xdrs, in->value, in->length, XDR_DECODE);

	/* Unwrap the input into out->value */
	if (!xdr_bytes(&xdrs, (char **)&out->value, &len, in->length))
		return (DH_DECODE_FAILURE);

	/* set the length */
	out->length = len;

	return (DH_SUCCESS);
}

/*
 * __QOPUnSeal: Unwrap the input message into output using the supplied QOP.
 * Note it is the callers responsibility to release the allocated output
 * buffer. If conf_req is true we return DH_CIPHER_FAILURE since we don't
 * support privacy.
 */
OM_uint32
__QOPUnSeal(dh_qop_t qop, /* The QOP to use */
	    gss_buffer_t input, /* The message to unwrap */
	    int conf_req, /* Is the message encrypted */
	    dh_key_set_t keys, /* The session keys to decrypt if conf_req */
	    gss_buffer_t output /* The unwraped message */)
{
_NOTE(ARGUNUSED(keys))
	OM_uint32 stat = DH_CIPHER_FAILURE;

	/* Check that the qop is valid */
	if (!__dh_is_valid_QOP(qop))
		return (DH_UNKNOWN_QOP);

	/* Set output to sane values */
	output->length = 0;
	output->value = NULL;

	/* Fail if this is privacy */
	if (conf_req)
		return (DH_CIPHER_FAILURE);

	/* Unwrap the input into the output, return the status */
	stat = unwrap_msg_body(input, output);

	return (stat);
}