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)
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;
}
/*
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;
(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 */
}
} 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;
--- /dev/null
+/* -*- 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;
+}
--- /dev/null
+#!/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')