xref: /freebsd/crypto/krb5/src/kdc/fast_util.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* kdc/fast_util.c */
3 /*
4  * Copyright (C) 2009, 2015 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26 
27 #include <k5-int.h>
28 
29 #include "kdc_util.h"
30 #include "extern.h"
31 
32 /* Let cookies be valid for ten minutes. */
33 #define COOKIE_LIFETIME 600
34 
armor_ap_request(struct kdc_request_state * state,krb5_fast_armor * armor)35 static krb5_error_code armor_ap_request
36 (struct kdc_request_state *state, krb5_fast_armor *armor)
37 {
38     krb5_error_code retval = 0;
39     krb5_auth_context authcontext = NULL;
40     krb5_ticket *ticket = NULL;
41     krb5_keyblock *subkey = NULL;
42     kdc_realm_t *realm = state->realm_data;
43     krb5_context context = realm->realm_context;
44 
45     assert(armor->armor_type == KRB5_FAST_ARMOR_AP_REQUEST);
46     krb5_clear_error_message(context);
47     retval = krb5_auth_con_init(context, &authcontext);
48     /*disable replay cache*/
49     if (retval == 0)
50         retval = krb5_auth_con_setflags(context, authcontext, 0);
51     if (retval == 0)
52         retval = krb5_rd_req(context, &authcontext, &armor->armor_value,
53                              NULL /*server*/, realm->realm_keytab,
54                              NULL, &ticket);
55     if (retval != 0) {
56         const char * errmsg = krb5_get_error_message(context, retval);
57         k5_setmsg(context, retval, _("%s while handling ap-request armor"),
58                   errmsg);
59         krb5_free_error_message(context, errmsg);
60     }
61     if (retval == 0) {
62         if (!krb5_principal_compare_any_realm(context, realm->realm_tgsprinc,
63                                               ticket->server)) {
64             k5_setmsg(context, KRB5KDC_ERR_SERVER_NOMATCH,
65                       _("ap-request armor for something other than the local "
66                         "TGS"));
67             retval = KRB5KDC_ERR_SERVER_NOMATCH;
68         }
69     }
70     if (retval == 0) {
71         retval = krb5_auth_con_getrecvsubkey(context, authcontext, &subkey);
72         if (retval != 0 || subkey == NULL) {
73             k5_setmsg(context, KRB5KDC_ERR_POLICY,
74                       _("ap-request armor without subkey"));
75             retval = KRB5KDC_ERR_POLICY;
76         }
77     }
78     if (retval == 0)
79         retval = krb5_c_fx_cf2_simple(context,
80                                       subkey, "subkeyarmor",
81                                       ticket->enc_part2->session, "ticketarmor",
82                                       &state->armor_key);
83     if (ticket)
84         krb5_free_ticket(context, ticket);
85     if (subkey)
86         krb5_free_keyblock(context, subkey);
87     if (authcontext)
88         krb5_auth_con_free(context, authcontext);
89     return retval;
90 }
91 
92 static krb5_error_code
encrypt_fast_reply(struct kdc_request_state * state,const krb5_fast_response * response,krb5_data ** fx_fast_reply)93 encrypt_fast_reply(struct kdc_request_state *state,
94                    const krb5_fast_response *response,
95                    krb5_data **fx_fast_reply)
96 {
97     krb5_context context = state->realm_data->realm_context;
98     krb5_error_code retval = 0;
99     krb5_enc_data encrypted_reply;
100     krb5_data *encoded_response = NULL;
101 
102     assert(state->armor_key);
103     retval = encode_krb5_fast_response(response, &encoded_response);
104     if (retval== 0)
105         retval = krb5_encrypt_helper(context, state->armor_key,
106                                      KRB5_KEYUSAGE_FAST_REP,
107                                      encoded_response, &encrypted_reply);
108     if (encoded_response)
109         krb5_free_data(context, encoded_response);
110     encoded_response = NULL;
111     if (retval == 0) {
112         retval = encode_krb5_pa_fx_fast_reply(&encrypted_reply,
113                                               fx_fast_reply);
114         krb5_free_data_contents(context, &encrypted_reply.ciphertext);
115     }
116     return retval;
117 }
118 
119 
120 /*
121  * This function will find the FAST padata and, if FAST is successfully
122  * processed, will free the outer request and update the pointer to point to
123  * the inner request.  checksummed_data points to the data that is in the
124  * armored_fast_request checksum; either the pa-tgs-req or the kdc-req-body.
125  */
126 krb5_error_code
kdc_find_fast(krb5_kdc_req ** requestptr,krb5_data * checksummed_data,krb5_keyblock * tgs_subkey,krb5_keyblock * tgs_session,struct kdc_request_state * state,krb5_data ** inner_body_out)127 kdc_find_fast(krb5_kdc_req **requestptr,
128               krb5_data *checksummed_data,
129               krb5_keyblock *tgs_subkey,
130               krb5_keyblock *tgs_session,
131               struct kdc_request_state *state,
132               krb5_data **inner_body_out)
133 {
134     krb5_context context = state->realm_data->realm_context;
135     krb5_error_code retval = 0;
136     krb5_pa_data *fast_padata;
137     krb5_data scratch, plaintext, *inner_body = NULL;
138     krb5_fast_req * fast_req = NULL;
139     krb5_kdc_req *request = *requestptr;
140     krb5_fast_armored_req *fast_armored_req = NULL;
141     krb5_checksum *cksum;
142     krb5_boolean cksum_valid;
143     krb5_keyblock empty_keyblock;
144 
145     if (inner_body_out != NULL)
146         *inner_body_out = NULL;
147     scratch.data = NULL;
148     krb5_clear_error_message(context);
149     memset(&empty_keyblock, 0, sizeof(krb5_keyblock));
150     fast_padata = krb5int_find_pa_data(context, request->padata,
151                                        KRB5_PADATA_FX_FAST);
152     if (fast_padata !=  NULL){
153         scratch.length = fast_padata->length;
154         scratch.data = (char *) fast_padata->contents;
155         retval = decode_krb5_pa_fx_fast_request(&scratch, &fast_armored_req);
156         if (retval == 0 &&fast_armored_req->armor) {
157             switch (fast_armored_req->armor->armor_type) {
158             case KRB5_FAST_ARMOR_AP_REQUEST:
159                 if (tgs_subkey) {
160                     retval = KRB5KDC_ERR_PREAUTH_FAILED;
161                     k5_setmsg(context, retval,
162                               _("Ap-request armor not permitted with TGS"));
163                     break;
164                 }
165                 retval = armor_ap_request(state, fast_armored_req->armor);
166                 break;
167             default:
168                 k5_setmsg(context, KRB5KDC_ERR_PREAUTH_FAILED,
169                           _("Unknown FAST armor type %d"),
170                           fast_armored_req->armor->armor_type);
171                 retval = KRB5KDC_ERR_PREAUTH_FAILED;
172             }
173         }
174         if (retval == 0 && !state->armor_key) {
175             if (tgs_subkey)
176                 retval = krb5_c_fx_cf2_simple(context,
177                                               tgs_subkey, "subkeyarmor",
178                                               tgs_session, "ticketarmor",
179                                               &state->armor_key);
180             else {
181                 retval = KRB5KDC_ERR_PREAUTH_FAILED;
182                 k5_setmsg(context, retval,
183                           _("No armor key but FAST armored request present"));
184             }
185         }
186         if (retval == 0) {
187             plaintext.length = fast_armored_req->enc_part.ciphertext.length;
188             plaintext.data = k5alloc(plaintext.length, &retval);
189         }
190         if (retval == 0) {
191             retval = krb5_c_decrypt(context, state->armor_key,
192                                     KRB5_KEYUSAGE_FAST_ENC, NULL,
193                                     &fast_armored_req->enc_part,
194                                     &plaintext);
195             if (retval == 0)
196                 retval = decode_krb5_fast_req(&plaintext, &fast_req);
197             if (retval == 0 && inner_body_out != NULL) {
198                 retval = fetch_asn1_field((unsigned char *)plaintext.data,
199                                           1, 2, &scratch);
200                 if (retval == 0) {
201                     retval = krb5_copy_data(context, &scratch, &inner_body);
202                 }
203             }
204             if (plaintext.data)
205                 free(plaintext.data);
206         }
207         cksum = &fast_armored_req->req_checksum;
208         if (retval == 0)
209             retval = krb5_c_verify_checksum(context, state->armor_key,
210                                             KRB5_KEYUSAGE_FAST_REQ_CHKSUM,
211                                             checksummed_data, cksum,
212                                             &cksum_valid);
213         if (retval == 0 && !cksum_valid) {
214             retval = KRB5KRB_AP_ERR_MODIFIED;
215             k5_setmsg(context, retval,
216                       _("FAST req_checksum invalid; request modified"));
217         }
218         if (retval == 0) {
219             if (!krb5_c_is_keyed_cksum(cksum->checksum_type)) {
220                 retval = KRB5KDC_ERR_POLICY;
221                 k5_setmsg(context, retval,
222                           _("Unkeyed checksum used in fast_req"));
223             }
224         }
225         if (retval == 0) {
226             if ((fast_req->fast_options & UNSUPPORTED_CRITICAL_FAST_OPTIONS) != 0)
227                 retval = KRB5KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTION;
228         }
229         if (retval == 0) {
230             state->fast_options = fast_req->fast_options;
231             fast_req->req_body->msg_type = request->msg_type;
232             krb5_free_kdc_req(context, request);
233             *requestptr = fast_req->req_body;
234             fast_req->req_body = NULL;
235         }
236     }
237     if (retval == 0 && inner_body_out != NULL) {
238         *inner_body_out = inner_body;
239         inner_body = NULL;
240     }
241     krb5_free_data(context, inner_body);
242     if (fast_req)
243         krb5_free_fast_req(context, fast_req);
244     if (fast_armored_req)
245         krb5_free_fast_armored_req(context, fast_armored_req);
246     return retval;
247 }
248 
249 
250 krb5_error_code
kdc_make_rstate(kdc_realm_t * active_realm,struct kdc_request_state ** out)251 kdc_make_rstate(kdc_realm_t *active_realm, struct kdc_request_state **out)
252 {
253     struct kdc_request_state *state = malloc( sizeof(struct kdc_request_state));
254     if (state == NULL)
255         return ENOMEM;
256     memset( state, 0, sizeof(struct kdc_request_state));
257     state->realm_data = active_realm;
258     *out = state;
259     return 0;
260 }
261 
262 void
kdc_free_rstate(struct kdc_request_state * s)263 kdc_free_rstate (struct kdc_request_state *s)
264 {
265     if (s == NULL)
266         return;
267     if (s->armor_key)
268         krb5_free_keyblock(s->realm_data->realm_context, s->armor_key);
269     if (s->strengthen_key)
270         krb5_free_keyblock(s->realm_data->realm_context, s->strengthen_key);
271     k5_zapfree_pa_data(s->in_cookie_padata);
272     k5_zapfree_pa_data(s->out_cookie_padata);
273     free(s);
274 }
275 
276 krb5_error_code
kdc_fast_response_handle_padata(struct kdc_request_state * state,krb5_kdc_req * request,krb5_kdc_rep * rep,krb5_enctype enctype)277 kdc_fast_response_handle_padata(struct kdc_request_state *state,
278                                 krb5_kdc_req *request,
279                                 krb5_kdc_rep *rep, krb5_enctype enctype)
280 {
281     krb5_context context = state->realm_data->realm_context;
282     krb5_error_code retval = 0;
283     krb5_fast_finished finish;
284     krb5_fast_response fast_response;
285     krb5_data *encoded_ticket = NULL;
286     krb5_data *encrypted_reply = NULL;
287     krb5_pa_data *pa = NULL, **pa_array = NULL;
288     krb5_cksumtype cksumtype = CKSUMTYPE_RSA_MD5;
289     krb5_pa_data *empty_padata[] = {NULL};
290     krb5_keyblock *strengthen_key = NULL;
291 
292     if (!state->armor_key)
293         return 0;
294     memset(&finish, 0, sizeof(finish));
295     retval = krb5_init_keyblock(context, enctype, 0, &strengthen_key);
296     if (retval == 0)
297         retval = krb5_c_make_random_key(context, enctype, strengthen_key);
298     if (retval == 0) {
299         state->strengthen_key = strengthen_key;
300         strengthen_key = NULL;
301     }
302 
303     fast_response.padata = rep->padata;
304     if (fast_response.padata == NULL)
305         fast_response.padata = &empty_padata[0];
306     fast_response.strengthen_key = state->strengthen_key;
307     fast_response.nonce = request->nonce;
308     fast_response.finished = &finish;
309     finish.client = rep->client;
310     pa_array = calloc(3, sizeof(*pa_array));
311     if (pa_array == NULL)
312         retval = ENOMEM;
313     pa = calloc(1, sizeof(krb5_pa_data));
314     if (retval == 0 && pa == NULL)
315         retval = ENOMEM;
316     if (retval == 0)
317         retval = krb5_us_timeofday(context, &finish.timestamp, &finish.usec);
318     if (retval == 0)
319         retval = encode_krb5_ticket(rep->ticket, &encoded_ticket);
320     if (retval == 0)
321         retval = krb5int_c_mandatory_cksumtype(context,
322                                                state->armor_key->enctype,
323                                                &cksumtype);
324     if (retval == 0)
325         retval = krb5_c_make_checksum(context, cksumtype, state->armor_key,
326                                       KRB5_KEYUSAGE_FAST_FINISHED,
327                                       encoded_ticket, &finish.ticket_checksum);
328     if (retval == 0)
329         retval = encrypt_fast_reply(state, &fast_response, &encrypted_reply);
330     if (retval == 0) {
331         pa[0].pa_type = KRB5_PADATA_FX_FAST;
332         pa[0].length = encrypted_reply->length;
333         pa[0].contents = (unsigned char *)  encrypted_reply->data;
334         pa_array[0] = &pa[0];
335         krb5_free_pa_data(context, rep->padata);
336         rep->padata = pa_array;
337         pa_array = NULL;
338         free(encrypted_reply);
339         encrypted_reply = NULL;
340         pa = NULL;
341     }
342     if (pa)
343         free(pa);
344     if (pa_array)
345         free(pa_array);
346     if (encrypted_reply)
347         krb5_free_data(context, encrypted_reply);
348     if (encoded_ticket)
349         krb5_free_data(context, encoded_ticket);
350     if (strengthen_key != NULL)
351         krb5_free_keyblock(context, strengthen_key);
352     if (finish.ticket_checksum.contents)
353         krb5_free_checksum_contents(context, &finish.ticket_checksum);
354     return retval;
355 }
356 
357 
358 /*
359  * We assume the caller is responsible for passing us an in_padata
360  * sufficient to include in a FAST error.  In the FAST case we will
361  * set *fast_edata_out to the edata to be included in the error; in
362  * the non-FAST case we will set it to NULL.
363  */
364 krb5_error_code
kdc_fast_handle_error(krb5_context context,struct kdc_request_state * state,krb5_kdc_req * request,krb5_pa_data ** in_padata,krb5_error * err,krb5_data ** fast_edata_out)365 kdc_fast_handle_error(krb5_context context,
366                       struct kdc_request_state *state,
367                       krb5_kdc_req *request,
368                       krb5_pa_data  **in_padata, krb5_error *err,
369                       krb5_data **fast_edata_out)
370 {
371     krb5_error_code retval = 0;
372     krb5_fast_response resp;
373     krb5_error fx_error;
374     krb5_data *encoded_fx_error = NULL, *encrypted_reply = NULL;
375     krb5_pa_data pa[1];
376     krb5_pa_data *outer_pa[3];
377     krb5_pa_data **inner_pa = NULL;
378     size_t size = 0;
379 
380     *fast_edata_out = NULL;
381     memset(outer_pa, 0, sizeof(outer_pa));
382     if (state->armor_key == NULL)
383         return 0;
384     fx_error = *err;
385     fx_error.e_data.data = NULL;
386     fx_error.e_data.length = 0;
387     for (size = 0; in_padata&&in_padata[size]; size++);
388     inner_pa = calloc(size + 2, sizeof(krb5_pa_data *));
389     if (inner_pa == NULL)
390         retval = ENOMEM;
391     if (retval == 0)
392         for (size=0; in_padata&&in_padata[size]; size++)
393             inner_pa[size] = in_padata[size];
394     if (retval == 0)
395         retval = encode_krb5_error(&fx_error, &encoded_fx_error);
396     if (retval == 0) {
397         pa[0].pa_type = KRB5_PADATA_FX_ERROR;
398         pa[0].length = encoded_fx_error->length;
399         pa[0].contents = (unsigned char *) encoded_fx_error->data;
400         inner_pa[size++] = &pa[0];
401     }
402     if (retval == 0) {
403         resp.padata = inner_pa;
404         resp.nonce = request->nonce;
405         resp.strengthen_key = NULL;
406         resp.finished = NULL;
407     }
408     if (retval == 0)
409         retval = encrypt_fast_reply(state, &resp, &encrypted_reply);
410     if (inner_pa)
411         free(inner_pa); /*contained storage from caller and our stack*/
412     if (retval == 0) {
413         pa[0].pa_type = KRB5_PADATA_FX_FAST;
414         pa[0].length = encrypted_reply->length;
415         pa[0].contents = (unsigned char *) encrypted_reply->data;
416         outer_pa[0] = &pa[0];
417     }
418     retval = encode_krb5_padata_sequence(outer_pa, fast_edata_out);
419     if (encrypted_reply)
420         krb5_free_data(context, encrypted_reply);
421     if (encoded_fx_error)
422         krb5_free_data(context, encoded_fx_error);
423     return retval;
424 }
425 
426 krb5_error_code
kdc_fast_handle_reply_key(struct kdc_request_state * state,krb5_keyblock * existing_key,krb5_keyblock ** out_key)427 kdc_fast_handle_reply_key(struct kdc_request_state *state,
428                           krb5_keyblock *existing_key,
429                           krb5_keyblock **out_key)
430 {
431     krb5_context context = state->realm_data->realm_context;
432     krb5_error_code retval = 0;
433 
434     if (state->armor_key)
435         retval = krb5_c_fx_cf2_simple(context,
436                                       state->strengthen_key, "strengthenkey",
437                                       existing_key, "replykey", out_key);
438     else
439         retval = krb5_copy_keyblock(context, existing_key, out_key);
440     return retval;
441 }
442 
443 krb5_boolean
kdc_fast_hide_client(struct kdc_request_state * state)444 kdc_fast_hide_client(struct kdc_request_state *state)
445 {
446     return (state->fast_options & KRB5_FAST_OPTION_HIDE_CLIENT_NAMES) != 0;
447 }
448 
449 /* Create a pa-data entry with the specified type and contents. */
450 static krb5_error_code
make_padata(krb5_preauthtype pa_type,const void * contents,size_t len,krb5_pa_data ** out)451 make_padata(krb5_preauthtype pa_type, const void *contents, size_t len,
452             krb5_pa_data **out)
453 {
454     if (k5_alloc_pa_data(pa_type, len, out) != 0)
455         return ENOMEM;
456     memcpy((*out)->contents, contents, len);
457     return 0;
458 }
459 
460 /*
461  * Derive the secure cookie encryption key from tgt_key and client_princ.  The
462  * cookie key is derived with PRF+ using the concatenation of "COOKIE" and the
463  * unparsed client principal name as input.
464  */
465 static krb5_error_code
derive_cookie_key(krb5_context context,krb5_keyblock * tgt_key,krb5_const_principal client_princ,krb5_keyblock ** key_out)466 derive_cookie_key(krb5_context context, krb5_keyblock *tgt_key,
467                   krb5_const_principal client_princ, krb5_keyblock **key_out)
468 {
469     krb5_error_code ret;
470     krb5_data d;
471     char *princstr = NULL, *derive_input = NULL;
472 
473     *key_out = NULL;
474 
475     /* Construct the input string and derive the cookie key. */
476     ret = krb5_unparse_name(context, client_princ, &princstr);
477     if (ret)
478         goto cleanup;
479     if (asprintf(&derive_input, "COOKIE%s", princstr) < 0) {
480         ret = ENOMEM;
481         goto cleanup;
482     }
483     d = string2data(derive_input);
484     ret = krb5_c_derive_prfplus(context, tgt_key, &d, ENCTYPE_NULL, key_out);
485 
486 cleanup:
487     krb5_free_unparsed_name(context, princstr);
488     free(derive_input);
489     return ret;
490 }
491 
492 /* Derive the cookie key for the specified kvno in tgt.  tgt_key must be the
493  * decrypted first key data entry in tgt. */
494 static krb5_error_code
get_cookie_key(krb5_context context,krb5_db_entry * tgt,krb5_keyblock * tgt_key,krb5_kvno kvno,krb5_const_principal client_princ,krb5_keyblock ** key_out)495 get_cookie_key(krb5_context context, krb5_db_entry *tgt,
496                krb5_keyblock *tgt_key, krb5_kvno kvno,
497                krb5_const_principal client_princ, krb5_keyblock **key_out)
498 {
499     krb5_error_code ret;
500     krb5_keyblock storage, *key;
501     krb5_key_data *kd;
502 
503     *key_out = NULL;
504     memset(&storage, 0, sizeof(storage));
505 
506     if (kvno == current_kvno(tgt)) {
507         /* Use the already-decrypted first key. */
508         key = tgt_key;
509     } else {
510         /* The cookie used an older TGT key; find and decrypt it. */
511         ret = krb5_dbe_find_enctype(context, tgt, -1, -1, kvno, &kd);
512         if (ret)
513             return ret;
514         ret = krb5_dbe_decrypt_key_data(context, NULL, kd, &storage, NULL);
515         if (ret)
516             return ret;
517         key = &storage;
518     }
519 
520     ret = derive_cookie_key(context, key, client_princ, key_out);
521     krb5_free_keyblock_contents(context, &storage);
522     return ret;
523 }
524 
525 /* Return true if there is any overlap between padata types in cpadata
526  * (from the cookie) and rpadata (from the request). */
527 static krb5_boolean
is_relevant(krb5_pa_data * const * cpadata,krb5_pa_data * const * rpadata)528 is_relevant(krb5_pa_data *const *cpadata, krb5_pa_data *const *rpadata)
529 {
530     krb5_pa_data *const *p;
531 
532     for (p = cpadata; p != NULL && *p != NULL; p++) {
533         if (krb5int_find_pa_data(NULL, rpadata, (*p)->pa_type) != NULL)
534             return TRUE;
535     }
536     return FALSE;
537 }
538 
539 /*
540  * Locate and decode the FAST cookie in req, storing its contents in state for
541  * later access by preauth modules.  If the cookie is expired, return
542  * KRB5KDC_ERR_PREAUTH_EXPIRED if its contents are relevant to req, and ignore
543  * it if they aren't.
544  */
545 krb5_error_code
kdc_fast_read_cookie(krb5_context context,struct kdc_request_state * state,krb5_kdc_req * req,krb5_db_entry * local_tgt,krb5_keyblock * local_tgt_key)546 kdc_fast_read_cookie(krb5_context context, struct kdc_request_state *state,
547                      krb5_kdc_req *req, krb5_db_entry *local_tgt,
548                      krb5_keyblock *local_tgt_key)
549 {
550     krb5_error_code ret;
551     krb5_secure_cookie *cookie = NULL;
552     krb5_timestamp now;
553     krb5_keyblock *key = NULL;
554     krb5_enc_data enc;
555     krb5_pa_data *pa;
556     krb5_kvno kvno;
557     krb5_data plain = empty_data();
558 
559     pa = krb5int_find_pa_data(context, req->padata, KRB5_PADATA_FX_COOKIE);
560     if (pa == NULL)
561         return 0;
562 
563     /* If it's not an MIT version 1 cookie, ignore it.  It may be an empty
564      * "MIT" cookie or a cookie generated by a different KDC implementation. */
565     if (pa->length <= 8 || memcmp(pa->contents, "MIT1", 4) != 0)
566         return 0;
567 
568     /* Extract the kvno and generate the corresponding cookie key. */
569     kvno = load_32_be(pa->contents + 4);
570     ret = get_cookie_key(context, local_tgt, local_tgt_key, kvno, req->client,
571                          &key);
572     if (ret)
573         goto cleanup;
574 
575     /* Decrypt and decode the cookie. */
576     memset(&enc, 0, sizeof(enc));
577     enc.enctype = key->enctype;
578     enc.ciphertext = make_data(pa->contents + 8, pa->length - 8);
579     ret = alloc_data(&plain, pa->length - 8);
580     if (ret)
581         goto cleanup;
582     ret = krb5_c_decrypt(context, key, KRB5_KEYUSAGE_PA_FX_COOKIE, NULL, &enc,
583                          &plain);
584     if (ret)
585         goto cleanup;
586     ret = decode_krb5_secure_cookie(&plain, &cookie);
587     if (ret)
588         goto cleanup;
589 
590     /* Check if the cookie is expired. */
591     ret = krb5_timeofday(context, &now);
592     if (ret)
593         goto cleanup;
594     if (ts2tt(now) > cookie->time + COOKIE_LIFETIME) {
595         /* Don't accept the cookie contents.  Only return an error if the
596          * cookie is relevant to the request. */
597         if (is_relevant(cookie->data, req->padata))
598             ret = KRB5KDC_ERR_PREAUTH_EXPIRED;
599         goto cleanup;
600     }
601 
602     /* Steal the pa-data list pointer from the cookie and store it in state. */
603     state->in_cookie_padata = cookie->data;
604     cookie->data = NULL;
605 
606 cleanup:
607     zapfree(plain.data, plain.length);
608     krb5_free_keyblock(context, key);
609     k5_free_secure_cookie(context, cookie);
610     return 0;
611 }
612 
613 /* If state contains a cookie value for pa_type, set *out to the corresponding
614  * data and return true.  Otherwise set *out to empty and return false. */
615 krb5_boolean
kdc_fast_search_cookie(struct kdc_request_state * state,krb5_preauthtype pa_type,krb5_data * out)616 kdc_fast_search_cookie(struct kdc_request_state *state,
617                        krb5_preauthtype pa_type, krb5_data *out)
618 {
619     krb5_pa_data *pa;
620 
621     pa = krb5int_find_pa_data(NULL, state->in_cookie_padata, pa_type);
622     if (pa == NULL) {
623         *out = empty_data();
624         return FALSE;
625     } else {
626         *out = make_data(pa->contents, pa->length);
627         return TRUE;
628     }
629 }
630 
631 /* Set a cookie value in state for data, to be included in the outgoing
632  * cookie.  Duplicate values are ignored. */
633 krb5_error_code
kdc_fast_set_cookie(struct kdc_request_state * state,krb5_preauthtype pa_type,const krb5_data * data)634 kdc_fast_set_cookie(struct kdc_request_state *state, krb5_preauthtype pa_type,
635                     const krb5_data *data)
636 {
637     krb5_pa_data **list = state->out_cookie_padata;
638     size_t count;
639 
640     for (count = 0; list != NULL && list[count] != NULL; count++) {
641         if (list[count]->pa_type == pa_type)
642             return 0;
643     }
644 
645     list = realloc(list, (count + 2) * sizeof(*list));
646     if (list == NULL)
647         return ENOMEM;
648     state->out_cookie_padata = list;
649     list[count] = list[count + 1] = NULL;
650     return make_padata(pa_type, data->data, data->length, &list[count]);
651 }
652 
653 /* Construct a cookie pa-data item using the cookie values from state, or a
654  * trivial "MIT" cookie if no values are set. */
655 krb5_error_code
kdc_fast_make_cookie(krb5_context context,struct kdc_request_state * state,krb5_db_entry * local_tgt,krb5_keyblock * local_tgt_key,krb5_const_principal client_princ,krb5_pa_data ** cookie_out)656 kdc_fast_make_cookie(krb5_context context, struct kdc_request_state *state,
657                      krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key,
658                      krb5_const_principal client_princ,
659                      krb5_pa_data **cookie_out)
660 {
661     krb5_error_code ret;
662     krb5_secure_cookie cookie;
663     krb5_pa_data **contents = state->out_cookie_padata, *pa;
664     krb5_keyblock *key = NULL;
665     krb5_timestamp now;
666     krb5_enc_data enc;
667     krb5_data *der_cookie = NULL;
668     size_t ctlen;
669 
670     *cookie_out = NULL;
671     memset(&enc, 0, sizeof(enc));
672 
673     /* Make a trivial cookie if there are no contents to marshal or we don't
674      * have a TGT entry to encrypt them. */
675     if (contents == NULL || *contents == NULL || local_tgt_key == NULL)
676         return make_padata(KRB5_PADATA_FX_COOKIE, "MIT", 3, cookie_out);
677 
678     ret = derive_cookie_key(context, local_tgt_key, client_princ, &key);
679     if (ret)
680         goto cleanup;
681 
682     /* Encode the cookie. */
683     ret = krb5_timeofday(context, &now);
684     if (ret)
685         goto cleanup;
686     cookie.time = ts2tt(now);
687     cookie.data = contents;
688     ret = encode_krb5_secure_cookie(&cookie, &der_cookie);
689     if (ret)
690         goto cleanup;
691 
692     /* Encrypt the cookie in key. */
693     ret = krb5_c_encrypt_length(context, key->enctype, der_cookie->length,
694                                 &ctlen);
695     if (ret)
696         goto cleanup;
697     ret = alloc_data(&enc.ciphertext, ctlen);
698     if (ret)
699         goto cleanup;
700     ret = krb5_c_encrypt(context, key, KRB5_KEYUSAGE_PA_FX_COOKIE, NULL,
701                          der_cookie, &enc);
702     if (ret)
703         goto cleanup;
704 
705     /* Construct the cookie pa-data entry. */
706     ret = k5_alloc_pa_data(KRB5_PADATA_FX_COOKIE, 8 + enc.ciphertext.length,
707                            &pa);
708     memcpy(pa->contents, "MIT1", 4);
709     store_32_be(current_kvno(local_tgt), pa->contents + 4);
710     memcpy(pa->contents + 8, enc.ciphertext.data, enc.ciphertext.length);
711     *cookie_out = pa;
712 
713 cleanup:
714     krb5_free_keyblock(context, key);
715     if (der_cookie != NULL) {
716         zapfree(der_cookie->data, der_cookie->length);
717         free(der_cookie);
718     }
719     krb5_free_data_contents(context, &enc.ciphertext);
720     return ret;
721 }
722