From: Greg Hudson Date: Mon, 17 Aug 2015 22:26:36 +0000 (-0400) Subject: Add etype-info2 to MORE_PREAUTH_DATA_REQUIRED X-Git-Tag: krb5-1.14-alpha1~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1b4bd4e388faa5685aa483fdc2bded02c95350bc;p=thirdparty%2Fkrb5.git Add etype-info2 to MORE_PREAUTH_DATA_REQUIRED A multi-round-trip preauth mechanism may require key information, but not for the initial message from the client. To support optimistic preauth for such mechanisms, make the KDC include etype-info2 information in a MORE_PREAUTH_DATA_REQUIRED error if the client didn't include a PA-FX-COOKIE in its request. Add optimistic preauth support to the test preauth module and to etinfo.c, and add a test case to t_etype_info.py to verify that etype-info2 is included in the optimistic multi-hop scenario. ticket: 8234 (new) --- diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c index 2509c38d50..605fcb7add 100644 --- a/src/kdc/kdc_preauth.c +++ b/src/kdc/kdc_preauth.c @@ -101,6 +101,11 @@ typedef struct preauth_system_st { krb5_kdcpreauth_loop_fn loop; } preauth_system; +static krb5_error_code +make_etype_info(krb5_context context, krb5_preauthtype pa_type, + krb5_principal client, krb5_key_data *client_key, + krb5_enctype enctype, krb5_pa_data **pa_out); + static void get_etype_info(krb5_context context, krb5_kdc_req *request, krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock, @@ -985,6 +990,47 @@ filter_preauth_error(krb5_error_code code) } } +/* + * If the client performed optimistic pre-authentication for a multi-round-trip + * mechanism, it may need key information to complete the exchange, so send it + * a PA-ETYPE-INFO2 element in addition to the pa-data from the module. + */ +static krb5_error_code +maybe_add_etype_info2(struct padata_state *state, krb5_error_code code) +{ + krb5_context context = state->context; + krb5_kdcpreauth_rock rock = state->rock; + krb5_pa_data **list = state->pa_e_data; + size_t count; + + /* Only add key information when requesting another preauth round trip. */ + if (code != KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED) + return 0; + + /* Don't try to add key information when there is no key. */ + if (rock->client_key == NULL) + return 0; + + /* If the client sent a cookie, it has already seen a KDC response with key + * information. */ + if (krb5int_find_pa_data(context, state->request->padata, + KRB5_PADATA_FX_COOKIE) != NULL) + return 0; + + /* Reallocate state->pa_e_data to make room for the etype-info2 element. */ + for (count = 0; list != NULL && list[count] != NULL; count++); + list = realloc(list, (count + 2) * sizeof(*list)); + if (list == NULL) + return ENOMEM; + list[count] = list[count + 1] = NULL; + state->pa_e_data = list; + + /* Generate an etype-info2 element in the new slot. */ + return make_etype_info(context, KRB5_PADATA_ETYPE_INFO2, + rock->client->princ, rock->client_key, + rock->client_keyblock->enctype, &list[count]); +} + /* Release state and respond to the AS-REQ processing code with the result of * checking pre-authentication data. */ static void @@ -1000,6 +1046,12 @@ finish_check_padata(struct padata_state *state, krb5_error_code code) goto cleanup; } + /* Add key information to the saved error pa-data if required. */ + if (maybe_add_etype_info2(state, code) != 0) { + code = KRB5KDC_ERR_PREAUTH_FAILED; + goto cleanup; + } + /* Return any saved error pa-data, stealing the pointer from state. */ *state->e_data_out = state->pa_e_data; *state->typed_e_data_out = state->typed_e_data_flag; diff --git a/src/plugins/preauth/test/cltest.c b/src/plugins/preauth/test/cltest.c index 5244a7d8d1..4c31e1c0f8 100644 --- a/src/plugins/preauth/test/cltest.c +++ b/src/plugins/preauth/test/cltest.c @@ -120,7 +120,22 @@ test_process(krb5_context context, krb5_clpreauth_moddata moddata, krb5_data plain; const char *indstr; - if (reqst->second_round_trip) { + if (pa_data->length == 0) { + /* This is an optimistic preauth test. Send a recognizable padata + * value so the KDC knows not to expect a cookie. */ + list = k5calloc(2, sizeof(*list), &ret); + assert(!ret); + pa = k5alloc(sizeof(*pa), &ret); + assert(!ret); + pa->pa_type = TEST_PA_TYPE; + pa->contents = (uint8_t *)strdup("optimistic"); + assert(pa->contents != NULL); + pa->length = 10; + list[0] = pa; + list[1] = NULL; + *out_pa_data = list; + return 0; + } else if (reqst->second_round_trip) { printf("2rt: %.*s\n", pa_data->length, pa_data->contents); } else if (pa_data->length == 6 && memcmp(pa_data->contents, "no key", 6) == 0) { diff --git a/src/plugins/preauth/test/kdctest.c b/src/plugins/preauth/test/kdctest.c index 8c1d01dab8..82a03b0e2e 100644 --- a/src/plugins/preauth/test/kdctest.c +++ b/src/plugins/preauth/test/kdctest.c @@ -120,12 +120,15 @@ test_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request, assert(!ret); /* Check the incoming cookie value. */ - if (!cb->get_cookie(context, rock, TEST_PA_TYPE, &cookie_data)) - abort(); - if (data_eq_string(cookie_data, "more")) + if (!cb->get_cookie(context, rock, TEST_PA_TYPE, &cookie_data)) { + /* Make sure we are seeing optimistic preauth and not a lost cookie. */ + d = make_data(data->contents, data->length); + assert(data_eq_string(d, "optimistic")); + } else if (data_eq_string(cookie_data, "more")) { second_round_trip = TRUE; - else + } else { assert(data_eq_string(cookie_data, "method-data")); + } if (attr == NULL || second_round_trip) { /* Parse and assert the indicators. */ diff --git a/src/tests/etinfo.c b/src/tests/etinfo.c index dc4563877a..3f0c42d3e5 100644 --- a/src/tests/etinfo.c +++ b/src/tests/etinfo.c @@ -113,17 +113,19 @@ int main(int argc, char **argv) { krb5_principal client; + krb5_get_init_creds_opt *opt; krb5_init_creds_context icc; krb5_data reply, request, realm; krb5_error *error; krb5_kdc_rep *asrep; krb5_pa_data **padata; krb5_enctype *enctypes, def[] = { ENCTYPE_NULL }; + krb5_preauthtype pa_type = KRB5_PADATA_NONE; unsigned int flags; int master = 0; - if (argc < 2 && argc > 3) { - fprintf(stderr, "Usage: %s princname [enctypes]\n", argv[0]); + if (argc < 2 && argc > 4) { + fprintf(stderr, "Usage: %s princname [enctypes] [patype]\n", argv[0]); exit(1); } check(krb5_init_context(&ctx)); @@ -133,8 +135,14 @@ main(int argc, char **argv) krb5_set_default_in_tkt_ktypes(ctx, enctypes); free(enctypes); } + if (argc >= 4) + pa_type = atoi(argv[3]); - check(krb5_init_creds_init(ctx, client, NULL, NULL, 0, NULL, &icc)); + check(krb5_get_init_creds_opt_alloc(ctx, &opt)); + if (pa_type != KRB5_PADATA_NONE) + krb5_get_init_creds_opt_set_preauth_list(opt, &pa_type, 1); + + check(krb5_init_creds_init(ctx, client, NULL, NULL, 0, opt, &icc)); reply = empty_data(); check(krb5_init_creds_step(ctx, icc, &reply, &request, &realm, &flags)); assert(flags == KRB5_INIT_CREDS_STEP_FLAG_CONTINUE); @@ -142,11 +150,14 @@ main(int argc, char **argv) if (decode_krb5_error(&reply, &error) == 0) { decode_krb5_padata_sequence(&error->e_data, &padata); - if (error->error != KDC_ERR_PREAUTH_REQUIRED) { + if (error->error == KDC_ERR_PREAUTH_REQUIRED) { + display_padata(padata, "error"); + } else if (error->error == KDC_ERR_MORE_PREAUTH_DATA_REQUIRED) { + display_padata(padata, "more"); + } else { fprintf(stderr, "Unexpected error %d\n", (int)error->error); return 1; } - display_padata(padata, "error"); krb5_free_pa_data(ctx, padata); krb5_free_error(ctx, error); } else if (decode_krb5_as_rep(&reply, &asrep) == 0) { @@ -159,6 +170,7 @@ main(int argc, char **argv) krb5_free_data_contents(ctx, &request); krb5_free_data_contents(ctx, &reply); krb5_free_data_contents(ctx, &realm); + krb5_get_init_creds_opt_free(ctx, opt); krb5_init_creds_free(ctx, icc); krb5_free_principal(ctx, client); krb5_free_context(ctx); diff --git a/src/tests/t_etype_info.py b/src/tests/t_etype_info.py index 8ff6ad6664..b2eb0f7aff 100644 --- a/src/tests/t_etype_info.py +++ b/src/tests/t_etype_info.py @@ -73,4 +73,16 @@ test_etinfo('user', 'des-cbc-md5 rc4', test_etinfo('rc4user', 'des3', []) test_etinfo('nokeyuser', 'des3', []) +# Verify that etype-info2 is included in a MORE_PREAUTH_DATA_REQUIRED +# error if the client does optimistic preauth. +realm.stop() +testpreauth = os.path.join(buildtop, 'plugins', 'preauth', 'test', 'test.so') +conf = {'plugins': {'kdcpreauth': {'module': 'test:' + testpreauth}, + 'clpreauth': {'module': 'test:' + testpreauth}}} +realm = K5Realm(create_host=False, get_creds=False, krb5_conf=conf) +realm.run([kadminl, 'setstr', realm.user_princ, '2rt', '2rtval']) +out = realm.run(['./etinfo', realm.user_princ, 'aes128-cts', '-123']) +if out != 'more etype_info2 aes128-cts KRBTEST.COMuser\n': + fail('Unexpected output for MORE_PREAUTH_DATA_REQUIRED test') + success('KDC etype-info tests')