From 83c99246ae9b157e462142daddccca5e18c2f3fd Mon Sep 17 00:00:00 2001 From: Julien Rische Date: Wed, 15 Mar 2023 15:56:34 +0100 Subject: [PATCH] [downstream] Allow to set PAC ticket signature as optional MS-PAC states that "The ticket signature SHOULD be included in tickets that are not encrypted to the krbtgt account". However, the implementation of krb5_kdc_verify_ticket() will require the ticket signature to be present in case the target of the request is a service principal. In gradual upgrade environments, it results in S4U2Proxy requests against a 1.20 KDC using a service ticket generated by an older version KDC to fail. This commit adds a krb5_kdc_verify_ticket_ext() function with an extra switch parameter to tolerate the absence of ticket signature in this scenario. If the ticket signature is present, it has to be valid, regardless of this parameter. This parameter is set based on the "optional_pac_tkt_chksum" string attribute of the TGT KDB entry. --- doc/admin/admin_commands/kadmin_local.rst | 6 ++++ doc/appdev/refs/api/index.rst | 1 + src/include/kdb.h | 1 + src/include/krb5/krb5.hin | 40 +++++++++++++++++++++++ src/kdc/kdc_util.c | 32 ++++++++++++++---- src/lib/krb5/krb/pac.c | 31 +++++++++++++++--- src/lib/krb5/libkrb5.exports | 1 + src/man/kadmin.man | 6 ++++ 8 files changed, 108 insertions(+), 10 deletions(-) diff --git a/doc/admin/admin_commands/kadmin_local.rst b/doc/admin/admin_commands/kadmin_local.rst index 2435b3c361..58ac79549f 100644 --- a/doc/admin/admin_commands/kadmin_local.rst +++ b/doc/admin/admin_commands/kadmin_local.rst @@ -658,6 +658,12 @@ KDC: Directory realm when using aes-sha2 keys on the local krbtgt entry. +**optional_pac_tkt_chksum** + Boolean value defining the behavior of the KDC in case an expected + ticket checksum signed with one of this principal keys is not + present in the PAC. This is typically the case for TGS or + cross-realm TGS principals when processing S4U2Proxy requests. + This command requires the **modify** privilege. Alias: **setstr** diff --git a/doc/appdev/refs/api/index.rst b/doc/appdev/refs/api/index.rst index d12be47c3c..9b95ebd0f9 100644 --- a/doc/appdev/refs/api/index.rst +++ b/doc/appdev/refs/api/index.rst @@ -225,6 +225,7 @@ Rarely used public interfaces krb5_is_referral_realm.rst krb5_kdc_sign_ticket.rst krb5_kdc_verify_ticket.rst + krb5_kdc_verify_ticket_ext.rst krb5_kt_add_entry.rst krb5_kt_end_seq_get.rst krb5_kt_get_entry.rst diff --git a/src/include/kdb.h b/src/include/kdb.h index 745b24f351..6075349e5e 100644 --- a/src/include/kdb.h +++ b/src/include/kdb.h @@ -136,6 +136,7 @@ #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" +#define KRB5_KDB_SK_OPTIONAL_PAC_TKT_CHKSUM "optional_pac_tkt_chksum" #if !defined(_WIN32) diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index 350bcf86f2..17e1b52266 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -8356,6 +8356,46 @@ krb5_kdc_verify_ticket(krb5_context context, const krb5_enc_tkt_part *enc_tkt, const krb5_keyblock *server, const krb5_keyblock *privsvr, krb5_pac *pac_out); +/** + * Verify a PAC, possibly including ticket signature + * + * @param [in] context Library context + * @param [in] enc_tkt Ticket enc-part, possibly containing a PAC + * @param [in] server_princ Canonicalized name of ticket server + * @param [in] server Key to validate server checksum (or NULL) + * @param [in] privsvr Key to validate KDC checksum (or NULL) + * @paran [in] optional_tkt_chksum Whether to require a ticket checksum + * @param [out] pac_out Verified PAC (NULL if no PAC included) + * + * This function is an extension of krb5_kdc_verify_ticket(), adding the @a + * optional_tkt_chksum parameter allowing to tolerate the absence of the PAC + * ticket signature. + * + * If a PAC is present in @a enc_tkt, verify its signatures. If @a privsvr is + * not NULL and @a server_princ is not a krbtgt or kadmin/changepw service and + * @a optional_tkt_chksum is FALSE, require a ticket signature over @a enc_tkt + * in addition to the KDC signature. Place the verified PAC in @a pac_out. If + * an invalid PAC signature is found, return an error matching the Windows KDC + * protocol code for that condition as closely as possible. + * + * If no PAC is present in @a enc_tkt, set @a pac_out to NULL and return + * successfully. + * + * @note This function does not validate the PAC_CLIENT_INFO buffer. If a + * specific value is expected, the caller can make a separate call to + * krb5_pac_verify_ext() with a principal but no keys. + * + * @retval 0 Success; otherwise - Kerberos error codes + */ +krb5_error_code KRB5_CALLCONV +krb5_kdc_verify_ticket_ext(krb5_context context, + const krb5_enc_tkt_part *enc_tkt, + krb5_const_principal server_princ, + const krb5_keyblock *server, + const krb5_keyblock *privsvr, + krb5_boolean optional_tkt_chksum, + krb5_pac *pac_out); + /** @deprecated Use krb5_kdc_sign_ticket() instead. */ krb5_error_code KRB5_CALLCONV krb5_pac_sign(krb5_context context, krb5_pac pac, krb5_timestamp authtime, diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c index fe4e48209a..93415ba862 100644 --- a/src/kdc/kdc_util.c +++ b/src/kdc/kdc_util.c @@ -560,16 +560,36 @@ cleanup: 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_db_entry *tgt, const krb5_keyblock *tgt_key, + krb5_pac *pac_out) { krb5_error_code ret; + krb5_boolean optional_tkt_chksum; + char *str = NULL; 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); + + /* Check if the absence of ticket signature is tolerated for this realm */ + ret = krb5_dbe_get_string(context, tgt, + KRB5_KDB_SK_OPTIONAL_PAC_TKT_CHKSUM, &str); + /* TODO: should be using _krb5_conf_boolean(), but os-proto.h is not + * available here. + */ + optional_tkt_chksum = !ret && str && (strncasecmp(str, "true", 4) == 0 + || strncasecmp(str, "t", 1) == 0 + || strncasecmp(str, "yes", 3) == 0 + || strncasecmp(str, "y", 1) == 0 + || strncasecmp(str, "1", 1) == 0 + || strncasecmp(str, "on", 2) == 0); + + krb5_dbe_free_string(context, str); + + ret = krb5_kdc_verify_ticket_ext(context, enc_tkt, server->princ, + server_key, privsvr_key, + optional_tkt_chksum, pac_out); krb5_free_keyblock(context, privsvr_key); return ret; } @@ -599,7 +619,7 @@ get_verified_pac(krb5_context context, const krb5_enc_tkt_part *enc_tkt, server_key, NULL, pac_out); } - ret = try_verify_pac(context, enc_tkt, server, server_key, tgt_key, + ret = try_verify_pac(context, enc_tkt, server, server_key, tgt, tgt_key, pac_out); if (ret != KRB5KRB_AP_ERR_MODIFIED && ret != KRB5_BAD_ENCTYPE) return ret; @@ -613,8 +633,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 = try_verify_pac(context, enc_tkt, server, server_key, &old_key, - pac_out); + ret = try_verify_pac(context, enc_tkt, server, server_key, tgt, + &old_key, pac_out); krb5_free_keyblock_contents(context, &old_key); if (!ret) return 0; diff --git a/src/lib/krb5/krb/pac.c b/src/lib/krb5/krb/pac.c index 5d1fdf1ba0..0c0e2ada68 100644 --- a/src/lib/krb5/krb/pac.c +++ b/src/lib/krb5/krb/pac.c @@ -594,6 +594,19 @@ krb5_kdc_verify_ticket(krb5_context context, const krb5_enc_tkt_part *enc_tkt, krb5_const_principal server_princ, const krb5_keyblock *server, const krb5_keyblock *privsvr, krb5_pac *pac_out) +{ + return krb5_kdc_verify_ticket_ext(context, enc_tkt, server_princ, server, + privsvr, FALSE, pac_out); +} + +krb5_error_code KRB5_CALLCONV +krb5_kdc_verify_ticket_ext(krb5_context context, + const krb5_enc_tkt_part *enc_tkt, + krb5_const_principal server_princ, + const krb5_keyblock *server, + const krb5_keyblock *privsvr, + krb5_boolean optional_tkt_chksum, + krb5_pac *pac_out) { krb5_error_code ret; krb5_pac pac = NULL; @@ -602,7 +615,7 @@ krb5_kdc_verify_ticket(krb5_context context, const krb5_enc_tkt_part *enc_tkt, krb5_authdata *orig, **ifrel = NULL, **recoded_ifrel = NULL; uint8_t z = 0; krb5_authdata zpac = { KV5M_AUTHDATA, KRB5_AUTHDATA_WIN2K_PAC, 1, &z }; - krb5_boolean is_service_tkt; + krb5_boolean is_service_tkt, has_tkt_chksum = FALSE; size_t i, j; *pac_out = NULL; @@ -667,11 +680,21 @@ krb5_kdc_verify_ticket(krb5_context context, const krb5_enc_tkt_part *enc_tkt, ret = verify_checksum(context, pac, KRB5_PAC_TICKET_CHECKSUM, privsvr, KRB5_KEYUSAGE_APP_DATA_CKSUM, recoded_tkt); - if (ret) - goto cleanup; + if (ret) { + if (!optional_tkt_chksum) + goto cleanup; + else if (ret != ENOENT) + goto cleanup; + /* Otherwise ticket signature is absent but optional. Proceed... */ + } else { + has_tkt_chksum = TRUE; + } } + /* Else, we make the assumption the ticket signature is absent in case this + * is not a service ticket. + */ - ret = verify_pac_checksums(context, pac, is_service_tkt, server, privsvr); + ret = verify_pac_checksums(context, pac, has_tkt_chksum, server, privsvr); if (ret) goto cleanup; diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index 4c50e935a2..d4b0455c8c 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -463,6 +463,7 @@ krb5_is_thread_safe krb5_kdc_rep_decrypt_proc krb5_kdc_sign_ticket krb5_kdc_verify_ticket +krb5_kdc_verify_ticket_ext krb5_kt_add_entry krb5_kt_client_default krb5_kt_close diff --git a/src/man/kadmin.man b/src/man/kadmin.man index c29638a227..1da1609cc8 100644 --- a/src/man/kadmin.man +++ b/src/man/kadmin.man @@ -724,6 +724,12 @@ 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. +.TP +\fBoptional_pac_tkt_chksum\fP +Boolean value defining the behavior of the KDC in case an expected ticket +checksum signed with one of this principal keys is not present in the PAC. This +is typically the case for TGS or cross-realm TGS principals when processing +S4U2Proxy requests. .UNINDENT .sp This command requires the \fBmodify\fP privilege. -- 2.41.0