root/usr/src/lib/gss_mechs/mech_krb5/krb5/krb/rd_req_dec.c
/*
 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
 */
/*
 * lib/krb5/krb/rd_req_dec.c
 *
 * Copyright (c) 1994 CyberSAFE Corporation.
 * Copyright 1990,1991 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 <locale.h>
#include <syslog.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.
 *
 *  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
krb5_rd_req_decrypt_tkt_part(krb5_context context, const krb5_ap_req *req, krb5_keytab keytab)
{
    krb5_error_code       retval;
    krb5_enctype          enctype;
    krb5_keytab_entry     ktent;

    enctype = req->ticket->enc_part.enctype;

    /* Solaris Kerberos: */
    memset(&ktent, 0, sizeof(krb5_keytab_entry));
    if ((retval = krb5_kt_get_entry(context, keytab, req->ticket->server,
                                    req->ticket->enc_part.kvno,
                                    enctype, &ktent)))
        return retval;


    /*
     * Solaris Kerberos:
     * If we get this far then we know that the enc types are similar,
     * therefore we should change the enc type to match that of what
     * we are decrypting.
     */
    ktent.key.enctype = enctype;

    retval = krb5_decrypt_tkt_part(context, &ktent.key, req->ticket);
    /* Upon error, Free keytab entry first, then return */

    if (retval == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
        /* Solaris Kerberos: spruce-up the err msg */
        krb5_principal princ = (krb5_principal) req->ticket->server;
        char *s_name = NULL;
        int kret = krb5_unparse_name(context, princ, &s_name);
        if (kret == 0) {
            krb5_set_error_message(context, retval,
                                dgettext(TEXT_DOMAIN,
                                        "AP Request ticket decrypt fail for principal '%s' (kvno=%d, enctype=%d)"),
                                s_name,
                                req->ticket->enc_part.kvno,
                                enctype);
           krb5_free_unparsed_name(context, s_name);
        }
    }

    (void) krb5_kt_free_entry(context, &ktent);
    return retval;
}

/*
 * Solaris Kerberos
 * Same as krb5int_check_clockskew() plus return the skew in seconds.
 */
static krb5_error_code
krb5int_check_clockskew2(krb5_context context,
                        krb5_timestamp date,
                        krb5_timestamp *ret_skew)
{
    krb5_timestamp currenttime, skew;
    krb5_error_code retval;

    retval = krb5_timeofday(context, &currenttime);
    if (retval)
        return retval;

    skew = labs((date)-currenttime);
    if (!(skew < context->clockskew)) {
        *ret_skew = skew;
        return KRB5KRB_AP_ERR_SKEW;
    }

    return 0;
}

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_principal_data princ_data;
    krb5_timestamp        skew = 0; /* Solaris Kerberos */

    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,
                                dgettext(TEXT_DOMAIN,
                                        "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 == KRB5KRB_AP_ERR_SKEW)
            goto err_skew;

        if (retval)
            goto cleanup;
    }

    retval = krb5_validate_times(context, &req->ticket->enc_part2->times);
    if (retval != 0)
            goto cleanup;

err_skew:
    if ((retval = krb5int_check_clockskew2(context,
                                        (*auth_context)->authentp->ctime,
                                        &skew))) {
        /* Solaris Kerberos */
        char *s_name = NULL;
        char *c_name = NULL;
        krb5_error_code serr, cerr;
        serr = krb5_unparse_name(context, req->ticket->server, &s_name);
        cerr = krb5_unparse_name(context, req->ticket->enc_part2->client,
                                &c_name);
        krb5_set_error_message(context, retval,
                            dgettext(TEXT_DOMAIN,
                                    "Clock skew too great: client '%s' AP request with ticket for '%s'. Skew is %dm (allowable %dm)."),
                            cerr == 0 ? c_name : "unknown",
                            serr == 0 ? s_name : "unknown",
                            skew > 0 ? skew/60 : 0,
                            context->clockskew > 0 ? context->clockskew/60 : 0);
        if (s_name)
            krb5_free_unparsed_name(context, s_name);
        if (c_name)
            krb5_free_unparsed_name(context, c_name);
        goto cleanup;
    }

    if (check_valid_flag) {
        if (req->ticket->enc_part2->flags & TKT_FLG_INVALID) {
            /* Solaris Kerberos */
            char *s_name = NULL;
            int err = krb5_unparse_name(context, req->ticket->server, &s_name);
            retval = KRB5KRB_AP_ERR_TKT_INVALID;
            if (!err) {
                krb5_set_error_message(context, retval,
                                    dgettext(TEXT_DOMAIN,
                                    "Ticket has invalid flag set for server '%s'"),
                                    s_name);
                krb5_free_unparsed_name(context, s_name);
            }
            goto cleanup;
        }
    }

    /* check if the various etypes are permitted */

    if ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_PERMIT_ALL) {
        /* no etype check needed */
        /*EMPTY*/
        ;
    } 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,
                                    dgettext(TEXT_DOMAIN,
                                            "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,
                                    dgettext(TEXT_DOMAIN,
                                            "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,
                                    dgettext(TEXT_DOMAIN,
                                            "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,
                                        dgettext(TEXT_DOMAIN,
                                            "Encryption type %s not permitted"),
                                        enctype_name);
                goto cleanup;
            }
        }
    }

    (*auth_context)->remote_seq_number = (*auth_context)->authentp->seq_number;
    if ((*auth_context)->authentp->subkey) {
        /* Solaris Kerberos */
        if ((*auth_context)->recv_subkey != NULL) {
            krb5_free_keyblock(context, (*auth_context)->recv_subkey);
            (*auth_context)->recv_subkey = NULL;
        }

        if ((retval = krb5_copy_keyblock(context,
                                         (*auth_context)->authentp->subkey,
                                         &((*auth_context)->recv_subkey))))
            goto cleanup;
        /* Solaris Kerberos */
        if ((*auth_context)->send_subkey != NULL) {
            krb5_free_keyblock(context, (*auth_context)->send_subkey);
            (*auth_context)->send_subkey = NULL;
        }

        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;
    }
    /* Solaris Kerberos */
    if ((*auth_context)->keyblock != NULL) {
        krb5_free_keyblock(context, (*auth_context)->keyblock);
        (*auth_context)->keyblock = NULL;
    }
    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
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 = krb5_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 = krb5_rd_req_decoded_opt(context, auth_context,
                                   req, server, keytab,
                                   ap_req_options, ticket,
                                   0); /* don't check_valid_flag */
  return retval;
}

/*ARGSUSED*/
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;
    }
    clean_scratch();
    return retval;
}

krb5_error_code
krb5int_check_clockskew(krb5_context context, krb5_timestamp date)
{
    krb5_timestamp currenttime;
    krb5_error_code retval;

    retval = krb5_timeofday(context, &currenttime);
    if (retval)
        return retval;
    if (!(labs((date)-currenttime) < context->clockskew))
        return KRB5KRB_AP_ERR_SKEW;
    return 0;
}