Path: blob/main/crypto/krb5/src/kdc/kdc_authdata.c
34878 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* kdc/kdc_authdata.c - Authorization data routines for the KDC */2/*3* Copyright (C) 2007 Apple Inc. All Rights Reserved.4* Copyright (C) 2008, 2009 by the Massachusetts Institute of Technology.5*6* Export of this software from the United States of America may7* require a specific license from the United States Government.8* It is the responsibility of any person or organization contemplating9* export to obtain such a license before exporting.10*11* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and12* distribute this software and its documentation for any purpose and13* without fee is hereby granted, provided that the above copyright14* notice appear in all copies and that both that copyright notice and15* this permission notice appear in supporting documentation, and that16* the name of M.I.T. not be used in advertising or publicity pertaining17* to distribution of the software without specific, written prior18* permission. Furthermore if you modify this software you must label19* your software as modified software and not distribute it in such a20* fashion that it might be confused with the original M.I.T. software.21* M.I.T. makes no representations about the suitability of22* this software for any purpose. It is provided "as is" without express23* or implied warranty.24*/2526#include "k5-int.h"27#include "kdc_util.h"28#include "extern.h"29#include <stdio.h>30#include "adm_proto.h"3132#include <syslog.h>3334#include <assert.h>35#include <krb5/kdcauthdata_plugin.h>3637typedef struct kdcauthdata_handle_st {38struct krb5_kdcauthdata_vtable_st vt;39krb5_kdcauthdata_moddata data;40} kdcauthdata_handle;4142static kdcauthdata_handle *authdata_modules;43static size_t n_authdata_modules;4445/* Load authdata plugin modules. */46krb5_error_code47load_authdata_plugins(krb5_context context)48{49krb5_error_code ret;50krb5_plugin_initvt_fn *modules = NULL, *mod;51kdcauthdata_handle *list, *h;52size_t count;5354ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_KDCAUTHDATA, &modules);55if (ret)56return ret;5758/* Allocate a large enough list of handles. */59for (count = 0; modules[count] != NULL; count++);60list = calloc(count + 1, sizeof(*list));61if (list == NULL) {62k5_plugin_free_modules(context, modules);63return ENOMEM;64}6566/* Initialize each module's vtable and module data. */67count = 0;68for (mod = modules; *mod != NULL; mod++) {69h = &list[count];70memset(h, 0, sizeof(*h));71ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&h->vt);72if (ret) /* Version mismatch, keep going. */73continue;74if (h->vt.init != NULL) {75ret = h->vt.init(context, &h->data);76if (ret) {77kdc_err(context, ret, _("while loading authdata module %s"),78h->vt.name);79continue;80}81}82count++;83}8485authdata_modules = list;86n_authdata_modules = count;87k5_plugin_free_modules(context, modules);88return 0;89}9091krb5_error_code92unload_authdata_plugins(krb5_context context)93{94kdcauthdata_handle *h;95size_t i;9697for (i = 0; i < n_authdata_modules; i++) {98h = &authdata_modules[i];99if (h->vt.fini != NULL)100h->vt.fini(context, h->data);101}102free(authdata_modules);103authdata_modules = NULL;104return 0;105}106107/* Return true if authdata should be filtered when copying from untrusted108* authdata. If desired_type is non-zero, look only for that type. */109static krb5_boolean110is_kdc_issued_authdatum(krb5_authdata *authdata,111krb5_authdatatype desired_type)112{113krb5_boolean result = FALSE;114krb5_authdatatype ad_type;115unsigned int i, count = 0;116krb5_authdatatype *ad_types, *containee_types = NULL;117118if (authdata->ad_type == KRB5_AUTHDATA_IF_RELEVANT) {119if (krb5int_get_authdata_containee_types(NULL, authdata, &count,120&containee_types) != 0)121goto cleanup;122ad_types = containee_types;123} else {124ad_type = authdata->ad_type;125count = 1;126ad_types = &ad_type;127}128129for (i = 0; i < count; i++) {130switch (ad_types[i]) {131case KRB5_AUTHDATA_SIGNTICKET:132case KRB5_AUTHDATA_KDC_ISSUED:133case KRB5_AUTHDATA_WIN2K_PAC:134case KRB5_AUTHDATA_CAMMAC:135case KRB5_AUTHDATA_AUTH_INDICATOR:136result = desired_type ? (desired_type == ad_types[i]) : TRUE;137break;138default:139result = FALSE;140break;141}142if (result)143break;144}145146cleanup:147free(containee_types);148return result;149}150151/* Return true if authdata contains any mandatory-for-KDC elements. */152static krb5_boolean153has_mandatory_for_kdc_authdata(krb5_context context, krb5_authdata **authdata)154{155int i;156157if (authdata == NULL)158return FALSE;159for (i = 0; authdata[i] != NULL; i++) {160if (authdata[i]->ad_type == KRB5_AUTHDATA_MANDATORY_FOR_KDC)161return TRUE;162}163return FALSE;164}165166/* Add elements from *new_elements to *existing_list, reallocating as167* necessary. On success, release *new_elements and set it to NULL. */168static krb5_error_code169merge_authdata(krb5_authdata ***existing_list, krb5_authdata ***new_elements)170{171size_t count = 0, ncount = 0;172krb5_authdata **list = *existing_list, **nlist = *new_elements;173174if (nlist == NULL)175return 0;176177for (count = 0; list != NULL && list[count] != NULL; count++);178for (ncount = 0; nlist[ncount] != NULL; ncount++);179180list = realloc(list, (count + ncount + 1) * sizeof(*list));181if (list == NULL)182return ENOMEM;183184memcpy(list + count, nlist, ncount * sizeof(*nlist));185list[count + ncount] = NULL;186free(nlist);187188if (list[0] == NULL) {189free(list);190list = NULL;191}192193*new_elements = NULL;194*existing_list = list;195return 0;196}197198/* Add a copy of new_elements to *existing_list, omitting KDC-issued199* authdata. */200static krb5_error_code201add_filtered_authdata(krb5_authdata ***existing_list,202krb5_authdata **new_elements)203{204krb5_error_code ret;205krb5_authdata **copy;206size_t i, j;207208if (new_elements == NULL)209return 0;210211ret = krb5_copy_authdata(NULL, new_elements, ©);212if (ret)213return ret;214215/* Remove KDC-issued elements from copy. */216j = 0;217for (i = 0; copy[i] != NULL; i++) {218if (is_kdc_issued_authdatum(copy[i], 0)) {219free(copy[i]->contents);220free(copy[i]);221} else {222copy[j++] = copy[i];223}224}225copy[j] = NULL;226227/* Destructively merge the filtered copy into existing_list. */228ret = merge_authdata(existing_list, ©);229krb5_free_authdata(NULL, copy);230return ret;231}232233/* Copy TGS-REQ authorization data into the ticket authdata. */234static krb5_error_code235copy_request_authdata(krb5_context context, krb5_keyblock *client_key,236krb5_kdc_req *req, krb5_enc_tkt_part *enc_tkt_req,237krb5_authdata ***tkt_authdata)238{239krb5_error_code ret;240krb5_data plaintext;241242assert(enc_tkt_req != NULL);243244ret = alloc_data(&plaintext, req->authorization_data.ciphertext.length);245if (ret)246return ret;247248/*249* RFC 4120 requires authdata in the TGS body to be encrypted in the subkey250* with usage 5 if a subkey is present, and in the TGS session key with key251* usage 4 if it is not. Prior to krb5 1.7, we got this wrong, always252* decrypting the authorization data with the TGS session key and usage 4.253* For the sake of conservatism, try the decryption the old way (wrong if254* client_key is a subkey) first, and then try again the right way (in the255* case where client_key is a subkey) if the first way fails.256*/257ret = krb5_c_decrypt(context, enc_tkt_req->session,258KRB5_KEYUSAGE_TGS_REQ_AD_SESSKEY, 0,259&req->authorization_data, &plaintext);260if (ret) {261ret = krb5_c_decrypt(context, client_key,262KRB5_KEYUSAGE_TGS_REQ_AD_SUBKEY, 0,263&req->authorization_data, &plaintext);264}265if (ret)266goto cleanup;267268/* Decode the decrypted authdata and make it available to modules in the269* request. */270ret = decode_krb5_authdata(&plaintext, &req->unenc_authdata);271if (ret)272goto cleanup;273274if (has_mandatory_for_kdc_authdata(context, req->unenc_authdata)) {275ret = KRB5KDC_ERR_POLICY;276goto cleanup;277}278279ret = add_filtered_authdata(tkt_authdata, req->unenc_authdata);280281cleanup:282free(plaintext.data);283return ret;284}285286/* Copy TGT authorization data into the ticket authdata. */287static krb5_error_code288copy_tgt_authdata(krb5_context context, krb5_kdc_req *request,289krb5_authdata **tgt_authdata, krb5_authdata ***tkt_authdata)290{291if (has_mandatory_for_kdc_authdata(context, tgt_authdata))292return KRB5KDC_ERR_POLICY;293294return add_filtered_authdata(tkt_authdata, tgt_authdata);295}296297/* Add authentication indicator authdata to enc_tkt_reply, wrapped in a CAMMAC298* and an IF-RELEVANT container. */299static krb5_error_code300add_auth_indicators(krb5_context context, krb5_data *const *auth_indicators,301krb5_keyblock *server_key, krb5_db_entry *krbtgt,302krb5_keyblock *krbtgt_key,303krb5_enc_tkt_part *enc_tkt_reply)304{305krb5_error_code ret;306krb5_data *der_indicators = NULL;307krb5_authdata ad, *list[2], **cammac = NULL;308309if (auth_indicators == NULL || *auth_indicators == NULL)310return 0;311312/* Format the authentication indicators into an authdata list. */313ret = encode_utf8_strings(auth_indicators, &der_indicators);314if (ret)315goto cleanup;316ad.ad_type = KRB5_AUTHDATA_AUTH_INDICATOR;317ad.length = der_indicators->length;318ad.contents = (uint8_t *)der_indicators->data;319list[0] = &ad;320list[1] = NULL;321322/* Wrap the list in CAMMAC and IF-RELEVANT containers. */323ret = cammac_create(context, enc_tkt_reply, server_key, krbtgt, krbtgt_key,324list, &cammac);325if (ret)326goto cleanup;327328/* Add the wrapped authdata to the ticket, without copying or filtering. */329ret = merge_authdata(&enc_tkt_reply->authorization_data, &cammac);330331cleanup:332krb5_free_data(context, der_indicators);333krb5_free_authdata(context, cammac);334return ret;335}336337/* Extract any properly verified authentication indicators from the authdata in338* enc_tkt. */339krb5_error_code340get_auth_indicators(krb5_context context, krb5_enc_tkt_part *enc_tkt,341krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key,342krb5_data ***indicators_out)343{344krb5_error_code ret;345krb5_authdata **cammacs = NULL, **adp;346krb5_cammac *cammac = NULL;347krb5_data **indicators = NULL, der_cammac;348349*indicators_out = NULL;350351ret = krb5_find_authdata(context, enc_tkt->authorization_data, NULL,352KRB5_AUTHDATA_CAMMAC, &cammacs);353if (ret)354goto cleanup;355356for (adp = cammacs; adp != NULL && *adp != NULL; adp++) {357der_cammac = make_data((*adp)->contents, (*adp)->length);358ret = decode_krb5_cammac(&der_cammac, &cammac);359if (ret)360goto cleanup;361if (cammac_check_kdcver(context, cammac, enc_tkt, local_tgt,362local_tgt_key)) {363ret = authind_extract(context, cammac->elements, &indicators);364if (ret)365goto cleanup;366}367k5_free_cammac(context, cammac);368cammac = NULL;369}370371*indicators_out = indicators;372indicators = NULL;373374cleanup:375krb5_free_authdata(context, cammacs);376k5_free_cammac(context, cammac);377k5_free_data_ptr_list(indicators);378return ret;379}380381static krb5_error_code382update_delegation_info(krb5_context context, krb5_kdc_req *req,383krb5_pac old_pac, krb5_pac new_pac)384{385krb5_error_code ret;386krb5_data ndr_di_in = empty_data(), ndr_di_out = empty_data();387struct pac_s4u_delegation_info *di = NULL;388char *namestr = NULL;389390ret = krb5_pac_get_buffer(context, old_pac, KRB5_PAC_DELEGATION_INFO,391&ndr_di_in);392if (ret && ret != ENOENT)393goto cleanup;394if (ret) {395/* Create new delegation info. */396di = k5alloc(sizeof(*di), &ret);397if (di == NULL)398goto cleanup;399di->transited_services = k5calloc(1, sizeof(char *), &ret);400if (di->transited_services == NULL)401goto cleanup;402} else {403/* Decode and modify old delegation info. */404ret = ndr_dec_delegation_info(&ndr_di_in, &di);405if (ret)406goto cleanup;407}408409/* Set proxy_target to the requested server, without realm. */410ret = krb5_unparse_name_flags(context, req->server,411KRB5_PRINCIPAL_UNPARSE_DISPLAY |412KRB5_PRINCIPAL_UNPARSE_NO_REALM,413&namestr);414if (ret)415goto cleanup;416free(di->proxy_target);417di->proxy_target = namestr;418419/* Add a transited entry for the requesting service, with realm. */420assert(req->second_ticket != NULL && req->second_ticket[0] != NULL);421ret = krb5_unparse_name(context, req->second_ticket[0]->server, &namestr);422if (ret)423goto cleanup;424di->transited_services[di->transited_services_length++] = namestr;425426ret = ndr_enc_delegation_info(di, &ndr_di_out);427if (ret)428goto cleanup;429430ret = krb5_pac_add_buffer(context, new_pac, KRB5_PAC_DELEGATION_INFO,431&ndr_di_out);432433cleanup:434krb5_free_data_contents(context, &ndr_di_in);435krb5_free_data_contents(context, &ndr_di_out);436ndr_free_delegation_info(di);437return ret;438}439440static krb5_error_code441copy_pac_buffer(krb5_context context, uint32_t buffer_type, krb5_pac old_pac,442krb5_pac new_pac)443{444krb5_error_code ret;445krb5_data data;446447ret = krb5_pac_get_buffer(context, old_pac, buffer_type, &data);448if (ret)449return ret;450ret = krb5_pac_add_buffer(context, new_pac, buffer_type, &data);451krb5_free_data_contents(context, &data);452return ret;453}454455/*456* Possibly add a signed PAC to enc_tkt_reply. Also possibly add auth457* indicators; these are handled here so that the KDB module's issue_pac()458* method can alter the auth indicator list.459*/460static krb5_error_code461handle_pac(kdc_realm_t *realm, unsigned int flags, krb5_db_entry *client,462krb5_db_entry *server, krb5_db_entry *subject_server,463krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key,464krb5_keyblock *server_key, krb5_keyblock *subject_key,465krb5_keyblock *replaced_reply_key, krb5_enc_tkt_part *subject_tkt,466krb5_pac subject_pac, krb5_kdc_req *req,467krb5_const_principal altcprinc, krb5_timestamp authtime,468krb5_enc_tkt_part *enc_tkt_reply, krb5_data ***auth_indicators)469{470krb5_context context = realm->realm_context;471krb5_error_code ret;472krb5_pac new_pac = NULL;473krb5_const_principal pac_client = NULL;474krb5_boolean with_realm, is_as_req = (req->msg_type == KRB5_AS_REQ);475krb5_db_entry *signing_tgt;476krb5_keyblock *privsvr_key = NULL;477478/* Don't add a PAC or auth indicators if the server disables authdata. */479if (server->attributes & KRB5_KDB_NO_AUTH_DATA_REQUIRED)480return 0;481482/*483* Don't add a PAC if the realm disables them, or to an anonymous ticket,484* or for an AS-REQ if the client requested not to get one, or for a485* TGS-REQ if the subject ticket didn't contain one.486*/487if (realm->realm_disable_pac ||488(enc_tkt_reply->flags & TKT_FLG_ANONYMOUS) ||489(is_as_req && !include_pac_p(context, req)) ||490(!is_as_req && subject_pac == NULL)) {491return add_auth_indicators(context, *auth_indicators, server_key,492local_tgt, local_tgt_key, enc_tkt_reply);493}494495ret = krb5_pac_init(context, &new_pac);496if (ret)497goto cleanup;498499if (subject_pac == NULL)500signing_tgt = NULL;501else if (krb5_is_tgs_principal(subject_server->princ))502signing_tgt = subject_server;503else504signing_tgt = local_tgt;505506ret = krb5_db_issue_pac(context, flags, client, replaced_reply_key, server,507signing_tgt, authtime, subject_pac, new_pac,508auth_indicators);509if (ret) {510if (ret == KRB5_PLUGIN_OP_NOTSUPP)511ret = 0;512if (ret)513goto cleanup;514}515516ret = add_auth_indicators(context, *auth_indicators, server_key,517local_tgt, local_tgt_key, enc_tkt_reply);518519if ((flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) &&520!(flags & KRB5_KDB_FLAG_CROSS_REALM)) {521/* Add delegation info for the first S4U2Proxy request. */522ret = update_delegation_info(context, req, subject_pac, new_pac);523if (ret)524goto cleanup;525} else if (subject_pac != NULL) {526/* Copy delegation info if it was present in the subject PAC. */527ret = copy_pac_buffer(context, KRB5_PAC_DELEGATION_INFO, subject_pac,528new_pac);529if (ret && ret != ENOENT)530goto cleanup;531}532533if ((flags & KRB5_KDB_FLAGS_S4U) &&534(flags & KRB5_KDB_FLAG_ISSUING_REFERRAL)) {535/* When issuing a referral for either kind of S4U request, add client536* info for the subject with realm. */537pac_client = altcprinc;538with_realm = TRUE;539} else if (subject_pac == NULL || (flags & KRB5_KDB_FLAGS_S4U)) {540/* For a new PAC or when issuing a final ticket for either kind of S4U541* request, add client info for the ticket client without the realm. */542pac_client = enc_tkt_reply->client;543with_realm = FALSE;544} else {545/*546* For regular TGS and transitive RBCD requests, copy the client info547* from the incoming PAC, and don't add client info during signing. We548* validated the incoming client info in validate_tgs_request().549*/550ret = copy_pac_buffer(context, KRB5_PAC_CLIENT_INFO, subject_pac,551new_pac);552if (ret)553goto cleanup;554pac_client = NULL;555with_realm = FALSE;556}557558ret = pac_privsvr_key(context, server, local_tgt_key, &privsvr_key);559if (ret)560goto cleanup;561ret = krb5_kdc_sign_ticket(context, enc_tkt_reply, new_pac, server->princ,562pac_client, server_key, privsvr_key,563with_realm);564if (ret)565goto cleanup;566567ret = 0;568569cleanup:570krb5_pac_free(context, new_pac);571krb5_free_keyblock(context, privsvr_key);572return ret;573}574575krb5_error_code576handle_authdata(kdc_realm_t *realm, unsigned int flags, krb5_db_entry *client,577krb5_db_entry *server, krb5_db_entry *subject_server,578krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key,579krb5_keyblock *client_key, krb5_keyblock *server_key,580krb5_keyblock *subject_key, krb5_keyblock *replaced_reply_key,581krb5_data *req_pkt, krb5_kdc_req *req,582krb5_const_principal altcprinc, krb5_pac subject_pac,583krb5_enc_tkt_part *enc_tkt_req, krb5_data ***auth_indicators,584krb5_enc_tkt_part *enc_tkt_reply)585{586krb5_context context = realm->realm_context;587kdcauthdata_handle *h;588krb5_error_code ret = 0;589size_t i;590591if (req->msg_type == KRB5_TGS_REQ &&592req->authorization_data.ciphertext.data != NULL) {593/* Copy TGS request authdata. This must be done first so that modules594* have access to the unencrypted request authdata. */595ret = copy_request_authdata(context, client_key, req, enc_tkt_req,596&enc_tkt_reply->authorization_data);597if (ret)598return ret;599}600601/* Invoke loaded module handlers. */602if (!isflagset(enc_tkt_reply->flags, TKT_FLG_ANONYMOUS)) {603for (i = 0; i < n_authdata_modules; i++) {604h = &authdata_modules[i];605ret = h->vt.handle(context, h->data, flags, client, server,606subject_server, client_key, server_key,607subject_key, req_pkt, req, altcprinc,608enc_tkt_req, enc_tkt_reply);609if (ret)610kdc_err(context, ret, "from authdata module %s", h->vt.name);611}612}613614if (req->msg_type == KRB5_TGS_REQ) {615/* Copy authdata from the TGT to the issued ticket. */616ret = copy_tgt_authdata(context, req, enc_tkt_req->authorization_data,617&enc_tkt_reply->authorization_data);618if (ret)619return ret;620}621622return handle_pac(realm, flags, client, server, subject_server, local_tgt,623local_tgt_key, server_key, subject_key,624replaced_reply_key, enc_tkt_req, subject_pac, req,625altcprinc, enc_tkt_reply->times.authtime, enc_tkt_reply,626auth_indicators);627}628629630