const int vague_errors = 0;
#endif
+static krb5_error_code kdc_rd_ap_req(krb5_ap_req *apreq,
+ krb5_auth_context auth_context,
+ krb5_db_entry **server,
+ krb5_keyblock **tgskey,
+ krb5_ticket **ticket);
+static krb5_error_code find_server_key(krb5_db_entry *, krb5_enctype,
+ krb5_kvno, krb5_keyblock **,
+ krb5_kvno *);
+
/*
* concatenate first two authdata arrays, returning an allocated replacement.
* The replacement should be freed with krb5_free_authdata().
krb5_auth_context auth_context = NULL;
krb5_authenticator * authenticator = NULL;
krb5_checksum * his_cksum = NULL;
- krb5_kvno kvno = 0;
krb5_db_entry * krbtgt = NULL;
*krbtgt_ptr = NULL;
from->address)) )
goto cleanup_auth_context;
- if ((retval = kdc_get_server_key(apreq->ticket, 0, foreign_server,
- &krbtgt, tgskey, &kvno)))
- goto cleanup_auth_context;
- /*
- * We do not use the KDB keytab because other parts of the TGS need the TGT key.
- */
- retval = krb5_auth_con_setuseruserkey(kdc_context, auth_context, *tgskey);
+ retval = kdc_rd_ap_req(apreq, auth_context, &krbtgt, tgskey, ticket);
if (retval)
goto cleanup_auth_context;
- if ((retval = krb5_rd_req_decoded_anyflag(kdc_context, &auth_context, apreq,
- apreq->ticket->server,
- kdc_active_realm->realm_keytab,
- NULL, ticket)))
- goto cleanup_auth_context;
-
/* "invalid flag" tickets can must be used to validate */
if (isflagset((*ticket)->enc_part2->flags, TKT_FLG_INVALID)
&& !isflagset(request->kdc_options, KDC_OPT_VALIDATE)) {
return retval;
}
-/* XXX This function should no longer be necessary.
- * The KDC should take the keytab associated with the realm and pass that to
- * the krb5_rd_req_decode(). --proven
+/*
+ * This is a KDC wrapper around krb5_rd_req_decoded_anyflag().
*
- * It's actually still used by do_tgs_req() for u2u auth, and not too
- * much else. -- tlyu
+ * We can't depend on KDB-as-keytab for handling the AP-REQ here for
+ * optimization reasons: we want to minimize the number of KDB lookups. We'll
+ * need the KDB entry for the TGS principal, and the TGS key used to decrypt
+ * the TGT, elsewhere in the TGS code.
+ *
+ * This function also implements key rollover support for kvno 0 cross-realm
+ * TGTs issued by AD.
+ */
+static
+krb5_error_code
+kdc_rd_ap_req(krb5_ap_req *apreq, krb5_auth_context auth_context,
+ krb5_db_entry **server, krb5_keyblock **tgskey,
+ krb5_ticket **ticket)
+{
+ krb5_error_code retval;
+ krb5_enctype search_enctype = apreq->ticket->enc_part.enctype;
+ krb5_boolean match_enctype = 1;
+ krb5_kvno kvno;
+ size_t tries = 3;
+
+ /*
+ * When we issue tickets we use the first key in the principals' highest
+ * kvno keyset. For non-cross-realm krbtgt principals we want to only
+ * allow the use of the first key of the principal's keyset that matches
+ * the given kvno.
+ */
+ if (krb5_is_tgs_principal(apreq->ticket->server) &&
+ !is_cross_tgs_principal(apreq->ticket->server)) {
+ search_enctype = -1;
+ match_enctype = 0;
+ }
+
+ retval = kdc_get_server_key(apreq->ticket, 0, match_enctype, server, NULL,
+ NULL);
+ if (retval)
+ return retval;
+
+ *tgskey = NULL;
+ kvno = apreq->ticket->enc_part.kvno;
+ do {
+ krb5_free_keyblock(kdc_context, *tgskey);
+ retval = find_server_key(*server, search_enctype, kvno, tgskey, &kvno);
+ if (retval)
+ continue;
+
+ /* Make the TGS key available to krb5_rd_req_decoded_anyflag() */
+ retval = krb5_auth_con_setuseruserkey(kdc_context, auth_context,
+ *tgskey);
+ if (retval)
+ return retval;
+
+ retval = krb5_rd_req_decoded_anyflag(kdc_context, &auth_context, apreq,
+ apreq->ticket->server,
+ kdc_active_realm->realm_keytab,
+ NULL, ticket);
+ } while (retval && apreq->ticket->enc_part.kvno == 0 && kvno-- > 1 &&
+ --tries > 0);
+
+ return retval;
+}
+
+/*
+ * The KDC should take the keytab associated with the realm and pass
+ * that to the krb5_rd_req_decoded_anyflag(), but we still need to use
+ * the service (TGS, here) key elsewhere. This approach is faster than
+ * the KDB keytab approach too.
+ *
+ * This is also used by do_tgs_req() for u2u auth.
*/
krb5_error_code
kdc_get_server_key(krb5_ticket *ticket, unsigned int flags,
krb5_keyblock **key, krb5_kvno *kvno)
{
krb5_error_code retval;
- krb5_boolean similar;
- krb5_key_data * server_key;
krb5_db_entry * server = NULL;
+ krb5_enctype search_enctype = -1;
+ krb5_kvno search_kvno = -1;
+
+ if (match_enctype)
+ search_enctype = ticket->enc_part.enctype;
+ if (ticket->enc_part.kvno)
+ search_kvno = ticket->enc_part.kvno;
*server_ptr = NULL;
goto errout;
}
- retval = krb5_dbe_find_enctype(kdc_context, server,
- match_enctype ? ticket->enc_part.enctype : -1,
- -1, (krb5_int32)ticket->enc_part.kvno,
- &server_key);
- if (retval)
- goto errout;
- if (!server_key) {
- retval = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
- goto errout;
- }
- if ((*key = (krb5_keyblock *)malloc(sizeof **key))) {
- retval = krb5_dbe_decrypt_key_data(kdc_context, NULL, server_key,
- *key, NULL);
- } else
- retval = ENOMEM;
- retval = krb5_c_enctype_compare(kdc_context, ticket->enc_part.enctype,
- (*key)->enctype, &similar);
- if (retval)
- goto errout;
- if (!similar) {
- retval = KRB5_KDB_NO_PERMITTED_KEY;
- goto errout;
+ if (key) {
+ retval = find_server_key(server, search_enctype, search_kvno, key, kvno);
+ if (retval)
+ goto errout;
}
- (*key)->enctype = ticket->enc_part.enctype;
- *kvno = server_key->key_data_kvno;
*server_ptr = server;
server = NULL;
+ return 0;
+
errout:
krb5_db_free_principal(kdc_context, server);
return retval;
}
+/*
+ * A utility function to get the right key from a KDB entry. Used in handling
+ * of kvno 0 TGTs, for example.
+ */
+static
+krb5_error_code
+find_server_key(krb5_db_entry *server, krb5_enctype enctype, krb5_kvno kvno,
+ krb5_keyblock **key_out, krb5_kvno *kvno_out)
+{
+ krb5_error_code retval;
+ krb5_key_data * server_key;
+ krb5_keyblock * key;
+
+ *key_out = NULL;
+ retval = krb5_dbe_find_enctype(kdc_context, server, enctype, -1,
+ kvno ? (krb5_int32)kvno : -1, &server_key);
+ if (retval)
+ return retval;
+ if (!server_key)
+ return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ if ((key = (krb5_keyblock *)malloc(sizeof *key)) == NULL)
+ return ENOMEM;
+ retval = krb5_dbe_decrypt_key_data(kdc_context, NULL, server_key,
+ key, NULL);
+ if (retval)
+ goto errout;
+ if (enctype != -1) {
+ krb5_boolean similar;
+ retval = krb5_c_enctype_compare(kdc_context, enctype, key->enctype,
+ &similar);
+ if (retval)
+ goto errout;
+ if (!similar) {
+ retval = KRB5_KDB_NO_PERMITTED_KEY;
+ goto errout;
+ }
+ key->enctype = enctype;
+ }
+ *key_out = key;
+ key = NULL;
+ if (kvno_out)
+ *kvno_out = server_key->key_data_kvno;
+errout:
+ krb5_free_keyblock(kdc_context, key);
+ return retval;
+}
+
/* This probably wants to be updated if you support last_req stuff */
static krb5_last_req_entry nolrentry = { KV5M_LAST_REQ_ENTRY, KRB5_LRQ_NONE, 0 };
if expected not in output:
fail('keyrollover: expected TGS enctype not found after change')
+# Test that the KDC only accepts the first enctype for a kvno, for a
+# local-realm TGS request. To set this up, we abuse an edge-case
+# behavior of modprinc -kvno. First, set up a DES3 krbtgt entry at
+# kvno 1 and cache a krbtgt ticket.
+realm.run_kadminl('cpw -randkey -e des3-cbc-sha1:normal krbtgt/%s' %
+ realm.realm)
+realm.run_kadminl('modprinc -kvno 1 krbtgt/%s' % realm.realm)
+realm.kinit(realm.user_princ, password('user'))
+# Add an AES krbtgt entry at kvno 2, and then reset it to kvno 1
+# (modprinc -kvno sets the kvno on all entries without deleting any).
+realm.run_kadminl('cpw -randkey -keepold -e aes256-cts:normal krbtgt/%s' %
+ realm.realm)
+realm.run_kadminl('modprinc -kvno 1 krbtgt/%s' % realm.realm)
+output = realm.run_kadminl('getprinc krbtgt/%s' % realm.realm)
+if 'vno 1, aes256' not in output or 'vno 1, des3' not in output:
+ fail('keyrollover: setup for TGS enctype test failed')
+# Now present the DES3 ticket to the KDC and make sure it's rejected.
+realm.run_as_client([kvno, realm.host_princ], expected_code=1)
+
+realm.stop()
+
+# Test a cross-realm TGT key rollover scenario where realm 1 mimics
+# the Active Directory behavior of always using kvno 0 when issuing
+# cross-realm TGTs. The first kvno invocation caches a cross-realm
+# TGT with the old key, and the second kvno invocation sends it to
+# r2's KDC with no kvno to identify it, forcing the KDC to try
+# multiple keys.
+r1, r2 = cross_realms(2, start_kadmind=False)
+r1.run_kadminl('modprinc -kvno 0 krbtgt/%s' % r2.realm)
+r1.run_as_client([kvno, r2.host_princ])
+r2.run_kadminl('cpw -pw newcross -keepold krbtgt/%s@%s' % (r2.realm, r1.realm))
+r1.run_kadminl('cpw -pw newcross krbtgt/%s' % r2.realm)
+r1.run_kadminl('modprinc -kvno 0 krbtgt/%s' % r2.realm)
+r1.run_as_client([kvno, r2.user_princ])
+
success('keyrollover')