Merge branch 'master' of ssh://moonshot.suchdamage.org:822/srv/git/moonshot
[mech_eap.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 void
40 gssEapDestroyKrbContext(krb5_context context)
41 {
42     if (context != NULL)
43         krb5_free_context(context);
44 }
45
46 static krb5_error_code
47 initKrbContext(krb5_context *pKrbContext)
48 {
49     krb5_context krbContext;
50     krb5_error_code code;
51     char *defaultRealm = NULL;
52
53     *pKrbContext = NULL;
54
55     code = krb5_init_context(&krbContext);
56     if (code != 0)
57         goto cleanup;
58
59     krb5_appdefault_string(krbContext, "eap_gss",
60                            NULL, "default_realm", "", &defaultRealm);
61
62     if (defaultRealm != NULL && defaultRealm[0] != '\0') {
63         code = krb5_set_default_realm(krbContext, defaultRealm);
64         if (code != 0)
65             goto cleanup;
66     }
67
68     *pKrbContext = krbContext;
69
70 cleanup:
71     krb5_free_default_realm(krbContext, defaultRealm);
72
73     if (code != 0 && krbContext != NULL)
74         krb5_free_context(krbContext);
75
76     return code;
77 }
78
79 OM_uint32
80 gssEapKerberosInit(OM_uint32 *minor, krb5_context *context)
81 {
82     struct gss_eap_thread_local_data *tld;
83
84     *minor = 0;
85     *context = NULL;
86
87     tld = gssEapGetThreadLocalData();
88     if (tld != NULL) {
89         if (tld->krbContext == NULL) {
90             *minor = initKrbContext(&tld->krbContext);
91             if (*minor != 0)
92                 tld->krbContext = NULL;
93         }
94         *context = tld->krbContext;
95     } else {
96         *minor = GSSEAP_GET_LAST_ERROR();
97     }
98
99     GSSEAP_ASSERT(*context != NULL || *minor != 0);
100
101     return (*minor == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
102 }
103
104 /*
105  * Derive a key K for RFC 4121 use by using the following
106  * derivation function (based on RFC 4402);
107  *
108  * KMSK = random-to-key(MSK)
109  * Tn = pseudo-random(KMSK, n || "rfc4121-gss-eap")
110  * L = output key size
111  * K = truncate(L, T1 || T2 || .. || Tn)
112  *
113  * The output must be freed by krb5_free_keyblock_contents(),
114  * not GSSEAP_FREE().
115  */
116 OM_uint32
117 gssEapDeriveRfc3961Key(OM_uint32 *minor,
118                        const unsigned char *inputKey,
119                        size_t inputKeyLength,
120                        krb5_enctype encryptionType,
121                        krb5_keyblock *pKey)
122 {
123     krb5_context krbContext;
124 #ifndef HAVE_HEIMDAL_VERSION
125     krb5_data data;
126 #endif
127     krb5_data ns, t, derivedKeyData;
128     krb5_keyblock kd;
129     krb5_error_code code;
130     size_t randomLength, keyLength, prfLength;
131     unsigned char constant[4 + sizeof("rfc4121-gss-eap") - 1], *p;
132     ssize_t i, remain;
133
134     GSSEAP_KRB_INIT(&krbContext);
135     GSSEAP_ASSERT(encryptionType != ENCTYPE_NULL);
136
137     KRB_KEY_INIT(pKey);
138     KRB_KEY_INIT(&kd);
139     KRB_KEY_TYPE(&kd) = encryptionType;
140
141     KRB_DATA_INIT(&ns);
142     KRB_DATA_INIT(&t);
143     KRB_DATA_INIT(&derivedKeyData);
144
145     code = krb5_c_keylengths(krbContext, encryptionType,
146                              &randomLength, &keyLength);
147     if (code != 0)
148         goto cleanup;
149
150     /* Convert EAP MSK into a Kerberos key */
151
152 #ifdef HAVE_HEIMDAL_VERSION
153     code = krb5_random_to_key(krbContext, encryptionType, inputKey,
154                               MIN(inputKeyLength, randomLength), &kd);
155 #else
156     data.length = MIN(inputKeyLength, randomLength);
157     data.data = (char *)inputKey;
158
159     KRB_KEY_DATA(&kd) = KRB_MALLOC(keyLength);
160     if (KRB_KEY_DATA(&kd) == NULL) {
161         code = ENOMEM;
162         goto cleanup;
163     }
164     KRB_KEY_LENGTH(&kd) = keyLength;
165
166     code = krb5_c_random_to_key(krbContext, encryptionType, &data, &kd);
167 #endif /* HAVE_HEIMDAL_VERSION */
168     if (code != 0)
169         goto cleanup;
170
171     memset(&constant[0], 0, 4);
172     memcpy(&constant[4], "rfc4121-gss-eap", sizeof("rfc4121-gss-eap") - 1);
173
174     ns.length = sizeof(constant);
175     ns.data = (char *)constant;
176
177     /* Plug derivation constant and key into PRF */
178     code = krb5_c_prf_length(krbContext, encryptionType, &prfLength);
179     if (code != 0)
180         goto cleanup;
181
182 #ifndef HAVE_HEIMDAL_VERSION
183     /* Same API, but different allocation rules, unfortunately. */
184     t.length = prfLength;
185     t.data = GSSEAP_MALLOC(t.length);
186     if (t.data == NULL) {
187         code = ENOMEM;
188         goto cleanup;
189     }
190 #endif
191
192     derivedKeyData.length = randomLength;
193     derivedKeyData.data = GSSEAP_MALLOC(derivedKeyData.length);
194     if (derivedKeyData.data == NULL) {
195         code = ENOMEM;
196         goto cleanup;
197     }
198
199     for (i = 0, p = (unsigned char *)derivedKeyData.data, remain = randomLength;
200          remain > 0;
201          p += t.length, remain -= t.length, i++)
202     {
203         store_uint32_be(i, ns.data);
204
205         code = krb5_c_prf(krbContext, &kd, &ns, &t);
206         if (code != 0)
207             goto cleanup;
208
209         memcpy(p, t.data, MIN(t.length, remain));
210      }
211
212     /* Finally, convert PRF output into a new key which we will return */
213 #ifdef HAVE_HEIMDAL_VERSION
214     krb5_free_keyblock_contents(krbContext, &kd);
215     KRB_KEY_INIT(&kd);
216
217     code = krb5_random_to_key(krbContext, encryptionType,
218                               derivedKeyData.data, derivedKeyData.length, &kd);
219 #else
220     code = krb5_c_random_to_key(krbContext, encryptionType,
221                                 &derivedKeyData, &kd);
222 #endif
223     if (code != 0)
224         goto cleanup;
225
226     *pKey = kd;
227
228 cleanup:
229     if (code != 0)
230         krb5_free_keyblock_contents(krbContext, &kd);
231 #ifdef HAVE_HEIMDAL_VERSION
232     krb5_free_data_contents(krbContext, &t);
233 #else
234     if (t.data != NULL) {
235         memset(t.data, 0, t.length);
236         GSSEAP_FREE(t.data);
237     }
238 #endif
239     if (derivedKeyData.data != NULL) {
240         memset(derivedKeyData.data, 0, derivedKeyData.length);
241         GSSEAP_FREE(derivedKeyData.data);
242     }
243
244     *minor = code;
245
246     return (code == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
247 }
248
249 #ifdef HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE
250 extern krb5_error_code
251 krb5int_c_mandatory_cksumtype(krb5_context, krb5_enctype, krb5_cksumtype *);
252 #endif
253
254 OM_uint32
255 rfc3961ChecksumTypeForKey(OM_uint32 *minor,
256                           krb5_keyblock *key,
257                           krb5_cksumtype *cksumtype)
258 {
259     krb5_context krbContext;
260 #ifndef HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE
261     krb5_data data;
262     krb5_checksum cksum;
263 #endif
264
265     GSSEAP_KRB_INIT(&krbContext);
266
267 #ifdef HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE
268     *minor = krb5int_c_mandatory_cksumtype(krbContext, KRB_KEY_TYPE(key),
269                                            cksumtype);
270     if (*minor != 0)
271         return GSS_S_FAILURE;
272 #else
273     KRB_DATA_INIT(&data);
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 = (OM_uint32)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     free(buf); /* match ASN1_MALLOC_ENCODE */
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         free(buf); /* match ASN1_MALLOC_ENCODE */
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 }