From 2087e07c1ef53b11163e9efdff71becdbc0212f7 Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Tue, 14 Dec 2021 12:40:31 +1100 Subject: [PATCH] kdc: update PAC hooks for Samba Samba includes the user's long-term credentials (encrypted in the AS reply key) to allow legacy authentication protocols such as NTLM to work even if the pre-authentication mechanism replaced the reply key (as PKINIT does). Samba also needs to know whether the client explicitly requested a PAC be included (or excluded), in order to defer PAC exclusion until a service ticket is issued (thereby avoiding a name binding attack if the user is renamed between TGT and service ticket issuance). References: https://bugzilla.samba.org/show_bug.cgi?id=11441 https://bugzilla.samba.org/show_bug.cgi?id=14561 Closes: #864 Original authors: - Joseph Sutton - Andrew Bartlett - Stefan Metzmacher --- kdc/kdc_locl.h | 1 + kdc/kerberos5.c | 45 +++++++++++++++++++++++++++++++------------- kdc/krb5tgs.c | 21 ++++++++++++--------- kdc/windc.c | 18 ++++++++++++++++-- kdc/windc_plugin.h | 8 ++++++-- tests/plugin/windc.c | 11 ++++++++++- 6 files changed, 77 insertions(+), 27 deletions(-) diff --git a/kdc/kdc_locl.h b/kdc/kdc_locl.h index 5e776eb5b..f1ef6d105 100644 --- a/kdc/kdc_locl.h +++ b/kdc/kdc_locl.h @@ -99,6 +99,7 @@ struct astgs_request_desc { /* only valid for tgs-req */ unsigned int rk_is_subkey : 1; unsigned int fast_asserted : 1; + unsigned int replaced_reply_key : 1; krb5_crypto armor_crypto; diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index f7cbc4dec..926f6a927 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -880,6 +880,7 @@ struct kdc_patypes { #define PA_ANNOUNCE 1 #define PA_REQ_FAST 2 /* only use inside fast */ #define PA_SYNTHETIC_OK 4 +#define PA_REPLACE_REPLY_KEY 8 krb5_error_code (*validate)(astgs_request_t, const PA_DATA *pa); }; @@ -887,11 +888,11 @@ static const struct kdc_patypes pat[] = { #ifdef PKINIT { KRB5_PADATA_PK_AS_REQ, "PK-INIT(ietf)", - PA_ANNOUNCE | PA_SYNTHETIC_OK, + PA_ANNOUNCE | PA_SYNTHETIC_OK | PA_REPLACE_REPLY_KEY, pa_pkinit_validate }, { - KRB5_PADATA_PK_AS_REQ_WIN, "PK-INIT(win2k)", PA_ANNOUNCE, + KRB5_PADATA_PK_AS_REQ_WIN, "PK-INIT(win2k)", PA_ANNOUNCE | PA_REPLACE_REPLY_KEY, pa_pkinit_validate }, { @@ -920,7 +921,7 @@ static const struct kdc_patypes pat[] = { { KRB5_PADATA_FX_COOKIE, "FX-COOKIE", 0, NULL }, { KRB5_PADATA_GSS , "GSS", - PA_ANNOUNCE | PA_SYNTHETIC_OK, + PA_ANNOUNCE | PA_SYNTHETIC_OK | PA_REPLACE_REPLY_KEY, pa_gss_validate }, }; @@ -1731,29 +1732,31 @@ _kdc_check_anon_policy(astgs_request_t r) * */ -static krb5_boolean -send_pac_p(krb5_context context, KDC_REQ *req) +static krb5_error_code +check_pa_pac_request(krb5_context context, + KDC_REQ *req, + krb5_boolean *include_pac) { krb5_error_code ret; PA_PAC_REQUEST pacreq; const PA_DATA *pa; int i = 0; + *include_pac = TRUE; + pa = _kdc_find_padata(req, &i, KRB5_PADATA_PA_PAC_REQUEST); if (pa == NULL) - return TRUE; + return KRB5KDC_ERR_PADATA_TYPE_NOSUPP; ret = decode_PA_PAC_REQUEST(pa->padata_value.data, pa->padata_value.length, &pacreq, NULL); if (ret) - return TRUE; - i = pacreq.include_pac; + return KRB5KDC_ERR_PADATA_TYPE_NOSUPP; + *include_pac = pacreq.include_pac; free_PA_PAC_REQUEST(&pacreq); - if (i == 0) - return FALSE; - return TRUE; + return 0; } /* @@ -1768,8 +1771,23 @@ generate_pac(astgs_request_t r, const Key *skey, const Key *tkey) krb5_data data; uint16_t rodc_id; krb5_principal client; + krb5_boolean client_sent_pac_req, pac_request; - ret = _kdc_pac_generate(r->context, r->client, &p); + client_sent_pac_req = + (check_pa_pac_request(r->context, &r->req, &pac_request) == 0); + + /* + * When a PA mech replaces the reply key, the PAC may include the + * client's long term key (encrypted in the reply key) for use by + * other shared secret authentication protocols, e.g. NTLM. + */ + + ret = _kdc_pac_generate(r->context, + r->client, + r->server, + r->replaced_reply_key ? &r->reply_key : NULL, + client_sent_pac_req ? &pac_request : NULL, + &p); if (ret) { _kdc_r_log(r, 4, "PAC generation failed for -- %s", r->cname); @@ -2125,6 +2143,7 @@ _kdc_as_rep(astgs_request_t r) "%s pre-authentication succeeded -- %s", pat[n].name, r->cname); found_pa = 1; + r->replaced_reply_key = (pat[n].flags & PA_REPLACE_REPLY_KEY) != 0; r->et.flags.pre_authent = 1; } } @@ -2502,7 +2521,7 @@ _kdc_as_rep(astgs_request_t r) } /* Add the PAC */ - if (send_pac_p(r->context, req) && !r->et.flags.anonymous) { + if (!r->et.flags.anonymous) { generate_pac(r, skey, krbtgt_key); } diff --git a/kdc/krb5tgs.c b/kdc/krb5tgs.c index d4d92361d..a7a0addcf 100644 --- a/kdc/krb5tgs.c +++ b/kdc/krb5tgs.c @@ -1969,15 +1969,18 @@ server_lookup: goto out; /* kdc_check_flags() calls _kdc_audit_addreason() */ /* If we were about to put a PAC into the ticket, we better fix it to be the right PAC */ - if (mspac) { - krb5_pac_free(context, mspac); - mspac = NULL; - ret = _kdc_pac_generate(context, s4u2self_impersonated_client, &mspac); - if (ret) { - kdc_log(context, config, 4, "PAC generation failed for -- %s", - tpn); - goto out; - } + krb5_pac_free(context, mspac); + mspac = NULL; + + ret = _kdc_pac_generate(context, + s4u2self_impersonated_client, + server, + NULL, + NULL, + &mspac); + if (ret) { + kdc_log(context, config, 4, "PAC generation failed for -- %s", tpn); + goto out; } /* diff --git a/kdc/windc.c b/kdc/windc.c index 5a4f3c42d..c472a56e9 100644 --- a/kdc/windc.c +++ b/kdc/windc.c @@ -71,7 +71,10 @@ krb5_kdc_windc_init(krb5_context context) struct generate_uc { hdb_entry_ex *client; + hdb_entry_ex *server; + const krb5_keyblock *reply_key; krb5_pac *pac; + const krb5_boolean *pac_request; }; static krb5_error_code KRB5_LIB_CALL @@ -82,13 +85,22 @@ generate(krb5_context context, const void *plug, void *plugctx, void *userctx) if (ft->pac_generate == NULL) return KRB5_PLUGIN_NO_HANDLE; - return ft->pac_generate((void *)plug, context, uc->client, uc->pac); + + return ft->pac_generate((void *)plug, context, + uc->client, + uc->server, + uc->reply_key, + uc->pac_request, + uc->pac); } krb5_error_code _kdc_pac_generate(krb5_context context, hdb_entry_ex *client, + hdb_entry_ex *server, + const krb5_keyblock *reply_key, + const krb5_boolean *pac_request, krb5_pac *pac) { krb5_error_code ret = 0; @@ -102,9 +114,11 @@ _kdc_pac_generate(krb5_context context, return 0; if (have_plugin) { - uc.client = client; + uc.server = server; + uc.reply_key = reply_key; uc.pac = pac; + uc.pac_request = pac_request; ret = _krb5_plugin_run_f(context, &windc_plugin_data, 0, &uc, generate); diff --git a/kdc/windc_plugin.h b/kdc/windc_plugin.h index 0623f5a18..2dfa8ba4d 100644 --- a/kdc/windc_plugin.h +++ b/kdc/windc_plugin.h @@ -54,7 +54,11 @@ struct hdb_entry_ex; typedef krb5_error_code (KRB5_CALLCONV *krb5plugin_windc_pac_generate)(void *, krb5_context, - struct hdb_entry_ex *, krb5_pac *); + struct hdb_entry_ex *, /* client */ + struct hdb_entry_ex *, /* server */ + const krb5_keyblock *, /* pk_replykey */ + const krb5_boolean *, /* pac_request */ + krb5_pac *); typedef krb5_error_code (KRB5_CALLCONV *krb5plugin_windc_pac_verify)(void *, krb5_context, @@ -74,7 +78,7 @@ typedef krb5_error_code KDC_REQ *, METHOD_DATA *); -#define KRB5_WINDC_PLUGIN_MINOR 6 +#define KRB5_WINDC_PLUGIN_MINOR 7 #define KRB5_WINDC_PLUGING_MINOR KRB5_WINDC_PLUGIN_MINOR typedef struct krb5plugin_windc_ftable { diff --git a/tests/plugin/windc.c b/tests/plugin/windc.c index 00a8aa744..2664f658e 100644 --- a/tests/plugin/windc.c +++ b/tests/plugin/windc.c @@ -20,11 +20,20 @@ windc_fini(void *ctx) static krb5_error_code KRB5_CALLCONV pac_generate(void *ctx, krb5_context context, - struct hdb_entry_ex *client, krb5_pac *pac) + struct hdb_entry_ex *client, + struct hdb_entry_ex *server, + const krb5_keyblock *pk_replykey, + const krb5_boolean *pac_request, + krb5_pac *pac) { krb5_error_code ret; krb5_data data; + if (pac_request != NULL && *pac_request == FALSE) { + *pac = NULL; + return 0; + } + krb5_warnx(context, "pac generate"); data.data = "\x00\x01";