krb5 commit: Add pac_privsvr_enctype string attribute

ghudson at mit.edu ghudson at mit.edu
Mon Mar 27 14:27:09 EDT 2023


https://github.com/krb5/krb5/commit/5af907156f8f502bbe268f0c62274f88a61261e4
commit 5af907156f8f502bbe268f0c62274f88a61261e4
Author: Greg Hudson <ghudson at mit.edu>
Date:   Tue Mar 21 00:51:17 2023 -0400

    Add pac_privsvr_enctype string attribute
    
    The KDC uses the first local TGT key for the privsvr and full PAC
    checksums.  If this key is of an aes-sha2 enctype in a cross-realm
    TGT, a Microsoft KDC in the target realm may reject the ticket because
    it has an unexpectedly large privsvr checksum buffer.  This behavior
    is unnecessarily picky as the target realm KDC cannot and does not
    need to very the privsvr checksum, but [MS-PAC] 2.8.2 does limit the
    checksum key to three specific enctypes.
    
    As a workaround, add a string attribute which can force the privsvr
    key to use a specified enctype using key derivation when issuing
    tickets to that principal.  This attribute can be set on cross-realm
    TGT entries when the target realm uses Active Directory and the local
    TGT uses an aes-sha2 primary key.
    
    ticket: 9089 (new)

 doc/admin/admin_commands/kadmin_local.rst |  9 ++++
 src/include/kdb.h                         |  1 +
 src/kdc/do_tgs_req.c                      |  6 +--
 src/kdc/kdc_authdata.c                    |  7 ++-
 src/kdc/kdc_util.c                        | 72 +++++++++++++++++++++++++++----
 src/kdc/kdc_util.h                        |  6 ++-
 src/tests/t_authdata.py                   | 19 +++++++-
 7 files changed, 105 insertions(+), 15 deletions(-)

diff --git a/doc/admin/admin_commands/kadmin_local.rst b/doc/admin/admin_commands/kadmin_local.rst
index 01b92d6fc..2435b3c36 100644
--- a/doc/admin/admin_commands/kadmin_local.rst
+++ b/doc/admin/admin_commands/kadmin_local.rst
@@ -649,6 +649,15 @@ KDC:
     is in the same format as those used by the **pkinit_cert_match**
     option in :ref:`krb5.conf(5)`.  (New in release 1.16.)
 
+**pac_privsvr_enctype**
+    Forces the encryption type of the PAC KDC checksum buffers to the
+    specified encryption type for tickets issued to this server, by
+    deriving a key from the local krbtgt key if it is of a different
+    encryption type.  It may be necessary to set this value to
+    "aes256-sha1" on the cross-realm krbtgt entry for an Active
+    Directory realm when using aes-sha2 keys on the local krbtgt
+    entry.
+
 This command requires the **modify** privilege.
 
 Alias: **setstr**
diff --git a/src/include/kdb.h b/src/include/kdb.h
index bbae9d101..745b24f35 100644
--- a/src/include/kdb.h
+++ b/src/include/kdb.h
@@ -133,6 +133,7 @@
 #define KRB5_DB_ITER_RECURSE    0x00000004
 
 /* String attribute names recognized by krb5 */
+#define KRB5_KDB_SK_PAC_PRIVSVR_ENCTYPE         "pac_privsvr_enctype"
 #define KRB5_KDB_SK_SESSION_ENCTYPES            "session_enctypes"
 #define KRB5_KDB_SK_REQUIRE_AUTH                "require_auth"
 
diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c
index bdf6a13ae..6e4c8fa9f 100644
--- a/src/kdc/do_tgs_req.c
+++ b/src/kdc/do_tgs_req.c
@@ -288,8 +288,8 @@ decrypt_2ndtkt(krb5_context context, krb5_kdc_req *req, krb5_flags flags,
         *status = "2ND_TKT_DECRYPT";
         goto cleanup;
     }
