krb5 commit: Add initiator-side IAKERB realm discovery
ghudson at mit.edu
ghudson at mit.edu
Tue Mar 25 11:12:41 EDT 2025
https://github.com/krb5/krb5/commit/cc3511f66de78a955d0bd50d3f5bf2662bd3eda8
commit cc3511f66de78a955d0bd50d3f5bf2662bd3eda8
Author: Alexander Bokovoy <abokovoy at redhat.com>
Date: Mon Mar 10 09:40:37 2025 +0200
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 at 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 360835918..dad31e4b3 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 da7c1cfac..1ed71fc81 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 6c7cf2344..8bc6f072f 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 539b23195..603433608 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 cc6883b5f..a067d0742 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 e1ed571fd..5e566563f 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 at 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 at 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 a81b526e7..1bab1823f 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 <stdio.h>
#include <string.h>
-#include <assert.h>
#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;
}
More information about the cvs-krb5
mailing list