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**
#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"
*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;
}
/* 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) {
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)
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;
cleanup:
krb5_pac_free(context, new_pac);
+ krb5_free_keyblock(context, privsvr_key);
return ret;
}
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.
*/
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;
*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;
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;
/* 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);
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
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: