/* Return true if princ is in the default realm of ldap_context or is a * cross-realm TGS principal for that realm. */ krb5_boolean is_principal_in_realm(krb5_ldap_context *ldap_context, krb5_const_principal princ) { const char *realm = ldap_context->lrparams->realm_name; if (princ->length == 2 && data_eq_string(princ->data[0], "krbtgt") && data_eq_string(princ->data[1], realm)) return TRUE; return data_eq_string(princ->realm, realm); }
/* Return true if princ is a local (not cross-realm) krbtgt principal. */ krb5_boolean is_local_tgt(krb5_principal princ) { return princ->length == 2 && data_eq_string(princ->data[0], KRB5_TGS_NAME) && data_eq(princ->realm, princ->data[1]); }
/* Return true if princ is the local krbtgt principal for local_realm. */ static krb5_boolean is_local_tgt(krb5_principal princ, krb5_data *realm) { return princ->length == 2 && data_eq(princ->realm, *realm) && data_eq_string(princ->data[0], KRB5_TGS_NAME) && data_eq(princ->data[1], *realm); }
/* * Try to find tokeninfos which match configuration data recorded in the input * ccache, and if exactly one is found, drop the rest. */ static krb5_error_code filter_config_tokeninfos(krb5_context context, krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock, krb5_otp_tokeninfo **tis) { krb5_otp_tokeninfo *match = NULL; size_t i, j; const char *vendor, *alg_id, *token_id; /* Pull up what we know about the token we want to use. */ vendor = cb->get_cc_config(context, rock, "vendor"); alg_id = cb->get_cc_config(context, rock, "algID"); token_id = cb->get_cc_config(context, rock, "tokenID"); /* Look for a single matching entry. */ for (i = 0; tis[i] != NULL; i++) { if (vendor != NULL && tis[i]->vendor.length > 0 && !data_eq_string(tis[i]->vendor, vendor)) continue; if (alg_id != NULL && tis[i]->alg_id.length > 0 && !data_eq_string(tis[i]->alg_id, alg_id)) continue; if (token_id != NULL && tis[i]->token_id.length > 0 && !data_eq_string(tis[i]->token_id, token_id)) continue; /* Oh, we already had a matching entry. More than one -> no change. */ if (match != NULL) return 0; match = tis[i]; } /* No matching entry -> no change. */ if (match == NULL) return 0; /* Prune out everything except the best match. */ for (i = 0, j = 0; tis[i] != NULL; i++) { if (tis[i] != match) k5_free_otp_tokeninfo(context, tis[i]); else tis[j++] = tis[i]; } tis[j] = NULL; return 0; }
/* Return true if a TGS credential is for the client's local realm. */ static inline int tgt_is_local_realm(krb5_creds *tgt) { return (tgt->server->length == 2 && data_eq_string(tgt->server->data[0], KRB5_TGS_NAME) && data_eq(tgt->server->data[1], tgt->client->realm) && data_eq(tgt->server->realm, tgt->client->realm)); }
/* Possibly try a non-referral request after a referral request failure. * Expects ctx->reply_code to be set to the error from a referral request. */ static krb5_error_code try_fallback(krb5_context context, krb5_tkt_creds_context ctx) { krb5_error_code code; char **hrealms; /* Only fall back if our error was from the first referral request. */ if (ctx->referral_count > 1) return ctx->reply_code; /* If the request used a specified realm, make a non-referral request to * that realm (in case it's a KDC which rejects KDC_OPT_CANONICALIZE). */ if (!krb5_is_referral_realm(&ctx->req_server->realm)) return begin_non_referral(context, ctx); if (ctx->server->length < 2) { /* We need a type/host format principal to find a fallback realm. */ return KRB5_ERR_HOST_REALM_UNKNOWN; } /* We expect this to give exactly one answer (XXX clean up interface). */ code = krb5_get_fallback_host_realm(context, &ctx->server->data[1], &hrealms); if (code != 0) return code; /* If the fallback realm isn't any different, use the existing TGT. */ if (data_eq_string(ctx->server->realm, hrealms[0])) { krb5_free_host_realm(context, hrealms); return begin_non_referral(context, ctx); } /* Rewrite server->realm to be the fallback realm. */ krb5_free_data_contents(context, &ctx->server->realm); ctx->server->realm = string2data(hrealms[0]); free(hrealms); TRACE_TKT_CREDS_FALLBACK(context, &ctx->server->realm); /* Obtain a TGT for the new service realm. */ ctx->getting_tgt_for = STATE_NON_REFERRAL; return begin_get_tgt(context, ctx); }
/* * Find a remote realm TGS principal for an unknown host-based service * principal. */ static krb5_int32 find_referral_tgs(kdc_realm_t *kdc_active_realm, krb5_kdc_req *request, krb5_principal *krbtgt_princ) { krb5_error_code retval = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; char **realms = NULL, *hostname = NULL; krb5_data srealm = request->server->realm; if (!is_referral_req(kdc_active_realm, request)) goto cleanup; hostname = data2string(krb5_princ_component(kdc_context, request->server, 1)); if (hostname == NULL) { retval = ENOMEM; goto cleanup; } /* If the hostname doesn't contain a '.', it's not a FQDN. */ if (strchr(hostname, '.') == NULL) goto cleanup; retval = krb5_get_host_realm(kdc_context, hostname, &realms); if (retval) { /* no match found */ kdc_err(kdc_context, retval, "unable to find realm of host"); goto cleanup; } /* Don't return a referral to the empty realm or the service realm. */ if (realms == NULL || realms[0] == NULL || *realms[0] == '\0' || data_eq_string(srealm, realms[0])) { retval = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto cleanup; } retval = krb5_build_principal(kdc_context, krbtgt_princ, srealm.length, srealm.data, "krbtgt", realms[0], (char *)0); cleanup: krb5_free_host_realm(kdc_context, realms); free(hostname); return retval; }
/* * See if two data entries match. If e1 is a wildcard (matching a whole * component only) and targetflag is false, save an alias to e2 into * ws->backref. If e1 is a back-reference and targetflag is true, compare the * appropriate entry in ws->backref to e2. If ws is NULL, do not store or * match back-references. */ static krb5_boolean match_data(const krb5_data *e1, const krb5_data *e2, krb5_boolean targetflag, struct wildstate *ws) { int n; if (data_eq_string(*e1, "*")) { if (ws != NULL && !targetflag) { if (ws->nwild < 9) ws->backref[ws->nwild++] = e2; } return TRUE; } if (ws != NULL && targetflag && e1->length == 2 && e1->data[0] == '*' && e1->data[1] >= '1' && e1->data[1] <= '9') { n = e1->data[1] - '1'; if (n >= ws->nwild) return FALSE; return data_eq(*e2, *ws->backref[n]); } else { return data_eq(*e2, *e1); } }
/* Return a list of all unique host service princs in keytab. */ static krb5_error_code get_host_princs_from_keytab(krb5_context context, krb5_keytab keytab, krb5_principal **princ_list_out) { krb5_error_code ret; krb5_kt_cursor cursor; krb5_keytab_entry kte; krb5_principal *plist = NULL, p; *princ_list_out = NULL; ret = krb5_kt_start_seq_get(context, keytab, &cursor); if (ret) goto cleanup; while ((ret = krb5_kt_next_entry(context, keytab, &kte, &cursor)) == 0) { p = kte.principal; if (p->length == 2 && data_eq_string(p->data[0], "host")) ret = add_princ_list(context, p, &plist); krb5_kt_free_entry(context, &kte); if (ret) break; } (void)krb5_kt_end_seq_get(context, keytab, &cursor); if (ret == KRB5_KT_END) ret = 0; if (ret) goto cleanup; *princ_list_out = plist; plist = NULL; cleanup: free_princ_list(context, plist); return ret; }
static krb5_int32 prep_reprocess_req(krb5_kdc_req *request, krb5_principal *krbtgt_princ) { krb5_error_code retval = KRB5KRB_AP_ERR_BADMATCH; char **realms, **cpp, *temp_buf=NULL; krb5_data *comp1 = NULL, *comp2 = NULL; char *comp1_str = NULL; /* By now we know that server principal name is unknown. * If CANONICALIZE flag is set in the request * If req is not U2U authn. req * the requested server princ. has exactly two components * either * the name type is NT-SRV-HST * or name type is NT-UNKNOWN and * the 1st component is listed in conf file under host_based_services * the 1st component is not in a list in conf under "no_host_referral" * the 2d component looks like fully-qualified domain name (FQDN) * If all of these conditions are satisfied - try mapping the FQDN and * re-process the request as if client had asked for cross-realm TGT. */ if (isflagset(request->kdc_options, KDC_OPT_CANONICALIZE) && !isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY) && krb5_princ_size(kdc_context, request->server) == 2) { comp1 = krb5_princ_component(kdc_context, request->server, 0); comp2 = krb5_princ_component(kdc_context, request->server, 1); comp1_str = calloc(1,comp1->length+1); if (!comp1_str) { retval = ENOMEM; goto cleanup; } strlcpy(comp1_str,comp1->data,comp1->length+1); if ((krb5_princ_type(kdc_context, request->server) == KRB5_NT_SRV_HST || krb5_princ_type(kdc_context, request->server) == KRB5_NT_SRV_INST || (krb5_princ_type(kdc_context, request->server) == KRB5_NT_UNKNOWN && kdc_active_realm->realm_host_based_services != NULL && (krb5_match_config_pattern(kdc_active_realm->realm_host_based_services, comp1_str) == TRUE || krb5_match_config_pattern(kdc_active_realm->realm_host_based_services, KRB5_CONF_ASTERISK) == TRUE))) && (kdc_active_realm->realm_no_host_referral == NULL || (krb5_match_config_pattern(kdc_active_realm->realm_no_host_referral, KRB5_CONF_ASTERISK) == FALSE && krb5_match_config_pattern(kdc_active_realm->realm_no_host_referral, comp1_str) == FALSE))) { if (memchr(comp2->data, '.', comp2->length) == NULL) goto cleanup; temp_buf = calloc(1, comp2->length+1); if (!temp_buf) { retval = ENOMEM; goto cleanup; } strlcpy(temp_buf, comp2->data,comp2->length+1); retval = krb5int_get_domain_realm_mapping(kdc_context, temp_buf, &realms); free(temp_buf); if (retval) { /* no match found */ kdc_err(kdc_context, retval, "unable to find realm of host"); goto cleanup; } if (realms == 0) { retval = KRB5KRB_AP_ERR_BADMATCH; goto cleanup; } /* Don't return a referral to the null realm or the service * realm. */ if (realms[0] == 0 || data_eq_string(request->server->realm, realms[0])) { free(realms[0]); free(realms); retval = KRB5KRB_AP_ERR_BADMATCH; goto cleanup; } /* Modify request. * Construct cross-realm tgt : krbtgt/REMOTE_REALM@LOCAL_REALM * and use it as a principal in this req. */ retval = krb5_build_principal(kdc_context, krbtgt_princ, (*request->server).realm.length, (*request->server).realm.data, "krbtgt", realms[0], (char *)0); for (cpp = realms; *cpp; cpp++) free(*cpp); } } cleanup: free(comp1_str); return retval; }
static krb5_error_code rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context, const krb5_ap_req *req, krb5_const_principal server, krb5_keytab keytab, krb5_flags *ap_req_options, krb5_ticket **ticket, int check_valid_flag) { krb5_error_code retval = 0; krb5_enctype *desired_etypes = NULL; int desired_etypes_len = 0; int rfc4537_etypes_len = 0; krb5_enctype *permitted_etypes = NULL; int permitted_etypes_len = 0; krb5_keyblock decrypt_key; decrypt_key.enctype = ENCTYPE_NULL; decrypt_key.contents = NULL; req->ticket->enc_part2 = NULL; /* if (req->ap_options & AP_OPTS_USE_SESSION_KEY) do we need special processing here ? */ /* decrypt the ticket */ if ((*auth_context)->key) { /* User to User authentication */ if ((retval = krb5_decrypt_tkt_part(context, &(*auth_context)->key->keyblock, req->ticket))) goto cleanup; if (check_valid_flag) { decrypt_key = (*auth_context)->key->keyblock; (*auth_context)->key->keyblock.contents = NULL; } krb5_k_free_key(context, (*auth_context)->key); (*auth_context)->key = NULL; } else { retval = decrypt_ticket(context, req, server, keytab, check_valid_flag ? &decrypt_key : NULL); if (retval) goto cleanup; } TRACE_RD_REQ_TICKET(context, req->ticket->enc_part2->client, req->ticket->server, req->ticket->enc_part2->session); /* XXX this is an evil hack. check_valid_flag is set iff the call is not from inside the kdc. we can use this to determine which key usage to use */ #ifndef LEAN_CLIENT if ((retval = decrypt_authenticator(context, req, &((*auth_context)->authentp), check_valid_flag))) goto cleanup; #endif if (!krb5_principal_compare(context, (*auth_context)->authentp->client, req->ticket->enc_part2->client)) { retval = KRB5KRB_AP_ERR_BADMATCH; goto cleanup; } if ((*auth_context)->remote_addr && !krb5_address_search(context, (*auth_context)->remote_addr, req->ticket->enc_part2->caddrs)) { retval = KRB5KRB_AP_ERR_BADADDR; goto cleanup; } if (!server) { server = req->ticket->server; } /* Get an rcache if necessary. */ if (((*auth_context)->rcache == NULL) && ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_DO_TIME) && server) { if ((retval = krb5_get_server_rcache(context, krb5_princ_component(context,server,0), &(*auth_context)->rcache))) goto cleanup; } /* okay, now check cross-realm policy */ #if defined(_SINGLE_HOP_ONLY) /* Single hop cross-realm tickets only */ { krb5_transited *trans = &(req->ticket->enc_part2->transited); /* If the transited list is empty, then we have at most one hop */ if (trans->tr_contents.length > 0 && trans->tr_contents.data[0]) retval = KRB5KRB_AP_ERR_ILL_CR_TKT; } #elif defined(_NO_CROSS_REALM) /* No cross-realm tickets */ { char * lrealm; krb5_data * realm; krb5_transited * trans; realm = krb5_princ_realm(context, req->ticket->enc_part2->client); trans = &(req->ticket->enc_part2->transited); /* * If the transited list is empty, then we have at most one hop * So we also have to check that the client's realm is the local one */ krb5_get_default_realm(context, &lrealm); if ((trans->tr_contents.length > 0 && trans->tr_contents.data[0]) || !data_eq_string(*realm, lrealm)) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; } free(lrealm); } #else /* Hierarchical Cross-Realm */ { krb5_data * realm; krb5_transited * trans; realm = krb5_princ_realm(context, req->ticket->enc_part2->client); trans = &(req->ticket->enc_part2->transited); /* * If the transited list is not empty, then check that all realms * transited are within the hierarchy between the client's realm * and the local realm. */ if (trans->tr_contents.length > 0 && trans->tr_contents.data[0]) { retval = krb5_check_transited_list(context, &(trans->tr_contents), realm, krb5_princ_realm (context,server)); } } #endif if (retval) goto cleanup; /* only check rcache if sender has provided one---some services may not be able to use replay caches (such as datagram servers) */ if ((*auth_context)->rcache) { krb5_donot_replay rep; krb5_tkt_authent tktauthent; tktauthent.ticket = req->ticket; tktauthent.authenticator = (*auth_context)->authentp; if (!(retval = krb5_auth_to_rep(context, &tktauthent, &rep))) { retval = krb5_rc_hash_message(context, &req->authenticator.ciphertext, &rep.msghash); if (!retval) { retval = krb5_rc_store(context, (*auth_context)->rcache, &rep); free(rep.msghash); } free(rep.server); free(rep.client); } if (retval) goto cleanup; } retval = krb5int_validate_times(context, &req->ticket->enc_part2->times); if (retval != 0) goto cleanup; if ((retval = krb5_check_clockskew(context, (*auth_context)->authentp->ctime))) goto cleanup; if (check_valid_flag) { if (req->ticket->enc_part2->flags & TKT_FLG_INVALID) { retval = KRB5KRB_AP_ERR_TKT_INVALID; goto cleanup; } if ((retval = krb5_authdata_context_init(context, &(*auth_context)->ad_context))) goto cleanup; if ((retval = krb5int_authdata_verify(context, (*auth_context)->ad_context, AD_USAGE_MASK, auth_context, &decrypt_key, req))) goto cleanup; } /* read RFC 4537 etype list from sender */ retval = decode_etype_list(context, (*auth_context)->authentp, &desired_etypes, &rfc4537_etypes_len); if (retval != 0) goto cleanup; if (desired_etypes == NULL) desired_etypes = (krb5_enctype *)calloc(4, sizeof(krb5_enctype)); else desired_etypes = (krb5_enctype *)realloc(desired_etypes, (rfc4537_etypes_len + 4) * sizeof(krb5_enctype)); if (desired_etypes == NULL) { retval = ENOMEM; goto cleanup; } desired_etypes_len = rfc4537_etypes_len; /* * RFC 4537: * * If the EtypeList is present and the server prefers an enctype from * the client's enctype list over that of the AP-REQ authenticator * subkey (if that is present) or the service ticket session key, the * server MUST create a subkey using that enctype. This negotiated * subkey is sent in the subkey field of AP-REP message, and it is then * used as the protocol key or base key [RFC3961] for subsequent * communication. * * If the enctype of the ticket session key is included in the enctype * list sent by the client, it SHOULD be the last on the list; * otherwise, this enctype MUST NOT be negotiated if it was not included * in the list. * * The second paragraph does appear to contradict the first with respect * to whether it is legal to negotiate the ticket session key type if it * is absent in the EtypeList. A literal reading suggests that we can use * the AP-REQ subkey enctype. Also a client has no way of distinguishing * a server that does not RFC 4537 from one that has chosen the same * enctype as the ticket session key for the acceptor subkey, surely. */ if ((*auth_context)->authentp->subkey != NULL) { desired_etypes[desired_etypes_len++] = (*auth_context)->authentp->subkey->enctype; } desired_etypes[desired_etypes_len++] = req->ticket->enc_part2->session->enctype; desired_etypes[desired_etypes_len] = ENCTYPE_NULL; if (((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_PERMIT_ALL) == 0) { if ((*auth_context)->permitted_etypes != NULL) { permitted_etypes = (*auth_context)->permitted_etypes; } else { retval = krb5_get_permitted_enctypes(context, &permitted_etypes); if (retval != 0) goto cleanup; } permitted_etypes_len = krb5int_count_etypes(permitted_etypes); } else { permitted_etypes = NULL; permitted_etypes_len = 0; } /* check if the various etypes are permitted */ retval = negotiate_etype(context, desired_etypes, desired_etypes_len, rfc4537_etypes_len, permitted_etypes, permitted_etypes_len, &(*auth_context)->negotiated_etype); if (retval != 0) goto cleanup; TRACE_RD_REQ_NEGOTIATED_ETYPE(context, (*auth_context)->negotiated_etype); assert((*auth_context)->negotiated_etype != ENCTYPE_NULL); (*auth_context)->remote_seq_number = (*auth_context)->authentp->seq_number; if ((*auth_context)->authentp->subkey) { TRACE_RD_REQ_SUBKEY(context, (*auth_context)->authentp->subkey); if ((retval = krb5_k_create_key(context, (*auth_context)->authentp->subkey, &((*auth_context)->recv_subkey)))) goto cleanup; retval = krb5_k_create_key(context, (*auth_context)->authentp->subkey, &((*auth_context)->send_subkey)); if (retval) { krb5_k_free_key(context, (*auth_context)->recv_subkey); (*auth_context)->recv_subkey = NULL; goto cleanup; } } else { (*auth_context)->recv_subkey = 0; (*auth_context)->send_subkey = 0; } if ((retval = krb5_k_create_key(context, req->ticket->enc_part2->session, &((*auth_context)->key)))) goto cleanup; debug_log_authz_data("ticket", req->ticket->enc_part2->authorization_data); /* * If not AP_OPTS_MUTUAL_REQUIRED then and sequence numbers are used * then the default sequence number is the one's complement of the * sequence number sent ot us. */ if ((!(req->ap_options & AP_OPTS_MUTUAL_REQUIRED)) && (*auth_context)->remote_seq_number) { (*auth_context)->local_seq_number ^= (*auth_context)->remote_seq_number; } if (ticket) if ((retval = krb5_copy_ticket(context, req->ticket, ticket))) goto cleanup; if (ap_req_options) { *ap_req_options = req->ap_options & AP_OPTS_WIRE_MASK; if (rfc4537_etypes_len != 0) *ap_req_options |= AP_OPTS_ETYPE_NEGOTIATION; if ((*auth_context)->negotiated_etype != krb5_k_key_enctype(context, (*auth_context)->key)) *ap_req_options |= AP_OPTS_USE_SUBKEY; } retval = 0; cleanup: if (desired_etypes != NULL) free(desired_etypes); if (permitted_etypes != NULL && permitted_etypes != (*auth_context)->permitted_etypes) free(permitted_etypes); if (retval) { /* only free if we're erroring out...otherwise some applications will need the output. */ if (req->ticket->enc_part2) krb5_free_enc_tkt_part(context, req->ticket->enc_part2); req->ticket->enc_part2 = NULL; } if (check_valid_flag) krb5_free_keyblock_contents(context, &decrypt_key); return retval; }