From cc3511f66de78a955d0bd50d3f5bf2662bd3eda8 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Mon, 10 Mar 2025 09:40:37 +0200 Subject: [PATCH] Add initiator-side IAKERB realm discovery When importing a name to IAKERB, don't add the default realm when we parse strings. Host-based name imports will continue to use krb5_sname_to_principal(), which may add a realm from [domain_realm] but won't add the default realm. In the IAKERB state machine, query for the service's realm if the client name doesn't have a realm. To reduce code duplication, make iakerb_make_token() responsible for saving the token and incrementing the message count. [ghudson@mit.edu: added tests; added a discovery state to the machine; expanded import; adjusted iakerb_make_token() contract; rewrote commit message] ticket: 9167 (new) --- src/appl/gss-sample/t_gss_sample.py | 7 ++- src/lib/gssapi/krb5/gssapiP_krb5.h | 7 +++ src/lib/gssapi/krb5/gssapi_krb5.c | 2 +- src/lib/gssapi/krb5/iakerb.c | 67 +++++++++++++--------- src/lib/gssapi/krb5/import_name.c | 26 ++++++++- src/tests/gssapi/t_gssapi.py | 32 +++++++++-- src/tests/gssapi/t_iakerb.c | 89 ++++++++++++++--------------- 7 files changed, 149 insertions(+), 81 deletions(-) diff --git a/src/appl/gss-sample/t_gss_sample.py b/src/appl/gss-sample/t_gss_sample.py index 3608359185..dad31e4b35 100755 --- a/src/appl/gss-sample/t_gss_sample.py +++ b/src/appl/gss-sample/t_gss_sample.py @@ -78,7 +78,12 @@ def tgs_test(realm, options, server_options=[]): def pw_test(realm, options, server_options=[]): if os.path.exists(realm.ccache): os.remove(realm.ccache) - options = options + ['-user', realm.user_princ, '-pass', password('user')] + if '-iakerb' in options: + # Use IAKERB realm discovery. + user = realm.user_princ.split('@')[0] + else: + user = realm.user_princ + options = options + ['-user', user, '-pass', password('user')] server_client_test(realm, options, server_options) if os.path.exists(realm.ccache): fail('gss_acquire_cred_with_password created ccache') diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h index da7c1cfac0..1ed71fc81f 100644 --- a/src/lib/gssapi/krb5/gssapiP_krb5.h +++ b/src/lib/gssapi/krb5/gssapiP_krb5.h @@ -706,6 +706,13 @@ OM_uint32 KRB5_CALLCONV krb5_gss_import_name gss_name_t* /* output_name */ ); +OM_uint32 KRB5_CALLCONV iakerb_gss_import_name +(OM_uint32*, /* minor_status */ + gss_buffer_t, /* input_name_buffer */ + gss_OID, /* input_name_type */ + gss_name_t* /* output_name */ +); + OM_uint32 KRB5_CALLCONV krb5_gss_release_name (OM_uint32*, /* minor_status */ gss_name_t* /* input_name */ diff --git a/src/lib/gssapi/krb5/gssapi_krb5.c b/src/lib/gssapi/krb5/gssapi_krb5.c index 6c7cf2344a..8bc6f072fe 100644 --- a/src/lib/gssapi/krb5/gssapi_krb5.c +++ b/src/lib/gssapi/krb5/gssapi_krb5.c @@ -934,7 +934,7 @@ static struct gss_config iakerb_mechanism = { krb5_gss_indicate_mechs, krb5_gss_compare_name, krb5_gss_display_name, - krb5_gss_import_name, + iakerb_gss_import_name, krb5_gss_release_name, krb5_gss_inquire_cred, NULL, /* add_cred */ diff --git a/src/lib/gssapi/krb5/iakerb.c b/src/lib/gssapi/krb5/iakerb.c index 539b231957..603433608d 100644 --- a/src/lib/gssapi/krb5/iakerb.c +++ b/src/lib/gssapi/krb5/iakerb.c @@ -31,6 +31,7 @@ */ enum iakerb_state { + IAKERB_REALM_DISCOVERY, /* querying server for its realm */ IAKERB_AS_REQ, /* acquiring ticket with initial creds */ IAKERB_TGS_REQ, /* acquiring ticket with TGT */ IAKERB_AP_REQ /* hand-off to normal GSS AP-REQ exchange */ @@ -220,7 +221,8 @@ cleanup: } /* - * Create a token from IAKERB-HEADER and KRB-KDC-REQ/REP + * Create a token from IAKERB-HEADER and KRB-KDC-REQ/REP. Save the generated + * token for the finish checksum and increment the message count. */ static krb5_error_code iakerb_make_token(iakerb_ctx_id_t ctx, @@ -276,6 +278,11 @@ iakerb_make_token(iakerb_ctx_id_t ctx, k5_buf_add_len(&buf, data->data, data->length); assert(buf.len == token->length); + code = iakerb_save_token(ctx, token); + if (code != 0) + goto cleanup; + ctx->count++; + cleanup: krb5_free_data(ctx->k5c, data); @@ -315,11 +322,6 @@ iakerb_acceptor_realm(iakerb_ctx_id_t ctx, gss_cred_id_t verifier_cred, ret = iakerb_make_token(ctx, &realm, NULL, &reply, output_token); if (ret) goto cleanup; - ret = iakerb_save_token(ctx, output_token); - if (ret) - goto cleanup; - - ctx->count++; cleanup: if (ret) @@ -409,14 +411,6 @@ iakerb_acceptor_step(iakerb_ctx_id_t ctx, gss_cred_id_t verifier_cred, goto cleanup; code = iakerb_make_token(ctx, &realm, NULL, &reply, output_token); - if (code != 0) - goto cleanup; - - code = iakerb_save_token(ctx, output_token); - if (code != 0) - goto cleanup; - - ctx->count++; cleanup: if (code != 0) @@ -548,17 +542,21 @@ iakerb_initiator_step(iakerb_ctx_id_t ctx, gss_buffer_t output_token) { krb5_error_code code = 0; - krb5_data in = empty_data(), out = empty_data(), realm = empty_data(); + krb5_data in = empty_data(), out = empty_data(); + krb5_data realm = empty_data(), server_realm = empty_data(); krb5_data *cookie = NULL; OM_uint32 tmp; unsigned int flags = 0; krb5_ticket_times times; + krb5_boolean first_token; output_token->length = 0; output_token->value = NULL; - if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) { - code = iakerb_parse_token(ctx, input_token, NULL, &cookie, &in); + first_token = (input_token == GSS_C_NO_BUFFER || input_token->length == 0); + if (!first_token) { + code = iakerb_parse_token(ctx, input_token, &server_realm, &cookie, + &in); if (code != 0) goto cleanup; @@ -568,6 +566,25 @@ iakerb_initiator_step(iakerb_ctx_id_t ctx, } switch (ctx->state) { + case IAKERB_REALM_DISCOVERY: + if (first_token) { + /* Send the discovery request. */ + code = iakerb_make_token(ctx, &realm, cookie, &out, output_token); + goto cleanup; + } + + /* The acceptor should have sent us its realm. */ + if (server_realm.length == 0) { + code = KRB5_BAD_MSIZE; + goto cleanup; + } + + /* Steal the received server realm for the client principal. */ + krb5_free_data_contents(ctx->k5c, &cred->name->princ->realm); + cred->name->princ->realm = server_realm; + server_realm = empty_data(); + + /* Done with realm discovery; fall through to AS request. */ case IAKERB_AS_REQ: if (ctx->icc == NULL) { code = iakerb_init_creds_ctx(ctx, cred, time_req); @@ -626,17 +643,7 @@ iakerb_initiator_step(iakerb_ctx_id_t ctx, if (out.length != 0) { assert(ctx->state != IAKERB_AP_REQ); - code = iakerb_make_token(ctx, &realm, cookie, &out, output_token); - if (code != 0) - goto cleanup; - - /* Save the token for generating a future checksum */ - code = iakerb_save_token(ctx, output_token); - if (code != 0) - goto cleanup; - - ctx->count++; } cleanup: @@ -644,6 +651,7 @@ cleanup: gss_release_buffer(&tmp, output_token); krb5_free_data(ctx->k5c, cookie); krb5_free_data_contents(ctx->k5c, &out); + krb5_free_data_contents(ctx->k5c, &server_realm); krb5_free_data_contents(ctx->k5c, &realm); return code; @@ -663,6 +671,11 @@ iakerb_get_initial_state(iakerb_ctx_id_t ctx, krb5_creds in_creds, *out_creds = NULL; krb5_error_code code; + if (cred->name->princ->realm.length == 0) { + *state = IAKERB_REALM_DISCOVERY; + return 0; + } + memset(&in_creds, 0, sizeof(in_creds)); in_creds.client = cred->name->princ; diff --git a/src/lib/gssapi/krb5/import_name.c b/src/lib/gssapi/krb5/import_name.c index cc6883b5fe..a067d07423 100644 --- a/src/lib/gssapi/krb5/import_name.c +++ b/src/lib/gssapi/krb5/import_name.c @@ -119,9 +119,10 @@ parse_hostbased(const char *str, size_t len, return 0; } -OM_uint32 KRB5_CALLCONV -krb5_gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, - gss_OID input_name_type, gss_name_t *output_name) +static OM_uint32 KRB5_CALLCONV +import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, + gss_OID input_name_type, krb5_boolean iakerb, + gss_name_t *output_name) { krb5_context context; krb5_principal princ = NULL; @@ -304,6 +305,9 @@ krb5_gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, /* At this point, stringrep is set, or if not, code is. */ if (stringrep) { + /* For IAKERB, use realm discovery instead of the default realm. */ + if (iakerb) + flags |= KRB5_PRINCIPAL_PARSE_NO_DEF_REALM; code = krb5_parse_name_flags(context, stringrep, flags, &princ); if (code) goto cleanup; @@ -340,3 +344,19 @@ cleanup: free(host); return status; } + +OM_uint32 KRB5_CALLCONV +krb5_gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, + gss_OID input_name_type, gss_name_t *output_name) +{ + return import_name(minor_status, input_name_buffer, input_name_type, FALSE, + output_name); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, + gss_OID input_name_type, gss_name_t *output_name) +{ + return import_name(minor_status, input_name_buffer, input_name_type, TRUE, + output_name); +} diff --git a/src/tests/gssapi/t_gssapi.py b/src/tests/gssapi/t_gssapi.py index e1ed571fdd..5e566563f4 100755 --- a/src/tests/gssapi/t_gssapi.py +++ b/src/tests/gssapi/t_gssapi.py @@ -10,11 +10,37 @@ for realm in multipass_realms(): realm = K5Realm() +remove_default = {'libdefaults': {'default_realm': None}} +change_default = {'libdefaults': {'default_realm': 'WRONG.REALM'}} +no_default = realm.special_env('no_default', False, krb5_conf=remove_default) +wrong_default = realm.special_env('wrong_default', False, + krb5_conf=change_default) + +# Test IAKERB with credentials. +realm.run(['./t_iakerb', 'p:' + realm.user_princ, '-', 'h:host@' + hostname, + 'h:host']) + +# Test IAKERB getting initial credentials. +realm.run(['./t_iakerb', 'p:' + realm.user_princ, password('user'), + 'h:host@' + hostname, 'h:host']) + +# Test IAKERB realm discovery. +realm.run(['./t_iakerb', 'e:user', password('user'), 'h:host@' + hostname, + 'h:host']) + +# Test IAKERB realm discovery without default_realm set. (Use a +# GSS_KRB5_NT_PRINCIPAL_NAME acceptor name so that +# gss_accept_sec_context() knows the realm.) +realm.run(['./t_iakerb', 'e:user', password('user'), 'h:host@' + hostname, + 'p:' + realm.host_princ], env=no_default) + +# Test IAKERB realm discovery with a non-useful default_realm set. +realm.run(['./t_iakerb', 'e:user', password('user'), 'h:host@' + hostname, + 'p:' + realm.host_princ], env=wrong_default) + # Test gss_add_cred(). realm.run(['./t_add_cred']) -realm.run(['./t_iakerb']) - ### Test acceptor name behavior. # Create some host-based principals and put most of them into the @@ -34,8 +60,6 @@ realm.run([kadminl, 'renprinc', 'service1/abraham', 'service1/andrew']) # Test with no default realm and no dots in the server name. realm.run(['./t_accname', 'h:http@localhost'], expected_msg='http/localhost') -remove_default = {'libdefaults': {'default_realm': None}} -no_default = realm.special_env('no_default', False, krb5_conf=remove_default) realm.run(['./t_accname', 'h:http@localhost'], expected_msg='http/localhost', env=no_default) diff --git a/src/tests/gssapi/t_iakerb.c b/src/tests/gssapi/t_iakerb.c index a81b526e73..1bab1823fa 100644 --- a/src/tests/gssapi/t_iakerb.c +++ b/src/tests/gssapi/t_iakerb.c @@ -1,7 +1,7 @@ /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* tests/gssapi/t_iakerb.c - IAKERB tests */ /* - * Copyright (C) 2024 by the Massachusetts Institute of Technology. + * Copyright (C) 2024, 2025 by the Massachusetts Institute of Technology. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,57 +32,56 @@ #include #include -#include #include "common.h" -static uint8_t -realm_query[] = { - /* ASN.1 wrapper for IAKERB mech */ - 0x60, 0x10, - 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05, 0x02, 0x05, - /* IAKERB_PROXY token type */ - 0x05, 0x01, - /* IAKERB-HEADER with empty target-realm */ - 0x30, 0x04, - 0xA1, 0x02, 0x0C, 0x00 -}; - -static uint8_t -realm_response[] = { - /* ASN.1 wrapper for IAKERB mech */ - 0x60, 0x1B, - 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05, 0x02, 0x05, - /* IAKERB_PROXY token type */ - 0x05, 0x01, - /* IAKERB-HEADER with configured realm */ - 0x30, 0x0F, - 0xA1, 0x0D, 0x0C, 0x0B, - 'K', 'R', 'B', 'T', 'E', 'S', 'T', '.', 'C', 'O', 'M' -}; - int -main(void) +main(int argc, char **argv) { OM_uint32 major, minor; - gss_cred_id_t cred; - gss_buffer_desc in, out; - gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + const char *password; + gss_name_t iname, tname, aname; + gss_cred_id_t icred, acred; + gss_ctx_id_t ictx, actx; + gss_buffer_desc pwbuf; + + if (argc != 5) { + fprintf(stderr, "Usage: %s initiatorname password|- targetname " + "acceptorname\n", argv[0]); + return 1; + } + + iname = import_name(argv[1]); + password = argv[2]; + tname = import_name(argv[3]); + aname = import_name(argv[4]); + + if (strcmp(password, "-") != 0) { + pwbuf.value = (void *)password; + pwbuf.length = strlen(password); + major = gss_acquire_cred_with_password(&minor, iname, &pwbuf, 0, + &mechset_iakerb, GSS_C_INITIATE, + &icred, NULL, NULL); + check_gsserr("gss_acquire_cred_with_password", major, minor); + } else { + major = gss_acquire_cred(&minor, iname, GSS_C_INDEFINITE, + &mechset_iakerb, GSS_C_INITIATE, &icred, NULL, + NULL); + check_gsserr("gss_acquire_cred(iname)", major, minor); + } - major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, &mechset_iakerb, - GSS_C_ACCEPT, &cred, NULL, NULL); - check_gsserr("gss_acquire_cred", major, minor); + major = gss_acquire_cred(&minor, aname, GSS_C_INDEFINITE, &mechset_iakerb, + GSS_C_ACCEPT, &acred, NULL, NULL); + check_gsserr("gss_acquire_cred(aname)", major, minor); - in.value = realm_query; - in.length = sizeof(realm_query); - major = gss_accept_sec_context(&minor, &ctx, cred, &in, - GSS_C_NO_CHANNEL_BINDINGS, NULL, NULL, &out, - NULL, NULL, NULL); - check_gsserr("gss_accept_sec_context", major, minor); - assert(out.length == sizeof(realm_response)); - assert(memcmp(out.value, realm_response, out.length) == 0); + establish_contexts(&mech_iakerb, icred, acred, tname, 0, &ictx, &actx, + NULL, NULL, NULL); - gss_release_buffer(&minor, &out); - gss_delete_sec_context(&minor, &ctx, NULL); - gss_release_cred(&minor, &cred); + (void)gss_release_name(&minor, &iname); + (void)gss_release_name(&minor, &tname); + (void)gss_release_name(&minor, &aname); + (void)gss_release_cred(&minor, &icred); + (void)gss_release_cred(&minor, &acred); + (void)gss_delete_sec_context(&minor, &ictx, NULL); + (void)gss_delete_sec_context(&minor, &actx, NULL); return 0; } -- 2.47.2