*** empty log message ***
[arla.git] / rxkad / rxk_serv.c
blob0fd9dec63e3eee3106f3aa20ec62240e1e217eec
1 /*
2 * Copyright (c) 1995-1997, 2003-2006 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include "rxkad_locl.h"
36 RCSID("$Id$");
38 #ifdef HAVE_KRB5
39 #include <krb5.h>
40 #endif
42 #undef HAVE_KRB4
44 #ifdef HAVE_KRB4
45 #include <krb.h>
46 #else
47 #include <krb4.h>
48 #endif
50 #ifndef KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS
51 #undef HAVE_KRB5 /* no heimdal, no kerberos support */
52 #endif
54 #ifdef HAVE_OPENSSL
55 #include <openssl/rand.h>
56 #endif
58 /* Security object specific server data */
59 typedef struct rxkad_serv_class {
60 struct rx_securityClass klass;
61 rxkad_level min_level;
62 void *appl_data;
63 int (*get_key)(void *appl_data, int kvno, struct ktc_encryptionKey *key);
64 int (*user_ok)(char *name, int kvno);
65 } rxkad_serv_class;
67 static
68 int
69 server_NewConnection(struct rx_securityClass *obj, struct rx_connection *con)
71 assert(con->securityData == 0);
72 obj->refCount++;
73 con->securityData = (char *) osi_Alloc(sizeof(serv_con_data));
74 memset(con->securityData, 0x0, sizeof(serv_con_data));
75 return 0;
78 static
79 int
80 server_Close(struct rx_securityClass *obj)
82 obj->refCount--;
83 if (obj->refCount <= 0)
84 osi_Free(obj, sizeof(rxkad_serv_class));
85 return 0;
88 static
89 int
90 server_DestroyConnection(struct rx_securityClass *obj,
91 struct rx_connection *con)
93 serv_con_data *cdat = (serv_con_data *)con->securityData;
95 if (cdat)
97 if (cdat->user)
98 free(cdat->user);
99 free(cdat);
101 return server_Close(obj);
105 * Check whether a connection authenticated properly.
106 * Zero is good (authentication succeeded).
108 static
110 server_CheckAuthentication(struct rx_securityClass *obj,
111 struct rx_connection *con)
113 serv_con_data *cdat = (serv_con_data *) con->securityData;
115 if (cdat)
116 return !cdat->authenticated;
117 else
118 return RXKADNOAUTH;
122 * Select a nonce for later use.
124 static
126 server_CreateChallenge(struct rx_securityClass *obj_,
127 struct rx_connection *con)
129 rxkad_serv_class *obj = (rxkad_serv_class *) obj_;
130 serv_con_data *cdat = (serv_con_data *) con->securityData;
132 /* Any good random numbers will do, no real need to use
133 * cryptographic techniques here */
135 #ifdef HAVE_OPENSSL
136 uint32_t rnd[2];
137 RAND_pseudo_bytes((void *)rnd, sizeof(rnd));
138 cdat->nonce = rnd[0] ^ rnd[1];
139 #else
140 union {
141 uint32_t rnd[2];
142 des_cblock k;
143 } u;
144 des_random_key(&u.k);
145 cdat->nonce = u.rnd[0] ^ u.rnd[1];
146 #endif
148 cdat->authenticated = 0;
149 cdat->cur_level = obj->min_level;
150 return 0;
154 * Wrap the nonce in a challenge packet.
156 static
158 server_GetChallenge(const struct rx_securityClass *obj,
159 const struct rx_connection *con,
160 struct rx_packet *pkt)
162 serv_con_data *cdat = (serv_con_data *) con->securityData;
163 rxkad_challenge c;
165 /* Make challenge */
166 c.version = htonl(RXKAD_VERSION);
167 c.nonce = htonl(cdat->nonce);
168 c.min_level = htonl((int32_t)cdat->cur_level);
169 c.unused = 0; /* Use this to hint client we understand krb5 tickets??? */
171 /* Stuff into packet */
172 if (rx_SlowWritePacket(pkt, 0, sizeof(c), &c) != sizeof(c))
173 return RXKADPACKETSHORT;
174 rx_SetDataSize(pkt, sizeof(c));
175 return 0;
178 static
180 decode_krb5_ticket(rxkad_serv_class *obj,
181 int serv_kvno,
182 void *ticket,
183 int32_t ticket_len,
184 /* OUT parms */
185 struct ktc_encryptionKey *session_key,
186 uint32_t *expires,
187 char **user)
189 #ifndef HAVE_KRB5
190 return RXKADBADTICKET;
191 #else
192 struct ktc_encryptionKey serv_key; /* Service's secret key */
193 krb5_keyblock key; /* Uses serv_key above */
194 int code;
195 size_t siz;
196 krb5_principal principal;
197 krb5_crypto crypto;
199 Ticket t5; /* Must free */
200 EncTicketPart decr_part; /* Must free */
201 krb5_context context; /* Must free */
202 krb5_data plain; /* Must free */
204 memset(&t5, 0x0, sizeof(t5));
205 memset(&decr_part, 0x0, sizeof(decr_part));
206 krb5_init_context(&context);
207 krb5_data_zero(&plain);
209 assert(serv_kvno == RXKAD_TKT_TYPE_KERBEROS_V5 ||
210 serv_kvno == RXKAD_TKT_TYPE_KERBEROS_V5_ENCPART_ONLY);
212 if (serv_kvno == RXKAD_TKT_TYPE_KERBEROS_V5_ENCPART_ONLY) {
213 code = decode_EncryptedData(ticket, ticket_len, &t5.enc_part, &siz);
214 if (code != 0)
215 goto bad_ticket;
217 serv_kvno = 0;
218 } else {
219 code = decode_Ticket(ticket, ticket_len, &t5, &siz);
220 if (code != 0)
221 goto bad_ticket;
223 serv_kvno = t5.tkt_vno;
226 /* Check that the key type really fit into 8 bytes */
227 switch (t5.enc_part.etype) {
228 case ETYPE_DES_CBC_CRC:
229 case ETYPE_DES_CBC_MD4:
230 case ETYPE_DES_CBC_MD5:
231 key.keytype = t5.enc_part.etype;
232 key.keyvalue.length = 8;
233 key.keyvalue.data = serv_key.data;
234 break;
235 default:
236 goto unknown_key;
239 /* Get the service key. We have to assume that the key type is of
240 * size 8 bytes or else we can't store service keys for both krb4
241 * and krb5 in the same way in /usr/afs/etc/KeyFile.
243 code = (*obj->get_key)(obj->appl_data, serv_kvno, &serv_key);
244 if (code)
245 goto unknown_key;
247 code = krb5_crypto_init(context, &key, 0, &crypto);
248 if (code)
249 goto unknown_key;
251 /* Decrypt ticket */
252 code = krb5_decrypt(context,
253 crypto,
255 t5.enc_part.cipher.data,
256 t5.enc_part.cipher.length,
257 &plain);
258 krb5_crypto_destroy(context, crypto);
259 if (code != 0)
260 goto bad_ticket;
262 /* Decode ticket */
263 code = decode_EncTicketPart(plain.data, plain.length, &decr_part, &siz);
264 if (code != 0)
265 goto bad_ticket;
267 /* Check that the key type really fit into 8 bytes */
268 switch (decr_part.key.keytype) {
269 case ETYPE_DES_CBC_CRC:
270 case ETYPE_DES_CBC_MD4:
271 case ETYPE_DES_CBC_MD5:
272 break;
273 default:
274 goto unknown_key;
278 principal = calloc(1, sizeof(*principal));
279 if (principal == NULL) {
280 code = ENOMEM;
281 goto cleanup;
283 code = copy_PrincipalName(&decr_part.cname, &principal->name);
284 if (code)
285 goto cleanup;
286 principal->realm = strdup(decr_part.crealm);
287 if (principal->realm == NULL) {
288 code = ENOMEM;
289 goto cleanup;
293 /* Extract realm and principal */
294 code = krb5_unparse_name(context, principal, user);
295 krb5_free_principal(context, principal);
296 if (code)
297 goto cleanup;
299 /* Extract session key */
300 memcpy(session_key->data, decr_part.key.keyvalue.data, 8);
302 /* Check lifetimes and host addresses, flags etc */
304 time_t now = time(0); /* Use fast time package instead??? */
305 time_t start = decr_part.authtime;
306 time_t end = decr_part.endtime;
308 if (decr_part.starttime)
309 start = *decr_part.starttime;
311 if (start >= end
312 || start > now + KTC_TIME_UNCERTAINTY
313 || decr_part.flags.invalid)
314 goto no_auth;
316 if (now > end + KTC_TIME_UNCERTAINTY)
317 goto tkt_expired;
319 *expires = end;
322 code = 0;
324 #if 0
325 /* Check host addresses */
326 #endif
328 cleanup:
329 if (code) {
330 free(*user);
331 *user = NULL;
333 free_Ticket(&t5);
334 free_EncTicketPart(&decr_part);
335 krb5_free_context(context);
336 krb5_data_free(&plain);
337 return code;
339 unknown_key:
340 code = RXKADUNKNOWNKEY;
341 goto cleanup;
342 no_auth:
343 code = RXKADNOAUTH;
344 goto cleanup;
345 tkt_expired:
346 code = RXKADEXPIRED;
347 goto cleanup;
348 bad_ticket:
349 code = RXKADBADTICKET;
350 goto cleanup;
351 #endif /* HAVE_KRB5 */
354 static
356 decode_krb4_ticket(rxkad_serv_class *obj,
357 int serv_kvno,
358 void *ticket,
359 int32_t ticket_len,
360 /* OUT parms */
361 struct ktc_encryptionKey *session_key,
362 uint32_t *expires,
363 char **user)
365 #if !defined(HAVE_KRB4) && !defined(HAVE_KRB5)
366 return RXKADBADTICKET;
367 #else
368 u_char kflags;
369 int klife;
370 uint32_t start;
371 uint32_t paddress;
372 char sname[SNAME_SZ], sinstance[INST_SZ];
373 char name[ANAME_SZ], instance[INST_SZ], realm[REALM_SZ];
374 KTEXT_ST tkt;
375 struct ktc_encryptionKey serv_key; /* Service's secret key */
376 DES_cblock key;
377 DES_key_schedule serv_sched; /* Service's schedule */
379 /* First get service key */
380 int code = (*obj->get_key)(obj->appl_data, serv_kvno, &serv_key);
381 if (code)
382 return RXKADUNKNOWNKEY;
384 memcpy(&key, serv_key.data, sizeof(key));
385 DES_key_sched(&key, &serv_sched);
386 tkt.length = ticket_len;
387 memcpy(tkt.dat, ticket, ticket_len);
388 code = decomp_ticket(&tkt, &kflags,
389 name, instance, realm, &paddress,
390 (void*)session_key->data, &klife, &start,
391 sname, sinstance,
392 (DES_cblock *)&serv_key, &serv_sched);
393 if (code != KSUCCESS)
394 return RXKADBADTICKET;
396 #if 0
397 if (paddress != ntohl(con->peer->host))
398 return RXKADBADTICKET;
399 #endif
402 #ifndef HAVE_KRB4
403 time_t end = krb4_life_to_time(start, klife);
404 #else
405 time_t end = krb_life_to_time(start, klife);
406 #endif
407 time_t now = time(0);
408 if (start > CLOCK_SKEW) /* Transarc sends 0 as start if localauth */
409 start -= CLOCK_SKEW;
410 if (now < start)
411 return RXKADNOAUTH;
412 else if (now > end)
413 return RXKADEXPIRED;
414 *expires = end;
416 asprintf(user, "%s%s%s@%s",
417 name,
418 instance[0] ? "." : "",
419 instance[0] ? instance : "",
420 realm);
421 if (*user == NULL)
422 return ENOMEM;
423 return 0; /* Success */
424 #endif /* HAVE_KRB4 */
428 * Process a response to a challange.
430 static
432 server_CheckResponse(struct rx_securityClass *obj_,
433 struct rx_connection *con,
434 struct rx_packet *pkt)
436 rxkad_serv_class *obj = (rxkad_serv_class *) obj_;
437 serv_con_data *cdat = (serv_con_data *) con->securityData;
439 int serv_kvno; /* Service's kvno we used */
440 int32_t ticket_len;
441 char ticket[MAXKRB5TICKETLEN];
442 int code;
443 rxkad_response r;
444 char *user = NULL;
445 uint32_t cksum;
447 if (rx_SlowReadPacket(pkt, 0, sizeof(r), &r) != sizeof(r))
448 return RXKADPACKETSHORT;
450 serv_kvno = ntohl(r.kvno);
451 ticket_len = ntohl(r.ticket_len);
453 if (ticket_len > MAXKRB5TICKETLEN)
454 return RXKADTICKETLEN;
456 if (rx_SlowReadPacket(pkt, sizeof(r), ticket_len, ticket) != ticket_len)
457 return RXKADPACKETSHORT;
459 /* Disassemble kerberos ticket */
460 if (serv_kvno == RXKAD_TKT_TYPE_KERBEROS_V5 ||
461 serv_kvno == RXKAD_TKT_TYPE_KERBEROS_V5_ENCPART_ONLY)
462 code = decode_krb5_ticket(obj, serv_kvno, ticket, ticket_len,
463 &cdat->k.key, &cdat->expires, &user);
464 else
465 code = decode_krb4_ticket(obj, serv_kvno, ticket, ticket_len,
466 &cdat->k.key, &cdat->expires, &user);
467 if (code != 0)
468 return code;
470 fc_keysched(cdat->k.key.data, cdat->k.keysched);
472 /* Unseal r.encrypted */
473 fc_cbc_enc2(&r.encrypted, &r.encrypted, sizeof(r.encrypted),
474 cdat->k.keysched, (uint32_t*)cdat->k.key.data, FC_DECRYPT);
476 /* Verify response integrity */
477 cksum = r.encrypted.cksum;
478 r.encrypted.cksum = 0;
479 if (r.encrypted.epoch != ntohl(con->epoch)
480 || r.encrypted.cid != ntohl(con->cid & RX_CIDMASK)
481 || r.encrypted.security_index != ntohl(con->securityIndex)
482 || cksum != rxkad_cksum_response(&r)) {
483 free(user);
484 return RXKADSEALEDINCON;
487 int i;
488 for (i = 0; i < RX_MAXCALLS; i++)
490 r.encrypted.call_numbers[i] = ntohl(r.encrypted.call_numbers[i]);
491 if (r.encrypted.call_numbers[i] < 0) {
492 free(user);
493 return RXKADSEALEDINCON;
498 if (ntohl(r.encrypted.inc_nonce) != cdat->nonce+1) {
499 free(user);
500 return RXKADOUTOFSEQUENCE;
504 int level = ntohl(r.encrypted.level);
505 if ((level < cdat->cur_level) || (level > rxkad_crypt)) {
506 free(user);
507 return RXKADLEVELFAIL;
509 cdat->cur_level = level;
510 /* We don't use trailers but the transarc implementation breaks if
511 * we don't set the trailer size, packets get to large */
512 if (level == rxkad_auth)
514 rx_SetSecurityHeaderSize(con, 4);
515 rx_SetSecurityMaxTrailerSize(con, 4);
517 else if (level == rxkad_crypt)
519 rx_SetSecurityHeaderSize(con, 8);
520 rx_SetSecurityMaxTrailerSize(con, 8);
524 rxi_SetCallNumberVector(con, r.encrypted.call_numbers);
526 rxkad_calc_header_iv(con, cdat->k.keysched,
527 (void *)&cdat->k.key, cdat->e.header_iv);
528 cdat->authenticated = 1;
530 cdat->user = user;
531 if (obj->user_ok)
533 code = obj->user_ok(user, serv_kvno);
534 if (code)
535 return RXKADNOAUTH;
538 return 0;
542 * Checksum and/or encrypt packet
544 static
546 server_PreparePacket(struct rx_securityClass *obj_,
547 struct rx_call *call,
548 struct rx_packet *pkt)
550 struct rx_connection *con = rx_ConnectionOf(call);
551 serv_con_data *cdat = (serv_con_data *) con->securityData;
552 key_stuff *k = &cdat->k;
553 end_stuff *e = &cdat->e;
555 return rxkad_prepare_packet(pkt, con, cdat->cur_level, k, e);
559 * Verify checksum and/or decrypt packet.
561 static
563 server_CheckPacket(struct rx_securityClass *obj_,
564 struct rx_call *call,
565 struct rx_packet *pkt)
567 struct rx_connection *con = rx_ConnectionOf(call);
568 serv_con_data *cdat = (serv_con_data *) con->securityData;
569 key_stuff *k = &cdat->k;
570 end_stuff *e = &cdat->e;
572 if (time(0) > cdat->expires) /* Use fast time package instead??? */
573 return RXKADEXPIRED;
575 return rxkad_check_packet(pkt, con, cdat->cur_level, k, e);
578 static
580 server_GetStats(const struct rx_securityClass *obj_,
581 const struct rx_connection *con,
582 struct rx_securityObjectStats *st)
584 rxkad_serv_class *obj = (rxkad_serv_class *) obj_;
585 serv_con_data *cdat = (serv_con_data *) con->securityData;
587 st->type = rxkad_disipline;
588 st->level = obj->min_level;
589 st->flags = rxkad_checksummed;
590 if (cdat == 0)
591 st->flags |= rxkad_unallocated;
593 st->bytesReceived = cdat->e.bytesReceived;
594 st->packetsReceived = cdat->e.packetsReceived;
595 st->bytesSent = cdat->e.bytesSent;
596 st->packetsSent = cdat->e.packetsSent;
597 st->expires = cdat->expires;
598 st->level = cdat->cur_level;
599 if (cdat->authenticated)
600 st->flags |= rxkad_authenticated;
602 return 0;
605 static struct rx_securityOps server_ops = {
606 server_Close,
607 server_NewConnection,
608 server_PreparePacket,
610 server_CheckAuthentication,
611 server_CreateChallenge,
612 server_GetChallenge,
614 server_CheckResponse,
615 server_CheckPacket,
616 server_DestroyConnection,
617 server_GetStats,
620 struct rx_securityClass *
621 rxkad_NewServerSecurityObjectNew(/*rxkad_level*/ int min_level,
622 void *appl_data,
623 int (*get_key)(void *appl_data,
624 int kvno,
625 struct ktc_encryptionKey *key),
626 int (*user_ok)(char *name,
627 int kvno))
629 rxkad_serv_class *obj;
631 if (!get_key)
632 return 0;
634 obj = (rxkad_serv_class *) osi_Alloc(sizeof(rxkad_serv_class));
635 obj->klass.refCount = 1;
636 obj->klass.ops = &server_ops;
637 obj->klass.privateData = (char *) obj;
639 obj->min_level = min_level;
640 obj->appl_data = appl_data;
641 obj->get_key = get_key;
642 obj->user_ok = user_ok;
644 return &obj->klass;