krb5_error_code KRB5_CALLCONV krb5_rd_priv(krb5_context context, krb5_auth_context auth_context, const krb5_data *inbuf, krb5_data *outbuf, krb5_replay_data *outdata) { krb5_error_code retval; krb5_keyblock * keyblock; krb5_replay_data replaydata; /* Get keyblock */ if ((keyblock = auth_context->recv_subkey) == NULL) keyblock = auth_context->keyblock; if (((auth_context->auth_context_flags & KRB5_AUTH_CONTEXT_RET_TIME) || (auth_context->auth_context_flags & KRB5_AUTH_CONTEXT_RET_SEQUENCE)) && (outdata == NULL)) /* Need a better error */ return KRB5_RC_REQUIRED; if ((auth_context->auth_context_flags & KRB5_AUTH_CONTEXT_DO_TIME) && (auth_context->rcache == NULL)) return KRB5_RC_REQUIRED; { krb5_address * premote_fulladdr = NULL; krb5_address * plocal_fulladdr = NULL; krb5_address remote_fulladdr; krb5_address local_fulladdr; CLEANUP_INIT(2); if (auth_context->local_addr) { if (auth_context->local_port) { if (!(retval = krb5_make_fulladdr(context, auth_context->local_addr, auth_context->local_port, &local_fulladdr))){ CLEANUP_PUSH(local_fulladdr.contents, free); plocal_fulladdr = &local_fulladdr; } else { return retval; } } else { plocal_fulladdr = auth_context->local_addr; } } if (auth_context->remote_addr) { if (auth_context->remote_port) { if (!(retval = krb5_make_fulladdr(context,auth_context->remote_addr, auth_context->remote_port, &remote_fulladdr))){ CLEANUP_PUSH(remote_fulladdr.contents, free); premote_fulladdr = &remote_fulladdr; } else { CLEANUP_DONE(); return retval; } } else { premote_fulladdr = auth_context->remote_addr; } } if ((retval = krb5_rd_priv_basic(context, inbuf, keyblock, plocal_fulladdr, premote_fulladdr, auth_context->i_vector, &replaydata, outbuf))) { CLEANUP_DONE(); return retval; } CLEANUP_DONE(); } if (auth_context->auth_context_flags & KRB5_AUTH_CONTEXT_DO_TIME) { krb5_donot_replay replay; krb5_timestamp currenttime; if ((retval = krb5_timeofday(context, ¤ttime))) goto error; if (!in_clock_skew(replaydata.timestamp)) { retval = KRB5KRB_AP_ERR_SKEW; goto error; } if ((retval = krb5_gen_replay_name(context, auth_context->remote_addr, "_priv", &replay.client))) goto error; replay.server = ""; /* XXX */ replay.cusec = replaydata.usec; replay.ctime = replaydata.timestamp; if ((retval = krb5_rc_store(context, auth_context->rcache, &replay))) { krb5_xfree(replay.client); goto error; } krb5_xfree(replay.client); } if (auth_context->auth_context_flags & KRB5_AUTH_CONTEXT_DO_SEQUENCE) { if (!krb5int_auth_con_chkseqnum(context, auth_context, replaydata.seq)) { retval = KRB5KRB_AP_ERR_BADORDER; goto error; } auth_context->remote_seq_number++; } if ((auth_context->auth_context_flags & KRB5_AUTH_CONTEXT_RET_TIME) || (auth_context->auth_context_flags & KRB5_AUTH_CONTEXT_RET_SEQUENCE)) { outdata->timestamp = replaydata.timestamp; outdata->usec = replaydata.usec; outdata->seq = replaydata.seq; } /* everything is ok - return data to the user */ return 0; error:; krb5_xfree(outbuf->data); return retval; }
static krb5_error_code krb5_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_timestamp currenttime; krb5_principal_data princ_data; req->ticket->enc_part2 == NULL; if (server && krb5_is_referral_realm(&server->realm)) { char *realm; princ_data = *server; server = &princ_data; retval = krb5_get_default_realm(context, &realm); if (retval) return retval; princ_data.realm.data = realm; princ_data.realm.length = strlen(realm); } if (server && !krb5_principal_compare(context, server, req->ticket->server)) { char *found_name = 0, *wanted_name = 0; if (krb5_unparse_name(context, server, &wanted_name) == 0 && krb5_unparse_name(context, req->ticket->server, &found_name) == 0) krb5_set_error_message(context, KRB5KRB_AP_WRONG_PRINC, "Wrong principal in request (found %s, wanted %s)", found_name, wanted_name); krb5_free_unparsed_name(context, wanted_name); krb5_free_unparsed_name(context, found_name); retval = KRB5KRB_AP_WRONG_PRINC; goto cleanup; } /* if (req->ap_options & AP_OPTS_USE_SESSION_KEY) do we need special processing here ? */ /* decrypt the ticket */ if ((*auth_context)->keyblock) { /* User to User authentication */ if ((retval = krb5_decrypt_tkt_part(context, (*auth_context)->keyblock, req->ticket))) goto cleanup; krb5_free_keyblock(context, (*auth_context)->keyblock); (*auth_context)->keyblock = NULL; } else { if ((retval = krb5_rd_req_decrypt_tkt_part(context, req, keytab))) goto cleanup; } /* 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 */ if ((retval = decrypt_authenticator(context, req, &((*auth_context)->authentp), check_valid_flag))) goto cleanup; 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; } /* 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.data && 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.data && trans->tr_contents.data[0]) || strlen(lrealm) != realm->length || memcmp(lrealm, realm->data, strlen(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.data && 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_store(context, (*auth_context)->rcache, &rep); krb5_xfree(rep.server); krb5_xfree(rep.client); } if (retval) goto cleanup; } retval = krb5_validate_times(context, &req->ticket->enc_part2->times); if (retval != 0) goto cleanup; if ((retval = krb5_timeofday(context, ¤ttime))) goto cleanup; if (!in_clock_skew((*auth_context)->authentp->ctime)) { retval = KRB5KRB_AP_ERR_SKEW; goto cleanup; } if (check_valid_flag) { if (req->ticket->enc_part2->flags & TKT_FLG_INVALID) { retval = KRB5KRB_AP_ERR_TKT_INVALID; goto cleanup; } } /* check if the various etypes are permitted */ if ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_PERMIT_ALL) { /* no etype check needed */; } else if ((*auth_context)->permitted_etypes == NULL) { int etype; /* check against the default set */ if ((!krb5_is_permitted_enctype(context, etype = req->ticket->enc_part.enctype)) || (!krb5_is_permitted_enctype(context, etype = req->ticket->enc_part2->session->enctype)) || (((*auth_context)->authentp->subkey) && !krb5_is_permitted_enctype(context, etype = (*auth_context)->authentp->subkey->enctype))) { char enctype_name[30]; retval = KRB5_NOPERM_ETYPE; if (krb5_enctype_to_string(etype, enctype_name, sizeof(enctype_name)) == 0) krb5_set_error_message(context, retval, "Encryption type %s not permitted", enctype_name); goto cleanup; } } else { /* check against the set in the auth_context */ int i; for (i=0; (*auth_context)->permitted_etypes[i]; i++) if ((*auth_context)->permitted_etypes[i] == req->ticket->enc_part.enctype) break; if (!(*auth_context)->permitted_etypes[i]) { char enctype_name[30]; retval = KRB5_NOPERM_ETYPE; if (krb5_enctype_to_string(req->ticket->enc_part.enctype, enctype_name, sizeof(enctype_name)) == 0) krb5_set_error_message(context, retval, "Encryption type %s not permitted", enctype_name); goto cleanup; } for (i=0; (*auth_context)->permitted_etypes[i]; i++) if ((*auth_context)->permitted_etypes[i] == req->ticket->enc_part2->session->enctype) break; if (!(*auth_context)->permitted_etypes[i]) { char enctype_name[30]; retval = KRB5_NOPERM_ETYPE; if (krb5_enctype_to_string(req->ticket->enc_part2->session->enctype, enctype_name, sizeof(enctype_name)) == 0) krb5_set_error_message(context, retval, "Encryption type %s not permitted", enctype_name); goto cleanup; } if ((*auth_context)->authentp->subkey) { for (i=0; (*auth_context)->permitted_etypes[i]; i++) if ((*auth_context)->permitted_etypes[i] == (*auth_context)->authentp->subkey->enctype) break; if (!(*auth_context)->permitted_etypes[i]) { char enctype_name[30]; retval = KRB5_NOPERM_ETYPE; if (krb5_enctype_to_string((*auth_context)->authentp->subkey->enctype, enctype_name, sizeof(enctype_name)) == 0) krb5_set_error_message(context, retval, "Encryption type %s not permitted", enctype_name); goto cleanup; } } } (*auth_context)->remote_seq_number = (*auth_context)->authentp->seq_number; if ((*auth_context)->authentp->subkey) { if ((retval = krb5_copy_keyblock(context, (*auth_context)->authentp->subkey, &((*auth_context)->recv_subkey)))) goto cleanup; retval = krb5_copy_keyblock(context, (*auth_context)->authentp->subkey, &((*auth_context)->send_subkey)); if (retval) { krb5_free_keyblock(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_copy_keyblock(context, req->ticket->enc_part2->session, &((*auth_context)->keyblock)))) goto cleanup; /* * 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; retval = 0; cleanup: if (server == &princ_data) krb5_free_default_realm(context, princ_data.realm.data); 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; } return retval; }
krb5_error_code krb5int_process_tgs_reply(krb5_context context, struct krb5int_fast_request_state *fast_state, krb5_data *response_data, krb5_creds *tkt, krb5_flags kdcoptions, krb5_address *const *address, krb5_pa_data **in_padata, krb5_creds *in_cred, krb5_timestamp timestamp, krb5_int32 nonce, krb5_keyblock *subkey, krb5_pa_data ***out_padata, krb5_pa_data ***out_enc_padata, krb5_creds **out_cred) { krb5_error_code retval; krb5_kdc_rep *dec_rep = NULL; krb5_error *err_reply = NULL; krb5_boolean s4u2self; s4u2self = krb5int_find_pa_data(context, in_padata, KRB5_PADATA_S4U_X509_USER) || krb5int_find_pa_data(context, in_padata, KRB5_PADATA_FOR_USER); if (krb5_is_krb_error(response_data)) { retval = decode_krb5_error(response_data, &err_reply); if (retval != 0) goto cleanup; retval = krb5int_fast_process_error(context, fast_state, &err_reply, NULL, NULL); if (retval) goto cleanup; retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5; if (err_reply->text.length > 0) { switch (err_reply->error) { case KRB_ERR_GENERIC: k5_setmsg(context, retval, _("KDC returned error string: %.*s"), err_reply->text.length, err_reply->text.data); break; case KDC_ERR_S_PRINCIPAL_UNKNOWN: { char *s_name; if (err_reply->server && krb5_unparse_name(context, err_reply->server, &s_name) == 0) { k5_setmsg(context, retval, _("Server %s not found in Kerberos database"), s_name); krb5_free_unparsed_name(context, s_name); } else /* In case there's a stale S_PRINCIPAL_UNKNOWN report already noted. */ krb5_clear_error_message(context); } break; } } krb5_free_error(context, err_reply); goto cleanup; } else if (!krb5_is_tgs_rep(response_data)) { retval = KRB5KRB_AP_ERR_MSG_TYPE; goto cleanup; } /* Unfortunately, Heimdal at least up through 1.2 encrypts using the session key not the subsession key. So we try both. */ retval = krb5int_decode_tgs_rep(context, fast_state, response_data, subkey, KRB5_KEYUSAGE_TGS_REP_ENCPART_SUBKEY, &dec_rep); if (retval) { TRACE_TGS_REPLY_DECODE_SESSION(context, &tkt->keyblock); if ((krb5int_decode_tgs_rep(context, fast_state, response_data, &tkt->keyblock, KRB5_KEYUSAGE_TGS_REP_ENCPART_SESSKEY, &dec_rep)) == 0) retval = 0; else goto cleanup; } if (dec_rep->msg_type != KRB5_TGS_REP) { retval = KRB5KRB_AP_ERR_MSG_TYPE; goto cleanup; } /* * Don't trust the ok-as-delegate flag from foreign KDCs unless the * cross-realm TGT also had the ok-as-delegate flag set. */ if (!tgt_is_local_realm(tkt) && !(tkt->ticket_flags & TKT_FLG_OK_AS_DELEGATE)) dec_rep->enc_part2->flags &= ~TKT_FLG_OK_AS_DELEGATE; /* make sure the response hasn't been tampered with..... */ retval = 0; if (s4u2self && !IS_TGS_PRINC(dec_rep->ticket->server)) { /* Final hop, check whether KDC supports S4U2Self */ if (krb5_principal_compare(context, dec_rep->client, in_cred->server)) retval = KRB5KDC_ERR_PADATA_TYPE_NOSUPP; } else if ((kdcoptions & KDC_OPT_CNAME_IN_ADDL_TKT) == 0) { /* XXX for constrained delegation this check must be performed by caller * as we don't have access to the key to decrypt the evidence ticket. */ if (!krb5_principal_compare(context, dec_rep->client, tkt->client)) retval = KRB5_KDCREP_MODIFIED; } if (retval == 0) retval = check_reply_server(context, kdcoptions, in_cred, dec_rep); if (dec_rep->enc_part2->nonce != nonce) retval = KRB5_KDCREP_MODIFIED; if ((kdcoptions & KDC_OPT_POSTDATED) && (in_cred->times.starttime != 0) && (in_cred->times.starttime != dec_rep->enc_part2->times.starttime)) retval = KRB5_KDCREP_MODIFIED; if ((in_cred->times.endtime != 0) && (dec_rep->enc_part2->times.endtime > in_cred->times.endtime)) retval = KRB5_KDCREP_MODIFIED; if ((kdcoptions & KDC_OPT_RENEWABLE) && (in_cred->times.renew_till != 0) && (dec_rep->enc_part2->times.renew_till > in_cred->times.renew_till)) retval = KRB5_KDCREP_MODIFIED; if ((kdcoptions & KDC_OPT_RENEWABLE_OK) && (dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) && (in_cred->times.endtime != 0) && (dec_rep->enc_part2->times.renew_till > in_cred->times.endtime)) retval = KRB5_KDCREP_MODIFIED; if (retval != 0) goto cleanup; if (!in_cred->times.starttime && !in_clock_skew(dec_rep->enc_part2->times.starttime, timestamp)) { retval = KRB5_KDCREP_SKEW; goto cleanup; } if (out_padata != NULL) { *out_padata = dec_rep->padata; dec_rep->padata = NULL; } if (out_enc_padata != NULL) { *out_enc_padata = dec_rep->enc_part2->enc_padata; dec_rep->enc_part2->enc_padata = NULL; } retval = kdcrep2creds(context, dec_rep, address, &in_cred->second_ticket, out_cred); if (retval != 0) goto cleanup; cleanup: if (dec_rep != NULL) { memset(dec_rep->enc_part2->session->contents, 0, dec_rep->enc_part2->session->length); krb5_free_kdc_rep(context, dec_rep); } return retval; }