Add PKINIT KDC support for freshness token

Also, fix securid_sam2 preauth for non-default salt
This commit is contained in:
Robbie Harwood 2018-03-19 18:15:48 -04:00
parent ed142b51b1
commit a387becbf5
7 changed files with 2189 additions and 1 deletions

View File

@ -0,0 +1,631 @@
From 4ddfba7c9c12056f9f5819648f20f68e5625dced Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Mon, 12 Mar 2018 11:31:46 -0400
Subject: [PATCH] Add PKINIT KDC support for freshness token
Send a freshness token in the preauth hint list if PKINIT is
configured and the request padata indicates support. Verify the
freshness token if the client includes one in a PKINIT request, and
log whether one was received. If pkinit_require_freshness is set to
true in the realm config, reject non-anonymous requests which don't
contain a freshness token.
Add freshness token tests to t_pkinit.py with some related changes.
Remove client long-term keys after testing password preauth so we get
better error reporting when pkinit_require_freshness is set and a
token is not sent. Remove ./responder invocations for test cases
which don't ask PKINIT responder questions, or else the responder
would fail now that it isn't being asked for the password. Leave
anonymous PKINIT enabled after the anonymous tests so that we can use
it again when testing enforcement of pkinit_require_freshness. Add
expected trace messages for the basic test, including one for
receiving a freshness token. Add minimal expected trace messages for
the RSA test.
ticket: 8648
(cherry picked from commit 4a9050df0bc34bfb08ba24462d6e2514640f4b8e)
---
doc/admin/conf_files/kdc_conf.rst | 4 +
doc/admin/pkinit.rst | 25 ++++++
doc/appdev/refs/macros/index.rst | 2 +
doc/formats/freshness_token.rst | 19 +++++
doc/formats/index.rst | 1 +
src/include/krb5/kdcpreauth_plugin.h | 17 +++++
src/include/krb5/krb5.hin | 3 +
src/kdc/do_as_req.c | 2 +
src/kdc/kdc_preauth.c | 130 +++++++++++++++++++++++++++++++-
src/kdc/kdc_util.h | 2 +
src/plugins/preauth/pkinit/pkinit.h | 2 +
src/plugins/preauth/pkinit/pkinit_srv.c | 51 ++++++++++++-
src/tests/t_pkinit.py | 50 ++++++++----
13 files changed, 292 insertions(+), 16 deletions(-)
create mode 100644 doc/formats/freshness_token.rst
diff --git a/doc/admin/conf_files/kdc_conf.rst b/doc/admin/conf_files/kdc_conf.rst
index 3af1c3796..1ac1a37c2 100644
--- a/doc/admin/conf_files/kdc_conf.rst
+++ b/doc/admin/conf_files/kdc_conf.rst
@@ -798,6 +798,10 @@ For information about the syntax of some of these options, see
**pkinit_require_crl_checking** should be set to true if the
policy is such that up-to-date CRLs must be present for every CA.
+**pkinit_require_freshness**
+ Specifies whether to require clients to include a freshness token
+ in PKINIT requests. The default value is false. (New in release
+ 1.17.)
.. _Encryption_types:
diff --git a/doc/admin/pkinit.rst b/doc/admin/pkinit.rst
index c601c5c9e..bec4fc800 100644
--- a/doc/admin/pkinit.rst
+++ b/doc/admin/pkinit.rst
@@ -327,3 +327,28 @@ appropriate :ref:`kdc_realms` subsection of the KDC's
To obtain anonymous credentials on a client, run ``kinit -n``, or
``kinit -n @REALMNAME`` to specify a realm. The resulting tickets
will have the client name ``WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS``.
+
+
+Freshness tokens
+----------------
+
+Freshness tokens can ensure that the client has recently had access to
+its certificate private key. If freshness tokens are not required by
+the KDC, a client program with temporary possession of the private key
+can compose requests for future timestamps and use them later.
+
+In release 1.17 and later, freshness tokens are supported by the
+client and are sent by the KDC when the client indicates support for
+them. Because not all clients support freshness tokens yet, they are
+not required by default. To check if freshness tokens are supported
+by a realm's clients, look in the KDC logs for the lines::
+
+ PKINIT: freshness token received from <client principal>
+ PKINIT: no freshness token received from <client principal>
+
+To require freshness tokens for all clients in a realm (except for
+clients authenticating anonymously), set the
+**pkinit_require_freshness** variable to ``true`` in the appropriate
+:ref:`kdc_realms` subsection of the KDC's :ref:`kdc.conf(5)` file. To
+test that this option is in effect, run ``kinit -X disable_freshness``
+and verify that authentication is unsuccessful.
diff --git a/doc/appdev/refs/macros/index.rst b/doc/appdev/refs/macros/index.rst
index e76747102..dba818b26 100644
--- a/doc/appdev/refs/macros/index.rst
+++ b/doc/appdev/refs/macros/index.rst
@@ -181,6 +181,7 @@ Public
KRB5_KEYUSAGE_KRB_ERROR_CKSUM.rst
KRB5_KEYUSAGE_KRB_PRIV_ENCPART.rst
KRB5_KEYUSAGE_KRB_SAFE_CKSUM.rst
+ KRB5_KEYUSAGE_PA_AS_FRESHNESS.rst
KRB5_KEYUSAGE_PA_FX_COOKIE.rst
KRB5_KEYUSAGE_PA_OTP_REQUEST.rst
KRB5_KEYUSAGE_PA_PKINIT_KX.rst
@@ -241,6 +242,7 @@ Public
KRB5_PADATA_AFS3_SALT.rst
KRB5_PADATA_AP_REQ.rst
KRB5_PADATA_AS_CHECKSUM.rst
+ KRB5_PADATA_AS_FRESHNESS.rst
KRB5_PADATA_ENCRYPTED_CHALLENGE.rst
KRB5_PADATA_ENC_SANDIA_SECURID.rst
KRB5_PADATA_ENC_TIMESTAMP.rst
diff --git a/doc/formats/freshness_token.rst b/doc/formats/freshness_token.rst
new file mode 100644
index 000000000..3127621a9
--- /dev/null
+++ b/doc/formats/freshness_token.rst
@@ -0,0 +1,19 @@
+PKINIT freshness tokens
+=======================
+
+:rfc:`8070` specifies a pa-data type PA_AS_FRESHNESS, which clients
+should reflect within signed PKINIT data to prove recent access to the
+client certificate private key. The contents of a freshness token are
+left to the KDC implementation. The MIT krb5 KDC uses the following
+format for freshness tokens (starting in release 1.17):
+
+* a four-byte big-endian POSIX timestamp
+* a four-byte big-endian key version number
+* an :rfc:`3961` checksum, with no ASN.1 wrapper
+
+The checksum is computed using the first key in the local krbtgt
+principal entry for the realm (e.g. ``krbtgt/KRBTEST.COM@KRBTEST.COM``
+if the request is to the ``KRBTEST.COM`` realm) of the indicated key
+version. The checksum type must be the mandatory checksum type for
+the encryption type of the krbtgt key. The key usage value for the
+checksum is 514.
diff --git a/doc/formats/index.rst b/doc/formats/index.rst
index 8b30626d4..4ad534424 100644
--- a/doc/formats/index.rst
+++ b/doc/formats/index.rst
@@ -7,3 +7,4 @@ Protocols and file formats
ccache_file_format
keytab_file_format
cookie
+ freshness_token
diff --git a/src/include/krb5/kdcpreauth_plugin.h b/src/include/krb5/kdcpreauth_plugin.h
index f38820099..3a4754234 100644
--- a/src/include/krb5/kdcpreauth_plugin.h
+++ b/src/include/krb5/kdcpreauth_plugin.h
@@ -240,6 +240,23 @@ typedef struct krb5_kdcpreauth_callbacks_st {
/* End of version 4 kdcpreauth callbacks. */
+ /*
+ * Instruct the KDC to send a freshness token in the method data
+ * accompanying a PREAUTH_REQUIRED or PREAUTH_FAILED error, if the client
+ * indicated support for freshness tokens. This callback should only be
+ * invoked from the edata method.
+ */
+ void (*send_freshness_token)(krb5_context context,
+ krb5_kdcpreauth_rock rock);
+
+ /* Validate a freshness token sent by the client. Return 0 on success,
+ * KRB5KDC_ERR_PREAUTH_EXPIRED on error. */
+ krb5_error_code (*check_freshness_token)(krb5_context context,
+ krb5_kdcpreauth_rock rock,
+ const krb5_data *token);
+
+ /* End of version 5 kdcpreauth callbacks. */
+
} *krb5_kdcpreauth_callbacks;
/* Optional: preauth plugin initialization function. */
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index 833e72335..a650ecece 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -1035,7 +1035,10 @@ krb5_c_keyed_checksum_types(krb5_context context, krb5_enctype enctype,
#define KRB5_KEYUSAGE_AS_REQ 56
#define KRB5_KEYUSAGE_CAMMAC 64
+/* Key usage values 512-1023 are reserved for uses internal to a Kerberos
+ * implementation. */
#define KRB5_KEYUSAGE_PA_FX_COOKIE 513 /**< Used for encrypted FAST cookies */
+#define KRB5_KEYUSAGE_PA_AS_FRESHNESS 514 /**< Used for freshness tokens */
/** @} */ /* end of KRB5_KEYUSAGE group */
/**
diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c
index 7c8da63e1..588c1375a 100644
--- a/src/kdc/do_as_req.c
+++ b/src/kdc/do_as_req.c
@@ -563,6 +563,7 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
state->rock.rstate = state->rstate;
state->rock.vctx = vctx;
state->rock.auth_indicators = &state->auth_indicators;
+ state->rock.send_freshness_token = FALSE;
if (!state->request->client) {
state->status = "NULL_CLIENT";
errcode = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
@@ -659,6 +660,7 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
state->status = "GET_LOCAL_TGT";
goto errout;
}
+ state->rock.local_tgt = state->local_tgt;
au_state->stage = VALIDATE_POL;
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c
index 6f34dc289..80b130222 100644
--- a/src/kdc/kdc_preauth.c
+++ b/src/kdc/kdc_preauth.c
@@ -87,6 +87,9 @@
#include <assert.h>
#include <krb5/kdcpreauth_plugin.h>
+/* Let freshness tokens be valid for ten minutes. */
+#define FRESHNESS_LIFETIME 600
+
typedef struct preauth_system_st {
const char *name;
int type;
@@ -497,8 +500,68 @@ client_name(krb5_context context, krb5_kdcpreauth_rock rock)
return rock->client->princ;
}
+static void
+send_freshness_token(krb5_context context, krb5_kdcpreauth_rock rock)
+{
+ rock->send_freshness_token = TRUE;
+}
+
+static krb5_error_code
+check_freshness_token(krb5_context context, krb5_kdcpreauth_rock rock,
+ const krb5_data *token)
+{
+ krb5_timestamp token_ts, now;
+ krb5_key_data *kd;
+ krb5_keyblock kb;
+ krb5_kvno token_kvno;
+ krb5_checksum cksum;
+ krb5_data d;
+ uint8_t *token_cksum;
+ size_t token_cksum_len;
+ krb5_boolean valid = FALSE;
+ char ckbuf[4];
+
+ memset(&kb, 0, sizeof(kb));
+
+ if (krb5_timeofday(context, &now) != 0)
+ goto cleanup;
+
+ if (token->length <= 8)
+ goto cleanup;
+ token_ts = load_32_be(token->data);
+ token_kvno = load_32_be(token->data + 4);
+ token_cksum = (uint8_t *)token->data + 8;
+ token_cksum_len = token->length - 8;
+
+ /* Check if the token timestamp is too old. */
+ if (ts_after(now, ts_incr(token_ts, FRESHNESS_LIFETIME)))
+ goto cleanup;
+
+ /* Fetch and decrypt the local krbtgt key of the token's kvno. */
+ if (krb5_dbe_find_enctype(context, rock->local_tgt, -1, -1, token_kvno,
+ &kd) != 0)
+ goto cleanup;
+ if (krb5_dbe_decrypt_key_data(context, NULL, kd, &kb, NULL) != 0)
+ goto cleanup;
+
+ /* Verify the token checksum against the current KDC time. The checksum
+ * must use the mandatory checksum type of the krbtgt key's enctype. */
+ store_32_be(token_ts, ckbuf);
+ d = make_data(ckbuf, sizeof(ckbuf));
+ cksum.magic = KV5M_CHECKSUM;
+ cksum.checksum_type = 0;
+ cksum.length = token_cksum_len;
+ cksum.contents = token_cksum;
+ (void)krb5_c_verify_checksum(context, &kb, KRB5_KEYUSAGE_PA_AS_FRESHNESS,
+ &d, &cksum, &valid);
+
+cleanup:
+ krb5_free_keyblock_contents(context, &kb);
+ return valid ? 0 : KRB5KDC_ERR_PREAUTH_EXPIRED;
+}
+
static struct krb5_kdcpreauth_callbacks_st callbacks = {
- 4,
+ 5,
max_time_skew,
client_keys,
free_keys,
@@ -514,7 +577,9 @@ static struct krb5_kdcpreauth_callbacks_st callbacks = {
get_cookie,
set_cookie,
match_client,
- client_name
+ client_name,
+ send_freshness_token,
+ check_freshness_token
};
static krb5_error_code
@@ -770,6 +835,62 @@ cleanup:
return ret;
}
+static krb5_error_code
+add_freshness_token(krb5_context context, krb5_kdcpreauth_rock rock,
+ krb5_pa_data ***pa_list)
+{
+ krb5_error_code ret;
+ krb5_timestamp now;
+ krb5_key_data *kd;
+ krb5_keyblock kb;
+ krb5_checksum cksum;
+ krb5_data d;
+ krb5_pa_data *pa;
+ char ckbuf[4];
+
+ memset(&cksum, 0, sizeof(cksum));
+ memset(&kb, 0, sizeof(kb));
+
+ if (!rock->send_freshness_token)
+ return 0;
+ if (krb5int_find_pa_data(context, rock->request->padata,
+ KRB5_PADATA_AS_FRESHNESS) == NULL)
+ return 0;
+
+ /* Fetch and decrypt the current local krbtgt key. */
+ ret = krb5_dbe_find_enctype(context, rock->local_tgt, -1, -1, 0, &kd);
+ if (ret)
+ goto cleanup;
+ ret = krb5_dbe_decrypt_key_data(context, NULL, kd, &kb, NULL);
+ if (ret)
+ goto cleanup;
+
+ /* Compute a checksum over the current KDC time. */
+ ret = krb5_timeofday(context, &now);
+ if (ret)
+ goto cleanup;
+ store_32_be(now, ckbuf);
+ d = make_data(ckbuf, sizeof(ckbuf));
+ ret = krb5_c_make_checksum(context, 0, &kb, KRB5_KEYUSAGE_PA_AS_FRESHNESS,
+ &d, &cksum);
+
+ /* Compose a freshness token from the time, krbtgt kvno, and checksum. */
+ ret = alloc_pa_data(KRB5_PADATA_AS_FRESHNESS, 8 + cksum.length, &pa);
+ if (ret)
+ goto cleanup;
+ store_32_be(now, pa->contents);
+ store_32_be(kd->key_data_kvno, pa->contents + 4);
+ memcpy(pa->contents + 8, cksum.contents, cksum.length);
+
+ /* add_pa_data_element() claims pa on success or failure. */
+ ret = add_pa_data_element(pa_list, pa);
+
+cleanup:
+ krb5_free_keyblock_contents(context, &kb);
+ krb5_free_checksum_contents(context, &cksum);
+ return ret;
+}
+
struct hint_state {
kdc_hint_respond_fn respond;
void *arg;
@@ -792,6 +913,11 @@ hint_list_finish(struct hint_state *state, krb5_error_code code)
void *oldarg = state->arg;
kdc_realm_t *kdc_active_realm = state->realm;
+ /* Add a freshness token if a preauth module requested it and the client
+ * request indicates support for it. */
+ if (!code)
+ code = add_freshness_token(kdc_context, state->rock, &state->pa_data);
+
if (!code) {
if (state->pa_data == NULL) {
krb5_klog_syslog(LOG_INFO,
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index 18649b8ad..a63af2503 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -427,11 +427,13 @@ struct krb5_kdcpreauth_rock_st {
krb5_kdc_req *request;
krb5_data *inner_body;
krb5_db_entry *client;
+ krb5_db_entry *local_tgt;
krb5_key_data *client_key;
krb5_keyblock *client_keyblock;
struct kdc_request_state *rstate;
verto_ctx *vctx;
krb5_data ***auth_indicators;
+ krb5_boolean send_freshness_token;
};
#define isflagset(flagfield, flag) (flagfield & (flag))
diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h
index 8489a3e23..fe2ec0d31 100644
--- a/src/plugins/preauth/pkinit/pkinit.h
+++ b/src/plugins/preauth/pkinit/pkinit.h
@@ -77,6 +77,7 @@
#define KRB5_CONF_PKINIT_KDC_OCSP "pkinit_kdc_ocsp"
#define KRB5_CONF_PKINIT_POOL "pkinit_pool"
#define KRB5_CONF_PKINIT_REQUIRE_CRL_CHECKING "pkinit_require_crl_checking"
+#define KRB5_CONF_PKINIT_REQUIRE_FRESHNESS "pkinit_require_freshness"
#define KRB5_CONF_PKINIT_REVOKE "pkinit_revoke"
/* Make pkiDebug(fmt,...) print, or not. */
@@ -148,6 +149,7 @@ typedef struct _pkinit_plg_opts {
int allow_upn; /* allow UPN-SAN instead of pkinit-SAN */
int dh_or_rsa; /* selects DH or RSA based pkinit */
int require_crl_checking; /* require CRL for a CA (default is false) */
+ int require_freshness; /* require freshness token (default is false) */
int disable_freshness; /* disable freshness token on client for testing */
int dh_min_bits; /* minimum DH modulus size allowed */
} pkinit_plg_opts;
diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c
index 4e9685885..bbfde34b2 100644
--- a/src/plugins/preauth/pkinit/pkinit_srv.c
+++ b/src/plugins/preauth/pkinit/pkinit_srv.c
@@ -161,6 +161,10 @@ pkinit_server_get_edata(krb5_context context,
if (plgctx == NULL)
retval = EINVAL;
+ /* Send a freshness token if the client requested one. */
+ if (!retval)
+ cb->send_freshness_token(context, rock);
+
(*respond)(arg, retval, NULL);
}
@@ -396,6 +400,31 @@ cleanup:
return ret;
}
+/* Return an error if freshness tokens are required and one was not received.
+ * Log an appropriate message indicating whether a valid token was received. */
+static krb5_error_code
+check_log_freshness(krb5_context context, pkinit_kdc_context plgctx,
+ krb5_kdc_req *request, krb5_boolean valid_freshness_token)
+{
+ krb5_error_code ret;
+ char *name = NULL;
+
+ ret = krb5_unparse_name(context, request->client, &name);
+ if (ret)
+ return ret;
+ if (plgctx->opts->require_freshness && !valid_freshness_token) {
+ com_err("", 0, _("PKINIT: no freshness token, rejecting auth from %s"),
+ name);
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ } else if (valid_freshness_token) {
+ com_err("", 0, _("PKINIT: freshness token received from %s"), name);
+ } else {
+ com_err("", 0, _("PKINIT: no freshness token received from %s"), name);
+ }
+ krb5_free_unparsed_name(context, name);
+ return ret;
+}
+
static void
pkinit_server_verify_padata(krb5_context context,
krb5_data *req_pkt,
@@ -418,10 +447,11 @@ pkinit_server_verify_padata(krb5_context context,
pkinit_kdc_req_context reqctx = NULL;
krb5_checksum cksum = {0, 0, 0, NULL};
krb5_data *der_req = NULL;
- krb5_data k5data;
+ krb5_data k5data, *ftoken;
int is_signed = 1;
krb5_pa_data **e_data = NULL;
krb5_kdcpreauth_modreq modreq = NULL;
+ krb5_boolean valid_freshness_token = FALSE;
char **sp;
pkiDebug("pkinit_verify_padata: entered!\n");
@@ -592,6 +622,14 @@ pkinit_server_verify_padata(krb5_context context,
goto cleanup;
}
+ ftoken = auth_pack->pkAuthenticator.freshnessToken;
+ if (ftoken != NULL) {
+ retval = cb->check_freshness_token(context, rock, ftoken);
+ if (retval)
+ goto cleanup;
+ valid_freshness_token = TRUE;
+ }
+
/* check if kdcPkId present and match KDC's subjectIdentifier */
if (reqp->kdcPkId.data != NULL) {
int valid_kdcPkId = 0;
@@ -634,6 +672,13 @@ pkinit_server_verify_padata(krb5_context context,
break;
}
+ if (is_signed) {
+ retval = check_log_freshness(context, plgctx, request,
+ valid_freshness_token);
+ if (retval)
+ goto cleanup;
+ }
+
if (is_signed && plgctx->auth_indicators != NULL) {
/* Assert configured authentication indicators. */
for (sp = plgctx->auth_indicators; *sp != NULL; sp++) {
@@ -1323,6 +1368,10 @@ pkinit_init_kdc_profile(krb5_context context, pkinit_kdc_context plgctx)
KRB5_CONF_PKINIT_REQUIRE_CRL_CHECKING,
0, &plgctx->opts->require_crl_checking);
+ pkinit_kdcdefault_boolean(context, plgctx->realmname,
+ KRB5_CONF_PKINIT_REQUIRE_FRESHNESS,
+ 0, &plgctx->opts->require_freshness);
+
pkinit_kdcdefault_string(context, plgctx->realmname,
KRB5_CONF_PKINIT_EKU_CHECKING,
&eku_string);
diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py
index b790a7cda..3030322e1 100755
--- a/src/tests/t_pkinit.py
+++ b/src/tests/t_pkinit.py
@@ -39,6 +39,8 @@ pkinit_kdc_conf = {'realms': {'$realm': {
'pkinit_indicator': ['indpkinit1', 'indpkinit2']}}}
restrictive_kdc_conf = {'realms': {'$realm': {
'restrict_anonymous_to_tgt': 'true' }}}
+freshness_kdc_conf = {'realms': {'$realm': {
+ 'pkinit_require_freshness': 'true'}}}
testprincs = {'krbtgt/KRBTEST.COM': {'keys': 'aes128-cts'},
'user': {'keys': 'aes128-cts', 'flags': '+preauth'},
@@ -118,6 +120,10 @@ realm.kinit(realm.user_princ, password=password('user'))
realm.klist(realm.user_princ)
realm.run([kvno, realm.host_princ])
+# Having tested password preauth, remove the keys for better error
+# reporting.
+realm.run([kadminl, 'purgekeys', '-all', realm.user_princ])
+
# Test anonymous PKINIT.
realm.kinit('@%s' % realm.realm, flags=['-n'], expected_code=1,
expected_msg='not found in Kerberos database')
@@ -153,23 +159,32 @@ realm.run([kvno, realm.host_princ], expected_code=1,
realm.kinit(realm.host_princ, flags=['-k'])
realm.run([kvno, '-U', 'user', realm.host_princ])
-# Go back to a normal KDC and disable anonymous PKINIT.
+# Go back to the normal KDC environment.
realm.stop_kdc()
realm.start_kdc()
-realm.run([kadminl, 'delprinc', 'WELLKNOWN/ANONYMOUS'])
# Run the basic test - PKINIT with FILE: identity, with no password on the key.
-realm.run(['./responder', '-x', 'pkinit=',
- '-X', 'X509_user_identity=%s' % file_identity, realm.user_princ])
realm.kinit(realm.user_princ,
- flags=['-X', 'X509_user_identity=%s' % file_identity])
+ flags=['-X', 'X509_user_identity=%s' % file_identity],
+ expected_trace=('Sending unauthenticated request',
+ '/Additional pre-authentication required',
+ 'Preauthenticating using KDC method data',
+ 'PKINIT client received freshness token from KDC',
+ 'PKINIT loading CA certs and CRLs from FILE',
+ 'PKINIT client making DH request',
+ 'Produced preauth for next request: 133, 16',
+ 'PKINIT client verified DH reply',
+ 'PKINIT client found id-pkinit-san in KDC cert',
+ 'PKINIT client matched KDC principal krbtgt/'))
realm.klist(realm.user_princ)
realm.run([kvno, realm.host_princ])
# Try again using RSA instead of DH.
realm.kinit(realm.user_princ,
flags=['-X', 'X509_user_identity=%s' % file_identity,
- '-X', 'flag_RSA_PROTOCOL=yes'])
+ '-X', 'flag_RSA_PROTOCOL=yes'],
+ expected_trace=('PKINIT client making RSA request',
+ 'PKINIT client verified RSA reply'))
realm.klist(realm.user_princ)
# Test a DH parameter renegotiation by temporarily setting a 4096-bit
@@ -192,8 +207,23 @@ expected_trace = ('Sending unauthenticated request',
realm.kinit(realm.user_princ,
flags=['-X', 'X509_user_identity=%s' % file_identity],
expected_trace=expected_trace)
+
+# Test enforcement of required freshness tokens. (We can leave
+# freshness tokens required after this test.)
+realm.kinit(realm.user_princ,
+ flags=['-X', 'X509_user_identity=%s' % file_identity,
+ '-X', 'disable_freshness=yes'])
+f_env = realm.special_env('freshness', True, kdc_conf=freshness_kdc_conf)
realm.stop_kdc()
-realm.start_kdc()
+realm.start_kdc(env=f_env)
+realm.kinit(realm.user_princ,
+ flags=['-X', 'X509_user_identity=%s' % file_identity])
+realm.kinit(realm.user_princ,
+ flags=['-X', 'X509_user_identity=%s' % file_identity,
+ '-X', 'disable_freshness=yes'],
+ expected_code=1, expected_msg='Preauthentication failed')
+# Anonymous should never require a freshness token.
+realm.kinit('@%s' % realm.realm, flags=['-n', '-X', 'disable_freshness=yes'])
# Run the basic test - PKINIT with FILE: identity, with a password on the key,
# supplied by the prompter.
@@ -229,8 +259,6 @@ shutil.copy(privkey_pem, os.path.join(path, 'user.key'))
shutil.copy(privkey_enc_pem, os.path.join(path_enc, 'user.key'))
shutil.copy(user_pem, os.path.join(path, 'user.crt'))
shutil.copy(user_pem, os.path.join(path_enc, 'user.crt'))
-realm.run(['./responder', '-x', 'pkinit=', '-X',
- 'X509_user_identity=%s' % dir_identity, realm.user_princ])
realm.kinit(realm.user_princ,
flags=['-X', 'X509_user_identity=%s' % dir_identity])
realm.klist(realm.user_princ)
@@ -262,8 +290,6 @@ realm.klist(realm.user_princ)
realm.run([kvno, realm.host_princ])
# PKINIT with PKCS12: identity, with no password on the bundle.
-realm.run(['./responder', '-x', 'pkinit=',
- '-X', 'X509_user_identity=%s' % p12_identity, realm.user_princ])
realm.kinit(realm.user_princ,
flags=['-X', 'X509_user_identity=%s' % p12_identity])
realm.klist(realm.user_princ)
@@ -350,8 +376,6 @@ conf = open(softpkcs11rc, 'w')
conf.write("%s\t%s\t%s\t%s\n" % ('user', 'user token', user_pem, privkey_pem))
conf.close()
# Expect to succeed without having to supply any more information.
-realm.run(['./responder', '-x', 'pkinit=',
- '-X', 'X509_user_identity=%s' % p11_identity, realm.user_princ])
realm.kinit(realm.user_princ,
flags=['-X', 'X509_user_identity=%s' % p11_identity])
realm.klist(realm.user_princ)

View File

@ -0,0 +1,336 @@
From 7eb2df66aef8e2b58ec7dfa13e9ee19f5e3b5b34 Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Tue, 31 Jan 2017 17:02:34 -0500
Subject: [PATCH] Add PKINIT client support for freshness token
Send an empty PA_AS_FRESHNESS padata item in unauthenticated AS
requests to indicate support for RFC 8070. If the KDC includes a
PA_AS_FRESHNESS value in its method data, echo it back in the new
freshnessToken field of pkAuthenticator
ticket: 8648
(cherry picked from commit 085785362e01467cb25c79a90dcebfba9ea019d8)
---
doc/user/user_commands/kinit.rst | 3 +++
src/include/k5-int-pkinit.h | 1 +
src/include/krb5/krb5.hin | 1 +
src/lib/krb5/asn.1/asn1_k_encode.c | 5 ++++-
src/lib/krb5/krb/get_in_tkt.c | 12 ++++++++----
src/lib/krb5/krb/init_creds_ctx.h | 2 +-
src/plugins/preauth/pkinit/pkinit.h | 3 +++
src/plugins/preauth/pkinit/pkinit_clnt.c | 19 ++++++++++++++++++-
src/plugins/preauth/pkinit/pkinit_lib.c | 3 +++
src/plugins/preauth/pkinit/pkinit_trace.h | 2 ++
src/tests/asn.1/ktest.c | 4 ++++
src/tests/asn.1/pkinit_encode.out | 2 +-
src/tests/asn.1/pkinit_trval.out | 1 +
13 files changed, 50 insertions(+), 8 deletions(-)
diff --git a/doc/user/user_commands/kinit.rst b/doc/user/user_commands/kinit.rst
index 3f9d5340f..1f696920f 100644
--- a/doc/user/user_commands/kinit.rst
+++ b/doc/user/user_commands/kinit.rst
@@ -197,6 +197,9 @@ OPTIONS
specify use of RSA, rather than the default Diffie-Hellman
protocol
+ **disable_freshness**\ [**=yes**]
+ disable sending freshness tokens (for testing purposes only)
+
ENVIRONMENT
-----------
diff --git a/src/include/k5-int-pkinit.h b/src/include/k5-int-pkinit.h
index 7b2f595cb..4622a629e 100644
--- a/src/include/k5-int-pkinit.h
+++ b/src/include/k5-int-pkinit.h
@@ -42,6 +42,7 @@ typedef struct _krb5_pk_authenticator {
krb5_timestamp ctime;
krb5_int32 nonce; /* (0..4294967295) */
krb5_checksum paChecksum;
+ krb5_data *freshnessToken;
} krb5_pk_authenticator;
/* PKAuthenticator draft9 */
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index e81bb0a6d..833e72335 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -1879,6 +1879,7 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype,
#define KRB5_PADATA_OTP_PIN_CHANGE 144 /**< RFC 6560 section 4.3 */
#define KRB5_PADATA_PKINIT_KX 147 /**< RFC 6112 */
#define KRB5_ENCPADATA_REQ_ENC_PA_REP 149 /**< RFC 6806 */
+#define KRB5_PADATA_AS_FRESHNESS 150 /**< RFC 8070 */
#define KRB5_SAM_USE_SAD_AS_KEY 0x80000000
#define KRB5_SAM_SEND_ENCRYPTED_SAD 0x40000000
diff --git a/src/lib/krb5/asn.1/asn1_k_encode.c b/src/lib/krb5/asn.1/asn1_k_encode.c
index 889460989..3b23fe34a 100644
--- a/src/lib/krb5/asn.1/asn1_k_encode.c
+++ b/src/lib/krb5/asn.1/asn1_k_encode.c
@@ -1442,9 +1442,12 @@ DEFFIELD(pk_authenticator_1, krb5_pk_authenticator, ctime, 1, kerberos_time);
DEFFIELD(pk_authenticator_2, krb5_pk_authenticator, nonce, 2, int32);
DEFFIELD(pk_authenticator_3, krb5_pk_authenticator, paChecksum, 3,
ostring_checksum);
+DEFFIELD(pk_authenticator_4, krb5_pk_authenticator, freshnessToken, 4,
+ opt_ostring_data_ptr);
static const struct atype_info *pk_authenticator_fields[] = {
&k5_atype_pk_authenticator_0, &k5_atype_pk_authenticator_1,
- &k5_atype_pk_authenticator_2, &k5_atype_pk_authenticator_3
+ &k5_atype_pk_authenticator_2, &k5_atype_pk_authenticator_3,
+ &k5_atype_pk_authenticator_4
};
DEFSEQTYPE(pk_authenticator, krb5_pk_authenticator, pk_authenticator_fields);
diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c
index 47a00bf2c..1d96ff163 100644
--- a/src/lib/krb5/krb/get_in_tkt.c
+++ b/src/lib/krb5/krb/get_in_tkt.c
@@ -895,7 +895,7 @@ krb5_init_creds_init(krb5_context context,
ctx->request = k5alloc(sizeof(krb5_kdc_req), &code);
if (code != 0)
goto cleanup;
- ctx->enc_pa_rep_permitted = TRUE;
+ ctx->info_pa_permitted = TRUE;
code = krb5_copy_principal(context, client, &ctx->request->client);
if (code != 0)
goto cleanup;
@@ -1389,7 +1389,11 @@ init_creds_step_request(krb5_context context,
krb5_free_data(context, ctx->encoded_previous_request);
ctx->encoded_previous_request = NULL;
}
- if (ctx->enc_pa_rep_permitted) {
+ if (ctx->info_pa_permitted) {
+ code = add_padata(&ctx->request->padata, KRB5_PADATA_AS_FRESHNESS,
+ NULL, 0);
+ if (code)
+ goto cleanup;
code = add_padata(&ctx->request->padata, KRB5_ENCPADATA_REQ_ENC_PA_REP,
NULL, 0);
}
@@ -1530,7 +1534,7 @@ init_creds_step_reply(krb5_context context,
ctx->selected_preauth_type == KRB5_PADATA_NONE) {
/* The KDC didn't like our informational padata (probably a pre-1.7
* MIT krb5 KDC). Retry without it. */
- ctx->enc_pa_rep_permitted = FALSE;
+ ctx->info_pa_permitted = FALSE;
ctx->restarted = TRUE;
code = restart_init_creds_loop(context, ctx, FALSE);
} else if (reply_code == KDC_ERR_PREAUTH_EXPIRED) {
@@ -1574,7 +1578,7 @@ init_creds_step_reply(krb5_context context,
goto cleanup;
/* Reset per-realm negotiation state. */
ctx->restarted = FALSE;
- ctx->enc_pa_rep_permitted = TRUE;
+ ctx->info_pa_permitted = TRUE;
code = restart_init_creds_loop(context, ctx, FALSE);
} else {
if (retry && ctx->selected_preauth_type != KRB5_PADATA_NONE) {
diff --git a/src/lib/krb5/krb/init_creds_ctx.h b/src/lib/krb5/krb/init_creds_ctx.h
index fe769685b..b19410a13 100644
--- a/src/lib/krb5/krb/init_creds_ctx.h
+++ b/src/lib/krb5/krb/init_creds_ctx.h
@@ -58,7 +58,7 @@ struct _krb5_init_creds_context {
krb5_data s2kparams;
krb5_keyblock as_key;
krb5_enctype etype;
- krb5_boolean enc_pa_rep_permitted;
+ krb5_boolean info_pa_permitted;
krb5_boolean restarted;
struct krb5_responder_context_st rctx;
krb5_preauthtype selected_preauth_type;
diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h
index f3de9ad7a..8489a3e23 100644
--- a/src/plugins/preauth/pkinit/pkinit.h
+++ b/src/plugins/preauth/pkinit/pkinit.h
@@ -148,6 +148,7 @@ typedef struct _pkinit_plg_opts {
int allow_upn; /* allow UPN-SAN instead of pkinit-SAN */
int dh_or_rsa; /* selects DH or RSA based pkinit */
int require_crl_checking; /* require CRL for a CA (default is false) */
+ int disable_freshness; /* disable freshness token on client for testing */
int dh_min_bits; /* minimum DH modulus size allowed */
} pkinit_plg_opts;
@@ -162,6 +163,7 @@ typedef struct _pkinit_req_opts {
int require_crl_checking;
int dh_size; /* initial request DH modulus size (default=1024) */
int require_hostname_match;
+ int disable_freshness;
} pkinit_req_opts;
/*
@@ -214,6 +216,7 @@ struct _pkinit_req_context {
int identity_initialized;
int identity_prompted;
krb5_error_code identity_prompt_retval;
+ krb5_data *freshness_token;
};
typedef struct _pkinit_req_context *pkinit_req_context;
diff --git a/src/plugins/preauth/pkinit/pkinit_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c
index f1bc6b21d..9483d69e5 100644
--- a/src/plugins/preauth/pkinit/pkinit_clnt.c
+++ b/src/plugins/preauth/pkinit/pkinit_clnt.c
@@ -231,6 +231,8 @@ pkinit_as_req_create(krb5_context context,
auth_pack.pkAuthenticator.cusec = cusec;
auth_pack.pkAuthenticator.nonce = nonce;
auth_pack.pkAuthenticator.paChecksum = *cksum;
+ if (!reqctx->opts->disable_freshness)
+ auth_pack.pkAuthenticator.freshnessToken = reqctx->freshness_token;
auth_pack.clientDHNonce.length = 0;
auth_pack.clientPublicValue = &info;
auth_pack.supportedKDFs = (krb5_data **)supported_kdf_alg_ids;
@@ -1162,6 +1164,7 @@ pkinit_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
pkinit_context plgctx = (pkinit_context)moddata;
pkinit_req_context reqctx = (pkinit_req_context)modreq;
krb5_keyblock as_key;
+ krb5_data d;
pkiDebug("pkinit_client_process %p %p %p %p\n",
context, plgctx, reqctx, request);
@@ -1174,6 +1177,12 @@ pkinit_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
case KRB5_PADATA_PKINIT_KX:
reqctx->rfc6112_kdc = 1;
return 0;
+ case KRB5_PADATA_AS_FRESHNESS:
+ TRACE_PKINIT_CLIENT_FRESHNESS_TOKEN(context);
+ krb5_free_data(context, reqctx->freshness_token);
+ reqctx->freshness_token = NULL;
+ d = make_data(in_padata->contents, in_padata->length);
+ return krb5_copy_data(context, &d, &reqctx->freshness_token);
case KRB5_PADATA_PK_AS_REQ:
reqctx->rfc4556_kdc = 1;
pkiDebug("processing KRB5_PADATA_PK_AS_REQ\n");
@@ -1359,7 +1368,7 @@ cleanup:
static int
pkinit_client_get_flags(krb5_context kcontext, krb5_preauthtype patype)
{
- if (patype == KRB5_PADATA_PKINIT_KX)
+ if (patype == KRB5_PADATA_PKINIT_KX || patype == KRB5_PADATA_AS_FRESHNESS)
return PA_INFO;
return PA_REAL;
}
@@ -1376,6 +1385,7 @@ static krb5_preauthtype supported_client_pa_types[] = {
KRB5_PADATA_PK_AS_REP_OLD,
KRB5_PADATA_PK_AS_REQ_OLD,
KRB5_PADATA_PKINIT_KX,
+ KRB5_PADATA_AS_FRESHNESS,
0
};
@@ -1400,6 +1410,7 @@ pkinit_client_req_init(krb5_context context,
reqctx->opts = NULL;
reqctx->idctx = NULL;
reqctx->idopts = NULL;
+ reqctx->freshness_token = NULL;
retval = pkinit_init_req_opts(&reqctx->opts);
if (retval)
@@ -1410,6 +1421,7 @@ pkinit_client_req_init(krb5_context context,
reqctx->opts->dh_or_rsa = plgctx->opts->dh_or_rsa;
reqctx->opts->allow_upn = plgctx->opts->allow_upn;
reqctx->opts->require_crl_checking = plgctx->opts->require_crl_checking;
+ reqctx->opts->disable_freshness = plgctx->opts->disable_freshness;
retval = pkinit_init_req_crypto(&reqctx->cryptoctx);
if (retval)
@@ -1468,6 +1480,8 @@ pkinit_client_req_fini(krb5_context context, krb5_clpreauth_moddata moddata,
if (reqctx->idopts != NULL)
pkinit_fini_identity_opts(reqctx->idopts);
+ krb5_free_data(context, reqctx->freshness_token);
+
free(reqctx);
return;
}
@@ -1580,6 +1594,9 @@ handle_gic_opt(krb5_context context,
pkiDebug("Setting flag to use RSA_PROTOCOL\n");
plgctx->opts->dh_or_rsa = RSA_PROTOCOL;
}
+ } else if (strcmp(attr, "disable_freshness") == 0) {
+ if (strcmp(value, "yes") == 0)
+ plgctx->opts->disable_freshness = 1;
}
return 0;
}
diff --git a/src/plugins/preauth/pkinit/pkinit_lib.c b/src/plugins/preauth/pkinit/pkinit_lib.c
index 2f88545da..d5858c424 100644
--- a/src/plugins/preauth/pkinit/pkinit_lib.c
+++ b/src/plugins/preauth/pkinit/pkinit_lib.c
@@ -82,6 +82,8 @@ pkinit_init_plg_opts(pkinit_plg_opts **plgopts)
opts->dh_or_rsa = DH_PROTOCOL;
opts->allow_upn = 0;
opts->require_crl_checking = 0;
+ opts->require_freshness = 0;
+ opts->disable_freshness = 0;
opts->dh_min_bits = PKINIT_DEFAULT_DH_MIN_BITS;
@@ -145,6 +147,7 @@ free_krb5_auth_pack(krb5_auth_pack **in)
free((*in)->clientPublicValue);
}
free((*in)->pkAuthenticator.paChecksum.contents);
+ krb5_free_data(NULL, (*in)->pkAuthenticator.freshnessToken);
if ((*in)->supportedCMSTypes != NULL)
free_krb5_algorithm_identifiers(&((*in)->supportedCMSTypes));
if ((*in)->supportedKDFs) {
diff --git a/src/plugins/preauth/pkinit/pkinit_trace.h b/src/plugins/preauth/pkinit/pkinit_trace.h
index d4eb39d88..67e0caeb4 100644
--- a/src/plugins/preauth/pkinit/pkinit_trace.h
+++ b/src/plugins/preauth/pkinit/pkinit_trace.h
@@ -41,6 +41,8 @@
TRACE(c, "PKINIT client found no acceptable EKU in KDC cert")
#define TRACE_PKINIT_CLIENT_EKU_SKIP(c) \
TRACE(c, "PKINIT client skipping EKU check due to configuration")
+#define TRACE_PKINIT_CLIENT_FRESHNESS_TOKEN(c) \
+ TRACE(c, "PKINIT client received freshness token from KDC")
#define TRACE_PKINIT_CLIENT_KDF_ALG(c, kdf, keyblock) \
TRACE(c, "PKINIT client used KDF {hexdata} to compute reply key " \
"{keyblock}", kdf, keyblock)
diff --git a/src/tests/asn.1/ktest.c b/src/tests/asn.1/ktest.c
index 43084cbbd..cf63f3f66 100644
--- a/src/tests/asn.1/ktest.c
+++ b/src/tests/asn.1/ktest.c
@@ -725,6 +725,8 @@ ktest_make_sample_pk_authenticator(krb5_pk_authenticator *p)
ktest_make_sample_checksum(&p->paChecksum);
/* We don't encode the checksum type, only the contents. */
p->paChecksum.checksum_type = 0;
+ p->freshnessToken = ealloc(sizeof(krb5_data));
+ ktest_make_sample_data(p->freshnessToken);
}
static void
@@ -1651,6 +1653,8 @@ ktest_empty_pk_authenticator(krb5_pk_authenticator *p)
{
ktest_empty_checksum(&p->paChecksum);
p->paChecksum.contents = NULL;
+ krb5_free_data(NULL, p->freshnessToken);
+ p->freshnessToken = NULL;
}
static void
diff --git a/src/tests/asn.1/pkinit_encode.out b/src/tests/asn.1/pkinit_encode.out
index 463128de0..3b0f7190a 100644
--- a/src/tests/asn.1/pkinit_encode.out
+++ b/src/tests/asn.1/pkinit_encode.out
@@ -4,7 +4,7 @@ encode_krb5_pa_pk_as_rep(dhInfo): A0 28 30 26 80 08 6B 72 62 35 64 61 74 61 A1 0
encode_krb5_pa_pk_as_rep(encKeyPack): 81 08 6B 72 62 35 64 61 74 61
encode_krb5_pa_pk_as_rep_draft9(dhSignedData): 80 08 6B 72 62 35 64 61 74 61
encode_krb5_pa_pk_as_rep_draft9(encKeyPack): 81 08 6B 72 62 35 64 61 74 61
-encode_krb5_auth_pack: 30 81 93 A0 29 30 27 A0 05 02 03 01 E2 40 A1 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A2 03 02 01 2A A3 06 04 04 31 32 33 34 A1 22 30 20 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 03 09 00 6B 72 62 35 64 61 74 61 A2 24 30 22 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 30 0B 06 09 2A 86 48 86 F7 12 01 02 02 A3 0A 04 08 6B 72 62 35 64 61 74 61 A4 10 30 0E 30 0C A0 0A 06 08 6B 72 62 35 64 61 74 61
+encode_krb5_auth_pack: 30 81 9F A0 35 30 33 A0 05 02 03 01 E2 40 A1 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A2 03 02 01 2A A3 06 04 04 31 32 33 34 A4 0A 04 08 6B 72 62 35 64 61 74 61 A1 22 30 20 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 03 09 00 6B 72 62 35 64 61 74 61 A2 24 30 22 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 30 0B 06 09 2A 86 48 86 F7 12 01 02 02 A3 0A 04 08 6B 72 62 35 64 61 74 61 A4 10 30 0E 30 0C A0 0A 06 08 6B 72 62 35 64 61 74 61
encode_krb5_auth_pack_draft9: 30 75 A0 4F 30 4D A0 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A1 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A2 05 02 03 01 E2 40 A3 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A4 03 02 01 2A A1 22 30 20 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 03 09 00 6B 72 62 35 64 61 74 61
encode_krb5_kdc_dh_key_info: 30 25 A0 0B 03 09 00 6B 72 62 35 64 61 74 61 A1 03 02 01 2A A2 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A
encode_krb5_reply_key_pack: 30 26 A0 13 30 11 A0 03 02 01 01 A1 0A 04 08 31 32 33 34 35 36 37 38 A1 0F 30 0D A0 03 02 01 01 A1 06 04 04 31 32 33 34
diff --git a/src/tests/asn.1/pkinit_trval.out b/src/tests/asn.1/pkinit_trval.out
index 58d870631..f9edbe154 100644
--- a/src/tests/asn.1/pkinit_trval.out
+++ b/src/tests/asn.1/pkinit_trval.out
@@ -57,6 +57,7 @@ encode_krb5_auth_pack:
. . [1] [Generalized Time] "19940610060317Z"
. . [2] [Integer] 42
. . [3] [Octet String] "1234"
+. . [4] [Octet String] "krb5data"
. [1] [Sequence/Sequence Of]
. . [Sequence/Sequence Of]
. . . [Object Identifier] <9>

View File

@ -0,0 +1,43 @@
From afe1c26d08f0aead0d4ac49ad06715b1e8be7b6d Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Wed, 3 Jan 2018 12:06:08 -0500
Subject: [PATCH] Fix securid_sam2 preauth for non-default salt
When looking up the client long-term key, look for any salt type, not
just the default salt type.
ticket: 8629
(cherry picked from commit a2339099ad13c84de0843fd04d0ba612fc194a1e)
---
src/plugins/preauth/securid_sam2/grail.c | 3 +--
src/plugins/preauth/securid_sam2/securid2.c | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/plugins/preauth/securid_sam2/grail.c b/src/plugins/preauth/securid_sam2/grail.c
index 18d48f924..48b61b0d1 100644
--- a/src/plugins/preauth/securid_sam2/grail.c
+++ b/src/plugins/preauth/securid_sam2/grail.c
@@ -213,8 +213,7 @@ verify_grail_data(krb5_context context, krb5_db_entry *client,
return KRB5KDC_ERR_PREAUTH_FAILED;
ret = krb5_dbe_find_enctype(context, client,
- sr2->sam_enc_nonce_or_sad.enctype,
- KRB5_KDB_SALTTYPE_NORMAL,
+ sr2->sam_enc_nonce_or_sad.enctype, -1,
sr2->sam_enc_nonce_or_sad.kvno,
&client_key_data);
if (ret)
diff --git a/src/plugins/preauth/securid_sam2/securid2.c b/src/plugins/preauth/securid_sam2/securid2.c
index ca99ce3ef..363e17a10 100644
--- a/src/plugins/preauth/securid_sam2/securid2.c
+++ b/src/plugins/preauth/securid_sam2/securid2.c
@@ -313,8 +313,7 @@ verify_securid_data_2(krb5_context context, krb5_db_entry *client,
}
retval = krb5_dbe_find_enctype(context, client,
- sr2->sam_enc_nonce_or_sad.enctype,
- KRB5_KDB_SALTTYPE_NORMAL,
+ sr2->sam_enc_nonce_or_sad.enctype, -1,
sr2->sam_enc_nonce_or_sad.kvno,
&client_key_data);
if (retval) {

View File

@ -0,0 +1,38 @@
From be4a469216fb87408484b90be9a1da772ba923df Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Wed, 3 Jan 2018 11:59:14 -0500
Subject: [PATCH] Include etype-info in for hardware preauth hints
If a principal has the requires_hwauth bit set, include PA-ETYPE-INFO
or PA-ETYPE-INFO2 padata in the PREAUTH_REQUIRED error, as preauth
mechs involving hardware tokens may also use the principal's Kerberos
password.
ticket: 8629
(cherry picked from commit ba92da05accc524b8037453b63ced1a6c65fd2a1)
---
src/kdc/kdc_preauth.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c
index 81d0b8cff..739c5e776 100644
--- a/src/kdc/kdc_preauth.c
+++ b/src/kdc/kdc_preauth.c
@@ -144,7 +144,7 @@ static preauth_system static_preauth_systems[] = {
{
"etype-info",
KRB5_PADATA_ETYPE_INFO,
- 0,
+ PA_HARDWARE,
NULL,
NULL,
NULL,
@@ -155,7 +155,7 @@ static preauth_system static_preauth_systems[] = {
{
"etype-info2",
KRB5_PADATA_ETYPE_INFO2,
- 0,
+ PA_HARDWARE,
NULL,
NULL,
NULL,

View File

@ -0,0 +1,393 @@
From 61e3f0142b09cb230be3a2a110f5224e773f1281 Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Thu, 21 Dec 2017 11:28:52 -0500
Subject: [PATCH] Refactor KDC krb5_pa_data utility functions
Move alloc_padata from fast_util.c to kdc_util.c and make it
non-static so it can be used by other files. Rename it to
alloc_pa_data for consistency with add_pa_data_element. Make it
correctly handle zero length using a null contents pointer.
Make add_pa_data_element claim both the container and contents memory
from the caller, now that callers can use alloc_pa_data to simplify
allocation and copying. Remove the copy parameter and the unused
context parameter, and put the list parameter first. Adjust all
callers accordingly, making small simplifications to memory handling
where applicable.
(cherry picked from commit 4af478c18b02e1d2444a328bb79e6976ef3d312b)
---
src/kdc/fast_util.c | 28 +-------
src/kdc/kdc_preauth.c | 14 ++--
src/kdc/kdc_util.c | 187 +++++++++++++++++++++++++-------------------------
src/kdc/kdc_util.h | 8 +--
4 files changed, 109 insertions(+), 128 deletions(-)
diff --git a/src/kdc/fast_util.c b/src/kdc/fast_util.c
index e05107ef3..6a3fc11b9 100644
--- a/src/kdc/fast_util.c
+++ b/src/kdc/fast_util.c
@@ -451,36 +451,12 @@ kdc_fast_hide_client(struct kdc_request_state *state)
return (state->fast_options & KRB5_FAST_OPTION_HIDE_CLIENT_NAMES) != 0;
}
-/* Allocate a pa-data entry with an uninitialized buffer of size len. */
-static krb5_error_code
-alloc_padata(krb5_preauthtype pa_type, size_t len, krb5_pa_data **out)
-{
- krb5_pa_data *pa;
- uint8_t *buf;
-
- *out = NULL;
- buf = malloc(len);
- if (buf == NULL)
- return ENOMEM;
- pa = malloc(sizeof(*pa));
- if (pa == NULL) {
- free(buf);
- return ENOMEM;
- }
- pa->magic = KV5M_PA_DATA;
- pa->pa_type = pa_type;
- pa->length = len;
- pa->contents = buf;
- *out = pa;
- return 0;
-}
-
/* Create a pa-data entry with the specified type and contents. */
static krb5_error_code
make_padata(krb5_preauthtype pa_type, const void *contents, size_t len,
krb5_pa_data **out)
{
- if (alloc_padata(pa_type, len, out) != 0)
+ if (alloc_pa_data(pa_type, len, out) != 0)
return ENOMEM;
memcpy((*out)->contents, contents, len);
return 0;
@@ -720,7 +696,7 @@ kdc_fast_make_cookie(krb5_context context, struct kdc_request_state *state,
goto cleanup;
/* Construct the cookie pa-data entry. */
- ret = alloc_padata(KRB5_PADATA_FX_COOKIE, 8 + enc.ciphertext.length, &pa);
+ ret = alloc_pa_data(KRB5_PADATA_FX_COOKIE, 8 + enc.ciphertext.length, &pa);
memcpy(pa->contents, "MIT1", 4);
store_32_be(kvno, pa->contents + 4);
memcpy(pa->contents + 8, enc.ciphertext.data, enc.ciphertext.length);
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c
index 739c5e776..edc30bd83 100644
--- a/src/kdc/kdc_preauth.c
+++ b/src/kdc/kdc_preauth.c
@@ -1617,18 +1617,20 @@ return_referral_enc_padata( krb5_context context,
{
krb5_error_code code;
krb5_tl_data tl_data;
- krb5_pa_data pa_data;
+ krb5_pa_data *pa;
tl_data.tl_data_type = KRB5_TL_SVR_REFERRAL_DATA;
code = krb5_dbe_lookup_tl_data(context, server, &tl_data);
if (code || tl_data.tl_data_length == 0)
return 0;
- pa_data.magic = KV5M_PA_DATA;
- pa_data.pa_type = KRB5_PADATA_SVR_REFERRAL_INFO;
- pa_data.length = tl_data.tl_data_length;
- pa_data.contents = tl_data.tl_data_contents;
- return add_pa_data_element(context, &pa_data, &reply->enc_padata, TRUE);
+ code = alloc_pa_data(KRB5_PADATA_SVR_REFERRAL_INFO, tl_data.tl_data_length,
+ &pa);
+ if (code)
+ return code;
+ memcpy(pa->contents, tl_data.tl_data_contents, tl_data.tl_data_length);
+ /* add_pa_data_element() claims pa on success or failure. */
+ return add_pa_data_element(&reply->enc_padata, pa);
}
krb5_error_code
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
index 754570c01..13111215d 100644
--- a/src/kdc/kdc_util.c
+++ b/src/kdc/kdc_util.c
@@ -1353,9 +1353,9 @@ kdc_make_s4u2self_rep(krb5_context context,
krb5_enc_kdc_rep_part *reply_encpart)
{
krb5_error_code code;
- krb5_data *data = NULL;
+ krb5_data *der_user_id = NULL, *der_s4u_x509_user = NULL;
krb5_pa_s4u_x509_user rep_s4u_user;
- krb5_pa_data padata;
+ krb5_pa_data *pa;
krb5_enctype enctype;
krb5_keyusage usage;
@@ -1366,7 +1366,7 @@ kdc_make_s4u2self_rep(krb5_context context,
rep_s4u_user.user_id.options =
req_s4u_user->user_id.options & KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE;
- code = encode_krb5_s4u_userid(&rep_s4u_user.user_id, &data);
+ code = encode_krb5_s4u_userid(&rep_s4u_user.user_id, &der_user_id);
if (code != 0)
goto cleanup;
@@ -1377,29 +1377,25 @@ kdc_make_s4u2self_rep(krb5_context context,
code = krb5_c_make_checksum(context, req_s4u_user->cksum.checksum_type,
tgs_subkey != NULL ? tgs_subkey : tgs_session,
- usage, data,
- &rep_s4u_user.cksum);
+ usage, der_user_id, &rep_s4u_user.cksum);
if (code != 0)
goto cleanup;
- krb5_free_data(context, data);
- data = NULL;
-
- code = encode_krb5_pa_s4u_x509_user(&rep_s4u_user, &data);
+ code = encode_krb5_pa_s4u_x509_user(&rep_s4u_user, &der_s4u_x509_user);
if (code != 0)
goto cleanup;
- padata.magic = KV5M_PA_DATA;
- padata.pa_type = KRB5_PADATA_S4U_X509_USER;
- padata.length = data->length;
- padata.contents = (krb5_octet *)data->data;
-
- code = add_pa_data_element(context, &padata, &reply->padata, FALSE);
+ /* Add a padata element, stealing memory from der_s4u_x509_user. */
+ code = alloc_pa_data(KRB5_PADATA_S4U_X509_USER, 0, &pa);
+ if (code != 0)
+ goto cleanup;
+ pa->length = der_s4u_x509_user->length;
+ pa->contents = (uint8_t *)der_s4u_x509_user->data;
+ der_s4u_x509_user->data = NULL;
+ /* add_pa_data_element() claims pa on success or failure. */
+ code = add_pa_data_element(&reply->padata, pa);
if (code != 0)
goto cleanup;
-
- free(data);
- data = NULL;
if (tgs_subkey != NULL)
enctype = tgs_subkey->enctype;
@@ -1413,33 +1409,27 @@ kdc_make_s4u2self_rep(krb5_context context,
*/
if ((req_s4u_user->user_id.options & KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE) &&
enctype_requires_etype_info_2(enctype) == FALSE) {
- padata.length = req_s4u_user->cksum.length +
- rep_s4u_user.cksum.length;
- padata.contents = malloc(padata.length);
- if (padata.contents == NULL) {
- code = ENOMEM;
+ code = alloc_pa_data(KRB5_PADATA_S4U_X509_USER,
+ req_s4u_user->cksum.length +
+ rep_s4u_user.cksum.length, &pa);
+ if (code != 0)
goto cleanup;
- }
+ memcpy(pa->contents,
+ req_s4u_user->cksum.contents, req_s4u_user->cksum.length);
+ memcpy(&pa->contents[req_s4u_user->cksum.length],
+ rep_s4u_user.cksum.contents, rep_s4u_user.cksum.length);
- memcpy(padata.contents,
- req_s4u_user->cksum.contents,
- req_s4u_user->cksum.length);
- memcpy(&padata.contents[req_s4u_user->cksum.length],
- rep_s4u_user.cksum.contents,
- rep_s4u_user.cksum.length);
-
- code = add_pa_data_element(context,&padata,
- &reply_encpart->enc_padata, FALSE);
- if (code != 0) {
- free(padata.contents);
+ /* add_pa_data_element() claims pa on success or failure. */
+ code = add_pa_data_element(&reply_encpart->enc_padata, pa);
+ if (code != 0)
goto cleanup;
- }
}
cleanup:
if (rep_s4u_user.cksum.contents != NULL)
krb5_free_checksum_contents(context, &rep_s4u_user.cksum);
- krb5_free_data(context, data);
+ krb5_free_data(context, der_user_id);
+ krb5_free_data(context, der_s4u_x509_user);
return code;
}
@@ -1707,46 +1697,50 @@ enctype_requires_etype_info_2(krb5_enctype enctype)
}
}
-/* XXX where are the generic helper routines for this? */
+/* Allocate a pa-data entry with an uninitialized buffer of size len. */
krb5_error_code
-add_pa_data_element(krb5_context context,
- krb5_pa_data *padata,
- krb5_pa_data ***inout_padata,
- krb5_boolean copy)
+alloc_pa_data(krb5_preauthtype pa_type, size_t len, krb5_pa_data **out)
{
- int i;
- krb5_pa_data **p;
+ krb5_pa_data *pa;
+ uint8_t *buf = NULL;
- if (*inout_padata != NULL) {
- for (i = 0; (*inout_padata)[i] != NULL; i++)
- ;
- } else
- i = 0;
-
- p = realloc(*inout_padata, (i + 2) * sizeof(krb5_pa_data *));
- if (p == NULL)
- return ENOMEM;
-
- *inout_padata = p;
-
- p[i] = (krb5_pa_data *)malloc(sizeof(krb5_pa_data));
- if (p[i] == NULL)
- return ENOMEM;
- *(p[i]) = *padata;
-
- p[i + 1] = NULL;
-
- if (copy) {
- p[i]->contents = (krb5_octet *)malloc(padata->length);
- if (p[i]->contents == NULL) {
- free(p[i]);
- p[i] = NULL;
+ *out = NULL;
+ if (len > 0) {
+ buf = malloc(len);
+ if (buf == NULL)
return ENOMEM;
- }
-
- memcpy(p[i]->contents, padata->contents, padata->length);
}
+ pa = malloc(sizeof(*pa));
+ if (pa == NULL) {
+ free(buf);
+ return ENOMEM;
+ }
+ pa->magic = KV5M_PA_DATA;
+ pa->pa_type = pa_type;
+ pa->length = len;
+ pa->contents = buf;
+ *out = pa;
+ return 0;
+}
+/* Add pa to list, claiming its memory. Free pa on failure. */
+krb5_error_code
+add_pa_data_element(krb5_pa_data ***list, krb5_pa_data *pa)
+{
+ size_t count;
+ krb5_pa_data **newlist;
+
+ for (count = 0; *list != NULL && (*list)[count] != NULL; count++);
+
+ newlist = realloc(*list, (count + 2) * sizeof(*newlist));
+ if (newlist == NULL) {
+ free(pa->contents);
+ free(pa);
+ return ENOMEM;
+ }
+ newlist[count] = pa;
+ newlist[count + 1] = NULL;
+ *list = newlist;
return 0;
}
@@ -1850,38 +1844,47 @@ kdc_handle_protected_negotiation(krb5_context context,
{
krb5_error_code retval = 0;
krb5_checksum checksum;
- krb5_data *out = NULL;
- krb5_pa_data pa, *pa_in;
+ krb5_data *der_cksum = NULL;
+ krb5_pa_data *pa, *pa_in;
+
+ memset(&checksum, 0, sizeof(checksum));
+
pa_in = krb5int_find_pa_data(context, request->padata,
KRB5_ENCPADATA_REQ_ENC_PA_REP);
if (pa_in == NULL)
return 0;
- pa.magic = KV5M_PA_DATA;
- pa.pa_type = KRB5_ENCPADATA_REQ_ENC_PA_REP;
- memset(&checksum, 0, sizeof(checksum));
- retval = krb5_c_make_checksum(context,0, reply_key,
- KRB5_KEYUSAGE_AS_REQ, req_pkt, &checksum);
+
+ /* Compute and encode a checksum over the AS-REQ. */
+ retval = krb5_c_make_checksum(context, 0, reply_key, KRB5_KEYUSAGE_AS_REQ,
+ req_pkt, &checksum);
if (retval != 0)
goto cleanup;
- retval = encode_krb5_checksum(&checksum, &out);
+ retval = encode_krb5_checksum(&checksum, &der_cksum);
if (retval != 0)
goto cleanup;
- pa.contents = (krb5_octet *) out->data;
- pa.length = out->length;
- retval = add_pa_data_element(context, &pa, out_enc_padata, FALSE);
+
+ /* Add a pa-data element to the list, stealing memory from der_cksum. */
+ retval = alloc_pa_data(KRB5_ENCPADATA_REQ_ENC_PA_REP, 0, &pa);
if (retval)
goto cleanup;
- out->data = NULL;
- pa.magic = KV5M_PA_DATA;
- pa.pa_type = KRB5_PADATA_FX_FAST;
- pa.length = 0;
- pa.contents = NULL;
- retval = add_pa_data_element(context, &pa, out_enc_padata, FALSE);
+ pa->length = der_cksum->length;
+ pa->contents = (uint8_t *)der_cksum->data;
+ der_cksum->data = NULL;
+ /* add_pa_data_element() claims pa on success or failure. */
+ retval = add_pa_data_element(out_enc_padata, pa);
+ if (retval)
+ goto cleanup;
+
+ /* Add a zero-length PA-FX-FAST element to the list. */
+ retval = alloc_pa_data(KRB5_PADATA_FX_FAST, 0, &pa);
+ if (retval)
+ goto cleanup;
+ /* add_pa_data_element() claims pa on success or failure. */
+ retval = add_pa_data_element(out_enc_padata, pa);
+
cleanup:
- if (checksum.contents)
- krb5_free_checksum_contents(context, &checksum);
- if (out != NULL)
- krb5_free_data(context, out);
+ krb5_free_checksum_contents(context, &checksum);
+ krb5_free_data(context, der_cksum);
return retval;
}
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index f99efcf50..18649b8ad 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -203,10 +203,10 @@ void
free_padata_context(krb5_context context, void *padata_context);
krb5_error_code
-add_pa_data_element (krb5_context context,
- krb5_pa_data *padata,
- krb5_pa_data ***out_padata,
- krb5_boolean copy);
+alloc_pa_data(krb5_preauthtype pa_type, size_t len, krb5_pa_data **out);
+
+krb5_error_code
+add_pa_data_element(krb5_pa_data ***list, krb5_pa_data *pa);
/* kdc_preauth_ec.c */
krb5_error_code

View File

@ -0,0 +1,738 @@
From 0afb9c336dd8573faa025915fcb97e643cc3e748 Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Sun, 11 Feb 2018 15:23:35 -0500
Subject: [PATCH] Simplify kdc_preauth.c systems table
Get rid of static_preauth_systems, and replace it with explicit calls
to helper functions in get_preauth_hint_list() and return_padata().
Stop preallocating pa-data lists, instead reallocating on each
addition using add_pa_data_element(). Also simplify
maybe_add_etype_info2() using add_pa_data_element().
The KRB5_PADATA_PAC_REQUEST table entry did nothing, and was probably
originally added back when the KDC would error out on unrecognized
padata types. The KRB5_PADATA_SERVER_REFERRAL entry has been disabled
since it was first added.
(cherry picked from commit fea1a488924faa3938ef723feaa1ff12d22a91ff)
---
src/kdc/kdc_preauth.c | 526 ++++++++++++++++++--------------------------------
1 file changed, 184 insertions(+), 342 deletions(-)
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c
index edc30bd83..6f34dc289 100644
--- a/src/kdc/kdc_preauth.c
+++ b/src/kdc/kdc_preauth.c
@@ -101,108 +101,14 @@ typedef struct preauth_system_st {
krb5_kdcpreauth_loop_fn loop;
} preauth_system;
+static preauth_system *preauth_systems;
+static size_t n_preauth_systems;
+
static krb5_error_code
make_etype_info(krb5_context context, krb5_preauthtype pa_type,
krb5_principal client, krb5_key_data *client_key,
krb5_enctype enctype, krb5_pa_data **pa_out);
-static void
-get_etype_info(krb5_context context, krb5_kdc_req *request,
- krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
- krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
- krb5_kdcpreauth_edata_respond_fn respond, void *arg);
-
-static krb5_error_code
-return_etype_info(krb5_context, krb5_pa_data *padata,
- krb5_data *req_pkt, krb5_kdc_req *request,
- krb5_kdc_rep *reply, krb5_keyblock *encrypting_key,
- krb5_pa_data **send_pa, krb5_kdcpreauth_callbacks cb,
- krb5_kdcpreauth_rock rock, krb5_kdcpreauth_moddata moddata,
- krb5_kdcpreauth_modreq modreq);
-
-static krb5_error_code
-return_pw_salt(krb5_context, krb5_pa_data *padata,
- krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply,
- krb5_keyblock *encrypting_key, krb5_pa_data **send_pa,
- krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
- krb5_kdcpreauth_moddata moddata, krb5_kdcpreauth_modreq modreq);
-
-
-
-static preauth_system static_preauth_systems[] = {
- {
- "FAST",
- KRB5_PADATA_FX_FAST,
- PA_HARDWARE,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- 0
- },
- {
- "etype-info",
- KRB5_PADATA_ETYPE_INFO,
- PA_HARDWARE,
- NULL,
- NULL,
- NULL,
- get_etype_info,
- 0,
- return_etype_info
- },
- {
- "etype-info2",
- KRB5_PADATA_ETYPE_INFO2,
- PA_HARDWARE,
- NULL,
- NULL,
- NULL,
- get_etype_info,
- 0,
- return_etype_info
- },
- {
- "pw-salt",
- KRB5_PADATA_PW_SALT,
- PA_PSEUDO, /* Don't include this in the error list */
- NULL,
- NULL,
- NULL,
- 0,
- 0,
- return_pw_salt
- },
- {
- "pac-request",
- KRB5_PADATA_PAC_REQUEST,
- PA_PSEUDO,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL
- },
-#if 0
- {
- "server-referral",
- KRB5_PADATA_SERVER_REFERRAL,
- PA_PSEUDO,
- 0,
- 0,
- return_server_referral
- },
-#endif
-};
-
-#define NUM_STATIC_PREAUTH_SYSTEMS (sizeof(static_preauth_systems) / \
- sizeof(*static_preauth_systems))
-
-static preauth_system *preauth_systems;
-static size_t n_preauth_systems;
-
/* Get all available kdcpreauth vtables and a count of preauth types they
* support. Return an empty list on failure. */
static void
@@ -284,7 +190,6 @@ load_preauth_plugins(struct server_handle *handle, krb5_context context,
get_plugin_vtables(context, &vtables, &n_tables, &n_systems);
/* Allocate the list of static and plugin preauth systems. */
- n_systems += NUM_STATIC_PREAUTH_SYSTEMS;
preauth_systems = calloc(n_systems + 1, sizeof(preauth_system));
if (preauth_systems == NULL)
goto cleanup;
@@ -292,13 +197,8 @@ load_preauth_plugins(struct server_handle *handle, krb5_context context,
if (get_realm_names(handle, &realm_names))
goto cleanup;
- /* Add the static system to the list first. No static systems require
- * initialization, so just make a direct copy. */
- memcpy(preauth_systems, static_preauth_systems,
- sizeof(static_preauth_systems));
-
/* Add the dynamically-loaded mechanisms to the list. */
- n_systems = NUM_STATIC_PREAUTH_SYSTEMS;
+ n_systems = 0;
for (i = 0; i < n_tables; i++) {
/* Try to initialize this module. */
vt = &vtables[i];
@@ -622,7 +522,9 @@ find_pa_system(int type, preauth_system **preauth)
{
preauth_system *ap;
- ap = preauth_systems ? preauth_systems : static_preauth_systems;
+ if (preauth_systems == NULL)
+ return KRB5_PREAUTH_BAD_TYPE;
+ ap = preauth_systems;
while ((ap->type != -1) && (ap->type != type))
ap++;
if (ap->type == -1)
@@ -776,6 +678,98 @@ const char *missing_required_preauth(krb5_db_entry *client,
return 0;
}
+/* Return true if request's enctypes indicate support for etype-info2. */
+static krb5_boolean
+requires_info2(const krb5_kdc_req *request)
+{
+ int i;
+
+ for (i = 0; i < request->nktypes; i++) {
+ if (enctype_requires_etype_info_2(request->ktype[i]))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Add PA-ETYPE-INFO2 and possibly PA-ETYPE-INFO entries to pa_list as
+ * appropriate for the request and client principal. */
+static krb5_error_code
+add_etype_info(krb5_context context, krb5_kdcpreauth_rock rock,
+ krb5_pa_data ***pa_list)
+{
+ krb5_error_code ret;
+ krb5_pa_data *pa;
+
+ if (rock->client_key == NULL)
+ return 0;
+
+ if (!requires_info2(rock->request)) {
+ /* Include PA-ETYPE-INFO only for old clients. */
+ ret = make_etype_info(context, KRB5_PADATA_ETYPE_INFO,
+ rock->client->princ, rock->client_key,
+ rock->client_keyblock->enctype, &pa);
+ if (ret)
+ return ret;
+ /* add_pa_data_element() claims pa on success or failure. */
+ ret = add_pa_data_element(pa_list, pa);
+ if (ret)
+ return ret;
+ }
+
+ /* Always include PA-ETYPE-INFO2. */
+ ret = make_etype_info(context, KRB5_PADATA_ETYPE_INFO2,
+ rock->client->princ, rock->client_key,
+ rock->client_keyblock->enctype, &pa);
+ if (ret)
+ return ret;
+ /* add_pa_data_element() claims pa on success or failure. */
+ return add_pa_data_element(pa_list, pa);
+}
+
+/* Add PW-SALT or AFS3-SALT entries to pa_list as appropriate for the request
+ * and client principal. */
+static krb5_error_code
+add_pw_salt(krb5_context context, krb5_kdcpreauth_rock rock,
+ krb5_pa_data ***pa_list)
+{
+ krb5_error_code ret;
+ krb5_pa_data *pa;
+ krb5_data *salt = NULL;
+ krb5_int16 salttype;
+
+ /* Only include this pa-data for old clients. */
+ if (rock->client_key == NULL || requires_info2(rock->request))
+ return 0;
+
+ ret = krb5_dbe_compute_salt(context, rock->client_key,
+ rock->request->client, &salttype, &salt);
+ if (ret)
+ return 0;
+
+ if (salttype == KRB5_KDB_SALTTYPE_AFS3) {
+ ret = alloc_pa_data(KRB5_PADATA_AFS3_SALT, salt->length + 1, &pa);
+ if (ret)
+ goto cleanup;
+ memcpy(pa->contents, salt->data, salt->length);
+ pa->contents[salt->length] = '\0';
+ } else {
+ /* Steal memory from salt to make the pa-data entry. */
+ ret = alloc_pa_data(KRB5_PADATA_PW_SALT, 0, &pa);
+ if (ret)
+ goto cleanup;
+ pa->length = salt->length;
+ pa->contents = (uint8_t *)salt->data;
+ salt->data = NULL;
+ }
+
+ /* add_pa_data_element() claims pa on success or failure. */
+ ret = add_pa_data_element(pa_list, pa);
+
+cleanup:
+ krb5_free_data(context, salt);
+ return ret;
+}
+
struct hint_state {
kdc_hint_respond_fn respond;
void *arg;
@@ -787,7 +781,7 @@ struct hint_state {
int hw_only;
preauth_system *ap;
- krb5_pa_data **pa_data, **pa_cur;
+ krb5_pa_data **pa_data;
krb5_preauthtype pa_type;
};
@@ -799,7 +793,7 @@ hint_list_finish(struct hint_state *state, krb5_error_code code)
kdc_realm_t *kdc_active_realm = state->realm;
if (!code) {
- if (state->pa_data[0] == 0) {
+ if (state->pa_data == NULL) {
krb5_klog_syslog(LOG_INFO,
_("%spreauth required but hint list is empty"),
state->hw_only ? "hw" : "");
@@ -820,20 +814,27 @@ hint_list_next(struct hint_state *arg);
static void
finish_get_edata(void *arg, krb5_error_code code, krb5_pa_data *pa)
{
+ krb5_error_code ret;
struct hint_state *state = arg;
if (code == 0) {
if (pa == NULL) {
- /* Include an empty value of the current type. */
- pa = calloc(1, sizeof(*pa));
- pa->magic = KV5M_PA_DATA;
- pa->pa_type = state->pa_type;
+ ret = alloc_pa_data(state->pa_type, 0, &pa);
+ if (ret)
+ goto error;
}
- *state->pa_cur++ = pa;
+ /* add_pa_data_element() claims pa on success or failure. */
+ ret = add_pa_data_element(&state->pa_data, pa);
+ if (ret)
+ goto error;
}
state->ap++;
hint_list_next(state);
+ return;
+
+error:
+ hint_list_finish(state, ret);
}
static void
@@ -870,16 +871,16 @@ get_preauth_hint_list(krb5_kdc_req *request, krb5_kdcpreauth_rock rock,
krb5_pa_data ***e_data_out, kdc_hint_respond_fn respond,
void *arg)
{
+ kdc_realm_t *kdc_active_realm = rock->rstate->realm_data;
struct hint_state *state;
+ krb5_pa_data *pa;
*e_data_out = NULL;
/* Allocate our state. */
state = calloc(1, sizeof(*state));
- if (state == NULL) {
- (*respond)(arg);
- return;
- }
+ if (state == NULL)
+ goto error;
state->hw_only = isflagset(rock->client->attributes,
KRB5_KDB_REQUIRES_HW_AUTH);
state->respond = respond;
@@ -888,17 +889,27 @@ get_preauth_hint_list(krb5_kdc_req *request, krb5_kdcpreauth_rock rock,
state->rock = rock;
state->realm = rock->rstate->realm_data;
state->e_data_out = e_data_out;
-
- state->pa_data = calloc(n_preauth_systems + 1, sizeof(krb5_pa_data *));
- if (!state->pa_data) {
- free(state);
- (*respond)(arg);
- return;
- }
-
- state->pa_cur = state->pa_data;
+ state->pa_data = NULL;
state->ap = preauth_systems;
+
+ /* Add an empty PA-FX-FAST element to advertise FAST support. */
+ if (alloc_pa_data(KRB5_PADATA_FX_FAST, 0, &pa) != 0)
+ goto error;
+ /* add_pa_data_element() claims pa on success or failure. */
+ if (add_pa_data_element(&state->pa_data, pa) != 0)
+ goto error;
+
+ if (add_etype_info(kdc_context, rock, &state->pa_data) != 0)
+ goto error;
+
hint_list_next(state);
+ return;
+
+error:
+ if (state != NULL)
+ krb5_free_pa_data(kdc_context, state->pa_data);
+ free(state);
+ (*respond)(arg);
}
/*
@@ -1029,10 +1040,10 @@ filter_preauth_error(krb5_error_code code)
static krb5_error_code
maybe_add_etype_info2(struct padata_state *state, krb5_error_code code)
{
+ krb5_error_code ret;
krb5_context context = state->context;
krb5_kdcpreauth_rock rock = state->rock;
- krb5_pa_data **list = state->pa_e_data;
- size_t count;
+ krb5_pa_data *pa;
/* Only add key information when requesting another preauth round trip. */
if (code != KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED)
@@ -1048,18 +1059,14 @@ maybe_add_etype_info2(struct padata_state *state, krb5_error_code code)
KRB5_PADATA_FX_COOKIE) != NULL)
return 0;
- /* Reallocate state->pa_e_data to make room for the etype-info2 element. */
- for (count = 0; list != NULL && list[count] != NULL; count++);
- list = realloc(list, (count + 2) * sizeof(*list));
- if (list == NULL)
- return ENOMEM;
- list[count] = list[count + 1] = NULL;
- state->pa_e_data = list;
+ ret = make_etype_info(context, KRB5_PADATA_ETYPE_INFO2,
+ rock->client->princ, rock->client_key,
+ rock->client_keyblock->enctype, &pa);
+ if (ret)
+ return ret;
- /* Generate an etype-info2 element in the new slot. */
- return make_etype_info(context, KRB5_PADATA_ETYPE_INFO2,
- rock->client->princ, rock->client_key,
- rock->client_keyblock->enctype, &list[count]);
+ /* add_pa_data_element() claims pa on success or failure. */
+ return add_pa_data_element(&state->pa_e_data, pa);
}
/* Release state and respond to the AS-REQ processing code with the result of
@@ -1279,17 +1286,20 @@ return_padata(krb5_context context, krb5_kdcpreauth_rock rock,
{
krb5_error_code retval;
krb5_pa_data ** padata;
- krb5_pa_data ** send_pa_list;
- krb5_pa_data ** send_pa;
+ krb5_pa_data ** send_pa_list = NULL;
+ krb5_pa_data * send_pa;
krb5_pa_data * pa = 0;
krb5_pa_data null_item;
preauth_system * ap;
- int * pa_order;
+ int * pa_order = NULL;
int * pa_type;
int size = 0;
krb5_kdcpreauth_modreq *modreq_ptr;
krb5_boolean key_modified;
krb5_keyblock original_key;
+
+ memset(&original_key, 0, sizeof(original_key));
+
if ((!*padata_context) &&
(make_padata_context(context, padata_context) != 0)) {
return KRB5KRB_ERR_GENERIC;
@@ -1300,26 +1310,18 @@ return_padata(krb5_context context, krb5_kdcpreauth_rock rock,
size++;
}
- if ((send_pa_list = malloc((size+1) * sizeof(krb5_pa_data *))) == NULL)
- return ENOMEM;
- if ((pa_order = malloc((size+1) * sizeof(int))) == NULL) {
- free(send_pa_list);
- return ENOMEM;
- }
+ pa_order = k5calloc(size + 1, sizeof(int), &retval);
+ if (pa_order == NULL)
+ goto cleanup;
sort_pa_order(context, request, pa_order);
retval = krb5_copy_keyblock_contents(context, encrypting_key,
&original_key);
- if (retval) {
- free(send_pa_list);
- free(pa_order);
- return retval;
- }
+ if (retval)
+ goto cleanup;
key_modified = FALSE;
null_item.contents = NULL;
null_item.length = 0;
- send_pa = send_pa_list;
- *send_pa = 0;
for (pa_type = pa_order; *pa_type != -1; pa_type++) {
ap = &preauth_systems[*pa_type];
@@ -1349,20 +1351,30 @@ return_padata(krb5_context context, krb5_kdcpreauth_rock rock,
}
}
}
+ send_pa = NULL;
retval = ap->return_padata(context, pa, req_pkt, request, reply,
- encrypting_key, send_pa, &callbacks, rock,
+ encrypting_key, &send_pa, &callbacks, rock,
ap->moddata, *modreq_ptr);
if (retval)
goto cleanup;
- if (*send_pa)
- send_pa++;
- *send_pa = 0;
+ if (send_pa != NULL) {
+ /* add_pa_data_element() claims send_pa on success or failure. */
+ retval = add_pa_data_element(&send_pa_list, send_pa);
+ if (retval)
+ goto cleanup;
+ }
}
- retval = 0;
+ /* Add etype-info and pw-salt pa-data as needed. */
+ retval = add_etype_info(context, rock, &send_pa_list);
+ if (retval)
+ goto cleanup;
+ retval = add_pw_salt(context, rock, &send_pa_list);
+ if (retval)
+ goto cleanup;
- if (send_pa_list[0]) {
+ if (send_pa_list != NULL) {
reply->padata = send_pa_list;
send_pa_list = 0;
}
@@ -1370,8 +1382,7 @@ return_padata(krb5_context context, krb5_kdcpreauth_rock rock,
cleanup:
krb5_free_keyblock_contents(context, &original_key);
free(pa_order);
- if (send_pa_list)
- krb5_free_pa_data(context, send_pa_list);
+ krb5_free_pa_data(context, send_pa_list);
return (retval);
}
@@ -1438,9 +1449,8 @@ make_etype_info(krb5_context context, krb5_preauthtype pa_type,
krb5_enctype enctype, krb5_pa_data **pa_out)
{
krb5_error_code retval;
- krb5_pa_data *pa = NULL;
krb5_etype_info_entry **entry = NULL;
- krb5_data *scratch = NULL;
+ krb5_data *der_etype_info = NULL;
int etype_info2 = (pa_type == KRB5_PADATA_ETYPE_INFO2);
*pa_out = NULL;
@@ -1454,125 +1464,23 @@ make_etype_info(krb5_context context, krb5_preauthtype pa_type,
goto cleanup;
if (etype_info2)
- retval = encode_krb5_etype_info2(entry, &scratch);
+ retval = encode_krb5_etype_info2(entry, &der_etype_info);
else
- retval = encode_krb5_etype_info(entry, &scratch);
+ retval = encode_krb5_etype_info(entry, &der_etype_info);
if (retval)
goto cleanup;
- pa = k5alloc(sizeof(*pa), &retval);
- if (pa == NULL)
+
+ /* Steal the data from der_etype_info to create a pa-data element. */
+ retval = alloc_pa_data(pa_type, 0, pa_out);
+ if (retval)
goto cleanup;
- pa->magic = KV5M_PA_DATA;
- pa->pa_type = pa_type;
- pa->contents = (unsigned char *)scratch->data;
- pa->length = scratch->length;
- scratch->data = NULL;
- *pa_out = pa;
+ (*pa_out)->contents = (uint8_t *)der_etype_info->data;
+ (*pa_out)->length = der_etype_info->length;
+ der_etype_info->data = NULL;
cleanup:
krb5_free_etype_info(context, entry);
- krb5_free_data(context, scratch);
- return retval;
-}
-
-/* Return true if request's enctypes indicate support for etype-info2. */
-static krb5_boolean
-requires_info2(const krb5_kdc_req *request)
-{
- int i;
-
- for (i = 0; i < request->nktypes; i++) {
- if (enctype_requires_etype_info_2(request->ktype[i]))
- return TRUE;
- }
- return FALSE;
-}
-
-/* Generate hint list padata for PA-ETYPE-INFO or PA-ETYPE-INFO2. */
-static void
-get_etype_info(krb5_context context, krb5_kdc_req *request,
- krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
- krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
- krb5_kdcpreauth_edata_respond_fn respond, void *arg)
-{
- krb5_error_code ret;
- krb5_pa_data *pa = NULL;
-
- if (rock->client_key == NULL) {
- ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
- } else if (pa_type == KRB5_PADATA_ETYPE_INFO && requires_info2(request)) {
- ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
- } else {
- ret = make_etype_info(context, pa_type, rock->client->princ,
- rock->client_key, rock->client_keyblock->enctype,
- &pa);
- }
- (*respond)(arg, ret, pa);
-}
-
-/* Generate AS-REP padata for PA-ETYPE-INFO or PA-ETYPE-INFO2. */
-static krb5_error_code
-return_etype_info(krb5_context context, krb5_pa_data *padata,
- krb5_data *req_pkt, krb5_kdc_req *request,
- krb5_kdc_rep *reply, krb5_keyblock *encrypting_key,
- krb5_pa_data **send_pa, krb5_kdcpreauth_callbacks cb,
- krb5_kdcpreauth_rock rock, krb5_kdcpreauth_moddata moddata,
- krb5_kdcpreauth_modreq modreq)
-{
- *send_pa = NULL;
- if (rock->client_key == NULL)
- return 0;
- if (padata->pa_type == KRB5_PADATA_ETYPE_INFO && requires_info2(request))
- return 0;
- return make_etype_info(context, padata->pa_type, rock->client->princ,
- rock->client_key, encrypting_key->enctype, send_pa);
-}
-
-static krb5_error_code
-return_pw_salt(krb5_context context, krb5_pa_data *in_padata,
- krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply,
- krb5_keyblock *encrypting_key, krb5_pa_data **send_pa,
- krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
- krb5_kdcpreauth_moddata moddata, krb5_kdcpreauth_modreq modreq)
-{
- krb5_error_code retval;
- krb5_pa_data * padata;
- krb5_data * salt = NULL;
- krb5_int16 salttype;
- krb5_key_data * client_key = rock->client_key;
-
- if (client_key == NULL || requires_info2(request))
- return 0;
-
- retval = krb5_dbe_compute_salt(context, client_key, request->client,
- &salttype, &salt);
- if (retval)
- return 0;
-
- padata = k5alloc(sizeof(*padata), &retval);
- if (padata == NULL)
- goto cleanup;
- padata->magic = KV5M_PA_DATA;
-
- if (salttype == KRB5_KDB_SALTTYPE_AFS3) {
- padata->contents = k5memdup0(salt->data, salt->length, &retval);
- if (padata->contents == NULL)
- goto cleanup;
- padata->pa_type = KRB5_PADATA_AFS3_SALT;
- padata->length = salt->length + 1;
- } else {
- padata->pa_type = KRB5_PADATA_PW_SALT;
- padata->length = salt->length;
- padata->contents = (krb5_octet *)salt->data;
- salt->data = NULL;
- }
-
- *send_pa = padata;
- padata = NULL;
-
-cleanup:
- free(padata);
- krb5_free_data(context, salt);
+ krb5_free_data(context, der_etype_info);
return retval;
}
@@ -1656,69 +1564,3 @@ return_enc_padata(krb5_context context, krb5_data *req_pkt,
cleanup:
return code;
}
-
-
-#if 0
-static krb5_error_code return_server_referral(krb5_context context,
- krb5_pa_data * padata,
- krb5_db_entry *client,
- krb5_db_entry *server,
- krb5_kdc_req *request,
- krb5_kdc_rep *reply,
- krb5_key_data *client_key,
- krb5_keyblock *encrypting_key,
- krb5_pa_data **send_pa)
-{
- krb5_error_code code;
- krb5_tl_data tl_data;
- krb5_pa_data *pa_data;
- krb5_enc_data enc_data;
- krb5_data plain;
- krb5_data *enc_pa_data;
-
- *send_pa = NULL;
-
- tl_data.tl_data_type = KRB5_TL_SERVER_REFERRAL;
-
- code = krb5_dbe_lookup_tl_data(context, server, &tl_data);
- if (code || tl_data.tl_data_length == 0)
- return 0; /* no server referrals to return */
-
- plain.length = tl_data.tl_data_length;
- plain.data = tl_data.tl_data_contents;
-
- /* Encrypt ServerReferralData */
- code = krb5_encrypt_helper(context, encrypting_key,
- KRB5_KEYUSAGE_PA_SERVER_REFERRAL_DATA,
- &plain, &enc_data);
- if (code)
- return code;
-
- /* Encode ServerReferralData into PA-SERVER-REFERRAL-DATA */
- code = encode_krb5_enc_data(&enc_data, &enc_pa_data);
- if (code) {
- krb5_free_data_contents(context, &enc_data.ciphertext);
- return code;
- }
-
- krb5_free_data_contents(context, &enc_data.ciphertext);
-
- /* Return PA-SERVER-REFERRAL-DATA */
- pa_data = (krb5_pa_data *)malloc(sizeof(*pa_data));
- if (pa_data == NULL) {
- krb5_free_data(context, enc_pa_data);
- return ENOMEM;
- }
-
- pa_data->magic = KV5M_PA_DATA;
- pa_data->pa_type = KRB5_PADATA_SVR_REFERRAL_INFO;
- pa_data->length = enc_pa_data->length;
- pa_data->contents = enc_pa_data->data;
-
- free(enc_pa_data); /* don't free contents */
-
- *send_pa = pa_data;
-
- return 0;
-}
-#endif

View File

@ -18,7 +18,7 @@ Summary: The Kerberos network authentication system
Name: krb5 Name: krb5
Version: 1.16 Version: 1.16
# for prerelease, should be e.g., 0.% {prerelease}.1% { ?dist } (without spaces) # for prerelease, should be e.g., 0.% {prerelease}.1% { ?dist } (without spaces)
Release: 10%{?dist} Release: 11%{?dist}
# lookaside-cached sources; two downloads and a build artifact # lookaside-cached sources; two downloads and a build artifact
Source0: https://web.mit.edu/kerberos/dist/krb5/1.16/krb5-%{version}%{prerelease}.tar.gz Source0: https://web.mit.edu/kerberos/dist/krb5/1.16/krb5-%{version}%{prerelease}.tar.gz
@ -65,6 +65,12 @@ Patch38: Fix-flaws-in-LDAP-DN-checking.patch
Patch39: Fix-capaths-.-values-on-client.patch Patch39: Fix-capaths-.-values-on-client.patch
Patch40: Fix-hex-conversion-of-PKINIT-certid-strings.patch Patch40: Fix-hex-conversion-of-PKINIT-certid-strings.patch
Patch41: Exit-with-status-0-from-kadmind.patch Patch41: Exit-with-status-0-from-kadmind.patch
Patch42: Include-etype-info-in-for-hardware-preauth-hints.patch
Patch43: Fix-securid_sam2-preauth-for-non-default-salt.patch
Patch44: Refactor-KDC-krb5_pa_data-utility-functions.patch
Patch45: Simplify-kdc_preauth.c-systems-table.patch
Patch46: Add-PKINIT-client-support-for-freshness-token.patch
Patch47: Add-PKINIT-KDC-support-for-freshness-token.patch
License: MIT License: MIT
URL: http://web.mit.edu/kerberos/www/ URL: http://web.mit.edu/kerberos/www/
@ -714,6 +720,9 @@ exit 0
%{_libdir}/libkadm5srv_mit.so.* %{_libdir}/libkadm5srv_mit.so.*
%changelog %changelog
* Mon Mar 19 2018 Robbie Harwood <rharwood@redhat.com> - 1.16-11
- Add PKINIT KDC support for freshness token
* Wed Mar 14 2018 Robbie Harwood <rharwood@redhat.com> - 1.16-10 * Wed Mar 14 2018 Robbie Harwood <rharwood@redhat.com> - 1.16-10
- Exit with status 0 from kadmind - Exit with status 0 from kadmind