diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | src/include/k5-trace.h | 2 | ||||
-rw-r--r-- | src/lib/krb5/krb/rd_req_dec.c | 307 | ||||
-rw-r--r-- | src/lib/rpc/svc_auth_gssapi.c | 9 | ||||
-rw-r--r-- | src/lib/rpc/unit-test/rpc_test.0/gsserr.exp | 4 | ||||
-rw-r--r-- | src/tests/Makefile.in | 12 | ||||
-rwxr-xr-x | src/tests/gssapi/t_gssapi.py | 10 | ||||
-rw-r--r-- | src/tests/rdreq.c | 116 | ||||
-rw-r--r-- | src/tests/t_rdreq.py | 126 |
9 files changed, 529 insertions, 58 deletions
diff --git a/.gitignore b/.gitignore index 012175218..45a3e15d7 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 71ce73eb0..dfd34f634 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 fbd088d8a..637ff83d5 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 e3af08fb6..f3b3e35b8 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 c3e78b1a4..005971989 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 958b8a954..7347ed6c8 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 106910d8f..29d334edd 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 000000000..c010cb2c7 --- /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 000000000..42c5e29dc --- /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@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@KRBTEST.COM') +test(princ2, princ1, + '35 Cannot decrypt ticket for host/2@KRBTEST.COM using keytab key for ' + 'host/1@KRBTEST.COM') + +# Ticket server not found in keytab during iteration. +test(princ2, None, + '35 Request ticket server host/2@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@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@KRBTEST.COM kvno 1 not found in keytab; ' + 'ticket is likely out of date') +test(princ1, princ1, + '44 Cannot find key for host/1@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@KRBTEST.COM kvno 1 in keytab (request ' + 'ticket server host/2@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@KRBTEST.COM kvno 3 not found in keytab; ' + 'keytab is likely out of date') +test(princ1, princ1, + '44 Cannot find key for host/1@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@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@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@KRBTEST.COM kvno 3 enctype aes256-cts ' + 'found in keytab but cannot decrypt ticket') +test(princ1, princ1, + '31 Cannot decrypt ticket for host/1@KRBTEST.COM using keytab key for ' + 'host/1@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') |