diff options
author | Greg Hudson <ghudson@mit.edu> | 2009-01-12 18:29:42 +0000 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2009-01-12 18:29:42 +0000 |
commit | 529e72785f09c36a9aa34fd7f3fc30fb41a1c92e (patch) | |
tree | f3eabb982f7d8eb8cedf54fa4389d8ccc99ebd19 /src/lib/krb5/rcache | |
parent | 648e32a9034138369d52f9bb29d3d6293d5733e5 (diff) | |
download | krb5-529e72785f09c36a9aa34fd7f3fc30fb41a1c92e.tar.gz krb5-529e72785f09c36a9aa34fd7f3fc30fb41a1c92e.tar.xz krb5-529e72785f09c36a9aa34fd7f3fc30fb41a1c92e.zip |
Add message hash support to the replay interface, using extension
records (with an empty client string) to retain compatibility with old
code. For rd_req, the ciphertext of the authenticator (with no ASN.1
wrapping) is hashed; for other uses of the replay cache, no message
hash is used at this time.
This commit adds a command-line tool for testing the replay cache but
does not add any automated tests.
ticket: 1201
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@21723 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/lib/krb5/rcache')
-rw-r--r-- | src/lib/krb5/rcache/Makefile.in | 10 | ||||
-rw-r--r-- | src/lib/krb5/rcache/rc_conv.c | 41 | ||||
-rw-r--r-- | src/lib/krb5/rcache/rc_dfl.c | 127 | ||||
-rw-r--r-- | src/lib/krb5/rcache/t_replay.c | 227 |
4 files changed, 363 insertions, 42 deletions
diff --git a/src/lib/krb5/rcache/Makefile.in b/src/lib/krb5/rcache/Makefile.in index d4c133623..abbcc32de 100644 --- a/src/lib/krb5/rcache/Makefile.in +++ b/src/lib/krb5/rcache/Makefile.in @@ -2,6 +2,8 @@ thisconfigdir=../../.. myfulldir=lib/krb5/rcache mydir=lib/krb5/rcache BUILDTOP=$(REL)..$(S)..$(S).. +PROG_LIBPATH=-L$(TOPLIBD) +PROG_RPATH=$(KRB5_LIBDIR) DEFS= ##DOS##BUILDTOP = ..\..\.. @@ -36,12 +38,18 @@ SRCS= \ $(srcdir)/rc_none.c \ $(srcdir)/rc_conv.c \ $(srcdir)/ser_rc.c \ - $(srcdir)/rcfns.c + $(srcdir)/rcfns.c \ + $(srcdir)/t_replay.c ##DOS##LIBOBJS = $(OBJS) all-unix:: all-libobjs clean-unix:: clean-libobjs +T_REPLAY_OBJS= t_replay.o + +t_replay: $(T_REPLAY_OBJS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o t_replay $(T_REPLAY_OBJS) $(KRB5_BASE_LIBS) + @libobj_frag@ diff --git a/src/lib/krb5/rcache/rc_conv.c b/src/lib/krb5/rcache/rc_conv.c index 16ed9e7eb..3370f45df 100644 --- a/src/lib/krb5/rcache/rc_conv.c +++ b/src/lib/krb5/rcache/rc_conv.c @@ -36,3 +36,44 @@ krb5_auth_to_rep(krb5_context context, krb5_tkt_authent *auth, krb5_donot_replay } return 0; } + +/* + * Generate a printable hash value for a message for use in a replay + * record. It is not necessary for this hash function to be + * collision-proof (the only thing you can do with a second preimage + * is produce a false replay error) but it is necessary for the + * function to be consistent across implementations. We do an unkeyed + * MD5 hash of the message and convert it into uppercase hex + * representation. + */ +krb5_error_code +krb5_rc_hash_message(krb5_context context, const krb5_data *message, + char **out) +{ + krb5_error_code retval; + krb5_checksum cksum; + char *hash, *ptr; + unsigned int i; + + *out = NULL; + + /* Calculate the binary checksum. */ + retval = krb5_c_make_checksum(context, CKSUMTYPE_RSA_MD5, 0, 0, + message, &cksum); + if (retval) + return retval; + + /* Convert the checksum into printable form. */ + hash = malloc(cksum.length * 2 + 1); + if (!hash) { + krb5_free_checksum_contents(context, &cksum); + return KRB5_RC_MALLOC; + } + ptr = hash; + for (i = 0, ptr = hash; i < cksum.length; i++, ptr += 2) + snprintf(ptr, 3, "%02X", cksum.contents[i]); + *ptr = '\0'; + *out = hash; + krb5_free_checksum_contents(context, &cksum); + return 0; +} diff --git a/src/lib/krb5/rcache/rc_dfl.c b/src/lib/krb5/rcache/rc_dfl.c index aa0b3a5f9..04a641dc2 100644 --- a/src/lib/krb5/rcache/rc_dfl.c +++ b/src/lib/krb5/rcache/rc_dfl.c @@ -85,8 +85,12 @@ cmp(krb5_donot_replay *old, krb5_donot_replay *new1, krb5_deltat t) if ((old->cusec == new1->cusec) && /* most likely to distinguish */ (old->ctime == new1->ctime) && (strcmp(old->client, new1->client) == 0) && - (strcmp(old->server, new1->server) == 0)) /* always true */ - return CMP_REPLAY; + (strcmp(old->server, new1->server) == 0)) { /* always true */ + /* If both records include message hashes, compare them as well. */ + if (old->msghash == NULL || new1->msghash == NULL || + strcmp(old->msghash, new1->msghash) == 0) + return CMP_REPLAY; + } return CMP_HOHUM; } @@ -157,17 +161,22 @@ rc_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep, ta->na = t->a; t->a = ta; ta->nh = t->h[rephash]; t->h[rephash] = ta; ta->rep = *rep; - if (!(ta->rep.client = strdup(rep->client))) { - FREE(ta); - return CMP_MALLOC; - } - if (!(ta->rep.server = strdup(rep->server))) { - FREE(ta->rep.client); - FREE(ta); - return CMP_MALLOC; - } - + ta->rep.client = ta->rep.server = ta->rep.msghash = NULL; + if (!(ta->rep.client = strdup(rep->client))) + goto error; + if (!(ta->rep.server = strdup(rep->server))) + goto error; + if (rep->msghash && !(ta->rep.msghash = strdup(rep->msghash))) + goto error; return CMP_HOHUM; +error: + if (ta->rep.client) + free(ta->rep.client); + if (ta->rep.server) + free(ta->rep.server); + if (ta->rep.msghash) + free(ta->rep.msghash); + return CMP_MALLOC; } char * KRB5_CALLCONV @@ -241,6 +250,8 @@ krb5_rc_dfl_close_no_free(krb5_context context, krb5_rcache id) t->a = q->na; FREE(q->rep.client); FREE(q->rep.server); + if (q->rep.msghash) + FREE(q->rep.msghash); FREE(q); } #ifndef NOIOSTUFF @@ -328,11 +339,13 @@ krb5_rc_free_entry(krb5_context context, krb5_donot_replay **rep) { if (rp->client) free(rp->client); - if (rp->server) free(rp->server); + if (rp->msghash) + free(rp->msghash); rp->client = NULL; rp->server = NULL; + rp->msghash = NULL; free(rp); } } @@ -345,7 +358,7 @@ krb5_rc_io_fetch(krb5_context context, struct dfl_data *t, unsigned int len; krb5_error_code retval; - rep->client = rep->server = 0; + rep->client = rep->server = rep->msghash = NULL; retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2, sizeof(len2)); @@ -423,6 +436,7 @@ krb5_rc_dfl_recover_locked(krb5_context context, krb5_rcache id) long max_size; int expired_entries = 0; krb5_int32 now; + char *msghash = NULL; if ((retval = krb5_rc_io_open(context, &t->d, t->name))) { return retval; @@ -443,8 +457,7 @@ krb5_rc_dfl_recover_locked(krb5_context context, krb5_rcache id) retval = KRB5_RC_MALLOC; goto io_fail; } - rep->client = NULL; - rep->server = NULL; + rep->client = rep->server = rep->msghash = NULL; if (krb5_timeofday(context, &now)) now = 0; @@ -463,21 +476,38 @@ krb5_rc_dfl_recover_locked(krb5_context context, krb5_rcache id) else if (retval != 0) goto io_fail; - - if (alive(now, rep, t->lifespan) != CMP_EXPIRED) { - if (rc_store(context, id, rep, now) == CMP_MALLOC) { - retval = KRB5_RC_MALLOC; goto io_fail; + if (!*rep->client) { + /* An empty client field indicates an extension record. */ + if (strncmp(rep->server, "HASH:", 5) == 0) { + msghash = strdup(rep->server + 5); + if (msghash == NULL) { + retval = KRB5_RC_MALLOC; + goto io_fail; + } } } else { - expired_entries++; + /* This is a normal record. */ + if (msghash) { + /* Use the hash from the prior extension record. */ + rep->msghash = msghash; + msghash = NULL; + } + if (alive(now, rep, t->lifespan) != CMP_EXPIRED) { + if (rc_store(context, id, rep, now) == CMP_MALLOC) { + retval = KRB5_RC_MALLOC; goto io_fail; + } + } else { + expired_entries++; + } } /* - * free fields allocated by rc_io_fetch + * free fields allocated by rc_io_fetch (or by us) */ FREE(rep->server); FREE(rep->client); - rep->server = 0; - rep->client = 0; + if (rep->msghash) + FREE(rep->msghash); + rep->client = rep->server = rep->msghash = NULL; } retval = 0; krb5_rc_io_unmark(context, &t->d); @@ -487,6 +517,8 @@ krb5_rc_dfl_recover_locked(krb5_context context, krb5_rcache id) */ io_fail: krb5_rc_free_entry(context, &rep); + if (msghash) + FREE(msghash); if (retval) krb5_rc_io_close(context, &t->d); else if (expired_entries > EXCESSREPS) @@ -529,27 +561,38 @@ static krb5_error_code krb5_rc_io_store(krb5_context context, struct dfl_data *t, krb5_donot_replay *rep) { - unsigned int clientlen, serverlen, len; - char *buf, *ptr; + unsigned int clientlen, serverlen; krb5_error_code ret; - + struct k5buf buf; + char *ptr; + + krb5int_buf_init_dynamic(&buf); + if (rep->msghash) { + clientlen = 1; + serverlen = strlen(rep->msghash) + 6; + krb5int_buf_add_len(&buf, (char *) &clientlen, sizeof(clientlen)); + krb5int_buf_add_len(&buf, "", 1); + krb5int_buf_add_len(&buf, (char *) &serverlen, sizeof(serverlen)); + krb5int_buf_add_fmt(&buf, "HASH:%s", rep->msghash); + krb5int_buf_add_len(&buf, "", 1); + krb5int_buf_add_len(&buf, (char *) &rep->cusec, sizeof(rep->cusec)); + krb5int_buf_add_len(&buf, (char *) &rep->ctime, sizeof(rep->ctime)); + } clientlen = strlen(rep->client) + 1; serverlen = strlen(rep->server) + 1; - len = sizeof(clientlen) + clientlen + sizeof(serverlen) + serverlen + - sizeof(rep->cusec) + sizeof(rep->ctime); - buf = malloc(len); - if (buf == 0) + krb5int_buf_add_len(&buf, (char *) &clientlen, sizeof(clientlen)); + krb5int_buf_add_len(&buf, rep->client, clientlen); + krb5int_buf_add_len(&buf, (char *) &serverlen, sizeof(serverlen)); + krb5int_buf_add_len(&buf, rep->server, serverlen); + krb5int_buf_add_len(&buf, (char *) &rep->cusec, sizeof(rep->cusec)); + krb5int_buf_add_len(&buf, (char *) &rep->ctime, sizeof(rep->ctime)); + + ptr = krb5int_buf_data(&buf); + if (ptr == NULL) return KRB5_RC_MALLOC; - ptr = buf; - memcpy(ptr, &clientlen, sizeof(clientlen)); ptr += sizeof(clientlen); - memcpy(ptr, rep->client, clientlen); ptr += clientlen; - memcpy(ptr, &serverlen, sizeof(serverlen)); ptr += sizeof(serverlen); - memcpy(ptr, rep->server, serverlen); ptr += serverlen; - memcpy(ptr, &rep->cusec, sizeof(rep->cusec)); ptr += sizeof(rep->cusec); - memcpy(ptr, &rep->ctime, sizeof(rep->ctime)); ptr += sizeof(rep->ctime); - - ret = krb5_rc_io_write(context, &t->d, buf, len); - free(buf); + + ret = krb5_rc_io_write(context, &t->d, ptr, krb5int_buf_len(&buf)); + krb5int_free_buf(&buf); return ret; } @@ -628,6 +671,8 @@ krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id) if (alive(now, &(*q)->rep, t->lifespan) == CMP_EXPIRED) { FREE((*q)->rep.client); FREE((*q)->rep.server); + if ((*q)->rep.msghash) + FREE((*q)->rep.msghash); FREE(*q); *q = *qt; /* why doesn't this feel right? */ } diff --git a/src/lib/krb5/rcache/t_replay.c b/src/lib/krb5/rcache/t_replay.c new file mode 100644 index 000000000..4147896ce --- /dev/null +++ b/src/lib/krb5/rcache/t_replay.c @@ -0,0 +1,227 @@ +/* -*- mode: c; indent-tabs-mode: nil -*- */ +/* + * test/threads/t_replay.c + * + * Copyright (C) 2009 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. + * + * t_replay.c: Command-line interfaces to aid testing of replay cache + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "k5-int.h" + +static void usage(const char *progname) +{ + fprintf(stderr, "%s: Usage:\n", progname); + fprintf(stderr, " %s dump <filename>\n", progname); + fprintf(stderr, " %s store <rc> <cli> <srv> <msg> <tstamp> <usec>" + " <now> <now-usec>\n", progname); + exit(1); +} + +static char *read_counted_string(FILE *fp) +{ + unsigned int len; + char *str; + + if (fread(&len, sizeof(len), 1, fp) != 1) + return NULL; + if (len == 0 || len > 10000) + return NULL; + if ((str = malloc(len)) == NULL) + return NULL; + if (fread(str, 1, len, fp) != len) + return NULL; + if (str[len - 1] != 0) + return NULL; + return str; +} + +static void dump_rcache(const char *filename) +{ + FILE *fp; + krb5_deltat lifespan; + krb5_int16 vno; + + fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "Can't open filename: %s\n", strerror(errno)); + return; + } + if (fread(&vno, sizeof(vno), 1, fp) != 1) + return; + if (fread(&lifespan, sizeof(lifespan), 1, fp) != 1) + return; + printf("Lifespan: %ld\n", (long) lifespan); + while (1) { + char *str; + krb5_int32 usec; + krb5_timestamp timestamp; + + printf("---\n"); + + if (!(str = read_counted_string(fp))) + return; + printf("Client: %s\n", str); + free(str); + + if (!(str = read_counted_string(fp))) + return; + printf("Server: %s\n", str); + free(str); + + if (fread(&usec, sizeof(usec), 1, fp) != 1) + return; + printf("Microseconds: %ld\n", (long) usec); + + if (fread(×tamp, sizeof(timestamp), 1, fp) != 1) + return; + printf("Timestamp: %ld\n", (long) timestamp); + } +} + +static void store(krb5_context ctx, char *rcspec, char *client, char *server, + char *msg, krb5_timestamp timestamp, krb5_int32 usec, + krb5_timestamp now_timestamp, krb5_int32 now_usec) +{ + krb5_rcache rc = NULL; + krb5_error_code retval = 0; + char *hash = NULL; + krb5_donot_replay rep; + + if (now_timestamp > 0) + krb5_set_debugging_time(ctx, now_timestamp, now_usec); + if ((retval = krb5_rc_resolve_full(ctx, &rc, rcspec))) + goto cleanup; + if ((retval = krb5_rc_recover_or_initialize(ctx, rc, ctx->clockskew))) + goto cleanup; + if (msg) { + krb5_data d; + + d.data = msg; + d.length = strlen(msg); + if ((retval = krb5_rc_hash_message(ctx, &d, &hash))) + goto cleanup; + } + rep.client = client; + rep.server = server; + rep.msghash = hash; + rep.cusec = usec; + rep.ctime = timestamp; + retval = krb5_rc_store(ctx, rc, &rep); +cleanup: + if (retval == KRB5KRB_AP_ERR_REPEAT) + printf("Replay\n"); + else if (!retval) + printf("Entry successfully stored\n"); + else + fprintf(stderr, "Failure: %s\n", krb5_get_error_message(ctx, retval)); + if (rc) + krb5_rc_close(ctx, rc); + if (hash) + free(hash); +} + +int main(int argc, char **argv) +{ + krb5_context ctx; + krb5_error_code retval; + const char *progname; + + retval = krb5_init_context(&ctx); + if (retval) { + fprintf(stderr, "krb5_init_context returned error %ld\n", + (long) retval); + exit(1); + } + progname = argv[0]; + + /* Parse arguments. */ + argc--; argv++; + while (argc) { + if (strcmp(*argv, "dump") == 0) { + /* + * Without going through the rcache interface, dump a + * named dfl-format rcache file to stdout. Takes a full + * pathname argument. + */ + const char *filename; + + argc--; argv++; + if (!argc) usage(progname); + filename = *argv; + dump_rcache(filename); + } else if (strcmp(*argv, "store") == 0) { + /* + * Using the rcache interface, store a replay record. + * Takes an rcache spec like dfl:host as the first + * argument. If non-empty, the "msg" argument will be + * hashed and provided in the replay record. The + * now-timestamp argument can be 0 to use the current + * time. + */ + char *rcspec, *client, *server, *msg; + krb5_timestamp timestamp, now_timestamp; + krb5_int32 usec, now_usec; + + argc--; argv++; + if (!argc) usage(progname); + rcspec = *argv; + argc--; argv++; + if (!argc) usage(progname); + client = *argv; + argc--; argv++; + if (!argc) usage(progname); + server = *argv; + argc--; argv++; + if (!argc) usage(progname); + msg = (**argv) ? *argv : NULL; + argc--; argv++; + if (!argc) usage(progname); + timestamp = (krb5_timestamp) atol(*argv); + argc--; argv++; + if (!argc) usage(progname); + usec = (krb5_int32) atol(*argv); + argc--; argv++; + if (!argc) usage(progname); + now_timestamp = (krb5_timestamp) atol(*argv); + argc--; argv++; + if (!argc) usage(progname); + now_usec = (krb5_int32) atol(*argv); + + store(ctx, rcspec, client, server, msg, timestamp, usec, + now_timestamp, now_usec); + } else + usage(progname); + argc--; argv++; + } + + krb5_free_context(ctx); + + return 0; +} |