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