diff options
-rw-r--r-- | src/external/krb5.m4 | 1 | ||||
-rw-r--r-- | src/providers/krb5/krb5_auth.c | 2 | ||||
-rw-r--r-- | src/providers/krb5/krb5_auth.h | 1 | ||||
-rw-r--r-- | src/providers/krb5/krb5_child.c | 223 | ||||
-rw-r--r-- | src/providers/krb5/krb5_child_handler.c | 7 | ||||
-rw-r--r-- | src/sss_client/sss_cli.h | 3 |
6 files changed, 236 insertions, 1 deletions
diff --git a/src/external/krb5.m4 b/src/external/krb5.m4 index f1679a15..56c64842 100644 --- a/src/external/krb5.m4 +++ b/src/external/krb5.m4 @@ -50,6 +50,7 @@ AC_CHECK_FUNCS([krb5_get_init_creds_opt_alloc krb5_get_error_message \ krb5_get_init_creds_opt_set_fast_ccache_name \ krb5_get_init_creds_opt_set_fast_flags \ krb5_get_init_creds_opt_set_canonicalize \ + krb5_get_init_creds_opt_set_responder \ krb5_unparse_name_flags \ krb5_get_init_creds_opt_set_change_password_prompt \ krb5_free_keytab_entry_contents \ diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c index e41e1a1e..d3e11a2d 100644 --- a/src/providers/krb5/krb5_auth.c +++ b/src/providers/krb5/krb5_auth.c @@ -1107,7 +1107,7 @@ static void krb5_auth_done(struct tevent_req *subreq) goto done; } - if (state->be_ctx->domain->cache_credentials == TRUE) { + if (state->be_ctx->domain->cache_credentials == TRUE && !res->otp) { krb5_auth_store_creds(state->sysdb, state->domain, pd); } diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h index 078a31d8..cf290ca0 100644 --- a/src/providers/krb5/krb5_auth.h +++ b/src/providers/krb5/krb5_auth.h @@ -81,6 +81,7 @@ struct krb5_child_response { struct tgt_times tgtt; char *ccname; char *correct_upn; + bool otp; }; errno_t diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c index 9cc0f554..65685333 100644 --- a/src/providers/krb5/krb5_child.c +++ b/src/providers/krb5/krb5_child.c @@ -146,6 +146,220 @@ static void sss_krb5_expire_callback_func(krb5_context context, void *data, return; } +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER +/* + * TODO: These features generally would requires a significant refactoring + * of SSSD and MIT krb5 doesn't support them anyway. They are listed here + * simply as a reminder of things that might become future feature potential. + * + * 1. tokeninfo selection + * 2. challenge + * 3. discreet token/pin prompting + * 4. interactive otp format correction + * 5. nextOTP + * + */ +typedef int (*checker)(int c); + +static inline checker pick_checker(int format) +{ + switch (format) { + case KRB5_RESPONDER_OTP_FORMAT_DECIMAL: + return isdigit; + case KRB5_RESPONDER_OTP_FORMAT_HEXADECIMAL: + return isxdigit; + case KRB5_RESPONDER_OTP_FORMAT_ALPHANUMERIC: + return isalnum; + } + + return NULL; +} + +static int token_pin_destructor(char *mem) +{ + safezero(mem, strlen(mem)); + return 0; +} + +static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx, + const krb5_responder_otp_tokeninfo *ti, + const char *pwd, size_t len, + char **out_token, char **out_pin) +{ + char *token = NULL, *pin = NULL; + checker check = NULL; + int i; + + + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_NEXTOTP) { + return ENOTSUP; + } + + if (ti->challenge != NULL) { + return ENOTSUP; + } + + /* This is a non-sensical value. */ + if (ti->length == 0) { + return EPROTO; + } + + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) { + /* ASSUMPTION: authtok has one of the following formats: + * 1. TokenValue + * 2. PIN+TokenValue + */ + token = talloc_strndup(mem_ctx, pwd, len); + if (token == NULL) { + return ENOMEM; + } + talloc_set_destructor(token, token_pin_destructor); + + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN) { + /* If the server desires a separate pin, we will split it. + * ASSUMPTION: Format of authtok is PIN+TokenValue. */ + if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_SEPARATE_PIN) { + if (ti->length < 1) { + talloc_free(token); + return ENOTSUP; + } + + if (ti->length >= len) { + talloc_free(token); + return EMSGSIZE; + } + + /* Copy the PIN from the front of the value. */ + pin = talloc_strndup(NULL, pwd, len - ti->length); + if (pin == NULL) { + talloc_free(token); + return ENOMEM; + } + talloc_set_destructor(pin, token_pin_destructor); + + /* Remove the PIN from the front of the token value. */ + memmove(token, token + len - ti->length, ti->length + 1); + + check = pick_checker(ti->format); + } else { + if (ti->length > 0 && ti->length > len) { + talloc_free(token); + return EMSGSIZE; + } + } + } else { + if (ti->length > 0 && ti->length != len) { + talloc_free(token); + return EMSGSIZE; + } + + check = pick_checker(ti->format); + } + } else { + pin = talloc_strndup(mem_ctx, pwd, len); + if (pin == NULL) { + return ENOMEM; + } + talloc_set_destructor(pin, token_pin_destructor); + } + + /* If check is set, we need to verify the contents of the token. */ + for (i = 0; check != NULL && token[i] != '\0'; i++) { + if (!check(token[i])) { + talloc_free(token); + talloc_free(pin); + return EBADMSG; + } + } + + *out_token = token; + *out_pin = pin; + return 0; +} + +static krb5_error_code answer_otp(krb5_context ctx, + struct krb5_req *kr, + krb5_responder_context rctx) +{ + krb5_responder_otp_challenge *chl; + char *token = NULL, *pin = NULL; + const char *pwd = NULL; + krb5_error_code ret; + size_t i, len; + + ret = krb5_responder_otp_get_challenge(ctx, rctx, &chl); + if (ret != EOK || chl == NULL) { + /* Either an error, or nothing to do. */ + return ret; + } + + if (chl->tokeninfo == NULL || chl->tokeninfo[0] == NULL) { + /* No tokeninfos? Absurd! */ + ret = EINVAL; + goto done; + } + + /* Validate our assumptions about the contents of authtok. */ + ret = sss_authtok_get_password(&kr->pd->authtok, &pwd, &len); + if (ret != EOK) + goto done; + + /* Find the first supported tokeninfo which matches our authtoken. */ + for (i = 0; chl->tokeninfo[i] != NULL; i++) { + ret = tokeninfo_matches(kr, chl->tokeninfo[i], pwd, len, &token, &pin); + if (ret == EOK) { + break; + } + + switch (ret) { + case EBADMSG: + case EMSGSIZE: + case ENOTSUP: + case EPROTO: + break; + default: + goto done; + } + } + if (chl->tokeninfo[i] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("No tokeninfos found which match our credentials.\n")); + ret = EOK; + goto done; + } + + if (chl->tokeninfo[i]->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) { + /* Don't let SSSD cache the OTP authtok since it is single-use. */ + ret = pam_add_response(kr->pd, SSS_OTP, 0, NULL); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + goto done; + } + } + + /* Respond with the appropriate answer. */ + ret = krb5_responder_otp_set_answer(ctx, rctx, i, token, pin); +done: + talloc_free(token); + talloc_free(pin); + krb5_responder_otp_challenge_free(ctx, rctx, chl); + return ret; +} + +static krb5_error_code sss_krb5_responder(krb5_context ctx, + void *data, + krb5_responder_context rctx) +{ + struct krb5_req *kr = talloc_get_type(data, struct krb5_req); + + if (kr == NULL) { + return EINVAL; + } + + return answer_otp(ctx, kr, rctx); +} +#endif + static krb5_error_code sss_krb5_prompter(krb5_context context, void *data, const char *name, const char *banner, int num_prompts, krb5_prompt prompts[]) @@ -1746,6 +1960,15 @@ static int k5c_setup(struct krb5_req *kr, uint32_t offline) return kerr; } +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER + kerr = krb5_get_init_creds_opt_set_responder(kr->ctx, kr->options, + sss_krb5_responder, kr); + if (kerr != 0) { + KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr); + return kerr; + } +#endif + #ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CHANGE_PASSWORD_PROMPT /* A prompter is used to catch messages about when a password will * expired. The library shall not use the prompter to ask for a new password diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c index 5adbcf70..99922b41 100644 --- a/src/providers/krb5/krb5_child_handler.c +++ b/src/providers/krb5/krb5_child_handler.c @@ -482,6 +482,7 @@ parse_krb5_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, ssize_t len, struct krb5_child_response *res; const char *upn = NULL; size_t upn_len; + bool otp = false; if ((size_t) len < sizeof(int32_t)) { DEBUG(SSSDBG_CRIT_FAILURE, ("message too short.\n")); @@ -563,6 +564,11 @@ parse_krb5_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, ssize_t len, } } + if (msg_type == SSS_OTP) { + otp = true; + skip = true; + } + if (!skip) { ret = pam_add_response(pd, msg_type, msg_len, &buf[p]); if (ret != EOK) { @@ -583,6 +589,7 @@ parse_krb5_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, ssize_t len, res = talloc_zero(mem_ctx, struct krb5_child_response); if (!res) return ENOMEM; + res->otp = otp; res->msg_status = msg_status; memcpy(&res->tgtt, &tgtt, sizeof(tgtt)); diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index 372bcee5..b0d7b821 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -368,6 +368,9 @@ enum response_type { * the user.This should only be used in the case where * it is not possile to use SSS_PAM_USER_INFO. * @param A zero terminated string. */ + SSS_OTP, /**< Indicates that the autotok was a OTP, so don't + * cache it. There is no message. + * @param None. */ }; /** |