krb5_ui_4 hash;
char *server; /* null-terminated */
char *client; /* null-terminated */
+ char *msghash; /* null-terminated */
krb5_int32 cusec;
krb5_timestamp ctime;
} krb5_donot_replay;
(krb5_context,
krb5_tkt_authent *,
krb5_donot_replay *);
+krb5_error_code krb5_rc_hash_message
+ (krb5_context context,
+ const krb5_data *message, char **out);
krb5_error_code KRB5_CALLCONV krb5_rc_initialize
/* Now check the replay cache. */
rep.client = princ_psr;
rep.server = "SAM/rc"; /* Should not match any principal name. */
+ rep.msghash = NULL;
rep.ctime = psr->stime;
rep.cusec = psr->susec;
retval = krb5_rc_store(kdc_context, kdc_rcache, &rep);
goto error;
replay.server = ""; /* XXX */
+ replay.msghash = NULL;
replay.cusec = replaydata.usec;
replay.ctime = replaydata.timestamp;
if ((retval = krb5_rc_store(context, auth_context->rcache, &replay))) {
}
replay.server = ""; /* XXX */
+ replay.msghash = NULL;
replay.cusec = replaydata.usec;
replay.ctime = replaydata.timestamp;
if ((retval = krb5_rc_store(context, auth_context->rcache, &replay))) {
}
replay.server = ""; /* XXX */
+ replay.msghash = NULL;
replay.cusec = replaydata.usec;
replay.ctime = replaydata.timestamp;
if ((retval = krb5_rc_store(context, auth_context->rcache, &replay))) {
goto error;
replay.server = ""; /* XXX */
+ replay.msghash = NULL;
replay.cusec = replaydata.usec;
replay.ctime = replaydata.timestamp;
if ((retval = krb5_rc_store(context, auth_context->rcache, &replay))) {
goto error;
replay.server = ""; /* XXX */
+ replay.msghash = NULL;
replay.cusec = replaydata.usec;
replay.ctime = replaydata.timestamp;
if ((retval = krb5_rc_store(context, auth_context->rcache, &replay))) {
tktauthent.ticket = req->ticket;
tktauthent.authenticator = (*auth_context)->authentp;
if (!(retval = krb5_auth_to_rep(context, &tktauthent, &rep))) {
- retval = krb5_rc_store(context, (*auth_context)->rcache, &rep);
+ retval = krb5_rc_hash_message(context,
+ &req->authenticator.ciphertext,
+ &rep.msghash);
+ if (!retval) {
+ retval = krb5_rc_store(context, (*auth_context)->rcache, &rep);
+ krb5_xfree(rep.msghash);
+ }
krb5_xfree(rep.server);
krb5_xfree(rep.client);
}
goto error;
replay.server = ""; /* XXX */
+ replay.msghash = NULL;
replay.cusec = replaydata.usec;
replay.ctime = replaydata.timestamp;
if ((retval = krb5_rc_store(context, auth_context->rcache, &replay))) {
krb5_rc_get_lifespan
krb5_rc_get_name
krb5_rc_get_type
+krb5_rc_hash_message
krb5_rc_initialize
krb5_rc_io_close
krb5_rc_io_creat
krb5_rc_io_unmark
krb5_rc_io_write
krb5_rc_recover
+krb5_rc_recover_or_initialize
krb5_rc_register_type
krb5_rc_resolve
krb5_rc_resolve_full
myfulldir=lib/krb5/rcache
mydir=lib/krb5/rcache
BUILDTOP=$(REL)..$(S)..$(S)..
+PROG_LIBPATH=-L$(TOPLIBD)
+PROG_RPATH=$(KRB5_LIBDIR)
DEFS=
##DOS##BUILDTOP = ..\..\..
$(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@
}
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;
+}
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;
}
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
t->a = q->na;
FREE(q->rep.client);
FREE(q->rep.server);
+ if (q->rep.msghash)
+ FREE(q->rep.msghash);
FREE(q);
}
#ifndef NOIOSTUFF
{
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);
}
}
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));
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;
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;
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);
*/
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)
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;
}
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? */
}
--- /dev/null
+/* -*- 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;
+}
buf);
r.server = buf;
r.client = (t->my_cusec & 7) + "abcdefgh@ATHENA.MIT.EDU";
+ r.msghash = NULL;
if (t->now != t->my_ctime) {
if (t->my_ctime != 0) {
snprintf(buf2, sizeof(buf2), "%3d: %ld %5d\n", t->idx,