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