krb5 commit: Improve krb5_rd_req decryption failure errors

Greg Hudson ghudson at MIT.EDU
Wed May 7 12:56:49 EDT 2014


https://github.com/krb5/krb5/commit/eba8c4909ec7ba0d7054d5d1b1061319e9970cc7
commit eba8c4909ec7ba0d7054d5d1b1061319e9970cc7
Author: Greg Hudson <ghudson at mit.edu>
Date:   Mon Apr 28 03:58:32 2014 -0400

    Improve krb5_rd_req decryption failure errors
    
    When krb5_rd_req cannot decrypt a ticket, try to produce the most
    helpful diagnostic we can, and return an error code which corresponds
    to the most applicable Kerberos protocol error.  Add a trace log
    containing the error message for ticket decryption failures, in case
    the application server does not log it.
    
    Add new tests to cover krb5_rd_req error messages and adjust existing
    tests to match the new messages.  Also adjust svc_auth_gssapi.c to
    look for KRB5KRB_AP_ERR_NOT_US instead of KRB5KRB_AP_WRONG_PRINC.
    
    ticket: 7232

 .gitignore                                  |    1 +
 src/include/k5-trace.h                      |    2 +
 src/lib/krb5/krb/rd_req_dec.c               |  305 +++++++++++++++++++++++----
 src/lib/rpc/svc_auth_gssapi.c               |    9 +-
 src/lib/rpc/unit-test/rpc_test.0/gsserr.exp |    4 +-
 src/tests/Makefile.in                       |   12 +-
 src/tests/gssapi/t_gssapi.py                |   10 +-
 src/tests/rdreq.c                           |  116 ++++++++++
 src/tests/t_rdreq.py                        |  126 +++++++++++
 9 files changed, 528 insertions(+), 57 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0121752..45a3e15 100644
--- a/.gitignore
+++ b/.gitignore
@@ -252,6 +252,7 @@ testlog
 /src/tests/kdc.conf
 /src/tests/krb5.conf
 /src/tests/plugorder
+/src/tests/rdreq
 /src/tests/responder
 /src/tests/s2p
 /src/tests/t_init_creds
diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h
index 71ce73e..dfd34f6 100644
--- a/src/include/k5-trace.h
+++ b/src/include/k5-trace.h
@@ -301,6 +301,8 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
 #define TRACE_RD_REQ_DECRYPT_SPECIFIC(c, princ, keyblock)               \
     TRACE(c, "Decrypted AP-REQ with specified server principal {princ}: " \
           "{keyblock}", princ, keyblock)
+#define TRACE_RD_REQ_DECRYPT_FAIL(c, err)                       \
+    TRACE(c, "Failed to decrypt AP-REQ ticket: {kerr}", err)
 #define TRACE_RD_REQ_NEGOTIATED_ETYPE(c, etype)                     \
     TRACE(c, "Negotiated enctype based on authenticator: {etype}",  \
           etype)
diff --git a/src/lib/krb5/krb/rd_req_dec.c b/src/lib/krb5/krb/rd_req_dec.c
index fbd088d..637ff83 100644
--- a/src/lib/krb5/krb/rd_req_dec.c
+++ b/src/lib/krb5/krb/rd_req_dec.c
@@ -85,6 +85,199 @@ negotiate_etype(krb5_context context,
                 int permitted_etypes_len,
                 krb5_enctype *negotiated_etype);
 
