cast to match signedness
[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     if (code != 0 && krbContext != NULL)
72         krb5_free_context(krbContext);
73
74     if (defaultRealm != NULL)
75         GSSEAP_FREE(defaultRealm);
76
77     return code;
78 }
79
80 OM_uint32
81 gssEapKerberosInit(OM_uint32 *minor, krb5_context *context)
82 {
83     struct gss_eap_thread_local_data *tld;
84
85     *minor = 0;
86
87     tld = gssEapGetThreadLocalData();
88     if (tld != NULL) {
89         *context = tld->krbContext;
90         if (*context == NULL) {
91             *minor = initKrbContext(context);
92             if (*minor == 0)
93                 tld->krbContext = *context;
94         }
95     }
96
97     return *minor == 0 ? GSS_S_COMPLETE : GSS_S_FAILURE;
98 }
99
100 /*
101  * Derive a key K for RFC 4121 use by using the following
102  * derivation function (based on RFC 4402);
103  *
104  * KMSK = random-to-key(MSK)
105  * Tn = pseudo-random(KMSK, n || "rfc4121-gss-eap")
106  * L = output key size
107  * K = truncate(L, T1 || T2 || .. || Tn)
108  */
109 OM_uint32
110 gssEapDeriveRfc3961Key(OM_uint32 *minor,
111                        const unsigned char *inputKey,
112                        size_t inputKeyLength,
113                        krb5_enctype encryptionType,
114                        krb5_keyblock *pKey)
115 {
116     krb5_context krbContext;
117 #ifndef HAVE_HEIMDAL_VERSION
118     krb5_data data;
119 #endif
120     krb5_data ns, t, prfOut;
121     krb5_keyblock kd;
122     krb5_error_code code;
123     size_t randomLength, keyLength, prfLength;
124     unsigned char constant[4 + sizeof("rfc4121-gss-eap") - 1], *p;
125     ssize_t i, remain;
126
127     assert(encryptionType != ENCTYPE_NULL);
128
129     memset(pKey, 0, sizeof(*pKey));
130
131     GSSEAP_KRB_INIT(&krbContext);
132
133     KRB_KEY_INIT(&kd);
134     KRB_KEY_TYPE(&kd) = encryptionType;
135
136     t.data = NULL;
137     t.length = 0;
138
139     prfOut.data = NULL;
140     prfOut.length = 0;
141
142     code = krb5_c_keylengths(krbContext, encryptionType,
143                              &randomLength, &keyLength);
144     if (code != 0)
145         goto cleanup;
146
147     KRB_KEY_DATA(&kd) = GSSEAP_MALLOC(keyLength);
148     if (KRB_KEY_DATA(&kd) == NULL) {
149         code = ENOMEM;
150         goto cleanup;
151     }
152     KRB_KEY_LENGTH(&kd) = keyLength;
153
154     /* Convert MSK into a Kerberos key */
155 #ifdef HAVE_HEIMDAL_VERSION
156     code = krb5_random_to_key(krbContext, encryptionType, inputKey,
157                               MIN(inputKeyLength, randomLength), &kd);
158 #else
159     data.length = MIN(inputKeyLength, randomLength);
160     data.data = (char *)inputKey;
161
162     code = krb5_c_random_to_key(krbContext, encryptionType, &data, &kd);
163 #endif
164     if (code != 0)
165         goto cleanup;
166
167     memset(&constant[0], 0, 4);
168     memcpy(&constant[4], "rfc4121-gss-eap", sizeof("rfc4121-gss-eap") - 1);
169
170     ns.length = sizeof(constant);
171     ns.data = (char *)constant;
172
173     /* Plug derivation constant and key into PRF */
174     code = krb5_c_prf_length(krbContext, encryptionType, &prfLength);
175     if (code != 0)
176         goto cleanup;
177
178     t.length = prfLength;
179     t.data = GSSEAP_MALLOC(t.length);
180     if (t.data == NULL) {
181         code = ENOMEM;
182         goto cleanup;
183     }
184
185     prfOut.length = randomLength;
186     prfOut.data = GSSEAP_MALLOC(prfOut.length);
187     if (prfOut.data == NULL) {
188         code = ENOMEM;
189         goto cleanup;
190     }
191
192     for (i = 0, p = (unsigned char *)prfOut.data, remain = randomLength;
193          remain > 0;
194          p += t.length, remain -= t.length, i++)
195     {
196         store_uint32_be(i, ns.data);
197
198         code = krb5_c_prf(krbContext, &kd, &ns, &t);
199         if (code != 0)
200             goto cleanup;
201
202         memcpy(p, t.data, MIN(t.length, remain));
203      }
204
205     /* Finally, convert PRF output into a new key which we will return */
206 #ifdef HAVE_HEIMDAL_VERSION
207     code = krb5_random_to_key(krbContext, encryptionType,
208                               prfOut.data, prfOut.length, &kd);
209 #else
210     code = krb5_c_random_to_key(krbContext, encryptionType, &prfOut, &kd);
211 #endif
212     if (code != 0)
213         goto cleanup;
214
215     *pKey = kd;
216     KRB_KEY_DATA(&kd) = NULL;
217
218 cleanup:
219     if (KRB_KEY_DATA(&kd) != NULL) {
220         memset(KRB_KEY_DATA(&kd), 0, KRB_KEY_LENGTH(&kd));
221         GSSEAP_FREE(KRB_KEY_DATA(&kd));
222     }
223     if (t.data != NULL) {
224         memset(t.data, 0, t.length);
225         GSSEAP_FREE(t.data);
226     }
227     if (prfOut.data != NULL) {
228         memset(prfOut.data, 0, prfOut.length);
229         GSSEAP_FREE(prfOut.data);
230     }
231     *minor = code;
232     return (code == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
233 }
234
235 #ifdef HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE
236 extern krb5_error_code
237 krb5int_c_mandatory_cksumtype(krb5_context, krb5_enctype, krb5_cksumtype *);
238 #endif
239
240 OM_uint32
241 rfc3961ChecksumTypeForKey(OM_uint32 *minor,
242                           krb5_keyblock *key,
243                           krb5_cksumtype *cksumtype)
244 {
245     krb5_context krbContext;
246 #ifndef HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE
247     krb5_data data;
248     krb5_checksum cksum;
249 #endif
250
251     GSSEAP_KRB_INIT(&krbContext);
252
253 #ifdef HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE
254     *minor = krb5int_c_mandatory_cksumtype(krbContext, KRB_KEY_TYPE(key),
255                                            cksumtype);
256     if (*minor != 0)
257         return GSS_S_FAILURE;
258 #else
259     data.length = 0;
260     data.data = NULL;
261
262     memset(&cksum, 0, sizeof(cksum));
263
264     /*
265      * This is a complete hack but it's the only way to work with
266      * MIT Kerberos pre-1.9 without using private API, as it does
267      * not support passing in zero as the checksum type.
268      */
269     *minor = krb5_c_make_checksum(krbContext, 0, key, 0, &data, &cksum);
270     if (*minor != 0)
271         return GSS_S_FAILURE;
272
273 #ifdef HAVE_HEIMDAL_VERSION
274     *cksumtype = cksum.cksumtype;
275 #else
276     *cksumtype = cksum.checksum_type;
277 #endif
278
279     krb5_free_checksum_contents(krbContext, &cksum);
280 #endif /* HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE */
281
282     if (!krb5_c_is_keyed_cksum(*cksumtype)) {
283         *minor = (OM_uint32)KRB5KRB_AP_ERR_INAPP_CKSUM;
284         return GSS_S_FAILURE;
285     }
286
287     return GSS_S_COMPLETE;
288 }
289
290 krb5_error_code
291 krbCryptoLength(krb5_context krbContext,
292 #ifdef HAVE_HEIMDAL_VERSION
293                 krb5_crypto krbCrypto,
294 #else
295                 krb5_keyblock *key,
296 #endif
297                 int type,
298                 size_t *length)
299 {
300 #ifdef HAVE_HEIMDAL_VERSION
301     return krb5_crypto_length(krbContext, krbCrypto, type, length);
302 #else
303     unsigned int len;
304     krb5_error_code code;
305
306     code = krb5_c_crypto_length(krbContext, KRB_KEY_TYPE(key), type, &len);
307     if (code == 0)
308         *length = (size_t)len;
309
310     return code;
311 #endif
312 }
313
314 krb5_error_code
315 krbPaddingLength(krb5_context krbContext,
316 #ifdef HAVE_HEIMDAL_VERSION
317                  krb5_crypto krbCrypto,
318 #else
319                  krb5_keyblock *key,
320 #endif
321                  size_t dataLength,
322                  size_t *padLength)
323 {
324     krb5_error_code code;
325 #ifdef HAVE_HEIMDAL_VERSION
326     size_t headerLength, paddingLength;
327
328     code = krbCryptoLength(krbContext, krbCrypto,
329                            KRB5_CRYPTO_TYPE_HEADER, &headerLength);
330     if (code != 0)
331         return code;
332
333     dataLength += headerLength;
334
335     code = krb5_crypto_length(krbContext, krbCrypto,
336                               KRB5_CRYPTO_TYPE_PADDING, &paddingLength);
337     if (code != 0)
338         return code;
339
340     if (paddingLength != 0 && (dataLength % paddingLength) != 0)
341         *padLength = paddingLength - (dataLength % paddingLength);
342     else
343         *padLength = 0;
344
345     return 0;
346 #else
347     unsigned int pad;
348
349     code = krb5_c_padding_length(krbContext, KRB_KEY_TYPE(key), dataLength, &pad);
350     if (code == 0)
351         *padLength = (size_t)pad;
352
353     return code;
354 #endif /* HAVE_HEIMDAL_VERSION */
355 }
356
357 krb5_error_code
358 krbBlockSize(krb5_context krbContext,
359 #ifdef HAVE_HEIMDAL_VERSION
360                  krb5_crypto krbCrypto,
361 #else
362                  krb5_keyblock *key,
363 #endif
364                  size_t *blockSize)
365 {
366 #ifdef HAVE_HEIMDAL_VERSION
367     return krb5_crypto_getblocksize(krbContext, krbCrypto, blockSize);
368 #else
369     return krb5_c_block_size(krbContext, KRB_KEY_TYPE(key), blockSize);
370 #endif
371 }
372
373 krb5_error_code
374 krbEnctypeToString(
375 #ifdef HAVE_HEIMDAL_VERSION
376                    krb5_context krbContext,
377 #else
378                    krb5_context krbContext GSSEAP_UNUSED,
379 #endif
380                    krb5_enctype enctype,
381                    const char *prefix,
382                    gss_buffer_t string)
383 {
384     krb5_error_code code;
385 #ifdef HAVE_HEIMDAL_VERSION
386     char *enctypeBuf = NULL;
387 #else
388     char enctypeBuf[128];
389 #endif
390     size_t prefixLength, enctypeLength;
391
392 #ifdef HAVE_HEIMDAL_VERSION
393     code = krb5_enctype_to_string(krbContext, enctype, &enctypeBuf);
394 #else
395     code = krb5_enctype_to_name(enctype, 0, enctypeBuf, sizeof(enctypeBuf));
396 #endif
397     if (code != 0)
398         return code;
399
400     prefixLength = (prefix != NULL) ? strlen(prefix) : 0;
401     enctypeLength = strlen(enctypeBuf);
402
403     string->value = GSSEAP_MALLOC(prefixLength + enctypeLength + 1);
404     if (string->value == NULL) {
405 #ifdef HAVE_HEIMDAL_VERSION
406         krb5_xfree(enctypeBuf);
407 #endif
408         return ENOMEM;
409     }
410
411     if (prefixLength != 0)
412         memcpy(string->value, prefix, prefixLength);
413     memcpy((char *)string->value + prefixLength, enctypeBuf, enctypeLength);
414
415     string->length = prefixLength + enctypeLength;
416     ((char *)string->value)[string->length] = '\0';
417
418 #ifdef HAVE_HEIMDAL_VERSION
419     krb5_xfree(enctypeBuf);
420 #endif
421
422     return 0;
423 }
424
425 krb5_error_code
426 krbMakeAuthDataKdcIssued(krb5_context context,
427                          const krb5_keyblock *key,
428                          krb5_const_principal issuer,
429 #ifdef HAVE_HEIMDAL_VERSION
430                          const AuthorizationData *authdata,
431                          AuthorizationData *adKdcIssued
432 #else
433                          krb5_authdata *const *authdata,
434                          krb5_authdata ***adKdcIssued
435 #endif
436                          )
437 {
438 #ifdef HAVE_HEIMDAL_VERSION
439     krb5_error_code code;
440     AD_KDCIssued kdcIssued;
441     AuthorizationDataElement adDatum;
442     unsigned char *buf;
443     size_t buf_size, len;
444     krb5_crypto crypto = NULL;
445
446     memset(&kdcIssued, 0, sizeof(kdcIssued));
447     memset(adKdcIssued, 0, sizeof(*adKdcIssued));
448
449     kdcIssued.i_realm = issuer->realm != NULL ? (Realm *)&issuer->realm : NULL;
450     kdcIssued.i_sname = (PrincipalName *)&issuer->name;
451     kdcIssued.elements = *authdata;
452
453     ASN1_MALLOC_ENCODE(AuthorizationData, buf, buf_size, authdata, &len, code);
454     if (code != 0)
455         goto cleanup;
456
457     code = krb5_crypto_init(context, key, 0, &crypto);
458     if (code != 0)
459         goto cleanup;
460
461     code = krb5_create_checksum(context, crypto, KRB5_KU_AD_KDC_ISSUED,
462                                 0, buf, buf_size, &kdcIssued.ad_checksum);
463     if (code != 0)
464         goto cleanup;
465
466     GSSEAP_FREE(buf);
467     buf = NULL;
468
469     ASN1_MALLOC_ENCODE(AD_KDCIssued, buf, buf_size, &kdcIssued, &len, code);
470     if (code != 0)
471         goto cleanup;
472
473     adDatum.ad_type = KRB5_AUTHDATA_KDC_ISSUED;
474     adDatum.ad_data.length = buf_size;
475     adDatum.ad_data.data = buf;
476
477     code = add_AuthorizationData(adKdcIssued, &adDatum);
478     if (code != 0)
479         goto cleanup;
480
481 cleanup:
482     if (buf != NULL)
483         GSSEAP_FREE(buf);
484     if (crypto != NULL)
485         krb5_crypto_destroy(context, crypto);
486     free_Checksum(&kdcIssued.ad_checksum);
487
488     return code;
489 #else
490     return krb5_make_authdata_kdc_issued(context, key, issuer, authdata,
491                                          adKdcIssued);
492 #endif /* HAVE_HEIMDAL_VERSION */
493 }
494
495 krb5_error_code
496 krbMakeCred(krb5_context krbContext,
497             krb5_auth_context authContext,
498             krb5_creds *creds,
499             krb5_data *data)
500 {
501     krb5_error_code code;
502 #ifdef HAVE_HEIMDAL_VERSION
503     KRB_CRED krbCred;
504     KrbCredInfo krbCredInfo;
505     EncKrbCredPart encKrbCredPart;
506     krb5_keyblock *key;
507     krb5_crypto krbCrypto = NULL;
508     krb5_data encKrbCredPartData;
509     krb5_replay_data rdata;
510     size_t len;
511 #else
512     krb5_data *d = NULL;
513 #endif
514
515     memset(data, 0, sizeof(*data));
516 #ifdef HAVE_HEIMDAL_VERSION
517     memset(&krbCred,        0, sizeof(krbCred));
518     memset(&krbCredInfo,    0, sizeof(krbCredInfo));
519     memset(&encKrbCredPart, 0, sizeof(encKrbCredPart));
520     memset(&rdata,          0, sizeof(rdata));
521
522     if (authContext->local_subkey)
523         key = authContext->local_subkey;
524     else if (authContext->remote_subkey)
525         key = authContext->remote_subkey;
526     else
527         key = authContext->keyblock;
528
529     krbCred.pvno = 5;
530     krbCred.msg_type = krb_cred;
531     krbCred.tickets.val = (Ticket *)GSSEAP_CALLOC(1, sizeof(Ticket));
532     if (krbCred.tickets.val == NULL) {
533         code = ENOMEM;
534         goto cleanup;
535     }
536     krbCred.tickets.len = 1;
537
538     code = decode_Ticket(creds->ticket.data,
539                          creds->ticket.length,
540                          krbCred.tickets.val, &len);
541     if (code != 0)
542         goto cleanup;
543
544     krbCredInfo.key         = creds->session;
545     krbCredInfo.prealm      = &creds->client->realm;
546     krbCredInfo.pname       = &creds->client->name;
547     krbCredInfo.flags       = &creds->flags.b;
548     krbCredInfo.authtime    = &creds->times.authtime;
549     krbCredInfo.starttime   = &creds->times.starttime;
550     krbCredInfo.endtime     = &creds->times.endtime;
551     krbCredInfo.renew_till  = &creds->times.renew_till;
552     krbCredInfo.srealm      = &creds->server->realm;
553     krbCredInfo.sname       = &creds->server->name;
554     krbCredInfo.caddr       = creds->addresses.len ? &creds->addresses : NULL;
555
556     encKrbCredPart.ticket_info.len = 1;
557     encKrbCredPart.ticket_info.val = &krbCredInfo;
558     if (authContext->flags & KRB5_AUTH_CONTEXT_DO_SEQUENCE) {
559         rdata.seq                  = authContext->local_seqnumber;
560         encKrbCredPart.nonce       = (int32_t *)&rdata.seq;
561     } else {
562         encKrbCredPart.nonce       = NULL;
563     }
564     if (authContext->flags & KRB5_AUTH_CONTEXT_DO_TIME) {
565         krb5_us_timeofday(krbContext, &rdata.timestamp, &rdata.usec);
566         encKrbCredPart.timestamp   = &rdata.timestamp;
567         encKrbCredPart.usec        = &rdata.usec;
568     } else {
569         encKrbCredPart.timestamp   = NULL;
570         encKrbCredPart.usec        = NULL;
571     }
572     encKrbCredPart.s_address       = authContext->local_address;
573     encKrbCredPart.r_address       = authContext->remote_address;
574
575     ASN1_MALLOC_ENCODE(EncKrbCredPart, encKrbCredPartData.data,
576                        encKrbCredPartData.length, &encKrbCredPart,
577                        &len, code);
578     if (code != 0)
579         goto cleanup;
580
581     code = krb5_crypto_init(krbContext, key, 0, &krbCrypto);
582     if (code != 0)
583         goto cleanup;
584
585     code = krb5_encrypt_EncryptedData(krbContext,
586                                       krbCrypto,
587                                       KRB5_KU_KRB_CRED,
588                                       encKrbCredPartData.data,
589                                       encKrbCredPartData.length,
590                                       0,
591                                       &krbCred.enc_part);
592     if (code != 0)
593         goto cleanup;
594
595     ASN1_MALLOC_ENCODE(KRB_CRED, data->data, data->length,
596                        &krbCred, &len, code);
597     if (code != 0)
598         goto cleanup;
599
600     if (authContext->flags & KRB5_AUTH_CONTEXT_DO_SEQUENCE)
601         authContext->local_seqnumber++;
602
603 cleanup:
604     if (krbCrypto != NULL)
605         krb5_crypto_destroy(krbContext, krbCrypto);
606     free_KRB_CRED(&krbCred);
607     krb5_data_free(&encKrbCredPartData);
608
609     return code;
610 #else
611     code = krb5_mk_1cred(krbContext, authContext, creds, &d, NULL);
612     if (code == 0) {
613         *data = *d;
614         GSSEAP_FREE(d);
615     }
616
617     return code;
618 #endif /* HAVE_HEIMDAL_VERSION */
619 }