From ef1d63a997a672096312bfe29131fc53e8abbe9b Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Thu, 5 Aug 2021 22:07:47 +1000 Subject: [PATCH] kinit: add --pk-anon-fast-armor option Add the --pk-anon-fast-armor option, which acquires a temporary anonymous PKINIT TGT to use as a FAST armor key. --- kuser/kinit.c | 23 ++- lib/hx509/cms.c | 77 ++++++++-- lib/hx509/hx509.h | 3 + lib/hx509/libhx509-exports.def | 1 + lib/hx509/version-script.map | 1 + lib/krb5/init_creds_pw.c | 244 +++++++++++++++++++++++++++----- lib/krb5/krb5_locl.h | 2 + lib/krb5/libkrb5-exports.def.in | 1 + lib/krb5/pkinit.c | 97 +++++++++---- lib/krb5/version-script.map | 1 + 10 files changed, 365 insertions(+), 85 deletions(-) diff --git a/kuser/kinit.c b/kuser/kinit.c index 6f8144f79..a8062e2d6 100644 --- a/kuser/kinit.c +++ b/kuser/kinit.c @@ -78,6 +78,7 @@ int pk_enterprise_flag = 0; struct hx509_certs_data *ent_user_id = NULL; char *pk_x509_anchors = NULL; int pk_use_enckey = 0; +int pk_anon_fast_armor = 0; static int canonicalize_flag = 0; static int enterprise_flag = 0; static int ok_as_delegate_flag = 0; @@ -183,6 +184,9 @@ static struct getargs args[] = { { "pk-use-enckey", 0, arg_flag, &pk_use_enckey, NP_("Use RSA encrypted reply (instead of DH)", ""), NULL }, + + { "pk-anon-fast-armor", 0, arg_flag, &pk_anon_fast_armor, + NP_("use unauthenticated anonymous PKINIT as FAST armor", ""), NULL }, #endif #ifndef NO_NTLM { "ntlm-domain", 0, arg_string, &ntlm_domain, @@ -620,6 +624,7 @@ get_new_tickets(krb5_context context, krb5_init_creds_context ctx = NULL; krb5_get_init_creds_opt *opt = NULL; krb5_prompter_fct prompter = krb5_prompter_posix; + #ifndef NO_NTLM struct ntlm_buf ntlmkey; memset(&ntlmkey, 0, sizeof(ntlmkey)); @@ -785,19 +790,30 @@ get_new_tickets(krb5_context context, } if (fast_armor_cache_string) { - krb5_ccache fastid; - + krb5_ccache fastid = NULL; + + if (pk_anon_fast_armor) + krb5_errx(context, 1, + N_("cannot specify FAST armor cache with FAST " + "anonymous PKINIT option", "")); + ret = krb5_cc_resolve(context, fast_armor_cache_string, &fastid); if (ret) { krb5_warn(context, ret, "krb5_cc_resolve(FAST cache)"); goto out; } - + ret = krb5_init_creds_set_fast_ccache(context, ctx, fastid); if (ret) { krb5_warn(context, ret, "krb5_init_creds_set_fast_ccache"); goto out; } + } else if (pk_anon_fast_armor) { + ret = krb5_init_creds_set_fast_anon_pkinit(context, ctx); + if (ret) { + krb5_warn(context, ret, "krb5_init_creds_set_fast_anon_pkinit"); + goto out; + } } if (use_keytab || keytab_str) { @@ -968,7 +984,6 @@ out: krb5_init_creds_free(context, ctx); if (tempccache) krb5_cc_destroy(context, tempccache); - if (enctype) free(enctype); diff --git a/lib/hx509/cms.c b/lib/hx509/cms.c index 0f17ce743..453762bd1 100644 --- a/lib/hx509/cms.c +++ b/lib/hx509/cms.c @@ -800,6 +800,60 @@ hx509_cms_verify_signed(hx509_context context, heim_oid *contentType, heim_octet_string *content, hx509_certs *signer_certs) +{ + unsigned int verify_flags; + + return hx509_cms_verify_signed_ext(context, + ctx, + flags, + data, + length, + signedContent, + pool, + contentType, + content, + signer_certs, + &verify_flags); +} + +/** + * Decode SignedData and verify that the signature is correct. + * + * @param context A hx509 context. + * @param ctx a hx509 verify context. + * @param flags to control the behaivor of the function. + * - HX509_CMS_VS_NO_KU_CHECK - Don't check KeyUsage + * - HX509_CMS_VS_ALLOW_DATA_OID_MISMATCH - allow oid mismatch + * - HX509_CMS_VS_ALLOW_ZERO_SIGNER - no signer, see below. + * @param data pointer to CMS SignedData encoded data. + * @param length length of the data that data point to. + * @param signedContent external data used for signature. + * @param pool certificate pool to build certificates paths. + * @param contentType free with der_free_oid(). + * @param content the output of the function, free with + * der_free_octet_string(). + * @param signer_certs list of the cerficates used to sign this + * request, free with hx509_certs_free(). + * @param verify_flags flags indicating whether the certificate + * was verified or not + * + * @return an hx509 error code. + * + * @ingroup hx509_cms + */ + +HX509_LIB_FUNCTION int HX509_LIB_CALL +hx509_cms_verify_signed_ext(hx509_context context, + hx509_verify_ctx ctx, + unsigned int flags, + const void *data, + size_t length, + const heim_octet_string *signedContent, + hx509_certs pool, + heim_oid *contentType, + heim_octet_string *content, + hx509_certs *signer_certs, + unsigned int *verify_flags) { SignerInfo *signer_info; hx509_cert cert = NULL; @@ -810,6 +864,8 @@ hx509_cms_verify_signed(hx509_context context, size_t i; *signer_certs = NULL; + *verify_flags = 0; + content->data = NULL; content->length = 0; contentType->length = 0; @@ -1038,22 +1094,19 @@ hx509_cms_verify_signed(hx509_context context, goto next_sigature; /** - * If HX509_CMS_VS_NO_VALIDATE flags is set, do not verify the - * signing certificates and leave that up to the caller. + * If HX509_CMS_VS_NO_VALIDATE flags is set, return the signer + * certificate unconditionally but do not set HX509_CMS_VSE_VALIDATED. */ + ret = hx509_verify_path(context, ctx, cert, certs); + if (ret == 0 || (flags & HX509_CMS_VS_NO_VALIDATE)) { + if (ret == 0) + *verify_flags |= HX509_CMS_VSE_VALIDATED; - if ((flags & HX509_CMS_VS_NO_VALIDATE) == 0) { - ret = hx509_verify_path(context, ctx, cert, certs); - if (ret) - goto next_sigature; + ret = hx509_certs_add(context, *signer_certs, cert); + if (ret == 0) + found_valid_sig++; } - ret = hx509_certs_add(context, *signer_certs, cert); - if (ret) - goto next_sigature; - - found_valid_sig++; - next_sigature: if (cert) hx509_cert_free(cert); diff --git a/lib/hx509/hx509.h b/lib/hx509/hx509.h index ce065c134..75d64734b 100644 --- a/lib/hx509/hx509.h +++ b/lib/hx509/hx509.h @@ -182,6 +182,9 @@ typedef enum { #define HX509_CMS_VS_ALLOW_ZERO_SIGNER 0x04 #define HX509_CMS_VS_NO_VALIDATE 0x08 +/* flags from hx509_cms_verify_signed_ext (out verify_flags) */ +#define HX509_CMS_VSE_VALIDATED 0x01 + /* selectors passed to hx509_crypto_select and hx509_crypto_available */ #define HX509_SELECT_ALL 0 #define HX509_SELECT_DIGEST 1 diff --git a/lib/hx509/libhx509-exports.def b/lib/hx509/libhx509-exports.def index 951b57b9c..745ad5a4d 100644 --- a/lib/hx509/libhx509-exports.def +++ b/lib/hx509/libhx509-exports.def @@ -150,6 +150,7 @@ EXPORTS hx509_cms_unenvelope hx509_cms_unwrap_ContentInfo hx509_cms_verify_signed + hx509_cms_verify_signed_ext hx509_cms_wrap_ContentInfo hx509_context_free hx509_context_init diff --git a/lib/hx509/version-script.map b/lib/hx509/version-script.map index a4e702da9..a6c81da1c 100644 --- a/lib/hx509/version-script.map +++ b/lib/hx509/version-script.map @@ -137,6 +137,7 @@ HEIMDAL_X509_1.2 { hx509_cms_unenvelope; hx509_cms_unwrap_ContentInfo; hx509_cms_verify_signed; + hx509_cms_verify_signed_ext; hx509_cms_wrap_ContentInfo; hx509_context_free; hx509_context_init; diff --git a/lib/krb5/init_creds_pw.c b/lib/krb5/init_creds_pw.c index 66608b98e..27a9b3b99 100644 --- a/lib/krb5/init_creds_pw.c +++ b/lib/krb5/init_creds_pw.c @@ -96,12 +96,16 @@ typedef struct krb5_get_init_creds_ctx { #define KRB5_FAST_REQUIRED 64 /* fast required by action of caller */ #define KRB5_FAST_DISABLED 128 #define KRB5_FAST_AP_ARMOR_SERVICE 256 +#define KRB5_FAST_ANON_PKINIT_ARMOR 512 +#define KRB5_FAST_KDC_VERIFIED 1024 krb5_keyblock *reply_key; krb5_ccache armor_ccache; krb5_principal armor_service; krb5_crypto armor_crypto; krb5_keyblock armor_key; krb5_keyblock *strengthen_key; + krb5_get_init_creds_opt *anon_pkinit_opt; + krb5_init_creds_context anon_pkinit_ctx; } fast_state; } krb5_get_init_creds_ctx; @@ -180,7 +184,12 @@ free_init_creds_ctx(krb5_context context, krb5_init_creds_context ctx) if (ctx->fast_state.strengthen_key) krb5_free_keyblock(context, ctx->fast_state.strengthen_key); krb5_free_keyblock_contents(context, &ctx->fast_state.armor_key); - + if (ctx->fast_state.flags & KRB5_FAST_ANON_PKINIT_ARMOR) + krb5_cc_destroy(context, ctx->fast_state.armor_ccache); + if (ctx->fast_state.anon_pkinit_ctx) + free_init_creds_ctx(context, ctx->fast_state.anon_pkinit_ctx); + if (ctx->fast_state.anon_pkinit_opt) + krb5_get_init_creds_opt_free(context, ctx->fast_state.anon_pkinit_opt); krb5_data_free(&ctx->req_buffer); krb5_free_cred_contents(context, &ctx->cred); free_METHOD_DATA(&ctx->md); @@ -1692,6 +1701,21 @@ krb5_init_creds_set_fast_ccache(krb5_context context, { ctx->fast_state.armor_ccache = fast_ccache; ctx->fast_state.flags |= KRB5_FAST_REQUIRED; + ctx->fast_state.flags |= KRB5_FAST_KDC_VERIFIED; + ctx->fast_state.flags &= ~(KRB5_FAST_ANON_PKINIT_ARMOR); + + return 0; +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_set_fast_anon_pkinit(krb5_context context, + krb5_init_creds_context ctx) +{ + if (ctx->fast_state.armor_ccache) + return EINVAL; + + ctx->fast_state.flags |= KRB5_FAST_REQUIRED; + ctx->fast_state.flags |= KRB5_FAST_ANON_PKINIT_ARMOR; return 0; } @@ -1855,6 +1879,7 @@ fast_unwrap_error(krb5_context context, struct fast_state *state, KRB_ERROR *err krb5_error_code _krb5_make_fast_ap_fxarmor(krb5_context context, krb5_ccache armor_ccache, + krb5_const_realm realm, krb5_data *armor_value, krb5_keyblock *armor_key, krb5_crypto *armor_crypto) @@ -1863,6 +1888,7 @@ _krb5_make_fast_ap_fxarmor(krb5_context context, krb5_creds cred, *credp = NULL; krb5_error_code ret; krb5_data empty; + krb5_const_realm tgs_realm; krb5_data_zero(&empty); @@ -1875,11 +1901,19 @@ _krb5_make_fast_ap_fxarmor(krb5_context context, ret = krb5_cc_get_principal(context, armor_ccache, &cred.client); if (ret) goto out; - + + /* + * Make sure we don't ask for a krbtgt/WELLKNOWN:ANONYMOUS + */ + if (strcmp(cred.client->realm, KRB5_ANON_REALM) == 0) + tgs_realm = realm; + else + tgs_realm = cred.client->realm; + ret = krb5_make_principal(context, &cred.server, - cred.client->realm, + tgs_realm, KRB5_TGS_NAME, - cred.client->realm, + tgs_realm, NULL); if (ret) { krb5_free_principal(context, cred.client); @@ -2162,34 +2196,13 @@ fast_wrap_req(krb5_context context, struct fast_state *state, KDC_REQ *req) } -/** - * The core loop if krb5_get_init_creds() function family. Create the - * packets and have the caller send them off to the KDC. - * - * If the caller want all work been done for them, use - * krb5_init_creds_get() instead. - * - * @param context a Kerberos 5 context. - * @param ctx ctx krb5_init_creds_context context. - * @param in input data from KDC, first round it should be reset by krb5_data_zer(). - * @param out reply to KDC. - * @param hostinfo KDC address info, first round it can be NULL. - * @param flags status of the round, if - * KRB5_INIT_CREDS_STEP_FLAG_CONTINUE is set, continue one more round. - * - * @return 0 for success, or an Kerberos 5 error code, see - * krb5_get_error_message(). - * - * @ingroup krb5_credential - */ - -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_init_creds_step(krb5_context context, - krb5_init_creds_context ctx, - krb5_data *in, - krb5_data *out, - krb5_krbhst_info *hostinfo, - unsigned int *flags) +static krb5_error_code +init_creds_step(krb5_context context, + krb5_init_creds_context ctx, + krb5_data *in, + krb5_data *out, + krb5_krbhst_info *hostinfo, + unsigned int *flags) { krb5_error_code ret; size_t len = 0; @@ -2201,10 +2214,8 @@ krb5_init_creds_step(krb5_context context, if (ctx->as_req.req_body.cname == NULL) { ret = init_as_req(context, ctx->flags, &ctx->cred, ctx->addrs, ctx->etypes, &ctx->as_req); - if (ret) { - free_init_creds_ctx(context, ctx); + if (ret) return ret; - } } #define MAX_PA_COUNTER 10 @@ -2468,10 +2479,8 @@ krb5_init_creds_step(krb5_context context, if (ctx->as_req.req_body.cname == NULL) { ret = init_as_req(context, ctx->flags, &ctx->cred, ctx->addrs, ctx->etypes, &ctx->as_req); - if (ret) { - free_init_creds_ctx(context, ctx); + if (ret) return ret; - } } if (ctx->as_req.padata) { @@ -2522,6 +2531,165 @@ krb5_init_creds_step(krb5_context context, return ret; } +static krb5_error_code +fast_anon_pkinit_step(krb5_context context, + krb5_init_creds_context ctx, + krb5_data *in, + krb5_data *out, + krb5_krbhst_info *hostinfo, + unsigned int *flags) +{ + krb5_error_code ret; + krb5_const_realm realm = ctx->cred.client->realm; + struct fast_state *state = &ctx->fast_state; + krb5_init_creds_context anon_pk_ctx; + krb5_principal principal = NULL; + krb5_ccache ccache = NULL; + krb5_creds cred; + + memset(&cred, 0, sizeof(cred)); + + if (state->anon_pkinit_opt == NULL) { + ret = krb5_get_init_creds_opt_alloc(context, &state->anon_pkinit_opt); + if (ret) + goto out; + + /* ticket lifetime matches FAST_EXPIRATION_TIME in kdc_locl.h */ + krb5_get_init_creds_opt_set_tkt_life(state->anon_pkinit_opt, 3 * 60); + krb5_get_init_creds_opt_set_anonymous(state->anon_pkinit_opt, TRUE); + + ret = krb5_make_principal(context, &principal, realm, + KRB5_WELLKNOWN_NAME, KRB5_ANON_NAME, NULL); + if (ret) + return ret; + + ret = krb5_get_init_creds_opt_set_pkinit(context, + state->anon_pkinit_opt, + principal, + NULL, NULL, NULL, NULL, + KRB5_GIC_OPT_PKINIT_ANONYMOUS | + KRB5_GIC_OPT_PKINIT_NO_KDC_ANCHOR, + NULL, NULL, NULL); + if (ret) + goto out; + + ret = krb5_init_creds_init(context, principal, NULL, NULL, + ctx->cred.times.starttime, + state->anon_pkinit_opt, + &state->anon_pkinit_ctx); + if (ret) + goto out; + + heim_assert((state->anon_pkinit_ctx->fast_state.flags & KRB5_FAST_ANON_PKINIT_ARMOR) == 0, + "Invalid recursive PKINIT armor state"); + } + + anon_pk_ctx = state->anon_pkinit_ctx; + + ret = init_creds_step(context, anon_pk_ctx, in, out, hostinfo, flags); + if (ret || + (*flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE)) + goto out; + + ret = krb5_process_last_request(context, state->anon_pkinit_opt, anon_pk_ctx); + if (ret) + goto out; + + ret = krb5_cc_new_unique(context, "MEMORY", NULL, &ccache); + if (ret) + goto out; + + ret = krb5_init_creds_get_creds(context, anon_pk_ctx, &cred); + if (ret) + goto out; + + if (!cred.flags.b.enc_pa_rep) { + ret = KRB5KDC_ERR_BADOPTION; /* KDC does not support FAST */ + goto out; + } + + ret = krb5_cc_initialize(context, ccache, anon_pk_ctx->cred.client); + if (ret) + goto out; + + ret = krb5_cc_store_cred(context, ccache, &cred); + if (ret) + goto out; + + if (_krb5_pk_is_kdc_verified(context, state->anon_pkinit_opt)) + state->flags |= KRB5_FAST_KDC_VERIFIED; + else + state->flags &= ~(KRB5_FAST_KDC_VERIFIED); + + state->armor_ccache = ccache; + ccache = NULL; + + free_init_creds_ctx(context, state->anon_pkinit_ctx); + state->anon_pkinit_ctx = NULL; + + krb5_get_init_creds_opt_free(context, state->anon_pkinit_opt); + state->anon_pkinit_opt = NULL; + + *flags |= KRB5_INIT_CREDS_STEP_FLAG_CONTINUE; + +out: + krb5_free_principal(context, principal); + krb5_free_cred_contents(context, &cred); + if (ccache) + krb5_cc_destroy(context, ccache); + + return ret; +} + +/** + * The core loop if krb5_get_init_creds() function family. Create the + * packets and have the caller send them off to the KDC. + * + * If the caller want all work been done for them, use + * krb5_init_creds_get() instead. + * + * @param context a Kerberos 5 context. + * @param ctx ctx krb5_init_creds_context context. + * @param in input data from KDC, first round it should be reset by krb5_data_zer(). + * @param out reply to KDC. + * @param hostinfo KDC address info, first round it can be NULL. + * @param flags status of the round, if + * KRB5_INIT_CREDS_STEP_FLAG_CONTINUE is set, continue one more round. + * + * @return 0 for success, or an Kerberos 5 error code, see + * krb5_get_error_message(). + * + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_step(krb5_context context, + krb5_init_creds_context ctx, + krb5_data *in, + krb5_data *out, + krb5_krbhst_info *hostinfo, + unsigned int *flags) +{ + krb5_error_code ret; + krb5_data empty; + + krb5_data_zero(&empty); + + if ((ctx->fast_state.flags & KRB5_FAST_ANON_PKINIT_ARMOR) && + ctx->fast_state.armor_ccache == NULL) { + ret = fast_anon_pkinit_step(context, ctx, in, out, + hostinfo, flags); + if (ret || + ((*flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE) == 0) || + out->length) + return ret; + + in = ∅ + } + + return init_creds_step(context, ctx, in, out, hostinfo, flags); +} + /** * Extract the newly acquired credentials from krb5_init_creds_context * context. diff --git a/lib/krb5/krb5_locl.h b/lib/krb5/krb5_locl.h index 8130412e2..87c36e86d 100644 --- a/lib/krb5/krb5_locl.h +++ b/lib/krb5/krb5_locl.h @@ -354,6 +354,7 @@ struct krb5_pk_identity { hx509_revoke_ctx revokectx; int flags; #define PKINIT_BTMM 1 +#define PKINIT_NO_KDC_ANCHOR 2 }; enum krb5_pk_type { @@ -380,6 +381,7 @@ struct krb5_pk_init_ctx_data { unsigned int require_hostname_match:1; unsigned int trustedCertifiers:1; unsigned int anonymous:1; + unsigned int kdc_verified:1; }; #endif /* PKINIT */ diff --git a/lib/krb5/libkrb5-exports.def.in b/lib/krb5/libkrb5-exports.def.in index 19f8ee421..c8e4be917 100644 --- a/lib/krb5/libkrb5-exports.def.in +++ b/lib/krb5/libkrb5-exports.def.in @@ -828,6 +828,7 @@ EXPORTS krb5_init_creds_get_creds krb5_init_creds_get_error krb5_init_creds_init + krb5_init_creds_set_fast_anon_pkinit krb5_init_creds_set_fast_ccache krb5_init_creds_set_keytab krb5_init_creds_set_password diff --git a/lib/krb5/pkinit.c b/lib/krb5/pkinit.c index 1e37a3d34..7f114884d 100644 --- a/lib/krb5/pkinit.c +++ b/lib/krb5/pkinit.c @@ -787,7 +787,7 @@ _krb5_pk_mk_padata(krb5_context context, NULL); if (ic_flags & KRB5_INIT_CREDS_NO_C_NO_EKU_CHECK) ctx->require_eku = 0; - if (ctx->id->flags & PKINIT_BTMM) + if (ctx->id->flags & (PKINIT_BTMM | PKINIT_NO_KDC_ANCHOR)) ctx->require_eku = 0; ctx->require_krbtgt_otherName = @@ -829,33 +829,39 @@ pk_verify_sign(krb5_context context, struct krb5_pk_cert **signer) { hx509_certs signer_certs; - int ret, flags = 0; + int ret; + unsigned flags = 0, verify_flags = 0; + + *signer = NULL; - /* BTMM is broken in Leo and SnowLeo */ if (id->flags & PKINIT_BTMM) { flags |= HX509_CMS_VS_ALLOW_DATA_OID_MISMATCH; flags |= HX509_CMS_VS_NO_KU_CHECK; flags |= HX509_CMS_VS_NO_VALIDATE; } + if (id->flags & PKINIT_NO_KDC_ANCHOR) + flags |= HX509_CMS_VS_NO_VALIDATE; - *signer = NULL; - - ret = hx509_cms_verify_signed(context->hx509ctx, - id->verify_ctx, - flags, - data, - length, - NULL, - id->certpool, - contentType, - content, - &signer_certs); + ret = hx509_cms_verify_signed_ext(context->hx509ctx, + id->verify_ctx, + flags, + data, + length, + NULL, + id->certpool, + contentType, + content, + &signer_certs, + &verify_flags); if (ret) { pk_copy_error(context, context->hx509ctx, ret, "CMS verify signed failed"); return ret; } + if ((verify_flags & HX509_CMS_VSE_VALIDATED) == 0) + goto out; + *signer = calloc(1, sizeof(**signer)); if (*signer == NULL) { krb5_clear_error_message(context); @@ -1061,7 +1067,9 @@ pk_verify_host(krb5_context context, free_KRB5PrincipalName(&r); } hx509_free_octet_string_list(&list); - if (matched == 0) { + + if (matched == 0 && + (ctx->id->flags & PKINIT_NO_KDC_ANCHOR) == 0) { ret = KRB5_KDC_ERR_INVALID_CERTIFICATE; /* XXX: Lost in translation... */ krb5_set_error_message(context, ret, @@ -1192,10 +1200,16 @@ pk_rd_pa_reply_enckey(krb5_context context, ret = krb5_data_copy(&content, unwrapped.data, unwrapped.length); der_free_octet_string(&unwrapped); - /* make sure that it is the kdc's certificate */ - ret = pk_verify_host(context, realm, hi, ctx, host); - if (ret) { - goto out; + heim_assert(host || (ctx->id->flags & PKINIT_NO_KDC_ANCHOR), + "KDC signature must be verified unless PKINIT_NO_KDC_ANCHOR set"); + + if (host) { + /* make sure that it is the kdc's certificate */ + ret = pk_verify_host(context, realm, hi, ctx, host); + if (ret) + goto out; + + ctx->kdc_verified = 1; } #if 0 @@ -1374,10 +1388,17 @@ pk_rd_pa_reply_dh(krb5_context context, if (ret) goto out; - /* make sure that it is the kdc's certificate */ - ret = pk_verify_host(context, realm, hi, ctx, host); - if (ret) - goto out; + heim_assert(host || (ctx->id->flags & PKINIT_NO_KDC_ANCHOR), + "KDC signature must be verified unless PKINIT_NO_KDC_ANCHOR set"); + + if (host) { + /* make sure that it is the kdc's certificate */ + ret = pk_verify_host(context, realm, hi, ctx, host); + if (ret) + goto out; + + ctx->kdc_verified = 1; + } if (der_heim_oid_cmp(&contentType, &asn1_oid_id_pkdhkeydata)) { ret = KRB5KRB_AP_ERR_MSG_TYPE; @@ -1836,12 +1857,6 @@ _krb5_pk_load_id(krb5_context context, *ret_id = NULL; - if (anchor_id == NULL) { - krb5_set_error_message(context, HEIM_PKINIT_NO_VALID_CA, - N_("PKINIT: No anchor given", "")); - return HEIM_PKINIT_NO_VALID_CA; - } - /* load cert */ id = calloc(1, sizeof(*id)); @@ -2383,6 +2398,13 @@ krb5_get_init_creds_opt_set_pkinit(krb5_context context, if (flags & KRB5_GIC_OPT_PKINIT_ANONYMOUS) opt->opt_private->pk_init_ctx->anonymous = 1; + if ((flags & KRB5_GIC_OPT_PKINIT_NO_KDC_ANCHOR) == 0 && + x509_anchors == NULL) { + krb5_set_error_message(context, HEIM_PKINIT_NO_VALID_CA, + N_("PKINIT: No anchor given", "")); + return HEIM_PKINIT_NO_VALID_CA; + } + ret = _krb5_pk_load_id(context, &opt->opt_private->pk_init_ctx->id, user_id, @@ -2402,9 +2424,10 @@ krb5_get_init_creds_opt_set_pkinit(krb5_context context, } if (flags & KRB5_GIC_OPT_PKINIT_BTMM) opt->opt_private->pk_init_ctx->id->flags |= PKINIT_BTMM; - if (principal && krb5_principal_is_lkdc(context, principal)) opt->opt_private->pk_init_ctx->id->flags |= PKINIT_BTMM; + if (flags & KRB5_GIC_OPT_PKINIT_NO_KDC_ANCHOR) + opt->opt_private->pk_init_ctx->id->flags |= PKINIT_NO_KDC_ANCHOR; if (opt->opt_private->pk_init_ctx->id->certs) { ret = _krb5_pk_set_user_id(context, @@ -2624,3 +2647,15 @@ krb5_pk_enterprise_cert(krb5_context context, return EINVAL; #endif } + +KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL +_krb5_pk_is_kdc_verified(krb5_context context, + krb5_get_init_creds_opt *opt) +{ + if (opt == NULL || + opt->opt_private == NULL || + opt->opt_private->pk_init_ctx == NULL) + return FALSE; + + return opt->opt_private->pk_init_ctx->kdc_verified; +} diff --git a/lib/krb5/version-script.map b/lib/krb5/version-script.map index d6a13282b..7312f20da 100644 --- a/lib/krb5/version-script.map +++ b/lib/krb5/version-script.map @@ -814,6 +814,7 @@ HEIMDAL_KRB5_2.0 { krb5_process_last_request; krb5_init_creds_init; krb5_init_creds_set_service; + krb5_init_creds_set_fast_anon_pkinit; krb5_init_creds_set_fast_ccache; krb5_init_creds_set_keytab; krb5_init_creds_get;