krb5 commit: Implement replaced_reply_key input to issue_pac()
Greg Hudson
ghudson at mit.edu
Thu Jan 27 16:57:39 EST 2022
https://github.com/krb5/krb5/commit/78fd66926c4be5910c1e21d9e553dfb792ae822a
commit 78fd66926c4be5910c1e21d9e553dfb792ae822a
Author: Greg Hudson <ghudson at mit.edu>
Date: Thu Jan 13 14:33:14 2022 -0500
Implement replaced_reply_key input to issue_pac()
If a kdcpreauth module fully replaces the reply key during an AS
request, pass the reply key as the replaced_reply_key input to
issue_pac(). In Windows environments this is used to provide an NTLM
hash to the LSA when the client cannot be presumed to have a password
to derive it from.
To test this, add a fake PAC_CREDENTIALS_INFO buffer to the PAC in the
test KDB module, and alter adata.c to display the set of PAC buffer
types when a PAC is present.
ticket: 9050 (new)
src/include/kdb.h | 3 +-
src/kdc/do_as_req.c | 7 ++-
src/kdc/do_tgs_req.c | 2 +-
src/kdc/kdc_authdata.c | 26 +++++-----
src/kdc/kdc_util.h | 1 +
src/plugins/kdb/test/kdb_test.c | 8 +++
src/tests/adata.c | 100 +++++++++++++++++++++-----------------
src/tests/t_authdata.py | 22 +++++++-
8 files changed, 105 insertions(+), 64 deletions(-)
diff --git a/src/include/kdb.h b/src/include/kdb.h
index 1fa7bc5..21bddcf 100644
--- a/src/include/kdb.h
+++ b/src/include/kdb.h
@@ -1421,8 +1421,7 @@ typedef struct _kdb_vftabl {
* the Kerberos password or long-term key was not used. The module may use
* this key to encrypt a PAC_CREDENTIALS_INFO buffer containing credentials
* (such as an NTLM hash) that the client would ordinarily derive from the
- * Kerberos password or long-term key. (Note: this feature is not yet
- * implemented and the caller will always pass NULL until it is.)
+ * Kerberos password or long-term key.
*
* server is the database entry of the server the ticket will be issued to,
* which may be a referral TGS.
diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c
index 34723fa..40c0ec2 100644
--- a/src/kdc/do_as_req.c
+++ b/src/kdc/do_as_req.c
@@ -201,6 +201,7 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
void *oldarg;
kdc_realm_t *kdc_active_realm = state->active_realm;
krb5_audit_state *au_state = state->au_state;
+ krb5_keyblock *replaced_reply_key = NULL;
assert(state);
oldrespond = state->respond;
@@ -262,10 +263,14 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
goto egress;
}
+ if (state->rock.replaced_reply_key)
+ replaced_reply_key = &state->client_keyblock;
+
errcode = handle_authdata(kdc_active_realm, state->c_flags, state->client,
state->server, NULL, state->local_tgt,
&state->local_tgt_key, &state->client_keyblock,
- &state->server_keyblock, NULL, state->req_pkt,
+ &state->server_keyblock, NULL,
+ replaced_reply_key, state->req_pkt,
state->request, NULL, NULL, NULL,
&state->auth_indicators, &state->enc_tkt_reply);
if (errcode) {
diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c
index b1a190f..f90c7cf 100644
--- a/src/kdc/do_tgs_req.c
+++ b/src/kdc/do_tgs_req.c
@@ -591,7 +591,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
subject_server, local_tgt, &local_tgt_key,
subkey != NULL ? subkey :
header_ticket->enc_part2->session,
- encrypting_key, subject_key, pkt, request,
+ encrypting_key, subject_key, NULL, pkt, request,
altcprinc, subject_pac, subject_tkt,
&auth_indicators, &enc_tkt_reply);
if (errcode) {
diff --git a/src/kdc/kdc_authdata.c b/src/kdc/kdc_authdata.c
index 3909daf..ce80ac3 100644
--- a/src/kdc/kdc_authdata.c
+++ b/src/kdc/kdc_authdata.c
@@ -463,10 +463,11 @@ handle_pac(kdc_realm_t *kdc_active_realm, unsigned int flags,
krb5_db_entry *client, krb5_db_entry *server,
krb5_db_entry *subject_server, krb5_db_entry *local_tgt,
krb5_keyblock *local_tgt_key, krb5_keyblock *server_key,
- krb5_keyblock *subject_key, krb5_enc_tkt_part *subject_tkt,
- krb5_pac subject_pac, krb5_kdc_req *req,
- krb5_const_principal altcprinc, krb5_timestamp authtime,
- krb5_enc_tkt_part *enc_tkt_reply, krb5_data ***auth_indicators)
+ krb5_keyblock *subject_key, krb5_keyblock *replaced_reply_key,
+ krb5_enc_tkt_part *subject_tkt, krb5_pac subject_pac,
+ krb5_kdc_req *req, krb5_const_principal altcprinc,
+ krb5_timestamp authtime, krb5_enc_tkt_part *enc_tkt_reply,
+ krb5_data ***auth_indicators)
{
krb5_error_code ret;
krb5_context context = kdc_context;
@@ -503,8 +504,9 @@ handle_pac(kdc_realm_t *kdc_active_realm, unsigned int flags,
else
signing_tgt = local_tgt;
- ret = krb5_db_issue_pac(context, flags, client, NULL, server, signing_tgt,
- authtime, subject_pac, new_pac, auth_indicators);
+ ret = krb5_db_issue_pac(context, flags, client, replaced_reply_key, server,
+ signing_tgt, authtime, subject_pac, new_pac,
+ auth_indicators);
if (ret) {
if (ret == KRB5_PLUGIN_OP_NOTSUPP)
ret = 0;
@@ -573,10 +575,10 @@ handle_authdata(kdc_realm_t *kdc_active_realm, unsigned int flags,
krb5_db_entry *subject_server, krb5_db_entry *local_tgt,
krb5_keyblock *local_tgt_key, krb5_keyblock *client_key,
krb5_keyblock *server_key, krb5_keyblock *subject_key,
- krb5_data *req_pkt, krb5_kdc_req *req,
- krb5_const_principal altcprinc, krb5_pac subject_pac,
- krb5_enc_tkt_part *enc_tkt_req, krb5_data ***auth_indicators,
- krb5_enc_tkt_part *enc_tkt_reply)
+ krb5_keyblock *replaced_reply_key, krb5_data *req_pkt,
+ krb5_kdc_req *req, krb5_const_principal altcprinc,
+ krb5_pac subject_pac, krb5_enc_tkt_part *enc_tkt_req,
+ krb5_data ***auth_indicators, krb5_enc_tkt_part *enc_tkt_reply)
{
kdcauthdata_handle *h;
krb5_error_code ret = 0;
@@ -616,7 +618,7 @@ handle_authdata(kdc_realm_t *kdc_active_realm, unsigned int flags,
return handle_pac(kdc_active_realm, flags, client, server, subject_server,
local_tgt, local_tgt_key, server_key, subject_key,
- enc_tkt_req, subject_pac, req, altcprinc,
- enc_tkt_reply->times.authtime, enc_tkt_reply,
+ replaced_reply_key, enc_tkt_req, subject_pac, req,
+ altcprinc, enc_tkt_reply->times.authtime, enc_tkt_reply,
auth_indicators);
}
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index ded2912..4aa8e7e 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -236,6 +236,7 @@ handle_authdata (kdc_realm_t *kdc_active_realm,
krb5_keyblock *client_key,
krb5_keyblock *server_key,
krb5_keyblock *header_key,
+ krb5_keyblock *replaced_reply_key,
krb5_data *req_pkt,
krb5_kdc_req *request,
krb5_const_principal altcprinc,
diff --git a/src/plugins/kdb/test/kdb_test.c b/src/plugins/kdb/test/kdb_test.c
index 8e7015d..e6d7aae 100644
--- a/src/plugins/kdb/test/kdb_test.c
+++ b/src/plugins/kdb/test/kdb_test.c
@@ -656,6 +656,14 @@ test_issue_pac(krb5_context context, unsigned int flags, krb5_db_entry *client,
data = string2data("fake");
check(krb5_pac_add_buffer(context, new_pac, KRB5_PAC_LOGON_INFO,
&data));
+
+ if (replaced_reply_key != NULL) {
+ /* Add a fake PAC_CREDENTIALS_INFO buffer so we can test whether
+ * this parameter was set. */
+ data = string2data("fake credinfo");
+ check(krb5_pac_add_buffer(context, new_pac,
+ KRB5_PAC_CREDENTIALS_INFO, &data));
+ }
return 0;
} else {
/* Field copying - my favorite! */
diff --git a/src/tests/adata.c b/src/tests/adata.c
index 3869aec..58981c9 100644
--- a/src/tests/adata.c
+++ b/src/tests/adata.c
@@ -55,9 +55,10 @@
static krb5_context ctx;
-static void display_authdata_list(krb5_authdata **list, krb5_keyblock *skey,
+static void display_authdata_list(krb5_authdata **list,
+ krb5_enc_tkt_part *enc_tkt,
krb5_keyblock *tktkey, char prefix_byte,
- krb5_boolean pac_expected);
+ krb5_boolean pac_ok);
static void
check(krb5_error_code code)
@@ -165,6 +166,43 @@ get_container_contents(krb5_authdata *ad, krb5_keyblock *skey,
return inner_ad;
}
+static int
+compare_uint32(const void *p1, const void *p2)
+{
+ uint32_t t1 = *(uint32_t *)p1, t2 = *(uint32_t *)p2;
+
+ return (t1 > t2) ? 1 : (t1 == t2) ? 0 : -1;
+}
+
+static void
+display_pac(krb5_authdata *ad, krb5_enc_tkt_part *enc_tkt,
+ krb5_keyblock *tktkey)
+{
+ krb5_pac pac;
+ size_t tlen, i;
+ uint32_t *types;
+
+ assert(ad->ad_type == KRB5_AUTHDATA_WIN2K_PAC);
+ check(krb5_pac_parse(ctx, ad->contents, ad->length, &pac));
+
+ check(krb5_pac_verify(ctx, pac, enc_tkt->times.authtime, enc_tkt->client,
+ tktkey, NULL));
+
+ check(krb5_pac_get_types(ctx, pac, &tlen, &types));
+ qsort(types, tlen, sizeof(*types), compare_uint32);
+
+ printf("[");
+ for (i = 0; i < tlen; i++) {
+ printf("%d", (int)types[i]);
+ if (i + 1 < tlen)
+ printf(", ");
+ }
+ printf("]");
+
+ free(types);
+ krb5_pac_free(ctx, pac);
+}
+
/* Decode and display authentication indicator authdata. */
static void
display_auth_indicator(krb5_authdata *ad)
@@ -206,8 +244,8 @@ display_binary_or_ascii(krb5_authdata *ad)
/* Display the contents of an authdata element, prefixed by prefix_byte. skey
* must be the ticket session key. */
static void
-display_authdata(krb5_authdata *ad, krb5_keyblock *skey, krb5_keyblock *tktkey,
- int prefix_byte, krb5_boolean pac_expected)
+display_authdata(krb5_authdata *ad, krb5_enc_tkt_part *enc_tkt,
+ krb5_keyblock *tktkey, int prefix_byte, krb5_boolean pac_ok)
{
krb5_authdata **inner_ad;
@@ -216,21 +254,23 @@ display_authdata(krb5_authdata *ad, krb5_keyblock *skey, krb5_keyblock *tktkey,
ad->ad_type == KRB5_AUTHDATA_KDC_ISSUED ||
ad->ad_type == KRB5_AUTHDATA_CAMMAC) {
if (ad->ad_type != KRB5_AUTHDATA_IF_RELEVANT)
- pac_expected = FALSE;
+ pac_ok = FALSE;
/* Decode and display the contents. */
- inner_ad = get_container_contents(ad, skey, tktkey);
- display_authdata_list(inner_ad, skey, tktkey, get_prefix_byte(ad),
- pac_expected);
+ inner_ad = get_container_contents(ad, enc_tkt->session, tktkey);
+ display_authdata_list(inner_ad, enc_tkt, tktkey, get_prefix_byte(ad),
+ pac_ok);
krb5_free_authdata(ctx, inner_ad);
return;
}
- assert(!pac_expected || ad->ad_type == KRB5_AUTHDATA_WIN2K_PAC);
+ assert(pac_ok || ad->ad_type != KRB5_AUTHDATA_WIN2K_PAC);
printf("%c", prefix_byte);
printf("%d: ", (int)ad->ad_type);
- if (ad->ad_type == KRB5_AUTHDATA_AUTH_INDICATOR)
+ if (ad->ad_type == KRB5_AUTHDATA_WIN2K_PAC)
+ display_pac(ad, enc_tkt, tktkey);
+ else if (ad->ad_type == KRB5_AUTHDATA_AUTH_INDICATOR)
display_auth_indicator(ad);
else
display_binary_or_ascii(ad);
@@ -238,46 +278,19 @@ display_authdata(krb5_authdata *ad, krb5_keyblock *skey, krb5_keyblock *tktkey,
}
static void
-display_authdata_list(krb5_authdata **list, krb5_keyblock *skey,
+display_authdata_list(krb5_authdata **list, krb5_enc_tkt_part *tkt_enc,
krb5_keyblock *tktkey, char prefix_byte,
- krb5_boolean pac_expected)
+ krb5_boolean pac_ok)
{
if (list == NULL)
return;
/* Only expect a PAC in the first element, if at all. */
for (; *list != NULL; list++) {
- display_authdata(*list, skey, tktkey, prefix_byte, pac_expected);
- pac_expected = FALSE;
+ display_authdata(*list, tkt_enc, tktkey, prefix_byte, pac_ok);
+ pac_ok = FALSE;
}
}
-/* If a PAC is present in enc_part2, verify its service signature with key and
- * set *has_pac to true. */
-static void
-check_pac(krb5_context context, krb5_enc_tkt_part *enc_part2,
- const krb5_keyblock *key, krb5_boolean *has_pac)
-{
- krb5_authdata **authdata;
- krb5_pac pac;
-
- *has_pac = FALSE;
-
- check(krb5_find_authdata(context, enc_part2->authorization_data, NULL,
- KRB5_AUTHDATA_WIN2K_PAC, &authdata));
- if (authdata == NULL)
- return;
-
- assert(authdata[1] == NULL);
- check(krb5_pac_parse(context, authdata[0]->contents, authdata[0]->length,
- &pac));
- krb5_free_authdata(context, authdata);
-
- check(krb5_pac_verify(context, pac, enc_part2->times.authtime,
- enc_part2->client, key, NULL));
- krb5_pac_free(context, pac);
- *has_pac = TRUE;
-}
-
int
main(int argc, char **argv)
{
@@ -289,7 +302,6 @@ main(int argc, char **argv)
krb5_ticket *ticket;
krb5_authdata **req_authdata = NULL, *ad;
krb5_keytab_entry ktent;
- krb5_boolean with_pac;
size_t count;
int c;
@@ -349,10 +361,8 @@ main(int argc, char **argv)
ticket->enc_part.enctype, &ktent));
check(krb5_decrypt_tkt_part(ctx, &ktent.key, ticket));
- check_pac(ctx, ticket->enc_part2, &ktent.key, &with_pac);
display_authdata_list(ticket->enc_part2->authorization_data,
- ticket->enc_part2->session, &ktent.key, ' ',
- with_pac);
+ ticket->enc_part2, &ktent.key, ' ', TRUE);
while (count > 0) {
free(req_authdata[--count]->contents);
diff --git a/src/tests/t_authdata.py b/src/tests/t_authdata.py
index cea5007..97e2474 100644
--- a/src/tests/t_authdata.py
+++ b/src/tests/t_authdata.py
@@ -11,7 +11,7 @@ realm = K5Realm(krb5_conf=conf)
# container.
mark('baseline authdata')
out = realm.run(['./adata', realm.host_princ])
-if '?128: ' not in out or '^-42: Hello' not in out:
+if '?128: [6, 7, 10, 16]' not in out or '^-42: Hello' not in out:
fail('expected authdata not seen for basic request')
# Requested authdata is copied into the ticket, with KDC-only types
@@ -181,7 +181,8 @@ realm.stop()
realm2.stop()
# Load the test KDB module to allow successful S4U2Proxy
-# auth-indicator requests.
+# auth-indicator requests and to detect whether replaced_reply_key is
+# set.
testprincs = {'krbtgt/KRBTEST.COM': {'keys': 'aes128-cts'},
'krbtgt/FOREIGN': {'keys': 'aes128-cts'},
'user': {'keys': 'aes128-cts', 'flags': '+preauth'},
@@ -197,7 +198,8 @@ kdcconf = {'realms': {'$realm': {'database_module': 'test'}},
'dbmodules': {'test': {'db_library': 'test',
'princs': testprincs,
'delegation': {'service/1': 'service/2'}}}}
-realm = K5Realm(krb5_conf=krb5conf, kdc_conf=kdcconf, create_kdb=False)
+realm = K5Realm(krb5_conf=krb5conf, kdc_conf=kdcconf, create_kdb=False,
+ pkinit=True)
usercache = 'FILE:' + os.path.join(realm.testdir, 'usercache')
realm.extract_keytab(realm.krbtgt_princ, realm.keytab)
realm.extract_keytab('krbtgt/FOREIGN', realm.keytab)
@@ -208,6 +210,17 @@ realm.extract_keytab('service/2', realm.keytab)
realm.extract_keytab('noauthdata', realm.keytab)
realm.start_kdc()
+if not pkinit_enabled:
+ skipped('replaced_reply_key test', 'PKINIT not built')
+else:
+ # Check that replaced_reply_key is set in issue_pac() when PKINIT
+ # is used. The test KDB module will indicate this by including a
+ # fake PAC_CREDENTIAL_INFO(2) buffer in the PAC.
+ mark('PKINIT (replaced_reply_key set)')
+ realm.pkinit(realm.user_princ)
+ realm.run(['./adata', realm.krbtgt_princ],
+ expected_msg='?128: [1, 2, 6, 7, 10]')
+
# S4U2Self (should have no indicators since client did not authenticate)
mark('S4U2Self (no auth indicators expected)')
realm.kinit('service/1', None, ['-k', '-f', '-X', 'indicators=inds1'])
@@ -229,6 +242,9 @@ realm.run(['./s4u2proxy', usercache, 'service/2'])
out = realm.run(['./adata', '-p', realm.user_princ, 'service/2'])
if '+97: [indcl]' not in out or '[inds1]' in out:
fail('correct auth-indicator not seen for S4U2Proxy req')
+# Make sure a PAC with an S4U_DELEGATION_INFO(11) buffer is included.
+if '?128: [1, 6, 7, 10, 11, 16]' not in out:
+ fail('PAC with delegation info not seen for S4U2Proxy req')
# Get another S4U2Proxy ticket including request-authdata.
realm.run(['./s4u2proxy', usercache, 'service/2', '-2', 'proxy_ad'])
More information about the cvs-krb5
mailing list