]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
2929. [bug] Improved handling of GSS security contexts:
authorEvan Hunt <each@isc.org>
Fri, 9 Jul 2010 05:14:08 +0000 (05:14 +0000)
committerEvan Hunt <each@isc.org>
Fri, 9 Jul 2010 05:14:08 +0000 (05:14 +0000)
 - added LRU expiration for generated TSIGs
 - added the ability to use a non-default realm
                         - added new "realm" keyword in nsupdate
 - limited lifetime of generated keys to 1 hour
   or the lifetime of the context (whichever is
   smaller)
[RT #19737]

CHANGES
bin/nsupdate/nsupdate.c
bin/nsupdate/nsupdate.docbook
doc/arm/Bv9ARM-book.xml
lib/dns/gssapictx.c
lib/dns/include/dns/name.h
lib/dns/include/dns/tsig.h
lib/dns/name.c
lib/dns/tkey.c
lib/dns/tsig.c

diff --git a/CHANGES b/CHANGES
index 947dae171df8b1c983820d63309ab2f7354da07b..25a2c8b2c7ab9d766b9bdee4c831ead085a2815a 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,12 @@
+2929.  [bug]           Improved handling of GSS security contexts: 
+                        - added LRU expiration for generated TSIGs
+                        - added the ability to use a non-default realm
+                         - added new "realm" keyword in nsupdate
+                        - limited lifetime of generated keys to 1 hour
+                          or the lifetime of the context (whichever is
+                          smaller)
+                       [RT #19737]
+
 2925.  [bug]           Named failed to accept uncachable negative responses
                        from insecure zones. [RT# 21555]
 
index b70ea5bc3b27e2c2e53480fea809cfa185565fff..fd663071ff49ab30e94db19873f97ff1e6cf9069 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: nsupdate.c,v 1.173.66.4 2010/05/18 06:24:27 marka Exp $ */
+/* $Id: nsupdate.c,v 1.173.66.5 2010/07/09 05:14:07 each Exp $ */
 
 /*! \file */
 
@@ -195,6 +195,7 @@ ddebug(const char *format, ...) ISC_FORMAT_PRINTF(1, 2);
 #ifdef GSSAPI
 static dns_fixedname_t fkname;
 static isc_sockaddr_t *kserver = NULL;
+static char *realm = NULL;
 static char servicename[DNS_NAME_FORMATSIZE];
 static dns_name_t *keyname;
 typedef struct nsu_gssinfo {
@@ -548,7 +549,8 @@ setup_keystr(void) {
 
        debug("keycreate");
        result = dns_tsigkey_create(keyname, hmacname, secret, secretlen,
-                                   ISC_TRUE, NULL, 0, 0, mctx, NULL, &tsigkey);
+                                   ISC_FALSE, NULL, 0, 0, mctx, NULL,
+                                   &tsigkey);
        if (result != ISC_R_SUCCESS)
                fprintf(stderr, "could not create key from %s: %s\n",
                        keystr, dns_result_totext(result));
@@ -1462,7 +1464,7 @@ evaluate_key(char *cmdline) {
        if (tsigkey != NULL)
                dns_tsigkey_detach(&tsigkey);
        result = dns_tsigkey_create(keyname, hmacname, secret, secretlen,
-                                   ISC_TRUE, NULL, 0, 0, mctx, NULL,
+                                   ISC_FALSE, NULL, 0, 0, mctx, NULL,
                                    &tsigkey);
        isc_mem_free(mctx, secret);
        if (result != ISC_R_SUCCESS) {
@@ -1500,6 +1502,31 @@ evaluate_zone(char *cmdline) {
        return (STATUS_MORE);
 }
 
+static isc_uint16_t
+evaluate_realm(char *cmdline) {
+#ifdef GSSAPI
+       char *word;
+       char buf[1024];
+
+       word = nsu_strsep(&cmdline, " \t\r\n");
+       if (*word == 0) {
+               if (realm != NULL)
+                       isc_mem_free(mctx, realm);
+               realm = NULL;
+               return (STATUS_MORE);
+       }
+
+       snprintf(buf, sizeof(buf), "@%s", word);
+       realm = isc_mem_strdup(mctx, buf);
+       if (realm == NULL)
+               fatal("out of memory");
+       return (STATUS_MORE);
+#else
+        UNUSED(cmdline);
+       return (STATUS_SYNTAX);
+#endif
+}
+
 static isc_uint16_t
 evaluate_ttl(char *cmdline) {
        char *word;
@@ -1891,6 +1918,8 @@ get_next_command(void) {
                usegsstsig = ISC_FALSE;
                return (evaluate_key(cmdline));
        }
+       if (strcasecmp(word, "realm") == 0)
+               return (evaluate_realm(cmdline));
        if (strcasecmp(word, "gsstsig") == 0) {
 #ifdef GSSAPI
                usegsstsig = ISC_TRUE;
@@ -2423,7 +2452,7 @@ start_gssrequest(dns_name_t *master)
        servname = dns_fixedname_name(&fname);
 
        result = isc_string_printf(servicename, sizeof(servicename),
-                                  "DNS/%s", namestr);
+                                  "DNS/%s%s", namestr, realm ? realm : "");
        if (result != ISC_R_SUCCESS)
                fatal("isc_string_printf(servicename) failed: %s",
                      isc_result_totext(result));
@@ -2461,7 +2490,6 @@ start_gssrequest(dns_name_t *master)
                      isc_result_totext(result));
 
        /* Build first request. */
-
        context = GSS_C_NO_CONTEXT;
        result = dns_tkey_buildgssquery(rmsg, keyname, servname, NULL, 0,
                                        &context, use_win2k_gsstsig);
@@ -2763,6 +2791,10 @@ cleanup(void) {
                isc_mem_put(mctx, kserver, sizeof(isc_sockaddr_t));
                kserver = NULL;
        }
+       if (realm != NULL) {
+               isc_mem_free(mctx, realm);
+               realm = NULL;
+       }
 #endif
 
        ddebug("Shutting down task manager");
index e3e1469ccf6ee549c06250f9929005e2de7509b4..ee0075ad1f0e9778bd70f953d3c23c71062c9c7b 100644 (file)
@@ -18,7 +18,7 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id: nsupdate.docbook,v 1.41.42.1 2009/12/16 07:12:49 each Exp $ -->
+<!-- $Id: nsupdate.docbook,v 1.41.42.2 2010/07/09 05:14:07 each Exp $ -->
 <refentry id="man.nsupdate">
   <refentryinfo>
     <date>Aug 25, 2009</date>
           </listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term>
+            <command>gsstsig</command>
+          </term>
+          <listitem>
+            <para>
+             Use GSS-TSIG to sign the updated.  This is equivalent to
+             specifying <option>-g</option> on the commandline.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+            <command>oldgsstsig</command>
+          </term>
+          <listitem>
+            <para>
+             Use the Windows 2000 version of GSS-TSIG to sign the updated.
+             This is equivalent to specifying <option>-o</option> on the
+             commandline.
+            </para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+            <command>realm</command>
+            <arg choice="req"><optional>realm_name</optional></arg>
+          </term>
+          <listitem>
+            <para>
+             When using GSS-TSIG use <parameter>realm_name</parameter> rather
+             than the default realm in <filename>krb5.conf</filename>.  If no
+             realm is specified the saved realm is cleared.
+            </para>
+          </listitem>
+        </varlistentry>
+
         <varlistentry>
           <term>
               <command>prereq nxdomain</command>
index e2c5833f517bb887528b54521f6f73d511f9146e..7e827c4e13b09995a83e9035a054329b44ca32df 100644 (file)
@@ -18,7 +18,7 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- File: $Id: Bv9ARM-book.xml,v 1.450.4.9 2010/06/25 03:51:07 marka Exp $ -->
+<!-- File: $Id: Bv9ARM-book.xml,v 1.450.4.10 2010/07/09 05:14:07 each Exp $ -->
 <book xmlns:xi="http://www.w3.org/2001/XInclude">
   <title>BIND 9 Administrator Reference Manual</title>
 
@@ -5243,7 +5243,7 @@ badresp:1,adberr:0,findfail:0,valfail:0]
                the server can acquire through the default system
                key file, normally <filename>/etc/krb5.keytab</filename>.
                Normally this principal is of the form
-               "<userinput>dns/</userinput><varname>server.domain</varname>".
+               "<userinput>DNS/</userinput><varname>server.domain</varname>".
                To use GSS-TSIG, <command>tkey-domain</command>
                must also be set.
              </para>
index 4e0ae0a56243f902fda707517a14284c02a8d280..e7c05408546b7c814c5612b2a3daca37b9ae2345 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: gssapictx.c,v 1.14.104.3 2010/06/03 02:31:58 marka Exp $ */
+/* $Id: gssapictx.c,v 1.14.104.4 2010/07/09 05:14:08 each Exp $ */
 
 #include <config.h>
 
@@ -132,7 +132,7 @@ name_to_gbuffer(dns_name_t *name, isc_buffer_t *buffer,
                namep = &tname;
        }
 
-       result = dns_name_totext(namep, ISC_FALSE, buffer);
+       result = dns_name_toprincipal(namep, buffer);
        isc_buffer_putuint8(buffer, 0);
        isc_buffer_usedregion(buffer, &r);
        REGION_TO_GBUFFER(r, *gbuffer);
@@ -336,12 +336,15 @@ dst_gssapi_identitymatchesrealmkrb5(dns_name_t *signer, dns_name_t *name,
        char rbuf[DNS_NAME_FORMATSIZE];
        char *sname;
        char *rname;
+       isc_buffer_t buffer;
 
        /*
         * It is far, far easier to write the names we are looking at into
         * a string, and do string operations on them.
         */
-       dns_name_format(signer, sbuf, sizeof(sbuf));
+       isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
+       dns_name_toprincipal(signer, &buffer);
+       isc_buffer_putuint8(&buffer, 0);
        if (name != NULL)
                dns_name_format(name, nbuf, sizeof(nbuf));
        dns_name_format(realm, rbuf, sizeof(rbuf));
@@ -351,7 +354,7 @@ dst_gssapi_identitymatchesrealmkrb5(dns_name_t *signer, dns_name_t *name,
         * does not exist, we don't have something we like, so we fail our
         * compare.
         */
-       rname = strstr(sbuf, "\\@");
+       rname = strchr(sbuf, '@');
        if (rname == NULL)
                return (isc_boolean_false);
        *rname = '\0';
@@ -405,12 +408,15 @@ dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
        char *sname;
        char *nname;
        char *rname;
+       isc_buffer_t buffer;
 
        /*
         * It is far, far easier to write the names we are looking at into
         * a string, and do string operations on them.
         */
-       dns_name_format(signer, sbuf, sizeof(sbuf));
+       isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
+       dns_name_toprincipal(signer, &buffer);
+       isc_buffer_putuint8(&buffer, 0);
        if (name != NULL)
                dns_name_format(name, nbuf, sizeof(nbuf));
        dns_name_format(realm, rbuf, sizeof(rbuf));
@@ -420,17 +426,17 @@ dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
         * does not exist, we don't have something we like, so we fail our
         * compare.
         */
-       rname = strstr(sbuf, "\\@");
+       rname = strchr(sbuf, '@');
        if (rname == NULL)
                return (isc_boolean_false);
-       sname = strstr(sbuf, "\\$");
+       sname = strchr(sbuf, '$');
        if (sname == NULL)
                return (isc_boolean_false);
 
        /*
         * Verify that the $ and @ follow one another.
         */
-       if (rname - sname != 2)
+       if (rname - sname != 1)
                return (isc_boolean_false);
 
        /*
@@ -442,8 +448,7 @@ dst_gssapi_identitymatchesrealmms(dns_name_t *signer, dns_name_t *name,
         *    machinename$@EXAMPLE.COM
         * format.
         */
-       *rname = '\0';
-       rname += 2;
+       rname++;
        *sname = '\0';
        sname = sbuf;
 
index 3c924bbddf8724b1f42ecdd135e0aae429217617..f60b01caea7e669aabc5878cff5f5be88856caff 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: name.h,v 1.132.104.1 2009/12/24 00:35:21 each Exp $ */
+/* $Id: name.h,v 1.132.104.2 2010/07/09 05:14:08 each Exp $ */
 
 #ifndef DNS_NAME_H
 #define DNS_NAME_H 1
@@ -802,9 +802,18 @@ dns_name_fromtext(dns_name_t *name, isc_buffer_t *source,
  *\li  #ISC_R_UNEXPECTEDEND
  */
 
+#define DNS_NAME_OMITFINALDOT  0x01U
+#define DNS_NAME_MASTERFILE    0x02U   /* escape $ and @ */
+
+isc_result_t
+dns_name_toprincipal(dns_name_t *name, isc_buffer_t *target);
+
 isc_result_t
 dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
                isc_buffer_t *target);
+
+isc_result_t
+dns_name_totext2(dns_name_t *name, unsigned int options, isc_buffer_t *target);
 /*%<
  * Convert 'name' into text format, storing the result in 'target'.
  *
@@ -812,6 +821,12 @@ dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
  *\li  If 'omit_final_dot' is true, then the final '.' in absolute
  *     names other than the root name will be omitted.
  *
+ *\li  If DNS_NAME_OMITFINALDOT is set in options, then the final '.'
+ *     in absolute names other than the root name will be omitted.
+ *
+ *\li  If DNS_NAME_MASTERFILE is set in options, '$' and '@' will also
+ *     be escaped.
+ *
  *\li  If dns_name_countlabels == 0, the name will be "@", representing the
  *     current origin as described by RFC1035.
  *
index b4770b4ed1c18d32ab2b25b55655a82b7337c2e2..5b106302f83dc080cbe57d070385728092c74f5c 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: tsig.h,v 1.53 2009/06/11 23:47:55 tbox Exp $ */
+/* $Id: tsig.h,v 1.53.136.1 2010/07/09 05:14:08 each Exp $ */
 
 #ifndef DNS_TSIG_H
 #define DNS_TSIG_H 1
@@ -62,6 +62,13 @@ struct dns_tsig_keyring {
        unsigned int writecount;
        isc_rwlock_t lock;
        isc_mem_t *mctx;
+       /*
+        * LRU list of generated key along with a count of the keys on the
+        * list and a maximum size.
+        */
+       unsigned int generated;
+       unsigned int maxgenerated;
+       ISC_LIST(dns_tsigkey_t) lru;
 };
 
 struct dns_tsigkey {
@@ -77,6 +84,7 @@ struct dns_tsigkey {
        isc_stdtime_t           expire;         /*%< end of validity period */
        dns_tsig_keyring_t      *ring;          /*%< the enclosing keyring */
        isc_refcount_t          refs;           /*%< reference counter */
+       ISC_LINK(dns_tsigkey_t) link;
 };
 
 #define dns_tsigkey_identity(tsigkey) \
index 58704876e07f7ba52c4622b886362457be34b4ad..00d83c35471a0aa0260a0bb161336ddfc128371a 100644 (file)
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: name.c,v 1.169.104.2 2010/05/12 23:50:01 tbox Exp $ */
+/* $Id: name.c,v 1.169.104.3 2010/07/09 05:14:08 each Exp $ */
 
 /*! \file */
 
@@ -1323,6 +1323,21 @@ totext_filter_proc_key_init(void) {
 isc_result_t
 dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
                isc_buffer_t *target)
+{
+       unsigned int options = DNS_NAME_MASTERFILE;
+
+       if (omit_final_dot)
+               options |= DNS_NAME_OMITFINALDOT;
+       return (dns_name_totext2(name, options, target));
+}
+
+isc_result_t
+dns_name_toprincipal(dns_name_t *name, isc_buffer_t *target) {
+       return (dns_name_totext2(name, DNS_NAME_OMITFINALDOT, target));
+}
+
+isc_result_t
+dns_name_totext2(dns_name_t *name, unsigned int options, isc_buffer_t *target)
 {
        unsigned char *ndata;
        char *tdata;
@@ -1337,6 +1352,8 @@ dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
        dns_name_totextfilter_t totext_filter_proc = NULL;
        isc_result_t result;
 #endif
+       isc_boolean_t omit_final_dot =
+               ISC_TF(options & DNS_NAME_OMITFINALDOT);
 
        /*
         * This function assumes the name is in proper uncompressed
@@ -1412,15 +1429,17 @@ dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
                        while (count > 0) {
                                c = *ndata;
                                switch (c) {
+                               /* Special modifiers in zone files. */
+                               case 0x40: /* '@' */
+                               case 0x24: /* '$' */
+                                       if ((options & DNS_NAME_MASTERFILE) == 0)
+                                               goto no_escape;
                                case 0x22: /* '"' */
                                case 0x28: /* '(' */
                                case 0x29: /* ')' */
                                case 0x2E: /* '.' */
                                case 0x3B: /* ';' */
                                case 0x5C: /* '\\' */
-                               /* Special modifiers in zone files. */
-                               case 0x40: /* '@' */
-                               case 0x24: /* '$' */
                                        if (trem < 2)
                                                return (ISC_R_NOSPACE);
                                        *tdata++ = '\\';
@@ -1430,6 +1449,7 @@ dns_name_totext(dns_name_t *name, isc_boolean_t omit_final_dot,
                                        trem -= 2;
                                        nlen--;
                                        break;
+                               no_escape:
                                default:
                                        if (c > 0x20 && c < 0x7f) {
                                                if (trem == 0)
index 02f93debd60dda99049dde4236a1d31d7a6cf0ce..d6bd2f633fd4ce423263f785a1afef26d940131d 100644 (file)
@@ -16,7 +16,7 @@
  */
 
 /*
- * $Id: tkey.c,v 1.92 2009/09/02 23:48:02 tbox Exp $
+ * $Id: tkey.c,v 1.92.104.1 2010/07/09 05:14:08 each Exp $
  */
 /*! \file */
 #include <config.h>
@@ -456,18 +456,15 @@ process_gsstkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name,
        if (result == ISC_R_SUCCESS)
                gss_ctx = dst_key_getgssctx(tsigkey->key);
 
-
        dns_fixedname_init(&principal);
 
        result = dst_gssapi_acceptctx(tctx->gsscred, &intoken,
                                      &outtoken, &gss_ctx,
                                      dns_fixedname_name(&principal),
                                      tctx->mctx);
-
-       if (tsigkey != NULL)
-               dns_tsigkey_detach(&tsigkey);
-
        if (result == DNS_R_INVALIDTKEY) {
+               if (tsigkey != NULL)
+                       dns_tsigkey_detach(&tsigkey);
                tkeyout->error = dns_tsigerror_badkey;
                tkey_log("process_gsstkey(): dns_tsigerror_badkey");    /* XXXSRA */
                return (ISC_R_SUCCESS);
@@ -478,20 +475,38 @@ process_gsstkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name,
         * XXXDCL Section 4.1.3: Limit GSS_S_CONTINUE_NEEDED to 10 times.
         */
 
+       isc_stdtime_get(&now);
+
        if (tsigkey == NULL) {
+#ifdef GSSAPI
+               OM_uint32 gret, minor, lifetime;
+#endif
+               isc_uint32_t expire;
+
                RETERR(dst_key_fromgssapi(name, gss_ctx, msg->mctx, &dstkey));
+               /*
+                * Limit keys to 1 hour or the context's lifetime whichever
+                * is smaller.
+                */
+               expire = now + 3600;
+#ifdef GSSAPI
+               gret = gss_context_time(&minor, gss_ctx, &lifetime);
+               if (gret == GSS_S_COMPLETE && now + lifetime < expire)
+                       expire = now + lifetime;
+#endif
                RETERR(dns_tsigkey_createfromkey(name, &tkeyin->algorithm,
                                                 dstkey, ISC_TRUE,
                                                 dns_fixedname_name(&principal),
-                                                tkeyin->inception,
-                                                tkeyin->expire,
-                                                ring->mctx, ring, NULL));
+                                                now, expire, ring->mctx, ring,
+                                                NULL));
+               tkeyout->inception = now;
+               tkeyout->expire = expire;
+       } else {
+               tkeyout->inception = tsigkey->inception;
+               tkeyout->expire = tkeyout->expire;
+               dns_tsigkey_detach(&tsigkey);
        }
 
-       isc_stdtime_get(&now);
-       tkeyout->inception = tkeyin->inception;
-       tkeyout->expire = tkeyin->expire;
-
        if (outtoken) {
                tkeyout->key = isc_mem_get(tkeyout->mctx,
                                           isc_buffer_usedlength(outtoken));
@@ -520,6 +535,9 @@ process_gsstkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name,
        return (ISC_R_SUCCESS);
 
 failure:
+       if (tsigkey != NULL)
+               dns_tsigkey_detach(&tsigkey);
+
        if (dstkey != NULL)
                dst_key_free(&dstkey);
 
@@ -1364,10 +1382,10 @@ dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg,
 
        if (win2k == ISC_TRUE)
                RETERR(find_tkey(qmsg, &tkeyname, &qtkeyrdata,
-                        DNS_SECTION_ANSWER));
+                                DNS_SECTION_ANSWER));
        else
                RETERR(find_tkey(qmsg, &tkeyname, &qtkeyrdata,
-                        DNS_SECTION_ADDITIONAL));
+                                DNS_SECTION_ADDITIONAL));
 
        RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL));
 
index 265fc275b3fad9d4cb5ca0ce90623e96c7cc978a..6e1844e22ca3b43b243dbade51e278d282189815 100644 (file)
@@ -16,7 +16,7 @@
  */
 
 /*
- * $Id: tsig.c,v 1.138.136.2 2010/03/12 23:49:56 tbox Exp $
+ * $Id: tsig.c,v 1.138.136.3 2010/07/09 05:14:08 each Exp $
  */
 /*! \file */
 #include <config.h>
@@ -26,6 +26,7 @@
 #include <isc/mem.h>
 #include <isc/print.h>
 #include <isc/refcount.h>
+#include <isc/serial.h>
 #include <isc/string.h>                /* Required for HP/UX (and others?) */
 #include <isc/util.h>
 #include <isc/time.h>
 #define TSIG_MAGIC             ISC_MAGIC('T', 'S', 'I', 'G')
 #define VALID_TSIG_KEY(x)      ISC_MAGIC_VALID(x, TSIG_MAGIC)
 
+#ifndef DNS_TSIG_MAXGENERATEDKEYS
+#define DNS_TSIG_MAXGENERATEDKEYS 4096
+#endif
+
 #define is_response(msg) (msg->flags & DNS_MESSAGEFLAG_QR)
 #define algname_is_allocated(algname) \
        ((algname) != dns_tsig_hmacmd5_name && \
@@ -215,6 +220,31 @@ tsig_log(dns_tsigkey_t *key, int level, const char *fmt, ...) {
                              level, "tsig key '%s': %s", namestr, message);
 }
 
+static void
+remove_fromring(dns_tsigkey_t *tkey) {
+       if (tkey->generated) {
+               ISC_LIST_UNLINK(tkey->ring->lru, tkey, link);
+               tkey->ring->generated--;
+       }
+       (void)dns_rbt_deletename(tkey->ring->keys, &tkey->name, ISC_FALSE);
+}
+
+static void
+adjust_lru(dns_tsigkey_t *tkey) {
+       if (tkey->generated) {
+               RWLOCK(&tkey->ring->lock, isc_rwlocktype_write);
+               /*
+                * We may have been removed from the LRU list between
+                * removing the read lock and aquiring the write lock.
+                */
+               if (ISC_LINK_LINKED(tkey, link)) {
+                       ISC_LIST_UNLINK(tkey->ring->lru, tkey, link);
+                       ISC_LIST_APPEND(tkey->ring->lru, tkey, link);
+               }
+               RWUNLOCK(&tkey->ring->lock, isc_rwlocktype_write);
+       }
+}
+
 /*
  * A supplemental routine just to add a key to ring.  Note that reference
  * counter should be counted separately because we may be adding the key
@@ -241,6 +271,15 @@ keyring_add(dns_tsig_keyring_t *ring, dns_name_t *name,
        }
 
        result = dns_rbt_addname(ring->keys, name, tkey);
+       if (tkey->generated) {
+               /*
+                * Add the new key to the LRU list and remove the least
+                * recently used key if there are too many keys on the list.
+                */
+               ISC_LIST_INITANDAPPEND(ring->lru, tkey, link);
+               if (ring->generated++ > ring->maxgenerated)
+                       remove_fromring(ISC_LIST_HEAD(ring->lru));
+       }
        RWUNLOCK(&ring->lock, isc_rwlocktype_write);
 
        return (result);
@@ -470,9 +509,7 @@ cleanup_ring(dns_tsig_keyring_t *ring)
                                tsig_log(tkey, 2, "tsig expire: deleting");
                                /* delete the key */
                                dns_rbtnodechain_invalidate(&chain);
-                               (void)dns_rbt_deletename(ring->keys,
-                                                        &tkey->name,
-                                                        ISC_FALSE);
+                               remove_fromring(tkey);
                                goto again;
                        }
                }
@@ -482,7 +519,6 @@ cleanup_ring(dns_tsig_keyring_t *ring)
                        dns_rbtnodechain_invalidate(&chain);
                        return;
                }
-
        }
 }
 
