sssd/0021-krb5-child-add-preauth-and-split-2fa-token-support.patch

428 lines
15 KiB
Diff
Raw Normal View History

From 308a445c9e9c5eacd184fa6958a9753592e5eec4 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Tue, 24 Mar 2015 17:26:53 +0100
Subject: [PATCH 21/30] krb5-child: add preauth and split 2fa token support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Reviewed-by: Lukáš Slebodník <lslebodn@redhat.com>
(cherry picked from commit 4b1b2e60d0764fed289eada9a7afbfd1993cadcd)
---
src/providers/krb5/krb5_auth.c | 3 +-
src/providers/krb5/krb5_child.c | 265 +++++++++++++++++++++++++++++---
src/providers/krb5/krb5_child_handler.c | 4 +
src/sss_client/sss_cli.h | 6 +
4 files changed, 257 insertions(+), 21 deletions(-)
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index c0cfaf7cfae5e4aa897bf4fd915fb294c6c24161..6b818440717a9cfaa22a8332fc65440d21d79d00 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -450,7 +450,8 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
switch (pd->cmd) {
case SSS_PAM_AUTHENTICATE:
case SSS_PAM_CHAUTHTOK:
- if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD) {
+ if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD
+ && authtok_type != SSS_AUTHTOK_TYPE_2FA) {
/* handle empty password gracefully */
if (authtok_type == SSS_AUTHTOK_TYPE_EMPTY) {
DEBUG(SSSDBG_CRIT_FAILURE,
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 0fcec981633989593d7155a57811d02a997db251..4b976ddb86b7a1cf6fdc14f99d0b5f4b321814c0 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -54,6 +54,9 @@ struct krb5_req {
char* name;
krb5_creds *creds;
bool otp;
+ char *otp_vendor;
+ char *otp_token_id;
+ char *otp_challenge;
krb5_get_init_creds_opt *options;
struct pam_data *pd;
@@ -268,7 +271,87 @@ static int token_pin_destructor(char *mem)
return 0;
}
-static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
+static krb5_error_code tokeninfo_matches_2fa(TALLOC_CTX *mem_ctx,
+ const krb5_responder_otp_tokeninfo *ti,
+ const char *fa1, size_t fa1_len,
+ const char *fa2, size_t fa2_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) {
+ if (ti->length > 0 && ti->length != fa2_len) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Expected [%d] and given [%zu] token size "
+ "do not match.\n", ti->length, fa2_len);
+ return EMSGSIZE;
+ }
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN) {
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_SEPARATE_PIN) {
+
+ pin = talloc_strndup(mem_ctx, fa1, fa1_len);
+ if (pin == NULL) {
+ talloc_free(token);
+ return ENOMEM;
+ }
+ talloc_set_destructor(pin, token_pin_destructor);
+
+ token = talloc_strndup(mem_ctx, fa2, fa2_len);
+ if (token == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(token, token_pin_destructor);
+
+ check = pick_checker(ti->format);
+ }
+ } else {
+ token = talloc_asprintf(mem_ctx, "%s%s", fa1, fa2);
+ if (token == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(token, token_pin_destructor);
+
+ check = pick_checker(ti->format);
+ }
+ } else {
+ /* Assuming PIN only required */
+ pin = talloc_strndup(mem_ctx, fa1, fa1_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 tokeninfo_matches_pwd(TALLOC_CTX *mem_ctx,
const krb5_responder_otp_tokeninfo *ti,
const char *pwd, size_t len,
char **out_token, char **out_pin)
@@ -364,15 +447,52 @@ static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
return 0;
}
+static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
+ const krb5_responder_otp_tokeninfo *ti,
+ struct sss_auth_token *auth_tok,
+ char **out_token, char **out_pin)
+{
+ int ret;
+ const char *pwd;
+ size_t len;
+ const char *fa2;
+ size_t fa2_len;
+
+ switch (sss_authtok_get_type(auth_tok)) {
+ case SSS_AUTHTOK_TYPE_PASSWORD:
+ ret = sss_authtok_get_password(auth_tok, &pwd, &len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_password failed.\n");
+ return ret;
+ }
+
+ return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin);
+ break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ ret = sss_authtok_get_2fa(auth_tok, &pwd, &len, &fa2, &fa2_len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_2fa failed.\n");
+ return ret;
+ }
+
+ return tokeninfo_matches_2fa(mem_ctx, ti, pwd, len, fa2, fa2_len,
+ out_token, out_pin);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type.\n");
+ }
+
+ return EINVAL;
+}
+
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;
+ size_t i;
ret = krb5_responder_otp_get_challenge(ctx, rctx, &chl);
if (ret != EOK || chl == NULL) {
@@ -388,14 +508,37 @@ static krb5_error_code answer_otp(krb5_context ctx,
kr->otp = true;
- /* Validate our assumptions about the contents of authtok. */
- ret = sss_authtok_get_password(kr->pd->authtok, &pwd, &len);
- if (ret != EOK)
- goto done;
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ for (i = 0; chl->tokeninfo[i] != NULL; i++) {
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Vendor [%s].\n",
+ i, chl->tokeninfo[i]->vendor);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Token-ID [%s].\n",
+ i, chl->tokeninfo[i]->token_id);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Challenge [%s].\n",
+ i, chl->tokeninfo[i]->challenge);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Flags [%d].\n",
+ i, chl->tokeninfo[i]->flags);
+ }
+
+ if (chl->tokeninfo[0]->vendor != NULL) {
+ kr->otp_vendor = talloc_strdup(kr, chl->tokeninfo[0]->vendor);
+ }
+ if (chl->tokeninfo[0]->token_id != NULL) {
+ kr->otp_token_id = talloc_strdup(kr, chl->tokeninfo[0]->token_id);
+ }
+ if (chl->tokeninfo[0]->challenge != NULL) {
+ kr->otp_challenge = talloc_strdup(kr, chl->tokeninfo[0]->challenge);
+ }
+ /* Allocation errors are ignored on purpose */
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Exit answer_otp during pre-auth.\n");
+ return EAGAIN;
+ }
/* 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);
+ ret = tokeninfo_matches(kr, chl->tokeninfo[i], kr->pd->authtok,
+ &token, &pin);
if (ret == EOK) {
break;
}
@@ -683,6 +826,58 @@ static errno_t pack_response_packet(TALLOC_CTX *mem_ctx, errno_t error,
return EOK;
}
+static errno_t k5c_attach_otp_info_msg(struct krb5_req *kr)
+{
+ uint8_t *msg = NULL;
+ size_t msg_len;
+ int ret;
+ size_t vendor_len = 0;
+ size_t token_id_len = 0;
+ size_t challenge_len = 0;
+ size_t idx = 0;
+
+ msg_len = 3;
+ if (kr->otp_vendor != NULL) {
+ vendor_len = strlen(kr->otp_vendor);
+ msg_len += vendor_len;
+ }
+
+ if (kr->otp_token_id != NULL) {
+ token_id_len = strlen(kr->otp_token_id);
+ msg_len += token_id_len;
+ }
+
+ if (kr->otp_challenge != NULL) {
+ challenge_len = strlen(kr->otp_challenge);
+ msg_len += challenge_len;
+ }
+
+ msg = talloc_zero_size(kr, msg_len);
+ if (msg == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+ return ENOMEM;
+ }
+
+ if (kr->otp_vendor != NULL) {
+ memcpy(msg, kr->otp_vendor, vendor_len);
+ }
+ idx += vendor_len +1;
+
+ if (kr->otp_token_id != NULL) {
+ memcpy(msg + idx, kr->otp_token_id, token_id_len);
+ }
+ idx += token_id_len +1;
+
+ if (kr->otp_challenge != NULL) {
+ memcpy(msg + idx, kr->otp_challenge, challenge_len);
+ }
+
+ ret = pam_add_response(kr->pd, SSS_PAM_OTP_INFO, msg_len, msg);
+ talloc_zfree(msg);
+
+ return ret;
+}
+
static errno_t k5c_attach_ccname_msg(struct krb5_req *kr)
{
char *msg = NULL;
@@ -996,9 +1191,18 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
discard_const(password),
sss_krb5_prompter, kr, 0,
NULL, kr->options);
- if (kerr != 0) {
- KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
- return kerr;
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ /* Any errors are ignored during pre-auth, only data is collected to
+ * be send back to the client.*/
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "krb5_get_init_creds_password returned [%d} during pre-auth.\n",
+ kerr);
+ return 0;
+ } else {
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
}
if (kr->validate) {
@@ -1300,8 +1504,11 @@ static errno_t tgt_req_child(struct krb5_req *kr)
DEBUG(SSSDBG_TRACE_LIBS, "Attempting to get a TGT\n");
- ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL);
- switch (ret) {
+ /* No password is needed for pre-auth, or if we have 2FA */
+ if (kr->pd->cmd != SSS_PAM_PREAUTH
+ && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA) {
+ ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL);
+ switch (ret) {
case EOK:
break;
@@ -1314,13 +1521,21 @@ static errno_t tgt_req_child(struct krb5_req *kr)
DEBUG(SSSDBG_OP_FAILURE, "No credentials available\n");
return ERR_NO_CREDS;
break;
+ }
}
kerr = get_and_save_tgt(kr, password);
if (kerr != KRB5KDC_ERR_KEY_EXP) {
- if (kerr == 0) {
- kerr = k5c_attach_ccname_msg(kr);
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ /* add OTP tokeninfo messge if available */
+ if (kr->otp) {
+ kerr = k5c_attach_otp_info_msg(kr);
+ }
+ } else {
+ if (kerr == 0) {
+ kerr = k5c_attach_ccname_msg(kr);
+ }
}
ret = map_krb5_error(kerr);
goto done;
@@ -1523,6 +1738,10 @@ static errno_t unpack_authtok(struct sss_auth_token *tok,
case SSS_AUTHTOK_TYPE_CCFILE:
ret = sss_authtok_set_ccfile(tok, (char *)(buf + *p), 0);
break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ ret = sss_authtok_set(tok, SSS_AUTHTOK_TYPE_2FA, (buf + *p),
+ auth_token_length);
+ break;
default:
return EINVAL;
}
@@ -2285,11 +2504,13 @@ static krb5_error_code privileged_krb5_setup(struct krb5_req *kr,
}
/* For ccache types FILE: and DIR: we might need to create some directory
- * components as root */
- ret = k5c_ccache_setup(kr, offline);
- if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup failed.\n");
- return ret;
+ * components as root. Cache files are not needed during preauth. */
+ if (kr->pd->cmd != SSS_PAM_PREAUTH) {
+ ret = k5c_ccache_setup(kr, offline);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup failed.\n");
+ return ret;
+ }
}
if (!(offline ||
@@ -2464,6 +2685,10 @@ int main(int argc, const char *argv[])
DEBUG(SSSDBG_TRACE_FUNC, "Will perform ticket renewal\n");
ret = renew_tgt_child(kr);
break;
+ case SSS_PAM_PREAUTH:
+ DEBUG(SSSDBG_TRACE_FUNC, "Will perform pre-auth\n");
+ ret = tgt_req_child(kr);
+ break;
default:
DEBUG(SSSDBG_CRIT_FAILURE,
"PAM command [%d] not supported.\n", kr->pd->cmd);
diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c
index 633cd917737d3f39526b049cc3d930b67f8b5c66..1f839ab5ebf93271556371b2f172f6c524da6270 100644
--- a/src/providers/krb5/krb5_child_handler.c
+++ b/src/providers/krb5/krb5_child_handler.c
@@ -77,6 +77,10 @@ static errno_t pack_authtok(struct io_buffer *buf, size_t *rp,
ret = sss_authtok_get_ccfile(tok, &data, &len);
auth_token_length = len + 1;
break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ data = (char *) sss_authtok_get_data(tok);
+ auth_token_length = sss_authtok_get_size(tok);
+ break;
default:
ret = EINVAL;
}
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 2895659b9c3ed4ab520ca90846379c22fd9567f7..1d7e8549cd548b00eeedba95080f346439afc3dd 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -402,6 +402,12 @@ 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_PAM_OTP_INFO, /**< A message which optionally may contain the name
+ * of the vendor, the ID of an OTP token and a
+ * challenge.
+ * @param Three zero terminated strings, if one of the
+ * strings is missing the message will contain only
+ * an empty string (\0) for that component. */
SSS_OTP, /**< Indicates that the autotok was a OTP, so don't
* cache it. There is no message.
* @param None. */
--
2.4.3