diff options
author | Greg Hudson <ghudson@mit.edu> | 2023-03-21 00:51:17 -0400 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2023-03-27 14:25:56 -0400 |
commit | 5af907156f8f502bbe268f0c62274f88a61261e4 (patch) | |
tree | 4890644bc0e69a304eb80cf5559695d51b1ef1d8 /src | |
parent | 654f257b8843e3d85e368d8816511d99a8bab987 (diff) | |
download | krb5-5af907156f8f502bbe268f0c62274f88a61261e4.zip krb5-5af907156f8f502bbe268f0c62274f88a61261e4.tar.gz krb5-5af907156f8f502bbe268f0c62274f88a61261e4.tar.bz2 |
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)
Diffstat (limited to 'src')
-rw-r--r-- | src/include/kdb.h | 1 | ||||
-rw-r--r-- | src/kdc/do_tgs_req.c | 6 | ||||
-rw-r--r-- | src/kdc/kdc_authdata.c | 7 | ||||
-rw-r--r-- | src/kdc/kdc_util.c | 72 | ||||
-rw-r--r-- | src/kdc/kdc_util.h | 6 | ||||
-rw-r--r-- | src/tests/t_authdata.py | 19 |
6 files changed, 96 insertions, 15 deletions
diff --git a/src/include/kdb.h b/src/include/kdb.h index bbae9d1..745b24f 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 bdf6a13..6e4c8fa 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 1cded64..398df21 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 df0d35f..e54cc75 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 6842013..58b2f74 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 6a5af71..bde1c36 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: |