krb5 commit: Add API to retrieve etype-info from KDC

Greg Hudson ghudson at mit.edu
Thu Oct 4 16:56:09 EDT 2018


https://github.com/krb5/krb5/commit/c67c0e1a446d2230a1daa28ab058e37e5c6f4734
commit c67c0e1a446d2230a1daa28ab058e37e5c6f4734
Author: Mubashir Kazia <mkazia at gmail.com>
Date:   Sat Jun 24 19:47:53 2017 +0000

    Add API to retrieve etype-info from KDC
    
    Add the krb5_get_etype_info() API, which sends an initial ticket
    request to the KDC and extracts an enctype, salt, and s2kparams value
    from the first KDC reply.  This function will help ktutil use the
    correct salt and s2kparams when creating keytabs.
    
    [ghudson at mit.edu: renamed API to krb5_get_etype_info() and adjusted
    behavior]
    
    ticket: 8587

 .gitignore                           |    1 +
 doc/appdev/refs/api/index.rst        |    1 +
 src/include/krb5/krb5.hin            |   37 +++++++-
 src/lib/krb5/krb/Makefile.in         |   14 +++-
 src/lib/krb5/krb/get_etype_info.c    |  180 ++++++++++++++++++++++++++++++++++
 src/lib/krb5/krb/int-proto.h         |    4 +
 src/lib/krb5/krb/preauth2.c          |    8 +-
 src/lib/krb5/krb/t_get_etype_info.c  |  110 +++++++++++++++++++++
 src/lib/krb5/krb/t_get_etype_info.py |   63 ++++++++++++
 src/lib/krb5/libkrb5.exports         |    1 +
 src/lib/krb5_32.def                  |    1 +
 11 files changed, 413 insertions(+), 7 deletions(-)

diff --git a/.gitignore b/.gitignore
index 713d4a6..9e0546d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -371,6 +371,7 @@ local.properties
 /src/lib/krb5/krb/t_etypes
 /src/lib/krb5/krb/t_expand
 /src/lib/krb5/krb/t_expire_warn
+/src/lib/krb5/krb/t_get_etype_info
 /src/lib/krb5/krb/t_in_ccache
 /src/lib/krb5/krb/t_kerb
 /src/lib/krb5/krb/t_pac
diff --git a/doc/appdev/refs/api/index.rst b/doc/appdev/refs/api/index.rst
index f2f27fe..66aff59 100644
--- a/doc/appdev/refs/api/index.rst
+++ b/doc/appdev/refs/api/index.rst
@@ -212,6 +212,7 @@ Rarely used public interfaces
    krb5_free_string.rst
    krb5_free_ticket.rst
    krb5_free_unparsed_name.rst
+   krb5_get_etype_info.rst
    krb5_get_permitted_enctypes.rst
    krb5_get_server_rcache.rst
    krb5_get_time_offsets.rst
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index 384bb83..21fabb4 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -7317,6 +7317,42 @@ krb5_get_init_creds_password(krb5_context context, krb5_creds *creds,
                              const char *in_tkt_service,
                              krb5_get_init_creds_opt *k5_gic_options);
 
