URL: https://github.com/SSSD/sssd/pull/5852
Author: ikerexxe
 Title: #5852: ifp: new interface to validate a certificate
Action: synchronized

To pull the PR as Git branch:
git remote add ghsssd https://github.com/SSSD/sssd
git fetch ghsssd pull/5852/head:pr5852
git checkout pr5852
From ce04009aae3ad37f1bb5a7f0a269beb512c3431f Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedr...@redhat.com>
Date: Fri, 29 Oct 2021 10:56:58 +0200
Subject: [PATCH 1/2] ifp: new interface to validate a certificate

New interface to validate a certificate. The input is the certificate to
validate and the output the user path.

Resolves: https://github.com/SSSD/sssd/issues/5224

Signed-off-by: Iker Pedrosa <ipedr...@redhat.com>
---
 src/responder/ifp/ifp_iface/ifp_iface.c       |   3 +-
 src/responder/ifp/ifp_iface/ifp_iface.xml     |   4 +
 .../ifp/ifp_iface/sbus_ifp_client_sync.c      |  14 +
 .../ifp/ifp_iface/sbus_ifp_client_sync.h      |   9 +
 .../ifp/ifp_iface/sbus_ifp_interface.h        |  22 ++
 .../ifp/ifp_iface/sbus_ifp_symbols.c          |  12 +
 .../ifp/ifp_iface/sbus_ifp_symbols.h          |   3 +
 src/responder/ifp/ifp_users.c                 | 346 ++++++++++++++++++
 src/responder/ifp/ifp_users.h                 |  12 +
 9 files changed, 424 insertions(+), 1 deletion(-)

diff --git a/src/responder/ifp/ifp_iface/ifp_iface.c b/src/responder/ifp/ifp_iface/ifp_iface.c
index 833cf6843a..16035de57a 100644
--- a/src/responder/ifp/ifp_iface/ifp_iface.c
+++ b/src/responder/ifp/ifp_iface/ifp_iface.c
@@ -153,7 +153,8 @@ ifp_register_sbus_interface(struct sbus_connection *conn,
             SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, ListByCertificate, ifp_users_list_by_cert_send, ifp_users_list_by_cert_recv, ctx),
             SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, FindByNameAndCertificate, ifp_users_find_by_name_and_cert_send, ifp_users_find_by_name_and_cert_recv, ctx),
             SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, ListByName, ifp_users_list_by_name_send, ifp_users_list_by_name_recv, ctx),
-            SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, ListByDomainAndName, ifp_users_list_by_domain_and_name_send, ifp_users_list_by_domain_and_name_recv, ctx)
+            SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, ListByDomainAndName, ifp_users_list_by_domain_and_name_send, ifp_users_list_by_domain_and_name_recv, ctx),
+            SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, FindByValidCertificate, ifp_users_find_by_valid_cert_send, ifp_users_find_by_valid_cert_recv, ctx)
         ),
         SBUS_SIGNALS(SBUS_NO_SIGNALS),
         SBUS_PROPERTIES(SBUS_NO_PROPERTIES)
diff --git a/src/responder/ifp/ifp_iface/ifp_iface.xml b/src/responder/ifp/ifp_iface/ifp_iface.xml
index a4b2f861a8..555513c0d8 100644
--- a/src/responder/ifp/ifp_iface/ifp_iface.xml
+++ b/src/responder/ifp/ifp_iface/ifp_iface.xml
@@ -182,6 +182,10 @@
             <arg name="limit" type="u" direction="in" key="3" />
             <arg name="result" type="ao" direction="out"/>
         </method>
+        <method name="FindByValidCertificate">
+            <arg name="pem_cert" type="s" direction="in" />
+            <arg name="result" type="o" direction="out" />
+        </method>
     </interface>
 
     <interface name="org.freedesktop.sssd.infopipe.Users.User">
diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.c b/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.c
index 1f0a8e3679..03b668e78a 100644
--- a/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.c
+++ b/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.c
@@ -1161,6 +1161,20 @@ sbus_call_ifp_users_FindByNameAndCertificate
           _arg_result);
 }
 
+errno_t
+sbus_call_ifp_users_FindByValidCertificate
+    (TALLOC_CTX *mem_ctx,
+     struct sbus_sync_connection *conn,
+     const char *busname,
+     const char *object_path,
+     const char * arg_pem_cert,
+     const char ** _arg_result)
+{
+     return sbus_method_in_s_out_o(mem_ctx, conn,
+          busname, object_path, "org.freedesktop.sssd.infopipe.Users", "FindByValidCertificate", arg_pem_cert,
+          _arg_result);
+}
+
 errno_t
 sbus_call_ifp_users_ListByCertificate
     (TALLOC_CTX *mem_ctx,
diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.h b/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.h
index 41e329d0d6..e48a6ebd9f 100644
--- a/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.h
+++ b/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.h
@@ -275,6 +275,15 @@ sbus_call_ifp_users_FindByNameAndCertificate
      const char * arg_pem_cert,
      const char ** _arg_result);
 
+errno_t
+sbus_call_ifp_users_FindByValidCertificate
+    (TALLOC_CTX *mem_ctx,
+     struct sbus_sync_connection *conn,
+     const char *busname,
+     const char *object_path,
+     const char * arg_pem_cert,
+     const char ** _arg_result);
+
 errno_t
 sbus_call_ifp_users_ListByCertificate
     (TALLOC_CTX *mem_ctx,
diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_interface.h b/src/responder/ifp/ifp_iface/sbus_ifp_interface.h
index 214250e583..2e1fdb5d03 100644
--- a/src/responder/ifp/ifp_iface/sbus_ifp_interface.h
+++ b/src/responder/ifp/ifp_iface/sbus_ifp_interface.h
@@ -1557,6 +1557,28 @@
         (handler_send), (handler_recv), (data)); \
 })
 
+/* Method: org.freedesktop.sssd.infopipe.Users.FindByValidCertificate */
+#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate(handler, data) ({ \
+    SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \
+    sbus_method_sync("FindByValidCertificate", \
+        &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate, \
+        NULL, \
+        _sbus_ifp_invoke_in_s_out_o_send, \
+        NULL, \
+        (handler), (data)); \
+})
+
+#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate(handler_send, handler_recv, data) ({ \
+    SBUS_CHECK_SEND((handler_send), (data), const char *); \
+    SBUS_CHECK_RECV((handler_recv), const char **); \
+    sbus_method_async("FindByValidCertificate", \
+        &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate, \
+        NULL, \
+        _sbus_ifp_invoke_in_s_out_o_send, \
+        NULL, \
+        (handler_send), (handler_recv), (data)); \
+})
+
 /* Method: org.freedesktop.sssd.infopipe.Users.ListByCertificate */
 #define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_ListByCertificate(handler, data) ({ \
     SBUS_CHECK_SYNC((handler), (data), const char *, uint32_t, const char ***); \
diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_symbols.c b/src/responder/ifp/ifp_iface/sbus_ifp_symbols.c
index 6b0630f666..2d316064b4 100644
--- a/src/responder/ifp/ifp_iface/sbus_ifp_symbols.c
+++ b/src/responder/ifp/ifp_iface/sbus_ifp_symbols.c
@@ -359,6 +359,18 @@ _sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByNameAndCertificate = {
     }
 };
 
