[30451] in CVS-changelog-for-Kerberos-V5

home help back first fref pref prev next nref lref last post

krb5 commit: Use memory replay cache for DO_TIME auth contexts

daemon@ATHENA.MIT.EDU (Greg Hudson)
Tue Mar 12 14:14:03 2019

Date: Tue, 12 Mar 2019 14:13:32 -0400
From: Greg Hudson <ghudson@mit.edu>
Message-ID: <201903121813.x2CIDWu3007793@drugstore.mit.edu>
To: <cvs-krb5@mit.edu>
MIME-Version: 1.0
Reply-To: krbdev@mit.edu
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Errors-To: cvs-krb5-bounces@mit.edu

https://github.com/krb5/krb5/commit/0b1c78fcbdef574fb6835f0cd0a89388f4098cb5
commit 0b1c78fcbdef574fb6835f0cd0a89388f4098cb5
Author: Greg Hudson <ghudson@mit.edu>
Date:   Thu Feb 28 21:28:43 2019 -0500

    Use memory replay cache for DO_TIME auth contexts
    
    Instead of requiring the caller to set up a persistent replay cache
    for KRB-PRIV/KRB-SAFE/KRB-CRED messages produced in DO_TIME auth
    contexts, use an in-memory replay cache.
    
    Update the API documentation for the affected functions and correct
    some inaccuracies.
    
    ticket: 8785 (new)

 .gitignore                    |    1 +
 src/include/k5-int.h          |    6 ++
 src/include/krb5/krb5.hin     |  138 ++++++++++++++++++++-------------
 src/lib/krb5/krb/auth_con.c   |    1 +
 src/lib/krb5/krb/auth_con.h   |    3 +
 src/lib/krb5/krb/int-proto.h  |   11 ++-
 src/lib/krb5/krb/mk_cred.c    |   28 +++++--
 src/lib/krb5/krb/mk_priv.c    |   21 +++--
 src/lib/krb5/krb/mk_safe.c    |   23 ++++--
 src/lib/krb5/krb/privsafe.c   |   40 +++++-----
 src/lib/krb5/krb/rd_cred.c    |   17 ++---
 src/lib/krb5/krb/rd_priv.c    |   22 +++--
 src/lib/krb5/krb/rd_safe.c    |   31 +++++---
 src/lib/krb5/rcache/rc_conv.c |   20 +++++
 src/tests/Makefile.in         |   14 ++-
 src/tests/replay.c            |  172 +++++++++++++++++++++++++++++++++++++++++
 src/tests/t_replay.py         |    6 ++
 17 files changed, 414 insertions(+), 140 deletions(-)

diff --git a/.gitignore b/.gitignore
index 8117674..f39dd6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -438,6 +438,7 @@ local.properties
 /src/tests/localauth
 /src/tests/plugorder
 /src/tests/rdreq
