summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Hudson <ghudson@mit.edu>2023-03-21 00:51:17 -0400
committerGreg Hudson <ghudson@mit.edu>2023-03-27 14:25:56 -0400
commit5af907156f8f502bbe268f0c62274f88a61261e4 (patch)
tree4890644bc0e69a304eb80cf5559695d51b1ef1d8
parent654f257b8843e3d85e368d8816511d99a8bab987 (diff)
downloadkrb5-5af907156f8f502bbe268f0c62274f88a61261e4.tar.gz
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)
-rw-r--r--doc/admin/admin_commands/kadmin_local.rst9
-rw-r--r--src/include/kdb.h1
-rw-r--r--src/kdc/do_tgs_req.c6
-rw-r--r--src/kdc/kdc_authdata.c7
-rw-r--r--src/kdc/kdc_util.c72
-rw-r--r--src/kdc/kdc_util.h6
-rw-r--r--src/tests/t_authdata.py19
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
@@ -253,8 +253,12 @@ void kdc_free_lookaside(krb5_context);
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: