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