diff options
author | Greg Hudson <ghudson@mit.edu> | 2022-01-13 14:33:14 -0500 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2022-01-27 15:54:43 -0500 |
commit | 78fd66926c4be5910c1e21d9e553dfb792ae822a (patch) | |
tree | f4c60ac72039a551ef1f0b7cbd68e787a8d5f26d | |
parent | ff57dc682a27bd205d715f3c0bed84890f2453c4 (diff) | |
download | krb5-78fd66926c4be5910c1e21d9e553dfb792ae822a.zip krb5-78fd66926c4be5910c1e21d9e553dfb792ae822a.tar.gz krb5-78fd66926c4be5910c1e21d9e553dfb792ae822a.tar.bz2 |
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)
-rw-r--r-- | src/include/kdb.h | 3 | ||||
-rw-r--r-- | src/kdc/do_as_req.c | 7 | ||||
-rw-r--r-- | src/kdc/do_tgs_req.c | 2 | ||||
-rw-r--r-- | src/kdc/kdc_authdata.c | 26 | ||||
-rw-r--r-- | src/kdc/kdc_util.h | 1 | ||||
-rw-r--r-- | src/plugins/kdb/test/kdb_test.c | 8 | ||||
-rw-r--r-- | src/tests/adata.c | 100 | ||||
-rw-r--r-- | 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']) |