use RFC3961 checksums for CB/exts MIC
[moonshot.git] / moonshot / 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     *cksumtype = KRB_CHECKSUM_TYPE(&cksum);
287
288     krb5_free_checksum_contents(krbContext, &cksum);
289 #endif /* HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE */
290
291     if (!krb5_c_is_keyed_cksum(*cksumtype)) {
292         *minor = (OM_uint32)KRB5KRB_AP_ERR_INAPP_CKSUM;
293         return GSS_S_FAILURE;
294     }
295
296     return GSS_S_COMPLETE;
297 }
298
299 krb5_error_code
300 krbCryptoLength(krb5_context krbContext,
301 #ifdef HAVE_HEIMDAL_VERSION
302                 krb5_crypto krbCrypto,
303 #else
304                 krb5_keyblock *key,
305 #endif
306                 int type,
307                 size_t *length)
308 {
309 #ifdef HAVE_HEIMDAL_VERSION
310     return krb5_crypto_length(krbContext, krbCrypto, type, length);
311 #else
312     unsigned int len;
313     krb5_error_code code;
314
315     code = krb5_c_crypto_length(krbContext, KRB_KEY_TYPE(key), type, &len);
316     if (code == 0)
317         *length = (size_t)len;
318
319     return code;
320 #endif
321 }
322
323 krb5_error_code
324 krbPaddingLength(krb5_context krbContext,
325 #ifdef HAVE_HEIMDAL_VERSION
326                  krb5_crypto krbCrypto,
327 #else
328                  krb5_keyblock *key,
329 #endif
330                  size_t dataLength,
331                  size_t *padLength)
332 {
333     krb5_error_code code;
334 #ifdef HAVE_HEIMDAL_VERSION
335     size_t headerLength, paddingLength;
336
337     code = krbCryptoLength(krbContext, krbCrypto,
338                            KRB5_CRYPTO_TYPE_HEADER, &headerLength);
339     if (code != 0)
340         return code;
341
342     dataLength += headerLength;
343
344     code = krb5_crypto_length(krbContext, krbCrypto,
345                               KRB5_CRYPTO_TYPE_PADDING, &paddingLength);
346     if (code != 0)
347         return code;
348
349     if (paddingLength != 0 && (dataLength % paddingLength) != 0)
350         *padLength = paddingLength - (dataLength % paddingLength);
351     else
352         *padLength = 0;
353
354     return 0;
355 #else
356     unsigned int pad;
357
358     code = krb5_c_padding_length(krbContext, KRB_KEY_TYPE(key), dataLength, &pad);
359     if (code == 0)
360         *padLength = (size_t)pad;
361
362     return code;
363 #endif /* HAVE_HEIMDAL_VERSION */
364 }
365
366 krb5_error_code
367 krbBlockSize(krb5_context krbContext,
368 #ifdef HAVE_HEIMDAL_VERSION
369                  krb5_crypto krbCrypto,
370 #else
371                  krb5_keyblock *key,
372 #endif
373                  size_t *blockSize)
374 {
375 #ifdef HAVE_HEIMDAL_VERSION
376     return krb5_crypto_getblocksize(krbContext, krbCrypto, blockSize);
377 #else
378     return krb5_c_block_size(krbContext, KRB_KEY_TYPE(key), blockSize);
379 #endif
380 }
381
382 krb5_error_code
383 krbEnctypeToString(
384 #ifdef HAVE_HEIMDAL_VERSION
385                    krb5_context krbContext,
386 #else
387                    krb5_context krbContext GSSEAP_UNUSED,
388 #endif
389                    krb5_enctype enctype,
390                    const char *prefix,
391                    gss_buffer_t string)
392 {
393     krb5_error_code code;
394 #ifdef HAVE_HEIMDAL_VERSION
395     char *enctypeBuf = NULL;
396 #else
397     char enctypeBuf[128];
398 #endif
399     size_t prefixLength, enctypeLength;
400
401 #ifdef HAVE_HEIMDAL_VERSION
402     code = krb5_enctype_to_string(krbContext, enctype, &enctypeBuf);
403 #else
404     code = krb5_enctype_to_name(enctype, 0, enctypeBuf, sizeof(enctypeBuf));
405 #endif
406     if (code != 0)
407         return code;
408
409     prefixLength = (prefix != NULL) ? strlen(prefix) : 0;
410     enctypeLength = strlen(enctypeBuf);
411
412     string->value = GSSEAP_MALLOC(prefixLength + enctypeLength + 1);
413     if (string->value == NULL) {
414 #ifdef HAVE_HEIMDAL_VERSION
415         krb5_xfree(enctypeBuf);
416 #endif
417         return ENOMEM;
418     }
419
420     if (prefixLength != 0)
421         memcpy(string->value, prefix, prefixLength);
422     memcpy((char *)string->value + prefixLength, enctypeBuf, enctypeLength);
423
424     string->length = prefixLength + enctypeLength;
425     ((char *)string->value)[string->length] = '\0';
426
427 #ifdef HAVE_HEIMDAL_VERSION
428     krb5_xfree(enctypeBuf);
429 #endif
430
431     return 0;
432 }
433
434 krb5_error_code
435 krbMakeAuthDataKdcIssued(krb5_context context,
436                          const krb5_keyblock *key,
437                          krb5_const_principal issuer,
438 #ifdef HAVE_HEIMDAL_VERSION
439                          const AuthorizationData *authdata,
440                          AuthorizationData *adKdcIssued
441 #else
442                          krb5_authdata *const *authdata,
443                          krb5_authdata ***adKdcIssued
444 #endif
445                          )
446 {
447 #ifdef HAVE_HEIMDAL_VERSION
448     krb5_error_code code;
449     AD_KDCIssued kdcIssued;
450     AuthorizationDataElement adDatum;
451     unsigned char *buf;
452     size_t buf_size, len;
453     krb5_crypto crypto = NULL;
454
455     memset(&kdcIssued, 0, sizeof(kdcIssued));
456     memset(adKdcIssued, 0, sizeof(*adKdcIssued));
457
458     kdcIssued.i_realm = issuer->realm != NULL ? (Realm *)&issuer->realm : NULL;
459     kdcIssued.i_sname = (PrincipalName *)&issuer->name;
460     kdcIssued.elements = *authdata;
461
462     ASN1_MALLOC_ENCODE(AuthorizationData, buf, buf_size, authdata, &len, code);
463     if (code != 0)
464         goto cleanup;
465
466     code = krb5_crypto_init(context, key, 0, &crypto);
467     if (code != 0)
468         goto cleanup;
469
470     code = krb5_create_checksum(context, crypto, KRB5_KU_AD_KDC_ISSUED,
471                                 0, buf, buf_size, &kdcIssued.ad_checksum);
472     if (code != 0)
473         goto cleanup;
474
475     free(buf); /* match ASN1_MALLOC_ENCODE */
476     buf = NULL;
477
478     ASN1_MALLOC_ENCODE(AD_KDCIssued, buf, buf_size, &kdcIssued, &len, code);
479     if (code != 0)
480         goto cleanup;
481
482     adDatum.ad_type = KRB5_AUTHDATA_KDC_ISSUED;
483     adDatum.ad_data.length = buf_size;
484     adDatum.ad_data.data = buf;
485
486     code = add_AuthorizationData(adKdcIssued, &adDatum);
487     if (code != 0)
488         goto cleanup;
489
490 cleanup:
491     if (buf != NULL)
492         free(buf); /* match ASN1_MALLOC_ENCODE */
493     if (crypto != NULL)
494         krb5_crypto_destroy(context, crypto);
495     free_Checksum(&kdcIssued.ad_checksum);
496
497     return code;
498 #else
499     return krb5_make_authdata_kdc_issued(context, key, issuer, authdata,
500                                          adKdcIssued);
501 #endif /* HAVE_HEIMDAL_VERSION */
502 }
503
504 krb5_error_code
505 krbMakeCred(krb5_context krbContext,
506             krb5_auth_context authContext,
507             krb5_creds *creds,
508             krb5_data *data)
509 {
510     krb5_error_code code;
511 #ifdef HAVE_HEIMDAL_VERSION
512     KRB_CRED krbCred;
513     KrbCredInfo krbCredInfo;
514     EncKrbCredPart encKrbCredPart;
515     krb5_keyblock *key;
516     krb5_crypto krbCrypto = NULL;
517     krb5_data encKrbCredPartData;
518     krb5_replay_data rdata;
519     size_t len;
520 #else
521     krb5_data *d = NULL;
522 #endif
523
524     memset(data, 0, sizeof(*data));
525 #ifdef HAVE_HEIMDAL_VERSION
526     memset(&krbCred,        0, sizeof(krbCred));
527     memset(&krbCredInfo,    0, sizeof(krbCredInfo));
528     memset(&encKrbCredPart, 0, sizeof(encKrbCredPart));
529     memset(&rdata,          0, sizeof(rdata));
530
531     if (authContext->local_subkey)
532         key = authContext->local_subkey;
533     else if (authContext->remote_subkey)
534         key = authContext->remote_subkey;
535     else
536         key = authContext->keyblock;
537
538     krbCred.pvno = 5;
539     krbCred.msg_type = krb_cred;
540     krbCred.tickets.val = (Ticket *)GSSEAP_CALLOC(1, sizeof(Ticket));
541     if (krbCred.tickets.val == NULL) {
542         code = ENOMEM;
543         goto cleanup;
544     }
545     krbCred.tickets.len = 1;
546
547     code = decode_Ticket(creds->ticket.data,
548                          creds->ticket.length,
549                          krbCred.tickets.val, &len);
550     if (code != 0)
551         goto cleanup;
552
553     krbCredInfo.key         = creds->session;
554     krbCredInfo.prealm      = &creds->client->realm;
555     krbCredInfo.pname       = &creds->client->name;
556     krbCredInfo.flags       = &creds->flags.b;
557     krbCredInfo.authtime    = &creds->times.authtime;
558     krbCredInfo.starttime   = &creds->times.starttime;
559     krbCredInfo.endtime     = &creds->times.endtime;
560     krbCredInfo.renew_till  = &creds->times.renew_till;
561     krbCredInfo.srealm      = &creds->server->realm;
562     krbCredInfo.sname       = &creds->server->name;
563     krbCredInfo.caddr       = creds->addresses.len ? &creds->addresses : NULL;
564
565     encKrbCredPart.ticket_info.len = 1;
566     encKrbCredPart.ticket_info.val = &krbCredInfo;
567     if (authContext->flags & KRB5_AUTH_CONTEXT_DO_SEQUENCE) {
568         rdata.seq                  = authContext->local_seqnumber;
569         encKrbCredPart.nonce       = (int32_t *)&rdata.seq;
570     } else {
571         encKrbCredPart.nonce       = NULL;
572     }
573     if (authContext->flags & KRB5_AUTH_CONTEXT_DO_TIME) {
574         krb5_us_timeofday(krbContext, &rdata.timestamp, &rdata.usec);
575         encKrbCredPart.timestamp   = &rdata.timestamp;
576         encKrbCredPart.usec        = &rdata.usec;
577     } else {
578         encKrbCredPart.timestamp   = NULL;
579         encKrbCredPart.usec        = NULL;
580     }
581     encKrbCredPart.s_address       = authContext->local_address;
582     encKrbCredPart.r_address       = authContext->remote_address;
583
584     ASN1_MALLOC_ENCODE(EncKrbCredPart, encKrbCredPartData.data,
585                        encKrbCredPartData.length, &encKrbCredPart,
586                        &len, code);
587     if (code != 0)
588         goto cleanup;
589
590     code = krb5_crypto_init(krbContext, key, 0, &krbCrypto);
591     if (code != 0)
592         goto cleanup;
593
594     code = krb5_encrypt_EncryptedData(krbContext,
595                                       krbCrypto,
596                                       KRB5_KU_KRB_CRED,
597                                       encKrbCredPartData.data,
598                                       encKrbCredPartData.length,
599                                       0,
600                                       &krbCred.enc_part);
601     if (code != 0)
602         goto cleanup;
603
604     ASN1_MALLOC_ENCODE(KRB_CRED, data->data, data->length,
605                        &krbCred, &len, code);
606     if (code != 0)
607         goto cleanup;
608
609     if (authContext->flags & KRB5_AUTH_CONTEXT_DO_SEQUENCE)
610         authContext->local_seqnumber++;
611
612 cleanup:
613     if (krbCrypto != NULL)
614         krb5_crypto_destroy(krbContext, krbCrypto);
615     free_KRB_CRED(&krbCred);
616     krb5_data_free(&encKrbCredPartData);
617
618     return code;
619 #else
620     code = krb5_mk_1cred(krbContext, authContext, creds, &d, NULL);
621     if (code == 0) {
622         *data = *d;
623         GSSEAP_FREE(d);
624     }
625
626     return code;
627 #endif /* HAVE_HEIMDAL_VERSION */
628 }