Mercurial > illumos > onarm
diff usr/src/cmd/krb5/krb5kdc/kdc_preauth.c @ 0:c9caec207d52 b86
Initial porting based on b86
author | Koji Uno <koji.uno@sun.com> |
---|---|
date | Tue, 02 Jun 2009 18:56:50 +0900 |
parents | |
children | 1a15d5aaf794 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/krb5/krb5kdc/kdc_preauth.c Tue Jun 02 18:56:50 2009 +0900 @@ -0,0 +1,1573 @@ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "@(#)kdc_preauth.c 1.11 06/09/27 SMI" + +/* + * kdc/kdc_preauth.c + * + * Copyright 1995, 2003 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. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * Preauthentication routines for the KDC. + */ + +/* + * Copyright (C) 1998 by the FundsXpress, INC. + * + * 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 FundsXpress. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. FundsXpress makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "k5-int.h" +#include "kdc_util.h" +#include "extern.h" +#include "com_err.h" +#include <assert.h> +#include <stdio.h> +#include "adm_proto.h" +#include <libintl.h> +#include <syslog.h> + +#include <assert.h> + +/* XXX This is ugly and should be in a header file somewhere */ +#ifndef KRB5INT_DES_TYPES_DEFINED +#define KRB5INT_DES_TYPES_DEFINED +typedef unsigned char des_cblock[8]; /* crypto-block size */ +#endif +typedef des_cblock mit_des_cblock; +extern void mit_des_fixup_key_parity (mit_des_cblock ); +extern int mit_des_is_weak_key (mit_des_cblock ); + +typedef krb5_error_code (*verify_proc) + (krb5_context, krb5_db_entry *client, + krb5_kdc_req *request, + krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data); + +typedef krb5_error_code (*edata_proc) + (krb5_context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_pa_data *data); + +typedef krb5_error_code (*return_proc) + (krb5_context, krb5_pa_data * padata, + krb5_db_entry *client, + krb5_kdc_req *request, krb5_kdc_rep *reply, + krb5_key_data *client_key, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa); + +typedef struct _krb5_preauth_systems { + char * name; + int type; + int flags; + edata_proc get_edata; + verify_proc verify_padata; + return_proc return_padata; +} krb5_preauth_systems; + +static krb5_error_code verify_enc_timestamp + (krb5_context, krb5_db_entry *client, + krb5_kdc_req *request, + krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data); + +static krb5_error_code get_etype_info + (krb5_context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_pa_data *data); +static krb5_error_code +get_etype_info2(krb5_context context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_pa_data *pa_data); +static krb5_error_code +return_etype_info2(krb5_context, krb5_pa_data * padata, + krb5_db_entry *client, + krb5_kdc_req *request, krb5_kdc_rep *reply, + krb5_key_data *client_key, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa); + +static krb5_error_code return_pw_salt + (krb5_context, krb5_pa_data * padata, + krb5_db_entry *client, + krb5_kdc_req *request, krb5_kdc_rep *reply, + krb5_key_data *client_key, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa); + +/* SAM preauth support */ +static krb5_error_code verify_sam_response + (krb5_context, krb5_db_entry *client, + krb5_kdc_req *request, + krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data); + +static krb5_error_code get_sam_edata + (krb5_context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_pa_data *data); +static krb5_error_code return_sam_data + (krb5_context, krb5_pa_data * padata, + krb5_db_entry *client, + krb5_kdc_req *request, krb5_kdc_rep *reply, + krb5_key_data *client_key, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa); +/* + * Preauth property flags + */ +#define PA_HARDWARE 0x00000001 +#define PA_REQUIRED 0x00000002 +#define PA_SUFFICIENT 0x00000004 + /* Not really a padata, so don't include it in the etype list*/ +#define PA_PSEUDO 0x00000008 + +static krb5_preauth_systems preauth_systems[] = { + { + "timestamp", + KRB5_PADATA_ENC_TIMESTAMP, + 0, + 0, + verify_enc_timestamp, + 0 + }, + { + "etype-info", + KRB5_PADATA_ETYPE_INFO, + 0, + get_etype_info, + 0, + 0 + }, + { + "etype-info2", + KRB5_PADATA_ETYPE_INFO2, + 0, + get_etype_info2, + 0, + return_etype_info2 + }, + { + "pw-salt", + KRB5_PADATA_PW_SALT, + PA_PSEUDO, /* Don't include this in the error list */ + 0, + 0, + return_pw_salt + }, + { + "sam-response", + KRB5_PADATA_SAM_RESPONSE, + 0, + 0, + verify_sam_response, + return_sam_data + }, + { + "sam-challenge", + KRB5_PADATA_SAM_CHALLENGE, + PA_HARDWARE, /* causes get_preauth_hint_list to use this */ + get_sam_edata, + 0, + 0 + }, + { "[end]", -1,} +}; + +#define MAX_PREAUTH_SYSTEMS (sizeof(preauth_systems)/sizeof(preauth_systems[0])) + +static krb5_error_code +find_pa_system(int type, krb5_preauth_systems **preauth) +{ + krb5_preauth_systems *ap = preauth_systems; + + while ((ap->type != -1) && (ap->type != type)) + ap++; + if (ap->type == -1) + return(KRB5_PREAUTH_BAD_TYPE); + *preauth = ap; + return 0; +} + +const char *missing_required_preauth(krb5_db_entry *client, + krb5_db_entry *server, + krb5_enc_tkt_part *enc_tkt_reply) +{ +#if 0 + /* + * If this is the pwchange service, and the pre-auth bit is set, + * allow it even if the HW preauth would normally be required. + * + * Sandia national labs wanted this for some strange reason... we + * leave it disabled normally. + */ + if (isflagset(server->attributes, KRB5_KDB_PWCHANGE_SERVICE) && + isflagset(enc_tkt_reply->flags, TKT_FLG_PRE_AUTH)) + return 0; +#endif + +#ifdef DEBUG + krb5_klog_syslog (LOG_DEBUG, + "client needs %spreauth, %shw preauth; request has %spreauth, %shw preauth", + isflagset (client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) ? "" : "no ", + isflagset (client->attributes, KRB5_KDB_REQUIRES_HW_AUTH) ? "" : "no ", + isflagset (enc_tkt_reply->flags, TKT_FLG_PRE_AUTH) ? "" : "no ", + isflagset (enc_tkt_reply->flags, TKT_FLG_HW_AUTH) ? "" : "no "); +#endif + + if (isflagset(client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) && + !isflagset(enc_tkt_reply->flags, TKT_FLG_PRE_AUTH)) + return "NEEDED_PREAUTH"; + + if (isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH) && + !isflagset(enc_tkt_reply->flags, TKT_FLG_HW_AUTH)) + return "NEEDED_HW_PREAUTH"; + + return 0; +} + +void get_preauth_hint_list(krb5_kdc_req *request, krb5_db_entry *client, + krb5_db_entry *server, krb5_data *e_data) +{ + int hw_only; + krb5_preauth_systems *ap; + krb5_pa_data **pa_data, **pa; + krb5_data *edat; + krb5_error_code retval; + + /* Zero these out in case we need to abort */ + e_data->length = 0; + e_data->data = 0; + + hw_only = isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH); + pa_data = malloc(sizeof(krb5_pa_data *) * (MAX_PREAUTH_SYSTEMS+1)); + if (pa_data == 0) + return; + memset(pa_data, 0, sizeof(krb5_pa_data *) * (MAX_PREAUTH_SYSTEMS+1)); + pa = pa_data; + + for (ap = preauth_systems; ap->type != -1; ap++) { + if (hw_only && !(ap->flags & PA_HARDWARE)) + continue; + if (ap->flags & PA_PSEUDO) + continue; + *pa = malloc(sizeof(krb5_pa_data)); + if (*pa == 0) + goto errout; + memset(*pa, 0, sizeof(krb5_pa_data)); + (*pa)->magic = KV5M_PA_DATA; + (*pa)->pa_type = ap->type; + if (ap->get_edata) { + retval = (ap->get_edata)(kdc_context, request, client, server, *pa); + if (retval) { + /* just failed on this type, continue */ + free(*pa); + *pa = 0; + continue; + } + } + pa++; + } + if (pa_data[0] == 0) { + krb5_klog_syslog (LOG_INFO, + "%spreauth required but hint list is empty", + hw_only ? "hw" : ""); + } + retval = encode_krb5_padata_sequence((const krb5_pa_data **) pa_data, + &edat); + if (retval) + goto errout; + *e_data = *edat; + free(edat); + +errout: + krb5_free_pa_data(kdc_context, pa_data); + return; +} + +/* + * This routine is called to verify the preauthentication information + * for a V5 request. + * + * Returns 0 if the pre-authentication is valid, non-zero to indicate + * an error code of some sort. + */ + +krb5_error_code +check_padata (krb5_context context, krb5_db_entry *client, + krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply) +{ + krb5_error_code retval = 0; + krb5_pa_data **padata; + krb5_preauth_systems *pa_sys; + int pa_ok = 0, pa_found = 0; + + if (request->padata == 0) + return 0; + +#ifdef DEBUG + krb5_klog_syslog (LOG_DEBUG, "checking padata"); +#endif + for (padata = request->padata; *padata; padata++) { +#ifdef DEBUG + krb5_klog_syslog (LOG_DEBUG, ".. pa_type 0x%x", (*padata)->pa_type); +#endif + if (find_pa_system((*padata)->pa_type, &pa_sys)) + continue; +#ifdef DEBUG + krb5_klog_syslog (LOG_DEBUG, ".. pa_type %s", pa_sys->name); +#endif + if (pa_sys->verify_padata == 0) + continue; + pa_found++; + retval = pa_sys->verify_padata(context, client, request, + enc_tkt_reply, *padata); + if (retval) { + krb5_klog_syslog (LOG_INFO, "preauth (%s) verify failure: %s", + pa_sys->name, error_message (retval)); + if (pa_sys->flags & PA_REQUIRED) { + pa_ok = 0; + break; + } + } else { +#ifdef DEBUG + krb5_klog_syslog (LOG_DEBUG, ".. .. ok"); +#endif + pa_ok = 1; + if (pa_sys->flags & PA_SUFFICIENT) + break; + } + } + if (pa_ok) + return 0; + + /* pa system was not found, but principal doesn't require preauth */ + if (!pa_found && + !isflagset(client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) && + !isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH)) + return 0; + + if (!pa_found) + krb5_klog_syslog (LOG_INFO, "no valid preauth type found: %s", + error_message (retval)); +/* The following switch statement allows us + * to return some preauth system errors back to the client. + */ + switch(retval) { + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + case KRB5KRB_AP_ERR_SKEW: + return retval; + default: + return KRB5KDC_ERR_PREAUTH_FAILED; + } +} + +/* + * return_padata creates any necessary preauthentication + * structures which should be returned by the KDC to the client + */ +krb5_error_code +return_padata(krb5_context context, krb5_db_entry *client, + krb5_kdc_req *request, krb5_kdc_rep *reply, + krb5_key_data *client_key, krb5_keyblock *encrypting_key) +{ + krb5_error_code retval; + krb5_pa_data ** padata; + krb5_pa_data ** send_pa_list; + krb5_pa_data ** send_pa; + krb5_pa_data * pa = 0; + krb5_preauth_systems * ap; + int size = 0; + + for (ap = preauth_systems; ap->type != -1; ap++) { + if (ap->return_padata) + size++; + } + + if ((send_pa_list = malloc((size+1) * sizeof(krb5_pa_data *))) == NULL) + return ENOMEM; + + send_pa = send_pa_list; + *send_pa = 0; + + for (ap = preauth_systems; ap->type != -1; ap++) { + if (ap->return_padata == 0) + continue; + pa = 0; + if (request->padata) { + for (padata = request->padata; *padata; padata++) { + if ((*padata)->pa_type == ap->type) { + pa = *padata; + break; + } + } + } + if ((retval = ap->return_padata(context, pa, client, request, reply, + client_key, encrypting_key, send_pa))) + goto cleanup; + + if (*send_pa) + send_pa++; + *send_pa = 0; + } + + retval = 0; + + if (send_pa_list[0]) { + reply->padata = send_pa_list; + send_pa_list = 0; + } + +cleanup: + if (send_pa_list) + krb5_free_pa_data(context, send_pa_list); + return (retval); +} + +static krb5_boolean +enctype_requires_etype_info_2(krb5_enctype enctype) +{ + switch(enctype) { + case ENCTYPE_DES_CBC_CRC: + case ENCTYPE_DES_CBC_MD4: + case ENCTYPE_DES_CBC_MD5: + case ENCTYPE_DES3_CBC_SHA1: + case ENCTYPE_DES3_CBC_RAW: + case ENCTYPE_ARCFOUR_HMAC: + case ENCTYPE_ARCFOUR_HMAC_EXP : + return 0; + default: + if (krb5_c_valid_enctype(enctype)) + return 1; + else return 0; + } +} + +static krb5_boolean +request_contains_enctype (krb5_context context, const krb5_kdc_req *request, + krb5_enctype enctype) +{ + int i; + for (i =0; i < request->nktypes; i++) + if (request->ktype[i] == enctype) + return 1; + return 0; +} + + +static krb5_error_code +verify_enc_timestamp(krb5_context context, krb5_db_entry *client, + krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *pa) +{ + krb5_pa_enc_ts * pa_enc = 0; + krb5_error_code retval; + krb5_data scratch; + krb5_data enc_ts_data; + krb5_enc_data *enc_data = 0; + krb5_keyblock key; + krb5_key_data * client_key; + krb5_int32 start; + krb5_timestamp timenow; + krb5_error_code decrypt_err; + + (void) memset(&key, 0, sizeof(krb5_keyblock)); + scratch.data = (char *) pa->contents; + scratch.length = pa->length; + + enc_ts_data.data = 0; + + if ((retval = decode_krb5_enc_data(&scratch, &enc_data)) != 0) + goto cleanup; + + enc_ts_data.length = enc_data->ciphertext.length; + if ((enc_ts_data.data = (char *) malloc(enc_ts_data.length)) == NULL) + goto cleanup; + + start = 0; + decrypt_err = 0; + while (1) { + if ((retval = krb5_dbe_search_enctype(context, client, + &start, enc_data->enctype, + -1, 0, &client_key))) + goto cleanup; + + if ((retval = krb5_dbekd_decrypt_key_data(context, &master_keyblock, + client_key, &key, NULL))) + goto cleanup; + + key.enctype = enc_data->enctype; + + retval = krb5_c_decrypt(context, &key, KRB5_KEYUSAGE_AS_REQ_PA_ENC_TS, + 0, enc_data, &enc_ts_data); + krb5_free_keyblock_contents(context, &key); + if (retval == 0) + break; + else + decrypt_err = retval; + } + + if ((retval = decode_krb5_pa_enc_ts(&enc_ts_data, &pa_enc)) != 0) + goto cleanup; + + if ((retval = krb5_timeofday(context, &timenow)) != 0) + goto cleanup; + + if (labs(timenow - pa_enc->patimestamp) > context->clockskew) { + retval = KRB5KRB_AP_ERR_SKEW; + goto cleanup; + } + + setflag(enc_tkt_reply->flags, TKT_FLG_PRE_AUTH); + + retval = 0; + +cleanup: + if (enc_data) { + krb5_free_data_contents(context, &enc_data->ciphertext); + free(enc_data); + } + krb5_free_data_contents(context, &enc_ts_data); + if (pa_enc) + free(pa_enc); + /* + * If we get NO_MATCHING_KEY and decryption previously failed, and + * we failed to find any other keys of the correct enctype after + * that failed decryption, it probably means that the password was + * incorrect. + */ + if (retval == KRB5_KDB_NO_MATCHING_KEY && decrypt_err != 0) + retval = decrypt_err; + return retval; +} + +static krb5_error_code +_make_etype_info_entry(krb5_context context, + krb5_kdc_req *request, krb5_key_data *client_key, + krb5_enctype etype, krb5_etype_info_entry **entry, + int etype_info2) +{ + krb5_data salt; + krb5_etype_info_entry * tmp_entry; + krb5_error_code retval; + + if ((tmp_entry = malloc(sizeof(krb5_etype_info_entry))) == NULL) + return ENOMEM; + + salt.data = 0; + + tmp_entry->magic = KV5M_ETYPE_INFO_ENTRY; + tmp_entry->etype = etype; + tmp_entry->length = KRB5_ETYPE_NO_SALT; + tmp_entry->salt = 0; + tmp_entry->s2kparams.data = NULL; + tmp_entry->s2kparams.length = 0; + retval = get_salt_from_key(context, request->client, + client_key, &salt); + if (retval) + goto fail; + if (etype_info2 && client_key->key_data_ver > 1 && + client_key->key_data_type[1] == KRB5_KDB_SALTTYPE_AFS3) { + switch (etype) { + case ENCTYPE_DES_CBC_CRC: + case ENCTYPE_DES_CBC_MD4: + case ENCTYPE_DES_CBC_MD5: + tmp_entry->s2kparams.data = malloc(1); + if (tmp_entry->s2kparams.data == NULL) { + retval = ENOMEM; + goto fail; + } + tmp_entry->s2kparams.length = 1; + tmp_entry->s2kparams.data[0] = 1; + break; + default: + break; + } + } + + if (salt.length >= 0) { + tmp_entry->length = salt.length; + tmp_entry->salt = (unsigned char *) salt.data; + salt.data = 0; + } + *entry = tmp_entry; + return 0; + +fail: + if (tmp_entry) { + if (tmp_entry->s2kparams.data) + free(tmp_entry->s2kparams.data); + free(tmp_entry); + } + if (salt.data) + free(salt.data); + return retval; +} +/* + * This function returns the etype information for a particular + * client, to be passed back in the preauth list in the KRB_ERROR + * message. It supports generating both etype_info and etype_info2 + * as most of the work is the same. + */ +static krb5_error_code +etype_info_helper(krb5_context context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_pa_data *pa_data, int etype_info2) +{ + krb5_etype_info_entry ** entry = 0; + krb5_key_data *client_key; + krb5_error_code retval; + krb5_data * scratch; + krb5_enctype db_etype; + int i = 0; + int start = 0; + int seen_des = 0; + + entry = malloc((client->n_key_data * 2 + 1) * sizeof(krb5_etype_info_entry *)); + if (entry == NULL) + return ENOMEM; + entry[0] = NULL; + + while (1) { + retval = krb5_dbe_search_enctype(context, client, &start, -1, + -1, 0, &client_key); + if (retval == KRB5_KDB_NO_MATCHING_KEY) + break; + if (retval) + goto cleanup; + db_etype = client_key->key_data_type[0]; + if (db_etype == ENCTYPE_DES_CBC_MD4) + db_etype = ENCTYPE_DES_CBC_MD5; + + if (request_contains_enctype(context, request, db_etype)) { + assert(etype_info2 || + !enctype_requires_etype_info_2(db_etype)); + if ((retval = _make_etype_info_entry(context, request, client_key, + db_etype, &entry[i], etype_info2)) != 0) { + goto cleanup; + } + entry[i+1] = 0; + i++; + } + + /* + * If there is a des key in the kdb, try the "similar" enctypes, + * avoid duplicate entries. + */ + if (!seen_des) { + switch (db_etype) { + case ENCTYPE_DES_CBC_MD5: + db_etype = ENCTYPE_DES_CBC_CRC; + break; + case ENCTYPE_DES_CBC_CRC: + db_etype = ENCTYPE_DES_CBC_MD5; + break; + default: + continue; + + } + if (request_contains_enctype(context, request, db_etype)) { + if ((retval = _make_etype_info_entry(context, request, + client_key, db_etype, &entry[i], etype_info2)) != 0) { + goto cleanup; + } + entry[i+1] = 0; + i++; + } + seen_des++; + } + } + if (etype_info2) + retval = encode_krb5_etype_info2((const krb5_etype_info_entry **) entry, + &scratch); + else retval = encode_krb5_etype_info((const krb5_etype_info_entry **) entry, + &scratch); + if (retval) + goto cleanup; + pa_data->contents = (unsigned char *)scratch->data; + pa_data->length = scratch->length; + /* + * note, don't free scratch->data as it is in use (don't use + * krb5_free_data() either). + */ + free(scratch); + + retval = 0; + +cleanup: + if (entry) + krb5_free_etype_info(context, entry); + return retval; +} + +static krb5_error_code +get_etype_info(krb5_context context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_pa_data *pa_data) +{ + int i; + for (i=0; i < request->nktypes; i++) { + if (enctype_requires_etype_info_2(request->ktype[i])) + return KRB5KDC_ERR_PADATA_TYPE_NOSUPP ;;;; /*Caller will + * skip this + * type*/ + } + return etype_info_helper(context, request, client, server, pa_data, 0); +} + +static krb5_error_code +get_etype_info2(krb5_context context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_pa_data *pa_data) +{ + return etype_info_helper( context, request, client, server, pa_data, 1); +} + +static krb5_error_code +return_etype_info2(krb5_context context, krb5_pa_data * padata, + krb5_db_entry *client, + krb5_kdc_req *request, krb5_kdc_rep *reply, + krb5_key_data *client_key, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa) +{ + krb5_error_code retval; + krb5_pa_data *tmp_padata; + krb5_etype_info_entry **entry = NULL; + krb5_data *scratch = NULL; + tmp_padata = malloc( sizeof(krb5_pa_data)); + if (tmp_padata == NULL) + return ENOMEM; + tmp_padata->pa_type = KRB5_PADATA_ETYPE_INFO2; + entry = malloc(2 * sizeof(krb5_etype_info_entry *)); + if (entry == NULL) { + retval = ENOMEM; + goto cleanup; + } + entry[0] = NULL; + entry[1] = NULL; + /* using encrypting_key->enctype as this is specified in rfc4120 */ + retval = _make_etype_info_entry(context, request, client_key, encrypting_key->enctype, + entry, 1); + if (retval) + goto cleanup; + retval = encode_krb5_etype_info2((const krb5_etype_info_entry **) entry, &scratch); + if (retval) + goto cleanup; + tmp_padata->contents = (uchar_t *)scratch->data; + tmp_padata->length = scratch->length; + *send_pa = tmp_padata; + + /* For cleanup - we no longer own the contents of the krb5_data + * only to pointer to the krb5_data + */ + scratch->data = 0; + + cleanup: + if (entry) + krb5_free_etype_info(context, entry); + if (retval) { + if (tmp_padata) + free(tmp_padata); + } + if (scratch) + krb5_free_data(context, scratch); + return retval; +} + + +static krb5_error_code +return_pw_salt(krb5_context context, krb5_pa_data *in_padata, + krb5_db_entry *client, krb5_kdc_req *request, + krb5_kdc_rep *reply, krb5_key_data *client_key, + krb5_keyblock *encrypting_key, krb5_pa_data **send_pa) +{ + krb5_error_code retval; + krb5_pa_data * padata; + krb5_data * scratch; + krb5_data salt_data; + int i; + + for (i = 0; i < request->nktypes; i++) { + if (enctype_requires_etype_info_2(request->ktype[i])) + return 0; + } + if (client_key->key_data_ver == 1 || + client_key->key_data_type[1] == KRB5_KDB_SALTTYPE_NORMAL) + return 0; + + if ((padata = malloc(sizeof(krb5_pa_data))) == NULL) + return ENOMEM; + padata->magic = KV5M_PA_DATA; + padata->pa_type = KRB5_PADATA_PW_SALT; + + switch (client_key->key_data_type[1]) { + case KRB5_KDB_SALTTYPE_V4: + /* send an empty (V4) salt */ + padata->contents = 0; + padata->length = 0; + break; + case KRB5_KDB_SALTTYPE_NOREALM: + if ((retval = krb5_principal2salt_norealm(kdc_context, + request->client, + &salt_data))) + goto cleanup; + padata->contents = (krb5_octet *)salt_data.data; + padata->length = salt_data.length; + break; + case KRB5_KDB_SALTTYPE_AFS3: + /* send an AFS style realm-based salt */ + /* for now, just pass the realm back and let the client + do the work. In the future, add a kdc configuration + variable that specifies the old cell name. */ + padata->pa_type = KRB5_PADATA_AFS3_SALT; + /* it would be just like ONLYREALM, but we need to pass the 0 */ + scratch = krb5_princ_realm(kdc_context, request->client); + if ((padata->contents = malloc(scratch->length+1)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + memcpy(padata->contents, scratch->data, scratch->length); + padata->length = scratch->length+1; + padata->contents[scratch->length] = 0; + break; + case KRB5_KDB_SALTTYPE_ONLYREALM: + scratch = krb5_princ_realm(kdc_context, request->client); + if ((padata->contents = malloc(scratch->length)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + memcpy(padata->contents, scratch->data, scratch->length); + padata->length = scratch->length; + break; + case KRB5_KDB_SALTTYPE_SPECIAL: + if ((padata->contents = malloc(client_key->key_data_length[1])) + == NULL) { + retval = ENOMEM; + goto cleanup; + } + memcpy(padata->contents, client_key->key_data_contents[1], + client_key->key_data_length[1]); + padata->length = client_key->key_data_length[1]; + break; + default: + free(padata); + return 0; + } + + *send_pa = padata; + return 0; + +cleanup: + free(padata); + return retval; +} + +static krb5_error_code +return_sam_data(krb5_context context, krb5_pa_data *in_padata, + krb5_db_entry *client, krb5_kdc_req *request, + krb5_kdc_rep *reply, krb5_key_data *client_key, + krb5_keyblock *encrypting_key, krb5_pa_data **send_pa) +{ + krb5_error_code retval; + krb5_data scratch; + int i; + + krb5_sam_response *sr = 0; + krb5_predicted_sam_response *psr = 0; + + if (in_padata == 0) + return 0; + + /* + * We start by doing the same thing verify_sam_response() does: + * extract the psr from the padata (which is an sr). Nothing + * here should generate errors! We've already successfully done + * all this once. + */ + + scratch.data = (char *) in_padata->contents; /* SUNWresync121 XXX */ + scratch.length = in_padata->length; + + if ((retval = decode_krb5_sam_response(&scratch, &sr))) { + com_err("krb5kdc", retval, + gettext("return_sam_data(): decode_krb5_sam_response failed")); + goto cleanup; + } + + { + krb5_enc_data tmpdata; + + tmpdata.enctype = ENCTYPE_UNKNOWN; + tmpdata.ciphertext = sr->sam_track_id; + + scratch.length = tmpdata.ciphertext.length; + if ((scratch.data = (char *) malloc(scratch.length)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + + if ((retval = krb5_c_decrypt(context, &psr_key, /* XXX */ 0, 0, + &tmpdata, &scratch))) { + com_err("krb5kdc", retval, + gettext("return_sam_data(): decrypt track_id failed")); + free(scratch.data); + goto cleanup; + } + } + + if ((retval = decode_krb5_predicted_sam_response(&scratch, &psr))) { + com_err("krb5kdc", retval, + gettext( + "return_sam_data(): decode_krb5_predicted_sam_response failed")); + free(scratch.data); + goto cleanup; + } + + /* We could use sr->sam_flags, but it may be absent or altered. */ + if (psr->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) { + com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED, + gettext("Unsupported SAM flag must-pk-encrypt-sad")); + goto cleanup; + } + if (psr->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) { + /* No key munging */ + goto cleanup; + } + if (psr->sam_flags & KRB5_SAM_USE_SAD_AS_KEY) { + /* Use sam_key instead of client key */ + krb5_free_keyblock_contents(context, encrypting_key); + krb5_copy_keyblock_contents(context, &psr->sam_key, encrypting_key); + /* XXX Attach a useful pa_data */ + goto cleanup; + } + + /* Otherwise (no flags set), we XOR the keys */ + /* XXX The passwords-04 draft is underspecified here wrt different + key types. We will do what I hope to get into the -05 draft. */ + { + krb5_octet *p = encrypting_key->contents; + krb5_octet *q = psr->sam_key.contents; + int length = ((encrypting_key->length < psr->sam_key.length) + ? encrypting_key->length + : psr->sam_key.length); + + for (i = 0; i < length; i++) + p[i] ^= q[i]; + } + + /* Post-mixing key correction */ + switch (encrypting_key->enctype) { + case ENCTYPE_DES_CBC_CRC: + case ENCTYPE_DES_CBC_MD4: + case ENCTYPE_DES_CBC_MD5: + case ENCTYPE_DES_CBC_RAW: + mit_des_fixup_key_parity(encrypting_key->contents); + if (mit_des_is_weak_key(encrypting_key->contents)) + ((krb5_octet *) encrypting_key->contents)[7] ^= 0xf0; + break; + + /* XXX case ENCTYPE_DES3_CBC_MD5: listed in 1510bis-04 draft */ + case ENCTYPE_DES3_CBC_SHA: /* XXX deprecated? */ + case ENCTYPE_DES3_CBC_RAW: + case ENCTYPE_DES3_CBC_SHA1: + for (i = 0; i < 3; i++) { + mit_des_fixup_key_parity(encrypting_key->contents + i * 8); + if (mit_des_is_weak_key(encrypting_key->contents + i * 8)) + ((krb5_octet *) encrypting_key->contents)[7 + i * 8] ^= 0xf0; + } + break; + + default: + com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED, + gettext("Unimplemented keytype for SAM key mixing")); + goto cleanup; + } + + /* XXX Attach a useful pa_data */ +cleanup: + if (sr) + krb5_free_sam_response(context, sr); + if (psr) + krb5_free_predicted_sam_response(context, psr); + + return retval; +} + +static struct { + char* name; + int sam_type; +} *sam_ptr, sam_inst_map[] = { +#if 0 /* SUNWresync121 - unsupported hardware and kdc.log annoyance */ + { "SNK4", PA_SAM_TYPE_DIGI_PATH, }, + { "SECURID", PA_SAM_TYPE_SECURID, }, + { "GRAIL", PA_SAM_TYPE_GRAIL, }, +#endif + { 0, 0 }, +}; + +static krb5_error_code +get_sam_edata(krb5_context context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_pa_data *pa_data) +{ + krb5_error_code retval; + krb5_sam_challenge sc; + krb5_predicted_sam_response psr; + krb5_data * scratch; + krb5_keyblock encrypting_key; + char response[9]; + char inputblock[8]; + krb5_data predict_response; + + (void) memset(&encrypting_key, 0, sizeof(krb5_keyblock)); + (void) memset(&sc, 0, sizeof(sc)); + (void) memset(&psr, 0, sizeof(psr)); + + /* Given the client name we can figure out what type of preauth + they need. The spec is currently for querying the database for + names that match the types of preauth used. Later we should + make this mapping show up in kdc.conf. In the meantime, we + hardcode the following: + /SNK4 -- Digital Pathways SNK/4 preauth. + /GRAIL -- experimental preauth + The first one found is used. See sam_inst_map above. + + For SNK4 in particular, the key in the database is the key for + the device; kadmin needs a special interface for it. + */ + + { + int npr = 1; + krb5_boolean more; + krb5_db_entry assoc; + krb5_key_data *assoc_key; + krb5_principal newp; + int probeslot; + + sc.sam_type = 0; + + retval = krb5_copy_principal(kdc_context, request->client, &newp); + if (retval) { + com_err(gettext("krb5kdc"), + retval, + gettext("copying client name for preauth probe")); + return retval; + } + + probeslot = krb5_princ_size(context, newp)++; + krb5_princ_name(kdc_context, newp) = + realloc(krb5_princ_name(kdc_context, newp), + krb5_princ_size(context, newp) * sizeof(krb5_data)); + + for(sam_ptr = sam_inst_map; sam_ptr->name; sam_ptr++) { + krb5_princ_component(kdc_context,newp,probeslot)->data = sam_ptr->name; + krb5_princ_component(kdc_context,newp,probeslot)->length = + strlen(sam_ptr->name); + npr = 1; + retval = krb5_db_get_principal(kdc_context, newp, &assoc, &npr, (uint *)&more); + if(!retval && npr) { + sc.sam_type = sam_ptr->sam_type; + break; + } + } + + krb5_princ_component(kdc_context,newp,probeslot)->data = 0; + krb5_princ_component(kdc_context,newp,probeslot)->length = 0; + krb5_princ_size(context, newp)--; + + krb5_free_principal(kdc_context, newp); + + /* if sc.sam_type is set, it worked */ + if (sc.sam_type) { + /* so use assoc to get the key out! */ + { + /* here's what do_tgs_req does */ + retval = krb5_dbe_find_enctype(kdc_context, &assoc, + ENCTYPE_DES_CBC_RAW, + KRB5_KDB_SALTTYPE_NORMAL, + 0, /* Get highest kvno */ + &assoc_key); + if (retval) { + char *sname; + krb5_unparse_name(kdc_context, request->client, &sname); + com_err(gettext("krb5kdc"), + retval, + gettext("snk4 finding the enctype and key <%s>"), + sname); + free(sname); + return retval; + } + /* convert server.key into a real key */ + retval = krb5_dbekd_decrypt_key_data(kdc_context, + &master_keyblock, + assoc_key, &encrypting_key, + NULL); + if (retval) { + com_err(gettext("krb5kdc"), + retval, + gettext("snk4 pulling out key entry")); + return retval; + } + /* now we can use encrypting_key... */ + } + } else { + /* SAM is not an option - so don't return as hint */ + return KRB5_PREAUTH_BAD_TYPE; + } + } + sc.magic = KV5M_SAM_CHALLENGE; + psr.sam_flags = sc.sam_flags = KRB5_SAM_USE_SAD_AS_KEY; + + /* Replay prevention */ + if ((retval = krb5_copy_principal(context, request->client, &psr.client))) + return retval; +#ifdef USE_RCACHE + if ((retval = krb5_us_timeofday(context, &psr.stime, &psr.susec))) + return retval; +#endif /* USE_RCACHE */ + + switch (sc.sam_type) { + case PA_SAM_TYPE_GRAIL: + sc.sam_type_name.data = "Experimental System"; + sc.sam_type_name.length = strlen(sc.sam_type_name.data); + sc.sam_challenge_label.data = "experimental challenge label"; + sc.sam_challenge_label.length = strlen(sc.sam_challenge_label.data); + sc.sam_challenge.data = "12345"; + sc.sam_challenge.length = strlen(sc.sam_challenge.data); + +#if 0 /* Enable this to test "normal" (no flags set) mode. */ + psr.sam_flags = sc.sam_flags = 0; +#endif + + psr.magic = KV5M_PREDICTED_SAM_RESPONSE; + /* string2key on sc.sam_challenge goes in here */ + /* eblock is just to set the enctype */ + { + const krb5_enctype type = ENCTYPE_DES_CBC_MD5; + + if ((retval = krb5_c_string_to_key(context, type, &sc.sam_challenge, + 0 /* salt */, &psr.sam_key))) + goto cleanup; + + if ((retval = encode_krb5_predicted_sam_response(&psr, &scratch))) + goto cleanup; + + { + size_t enclen; + krb5_enc_data tmpdata; + + if ((retval = krb5_c_encrypt_length(context, + psr_key.enctype, + scratch->length, &enclen))) + goto cleanup; + + if ((tmpdata.ciphertext.data = (char *) malloc(enclen)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + tmpdata.ciphertext.length = enclen; + + if ((retval = krb5_c_encrypt(context, &psr_key, + /* XXX */ 0, 0, scratch, &tmpdata))) + goto cleanup; + + sc.sam_track_id = tmpdata.ciphertext; + } + } + + sc.sam_response_prompt.data = "response prompt"; + sc.sam_response_prompt.length = strlen(sc.sam_response_prompt.data); + sc.sam_pk_for_sad.length = 0; + sc.sam_nonce = 0; + /* Generate checksum */ + /*krb5_checksum_size(context, ctype)*/ + /*krb5_calculate_checksum(context,ctype,in,in_length,seed, + seed_length,outcksum) */ + /*krb5_verify_checksum(context,ctype,cksum,in,in_length,seed, + seed_length) */ +#if 0 /* XXX a) glue appears broken; b) this gives up the SAD */ + sc.sam_cksum.contents = (krb5_octet *) + malloc(krb5_checksum_size(context, CKSUMTYPE_RSA_MD5_DES)); + if (sc.sam_cksum.contents == NULL) return(ENOMEM); + + retval = krb5_calculate_checksum(context, CKSUMTYPE_RSA_MD5_DES, + sc.sam_challenge.data, + sc.sam_challenge.length, + psr.sam_key.contents, /* key */ + psr.sam_key.length, /* key length */ + &sc.sam_cksum); + if (retval) { free(sc.sam_cksum.contents); return(retval); } +#endif /* 0 */ + + retval = encode_krb5_sam_challenge(&sc, &scratch); + if (retval) goto cleanup; + pa_data->magic = KV5M_PA_DATA; + pa_data->pa_type = KRB5_PADATA_SAM_CHALLENGE; + pa_data->contents = (unsigned char *) scratch->data; + pa_data->length = scratch->length; + + retval = 0; + break; + case PA_SAM_TYPE_DIGI_PATH: + sc.sam_type_name.data = "Digital Pathways"; + sc.sam_type_name.length = strlen(sc.sam_type_name.data); +#if 1 + sc.sam_challenge_label.data = "Enter the following on your keypad"; + sc.sam_challenge_label.length = strlen(sc.sam_challenge_label.data); +#endif + /* generate digit string, take it mod 1000000 (six digits.) */ + { + int j; + krb5_keyblock session_key; + char outputblock[8]; + int i; + + (void) memset(&session_key, 0, sizeof(krb5_keyblock)); + + (void) memset(inputblock, 0, 8); + + retval = krb5_c_make_random_key(kdc_context, ENCTYPE_DES_CBC_CRC, + &session_key); + + if (retval) { + /* random key failed */ + com_err(gettext("krb5kdc"), + retval, + gettext("generating random challenge for preauth")); + return retval; + } + /* now session_key has a key which we can pick bits out of */ + /* we need six decimal digits. Grab 6 bytes, div 2, mod 10 each. */ + if (session_key.length != 8) { + retval = KRB5KDC_ERR_ETYPE_NOSUPP, + com_err(gettext("krb5kdc"), + retval = KRB5KDC_ERR_ETYPE_NOSUPP, + gettext("keytype didn't match code expectations")); + return retval; + } + for(i = 0; i<6; i++) { + inputblock[i] = '0' + ((session_key.contents[i]/2) % 10); + } + if (session_key.contents) + krb5_free_keyblock_contents(kdc_context, &session_key); + + /* retval = krb5_finish_key(kdc_context, &eblock); */ + /* now we have inputblock containing the 8 byte input to DES... */ + sc.sam_challenge.data = inputblock; + sc.sam_challenge.length = 6; + + encrypting_key.enctype = ENCTYPE_DES_CBC_RAW; + + if (retval) { + com_err(gettext("krb5kdc"), + retval, + gettext("snk4 processing key")); + } + + { + krb5_data plain; + krb5_enc_data cipher; + + plain.length = 8; + plain.data = inputblock; + + /* XXX I know this is enough because of the fixed raw enctype. + if it's not, the underlying code will return a reasonable + error, which should never happen */ + cipher.ciphertext.length = 8; + cipher.ciphertext.data = outputblock; + + if ((retval = krb5_c_encrypt(kdc_context, &encrypting_key, + /* XXX */ 0, 0, &plain, &cipher))) { + com_err(gettext("krb5kdc"), + retval, + gettext("snk4 response generation failed")); + return retval; + } + } + + /* now output block is the raw bits of the response; convert it + to display form */ + for (j=0; j<4; j++) { + char n[2]; + int k; + n[0] = outputblock[j] & 0xf; + n[1] = (outputblock[j]>>4) & 0xf; + for (k=0; k<2; k++) { + if(n[k] > 9) n[k] = ((n[k]-1)>>2); + /* This is equivalent to: + if(n[k]>=0xa && n[k]<=0xc) n[k] = 2; + if(n[k]>=0xd && n[k]<=0xf) n[k] = 3; + */ + } + /* for v4, we keygen: *(j+(char*)&key1) = (n[1]<<4) | n[0]; */ + /* for v5, we just generate a string */ + response[2*j+0] = '0' + n[1]; + response[2*j+1] = '0' + n[0]; + /* and now, response has what we work with. */ + } + response[8] = 0; + predict_response.data = response; + predict_response.length = 8; +#if 0 /* for debugging, hack the output too! */ +sc.sam_challenge_label.data = response; +sc.sam_challenge_label.length = strlen(sc.sam_challenge_label.data); +#endif + } + + psr.magic = KV5M_PREDICTED_SAM_RESPONSE; + /* string2key on sc.sam_challenge goes in here */ + /* eblock is just to set the enctype */ + { + retval = krb5_c_string_to_key(context, ENCTYPE_DES_CBC_MD5, + &predict_response, 0 /* salt */, + &psr.sam_key); + if (retval) goto cleanup; + + retval = encode_krb5_predicted_sam_response(&psr, &scratch); + if (retval) goto cleanup; + + { + size_t enclen; + krb5_enc_data tmpdata; + + if ((retval = krb5_c_encrypt_length(context, + psr_key.enctype, + scratch->length, &enclen))) + goto cleanup; + + if ((tmpdata.ciphertext.data = (char *) malloc(enclen)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + tmpdata.ciphertext.length = enclen; + + if ((retval = krb5_c_encrypt(context, &psr_key, + /* XXX */ 0, 0, scratch, &tmpdata))) + goto cleanup; + + sc.sam_track_id = tmpdata.ciphertext; + } + if (retval) goto cleanup; + } + + sc.sam_response_prompt.data = "Enter the displayed response"; + sc.sam_response_prompt.length = strlen(sc.sam_response_prompt.data); + sc.sam_pk_for_sad.length = 0; + sc.sam_nonce = 0; + /* Generate checksum */ + /*krb5_checksum_size(context, ctype)*/ + /*krb5_calculate_checksum(context,ctype,in,in_length,seed, + seed_length,outcksum) */ + /*krb5_verify_checksum(context,ctype,cksum,in,in_length,seed, + seed_length) */ +#if 0 /* XXX a) glue appears broken; b) this gives up the SAD */ + sc.sam_cksum.contents = (krb5_octet *) + malloc(krb5_checksum_size(context, CKSUMTYPE_RSA_MD5_DES)); + if (sc.sam_cksum.contents == NULL) return(ENOMEM); + + retval = krb5_calculate_checksum(context, CKSUMTYPE_RSA_MD5_DES, + sc.sam_challenge.data, + sc.sam_challenge.length, + psr.sam_key.contents, /* key */ + psr.sam_key.length, /* key length */ + &sc.sam_cksum); + if (retval) { free(sc.sam_cksum.contents); return(retval); } +#endif /* 0 */ + + retval = encode_krb5_sam_challenge(&sc, &scratch); + if (retval) goto cleanup; + pa_data->magic = KV5M_PA_DATA; + pa_data->pa_type = KRB5_PADATA_SAM_CHALLENGE; + pa_data->contents = (unsigned char *) scratch->data; + pa_data->length = scratch->length; + + retval = 0; + break; + } + +cleanup: + krb5_free_keyblock_contents(context, &encrypting_key); + return retval; +} + +static krb5_error_code +verify_sam_response(krb5_context context, krb5_db_entry *client, + krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *pa) +{ + krb5_error_code retval; + krb5_data scratch; + krb5_sam_response *sr = 0; + krb5_predicted_sam_response *psr = 0; + krb5_enc_sam_response_enc *esre = 0; + krb5_timestamp timenow; + char *princ_req = 0, *princ_psr = 0; + + scratch.data = (char *) pa->contents; + scratch.length = pa->length; + + if ((retval = decode_krb5_sam_response(&scratch, &sr))) { + scratch.data = 0; + com_err("krb5kdc", retval, gettext("decode_krb5_sam_response failed")); + goto cleanup; + } + + /* XXX We can only handle the challenge/response model of SAM. + See passwords-04, par 4.1, 4.2 */ + { + krb5_enc_data tmpdata; + + tmpdata.enctype = ENCTYPE_UNKNOWN; + tmpdata.ciphertext = sr->sam_track_id; + + scratch.length = tmpdata.ciphertext.length; + if ((scratch.data = (char *) malloc(scratch.length)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + + if ((retval = krb5_c_decrypt(context, &psr_key, /* XXX */ 0, 0, + &tmpdata, &scratch))) { + com_err(gettext("krb5kdc"), + retval, + gettext("decrypt track_id failed")); + goto cleanup; + } + } + + if ((retval = decode_krb5_predicted_sam_response(&scratch, &psr))) { + com_err(gettext("krb5kdc"), retval, + gettext("decode_krb5_predicted_sam_response failed -- replay attack?")); + goto cleanup; + } + + /* Replay detection */ + if ((retval = krb5_unparse_name(context, request->client, &princ_req))) + goto cleanup; + if ((retval = krb5_unparse_name(context, psr->client, &princ_psr))) + goto cleanup; + if (strcmp(princ_req, princ_psr) != 0) { + com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED, + gettext("Principal mismatch in SAM psr! -- replay attack?")); + goto cleanup; + } + + if ((retval = krb5_timeofday(context, &timenow))) + goto cleanup; + +#ifdef USE_RCACHE + { + krb5_donot_replay rep; + extern krb5_deltat rc_lifetime; + /* + * Verify this response came back in a timely manner. + * We do this b/c otherwise very old (expunged from the rcache) + * psr's would be able to be replayed. + */ + if (timenow - psr->stime > rc_lifetime) { + com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED, + gettext("SAM psr came back too late! -- replay attack?")); + goto cleanup; + } + + /* Now check the replay cache. */ + rep.client = princ_psr; + rep.server = "SAM/rc"; /* Should not match any principal name. */ + rep.ctime = psr->stime; + rep.cusec = psr->susec; + retval = krb5_rc_store(kdc_context, kdc_rcache, &rep); + if (retval) { + com_err("krb5kdc", retval, gettext("SAM psr replay attack!")); + goto cleanup; + } + } +#endif /* USE_RCACHE */ + + + { + free(scratch.data); + scratch.length = sr->sam_enc_nonce_or_ts.ciphertext.length; + if ((scratch.data = (char *) malloc(scratch.length)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + + if ((retval = krb5_c_decrypt(context, &psr->sam_key, /* XXX */ 0, + 0, &sr->sam_enc_nonce_or_ts, &scratch))) { + com_err("krb5kdc", retval, gettext("decrypt nonce_or_ts failed")); + goto cleanup; + } + } + + if ((retval = decode_krb5_enc_sam_response_enc(&scratch, &esre))) { + com_err("krb5kdc", retval, gettext("decode_krb5_enc_sam_response_enc failed")); + goto cleanup; + } + + if (esre->sam_timestamp != sr->sam_patimestamp) { + retval = KRB5KDC_ERR_PREAUTH_FAILED; + goto cleanup; + } + + if (labs(timenow - sr->sam_patimestamp) > context->clockskew) { + retval = KRB5KRB_AP_ERR_SKEW; + goto cleanup; + } + + setflag(enc_tkt_reply->flags, TKT_FLG_HW_AUTH); + + cleanup: + if (retval) com_err(gettext("krb5kdc"), + retval, + gettext("sam verify failure")); + if (scratch.data) free(scratch.data); + if (sr) free(sr); + if (psr) free(psr); + if (esre) free(esre); + if (princ_psr) free(princ_psr); + if (princ_req) free(princ_req); + + return retval; +}