krb5 commit: Implement replaced_reply_key input to issue_pac()

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/78fd66926c4be5910c1e21d9e553dfb792ae822a
commit 78fd66926c4be5910c1e21d9e553dfb792ae822a
Author: Greg Hudson <[email protected]>
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'])
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.