diff options
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; |