+/src/tests/replay
 /src/tests/responder
 /src/tests/s2p
 /src/tests/s4u2proxy
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index 7d3c129..71dce73 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -1989,6 +1989,12 @@ krb5_error_code krb5_auth_to_rep(krb5_context, krb5_tkt_authent *,
 krb5_error_code krb5_rc_hash_message(krb5_context context,
                                      const krb5_data *message, char **out);
 
+/* Set *tag_out to the integrity tag of *enc.  (Does not allocate memory;
+ * returned buffer is a subrange of *ctext.) */
+krb5_error_code
+k5_rc_tag_from_ciphertext(krb5_context context, const krb5_enc_data *enc,
+                          krb5_data *tag_out);
+
 krb5_error_code KRB5_CALLCONV
 krb5_rc_initialize(krb5_context, krb5_rcache, krb5_deltat);
 
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index a67eec1..eb31607 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -3316,25 +3316,24 @@ krb5_rd_error(krb5_context context, const krb5_data *enc_errbuf,
  * This function parses a @c KRB-SAFE message, verifies its integrity, and
  * stores its data into @a userdata_out.
  *
- * @note The @a rdata_out argument is required if #KRB5_AUTH_CONTEXT_RET_TIME
- * or #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set in the @a auth_context.
+ * @note The @a rdata_out argument is required if the
+ * #KRB5_AUTH_CONTEXT_RET_TIME or #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set
+ * in @a auth_context.
  *
- * @note @a auth_context must have a remote address set.  This address will be
- * used to verify the sender address in the KRB-SAFE message.  If @a
- * auth_context has a local address set, it will be used to verify the receiver
- * address in the KRB-SAFE message if the message contains one.  Both addresses
- * must use type @c ADDRTYPE_ADDRPORT.
+ * If @a auth_context has a remote address set, the address will be used to
+ * verify the sender address in the KRB-SAFE message.  If @a auth_context has a
+ * local address set, it will be used to verify the receiver address in the
+ * KRB-SAFE message if the message contains one.
  *
  * If the #KRB5_AUTH_CONTEXT_DO_SEQUENCE flag is set in @a auth_context, the
  * sequence number of the KRB-SAFE message is checked against the remote
  * sequence number field of @a auth_context.  Otherwise, the sequence number is
  * not used.
  *
- * If the #KRB5_AUTH_CONTEXT_DO_TIME flag is set in @a auth_context,
- * then two additional checks are performed:
- * @li The timestamp in the message must be within the permitted clock skew
- *     (which is usually five minutes).
- * @li The message must not be a replayed message field in @a auth_context.
+ * If the #KRB5_AUTH_CONTEXT_DO_TIME flag is set in @a auth_context, then the
+ * timestamp in the message is verified to be within the permitted clock skew
+ * of the current time, and the message is checked against an in-memory replay
+ * cache to detect reflections or replays.
  *
  * Use krb5_free_data_contents() to free @a userdata_out when it is no longer
  * needed.
@@ -3358,25 +3357,24 @@ krb5_rd_safe(krb5_context context, krb5_auth_context auth_context,
  * This function parses a @c KRB-PRIV message, verifies its integrity, and
  * stores its unencrypted data into @a userdata_out.
  *
- * @note If the #KRB5_AUTH_CONTEXT_RET_TIME or #KRB5_AUTH_CONTEXT_RET_SEQUENCE
- * flag is set in @a auth_context, @a rdata_out is required.
+ * @note The @a rdata_out argument is required if the
+ * #KRB5_AUTH_CONTEXT_RET_TIME or #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set
+ * in @a auth_context.
  *
- * @note @a auth_context must have a remote address set.  This address will be
- * used to verify the sender address in the KRB-PRIV message.  If @a
- * auth_context has a local address set, it will be used to verify the receiver
- * address in the KRB-PRIV message if the message contains one.  Both addresses
- * must use type @c ADDRTYPE_ADDRPORT.
+ * If @a auth_context has a remote address set, the address will be used to
+ * verify the sender address in the KRB-PRIV message.  If @a auth_context has a
+ * local address set, it will be used to verify the receiver address in the
+ * KRB-PRIV message if the message contains one.
  *
  * If the #KRB5_AUTH_CONTEXT_DO_SEQUENCE flag is set in @a auth_context, the
- * sequence number of the KRB-SAFE message is checked against the remote
+ * sequence number of the KRB-PRIV message is checked against the remote
  * sequence number field of @a auth_context.  Otherwise, the sequence number is
  * not used.
  *
- * If the #KRB5_AUTH_CONTEXT_DO_TIME flag is set in @a auth_context,
- * then two additional checks are performed:
- * @li The timestamp in the message must be within the permitted clock skew
- *     (which is usually five minutes).
- * @li The message must not be a replayed message field in @a auth_context.
+ * If the #KRB5_AUTH_CONTEXT_DO_TIME flag is set in @a auth_context, then the
+ * timestamp in the message is verified to be within the permitted clock skew
+ * of the current time, and the message is checked against an in-memory replay
+ * cache to detect reflections or replays.
  *
  * Use krb5_free_data_contents() to free @a userdata_out when it is no longer
  * needed.
@@ -5263,18 +5261,22 @@ krb5_kt_read_service_key(krb5_context context, krb5_pointer keyprocarg,
  * optional; if specified, it will be used to form the receiver address used in
  * the message.
  *
- * If #KRB5_AUTH_CONTEXT_DO_TIME flag is set in the @a auth_context, an entry
- * describing the message is entered in the replay cache @a
- * auth_context->rcache which enables the caller to detect if this message is
- * reflected by an attacker.  If #KRB5_AUTH_CONTEXT_DO_TIME is not set, the
- * replay cache is not used.
+ * @note The @a rdata_out argument is required if the
+ * #KRB5_AUTH_CONTEXT_RET_TIME or #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set
+ * in @a auth_context.
  *
- * If either #KRB5_AUTH_CONTEXT_DO_SEQUENCE or
- * #KRB5_AUTH_CONTEXT_RET_SEQUENCE is set, the @a auth_context local sequence
- * number will be placed in @a rdata_out as its sequence number.
+ * If the #KRB5_AUTH_CONTEXT_DO_TIME flag is set in @a auth_context, a
+ * timestamp is included in the KRB-SAFE message, and an entry for the message
+ * is entered in an in-memory replay cache to detect if the message is
+ * reflected by an attacker.  If #KRB5_AUTH_CONTEXT_DO_TIME is not set, no
+ * replay cache is used.  If #KRB5_AUTH_CONTEXT_RET_TIME is set in @a
+ * auth_context, a timestamp is included in the KRB-SAFE message and is stored
+ * in @a rdata_out.
  *
- * @note The @a rdata_out argument is required if #KRB5_AUTH_CONTEXT_RET_TIME
- * or #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set in the @a auth_context.
+ * If either #KRB5_AUTH_CONTEXT_DO_SEQUENCE or #KRB5_AUTH_CONTEXT_RET_SEQUENCE
+ * is set, the @a auth_context local sequence number is included in the
+ * KRB-SAFE message and then incremented.  If #KRB5_AUTH_CONTEXT_RET_SEQUENCE
+ * is set, the sequence number used is stored in @a rdata_out.
  *
  * Use krb5_free_data_contents() to free @a der_out when it is no longer
  * needed.
@@ -5293,30 +5295,35 @@ krb5_mk_safe(krb5_context context, krb5_auth_context auth_context,
  * @param [in]  auth_context    Authentication context
  * @param [in]  userdata        User data for @c KRB-PRIV message
  * @param [out] der_out         Formatted @c KRB-PRIV message
- * @param [out] rdata_out       Replay cache handle (NULL if not needed)
+ * @param [out] rdata_out       Replay data (NULL if not needed)
  *
  * This function is similar to krb5_mk_safe(), but the message is encrypted and
  * integrity-protected, not just integrity-protected.
  *
  * The local address in @a auth_context must be set, and is used to form the
- * sender address used in the KRB-SAFE message.  The remote address is
+ * sender address used in the KRB-PRIV message.  The remote address is
  * optional; if specified, it will be used to form the receiver address used in
  * the message.
  *
- * @note If the #KRB5_AUTH_CONTEXT_RET_TIME or
- * #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set in @a auth_context, the @a
- * rdata_out is required.
+ * @note The @a rdata_out argument is required if the
+ * #KRB5_AUTH_CONTEXT_RET_TIME or #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set
+ * in @a auth_context.
  *
- * @note The flags from @a auth_context specify whether sequence numbers or
- * timestamps will be used to identify the message.  Valid values are:
+ * If the #KRB5_AUTH_CONTEXT_DO_TIME flag is set in @a auth_context, a
+ * timestamp is included in the KRB-PRIV message, and an entry for the message
+ * is entered in an in-memory replay cache to detect if the message is
+ * reflected by an attacker.  If #KRB5_AUTH_CONTEXT_DO_TIME is not set, no
+ * replay cache is used.  If #KRB5_AUTH_CONTEXT_RET_TIME is set in @a
+ * auth_context, a timestamp is included in the KRB-PRIV message and is stored
+ * in @a rdata_out.
  *
- * @li #KRB5_AUTH_CONTEXT_DO_TIME      - Use timestamps in @a outdata
- * @li #KRB5_AUTH_CONTEXT_RET_TIME     - Copy timestamp to @a outdata.
- * @li #KRB5_AUTH_CONTEXT_DO_SEQUENCE  - Use local sequence numbers from
- *                                       @a auth_context in replay cache.
- * @li #KRB5_AUTH_CONTEXT_RET_SEQUENCE - Use local sequence numbers from
- *                                       @a auth_context as a sequence number
- *                                       in the encrypted message @a der_out.
+ * If either #KRB5_AUTH_CONTEXT_DO_SEQUENCE or #KRB5_AUTH_CONTEXT_RET_SEQUENCE
+ * is set, the @a auth_context local sequence number is included in the
+ * KRB-PRIV message and then incremented.  If #KRB5_AUTH_CONTEXT_RET_SEQUENCE
+ * is set, the sequence number used is stored in @a rdata_out.
+ *
+ * Use krb5_free_data_contents() to free @a der_out when it is no longer
+ * needed.
  *
  * @retval 0 Success; otherwise - Kerberos error codes
  */
@@ -5448,11 +5455,33 @@ krb5_recvauth_version(krb5_context context,
  * This function takes an array of credentials @a creds and formats
  * a @c KRB-CRED message @a der_out to pass to krb5_rd_cred().
  *
- * @note If the #KRB5_AUTH_CONTEXT_RET_TIME or #KRB5_AUTH_CONTEXT_RET_SEQUENCE
- * flag is set in @a auth_context, @a rdata_out is required.
+ * The local and remote addresses in @a auth_context are optional; if either is
+ * specified, they are used to form the sender and receiver addresses in the
+ * KRB-CRED message.
+ *
+ * @note The @a rdata_out argument is required if the
+ * #KRB5_AUTH_CONTEXT_RET_TIME or #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set
+ * in @a auth_context.
+ *
+ * If the #KRB5_AUTH_CONTEXT_DO_TIME flag is set in @a auth_context, an entry
+ * for the message is entered in an in-memory replay cache to detect if the
+ * message is reflected by an attacker.  If #KRB5_AUTH_CONTEXT_DO_TIME is not
+ * set, no replay cache is used.  If #KRB5_AUTH_CONTEXT_RET_TIME is set in @a
+ * auth_context, the timestamp used for the KRB-CRED message is stored in @a
+ * rdata_out.
+ *
+ * If either #KRB5_AUTH_CONTEXT_DO_SEQUENCE or #KRB5_AUTH_CONTEXT_RET_SEQUENCE
+ * is set, the @a auth_context local sequence number is included in the
+ * KRB-CRED message and then incremented.  If #KRB5_AUTH_CONTEXT_RET_SEQUENCE
+ * is set, the sequence number used is stored in @a rdata_out.
+ *
+ * Use krb5_free_data_contents() to free @a der_out when it is no longer
+ * needed.
  *
  * The message will be encrypted using the send subkey of @a auth_context if it
- * is present, or the session key otherwise.
+ * is present, or the session key otherwise.  If neither key is present, the
+ * credentials will not be encrypted, and the message should only be sent over
+ * a secure channel.  No replay cache entry is used in this case.
  *
  * @retval
  *  0 Success
@@ -5503,8 +5532,9 @@ krb5_mk_1cred(krb5_context context, krb5_auth_context auth_context,
  * @param [out] creds_out       Null-terminated array of forwarded credentials
  * @param [out] rdata_out       Replay data (NULL if not needed)
  *
- * @note The @a rdata_out argument is required if #KRB5_AUTH_CONTEXT_RET_TIME
- * or #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set in the @a auth_context.`
+ * @note The @a rdata_out argument is required if the
+ * #KRB5_AUTH_CONTEXT_RET_TIME or #KRB5_AUTH_CONTEXT_RET_SEQUENCE flag is set
+ * in @a auth_context.`
  *
  * @a creddata will be decrypted using the receiving subkey if it is present in
  * @a auth_context, or the session key if the receiving subkey is not present
diff --git a/src/lib/krb5/krb/auth_con.c b/src/lib/krb5/krb/auth_con.c
index c86a4af..a8a97eb 100644
--- a/src/lib/krb5/krb/auth_con.c
+++ b/src/lib/krb5/krb/auth_con.c
@@ -78,6 +78,7 @@ krb5_auth_con_free(krb5_context context, krb5_auth_context auth_context)
     if (auth_context->ad_context)
         krb5_authdata_context_free(context, auth_context->ad_context);
     free(auth_context);
+    k5_memrcache_free(context, auth_context->memrcache);
     return 0;
 }
 
diff --git a/src/lib/krb5/krb/auth_con.h b/src/lib/krb5/krb/auth_con.h
index 821b41e..a010ae4 100644
--- a/src/lib/krb5/krb/auth_con.h
+++ b/src/lib/krb5/krb/auth_con.h
@@ -3,6 +3,8 @@
 #ifndef KRB5_AUTH_CONTEXT
 #define KRB5_AUTH_CONTEXT
 
+#include "../rcache/memrcache.h"
+
 struct _krb5_auth_context {
     krb5_magic          magic;
     krb5_address      * remote_addr;
@@ -21,6 +23,7 @@ struct _krb5_auth_context {
     krb5_cksumtype      safe_cksumtype;         /* mk_safe, ... */
     krb5_data           cstate;                 /* mk_priv, rd_priv only */
     krb5_rcache         rcache;
+    k5_memrcache        memrcache;
     krb5_enctype      * permitted_etypes;       /* rd_req */
     krb5_mk_req_checksum_func checksum_func;
     void *checksum_func_data;
diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h
index 3e80241..d45adab 100644
--- a/src/lib/krb5/krb/int-proto.h
+++ b/src/lib/krb5/krb/int-proto.h
@@ -159,13 +159,16 @@ k5_privsafe_gen_addrs(krb5_context context, krb5_auth_context authcon,
                       krb5_address *lstorage, krb5_address *rstorage,
                       krb5_address **local_out, krb5_address **remote_out);
 
-/* If the DO_TIME flag is set in authcon, store a replay record.  If check_time
- * is true, also check that the timestamp is within clock skew. */
+/*
+ * If the DO_TIME flag is set in authcon, store a replay record in a memory
+ * replay cache (initializing one if necessary).  Either enc or cksum must be
+ * non-null.  If rdata is not null, also check that its timestamp is within
+ * clock skew.
+ */
 krb5_error_code
 k5_privsafe_check_replay(krb5_context context, krb5_auth_context authcon,
-                         krb5_address *addr, const char *uniq,
                          const krb5_replay_data *rdata,
-                         krb5_boolean check_time);
+                         const krb5_enc_data *enc, const krb5_checksum *cksum);
 
 krb5_boolean
 k5_privsafe_check_seqnum(krb5_context ctx, krb5_auth_context ac,
diff --git a/src/lib/krb5/krb/mk_cred.c b/src/lib/krb5/krb/mk_cred.c
index 003a0fd..62224f1 100644
--- a/src/lib/krb5/krb/mk_cred.c
+++ b/src/lib/krb5/krb/mk_cred.c
@@ -66,14 +66,16 @@ encrypt_credencpart(krb5_context context, krb5_cred_enc_part *encpart,
 
 /*
  * Marshal a KRB-CRED message into der_out, encrypted with key (or unencrypted
- * if key is NULL).  Use the timestamp and sequence number from rdata and the
- * addresses from local_addr and remote_addr (either of which may be NULL).
- * der_out should be freed by the caller when finished.
+ * if key is NULL).  Store the ciphertext in enc_out.  Use the timestamp and
+ * sequence number from rdata and the addresses from local_addr and remote_addr
+ * (either of which may be NULL).  der_out and enc_out should be freed by the
+ * caller when finished.
  */
 static krb5_error_code
 create_krbcred(krb5_context context, krb5_creds **creds, krb5_key key,
                const krb5_replay_data *rdata, krb5_address *local_addr,
-               krb5_address *remote_addr, krb5_data **der_out)
+               krb5_address *remote_addr, krb5_data **der_out,
+               krb5_enc_data *enc_out)
 {
     krb5_error_code ret;
     krb5_cred_enc_part credenc;
@@ -84,6 +86,7 @@ create_krbcred(krb5_context context, krb5_creds **creds, krb5_key key,
     size_t i, ncreds;
 
     *der_out = NULL;
+    memset(enc_out, 0, sizeof(*enc_out));
     memset(&enc, 0, sizeof(enc));
 
     for (ncreds = 0; creds[ncreds] != NULL; ncreds++);
@@ -137,6 +140,9 @@ create_krbcred(krb5_context context, krb5_creds **creds, krb5_key key,
     if (ret)
         goto cleanup;
 
+    *enc_out = enc;
+    memset(&enc, 0, sizeof(enc));
+
 cleanup:
     krb5_free_tickets(context, tickets);
     krb5_free_data_contents(context, &enc.ciphertext);
@@ -154,9 +160,11 @@ krb5_mk_ncred(krb5_context context, krb5_auth_context authcon,
     krb5_key key;
     krb5_replay_data rdata;
     krb5_data *der_krbcred = NULL;
+    krb5_enc_data enc;
     krb5_address *local_addr, *remote_addr, lstorage, rstorage;
 
     *der_out = NULL;
+    memset(&enc, 0, sizeof(enc));
     memset(&lstorage, 0, sizeof(lstorage));
     memset(&rstorage, 0, sizeof(rstorage));
 
@@ -180,14 +188,15 @@ krb5_mk_ncred(krb5_context context, krb5_auth_context authcon,
 
     key = (authcon->send_subkey != NULL) ? authcon->send_subkey : authcon->key;
     ret = create_krbcred(context, creds, key, &rdata, local_addr, remote_addr,
-                         &der_krbcred);
+                         &der_krbcred, &enc);
     if (ret)
         goto cleanup;
 
-    ret = k5_privsafe_check_replay(context, authcon, authcon->local_addr,
-                                   "_forw", &rdata, FALSE);
-    if (ret)
-        goto cleanup;
+    if (key != NULL) {
+        ret = k5_privsafe_check_replay(context, authcon, NULL, &enc, NULL);
+        if (ret)
+            goto cleanup;
+    }
 
     *der_out = der_krbcred;
     der_krbcred = NULL;
@@ -196,6 +205,7 @@ krb5_mk_ncred(krb5_context context, krb5_auth_context authcon,
         authcon->local_seq_number++;
 
 cleanup:
+    krb5_free_data_contents(context, &enc.ciphertext);
     free(lstorage.contents);
     free(rstorage.contents);
     if (der_krbcred != NULL) {
diff --git a/src/lib/krb5/krb/mk_priv.c b/src/lib/krb5/krb/mk_priv.c
index d66ab8a..d023169 100644
--- a/src/lib/krb5/krb/mk_priv.c
+++ b/src/lib/krb5/krb/mk_priv.c
@@ -35,16 +35,16 @@
 #include "auth_con.h"
 
 /*
- * Marshal a KRB-PRIV message into der_out, encrypted with key.  Use the
- * timestamp and sequence number from rdata and the addresses from local_addr
- * and remote_addr (the second of which may be NULL).  der_out should be freed
- * by the caller when finished.
+ * Marshal a KRB-PRIV message into der_out, encrypted with key.  Store the
+ * ciphertext in enc_out.  Use the timestamp and sequence number from rdata and
+ * the addresses from local_addr and remote_addr (the second of which may be
+ * NULL).  der_out and enc_out should be freed by the caller when finished.
  */
 static krb5_error_code
 create_krbpriv(krb5_context context, const krb5_data *userdata,
                krb5_key key, const krb5_replay_data *rdata,
                krb5_address *local_addr, krb5_address *remote_addr,
-               krb5_data *cstate, krb5_data *der_out)
+               krb5_data *cstate, krb5_data *der_out, krb5_enc_data *enc_out)
 {
     krb5_enctype enctype = krb5_k_key_enctype(context, key);
     krb5_error_code ret;
@@ -91,6 +91,9 @@ create_krbpriv(krb5_context context, const krb5_data *userdata,
     *der_out = *der_krbpriv;
     free(der_krbpriv);
 
+    *enc_out = privmsg.enc_part;
+    memset(&privmsg.enc_part, 0, sizeof(privmsg.enc_part));
+
 cleanup:
     zapfree(privmsg.enc_part.ciphertext.data,
             privmsg.enc_part.ciphertext.length);
@@ -111,9 +114,11 @@ krb5_mk_priv(krb5_context context, krb5_auth_context authcon,
     krb5_key key;
     krb5_replay_data rdata;
     krb5_data der_krbpriv = empty_data();
+    krb5_enc_data enc;
     krb5_address *local_addr, *remote_addr, lstorage, rstorage;
 
     *der_out = empty_data();
+    memset(&enc, 0, sizeof(enc));
     memset(&lstorage, 0, sizeof(lstorage));
     memset(&rstorage, 0, sizeof(rstorage));
     if (!authcon->local_addr)
@@ -130,12 +135,11 @@ krb5_mk_priv(krb5_context context, krb5_auth_context authcon,
 
     key = (authcon->send_subkey != NULL) ? authcon->send_subkey : authcon->key;
     ret = create_krbpriv(context, userdata, key, &rdata, local_addr,
-                         remote_addr, &authcon->cstate, &der_krbpriv);
+                         remote_addr, &authcon->cstate, &der_krbpriv, &enc);
     if (ret)
         goto cleanup;
 
-    ret = k5_privsafe_check_replay(context, authcon, authcon->local_addr,
-                                   "_priv", &rdata, FALSE);
+    ret = k5_privsafe_check_replay(context, authcon, NULL, &enc, NULL);
     if (ret)
         goto cleanup;
 
@@ -147,6 +151,7 @@ krb5_mk_priv(krb5_context context, krb5_auth_context authcon,
 
 cleanup:
     krb5_free_data_contents(context, &der_krbpriv);
+    zapfree(enc.ciphertext.data, enc.ciphertext.length);
     free(lstorage.contents);
     free(rstorage.contents);
     return ret;
diff --git a/src/lib/krb5/krb/mk_safe.c b/src/lib/krb5/krb/mk_safe.c
index 02ee725..8a10616 100644
--- a/src/lib/krb5/krb/mk_safe.c
+++ b/src/lib/krb5/krb/mk_safe.c
@@ -36,15 +36,16 @@
 
 /*
  * Marshal a KRB-SAFE message into der_out, with a keyed checksum of type
- * sumtype.  Use the timestamp and sequence number from rdata and the addresses
- * from local_addr and remote_addr (the second of which may be NULL).  der_out
- * should be freed by the caller when finished.
+ * sumtype.  Store the checksum in cksum_out.  Use the timestamp and sequence
+ * number from rdata and the addresses from local_addr and remote_addr (the
+ * second of which may be NULL).  der_out and cksum_out should be freed by the
+ * caller when finished.
  */
 static krb5_error_code
 create_krbsafe(krb5_context context, const krb5_data *userdata, krb5_key key,
                const krb5_replay_data *rdata, krb5_address *local_addr,
                krb5_address *remote_addr, krb5_cksumtype sumtype,
-               krb5_data *der_out)
+               krb5_data *der_out, krb5_checksum *cksum_out)
 {
     krb5_error_code ret;
     krb5_safe safemsg;
@@ -85,12 +86,14 @@ create_krbsafe(krb5_context context, const krb5_data *userdata, krb5_key key,
     /* Encode the message again with the real checksum. */
     safemsg.checksum = &safe_checksum;
     ret = encode_krb5_safe(&safemsg, &der_krbsafe);
-    krb5_free_checksum_contents(context, &safe_checksum);
-    if (ret)
+    if (ret) {
+        krb5_free_checksum_contents(context, &safe_checksum);
         return ret;
+    }
 
     *der_out = *der_krbsafe;
     free(der_krbsafe);
+    *cksum_out = safe_checksum;
     return 0;
 }
 
@@ -126,10 +129,12 @@ krb5_mk_safe(krb5_context context, krb5_auth_context authcon,
     krb5_key key;
     krb5_replay_data rdata;
     krb5_data der_krbsafe = empty_data();
+    krb5_checksum cksum;
     krb5_address *local_addr, *remote_addr, lstorage, rstorage;
     krb5_cksumtype sumtype;
 
     *der_out = empty_data();
+    memset(&cksum, 0, sizeof(cksum));
     memset(&lstorage, 0, sizeof(lstorage));
     memset(&rstorage, 0, sizeof(rstorage));
     if (authcon->local_addr == NULL)
@@ -147,12 +152,11 @@ krb5_mk_safe(krb5_context context, krb5_auth_context authcon,
     key = (authcon->send_subkey != NULL) ? authcon->send_subkey : authcon->key;
     sumtype = safe_cksumtype(context, authcon, key->keyblock.enctype);
     ret = create_krbsafe(context, userdata, key, &rdata, local_addr,
-                         remote_addr, sumtype, &der_krbsafe);
+                         remote_addr, sumtype, &der_krbsafe, &cksum);
     if (ret)
         goto cleanup;
 
-    ret = k5_privsafe_check_replay(context, authcon, authcon->local_addr,
-                                   "_safe", &rdata, FALSE);
+    ret = k5_privsafe_check_replay(context, authcon, NULL, NULL, &cksum);
     if (ret)
         goto cleanup;
 
@@ -164,6 +168,7 @@ krb5_mk_safe(krb5_context context, krb5_auth_context authcon,
 
 cleanup:
     krb5_free_data_contents(context, &der_krbsafe);
+    krb5_free_checksum_contents(context, &cksum);
     free(lstorage.contents);
     free(rstorage.contents);
     return ret;
diff --git a/src/lib/krb5/krb/privsafe.c b/src/lib/krb5/krb/privsafe.c
index a1e5230..461e11f 100644
--- a/src/lib/krb5/krb/privsafe.c
+++ b/src/lib/krb5/krb/privsafe.c
@@ -106,38 +106,38 @@ k5_privsafe_gen_addrs(krb5_context context, krb5_auth_context authcon,
 
 krb5_error_code
 k5_privsafe_check_replay(krb5_context context, krb5_auth_context authcon,
-                         krb5_address *addr, const char *uniq,
                          const krb5_replay_data *rdata,
-                         krb5_boolean check_time)
+                         const krb5_enc_data *enc, const krb5_checksum *cksum)
 {
     krb5_error_code ret;
-    krb5_donot_replay replay;
-    char *client = NULL;
+    krb5_data tag;
+
+    assert(enc != NULL || cksum != NULL);
 
     if (!(authcon->auth_context_flags & KRB5_AUTH_CONTEXT_DO_TIME))
         return 0;
 
-    if (authcon->rcache == NULL)
-        return KRB5_RC_REQUIRED;
-
-    if (check_time) {
+    if (rdata != NULL) {
         ret = krb5_check_clockskew(context, rdata->timestamp);
         if (ret)
             return ret;
     }
 
-    ret = krb5_gen_replay_name(context, addr, uniq, &client);
-    if (ret)
-        return ret;
-
-    replay.client = client;
-    replay.server = "";
-    replay.msghash = NULL;
-    replay.cusec = rdata->usec;
-    replay.ctime = rdata->timestamp;
-    ret = krb5_rc_store(context, authcon->rcache, &replay);
-    free(replay.client);
-    return ret;
+    if (enc != NULL) {
+        ret = k5_rc_tag_from_ciphertext(context, enc, &tag);
+        if (ret)
+            return ret;
+    } else {
+        tag = make_data(cksum->contents, cksum->length);
+    }
+
+    if (authcon->memrcache == NULL) {
+        ret = k5_memrcache_create(context, &authcon->memrcache);
+        if (ret)
+            return ret;
+    }
+
+    return k5_memrcache_store(context, authcon->memrcache, &tag);
 }
 
 /*
diff --git a/src/lib/krb5/krb/rd_cred.c b/src/lib/krb5/krb/rd_cred.c
index 7fdd103..720147c 100644
--- a/src/lib/krb5/krb/rd_cred.c
+++ b/src/lib/krb5/krb/rd_cred.c
@@ -158,9 +158,6 @@ krb5_rd_cred(krb5_context context, krb5_auth_context authcon,
         replaydata_out == NULL)
         return KRB5_RC_REQUIRED;
 
-    if ((flags & KRB5_AUTH_CONTEXT_DO_TIME) && authcon->rcache == NULL)
-        return KRB5_RC_REQUIRED;
-
     ret = decode_krb5_cred(creddata, &krbcred);
     if (ret)
         goto cleanup;
@@ -173,13 +170,13 @@ krb5_rd_cred(krb5_context context, krb5_auth_context authcon,
     if (ret)
         goto cleanup;
 
-    rdata.timestamp = encpart->timestamp;
-    rdata.usec = encpart->usec;
-    rdata.seq = encpart->nonce;
-    ret = k5_privsafe_check_replay(context, authcon, authcon->remote_addr,
-                                   "_forw", &rdata, TRUE);
-    if (ret)
-        goto cleanup;
+    if (authcon->recv_subkey != NULL || authcon->key != NULL) {
+        rdata.timestamp = encpart->timestamp;
+        ret = k5_privsafe_check_replay(context, authcon, &rdata,
+                                       &krbcred->enc_part, NULL);
+        if (ret)
+            goto cleanup;
+    }
 
     if (flags & KRB5_AUTH_CONTEXT_DO_SEQUENCE) {
         if (authcon->remote_seq_number != (uint32_t)encpart->nonce) {
diff --git a/src/lib/krb5/krb/rd_priv.c b/src/lib/krb5/krb/rd_priv.c
index 34eb656..29a9746 100644
--- a/src/lib/krb5/krb/rd_priv.c
+++ b/src/lib/krb5/krb/rd_priv.c
@@ -36,13 +36,15 @@
 
 /*
  * Unmarshal a KRB-PRIV message from der_krbpriv, placing the confidential user
- * data in *userdata_out and replay data in *rdata_out.  The caller should free
- * *userdata_out when finished.
+ * data in *userdata_out, ciphertext in *enc_out, and replay data in
+ * *rdata_out.  The caller should free *userdata_out and *enc_out when
+ * finished.
  */
 static krb5_error_code
 read_krbpriv(krb5_context context, krb5_auth_context authcon,
              const krb5_data *der_krbpriv, const krb5_key key,
-             krb5_replay_data *rdata_out, krb5_data *userdata_out)
+             krb5_replay_data *rdata_out, krb5_data *userdata_out,
+             krb5_enc_data *enc_out)
 {
     krb5_error_code ret;
     krb5_priv *privmsg = NULL;
@@ -84,6 +86,9 @@ read_krbpriv(krb5_context context, krb5_auth_context authcon,
     *userdata_out = encpart->user_data;
     encpart->user_data.data = NULL;
 
+    *enc_out = privmsg->enc_part;
+    memset(&privmsg->enc_part, 0, sizeof(privmsg->enc_part));
+
 cleanup:
     krb5_free_priv_enc_part(context, encpart);
     krb5_free_priv(context, privmsg);
@@ -99,26 +104,24 @@ krb5_rd_priv(krb5_context context, krb5_auth_context authcon,
     krb5_error_code ret;
     krb5_key key;
     krb5_replay_data rdata;
+    krb5_enc_data enc;
     krb5_data userdata = empty_data();
     const krb5_int32 flags = authcon->auth_context_flags;
 
     *userdata_out = empty_data();
+    memset(&enc, 0, sizeof(enc));
 
     if (((flags & KRB5_AUTH_CONTEXT_RET_TIME) ||
          (flags & KRB5_AUTH_CONTEXT_RET_SEQUENCE)) && rdata_out == NULL)
         return KRB5_RC_REQUIRED;
 
-    if ((flags & KRB5_AUTH_CONTEXT_DO_TIME) && authcon->remote_addr == NULL)
-        return KRB5_REMOTE_ADDR_REQUIRED;
-
     key = (authcon->recv_subkey != NULL) ? authcon->recv_subkey : authcon->key;
     memset(&rdata, 0, sizeof(rdata));
-    ret = read_krbpriv(context, authcon, inbuf, key, &rdata, &userdata);
+    ret = read_krbpriv(context, authcon, inbuf, key, &rdata, &userdata, &enc);
     if (ret)
         goto cleanup;
 
-    ret = k5_privsafe_check_replay(context, authcon, authcon->remote_addr,
-                                   "_priv", &rdata, TRUE);
+    ret = k5_privsafe_check_replay(context, authcon, &rdata, &enc, NULL);
     if (ret)
         goto cleanup;
 
@@ -141,6 +144,7 @@ krb5_rd_priv(krb5_context context, krb5_auth_context authcon,
     userdata = empty_data();
 
 cleanup:
+    krb5_free_data_contents(context, &enc.ciphertext);
     krb5_free_data_contents(context, &userdata);
     return ret;
 }
diff --git a/src/lib/krb5/krb/rd_safe.c b/src/lib/krb5/krb/rd_safe.c
index eab7f6d..17f2b27 100644
--- a/src/lib/krb5/krb/rd_safe.c
+++ b/src/lib/krb5/krb/rd_safe.c
@@ -36,23 +36,26 @@
 
 /*
  * Unmarshal a KRB-SAFE message from der_krbsafe, placing the
- * integrity-protected user data in *userdata_out and replay data in
- * *rdata_out.  The caller should free *userdata_out when finished.
+ * integrity-protected user data in *userdata_out, replay data in *rdata_out,
+ * and checksum in *cksum_out.  The caller should free *userdata_out and
+ * *cksum_out when finished.
  */
 static krb5_error_code
 read_krbsafe(krb5_context context, krb5_auth_context ac,
              const krb5_data *der_krbsafe, krb5_key key,
-             krb5_replay_data *rdata_out, krb5_data *userdata_out)
+             krb5_replay_data *rdata_out, krb5_data *userdata_out,
+             krb5_checksum **cksum_out)
 {
     krb5_error_code ret;
     krb5_safe *krbsafe;
     krb5_data *safe_body = NULL, *der_zerosafe = NULL;
-    krb5_checksum zero_cksum, *safe_cksum;
+    krb5_checksum zero_cksum, *safe_cksum = NULL;
     krb5_octet zero_octet = 0;
     krb5_boolean valid;
     struct krb5_safe_with_body swb;
 
     *userdata_out = empty_data();
+    *cksum_out = NULL;
     if (!krb5_is_krb_safe(der_krbsafe))
         return KRB5KRB_AP_ERR_MSG_TYPE;
 
@@ -75,7 +78,8 @@ read_krbsafe(krb5_context context, krb5_auth_context ac,
     if (ret)
         goto cleanup;
 
-    /* Regenerate the KRB-SAFE message without the checksum. */
+    /* Regenerate the KRB-SAFE message without the checksum.  Save the message
+     * checksum to verify. */
     safe_cksum = krbsafe->checksum;
     zero_cksum.length = 0;
     zero_cksum.checksum_type = 0;
@@ -84,7 +88,7 @@ read_krbsafe(krb5_context context, krb5_auth_context ac,
     swb.body = safe_body;
     swb.safe = krbsafe;
     ret = encode_krb5_safe_with_body(&swb, &der_zerosafe);
-    krbsafe->checksum = safe_cksum;
+    krbsafe->checksum = NULL;
     if (ret)
         goto cleanup;
 
@@ -109,6 +113,9 @@ read_krbsafe(krb5_context context, krb5_auth_context ac,
     *userdata_out = krbsafe->user_data;
     krbsafe->user_data.data = NULL;
 
+    *cksum_out = safe_cksum;
+    safe_cksum = NULL;
+
 cleanup:
     if (der_zerosafe != NULL) {
         zap(der_zerosafe->data, der_zerosafe->length);
@@ -116,6 +123,7 @@ cleanup:
     }
     krb5_free_data(context, safe_body);
     krb5_free_safe(context, krbsafe);
+    krb5_free_checksum(context, safe_cksum);
     return ret;
 }
 
@@ -128,6 +136,7 @@ krb5_rd_safe(krb5_context context, krb5_auth_context authcon,
     krb5_key key;
     krb5_replay_data rdata;
     krb5_data userdata = empty_data();
+    krb5_checksum *cksum;
     const krb5_int32 flags = authcon->auth_context_flags;
 
     *userdata_out = empty_data();
@@ -136,17 +145,14 @@ krb5_rd_safe(krb5_context context, krb5_auth_context authcon,
          (flags & KRB5_AUTH_CONTEXT_RET_SEQUENCE)) && rdata_out == NULL)
         return KRB5_RC_REQUIRED;
 
-    if ((flags & KRB5_AUTH_CONTEXT_DO_TIME) && authcon->remote_addr == NULL)
-        return KRB5_REMOTE_ADDR_REQUIRED;
-
     key = (authcon->recv_subkey != NULL) ? authcon->recv_subkey : authcon->key;
     memset(&rdata, 0, sizeof(rdata));
-    ret = read_krbsafe(context, authcon, inbuf, key, &rdata, &userdata);
+    ret = read_krbsafe(context, authcon, inbuf, key, &rdata, &userdata,
+                       &cksum);
     if (ret)
         goto cleanup;
 
-    ret = k5_privsafe_check_replay(context, authcon, authcon->remote_addr,
-                                   "_safe", &rdata, TRUE);
+    ret = k5_privsafe_check_replay(context, authcon, &rdata, NULL, cksum);
     if (ret)
         goto cleanup;
 
@@ -170,5 +176,6 @@ krb5_rd_safe(krb5_context context, krb5_auth_context authcon,
 
 cleanup:
     krb5_free_data_contents(context, &userdata);
+    krb5_free_checksum(context, cksum);
     return ret;
 }
diff --git a/src/lib/krb5/rcache/rc_conv.c b/src/lib/krb5/rcache/rc_conv.c
index f2fe528..ff33a97 100644
--- a/src/lib/krb5/rcache/rc_conv.c
+++ b/src/lib/krb5/rcache/rc_conv.c
@@ -74,3 +74,23 @@ krb5_rc_hash_message(krb5_context context, const krb5_data *message,
     *out = hash;
     return 0;
 }
+
+krb5_error_code
+k5_rc_tag_from_ciphertext(krb5_context context, const krb5_enc_data *enc,
+                          krb5_data *tag_out)
+{
+    krb5_error_code ret;
+    const krb5_data *cdata = &enc->ciphertext;
+    unsigned int len;
+
+    *tag_out = empty_data();
+
+    ret = krb5_c_crypto_length(context, enc->enctype,
+                               KRB5_CRYPTO_TYPE_CHECKSUM, &len);
+    if (ret)
+        return ret;
+    if (cdata->length < len)
+        return EINVAL;
+    *tag_out = make_data(cdata->data + cdata->length - len, len);
+    return 0;
+}
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 0eec2b6..5afbe21 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -8,10 +8,10 @@ RUN_DB_TEST = $(RUN_SETUP) KRB5_KDC_PROFILE=kdc.conf KRB5_CONFIG=krb5.conf \
 
 OBJS= adata.o etinfo.o forward.o gcred.o hist.o hooks.o hrealm.o \
 	icinterleave.o icred.o kdbtest.o localauth.o plugorder.o rdreq.o \
-	responder.o s2p.o s4u2proxy.o unlockiter.o
+	replay.o responder.o s2p.o s4u2proxy.o unlockiter.o
 EXTRADEPSRCS= adata.c etinfo.c forward.c gcred.c hist.c hooks.c hrealm.c \
-	icinterleave.c icred.c kdbtest.c localauth.c plugorder.c rdreq.o \
-	responder.c s2p.c s4u2proxy.c unlockiter.c
+	icinterleave.c icred.c kdbtest.c localauth.c plugorder.c rdreq.c \
+	replay.c responder.c s2p.c s4u2proxy.c unlockiter.c
 
 TEST_DB = ./testdb
 TEST_REALM = FOO.TEST.REALM
@@ -63,6 +63,9 @@ plugorder: plugorder.o $(KRB5_BASE_DEPLIBS)
 rdreq: rdreq.o $(KRB5_BASE_DEPLIBS)
 	$(CC_LINK) -o $@ rdreq.o $(KRB5_BASE_LIBS)
 
+replay: replay.o $(KRB5_BASE_DEPLIBS)
+	$(CC_LINK) -o $@ replay.o $(KRB5_BASE_LIBS)
+
 responder: responder.o $(KRB5_BASE_DEPLIBS)
 	$(CC_LINK) -o $@ responder.o $(KRB5_BASE_LIBS)
 
@@ -119,7 +122,7 @@ kdb_check: kdc.conf krb5.conf
 	$(RM) $(TEST_DB)* stash_file
 
 check-pytests: adata etinfo forward gcred hist hooks hrealm icinterleave icred
-check-pytests: kdbtest localauth plugorder rdreq responder s2p s4u2proxy
+check-pytests: kdbtest localauth plugorder rdreq replay responder s2p s4u2proxy
 check-pytests: unlockiter
 	$(RUNPYTEST) $(srcdir)/t_general.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_hooks.py $(PYTESTFLAGS)
@@ -178,10 +181,11 @@ check-pytests: unlockiter
 	$(RUNPYTEST) $(srcdir)/t_kdcpolicy.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_u2u.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_kdcoptions.py $(PYTESTFLAGS)
+	$(RUNPYTEST) $(srcdir)/t_replay.py $(PYTESTFLAGS)
 
 clean:
 	$(RM) adata etinfo forward gcred hist hooks hrealm icinterleave icred
-	$(RM) kdbtest localauth plugorder rdreq responder s2p s4u2proxy
+	$(RM) kdbtest localauth plugorder rdreq replay responder s2p s4u2proxy
 	$(RM) unlockiter
 	$(RM) krb5.conf kdc.conf
 	$(RM) -rf kdc_realm/sandbox ldap
diff --git a/src/tests/replay.c b/src/tests/replay.c
new file mode 100644
index 0000000..1703123
--- /dev/null
+++ b/src/tests/replay.c
@@ -0,0 +1,172 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* tests/replay.c - test replay cache using libkrb5 functions */
+/*
+ * Copyright (C) 2019 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDER 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 "k5-int.h"
+
+int
+main(int argc, char **argv)
+{
+    krb5_error_code ret;
+    krb5_context ctx;
+    krb5_auth_context c_authcon, s_authcon, s_authcon2;
+    krb5_rcache rc;
+    krb5_ccache cc;
+    krb5_principal client, server;
+    krb5_creds mcred, *cred, **tmpcreds;
+    krb5_data der_apreq, der_krbsafe, der_krbpriv, *der_krbcred, tmpdata;
+    krb5_address addr;
+    struct in_addr inaddr;
+    const char *server_name;
+
+    assert(argc == 2);
+    server_name = argv[1];
+
+    /* Create client and server auth contexts.  (They will use a replay cache
+     * by default.) */
+    ret = krb5_init_context(&ctx);
+    assert(ret == 0);
+    ret = krb5_auth_con_init(ctx, &c_authcon);
+    assert(ret == 0);
+    ret = krb5_auth_con_init(ctx, &s_authcon);
+    assert(ret == 0);
+
+    /* Set dummy addresses for the auth contexts. */
+    memset(&inaddr, 0, sizeof(inaddr));
+    addr.addrtype = ADDRTYPE_INET;
+    addr.length = sizeof(inaddr);
+    addr.contents = (uint8_t *)&inaddr;
+    ret = krb5_auth_con_setaddrs(ctx, c_authcon, &addr, &addr);
+    assert(ret == 0);
+    ret = krb5_auth_con_setaddrs(ctx, s_authcon, &addr, &addr);
+    assert(ret == 0);
+
+    /* Set up replay caches for the auth contexts. */
+    tmpdata = string2data("testclient");
+    ret = krb5_get_server_rcache(ctx, &tmpdata, &rc);
+    assert(ret == 0);
+    ret = krb5_auth_con_setrcache(ctx, c_authcon, rc);
+    assert(ret == 0);
+    tmpdata = string2data("testserver");
+    ret = krb5_get_server_rcache(ctx, &tmpdata, &rc);
+    assert(ret == 0);
+    ret = krb5_auth_con_setrcache(ctx, s_authcon, rc);
+    assert(ret == 0);
+
+    /* Construct the client and server principal names. */
+    ret = krb5_cc_default(ctx, &cc);
+    assert(ret == 0);
+    ret = krb5_cc_get_principal(ctx, cc, &client);
+    assert(ret == 0);
+    ret = krb5_parse_name(ctx, server_name, &server);
+    assert(ret == 0);
+
+    /* Get credentials for the client. */
+    memset(&mcred, 0, sizeof(mcred));
+    mcred.client = client;
+    mcred.server = server;
+    ret = krb5_get_credentials(ctx, 0, cc, &mcred, &cred);
+    assert(ret == 0);
+
+    /* Send an AP-REP to establish the sessions. */
+    ret = krb5_mk_req_extended(ctx, &c_authcon, 0, NULL, cred, &der_apreq);
+    assert(ret == 0);
+    ret = krb5_rd_req(ctx, &s_authcon, &der_apreq, NULL, NULL, NULL, NULL);
+    assert(ret == 0);
+
+    /* Set up another server auth context with the same rcache name and replay
+     * the AP-REQ. */
+    ret = krb5_auth_con_init(ctx, &s_authcon2);
+    assert(ret == 0);
+    tmpdata = string2data("testserver");
+    ret = krb5_get_server_rcache(ctx, &tmpdata, &rc);
+    assert(ret == 0);
+    ret = krb5_auth_con_setrcache(ctx, s_authcon2, rc);
+    assert(ret == 0);
+    ret = krb5_rd_req(ctx, &s_authcon2, &der_apreq, NULL, NULL, NULL, NULL);
+    assert(ret == KRB5KRB_AP_ERR_REPEAT);
+    krb5_auth_con_free(ctx, s_authcon2);
+
+    /* Make a KRB-SAFE message with the client auth context. */
+    tmpdata = string2data("safemsg");
+    ret = krb5_mk_safe(ctx, c_authcon, &tmpdata, &der_krbsafe, NULL);
+    assert(ret == 0);
+    /* Play it back to the client to detect a reflection. */
+    ret = krb5_rd_safe(ctx, c_authcon, &der_krbsafe, &tmpdata, NULL);
+    assert(ret == KRB5KRB_AP_ERR_REPEAT);
+    /* Send it to the server auth context twice, to detect a replay. */
+    ret = krb5_rd_safe(ctx, s_authcon, &der_krbsafe, &tmpdata, NULL);
+    assert(ret == 0);
+    krb5_free_data_contents(ctx, &tmpdata);
+    ret = krb5_rd_safe(ctx, s_authcon, &der_krbsafe, &tmpdata, NULL);
+    assert(ret == KRB5KRB_AP_ERR_REPEAT);
+
+    /* Make a KRB-PRIV message with the client auth context. */
+    tmpdata = string2data("safemsg");
+    ret = krb5_mk_priv(ctx, c_authcon, &tmpdata, &der_krbpriv, NULL);
+    assert(ret == 0);
+    /* Play it back to the client to detect a reflection. */
+    ret = krb5_rd_priv(ctx, c_authcon, &der_krbpriv, &tmpdata, NULL);
+    assert(ret == KRB5KRB_AP_ERR_REPEAT);
+    /* Send it to the server auth context twice, to detect a replay. */
+    ret = krb5_rd_priv(ctx, s_authcon, &der_krbpriv, &tmpdata, NULL);
+    assert(ret == 0);
+    krb5_free_data_contents(ctx, &tmpdata);
+    ret = krb5_rd_priv(ctx, s_authcon, &der_krbpriv, &tmpdata, NULL);
+    assert(ret == KRB5KRB_AP_ERR_REPEAT);
+
+    /* Make a KRB-CRED message with the client auth context. */
+    tmpdata = string2data("safemsg");
+    ret = krb5_mk_1cred(ctx, c_authcon, cred, &der_krbcred, NULL);
+    assert(ret == 0);
+    /* Play it back to the client to detect a reflection. */
+    ret = krb5_rd_cred(ctx, c_authcon, der_krbcred, &tmpcreds, NULL);
+    assert(ret == KRB5KRB_AP_ERR_REPEAT);
+    /* Send it to the server auth context twice, to detect a replay. */
+    ret = krb5_rd_cred(ctx, s_authcon, der_krbcred, &tmpcreds, NULL);
+    assert(ret == 0);
+    krb5_free_tgt_creds(ctx, tmpcreds);
+    ret = krb5_rd_cred(ctx, s_authcon, der_krbcred, &tmpcreds, NULL);
+    assert(ret == KRB5KRB_AP_ERR_REPEAT);
+
+    krb5_free_data_contents(ctx, &der_apreq);
+    krb5_free_data_contents(ctx, &der_krbsafe);
+    krb5_free_data_contents(ctx, &der_krbpriv);
+    krb5_free_data(ctx, der_krbcred);
+    krb5_free_creds(ctx, cred);
+    krb5_cc_close(ctx, cc);
+    krb5_free_principal(ctx, client);
+    krb5_free_principal(ctx, server);
+    krb5_auth_con_free(ctx, c_authcon);
+    krb5_auth_con_free(ctx, s_authcon);
+    krb5_free_context(ctx);
+    return 0;
+}
diff --git a/src/tests/t_replay.py b/src/tests/t_replay.py
new file mode 100644
index 0000000..6ad58fe
--- /dev/null
+++ b/src/tests/t_replay.py
@@ -0,0 +1,6 @@
+from k5test import *
+
+realm = K5Realm()
+realm.run(['./replay', realm.host_princ])
+
+success('Replay tests')
_______________________________________________
cvs-krb5 mailing list
cvs-krb5@mit.edu
https://mailman.mit.edu/mailman/listinfo/cvs-krb5

home help back first fref pref prev next nref lref last post