pull up r23628 from trunk
[krb5.git] / src / kdc / fast_util.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * kdc/fast_util.c
4  *
5  * Copyright (C) 2009 by the Massachusetts Institute of Technology.
6  * All rights reserved.
7  *
8  * Export of this software from the United States of America may
9  *   require a specific license from the United States Government.
10  *   It is the responsibility of any person or organization contemplating
11  *   export to obtain such a license before exporting.
12  *
13  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
14  * distribute this software and its documentation for any purpose and
15  * without fee is hereby granted, provided that the above copyright
16  * notice appear in all copies and that both that copyright notice and
17  * this permission notice appear in supporting documentation, and that
18  * the name of M.I.T. not be used in advertising or publicity pertaining
19  * to distribution of the software without specific, written prior
20  * permission.  Furthermore if you modify this software you must label
21  * your software as modified software and not distribute it in such a
22  * fashion that it might be confused with the original M.I.T. software.
23  * M.I.T. makes no representations about the suitability of
24  * this software for any purpose.  It is provided "as is" without express
25  * or implied warranty.
26  */
27
28 #include <k5-int.h>
29
30 #include "kdc_util.h"
31 #include "extern.h"
32
33
34 /*
35  * This function will find the fast and cookie padata and if fast is
36  * successfully processed, will throw away (and free) the outer
37  * request and update the pointer to point to the inner request.  The
38  * checksummed_data points to the data that is in the
39  * armored_fast_request checksum; either the pa-tgs-req or the
40  * kdc-req-body.
41  */
42
43 static krb5_error_code armor_ap_request
44 (struct kdc_request_state *state, krb5_fast_armor *armor)
45 {
46     krb5_error_code retval = 0;
47     krb5_auth_context authcontext = NULL;
48     krb5_ticket *ticket = NULL;
49     krb5_keyblock *subkey = NULL;
50
51     assert(armor->armor_type == KRB5_FAST_ARMOR_AP_REQUEST);
52     krb5_clear_error_message(kdc_context);
53     retval = krb5_auth_con_init(kdc_context, &authcontext);
54     if (retval == 0)
55         retval = krb5_auth_con_setflags(kdc_context,
56                                         authcontext, 0); /*disable replay cache*/
57     retval = krb5_rd_req(kdc_context, &authcontext,
58                          &armor->armor_value, NULL /*server*/,
59                          kdc_active_realm->realm_keytab,  NULL, &ticket);
60     if (retval != 0) {
61         const char * errmsg = krb5_get_error_message(kdc_context, retval);
62         krb5_set_error_message(kdc_context, retval,
63                                "%s while handling ap-request armor", errmsg);
64         krb5_free_error_message(kdc_context, errmsg);
65     }
66     if (retval == 0) {
67         if (!krb5_principal_compare_any_realm(kdc_context,
68                                               tgs_server,
69                                               ticket->server)) {
70             krb5_set_error_message(kdc_context, KRB5KDC_ERR_SERVER_NOMATCH,
71                                    "ap-request armor for something other than  the local TGS");
72             retval = KRB5KDC_ERR_SERVER_NOMATCH;
73         }
74     }
75     if (retval == 0) {
76         retval = krb5_auth_con_getrecvsubkey(kdc_context, authcontext, &subkey);
77         if (retval != 0 || subkey == NULL) {
78             krb5_set_error_message(kdc_context, KRB5KDC_ERR_POLICY,
79                                    "ap-request armor without subkey");
80             retval = KRB5KDC_ERR_POLICY;
81         }
82     }
83     if (retval == 0)
84         retval = krb5_c_fx_cf2_simple(kdc_context,
85                                       subkey, "subkeyarmor",
86                                       ticket->enc_part2->session, "ticketarmor",
87                                       &state->armor_key);
88     if (ticket)
89         krb5_free_ticket(kdc_context, ticket);
90     if (subkey)
91         krb5_free_keyblock(kdc_context, subkey);
92     if (authcontext)
93         krb5_auth_con_free(kdc_context, authcontext);
94     return retval;
95 }
96
97 static krb5_error_code
98 encrypt_fast_reply(struct kdc_request_state *state,
99                    const krb5_fast_response *response,
100                    krb5_data **fx_fast_reply)
101 {
102     krb5_error_code retval = 0;
103     krb5_enc_data encrypted_reply;
104     krb5_data *encoded_response = NULL;
105     assert(state->armor_key);
106     retval = encode_krb5_fast_response(response, &encoded_response);
107     if (retval== 0)
108         retval = krb5_encrypt_helper(kdc_context, state->armor_key,
109                                      KRB5_KEYUSAGE_FAST_REP,
110                                      encoded_response, &encrypted_reply);
111     if (encoded_response)
112         krb5_free_data(kdc_context, encoded_response);
113     encoded_response = NULL;
114     if (retval == 0) {
115         retval = encode_krb5_pa_fx_fast_reply(&encrypted_reply,
116                                               fx_fast_reply);
117         krb5_free_data_contents(kdc_context, &encrypted_reply.ciphertext);
118     }
119     return retval;
120 }
121
122
123 krb5_error_code
124 kdc_find_fast(krb5_kdc_req **requestptr,
125               krb5_data *checksummed_data,
126               krb5_keyblock *tgs_subkey,
127               krb5_keyblock *tgs_session,
128               struct kdc_request_state *state)
129 {
130     krb5_error_code retval = 0;
131     krb5_pa_data *fast_padata, *cookie_padata = NULL;
132     krb5_data scratch;
133     krb5_fast_req * fast_req = NULL;
134     krb5_kdc_req *request = *requestptr;
135     krb5_fast_armored_req *fast_armored_req = NULL;
136     krb5_checksum *cksum;
137     krb5_boolean cksum_valid;
138     krb5_keyblock empty_keyblock;
139
140     scratch.data = NULL;
141     krb5_clear_error_message(kdc_context);
142     memset(&empty_keyblock, 0, sizeof(krb5_keyblock));
143     fast_padata = find_pa_data(request->padata,
144                                KRB5_PADATA_FX_FAST);
145     if (fast_padata !=  NULL){
146         scratch.length = fast_padata->length;
147         scratch.data = (char *) fast_padata->contents;
148         retval = decode_krb5_pa_fx_fast_request(&scratch, &fast_armored_req);
149         if (retval == 0 &&fast_armored_req->armor) {
150             switch (fast_armored_req->armor->armor_type) {
151             case KRB5_FAST_ARMOR_AP_REQUEST:
152                 if (tgs_subkey) {
153                     krb5_set_error_message( kdc_context, KRB5KDC_ERR_PREAUTH_FAILED,
154                                             "Ap-request armor not permitted with TGS");
155                     retval =  KRB5KDC_ERR_PREAUTH_FAILED;
156                     break;
157                 }
158                 retval = armor_ap_request(state, fast_armored_req->armor);
159                 break;
160             default:
161                 krb5_set_error_message(kdc_context, KRB5KDC_ERR_PREAUTH_FAILED,
162                                        "Unknow FAST armor type %d",
163                                        fast_armored_req->armor->armor_type);
164                 retval = KRB5KDC_ERR_PREAUTH_FAILED;
165             }
166         }
167         if (retval == 0 && !state->armor_key) {
168             if (tgs_subkey)
169                 retval = krb5_c_fx_cf2_simple(kdc_context,
170                                               tgs_subkey, "subkeyarmor",
171                                               tgs_session, "ticketarmor",
172                                               &state->armor_key);
173             else {
174                 krb5_set_error_message(kdc_context, KRB5KDC_ERR_PREAUTH_FAILED,
175                                        "No armor key but FAST armored request present");
176                 retval = KRB5KDC_ERR_PREAUTH_FAILED;
177             }
178         }
179         if (retval == 0) {
180             krb5_data plaintext;
181             plaintext.length = fast_armored_req->enc_part.ciphertext.length;
182             plaintext.data = malloc(plaintext.length);
183             if (plaintext.data == NULL)
184                 retval = ENOMEM;
185             retval = krb5_c_decrypt(kdc_context,
186                                     state->armor_key,
187                                     KRB5_KEYUSAGE_FAST_ENC, NULL,
188                                     &fast_armored_req->enc_part,
189                                     &plaintext);
190             if (retval == 0)
191                 retval = decode_krb5_fast_req(&plaintext, &fast_req);
192             if (plaintext.data)
193                 free(plaintext.data);
194         }
195         cksum = &fast_armored_req->req_checksum;
196         if (retval == 0)
197             retval = krb5_c_verify_checksum(kdc_context, state->armor_key,
198                                             KRB5_KEYUSAGE_FAST_REQ_CHKSUM,
199                                             checksummed_data, cksum,
200                                             &cksum_valid);
201         if (retval == 0 && !cksum_valid) {
202             retval = KRB5KRB_AP_ERR_MODIFIED;
203             krb5_set_error_message(kdc_context, KRB5KRB_AP_ERR_MODIFIED,
204                                    "FAST req_checksum invalid; request modified");
205         }
206         if (retval == 0) {
207             if (!krb5_c_is_keyed_cksum(cksum->checksum_type)) {
208                 retval = KRB5KDC_ERR_POLICY;
209                 krb5_set_error_message(kdc_context, KRB5KDC_ERR_POLICY,
210                                        "Unkeyed checksum used in fast_req");
211             }
212         }
213         if (retval == 0) {
214             if ((fast_req->fast_options & UNSUPPORTED_CRITICAL_FAST_OPTIONS) != 0)
215                 retval = KRB5KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTION;
216         }
217         if (retval == 0)
218             cookie_padata = find_pa_data(fast_req->req_body->padata,
219                                          KRB5_PADATA_FX_COOKIE);
220         if (retval == 0) {
221             state->fast_options = fast_req->fast_options;
222             if (request->kdc_state == state)
223                 request->kdc_state = NULL;
224             krb5_free_kdc_req( kdc_context, request);
225             *requestptr = fast_req->req_body;
226             fast_req->req_body = NULL;
227         }
228     }
229     else cookie_padata = find_pa_data(request->padata, KRB5_PADATA_FX_COOKIE);
230     if (retval == 0 && cookie_padata != NULL) {
231         krb5_pa_data *new_padata = malloc(sizeof (krb5_pa_data));
232         if (new_padata == NULL) {
233             retval = ENOMEM;
234         } else {
235             new_padata->pa_type = KRB5_PADATA_FX_COOKIE;
236             new_padata->length = cookie_padata->length;
237             new_padata->contents = malloc(new_padata->length);
238             if (new_padata->contents == NULL) {
239                 retval = ENOMEM;
240                 free(new_padata);
241             } else {
242                 memcpy(new_padata->contents, cookie_padata->contents,
243                        new_padata->length);
244                 state->cookie = new_padata;
245             }
246         }
247     }
248     if (fast_req)
249         krb5_free_fast_req( kdc_context, fast_req);
250     if (fast_armored_req)
251         krb5_free_fast_armored_req(kdc_context, fast_armored_req);
252     return retval;
253 }
254
255
256 krb5_error_code
257 kdc_make_rstate(struct kdc_request_state **out)
258 {
259     struct kdc_request_state *state = malloc( sizeof(struct kdc_request_state));
260     if (state == NULL)
261         return ENOMEM;
262     memset( state, 0, sizeof(struct kdc_request_state));
263     *out = state;
264     return 0;
265 }
266
267 void
268 kdc_free_rstate (struct kdc_request_state *s)
269 {
270     if (s == NULL)
271         return;
272     if (s->armor_key)
273         krb5_free_keyblock(kdc_context, s->armor_key);
274     if (s->strengthen_key)
275         krb5_free_keyblock(kdc_context, s->strengthen_key);
276     if (s->cookie) {
277         free(s->cookie->contents);
278         free(s->cookie);
279     }
280     free(s);
281 }
282
283 krb5_error_code
284 kdc_fast_response_handle_padata(struct kdc_request_state *state,
285                                 krb5_kdc_req *request,
286                                 krb5_kdc_rep *rep, krb5_enctype enctype)
287 {
288     krb5_error_code retval = 0;
289     krb5_fast_finished finish;
290     krb5_fast_response fast_response;
291     krb5_data *encoded_ticket = NULL;
292     krb5_data *encrypted_reply = NULL;
293     krb5_pa_data *pa = NULL, **pa_array = NULL;
294     krb5_cksumtype cksumtype = CKSUMTYPE_RSA_MD5;
295     krb5_pa_data *empty_padata[] = {NULL};
296     krb5_keyblock *strengthen_key = NULL;
297
298     if (!state->armor_key)
299         return 0;
300     memset(&finish, 0, sizeof(finish));
301     retval = krb5_init_keyblock(kdc_context, enctype, 0, &strengthen_key);
302     if (retval == 0)
303         retval = krb5_c_make_random_key(kdc_context, enctype, strengthen_key);
304     if (retval == 0) {
305         state->strengthen_key = strengthen_key;
306         strengthen_key = NULL;
307     }
308
309     fast_response.padata = rep->padata;
310     if (fast_response.padata == NULL)
311         fast_response.padata = &empty_padata[0];
312     fast_response.strengthen_key = state->strengthen_key;
313     fast_response.nonce = request->nonce;
314     fast_response.finished = &finish;
315     finish.client = rep->client;
316     pa_array = calloc(3, sizeof(*pa_array));
317     if (pa_array == NULL)
318         retval = ENOMEM;
319     pa = calloc(1, sizeof(krb5_pa_data));
320     if (retval == 0 && pa == NULL)
321         retval = ENOMEM;
322     if (retval == 0)
323         retval = krb5_us_timeofday(kdc_context, &finish.timestamp, &finish.usec);
324     if (retval == 0)
325         retval = encode_krb5_ticket(rep->ticket, &encoded_ticket);
326     if (retval == 0)
327         retval = krb5int_c_mandatory_cksumtype(kdc_context,
328                                                state->armor_key->enctype,
329                                                &cksumtype);
330     if (retval == 0)
331         retval = krb5_c_make_checksum(kdc_context, cksumtype,
332                                       state->armor_key,
333                                       KRB5_KEYUSAGE_FAST_FINISHED,
334                                       encoded_ticket, &finish.ticket_checksum);
335     if (retval == 0)
336         retval = encrypt_fast_reply(state, &fast_response, &encrypted_reply);
337     if (retval == 0) {
338         pa[0].pa_type = KRB5_PADATA_FX_FAST;
339         pa[0].length = encrypted_reply->length;
340         pa[0].contents = (unsigned char *)  encrypted_reply->data;
341         pa_array[0] = &pa[0];
342         rep->padata = pa_array;
343         pa_array = NULL;
344         free(encrypted_reply);
345         encrypted_reply = NULL;
346         pa = NULL;
347     }
348     if (pa)
349         free(pa);
350     if (pa_array)
351         free(pa_array);
352     if (encrypted_reply)
353         krb5_free_data(kdc_context, encrypted_reply);
354     if (encoded_ticket)
355         krb5_free_data(kdc_context, encoded_ticket);
356     if (strengthen_key != NULL)
357         krb5_free_keyblock(kdc_context, strengthen_key);
358     if (finish.ticket_checksum.contents)
359         krb5_free_checksum_contents(kdc_context, &finish.ticket_checksum);
360     return retval;
361 }
362
363
364 /*
365  * We assume the caller is responsible for passing us an in_padata
366  * sufficient to include in a FAST error.  In the FAST case we will
367  * throw away the e_data in the error (if any); in the non-FAST case
368  * we will not use the in_padata.
369  */
370 krb5_error_code
371 kdc_fast_handle_error(krb5_context context,
372                       struct kdc_request_state *state,
373                       krb5_kdc_req *request,
374                       krb5_pa_data  **in_padata, krb5_error *err)
375 {
376     krb5_error_code retval = 0;
377     krb5_fast_response resp;
378     krb5_error fx_error;
379     krb5_data *encoded_fx_error = NULL, *encrypted_reply = NULL;
380     krb5_pa_data pa[1];
381     krb5_pa_data *outer_pa[3], *cookie = NULL;
382     krb5_pa_data **inner_pa = NULL;
383     size_t size = 0;
384     krb5_data *encoded_e_data = NULL;
385
386     memset(outer_pa, 0, sizeof(outer_pa));
387     if (!state->armor_key)
388         return 0;
389     fx_error = *err;
390     fx_error.e_data.data = NULL;
391     fx_error.e_data.length = 0;
392     for (size = 0; in_padata&&in_padata[size]; size++);
393     size +=3;
394     inner_pa = calloc(size, sizeof(krb5_pa_data *));
395     if (inner_pa == NULL)
396         retval = ENOMEM;
397     if (retval == 0)
398         for (size=0; in_padata&&in_padata[size]; size++)
399             inner_pa[size] = in_padata[size];
400     if (retval == 0)
401         retval = encode_krb5_error(&fx_error, &encoded_fx_error);
402     if (retval == 0) {
403         pa[0].pa_type = KRB5_PADATA_FX_ERROR;
404         pa[0].length = encoded_fx_error->length;
405         pa[0].contents = (unsigned char *) encoded_fx_error->data;
406         inner_pa[size++] = &pa[0];
407         if (find_pa_data(inner_pa, KRB5_PADATA_FX_COOKIE) == NULL)
408             retval = kdc_preauth_get_cookie(state, &cookie);
409     }
410     if (cookie != NULL)
411         inner_pa[size++] = cookie;
412     if (retval == 0) {
413         resp.padata = inner_pa;
414         resp.nonce = request->nonce;
415         resp.strengthen_key = NULL;
416         resp.finished = NULL;
417     }
418     if (retval == 0)
419         retval = encrypt_fast_reply(state, &resp, &encrypted_reply);
420     if (inner_pa)
421         free(inner_pa); /*contained storage from caller and our stack*/
422     if (cookie) {
423         free(cookie->contents);
424         free(cookie);
425         cookie = NULL;
426     }
427     if (retval == 0) {
428         pa[0].pa_type = KRB5_PADATA_FX_FAST;
429         pa[0].length = encrypted_reply->length;
430         pa[0].contents = (unsigned char *) encrypted_reply->data;
431         outer_pa[0] = &pa[0];
432     }
433     retval = encode_krb5_padata_sequence(outer_pa, &encoded_e_data);
434     if (retval == 0) {
435         /*process_as holds onto a pointer to the original e_data and frees it*/
436         err->e_data = *encoded_e_data;
437         free(encoded_e_data); /*contents belong to err*/
438         encoded_e_data = NULL;
439     }
440     if (encoded_e_data)
441         krb5_free_data(kdc_context, encoded_e_data);
442     if (encrypted_reply)
443         krb5_free_data(kdc_context, encrypted_reply);
444     if (encoded_fx_error)
445         krb5_free_data(kdc_context, encoded_fx_error);
446     return retval;
447 }
448
449 krb5_error_code
450 kdc_fast_handle_reply_key(struct kdc_request_state *state,
451                           krb5_keyblock *existing_key,
452                           krb5_keyblock **out_key)
453 {
454     krb5_error_code retval = 0;
455     if (state->armor_key)
456         retval = krb5_c_fx_cf2_simple(kdc_context,
457                                       state->strengthen_key, "strengthenkey",
458                                       existing_key,
459                                       "replykey", out_key);
460     else
461         retval = krb5_copy_keyblock(kdc_context, existing_key, out_key);
462     return retval;
463 }
464
465
466 krb5_error_code
467 kdc_preauth_get_cookie(struct kdc_request_state *state,
468                        krb5_pa_data **cookie)
469 {
470     char *contents;
471     krb5_pa_data *pa = NULL;
472     /* In our current implementation, the only purpose served by
473      * returning a cookie is to indicate that a conversation should
474      * continue on error.  Thus, the cookie can have a constant
475      * string.  If cookies are used for real, versioning so that KDCs
476      * can be upgraded, keying, expiration and many other issues need
477      * to be considered.
478      */
479     contents = strdup("MIT");
480     if (contents == NULL)
481         return ENOMEM;
482     pa = calloc(1, sizeof(krb5_pa_data));
483     if (pa == NULL) {
484         free(contents);
485         return ENOMEM;
486     }
487     pa->pa_type = KRB5_PADATA_FX_COOKIE;
488     pa->length = strlen(contents);
489     pa->contents = (unsigned char *) contents;
490     *cookie = pa;
491     return 0;
492 }