@@ -647,7 +683,7 @@ dns_tsigkey_setdeleted(dns_tsigkey_t *key) {
        REQUIRE(key->ring != NULL);
 
        RWLOCK(&key->ring->lock, isc_rwlocktype_write);
-       (void)dns_rbt_deletename(key->ring->keys, &key->name, ISC_FALSE);
+       remove_fromring(key);
        RWUNLOCK(&key->ring->lock, isc_rwlocktype_write);
 }
 
@@ -1490,19 +1526,30 @@ dns_tsigkey_find(dns_tsigkey_t **tsigkey, dns_name_t *name,
                RWUNLOCK(&ring->lock, isc_rwlocktype_read);
                return (ISC_R_NOTFOUND);
        }
-       if (key->inception != key->expire && key->expire < now) {
+       if (key->inception != key->expire && isc_serial_lt(key->expire, now)) {
                /*
                 * The key has expired.
                 */
                RWUNLOCK(&ring->lock, isc_rwlocktype_read);
                RWLOCK(&ring->lock, isc_rwlocktype_write);
-               (void)dns_rbt_deletename(ring->keys, name, ISC_FALSE);
+               remove_fromring(key);
                RWUNLOCK(&ring->lock, isc_rwlocktype_write);
                return (ISC_R_NOTFOUND);
        }
-
+#if 0
+       /*
+        * MPAXXX We really should look at the inception time.
+        */
+       if (key->inception != key->expire &&
+           isc_serial_lt(key->inception, now)) {
+               RWUNLOCK(&ring->lock, isc_rwlocktype_read);
+               adjust_lru(key);
+               return (ISC_R_NOTFOUND);
+       }
+#endif
        isc_refcount_increment(&key->refs, NULL);
        RWUNLOCK(&ring->lock, isc_rwlocktype_read);
+       adjust_lru(key);
        *tsigkey = key;
        return (ISC_R_SUCCESS);
 }
@@ -1548,6 +1595,9 @@ dns_tsigkeyring_create(isc_mem_t *mctx, dns_tsig_keyring_t **ringp) {
 
        ring->writecount = 0;
        ring->mctx = NULL;
+       ring->generated = 0;
+       ring->maxgenerated = DNS_TSIG_MAXGENERATEDKEYS;
+       ISC_LIST_INIT(ring->lru);
        isc_mem_attach(mctx, &ring->mctx);
 
        *ringp = ring;