From e54dde110c92031ea29df4862f078851544f34dc Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Fri, 10 Jul 2015 17:54:07 +0200 Subject: [PATCH 5/7] PAM: add certificate support to PAM (pre-)auth requests --- Makefile.am | 5 + configure.ac | 3 + src/confdb/confdb.h | 2 + src/responder/pam/pamsrv.c | 34 +++ src/responder/pam/pamsrv.h | 22 ++ src/responder/pam/pamsrv_cmd.c | 312 ++++++++++++++++++--- src/responder/pam/pamsrv_p11.c | 527 ++++++++++++++++++++++++++++++++++++ src/sss_client/sss_cli.h | 1 + src/tests/cmocka/p11_nssdb/cert9.db | Bin 0 -> 13312 bytes src/tests/cmocka/p11_nssdb/key4.db | Bin 0 -> 14336 bytes src/tests/cmocka/test_pam_srv.c | 509 +++++++++++++++++++++++++++++++++- src/util/util_errors.c | 1 + src/util/util_errors.h | 1 + 13 files changed, 1378 insertions(+), 39 deletions(-) create mode 100644 src/responder/pam/pamsrv_p11.c create mode 100644 src/tests/cmocka/p11_nssdb/cert9.db create mode 100644 src/tests/cmocka/p11_nssdb/key4.db diff --git a/Makefile.am b/Makefile.am index e4add9757058773915f1971786b8a9ad584ec51f..fd78c1bb770b98adee9a6c1ead3b0d6f4345fb9b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -382,6 +382,8 @@ dist_noinst_DATA = \ contrib/ci/distro.sh \ contrib/ci/misc.sh \ contrib/ci/sssd.supp \ + src/tests/cmocka/p11_nssdb/cert9.db \ + src/tests/cmocka/p11_nssdb/key4.db \ $(NULL) ############################### @@ -1086,6 +1088,7 @@ sssd_pam_SOURCES = \ src/responder/pam/pam_LOCAL_domain.c \ src/responder/pam/pamsrv.c \ src/responder/pam/pamsrv_cmd.c \ + src/responder/pam/pamsrv_p11.c \ src/responder/pam/pamsrv_dp.c \ src/responder/pam/pam_helpers.c \ $(SSSD_RESPONDER_OBJ) @@ -1877,11 +1880,13 @@ pam_srv_tests_SOURCES = \ src/tests/cmocka/test_pam_srv.c \ src/sss_client/pam_message.c \ src/responder/pam/pamsrv_cmd.c \ + src/responder/pam/pamsrv_p11.c \ src/responder/pam/pam_helpers.c \ src/responder/pam/pamsrv_dp.c \ src/responder/pam/pam_LOCAL_domain.c \ $(NULL) pam_srv_tests_CFLAGS = \ + -U SSSD_LIBEXEC_PATH -DSSSD_LIBEXEC_PATH=\"$(abs_builddir)\" \ $(AM_CFLAGS) \ $(NULL) pam_srv_tests_LDFLAGS = \ diff --git a/configure.ac b/configure.ac index 4b4f8f3228bf13c594c86e1e39474a1d02ffd984..254752bc23b20fd13eb1cec9ebdfe72e8ec91590 100644 --- a/configure.ac +++ b/configure.ac @@ -400,6 +400,9 @@ abs_build_dir=`pwd` AC_DEFINE_UNQUOTED([ABS_BUILD_DIR], ["$abs_build_dir"], [Absolute path to the build directory]) AC_SUBST([abs_builddir], $abs_build_dir) +my_srcdir=`readlink -f $srcdir` +AC_DEFINE_UNQUOTED([ABS_SRC_DIR], ["$my_srcdir"], [Absolute path to the source directory]) + AC_CONFIG_FILES([Makefile contrib/sssd.spec src/examples/rwtab src/doxy.config src/sysv/sssd src/sysv/gentoo/sssd src/sysv/SUSE/sssd po/Makefile.in src/man/Makefile src/tests/cwrap/Makefile diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 0b0ae0dcf2cd26462a9b0c895d833faf5c85b4e5..c3cdf49e08d10367e9e98c093a4aa2569b985170 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -115,6 +115,8 @@ #define CONFDB_PAM_TRUSTED_USERS "pam_trusted_users" #define CONFDB_PAM_PUBLIC_DOMAINS "pam_public_domains" #define CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE "pam_account_expired_message" +#define CONFDB_PAM_CERT_AUTH "pam_cert_auth" +#define CONFDB_PAM_CERT_DB_PATH "pam_cert_db_path" /* SUDO */ #define CONFDB_SUDO_CONF_ENTRY "config/sudo" diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c index aa0d2796b1357d2457a7545415960bd0aaf241bf..3fe467c3cfc4c63b9c261065a17a54c20ea4a546 100644 --- a/src/responder/pam/pamsrv.c +++ b/src/responder/pam/pamsrv.c @@ -50,6 +50,8 @@ #define ALL_DOMAIMS_ARE_PUBLIC "all" #define NO_DOMAIMS_ARE_PUBLIC "none" #define DEFAULT_ALLOWED_UIDS ALL_UIDS_ALLOWED +#define DEFAULT_PAM_CERT_AUTH false +#define DEFAULT_PAM_CERT_DB_PATH SYSCONFDIR"/pki/nssdb" struct mon_cli_iface monitor_pam_methods = { { &mon_cli_iface_meta, 0 }, @@ -302,6 +304,38 @@ static int pam_process_init(TALLOC_CTX *mem_ctx, goto done; } + /* Check if certificate based authentication is enabled */ + ret = confdb_get_bool(pctx->rctx->cdb, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_AUTH, + DEFAULT_PAM_CERT_AUTH, + &pctx->cert_auth); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to determine get cert db path.\n"); + goto done; + } + + pctx->p11_child_debug_fd = -1; + if (pctx->cert_auth) { + ret = p11_child_init(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "p11_child_init failed.\n"); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_DB_PATH, + DEFAULT_PAM_CERT_DB_PATH, + &pctx->nss_db); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine if certificate based authentication is " \ + "enabled or not.\n"); + goto done; + } + } + ret = EOK; done: diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index 0278006467a41b28a1d47a8777074f265c5c478e..59831f2e73f923053e53cad838fac715330546dd 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -43,6 +43,10 @@ struct pam_ctx { /* List of domains that are accessible even for untrusted users. */ char **public_domains; int public_domains_count; + + bool cert_auth; + int p11_child_debug_fd; + char *nss_db; }; struct pam_auth_dp_req { @@ -65,6 +69,9 @@ struct pam_auth_req { bool cached_auth_failed; struct pam_auth_dp_req *dpreq_spy; + + struct ldb_message *cert_user_obj; + char *token_name; }; struct sss_cmd_table *get_pam_cmds(void); @@ -73,4 +80,19 @@ int pam_dp_send_req(struct pam_auth_req *preq, int timeout); int LOCAL_pam_handler(struct pam_auth_req *preq); +errno_t p11_child_init(struct pam_ctx *pctx); + +struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int child_debug_fd, + const char *nss_db, + time_t timeout, + struct pam_data *pd); +errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **cert, char **token_name); + +errno_t add_pam_cert_response(struct pam_data *pd, const char *user, + const char *token_name); + +bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd); #endif /* __PAMSRV_H__ */ diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 9c32f40ff4ee24fdc8d0507efe6205b72ffbcb28..3b84fb864c5a1b08dc4126ff97e258a792f312bc 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -31,6 +31,7 @@ #include "providers/data_provider.h" #include "responder/pam/pamsrv.h" #include "responder/pam/pam_helpers.h" +#include "responder/common/responder_cache_req.h" #include "db/sysdb.h" enum pam_verbosity { @@ -49,6 +50,7 @@ static errno_t pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, const char *name, uint64_t *_value); + static void pam_reply(struct pam_auth_req *preq); static errno_t pack_user_info_account_expired(TALLOC_CTX *mem_ctx, @@ -154,6 +156,13 @@ static int extract_authtok_v2(struct sss_auth_token *tok, ret = sss_authtok_set(tok, SSS_AUTHTOK_TYPE_2FA, auth_token_data, auth_token_length); break; + case SSS_AUTHTOK_TYPE_SC_PIN: + ret = sss_authtok_set_sc_pin(tok, (const char *) auth_token_data, + auth_token_length); + break; + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + sss_authtok_set_sc_keypad(tok); + break; default: return EINVAL; } @@ -892,6 +901,7 @@ static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, } static void pam_forwarder_cb(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req); static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static int pam_check_user_search(struct pam_auth_req *preq); @@ -939,9 +949,22 @@ static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *p goto done; } - ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, - cctx->rctx->default_domain, pd->logon_name, - &pd->domain, &pd->user); + if (pd->logon_name != NULL) { + ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, + cctx->rctx->default_domain, + pd->logon_name, + &pd->domain, &pd->user); + } else { + /* Only SSS_PAM_PREAUTH request may have a missing name, e.g. if the + * name is determined with the help of a certificate */ + if (pd->cmd == SSS_PAM_PREAUTH) { + ret = EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n"); + ret = EINVAL; + goto done; + } + } DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); @@ -1052,49 +1075,66 @@ static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) goto done; } - /* now check user is valid */ - if (pd->domain) { - preq->domain = responder_get_domain(cctx->rctx, pd->domain); - if (!preq->domain) { - ret = ENOENT; - goto done; - } - - ncret = sss_ncache_check_user(pctx->ncache, pctx->neg_timeout, - preq->domain, pd->user); - if (ncret == EEXIST) { - /* User found in the negative cache */ - ret = ENOENT; - goto done; - } - } else { - for (dom = preq->cctx->rctx->domains; - dom; - dom = get_next_domain(dom, false)) { - if (dom->fqnames) continue; + if (pd->user != NULL) { + /* now check user is valid */ + if (pd->domain) { + preq->domain = responder_get_domain(cctx->rctx, pd->domain); + if (!preq->domain) { + ret = ENOENT; + goto done; + } ncret = sss_ncache_check_user(pctx->ncache, pctx->neg_timeout, - dom, pd->user); - if (ncret == ENOENT) { - /* User not found in the negative cache - * Proceed with PAM actions - */ - break; + preq->domain, pd->user); + if (ncret == EEXIST) { + /* User found in the negative cache */ + ret = ENOENT; + goto done; } + } else { + for (dom = preq->cctx->rctx->domains; + dom; + dom = get_next_domain(dom, false)) { + if (dom->fqnames) continue; - /* Try the next domain */ - DEBUG(SSSDBG_TRACE_FUNC, - "User [%s@%s] filtered out (negative cache). " - "Trying next domain.\n", pd->user, dom->name); + ncret = sss_ncache_check_user(pctx->ncache, pctx->neg_timeout, + dom, pd->user); + if (ncret == ENOENT) { + /* User not found in the negative cache + * Proceed with PAM actions + */ + break; + } + + /* Try the next domain */ + DEBUG(SSSDBG_TRACE_FUNC, + "User [%s@%s] filtered out (negative cache). " + "Trying next domain.\n", pd->user, dom->name); + } + + if (!dom) { + ret = ENOENT; + goto done; + } + preq->domain = dom; } + } - if (!dom) { - ret = ENOENT; - goto done; + if (may_do_cert_auth(pctx, pd)) { + req = pam_check_cert_send(cctx, cctx->ev, pctx->p11_child_debug_fd, + pctx->nss_db, 10, pd); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); + ret = ENOMEM; + } else { + tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); + ret = EAGAIN; } - preq->domain = dom; + + goto done; } + if (preq->domain->provider == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Domain [%s] has no auth provider.\n", preq->domain->name); @@ -1113,6 +1153,142 @@ done: return pam_check_user_done(preq, ret); } +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct cli_ctx *cctx = preq->cctx; + struct pam_data *pd; + errno_t ret = EOK; + char *cert; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = pam_check_cert_recv(req, preq, &cert, &preq->token_name); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n"); + goto done; + } + + pd = preq->pd; + + if (cert == NULL) { + if (pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate found and no logon name given, " \ + "authentication not possible.\n");; + ret = ENOENT; + } else { + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate returned, authentication failed.\n"); + ret = ENOENT; + } else { + ret = pam_check_user_search(preq); + if (ret == EOK) { + pam_dom_forwarder(preq); + } + } + + } + goto done; + } + + + req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, + pctx->ncache, pctx->neg_timeout, + 0, NULL, cert); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq); + return; + +done: + pam_check_user_done(preq, ret); +} + +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req) +{ + int ret; + struct ldb_result *res; + struct sss_domain_info *domain; + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + const char *cert_user; + + + ret = cache_req_user_by_cert_recv(preq, req, &res, &domain, NULL); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); + goto done; + } + + if (ret == EOK && res->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Search by certificate returned more than one result.\n"); + ret = EINVAL; + goto done; + } + + if (ret == EOK) { + if (preq->domain == NULL) { + preq->domain = domain; + } + + preq->cert_user_obj = talloc_steal(preq, res->msgs[0]); + + if (preq->pd->logon_name == NULL) { + cert_user = ldb_msg_find_attr_as_string(preq->cert_user_obj, + SYSDB_NAME, NULL); + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has not name.\n"); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Found certificate user [%s].\n", + cert_user); + + ret = add_pam_cert_response(preq->pd, cert_user, preq->token_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + } + + preq->pd->domain = talloc_strdup(preq->pd, domain->name); + if (preq->pd->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + return; + } + } else { + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name and no certificate user found.\n"); + ret = ENOENT; + goto done; + } + } + + ret = pam_check_user_search(preq); + if (ret == EOK) { + pam_dom_forwarder(preq); + } + +done: + pam_check_user_done(preq, ret); +} + static void pam_forwarder_cb(struct tevent_req *req) { struct pam_auth_req *preq = tevent_req_callback_data(req, @@ -1120,6 +1296,8 @@ static void pam_forwarder_cb(struct tevent_req *req) struct cli_ctx *cctx = preq->cctx; struct pam_data *pd; errno_t ret = EOK; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); ret = sss_dp_get_domains_recv(req); talloc_free(req); @@ -1158,6 +1336,20 @@ static void pam_forwarder_cb(struct tevent_req *req) } } + if (may_do_cert_auth(pctx, pd)) { + req = pam_check_cert_send(cctx, cctx->ev, pctx->p11_child_debug_fd, + pctx->nss_db, 10, pd); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); + ret = ENOMEM; + } else { + tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); + ret = EAGAIN; + } + + goto done; + } + ret = pam_check_user_search(preq); if (ret == EOK) { pam_dom_forwarder(preq); @@ -1542,6 +1734,7 @@ static void pam_dom_forwarder(struct pam_auth_req *preq) int ret; struct pam_ctx *pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + const char *cert_user; if (!preq->pd->domain) { preq->pd->domain = preq->domain->name; @@ -1579,6 +1772,51 @@ static void pam_dom_forwarder(struct pam_auth_req *preq) return; } + if (may_do_cert_auth(pctx, preq->pd) && preq->cert_user_obj != NULL) { + /* Check if user matches certificate user */ + cert_user = ldb_msg_find_attr_as_string(preq->cert_user_obj, SYSDB_NAME, + NULL); + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has not name.\n"); + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + /* pam_check_user_search() calls pd_set_primary_name() is the search + * was successful, so pd->user contains the canonical name as well */ + if (strcmp(cert_user, preq->pd->user) == 0) { + + preq->pd->pam_status = PAM_SUCCESS; + + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = add_pam_cert_response(preq->pd, cert_user, + preq->token_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + preq->callback = pam_reply; + pam_reply(preq); + return; + } else { + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + DEBUG(SSSDBG_TRACE_FUNC, + "User and certificate user do not match, " \ + "continue with other authentication methods.\n"); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "User and certificate user do not match.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + if (!NEED_CHECK_PROVIDER(preq->domain->provider) ) { preq->callback = pam_reply; ret = LOCAL_pam_handler(preq); diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c new file mode 100644 index 0000000000000000000000000000000000000000..afb28fd529245975efbf34a04624adb3f09704f2 --- /dev/null +++ b/src/responder/pam/pamsrv_p11.c @@ -0,0 +1,527 @@ +/* + SSSD + + PAM Responder - certificate realted requests + + Copyright (C) Sumit Bose 2015 + + 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 . +*/ + +#include + +#include "util/util.h" +#include "providers/data_provider.h" +#include "util/child_common.h" +#include "util/strtonum.h" +#include "responder/pam/pamsrv.h" + + +#ifndef SSSD_LIBEXEC_PATH +#error "SSSD_LIBEXEC_PATH not defined" +#endif /* SSSD_LIBEXEC_PATH */ + +#define P11_CHILD_LOG_FILE "p11_child" +#define P11_CHILD_PATH SSSD_LIBEXEC_PATH"/p11_child" + +errno_t p11_child_init(struct pam_ctx *pctx) +{ + return child_debug_init(P11_CHILD_LOG_FILE, &pctx->p11_child_debug_fd); +} + +bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd) +{ + size_t c; + const char *sc_services[] = { "login", "su", "su-l", "gdm-smartcard", + "gdm-password", "kdm", "sudo", "sudo-i", + NULL }; + if (!pctx->cert_auth) { + return false; + } + + if (pd->cmd != SSS_PAM_PREAUTH && pd->cmd != SSS_PAM_AUTHENTICATE) { + return false; + } + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_SC_PIN + && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_SC_KEYPAD) { + return false; + } + + /* TODO: make services configurable */ + if (pd->service == NULL || *pd->service == '\0') { + return false; + } + for (c = 0; sc_services[c] != NULL; c++) { + if (strcmp(pd->service, sc_services[c]) == 0) { + break; + } + } + if (sc_services[c] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Smartcard authentication for service [%s] not supported.\n", + pd->service); + return false; + } + + return true; +} + +static errno_t get_p11_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; + } + + switch (sss_authtok_get_type(pd->authtok)) { + case SSS_AUTHTOK_TYPE_SC_PIN: + ret = sss_authtok_get_sc_pin(pd->authtok, &pin, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc_pin failed.\n"); + return ret; + } + if (pin == NULL || len == 0) { + DEBUG(SSSDBG_CRIT_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; + } + + safealign_memcpy(buf, pin, len, NULL); + + break; + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + /* Nothing to send */ + len = 0; + buf = NULL; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + return EINVAL; + } + + *_len = len; + *_buf = buf; + + return EOK; +} + +static errno_t parse_p11_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, + ssize_t buf_len, char **_cert, + char **_token_name) +{ + int ret; + TALLOC_CTX *tmp_ctx = NULL; + uint8_t *p; + uint8_t *pn; + char *cert = NULL; + char *token_name = NULL; + + if (buf_len < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error occured while reading data from p11_child.\n"); + return EIO; + } + + if (buf_len == 0) { + DEBUG(SSSDBG_TRACE_LIBS, "No certificate found.\n"); + ret = EOK; + goto done; + } + + p = memchr(buf, '\n', buf_len); + if (p == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing new-line in p11_child response.\n"); + return EINVAL; + } + if (p == buf) { + DEBUG(SSSDBG_OP_FAILURE, "Missing counter in p11_child response.\n"); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + token_name = talloc_strndup(tmp_ctx, (char*) buf, (p - buf)); + if (token_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + + p++; + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, "Missing cert in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert = talloc_strndup(tmp_ctx, (char *) p, (pn - p)); + if(cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found cert [%s].\n", cert); + + ret = EOK; + +done: + if (ret == EOK) { + *_token_name = talloc_steal(mem_ctx, token_name); + *_cert = talloc_steal(mem_ctx, cert); + } + + talloc_free(tmp_ctx); + + return ret; +} + +struct pam_check_cert_state { + int child_status; + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; + struct tevent_context *ev; + + int write_to_child_fd; + int read_from_child_fd; + char *cert; + char *token_name; +}; + +static void p11_child_write_done(struct tevent_req *subreq); +static void p11_child_done(struct tevent_req *subreq); +static void p11_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int child_debug_fd, + const char *nss_db, + time_t timeout, + struct pam_data *pd) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct pam_check_cert_state *state; + pid_t child_pid; + struct timeval tv; + int pipefd_to_child[2]; + int pipefd_from_child[2]; + const char *extra_args[5] = {NULL, NULL, NULL, NULL, NULL}; + uint8_t *write_buf = NULL; + size_t write_buf_len = 0; + + req = tevent_req_create(mem_ctx, &state, struct pam_check_cert_state); + if (req == NULL) { + return NULL; + } + + if (nss_db == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing NSS DB.\n"); + ret = EINVAL; + goto done; + } + + /* extra_args are added in revers order */ + extra_args[1] = "--nssdb"; + extra_args[0] = nss_db; + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + extra_args[2] = "--auth"; + switch (sss_authtok_get_type(pd->authtok)) { + case SSS_AUTHTOK_TYPE_SC_PIN: + extra_args[3] = "--pin"; + break; + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + extra_args[3] = "--keypad"; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unsupported authtok type.\n"); + ret = EINVAL; + goto done; + } + } else if (pd->cmd == SSS_PAM_PREAUTH) { + extra_args[2] = "--pre"; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected PAM command [%d}.\n", pd->cmd); + ret = EINVAL; + goto done; + } + + state->ev = ev; + state->child_status = EFAULT; + state->read_from_child_fd = -1; + state->write_to_child_fd = -1; + state->cert = NULL; + state->token_name = NULL; + + 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; + } + + if (child_debug_fd == -1) { + child_debug_fd = STDERR_FILENO; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + ret = exec_child_ex(state, pipefd_to_child, pipefd_from_child, + P11_CHILD_PATH, child_debug_fd, extra_args, + STDIN_FILENO, STDOUT_FILENO); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec p11 child: [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + } else if (child_pid > 0) { /* parent */ + + state->read_from_child_fd = pipefd_from_child[0]; + close(pipefd_from_child[1]); + sss_fd_nonblocking(state->read_from_child_fd); + + state->write_to_child_fd = pipefd_to_child[1]; + close(pipefd_to_child[0]); + sss_fd_nonblocking(state->write_to_child_fd); + + /* Set up SIGCHLD handler */ + ret = child_handler_setup(ev, child_pid, NULL, NULL, &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_P11_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(timeout, 0); + state->timeout_handler = tevent_add_timer(ev, req, tv, + p11_child_timeout, req); + if(state->timeout_handler == NULL) { + ret = ERR_P11_CHILD; + goto done; + } + + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + ret = get_p11_child_write_buffer(state, pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_p11_child_write_buffer failed.\n"); + goto done; + } + } + + if (write_buf_len != 0) { + subreq = write_pipe_send(state, ev, write_buf, write_buf_len, + state->write_to_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); + ret = ERR_P11_CHILD; + goto done; + } + tevent_req_set_callback(subreq, p11_child_write_done, req); + } else { + subreq = read_pipe_send(state, ev, state->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + ret = ERR_P11_CHILD; + goto done; + } + tevent_req_set_callback(subreq, p11_child_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; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void p11_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_check_cert_state *state = tevent_req_data(req, + struct pam_check_cert_state); + int ret; + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + close(state->write_to_child_fd); + state->write_to_child_fd = -1; + + subreq = read_pipe_send(state, state->ev, state->read_from_child_fd); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, p11_child_done, req); +} + +static void p11_child_done(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_check_cert_state *state = tevent_req_data(req, + struct pam_check_cert_state); + int ret; + + talloc_zfree(state->timeout_handler); + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + close(state->read_from_child_fd); + state->read_from_child_fd = -1; + + ret = parse_p11_child_response(state, buf, buf_len, &state->cert, + &state->token_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "parse_p11_child_respose failed.\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +static void p11_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_check_cert_state *state = + tevent_req_data(req, struct pam_check_cert_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for p11_child.\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_P11_CHILD); +} + +errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **cert, char **token_name) +{ + struct pam_check_cert_state *state = + tevent_req_data(req, struct pam_check_cert_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (cert != NULL) { + *cert = talloc_steal(mem_ctx, state->cert); + } + + if (token_name != NULL) { + *token_name = talloc_steal(mem_ctx, state->token_name); + } + + return EOK; +} + +errno_t add_pam_cert_response(struct pam_data *pd, const char *user, + const char *token_name) +{ + uint8_t *msg = NULL; + size_t user_len; + size_t msg_len; + size_t slot_len; + int ret; + + if (user == NULL || token_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing mandatory user or slot name.\n"); + return EINVAL; + } + + user_len = strlen(user) + 1; + slot_len = strlen(token_name) + 1; + msg_len = user_len + slot_len; + + msg = talloc_zero_size(pd, msg_len); + if (msg == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n"); + return ENOMEM; + } + + memcpy(msg, user, user_len); + memcpy(msg + user_len, token_name, slot_len); + + ret = pam_add_response(pd, SSS_PAM_CERT_INFO, msg_len, msg); + talloc_free(msg); + + return ret; +} diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index 3c4e938ae37c042879b1ae26fe389fa37cef682c..f39ceba5e401b742b27a012ea0ef3059cb19aecc 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -417,6 +417,7 @@ enum response_type { * @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_PAM_CERT_INFO, SSS_OTP, /**< Indicates that the autotok was a OTP, so don't * cache it. There is no message. * @param None. */ diff --git a/src/tests/cmocka/p11_nssdb/cert9.db b/src/tests/cmocka/p11_nssdb/cert9.db new file mode 100644 index 0000000000000000000000000000000000000000..c639f5cac07030e1c9d4330b4b91961714aaaee8 GIT binary patch literal 13312 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 0) { + wait(&status); + } else { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, "fork() failed\n"); + return ret; + } + + fp = fopen(NSS_DB_PATH"/pkcs11.txt", "w"); + if (fp == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "fopen() failed.\n"); + return ret; + } + ret = fprintf(fp, "library=libsoftokn3.so\nname=soft\n"); + if (ret < 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "fprintf() failed.\n"); + return ret; + } + ret = fprintf(fp, "parameters=configdir='sql:%s/src/tests/cmocka/p11_nssdb' dbSlotDescription='SSSD Test Slot' dbTokenDescription='SSSD Test Token' secmod='secmod.db' flags=readOnly \n\n", ABS_SRC_DIR); + if (ret < 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "fprintf() failed.\n"); + return ret; + } + ret = fclose(fp); + if (ret != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "fclose() failed.\n"); + return ret; + } + + return EOK; +} + +static void cleanup_nss_db(void) +{ + int ret; + + ret = unlink(NSS_DB_PATH"/cert9.db"); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to remove cert9.db.\n"); + } + + ret = unlink(NSS_DB_PATH"/key4.db"); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to remove key4.db.\n"); + } + + ret = unlink(NSS_DB_PATH"/pkcs11.txt"); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to remove pkcs11.db.\n"); + } + + ret = rmdir(NSS_DB_PATH); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to remove " NSS_DB_PATH "\n"); + } +} + struct pam_ctx *mock_pctx(TALLOC_CTX *mem_ctx) { struct pam_ctx *pctx; @@ -113,6 +222,7 @@ void test_pam_setup(struct sss_test_conf_param params[], assert_non_null(pam_test_ctx->cctx); pam_test_ctx->cctx->cli_protocol_version = register_cli_protocol_version(); + pam_test_ctx->cctx->ev = pam_test_ctx->tctx->ev; } static int pam_test_setup(void **state) @@ -141,6 +251,22 @@ static int pam_test_setup(void **state) discard_const("pamuser"), pam_test_ctx->pctx->id_timeout); assert_int_equal(ret, EOK); + + /* Prime the cache with a user for wrong matches */ + ret = sysdb_add_user(pam_test_ctx->tctx->dom, + "wronguser", 321, 654, "wrong user", + "/home/wringuser", "/bin/sh", NULL, + NULL, 300, 0); + assert_int_equal(ret, EOK); + + /* Add entry to the initgr cache to make sure no initgr request is sent to + * the backend */ + ret = pam_initgr_cache_set(pam_test_ctx->pctx->rctx->ev, + pam_test_ctx->pctx->id_table, + discard_const("wronguser"), + pam_test_ctx->pctx->id_timeout); + assert_int_equal(ret, EOK); + return 0; } @@ -151,12 +277,19 @@ static int pam_test_teardown(void **state) ret = sysdb_delete_user(pam_test_ctx->tctx->dom, "pamuser", 0); assert_int_equal(ret, EOK); + ret = sysdb_delete_user(pam_test_ctx->tctx->dom, "wronguser", 0); + assert_int_equal(ret, EOK); + talloc_free(pam_test_ctx); return 0; } typedef int (*cmd_cb_fn_t)(uint32_t, uint8_t *, size_t); + +int __real_read_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint8_t **buf, ssize_t *len); + void __real_sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen); @@ -239,8 +372,13 @@ static void mock_input_pam(TALLOC_CTX *mem_ctx, const char *name, size_t needed_size; uint8_t *authtok; - pi.pam_user = name; - pi.pam_user_size = strlen(pi.pam_user) + 1; + if (name != NULL) { + pi.pam_user = name; + pi.pam_user_size = strlen(pi.pam_user) + 1; + } else { + pi.pam_user = ""; + pi.pam_user_size = 0; + } if (pwd != NULL) { if (fa2 != NULL) { @@ -287,6 +425,52 @@ static void mock_input_pam(TALLOC_CTX *mem_ctx, const char *name, will_return(__wrap_sss_packet_get_body, buf_size); } +static void mock_input_pam_cert(TALLOC_CTX *mem_ctx, const char *name, + const char *pin) +{ + size_t buf_size; + uint8_t *m_buf; + uint8_t *buf; + struct pam_items pi = { 0 }; + int ret; + + if (name != NULL) { + pi.pam_user = name; + pi.pam_user_size = strlen(pi.pam_user) + 1; + } else { + pi.pam_user = ""; + pi.pam_user_size = 0; + } + + if (pin != NULL) { + pi.pam_authtok = discard_const(pin); + pi.pam_authtok_size = strlen(pi.pam_authtok) + 1; + pi.pam_authtok_type = SSS_AUTHTOK_TYPE_SC_PIN; + } + + pi.pam_service = "login"; + pi.pam_service_size = strlen(pi.pam_service) + 1; + pi.pam_tty = "/dev/tty"; + pi.pam_tty_size = strlen(pi.pam_tty) + 1; + pi.pam_ruser = "remuser"; + pi.pam_ruser_size = strlen(pi.pam_ruser) + 1; + pi.pam_rhost = "remhost"; + pi.pam_rhost_size = strlen(pi.pam_rhost) + 1; + pi.requested_domains = ""; + pi.cli_pid = 12345; + + ret = pack_message_v3(&pi, &buf_size, &m_buf); + assert_int_equal(ret, 0); + + buf = talloc_memdup(mem_ctx, m_buf, buf_size); + free(m_buf); + assert_non_null(buf); + + will_return(__wrap_sss_packet_get_body, WRAP_CALL_WRAPPER); + will_return(__wrap_sss_packet_get_body, buf); + will_return(__wrap_sss_packet_get_body, buf_size); +} + static int test_pam_simple_check(uint32_t status, uint8_t *body, size_t blen) { size_t rp = 0; @@ -312,6 +496,46 @@ static int test_pam_simple_check(uint32_t status, uint8_t *body, size_t blen) return EOK; } +static int test_pam_cert_check(uint32_t status, uint8_t *body, size_t blen) +{ + size_t rp = 0; + uint32_t val; + + assert_int_equal(status, 0); + + SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); + assert_int_equal(val, pam_test_ctx->exp_pam_status); + + SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); + assert_int_equal(val, 2); + + SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); + assert_int_equal(val, SSS_PAM_DOMAIN_NAME); + + SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); + assert_int_equal(val, 9); + + assert_int_equal(*(body + rp + val - 1), 0); + assert_string_equal(body + rp, TEST_DOM_NAME); + rp += val; + + SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); + assert_int_equal(val, SSS_PAM_CERT_INFO); + + SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); + assert_int_equal(val, (sizeof("pamuser") + sizeof(TEST_TOKEN_NAME))); + + assert_int_equal(*(body + rp + sizeof("pamuser") - 1), 0); + assert_string_equal(body + rp, "pamuser"); + rp += sizeof("pamuser"); + + assert_int_equal(*(body + rp + sizeof(TEST_TOKEN_NAME) - 1), 0); + assert_string_equal(body + rp, TEST_TOKEN_NAME); + + return EOK; +} + + static int test_pam_offline_chauthtok_check(uint32_t status, uint8_t *body, size_t blen) { @@ -372,6 +596,23 @@ static int test_pam_wrong_pw_offline_auth_check(uint32_t status, return test_pam_simple_check(status, body, blen); } +static int test_pam_user_unknown_check(uint32_t status, + uint8_t *body, size_t blen) +{ + size_t rp = 0; + uint32_t val; + + assert_int_equal(status, 0); + + SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); + assert_int_equal(val, PAM_USER_UNKNOWN); + + SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); + assert_int_equal(val, 0); + + return EOK; +} + void test_pam_authenticate(void **state) { int ret; @@ -859,6 +1100,245 @@ void test_pam_offline_chauthtok(void **state) assert_int_equal(ret, EOK); } +static void set_cert_auth_param(struct pam_ctx *pctx, const char *dbpath) +{ + pam_test_ctx->pctx->cert_auth = true; + pam_test_ctx->pctx->nss_db = discard_const(dbpath); +} + +void test_pam_preauth_cert_nocert(void **state) +{ + int ret; + + set_cert_auth_param(pam_test_ctx->pctx, "/no/path"); + + mock_input_pam_cert(pam_test_ctx, "pamuser", NULL); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + + set_cmd_cb(test_pam_simple_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + +static int test_lookup_by_cert_cb(void *pvt) +{ + int ret; + struct sysdb_attrs *attrs; + unsigned char *der = NULL; + size_t der_size; + + if (pvt != NULL) { + + attrs = sysdb_new_attrs(pam_test_ctx); + assert_non_null(attrs); + + der = sss_base64_decode(pam_test_ctx, pvt, &der_size); + assert_non_null(der); + + ret = sysdb_attrs_add_mem(attrs, SYSDB_USER_CERT, der, der_size); + talloc_free(der); + assert_int_equal(ret, EOK); + + ret = sysdb_set_user_attr(pam_test_ctx->tctx->dom, "pamuser", attrs, + LDB_FLAG_MOD_ADD); + assert_int_equal(ret, EOK); + } + + return EOK; +} + +static int test_lookup_by_cert_wrong_user_cb(void *pvt) +{ + int ret; + struct sysdb_attrs *attrs; + unsigned char *der = NULL; + size_t der_size; + + if (pvt != NULL) { + attrs = sysdb_new_attrs(pam_test_ctx); + assert_non_null(attrs); + + der = sss_base64_decode(pam_test_ctx, pvt, &der_size); + assert_non_null(der); + + ret = sysdb_attrs_add_mem(attrs, SYSDB_USER_CERT, der, der_size); + talloc_free(der); + assert_int_equal(ret, EOK); + + ret = sysdb_set_user_attr(pam_test_ctx->tctx->dom, "wronguser", attrs, + LDB_FLAG_MOD_ADD); + assert_int_equal(ret, EOK); + } + + return EOK; +} + + +void test_pam_preauth_cert_nomatch(void **state) +{ + int ret; + + set_cert_auth_param(pam_test_ctx->pctx, NSS_DB); + + mock_input_pam_cert(pam_test_ctx, "pamuser", NULL); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + mock_account_recv(0, 0, NULL, test_lookup_by_cert_cb, NULL); + + set_cmd_cb(test_pam_simple_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void test_pam_preauth_cert_match(void **state) +{ + int ret; + + set_cert_auth_param(pam_test_ctx->pctx, NSS_DB); + + mock_input_pam_cert(pam_test_ctx, "pamuser", NULL); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + mock_account_recv(0, 0, NULL, test_lookup_by_cert_cb, + discard_const(TEST_TOKEN_CERT)); + + set_cmd_cb(test_pam_cert_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void test_pam_preauth_cert_match_wrong_user(void **state) +{ + int ret; + + set_cert_auth_param(pam_test_ctx->pctx, NSS_DB); + + mock_input_pam_cert(pam_test_ctx, "pamuser", NULL); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + mock_account_recv(0, 0, NULL, test_lookup_by_cert_wrong_user_cb, + discard_const(TEST_TOKEN_CERT)); + + set_cmd_cb(test_pam_simple_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + + +void test_pam_preauth_cert_no_logon_name(void **state) +{ + int ret; + + set_cert_auth_param(pam_test_ctx->pctx, NSS_DB); + + mock_input_pam_cert(pam_test_ctx, NULL, NULL); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + mock_account_recv(0, 0, NULL, test_lookup_by_cert_cb, + discard_const(TEST_TOKEN_CERT)); + + set_cmd_cb(test_pam_cert_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void test_pam_preauth_no_cert_no_logon_name(void **state) +{ + int ret; + + set_cert_auth_param(pam_test_ctx->pctx, "/no/path"); + + mock_input_pam_cert(pam_test_ctx, NULL, NULL); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + + set_cmd_cb(test_pam_user_unknown_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void test_pam_preauth_cert_no_logon_name_no_match(void **state) +{ + int ret; + + set_cert_auth_param(pam_test_ctx->pctx, NSS_DB); + + mock_input_pam_cert(pam_test_ctx, NULL, NULL); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + mock_account_recv(0, 0, NULL, test_lookup_by_cert_cb, NULL); + + set_cmd_cb(test_pam_user_unknown_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void test_pam_cert_auth(void **state) +{ + int ret; + + set_cert_auth_param(pam_test_ctx->pctx, NSS_DB); + + mock_input_pam_cert(pam_test_ctx, "pamuser", "123456"); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + mock_account_recv(0, 0, NULL, test_lookup_by_cert_cb, + discard_const(TEST_TOKEN_CERT)); + + set_cmd_cb(test_pam_simple_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + int main(int argc, const char *argv[]) { int rv; @@ -925,6 +1405,23 @@ int main(int argc, const char *argv[]) pam_test_setup, pam_test_teardown), cmocka_unit_test_setup_teardown(test_pam_offline_chauthtok, pam_test_setup, pam_test_teardown), + cmocka_unit_test_setup_teardown(test_pam_preauth_cert_nocert, + pam_test_setup, pam_test_teardown), + cmocka_unit_test_setup_teardown(test_pam_preauth_cert_nomatch, + pam_test_setup, pam_test_teardown), + cmocka_unit_test_setup_teardown(test_pam_preauth_cert_match, + pam_test_setup, pam_test_teardown), + cmocka_unit_test_setup_teardown(test_pam_preauth_cert_match_wrong_user, + pam_test_setup, pam_test_teardown), + cmocka_unit_test_setup_teardown(test_pam_preauth_cert_no_logon_name, + pam_test_setup, pam_test_teardown), + cmocka_unit_test_setup_teardown(test_pam_preauth_no_cert_no_logon_name, + pam_test_setup, pam_test_teardown), + cmocka_unit_test_setup_teardown( + test_pam_preauth_cert_no_logon_name_no_match, + pam_test_setup, pam_test_teardown), + cmocka_unit_test_setup_teardown(test_pam_cert_auth, + pam_test_setup, pam_test_teardown), }; /* Set debug level to invalid value so we can deside if -d 0 was used. */ @@ -950,8 +1447,16 @@ int main(int argc, const char *argv[]) test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME); test_dom_suite_setup(TESTS_PATH); + cleanup_nss_db(); + rv = setup_nss_db(); + if (rv != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "setup_nss_db failed.\n"); + exit(-1); + } + rv = cmocka_run_group_tests(tests, NULL, NULL); if (rv == 0 && !no_cleanup) { + cleanup_nss_db(); test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME); } diff --git a/src/util/util_errors.c b/src/util/util_errors.c index 61818c9fc3be49358eebd5b38e8d21cb6e9ab3db..735f6dcfc7af33edcc886fd106cb3655bcc9566a 100644 --- a/src/util/util_errors.c +++ b/src/util/util_errors.c @@ -78,6 +78,7 @@ struct err_string error_to_str[] = { { "Unsupported trust direction" }, /* ERR_TRUST_NOT_SUPPORTED */ { "Retrieving keytab failed" }, /* ERR_IPA_GETKEYTAB_FAILED */ { "Trusted forest root unknown" }, /* ERR_TRUST_FOREST_UNKNOWN */ + { "p11_child failed" }, /* ERR_P11_CHILD */ { "ERR_LAST" } /* ERR_LAST */ }; diff --git a/src/util/util_errors.h b/src/util/util_errors.h index 7e03f00e899ee718a35bfaa9340d0b11565862db..fbfbdef334be1fb8a525b78ab6336d616b31a189 100644 --- a/src/util/util_errors.h +++ b/src/util/util_errors.h @@ -100,6 +100,7 @@ enum sssd_errors { ERR_TRUST_NOT_SUPPORTED, ERR_IPA_GETKEYTAB_FAILED, ERR_TRUST_FOREST_UNKNOWN, + ERR_P11_CHILD, ERR_LAST /* ALWAYS LAST */ }; -- 2.1.0