-    retval = get_verified_pac(context, stkt->enc_part2, server->princ, key,
-                              local_tgt, local_tgt_key, pac_out);
+    retval = get_verified_pac(context, stkt->enc_part2, server, key, local_tgt,
+                              local_tgt_key, pac_out);
     if (retval != 0) {
         *status = "2ND_TKT_PAC";
         goto cleanup;
@@ -655,7 +655,7 @@ gather_tgs_req_info(kdc_realm_t *realm, krb5_kdc_req **reqptr, krb5_data *pkt,
     }
 
     /* Decode and verify the header ticket PAC. */
-    ret = get_verified_pac(context, header_enc, t->header_server->princ,
+    ret = get_verified_pac(context, header_enc, t->header_server,
                            t->header_key, t->local_tgt, &t->local_tgt_key,
                            &t->header_pac);
     if (ret) {
diff --git a/src/kdc/kdc_authdata.c b/src/kdc/kdc_authdata.c
index 1cded64d8..398df2196 100644
--- a/src/kdc/kdc_authdata.c
+++ b/src/kdc/kdc_authdata.c
@@ -474,6 +474,7 @@ handle_pac(kdc_realm_t *realm, unsigned int flags, krb5_db_entry *client,
     krb5_const_principal pac_client = NULL;
     krb5_boolean with_realm, is_as_req = (req->msg_type == KRB5_AS_REQ);
     krb5_db_entry *signing_tgt;
+    krb5_keyblock *privsvr_key = NULL;
 
     /* Don't add a PAC or auth indicators if the server disables authdata. */
     if (server->attributes & KRB5_KDB_NO_AUTH_DATA_REQUIRED)
@@ -555,8 +556,11 @@ handle_pac(kdc_realm_t *realm, unsigned int flags, krb5_db_entry *client,
         with_realm = FALSE;
     }
 
+    ret = pac_privsvr_key(context, server, local_tgt_key, &privsvr_key);
+    if (ret)
+        goto cleanup;
     ret = krb5_kdc_sign_ticket(context, enc_tkt_reply, new_pac, server->princ,
-                               pac_client, server_key, local_tgt_key,
+                               pac_client, server_key, privsvr_key,
                                with_realm);
     if (ret)
         goto cleanup;
@@ -565,6 +569,7 @@ handle_pac(kdc_realm_t *realm, unsigned int flags, krb5_db_entry *client,
 
 cleanup:
     krb5_pac_free(context, new_pac);
+    krb5_free_keyblock(context, privsvr_key);
     return ret;
 }
 
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
index df0d35f60..e54cc751f 100644
--- a/src/kdc/kdc_util.c
+++ b/src/kdc/kdc_util.c
@@ -518,6 +518,62 @@ cleanup:
     return ret;
 }
 
+/* If server has a pac_privsvr_enctype attribute and it differs from tgt_key's
+ * enctype, derive a key of the specified enctype.  Otherwise copy tgt_key. */
+krb5_error_code
+pac_privsvr_key(krb5_context context, krb5_db_entry *server,
+                const krb5_keyblock *tgt_key, krb5_keyblock **key_out)
+{
+    krb5_error_code ret;
+    char *attrval = NULL;
+    krb5_enctype privsvr_enctype;
+    krb5_data prf_input = string2data("pac_privsvr");
+
+    ret = krb5_dbe_get_string(context, server, KRB5_KDB_SK_PAC_PRIVSVR_ENCTYPE,
+                              &attrval);
+    if (ret)
+        return ret;
+    if (attrval == NULL)
+        return krb5_copy_keyblock(context, tgt_key, key_out);
+
+    ret = krb5_string_to_enctype(attrval, &privsvr_enctype);
+    if (ret) {
+        k5_setmsg(context, ret, _("Invalid pac_privsvr_enctype value %s"),
+                  attrval);
+        goto cleanup;
+    }
+
+    if (tgt_key->enctype == privsvr_enctype) {
+        ret = krb5_copy_keyblock(context, tgt_key, key_out);
+    } else {
+        ret = krb5_c_derive_prfplus(context, tgt_key, &prf_input,
+                                    privsvr_enctype, key_out);
+    }
+
+cleanup:
+    krb5_dbe_free_string(context, attrval);
+    return ret;
+}
+
+/* Try verifying a ticket's PAC using a privsvr key either equal to or derived
+ * from tgt_key, respecting the server's pac_privsvr_enctype value if set. */
+static krb5_error_code
+try_verify_pac(krb5_context context, const krb5_enc_tkt_part *enc_tkt,
+               krb5_db_entry *server, krb5_keyblock *server_key,
+               const krb5_keyblock *tgt_key, krb5_pac *pac_out)
+{
+    krb5_error_code ret;
+    krb5_keyblock *privsvr_key;
+
+    ret = pac_privsvr_key(context, server, tgt_key, &privsvr_key);
+    if (ret)
+        return ret;
+    ret = krb5_kdc_verify_ticket(context, enc_tkt, server->princ, server_key,
+                                 privsvr_key, pac_out);
+    krb5_free_keyblock(context, privsvr_key);
+    return ret;
+}
+
 /*
  * If a PAC is present in enc_tkt, verify it and place it in *pac_out.  sprinc
  * is the canonical name of the server principal entry used to decrypt enc_tkt.
@@ -526,7 +582,7 @@ cleanup:
  */
 krb5_error_code
 get_verified_pac(krb5_context context, const krb5_enc_tkt_part *enc_tkt,
-                 krb5_const_principal sprinc, krb5_keyblock *server_key,
+                 krb5_db_entry *server, krb5_keyblock *server_key,
                  krb5_db_entry *tgt, krb5_keyblock *tgt_key, krb5_pac *pac_out)
 {
     krb5_error_code ret;
@@ -538,13 +594,13 @@ get_verified_pac(krb5_context context, const krb5_enc_tkt_part *enc_tkt,
     *pac_out = NULL;
 
     /* For local or cross-realm TGTs we only check the server signature. */
-    if (krb5_is_tgs_principal(sprinc)) {
-        return krb5_kdc_verify_ticket(context, enc_tkt, sprinc, server_key,
-                                      NULL, pac_out);
+    if (krb5_is_tgs_principal(server->princ)) {
+        return krb5_kdc_verify_ticket(context, enc_tkt, server->princ,
+                                      server_key, NULL, pac_out);
     }
 
-    ret = krb5_kdc_verify_ticket(context, enc_tkt, sprinc, server_key,
-                                 tgt_key, pac_out);
+    ret = try_verify_pac(context, enc_tkt, server, server_key, tgt_key,
+                         pac_out);
     if (ret != KRB5KRB_AP_ERR_MODIFIED && ret != KRB5_BAD_ENCTYPE)
         return ret;
 
@@ -557,8 +613,8 @@ get_verified_pac(krb5_context context, const krb5_enc_tkt_part *enc_tkt,
         ret = krb5_dbe_decrypt_key_data(context, NULL, kd, &old_key, NULL);
         if (ret)
             return ret;
-        ret = krb5_kdc_verify_ticket(context, enc_tkt, sprinc, server_key,
-                                     &old_key, pac_out);
+        ret = try_verify_pac(context, enc_tkt, server, server_key, &old_key,
+                             pac_out);
         krb5_free_keyblock_contents(context, &old_key);
         if (!ret)
             return 0;
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index 684201308..58b2f74de 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -252,9 +252,13 @@ void kdc_free_lookaside(krb5_context);
 /* kdc_util.c */
 void reset_for_hangup(void *);
 
+krb5_error_code
+pac_privsvr_key(krb5_context context, krb5_db_entry *server,
+                const krb5_keyblock *tgt_key, krb5_keyblock **key_out);
+
 krb5_error_code
 get_verified_pac(krb5_context context, const krb5_enc_tkt_part *enc_tkt,
-                 krb5_const_principal sprinc, krb5_keyblock *server_key,
+                 krb5_db_entry *server, krb5_keyblock *server_key,
                  krb5_db_entry *tgt, krb5_keyblock *tgt_key,
                  krb5_pac *pac_out);
 
diff --git a/src/tests/t_authdata.py b/src/tests/t_authdata.py
index 6a5af71d5..bde1c3684 100644
--- a/src/tests/t_authdata.py
+++ b/src/tests/t_authdata.py
@@ -1,9 +1,10 @@
 from k5test import *
 
-# Load the sample KDC authdata module.
+# Load the sample KDC authdata module.  Allow renewable tickets.
 greet_path = os.path.join(buildtop, 'plugins', 'authdata', 'greet_server',
                           'greet_server.so')
-conf = {'plugins': {'kdcauthdata': {'module': 'greet:' + greet_path}}}
+conf = {'realms': {'$realm': {'max_life': '20h', 'max_renewable_life': '20h'}},
+        'plugins': {'kdcauthdata': {'module': 'greet:' + greet_path}}}
 realm = K5Realm(krb5_conf=conf)
 
 # With no requested authdata, we expect to see PAC (128) in an
@@ -49,6 +50,20 @@ out = realm.run(['./adata', 'krbtgt/XREALM', '-3', 'test'])
 if '128:' not in out or  '^-42: Hello' not in out or ' -3: test' not in out:
     fail('expected authdata not seen for cross-realm TGT request')
 
+mark('pac_privsvr_enctype')
+# Change the privsvr enctype and make sure we can still verify the PAC
+# on a service ticket in a TGS request.
+realm.run([kadminl, 'setstr', realm.host_princ,
+           'pac_privsvr_enctype', 'aes128-sha1'])
+realm.kinit(realm.user_princ, password('user'),
+            ['-S', realm.host_princ, '-r', '1h'])
+realm.kinit(realm.user_princ, None, ['-S', realm.host_princ, '-R'])
+# Remove the attribute and make sure the previously-issued service
+# ticket PAC no longer verifies.
+realm.run([kadminl, 'delstr', realm.host_princ, 'pac_privsvr_enctype'])
+realm.kinit(realm.user_princ, None, ['-S', realm.host_princ, '-R'],
+            expected_code=1, expected_msg='Message stream modified')
+
 realm.stop()
 
 if not pkinit_enabled:


More information about the cvs-krb5 mailing list