+/* Unparse the specified server principal (which may be NULL) and the ticket
+ * server principal. */
+static krb5_error_code
+unparse_princs(krb5_context context, krb5_const_principal server,
+               krb5_const_principal tkt_server, char **sname_out,
+               char **tsname_out)
+{
+    krb5_error_code ret;
+    char *sname = NULL, *tsname;
+
+    *sname_out = *tsname_out = NULL;
+    if (server != NULL) {
+        ret = krb5_unparse_name(context, server, &sname);
+        if (ret)
+            return ret;
+    }
+    ret = krb5_unparse_name(context, tkt_server, &tsname);
+    if (ret) {
+        krb5_free_unparsed_name(context, sname);
+        return ret;
+    }
+    *sname_out = sname;
+    *tsname_out = tsname;
+    return 0;
+}
+
+/* Return a helpful code and error when we cannot look up the keytab entry for
+ * an explicit server principal using the ticket's kvno and enctype. */
+static krb5_error_code
+keytab_fetch_error(krb5_context context, krb5_error_code code,
+                   krb5_const_principal princ,
+                   krb5_const_principal tkt_server, krb5_kvno tkt_kvno,
+                   krb5_boolean explicit_server)
+{
+    krb5_error_code ret;
+    char *sname = NULL, *tsname = NULL;
+
+    if (code == ENOENT || code == EPERM || code == EACCES) {
+        k5_change_error_message_code(context, code, KRB5KRB_AP_ERR_NOKEY);
+        return KRB5KRB_AP_ERR_NOKEY;
+    }
+
+    if (code == KRB5_KT_NOTFOUND) {
+        ret = explicit_server ? KRB5KRB_AP_ERR_NOKEY : KRB5KRB_AP_ERR_NOT_US;
+        k5_change_error_message_code(context, code, ret);
+        return ret;
+    }
+
+    if (code != KRB5_KT_KVNONOTFOUND)
+        return code;
+
+    assert(princ != NULL);
+    ret = unparse_princs(context, princ, tkt_server, &sname, &tsname);
+    if (ret)
+        return ret;
+    if (krb5_principal_compare(context, princ, tkt_server)) {
+        ret = KRB5KRB_AP_ERR_BADKEYVER;
+        krb5_set_error_message(context, ret,
+                               _("Cannot find key for %s kvno %d in keytab"),
+                               sname, (int)tkt_kvno);
+    } else {
+        ret = KRB5KRB_AP_ERR_NOT_US;
+        krb5_set_error_message(context, ret,
+                               _("Cannot find key for %s kvno %d in keytab "
+                                 "(request ticket server %s)"),
+                               sname, (int)tkt_kvno, tsname);
+    }
+    krb5_free_unparsed_name(context, sname);
+    krb5_free_unparsed_name(context, tsname);
+    return ret;
+}
+
+/* Return a helpful code and error when ticket decryption fails using the key
+ * for an explicit server principal. */
+static krb5_error_code
+integrity_error(krb5_context context, krb5_const_principal server,
+                krb5_const_principal tkt_server)
+{
+    krb5_error_code ret;
+    char *sname = NULL, *tsname = NULL;
+
+    assert(server != NULL);
+    ret = unparse_princs(context, server, tkt_server, &sname, &tsname);
+    if (ret)
+        return ret;
+
+    ret = krb5_principal_compare(context, server, tkt_server) ?
+        KRB5KRB_AP_ERR_BAD_INTEGRITY : KRB5KRB_AP_ERR_NOT_US;
+    krb5_set_error_message(context, ret,
+                           _("Cannot decrypt ticket for %s using keytab "
+                             "key for %s"), tsname, sname);
+    krb5_free_unparsed_name(context, sname);
+    krb5_free_unparsed_name(context, tsname);
+    return ret;
+}
+
+/* Return a helpful code and error when we cannot iterate over the keytab and
+ * the specified server does not match the ticket server. */
+static krb5_error_code
+nomatch_error(krb5_context context, krb5_const_principal server,
+              krb5_const_principal tkt_server)
+{
+    krb5_error_code ret;
+    char *sname = NULL, *tsname = NULL;
+
+    assert(server != NULL);
+    ret = unparse_princs(context, server, tkt_server, &sname, &tsname);
+    if (ret)
+        return ret;
+
+    krb5_set_error_message(context, KRB5KRB_AP_ERR_NOT_US,
+                           _("Server principal %s does not match request "
+                             "ticket server %s"), sname, tsname);
+    krb5_free_unparsed_name(context, sname);
+    krb5_free_unparsed_name(context, tsname);
+    return KRB5KRB_AP_ERR_NOT_US;
+}
+
+/* Return a helpful error code and message when we fail to find a key after
+ * iterating over the keytab. */
+static krb5_error_code
+iteration_error(krb5_context context, krb5_const_principal server,
+                krb5_const_principal tkt_server, krb5_kvno tkt_kvno,
+                krb5_enctype tkt_etype, krb5_boolean tkt_server_mismatch,
+                krb5_boolean found_server_match, krb5_boolean found_tkt_server,
+                krb5_boolean found_kvno, krb5_boolean found_higher_kvno,
+                krb5_boolean found_enctype)
+{
+    krb5_error_code ret;
+    char *sname = NULL, *tsname = NULL, encname[128];
+
+    ret = unparse_princs(context, server, tkt_server, &sname, &tsname);
+    if (ret)
+        return ret;
+    if (krb5_enctype_to_name(tkt_etype, TRUE, encname, sizeof(encname)) != 0)
+        (void)snprintf(encname, sizeof(encname), "%d", (int)tkt_etype);
+
+    if (!found_server_match) {
+        ret = KRB5KRB_AP_ERR_NOKEY;
+        if (sname == NULL)  {
+            krb5_set_error_message(context, ret, _("No keys in keytab"));
+        } else {
+            krb5_set_error_message(context, ret,
+                                   _("Server principal %s does not match any "
+                                     "keys in keytab"), sname);
+        }
+    } else if (tkt_server_mismatch) {
+        assert(sname != NULL);  /* Null server princ would match anything. */
+        ret = KRB5KRB_AP_ERR_NOT_US;
+        krb5_set_error_message(context, ret,
+                               _("Request ticket server %s found in keytab "
+                                 "but does not match server principal %s"),
+                               tsname, sname);
+    } else if (!found_tkt_server) {
+        ret = KRB5KRB_AP_ERR_NOT_US;
+        krb5_set_error_message(context, ret,
+                               _("Request ticket server %s not found in "
+                                 "keytab (ticket kvno %d)"),
+                               tsname, (int)tkt_kvno);
+    } else if (!found_kvno) {
+        ret = KRB5KRB_AP_ERR_BADKEYVER;
+        if (found_higher_kvno) {
+            krb5_set_error_message(context, ret,
+                                   _("Request ticket server %s kvno %d not "
+                                     "found in keytab; ticket is likely out "
+                                     "of date"), tsname, (int)tkt_kvno);
+        } else {
+            krb5_set_error_message(context, ret,
+                                   _("Request ticket server %s kvno %d not "
+                                     "found in keytab; keytab is likely out "
+                                     "of date"), tsname, (int)tkt_kvno);
+        }
+    } else if (!found_enctype) {
+        /* There's no defined error for having the key version but not the
+         * enctype. */
+        ret = KRB5KRB_AP_ERR_BADKEYVER;
+        krb5_set_error_message(context, ret,
+                               _("Request ticket server %s kvno %d found in "
+                                 "keytab but not with enctype %s"),
+                               tsname, (int)tkt_kvno, encname);
+    } else {
+        ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
+        krb5_set_error_message(context, ret,
+                               _("Request ticket server %s kvno %d enctype %s "
+                                 "found in keytab but cannot decrypt ticket"),
+                               tsname, (int)tkt_kvno, encname);
+    }
+
+    krb5_free_unparsed_name(context, sname);
+    krb5_free_unparsed_name(context, tsname);
+    return ret;
+}
+
 /* Return true if princ might match multiple principals. */
 static inline krb5_boolean
 is_matching(krb5_context context, krb5_const_principal princ)
