#include <sys/stat.h>
#include "pkinit.h"
+#include "k5-json.h"
/*
* It is anticipated that all the special checks currently
}
}
+/*
+ * Convert a PKCS11 token flags value to the subset that we're interested in
+ * passing along to our API callers.
+ */
+static long long
+pkinit_client_get_token_flags(unsigned long pkcs11_token_flags)
+{
+ long long flags = 0;
+
+ if (pkcs11_token_flags & CKF_USER_PIN_COUNT_LOW)
+ flags |= KRB5_RESPONDER_PKINIT_FLAGS_TOKEN_USER_PIN_COUNT_LOW;
+ if (pkcs11_token_flags & CKF_USER_PIN_FINAL_TRY)
+ flags |= KRB5_RESPONDER_PKINIT_FLAGS_TOKEN_USER_PIN_FINAL_TRY;
+ if (pkcs11_token_flags & CKF_USER_PIN_LOCKED)
+ flags |= KRB5_RESPONDER_PKINIT_FLAGS_TOKEN_USER_PIN_LOCKED;
+ return flags;
+}
+
+/*
+ * Phase one of loading client identity information - call
+ * identity_initialize() to load any identities which we can without requiring
+ * help from the calling user, and use their names of those which we can't load
+ * to construct the challenge for the responder callback.
+ */
+static krb5_error_code
+pkinit_client_prep_questions(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq,
+ krb5_get_init_creds_opt *opt,
+ krb5_clpreauth_callbacks cb,
+ krb5_clpreauth_rock rock,
+ krb5_kdc_req *request,
+ krb5_data *encoded_request_body,
+ krb5_data *encoded_previous_request,
+ krb5_pa_data *pa_data)
+{
+ krb5_error_code retval;
+ pkinit_context plgctx = (pkinit_context)moddata;
+ pkinit_req_context reqctx = (pkinit_req_context)modreq;
+ int i, n;
+ const pkinit_deferred_id *deferred_ids;
+ const char *identity;
+ unsigned long ck_flags;
+ char *encoded;
+ k5_json_object jval = NULL;
+ k5_json_number jflag = NULL;
+
+ if (!reqctx->identity_initialized) {
+ pkinit_client_profile(context, plgctx, reqctx, cb, rock,
+ &request->server->realm);
+ retval = pkinit_identity_initialize(context, plgctx->cryptoctx,
+ reqctx->cryptoctx, reqctx->idopts,
+ reqctx->idctx, cb, rock,
+ request->client);
+ if (retval != 0) {
+ TRACE_PKINIT_CLIENT_NO_IDENTITY(context);
+ pkiDebug("pkinit_identity_initialize returned %d (%s)\n",
+ retval, error_message(retval));
+ }
+
+ reqctx->identity_initialized = TRUE;
+ crypto_free_cert_info(context, plgctx->cryptoctx,
+ reqctx->cryptoctx, reqctx->idctx);
+ if (retval != 0) {
+ pkiDebug("%s: not asking responder question\n", __FUNCTION__);
+ retval = 0;
+ goto cleanup;
+ }
+ }
+
+ deferred_ids = crypto_get_deferred_ids(context, reqctx->idctx);
+ for (i = 0; deferred_ids != NULL && deferred_ids[i] != NULL; i++)
+ continue;
+ n = i;
+
+ /* Create the top-level object. */
+ retval = k5_json_object_create(&jval);
+ if (retval != 0)
+ goto cleanup;
+
+ for (i = 0; i < n; i++) {
+ /* Add an entry to the top-level object for the identity. */
+ identity = deferred_ids[i]->identity;
+ ck_flags = deferred_ids[i]->ck_flags;
+ /* Calculate the flags value for the bits that that we care about. */
+ retval = k5_json_number_create(pkinit_client_get_token_flags(ck_flags),
+ &jflag);
+ if (retval != 0)
+ goto cleanup;
+ retval = k5_json_object_set(jval, identity, jflag);
+ if (retval != 0)
+ goto cleanup;
+ k5_json_release(jflag);
+ jflag = NULL;
+ }
+
+ /* Encode and done. */
+ retval = k5_json_encode(jval, &encoded);
+ if (retval == 0) {
+ cb->ask_responder_question(context, rock,
+ KRB5_RESPONDER_QUESTION_PKINIT,
+ encoded);
+ pkiDebug("%s: asking question '%s'\n", __FUNCTION__, encoded);
+ free(encoded);
+ }
+
+cleanup:
+ k5_json_release(jval);
+ k5_json_release(jflag);
+
+ pkiDebug("%s returning %d\n", __FUNCTION__, retval);
+
+ return retval;
+}
+
+/*
+ * Parse data supplied by the application's responder callback, saving off any
+ * PINs and passwords for identities which we noted needed them.
+ */
+struct save_one_password_data {
+ krb5_context context;
+ krb5_clpreauth_modreq modreq;
+ const char *caller;
+};
+
+static void
+save_one_password(void *arg, const char *key, k5_json_value val)
+{
+ struct save_one_password_data *data = arg;
+ pkinit_req_context reqctx = (pkinit_req_context)data->modreq;
+ const char *password;
+
+ if (k5_json_get_tid(val) == K5_JSON_TID_STRING) {
+ password = k5_json_string_utf8(val);
+ pkiDebug("%s: \"%s\": %p\n", data->caller, key, password);
+ crypto_set_deferred_id(data->context, reqctx->idctx, key, password);
+ }
+}
+
+static krb5_error_code
+pkinit_client_parse_answers(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq,
+ krb5_clpreauth_callbacks cb,
+ krb5_clpreauth_rock rock)
+{
+ krb5_error_code retval;
+ const char *encoded;
+ k5_json_value jval;
+ struct save_one_password_data data;
+
+ data.context = context;
+ data.modreq = modreq;
+ data.caller = __FUNCTION__;
+
+ encoded = cb->get_responder_answer(context, rock,
+ KRB5_RESPONDER_QUESTION_PKINIT);
+ if (encoded == NULL)
+ return 0;
+
+ pkiDebug("pkinit_client_parse_answers: %s\n", encoded);
+
+ retval = k5_json_decode(encoded, &jval);
+ if (retval != 0)
+ goto cleanup;
+
+ /* Expect that the top-level answer is an object. */
+ if (k5_json_get_tid(jval) != K5_JSON_TID_OBJECT) {
+ retval = EINVAL;
+ goto cleanup;
+ }
+
+ /* Store the passed-in per-identity passwords. */
+ k5_json_object_iterate(jval, &save_one_password, &data);
+ retval = 0;
+
+cleanup:
+ if (jval != NULL)
+ k5_json_release(jval);
+ return retval;
+}
+
static krb5_error_code
pkinit_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
krb5_clpreauth_modreq modreq,
if (processing_request) {
pkinit_client_profile(context, plgctx, reqctx, cb, rock,
&request->server->realm);
- retval = pkinit_identity_initialize(context, plgctx->cryptoctx, NULL,
- reqctx->idopts, reqctx->idctx,
- cb, rock, request->client);
+ /* Pull in PINs and passwords for identities which we deferred
+ * loading earlier. */
+ retval = pkinit_client_parse_answers(context, moddata, modreq,
+ cb, rock);
if (retval) {
- TRACE_PKINIT_CLIENT_NO_IDENTITY(context);
- pkiDebug("pkinit_identity_prompt returned %d (%s)\n",
- retval, error_message(retval));
+ if (retval == KRB5KRB_ERR_GENERIC)
+ pkiDebug("pkinit responder answers were invalid\n");
return retval;
}
- /*
- * Load identities (again, potentially), prompting, if we can, for
- * anything for which we didn't get an answer from the responder
- * callback.
- */
- pkinit_identity_set_prompter(reqctx->idctx, prompter, prompter_data);
- retval = pkinit_identity_prompt(context, plgctx->cryptoctx,
- reqctx->cryptoctx, reqctx->idopts,
- reqctx->idctx, cb, rock,
- reqctx->do_identity_matching,
- request->client);
- if (retval) {
+ if (!reqctx->identity_prompted) {
+ reqctx->identity_prompted = TRUE;
+ /*
+ * Load identities (again, potentially), prompting, if we can, for
+ * anything for which we didn't get an answer from the responder
+ * callback.
+ */
+ pkinit_identity_set_prompter(reqctx->idctx, prompter,
+ prompter_data);
+ retval = pkinit_identity_prompt(context, plgctx->cryptoctx,
+ reqctx->cryptoctx, reqctx->idopts,
+ reqctx->idctx, cb, rock,
+ reqctx->do_identity_matching,
+ request->client);
+ pkinit_identity_set_prompter(reqctx->idctx, NULL, NULL);
+ reqctx->identity_prompt_retval = retval;
+ if (retval) {
+ TRACE_PKINIT_CLIENT_NO_IDENTITY(context);
+ pkiDebug("pkinit_identity_prompt returned %d (%s)\n",
+ retval, error_message(retval));
+ return retval;
+ }
+ } else if (reqctx->identity_prompt_retval) {
+ retval = reqctx->identity_prompt_retval;
TRACE_PKINIT_CLIENT_NO_IDENTITY(context);
- pkiDebug("pkinit_identity_prompt returned %d (%s)\n",
+ pkiDebug("pkinit_identity_prompt previously returned %d (%s)\n",
retval, error_message(retval));
return retval;
}
vt->fini = pkinit_client_plugin_fini;
vt->flags = pkinit_client_get_flags;
vt->request_init = pkinit_client_req_init;
+ vt->prep_questions = pkinit_client_prep_questions;
vt->request_fini = pkinit_client_req_fini;
vt->process = pkinit_client_process;
vt->tryagain = pkinit_client_tryagain;