diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
commit | 74aa0bc6779af38018a03fd2cf4419fe85917904 (patch) | |
tree | 9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/responder/pam/pamsrv_passkey.c | |
parent | Initial commit. (diff) | |
download | sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip |
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/responder/pam/pamsrv_passkey.c')
-rw-r--r-- | src/responder/pam/pamsrv_passkey.c | 1438 |
1 files changed, 1438 insertions, 0 deletions
diff --git a/src/responder/pam/pamsrv_passkey.c b/src/responder/pam/pamsrv_passkey.c new file mode 100644 index 0000000..1125840 --- /dev/null +++ b/src/responder/pam/pamsrv_passkey.c @@ -0,0 +1,1438 @@ +/* + SSSD + + PAM Responder - passkey related requests + + Copyright (C) Justin Stephenson <jstephen@redhat.com> 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/child_common.h" +#include "util/authtok.h" +#include "db/sysdb.h" +#include "db/sysdb_passkey_user_verification.h" +#include "responder/pam/pamsrv.h" + +#include "responder/pam/pamsrv_passkey.h" + +struct pam_passkey_verification_enum_str { + enum passkey_user_verification verification; + const char *option; +}; + +struct pam_passkey_table_data { + hash_table_t *table; + char *key; + struct pk_child_user_data *data; +}; + +struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { + { PAM_PASSKEY_VERIFICATION_ON, "on" }, + { PAM_PASSKEY_VERIFICATION_OFF, "off" }, + { PAM_PASSKEY_VERIFICATION_OMIT, "unset" }, + { PAM_PASSKEY_VERIFICATION_INVALID, NULL } +}; + +#define PASSKEY_PREFIX "passkey:" +#define USER_VERIFICATION "user_verification=" +#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) + +const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) +{ + size_t c; + + for (c = 0 ; pam_passkey_verification_enum_str[c].option != NULL; c++) { + if (pam_passkey_verification_enum_str[c].verification == verification) { + return pam_passkey_verification_enum_str[c].option; + } + } + + return "(NULL)"; +} + +struct passkey_ctx { + struct pam_ctx *pam_ctx; + struct tevent_context *ev; + struct pam_data *pd; + struct pam_auth_req *preq; +}; + +void pam_forwarder_passkey_cb(struct tevent_req *req); + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + bool kerberos_pa, + char **_result_kh, + char **_result_ph); + +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pctx); +void pam_passkey_get_user_done(struct tevent_req *req); +void pam_passkey_get_mapping_done(struct tevent_req *req); +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result); + +struct passkey_get_mapping_state { + struct pam_data *pd; + struct cache_req_result *result; +}; + +void passkey_kerberos_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + + pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq) +{ + errno_t ret; + const char *prompt; + const char *key; + const char *pin; + size_t pin_len; + struct pk_child_user_data *data; + struct tevent_req *req; + int timeout; + char *verify_opts; + bool debug_libfido2; + enum passkey_user_verification verification; + + ret = sss_authtok_get_passkey(preq, preq->pd->authtok, + &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failure to get passkey authtok\n"); + return EIO; + } + + if (prompt == NULL || key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey prompt and key are missing or invalid.\n"); + return EIO; + } + + data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, + struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to lookup passkey authtok\n"); + return EIO; + } + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Always use verification sent from passkey krb5 plugin */ + if (strcasecmp(data->user_verification, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } else { + verification = PAM_PASSKEY_VERIFICATION_ON; + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, + verification, pd, data, true); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, passkey_kerberos_cb, preq); + + ret = EAGAIN; + +done: + + return ret; + +} + + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + struct tevent_req *req; + struct passkey_ctx *pctx; + errno_t ret; + + pctx = talloc_zero(mem_ctx, struct passkey_ctx); + if (pctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); + return ENOMEM; + } + + pctx->pd = pd; + pctx->pam_ctx = pam_ctx; + pctx->ev = ev; + pctx->preq = preq; + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + + req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(pctx); + } + + return ret; +} + +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pk_ctx) +{ + + struct passkey_get_mapping_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + int ret; + static const char *attrs[] = { SYSDB_NAME, SYSDB_USER_PASSKEY, NULL }; + + req = tevent_req_create(mem_ctx, &state, struct passkey_get_mapping_state); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, + pk_ctx->pam_ctx->rctx, + pk_ctx->pam_ctx->rctx->ncache, 0, + pk_ctx->pd->domain, + pk_ctx->pd->user, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, pam_passkey_get_mapping_done, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +void pam_passkey_get_mapping_done(struct tevent_req *subreq) +{ + struct cache_req_result *result; + struct tevent_req *req; + struct passkey_get_mapping_state *state; + + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct passkey_get_mapping_state); + + ret = cache_req_user_by_name_attrs_recv(state, subreq, &result); + state->result = result; + + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result) +{ + struct passkey_get_mapping_state *state = NULL; + + state = tevent_req_data(req, struct passkey_get_mapping_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_result = talloc_steal(mem_ctx, state->result); + return EOK; +} + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification) +{ + int ret; + TALLOC_CTX *tmp_ctx; + char **opts; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (verify_opts == NULL) { + ret = EOK; + goto done; + } + + ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strncasecmp(opts[c], USER_VERIFICATION, USER_VERIFICATION_LEN) == 0) { + if (strcasecmp("true", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to true.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp("false", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to false.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_OFF; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unsupported passkey verification option [%s], " \ + "skipping.\n", opts[c]); + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, + struct passkey_ctx *pctx, + struct confdb_ctx *cdb, + struct sysdb_ctx *sysdb, + const char *domain_name, + struct pk_child_user_data *pk_data, + enum passkey_user_verification *_user_verification, + bool *_debug_libfido2) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *verification_from_ldap; + char *verify_opts = NULL; + bool debug_libfido2 = false; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_domain_get_passkey_user_verification(tmp_ctx, sysdb, domain_name, + &verification_from_ldap); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkeyUserVerification from sysdb: [%d]: %s\n", + ret, sss_strerror(ret)); + /* This is expected for AD and LDAP */ + ret = EOK; + goto done; + } + + ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* If require user verification setting is set in LDAP, use it */ + if (verification_from_ldap != NULL) { + if (strcasecmp(verification_from_ldap, "true") == 0) { + verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp(verification_from_ldap, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); + } else { + /* No verification set in LDAP, fallback to local sssd.conf setting */ + ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + + ret = read_passkey_conf_verification(tmp_ctx, verify_opts, &verification); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse passkey verificaton options.\n"); + /* Continue anyway */ + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from local configuration\n"); + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification setting [%s]\n", + pam_passkey_verification_enum_to_string(verification)); + + *_user_verification = verification; + *_debug_libfido2 = debug_libfido2; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t process_passkey_data(TALLOC_CTX *mem_ctx, + struct ldb_message *user_mesg, + const char *domain, + struct pk_child_user_data *_data) +{ + struct ldb_message_element *el; + TALLOC_CTX *tmp_ctx; + int ret; + char **mappings; + const char **kh_mappings; + const char **public_keys; + const char *domain_name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + el = ldb_msg_find_element(user_mesg, SYSDB_USER_PASSKEY); + if (el == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found\n"); + ret = ENOENT; + goto done; + } + + /* This attribute may contain other mapping data unrelated to passkey. In that case + * let's omit it. For example, AD user altSecurityIdentities may store ssh public key + * or smart card mapping data */ + ret = split_on_separator(tmp_ctx, (const char *) el->values[0].data, ':', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + } else if (strcasecmp(mappings[0], "passkey") != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Mapping data found is not passkey related\n"); + ret = ENOENT; + goto done; + } + + kh_mappings = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (kh_mappings == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (public_keys == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < el->num_values; i++) { + ret = split_on_separator(tmp_ctx, (const char *) el->values[i].data, ',', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + kh_mappings[i] = talloc_strdup(kh_mappings, mappings[0] + strlen(PASSKEY_PREFIX)); + if (kh_mappings[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key handle failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys[i] = talloc_strdup(public_keys, mappings[1]); + if (public_keys[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup public key failed.\n"); + ret = ENOMEM; + goto done; + } + } + + domain_name = talloc_strdup(tmp_ctx, domain); + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup domain failed.\n"); + ret = ENOMEM; + goto done; + } + + _data->domain = talloc_steal(mem_ctx, domain_name); + _data->key_handles = talloc_steal(mem_ctx, kh_mappings); + _data->public_keys = talloc_steal(mem_ctx, public_keys); + _data->num_credentials = el->num_values; + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +void pam_forwarder_passkey_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + preq->pd->passkey_local_done = true; + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + + return; + +done: + pam_check_user_done(preq, ret); +} + +void pam_passkey_get_user_done(struct tevent_req *req) +{ + int ret; + struct passkey_ctx *pctx; + bool debug_libfido2 = false; + char *domain_name = NULL; + int timeout; + struct cache_req_result *result = NULL; + struct pk_child_user_data *pk_data = NULL; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + pctx = tevent_req_callback_data(req, struct passkey_ctx); + + ret = pam_passkey_get_mapping_recv(pctx, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + pk_data = talloc_zero(pctx, struct pk_child_user_data); + if (!pk_data) { + DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + /* Get passkey data */ + DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); + ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "process_passkey_data failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* timeout */ + ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, + result->domain->sysdb, result->domain->dns_name, + pk_data, &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Preauth respond with prompt_pin true or false based on user verification */ + if (pctx->pd->cmd == SSS_PAM_PREAUTH) { + const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; + + ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, + (const uint8_t *) prompt_pin); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + pctx->pd->pam_status = PAM_SUCCESS; + pam_reply(pctx->preq); + talloc_free(pk_data); + return; + } + + req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, + verification, pctx->pd, pk_data, false); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); + +done: + if (pk_data != NULL) { + talloc_free(pk_data); + } + + if (ret == ENOENT) { + /* No passkey data, continue through to typical auth flow */ + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); + pctx->preq->passkey_data_exists = false; + pam_check_user_search(pctx->preq); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s." + " Skipping passkey auth\n", + ret, sss_strerror(ret)); + pam_check_user_search(pctx->preq); + } + + return; +} + + +struct pam_passkey_auth_send_state { + struct pam_data *pd; + struct tevent_context *ev; + struct tevent_timer *timeout_handler; + struct sss_child_ctx_old *child_ctx; + struct child_io_fds *io; + const char *logfile; + const char **extra_args; + char *verify_opts; + int timeout; + int child_status; + bool kerberos_pa; +}; + +static errno_t passkey_child_exec(struct tevent_req *req); +static void pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +static int pin_destructor(void *ptr) +{ + uint8_t *pin = talloc_get_type(ptr, uint8_t); + if (pin == NULL) return EOK; + + sss_erase_talloc_mem_securely(pin); + + return EOK; +} + +errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + uint8_t **_buf, size_t *_len) +{ + int ret; + uint8_t *buf; + size_t len; + const char *pin = NULL; + + if (pd == NULL || pd->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); + return EINVAL; + } + + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || + sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey_pin failed [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (pin == NULL || len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + talloc_set_destructor((void *) buf, pin_destructor); + + safealign_memcpy(buf, pin, len, NULL); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + return EINVAL; + } + + *_len = len; + *_buf = buf; + + return EOK; +} + +static void pam_passkey_child_read_data(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + char *str; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + int ret; + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + str = malloc(sizeof(char) * buf_len); + if (str == NULL) { + return; + } + + snprintf(str, buf_len, "%s", buf); + + sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); + + free(str); + + tevent_req_done(req); + return; +} + +static void passkey_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + int ret; + + DEBUG(SSSDBG_TRACE_LIBS, "Sending passkey data complete\n"); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + if (state->kerberos_pa) { + /* Read data back from passkey child */ + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + return; + } + + tevent_req_set_callback(subreq, pam_passkey_child_read_data, req); + } +} + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + bool kerberos_pa, + char **_result_kh, + char **_result_pk) +{ + errno_t ret; + char *result_kh = NULL; + char *result_pk = NULL; + + result_kh = talloc_strdup(mem_ctx, pk_data->key_handles[0]); + if (!kerberos_pa) { + result_pk = talloc_strdup(mem_ctx, pk_data->public_keys[0]); + } + + for (int i = 1; i < pk_data->num_credentials; i++) { + result_kh = talloc_strdup_append(result_kh, ","); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + result_kh = talloc_strdup_append(result_kh, pk_data->key_handles[i]); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + if (!kerberos_pa) { + result_pk = talloc_strdup_append(result_pk, ","); + if (result_pk == NULL) { + ret = ENOMEM; + goto done; + } + + result_pk = talloc_strdup_append(result_pk, pk_data->public_keys[i]); + if (result_kh == NULL || result_pk == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + *_result_kh = result_kh; + *_result_pk = result_pk; + + ret = EOK; +done: + return ret; +} + +struct tevent_req * +pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + bool kerberos_pa) +{ + struct tevent_req *req; + struct pam_passkey_auth_send_state *state; + size_t arg_c = 0; + char *result_kh; + char *result_pk; + int num_args; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct pam_passkey_auth_send_state); + if (req == NULL) { + return NULL; + } + + state->pd = pd; + state->ev = ev; + + state->timeout = timeout; + state->kerberos_pa = kerberos_pa; + state->logfile = PASSKEY_CHILD_LOG_FILE; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc child fds failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + num_args = 11; + state->extra_args = talloc_zero_array(state, const char *, num_args + 1); + if (state->extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + switch (verification) { + case PAM_PASSKEY_VERIFICATION_ON: + state->extra_args[arg_c++] = "--user-verification=true"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); + break; + case PAM_PASSKEY_VERIFICATION_OFF: + state->extra_args[arg_c++] = "--user-verification=false"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); + break; + default: + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); + break; + } + + ret = pam_passkey_concatenate_keys(state, pk_data, state->kerberos_pa, + &result_kh, &result_pk); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + + if (state->kerberos_pa) { + state->extra_args[arg_c++] = pk_data->crypto_challenge; + state->extra_args[arg_c++] = "--cryptographic-challenge"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--get-assert"; + } else { + state->extra_args[arg_c++] = result_pk; + state->extra_args[arg_c++] = "--public-key"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = state->pd->user; + state->extra_args[arg_c++] = "--username"; + state->extra_args[arg_c++] = "--authenticate"; + } + + ret = passkey_child_exec(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +passkey_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for passkey child, " + "consider increasing passkey_child_timeout\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_PASSKEY_CHILD_TIMEOUT); +} + +static errno_t passkey_child_exec(struct tevent_req *req) +{ + struct pam_passkey_auth_send_state *state; + struct tevent_req *subreq; + int pipefd_from_child[2] = PIPE_INIT; + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + uint8_t *write_buf = NULL; + size_t write_buf_len = 0; + struct timeval tv; + int ret; + + state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + PASSKEY_CHILD_PATH, state->logfile, state->extra_args, + false, STDIN_FILENO, STDOUT_FILENO); + /* We should never get here */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec passkey child\n"); + goto done; + } else if (child_pid > 0) { /* parent */ + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + if (state->kerberos_pa) { + ret = child_handler_setup(state->ev, child_pid, NULL, req, &state->child_ctx); + } else { + ret = child_handler_setup(state->ev, child_pid, + pam_passkey_auth_done, req, + &state->child_ctx); + } + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_PASSKEY_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(state->timeout, 0); + state->timeout_handler = tevent_add_timer(state->ev, req, tv, + passkey_child_timeout, req); + if (state->timeout_handler == NULL) { + ret = ERR_PASSKEY_CHILD; + goto done; + } + + /* PIN is needed */ + if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { + ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_passkey_child_write_buffer failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (write_buf_len != 0) { + subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, + state->io->write_to_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); + ret = ERR_PASSKEY_CHILD; + goto done; + } + tevent_req_set_callback(subreq, passkey_child_write_done, req); + } + /* Now either wait for the timeout to fire or the child to finish */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + return EAGAIN; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + } + + return ret; +} + +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status) +{ + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + *child_status = state->child_status; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx, + uint8_t *buf, + size_t len, + struct pk_child_user_data **_data) +{ + + size_t p = 0; + size_t pctr = 0; + errno_t ret; + size_t offset; + struct pk_child_user_data *data = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + data = talloc_zero(tmp_ctx, struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to talloc passkey data.\n"); + ret = ENOMEM; + goto done; + } + + data->user_verification = talloc_strdup(data, (char *) &buf[p]); + if (data->user_verification == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey prompt.\n"); + ret = ENOMEM; + goto done; + } + + offset = strlen(data->user_verification) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey prompt offset failure.\n"); + ret = EIO; + goto done; + } + + data->crypto_challenge = talloc_strdup(data, (char *) &buf[p + offset]); + if (data->crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey challenge.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->crypto_challenge) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey challenge offset failure.\n"); + ret = EIO; + goto done; + } + + + data->domain = talloc_strdup(data, (char *) &buf[p] + offset); + if (data->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey domain.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->domain) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey domain offset failure.\n"); + ret = EIO; + goto done; + } + + SAFEALIGN_COPY_UINT32(&data->num_credentials, &buf[p + offset], &pctr); + size_t list_sz = (size_t) data->num_credentials; + + offset += sizeof(uint32_t); + + data->key_handles = talloc_zero_array(data, const char *, list_sz); + + for (int i = 0; i < list_sz; i++) { + data->key_handles[i] = talloc_strdup(data->key_handles, (char *) &buf[p + offset]); + if (data->key_handles[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey list.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->key_handles[i]) + 1; + } + + *_data = talloc_steal(mem_ctx, data); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t save_passkey_data(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pk_child_user_data *data, + struct pam_auth_req *preq) +{ + char *pk_key; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Passkey data (pk_table_data) is stolen onto client ctx, it will + * be freed when the client closes, and the sss_ptr_hash interface + * takes care of automatically removing it from the hash table then */ + pctx->pk_table_data = talloc_zero(tmp_ctx, struct pam_passkey_table_data); + if (pctx->pk_table_data == NULL) { + return ENOMEM; + } + + if (pctx->pk_table_data->table == NULL) { + pctx->pk_table_data->table = sss_ptr_hash_create(pctx->pk_table_data, + NULL, NULL); + if (pctx->pk_table_data->table == NULL) { + ret = ENOMEM; + goto done; + } + } + + pk_key = talloc_asprintf(tmp_ctx, "%s", data->crypto_challenge); + if (pk_key == NULL) { + ret = ENOMEM; + goto done; + } + + pctx->pk_table_data->key = talloc_strdup(pctx->pk_table_data, pk_key); + if (pctx->pk_table_data->key == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_ptr_hash_add(pctx->pk_table_data->table, pk_key, data, + struct pk_child_user_data); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "pk_table key [%s] already exists\n", + pk_key); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add pk data to hash table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_steal(mem_ctx, pctx->pk_table_data); + pctx->pk_table_data->data = talloc_steal(mem_ctx, data); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done) +{ + struct response_data *pk_resp; + struct pk_child_user_data *pk_data; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + pk_resp = pd->resp_list; + + while (pk_resp != NULL) { + switch (pk_resp->type) { + case SSS_PAM_PASSKEY_KRB_INFO: + if (!pctx->passkey_auth) { + /* Passkey auth is disabled. To avoid passkey prompts appearing, + * don't send SSS_PAM_PASSKEY_KRB_INFO to the client and + * add a dummy response to fallback to normal auth */ + pk_resp->do_not_send_to_client = true; + ret = pam_add_response(pd, SSS_OTP, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; + } + ret = decode_pam_passkey_msg(tmp_ctx, pk_resp->data, pk_resp->len, &pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode passkey msg\n"); + ret = EIO; + goto done; + } + + ret = save_passkey_data(preq->cctx, pctx, pk_data, preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to save passkey msg\n"); + ret = EIO; + goto done; + } + break; + /* Passkey non-kerberos preauth has already run */ + case SSS_PAM_PASSKEY_INFO: + *_pk_preauth_done = true; + default: + break; + } + pk_resp = pk_resp->next; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + + +static void +pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + state->child_status = WEXITSTATUS(child_status); + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " failed with status [%d]. Check passkey_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_PASSKEY_CHILD); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " was terminated by signal [%d]. Check passkey_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey data is valid. Mark done\n"); + + tevent_req_done(req); + return; +} + +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd) +{ +#ifndef BUILD_PASSKEY + DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); + return false; +#else + if (!pctx->passkey_auth) { + return false; + } + + if (pd->cmd != SSS_PAM_PREAUTH && pd->cmd != SSS_PAM_AUTHENTICATE) { + return false; + } + + if (pd->service == NULL || *pd->service == '\0') { + return false; + } + + return true; +#endif /* BUILD_PASSKEY */ +} |