NetBSD/crypto/dist/krb4/lib/rxkad/rxk_serv.c
2000-12-29 01:42:08 +00:00

567 lines
14 KiB
C

/*
* Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "rxkad_locl.h"
#if defined(KRB5)
#include <krb5.h>
#endif
RCSID("$Id: rxk_serv.c,v 1.1.1.2 2000/12/29 01:43:22 assar Exp $");
static inline
unsigned int
FT_Time()
{
struct timeval tv;
FT_GetTimeOfDay(&tv, 0);
return tv.tv_sec;
}
/* Security object specific server data */
typedef struct rxkad_serv_class {
struct rx_securityClass klass;
rxkad_level min_level;
void *appl_data;
int (*get_key)(void *appl_data, int kvno, des_cblock *key);
int (*user_ok)(char *name, char *inst, char *realm, int kvno);
} rxkad_serv_class;
static
int
server_NewConnection(struct rx_securityClass *obj, struct rx_connection *con)
{
assert(con->securityData == 0);
obj->refCount++;
con->securityData = (char *) osi_Alloc(sizeof(serv_con_data));
memset(con->securityData, 0x0, sizeof(serv_con_data));
return 0;
}
static
int
server_Close(struct rx_securityClass *obj)
{
obj->refCount--;
if (obj->refCount <= 0)
osi_Free(obj, sizeof(rxkad_serv_class));
return 0;
}
static
int
server_DestroyConnection(struct rx_securityClass *obj,
struct rx_connection *con)
{
serv_con_data *cdat = (serv_con_data *)con->securityData;
if (cdat)
{
if (cdat->user)
osi_Free(cdat->user, sizeof(krb_principal));
osi_Free(cdat, sizeof(serv_con_data));
}
return server_Close(obj);
}
/*
* Check whether a connection authenticated properly.
* Zero is good (authentication succeeded).
*/
static
int
server_CheckAuthentication(struct rx_securityClass *obj,
struct rx_connection *con)
{
serv_con_data *cdat = (serv_con_data *) con->securityData;
if (cdat)
return !cdat->authenticated;
else
return RXKADNOAUTH;
}
/*
* Select a nonce for later use.
*/
static
int
server_CreateChallenge(struct rx_securityClass *obj_,
struct rx_connection *con)
{
rxkad_serv_class *obj = (rxkad_serv_class *) obj_;
serv_con_data *cdat = (serv_con_data *) con->securityData;
union {
u_int32 rnd[2];
des_cblock k;
} u;
/* Any good random numbers will do, no real need to use
* cryptographic techniques here */
des_random_key(u.k);
cdat->nonce = u.rnd[0] ^ u.rnd[1];
cdat->authenticated = 0;
cdat->cur_level = obj->min_level;
return 0;
}
/*
* Wrap the nonce in a challenge packet.
*/
static
int
server_GetChallenge(const struct rx_securityClass *obj,
const struct rx_connection *con,
struct rx_packet *pkt)
{
serv_con_data *cdat = (serv_con_data *) con->securityData;
rxkad_challenge c;
/* Make challenge */
c.version = htonl(RXKAD_VERSION);
c.nonce = htonl(cdat->nonce);
c.min_level = htonl((int32)cdat->cur_level);
c.unused = 0; /* Use this to hint client we understand krb5 tickets??? */
/* Stuff into packet */
if (rx_SlowWritePacket(pkt, 0, sizeof(c), &c) != sizeof(c))
return RXKADPACKETSHORT;
rx_SetDataSize(pkt, sizeof(c));
return 0;
}
static
int
decode_krb5_ticket(rxkad_serv_class *obj,
int serv_kvno,
char *ticket,
int32 ticket_len,
/* OUT parms */
des_cblock session_key,
u_int32 *expires,
krb_principal *p)
{
#if !defined(KRB5)
return RXKADBADTICKET;
#else
des_cblock serv_key; /* Service's secret key */
krb5_keyblock key; /* Uses serv_key above */
int code;
size_t siz;
Ticket t5; /* Must free */
EncTicketPart decr_part; /* Must free */
krb5_context context; /* Must free */
krb5_data plain; /* Must free */
memset(&t5, 0x0, sizeof(t5));
memset(&decr_part, 0x0, sizeof(decr_part));
krb5_init_context(&context);
krb5_data_zero(&plain);
assert(serv_kvno == RXKAD_TKT_TYPE_KERBEROS_V5);
code = decode_Ticket(ticket, ticket_len, &t5, &siz);
if (code != 0)
goto bad_ticket;
/* Find the real service key version number */
serv_kvno = t5.tkt_vno;
/* Check that the key type really fit into 8 bytes */
switch (t5.enc_part.etype) {
case ETYPE_DES_CBC_CRC:
case ETYPE_DES_CBC_MD4:
case ETYPE_DES_CBC_MD5:
key.keytype = KEYTYPE_DES;
key.keyvalue.length = 8;
key.keyvalue.data = serv_key;
break;
default:
goto unknown_key;
}
/* Get the service key. We have to assume that the key type is of
* size 8 bytes or else we can't store service keys for both krb4
* and krb5 in the same way in /usr/afs/etc/KeyFile.
*/
code = (*obj->get_key)(obj->appl_data, serv_kvno, &serv_key);
if (code)
goto unknown_key;
/* Decrypt ticket */
code = krb5_decrypt(context,
t5.enc_part.cipher.data,
t5.enc_part.cipher.length,
t5.enc_part.etype,
&key,
&plain);
if (code != 0)
goto bad_ticket;
/* Decode ticket */
code = decode_EncTicketPart(plain.data, plain.length, &decr_part, &siz);
if (code != 0)
goto bad_ticket;
/* Extract realm and principal */
memset(p, 0x0, sizeof(p));
strlcpy(p->realm, decr_part.crealm, REALM_SZ);
switch (decr_part.cname.name_string.len) {
case 2:
strlcpy(p->instance,
decr_part.cname.name_string.val[1],
ANAME_SZ);
case 1:
strlcpy(p->name,
decr_part.cname.name_string.val[0],
INST_SZ);
break;
default:
goto bad_ticket;
}
/* Extract session key */
memcpy(session_key, decr_part.key.keyvalue.data, 8);
/* Check lifetimes and host addresses, flags etc */
{
time_t now = FT_Time(); /* Use fast time package but not approx time */
time_t start = decr_part.authtime;
if (decr_part.starttime)
start = *decr_part.starttime;
if (start - now > context->max_skew || decr_part.flags.invalid)
goto no_auth;
if (now > decr_part.endtime)
goto tkt_expired;
*expires = decr_part.endtime;
}
#if 0
/* Check host addresses */
#endif
cleanup:
free_Ticket(&t5);
free_EncTicketPart(&decr_part);
krb5_free_context(context);
krb5_data_free(&plain);
return code;
unknown_key:
code = RXKADUNKNOWNKEY;
goto cleanup;
no_auth:
code = RXKADNOAUTH;
goto cleanup;
tkt_expired:
code = RXKADEXPIRED;
goto cleanup;
bad_ticket:
code = RXKADBADTICKET;
goto cleanup;
#endif /* KRB5 */
}
static
int
decode_krb4_ticket(rxkad_serv_class *obj,
int serv_kvno,
char *ticket,
int32 ticket_len,
/* OUT parms */
des_cblock session_key,
u_int32 *expires,
krb_principal *p)
{
u_char kflags;
int klife;
u_int32 start;
u_int32 paddress;
char sname[SNAME_SZ], sinstance[INST_SZ];
KTEXT_ST tkt;
des_cblock serv_key; /* Service's secret key */
des_key_schedule serv_sched; /* Service's schedule */
/* First get service key */
int code = (*obj->get_key)(obj->appl_data, serv_kvno, &serv_key);
if (code)
return RXKADUNKNOWNKEY;
des_key_sched(&serv_key, serv_sched);
tkt.length = ticket_len;
memcpy(tkt.dat, ticket, ticket_len);
code = decomp_ticket(&tkt, &kflags,
p->name, p->instance, p->realm, &paddress,
session_key, &klife, &start,
sname, sinstance,
&serv_key, serv_sched);
if (code != KSUCCESS)
return RXKADBADTICKET;
#if 0
if (paddress != ntohl(con->peer->host))
return RXKADBADTICKET;
#endif
{
time_t end = krb_life_to_time(start, klife);
time_t now = FT_Time(); /* Use fast time package but not approx time */
start -= CLOCK_SKEW;
if (now < start)
return RXKADNOAUTH;
else if (now > end)
return RXKADEXPIRED;
*expires = end;
}
return 0; /* Success */
}
/*
* Process a response to a challange.
*/
static
int
server_CheckResponse(struct rx_securityClass *obj_,
struct rx_connection *con,
struct rx_packet *pkt)
{
rxkad_serv_class *obj = (rxkad_serv_class *) obj_;
serv_con_data *cdat = (serv_con_data *) con->securityData;
int serv_kvno; /* Service's kvno we used */
int32 ticket_len;
char ticket[MAXKRB5TICKETLEN];
int code;
rxkad_response r;
krb_principal p;
u_int32 cksum;
if (rx_SlowReadPacket(pkt, 0, sizeof(r), &r) != sizeof(r))
return RXKADPACKETSHORT;
serv_kvno = ntohl(r.kvno);
ticket_len = ntohl(r.ticket_len);
if (ticket_len > MAXKRB5TICKETLEN)
return RXKADTICKETLEN;
if (rx_SlowReadPacket(pkt, sizeof(r), ticket_len, ticket) != ticket_len)
return RXKADPACKETSHORT;
/* Disassemble kerberos ticket */
if (serv_kvno == RXKAD_TKT_TYPE_KERBEROS_V5)
code = decode_krb5_ticket(obj, serv_kvno, ticket, ticket_len,
cdat->k.key, &cdat->expires, &p);
else
code = decode_krb4_ticket(obj, serv_kvno, ticket, ticket_len,
cdat->k.key, &cdat->expires, &p);
if (code != 0)
return code;
fc_keysched(cdat->k.key, cdat->k.keysched);
/* Unseal r.encrypted */
fc_cbc_enc2(&r.encrypted, &r.encrypted, sizeof(r.encrypted),
cdat->k.keysched, (u_int32*)cdat->k.key, DECRYPT);
/* Verify response integrity */
cksum = r.encrypted.cksum;
r.encrypted.cksum = 0;
if (r.encrypted.epoch != ntohl(con->epoch)
|| r.encrypted.cid != ntohl(con->cid & RX_CIDMASK)
|| r.encrypted.security_index != ntohl(con->securityIndex)
|| cksum != rxkad_cksum_response(&r))
return RXKADSEALEDINCON;
{
int i;
for (i = 0; i < RX_MAXCALLS; i++)
{
r.encrypted.call_numbers[i] = ntohl(r.encrypted.call_numbers[i]);
if (r.encrypted.call_numbers[i] < 0)
return RXKADSEALEDINCON;
}
}
if (ntohl(r.encrypted.inc_nonce) != cdat->nonce+1)
return RXKADOUTOFSEQUENCE;
{
int level = ntohl(r.encrypted.level);
if ((level < cdat->cur_level) || (level > rxkad_crypt))
return RXKADLEVELFAIL;
cdat->cur_level = level;
/* We don't use trailers but the transarc implementation breaks if
* we don't set the trailer size, packets get to large */
if (level == rxkad_auth)
{
rx_SetSecurityHeaderSize(con, 4);
rx_SetSecurityMaxTrailerSize(con, 4);
}
else if (level == rxkad_crypt)
{
rx_SetSecurityHeaderSize(con, 8);
rx_SetSecurityMaxTrailerSize(con, 8);
}
}
rxi_SetCallNumberVector(con, r.encrypted.call_numbers);
rxkad_calc_header_iv(con, cdat->k.keysched,
(const des_cblock *)&cdat->k.key, cdat->e.header_iv);
cdat->authenticated = 1;
if (obj->user_ok)
{
code = obj->user_ok(p.name, p.instance, p.realm, serv_kvno);
if (code)
return RXKADNOAUTH;
}
else
{
krb_principal *user = (krb_principal *) osi_Alloc(sizeof(krb_principal));
*user = p;
cdat->user = user;
}
return 0;
}
/*
* Checksum and/or encrypt packet
*/
static
int
server_PreparePacket(struct rx_securityClass *obj_,
struct rx_call *call,
struct rx_packet *pkt)
{
struct rx_connection *con = rx_ConnectionOf(call);
serv_con_data *cdat = (serv_con_data *) con->securityData;
key_stuff *k = &cdat->k;
end_stuff *e = &cdat->e;
return rxkad_prepare_packet(pkt, con, cdat->cur_level, k, e);
}
/*
* Verify checksum and/or decrypt packet.
*/
static
int
server_CheckPacket(struct rx_securityClass *obj_,
struct rx_call *call,
struct rx_packet *pkt)
{
struct rx_connection *con = rx_ConnectionOf(call);
serv_con_data *cdat = (serv_con_data *) con->securityData;
key_stuff *k = &cdat->k;
end_stuff *e = &cdat->e;
if (FT_ApproxTime() > cdat->expires) /* Use fast approx time */
return RXKADEXPIRED;
return rxkad_check_packet(pkt, con, cdat->cur_level, k, e);
}
static
int
server_GetStats(const struct rx_securityClass *obj_,
const struct rx_connection *con,
struct rx_securityObjectStats *st)
{
rxkad_serv_class *obj = (rxkad_serv_class *) obj_;
serv_con_data *cdat = (serv_con_data *) con->securityData;
st->type = rxkad_disipline;
st->level = obj->min_level;
st->flags = rxkad_checksummed;
if (cdat == 0)
st->flags |= rxkad_unallocated;
else
{
st->bytesReceived = cdat->e.bytesReceived;
st->packetsReceived = cdat->e.packetsReceived;
st->bytesSent = cdat->e.bytesSent;
st->packetsSent = cdat->e.packetsSent;
st->expires = cdat->expires;
st->level = cdat->cur_level;
if (cdat->authenticated)
st->flags |= rxkad_authenticated;
}
return 0;
}
static struct rx_securityOps server_ops = {
server_Close,
server_NewConnection,
server_PreparePacket,
0,
server_CheckAuthentication,
server_CreateChallenge,
server_GetChallenge,
0,
server_CheckResponse,
server_CheckPacket,
server_DestroyConnection,
server_GetStats,
};
struct rx_securityClass *
rxkad_NewServerSecurityObject(/*rxkad_level*/ int min_level,
void *appl_data,
int (*get_key)(void *appl_data,
int kvno,
des_cblock *key),
int (*user_ok)(char *name,
char *inst,
char *realm,
int kvno))
{
rxkad_serv_class *obj;
if (!get_key)
return 0;
obj = (rxkad_serv_class *) osi_Alloc(sizeof(rxkad_serv_class));
obj->klass.refCount = 1;
obj->klass.ops = &server_ops;
obj->klass.privateData = (char *) obj;
obj->min_level = min_level;
obj->appl_data = appl_data;
obj->get_key = get_key;
obj->user_ok = user_ok;
return &obj->klass;
}