+const struct sbus_method_arguments
+_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate = {
+    .input = (const struct sbus_argument[]){
+        {.type = "s", .name = "pem_cert"},
+        {NULL}
+    },
+    .output = (const struct sbus_argument[]){
+        {.type = "o", .name = "result"},
+        {NULL}
+    }
+};
+
 const struct sbus_method_arguments
 _sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByCertificate = {
     .input = (const struct sbus_argument[]){
diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_symbols.h b/src/responder/ifp/ifp_iface/sbus_ifp_symbols.h
index 53d303c4c4..e660dd8a9a 100644
--- a/src/responder/ifp/ifp_iface/sbus_ifp_symbols.h
+++ b/src/responder/ifp/ifp_iface/sbus_ifp_symbols.h
@@ -109,6 +109,9 @@ _sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByName;
 extern const struct sbus_method_arguments
 _sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByNameAndCertificate;
 
+extern const struct sbus_method_arguments
+_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate;
+
 extern const struct sbus_method_arguments
 _sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByCertificate;
 
diff --git a/src/responder/ifp/ifp_users.c b/src/responder/ifp/ifp_users.c
index ac9330858f..77b7466cee 100644
--- a/src/responder/ifp/ifp_users.c
+++ b/src/responder/ifp/ifp_users.c
@@ -26,6 +26,8 @@
 #include "util/util.h"
 #include "util/strtonum.h"
 #include "util/cert.h"
+#include "util/child_common.h"
+#include "util/crypto/sss_crypto.h"
 #include "responder/common/responder.h"
 #include "responder/common/cache_req/cache_req.h"
 #include "responder/ifp/ifp_users.h"
@@ -1029,6 +1031,350 @@ ifp_users_list_by_domain_and_name_recv(TALLOC_CTX *mem_ctx,
     return EOK;
 }
 
+struct ifp_users_cert_validation {
+    struct ifp_ctx *ifp_ctx;
+    struct tevent_context *ev;
+    const char *logfile;
+    time_t timeout;
+    char *ca_db;
+    char *verify_opts;
+    char *derb64;
+    const char **extra_args;
+    const char *path;
+
+    struct sss_child_ctx_old *child_ctx;
+    struct tevent_timer *timeout_handler;
+    struct child_io_fds *io;
+};
+
+static errno_t p11_child_exec(struct tevent_req *req);
+static void ifp_users_find_by_valid_cert_step(struct tevent_req *subreq);
+static struct tevent_req *
+ifp_users_find_by_valid_cert_done(int child_status,
+                                  struct tevent_signal *sige,
+                                  void *pvt);
+
+struct tevent_req *
+ifp_users_find_by_valid_cert_send(TALLOC_CTX *mem_ctx,
+                                  struct tevent_context *ev,
+                                  struct sbus_request *sbus_req,
+                                  struct ifp_ctx *ctx,
+                                  const char *pem_cert)
+{
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct tevent_req *req;
+    struct ifp_users_cert_validation *state;
+    size_t arg_c = 0;
+    int ret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    req = tevent_req_create(mem_ctx, &state, struct ifp_users_cert_validation);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    state->ifp_ctx = ctx;
+
+    ret = confdb_get_string(ctx->rctx->cdb, tmp_ctx,
+                            CONFDB_IFP_CONF_ENTRY, CONFDB_SSH_CA_DB,
+                            CONFDB_DEFAULT_SSH_CA_DB, &state->ca_db);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Error reading CA DB from confdb (%d) [%s]\n",
+              ret, strerror(ret));
+        goto done;
+    }
+
+    ret = confdb_get_int(ctx->rctx->cdb, CONFDB_IFP_CONF_ENTRY,
+                         CONFDB_PAM_P11_CHILD_TIMEOUT, -1,
+                         (int *) &state->timeout);
+    if (ret != EOK || state->timeout == -1) {
+        /* check pam configuration as well or use default */
+        ret = confdb_get_int(ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY,
+                             CONFDB_PAM_P11_CHILD_TIMEOUT,
+                             P11_CHILD_TIMEOUT_DEFAULT,
+                             (int *) &state->timeout);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Failed to read p11_child_timeout from confdb: [%d]: %s\n",
+                  ret, sss_strerror(ret));
+            goto done;
+        }
+    }
+
+    ret = confdb_get_string(ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY,
+                            CONFDB_MONITOR_CERT_VERIFICATION, NULL,
+                            &state->verify_opts);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+            "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n",
+            ret, sss_strerror(ret));
+        goto done;
+    }
+
+    state->ev = ev;
+    state->logfile = P11_CHILD_LOG_FILE;
+    state->io = talloc(state, struct child_io_fds);
+    if (state->io == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc 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);
+
+    ret = sss_cert_pem_to_derb64(state, pem_cert, &state->derb64);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_cert_pem_to_derb64 failed.\n");
+        goto done;
+    }
+
+    state->extra_args = talloc_zero_array(state, const char *, 8);
+    if (state->extra_args == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+    state->extra_args[arg_c++] = state->derb64;
+    state->extra_args[arg_c++] = "--certificate";
+    state->extra_args[arg_c++] = state->ca_db;
+    state->extra_args[arg_c++] = "--ca_db";
+    if (state->verify_opts != NULL) {
+        state->extra_args[arg_c++] = state->verify_opts;
+        state->extra_args[arg_c++] = "--verify";
+    }
+    state->extra_args[arg_c++] = "--verification";
+
+    ret = p11_child_exec(req);
+
+done:
+    talloc_free(tmp_ctx);
+
+    if (ret != EAGAIN) {
+        if (ret == EOK) {
+            tevent_req_done(req);
+        } else {
+            tevent_req_error(req, ret);
+        }
+        tevent_req_post(req, ev);
+    }
+
+    return req;
+}
+
+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 ifp_users_cert_validation *state =
+                             tevent_req_data(req, struct ifp_users_cert_validation);
+
+    DEBUG(SSSDBG_MINOR_FAILURE, "Timeout reached for p11_child.\n");
+    child_handler_destroy(state->child_ctx);
+    state->child_ctx = NULL;
+    tevent_req_error(req, ERR_P11_CHILD_TIMEOUT);
+}
+
+static errno_t p11_child_exec(struct tevent_req *req)
+{
+    struct ifp_users_cert_validation *state = tevent_req_data(req,
+                                                  struct ifp_users_cert_validation);
+    int ret;
+    int pipefd_from_child[2] = PIPE_INIT;
+    int pipefd_to_child[2] = PIPE_INIT;
+    pid_t child_pid;
+    struct timeval tv;
+
+    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, P11_CHILD_PATH,
+                      state->logfile, state->extra_args, false,
+                      STDIN_FILENO, STDOUT_FILENO);
+        /* We should never get here */
+        DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec p11 child\n");
+    } 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 */
+        ret = child_handler_setup(state->ev, child_pid, ifp_users_find_by_valid_cert_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_P11_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,
+                                                  p11_child_timeout,
+                                                  req);
+        if (state->timeout_handler == NULL) {
+            ret = ERR_P11_CHILD;
+            goto done;
+        }
+        /* 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;
+}
+
+static struct tevent_req *
+ifp_users_find_by_valid_cert_done(int child_status,
+                                  struct tevent_signal *sige,
+                                  void *pvt)
+{
+    struct tevent_req *subreq;
+    struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+    struct ifp_users_cert_validation *state = tevent_req_data(req,
+                                                  struct ifp_users_cert_validation);
+    bool valid = false;
+    errno_t ret;
+
+    PIPE_FD_CLOSE(state->io->read_from_child_fd);
+    PIPE_FD_CLOSE(state->io->write_to_child_fd);
+
+    if (WIFEXITED(child_status)) {
+        if (WEXITSTATUS(child_status) != 0) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  P11_CHILD_PATH " failed with status [%d]\n", child_status);
+        } else {
+            valid = true;
+        }
+    }
+
+    if (WIFSIGNALED(child_status)) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              P11_CHILD_PATH " was terminated by signal [%d]\n",
+              WTERMSIG(child_status));
+    }
+
+    if (valid) {
+        DEBUG(SSSDBG_TRACE_LIBS, "Certificate [%s] is valid.\n",
+                                  state->extra_args[0]);
+
+        subreq = cache_req_user_by_cert_send(state, state->ifp_ctx->rctx->ev,
+                                             state->ifp_ctx->rctx,
+                                             state->ifp_ctx->rctx->ncache, 0,
+                                             CACHE_REQ_ANY_DOM, NULL, state->derb64);
+        if (subreq == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n");
+            ret = ENOMEM;
+            goto done;
+        }
+
+        tevent_req_set_callback(subreq, ifp_users_find_by_valid_cert_step, req);
+    } else {
+        DEBUG(SSSDBG_MINOR_FAILURE, "Certificate [%s] is not valid.\n",
+                                    state->extra_args[0]);
+        tevent_req_error(req, ERR_INVALID_CERT);
+    }
+
+    ret = EAGAIN;
+
+done:
+    if (ret != EAGAIN) {
+        tevent_req_error(req, ret);
+        tevent_req_post(req, state->ifp_ctx->rctx->ev);
+    }
+
+    return req;
+}
+
+static void ifp_users_find_by_valid_cert_step(struct tevent_req *subreq)
+{
+    struct ifp_users_cert_validation *state;
+    struct cache_req_result *result;
+    struct tevent_req *req;
+    errno_t ret;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct ifp_users_cert_validation);
+
+    ret = cache_req_user_by_cert_recv(state, subreq, &result);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "Unable to find user [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (result->count > 1) {
+         DEBUG(SSSDBG_CRIT_FAILURE, "More than one user found. "
+               "Use ListByCertificate to get all.\n");
+         tevent_req_error(req, EINVAL);
+         return;
+    }
+
+    state->path = ifp_users_build_path_from_msg(state, result->domain,
+                                                    result->msgs[0]);
+    if (state->path == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    tevent_req_done(req);
+    return;
+}
+
+errno_t
+ifp_users_find_by_valid_cert_recv(TALLOC_CTX *mem_ctx,
+                                  struct tevent_req *req,
+                                  const char **_path)
+{
+    struct ifp_users_cert_validation *state = tevent_req_data(req,
+                                                struct ifp_users_cert_validation);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *_path = talloc_steal(mem_ctx, state->path);
+
+    return EOK;
+}
+
 static errno_t
 ifp_users_get_from_cache(TALLOC_CTX *mem_ctx,
                          struct sss_domain_info *domain,
diff --git a/src/responder/ifp/ifp_users.h b/src/responder/ifp/ifp_users.h
index 5dcc44cd4b..8a970eddb4 100644
--- a/src/responder/ifp/ifp_users.h
+++ b/src/responder/ifp/ifp_users.h
@@ -120,6 +120,18 @@ ifp_users_list_by_domain_and_name_recv(TALLOC_CTX *mem_ctx,
                                        struct tevent_req *req,
                                        const char ***_paths);
 
+struct tevent_req *
+ifp_users_find_by_valid_cert_send(TALLOC_CTX *mem_ctx,
+                                  struct tevent_context *ev,
+                                  struct sbus_request *sbus_req,
+                                  struct ifp_ctx *ctx,
+                                  const char *pem_cert);
+
+errno_t
+ifp_users_find_by_valid_cert_recv(TALLOC_CTX *mem_ctx,
+                                  struct tevent_req *req,
+                                  const char **_path);
+
 /* org.freedesktop.sssd.infopipe.Users.User */
 
 struct tevent_req *