+/**
+ * Retrieve enctype, salt and s2kparams from KDC
+ *
+ * @param [in]  context         Library context
+ * @param [in]  principal       Principal whose information is requested
+ * @param [in]  opt             Initial credential options
+ * @param [out] enctype_out     The enctype chosen by KDC
+ * @param [out] salt_out        Salt returned from KDC
+ * @param [out] s2kparams_out   String-to-key parameters returned from KDC
+ *
+ * Send an initial ticket request for @a principal and extract the encryption
+ * type, salt type, and string-to-key parameters from the KDC response.  If the
+ * KDC provides no etype-info, set @a enctype_out to @c ENCTYPE_NULL and set @a
+ * salt_out and @a s2kparams_out to empty.  If the KDC etype-info provides no
+ * salt, compute the default salt and place it in @a salt_out.  If the KDC
+ * etype-info provides no string-to-key parameters, set @a s2kparams_out to
+ * empty.
+ *
+ * @a opt may be used to specify options which affect the initial request, such
+ * as request encryption types or a FAST armor cache (see
+ * krb5_get_init_creds_opt_set_etype_list() and
+ * krb5_get_init_creds_opt_set_fast_ccache_name()).
+ *
+ * Use krb5_free_data_contents() to free @a salt_out and @a s2kparams_out when
+ * they are no longer needed.
+ *
+ * @version New in 1.17
+ *
+ * @retval 0 Success
+ * @return A Kerberos error code
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_get_etype_info(krb5_context context, krb5_principal principal,
+                    krb5_get_init_creds_opt *opt, krb5_enctype *enctype_out,
+                    krb5_data *salt_out, krb5_data *s2kparams_out);
+
 struct _krb5_init_creds_context;
 typedef struct _krb5_init_creds_context *krb5_init_creds_context;
 
@@ -8486,7 +8522,6 @@ void KRB5_CALLCONV
 krb5_set_kdc_recv_hook(krb5_context context, krb5_post_recv_fn recv_hook,
                        void *data);
 
-
 #if defined(TARGET_OS_MAC) && TARGET_OS_MAC
 #    pragma pack(pop)
 #endif
diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in
index e4015b6..69b9101 100644
--- a/src/lib/krb5/krb/Makefile.in
+++ b/src/lib/krb5/krb/Makefile.in
@@ -55,6 +55,7 @@ STLIBOBJS= \
 	gen_subkey.o	\
 	gen_save_subkey.o	\
 	get_creds.o	\
+	get_etype_info.o \
 	get_in_tkt.o	\
 	gic_keytab.o	\
 	gic_opt.o	\
@@ -167,6 +168,7 @@ OBJS=	$(OUTPRE)addr_comp.$(OBJEXT)	\
 	$(OUTPRE)gen_subkey.$(OBJEXT)	\
 	$(OUTPRE)gen_save_subkey.$(OBJEXT)	\
 	$(OUTPRE)get_creds.$(OBJEXT)	\
+	$(OUTPRE)get_etype_info.$(OBJEXT) \
 	$(OUTPRE)get_in_tkt.$(OBJEXT)	\
 	$(OUTPRE)gic_keytab.$(OBJEXT)	\
 	$(OUTPRE)gic_opt.$(OBJEXT)	\
@@ -279,6 +281,7 @@ SRCS=	$(srcdir)/addr_comp.c	\
 	$(srcdir)/gen_subkey.c	\
 	$(srcdir)/gen_save_subkey.c	\
 	$(srcdir)/get_creds.c	\
+	$(srcdir)/get_etype_info.c \
 	$(srcdir)/get_in_tkt.c	\
 	$(srcdir)/gic_keytab.c	\
 	$(srcdir)/gic_opt.c	\
@@ -353,6 +356,7 @@ SRCS=	$(srcdir)/addr_comp.c	\
 	$(srcdir)/t_ser.c	\
 	$(srcdir)/t_deltat.c	\
 	$(srcdir)/t_expand.c	\
+	$(srcdir)/t_get_etype_info.c \
 	$(srcdir)/t_pac.c	\
 	$(srcdir)/t_parse_host_string.c	\
 	$(srcdir)/t_princ.c	\
@@ -461,9 +465,12 @@ t_sname_match: t_sname_match.o sname_match.o $(KRB5_BASE_DEPLIBS)
 t_valid_times: t_valid_times.o valid_times.o $(KRB5_BASE_DEPLIBS)
 	$(CC_LINK) -o $@ t_valid_times.o valid_times.o $(KRB5_BASE_LIBS)
 
+t_get_etype_info: t_get_etype_info.o $(KRB5_BASE_DEPLIBS)
+	$(CC_LINK) -o $@ t_get_etype_info.o $(KRB5_BASE_LIBS)
+
 TEST_PROGS= t_walk_rtree t_kerb t_ser t_deltat t_expand t_authdata t_pac \
 	t_in_ccache t_cc_config t_copy_context t_princ t_etypes t_vfy_increds \
-	t_response_items t_sname_match t_valid_times
+	t_response_items t_sname_match t_valid_times t_get_etype_info
 
 check-unix: $(TEST_PROGS)
 	$(RUN_TEST_LOCAL_CONF) ./t_kerb \
@@ -502,10 +509,11 @@ check-unix: $(TEST_PROGS)
 	$(RUN_TEST) ./t_sname_match
 	$(RUN_TEST) ./t_valid_times
 
-check-pytests: t_expire_warn t_vfy_increds
+check-pytests: t_expire_warn t_get_etype_info t_vfy_increds
 	$(RUNPYTEST) $(srcdir)/t_expire_warn.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_vfy_increds.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_in_ccache_patypes.py $(PYTESTFLAGS)
+	$(RUNPYTEST) $(srcdir)/t_get_etype_info.py $(PYTESTFLAGS)
 
 check-cmocka: t_parse_host_string
 	$(RUN_TEST) ./t_parse_host_string > /dev/null
@@ -530,6 +538,8 @@ clean:
 	$(OUTPRE)t_response_items.$(OBJEXT) \
 	$(OUTPRE)t_sname_match$(EXEEXT) $(OUTPRE)t_sname_match.$(OBJEXT) \
 	$(OUTPRE)t_valid_times$(EXEEXT) $(OUTPRE)t_valid_times.$(OBJEXT) \
+	$(OUTPRE)t_get_etype_info$(EXEEXT) \
+	$(OUTPRE)t_get_etype_info.$(OBJEXT) \
 	$(OUTPRE)t_parse_host_string$(EXEEXT) \
 	$(OUTPRE)t_parse_host_string.$(OBJEXT)
 
diff --git a/src/lib/krb5/krb/get_etype_info.c b/src/lib/krb5/krb/get_etype_info.c
new file mode 100644
index 0000000..3a9589d
--- /dev/null
+++ b/src/lib/krb5/krb/get_etype_info.c
@@ -0,0 +1,180 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/krb/get_etype_salt_s2kp.c - Retrieve enctype, salt and s2kparams */
+/*
+ * Copyright (C) 2017 by Cloudera, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "k5-int.h"
+#include "fast.h"
+#include "init_creds_ctx.h"
+
+/* Extract etype info from the error message pkt into icc, if it is a
+ * PREAUTH_REQUIRED error.  Otherwise return the protocol error code. */
+static krb5_error_code
+get_from_error(krb5_context context, krb5_data *pkt,
+               krb5_init_creds_context icc)
+{
+    krb5_error *error = NULL;
+    krb5_pa_data **padata = NULL;
+    krb5_error_code ret;
+
+    ret = decode_krb5_error(pkt, &error);
+    if (ret)
+        return ret;
+    ret = krb5int_fast_process_error(context, icc->fast_state, &error, &padata,
+                                     NULL);
+    if (ret)
+        goto cleanup;
+    if (error->error != KDC_ERR_PREAUTH_REQUIRED) {
+        ret = ERROR_TABLE_BASE_krb5 + error->error;
+        goto cleanup;
+    }
+    ret = k5_get_etype_info(context, icc, padata);
+
+cleanup:
+    krb5_free_pa_data(context, padata);
+    krb5_free_error(context, error);
+    return ret;
+}
+
+/* Extract etype info from the AS reply pkt into icc. */
+static krb5_error_code
+get_from_reply(krb5_context context, krb5_data *pkt,
+               krb5_init_creds_context icc)
+{
+    krb5_kdc_rep *asrep = NULL;
+    krb5_error_code ret;
+    krb5_keyblock *strengthen_key = NULL;
+
+    ret = decode_krb5_as_rep(pkt, &asrep);
+    if (ret)
+        return ret;
+    ret = krb5int_fast_process_response(context, icc->fast_state, asrep,
+                                        &strengthen_key);
+    if (ret)
+        goto cleanup;
+    ret = k5_get_etype_info(context, icc, asrep->padata);
+
+cleanup:
+    krb5_free_kdc_rep(context, asrep);
+    krb5_free_keyblock(context, strengthen_key);
+    return ret;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_get_etype_info(krb5_context context, krb5_principal principal,
+                    krb5_get_init_creds_opt *opt, krb5_enctype *enctype_out,
+                    krb5_data *salt_out, krb5_data *s2kparams_out)
+{
+    krb5_init_creds_context icc = NULL;
+    krb5_data reply = empty_data(), req = empty_data(), realm = empty_data();
+    krb5_data salt = empty_data(), s2kparams = empty_data();
+    unsigned int flags;
+    int master, tcp_only;
+    krb5_error_code ret;
+
+    *enctype_out = ENCTYPE_NULL;
+    *salt_out = empty_data();
+    *s2kparams_out = empty_data();
+
+    /* Create an initial creds context and get the initial request packet. */
+    ret = krb5_init_creds_init(context, principal, NULL, NULL, 0, opt, &icc);
+    if (ret)
+        goto cleanup;
+    ret = krb5_init_creds_step(context, icc, &reply, &req, &realm, &flags);
+    if (ret)
+        goto cleanup;
+    if (flags != KRB5_INIT_CREDS_STEP_FLAG_CONTINUE) {
+        ret = KRB5KRB_AP_ERR_MSG_TYPE;
+        goto cleanup;
+    }
+
+    /* Send the packet (possibly once with UDP and again with TCP). */
+    tcp_only = 0;
+    for (;;) {
+        master = 0;
+        ret = krb5_sendto_kdc(context, &req, &realm, &reply, &master,
+                              tcp_only);
+        if (ret)
+            goto cleanup;
+
+        icc->etype = ENCTYPE_NULL;
+        if (krb5_is_krb_error(&reply)) {
+            ret = get_from_error(context, &reply, icc);
+            if (ret) {
+                if (!tcp_only && ret == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
+                    tcp_only = 1;
+                    krb5_free_data_contents(context, &reply);
+                    continue;
+                }
+                goto cleanup;
+            }
+        } else if (krb5_is_as_rep(&reply)) {
+            ret = get_from_reply(context, &reply, icc);
+            if (ret)
+                goto cleanup;
+        } else {
+            ret = KRB5KRB_AP_ERR_MSG_TYPE;
+            goto cleanup;
+        }
+        break;
+    }
+
+    /* If we found no etype-info, return successfully with all null values. */
+    if (icc->etype == ENCTYPE_NULL)
+        goto cleanup;
+
+    if (icc->default_salt)
+        ret = krb5_principal2salt(context, principal, &salt);
+    else if (icc->salt.length > 0)
+        ret = krb5int_copy_data_contents(context, &icc->salt, &salt);
+    if (ret)
+        goto cleanup;
+
+    if (icc->s2kparams.length > 0) {
+        ret = krb5int_copy_data_contents(context, &icc->s2kparams, &s2kparams);
+        if (ret)
+            goto cleanup;
+    }
+
+    *salt_out = salt;
+    *s2kparams_out = s2kparams;
+    *enctype_out = icc->etype;
+    salt = empty_data();
+    s2kparams = empty_data();
+
+cleanup:
+    krb5_free_data_contents(context, &req);
+    krb5_free_data_contents(context, &reply);
+    krb5_free_data_contents(context, &realm);
+    krb5_free_data_contents(context, &salt);
+    krb5_free_data_contents(context, &s2kparams);
+    krb5_init_creds_free(context, icc);
+    return ret;
+}
diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h
index d201338..9783548 100644
--- a/src/lib/krb5/krb/int-proto.h
+++ b/src/lib/krb5/krb/int-proto.h
@@ -334,4 +334,8 @@ k5_gic_opt_shallow_copy(krb5_get_init_creds_opt *opt);
 int
 k5_gic_opt_pac_request(krb5_get_init_creds_opt *opt);
 
+krb5_error_code
+k5_get_etype_info(krb5_context context, krb5_init_creds_context ctx,
+                  krb5_pa_data **padata);
+
 #endif /* KRB5_INT_FUNC_PROTO__ */
diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c
index c578548..a73568c 100644
--- a/src/lib/krb5/krb/preauth2.c
+++ b/src/lib/krb5/krb/preauth2.c
@@ -782,9 +782,9 @@ get_salt(krb5_context context, krb5_init_creds_context ctx,
 }
 
 /* Set etype info parameters in rock based on padata. */
-static krb5_error_code
-get_etype_info(krb5_context context, krb5_init_creds_context ctx,
-               krb5_pa_data **padata)
+krb5_error_code
+k5_get_etype_info(krb5_context context, krb5_init_creds_context ctx,
+                  krb5_pa_data **padata)
 {
     krb5_error_code ret = 0;
     krb5_pa_data *pa;
@@ -1023,7 +1023,7 @@ k5_preauth(krb5_context context, krb5_init_creds_context ctx,
     TRACE_PREAUTH_INPUT(context, in_padata);
 
     /* Scan the padata list and process etype-info or salt elements. */
-    ret = get_etype_info(context, ctx, in_padata);
+    ret = k5_get_etype_info(context, ctx, in_padata);
     if (ret)
         return ret;
 
diff --git a/src/lib/krb5/krb/t_get_etype_info.c b/src/lib/krb5/krb/t_get_etype_info.c
new file mode 100644
index 0000000..041c349
--- /dev/null
+++ b/src/lib/krb5/krb/t_get_etype_info.c
@@ -0,0 +1,110 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/krb/t_get_etype_info.c - test harness for krb5_get_etype_info() */
+/*
+ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "k5-platform.h"
+#include "k5-hex.h"
+#include <krb5.h>
+
+int
+main(int argc, char **argv)
+{
+    krb5_error_code ret;
+    krb5_context context;
+    krb5_principal princ;
+    krb5_get_init_creds_opt *opt = NULL;
+    krb5_enctype *etypes = NULL, *newptr, etype;
+    krb5_data salt, s2kparams;
+    const char *armor_ccache = NULL, *msg;
+    char buf[128], *hex;
+    int c, netypes = 0;
+
+    while ((c = getopt(argc, argv, "e:T:")) != -1) {
+        switch (c) {
+        case 'e':
+            newptr = realloc(etypes, (netypes + 1) * sizeof(*etypes));
+            assert(newptr != NULL);
+            etypes = newptr;
+            ret = krb5_string_to_enctype(optarg, &etypes[netypes]);
+            assert(!ret);
+            netypes++;
+            break;
+        case 'T':
+            armor_ccache = optarg;
+            break;
+        }
+    }
+    assert(argc == optind + 1);
+
+    ret = krb5_init_context(&context);
+    assert(!ret);
+    ret = krb5_parse_name(context, argv[optind], &princ);
+    assert(!ret);
+    if (netypes > 0 || armor_ccache != NULL) {
+        ret = krb5_get_init_creds_opt_alloc(context, &opt);
+        assert(!ret);
+        if (netypes > 0)
+            krb5_get_init_creds_opt_set_etype_list(opt, etypes, netypes);
+        if (armor_ccache != NULL) {
+            ret = krb5_get_init_creds_opt_set_fast_ccache_name(context, opt,
+                                                               armor_ccache);
+            assert(!ret);
+        }
+    }
+    ret = krb5_get_etype_info(context, princ, opt, &etype, &salt, &s2kparams);
+    if (ret) {
+        msg = krb5_get_error_message(context, ret);
+        fprintf(stderr, "%s\n", msg);
+        krb5_free_error_message(context, msg);
+        exit(1);
+    } else if (etype == ENCTYPE_NULL) {
+        printf("no etype-info\n");
+    } else {
+        ret = krb5_enctype_to_name(etype, TRUE, buf, sizeof(buf));
+        assert(!ret);
+        printf("etype: %s\n", buf);
+        printf("salt: %.*s\n", (int)salt.length, salt.data);
+        if (s2kparams.length > 0) {
+            ret = k5_hex_encode(s2kparams.data, s2kparams.length, TRUE, &hex);
+            assert(!ret);
+            printf("s2kparams: %s\n", hex);
+            free(hex);
+        }
+    }
+
+    krb5_free_data_contents(context, &salt);
+    krb5_free_data_contents(context, &s2kparams);
+    krb5_free_principal(context, princ);
+    krb5_get_init_creds_opt_free(context, opt);
+    krb5_free_context(context);
+    free(etypes);
+    return 0;
+}
diff --git a/src/lib/krb5/krb/t_get_etype_info.py b/src/lib/krb5/krb/t_get_etype_info.py
new file mode 100644
index 0000000..7c400be
--- /dev/null
+++ b/src/lib/krb5/krb/t_get_etype_info.py
@@ -0,0 +1,63 @@
+from k5test import *
+
+conf = {'libdefaults': {'allow_weak_crypto': 'true'}}
+realm = K5Realm(create_host=False, krb5_conf=conf)
+
+realm.run([kadminl, 'ank', '-pw', 'pw', '+preauth', 'puser'])
+realm.run([kadminl, 'ank', '-nokey', 'nokey'])
+realm.run([kadminl, 'ank', '-nokey', '+preauth', 'pnokey'])
+realm.run([kadminl, 'ank', '-e', 'aes256-cts:special', '-pw', 'pw', 'exp'])
+realm.run([kadminl, 'ank', '-e', 'aes256-cts:special', '-pw', 'pw', '+preauth',
+           'pexp'])
+realm.run([kadminl, 'ank', '-e', 'des-cbc-crc:afs3', '-pw', 'pw', 'afs'])
+realm.run([kadminl, 'ank', '-e', 'des-cbc-crc:afs3', '-pw', 'pw', '+preauth',
+           'pafs'])
+
+# Extract the explicit salt values from the database.
+out = realm.run([kdb5_util, 'tabdump', 'keyinfo'])
+salt_dict = {f[0]: f[5] for f in [l.split('\t') for l in out.splitlines()]}
+exp_salt = bytes.fromhex(salt_dict['exp at KRBTEST.COM']).decode('ascii')
+pexp_salt = bytes.fromhex(salt_dict['pexp at KRBTEST.COM']).decode('ascii')
+
+# Test an error reply (other than PREAUTH_REQUIRED).
+out = realm.run(['./t_get_etype_info', 'notfound'], expected_code=1,
+                expected_msg='Client not found in Kerberos database')
+
+# Test with default salt and no specific options, with and without
+# preauth.  (Our KDC always sends an explicit salt, so unfortunately
+# we aren't really testing client handling of the default salt.)
+realm.run(['./t_get_etype_info', 'user'],
+          expected_msg='etype: aes256-cts\nsalt: KRBTEST.COMuser\n')
+realm.run(['./t_get_etype_info', 'puser'],
+          expected_msg='etype: aes256-cts\nsalt: KRBTEST.COMpuser\n')
+
+# Test with a specified request enctype.
+msg = 'etype: aes128-cts\nsalt: KRBTEST.COMuser\n'
+realm.run(['./t_get_etype_info', '-e', 'aes128-cts', 'user'],
+          expected_msg='etype: aes128-cts\nsalt: KRBTEST.COMuser\n')
+realm.run(['./t_get_etype_info', '-e', 'aes128-cts', 'puser'],
+          expected_msg='etype: aes128-cts\nsalt: KRBTEST.COMpuser\n')
+
+# Test with FAST.
+msg = 'etype: aes256-cts\nsalt: KRBTEST.COMuser\n'
+realm.run(['./t_get_etype_info', '-T', realm.ccache, 'user'],
+          expected_msg='etype: aes256-cts\nsalt: KRBTEST.COMuser\n')
+realm.run(['./t_get_etype_info', '-T', realm.ccache, 'puser'],
+          expected_msg='etype: aes256-cts\nsalt: KRBTEST.COMpuser\n')
+
+# Test with no available etype-info.
+realm.run(['./t_get_etype_info', 'nokey'], expected_code=1,
+          expected_msg='KDC has no support for encryption type')
+realm.run(['./t_get_etype_info', 'pnokey'], expected_msg='no etype-info')
+
+# Test with explicit salt.
+realm.run(['./t_get_etype_info', 'exp'],
+          expected_msg='etype: aes256-cts\nsalt: ' + exp_salt + '\n')
+realm.run(['./t_get_etype_info', 'pexp'],
+          expected_msg='etype: aes256-cts\nsalt: ' + pexp_salt + '\n')
+
+msg = 'etype: des-cbc-crc\nsalt: KRBTEST.COM\ns2kparams: 01\n'
+realm.run(['./t_get_etype_info', 'afs'], expected_msg=msg)
+realm.run(['./t_get_etype_info', 'pafs'], expected_msg=msg)
+
+success('krb5_get_etype_info() tests')
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index 622bc36..542209d 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -374,6 +374,7 @@ krb5_get_default_config_files
 krb5_get_default_in_tkt_ktypes
 krb5_get_default_realm
 krb5_get_error_message
+krb5_get_etype_info
 krb5_get_fallback_host_realm
 krb5_get_host_realm
 krb5_get_in_tkt_with_keytab
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
index 7dee65d..3bb2a80 100644
--- a/src/lib/krb5_32.def
+++ b/src/lib/krb5_32.def
@@ -475,6 +475,7 @@ EXPORTS
 	k5_enctype_to_ssf				@438 ; PRIVATE GSSAPI
 
 ; new in 1.17
+	krb5_get_etype_info				@447
 ; private symbols used by SPAKE client module
 	profile_get_string				@439 ; PRIVATE
 	profile_release_string				@440 ; PRIVATE


More information about the cvs-krb5 mailing list