From 9c741377bb2ef2b6071803390d59f52b58f925da 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 | 26 ++++++++++++--- src/lib/krb5/krb/pac.c | 31 +++++++++++++++--- src/lib/krb5/libkrb5.exports | 1 + src/lib/krb5_32.def | 1 + src/man/kadmin.man | 6 ++++ 9 files changed, 105 insertions(+), 8 deletions(-) diff --git a/doc/admin/admin_commands/kadmin_local.rst b/doc/admin/admin_commands/kadmin_local.rst index cf75e61584..45cce8bb57 100644 --- a/doc/admin/admin_commands/kadmin_local.rst +++ b/doc/admin/admin_commands/kadmin_local.rst @@ -671,6 +671,12 @@ 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.) +**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 21bddcfb88..b65c300d8a 100644 --- a/src/include/kdb.h +++ b/src/include/kdb.h @@ -134,6 +134,7 @@ /* String attribute names recognized by krb5 */ #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 2ba4010514..404719a3a4 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -8355,6 +8355,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 b7a9aa4992..bdf1bf2dc8 100644 --- a/src/kdc/kdc_util.c +++ b/src/kdc/kdc_util.c @@ -535,6 +535,8 @@ get_verified_pac(krb5_context context, const krb5_enc_tkt_part *enc_tkt, krb5_key_data *kd; krb5_keyblock old_key; krb5_kvno kvno; + krb5_boolean optional_tkt_chksum; + char *str = NULL; int tries; *pac_out = NULL; @@ -545,8 +547,23 @@ get_verified_pac(krb5_context context, const krb5_enc_tkt_part *enc_tkt, NULL, pac_out); } - ret = krb5_kdc_verify_ticket(context, enc_tkt, sprinc, server_key, - tgt_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, sprinc, server_key, + tgt_key, optional_tkt_chksum, pac_out); if (ret != KRB5KRB_AP_ERR_MODIFIED && ret != KRB5_BAD_ENCTYPE) return ret; @@ -559,8 +576,9 @@ 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 = krb5_kdc_verify_ticket_ext(context, enc_tkt, sprinc, server_key, + &old_key, optional_tkt_chksum, + 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 954482e0c7..738887b388 100644 --- a/src/lib/krb5/krb/pac.c +++ b/src/lib/krb5/krb/pac.c @@ -636,6 +636,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; @@ -643,7 +656,7 @@ krb5_kdc_verify_ticket(krb5_context context, const krb5_enc_tkt_part *enc_tkt, krb5_authdata **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; @@ -706,11 +719,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/lib/krb5_32.def b/src/lib/krb5_32.def index b1610974b1..6ecd92c1ec 100644 --- a/src/lib/krb5_32.def +++ b/src/lib/krb5_32.def @@ -510,3 +510,4 @@ EXPORTS k5_sname_compare @474 ; PRIVATE GSSAPI krb5_kdc_sign_ticket @475 ; krb5_kdc_verify_ticket @476 ; + krb5_kdc_verify_ticket_ext @477 ; diff --git a/src/man/kadmin.man b/src/man/kadmin.man index 73e1b03cdb..54efd4ebc9 100644 --- a/src/man/kadmin.man +++ b/src/man/kadmin.man @@ -715,6 +715,12 @@ attributes required for the client certificate used by the principal during PKINIT authentication. The matching expression is in the same format as those used by the \fBpkinit_cert_match\fP option in krb5.conf(5)\&. (New in release 1.16.) +.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.39.2