@@ -130,28 +323,31 @@ try_one_entry(krb5_context context, const krb5_ap_req *req,
     return 0;
 }
 
-/* Decrypt the ticket in req using a principal looked up from keytab. */
+/* Decrypt the ticket in req using a principal looked up from keytab.
+ * explicit_server should be true if this is the only usable principal. */
 static krb5_error_code
 try_one_princ(krb5_context context, const krb5_ap_req *req,
               krb5_const_principal princ, krb5_keytab keytab,
-              krb5_keyblock *keyblock_out)
+              krb5_boolean explicit_server, krb5_keyblock *keyblock_out)
 {
     krb5_error_code ret;
     krb5_keytab_entry ent;
-
-    ret = krb5_kt_get_entry(context, keytab, princ,
-                            req->ticket->enc_part.kvno,
-                            req->ticket->enc_part.enctype, &ent);
-    if (ret)
-        return ret;
+    krb5_kvno tkt_kvno = req->ticket->enc_part.kvno;
+    krb5_enctype tkt_etype = req->ticket->enc_part.enctype;
+    krb5_principal tkt_server = req->ticket->server;
+
+    ret = krb5_kt_get_entry(context, keytab, princ, tkt_kvno, tkt_etype, &ent);
+    if (ret) {
+        return keytab_fetch_error(context, ret, princ, tkt_server, tkt_kvno,
+                                  explicit_server);
+    }
     ret = try_one_entry(context, req, &ent, keyblock_out);
     if (ret == 0)
         TRACE_RD_REQ_DECRYPT_SPECIFIC(context, ent.principal, &ent.key);
     (void)krb5_free_keytab_entry_contents(context, &ent);
-    if (ret)
-        return ret;
-
-    return 0;
+    if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY)
+        return integrity_error(context, princ, req->ticket->server);
+    return ret;
 }
 
 /*
@@ -167,38 +363,68 @@ decrypt_ticket(krb5_context context, const krb5_ap_req *req,
     krb5_error_code ret;
     krb5_keytab_entry ent;
     krb5_kt_cursor cursor;
-    krb5_boolean similar;
-    krb5_enctype req_etype = req->ticket->enc_part.enctype;
+    krb5_principal tkt_server = req->ticket->server;
+    krb5_kvno tkt_kvno = req->ticket->enc_part.kvno;
+    krb5_enctype tkt_etype = req->ticket->enc_part.enctype;
+    krb5_boolean similar_enctype;
+    krb5_boolean tkt_server_mismatch = FALSE, found_server_match = FALSE;
+    krb5_boolean found_tkt_server = FALSE, found_enctype = FALSE;
+    krb5_boolean found_kvno = FALSE, found_higher_kvno = FALSE;
 
 #ifdef LEAN_CLIENT
     return KRB5KRB_AP_WRONG_PRINC;
 #else
     /* If we have an explicit server principal, try just that one. */
