diff --git a/Add-PKINIT-KDC-support-for-freshness-token.patch b/Add-PKINIT-KDC-support-for-freshness-token.patch new file mode 100644 index 0000000..a2630db --- /dev/null +++ b/Add-PKINIT-KDC-support-for-freshness-token.patch @@ -0,0 +1,631 @@ +From 4ddfba7c9c12056f9f5819648f20f68e5625dced Mon Sep 17 00:00:00 2001 +From: Greg Hudson +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 ++ PKINIT: no freshness token received from ++ ++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 + #include + ++/* 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) diff --git a/Add-PKINIT-client-support-for-freshness-token.patch b/Add-PKINIT-client-support-for-freshness-token.patch new file mode 100644 index 0000000..478229b --- /dev/null +++ b/Add-PKINIT-client-support-for-freshness-token.patch @@ -0,0 +1,336 @@ +From 7eb2df66aef8e2b58ec7dfa13e9ee19f5e3b5b34 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +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> diff --git a/Fix-securid_sam2-preauth-for-non-default-salt.patch b/Fix-securid_sam2-preauth-for-non-default-salt.patch new file mode 100644 index 0000000..4d6365e --- /dev/null +++ b/Fix-securid_sam2-preauth-for-non-default-salt.patch @@ -0,0 +1,43 @@ +From afe1c26d08f0aead0d4ac49ad06715b1e8be7b6d Mon Sep 17 00:00:00 2001 +From: Greg Hudson +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) { diff --git a/Include-etype-info-in-for-hardware-preauth-hints.patch b/Include-etype-info-in-for-hardware-preauth-hints.patch new file mode 100644 index 0000000..21eef78 --- /dev/null +++ b/Include-etype-info-in-for-hardware-preauth-hints.patch @@ -0,0 +1,38 @@ +From be4a469216fb87408484b90be9a1da772ba923df Mon Sep 17 00:00:00 2001 +From: Greg Hudson +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, diff --git a/Refactor-KDC-krb5_pa_data-utility-functions.patch b/Refactor-KDC-krb5_pa_data-utility-functions.patch new file mode 100644 index 0000000..664e99b --- /dev/null +++ b/Refactor-KDC-krb5_pa_data-utility-functions.patch @@ -0,0 +1,393 @@ +From 61e3f0142b09cb230be3a2a110f5224e773f1281 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +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 diff --git a/Simplify-kdc_preauth.c-systems-table.patch b/Simplify-kdc_preauth.c-systems-table.patch new file mode 100644 index 0000000..fad6fc4 --- /dev/null +++ b/Simplify-kdc_preauth.c-systems-table.patch @@ -0,0 +1,738 @@ +From 0afb9c336dd8573faa025915fcb97e643cc3e748 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +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 diff --git a/krb5.spec b/krb5.spec index 5ba26b7..105326d 100644 --- a/krb5.spec +++ b/krb5.spec @@ -18,7 +18,7 @@ Summary: The Kerberos network authentication system Name: krb5 Version: 1.16 # 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 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 Patch40: Fix-hex-conversion-of-PKINIT-certid-strings.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 URL: http://web.mit.edu/kerberos/www/ @@ -714,6 +720,9 @@ exit 0 %{_libdir}/libkadm5srv_mit.so.* %changelog +* Mon Mar 19 2018 Robbie Harwood - 1.16-11 +- Add PKINIT KDC support for freshness token + * Wed Mar 14 2018 Robbie Harwood - 1.16-10 - Exit with status 0 from kadmind