Change krbCred member to reauthCred to better clarify purpose
[moonshot.git] / mech_eap / util_krb.c
1 /*
2  * Copyright (c) 2011, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32
33 /*
34  * Kerberos 5 helpers.
35  */
36
37 #include "gssapiP_eap.h"
38
39 static GSSEAP_THREAD_ONCE krbContextKeyOnce = GSSEAP_ONCE_INITIALIZER;
40 static GSSEAP_THREAD_KEY krbContextKey;
41
42 static void
43 destroyKrbContext(void *arg)
44 {
45     krb5_context context = (krb5_context)arg;
46
47     if (context != NULL)
48         krb5_free_context(context);
49 }
50
51 static void
52 createKrbContextKey(void)
53 {
54     GSSEAP_KEY_CREATE(&krbContextKey, destroyKrbContext);
55 }
56
57 static krb5_error_code
58 initKrbContext(krb5_context *pKrbContext)
59 {
60     krb5_context krbContext;
61     krb5_error_code code;
62     char *defaultRealm = NULL;
63
64     *pKrbContext = NULL;
65
66     code = krb5_init_context(&krbContext);
67     if (code != 0)
68         goto cleanup;
69
70     krb5_appdefault_string(krbContext, "eap_gss",
71                            NULL, "default_realm", "", &defaultRealm);
72
73     if (defaultRealm != NULL && defaultRealm[0] != '\0') {
74         code = krb5_set_default_realm(krbContext, defaultRealm);
75         if (code != 0)
76             goto cleanup;
77     }
78
79     *pKrbContext = krbContext;
80
81 cleanup:
82     if (code != 0 && krbContext != NULL)
83         krb5_free_context(krbContext);
84
85     if (defaultRealm != NULL)
86         GSSEAP_FREE(defaultRealm);
87
88     return code;
89 }
90
91 OM_uint32
92 gssEapKerberosInit(OM_uint32 *minor, krb5_context *context)
93 {
94     *minor = 0;
95
96     GSSEAP_ONCE(&krbContextKeyOnce, createKrbContextKey);
97
98     *context = GSSEAP_GETSPECIFIC(krbContextKey);
99     if (*context == NULL) {
100         *minor = initKrbContext(context);
101         if (*minor == 0) {
102             if (GSSEAP_SETSPECIFIC(krbContextKey, *context) != 0) {
103                 *minor = errno;
104                 krb5_free_context(*context);
105                 *context = NULL;
106             }
107         }
108     }
109
110     return *minor == 0 ? GSS_S_COMPLETE : GSS_S_FAILURE;
111 }
112
113 /*
114  * Derive a key K for RFC 4121 use by using the following
115  * derivation function (based on RFC 4402);
116  *
117  * KMSK = random-to-key(MSK)
118  * Tn = pseudo-random(KMSK, n || "rfc4121-gss-eap")
119  * L = output key size
120  * K = truncate(L, T1 || T2 || .. || Tn)
121  */
122 OM_uint32
123 gssEapDeriveRfc3961Key(OM_uint32 *minor,
124                        const unsigned char *inputKey,
125                        size_t inputKeyLength,
126                        krb5_enctype encryptionType,
127                        krb5_keyblock *pKey)
128 {
129     krb5_context krbContext;
130 #ifndef HAVE_HEIMDAL_VERSION
131     krb5_data data;
132 #endif
133     krb5_data ns, t, prfOut;
134     krb5_keyblock kd;
135     krb5_error_code code;
136     size_t randomLength, keyLength, prfLength;
137     unsigned char constant[4 + sizeof("rfc4121-gss-eap") - 1], *p;
138     ssize_t i, remain;
139
140     assert(encryptionType != ENCTYPE_NULL);
141
142     memset(pKey, 0, sizeof(*pKey));
143
144     GSSEAP_KRB_INIT(&krbContext);
145
146     KRB_KEY_INIT(&kd);
147     KRB_KEY_TYPE(&kd) = encryptionType;
148
149     t.data = NULL;
150     t.length = 0;
151
152     prfOut.data = NULL;
153     prfOut.length = 0;
154
155     code = krb5_c_keylengths(krbContext, encryptionType,
156                              &randomLength, &keyLength);
157     if (code != 0)
158         goto cleanup;
159
160     KRB_KEY_DATA(&kd) = GSSEAP_MALLOC(keyLength);
161     if (KRB_KEY_DATA(&kd) == NULL) {
162         code = ENOMEM;
163         goto cleanup;
164     }
165     KRB_KEY_LENGTH(&kd) = keyLength;
166
167     /* Convert MSK into a Kerberos key */
168 #ifdef HAVE_HEIMDAL_VERSION
169     code = krb5_random_to_key(krbContext, encryptionType, inputKey,
170                               MIN(inputKeyLength, randomLength), &kd);
171 #else
172     data.length = MIN(inputKeyLength, randomLength);
173     data.data = (char *)inputKey;
174
175     code = krb5_c_random_to_key(krbContext, encryptionType, &data, &kd);
176 #endif
177     if (code != 0)
178         goto cleanup;
179
180     memset(&constant[0], 0, 4);
181     memcpy(&constant[4], "rfc4121-gss-eap", sizeof("rfc4121-gss-eap") - 1);
182
183     ns.length = sizeof(constant);
184     ns.data = (char *)constant;
185
186     /* Plug derivation constant and key into PRF */
187     code = krb5_c_prf_length(krbContext, encryptionType, &prfLength);
188     if (code != 0)
189         goto cleanup;
190
191     t.length = prfLength;
192     t.data = GSSEAP_MALLOC(t.length);
193     if (t.data == NULL) {
194         code = ENOMEM;
195         goto cleanup;
196     }
197
198     prfOut.length = randomLength;
199     prfOut.data = GSSEAP_MALLOC(prfOut.length);
200     if (prfOut.data == NULL) {
201         code = ENOMEM;
202         goto cleanup;
203     }
204
205     for (i = 0, p = (unsigned char *)prfOut.data, remain = randomLength;
206          remain > 0;
207          p += t.length, remain -= t.length, i++)
208     {
209         store_uint32_be(i, ns.data);
210
211         code = krb5_c_prf(krbContext, &kd, &ns, &t);
212         if (code != 0)
213             goto cleanup;
214
215         memcpy(p, t.data, MIN(t.length, remain));
216      }
217
218     /* Finally, convert PRF output into a new key which we will return */
219 #ifdef HAVE_HEIMDAL_VERSION
220     code = krb5_random_to_key(krbContext, encryptionType,
221                               prfOut.data, prfOut.length, &kd);
222 #else
223     code = krb5_c_random_to_key(krbContext, encryptionType, &prfOut, &kd);
224 #endif
225     if (code != 0)
226         goto cleanup;
227
228     *pKey = kd;
229     KRB_KEY_DATA(&kd) = NULL;
230
231 cleanup:
232     if (KRB_KEY_DATA(&kd) != NULL) {
233         memset(KRB_KEY_DATA(&kd), 0, KRB_KEY_LENGTH(&kd));
234         GSSEAP_FREE(KRB_KEY_DATA(&kd));
235     }
236     if (t.data != NULL) {
237         memset(t.data, 0, t.length);
238         GSSEAP_FREE(t.data);
239     }
240     if (prfOut.data != NULL) {
241         memset(prfOut.data, 0, prfOut.length);
242         GSSEAP_FREE(prfOut.data);
243     }
244     *minor = code;
245     return (code == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
246 }
247
248 #ifdef HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE
249 extern krb5_error_code
250 krb5int_c_mandatory_cksumtype(krb5_context, krb5_enctype, krb5_cksumtype *);
251 #endif
252
253 OM_uint32
254 rfc3961ChecksumTypeForKey(OM_uint32 *minor,
255                           krb5_keyblock *key,
256                           krb5_cksumtype *cksumtype)
257 {
258     krb5_context krbContext;
259 #ifndef HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE
260     krb5_data data;
261     krb5_checksum cksum;
262 #endif
263
264     GSSEAP_KRB_INIT(&krbContext);
265
266 #ifdef HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE
267     *minor = krb5int_c_mandatory_cksumtype(krbContext, KRB_KEY_TYPE(key),
268                                            cksumtype);
269     if (*minor != 0)
270         return GSS_S_FAILURE;
271 #else
272     data.length = 0;
273     data.data = NULL;
274
275     memset(&cksum, 0, sizeof(cksum));
276
277     /*
278      * This is a complete hack but it's the only way to work with
279      * MIT Kerberos pre-1.9 without using private API, as it does
280      * not support passing in zero as the checksum type.
281      */
282     *minor = krb5_c_make_checksum(krbContext, 0, key, 0, &data, &cksum);
283     if (*minor != 0)
284         return GSS_S_FAILURE;
285
286 #ifdef HAVE_HEIMDAL_VERSION
287     *cksumtype = cksum.cksumtype;
288 #else
289     *cksumtype = cksum.checksum_type;
290 #endif
291
292     krb5_free_checksum_contents(krbContext, &cksum);
293 #endif /* HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE */
294
295     if (!krb5_c_is_keyed_cksum(*cksumtype)) {
296         *minor = KRB5KRB_AP_ERR_INAPP_CKSUM;
297         return GSS_S_FAILURE;
298     }
299
300     return GSS_S_COMPLETE;
301 }
302
303 krb5_error_code
304 krbCryptoLength(krb5_context krbContext,
305 #ifdef HAVE_HEIMDAL_VERSION
306                 krb5_crypto krbCrypto,
307 #else
308                 krb5_keyblock *key,
309 #endif
310                 int type,
311                 size_t *length)
312 {
313 #ifdef HAVE_HEIMDAL_VERSION
314     return krb5_crypto_length(krbContext, krbCrypto, type, length);
315 #else
316     unsigned int len;
317     krb5_error_code code;
318
319     code = krb5_c_crypto_length(krbContext, KRB_KEY_TYPE(key), type, &len);
320     if (code == 0)
321         *length = (size_t)len;
322
323     return code;
324 #endif
325 }
326
327 krb5_error_code
328 krbPaddingLength(krb5_context krbContext,
329 #ifdef HAVE_HEIMDAL_VERSION
330                  krb5_crypto krbCrypto,
331 #else
332                  krb5_keyblock *key,
333 #endif
334                  size_t dataLength,
335                  size_t *padLength)
336 {
337     krb5_error_code code;
338 #ifdef HAVE_HEIMDAL_VERSION
339     size_t headerLength, paddingLength;
340
341     code = krbCryptoLength(krbContext, krbCrypto,
342                            KRB5_CRYPTO_TYPE_HEADER, &headerLength);
343     if (code != 0)
344         return code;
345
346     dataLength += headerLength;
347
348     code = krb5_crypto_length(krbContext, krbCrypto,
349                               KRB5_CRYPTO_TYPE_PADDING, &paddingLength);
350     if (code != 0)
351         return code;
352
353     if (paddingLength != 0 && (dataLength % paddingLength) != 0)
354         *padLength = paddingLength - (dataLength % paddingLength);
355     else
356         *padLength = 0;
357
358     return 0;
359 #else
360     unsigned int pad;
361
362     code = krb5_c_padding_length(krbContext, KRB_KEY_TYPE(key), dataLength, &pad);
363     if (code == 0)
364         *padLength = (size_t)pad;
365
366     return code;
367 #endif /* HAVE_HEIMDAL_VERSION */
368 }
369
370 krb5_error_code
371 krbBlockSize(krb5_context krbContext,
372 #ifdef HAVE_HEIMDAL_VERSION
373                  krb5_crypto krbCrypto,
374 #else
375                  krb5_keyblock *key,
376 #endif
377                  size_t *blockSize)
378 {
379 #ifdef HAVE_HEIMDAL_VERSION
380     return krb5_crypto_getblocksize(krbContext, krbCrypto, blockSize);
381 #else
382     return krb5_c_block_size(krbContext, KRB_KEY_TYPE(key), blockSize);
383 #endif
384 }
385
386 krb5_error_code
387 krbEnctypeToString(
388 #ifdef HAVE_HEIMDAL_VERSION
389                    krb5_context krbContext,
390 #else
391                    krb5_context krbContext GSSEAP_UNUSED,
392 #endif
393                    krb5_enctype enctype,
394                    const char *prefix,
395                    gss_buffer_t string)
396 {
397     krb5_error_code code;
398 #ifdef HAVE_HEIMDAL_VERSION
399     char *enctypeBuf = NULL;
400 #else
401     char enctypeBuf[128];
402 #endif
403     size_t prefixLength, enctypeLength;
404
405 #ifdef HAVE_HEIMDAL_VERSION
406     code = krb5_enctype_to_string(krbContext, enctype, &enctypeBuf);
407 #else
408     code = krb5_enctype_to_name(enctype, 0, enctypeBuf, sizeof(enctypeBuf));
409 #endif
410     if (code != 0)
411         return code;
412
413     prefixLength = (prefix != NULL) ? strlen(prefix) : 0;
414     enctypeLength = strlen(enctypeBuf);
415
416     string->value = GSSEAP_MALLOC(prefixLength + enctypeLength + 1);
417     if (string->value == NULL) {
418 #ifdef HAVE_HEIMDAL_VERSION
419         krb5_xfree(enctypeBuf);
420 #endif
421         return ENOMEM;
422     }
423
424     if (prefixLength != 0)
425         memcpy(string->value, prefix, prefixLength);
426     memcpy((char *)string->value + prefixLength, enctypeBuf, enctypeLength);
427
428     string->length = prefixLength + enctypeLength;
429     ((char *)string->value)[string->length] = '\0';
430
431 #ifdef HAVE_HEIMDAL_VERSION
432     krb5_xfree(enctypeBuf);
433 #endif
434
435     return 0;
436 }
437
438 krb5_error_code
439 krbMakeAuthDataKdcIssued(krb5_context context,
440                          const krb5_keyblock *key,
441                          krb5_const_principal issuer,
442 #ifdef HAVE_HEIMDAL_VERSION
443                          const AuthorizationData *authdata,
444                          AuthorizationData *adKdcIssued
445 #else
446                          krb5_authdata *const *authdata,
447                          krb5_authdata ***adKdcIssued
448 #endif
449                          )
450 {
451 #ifdef HAVE_HEIMDAL_VERSION
452     krb5_error_code code;
453     AD_KDCIssued kdcIssued;
454     AuthorizationDataElement adDatum;
455     unsigned char *buf;
456     size_t buf_size, len;
457     krb5_crypto crypto = NULL;
458
459     memset(&kdcIssued, 0, sizeof(kdcIssued));
460     memset(adKdcIssued, 0, sizeof(*adKdcIssued));
461
462     kdcIssued.i_realm = issuer->realm != NULL ? (Realm *)&issuer->realm : NULL;
463     kdcIssued.i_sname = (PrincipalName *)&issuer->name;
464     kdcIssued.elements = *authdata;
465
466     ASN1_MALLOC_ENCODE(AuthorizationData, buf, buf_size, authdata, &len, code);
467     if (code != 0)
468         goto cleanup;
469
470     code = krb5_crypto_init(context, key, 0, &crypto);
471     if (code != 0)
472         goto cleanup;
473
474     code = krb5_create_checksum(context, crypto, KRB5_KU_AD_KDC_ISSUED,
475                                 0, buf, buf_size, &kdcIssued.ad_checksum);
476     if (code != 0)
477         goto cleanup;
478
479     GSSEAP_FREE(buf);
480     buf = NULL;
481
482     ASN1_MALLOC_ENCODE(AD_KDCIssued, buf, buf_size, &kdcIssued, &len, code);
483     if (code != 0)
484         goto cleanup;
485
486     adDatum.ad_type = KRB5_AUTHDATA_KDC_ISSUED;
487     adDatum.ad_data.length = buf_size;
488     adDatum.ad_data.data = buf;
489
490     code = add_AuthorizationData(adKdcIssued, &adDatum);
491     if (code != 0)
492         goto cleanup;
493
494 cleanup:
495     if (buf != NULL)
496         GSSEAP_FREE(buf);
497     if (crypto != NULL)
498         krb5_crypto_destroy(context, crypto);
499     free_Checksum(&kdcIssued.ad_checksum);
500
501     return code;
502 #else
503     return krb5_make_authdata_kdc_issued(context, key, issuer, authdata,
504                                          adKdcIssued);
505 #endif /* HAVE_HEIMDAL_VERSION */
506 }
507
508 krb5_error_code
509 krbMakeCred(krb5_context krbContext,
510             krb5_auth_context authContext,
511             krb5_creds *creds,
512             krb5_data *data)
513 {
514     krb5_error_code code;
515 #ifdef HAVE_HEIMDAL_VERSION
516     KRB_CRED krbCred;
517     KrbCredInfo krbCredInfo;
518     EncKrbCredPart encKrbCredPart;
519     krb5_keyblock *key;
520     krb5_crypto krbCrypto = NULL;
521     krb5_data encKrbCredPartData;
522     krb5_replay_data rdata;
523     size_t len;
524 #else
525     krb5_data *d = NULL;
526 #endif
527
528     memset(data, 0, sizeof(*data));
529 #ifdef HAVE_HEIMDAL_VERSION
530     memset(&krbCred,        0, sizeof(krbCred));
531     memset(&krbCredInfo,    0, sizeof(krbCredInfo));
532     memset(&encKrbCredPart, 0, sizeof(encKrbCredPart));
533     memset(&rdata,          0, sizeof(rdata));
534
535     if (authContext->local_subkey)
536         key = authContext->local_subkey;
537     else if (authContext->remote_subkey)
538         key = authContext->remote_subkey;
539     else
540         key = authContext->keyblock;
541
542     krbCred.pvno = 5;
543     krbCred.msg_type = krb_cred;
544     krbCred.tickets.val = (Ticket *)GSSEAP_CALLOC(1, sizeof(Ticket));
545     if (krbCred.tickets.val == NULL) {
546         code = ENOMEM;
547         goto cleanup;
548     }
549     krbCred.tickets.len = 1;
550
551     code = decode_Ticket(creds->ticket.data,
552                          creds->ticket.length,
553                          krbCred.tickets.val, &len);
554     if (code != 0)
555         goto cleanup;
556
557     krbCredInfo.key         = creds->session;
558     krbCredInfo.prealm      = &creds->client->realm;
559     krbCredInfo.pname       = &creds->client->name;
560     krbCredInfo.flags       = &creds->flags.b;
561     krbCredInfo.authtime    = &creds->times.authtime;
562     krbCredInfo.starttime   = &creds->times.starttime;
563     krbCredInfo.endtime     = &creds->times.endtime;
564     krbCredInfo.renew_till  = &creds->times.renew_till;
565     krbCredInfo.srealm      = &creds->server->realm;
566     krbCredInfo.sname       = &creds->server->name;
567     krbCredInfo.caddr       = creds->addresses.len ? &creds->addresses : NULL;
568
569     encKrbCredPart.ticket_info.len = 1;
570     encKrbCredPart.ticket_info.val = &krbCredInfo;
571     if (authContext->flags & KRB5_AUTH_CONTEXT_DO_SEQUENCE) {
572         rdata.seq                  = authContext->local_seqnumber;
573         encKrbCredPart.nonce       = (int32_t *)&rdata.seq;
574     } else {
575         encKrbCredPart.nonce       = NULL;
576     }
577     if (authContext->flags & KRB5_AUTH_CONTEXT_DO_TIME) {
578         krb5_us_timeofday(krbContext, &rdata.timestamp, &rdata.usec);
579         encKrbCredPart.timestamp   = &rdata.timestamp;
580         encKrbCredPart.usec        = &rdata.usec;
581     } else {
582         encKrbCredPart.timestamp   = NULL;
583         encKrbCredPart.usec        = NULL;
584     }
585     encKrbCredPart.s_address       = authContext->local_address;
586     encKrbCredPart.r_address       = authContext->remote_address;
587
588     ASN1_MALLOC_ENCODE(EncKrbCredPart, encKrbCredPartData.data,
589                        encKrbCredPartData.length, &encKrbCredPart,
590                        &len, code);
591     if (code != 0)
592         goto cleanup;
593
594     code = krb5_crypto_init(krbContext, key, 0, &krbCrypto);
595     if (code != 0)
596         goto cleanup;
597
598     code = krb5_encrypt_EncryptedData(krbContext,
599                                       krbCrypto,
600                                       KRB5_KU_KRB_CRED,
601                                       encKrbCredPartData.data,
602                                       encKrbCredPartData.length,
603                                       0,
604                                       &krbCred.enc_part);
605     if (code != 0)
606         goto cleanup;
607
608     ASN1_MALLOC_ENCODE(KRB_CRED, data->data, data->length,
609                        &krbCred, &len, code);
610     if (code != 0)
611         goto cleanup;
612
613     if (authContext->flags & KRB5_AUTH_CONTEXT_DO_SEQUENCE)
614         authContext->local_seqnumber++;
615
616 cleanup:
617     if (krbCrypto != NULL)
618         krb5_crypto_destroy(krbContext, krbCrypto);
619     free_KRB_CRED(&krbCred);
620     krb5_data_free(&encKrbCredPartData);
621
622     return code;
623 #else
624     code = krb5_mk_1cred(krbContext, authContext, creds, &d, NULL);
625     if (code == 0) {
626         *data = *d;
627         GSSEAP_FREE(d);
628     }
629
630     return code;
631 #endif /* HAVE_HEIMDAL_VERSION */
632 }