/* * 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 #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; }