-    if (!is_matching(context, server))
-        return try_one_princ(context, req, server, keytab, keyblock_out);
+    if (!is_matching(context, server)) {
+        return try_one_princ(context, req, server, keytab, TRUE,
+                             keyblock_out);
+    }
 
     if (keytab->ops->start_seq_get == NULL) {
         /* We can't iterate over the keytab.  Try the principal asserted by the
          * client if it's allowed by the server parameter. */
-        if (!krb5_sname_match(context, server, req->ticket->server))
-            return KRB5KRB_AP_WRONG_PRINC;
-        return try_one_princ(context, req, req->ticket->server, keytab,
+        if (!krb5_sname_match(context, server, tkt_server))
+            return nomatch_error(context, server, tkt_server);
+        return try_one_princ(context, req, tkt_server, keytab, FALSE,
                              keyblock_out);
     }
 
+    /* Scan all keys in the keytab, in case the ticket server is an alias for
+     * one of the principals in the keytab. */
     ret = krb5_kt_start_seq_get(context, keytab, &cursor);
-    if (ret)
-        goto cleanup;
-
+    if (ret) {
+        k5_change_error_message_code(context, ret, KRB5KRB_AP_ERR_NOKEY);
+        return KRB5KRB_AP_ERR_NOKEY;
+    }
     while ((ret = krb5_kt_next_entry(context, keytab, &ent, &cursor)) == 0) {
-        ret = krb5_c_enctype_compare(context, ent.key.enctype, req_etype,
-                                     &similar);
-        if (ret == 0 && similar &&
-            krb5_sname_match(context, server, ent.principal)) {
+        /* Only try keys which match the server principal. */
+        if (!krb5_sname_match(context, server, ent.principal)) {
+            if (krb5_principal_compare(context, ent.principal, tkt_server))
+                tkt_server_mismatch = TRUE;
+            continue;
+        }
+        found_server_match = TRUE;
+
+        if (krb5_c_enctype_compare(context, ent.key.enctype, tkt_etype,
+                                   &similar_enctype) != 0)
+            similar_enctype = FALSE;
+
+        if (krb5_principal_compare(context, ent.principal, tkt_server)) {
+            found_tkt_server = TRUE;
+            if (ent.vno == tkt_kvno) {
+                found_kvno = TRUE;
+                if (similar_enctype)
+                    found_enctype = TRUE;
+            } else if (ent.vno > tkt_kvno) {
+                found_higher_kvno = TRUE;
+            }
+        }
+
+        /* Only try keys with similar enctypes to the ticket enctype. */
+        if (similar_enctype) {
             /* Coerce inexact matches to the request enctype. */
-            ent.key.enctype = req_etype;
-            ret = try_one_entry(context, req, &ent, keyblock_out);
-            if (ret == 0) {
+            ent.key.enctype = tkt_etype;
+            if (try_one_entry(context, req, &ent, keyblock_out) == 0) {
                 TRACE_RD_REQ_DECRYPT_ANY(context, ent.principal, &ent.key);
                 (void)krb5_free_keytab_entry_contents(context, &ent);
                 break;
@@ -210,19 +436,12 @@ decrypt_ticket(krb5_context context, const krb5_ap_req *req,
 
     (void)krb5_kt_end_seq_get(context, keytab, &cursor);
 
-cleanup:
-    switch (ret) {
-    case KRB5_KT_KVNONOTFOUND:
-    case KRB5_KT_NOTFOUND:
-    case KRB5_KT_END:
-    case KRB5KRB_AP_ERR_BAD_INTEGRITY:
-        ret = KRB5KRB_AP_WRONG_PRINC;
-        break;
-    default:
-        break;
-    }
-
-    return ret;
+    if (ret != KRB5_KT_END)
+        return ret;
+    return iteration_error(context, server, tkt_server, tkt_kvno, tkt_etype,
+                           tkt_server_mismatch, found_server_match,
+                           found_tkt_server, found_kvno, found_higher_kvno,
+                           found_enctype);
 #endif /* LEAN_CLIENT */
 }
 
@@ -288,8 +507,10 @@ rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context,
     } else {
         retval = decrypt_ticket(context, req, server, keytab,
                                 check_valid_flag ? &decrypt_key : NULL);
-        if (retval)
+        if (retval) {
+            TRACE_RD_REQ_DECRYPT_FAIL(context, retval);
             goto cleanup;
+        }
         /* decrypt_ticket placed the principal of the keytab key in
          * req->ticket->server; always use this for later steps. */
         server = req->ticket->server;
diff --git a/src/lib/rpc/svc_auth_gssapi.c b/src/lib/rpc/svc_auth_gssapi.c
index e3af08f..f3b3e35 100644
--- a/src/lib/rpc/svc_auth_gssapi.c
+++ b/src/lib/rpc/svc_auth_gssapi.c
@@ -28,7 +28,7 @@
 
 #ifdef GSSAPI_KRB5
 /* This is here for the krb5_error_code typedef and the
-   KRB5KRB_AP_WRONG_PRINC #define.*/
+ * KRB5KRB_AP_ERR_NOT_US #define.*/
 #include <krb5.h>
 #endif
 
@@ -416,8 +416,9 @@ enum auth_stat gssrpc__svcauth_gssapi(
 	       if (server_creds == client_data->server_creds)
 		    break;
 
-	       PRINTF(("accept_sec_context returned 0x%x 0x%x wrong-princ=%#x\n",
-		       call_res.gss_major, call_res.gss_minor, (int) KRB5KRB_AP_WRONG_PRINC));
+	       PRINTF(("accept_sec_context returned 0x%x 0x%x not-us=%#x\n",
+		       call_res.gss_major, call_res.gss_minor,
+		       (int) KRB5KRB_AP_ERR_NOT_US));
 	       if (call_res.gss_major == GSS_S_COMPLETE ||
 		   call_res.gss_major == GSS_S_CONTINUE_NEEDED) {
 		    /* server_creds was right, set it! */
@@ -434,7 +435,7 @@ enum auth_stat gssrpc__svcauth_gssapi(
 			   * error
 			   */
 			  || ((krb5_error_code) call_res.gss_minor !=
-			      (krb5_error_code) KRB5KRB_AP_WRONG_PRINC)
+			      (krb5_error_code) KRB5KRB_AP_ERR_NOT_US)
 #endif
 			  ) {
 		    break;
diff --git a/src/lib/rpc/unit-test/rpc_test.0/gsserr.exp b/src/lib/rpc/unit-test/rpc_test.0/gsserr.exp
index c3e78b1..0059719 100644
--- a/src/lib/rpc/unit-test/rpc_test.0/gsserr.exp
+++ b/src/lib/rpc/unit-test/rpc_test.0/gsserr.exp
@@ -20,8 +20,8 @@ verbose "gss err: checking server output"
 
 expect {
 	-i $server_id
-	-re "rpc_test server: Authent.*failed: .* Wrong princ" {
-		pass "gss err: server logged auth error"
+	-re "rpc_test server: Authent.*failed: .* not found in keytab" {
+	    pass "gss err: server logged auth error"
 	}
 	eof { fail "gss err: server exited" }
 	timeout { fail "gss err: timeout waiting for server output" }
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 958b8a9..7347ed6 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -6,9 +6,9 @@ SUBDIRS = resolve asn.1 create hammer verify gssapi dejagnu shlib \
 RUN_SETUP = @KRB5_RUN_ENV@ KRB5_KDC_PROFILE=kdc.conf KRB5_CONFIG=krb5.conf
 
 OBJS= gcred.o hist.o hrealm.o kdbtest.o plugorder.o t_init_creds.o \
-	t_localauth.o responder.o s2p.o
+	t_localauth.o rdreq.o responder.o s2p.o
 EXTRADEPSRCS= gcred.c hist.c hrealm.c kdbtest.c plugorder.c t_init_creds.c \
-	t_localauth.c responder.c s2p.c
+	t_localauth.c rdreq.o responder.c s2p.c
 
 TEST_DB = ./testdb
 TEST_REALM = FOO.TEST.REALM
@@ -36,6 +36,9 @@ kdbtest: kdbtest.o $(KDB5_DEPLIBS) $(KADMSRV_DEPLIBS) $(KRB5_BASE_DEPLIBS)
 plugorder: plugorder.o $(KRB5_BASE_DEPLIBS)
 	$(CC_LINK) -o $@ plugorder.o $(KRB5_BASE_LIBS)
 
+rdreq: rdreq.o $(KRB5_BASE_DEPLIBS)
+	$(CC_LINK) -o $@ rdreq.o $(KRB5_BASE_LIBS)
+
 responder: responder.o $(KRB5_BASE_DEPLIBS)
 	$(CC_LINK) -o $@ responder.o $(KRB5_BASE_LIBS)
 
@@ -90,7 +93,7 @@ kdb_check: kdc.conf krb5.conf
 	$(RUN_SETUP) $(VALGRIND) ../kadmin/dbutil/kdb5_util $(KADMIN_OPTS) destroy -f
 	$(RM) $(TEST_DB)* stash_file
 
-check-pytests:: gcred hist hrealm kdbtest plugorder responder s2p
+check-pytests:: gcred hist hrealm kdbtest plugorder rdreq responder s2p
 check-pytests:: t_init_creds t_localauth
 	$(RUNPYTEST) $(srcdir)/t_general.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_dump.py $(PYTESTFLAGS)
@@ -119,6 +122,7 @@ check-pytests:: t_init_creds t_localauth
 	$(RUNPYTEST) $(srcdir)/t_kdb.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_keydata.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_mkey.py $(PYTESTFLAGS)
+	$(RUNPYTEST) $(srcdir)/t_rdreq.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_sn2princ.py $(PYTESTFLAGS) $(OFFLINE)
 	$(RUNPYTEST) $(srcdir)/t_cve-2012-1014.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_cve-2012-1015.py $(PYTESTFLAGS)
@@ -132,7 +136,7 @@ check-pytests:: t_init_creds t_localauth
 	$(RUNPYTEST) $(srcdir)/t_bogus_kdc_req.py $(PYTESTFLAGS)
 
 clean::
-	$(RM) gcred hist hrealm kdbtest plugorder responder s2p
+	$(RM) gcred hist hrealm kdbtest plugorder rdreq responder s2p
 	$(RM) t_init_creds t_localauth krb5.conf kdc.conf
 	$(RM) -rf kdc_realm/sandbox ldap
 	$(RM) au.log
diff --git a/src/tests/gssapi/t_gssapi.py b/src/tests/gssapi/t_gssapi.py
index 106910d..29d334e 100755
--- a/src/tests/gssapi/t_gssapi.py
+++ b/src/tests/gssapi/t_gssapi.py
@@ -37,7 +37,7 @@ output = realm.run(['./t_accname', 'p:service2/calvin'])
 if 'service2/calvin' not in output:
     fail('Expected service1/barack in t_accname output')
 output = realm.run(['./t_accname', 'p:service2/dwight'], expected_code=1)
-if 'Wrong principal in request' not in output:
+if ' not found in keytab' not in output:
     fail('Expected error message not seen in t_accname output')
 
 # Test with acceptor name containing service only, including
@@ -48,14 +48,14 @@ if 'service1/abraham' not in output:
     fail('Expected service1/abraham in t_accname output')
 output = realm.run(['./t_accname', 'p:service1/andrew', 'h:service2'],
                    expected_code=1)
-if 'Wrong principal in request' not in output:
+if ' not found in keytab' not in output:
     fail('Expected error message not seen in t_accname output')
 output = realm.run(['./t_accname', 'p:service2/calvin', 'h:service2'])
 if 'service2/calvin' not in output:
     fail('Expected service2/calvin in t_accname output')
 output = realm.run(['./t_accname', 'p:service2/calvin', 'h:service1'],
                    expected_code=1)
-if 'Wrong principal in request' not in output:
+if ' found in keytab but does not match server principal' not in output:
     fail('Expected error message not seen in t_accname output')
 
 # Test with acceptor name containing service and host.  Use the
@@ -68,7 +68,7 @@ if realm.host_princ not in output:
 output = realm.run(['./t_accname', 'p:host/-nomatch-',
                     'h:host@%s' % socket.gethostname()],
                    expected_code=1)
-if 'Wrong principal in request' not in output:
+if ' not found in keytab' not in output:
     fail('Expected error message not seen in t_accname output')
 
 # Test krb5_gss_import_cred.
@@ -76,7 +76,7 @@ realm.run(['./t_imp_cred', 'p:service1/barack'])
 realm.run(['./t_imp_cred', 'p:service1/barack', 'service1/barack'])
 realm.run(['./t_imp_cred', 'p:service1/andrew', 'service1/abraham'])
 output = realm.run(['./t_imp_cred', 'p:service2/dwight'], expected_code=1)
-if 'Wrong principal in request' not in output:
+if ' not found in keytab' not in output:
     fail('Expected error message not seen in t_imp_cred output')
 
 # Test credential store extension.
diff --git a/src/tests/rdreq.c b/src/tests/rdreq.c
new file mode 100644
index 0000000..c010cb2
--- /dev/null
+++ b/src/tests/rdreq.c
@@ -0,0 +1,116 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* tests/rdreq.c - Test harness for krb5_rd_req */
+/*
+ * Copyright (C) 2014 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <krb5.h>
+
+int
+main(int argc, char **argv)
+{
+    krb5_context context;
+    krb5_principal client_princ, tkt_princ, server_princ;
+    krb5_ccache ccache;
+    krb5_creds *cred, mcred;
+    krb5_auth_context auth_con;
+    krb5_data apreq;
+    krb5_error_code ret, code;
+    const char *tkt_name, *server_name, *emsg;
+
+    /* Parse arguments. */
+    if (argc < 2 || argc > 3) {
+        fprintf(stderr, "Usage: rdreq tktname [servername]\n");
+        exit(1);
+    }
+    tkt_name = argv[1];
+    server_name = argv[2];
+
+    if (krb5_init_context(&context) != 0)
+        abort();
+
+    /* Parse the requested principal names. */
+    if (krb5_parse_name(context, tkt_name, &tkt_princ) != 0)
+        abort();
+    if (server_name != NULL) {
+        if (krb5_parse_name(context, server_name, &server_princ) != 0)
+            abort();
+        server_princ->type = KRB5_NT_SRV_HST;
+    } else {
+        server_princ = NULL;
+    }
+
+    /* Produce an AP-REQ message. */
+    if (krb5_cc_default(context, &ccache) != 0)
+        abort();
+    if (krb5_cc_get_principal(context, ccache, &client_princ) != 0)
+        abort();
+    memset(&mcred, 0, sizeof(mcred));
+    mcred.client = client_princ;
+    mcred.server = tkt_princ;
+    if (krb5_get_credentials(context, 0, ccache, &mcred, &cred) != 0)
+        abort();
+    auth_con = NULL;
+    if (krb5_mk_req_extended(context, &auth_con, 0, NULL, cred, &apreq) != 0)
+        abort();
+
+    /* Consume the AP-REQ message without using a replay cache. */
+    krb5_auth_con_free(context, auth_con);
+    if (krb5_auth_con_init(context, &auth_con) != 0)
+        abort();
+    if (krb5_auth_con_setflags(context, auth_con, 0) != 0)
+        abort();
+    ret = krb5_rd_req(context, &auth_con, &apreq, server_princ, NULL, NULL,
+                      NULL);
+
+    /* Display the result. */
+    if (ret) {
+        code = ret - ERROR_TABLE_BASE_krb5;
+        if (code < 0 || code > 127)
+            code = 60;          /* KRB_ERR_GENERIC */
+        emsg = krb5_get_error_message(context, ret);
+        printf("%d %s\n", code, emsg);
+        krb5_free_error_message(context, emsg);
+    } else {
+        printf("0 success\n");
+    }
+
+    krb5_free_data_contents(context, &apreq);
+    krb5_auth_con_free(context, auth_con);
+    krb5_free_creds(context, cred);
+    krb5_cc_close(context, ccache);
+    krb5_free_principal(context, client_princ);
+    krb5_free_principal(context, tkt_princ);
+    krb5_free_principal(context, server_princ);
+    krb5_free_context(context);
+    return 0;
+}
diff --git a/src/tests/t_rdreq.py b/src/tests/t_rdreq.py
new file mode 100644
index 0000000..42c5e29
--- /dev/null
+++ b/src/tests/t_rdreq.py
@@ -0,0 +1,126 @@
+#!/usr/bin/python
+from k5test import *
+
+conf = {'realms': {'$realm': {'supported_enctypes': 'aes256-cts aes128-cts'}}}
+realm = K5Realm(create_host=False, kdc_conf=conf)
+
+# Define some server principal names.
+princ1 = 'host/1@%s' % realm.realm
+princ2 = 'host/2@%s' % realm.realm
+princ3 = 'HTTP/3@%s' % realm.realm
+princ4 = 'HTTP/4@%s' % realm.realm
+matchprinc = 'host/@'
+nomatchprinc = 'x/@'
+realm.addprinc(princ1)
+realm.addprinc(princ2)
+realm.addprinc(princ3)
+
+def test(tserver, server, expected):
+    args = ['./rdreq', tserver]
+    if server is not None:
+        args += [server]
+    out = realm.run(args)
+    if out.strip() != expected:
+        fail('unexpected rdreq output')
+
+
+# No keytab present.
+nokeytab_err = "45 Key table file '%s' not found" % realm.keytab
+test(princ1, None, nokeytab_err)
+test(princ1, princ1, nokeytab_err)
+test(princ1, matchprinc, nokeytab_err)
+
+# Keytab present, successful decryption.
+realm.extract_keytab(princ1, realm.keytab)
+test(princ1, None, '0 success')
+test(princ1, princ1, '0 success')
+test(princ1, matchprinc, '0 success')
+
+# Explicit server principal not found in keytab.
+test(princ2, princ2, '45 No key table entry found for host/2 at KRBTEST.COM')
+
+# Matching server principal does not match any entries in keytab (with
+# and without ticket server present in keytab).
+nomatch_err = '45 Server principal x/@ does not match any keys in keytab'
+test(princ1, nomatchprinc, nomatch_err)
+test(princ2, nomatchprinc, nomatch_err)
+
+# Ticket server does not match explicit server principal (with and
+# without ticket server present in keytab).
+test(princ1, princ2, '45 No key table entry found for host/2 at KRBTEST.COM')
+test(princ2, princ1,
+     '35 Cannot decrypt ticket for host/2 at KRBTEST.COM using keytab key for '
+     'host/1 at KRBTEST.COM')
+
+# Ticket server not found in keytab during iteration.
+test(princ2, None,
+     '35 Request ticket server host/2 at KRBTEST.COM not found in keytab '
+     '(ticket kvno 1)')
+
+# Ticket server found in keytab but is not matched by server principal
+# (but other principals in keytab do match).
+realm.extract_keytab(princ3, realm.keytab)
+test(princ3, matchprinc,
+     '35 Request ticket server HTTP/3 at KRBTEST.COM found in keytab but does '
+     'not match server principal host/@')
+
+# Service ticket is out of date.
+os.remove(realm.keytab)
+realm.run_kadminl('ktadd %s' % princ1)
+test(princ1, None,
+     '44 Request ticket server host/1 at KRBTEST.COM kvno 1 not found in keytab; '
+     'ticket is likely out of date')
+test(princ1, princ1,
+     '44 Cannot find key for host/1 at KRBTEST.COM kvno 1 in keytab')
+
+# kvno mismatch due to ticket principal mismatch with explicit server.
+test(princ2, princ1,
+     '35 Cannot find key for host/1 at KRBTEST.COM kvno 1 in keytab (request '
+     'ticket server host/2 at KRBTEST.COM)')
+
+# Keytab is out of date.
+realm.run_kadminl('cpw -randkey %s' % princ1)
+realm.kinit(realm.user_princ, password('user'))
+test(princ1, None,
+     '44 Request ticket server host/1 at KRBTEST.COM kvno 3 not found in keytab; '
+     'keytab is likely out of date')
+test(princ1, princ1,
+     '44 Cannot find key for host/1 at KRBTEST.COM kvno 3 in keytab')
+
+# Ticket server and kvno found but not with ticket enctype.
+os.remove(realm.keytab)
+realm.extract_keytab(princ1, realm.keytab)
+pkeytab = realm.keytab + '.partial'
+realm.run([ktutil], input=('rkt %s\ndelent 1\nwkt %s\n' %
+                           (realm.keytab, pkeytab)))
+os.rename(pkeytab, realm.keytab)
+realm.run([klist, '-ke'])
+test(princ1, None,
+     '44 Request ticket server host/1 at KRBTEST.COM kvno 3 found in keytab but '
+     'not with enctype aes256-cts')
+# This is a bad code (KRB_AP_ERR_NOKEY) and message, because
+# krb5_kt_get_entry returns the same result for this and not finding
+# the principal at all.  But it's an uncommon case; GSSAPI apps
+# usually use a matching principal and missing key enctypes are rare.
+test(princ1, princ1, '45 No key table entry found for host/1 at KRBTEST.COM')
+
+# Ticket server, kvno, and enctype matched, but key does not work.
+realm.run_kadminl('cpw -randkey %s' % princ1)
+realm.run_kadminl('modprinc -kvno 3 %s' % princ1)
+os.remove(realm.keytab)
+realm.extract_keytab(princ1, realm.keytab)
+test(princ1, None,
+     '31 Request ticket server host/1 at KRBTEST.COM kvno 3 enctype aes256-cts '
+     'found in keytab but cannot decrypt ticket')
+test(princ1, princ1,
+     '31 Cannot decrypt ticket for host/1 at KRBTEST.COM using keytab key for '
+     'host/1 at KRBTEST.COM')
+
+# Test that aliases work.  The ticket server (princ4) isn't present in
+# keytab, but there is a usable princ1 entry with the same key.
+realm.run_kadminl('renprinc -force %s %s' % (princ1, princ4))
+test(princ4, None, '0 success')
+test(princ4, princ1, '0 success')
+test(princ4, matchprinc, '0 success')
+
+success('krb5_rd_req tests')


More information about the cvs-krb5 mailing list