/* * lib/krb4/rd_req.c * * Copyright 1985, 1986, 1987, 1988, 2000, 2001, 2002 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. */ #include "des.h" #include "krb.h" #include "prot.h" #include #include #include extern int krb_ap_req_debug; static int krb_rd_req_with_key(KTEXT, char *, char *, KRB_UINT32, AUTH_DAT *, Key_schedule, krb5_keyblock *); /* declared in krb.h */ int krb_ignore_ip_address = 0; /* * Keep the following information around for subsequent calls * to this routine by the same server using the same key. */ static Key_schedule serv_key; /* Key sched to decrypt ticket */ static C_Block ky; /* Initialization vector */ static int st_kvno; /* version number for this key */ static char st_rlm[REALM_SZ]; /* server's realm */ static char st_nam[ANAME_SZ]; /* service name */ static char st_inst[INST_SZ]; /* server's instance */ static int krb5_key; /* whether krb5 key is used for decrypt */ /* * This file contains two functions. krb_set_key() takes a DES * key or password string and returns a DES key (either the original * key, or the password converted into a DES key) and a key schedule * for it. * * krb_rd_req() reads an authentication request and returns information * about the identity of the requestor, or an indication that the * identity information was not authentic. */ /* * krb_set_key() takes as its first argument either a DES key or a * password string. The "cvt" argument indicates how the first * argument "key" is to be interpreted: if "cvt" is null, "key" is * taken to be a DES key; if "cvt" is non-null, "key" is taken to * be a password string, and is converted into a DES key using * string_to_key(). In either case, the resulting key is returned * in the external static variable "ky". A key schedule is * generated for "ky" and returned in the external static variable * "serv_key". * * This routine returns the return value of des_key_sched. * * krb_set_key() needs to be in the same .o file as krb_rd_req() so that * the key set by krb_set_key() is available in private storage for * krb_rd_req(). */ static krb5_keyblock srv_k5key; int krb_set_key(key, cvt) char *key; int cvt; { if (krb5_key) /* XXX assumes that context arg is ignored */ krb5_free_keyblock_contents(NULL, &srv_k5key); krb5_key = 0; #ifdef NOENCRYPTION memset(ky, 0, sizeof(ky)); return KSUCCESS; #else /* Encrypt */ if (cvt) string_to_key(key, ky); else memcpy((char *)ky, key, 8); return des_key_sched(ky,serv_key); #endif /* NOENCRYPTION */ } int krb_set_key_krb5(ctx, key) krb5_context ctx; krb5_keyblock *key; { if (krb5_key) krb5_free_keyblock_contents(ctx, &srv_k5key); krb5_key = 1; return krb5_copy_keyblock_contents(ctx, key, &srv_k5key); } void krb_clear_key_krb5(ctx) krb5_context ctx; { if (krb5_key) krb5_free_keyblock_contents(ctx, &srv_k5key); krb5_key = 0; } /* * krb_rd_req() takes an AUTH_MSG_APPL_REQUEST or * AUTH_MSG_APPL_REQUEST_MUTUAL message created by krb_mk_req(), * checks its integrity and returns a judgement as to the requestor's * identity. * * The "authent" argument is a pointer to the received message. * The "service" and "instance" arguments name the receiving server, * and are used to get the service's ticket to decrypt the ticket * in the message, and to compare against the server name inside the * ticket. "from_addr" is the network address of the host from which * the message was received; this is checked against the network * address in the ticket. If "from_addr" is zero, the check is not * performed. "ad" is an AUTH_DAT structure which is * filled in with information about the sender's identity according * to the authenticator and ticket sent in the message. Finally, * "fn" contains the name of the file containing the server's key. * (If "fn" is NULL, the server's key is assumed to have been set * by krb_set_key(). If "fn" is the null string ("") the default * file KEYFILE, defined in "krb.h", is used.) * * krb_rd_req() returns RD_AP_OK if the authentication information * was genuine, or one of the following error codes (defined in * "krb.h"): * * RD_AP_VERSION - wrong protocol version number * RD_AP_MSG_TYPE - wrong message type * RD_AP_UNDEC - couldn't decipher the message * RD_AP_INCON - inconsistencies found * RD_AP_BADD - wrong network address * RD_AP_TIME - client time (in authenticator) * too far off server time * RD_AP_NYV - Kerberos time (in ticket) too * far off server time * RD_AP_EXP - ticket expired * * For the message format, see krb_mk_req(). * * Mutual authentication is not implemented. */ static int krb_rd_req_with_key(authent, service, instance, from_addr, ad, ks, k5key) register KTEXT authent; /* The received message */ char *service; /* Service name */ char *instance; /* Service instance */ unsigned KRB4_32 from_addr; /* Net address of originating host */ AUTH_DAT *ad; /* Structure to be filled in */ Key_schedule ks; krb5_keyblock *k5key; { KTEXT_ST ticket; /* Temp storage for ticket */ KTEXT tkt = &ticket; KTEXT_ST req_id_st; /* Temp storage for authenticator */ register KTEXT req_id = &req_id_st; char realm[REALM_SZ]; /* Realm of issuing kerberos */ Key_schedule seskey_sched; /* Key sched for session key */ char sname[SNAME_SZ]; /* Service name from ticket */ char iname[INST_SZ]; /* Instance name from ticket */ char r_aname[ANAME_SZ]; /* Client name from authenticator */ char r_inst[INST_SZ]; /* Client instance from authenticator */ char r_realm[REALM_SZ]; /* Client realm from authenticator */ unsigned int r_time_ms; /* Fine time from authenticator */ unsigned KRB4_32 r_time_sec; /* Coarse time from authenticator */ register unsigned char *ptr; /* For stepping through */ unsigned KRB4_32 t_local; /* Local time on our side of the protocol */ KRB4_32 delta_t; /* Time in authenticator minus local time */ #ifdef KRB_CRYPT_DEBUG KRB4_32 tkt_age; /* Age of ticket */ #endif int le; /* is little endian? */ int mutual; /* Mutual authentication requested? */ int t; /* msg type */ unsigned char s_kvno; /* Version number of the server's key Kerberos used to encrypt ticket */ int ret; int len; tkt->mbz = req_id->mbz = 0; if (authent->length < 1 + 1 + 1) return RD_AP_MODIFIED; ptr = authent->dat; #define AUTHENT_REMAIN (authent->length - (ptr - authent->dat)) /* get msg version, type and byte order, and server key version */ /* check version */ if (KRB_PROT_VERSION != *ptr++) return RD_AP_VERSION; /* byte order */ t = *ptr++; le = t & 1; /* check msg type */ mutual = 0; switch (t & ~1) { case AUTH_MSG_APPL_REQUEST: break; case AUTH_MSG_APPL_REQUEST_MUTUAL: mutual++; break; default: return RD_AP_MSG_TYPE; } #ifdef lint /* XXX mutual is set but not used; why??? */ /* this is a crock to get lint to shut up */ if (mutual) mutual = 0; #endif /* lint */ s_kvno = *ptr++; /* get server key version */ len = krb4int_strnlen((char *)ptr, AUTHENT_REMAIN) + 1; if (len <= 0 || len > sizeof(realm)) { return RD_AP_MODIFIED; /* must have been modified, the client wouldn't try to trick us with wacky data */ } /* And the realm of the issuing KDC */ (void)memcpy(realm, ptr, (size_t)len); ptr += len; /* skip the realm "hint" */ /* Get ticket length */ tkt->length = *ptr++; /* Get authenticator length while we're at it. */ req_id->length = *ptr++; if (AUTHENT_REMAIN < tkt->length + req_id->length) return RD_AP_MODIFIED; /* Copy ticket */ memcpy(tkt->dat, ptr, (size_t)tkt->length); ptr += tkt->length; #ifdef KRB_CRYPT_DEBUG if (krb_ap_req_debug) log("ticket->length: %d",tkt->length); if (krb_ap_req_debug) log("authent->length: %d", authent->length); #endif #ifndef NOENCRYPTION /* Decrypt and take apart ticket */ #endif if (k5key == NULL) { if (decomp_ticket(tkt,&ad->k_flags,ad->pname,ad->pinst,ad->prealm, &(ad->address),ad->session, &(ad->life), &(ad->time_sec),sname,iname,ky,ks)) { #ifdef KRB_CRYPT_DEBUG log("Can't decode ticket"); #endif return(RD_AP_UNDEC); } } else { if (decomp_tkt_krb5(tkt, &ad->k_flags, ad->pname, ad->pinst, ad->prealm, &ad->address, ad->session, &ad->life, &ad->time_sec, sname, iname, k5key)) { return RD_AP_UNDEC; } } #ifdef KRB_CRYPT_DEBUG if (krb_ap_req_debug) { log("Ticket Contents."); log(" Aname: %s%s%s@%s",ad->pname, ((int)*(ad->pinst) ? "." : ""), ad->pinst, ((int)*(ad->prealm) ? ad->prealm : "Athena")); log(" Service: %s%s%s",sname,((int)*iname ? "." : ""),iname); log(" sname=%s, sinst=%s", sname, iname); } #endif /* Extract the authenticator */ memcpy(req_id->dat, ptr, (size_t)req_id->length); #ifndef NOENCRYPTION /* And decrypt it with the session key from the ticket */ #ifdef KRB_CRYPT_DEBUG if (krb_ap_req_debug) log("About to decrypt authenticator"); #endif key_sched(ad->session, seskey_sched); pcbc_encrypt((C_Block *)req_id->dat, (C_Block *)req_id->dat, (long)req_id->length, seskey_sched, &ad->session, DES_DECRYPT); memset(seskey_sched, 0, sizeof(seskey_sched)); #ifdef KRB_CRYPT_DEBUG if (krb_ap_req_debug) log("Done."); #endif #endif /* NOENCRYPTION */ ptr = req_id->dat; #define REQID_REMAIN (req_id->length - (ptr - req_id->dat)) ret = RD_AP_MODIFIED; len = krb4int_strnlen((char *)ptr, REQID_REMAIN) + 1; if (len <= 0 || len > ANAME_SZ) goto cleanup; memcpy(r_aname, ptr, (size_t)len); /* Authentication name */ ptr += len; len = krb4int_strnlen((char *)ptr, REQID_REMAIN) + 1; if (len <= 0 || len > INST_SZ) goto cleanup; memcpy(r_inst, ptr, (size_t)len); /* Authentication instance */ ptr += len; len = krb4int_strnlen((char *)ptr, REQID_REMAIN) + 1; if (len <= 0 || len > REALM_SZ) goto cleanup; memcpy(r_realm, ptr, (size_t)len); /* Authentication name */ ptr += len; if (REQID_REMAIN < 4 + 1 + 4) goto cleanup; KRB4_GET32(ad->checksum, ptr, le); r_time_ms = *ptr++; /* Time (fine) */ #ifdef lint /* XXX r_time_ms is set but not used. why??? */ /* this is a crock to get lint to shut up */ if (r_time_ms) r_time_ms = 0; #endif /* lint */ /* Time (coarse) */ KRB4_GET32(r_time_sec, ptr, le); /* Check for authenticity of the request */ #ifdef KRB_CRYPT_DEBUG if (krb_ap_req_debug) log("Pname: %s %s",ad->pname,r_aname); #endif ret = RD_AP_INCON; if (strcmp(ad->pname,r_aname) != 0) goto cleanup; if (strcmp(ad->pinst,r_inst) != 0) goto cleanup; #ifdef KRB_CRYPT_DEBUG if (krb_ap_req_debug) log("Realm: %s %s",ad->prealm,r_realm); #endif if (strcmp(ad->prealm,r_realm) != 0) goto cleanup; /* check the time integrity of the msg */ ret = RD_AP_TIME; t_local = TIME_GMT_UNIXSEC; delta_t = t_local - r_time_sec; if (delta_t < 0) delta_t = -delta_t; /* Absolute value of difference */ if (delta_t > CLOCK_SKEW) { #ifdef KRB_CRYPT_DEBUG if (krb_ap_req_debug) log("Time out of range: %d - %d = %d", time_secs, r_time_sec, delta_t); #endif goto cleanup; } /* Now check for expiration of ticket */ ret = RD_AP_NYV; #ifdef KRB_CRYPT_DEBUG tkt_age = t_local - ad->time_sec; if (krb_ap_req_debug) log("Time: %d Issue Date: %d Diff: %d Life %x", time_secs, ad->time_sec, tkt_age, ad->life); #endif if (t_local < ad->time_sec) { if ((ad->time_sec - t_local) > CLOCK_SKEW) goto cleanup; } else if (krb_life_to_time((KRB4_32)ad->time_sec, ad->life) < t_local + CLOCK_SKEW) { /* * This calculation is different than the same expiration * calculation in krb5. In krb5 the ticket lasts for * clock_skew seconds longer than its expiration; in krb4 it * lasts clock_skew seconds less. This difference is * necessary to avoid using an almost expired tgt to get a new * tgt that will last for another 5 minutes. This code * interacts with the login in src/kdc/kerberos_v4.c to * back-date tickets to avoid them expiring late. The * combination may be overly conservative, but I'm fairly sure * either removing the kerberos_v4 backdating or replacing * this check with the krb5 check is sufficient to create a * security problem. */ ret = RD_AP_EXP; goto cleanup; } #ifdef KRB_CRYPT_DEBUG if (krb_ap_req_debug) log("Address: %d %d",ad->address,from_addr); #endif if (!krb_ignore_ip_address && from_addr && (ad->address != from_addr)) { ret = RD_AP_BADD; goto cleanup; } /* All seems OK */ ad->reply.length = 0; ret = 0; cleanup: if (ret) { /* Stomp on session key if there is an error. */ memset(ad->session, 0, sizeof(ad->session)); return ret; } return RD_AP_OK; } int KRB5_CALLCONV krb_rd_req_int(authent, service, instance, from_addr, ad, key) KTEXT authent; /* The received message */ char *service; /* Service name */ char *instance; /* Service instance */ KRB_UINT32 from_addr; /* Net address of originating host */ AUTH_DAT *ad; /* Structure to be filled in */ C_Block key; /* Key to decrypt ticket with */ { Key_schedule ks; int ret; do { ret = des_key_sched(key, ks); if (ret) break; ret = krb_rd_req_with_key(authent, service, instance, from_addr, ad, ks, NULL); } while (0); memset(ks, 0, sizeof(ks)); return ret; } int KRB5_CALLCONV krb_rd_req(authent, service, instance, from_addr, ad, fn) register KTEXT authent; /* The received message */ char *service; /* Service name */ char *instance; /* Service instance */ unsigned KRB4_32 from_addr; /* Net address of originating host */ AUTH_DAT *ad; /* Structure to be filled in */ char *fn; /* Filename to get keys from */ { unsigned char *ptr; unsigned char s_kvno; char realm[REALM_SZ]; unsigned char skey[KKEY_SZ]; #ifdef KRB4_USE_KEYTAB krb5_keyblock keyblock; #endif int len; int status; #define AUTHENT_REMAIN (authent->length - (ptr - authent->dat)) if (authent->length < 3) return RD_AP_MODIFIED; ptr = authent->dat + 2; s_kvno = *ptr++; /* get server key version */ len = krb4int_strnlen((char *)ptr, AUTHENT_REMAIN) + 1; if (len <= 0 || len > sizeof(realm)) return RD_AP_MODIFIED; (void)memcpy(realm, ptr, (size_t)len); #undef AUTHENT_REMAIN /* * If "fn" is NULL, key info should already be set; don't * bother with ticket file. Otherwise, check to see if we * already have key info for the given server and key version * (saved in the static st_* variables). If not, go get it * from the ticket file. If "fn" is the null string, use the * default ticket file. */ if (fn && (strcmp(st_nam,service) || strcmp(st_inst,instance) || strcmp(st_rlm,realm) || (st_kvno != s_kvno))) { if (*fn == 0) fn = KEYFILE; st_kvno = s_kvno; if (read_service_key(service,instance,realm, (int)s_kvno, fn, (char *)skey) == 0) { if ((status = krb_set_key((char *)skey,0))) return(status); #ifdef KRB4_USE_KEYTAB } else if (krb54_get_service_keyblock(service, instance, realm, (int)s_kvno, fn, &keyblock) == 0) { krb_set_key_krb5(krb5__krb4_context, &keyblock); krb5_free_keyblock_contents(krb5__krb4_context, &keyblock); #endif } else return RD_AP_UNDEC; len = krb4int_strnlen(realm, sizeof(st_rlm)) + 1; if (len <= 0) return KFAILURE; memcpy(st_rlm, realm, (size_t)len); len = krb4int_strnlen(service, sizeof(st_nam)) + 1; if (len <= 0) return KFAILURE; memcpy(st_nam, service, (size_t)len); len = krb4int_strnlen(instance, sizeof(st_inst)) + 1; if (len <= 0) return KFAILURE; memcpy(st_inst, instance, (size_t)len); } return krb_rd_req_with_key(authent, service, instance, from_addr, ad, krb5_key ? NULL : serv_key, krb5_key ? &srv_k5key : NULL); }