aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--doc/appdev/refs/api/index.rst1
-rw-r--r--src/include/krb5/krb5.hin37
-rw-r--r--src/lib/krb5/krb/Makefile.in14
-rw-r--r--src/lib/krb5/krb/get_etype_info.c180
-rw-r--r--src/lib/krb5/krb/int-proto.h4
-rw-r--r--src/lib/krb5/krb/preauth2.c8
-rw-r--r--src/lib/krb5/krb/t_get_etype_info.c110
-rw-r--r--src/lib/krb5/krb/t_get_etype_info.py63
-rw-r--r--src/lib/krb5/libkrb5.exports1
-rw-r--r--src/lib/krb5_32.def1
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@KRBTEST.COM']).decode('ascii')
+pexp_salt = bytes.fromhex(salt_dict['pexp@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