1 /* This file is part of the Project Athena Zephyr Notification System.
2 * It contains functions for dealing with Kerberos functions in the server.
4 * Created by: John T Kohl
6 * Copyright (c) 1988 by the Massachusetts Institute of Technology.
7 * For copying and distribution information, see the file
11 * $Source: /afs/dev.mit.edu/source/repository/athena/lib/zephyr/server/kstuff.c,v $
12 * $Header: /afs/dev.mit.edu/source/repository/athena/lib/zephyr/server/kstuff.c,v 1.26 2001/04/10 23:28:20 ghudson Exp $
19 static const char rcsid_kstuff_c[] = "$Id: kstuff.c,v 1.26 2001/04/10 23:28:20 ghudson Exp $";
25 /* Keep a hash table mapping tickets to session keys, so we can do a fast
26 * check of the cryptographic checksum without doing and DES decryptions.
27 * Also remember the expiry time of the ticket, so that we can sweep the
28 * table periodically. */
30 #define HASHTAB_SIZE 4091
32 typedef struct hash_entry Hash_entry;
34 /* The ticket comes at the end, in a variable-length array. */
38 char srcprincipal[ANAME_SZ+INST_SZ+REALM_SZ+4];
41 unsigned char ticket[1];
44 Hash_entry *hashtab[HASHTAB_SIZE];
46 static int hash_ticket __P((unsigned char *, int));
47 static void add_session_key __P((KTEXT, C_Block, char *, time_t));
48 static int find_session_key __P((KTEXT, C_Block, char *));
49 static ZChecksum_t compute_checksum __P((ZNotice_t *, C_Block));
50 static ZChecksum_t compute_rlm_checksum __P((ZNotice_t *, C_Block));
55 * get ticket from file descriptor and decode it.
56 * Return KFAILURE if we barf on reading the ticket, else return
57 * the value of rd_ap_req() applied to the ticket.
60 GetKerberosData(fd, haddr, kdata, service, srvtab)
61 int fd; /* file descr. to read from */
62 struct in_addr haddr; /* address of foreign host on fd */
63 AUTH_DAT *kdata; /* kerberos data (returned) */
64 char *service; /* service principal desired */
65 char *srvtab; /* file to get keys from */
68 KTEXT_ST ticket; /* will get Kerberos ticket from client */
70 char instance[INST_SZ];
73 * Get the Kerberos ticket. The first few characters, terminated
74 * by a blank, should give us a length; then get than many chars
75 * which will be the ticket proper.
77 for (i=0; i<20; i++) {
78 if (read(fd, &p[i], 1) != 1) {
79 syslog(LOG_WARNING,"bad read tkt len");
87 ticket.length = atoi(p);
88 if ((i==20) || (ticket.length<=0) || (ticket.length>MAX_KTXT_LEN)) {
89 syslog(LOG_WARNING,"bad tkt len %d",ticket.length);
92 for (i=0; i<ticket.length; i++) {
93 if (read(fd, (caddr_t) &(ticket.dat[i]), 1) != 1) {
94 syslog(LOG_WARNING,"bad tkt read");
99 * now have the ticket. use it to get the authenticated
100 * data from Kerberos.
102 (void) strcpy(instance,"*"); /* let Kerberos fill it in */
104 return(krb_rd_req(&ticket, service, instance, haddr.s_addr,
105 kdata, srvtab ? srvtab : ""));
111 * create and transmit a ticket over the file descriptor for service.host
112 * return failure codes if appropriate, or 0 if we
113 * get the ticket and write it to the file descriptor
117 SendKerberosData(fd, ticket, service, host)
118 int fd; /* file descriptor to write onto */
119 KTEXT ticket; /* where to put ticket (return) */
120 char *service; /* service name, foreign host */
128 rem = krb_mk_req(ticket, service, host, ZGetRealm(), (u_long) 0);
130 return rem + krb_err_base;
132 (void) sprintf(p,"%d ",ticket->length);
133 size_to_write = strlen (p);
134 if ((written = write(fd, p, size_to_write)) != size_to_write)
135 return (written < 0) ? errno : ZSRV_PKSHORT;
136 if ((written = write(fd, (caddr_t) (ticket->dat), ticket->length))
138 return (written < 0) ? errno : ZSRV_PKSHORT;
143 #endif /* HAVE_KRB4 */
146 ZCheckRealmAuthentication(notice, from, realm)
148 struct sockaddr_in *from;
153 char rlmprincipal[ANAME_SZ+INST_SZ+REALM_SZ+4+1024];
154 krb5_principal princ;
158 krb5_error_code result;
159 krb5_principal server;
160 krb5_keytab keytabid = 0;
161 krb5_auth_context authctx;
162 krb5_keyblock *keyblock;
163 krb5_enctype enctype;
164 krb5_cksumtype cksumtype;
166 #if HAVE_KRB5_C_MAKE_CHECKSUM
167 krb5_checksum checksum;
170 krb5_crypto cryptctx;
174 char *cksum0_base, *cksum1_base, *cksum2_base;
175 char *svcinst, *x, *y;
176 char *asn1_data, *key_data;
177 int asn1_len, key_len, cksum0_len, cksum1_len, cksum2_len;
178 #ifdef KRB5_AUTH_CON_GETAUTHENTICATOR_TAKES_DOUBLE_POINTER
179 krb5_authenticator *authenticator;
180 #define KRB5AUTHENT authenticator
182 krb5_authenticator authenticator;
183 #define KRB5AUTHENT &authenticator
190 /* Check for bogus authentication data length. */
191 if (notice->z_authent_len <= 0)
194 len = strlen(notice->z_ascii_authent)+1;
197 /* Read in the authentication data. */
198 if (ZReadZcode(notice->z_ascii_authent,
200 len, &len) == ZERR_BADFIELD) {
204 (void) sprintf(rlmprincipal, "%s/%s@%s", SERVER_SERVICE,
205 SERVER_INSTANCE, realm);
208 packet.data = authbuf;
210 result = krb5_kt_resolve(Z_krb5_ctx,
211 keytab_file, &keytabid);
217 /* HOLDING: authbuf, keytabid */
218 /* Create the auth context */
219 result = krb5_auth_con_init(Z_krb5_ctx, &authctx);
221 krb5_kt_close(Z_krb5_ctx, keytabid);
226 /* HOLDING: authbuf, authctx */
227 result = krb5_build_principal(Z_krb5_ctx, &server, strlen(__Zephyr_realm),
228 __Zephyr_realm, SERVER_SERVICE,
229 SERVER_INSTANCE, NULL);
231 result = krb5_rd_req(Z_krb5_ctx, &authctx, &packet, server,
233 krb5_free_principal(Z_krb5_ctx, server);
235 krb5_kt_close(Z_krb5_ctx, keytabid);
238 if (result == KRB5KRB_AP_ERR_REPEAT)
239 syslog(LOG_DEBUG, "k5 auth failed: %s", error_message(result));
241 syslog(LOG_WARNING,"k5 auth failed: %s", error_message(result));
243 krb5_auth_con_free(Z_krb5_ctx, authctx);
247 /* HOLDING: authbuf, authctx */
248 #ifndef HAVE_KRB5_TICKET_ENC_PART2
249 if (tkt == 0 || tkt->client == 0) {
250 if (tkt) krb5_free_ticket(Z_krb5_ctx, tkt);
252 krb5_auth_con_free(Z_krb5_ctx, authctx);
257 if (tkt == 0 || tkt->enc_part2 == 0) {
258 if (tkt) krb5_free_ticket(Z_krb5_ctx, tkt);
260 krb5_auth_con_free(Z_krb5_ctx, authctx);
263 princ = tkt->enc_part2->client;
266 krb5_free_ticket(Z_krb5_ctx, tkt);
268 krb5_auth_con_free(Z_krb5_ctx, authctx);
272 /* HOLDING: authbuf, authctx, tkt */
273 result = krb5_unparse_name(Z_krb5_ctx, princ, &name);
275 syslog(LOG_WARNING, "k5 unparse_name failed: %s",
276 error_message(result));
278 krb5_auth_con_free(Z_krb5_ctx, authctx);
279 krb5_free_ticket(Z_krb5_ctx, tkt);
283 krb5_free_ticket(Z_krb5_ctx, tkt);
285 /* HOLDING: authbuf, authctx, name */
286 if (strcmp(name, rlmprincipal)) {
287 syslog(LOG_WARNING, "k5 name mismatch: '%s' vs '%s'",
289 krb5_auth_con_free(Z_krb5_ctx, authctx);
297 /* HOLDING: authctx */
298 /* Get an authenticator so we can get the keyblock */
299 result = krb5_auth_con_getauthenticator (Z_krb5_ctx, authctx,
302 krb5_auth_con_free(Z_krb5_ctx, authctx);
306 /* HOLDING: authctx, authenticator */
307 result = krb5_auth_con_getkey(Z_krb5_ctx, authctx, &keyblock);
309 krb5_auth_con_free(Z_krb5_ctx, authctx);
310 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
311 return (ZAUTH_FAILED);
314 /* HOLDING: authctx, authenticator, keyblock */
315 /* Figure out what checksum type to use */
316 #if HAVE_KRB5_CREDS_KEYBLOCK_ENCTYPE
317 key_data = keyblock->contents;
318 key_len = keyblock->length;
319 enctype = keyblock->enctype;
320 result = Z_krb5_lookup_cksumtype(enctype, &cksumtype);
322 krb5_free_keyblock(Z_krb5_ctx, keyblock);
323 krb5_auth_con_free(Z_krb5_ctx, authctx);
324 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
325 return (ZAUTH_FAILED);
328 key_data = keyblock->keyvalue.data;
329 key_len = keyblock->keyvalue.length;
335 result = krb5_keytype_to_enctypes(Z_krb5_ctx, keyblock->keytype,
338 krb5_free_keyblock(Z_krb5_ctx, keyblock);
339 krb5_auth_con_free(Z_krb5_ctx, authctx);
340 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
341 return (ZAUTH_FAILED);
346 result = Z_krb5_lookup_cksumtype(val[i], &cksumtype);
348 } while (result != 0);
351 krb5_free_keyblock(Z_krb5_ctx, keyblock);
352 krb5_auth_con_free(Z_krb5_ctx, authctx);
353 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
354 return (ZAUTH_FAILED);
359 /* HOLDING: authctx, authenticator, keyblock */
361 /* Assemble the things to be checksummed */
362 /* first part is from start of packet through z_default_format:
364 * - z_num_other_fields
378 cksum0_base = notice->z_packet;
379 x = notice->z_default_format;
380 cksum0_len = x + strlen(x) + 1 - cksum0_base;
381 /* second part is from z_multinotice through other fields:
386 cksum1_base = notice->z_multinotice;
387 if (notice->z_num_other_fields)
388 x = notice->z_other_fields[notice->z_num_other_fields];
390 x = cksum1_base + strlen(cksum1_base) + 1; /* multiuid */
391 cksum1_len = x + strlen(x) + 1 - cksum1_base;
393 /* last part is the message body */
394 cksum2_base = notice->z_message;
395 cksum2_len = notice->z_message_len;
397 if ((!notice->z_ascii_checksum || *notice->z_ascii_checksum != 'Z') &&
399 (enctype == ENCTYPE_DES_CBC_CRC ||
400 enctype == ENCTYPE_DES_CBC_MD4 ||
401 enctype == ENCTYPE_DES_CBC_MD5)) {
402 /* try old-format checksum (covers cksum0 only) */
404 ZChecksum_t our_checksum;
406 our_checksum = des_quad_cksum(cksum0_base, NULL, cksum0_len, 0,
408 if (our_checksum == notice->z_checksum) {
409 krb5_free_keyblock(Z_krb5_ctx, keyblock);
410 krb5_auth_con_free(Z_krb5_ctx, authctx);
411 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
416 /* HOLDING: authctx, authenticator */
418 cksumbuf.length = cksum0_len + cksum1_len + cksum2_len;
419 cksumbuf.data = malloc(cksumbuf.length);
420 if (!cksumbuf.data) {
421 krb5_free_keyblock(Z_krb5_ctx, keyblock);
422 krb5_auth_con_free(Z_krb5_ctx, authctx);
423 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
426 /* HOLDING: authctx, authenticator, cksumbuf.data */
428 memcpy(cksumbuf.data, cksum0_base, cksum0_len);
429 memcpy(cksumbuf.data + cksum0_len, cksum1_base, cksum1_len);
430 memcpy(cksumbuf.data + cksum0_len + cksum1_len,
431 cksum2_base, cksum2_len);
433 /* decode zcoded checksum */
434 /* The encoded form is always longer than the original */
435 asn1_len = strlen(notice->z_ascii_checksum) + 1;
436 asn1_data = malloc(asn1_len);
438 krb5_free_keyblock(Z_krb5_ctx, keyblock);
439 krb5_auth_con_free(Z_krb5_ctx, authctx);
440 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
444 /* HOLDING: authctx, authenticator, cksumbuf.data, asn1_data */
445 result = ZReadZcode(notice->z_ascii_checksum,
446 asn1_data, asn1_len, &asn1_len);
447 if (result != ZERR_NONE) {
448 krb5_free_keyblock(Z_krb5_ctx, keyblock);
449 krb5_auth_con_free(Z_krb5_ctx, authctx);
450 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
455 /* HOLDING: asn1_data, cksumbuf.data */
457 #if HAVE_KRB5_C_MAKE_CHECKSUM
458 /* Verify the checksum -- MIT crypto API */
459 memset(&checksum, 0, sizeof(checksum));
460 checksum.length = asn1_len;
461 checksum.contents = asn1_data;
462 checksum.checksum_type = cksumtype;
463 result = krb5_c_verify_checksum(Z_krb5_ctx,
464 keyblock, Z_KEYUSAGE_SRV_CKSUM,
465 &cksumbuf, &checksum, &valid);
467 krb5_auth_con_free(Z_krb5_ctx, authctx);
468 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
469 krb5_free_keyblock(Z_krb5_ctx, keyblock);
471 if (!result && valid)
474 return (ZAUTH_FAILED);
476 /* Verify the checksum -- heimdal crypto API */
477 checksum.checksum.length = asn1_len;
478 checksum.checksum.data = asn1_data;
479 checksum.cksumtype = cksumtype;
481 /* HOLDING: authctx, authenticator, cksumbuf.data, asn1_data */
483 result = krb5_crypto_init(Z_krb5_ctx, keyblock, enctype, &cryptctx);
485 krb5_auth_con_free(Z_krb5_ctx, authctx);
486 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
487 krb5_free_keyblock(Z_krb5_ctx, keyblock);
492 /* HOLDING: authctx, authenticator, cryptctx, cksumbuf.data, checksum */
493 result = krb5_verify_checksum(Z_krb5_ctx, cryptctx,
494 Z_KEYUSAGE_SRV_CKSUM,
495 cksumbuf.data, cksumbuf.length,
497 krb5_free_keyblock(Z_krb5_ctx, keyblock);
498 krb5_crypto_destroy(Z_krb5_ctx, cryptctx);
499 krb5_auth_con_free(Z_krb5_ctx, authctx);
500 krb5_free_authenticator(Z_krb5_ctx, KRB5AUTHENT);
504 return (ZAUTH_FAILED);
509 return (notice->z_auth) ? ZAUTH_YES : ZAUTH_NO;
516 ZCheckAuthentication(notice, from)
518 struct sockaddr_in *from;
522 char srcprincipal[ANAME_SZ+INST_SZ+REALM_SZ+4];
523 KTEXT_ST authent, ticket;
525 ZChecksum_t checksum;
527 char instance[INST_SZ+1];
532 /* Check for bogus authentication data length. */
533 if (notice->z_authent_len <= 0)
536 /* Read in the authentication data. */
537 if (ZReadAscii(notice->z_ascii_authent,
538 strlen(notice->z_ascii_authent)+1,
539 (unsigned char *)authent.dat,
540 notice->z_authent_len) == ZERR_BADFIELD) {
543 authent.length = notice->z_authent_len;
546 /* Copy the ticket out of the authentication data. */
547 if (krb_find_ticket(&authent, &ticket) != RD_AP_OK)
550 /* Try to do a fast check against the cryptographic checksum. */
551 if (find_session_key(&ticket, session_key, srcprincipal) >= 0) {
552 if (strcmp(srcprincipal, notice->z_sender) != 0)
554 if (notice->z_time.tv_sec - NOW > CLOCK_SKEW)
556 checksum = compute_checksum(notice, session_key);
558 /* If checksum matches, packet is authentic. If not, we might
559 * have an outdated session key, so keep going the slow way.
561 if (checksum == notice->z_checksum) {
562 memcpy(__Zephyr_session, session_key, sizeof(C_Block));
568 strcpy(instance, SERVER_INSTANCE);
570 /* We don't have the session key cached; do it the long way. */
571 result = krb_rd_req(&authent, SERVER_SERVICE, instance,
572 from->sin_addr.s_addr, &dat, srvtab_file);
573 if (result == RD_AP_OK) {
574 memcpy(__Zephyr_session, dat.session, sizeof(C_Block));
575 sprintf(srcprincipal, "%s%s%s@%s", dat.pname, dat.pinst[0] ? "." : "",
576 dat.pinst, dat.prealm);
577 if (strcmp(srcprincipal, notice->z_sender))
580 return ZAUTH_FAILED; /* didn't decode correctly */
583 /* Check the cryptographic checksum. */
587 checksum = compute_checksum(notice, dat.session);
589 if (checksum != notice->z_checksum)
593 /* Record the session key, expiry time, and source principal in the
594 * hash table, so we can do a fast check next time. */
595 add_session_key(&ticket, dat.session, srcprincipal,
596 (time_t)(dat.time_sec + dat.life * 5 * 60));
601 #else /* !HAVE_KRB4 */
602 return (notice->z_auth) ? ZAUTH_YES : ZAUTH_NO;
609 static int hash_ticket(p, len)
613 unsigned long hashval = 0, g;
615 for (; len > 0; p++, len--) {
616 hashval = (hashval << 4) + *p;
617 g = hashval & 0xf0000000;
623 return hashval % HASHTAB_SIZE;
626 static void add_session_key(ticket, session_key, srcprincipal, expires)
635 /* If we can't allocate memory for the hash table entry, just forget
637 entry = (Hash_entry *) malloc(sizeof(Hash_entry) - 1 + ticket->length);
641 /* Initialize the new entry. */
642 memcpy(entry->session_key, session_key, sizeof(entry->session_key));
643 strcpy(entry->srcprincipal, srcprincipal);
644 entry->expires = expires;
645 entry->ticket_len = ticket->length;
646 memcpy(entry->ticket, ticket->dat, ticket->length * sizeof(unsigned char));
648 /* Insert the new entry in the hash table. */
649 hashval = hash_ticket(ticket->dat, ticket->length);
650 entry->next = hashtab[hashval];
651 hashtab[hashval] = entry;
654 static int find_session_key(ticket, key, srcprincipal)
664 len = ticket->length;
665 hashval = hash_ticket(dat, len);
667 for (entry = hashtab[hashval]; entry; entry = entry->next) {
668 if (entry->ticket_len == len && memcmp(entry->ticket, dat, len) == 0) {
669 memcpy(key, entry->session_key, sizeof(entry->session_key));
670 strcpy(srcprincipal, entry->srcprincipal);
677 static ZChecksum_t compute_checksum(notice, session_key)
684 ZChecksum_t checksum;
685 char *cstart, *cend, *hstart = notice->z_packet, *hend = notice->z_message;
687 cstart = notice->z_default_format + strlen(notice->z_default_format) + 1;
688 cend = cstart + strlen(cstart) + 1;
689 checksum = des_quad_cksum(hstart, NULL, cstart - hstart, 0, session_key);
690 checksum ^= des_quad_cksum(cend, NULL, hend - cend, 0, session_key);
691 checksum ^= des_quad_cksum(notice->z_message, NULL, notice->z_message_len,
697 static ZChecksum_t compute_rlm_checksum(notice, session_key)
704 ZChecksum_t checksum;
705 char *cstart, *cend, *hstart = notice->z_packet, *hend = notice->z_message;
707 cstart = notice->z_default_format + strlen(notice->z_default_format) + 1;
708 cend = cstart + strlen(cstart) + 1;
709 checksum = des_quad_cksum(hstart, NULL, cstart - hstart, 0, session_key);
714 void sweep_ticket_hash_table(arg)
718 Hash_entry **ptr, *entry;
720 for (i = 0; i < HASHTAB_SIZE; i++) {
724 if (entry->expires < NOW) {
732 timer_set_rel(SWEEP_INTERVAL, sweep_ticket_hash_table, NULL);
735 #endif /* HAVE_KRB4 */