diff options
| author | Nalin Dahyabhai <nalin@dahyabhai.net> | 2013-07-15 13:11:00 -0400 |
|---|---|---|
| committer | Greg Hudson <ghudson@mit.edu> | 2013-07-17 14:57:11 -0400 |
| commit | e8b63198029c632d097822104d6e17c9a67ef1a5 (patch) | |
| tree | ea37cbd8f9593d390db92810ccf2aad96a4b767a /src/plugins | |
| parent | 8899397ab78ea09b8d7dbb20347dd12c93eb15ee (diff) | |
| download | krb5-e8b63198029c632d097822104d6e17c9a67ef1a5.tar.gz krb5-e8b63198029c632d097822104d6e17c9a67ef1a5.tar.xz krb5-e8b63198029c632d097822104d6e17c9a67ef1a5.zip | |
Pass PKINIT identity prompts to the responder cb
Use the list of deferred identity prompts and warnings, which we have
after calling pkinit_identity_initialize(), to build a list of questions
to supply to responder callbacks.
Before calling pkinit_identity_prompt() to actually load identities that
are protected, save any passwords and PINs which a responder callback
may have supplied.
Because pkinit_client_prep_questions() can be called multiple times, and
we don't want to try to load all of our identities each of those times,
take some steps to ensure that we only call pkinit_identity_initialize()
and pkinit_identity_prompt() once per request.
ticket: 7680
Diffstat (limited to 'src/plugins')
| -rw-r--r-- | src/plugins/preauth/pkinit/pkinit.h | 3 | ||||
| -rw-r--r-- | src/plugins/preauth/pkinit/pkinit_clnt.c | 234 |
2 files changed, 218 insertions, 19 deletions
diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h index 38a43f503..f9e1485d0 100644 --- a/src/plugins/preauth/pkinit/pkinit.h +++ b/src/plugins/preauth/pkinit/pkinit.h @@ -231,6 +231,9 @@ struct _pkinit_req_context { int do_identity_matching; krb5_preauthtype pa_type; int rfc6112_kdc; + int identity_initialized; + int identity_prompted; + krb5_error_code identity_prompt_retval; }; 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 748b25e52..f708856c1 100644 --- a/src/plugins/preauth/pkinit/pkinit_clnt.c +++ b/src/plugins/preauth/pkinit/pkinit_clnt.c @@ -40,6 +40,7 @@ #include <sys/stat.h> #include "pkinit.h" +#include "k5-json.h" /* * It is anticipated that all the special checks currently @@ -1050,6 +1051,188 @@ pkinit_client_profile(krb5_context context, } } +/* + * 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, @@ -1107,29 +1290,41 @@ pkinit_client_process(krb5_context context, krb5_clpreauth_moddata moddata, 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; } @@ -1506,6 +1701,7 @@ clpreauth_pkinit_initvt(krb5_context context, int maj_ver, int min_ver, 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; |
