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 <josephsutton@catalyst.net.nz>
 - Andrew Bartlett <abartlet@samba.org>
 - Stefan Metzmacher <metze@samba.org>
This commit is contained in:
Luke Howard
2021-12-14 12:40:31 +11:00
parent fcd8e33a98
commit 2087e07c1e
6 changed files with 77 additions and 27 deletions

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;
}
/*

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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";