/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* lib/krb5/krb/rd_req_dec.c */ /* * Copyright (c) 1994 CyberSAFE Corporation. * Copyright 1990,1991,2007,2008 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * Neither M.I.T., the Open Computing Security Group, nor * CyberSAFE Corporation make any representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * krb5_rd_req_decoded() */ #include "k5-int.h" #include "auth_con.h" #include "authdata.h" #include "int-proto.h" /* * essentially the same as krb_rd_req, but uses a decoded AP_REQ as * the input rather than an encoded input. */ /* * Parses a KRB_AP_REQ message, returning its contents. * * server specifies the expected server's name for the ticket; if NULL, then * any server will be accepted if the key can be found, and the caller should * verify that the principal is something it trusts. With the exception of the * kdb keytab, the ticket's server field need not match the name passed in for * server. All that is required is that the ticket be encrypted with a key * from the keytab associated with the specified server principal. This * permits the KDC to have a set of aliases for the server without keeping * this information consistent with the server. So, when server is non-null, * the principal expected by the application needs to be consistent with the * local keytab, but not with the informational name in the ticket. * * rcache specifies a replay detection cache used to store authenticators and * server names * * keyproc specifies a procedure to generate a decryption key for the * ticket. If keyproc is non-NULL, keyprocarg is passed to it, and the result * used as a decryption key. If keyproc is NULL, then fetchfrom is checked; * if it is non-NULL, it specifies a parameter name from which to retrieve the * decryption key. If fetchfrom is NULL, then the default key store is * consulted. * * authdat is set to point at allocated storage structures; the caller * should free them when finished. * * returns system errors, encryption errors, replay errors */ static krb5_error_code decrypt_authenticator(krb5_context, const krb5_ap_req *, krb5_authenticator **, int); static krb5_error_code decode_etype_list(krb5_context context, const krb5_authenticator *authp, krb5_enctype **desired_etypes, int *desired_etypes_len); static krb5_error_code negotiate_etype(krb5_context context, const krb5_enctype *desired_etypes, int desired_etypes_len, int mandatory_etypes_index, const krb5_enctype *permitted_etypes, int permitted_etypes_len, krb5_enctype *negotiated_etype); /* Unparse the specified server principal (which may be NULL) and the ticket * server principal. */ static krb5_error_code unparse_princs(krb5_context context, krb5_const_principal server, krb5_const_principal tkt_server, char **sname_out, char **tsname_out) { krb5_error_code ret; char *sname = NULL, *tsname; *sname_out = *tsname_out = NULL; if (server != NULL) { ret = krb5_unparse_name(context, server, &sname); if (ret) return ret; } ret = krb5_unparse_name(context, tkt_server, &tsname); if (ret) { krb5_free_unparsed_name(context, sname); return ret; } *sname_out = sname; *tsname_out = tsname; return 0; } /* Return a helpful code and error when we cannot look up the keytab entry for * an explicit server principal using the ticket's kvno and enctype. */ static krb5_error_code keytab_fetch_error(krb5_context context, krb5_error_code code, krb5_const_principal princ, krb5_const_principal tkt_server, krb5_kvno tkt_kvno, krb5_boolean explicit_server) { krb5_error_code ret; char *sname = NULL, *tsname = NULL; if (code == ENOENT || code == EPERM || code == EACCES) { k5_change_error_message_code(context, code, KRB5KRB_AP_ERR_NOKEY); return KRB5KRB_AP_ERR_NOKEY; } if (code == KRB5_KT_NOTFOUND) { ret = explicit_server ? KRB5KRB_AP_ERR_NOKEY : KRB5KRB_AP_ERR_NOT_US; k5_change_error_message_code(context, code, ret); return ret; } if (code != KRB5_KT_KVNONOTFOUND) return code; assert(princ != NULL); ret = unparse_princs(context, princ, tkt_server, &sname, &tsname); if (ret) return ret; if (krb5_principal_compare(context, princ, tkt_server)) { ret = KRB5KRB_AP_ERR_BADKEYVER; k5_setmsg(context, ret, _("Cannot find key for %s kvno %d in keytab"), sname, (int)tkt_kvno); } else { ret = KRB5KRB_AP_ERR_NOT_US; k5_setmsg(context, ret, _("Cannot find key for %s kvno %d in keytab (request ticket " "server %s)"), sname, (int)tkt_kvno, tsname); } krb5_free_unparsed_name(context, sname); krb5_free_unparsed_name(context, tsname); return ret; } /* Return a helpful code and error when ticket decryption fails using the key * for an explicit server principal. */ static krb5_error_code integrity_error(krb5_context context, krb5_const_principal server, krb5_const_principal tkt_server) { krb5_error_code ret; char *sname = NULL, *tsname = NULL; assert(server != NULL); ret = unparse_princs(context, server, tkt_server, &sname, &tsname); if (ret) return ret; ret = krb5_principal_compare(context, server, tkt_server) ? KRB5KRB_AP_ERR_BAD_INTEGRITY : KRB5KRB_AP_ERR_NOT_US; k5_setmsg(context, ret, _("Cannot decrypt ticket for %s using keytab key for %s"), tsname, sname); krb5_free_unparsed_name(context, sname); krb5_free_unparsed_name(context, tsname); return ret; } /* Return a helpful code and error when we cannot iterate over the keytab and * the specified server does not match the ticket server. */ static krb5_error_code nomatch_error(krb5_context context, krb5_const_principal server, krb5_const_principal tkt_server) { krb5_error_code ret; char *sname = NULL, *tsname = NULL; assert(server != NULL); ret = unparse_princs(context, server, tkt_server, &sname, &tsname); if (ret) return ret; k5_setmsg(context, KRB5KRB_AP_ERR_NOT_US, _("Server principal %s does not match request ticket server %s"), sname, tsname); krb5_free_unparsed_name(context, sname); krb5_free_unparsed_name(context, tsname); return KRB5KRB_AP_ERR_NOT_US; } /* Return a helpful error code and message when we fail to find a key after * iterating over the keytab. */ static krb5_error_code iteration_error(krb5_context context, krb5_const_principal server, krb5_const_principal tkt_server, krb5_kvno tkt_kvno, krb5_enctype tkt_etype, krb5_boolean tkt_server_mismatch, krb5_boolean found_server_match, krb5_boolean found_tkt_server, krb5_boolean found_kvno, krb5_boolean found_higher_kvno, krb5_boolean found_enctype) { krb5_error_code ret; char *sname = NULL, *tsname = NULL, encname[128]; ret = unparse_princs(context, server, tkt_server, &sname, &tsname); if (ret) return ret; if (krb5_enctype_to_name(tkt_etype, TRUE, encname, sizeof(encname)) != 0) (void)snprintf(encname, sizeof(encname), "%d", (int)tkt_etype); if (!found_server_match) { ret = KRB5KRB_AP_ERR_NOKEY; if (sname == NULL) { k5_setmsg(context, ret, _("No keys in keytab")); } else { k5_setmsg(context, ret, _("Server principal %s does not match any keys in " "keytab"), sname); } } else if (tkt_server_mismatch) { assert(sname != NULL); /* Null server princ would match anything. */ ret = KRB5KRB_AP_ERR_NOT_US; k5_setmsg(context, ret, _("Request ticket server %s found in keytab but does not " "match server principal %s"), tsname, sname); } else if (!found_tkt_server) { ret = KRB5KRB_AP_ERR_NOT_US; k5_setmsg(context, ret, _("Request ticket server %s not found in keytab (ticket " "kvno %d)"), tsname, (int)tkt_kvno); } else if (!found_kvno) { ret = KRB5KRB_AP_ERR_BADKEYVER; if (found_higher_kvno) { k5_setmsg(context, ret, _("Request ticket server %s kvno %d not found in " "keytab; ticket is likely out of date"), tsname, (int)tkt_kvno); } else { k5_setmsg(context, ret, _("Request ticket server %s kvno %d not found in " "keytab; keytab is likely out of date"), tsname, (int)tkt_kvno); } } else if (!found_enctype) { /* There's no defined error for having the key version but not the * enctype. */ ret = KRB5KRB_AP_ERR_BADKEYVER; k5_setmsg(context, ret, _("Request ticket server %s kvno %d found in keytab but not " "with enctype %s"), tsname, (int)tkt_kvno, encname); } else { ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; k5_setmsg(context, ret, _("Request ticket server %s kvno %d enctype %s found in " "keytab but cannot decrypt ticket"), tsname, (int)tkt_kvno, encname); } krb5_free_unparsed_name(context, sname); krb5_free_unparsed_name(context, tsname); return ret; } /* Return true if princ might match multiple principals. */ static inline krb5_boolean is_matching(krb5_context context, krb5_const_principal princ) { if (princ == NULL) return TRUE; return (princ->type == KRB5_NT_SRV_HST && princ->length == 2 && (princ->realm.length == 0 || princ->data[1].length == 0 || context->ignore_acceptor_hostname)); } /* Decrypt the ticket in req using the key in ent. */ static krb5_error_code try_one_entry(krb5_context context, const krb5_ap_req *req, krb5_keytab_entry *ent, krb5_keyblock *keyblock_out) { krb5_error_code ret; krb5_principal tmp = NULL; /* Try decrypting the ticket with this entry's key. */ ret = krb5_decrypt_tkt_part(context, &ent->key, req->ticket); if (ret) return ret; /* Make a copy of the principal for the ticket server field. */ ret = krb5_copy_principal(context, ent->principal, &tmp); if (ret) return ret; /* Make a copy of the decrypting key if requested by the caller. */ if (keyblock_out != NULL) { ret = krb5_copy_keyblock_contents(context, &ent->key, keyblock_out); if (ret) { krb5_free_principal(context, tmp); return ret; } } /* Make req->ticket->server indicate the actual server principal. */ krb5_free_principal(context, req->ticket->server); req->ticket->server = tmp; return 0; } /* Decrypt the ticket in req using a principal looked up from keytab. * explicit_server should be true if this is the only usable principal. */ static krb5_error_code try_one_princ(krb5_context context, const krb5_ap_req *req, krb5_const_principal princ, krb5_keytab keytab, krb5_boolean explicit_server, krb5_keyblock *keyblock_out) { krb5_error_code ret; krb5_keytab_entry ent; krb5_kvno tkt_kvno = req->ticket->enc_part.kvno; krb5_enctype tkt_etype = req->ticket->enc_part.enctype; krb5_principal tkt_server = req->ticket->server; ret = krb5_kt_get_entry(context, keytab, princ, tkt_kvno, tkt_etype, &ent); if (ret) { return keytab_fetch_error(context, ret, princ, tkt_server, tkt_kvno, explicit_server); } ret = try_one_entry(context, req, &ent, keyblock_out); if (ret == 0) TRACE_RD_REQ_DECRYPT_SPECIFIC(context, ent.principal, &ent.key); (void)krb5_free_keytab_entry_contents(context, &ent); if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) return integrity_error(context, princ, req->ticket->server); return ret; } /* * Decrypt the ticket in req using an entry in keytab matching server (if * given). Set req->ticket->server to the principal of the keytab entry used. * Store the decrypting key in *keyblock_out if it is not NULL. */ static krb5_error_code decrypt_ticket(krb5_context context, const krb5_ap_req *req, krb5_const_principal server, krb5_keytab keytab, krb5_keyblock *keyblock_out) { krb5_error_code ret; krb5_keytab_entry ent; krb5_kt_cursor cursor; krb5_principal tkt_server = req->ticket->server; krb5_kvno tkt_kvno = req->ticket->enc_part.kvno; krb5_enctype tkt_etype = req->ticket->enc_part.enctype; krb5_boolean similar_enctype; krb5_boolean tkt_server_mismatch = FALSE, found_server_match = FALSE; krb5_boolean found_tkt_server = FALSE, found_enctype = FALSE; krb5_boolean found_kvno = FALSE, found_higher_kvno = FALSE; #ifdef LEAN_CLIENT return KRB5KRB_AP_WRONG_PRINC; #else /* If we have an explicit server principal, try just that one. */ if (!is_matching(context, server)) { return try_one_princ(context, req, server, keytab, TRUE, keyblock_out); } if (keytab->ops->start_seq_get == NULL) { /* We can't iterate over the keytab. Try the principal asserted by the * client if it's allowed by the server parameter. */ if (!krb5_sname_match(context, server, tkt_server)) return nomatch_error(context, server, tkt_server); return try_one_princ(context, req, tkt_server, keytab, FALSE, keyblock_out); } /* Scan all keys in the keytab, in case the ticket server is an alias for * one of the principals in the keytab. */ ret = krb5_kt_start_seq_get(context, keytab, &cursor); if (ret) { k5_change_error_message_code(context, ret, KRB5KRB_AP_ERR_NOKEY); return KRB5KRB_AP_ERR_NOKEY; } while ((ret = krb5_kt_next_entry(context, keytab, &ent, &cursor)) == 0) { /* Only try keys which match the server principal. */ if (!krb5_sname_match(context, server, ent.principal)) { if (krb5_principal_compare(context, ent.principal, tkt_server)) tkt_server_mismatch = TRUE; continue; } found_server_match = TRUE; if (krb5_c_enctype_compare(context, ent.key.enctype, tkt_etype, &similar_enctype) != 0) similar_enctype = FALSE; if (krb5_principal_compare(context, ent.principal, tkt_server)) { found_tkt_server = TRUE; if (ent.vno == tkt_kvno) { found_kvno = TRUE; if (similar_enctype) found_enctype = TRUE; } else if (ent.vno > tkt_kvno) { found_higher_kvno = TRUE; } } /* Only try keys with similar enctypes to the ticket enctype. */ if (similar_enctype) { /* Coerce inexact matches to the request enctype. */ ent.key.enctype = tkt_etype; if (try_one_entry(context, req, &ent, keyblock_out) == 0) { TRACE_RD_REQ_DECRYPT_ANY(context, ent.principal, &ent.key); (void)krb5_free_keytab_entry_contents(context, &ent); break; } } (void)krb5_free_keytab_entry_contents(context, &ent); } (void)krb5_kt_end_seq_get(context, keytab, &cursor); if (ret != KRB5_KT_END) return ret; return iteration_error(context, server, tkt_server, tkt_kvno, tkt_etype, tkt_server_mismatch, found_server_match, found_tkt_server, found_kvno, found_higher_kvno, found_enctype); #endif /* LEAN_CLIENT */ } #if 0 #include static void debug_log_authz_data(const char *which, krb5_authdata **a) { if (a) { syslog(LOG_ERR|LOG_DAEMON, "%s authz data:", which); while (*a) { syslog(LOG_ERR|LOG_DAEMON, " ad_type:%d length:%d '%.*s'", (*a)->ad_type, (*a)->length, (*a)->length, (char *) (*a)->contents); a++; } syslog(LOG_ERR|LOG_DAEMON, " [end]"); } else syslog(LOG_ERR|LOG_DAEMON, "no %s authz data", which); } #else static void debug_log_authz_data(const char *which, krb5_authdata **a) { } #endif 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; if (server == NULL) server = req->ticket->server; } else { retval = decrypt_ticket(context, req, server, keytab, check_valid_flag ? &decrypt_key : NULL); if (retval) { TRACE_RD_REQ_DECRYPT_FAIL(context, retval); goto cleanup; } /* decrypt_ticket placed the principal of the keytab key in * req->ticket->server; always use this for later steps. */ server = req->ticket->server; } 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; } /* Get an rcache if necessary. */ if (((*auth_context)->rcache == NULL) && ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_DO_TIME) && server != NULL && server->length > 0) { retval = krb5_get_server_rcache(context, &server->data[0], &(*auth_context)->rcache); if (retval) 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 = &req->ticket->enc_part2->client->realm; 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 = &req->ticket->enc_part2->client->realm; 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, &server->realm); } } #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 = k5_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 (check_valid_flag) krb5_free_keyblock_contents(context, &decrypt_key); return retval; } krb5_error_code krb5_rd_req_decoded(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) { krb5_error_code retval; retval = rd_req_decoded_opt(context, auth_context, req, server, keytab, ap_req_options, ticket, 1); /* check_valid_flag */ return retval; } krb5_error_code krb5_rd_req_decoded_anyflag(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) { krb5_error_code retval; retval = rd_req_decoded_opt(context, auth_context, req, server, keytab, ap_req_options, ticket, 0); /* don't check_valid_flag */ return retval; } #ifndef LEAN_CLIENT static krb5_error_code decrypt_authenticator(krb5_context context, const krb5_ap_req *request, krb5_authenticator **authpp, int is_ap_req) { krb5_authenticator *local_auth; krb5_error_code retval; krb5_data scratch; krb5_keyblock *sesskey; sesskey = request->ticket->enc_part2->session; scratch.length = request->authenticator.ciphertext.length; if (!(scratch.data = malloc(scratch.length))) return(ENOMEM); if ((retval = krb5_c_decrypt(context, sesskey, is_ap_req?KRB5_KEYUSAGE_AP_REQ_AUTH: KRB5_KEYUSAGE_TGS_REQ_AUTH, 0, &request->authenticator, &scratch))) { free(scratch.data); return(retval); } #define clean_scratch() {memset(scratch.data, 0, scratch.length); \ free(scratch.data);} /* now decode the decrypted stuff */ if (!(retval = decode_krb5_authenticator(&scratch, &local_auth))) { *authpp = local_auth; debug_log_authz_data("authenticator", local_auth->authorization_data); } clean_scratch(); return retval; } #endif static krb5_error_code negotiate_etype(krb5_context context, const krb5_enctype *desired_etypes, int desired_etypes_len, int mandatory_etypes_index, const krb5_enctype *permitted_etypes, int permitted_etypes_len, krb5_enctype *negotiated_etype) { int i, j; *negotiated_etype = ENCTYPE_NULL; /* mandatory segment of desired_etypes must be permitted */ for (i = mandatory_etypes_index; i < desired_etypes_len; i++) { krb5_boolean permitted = FALSE; for (j = 0; j < permitted_etypes_len; j++) { if (desired_etypes[i] == permitted_etypes[j]) { permitted = TRUE; break; } } if (permitted == FALSE) { char enctype_name[30]; if (krb5_enctype_to_string(desired_etypes[i], enctype_name, sizeof(enctype_name)) == 0) k5_setmsg(context, KRB5_NOPERM_ETYPE, _("Encryption type %s not permitted"), enctype_name); return KRB5_NOPERM_ETYPE; } } /* * permitted_etypes is ordered from most to least preferred; * find first desired_etype that matches. */ for (j = 0; j < permitted_etypes_len; j++) { for (i = 0; i < desired_etypes_len; i++) { if (desired_etypes[i] == permitted_etypes[j]) { *negotiated_etype = permitted_etypes[j]; return 0; } } } /*NOTREACHED*/ return KRB5_NOPERM_ETYPE; } static krb5_error_code decode_etype_list(krb5_context context, const krb5_authenticator *authp, krb5_enctype **desired_etypes, int *desired_etypes_len) { krb5_error_code code; krb5_authdata **ad_if_relevant = NULL; krb5_authdata *etype_adata = NULL; krb5_etype_list *etype_list = NULL; int i, j; krb5_data data; *desired_etypes = NULL; if (authp->authorization_data == NULL) return 0; /* * RFC 4537 says that ETYPE_NEGOTIATION auth data should be wrapped * in AD_IF_RELEVANT, but we handle the case where it is mandatory. */ for (i = 0; authp->authorization_data[i] != NULL; i++) { switch (authp->authorization_data[i]->ad_type) { case KRB5_AUTHDATA_IF_RELEVANT: code = krb5_decode_authdata_container(context, KRB5_AUTHDATA_IF_RELEVANT, authp->authorization_data[i], &ad_if_relevant); if (code != 0) continue; for (j = 0; ad_if_relevant[j] != NULL; j++) { if (ad_if_relevant[j]->ad_type == KRB5_AUTHDATA_ETYPE_NEGOTIATION) { etype_adata = ad_if_relevant[j]; break; } } if (etype_adata == NULL) { krb5_free_authdata(context, ad_if_relevant); ad_if_relevant = NULL; } break; case KRB5_AUTHDATA_ETYPE_NEGOTIATION: etype_adata = authp->authorization_data[i]; break; default: break; } if (etype_adata != NULL) break; } if (etype_adata == NULL) return 0; data.data = (char *)etype_adata->contents; data.length = etype_adata->length; code = decode_krb5_etype_list(&data, &etype_list); if (code == 0) { *desired_etypes = etype_list->etypes; *desired_etypes_len = etype_list->length; free(etype_list); } if (ad_if_relevant != NULL) krb5_free_authdata(context, ad_if_relevant); return code; }