From 7cc80d9c0d8f466f7080b7789f396b1cf03a2859 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedr...@redhat.com>
Date: Fri, 29 Oct 2021 11:01:54 +0200
Subject: [PATCH 2/2] Tests: ifp interface to validate certificate

Integration test to check the interface that validates the user
certificate.

Signed-off-by: Iker Pedrosa <ipedr...@redhat.com>
---
 src/tests/intg/Makefile.am                   |   2 +
 src/tests/intg/data/cert_schema.ldif         |  11 ++
 src/tests/intg/ds_openldap.py                |   6 ++
 src/tests/intg/ldap_ent.py                   |   2 +-
 src/tests/intg/test_infopipe.py              | 102 ++++++++++++++++++-
 src/tests/test_CA/SSSD_test_cert_0001.config |   1 +
 6 files changed, 119 insertions(+), 5 deletions(-)
 create mode 100644 src/tests/intg/data/cert_schema.ldif

diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index ad02109c20..0ff45e5727 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -31,6 +31,7 @@ dist_noinst_DATA = \
     test_pac_responder.py \
     data/ad_data.ldif \
     data/ad_schema.ldif \
+    data/cert_schema.ldif \
     data/sudo_schema.ldif \
     test_pysss_nss_idmap.py \
     test_infopipe.py \
