2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A/*
2N/A * lib/krb5/krb/send_tgs.c
2N/A *
2N/A * Copyright 1990,1991,2009 by the Massachusetts Institute of Technology.
2N/A * All Rights Reserved.
2N/A *
2N/A * Export of this software from the United States of America may
2N/A * require a specific license from the United States Government.
2N/A * It is the responsibility of any person or organization contemplating
2N/A * export to obtain such a license before exporting.
2N/A *
2N/A * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
2N/A * distribute this software and its documentation for any purpose and
2N/A * without fee is hereby granted, provided that the above copyright
2N/A * notice appear in all copies and that both that copyright notice and
2N/A * this permission notice appear in supporting documentation, and that
2N/A * the name of M.I.T. not be used in advertising or publicity pertaining
2N/A * to distribution of the software without specific, written prior
2N/A * permission. Furthermore if you modify this software you must label
2N/A * your software as modified software and not distribute it in such a
2N/A * fashion that it might be confused with the original M.I.T. software.
2N/A * M.I.T. makes no representations about the suitability of
2N/A * this software for any purpose. It is provided "as is" without express
2N/A * or implied warranty.
2N/A */
2N/A/*
2N/A * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A/*
2N/A * krb5_send_tgs()
2N/A */
2N/A
2N/A#include "k5-int.h"
2N/A#include "int-proto.h"
2N/A
2N/A/*
2N/A Constructs a TGS request
2N/A options is used for the options in the KRB_TGS_REQ.
2N/A timestruct values are used for from, till, rtime " " "
2N/A enctype is used for enctype " " ", and to encrypt the authorization data,
2N/A sname is used for sname " " "
2N/A addrs, if non-NULL, is used for addresses " " "
2N/A authorization_dat, if non-NULL, is used for authorization_dat " " "
2N/A second_ticket, if required by options, is used for the 2nd ticket in the req.
2N/A in_cred is used for the ticket & session key in the KRB_AP_REQ header " " "
2N/A (the KDC realm is extracted from in_cred->server's realm)
2N/A
2N/A The response is placed into *rep.
2N/A rep->response.data is set to point at allocated storage which should be
2N/A freed by the caller when finished.
2N/A
2N/A returns system errors
2N/A*/
2N/Astatic krb5_error_code
2N/Atgs_construct_tgsreq(krb5_context context, krb5_data *in_data,
2N/A krb5_creds *in_cred, krb5_data *outbuf, krb5_keyblock *subkey)
2N/A{
2N/A krb5_cksumtype cksumtype;
2N/A krb5_error_code retval;
2N/A krb5_checksum checksum;
2N/A krb5_authenticator authent;
2N/A krb5_ap_req request;
2N/A krb5_data * scratch = NULL;
2N/A krb5_data * toutbuf = NULL;
2N/A
2N/A checksum.contents = NULL;
2N/A request.authenticator.ciphertext.data = NULL;
2N/A request.authenticator.kvno = 0;
2N/A request.ap_options = 0;
2N/A request.ticket = 0;
2N/A switch (in_cred->keyblock.enctype) {
2N/A case ENCTYPE_DES_CBC_CRC:
2N/A case ENCTYPE_DES_CBC_MD4:
2N/A case ENCTYPE_DES_CBC_MD5:
2N/A case ENCTYPE_ARCFOUR_HMAC:
2N/A case ENCTYPE_ARCFOUR_HMAC_EXP:
2N/A cksumtype = context->kdc_req_sumtype;
2N/A break;
2N/A default:
2N/A retval = krb5int_c_mandatory_cksumtype(context, in_cred->keyblock.enctype, &cksumtype);
2N/A if (retval)
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* Generate checksum */
2N/A if ((retval = krb5_c_make_checksum(context, cksumtype,
2N/A &in_cred->keyblock,
2N/A KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
2N/A in_data, &checksum))) {
2N/A free(checksum.contents);
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* gen authenticator */
2N/A authent.subkey = subkey; /*owned by caller*/
2N/A authent.seq_number = 0;
2N/A authent.checksum = &checksum;
2N/A authent.client = in_cred->client;
2N/A authent.authorization_data = in_cred->authdata;
2N/A if ((retval = krb5_us_timeofday(context, &authent.ctime,
2N/A &authent.cusec)))
2N/A goto cleanup;
2N/A
2N/A
2N/A /* encode the authenticator */
2N/A if ((retval = encode_krb5_authenticator(&authent, &scratch)))
2N/A goto cleanup;
2N/A
2N/A free(checksum.contents);
2N/A checksum.contents = NULL;
2N/A
2N/A
2N/A if ((retval = decode_krb5_ticket(&(in_cred)->ticket, &request.ticket)))
2N/A /* Cleanup scratch and scratch data */
2N/A goto cleanup;
2N/A
2N/A /* call the encryption routine */
2N/A if ((retval = krb5_encrypt_helper(context, &in_cred->keyblock,
2N/A KRB5_KEYUSAGE_TGS_REQ_AUTH,
2N/A scratch, &request.authenticator)))
2N/A goto cleanup;
2N/A
2N/A if (!(retval = encode_krb5_ap_req(&request, &toutbuf))) {
2N/A *outbuf = *toutbuf;
2N/A free(toutbuf);
2N/A }
2N/A
2N/A memset(request.authenticator.ciphertext.data, 0,
2N/A request.authenticator.ciphertext.length);
2N/A free(request.authenticator.ciphertext.data);
2N/A request.authenticator.ciphertext.length = 0;
2N/A request.authenticator.ciphertext.data = 0;
2N/A
2N/A
2N/Acleanup:
2N/A if (request.ticket)
2N/A krb5_free_ticket(context, request.ticket);
2N/A
2N/A if (scratch != NULL && scratch->data != NULL) {
2N/A zap(scratch->data, scratch->length);
2N/A free(scratch->data);
2N/A }
2N/A free(scratch);
2N/A
2N/A return retval;
2N/A}
2N/A/*
2N/A * Note that this function fills in part of rep even on failure.
2N/A *
2N/A * The pacb_fct callback allows the caller access to the nonce
2N/A * and request subkey, for binding preauthentication data
2N/A */
2N/A
2N/A/*
2N/A * Solaris Kerberos
2N/A * Modified to return the krb5_kdc_req associated with request_data
2N/A * for use by the DTrace probes.
2N/A * Must be freed by caller.
2N/A */
2N/Akrb5_error_code
2N/Akrb5int_make_tgs_request_ext(krb5_context context,
2N/A krb5_flags kdcoptions,
2N/A const krb5_ticket_times *timestruct,
2N/A const krb5_enctype *ktypes,
2N/A krb5_const_principal sname,
2N/A krb5_address *const *addrs,
2N/A krb5_authdata *const *authorization_data,
2N/A krb5_pa_data *const *padata,
2N/A const krb5_data *second_ticket,
2N/A krb5_creds *in_cred,
2N/A krb5_error_code (*pacb_fct)(krb5_context,
2N/A krb5_keyblock *,
2N/A krb5_kdc_req *,
2N/A void *),
2N/A void *pacb_data,
2N/A krb5_data *request_data,
2N/A krb5_timestamp *timestamp,
2N/A krb5_int32 *nonce,
2N/A krb5_keyblock **subkey,
2N/A krb5_kdc_req **retreq)
2N/A{
2N/A krb5_error_code retval;
2N/A krb5_kdc_req tgsreq;
2N/A krb5_data *scratch, scratch2;
2N/A krb5_ticket *sec_ticket = 0;
2N/A krb5_ticket *sec_ticket_arr[2];
2N/A krb5_timestamp time_now;
2N/A krb5_pa_data **combined_padata = NULL;
2N/A krb5_pa_data ap_req_padata;
2N/A krb5_keyblock *local_subkey = NULL;
2N/A
2N/A assert (subkey != NULL);
2N/A *subkey = NULL;
2N/A
2N/A /*
2N/A * in_creds MUST be a valid credential NOT just a partially filled in
2N/A * place holder for us to get credentials for the caller.
2N/A */
2N/A if (!in_cred->ticket.length)
2N/A return(KRB5_NO_TKT_SUPPLIED);
2N/A
2N/A memset(&tgsreq, 0, sizeof(tgsreq));
2N/A
2N/A tgsreq.kdc_options = kdcoptions;
2N/A tgsreq.server = (krb5_principal) sname;
2N/A
2N/A tgsreq.from = timestruct->starttime;
2N/A tgsreq.till = timestruct->endtime ? timestruct->endtime : in_cred->times.endtime;
2N/A tgsreq.authorization_data.ciphertext.data = NULL;
2N/A tgsreq.rtime = timestruct->renew_till;
2N/A if ((retval = krb5_timeofday(context, &time_now)))
2N/A return(retval);
2N/A /* XXX we know they are the same size... */
2N/A *nonce = tgsreq.nonce = (krb5_int32)time_now;
2N/A *timestamp = time_now;
2N/A
2N/A tgsreq.addresses = (krb5_address **) addrs;
2N/A
2N/A /* Generate subkey*/
2N/A if ((retval = krb5_generate_subkey( context, &in_cred->keyblock,
2N/A &local_subkey)) != 0)
2N/A return retval;
2N/A
2N/A if (authorization_data) {
2N/A /* need to encrypt it in the request */
2N/A
2N/A if ((retval = encode_krb5_authdata(authorization_data, &scratch)))
2N/A goto send_tgs_error_1;
2N/A
2N/A if ((retval = krb5_encrypt_helper(context, local_subkey,
2N/A KRB5_KEYUSAGE_TGS_REQ_AD_SUBKEY,
2N/A scratch,
2N/A &tgsreq.authorization_data))) {
2N/A free(tgsreq.authorization_data.ciphertext.data);
2N/A krb5_free_data(context, scratch);
2N/A goto send_tgs_error_1;
2N/A }
2N/A
2N/A krb5_free_data(context, scratch);
2N/A }
2N/A
2N/A /* Get the encryption types list */
2N/A if (ktypes) {
2N/A /* Check passed ktypes and make sure they're valid. */
2N/A for (tgsreq.nktypes = 0; ktypes[tgsreq.nktypes]; tgsreq.nktypes++) {
2N/A if (!krb5_c_valid_enctype(ktypes[tgsreq.nktypes]))
2N/A return KRB5_PROG_ETYPE_NOSUPP;
2N/A }
2N/A tgsreq.ktype = (krb5_enctype *)ktypes;
2N/A } else {
2N/A /* Get the default ktypes */
2N/A krb5_get_tgs_ktypes(context, sname, &(tgsreq.ktype));
2N/A for(tgsreq.nktypes = 0; tgsreq.ktype[tgsreq.nktypes]; tgsreq.nktypes++);
2N/A }
2N/A
2N/A if (second_ticket) {
2N/A if ((retval = decode_krb5_ticket(second_ticket, &sec_ticket)))
2N/A goto send_tgs_error_1;
2N/A sec_ticket_arr[0] = sec_ticket;
2N/A sec_ticket_arr[1] = 0;
2N/A tgsreq.second_ticket = sec_ticket_arr;
2N/A } else
2N/A tgsreq.second_ticket = 0;
2N/A
2N/A ap_req_padata.contents = NULL;
2N/A
2N/A /* encode the body; then checksum it */
2N/A if ((retval = encode_krb5_kdc_req_body(&tgsreq, &scratch)))
2N/A goto send_tgs_error_2;
2N/A
2N/A /*
2N/A * Get an ap_req.
2N/A */
2N/A if ((retval = tgs_construct_tgsreq(context, scratch, in_cred,
2N/A &scratch2, local_subkey))) {
2N/A krb5_free_data(context, scratch);
2N/A goto send_tgs_error_2;
2N/A }
2N/A krb5_free_data(context, scratch);
2N/A
2N/A tgsreq.padata = (krb5_pa_data **)calloc(2, sizeof(krb5_pa_data *));
2N/A if (tgsreq.padata == NULL) {
2N/A free(scratch2.data);
2N/A goto send_tgs_error_2;
2N/A }
2N/A tgsreq.padata[0] = (krb5_pa_data *)malloc(sizeof(krb5_pa_data));
2N/A if (tgsreq.padata[0] == NULL) {
2N/A free(scratch2.data);
2N/A goto send_tgs_error_2;
2N/A }
2N/A tgsreq.padata[0]->pa_type = KRB5_PADATA_AP_REQ;
2N/A tgsreq.padata[0]->length = scratch2.length;
2N/A tgsreq.padata[0]->contents = (krb5_octet *)scratch2.data;
2N/A tgsreq.padata[1] = NULL;
2N/A
2N/A /* combine in any other supplied padata, unfortunately now it is
2N/A * necessary to copy it as the callback function might modify the
2N/A * padata, and having a separate path for the non-callback case,
2N/A * or attempting to determine which elements were changed by the
2N/A * callback, would have complicated the code significantly.
2N/A */
2N/A if (padata) {
2N/A krb5_pa_data **tmp;
2N/A int i;
2N/A
2N/A for (i = 0; padata[i]; i++)
2N/A ;
2N/A
2N/A tmp = (krb5_pa_data **)realloc(tgsreq.padata,
2N/A (i + 2) * sizeof(*combined_padata));
2N/A if (tmp == NULL)
2N/A goto send_tgs_error_2;
2N/A
2N/A tgsreq.padata = tmp;
2N/A
2N/A for (i = 0; padata[i]; i++) {
2N/A krb5_pa_data *pa;
2N/A
2N/A pa = tgsreq.padata[1 + i] = (krb5_pa_data *)malloc(sizeof(krb5_pa_data));
2N/A if (tgsreq.padata == NULL) {
2N/A retval = ENOMEM;
2N/A goto send_tgs_error_2;
2N/A }
2N/A
2N/A pa->pa_type = padata[i]->pa_type;
2N/A pa->length = padata[i]->length;
2N/A pa->contents = (krb5_octet *)malloc(padata[i]->length);
2N/A if (pa->contents == NULL) {
2N/A retval = ENOMEM;
2N/A goto send_tgs_error_2;
2N/A }
2N/A memcpy(pa->contents, padata[i]->contents, padata[i]->length);
2N/A }
2N/A tgsreq.padata[1 + i] = NULL;
2N/A }
2N/A
2N/A if (pacb_fct != NULL) {
2N/A if ((retval = (*pacb_fct)(context, local_subkey, &tgsreq, pacb_data)))
2N/A goto send_tgs_error_2;
2N/A }
2N/A /* the TGS_REQ is assembled in tgsreq, so encode it */
2N/A if ((retval = encode_krb5_tgs_req(&tgsreq, &scratch)))
2N/A goto send_tgs_error_2;
2N/A
2N/A /*
2N/A * Solaris Kerberos
2N/A * Copy the tgsreq structure so that it is available to the DTrace
2N/A * probes. Clear the kdc_state member as it is not used by the probes
2N/A * and will only cause assertion failures with non-debug bits.
2N/A */
2N/A if (retreq != NULL) {
2N/A if (krb5_copy_kdc_req(context, &tgsreq, retreq) == 0)
2N/A (*retreq)->kdc_state = NULL;
2N/A }
2N/A
2N/A /* now send request & get response from KDC */
2N/A krb5_free_pa_data(context, tgsreq.padata);
2N/A tgsreq.padata = NULL;
2N/A
2N/A *request_data = *scratch;
2N/A free(scratch);
2N/A scratch = NULL;
2N/A
2N/Asend_tgs_error_2:;
2N/A if (tgsreq.padata)
2N/A krb5_free_pa_data(context, tgsreq.padata);
2N/A if (sec_ticket)
2N/A krb5_free_ticket(context, sec_ticket);
2N/A
2N/Asend_tgs_error_1:;
2N/A if (ktypes == NULL)
2N/A free(tgsreq.ktype);
2N/A if (tgsreq.authorization_data.ciphertext.data) {
2N/A memset(tgsreq.authorization_data.ciphertext.data, 0,
2N/A tgsreq.authorization_data.ciphertext.length);
2N/A free(tgsreq.authorization_data.ciphertext.data);
2N/A }
2N/A
2N/A if (retval)
2N/A krb5_free_keyblock(context, local_subkey);
2N/A else
2N/A *subkey = local_subkey;
2N/A
2N/A return retval;
2N/A
2N/A}
2N/A
2N/A/* Solaris Kerberos: dead code begin */
2N/A#if 0 /************** Begin IFDEF'ed OUT *******************************/
2N/Akrb5_error_code
2N/Akrb5int_send_tgs(krb5_context context, krb5_flags kdcoptions,
2N/A const krb5_ticket_times *timestruct,
2N/A const krb5_enctype *ktypes,
2N/A krb5_const_principal sname, krb5_address *const *addrs,
2N/A krb5_authdata *const *authorization_data,
2N/A krb5_pa_data *const *padata, const krb5_data *second_ticket,
2N/A krb5_creds *in_cred,
2N/A krb5_error_code (*pacb_fct)(krb5_context,
2N/A krb5_keyblock *,
2N/A krb5_kdc_req *,
2N/A void *),
2N/A void *pacb_data,
2N/A krb5_response *rep, krb5_keyblock **subkey_out)
2N/A{
2N/A krb5_error_code retval;
2N/A krb5_data request;
2N/A int tcp_only = 0, use_master;
2N/A krb5_timestamp now;
2N/A krb5_int32 nonce;
2N/A krb5_keyblock *subkey;
2N/A krb5_error *err_reply = NULL;
2N/A krb5_ui_4 err;
2N/A
2N/A *subkey_out = NULL;
2N/A rep->message_type = KRB5_ERROR;
2N/A
2N/A retval = krb5int_make_tgs_request_ext(context, kdcoptions, timestruct,
2N/A ktypes, sname, addrs,
2N/A authorization_data, padata,
2N/A second_ticket, in_cred,
2N/A pacb_fct, pacb_data, &request, &now,
2N/A &nonce, &subkey);
2N/A if (retval != 0)
2N/A return retval;
2N/A
2N/A rep->expected_nonce = nonce;
2N/A rep->request_time = now;
2N/A
2N/A for (tcp_only = 0; tcp_only <= 1; tcp_only++) {
2N/A use_master = 0;
2N/A retval = krb5_sendto_kdc(context, &request,
2N/A krb5_princ_realm(context, sname),
2N/A &rep->response, &use_master, tcp_only);
2N/A if (retval != 0)
2N/A break;
2N/A
2N/A if (krb5_is_tgs_rep(&rep->response)) {
2N/A /* Successful response; set the output subkey. */
2N/A rep->message_type = KRB5_TGS_REP;
2N/A *subkey_out = subkey;
2N/A subkey = NULL;
2N/A break;
2N/A } else if (krb5_is_krb_error(&rep->response) && !tcp_only) {
2N/A /* Decode the error response to extract the code. */
2N/A retval = decode_krb5_error(&rep->response, &err_reply);
2N/A err = (retval == 0) ? err_reply->error : 0;
2N/A krb5_free_error(context, err_reply);
2N/A if (err == KRB_ERR_RESPONSE_TOO_BIG) {
2N/A /* Try again with TCP. */
2N/A krb5_free_data_contents(context, &rep->response);
2N/A continue;
2N/A }
2N/A }
2N/A /* Unexpected message type, or an error other than RESPONSE_TOO_BIG. */
2N/A rep->message_type = KRB5_ERROR;
2N/A break;
2N/A }
2N/A
2N/A krb5_free_data_contents(context, &request);
2N/A krb5_free_keyblock(context, subkey);
2N/A return retval;
2N/A}
2N/A#endif /**************** END IFDEF'ed OUT *******************************/
2N/A/* Solaris Kerberos: dead code end */