From 219d8c5aeddffb734012294ee756a0ec55f4092d Mon Sep 17 00:00:00 2001 From: Greg Kuchyt Date: Mon, 23 Mar 2015 14:34:06 -0400 Subject: [PATCH 1/4] Patches to iterate through WebKDC keytab in webauth_krb5_init_via_password to support one-way Kerberos realm trusts. --- lib/krb5.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/lib/krb5.c b/lib/krb5.c index f150efeb..218b1ac4 100644 --- a/lib/krb5.c +++ b/lib/krb5.c @@ -587,15 +587,13 @@ webauth_krb5_init_via_password(struct webauth_context *ctx, /* Verify the credentials if possible. */ if (get_principal == NULL && keytab != NULL) { krb5_principal princ = NULL; - krb5_keytab kt = NULL; char *name; - s = open_keytab(ctx, kc, keytab, server_principal, &princ, &kt); - if (s != WA_ERR_NONE) { - krb5_free_cred_contents(kc->ctx, &creds); - return s; - } - code = krb5_verify_init_creds(kc->ctx, &creds, princ, kt, NULL, NULL); + /* Iterate over keytab in verify_creds_iterate_keytab to find the + * first princ that will verify our creds. This enables one-way cross- + * realm trusts. + */ + code = verify_creds_iterate_keytab(ctx, kc, &princ, keytab, server_principal, creds); if (code != 0) error_set(ctx, kc, code, "credential verification failed for %s", username); @@ -608,7 +606,6 @@ webauth_krb5_init_via_password(struct webauth_context *ctx, error_set(ctx, kc, code, "cannot unparse server principal"); } } - krb5_kt_close(kc->ctx, kt); krb5_free_principal(kc->ctx, princ); if (code != 0) { krb5_free_cred_contents(kc->ctx, &creds); @@ -624,6 +621,89 @@ webauth_krb5_init_via_password(struct webauth_context *ctx, return WA_ERR_NONE; } +/* + * Attempt to verify creds against all principals in the supplied keytab. + * Though we only attempt to verify credentials that match the realm of the + * principal in *princ so we don't waste time verifying creds that will fail. + */ + +int +verify_creds_iterate_keytab(struct webauth_context *ctx, struct webauth_krb5 *kc, + krb5_principal *princ, const char *keytab, + const char *server_principal, krb5_creds creds) +{ + krb5_error_code code; + krb5_keytab kt; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + bool cursor_valid = false; + + code = krb5_kt_resolve(kc->ctx, keytab, &kt); + if (code != 0) + return error_set(ctx, kc, code, "cannot open keytab %s", keytab); + + code = krb5_kt_start_seq_get(kc->ctx, kt, &cursor); + if (code != 0) + return error_set(ctx, kc, code, "cannot start sequential get on keytab %s", keytab); + + cursor_valid = true; + /* + * If server_principal is given attempt to verify with that first. + * If verification fails or if server_principal is NULL, step through + * the keytab and attempt to verify with each principal if realms match + */ + if (server_principal != NULL) { + code = krb5_parse_name(kc->ctx, server_principal, princ); + if (code != 0) { + error_set(ctx, kc, code, "cannot parse principal %s", server_principal); + goto fail; + } + code = krb5_verify_init_creds(kc->ctx, &creds, *princ, kt, NULL, NULL); + } + + if (server_principal == NULL || code != 0) { + do { + code = krb5_kt_next_entry(kc->ctx, kt, &entry, &cursor); + if (code != 0) { + break; + } + + code = krb5_copy_principal(kc->ctx, entry.principal, princ); + if (code != 0) + error_set(ctx, kc, code, "cannot copy principal"); + + krb5_free_keytab_entry_contents(kc->ctx, &entry); + if (code != 0) + goto fail; + + if (krb5_realm_compare(kc->ctx, kc->princ, *princ)) { + code = krb5_verify_init_creds(kc->ctx, &creds, *princ, kt, NULL, NULL); + if (code == 0) + break; + } + } while (1); + } + + krb5_kt_end_seq_get(kc->ctx, kt, &cursor); + + krb5_kt_close(kc->ctx, kt); + if (code == KRB5_KT_END) { + return error_set(ctx, kc, code, "reached end of keytab without verifying principal"); + } else if (code != 0) { + return code; + } + + return WA_ERR_NONE; + +fail: + if (cursor_valid) + krb5_kt_end_seq_get(kc->ctx, kt, &cursor); + + if (kt != NULL) + krb5_kt_close(kc->ctx, kt); + + return WA_ERR_KRB5; +} /* * Prepare a context from obtained credentials. This uses existing From 35f6c0ae90f1604ffa28eb21e713e47072bc0c63 Mon Sep 17 00:00:00 2001 From: Greg Kuchyt Date: Mon, 23 Mar 2015 14:34:06 -0400 Subject: [PATCH 2/4] Patches to iterate through WebKDC keytab in webauth_krb5_init_via_password to support one-way Kerberos realm trusts. --- lib/krb5.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/lib/krb5.c b/lib/krb5.c index f150efeb..218b1ac4 100644 --- a/lib/krb5.c +++ b/lib/krb5.c @@ -587,15 +587,13 @@ webauth_krb5_init_via_password(struct webauth_context *ctx, /* Verify the credentials if possible. */ if (get_principal == NULL && keytab != NULL) { krb5_principal princ = NULL; - krb5_keytab kt = NULL; char *name; - s = open_keytab(ctx, kc, keytab, server_principal, &princ, &kt); - if (s != WA_ERR_NONE) { - krb5_free_cred_contents(kc->ctx, &creds); - return s; - } - code = krb5_verify_init_creds(kc->ctx, &creds, princ, kt, NULL, NULL); + /* Iterate over keytab in verify_creds_iterate_keytab to find the + * first princ that will verify our creds. This enables one-way cross- + * realm trusts. + */ + code = verify_creds_iterate_keytab(ctx, kc, &princ, keytab, server_principal, creds); if (code != 0) error_set(ctx, kc, code, "credential verification failed for %s", username); @@ -608,7 +606,6 @@ webauth_krb5_init_via_password(struct webauth_context *ctx, error_set(ctx, kc, code, "cannot unparse server principal"); } } - krb5_kt_close(kc->ctx, kt); krb5_free_principal(kc->ctx, princ); if (code != 0) { krb5_free_cred_contents(kc->ctx, &creds); @@ -624,6 +621,89 @@ webauth_krb5_init_via_password(struct webauth_context *ctx, return WA_ERR_NONE; } +/* + * Attempt to verify creds against all principals in the supplied keytab. + * Though we only attempt to verify credentials that match the realm of the + * principal in *princ so we don't waste time verifying creds that will fail. + */ + +int +verify_creds_iterate_keytab(struct webauth_context *ctx, struct webauth_krb5 *kc, + krb5_principal *princ, const char *keytab, + const char *server_principal, krb5_creds creds) +{ + krb5_error_code code; + krb5_keytab kt; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + bool cursor_valid = false; + + code = krb5_kt_resolve(kc->ctx, keytab, &kt); + if (code != 0) + return error_set(ctx, kc, code, "cannot open keytab %s", keytab); + + code = krb5_kt_start_seq_get(kc->ctx, kt, &cursor); + if (code != 0) + return error_set(ctx, kc, code, "cannot start sequential get on keytab %s", keytab); + + cursor_valid = true; + /* + * If server_principal is given attempt to verify with that first. + * If verification fails or if server_principal is NULL, step through + * the keytab and attempt to verify with each principal if realms match + */ + if (server_principal != NULL) { + code = krb5_parse_name(kc->ctx, server_principal, princ); + if (code != 0) { + error_set(ctx, kc, code, "cannot parse principal %s", server_principal); + goto fail; + } + code = krb5_verify_init_creds(kc->ctx, &creds, *princ, kt, NULL, NULL); + } + + if (server_principal == NULL || code != 0) { + do { + code = krb5_kt_next_entry(kc->ctx, kt, &entry, &cursor); + if (code != 0) { + break; + } + + code = krb5_copy_principal(kc->ctx, entry.principal, princ); + if (code != 0) + error_set(ctx, kc, code, "cannot copy principal"); + + krb5_free_keytab_entry_contents(kc->ctx, &entry); + if (code != 0) + goto fail; + + if (krb5_realm_compare(kc->ctx, kc->princ, *princ)) { + code = krb5_verify_init_creds(kc->ctx, &creds, *princ, kt, NULL, NULL); + if (code == 0) + break; + } + } while (1); + } + + krb5_kt_end_seq_get(kc->ctx, kt, &cursor); + + krb5_kt_close(kc->ctx, kt); + if (code == KRB5_KT_END) { + return error_set(ctx, kc, code, "reached end of keytab without verifying principal"); + } else if (code != 0) { + return code; + } + + return WA_ERR_NONE; + +fail: + if (cursor_valid) + krb5_kt_end_seq_get(kc->ctx, kt, &cursor); + + if (kt != NULL) + krb5_kt_close(kc->ctx, kt); + + return WA_ERR_KRB5; +} /* * Prepare a context from obtained credentials. This uses existing From d4dc8485841f781ddcf6908a654666b1b447270b Mon Sep 17 00:00:00 2001 From: Greg Kuchyt Date: Thu, 8 Oct 2015 07:04:20 -0400 Subject: [PATCH 3/4] Remove realm check, add more logging, and fix memory leaks --- lib/krb5.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/krb5.c b/lib/krb5.c index 218b1ac4..8af952b5 100644 --- a/lib/krb5.c +++ b/lib/krb5.c @@ -587,9 +587,10 @@ webauth_krb5_init_via_password(struct webauth_context *ctx, /* Verify the credentials if possible. */ if (get_principal == NULL && keytab != NULL) { krb5_principal princ = NULL; + krb5_keytab kt = NULL; char *name; - /* Iterate over keytab in verify_creds_iterate_keytab to find the + /* Iterate over open_keytab in verify_creds_iterate_keytab to find the * first princ that will verify our creds. This enables one-way cross- * realm trusts. */ @@ -605,8 +606,8 @@ webauth_krb5_init_via_password(struct webauth_context *ctx, } else { error_set(ctx, kc, code, "cannot unparse server principal"); } + krb5_free_principal(kc->ctx, princ); } - krb5_free_principal(kc->ctx, princ); if (code != 0) { krb5_free_cred_contents(kc->ctx, &creds); return WA_ERR_KRB5; @@ -624,7 +625,7 @@ webauth_krb5_init_via_password(struct webauth_context *ctx, /* * Attempt to verify creds against all principals in the supplied keytab. * Though we only attempt to verify credentials that match the realm of the - * principal in *princ so we don't waste time verifying creds that will fail. + * principal in *princ so we don't needlessly try to verify creds. */ int @@ -633,6 +634,7 @@ verify_creds_iterate_keytab(struct webauth_context *ctx, struct webauth_krb5 *kc const char *server_principal, krb5_creds creds) { krb5_error_code code; + krb5_error_code last_verify_code; krb5_keytab kt; krb5_kt_cursor cursor; krb5_keytab_entry entry; @@ -663,24 +665,31 @@ verify_creds_iterate_keytab(struct webauth_context *ctx, struct webauth_krb5 *kc if (server_principal == NULL || code != 0) { do { + krb5_free_principal(kc->ctx, *princ); code = krb5_kt_next_entry(kc->ctx, kt, &entry, &cursor); if (code != 0) { + error_set(ctx, kc, code, "cannot get next keytab entry"); break; } code = krb5_copy_principal(kc->ctx, entry.principal, princ); - if (code != 0) + if (code != 0) { error_set(ctx, kc, code, "cannot copy principal"); + goto fail; + } krb5_free_keytab_entry_contents(kc->ctx, &entry); - if (code != 0) + if (code != 0) { goto fail; + } - if (krb5_realm_compare(kc->ctx, kc->princ, *princ)) { - code = krb5_verify_init_creds(kc->ctx, &creds, *princ, kt, NULL, NULL); - if (code == 0) - break; + last_verify_code = krb5_verify_init_creds(kc->ctx, &creds, *princ, kt, NULL, NULL); + if (last_verify_code == 0) { + break; } + + wai_log_info(ctx, "failed to verify credentials; attempting next keytab entry"); + error_set(ctx, kc, code, "failed to verify credentials"); } while (1); } @@ -688,9 +697,7 @@ verify_creds_iterate_keytab(struct webauth_context *ctx, struct webauth_krb5 *kc krb5_kt_close(kc->ctx, kt); if (code == KRB5_KT_END) { - return error_set(ctx, kc, code, "reached end of keytab without verifying principal"); - } else if (code != 0) { - return code; + return error_set(ctx, kc, last_verify_code, "reached end of keytab without verifying principal"); } return WA_ERR_NONE; @@ -702,6 +709,9 @@ verify_creds_iterate_keytab(struct webauth_context *ctx, struct webauth_krb5 *kc if (kt != NULL) krb5_kt_close(kc->ctx, kt); + if (princ != NULL) + krb5_free_principal(kc->ctx, *princ); + return WA_ERR_KRB5; } From ce347190d09791f2b639a370ea1bbf905d442d97 Mon Sep 17 00:00:00 2001 From: Greg Kuchyt Date: Tue, 15 Dec 2015 15:02:21 -0500 Subject: [PATCH 4/4] Improve logging upon failed verification --- lib/krb5.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/krb5.c b/lib/krb5.c index 917e992a..028cdb27 100644 --- a/lib/krb5.c +++ b/lib/krb5.c @@ -687,8 +687,15 @@ verify_creds_iterate_keytab(struct webauth_context *ctx, struct webauth_krb5 *kc break; } - wai_log_info(ctx, "failed to verify credentials; attempting next keytab entry"); + const char *err_str = krb5_get_error_message(kc->ctx, last_verify_code); + + char * princ_name; + krb5_unparse_name(kc->ctx, *princ, &princ_name); + + wai_log_info(ctx, "failed to verify credentials with %s (%s); attempting next keytab entry", princ_name, err_str); error_set(ctx, kc, code, "failed to verify credentials"); + krb5_free_unparsed_name(kc->ctx, princ_name); + krb5_free_error_message(kc->ctx, err_str); } while (1); }