From 3ff4191ba950b6ba72e1864c4dc96618ea881b5e Mon Sep 17 00:00:00 2001 From: "Roland C. Dowdeswell" Date: Mon, 25 May 2026 14:48:55 +0100 Subject: [PATCH] krb5: fail krb5_verify_user() early if service key missing Fixes #1157. --- lib/krb5/test_keytab.c | 90 ++++++++++++++++++++++++++++++++++++++++++ lib/krb5/verify_user.c | 45 ++++++++++++++++++++- 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/lib/krb5/test_keytab.c b/lib/krb5/test_keytab.c index 134b0f17e..aff88a02f 100644 --- a/lib/krb5/test_keytab.c +++ b/lib/krb5/test_keytab.c @@ -63,6 +63,89 @@ test_empty_keytab(krb5_context context, const char *keytab) krb5_err(context, 1, ret, "krb5_kt_close"); } +static void +test_verify_user_empty_keytab(krb5_context context, const char *keytab) +{ + krb5_error_code ret; + krb5_keytab id; + krb5_principal principal; + krb5_verify_opt opt; + + ret = krb5_kt_resolve(context, keytab, &id); + if (ret) + krb5_err(context, 1, ret, "krb5_kt_resolve"); + + ret = krb5_parse_name(context, "lha@SU.SE", &principal); + if (ret) + krb5_err(context, 1, ret, "krb5_parse_name"); + + krb5_verify_opt_init(&opt); + krb5_verify_opt_set_keytab(&opt, id); + + ret = krb5_verify_user_opt(context, principal, "password", &opt); + if (ret != KRB5_KT_NOTFOUND) + krb5_errx(context, 1, + "krb5_verify_user_opt returned %d, expected %d", + (int)ret, (int)KRB5_KT_NOTFOUND); + + krb5_free_principal(context, principal); + + ret = krb5_kt_close(context, id); + if (ret) + krb5_err(context, 1, ret, "krb5_kt_close"); +} + +static void +test_verify_user_wrong_keytab(krb5_context context, const char *keytab) +{ + krb5_error_code ret; + krb5_keytab id; + krb5_keytab_entry entry; + krb5_principal principal; + krb5_verify_opt opt; + + ret = krb5_kt_resolve(context, keytab, &id); + if (ret) + krb5_err(context, 1, ret, "krb5_kt_resolve"); + + memset(&entry, 0, sizeof(entry)); + ret = krb5_parse_name(context, "not-host/foo@SU.SE", &entry.principal); + if (ret) + krb5_err(context, 1, ret, "krb5_parse_name"); + entry.vno = 1; + ret = krb5_generate_random_keyblock(context, + ETYPE_AES256_CTS_HMAC_SHA1_96, + &entry.keyblock); + if (ret) + krb5_err(context, 1, ret, "krb5_generate_random_keyblock"); + + ret = krb5_kt_add_entry(context, id, &entry); + if (ret) + krb5_err(context, 1, ret, "krb5_kt_add_entry"); + + ret = krb5_parse_name(context, "lha@SU.SE", &principal); + if (ret) + krb5_err(context, 1, ret, "krb5_parse_name"); + + krb5_verify_opt_init(&opt); + krb5_verify_opt_set_keytab(&opt, id); + + ret = krb5_verify_user_opt(context, principal, "password", &opt); + if (ret != KRB5_KT_NOTFOUND) + krb5_errx(context, 1, + "krb5_verify_user_opt returned %d, expected %d", + (int)ret, (int)KRB5_KT_NOTFOUND); + + krb5_free_principal(context, principal); + krb5_kt_remove_entry(context, id, &entry); + krb5_free_principal(context, entry.principal); + krb5_free_keyblock_contents(context, &entry.keyblock); + + ret = krb5_kt_close(context, id); + if (ret) + krb5_err(context, 1, ret, "krb5_kt_close"); +} + /* * Test that memory keytab are refcounted. */ @@ -281,6 +364,13 @@ main(int argc, char **argv) test_empty_keytab(context, "MEMORY:foo"); test_empty_keytab(context, "FILE:foo"); + ret = krb5_set_default_realm(context, "SU.SE"); + if (ret) + krb5_err(context, 1, ret, "krb5_set_default_realm"); + + test_verify_user_empty_keytab(context, "MEMORY:verify-user-empty"); + test_verify_user_wrong_keytab(context, "MEMORY:verify-user-wrong"); + test_memory_keytab(context, "MEMORY:foo", "MEMORY:foo2"); } diff --git a/lib/krb5/verify_user.c b/lib/krb5/verify_user.c index c6ead8e42..328410175 100644 --- a/lib/krb5/verify_user.c +++ b/lib/krb5/verify_user.c @@ -33,6 +33,40 @@ #include "krb5_locl.h" +static krb5_error_code +check_server_keytab(krb5_context context, krb5_keytab keytab, + const char *service) +{ + krb5_error_code ret, ret2; + krb5_keytab_entry entry; + krb5_keytab kt = keytab; + krb5_principal server; + + ret = krb5_sname_to_principal(context, NULL, service, KRB5_NT_SRV_HST, + &server); + if (ret) + return ret; + + if (kt == NULL) { + ret = krb5_kt_default(context, &kt); + if (ret) + goto out; + } + + ret = krb5_kt_get_entry(context, kt, server, 0, 0, &entry); + if (ret == 0) + krb5_kt_free_entry(context, &entry); + if (keytab == NULL) { + ret2 = krb5_kt_close(context, kt); + if (ret == 0) + ret = ret2; + } + +out: + krb5_free_principal(context, server); + return ret; +} + static krb5_error_code verify_common (krb5_context context, krb5_principal principal, @@ -185,7 +219,16 @@ krb5_verify_user_opt(krb5_context context, const char *password, krb5_verify_opt *opt) { - krb5_error_code ret; + krb5_error_code ret = 0; + +#define OPT(V, D) ((opt && (opt->V)) ? (opt->V) : (D)) + if (opt == NULL || opt->secure) { + ret = check_server_keytab(context, OPT(keytab, NULL), + OPT(service, "host")); + } +#undef OPT + if (ret) + return ret; if(opt && (opt->flags & KRB5_VERIFY_LREALMS)) { krb5_realm *realms, *r;