pull up r25483 and r25484 from trunk
[krb5.git] / src / lib / krb5 / krb / fast.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/fast.c */
3 /*
4  * Copyright (C) 2009 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 /*
30  * It is possible to support sending a request that includes both a FAST and
31  * normal version.  This would complicate the pre-authentication logic
32  * significantly.  You would need to maintain two contexts, one for FAST and
33  * one for normal use.  In adition, you would need to manage the security
34  * issues surrounding downgrades.  However trying FAST at all requires an armor
35  * key.  Generally in obtaining the armor key, the client learns enough to know
36  * that FAST is supported.  If not, the client can see FAST in the
37  * preauth_required error's padata and retry with FAST.  So, this
38  * implementation does not support FAST+normal.
39  *
40  * We store the outer version of the request to use.  The caller stores the
41  * inner version.  We handle the encoding of the request body (and request) and
42  * provide encoded request bodies for the caller to use as these may be used
43  * for checksums.  In the AS case we also evaluate whether to continue a
44  * conversation as one of the important questions there is the presence of a
45  * cookie.
46  */
47 #include "fast.h"
48 #include "int-proto.h"
49
50 static krb5_error_code
51 fast_armor_ap_request(krb5_context context,
52                       struct krb5int_fast_request_state *state,
53                       krb5_ccache ccache, krb5_principal target_principal)
54 {
55     krb5_error_code retval = 0;
56     krb5_creds creds, *out_creds = NULL;
57     krb5_auth_context authcontext = NULL;
58     krb5_data encoded_authenticator;
59     krb5_fast_armor *armor = NULL;
60     krb5_keyblock *subkey = NULL, *armor_key = NULL;
61
62     encoded_authenticator.data = NULL;
63     memset(&creds, 0, sizeof(creds));
64     creds.server = target_principal;
65     retval = krb5_cc_get_principal(context, ccache, &creds.client);
66     if (retval == 0)
67         retval = krb5_get_credentials(context, 0, ccache,  &creds, &out_creds);
68     if (retval == 0) {
69         TRACE_FAST_ARMOR_CCACHE_KEY(context, &out_creds->keyblock);
70         retval = krb5_mk_req_extended(context, &authcontext,
71                                       AP_OPTS_USE_SUBKEY, NULL /*data*/,
72                                       out_creds, &encoded_authenticator);
73     }
74     if (retval == 0)
75         retval = krb5_auth_con_getsendsubkey(context, authcontext, &subkey);
76     if (retval == 0)
77         retval = krb5_c_fx_cf2_simple(context, subkey, "subkeyarmor",
78                                       &out_creds->keyblock, "ticketarmor",
79                                       &armor_key);
80     if (retval == 0) {
81         TRACE_FAST_ARMOR_KEY(context, armor_key);
82         armor = calloc(1, sizeof(krb5_fast_armor));
83         if (armor == NULL)
84             retval = ENOMEM;
85     }
86     if (retval == 0) {
87         armor->armor_type = KRB5_FAST_ARMOR_AP_REQUEST;
88         armor->armor_value = encoded_authenticator;
89         encoded_authenticator.data = NULL;
90         encoded_authenticator.length = 0;
91         state->armor = armor;
92         armor = NULL;
93         state->armor_key = armor_key;
94         armor_key = NULL;
95     }
96     krb5_free_keyblock(context, armor_key);
97     krb5_free_keyblock(context, subkey);
98     if (out_creds)
99         krb5_free_creds(context, out_creds);
100     /* target_principal is owned by caller. */
101     creds.server = NULL;
102     krb5_free_cred_contents(context, &creds);
103     if (encoded_authenticator.data)
104         krb5_free_data_contents(context, &encoded_authenticator);
105     krb5_auth_con_free(context, authcontext);
106     return retval;
107 }
108
109 krb5_error_code
110 krb5int_fast_prep_req_body(krb5_context context,
111                            struct krb5int_fast_request_state *state,
112                            krb5_kdc_req *request,
113                            krb5_data **encoded_request_body)
114 {
115     krb5_error_code retval = 0;
116     krb5_data *local_encoded_request_body = NULL;
117
118     assert(state != NULL);
119     *encoded_request_body = NULL;
120     if (state->armor_key == NULL)
121         return encode_krb5_kdc_req_body(request, encoded_request_body);
122     state->fast_outer_request = *request;
123     state->fast_outer_request.padata = NULL;
124     if (retval == 0)
125         retval = encode_krb5_kdc_req_body(&state->fast_outer_request,
126                                           &local_encoded_request_body);
127     if (retval == 0) {
128         *encoded_request_body = local_encoded_request_body;
129         local_encoded_request_body = NULL;
130     }
131     if (local_encoded_request_body != NULL)
132         krb5_free_data(context, local_encoded_request_body);
133     return retval;
134 }
135
136 krb5_error_code
137 krb5int_fast_as_armor(krb5_context context,
138                       struct krb5int_fast_request_state *state,
139                       krb5_gic_opt_ext *opte,
140                       krb5_kdc_req *request)
141 {
142     krb5_error_code retval = 0;
143     krb5_ccache ccache = NULL;
144     krb5_principal target_principal = NULL;
145     krb5_data *target_realm;
146
147     krb5_clear_error_message(context);
148     target_realm = krb5_princ_realm(context, request->server);
149     if (opte->opt_private->fast_ccache_name) {
150         TRACE_FAST_ARMOR_CCACHE(context, opte->opt_private->fast_ccache_name);
151         state->fast_state_flags |= KRB5INT_FAST_ARMOR_AVAIL;
152         retval = krb5_cc_resolve(context, opte->opt_private->fast_ccache_name,
153                                  &ccache);
154         if (retval == 0) {
155             retval = krb5int_tgtname(context, target_realm, target_realm,
156                                      &target_principal);
157         }
158         if (retval == 0) {
159             krb5_data config_data;
160             config_data.data = NULL;
161             retval = krb5_cc_get_config(context, ccache, target_principal,
162                                         KRB5_CONF_FAST_AVAIL, &config_data);
163             if ((retval == 0) && config_data.data) {
164                 TRACE_FAST_CCACHE_CONFIG(context);
165                 state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
166             }
167             krb5_free_data_contents(context, &config_data);
168             retval = 0;
169         }
170         if (opte->opt_private->fast_flags & KRB5_FAST_REQUIRED) {
171             TRACE_FAST_REQUIRED(context);
172             state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
173         }
174         if (retval == 0 && (state->fast_state_flags & KRB5INT_FAST_DO_FAST)) {
175             retval = fast_armor_ap_request(context, state, ccache,
176                                            target_principal);
177         }
178         if (retval != 0) {
179             const char * errmsg;
180             errmsg = krb5_get_error_message(context, retval);
181             krb5_set_error_message(context, retval,
182                                    _("%s constructing AP-REQ armor"), errmsg);
183             krb5_free_error_message(context, errmsg);
184         }
185     }
186     if (ccache)
187         krb5_cc_close(context, ccache);
188     if (target_principal)
189         krb5_free_principal(context, target_principal);
190     return retval;
191 }
192
193
194 krb5_error_code
195 krb5int_fast_prep_req(krb5_context context,
196                       struct krb5int_fast_request_state *state,
197                       krb5_kdc_req *request,
198                       const krb5_data *to_be_checksummed,
199                       kdc_req_encoder_proc encoder,
200                       krb5_data **encoded_request)
201 {
202     krb5_error_code retval = 0;
203     krb5_pa_data *pa_array[2];
204     krb5_pa_data pa[2];
205     krb5_fast_req fast_req;
206     krb5_fast_armored_req *armored_req = NULL;
207     krb5_data *encoded_fast_req = NULL;
208     krb5_data *encoded_armored_req = NULL;
209     krb5_data *local_encoded_result = NULL;
210     krb5_data random_data;
211     char random_buf[4];
212
213     assert(state != NULL);
214     assert(state->fast_outer_request.padata == NULL);
215     memset(pa_array, 0, sizeof pa_array);
216     if (state->armor_key == NULL) {
217         return encoder(request, encoded_request);
218     }
219
220     TRACE_FAST_ENCODE(context);
221     /* Fill in a fresh random nonce for each inner request*/
222     random_data.length = 4;
223     random_data.data = (char *)random_buf;
224     retval = krb5_c_random_make_octets(context, &random_data);
225     if (retval == 0) {
226         request->nonce = 0x7fffffff & load_32_n(random_buf);
227         state->nonce = request->nonce;
228     }
229     fast_req.req_body =  request;
230     if (fast_req.req_body->padata == NULL) {
231         fast_req.req_body->padata = calloc(1, sizeof(krb5_pa_data *));
232         if (fast_req.req_body->padata == NULL)
233             retval = ENOMEM;
234     }
235     fast_req.fast_options = state->fast_options;
236     if (retval == 0)
237         retval = encode_krb5_fast_req(&fast_req, &encoded_fast_req);
238     if (retval == 0) {
239         armored_req = calloc(1, sizeof(krb5_fast_armored_req));
240         if (armored_req == NULL)
241             retval = ENOMEM;
242     }
243     if (retval == 0)
244         armored_req->armor = state->armor;
245     if (retval ==0)
246         retval = krb5_c_make_checksum(context, 0, state->armor_key,
247                                       KRB5_KEYUSAGE_FAST_REQ_CHKSUM,
248                                       to_be_checksummed,
249                                       &armored_req->req_checksum);
250     if (retval == 0)
251         retval = krb5_encrypt_helper(context, state->armor_key,
252                                      KRB5_KEYUSAGE_FAST_ENC, encoded_fast_req,
253                                      &armored_req->enc_part);
254     if (retval == 0)
255         retval = encode_krb5_pa_fx_fast_request(armored_req,
256                                                 &encoded_armored_req);
257     if (retval==0) {
258         pa[0].pa_type = KRB5_PADATA_FX_FAST;
259         pa[0].contents = (unsigned char *) encoded_armored_req->data;
260         pa[0].length = encoded_armored_req->length;
261         pa_array[0] = &pa[0];
262     }
263     state->fast_outer_request.padata = pa_array;
264     if(retval == 0)
265         retval = encoder(&state->fast_outer_request, &local_encoded_result);
266     if (retval == 0) {
267         *encoded_request = local_encoded_result;
268         local_encoded_result = NULL;
269     }
270     if (encoded_armored_req)
271         krb5_free_data(context, encoded_armored_req);
272     if (armored_req) {
273         armored_req->armor = NULL; /*owned by state*/
274         krb5_free_fast_armored_req(context, armored_req);
275     }
276     if (encoded_fast_req)
277         krb5_free_data(context, encoded_fast_req);
278     if (local_encoded_result)
279         krb5_free_data(context, local_encoded_result);
280     state->fast_outer_request.padata = NULL;
281     return retval;
282 }
283
284 static krb5_error_code
285 decrypt_fast_reply(krb5_context context,
286                    struct krb5int_fast_request_state *state,
287                    krb5_pa_data **in_padata,
288                    krb5_fast_response **response)
289 {
290     krb5_error_code retval = 0;
291     krb5_data scratch;
292     krb5_enc_data *encrypted_response = NULL;
293     krb5_pa_data *fx_reply = NULL;
294     krb5_fast_response *local_resp = NULL;
295
296     assert(state != NULL);
297     assert(state->armor_key);
298     fx_reply = krb5int_find_pa_data(context, in_padata, KRB5_PADATA_FX_FAST);
299     if (fx_reply == NULL)
300         retval = KRB5_ERR_FAST_REQUIRED;
301     TRACE_FAST_DECODE(context);
302     if (retval == 0) {
303         scratch.data = (char *) fx_reply->contents;
304         scratch.length = fx_reply->length;
305         retval = decode_krb5_pa_fx_fast_reply(&scratch, &encrypted_response);
306     }
307     scratch.data = NULL;
308     if (retval == 0) {
309         scratch.data = malloc(encrypted_response->ciphertext.length);
310         if (scratch.data == NULL)
311             retval = ENOMEM;
312         scratch.length = encrypted_response->ciphertext.length;
313     }
314     if (retval == 0)
315         retval = krb5_c_decrypt(context, state->armor_key,
316                                 KRB5_KEYUSAGE_FAST_REP, NULL,
317                                 encrypted_response, &scratch);
318     if (retval != 0) {
319         const char * errmsg;
320         errmsg = krb5_get_error_message(context, retval);
321         krb5_set_error_message(context, retval,
322                                _("%s while decrypting FAST reply"), errmsg);
323         krb5_free_error_message(context, errmsg);
324     }
325     if (retval == 0)
326         retval = decode_krb5_fast_response(&scratch, &local_resp);
327     if (retval == 0) {
328         if (local_resp->nonce != state->nonce) {
329             retval = KRB5_KDCREP_MODIFIED;
330             krb5_set_error_message(context, retval,
331                                    _("nonce modified in FAST response: "
332                                      "KDC response modified"));
333         }
334     }
335     if (retval == 0) {
336         *response = local_resp;
337         local_resp = NULL;
338     }
339     if (scratch.data)
340         free(scratch.data);
341     if (encrypted_response)
342         krb5_free_enc_data(context, encrypted_response);
343     if (local_resp)
344         krb5_free_fast_response(context, local_resp);
345     return retval;
346 }
347
348 /*
349  * If state contains an armor key and *err_replyptr contains a FAST error,
350  * decode it and set *err_replyptr to the inner error and *out_padata to the
351  * padata in the FAST response.  Otherwise, leave *err_replyptr alone and set
352  * *out_padata to the error e_data decoded as pa-data or typed-data, or to NULL
353  * if it doesn't decode as either.  In either case, set *retry to indicate
354  * whether the client should try to make a follow-up request.
355  */
356 krb5_error_code
357 krb5int_fast_process_error(krb5_context context,
358                            struct krb5int_fast_request_state *state,
359                            krb5_error **err_replyptr,
360                            krb5_pa_data ***out_padata,
361                            krb5_boolean *retry)
362 {
363     krb5_error_code retval = 0;
364     krb5_error *err_reply = *err_replyptr;
365
366     *out_padata = NULL;
367     *retry = 0;
368     if (state->armor_key) {
369         krb5_pa_data *fx_error_pa;
370         krb5_pa_data **result = NULL;
371         krb5_data scratch;
372         krb5_error *fx_error = NULL;
373         krb5_fast_response *fast_response = NULL;
374
375         retval = decode_krb5_padata_sequence(&err_reply->e_data, &result);
376         if (retval == 0)
377             retval = decrypt_fast_reply(context, state, result,
378                                         &fast_response);
379         if (retval) {
380             /*
381              * This can happen if the KDC does not understand FAST. We don't
382              * expect that, but treating it as the fatal error indicated by the
383              * KDC seems reasonable.
384              */
385             *retry = 0;
386             krb5_free_pa_data(context, result);
387             return 0;
388         }
389         krb5_free_pa_data(context, result);
390         result = NULL;
391         if (retval == 0) {
392             fx_error_pa = krb5int_find_pa_data(context, fast_response->padata,
393                                                KRB5_PADATA_FX_ERROR);
394             if (fx_error_pa == NULL) {
395                 krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED,
396                                        _("Expecting FX_ERROR pa-data inside "
397                                          "FAST container"));
398                 retval = KRB5KDC_ERR_PREAUTH_FAILED;
399             }
400         }
401         if (retval == 0) {
402             scratch.data = (char *) fx_error_pa->contents;
403             scratch.length = fx_error_pa->length;
404             retval = decode_krb5_error(&scratch, &fx_error);
405         }
406         if (retval == 0) {
407             krb5_free_error(context, err_reply);
408             *err_replyptr = fx_error;
409             fx_error = NULL;
410             *out_padata = fast_response->padata;
411             fast_response->padata = NULL;
412             /*
413              * If there is more than the fx_error padata, then we want
414              * to retry the error if a cookie is present
415              */
416             *retry = (*out_padata)[1] != NULL;
417             if (krb5int_find_pa_data(context, *out_padata,
418                                      KRB5_PADATA_FX_COOKIE) == NULL)
419                 *retry = 0;
420         }
421         if (fx_error)
422             krb5_free_error(context, fx_error);
423         krb5_free_fast_response(context, fast_response);
424     } else { /*not FAST*/
425         /* Possibly retry if there's any e_data to process. */
426         *retry = (err_reply->e_data.length > 0);
427         /* Try to decode e_data as pa-data or typed-data for out_padata. */
428         retval = decode_krb5_padata_sequence(&err_reply->e_data, out_padata);
429         if (retval != 0) {
430             krb5_typed_data **tdata;
431             /* krb5_typed data and krb5_pa_data are compatible structures. */
432             if (decode_krb5_typed_data(&err_reply->e_data, &tdata) == 0)
433                 *out_padata = (krb5_pa_data **)tdata;
434             retval = 0;
435         }
436     }
437     return retval;
438 }
439
440
441 krb5_error_code
442 krb5int_fast_process_response(krb5_context context,
443                               struct krb5int_fast_request_state *state,
444                               krb5_kdc_rep *resp,
445                               krb5_keyblock **strengthen_key)
446 {
447     krb5_error_code retval = 0;
448     krb5_fast_response *fast_response = NULL;
449     krb5_data *encoded_ticket = NULL;
450     krb5_boolean cksum_valid;
451
452     krb5_clear_error_message(context);
453     *strengthen_key = NULL;
454     if (state->armor_key == 0)
455         return 0;
456     retval = decrypt_fast_reply(context, state, resp->padata,
457                                 &fast_response);
458     if (retval == 0) {
459         if (fast_response->finished == 0) {
460             retval = KRB5_KDCREP_MODIFIED;
461             krb5_set_error_message(context, retval,
462                                    _("FAST response missing finish message "
463                                      "in KDC reply"));
464         }
465     }
466     if (retval == 0)
467         retval = encode_krb5_ticket(resp->ticket, &encoded_ticket);
468     if (retval == 0)
469         retval = krb5_c_verify_checksum(context, state->armor_key,
470                                         KRB5_KEYUSAGE_FAST_FINISHED,
471                                         encoded_ticket,
472                                         &fast_response->finished->ticket_checksum,
473                                         &cksum_valid);
474     if (retval == 0 && cksum_valid == 0) {
475         retval = KRB5_KDCREP_MODIFIED;
476         krb5_set_error_message(context, retval,
477                                _("Ticket modified in KDC reply"));
478     }
479     if (retval == 0) {
480         krb5_free_principal(context, resp->client);
481         resp->client = fast_response->finished->client;
482         fast_response->finished->client = NULL;
483         *strengthen_key = fast_response->strengthen_key;
484         fast_response->strengthen_key = NULL;
485         krb5_free_pa_data(context, resp->padata);
486         resp->padata = fast_response->padata;
487         fast_response->padata = NULL;
488     }
489     if (fast_response)
490         krb5_free_fast_response(context, fast_response);
491     if (encoded_ticket)
492         krb5_free_data(context, encoded_ticket);
493     return retval;
494 }
495
496 krb5_error_code
497 krb5int_fast_reply_key(krb5_context context,
498                        krb5_keyblock *strengthen_key,
499                        krb5_keyblock *existing_key,
500                        krb5_keyblock *out_key)
501 {
502     krb5_keyblock *key = NULL;
503     krb5_error_code retval = 0;
504     krb5_free_keyblock_contents(context, out_key);
505     if (strengthen_key) {
506         retval = krb5_c_fx_cf2_simple(context, strengthen_key,
507                                       "strengthenkey", existing_key,
508                                       "replykey", &key);
509         if (retval == 0) {
510             TRACE_FAST_REPLY_KEY(context, key);
511             *out_key = *key;
512             free(key);
513         }
514     } else {
515         retval = krb5_copy_keyblock_contents(context, existing_key, out_key);
516     }
517     return retval;
518 }
519
520
521 krb5_error_code
522 krb5int_fast_make_state(krb5_context context,
523                         struct krb5int_fast_request_state **state)
524 {
525     struct krb5int_fast_request_state *local_state ;
526
527     local_state = malloc(sizeof *local_state);
528     if (local_state == NULL)
529         return ENOMEM;
530     memset(local_state, 0, sizeof(*local_state));
531     *state = local_state;
532     return 0;
533 }
534
535 void
536 krb5int_fast_free_state(krb5_context context,
537                         struct krb5int_fast_request_state *state)
538 {
539     if (state == NULL)
540         return;
541     /*We are responsible for none of the store in the fast_outer_req*/
542     krb5_free_keyblock(context, state->armor_key);
543     krb5_free_fast_armor(context, state->armor);
544     free(state);
545 }
546
547 krb5_pa_data *
548 krb5int_find_pa_data(krb5_context context, krb5_pa_data *const *padata,
549                      krb5_preauthtype pa_type)
550 {
551     krb5_pa_data * const *tmppa;
552
553     if (padata == NULL)
554         return NULL;
555
556     for (tmppa = padata; *tmppa != NULL; tmppa++) {
557         if ((*tmppa)->pa_type == pa_type)
558             break;
559     }
560
561     return *tmppa;
562 }
563
564
565 krb5_error_code
566 krb5int_fast_verify_nego(krb5_context context,
567                          struct krb5int_fast_request_state *state,
568                          krb5_kdc_rep *rep, krb5_data *request,
569                          krb5_keyblock *decrypting_key,
570                          krb5_boolean *fast_avail)
571 {
572     krb5_error_code retval = 0;
573     krb5_checksum *checksum = NULL;
574     krb5_pa_data *pa;
575     krb5_data scratch;
576     krb5_boolean valid;
577
578     *fast_avail = FALSE;
579     if (rep->enc_part2->flags& TKT_FLG_ENC_PA_REP) {
580         pa = krb5int_find_pa_data(context, rep->enc_part2->enc_padata,
581                                   KRB5_ENCPADATA_REQ_ENC_PA_REP);
582         if (pa == NULL)
583             retval = KRB5_KDCREP_MODIFIED;
584         else {
585             scratch.data = (char *) pa->contents;
586             scratch.length = pa->length;
587         }
588         if (retval == 0)
589             retval = decode_krb5_checksum(&scratch, &checksum);
590         if (retval == 0)
591             retval = krb5_c_verify_checksum(context, decrypting_key,
592                                             KRB5_KEYUSAGE_AS_REQ,
593                                             request, checksum, &valid);
594         if (retval == 0 &&valid == 0)
595             retval = KRB5_KDCREP_MODIFIED;
596         if (retval == 0) {
597             pa = krb5int_find_pa_data(context, rep->enc_part2->enc_padata,
598                                       KRB5_PADATA_FX_FAST);
599             *fast_avail = (pa != NULL);
600         }
601     }
602     TRACE_FAST_NEGO(context, *fast_avail);
603     if (checksum)
604         krb5_free_checksum(context, checksum);
605     return retval;
606 }
607
608 krb5_boolean
609 krb5int_upgrade_to_fast_p(krb5_context context,
610                           struct krb5int_fast_request_state *state,
611                           krb5_pa_data **padata)
612 {
613     if (state->armor_key != NULL)
614         return FALSE; /* Already using FAST. */
615     if (!(state->fast_state_flags & KRB5INT_FAST_ARMOR_AVAIL))
616         return FALSE;
617     if (krb5int_find_pa_data(context, padata, KRB5_PADATA_FX_FAST) != NULL) {
618         TRACE_FAST_PADATA_UPGRADE(context);
619         state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
620         return TRUE;
621     }
622     return FALSE;
623 }