@@ -208,6 +209,7 @@ intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service
 	PAM_WRAPPER_SERVICE_DIR="$(abs_builddir)/$(PAM_SERVICE_DIR)" \
 	PAM_WRAPPER_PATH=$$(pkg-config --libs pam_wrapper) \
 	PAM_CERT_DB_PATH=$(PAM_CERT_DB_PATH) \
+	ABS_SRCDIR=$(abs_srcdir) \
 	SOFTHSM2_CONF=$(SOFTHSM2_CONF) \
 	KCM_RENEW=$(KCM_RENEW) \
 	DBUS_SOCK_DIR="$(DESTDIR)$(runstatedir)/dbus/" \
diff --git a/src/tests/intg/data/cert_schema.ldif b/src/tests/intg/data/cert_schema.ldif
new file mode 100644
index 0000000000..0003c66232
--- /dev/null
+++ b/src/tests/intg/data/cert_schema.ldif
@@ -0,0 +1,11 @@
+dn: cn=cert,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: cert
+olcAttributeTypes: ( 1.2.840.113556.1.4.645 NAME 'userCert'
+  DESC 'MANDATORY: X.509 user certificate'
+  EQUALITY octetStringMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
+olcObjectClasses: ( 1.2.840.113556.1.3.46 NAME 'mailRecipient' SUP top AUXILIARY
+  DESC 'MANDATORY: X.509 objectclass'
+  MAY ( userCert $ uid )
+  )
diff --git a/src/tests/intg/ds_openldap.py b/src/tests/intg/ds_openldap.py
index fc3140722b..047b61512b 100644
--- a/src/tests/intg/ds_openldap.py
+++ b/src/tests/intg/ds_openldap.py
@@ -199,6 +199,12 @@ def _setup_config(self):
              "-l", "data/sudo_schema.ldif"],
         )
 
