From: Isaac Boukris Date: Sat, 29 Sep 2018 04:21:56 +0000 (+0300) Subject: Add PAC APIs which can include a client realm X-Git-Tag: krb5-1.17-beta1~27 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3b56f54e31ee8db2b15a059e0d53609c1f4c3b83;p=thirdparty%2Fkrb5.git Add PAC APIs which can include a client realm These APIs are needed for KDC handling of cross-realm S4U2Self tickets; see [MS-SFU] 3.2.5.x. Note that we currently do not allow re-signing a PAC to include the realm; the caller must create a new one. [ghudson@mit.edu: added documentation; changed names and parameter order; edited commit message] ticket: 8749 (new) --- diff --git a/doc/appdev/refs/api/index.rst b/doc/appdev/refs/api/index.rst index 66aff5951e..f8a5aa5e67 100644 --- a/doc/appdev/refs/api/index.rst +++ b/doc/appdev/refs/api/index.rst @@ -256,7 +256,9 @@ Rarely used public interfaces krb5_pac_init.rst krb5_pac_parse.rst krb5_pac_sign.rst + krb5_pac_sign_ext.rst krb5_pac_verify.rst + krb5_pac_verify_ext.rst krb5_prepend_error_message.rst krb5_principal2salt.rst krb5_rd_cred.rst diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index 21fabb48c0..c40a6cca86 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -8312,6 +8312,30 @@ krb5_pac_verify(krb5_context context, const krb5_pac pac, krb5_timestamp authtime, krb5_const_principal principal, const krb5_keyblock *server, const krb5_keyblock *privsvr); +/** + * Verify a PAC, possibly from a specified realm. + * + * @param [in] context Library context + * @param [in] pac PAC handle + * @param [in] authtime Expected timestamp + * @param [in] principal Expected principal name (or NULL) + * @param [in] server Key to validate server checksum (or NULL) + * @param [in] privsvr Key to validate KDC checksum (or NULL) + * @param [in] with_realm If true, expect the realm of @a principal + * + * This function is similar to krb5_pac_verify(), but adds a parameter + * @a with_realm. If @a with_realm is true, the PAC_CLIENT_INFO field is + * expected to include the realm of @a principal as well as the name. This + * flag is necessary to verify PACs in cross-realm S4U2Self referral TGTs. + * + * @version New in 1.17 + */ +krb5_error_code KRB5_CALLCONV +krb5_pac_verify_ext(krb5_context context, const krb5_pac pac, + krb5_timestamp authtime, krb5_const_principal principal, + const krb5_keyblock *server, const krb5_keyblock *privsvr, + krb5_boolean with_realm); + /** * Sign a PAC. * @@ -8335,6 +8359,32 @@ krb5_pac_sign(krb5_context context, krb5_pac pac, krb5_timestamp authtime, krb5_const_principal principal, const krb5_keyblock *server_key, const krb5_keyblock *privsvr_key, krb5_data *data); +/** + * Sign a PAC, possibly with a specified realm. + * + * @param [in] context Library context + * @param [in] pac PAC handle + * @param [in] authtime Expected timestamp + * @param [in] principal Principal name (or NULL) + * @param [in] server_key Key for server checksum + * @param [in] privsvr_key Key for KDC checksum + * @param [in] with_realm If true, include the realm of @a principal + * @param [out] data Signed PAC encoding + * + * This function is similar to krb5_pac_sign(), but adds a parameter + * @a with_realm. If @a with_realm is true, the PAC_CLIENT_INFO field of the + * signed PAC will include the realm of @a principal as well as the name. This + * flag is necessary to generate PACs for cross-realm S4U2Self referrals. + * + * @version New in 1.17 + */ +krb5_error_code KRB5_CALLCONV +krb5_pac_sign_ext(krb5_context context, krb5_pac pac, krb5_timestamp authtime, + krb5_const_principal principal, + const krb5_keyblock *server_key, + const krb5_keyblock *privsvr_key, krb5_boolean with_realm, + krb5_data *data); + /** * Allow the appplication to override the profile's allow_weak_crypto setting. * diff --git a/src/lib/krb5/krb/authdata.h b/src/lib/krb5/krb/authdata.h index 1e5c08426b..74d663c0c5 100644 --- a/src/lib/krb5/krb/authdata.h +++ b/src/lib/krb5/krb/authdata.h @@ -90,7 +90,8 @@ krb5_error_code k5_pac_validate_client(krb5_context context, const krb5_pac pac, krb5_timestamp authtime, - krb5_const_principal principal); + krb5_const_principal principal, + krb5_boolean with_realm); krb5_error_code k5_pac_add_buffer(krb5_context context, diff --git a/src/lib/krb5/krb/pac.c b/src/lib/krb5/krb/pac.c index c9b5de30a2..cc74f378b0 100644 --- a/src/lib/krb5/krb/pac.c +++ b/src/lib/krb5/krb/pac.c @@ -403,7 +403,8 @@ krb5_error_code k5_pac_validate_client(krb5_context context, const krb5_pac pac, krb5_timestamp authtime, - krb5_const_principal principal) + krb5_const_principal principal, + krb5_boolean with_realm) { krb5_error_code ret; krb5_data client_info; @@ -413,7 +414,7 @@ k5_pac_validate_client(krb5_context context, krb5_ui_2 pac_princname_length; int64_t pac_nt_authtime; krb5_principal pac_principal; - int flags; + int flags = 0; ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_CLIENT_INFO, &client_info); @@ -442,10 +443,15 @@ k5_pac_validate_client(krb5_context context, return ret; /* Parse the UTF-8 name as an enterprise principal if we are matching - * against one; otherwise parse it as a regular principal with no realm. */ - flags = KRB5_PRINCIPAL_PARSE_NO_REALM; + * against one; otherwise parse it as a regular principal. */ if (principal->type == KRB5_NT_ENTERPRISE_PRINCIPAL) flags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE; + + if (with_realm) + flags |= KRB5_PRINCIPAL_PARSE_REQUIRE_REALM; + else + flags |= KRB5_PRINCIPAL_PARSE_NO_REALM; + ret = krb5_parse_name_flags(context, pac_princname, flags, &pac_principal); if (ret != 0) { free(pac_princname); @@ -458,6 +464,7 @@ k5_pac_validate_client(krb5_context context, !krb5_principal_compare_flags(context, pac_principal, principal, + with_realm ? 0 : KRB5_PRINCIPAL_COMPARE_IGNORE_REALM)) ret = KRB5KRB_AP_WRONG_PRINC; @@ -622,6 +629,19 @@ krb5_pac_verify(krb5_context context, krb5_const_principal principal, const krb5_keyblock *server, const krb5_keyblock *privsvr) +{ + return krb5_pac_verify_ext(context, pac, authtime, principal, server, + privsvr, FALSE); +} + +krb5_error_code KRB5_CALLCONV +krb5_pac_verify_ext(krb5_context context, + const krb5_pac pac, + krb5_timestamp authtime, + krb5_const_principal principal, + const krb5_keyblock *server, + const krb5_keyblock *privsvr, + krb5_boolean with_realm) { krb5_error_code ret; @@ -638,7 +658,8 @@ krb5_pac_verify(krb5_context context, } if (principal != NULL) { - ret = k5_pac_validate_client(context, pac, authtime, principal); + ret = k5_pac_validate_client(context, pac, authtime, + principal, with_realm); if (ret != 0) return ret; } diff --git a/src/lib/krb5/krb/pac_sign.c b/src/lib/krb5/krb/pac_sign.c index c94899c96a..12f0259b4f 100644 --- a/src/lib/krb5/krb/pac_sign.c +++ b/src/lib/krb5/krb/pac_sign.c @@ -33,7 +33,8 @@ static krb5_error_code k5_insert_client_info(krb5_context context, krb5_pac pac, krb5_timestamp authtime, - krb5_const_principal principal) + krb5_const_principal principal, + krb5_boolean with_realm) { krb5_error_code ret; krb5_data client_info; @@ -41,16 +42,23 @@ k5_insert_client_info(krb5_context context, unsigned char *princ_name_utf16 = NULL, *p; size_t princ_name_utf16_len = 0; uint64_t nt_authtime; + int flags = 0; /* If we already have a CLIENT_INFO buffer, then just validate it */ if (k5_pac_locate_buffer(context, pac, KRB5_PAC_CLIENT_INFO, &client_info) == 0) { - return k5_pac_validate_client(context, pac, authtime, principal); + return k5_pac_validate_client(context, pac, authtime, principal, + with_realm); } - ret = krb5_unparse_name_flags(context, principal, - KRB5_PRINCIPAL_UNPARSE_NO_REALM, - &princ_name_utf8); + if (!with_realm) { + flags |= KRB5_PRINCIPAL_UNPARSE_NO_REALM; + } else if (principal->type == KRB5_NT_ENTERPRISE_PRINCIPAL) { + /* Avoid quoting the first @ sign for enterprise name with realm. */ + flags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY; + } + + ret = krb5_unparse_name_flags(context, principal, flags, &princ_name_utf8); if (ret != 0) goto cleanup; @@ -182,6 +190,17 @@ krb5_error_code KRB5_CALLCONV krb5_pac_sign(krb5_context context, krb5_pac pac, krb5_timestamp authtime, krb5_const_principal principal, const krb5_keyblock *server_key, const krb5_keyblock *privsvr_key, krb5_data *data) +{ + return krb5_pac_sign_ext(context, pac, authtime, principal, server_key, + privsvr_key, FALSE, data); +} + +krb5_error_code KRB5_CALLCONV +krb5_pac_sign_ext(krb5_context context, krb5_pac pac, krb5_timestamp authtime, + krb5_const_principal principal, + const krb5_keyblock *server_key, + const krb5_keyblock *privsvr_key, krb5_boolean with_realm, + krb5_data *data) { krb5_error_code ret; krb5_data server_cksum, privsvr_cksum; @@ -192,7 +211,8 @@ krb5_pac_sign(krb5_context context, krb5_pac pac, krb5_timestamp authtime, data->data = NULL; if (principal != NULL) { - ret = k5_insert_client_info(context, pac, authtime, principal); + ret = k5_insert_client_info(context, pac, authtime, principal, + with_realm); if (ret != 0) return ret; } diff --git a/src/lib/krb5/krb/t_pac.c b/src/lib/krb5/krb/t_pac.c index 61fb51a98a..8f9579ba60 100644 --- a/src/lib/krb5/krb/t_pac.c +++ b/src/lib/krb5/krb/t_pac.c @@ -313,6 +313,138 @@ main(int argc, char **argv) free(list); } + { + krb5_principal ep; + + ret = krb5_parse_name_flags(context, user, + KRB5_PRINCIPAL_PARSE_ENTERPRISE, &ep); + if (ret) + err(context, ret, "krb5_parse_name_flags"); + + /* Try to verify as enterprise. */ + ret = krb5_pac_verify(context, pac, authtime, ep, &member_keyblock, + &kdc_keyblock); + if (!ret) + err(context, ret, "krb5_pac_verify should have failed"); + + ret = krb5_pac_sign(context, pac, authtime, ep, &member_keyblock, + &kdc_keyblock, &data); + if (!ret) + err(context, ret, "krb5_pac_sign should have failed"); + + /* Try to verify with realm. */ + ret = krb5_pac_verify_ext(context, pac, authtime, p, &member_keyblock, + &kdc_keyblock, TRUE); + if (!ret) + err(context, ret, "krb5_pac_verify_ext with realm should fail"); + + /* Currently we can't re-sign the PAC with realm (although that could + * be useful), only sign a new one. */ + ret = krb5_pac_sign_ext(context, pac, authtime, p, &member_keyblock, + &kdc_keyblock, TRUE, &data); + if (!ret) + err(context, ret, "krb5_pac_sign_ext with realm should fail"); + + krb5_pac_free(context, pac); + + /* Test enterprise. */ + ret = krb5_pac_init(context, &pac); + if (ret) + err(context, ret, "krb5_pac_init"); + + ret = krb5_pac_sign(context, pac, authtime, ep, &member_keyblock, + &kdc_keyblock, &data); + if (ret) + err(context, ret, "krb5_pac_sign enterprise failed"); + + krb5_pac_free(context, pac); + + ret = krb5_pac_parse(context, data.data, data.length, &pac); + krb5_free_data_contents(context, &data); + if (ret) + err(context, ret, "krb5_pac_parse failed"); + + ret = krb5_pac_verify(context, pac, authtime, ep, &member_keyblock, + &kdc_keyblock); + if (ret) + err(context, ret, "krb5_pac_verify enterprise failed"); + + ret = krb5_pac_verify(context, pac, authtime, p, &member_keyblock, + &kdc_keyblock); + if (!ret) + err(context, ret, "krb5_pac_verify should have failed"); + + krb5_pac_free(context, pac); + + /* Test with realm. */ + ret = krb5_pac_init(context, &pac); + if (ret) + err(context, ret, "krb5_pac_init"); + + ret = krb5_pac_sign_ext(context, pac, authtime, p, &member_keyblock, + &kdc_keyblock, TRUE, &data); + if (ret) + err(context, ret, "krb5_pac_sign_ext with realm failed"); + + krb5_pac_free(context, pac); + + ret = krb5_pac_parse(context, data.data, data.length, &pac); + krb5_free_data_contents(context, &data); + if (ret) + err(context, ret, "krb5_pac_parse failed"); + + ret = krb5_pac_verify_ext(context, pac, authtime, p, &member_keyblock, + &kdc_keyblock, TRUE); + if (ret) + err(context, ret, "krb5_pac_verify_ext with realm failed"); + + ret = krb5_pac_verify(context, pac, authtime, p, &member_keyblock, + &kdc_keyblock); + if (!ret) + err(context, ret, "krb5_pac_verify should have failed"); + + krb5_pac_free(context, pac); + + /* Test enterprise with realm. */ + ret = krb5_pac_init(context, &pac); + if (ret) + err(context, ret, "krb5_pac_init"); + + ret = krb5_pac_sign_ext(context, pac, authtime, ep, &member_keyblock, + &kdc_keyblock, TRUE, &data); + if (ret) + err(context, ret, "krb5_pac_sign_ext ent with realm failed"); + + krb5_pac_free(context, pac); + + ret = krb5_pac_parse(context, data.data, data.length, &pac); + krb5_free_data_contents(context, &data); + if (ret) + err(context, ret, "krb5_pac_parse failed"); + + ret = krb5_pac_verify_ext(context, pac, authtime, ep, &member_keyblock, + &kdc_keyblock, TRUE); + if (ret) + err(context, ret, "krb5_pac_verify_ext ent with realm failed"); + + ret = krb5_pac_verify(context, pac, authtime, p, &member_keyblock, + &kdc_keyblock); + if (!ret) + err(context, ret, "krb5_pac_verify should have failed"); + + ret = krb5_pac_verify(context, pac, authtime, ep, &member_keyblock, + &kdc_keyblock); + if (!ret) + err(context, ret, "krb5_pac_verify should have failed"); + + ret = krb5_pac_verify_ext(context, pac, authtime, p, &member_keyblock, + &kdc_keyblock, TRUE); + if (!ret) + err(context, ret, "krb5_pac_verify_ext should have failed"); + + krb5_free_principal(context, ep); + } + krb5_pac_free(context, pac); krb5_free_principal(context, p); diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index 542209d9d1..dfdb72dafc 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -487,7 +487,9 @@ krb5_pac_get_types krb5_pac_init krb5_pac_parse krb5_pac_sign +krb5_pac_sign_ext krb5_pac_verify +krb5_pac_verify_ext krb5_parse_name krb5_parse_name_flags krb5_prepend_error_message diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def index 3bb2a80f8d..c350229317 100644 --- a/src/lib/krb5_32.def +++ b/src/lib/krb5_32.def @@ -476,6 +476,8 @@ EXPORTS ; new in 1.17 krb5_get_etype_info @447 + krb5_pac_sign_ext @448 + krb5_pac_verify_ext @449 ; private symbols used by SPAKE client module profile_get_string @439 ; PRIVATE profile_release_string @440 ; PRIVATE