+        # Import cert schema
+        subprocess.check_call(
+            ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config",
+             "-l", "data/cert_schema.ldif"],
+        )
+
     def _start_daemon(self):
         """Start the instance."""
         if subprocess.call(["slapd", "-F", self.conf_slapd_d_dir,
diff --git a/src/tests/intg/ldap_ent.py b/src/tests/intg/ldap_ent.py
index 5dff2ff2b1..2a56f3d9cb 100644
--- a/src/tests/intg/ldap_ent.py
+++ b/src/tests/intg/ldap_ent.py
@@ -36,7 +36,7 @@ def user(base_dn, uid, uidNumber, gidNumber,
     user = (
         "uid=" + uid + ",ou=Users," + base_dn,
         [
-            ('objectClass', [b'top', b'inetOrgPerson',
+            ('objectClass', [b'top', b'inetOrgPerson', b'mailRecipient',
                              b'posixAccount', b'ldapPublicKey']),
             ('cn', [uidNumber if cn is None else cn.encode('utf-8')]),
             ('sn', [b'User' if sn is None else sn.encode('utf-8')]),
diff --git a/src/tests/intg/test_infopipe.py b/src/tests/intg/test_infopipe.py
index 5aefcac9e9..909cd9dc86 100644
--- a/src/tests/intg/test_infopipe.py
+++ b/src/tests/intg/test_infopipe.py
@@ -31,6 +31,7 @@
 import ldap.modlist
 import pytest
 import dbus
+import base64
 
 import config
 import ds_openldap
@@ -184,7 +185,7 @@ def create_ldap_fixture(request, ldap_conn, ent_list=None):
 SCHEMA_RFC2307_BIS = "rfc2307bis"
 
 
-def format_basic_conf(ldap_conn, schema):
+def format_basic_conf(ldap_conn, schema, config):
     """Format a basic SSSD configuration"""
     schema_conf = "ldap_schema         = " + schema + "\n"
     if schema == SCHEMA_RFC2307_BIS:
@@ -209,6 +210,7 @@ def format_basic_conf(ldap_conn, schema):
         # There is not such problem in 1st test. Just in following tests.
         command = {ifp_command} --uid 0 --gid 0 --logger=files
         user_attributes = +extraName
+        ca_db               = {config.PAM_CERT_DB_PATH}
 
         [domain/LDAP]
         {schema_conf}
@@ -216,6 +218,7 @@ def format_basic_conf(ldap_conn, schema):
         ldap_uri            = {ldap_conn.ds_inst.ldap_url}
         ldap_search_base    = {ldap_conn.ds_inst.base_dn}
         ldap_user_extra_attrs = extraName:uid
+        ldap_user_certificate = userCert
 
         [application/app]
         inherit_from = LDAP
@@ -237,6 +240,16 @@ def format_interactive_conf(ldap_conn, schema):
         """).format(INTERACTIVE_TIMEOUT)
 
 
+def format_certificate_conf(ldap_conn, schema, config):
+    """Format an SSSD configuration with all caches refreshing in 4 seconds"""
+    return \
+        format_basic_conf(ldap_conn, schema, config) + \
+        unindent("""
+            [certmap/LDAP/user1]
+            matchrule = <SUBJECT>.*CN = SSSD test cert 0001.*
+        """).format(**locals())
+
+
 def create_conf_file(contents):
     """Create sssd.conf with specified contents"""
     with open(config.CONF_PATH, "w") as conf:
@@ -318,7 +331,8 @@ def sanity_rfc2307(request, ldap_conn):
 
     create_ldap_fixture(request, ldap_conn, ent_list)
 
-    conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307)
+    config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+    conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307, config)
     create_conf_fixture(request, conf)
     create_sssd_fixture(request)
     return None
@@ -330,7 +344,8 @@ def simple_rfc2307(request, ldap_conn):
     ent_list.add_user('usr\\\\001', 181818, 181818)
     ent_list.add_group("group1", 181818)
     create_ldap_fixture(request, ldap_conn, ent_list)
-    conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307)
+    config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+    conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307, config)
     create_conf_fixture(request, conf)
     create_sssd_fixture(request)
     return None
@@ -347,8 +362,9 @@ def auto_private_groups_rfc2307(request, ldap_conn):
 
     create_ldap_fixture(request, ldap_conn, ent_list)
 
+    config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
     conf = \
-        format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+        format_basic_conf(ldap_conn, SCHEMA_RFC2307, config) + \
         unindent("""
             [domain/LDAP]
             auto_private_groups = True
@@ -358,6 +374,34 @@ def auto_private_groups_rfc2307(request, ldap_conn):
     return None
 
 
+@pytest.fixture
+def add_user_with_cert(request, ldap_conn):
+    config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+
+    ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+    ent_list.add_user("user1", 1001, 2001)
+
+    create_ldap_fixture(request, ldap_conn, ent_list)
+
+    der_path = os.path.dirname(config.PAM_CERT_DB_PATH)
+    der_path += "/SSSD_test_cert_x509_0001.der"
+    with open(der_path, 'rb') as f:
+        val = f.read()
+    dn = "uid=user1,ou=Users," + LDAP_BASE_DN
+    '''
+    Using 'userCert' instead of 'userCertificate' to hold the user certificate
+    because the default OpenLDAP has syntax and matching rules which are not
+    used in other LDAP servers.
+    '''
+    ldap_conn.modify_s(dn, [(ldap.MOD_ADD, 'userCert', val)])
+
+    conf = format_certificate_conf(ldap_conn, SCHEMA_RFC2307_BIS, config)
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+
+    return None
+
+
 def test_ping_raw(dbus_system_bus, ldap_conn, simple_rfc2307):
     # test with disabled introspection
     sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
@@ -669,3 +713,53 @@ def test_update_member_list_and_get_all(dbus_system_bus,
                                     'org.freedesktop.DBus.Properties')
     res = prop_interface.GetAll('org.freedesktop.sssd.infopipe.Groups.Group')
     assert not res.get("users")
+
+
+def test_find_by_valid_certificate(dbus_system_bus,
+                                   ldap_conn,
+                                   add_user_with_cert):
+    users_obj = dbus_system_bus.get_object(
+        'org.freedesktop.sssd.infopipe',
+        '/org/freedesktop/sssd/infopipe/Users')
+    users_iface = dbus.Interface(users_obj,
+                                 'org.freedesktop.sssd.infopipe.Users')
+    cert_path = os.path.dirname(config.PAM_CERT_DB_PATH)
+
+    # Valid certificate with user
+    cert_file = cert_path + "/SSSD_test_cert_x509_0001.pem"
+    with open(cert_file, "r") as f:
+        cert = f.read()
+    res = users_iface.FindByValidCertificate(cert)
+    assert res == "/org/freedesktop/sssd/infopipe/Users/app/user1_40app"
+
+    # Valid certificate without user
+    cert_file = cert_path + "/SSSD_test_cert_x509_0002.pem"
+    with open(cert_file, "r") as f:
+        cert = f.read()
+    try:
+        res = users_iface.FindByValidCertificate(cert)
+        assert False, "Previous call should raise an exception"
+    except dbus.exceptions.DBusException as ex:
+        assert str(ex) == "sbus.Error.NotFound: No such file or directory"
+
+    # Valid certificate from another CA
+    print(os.environ['ABS_SRCDIR'])
+    cert_file = os.environ['ABS_SRCDIR'] + \
+                "/../test_ECC_CA/SSSD_test_ECC_cert_key_0001.pem"
+    with open(cert_file, "r") as f:
+        cert = f.read()
+    try:
+        res = users_iface.FindByValidCertificate(cert)
+        assert False, "Previous call should raise an exception"
+    except dbus.exceptions.DBusException as ex:
+        assert str(ex) == \
+            "org.freedesktop.DBus.Error.IOError: Input/output error"
+
+    # Invalid certificate
+    cert = "Invalid cert"
+    try:
+        res = users_iface.FindByValidCertificate(cert)
+        assert False, "Previous call should raise an exception"
+    except dbus.exceptions.DBusException as ex:
+        error = "org.freedesktop.DBus.Error.IOError: Input/output error"
+        assert str(ex) == error
diff --git a/src/tests/test_CA/SSSD_test_cert_0001.config b/src/tests/test_CA/SSSD_test_cert_0001.config
index ce593ae5ea..d59023ca66 100644
--- a/src/tests/test_CA/SSSD_test_cert_0001.config
+++ b/src/tests/test_CA/SSSD_test_cert_0001.config
@@ -1,6 +1,7 @@
 # This certificate is used in
 # - src/tests/cmocka/test_cert_utils.c
 # - src/tests/cmocka/test_pam_srv.c
+# - src/tests/intg/test_infopipe.py
 [ req ]
 distinguished_name = req_distinguished_name
 prompt = no
_______________________________________________
sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org
To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org
Fedora Code of Conduct: 
https://docs.fedoraproject.org/en-US/project/code-of-conduct/
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org
Do not reply to spam on the list, report it: 
https://pagure.io/fedora-infrastructure

Reply via email to