From 3a9c577b71f1fd8b40a7371d3ca1566e62aa1916 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Wed, 28 Feb 2018 14:34:14 +0100
Subject: [PATCH 01/10] p11_child: move verification into separate functions

Related to https://pagure.io/SSSD/sssd/issue/3489
---
 src/p11_child/p11_child_nss.c | 149 ++++++++++++++++++++++++++----------------
 1 file changed, 93 insertions(+), 56 deletions(-)

diff --git a/src/p11_child/p11_child_nss.c b/src/p11_child/p11_child_nss.c
index a2e523853..42304dc33 100644
--- a/src/p11_child/p11_child_nss.c
+++ b/src/p11_child/p11_child_nss.c
@@ -100,6 +100,91 @@ static char *get_key_id_str(PK11SlotInfo *slot, CERTCertificate *cert)
     return key_id_str;
 }
 
+int init_verification(struct cert_verify_opts *cert_verify_opts,
+                      CERTCertDBHandle **_hnd)
+{
+    SECStatus rv;
+    CERTCertDBHandle *handle;
+
+    handle = CERT_GetDefaultCertDB();
+    if (handle == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "CERT_GetDefaultCertDB failed: [%d][%s].\n",
+              PR_GetError(), PORT_ErrorToString(PR_GetError()));
+        return EIO;
+    }
+
+    if (cert_verify_opts->do_ocsp) {
+        rv = CERT_EnableOCSPChecking(handle);
+        if (rv != SECSuccess) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "CERT_EnableOCSPChecking failed: [%d][%s].\n",
+                  PR_GetError(), PORT_ErrorToString(PR_GetError()));
+            return EIO;
+        }
+
+        if (cert_verify_opts->ocsp_default_responder != NULL
+            && cert_verify_opts->ocsp_default_responder_signing_cert != NULL) {
+            rv = CERT_SetOCSPDefaultResponder(handle,
+                         cert_verify_opts->ocsp_default_responder,
+                         cert_verify_opts->ocsp_default_responder_signing_cert);
+            if (rv != SECSuccess) {
+                DEBUG(SSSDBG_OP_FAILURE,
+                      "CERT_SetOCSPDefaultResponder failed: [%d][%s].\n",
+                      PR_GetError(), PORT_ErrorToString(PR_GetError()));
+                return EIO;
+            }
+
+            rv = CERT_EnableOCSPDefaultResponder(handle);
+            if (rv != SECSuccess) {
+                DEBUG(SSSDBG_OP_FAILURE,
+                      "CERT_EnableOCSPDefaultResponder failed: [%d][%s].\n",
+                      PR_GetError(), PORT_ErrorToString(PR_GetError()));
+                return EIO;
+            }
+        }
+    }
+
+    *_hnd = handle;
+    return 0;
+}
+
+void shutdown_verification(CERTCertDBHandle *handle,
+                           struct cert_verify_opts *cert_verify_opts)
+{
+    SECStatus rv;
+
+    /* Disable OCSP default responder so that NSS can shutdown properly */
+    if (cert_verify_opts->do_ocsp
+            && cert_verify_opts->ocsp_default_responder != NULL
+            && cert_verify_opts->ocsp_default_responder_signing_cert != NULL) {
+        rv = CERT_DisableOCSPDefaultResponder(handle);
+        if (rv != SECSuccess) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "CERT_DisableOCSPDefaultResponder failed: [%d][%s].\n",
+                  PR_GetError(), PORT_ErrorToString(PR_GetError()));
+        }
+    }
+}
+
+bool do_verification(CERTCertDBHandle *handle, CERTCertificate *cert)
+{
+    SECStatus rv;
+    SECCertificateUsage returned_usage = 0;
+
+    rv = CERT_VerifyCertificateNow(handle, cert, PR_TRUE,
+                                   certificateUsageCheckAllUsages,
+                                   NULL, &returned_usage);
+    if (rv != SECSuccess || ((returned_usage & EXP_USAGES) == 0)) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Certificate [%s][%s] not valid [%d][%s].\n",
+              cert->nickname, cert->subjectName,
+              PR_GetError(), PORT_ErrorToString(PR_GetError()));
+        return false;
+    }
+
+    return true;
+}
+
 int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
             enum op_mode mode, const char *pin,
             struct cert_verify_opts *cert_verify_opts,
@@ -145,7 +230,6 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
     char *cert_b64 = NULL;
     char *multi = NULL;
     PRCList *node;
-    SECCertificateUsage returned_usage = 0;
 
     nss_ctx = NSS_InitContext(nss_db, "", "", SECMOD_DB, &parameters, flags);
     if (nss_ctx == NULL) {
@@ -274,42 +358,10 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
         }
     }
 
-    handle = CERT_GetDefaultCertDB();
-    if (handle == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "CERT_GetDefaultCertDB failed: [%d][%s].\n",
-              PR_GetError(), PORT_ErrorToString(PR_GetError()));
-        return EIO;
-    }
-
-    if (cert_verify_opts->do_ocsp) {
-        rv = CERT_EnableOCSPChecking(handle);
-        if (rv != SECSuccess) {
-            DEBUG(SSSDBG_OP_FAILURE,
-                  "CERT_EnableOCSPChecking failed: [%d][%s].\n",
-                  PR_GetError(), PORT_ErrorToString(PR_GetError()));
-            return EIO;
-        }
-
-        if (cert_verify_opts->ocsp_default_responder != NULL
-            && cert_verify_opts->ocsp_default_responder_signing_cert != NULL) {
-            rv = CERT_SetOCSPDefaultResponder(handle,
-                         cert_verify_opts->ocsp_default_responder,
-                         cert_verify_opts->ocsp_default_responder_signing_cert);
-            if (rv != SECSuccess) {
-                DEBUG(SSSDBG_OP_FAILURE,
-                      "CERT_SetOCSPDefaultResponder failed: [%d][%s].\n",
-                      PR_GetError(), PORT_ErrorToString(PR_GetError()));
-                return EIO;
-            }
-
-            rv = CERT_EnableOCSPDefaultResponder(handle);
-            if (rv != SECSuccess) {
-                DEBUG(SSSDBG_OP_FAILURE,
-                      "CERT_EnableOCSPDefaultResponder failed: [%d][%s].\n",
-                      PR_GetError(), PORT_ErrorToString(PR_GetError()));
-                return EIO;
-            }
-        }
+    ret = init_verification(cert_verify_opts, &handle);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "init_verification failed.\n");
+        return ret;
     }
 
     found_cert = NULL;
@@ -336,16 +388,11 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
               cert_list_node->cert->subjectName);
 
         if (cert_verify_opts->do_verification) {
-            rv = CERT_VerifyCertificateNow(handle, cert_list_node->cert,
-                                           PR_TRUE,
-                                           certificateUsageCheckAllUsages,
-                                           NULL, &returned_usage);
-            if (rv != SECSuccess || ((returned_usage & EXP_USAGES) == 0)) {
+            if (!do_verification(handle, cert_list_node->cert)) {
                 DEBUG(SSSDBG_OP_FAILURE,
-                      "Certificate [%s][%s] not valid [%d][%s], skipping.\n",
+                      "Certificate [%s][%s] not valid, skipping.\n",
                       cert_list_node->cert->nickname,
-                      cert_list_node->cert->subjectName,
-                      PR_GetError(), PORT_ErrorToString(PR_GetError()));
+                      cert_list_node->cert->subjectName);
                 continue;
             }
         }
@@ -392,17 +439,7 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
         }
     }
 
-    /* Disable OCSP default responder so that NSS can shutdown properly */
-    if (cert_verify_opts->do_ocsp
-            && cert_verify_opts->ocsp_default_responder != NULL
-            && cert_verify_opts->ocsp_default_responder_signing_cert != NULL) {
-        rv = CERT_DisableOCSPDefaultResponder(handle);
-        if (rv != SECSuccess) {
-            DEBUG(SSSDBG_OP_FAILURE,
-                  "CERT_DisableOCSPDefaultResponder failed: [%d][%s].\n",
-                  PR_GetError(), PORT_ErrorToString(PR_GetError()));
-        }
-    }
+    shutdown_verification(handle, cert_verify_opts);
 
     if (CERT_LIST_EMPTY(valid_certs)) {
         DEBUG(SSSDBG_TRACE_ALL, "No certificate found.\n");

From 0e8bd526679b7d072a65dbc88e001e54ab107636 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Wed, 28 Feb 2018 18:13:43 +0100
Subject: [PATCH 02/10] p11_child: add verification option

With the new option p11_child can be used to verify a certificate given
on the command line. This will allow the ssh responder to call p11_child
to verify a certificate instead of doing it on its own. This does not
only reduce code-duplication but makes sure that the ssh responder is
not blocked while running OCSP checks.

Related to https://pagure.io/SSSD/sssd/issue/3489
---
 src/p11_child/p11_child_nss.c | 218 ++++++++++++++++++++++++++++++++----------
 1 file changed, 166 insertions(+), 52 deletions(-)

diff --git a/src/p11_child/p11_child_nss.c b/src/p11_child/p11_child_nss.c
index 42304dc33..a94817f38 100644
--- a/src/p11_child/p11_child_nss.c
+++ b/src/p11_child/p11_child_nss.c
@@ -57,7 +57,8 @@
 enum op_mode {
     OP_NONE,
     OP_AUTH,
-    OP_PREAUTH
+    OP_PREAUTH,
+    OP_VERIFIY
 };
 
 enum pin_mode {
@@ -66,6 +67,26 @@ enum pin_mode {
     PIN_KEYPAD
 };
 
+static const char *op_mode_str(enum op_mode mode)
+{
+    switch (mode) {
+    case OP_NONE:
+        return "none";
+        break;
+    case OP_AUTH:
+        return "auth";
+        break;
+    case OP_PREAUTH:
+        return "pre-auth";
+        break;
+    case OP_VERIFIY:
+        return "verifiy";
+        break;
+    default:
+        return "unknown";
+    }
+}
+
 static char *password_passthrough(PK11SlotInfo *slot, PRBool retry, void *arg)
 {
   /* give up if 1) no password was supplied, or 2) the password has already
@@ -100,6 +121,30 @@ static char *get_key_id_str(PK11SlotInfo *slot, CERTCertificate *cert)
     return key_id_str;
 }
 
+int b64_to_cert(CERTCertDBHandle *handle, const char *b64,
+                CERTCertificate **cert)
+{
+    CERTCertificate *c = NULL;
+    SECItem der_item = { 0 };
+
+    der_item.data = ATOB_AsciiToData(b64, &der_item.len);
+    if (der_item.data == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "ATOB_AsciiToData failed.\n");
+        return EIO;
+    }
+
+    c = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE);
+    PORT_Free(der_item.data);
+    if (c == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n");
+        return EINVAL;
+    }
+
+    *cert = c;
+
+    return 0;
+}
+
 int init_verification(struct cert_verify_opts *cert_verify_opts,
                       CERTCertDBHandle **_hnd)
 {
@@ -185,27 +230,19 @@ bool do_verification(CERTCertDBHandle *handle, CERTCertificate *cert)
     return true;
 }
 
-int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
+int do_work(TALLOC_CTX *mem_ctx, NSSInitContext *nss_ctx,
             enum op_mode mode, const char *pin,
-            struct cert_verify_opts *cert_verify_opts,
+            CERTCertDBHandle *handle,
             const char *module_name_in, const char *token_name_in,
             const char *key_id_in, char **_multi)
 {
     int ret;
     SECStatus rv;
-    NSSInitContext *nss_ctx;
     SECMODModuleList *mod_list;
     SECMODModuleList *mod_list_item;
     SECMODModule *module;
     const char *slot_name;
     const char *token_name;
-    uint32_t flags = NSS_INIT_READONLY
-                                   | NSS_INIT_FORCEOPEN
-                                   | NSS_INIT_NOROOTINIT
-                                   | NSS_INIT_OPTIMIZESPACE
-                                   | NSS_INIT_PK11RELOAD;
-    NSSInitParameters parameters = { 0 };
-    parameters.length =  sizeof (parameters);
     PK11SlotInfo *slot = NULL;
     CK_SLOT_ID slot_id;
     SECMODModuleID module_id;
@@ -215,7 +252,6 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
     const PK11DefaultArrayEntry friendly_attr = { "Publicly-readable certs",
                                                   SECMOD_FRIENDLY_FLAG,
                                                   CKM_INVALID_MECHANISM };
-    CERTCertDBHandle *handle;
     unsigned char random_value[128];
     SECKEYPrivateKey *priv_key;
     SECOidTag algtag;
@@ -231,13 +267,6 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
     char *multi = NULL;
     PRCList *node;
 
-    nss_ctx = NSS_InitContext(nss_db, "", "", SECMOD_DB, &parameters, flags);
-    if (nss_ctx == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "NSS_InitContext failed [%d][%s].\n",
-              PR_GetError(), PORT_ErrorToString(PR_GetError()));
-        return EIO;
-    }
-
     PK11_SetPasswordFunc(password_passthrough);
 
     DEBUG(SSSDBG_TRACE_ALL, "Default Module List:\n");
@@ -274,7 +303,8 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
                              NULL);
     if (list == NULL) {
         DEBUG(SSSDBG_OP_FAILURE, "PK11_GetAllTokens failed.\n");
-        return EIO;
+        ret = EIO;
+        goto done;
     }
 
     for (le = list->head; le; le = le->next) {
@@ -293,7 +323,8 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
     PK11_FreeSlotList(list);
     if (slot == NULL) {
         DEBUG(SSSDBG_OP_FAILURE, "No removable slots found.\n");
-        return EIO;
+        ret = EIO;
+        goto done;
     }
 
     slot_id = PK11_GetSlotID(slot);
@@ -329,7 +360,8 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
             if (rv !=  SECSuccess) {
                 DEBUG(SSSDBG_OP_FAILURE, "PK11_Authenticate failed: [%d][%s].\n",
                       PR_GetError(), PORT_ErrorToString(PR_GetError()));
-                return EIO;
+                ret = EIO;
+                goto done;
             }
         } else {
             DEBUG(SSSDBG_CRIT_FAILURE,
@@ -343,7 +375,8 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
     if (cert_list == NULL) {
         DEBUG(SSSDBG_OP_FAILURE, "PK11_ListCertsInSlot failed: [%d][%s].\n",
               PR_GetError(), PORT_ErrorToString(PR_GetError()));
-        return EIO;
+        ret = EIO;
+        goto done;
     }
 
     for (cert_list_node = CERT_LIST_HEAD(cert_list);
@@ -358,12 +391,6 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
         }
     }
 
-    ret = init_verification(cert_verify_opts, &handle);
-    if (ret != 0) {
-        DEBUG(SSSDBG_OP_FAILURE, "init_verification failed.\n");
-        return ret;
-    }
-
     found_cert = NULL;
     valid_certs = CERT_NewCertList();
     if (valid_certs == NULL) {
@@ -387,7 +414,7 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
               cert_list_node->cert->nickname,
               cert_list_node->cert->subjectName);
 
-        if (cert_verify_opts->do_verification) {
+        if (handle != NULL) {
             if (!do_verification(handle, cert_list_node->cert)) {
                 DEBUG(SSSDBG_OP_FAILURE,
                       "Certificate [%s][%s] not valid, skipping.\n",
@@ -439,8 +466,6 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
         }
     }
 
-    shutdown_verification(handle, cert_verify_opts);
-
     if (CERT_LIST_EMPTY(valid_certs)) {
         DEBUG(SSSDBG_TRACE_ALL, "No certificate found.\n");
         *_multi = NULL;
@@ -471,7 +496,8 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
             DEBUG(SSSDBG_OP_FAILURE,
                   "PK11_GenerateRandom failed [%d][%s].\n",
                   PR_GetError(), PORT_ErrorToString(PR_GetError()));
-            return EIO;
+            ret = EIO;
+            goto done;
         }
 
         priv_key = PK11_FindPrivateKeyFromCert(slot, found_cert, NULL);
@@ -610,12 +636,6 @@ int do_work(TALLOC_CTX *mem_ctx, const char *nss_db,
 
     talloc_free(cert_b64);
 
-    rv = NSS_ShutdownContext(nss_ctx);
-    if (rv != SECSuccess) {
-        DEBUG(SSSDBG_OP_FAILURE, "NSS_ShutdownContext failed [%d][%s].\n",
-              PR_GetError(), PORT_ErrorToString(PR_GetError()));
-    }
-
     return ret;
 }
 
@@ -659,6 +679,74 @@ static errno_t p11c_recv_data(TALLOC_CTX *mem_ctx, int fd, char **pin)
     return EOK;
 }
 
+static int do_nss(TALLOC_CTX *main_ctx, enum op_mode mode, const char *nss_db,
+                  struct cert_verify_opts *cert_verify_opts,
+                  const char *cert_b64, const char *pin,
+                  const char *module_name, const char *token_name,
+                  const char *key_id, char **multi)
+{
+    int ret;
+    NSSInitContext *nss_ctx = NULL;
+    CERTCertDBHandle *handle = NULL;
+    CERTCertificate *cert = NULL;
+    uint32_t flags = NSS_INIT_READONLY
+                                   | NSS_INIT_FORCEOPEN
+                                   | NSS_INIT_NOROOTINIT
+                                   | NSS_INIT_OPTIMIZESPACE
+                                   | NSS_INIT_PK11RELOAD;
+    NSSInitParameters parameters = { 0 };
+    parameters.length =  sizeof (parameters);
+    SECStatus rv;
+
+    nss_ctx = NSS_InitContext(nss_db, "", "", SECMOD_DB, &parameters, flags);
+    if (nss_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "NSS_InitContext failed [%d][%s].\n",
+              PR_GetError(), PORT_ErrorToString(PR_GetError()));
+        return EIO;
+    }
+
+    if (cert_verify_opts->do_verification) {
+        ret = init_verification(cert_verify_opts, &handle);
+        if (ret != 0) {
+            DEBUG(SSSDBG_OP_FAILURE, "init_verification failed.\n");
+            goto done;
+        }
+    }
+
+
+    if (mode == OP_VERIFIY) {
+        ret = b64_to_cert(handle, cert_b64, &cert);
+        if (ret == 0) {
+            if (do_verification(handle, cert)) {
+                DEBUG(SSSDBG_TRACE_FUNC, "Certificate is valid.\n");
+                ret = 0;
+            } else {
+                DEBUG(SSSDBG_TRACE_FUNC, "Certificate is NOT valid.\n");
+                ret = EINVAL;
+            }
+            CERT_DestroyCertificate(cert);
+        } else {
+            DEBUG(SSSDBG_OP_FAILURE, "Failed to convert certificate.\n");
+        }
+    } else {
+        ret = do_work(main_ctx, nss_ctx, mode, pin, handle,
+                      module_name, token_name, key_id, multi);
+    }
+
+done:
+    if (cert_verify_opts->do_verification && handle != NULL) {
+        shutdown_verification(handle, cert_verify_opts);
+    }
+
+    rv = NSS_ShutdownContext(nss_ctx);
+    if (rv != SECSuccess) {
+        DEBUG(SSSDBG_OP_FAILURE, "NSS_ShutdownContext failed [%d][%s].\n",
+              PR_GetError(), PORT_ErrorToString(PR_GetError()));
+    }
+
+    return ret;
+}
+
 int main(int argc, const char *argv[])
 {
     int opt;
@@ -677,6 +765,7 @@ int main(int argc, const char *argv[])
     char *module_name = NULL;
     char *token_name = NULL;
     char *key_id = NULL;
+    char *cert_b64 = NULL;
 
     struct poptOption long_options[] = {
         POPT_AUTOHELP
@@ -694,6 +783,8 @@ int main(int argc, const char *argv[])
         SSSD_LOGGER_OPTS
         {"auth", 0, POPT_ARG_NONE, NULL, 'a', _("Run in auth mode"), NULL},
         {"pre", 0, POPT_ARG_NONE, NULL, 'p', _("Run in pre-auth mode"), NULL},
+        {"verification", 0, POPT_ARG_NONE, NULL, 'v', _("Run in verification mode"),
+         NULL},
         {"pin", 0, POPT_ARG_NONE, NULL, 'i', _("Expect PIN on stdin"), NULL},
         {"keypad", 0, POPT_ARG_NONE, NULL, 'k', _("Expect PIN on keypad"),
          NULL},
@@ -707,6 +798,8 @@ int main(int argc, const char *argv[])
          _("Token name for authentication"), NULL},
         {"key_id", 0, POPT_ARG_STRING, &key_id, 0,
          _("Key ID for authentication"), NULL},
+        {"certificate", 0, POPT_ARG_STRING, &cert_b64, 0,
+         _("certificate to verify, base64 encoded"), NULL},
         POPT_TABLEEND
     };
 
@@ -729,8 +822,8 @@ int main(int argc, const char *argv[])
         case 'a':
             if (mode != OP_NONE) {
                 fprintf(stderr,
-                        "\n--auth and --pre are mutually exclusive and " \
-                        "should be only used once.\n\n");
+                        "\n--verifiy, --auth and --pre are mutually " \
+                        "exclusive and should be only used once.\n\n");
                 poptPrintUsage(pc, stderr, 0);
                 _exit(-1);
             }
@@ -739,13 +832,23 @@ int main(int argc, const char *argv[])
         case 'p':
             if (mode != OP_NONE) {
                 fprintf(stderr,
-                        "\n--auth and --pre are mutually exclusive and " \
-                        "should be only used once.\n\n");
+                        "\n--verifiy, --auth and --pre are mutually " \
+                        "exclusive and should be only used once.\n\n");
                 poptPrintUsage(pc, stderr, 0);
                 _exit(-1);
             }
             mode = OP_PREAUTH;
             break;
+        case 'v':
+            if (mode != OP_NONE) {
+                fprintf(stderr,
+                        "\n--verifiy, --auth and --pre are mutually " \
+                        "exclusive and should be only used once.\n\n");
+                poptPrintUsage(pc, stderr, 0);
+                _exit(-1);
+            }
+            mode = OP_VERIFIY;
+            break;
         case 'i':
             if (pin_mode != PIN_NONE) {
                 fprintf(stderr, "\n--pin and --keypad are mutually exclusive " \
@@ -779,8 +882,8 @@ int main(int argc, const char *argv[])
     }
 
     if (mode == OP_NONE) {
-        fprintf(stderr, "\nMissing operation mode, " \
-                        "either --auth or --pre must be specified.\n\n");
+        fprintf(stderr, "\nMissing operation mode, either " \
+                        "--verifiy, --auth or --pre must be specified.\n\n");
         poptPrintUsage(pc, stderr, 0);
         _exit(-1);
     } else if (mode == OP_AUTH && pin_mode == PIN_NONE) {
@@ -788,6 +891,12 @@ int main(int argc, const char *argv[])
                         "either --pin or --keypad must be specified.\n");
         poptPrintUsage(pc, stderr, 0);
         _exit(-1);
+    } else if (mode == OP_VERIFIY && cert_b64 == NULL) {
+        fprintf(stderr, "\nMissing certificate for verify operation, " \
+                        "--certificate base64_encoded_certificate " \
+                        "must be added.\n");
+        poptPrintUsage(pc, stderr, 0);
+        _exit(-1);
     }
 
     poptFreeContext(pc);
@@ -812,9 +921,7 @@ int main(int argc, const char *argv[])
 
     DEBUG(SSSDBG_TRACE_FUNC, "p11_child started.\n");
 
-    DEBUG(SSSDBG_TRACE_INTERNAL, "Running in [%s] mode.\n",
-          mode == OP_AUTH ? "auth"
-                          : (mode == OP_PREAUTH ? "pre-auth" : "unknown"));
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Running in [%s] mode.\n", op_mode_str(mode));
 
     DEBUG(SSSDBG_TRACE_INTERNAL,
           "Running with effective IDs: [%"SPRIuid"][%"SPRIgid"].\n",
@@ -847,6 +954,13 @@ int main(int argc, const char *argv[])
         goto fail;
     }
 
+    if (mode == OP_VERIFIY && !cert_verify_opts->do_verification) {
+        fprintf(stderr,
+                "Cannot run verification with option 'no_verification'.\n");
+        ret = EINVAL;
+        goto fail;
+    }
+
     if (mode == OP_AUTH && pin_mode == PIN_STDIN) {
         ret = p11c_recv_data(main_ctx, STDIN_FILENO, &pin);
         if (ret != EOK) {
@@ -855,10 +969,10 @@ int main(int argc, const char *argv[])
         }
     }
 
-    ret = do_work(main_ctx, nss_db, mode, pin, cert_verify_opts, module_name,
-                  token_name, key_id, &multi);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE, "do_work failed.\n");
+    ret = do_nss(main_ctx, mode, nss_db, cert_verify_opts, cert_b64,
+                 pin, module_name, token_name, key_id, &multi);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "do_nss failed.\n");
         goto fail;
     }
 

From 21162c0f49286948282166e8ad481bd2a988501d Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 19 Mar 2018 11:39:45 +0100
Subject: [PATCH 03/10] utils: add get_ssh_key_from_cert()

This new call only extracts the ssh key out of a certificate with the
help of NSS or OpenSSL without verifying the certificate.

Related to https://pagure.io/SSSD/sssd/issue/3489
---
 src/util/cert.h                |   4 ++
 src/util/cert/libcrypto/cert.c | 103 +++++++++++++++++++++++++++++++++++++
 src/util/cert/nss/cert.c       | 113 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 220 insertions(+)

diff --git a/src/util/cert.h b/src/util/cert.h
index 4598aa8df..111ef7707 100644
--- a/src/util/cert.h
+++ b/src/util/cert.h
@@ -52,4 +52,8 @@ errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db,
                         const uint8_t *der_blob, size_t der_size,
                         struct cert_verify_opts *cert_verify_opts,
                         uint8_t **key, size_t *key_size);
+
+errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx,
+                              uint8_t *der_blob, size_t der_size,
+                              uint8_t **key_blob, size_t *key_size);
 #endif /* __CERT_H__ */
diff --git a/src/util/cert/libcrypto/cert.c b/src/util/cert/libcrypto/cert.c
index 15de924a7..6d79502e4 100644
--- a/src/util/cert/libcrypto/cert.c
+++ b/src/util/cert/libcrypto/cert.c
@@ -276,3 +276,106 @@ errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db,
 
     return ret;
 }
+
+errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx,
+                              const uint8_t *der_blob, size_t der_size,
+                              uint8_t **key_blob, size_t *key_size)
+{
+    int ret;
+    size_t size;
+    const unsigned char *d;
+    uint8_t *buf = NULL;
+    size_t c;
+    X509 *cert = NULL;
+    EVP_PKEY *cert_pub_key = NULL;
+    const BIGNUM *n;
+    const BIGNUM *e;
+    int modulus_len;
+    unsigned char modulus[OPENSSL_RSA_MAX_MODULUS_BITS/8];
+    int exponent_len;
+    unsigned char exponent[OPENSSL_RSA_MAX_PUBEXP_BITS/8];
+
+    if (der_blob == NULL || der_size == 0) {
+        return EINVAL;
+    }
+
+    d = (const unsigned char *) der_blob;
+
+    cert = d2i_X509(NULL, &d, (int) der_size);
+    if (cert == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "d2i_X509 failed.\n");
+        return EINVAL;
+    }
+
+    cert_pub_key = X509_get_pubkey(cert);
+    if (cert_pub_key == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "X509_get_pubkey failed.\n");
+        ret = EIO;
+        goto done;
+    }
+
+    if (EVP_PKEY_base_id(cert_pub_key) != EVP_PKEY_RSA) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Expected RSA public key, found unsupported [%d].\n",
+              EVP_PKEY_base_id(cert_pub_key));
+        ret = EINVAL;
+        goto done;
+    }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+    RSA *rsa_pub_key = NULL;
+    rsa_pub_key = EVP_PKEY_get0_RSA(cert_pub_key);
+    if (rsa_pub_key == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    RSA_get0_key(rsa_pub_key, &n, &e, NULL);
+#else
+    n = cert_pub_key->pkey.rsa->n;
+    e = cert_pub_key->pkey.rsa->e;
+#endif
+    modulus_len = BN_bn2bin(n, modulus);
+    exponent_len = BN_bn2bin(e, exponent);
+
+    size = SSH_RSA_HEADER_LEN + 3 * sizeof(uint32_t)
+                + modulus_len
+                + exponent_len
+                + 1; /* see comment about missing 00 below */
+
+    buf = talloc_size(mem_ctx, size);
+    if (buf == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    c = 0;
+
+    SAFEALIGN_SET_UINT32(buf, htobe32(SSH_RSA_HEADER_LEN), &c);
+    safealign_memcpy(&buf[c], SSH_RSA_HEADER, SSH_RSA_HEADER_LEN, &c);
+    SAFEALIGN_SET_UINT32(&buf[c], htobe32(exponent_len), &c);
+    safealign_memcpy(&buf[c], exponent, exponent_len, &c);
+
+    /* Adding missing 00 which AFAIK is added to make sure
+     * the bigint is handled as positive number */
+    /* TODO: make a better check if 00 must be added or not, e.g. ... & 0x80)
+     */
+    SAFEALIGN_SET_UINT32(&buf[c], htobe32(modulus_len + 1), &c);
+    SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c);
+    safealign_memcpy(&buf[c], modulus, modulus_len, &c);
+
+    *key_blob = buf;
+    *key_size = size;
+
+    ret = EOK;
+
+done:
+    if (ret != EOK)  {
+        talloc_free(buf);
+    }
+    EVP_PKEY_free(cert_pub_key);
+    X509_free(cert);
+
+    return ret;
+}
diff --git a/src/util/cert/nss/cert.c b/src/util/cert/nss/cert.c
index 6fdce02ba..cb834c426 100644
--- a/src/util/cert/nss/cert.c
+++ b/src/util/cert/nss/cert.c
@@ -410,3 +410,116 @@ errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db,
 
     return ret;
 }
+
+errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx,
+                              uint8_t *der_blob, size_t der_size,
+                              uint8_t **key_blob, size_t *key_size)
+{
+    CERTCertDBHandle *handle;
+    CERTCertificate *cert = NULL;
+    SECItem der_item;
+    SECKEYPublicKey *cert_pub_key = NULL;
+    int ret;
+    size_t size;
+    uint8_t *buf = NULL;
+    size_t c;
+    size_t exponent_prefix_len;
+    size_t modulus_prefix_len;
+
+    if (der_blob == NULL || der_size == 0) {
+        return EINVAL;
+    }
+
+    /* initialize NSS if needed */
+    ret = nspr_nss_init();
+    if (ret != EOK) {
+        ret = EIO;
+        goto done;
+    }
+
+    handle = CERT_GetDefaultCertDB();
+
+    der_item.len = der_size;
+    der_item.data = discard_const(der_blob);
+
+    cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE);
+    if (cert == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    cert_pub_key = CERT_ExtractPublicKey(cert);
+    if (cert_pub_key == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "CERT_ExtractPublicKey failed.\n");
+        ret = EIO;
+        goto done;
+    }
+
+    if (cert_pub_key->keyType != rsaKey) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Expected RSA public key, found unsupported [%d].\n",
+              cert_pub_key->keyType);
+        ret = EINVAL;
+        goto done;
+    }
+
+    /* Looks like nss drops the leading 00 which AFAIK is added to make sure
+     * the bigint is handled as positive number if the leading bit is set. */
+    exponent_prefix_len = 0;
+    if (cert_pub_key->u.rsa.publicExponent.data[0] & 0x80) {
+        exponent_prefix_len = 1;
+    }
+
+    modulus_prefix_len = 0;
+    if (cert_pub_key->u.rsa.modulus.data[0] & 0x80) {
+        modulus_prefix_len = 1;
+    }
+    size = SSH_RSA_HEADER_LEN + 3 * sizeof(uint32_t)
+                + cert_pub_key->u.rsa.modulus.len
+                + cert_pub_key->u.rsa.publicExponent.len
+                + exponent_prefix_len + modulus_prefix_len;
+
+    buf = talloc_size(mem_ctx, size);
+    if (buf == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    c = 0;
+
+    SAFEALIGN_SET_UINT32(buf, htobe32(SSH_RSA_HEADER_LEN), &c);
+    safealign_memcpy(&buf[c], SSH_RSA_HEADER, SSH_RSA_HEADER_LEN, &c);
+    SAFEALIGN_SET_UINT32(&buf[c],
+                         htobe32(cert_pub_key->u.rsa.publicExponent.len
+                                    + exponent_prefix_len), &c);
+    if (exponent_prefix_len == 1) {
+        SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c);
+    }
+    safealign_memcpy(&buf[c], cert_pub_key->u.rsa.publicExponent.data,
+                     cert_pub_key->u.rsa.publicExponent.len, &c);
+
+    SAFEALIGN_SET_UINT32(&buf[c],
+                         htobe32(cert_pub_key->u.rsa.modulus.len
+                                    + modulus_prefix_len ), &c);
+    if (modulus_prefix_len == 1) {
+        SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c);
+    }
+    safealign_memcpy(&buf[c], cert_pub_key->u.rsa.modulus.data,
+                     cert_pub_key->u.rsa.modulus.len, &c);
+
+    *key_blob = buf;
+    *key_size = size;
+
+    ret = EOK;
+
+done:
+    if (ret != EOK)  {
+        talloc_free(buf);
+    }
+    SECKEY_DestroyPublicKey(cert_pub_key);
+    CERT_DestroyCertificate(cert);
+
+    return ret;
+}

From 4ae89edc4ff6543df1c7b5f47adee1f7386af0d6 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 19 Mar 2018 11:42:09 +0100
Subject: [PATCH 04/10] utils: move p11 child paths to util.h

To allow other responders to call p11_child too, some general defines
are moved to a common place.

Related to https://pagure.io/SSSD/sssd/issue/3489
---
 src/responder/pam/pamsrv_cmd.c | 1 -
 src/responder/pam/pamsrv_p11.c | 6 ------
 src/util/util.h                | 6 ++++++
 3 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 8610b6b80..94867a0fe 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -1308,7 +1308,6 @@ static errno_t check_cert(TALLOC_CTX *mctx,
                           struct pam_data *pd)
 {
     int p11_child_timeout;
-    const int P11_CHILD_TIMEOUT_DEFAULT = 10;
     char *cert_verification_opts;
     errno_t ret;
     struct tevent_req *req;
diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c
index 0c9822fe9..0b6a162a4 100644
--- a/src/responder/pam/pamsrv_p11.c
+++ b/src/responder/pam/pamsrv_p11.c
@@ -30,12 +30,6 @@
 #include "util/crypto/sss_crypto.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"
 #define CERT_AUTH_DEFAULT_MATCHING_RULE "KRB5:<EKU>clientAuth"
 
 struct cert_auth_info {
diff --git a/src/util/util.h b/src/util/util.h
index ef8ef7f57..4657ab0c6 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -710,4 +710,10 @@ int sss_create_dir(const char *parent_dir_path,
 int selinux_file_context(const char *dst_name);
 int reset_selinux_file_context(void);
 
+#ifdef SSSD_LIBEXEC_PATH
+#define P11_CHILD_LOG_FILE "p11_child"
+#define P11_CHILD_PATH SSSD_LIBEXEC_PATH"/p11_child"
+#define P11_CHILD_TIMEOUT_DEFAULT 10
+#endif  /* SSSD_LIBEXEC_PATH */
+
 #endif /* __SSSD_UTIL_H__ */

From d44eb5d180000765cdafc02b4453a725101866e9 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 19 Mar 2018 12:57:01 +0100
Subject: [PATCH 05/10] utils: add cert_to_ssh_key request

With this new request p11_child is used to verify the certificate before
the ssh key is extracted.

Related to https://pagure.io/SSSD/sssd/issue/3489
---
 Makefile.am                           |   3 +
 src/tests/dlopen-tests.c              |   3 +-
 src/util/cert.h                       |  11 ++
 src/util/cert/cert_common_p11_child.c | 328 ++++++++++++++++++++++++++++++++++
 4 files changed, 344 insertions(+), 1 deletion(-)
 create mode 100644 src/util/cert/cert_common_p11_child.c

diff --git a/Makefile.am b/Makefile.am
index 9c65aede4..a5f4c4a06 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -921,6 +921,7 @@ if HAVE_NSS
 
     SSS_CERT_SOURCES = \
         src/util/cert/cert_common.c \
+        src/util/cert/cert_common_p11_child.c \
         src/util/cert/nss/cert.c \
         $(NULL)
     SSS_CERT_CFLAGS = \
@@ -943,6 +944,7 @@ else
 
     SSS_CERT_SOURCES = \
         src/util/cert/cert_common.c \
+        src/util/cert/cert_common_p11_child.c \
         src/util/cert/libcrypto/cert.c \
         $(NULL)
     SSS_CERT_CFLAGS = \
@@ -980,6 +982,7 @@ libsss_cert_la_CFLAGS = \
 libsss_cert_la_LIBADD = \
     $(SSS_CERT_LIBS) \
     $(TALLOC_LIBS) \
+    $(TEVENT_LIBS) \
     libsss_crypt.la \
     libsss_debug.la \
     libsss_certmap.la \
diff --git a/src/tests/dlopen-tests.c b/src/tests/dlopen-tests.c
index 3914317de..fb7eb0625 100644
--- a/src/tests/dlopen-tests.c
+++ b/src/tests/dlopen-tests.c
@@ -77,7 +77,8 @@ struct so {
     { "libsss_child.so", { LIBPFX"libsss_util.so",
                            LIBPFX"libsss_child.so", NULL } },
     { "libsss_crypt.so", { LIBPFX"libsss_crypt.so", NULL } },
-    { "libsss_cert.so", { LIBPFX"libsss_cert.so", NULL } },
+    { "libsss_cert.so", { LIBPFX"libsss_util.so",
+                          LIBPFX"libsss_cert.so", NULL } },
     { "libsss_util.so", { LIBPFX"libsss_util.so", NULL } },
     { "libsss_simple.so", { LIBPFX"libdlopen_test_providers.so",
                             LIBPFX"libsss_simple.so", NULL } },
diff --git a/src/util/cert.h b/src/util/cert.h
index 111ef7707..d52802956 100644
--- a/src/util/cert.h
+++ b/src/util/cert.h
@@ -56,4 +56,15 @@ errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db,
 errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx,
                               uint8_t *der_blob, size_t der_size,
                               uint8_t **key_blob, size_t *key_size);
+
+struct tevent_req *cert_to_ssh_key_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        int child_debug_fd, time_t timeout,
+                                        const char *ca_db,
+                                        size_t cert_count,
+                                        struct ldb_val *bin_certs,
+                                        const char *verify_opts);
+
+errno_t cert_to_ssh_key_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                             struct ldb_val **keys, size_t *valid_keys);
 #endif /* __CERT_H__ */
diff --git a/src/util/cert/cert_common_p11_child.c b/src/util/cert/cert_common_p11_child.c
new file mode 100644
index 000000000..99f17edce
--- /dev/null
+++ b/src/util/cert/cert_common_p11_child.c
@@ -0,0 +1,328 @@
+/*
+   SSSD - certificate handling utils
+
+   Copyright (C) Sumit Bose <sbose@redhat.com> 2018
+
+   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/util.h"
+#include "util/cert.h"
+#include "util/crypto/sss_crypto.h"
+#include "util/child_common.h"
+
+struct cert_to_ssh_key_state {
+    struct tevent_context *ev;
+    int child_debug_fd;
+    time_t timeout;
+    const char **extra_args;
+    const char **certs;
+    struct ldb_val *bin_certs;
+    struct ldb_val *keys;
+    size_t cert_count;
+    size_t iter;
+    size_t valid_keys;
+
+    struct sss_child_ctx_old *child_ctx;
+    struct tevent_timer *timeout_handler;
+    struct child_io_fds *io;
+};
+
+static errno_t cert_to_ssh_key_step(struct tevent_req *req);
+static void cert_to_ssh_key_done(int child_status,
+                                 struct tevent_signal *sige,
+                                 void *pvt);
+
+struct tevent_req *cert_to_ssh_key_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        int child_debug_fd, time_t timeout,
+                                        const char *ca_db,
+                                        size_t cert_count,
+                                        struct ldb_val *bin_certs,
+                                        const char *verify_opts)
+{
+    struct tevent_req *req;
+    struct cert_to_ssh_key_state *state;
+    size_t arg_c;
+    size_t c;
+    int ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct cert_to_ssh_key_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    if (ca_db == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing NSS DB.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    state->ev = ev;
+    state->child_debug_fd = (child_debug_fd == -1) ? STDERR_FILENO
+                                                   : child_debug_fd;
+    state->timeout = timeout;
+    state->bin_certs = bin_certs;
+    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);
+
+    state->keys = talloc_zero_array(state, struct ldb_val, cert_count);
+    if (state->keys == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+    state->valid_keys = 0;
+
+    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;
+    }
+    /* extra_args are added in revers order, base64 encoded certificate is
+     * added at 0 */
+    arg_c = 1;
+    state->extra_args[arg_c++] = "--certificate";
+    state->extra_args[arg_c++] = ca_db;
+    state->extra_args[arg_c++] = "--nssdb";
+    if (verify_opts != NULL) {
+        state->extra_args[arg_c++] = verify_opts;
+        state->extra_args[arg_c++] = "--verify";
+    }
+    state->extra_args[arg_c++] = "--verification";
+
+    state->certs = talloc_zero_array(state, const char *, cert_count);
+    if (state->certs == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (c = 0; c < cert_count; c++) {
+        state->certs[c] = sss_base64_encode(state->certs, bin_certs[c].data,
+                                            bin_certs[c].length);
+        if (state->certs[c] == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n");
+            ret = EINVAL;
+            goto done;
+        }
+    }
+
+    state->cert_count = cert_count;
+    state->iter = 0;
+
+    ret = cert_to_ssh_key_step(req);
+
+done:
+    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 cert_to_ssh_key_state *state =
+                             tevent_req_data(req, struct cert_to_ssh_key_state);
+
+    DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for p11_child.\n");
+    child_handler_destroy(state->child_ctx);
+    state->child_ctx = NULL;
+    tevent_req_error(req, ERR_P11_CHILD);
+}
+
+static errno_t cert_to_ssh_key_step(struct tevent_req *req)
+{
+    struct cert_to_ssh_key_state *state = tevent_req_data(req,
+                                                  struct cert_to_ssh_key_state);
+    int ret;
+    int pipefd_from_child[2] = PIPE_INIT;
+    int pipefd_to_child[2] = PIPE_INIT;
+    pid_t child_pid;
+    struct timeval tv;
+
+    if (state-> iter >= state->cert_count) {
+        return EOK;
+    }
+
+    state->extra_args[0] = state->certs[state->iter];
+
+    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->child_debug_fd, 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, cert_to_ssh_key_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 void cert_to_ssh_key_done(int child_status,
+                                 struct tevent_signal *sige,
+                                 void *pvt)
+{
+    struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+    struct cert_to_ssh_key_state *state = tevent_req_data(req,
+                                                  struct cert_to_ssh_key_state);
+    int ret;
+    bool valid = false;
+
+    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->certs[state->iter]);
+        ret = get_ssh_key_from_cert(state->keys,
+                                    state->bin_certs[state->iter].data,
+                                    state->bin_certs[state->iter].length,
+                                    &state->keys[state->iter].data,
+                                    &state->keys[state->iter].length);
+        if (ret == EOK) {
+            state->valid_keys++;
+        } else {
+            DEBUG(SSSDBG_OP_FAILURE, "get_ssh_key_from_cert failed, "
+                                     "skipping certificate [%s].\n",
+                                     state->certs[state->iter]);
+            state->keys[state->iter].data = NULL;
+            state->keys[state->iter].length = 0;
+        }
+    } else {
+        DEBUG(SSSDBG_MINOR_FAILURE, "Certificate [%s] is not valid.\n",
+                                    state->certs[state->iter]);
+        state->keys[state->iter].data = NULL;
+        state->keys[state->iter].length = 0;
+    }
+
+    state->iter++;
+    ret = cert_to_ssh_key_step(req);
+
+    if (ret != EAGAIN) {
+        if (ret == EOK) {
+            tevent_req_done(req);
+        } else {
+            tevent_req_error(req, ret);
+        }
+    }
+
+    return;
+}
+
+errno_t cert_to_ssh_key_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                             struct ldb_val **keys, size_t *valid_keys)
+{
+    struct cert_to_ssh_key_state *state = tevent_req_data(req,
+                                                  struct cert_to_ssh_key_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (keys != NULL) {
+        *keys = talloc_steal(mem_ctx, state->keys);
+    }
+
+    if (valid_keys != NULL) {
+        *valid_keys = state->valid_keys;
+    }
+
+    return EOK;
+}

From c40f964a6ac7701d70ee20102d15a6e2556c01c9 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 19 Mar 2018 11:34:39 +0100
Subject: [PATCH 06/10] tests: add test for cert_to_ssh_key request

This patch adds cmocka base until tests for the new cert_to_ssh_key
request

There seems to be a memory leak in bash if a called binary returns an
error code. Since p11_child is called via a libtool wrapper in the unit
tests and returns an error code if a certificate is invalid this will be
reported during the CI valgrind run. To make the CI pass here a
suppression is added.

Related to https://pagure.io/SSSD/sssd/issue/3489
---
 Makefile.am                        |   2 +
 contrib/ci/sssd.supp               |  29 +++++
 src/tests/cmocka/test_cert_utils.c | 228 +++++++++++++++++++++++++++++++++++++
 3 files changed, 259 insertions(+)

diff --git a/Makefile.am b/Makefile.am
index a5f4c4a06..df8955d82 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3283,9 +3283,11 @@ test_krb5_wait_queue_LDADD = \
 
 test_cert_utils_SOURCES = \
     src/tests/cmocka/test_cert_utils.c \
+    src/util/cert/cert_common_p11_child.c \
     $(NULL)
 test_cert_utils_CFLAGS = \
     $(AM_CFLAGS) \
+    -U SSSD_LIBEXEC_PATH -DSSSD_LIBEXEC_PATH=\"$(abs_builddir)\" \
     -I$(abs_builddir)/src \
     $(CRYPTO_CFLAGS) \
     $(NULL)
diff --git a/contrib/ci/sssd.supp b/contrib/ci/sssd.supp
index 8f27708bd..e46e1129d 100644
--- a/contrib/ci/sssd.supp
+++ b/contrib/ci/sssd.supp
@@ -171,3 +171,32 @@
    fun:_cmocka_run_group_tests
    fun:main
 }
+
+# Leaks in bash if p11_child returns and error because due to libtool the
+# p11_child binary is not called directly during the unit tests but with the
+# help of a libtool wrapper
+{
+   bash-calling-p11-child-returning-error
+   Memcheck:Leak
+   ...
+   fun:malloc
+   fun:xmalloc
+   ...
+   fun:execute_command_internal
+   ...
+   fun:execute_command_internal
+   ...
+}
+
+{
+   bash-calling-p11-child-returning-error-debian
+   Memcheck:Free
+   fun:free
+   obj:/usr/bin/bash
+   fun:run_unwind_frame
+   fun:parse_and_execute
+   fun:command_substitute
+   obj:/usr/bin/bash
+   obj:/usr/bin/bash
+   ...
+}
diff --git a/src/tests/cmocka/test_cert_utils.c b/src/tests/cmocka/test_cert_utils.c
index dd58b73a7..077a8b0a2 100644
--- a/src/tests/cmocka/test_cert_utils.c
+++ b/src/tests/cmocka/test_cert_utils.c
@@ -24,6 +24,7 @@
 #include "config.h"
 
 #include <popt.h>
+#include <tevent.h>
 #ifdef HAVE_LIBCRYPTO
 #include <openssl/objects.h>
 #include <openssl/crypto.h>
@@ -37,11 +38,20 @@
 #ifdef HAVE_TEST_CA
 #include "tests/test_CA/SSSD_test_cert_pubsshkey_0001.h"
 #include "tests/test_CA/SSSD_test_cert_x509_0001.h"
+#include "tests/test_CA/SSSD_test_cert_pubsshkey_0002.h"
+#include "tests/test_CA/SSSD_test_cert_x509_0002.h"
 #else
 #define SSSD_TEST_CERT_0001 ""
 #define SSSD_TEST_CERT_SSH_KEY_0001 ""
+#define SSSD_TEST_CERT_0002 ""
+#define SSSD_TEST_CERT_SSH_KEY_0002 ""
 #endif
 
+/* When run under valgrind with --trace-children=yes we have to increase the
+ * timeout not because p11_child needs much more time under valgrind but
+ * because of the way valgrind handles the children. */
+#define P11_CHILD_TIMEOUT 40
+
 /* TODO: create a certificate for this test */
 const uint8_t test_cert_der[] = {
 0x30, 0x82, 0x04, 0x09, 0x30, 0x82, 0x02, 0xf1, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x09,
@@ -168,6 +178,7 @@ TEST_CERT_PEM
 
 struct test_state {
     void *dummy;
+    bool done;
 };
 
 static int setup(void **state)
@@ -364,6 +375,215 @@ void test_cert_to_ssh_key(void **state)
     talloc_free(exp_key);
 }
 
+void test_cert_to_ssh_key_done(struct tevent_req *req)
+{
+    int ret;
+    struct test_state *ts = tevent_req_callback_data(req, struct test_state);
+    struct ldb_val *keys;
+    uint8_t *exp_key;
+    size_t exp_key_size;
+    size_t valid_keys;
+
+    assert_non_null(ts);
+    ts->done = true;
+
+    ret = cert_to_ssh_key_recv(req, ts, &keys, &valid_keys);
+    talloc_free(req);
+    assert_int_equal(ret, 0);
+    assert_non_null(keys[0].data);
+    assert_int_equal(valid_keys, 1);
+
+    exp_key = sss_base64_decode(ts, SSSD_TEST_CERT_SSH_KEY_0001, &exp_key_size);
+    assert_non_null(exp_key);
+    assert_int_equal(keys[0].length, exp_key_size);
+    assert_memory_equal(keys[0].data, exp_key, exp_key_size);
+
+    talloc_free(exp_key);
+    talloc_free(keys);
+}
+
+void test_cert_to_ssh_key_send(void **state)
+{
+    struct tevent_context *ev;
+    struct tevent_req *req;
+    struct ldb_val val[1];
+
+    struct test_state *ts = talloc_get_type_abort(*state, struct test_state);
+    assert_non_null(ts);
+    ts->done = false;
+
+    val[0].data = sss_base64_decode(ts, SSSD_TEST_CERT_0001, &val[0].length);
+    assert_non_null(val[0].data);
+
+    ev = tevent_context_init(ts);
+    assert_non_null(ev);
+
+    req = cert_to_ssh_key_send(ts, ev, -1, P11_CHILD_TIMEOUT,
+                            "sql:" ABS_BUILD_DIR "/src/tests/test_CA/p11_nssdb",
+                            1, &val[0], NULL);
+    assert_non_null(req);
+
+    tevent_req_set_callback(req, test_cert_to_ssh_key_done, ts);
+
+    while (!ts->done) {
+        tevent_loop_once(ev);
+    }
+
+    talloc_free(val[0].data);
+    talloc_free(ev);
+}
+
+void test_cert_to_ssh_2keys_done(struct tevent_req *req)
+{
+    int ret;
+    struct test_state *ts = tevent_req_callback_data(req, struct test_state);
+    struct ldb_val *keys;
+    uint8_t *exp_key;
+    size_t exp_key_size;
+    size_t valid_keys;
+
+    assert_non_null(ts);
+    ts->done = true;
+
+    ret = cert_to_ssh_key_recv(req, ts, &keys, &valid_keys);
+    talloc_free(req);
+    assert_int_equal(ret, 0);
+    assert_non_null(keys[0].data);
+    assert_non_null(keys[1].data);
+    assert_int_equal(valid_keys, 2);
+
+    exp_key = sss_base64_decode(ts, SSSD_TEST_CERT_SSH_KEY_0001, &exp_key_size);
+    assert_non_null(exp_key);
+    assert_int_equal(keys[0].length, exp_key_size);
+    assert_memory_equal(keys[0].data, exp_key, exp_key_size);
+    talloc_free(exp_key);
+
+    exp_key = sss_base64_decode(ts, SSSD_TEST_CERT_SSH_KEY_0002, &exp_key_size);
+    assert_non_null(exp_key);
+    assert_int_equal(keys[1].length, exp_key_size);
+    assert_memory_equal(keys[1].data, exp_key, exp_key_size);
+    talloc_free(exp_key);
+
+    talloc_free(keys);
+}
+
+void test_cert_to_ssh_2keys_send(void **state)
+{
+    struct tevent_context *ev;
+    struct tevent_req *req;
+    struct ldb_val val[2];
+
+    struct test_state *ts = talloc_get_type_abort(*state, struct test_state);
+    assert_non_null(ts);
+    ts->done = false;
+
+    val[0].data = sss_base64_decode(ts, SSSD_TEST_CERT_0001,
+                                          &val[0].length);
+    assert_non_null(val[0].data);
+
+    val[1].data = sss_base64_decode(ts, SSSD_TEST_CERT_0002,
+                                          &val[1].length);
+    assert_non_null(val[1].data);
+
+    ev = tevent_context_init(ts);
+    assert_non_null(ev);
+
+    req = cert_to_ssh_key_send(ts, ev, -1, P11_CHILD_TIMEOUT,
+                            "sql:" ABS_BUILD_DIR "/src/tests/test_CA/p11_nssdb",
+                            2, &val[0], NULL);
+    assert_non_null(req);
+
+    tevent_req_set_callback(req, test_cert_to_ssh_2keys_done, ts);
+
+    while (!ts->done) {
+        tevent_loop_once(ev);
+    }
+
+    talloc_free(val[0].data);
+    talloc_free(val[1].data);
+    talloc_free(ev);
+}
+
+void test_cert_to_ssh_2keys_invalid_done(struct tevent_req *req)
+{
+    int ret;
+    struct test_state *ts = tevent_req_callback_data(req, struct test_state);
+    struct ldb_val *keys;
+    uint8_t *exp_key;
+    size_t exp_key_size;
+    size_t valid_keys;
+
+    assert_non_null(ts);
+    ts->done = true;
+
+    ret = cert_to_ssh_key_recv(req, ts, &keys, &valid_keys);
+    talloc_free(req);
+    assert_int_equal(ret, 0);
+    assert_non_null(keys[0].data);
+    assert_null(keys[1].data);
+    assert_int_equal(keys[1].length, 0);
+    assert_non_null(keys[2].data);
+    assert_int_equal(valid_keys, 2);
+
+    exp_key = sss_base64_decode(ts, SSSD_TEST_CERT_SSH_KEY_0001, &exp_key_size);
+    assert_non_null(exp_key);
+    assert_int_equal(keys[0].length, exp_key_size);
+    assert_memory_equal(keys[0].data, exp_key, exp_key_size);
+    talloc_free(exp_key);
+
+    exp_key = sss_base64_decode(ts, SSSD_TEST_CERT_SSH_KEY_0002, &exp_key_size);
+    assert_non_null(exp_key);
+    assert_int_equal(keys[2].length, exp_key_size);
+    assert_memory_equal(keys[2].data, exp_key, exp_key_size);
+    talloc_free(exp_key);
+
+    talloc_free(keys);
+}
+
+void test_cert_to_ssh_2keys_invalid_send(void **state)
+{
+    struct tevent_context *ev;
+    struct tevent_req *req;
+    struct ldb_val val[3];
+
+    struct test_state *ts = talloc_get_type_abort(*state, struct test_state);
+    assert_non_null(ts);
+    ts->done = false;
+
+    val[0].data = sss_base64_decode(ts, SSSD_TEST_CERT_0001,
+                                          &val[0].length);
+    assert_non_null(val[0].data);
+
+    val[1].data = sss_base64_decode(ts, SSSD_TEST_CERT_0002,
+                                          &val[1].length);
+    assert_non_null(val[1].data);
+    /* flip last bit to make the certificate invalid */
+    val[1].data[val[1].length - 1] ^= 1 << 0;
+
+    val[2].data = sss_base64_decode(ts, SSSD_TEST_CERT_0002,
+                                          &val[2].length);
+    assert_non_null(val[2].data);
+
+    ev = tevent_context_init(ts);
+    assert_non_null(ev);
+
+    req = cert_to_ssh_key_send(ts, ev, -1, P11_CHILD_TIMEOUT,
+                            "sql:" ABS_BUILD_DIR "/src/tests/test_CA/p11_nssdb",
+                            3, &val[0], NULL);
+    assert_non_null(req);
+
+    tevent_req_set_callback(req, test_cert_to_ssh_2keys_invalid_done, ts);
+
+    while (!ts->done) {
+        tevent_loop_once(ev);
+    }
+
+    talloc_free(val[0].data);
+    talloc_free(val[1].data);
+    talloc_free(val[2].data);
+    talloc_free(ev);
+}
+
 int main(int argc, const char *argv[])
 {
     poptContext pc;
@@ -388,9 +608,17 @@ int main(int argc, const char *argv[])
                                         setup, teardown),
         cmocka_unit_test_setup_teardown(test_sss_cert_derb64_to_ldap_filter,
                                         setup, teardown),
+#ifdef HAVE_NSS
 #ifdef HAVE_TEST_CA
         cmocka_unit_test_setup_teardown(test_cert_to_ssh_key,
                                         setup, teardown),
+        cmocka_unit_test_setup_teardown(test_cert_to_ssh_key_send,
+                                        setup, teardown),
+        cmocka_unit_test_setup_teardown(test_cert_to_ssh_2keys_send,
+                                        setup, teardown),
+        cmocka_unit_test_setup_teardown(test_cert_to_ssh_2keys_invalid_send,
+                                        setup, teardown),
+#endif
 #endif
     };
 

From c59687146274c9aa8fab676afd441ea1cf49d754 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 19 Mar 2018 17:23:35 +0100
Subject: [PATCH 07/10] ssh: use cert_to_ssh_key request to verify certifcate
 and get keys

Related to https://pagure.io/SSSD/sssd/issue/3489
---
 src/responder/ssh/ssh_private.h  |  16 +-
 src/responder/ssh/ssh_protocol.c |  43 ++++-
 src/responder/ssh/ssh_reply.c    | 399 ++++++++++++++++++++++-----------------
 3 files changed, 275 insertions(+), 183 deletions(-)

diff --git a/src/responder/ssh/ssh_private.h b/src/responder/ssh/ssh_private.h
index f32112e89..a7a9ad09d 100644
--- a/src/responder/ssh/ssh_private.h
+++ b/src/responder/ssh/ssh_private.h
@@ -56,10 +56,22 @@ void ssh_protocol_reply(struct cli_ctx *cli_ctx,
 errno_t
 ssh_protocol_done(struct cli_ctx *cli_ctx, errno_t error);
 
+struct tevent_req * ssh_get_output_keys_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        struct cli_ctx *cli_ctx,
+                                        struct sss_domain_info *domain,
+                                        struct ldb_message *msg);
+
+errno_t ssh_get_output_keys_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                                 struct sized_string *name,
+                                 struct ldb_message_element ***elements,
+                                 uint32_t *num_keys);
+
 errno_t
 ssh_protocol_build_reply(struct sss_packet *packet,
-                         struct ssh_ctx *ssh_ctx,
-                         struct cache_req_result *result);
+                         struct sized_string name,
+                         struct ldb_message_element **elements,
+                         uint32_t num_keys);
 
 errno_t
 ssh_update_known_hosts_file(struct sss_domain_info *domains,
diff --git a/src/responder/ssh/ssh_protocol.c b/src/responder/ssh/ssh_protocol.c
index 4198f8214..5a9081bb7 100644
--- a/src/responder/ssh/ssh_protocol.c
+++ b/src/responder/ssh/ssh_protocol.c
@@ -72,15 +72,50 @@ ssh_protocol_done(struct cli_ctx *cli_ctx, errno_t error)
     return EOK;
 }
 
+static void got_ssh_keys(struct tevent_req *req);
 void ssh_protocol_reply(struct cli_ctx *cli_ctx,
                         struct cache_req_result *result)
 {
-    struct cli_protocol *pctx;
-    struct ssh_ctx *ssh_ctx;
     errno_t ret;
+    struct tevent_req *req;
+
+    /* Make sure we have the results around until the end of the request. To
+     * avoid copying and memory allocation the keys and certificates from the
+     * result will be referenced during the next requests, so they should not
+     * be freed too early. */
+    result = talloc_steal(cli_ctx, result);
+
+    req = ssh_get_output_keys_send(cli_ctx, cli_ctx->ev, cli_ctx,
+                                   result->domain, result->msgs[0]);
+    if (req == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_get_output_keys_send failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+    tevent_req_set_callback(req, got_ssh_keys, cli_ctx);
+
+    return;
+
+done:
+    ssh_protocol_done(cli_ctx, ret);
+}
 
+static void got_ssh_keys(struct tevent_req *req)
+{
+    errno_t ret;
+    struct cli_ctx *cli_ctx = tevent_req_callback_data(req, struct cli_ctx);
+    struct cli_protocol *pctx;
     pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
-    ssh_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct ssh_ctx);
+    struct ldb_message_element **elements;
+    uint32_t num_keys;
+    struct sized_string name;
+
+    ret = ssh_get_output_keys_recv(req, cli_ctx, &name, &elements, &num_keys);
+    talloc_zfree(req);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_get_output_keys_revc failed");
+        goto done;
+    }
 
     ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in),
                          &pctx->creq->out);
@@ -88,7 +123,7 @@ void ssh_protocol_reply(struct cli_ctx *cli_ctx,
         goto done;
     }
 
-    ret = ssh_protocol_build_reply(pctx->creq->out, ssh_ctx, result);
+    ret = ssh_protocol_build_reply(pctx->creq->out, name, elements, num_keys);
     if (ret != EOK) {
         goto done;
     }
diff --git a/src/responder/ssh/ssh_reply.c b/src/responder/ssh/ssh_reply.c
index 1bb9d3318..478b1ab8d 100644
--- a/src/responder/ssh/ssh_reply.c
+++ b/src/responder/ssh/ssh_reply.c
@@ -37,95 +37,6 @@
  * should be used with care if libldb's I/O operations are involved. */
 #define SSS_EL_FLAG_BIN_DATA (1<<4)
 
-static errno_t get_valid_certs_keys(TALLOC_CTX *mem_ctx,
-                                    struct ssh_ctx *ssh_ctx,
-                                    struct ldb_message_element *el_cert,
-                                    struct ldb_message_element **_el_res)
-{
-    TALLOC_CTX *tmp_ctx;
-    uint8_t *key;
-    size_t key_len;
-    char *cert_verification_opts;
-    struct cert_verify_opts *cert_verify_opts;
-    int ret;
-    struct ldb_message_element *el_res;
-    size_t d;
-
-    if (el_cert == NULL) {
-        DEBUG(SSSDBG_TRACE_ALL, "Mssing element, nothing to do.\n");
-        return EOK;
-    }
-
-    tmp_ctx = talloc_new(NULL);
-    if (tmp_ctx == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
-        return ENOMEM;
-    }
-
-    ret = confdb_get_string(ssh_ctx->rctx->cdb, tmp_ctx,
-                            CONFDB_MONITOR_CONF_ENTRY,
-                            CONFDB_MONITOR_CERT_VERIFICATION, NULL,
-                            &cert_verification_opts);
-    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 = parse_cert_verify_opts(tmp_ctx, cert_verification_opts,
-                                 &cert_verify_opts);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_FATAL_FAILURE,
-              "Failed to parse verifiy option.\n");
-        goto done;
-    }
-
-    el_res = talloc_zero(tmp_ctx, struct ldb_message_element);
-    if (el_res == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
-        ret = ENOMEM;
-        goto done;
-    }
-
-    el_res->values = talloc_array(el_res, struct ldb_val, el_cert->num_values);
-    if (el_res->values == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
-        ret = ENOMEM;
-        goto done;
-    }
-
-    for (d = 0; d < el_cert->num_values; d++) {
-            ret = cert_to_ssh_key(tmp_ctx, ssh_ctx->ca_db,
-                                  el_cert->values[d].data,
-                                  el_cert->values[d].length,
-                                  cert_verify_opts, &key, &key_len);
-            if (ret != EOK) {
-                DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key failed, ignoring.\n");
-                continue;
-            }
-
-            el_res->values[el_res->num_values].data =
-                                              talloc_steal(el_res->values, key);
-            el_res->values[el_res->num_values].length = key_len;
-            el_res->num_values++;
-    }
-
-    if (el_res->num_values == 0) {
-        *_el_res = NULL;
-    } else {
-        *_el_res = talloc_steal(mem_ctx, el_res);
-    }
-
-    ret = EOK;
-
-done:
-
-    talloc_free(tmp_ctx);
-
-    return ret;
-}
-
 static errno_t decode_and_add_base64_data(struct sss_packet *packet,
                                           struct ldb_message_element *el,
                                           bool skip_base64_decode,
@@ -153,6 +64,10 @@ static errno_t decode_and_add_base64_data(struct sss_packet *packet,
     }
 
     for (d = 0; d < el->num_values; d++) {
+        if (el->values[d].length == 0 && el->values[d].data == NULL) {
+            /* skip empty keys, e.g. due to invalid certificate */
+            continue;
+        }
         if (skip_base64_decode || (el->flags & SSS_EL_FLAG_BIN_DATA)) {
             key = el->values[d].data;
             key_len = el->values[d].length;
@@ -190,140 +105,271 @@ static errno_t decode_and_add_base64_data(struct sss_packet *packet,
     return ret;
 }
 
-static errno_t
-ssh_get_output_keys(TALLOC_CTX *mem_ctx,
-                    struct ssh_ctx *ssh_ctx,
-                    struct sss_domain_info *domain,
-                    struct ldb_message *msg,
-                    struct ldb_message_element ***_elements,
-                    uint32_t *_num_keys)
-{
-    struct ldb_message_element **elements;
+struct ssh_get_output_keys_state {
+    struct tevent_context *ev;
+    struct cli_ctx *cli_ctx;
+    struct ldb_message *msg;
+    char *cert_verification_opts;
+    int p11_child_timeout;
+    struct ssh_ctx *ssh_ctx;
     struct ldb_message_element *user_cert;
-    uint32_t num_keys = 0;
-    uint32_t i = 0;
+    struct ldb_message_element *user_cert_override;
+    struct ldb_message_element *current_cert;
+
+    const char *name;
+    struct ldb_message_element **elements;
+    uint32_t num_keys;
+    size_t iter;
+};
+
+void ssh_get_output_keys_done(struct tevent_req *subreq);
+
+struct tevent_req *ssh_get_output_keys_send(TALLOC_CTX *mem_ctx,
+                                            struct tevent_context *ev,
+                                            struct cli_ctx *cli_ctx,
+                                            struct sss_domain_info *domain,
+                                            struct ldb_message *msg)
+{
+    struct tevent_req *req;
+    struct tevent_req *subreq;
     errno_t ret;
+    struct ssh_get_output_keys_state *state;
 
-    elements = talloc_zero_array(mem_ctx, struct ldb_message_element *, 6);
-    if (elements == NULL) {
-        return ENOMEM;
+    req = tevent_req_create(mem_ctx, &state, struct ssh_get_output_keys_state);
+    if (req == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n");
+        return NULL;
     }
 
-    elements[i] = ldb_msg_find_element(msg, SYSDB_SSH_PUBKEY);
-    if (elements[i] != NULL) {
-        num_keys += elements[i]->num_values;
-        i++;
+    state->ev = ev;
+    state->cli_ctx = cli_ctx;
+    state->msg = msg;
+    state->num_keys = 0;
+    state->iter = 0;
+    state->ssh_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct ssh_ctx);
+    if (state->ssh_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "Missing ssh responder context.\n");
+        ret = EINVAL;
+        goto done;
     }
 
-    elements[i] = ldb_msg_find_element(msg, ORIGINALAD_PREFIX SYSDB_SSH_PUBKEY);
-    if (elements[i] != NULL) {
-        num_keys += elements[i]->num_values;
-        i++;
+    state->name = ldb_msg_find_attr_as_string(state->msg, SYSDB_NAME, NULL);
+    if (state->name == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "Missing name.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    state->elements = talloc_zero_array(state, struct ldb_message_element *, 6);
+    if (state->elements == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    state->elements[state->iter] = ldb_msg_find_element(state->msg,
+                                                        SYSDB_SSH_PUBKEY);
+    if (state->elements[state->iter] != NULL) {
+        state->num_keys += state->elements[state->iter]->num_values;
+        state->iter++;
+    }
+
+    state->elements[state->iter] = ldb_msg_find_element(state->msg,
+                                            ORIGINALAD_PREFIX SYSDB_SSH_PUBKEY);
+    if (state->elements[state->iter] != NULL) {
+        state->num_keys += state->elements[state->iter]->num_values;
+        state->iter++;
     }
 
     if (DOM_HAS_VIEWS(domain)) {
-        elements[i] = ldb_msg_find_element(msg, OVERRIDE_PREFIX SYSDB_SSH_PUBKEY);
-        if (elements[i] != NULL) {
-            num_keys += elements[i]->num_values;
-            i++;
+        state->elements[state->iter] = ldb_msg_find_element(state->msg,
+                                              OVERRIDE_PREFIX SYSDB_SSH_PUBKEY);
+        if (state->elements[state->iter] != NULL) {
+            state->num_keys += state->elements[state->iter]->num_values;
+            state->iter++;
         }
     }
 
-    user_cert = ldb_msg_find_element(msg, SYSDB_USER_CERT);
-    if (user_cert != NULL) {
-        ret = get_valid_certs_keys(elements, ssh_ctx, user_cert, &elements[i]);
+    ret = confdb_get_string(cli_ctx->rctx->cdb, state,
+                            CONFDB_MONITOR_CONF_ENTRY,
+                            CONFDB_MONITOR_CERT_VERIFICATION, NULL,
+                            &state->cert_verification_opts);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to read verification options from confdb: [%d] %s\n",
+              ret, sss_strerror(ret));
+        goto done;
+    }
+
+    state->p11_child_timeout = -1;
+    ret = confdb_get_int(cli_ctx->rctx->cdb, CONFDB_SSH_CONF_ENTRY,
+                         CONFDB_PAM_P11_CHILD_TIMEOUT, -1,
+                         &state->p11_child_timeout);
+    if (ret != EOK || state->p11_child_timeout == -1) {
+        /* check pam configuration as well or use default */
+        ret = confdb_get_int(cli_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY,
+                             CONFDB_PAM_P11_CHILD_TIMEOUT,
+                             P11_CHILD_TIMEOUT_DEFAULT,
+                             &state->p11_child_timeout);
         if (ret != EOK) {
-            DEBUG(SSSDBG_OP_FAILURE, "get_valid_certs_keys failed.\n");
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Failed to read p11_child_timeout from confdb: [%d]: %s\n",
+                  ret, sss_strerror(ret));
             goto done;
         }
-
-        if (elements[i] != NULL) {
-            elements[i]->flags |= SSS_EL_FLAG_BIN_DATA;
-            num_keys += elements[i]->num_values;
-            i++;
-        }
     }
 
+    state->user_cert = ldb_msg_find_element(state->msg, SYSDB_USER_CERT);
     if (DOM_HAS_VIEWS(domain)) {
-        user_cert = ldb_msg_find_element(msg, OVERRIDE_PREFIX SYSDB_USER_CERT);
-        if (user_cert != NULL) {
-            ret = get_valid_certs_keys(elements, ssh_ctx, user_cert,
-                                       &elements[i]);
-            if (ret != EOK) {
-                DEBUG(SSSDBG_OP_FAILURE, "get_valid_certs_keys failed.\n");
-                goto done;
-            }
+        state->user_cert_override = ldb_msg_find_element(state->msg,
+                                               OVERRIDE_PREFIX SYSDB_USER_CERT);
+    }
 
-            if (elements[i] != NULL) {
-                elements[i]->flags |= SSS_EL_FLAG_BIN_DATA;
-                num_keys += elements[i]->num_values;
-                i++;
-            }
-        }
+    if (state->user_cert == NULL && state->user_cert_override == NULL) {
+        /* no certificates to convert, we are done */
+        ret = EOK;
+        goto done;
     }
 
-    *_elements = elements;
-    *_num_keys = num_keys;
+    state->current_cert = state->user_cert != NULL ? state->user_cert
+                                                   : state->user_cert_override;
+
+    subreq = cert_to_ssh_key_send(state, state->ev, -1,
+                                  state->p11_child_timeout,
+                                  state->ssh_ctx->ca_db,
+                                  state->current_cert->num_values,
+                                  state->current_cert->values,
+                                  state->cert_verification_opts);
+    if (subreq == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key_send failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+    tevent_req_set_callback(subreq, ssh_get_output_keys_done, req);
 
-    ret = EOK;
+    ret = EAGAIN;
 
 done:
+    if (ret != EAGAIN) {
+        if (ret == EOK) {
+            tevent_req_done(req);
+        } else {
+            tevent_req_error(req, ret);
+        }
+        tevent_req_post(req, ev);
+    }
+
+    return req;
+}
+
+void ssh_get_output_keys_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ssh_get_output_keys_state *state = tevent_req_data(req,
+                                              struct ssh_get_output_keys_state);
+    int ret;
+    struct ldb_val *keys;
+    size_t valid_keys;
+
+    ret = cert_to_ssh_key_recv(subreq, state, &keys, &valid_keys);
+    talloc_zfree(subreq);
     if (ret != EOK) {
-        talloc_free(elements);
+        DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key request failed.\n");
+        tevent_req_error(req, ret);
+        return;
     }
 
-    return ret;
+    state->elements[state->iter] = talloc_zero(state->elements,
+                                                struct ldb_message_element);
+    if (state->elements[state->iter] == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+    state->elements[state->iter]->values = talloc_steal(
+                                                   state->elements[state->iter],
+                                                   keys);
+    state->elements[state->iter]->num_values = state->current_cert->num_values;
+    state->elements[state->iter]->flags |= SSS_EL_FLAG_BIN_DATA;
+    state->num_keys += valid_keys;
+
+    if (state->current_cert == state->user_cert) {
+        state->current_cert = state->user_cert_override;
+    } else if (state->current_cert == state->user_cert_override) {
+        state->current_cert = NULL;
+    } else {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected certificate pointer.\n");
+        tevent_req_error(req, EINVAL);
+        return;
+    }
+
+    if (state->current_cert == NULL) {
+        /* done */
+        ret = EOK;
+        goto done;
+    }
+
+    subreq = cert_to_ssh_key_send(state, state->ev, -1,
+                                  state->p11_child_timeout,
+                                  state->ssh_ctx->ca_db,
+                                  state->current_cert->num_values,
+                                  state->current_cert->values,
+                                  state->cert_verification_opts);
+    if (subreq == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key_send failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+    tevent_req_set_callback(subreq, ssh_get_output_keys_done, req);
+    return;
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+
+    return;
 }
 
-static errno_t
-ssh_get_name(struct ldb_message *msg,
-             struct sized_string *sz_name)
+errno_t ssh_get_output_keys_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                                 struct sized_string *name,
+                                 struct ldb_message_element ***elements,
+                                 uint32_t *num_keys)
 {
-    const char *name;
+    struct ssh_get_output_keys_state *state = tevent_req_data(req,
+                                              struct ssh_get_output_keys_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (name != NULL) {
+        name->str = talloc_strdup(mem_ctx, state->name);
+        name->len = strlen(name->str) + 1;
+    }
 
-    name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
-    if (name == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "Got unnamed result!\n");
-        return ENOENT;
+    if (elements != NULL) {
+        *elements = talloc_steal(mem_ctx, state->elements);
     }
 
-    to_sized_string(sz_name, name);
+    if (num_keys != NULL) {
+        *num_keys = state->num_keys;
+    }
 
     return EOK;
 }
 
 errno_t
 ssh_protocol_build_reply(struct sss_packet *packet,
-                         struct ssh_ctx *ssh_ctx,
-                         struct cache_req_result *result)
+                         struct sized_string name,
+                         struct ldb_message_element **elements,
+                         uint32_t num_keys)
 {
-    TALLOC_CTX *tmp_ctx;
-    struct ldb_message_element **elements;
-    struct sized_string name;
-    uint32_t num_keys;
     size_t body_len;
     uint8_t *body;
     size_t c = 0;
     errno_t ret;
     int i;
 
-    tmp_ctx = talloc_new(NULL);
-    if (tmp_ctx == NULL) {
-        DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n");
-        return ENOMEM;
-    }
-
-    ret = ssh_get_output_keys(tmp_ctx, ssh_ctx, result->domain,
-                              result->msgs[0], &elements, &num_keys);
-    if (ret != EOK) {
-        goto done;
-    }
-
-    ret = ssh_get_name(result->msgs[0], &name);
-    if (ret != EOK) {
-        goto done;
-    }
-
     ret = sss_packet_grow(packet, 2 * sizeof(uint32_t));
     if (ret != EOK) {
         goto done;
@@ -351,7 +397,6 @@ ssh_protocol_build_reply(struct sss_packet *packet,
     ret = EOK;
 
 done:
-    talloc_free(tmp_ctx);
 
     return ret;
 }

From a9cf12214d413e11761852509d1b519a8b9bdf5c Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 19 Mar 2018 18:28:38 +0100
Subject: [PATCH 08/10] ssh: add option ssh_use_certificate_keys and enhance
 man page

This patch adds on option to switch the extraction of ssh keys from
X.509 certificates on and off and improves the general documentation
about this feature in the sss_ssh_authorizedkeys man page.

Related to https://pagure.io/SSSD/sssd/issue/3688
---
 src/confdb/confdb.h                  |  2 ++
 src/man/sss_ssh_authorizedkeys.1.xml | 57 ++++++++++++++++++++++++++++++++++++
 src/man/sssd.conf.5.xml              | 18 ++++++++++++
 src/responder/ssh/ssh_private.h      |  1 +
 src/responder/ssh/ssh_reply.c        |  6 ++++
 src/responder/ssh/sshsrv.c           | 12 ++++++++
 6 files changed, 96 insertions(+)

diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 1d322aaac..8e1b16541 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -154,6 +154,8 @@
 #define CONFDB_DEFAULT_SSH_KNOWN_HOSTS_TIMEOUT 180
 #define CONFDB_SSH_CA_DB "ca_db"
 #define CONFDB_DEFAULT_SSH_CA_DB SYSCONFDIR"/pki/nssdb"
+#define CONFDB_SSH_USE_CERT_KEYS "ssh_use_certificate_keys"
+#define CONFDB_DEFAULT_SSH_USE_CERT_KEYS true
 
 /* PAC */
 #define CONFDB_PAC_CONF_ENTRY "config/pac"
diff --git a/src/man/sss_ssh_authorizedkeys.1.xml b/src/man/sss_ssh_authorizedkeys.1.xml
index bfea0af8f..2f4756d89 100644
--- a/src/man/sss_ssh_authorizedkeys.1.xml
+++ b/src/man/sss_ssh_authorizedkeys.1.xml
@@ -60,6 +60,63 @@
   AuthorizedKeysCommandUser nobody
 </programlisting>
         </para>
+
+    <refsect2 id='cert_keys'>
+        <title>KEYS FROM CERTIFICATES</title>
+        <para>
+            In addition to the public SSH keys for user
+            <replaceable>USER</replaceable>
+            <command>sss_ssh_authorizedkeys</command> can return public SSH keys
+            derived from the public key of a X.509 certificate as well.
+        </para>
+        <para>
+            To enable this the <quote>ssh_use_certificate_keys</quote> option
+            must be set to true (default) in the [ssh] section of
+            <filename>sssd.conf</filename>. If the user entry contains
+            certificates (see <quote>ldap_user_certificate</quote> in
+            <citerefentry><refentrytitle>sssd-ldap</refentrytitle>
+            <manvolnum>5</manvolnum></citerefentry>
+            for details) or there is a certificate in an override entry for the
+            user (see
+            <citerefentry><refentrytitle>sss_override</refentrytitle>
+            <manvolnum>8</manvolnum></citerefentry>
+            or <citerefentry><refentrytitle>sssd-ipa</refentrytitle>
+            <manvolnum>5</manvolnum></citerefentry>
+            for details) and the certificate is valid SSSD will extract the
+            public key from the certificate and convert it into the format
+            expected by sshd.
+        </para>
+        <para>
+            Besides <quote>ssh_use_certificate_keys</quote> the options
+            <itemizedlist>
+                <listitem><para>ca_db</para></listitem>
+                <listitem><para>p11_child_timeout</para></listitem>
+                <listitem><para>certificate_verification</para></listitem>
+            </itemizedlist>
+            can be used to control how the certificates are validated (see
+            <citerefentry><refentrytitle>sssd.conf</refentrytitle>
+                        <manvolnum>5</manvolnum></citerefentry> for details).
+        </para>
+        <para>
+            The validation is the benefit of using X.509 certificates instead of
+            SSH keys directly because e.g. it gives a better control of the
+            lifetime of the keys. When the ssh client is configured to use the
+            private keys from a Smartcard with the help of a PKCS#11 shared
+            library (see
+            <citerefentry><refentrytitle>ssh</refentrytitle>
+            <manvolnum>1</manvolnum></citerefentry>
+            for details) it might be irritating that authentication is still
+            working even if the related X.509 certificate on the Smartcard is
+            already expired because neither <command>ssh</command> nor
+            <command>sshd</command> will look at the certificate at all.
+        </para>
+        <para>
+            It has to be noted that the derived public SSH key can still be
+            added to the <filename>authorized_keys</filename> file of the user
+            to bypass the certificate validation if the <command>sshd</command>
+            configuration permits this.
+        </para>
+    </refsect2>
     </refsect1>
 
     <refsect1 id='options'>
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index 7191bf46d..57cb77b37 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -1487,6 +1487,24 @@ pam_account_locked_message = Account locked, please contact help desk.
                         </para>
                     </listitem>
                 </varlistentry>
+                <varlistentry>
+                    <term>ssh_use_certificate_keys (bool)</term>
+                    <listitem>
+                        <para>
+                            If set to true the
+                            <command>sss_ssh_authorizedkeys</command> will
+                            return ssh keys derived from the public key of X.509
+                            certificates stored in the user entry as well. See
+                            <citerefentry>
+                                <refentrytitle>sss_ssh_authorizedkeys</refentrytitle>
+                                <manvolnum>1</manvolnum>
+                            </citerefentry> for details.
+                        </para>
+                        <para>
+                            Default: true
+                        </para>
+                    </listitem>
+                </varlistentry>
                 <varlistentry>
                     <term>ca_db (string)</term>
                     <listitem>
diff --git a/src/responder/ssh/ssh_private.h b/src/responder/ssh/ssh_private.h
index a7a9ad09d..a369fc83f 100644
--- a/src/responder/ssh/ssh_private.h
+++ b/src/responder/ssh/ssh_private.h
@@ -34,6 +34,7 @@ struct ssh_ctx {
     bool hash_known_hosts;
     int known_hosts_timeout;
     char *ca_db;
+    bool use_cert_keys;
 };
 
 struct sss_cmd_table *get_ssh_cmds(void);
diff --git a/src/responder/ssh/ssh_reply.c b/src/responder/ssh/ssh_reply.c
index 478b1ab8d..a8a26daf4 100644
--- a/src/responder/ssh/ssh_reply.c
+++ b/src/responder/ssh/ssh_reply.c
@@ -189,6 +189,12 @@ struct tevent_req *ssh_get_output_keys_send(TALLOC_CTX *mem_ctx,
         }
     }
 
+    if (!state->ssh_ctx->use_cert_keys) {
+        DEBUG(SSSDBG_TRACE_ALL, "Skipping keys from certificates.\n");
+        ret = EOK;
+        goto done;
+    }
+
     ret = confdb_get_string(cli_ctx->rctx->cdb, state,
                             CONFDB_MONITOR_CONF_ENTRY,
                             CONFDB_MONITOR_CERT_VERIFICATION, NULL,
diff --git a/src/responder/ssh/sshsrv.c b/src/responder/ssh/sshsrv.c
index 795dca2c9..9f1f618f2 100644
--- a/src/responder/ssh/sshsrv.c
+++ b/src/responder/ssh/sshsrv.c
@@ -158,6 +158,18 @@ int ssh_process_init(TALLOC_CTX *mem_ctx,
         goto fail;
     }
 
+    ret = confdb_get_bool(ssh_ctx->rctx->cdb, CONFDB_SSH_CONF_ENTRY,
+                          CONFDB_SSH_USE_CERT_KEYS,
+                          CONFDB_DEFAULT_SSH_USE_CERT_KEYS,
+                          &ssh_ctx->use_cert_keys);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,"Error reading option "
+                                    CONFDB_SSH_USE_CERT_KEYS
+                                    "from confdb (%d) [%s]\n",
+                                    ret, strerror(ret));
+        goto fail;
+    }
+
     ret = schedule_get_domains_task(rctx, rctx->ev, rctx, NULL);
     if (ret != EOK) {
         DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n");

From 27f3136bd6ab14b54a1786882b364e1b138b78d4 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Tue, 20 Mar 2018 15:31:41 +0100
Subject: [PATCH 09/10] utils: remove unused code from cert utils

Related to https://pagure.io/SSSD/sssd/issue/3489
---
 src/tests/cmocka/test_cert_utils.c |  34 -------
 src/util/cert/libcrypto/cert.c     | 106 ---------------------
 src/util/cert/nss/cert.c           | 188 -------------------------------------
 3 files changed, 328 deletions(-)

diff --git a/src/tests/cmocka/test_cert_utils.c b/src/tests/cmocka/test_cert_utils.c
index 077a8b0a2..bb998e876 100644
--- a/src/tests/cmocka/test_cert_utils.c
+++ b/src/tests/cmocka/test_cert_utils.c
@@ -343,38 +343,6 @@ void test_sss_cert_derb64_to_ldap_filter(void **state)
     talloc_free(filter);
 }
 
-void test_cert_to_ssh_key(void **state)
-{
-    int ret;
-    uint8_t *key;
-    size_t key_size;
-    uint8_t *exp_key;
-    size_t exp_key_size;
-    uint8_t *der;
-    size_t der_size;
-    struct cert_verify_opts cert_verify_opts = { .do_ocsp = false,
-                                                 .do_verification = true };
-
-    struct test_state *ts = talloc_get_type_abort(*state, struct test_state);
-    assert_non_null(ts);
-
-    der = sss_base64_decode(ts, SSSD_TEST_CERT_0001, &der_size);
-    assert_non_null(der);
-
-    exp_key = sss_base64_decode(ts, SSSD_TEST_CERT_SSH_KEY_0001, &exp_key_size);
-    assert_non_null(exp_key);
-
-    ret = cert_to_ssh_key(ts, "sql:" ABS_BUILD_DIR "/src/tests/test_CA/p11_nssdb",
-                          der, der_size, &cert_verify_opts, &key, &key_size);
-    assert_int_equal(ret, EOK);
-    assert_int_equal(key_size, exp_key_size);
-    assert_memory_equal(key, exp_key, exp_key_size);
-
-    talloc_free(der);
-    talloc_free(key);
-    talloc_free(exp_key);
-}
-
 void test_cert_to_ssh_key_done(struct tevent_req *req)
 {
     int ret;
@@ -610,8 +578,6 @@ int main(int argc, const char *argv[])
                                         setup, teardown),
 #ifdef HAVE_NSS
 #ifdef HAVE_TEST_CA
-        cmocka_unit_test_setup_teardown(test_cert_to_ssh_key,
-                                        setup, teardown),
         cmocka_unit_test_setup_teardown(test_cert_to_ssh_key_send,
                                         setup, teardown),
         cmocka_unit_test_setup_teardown(test_cert_to_ssh_2keys_send,
diff --git a/src/util/cert/libcrypto/cert.c b/src/util/cert/libcrypto/cert.c
index 6d79502e4..c8e07837f 100644
--- a/src/util/cert/libcrypto/cert.c
+++ b/src/util/cert/libcrypto/cert.c
@@ -171,112 +171,6 @@ errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem,
 #define SSH_RSA_HEADER "ssh-rsa"
 #define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1)
 
-errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db,
-                        const uint8_t *der_blob, size_t der_size,
-                        struct cert_verify_opts *cert_verify_opts,
-                        uint8_t **key, size_t *key_size)
-{
-    int ret;
-    size_t size;
-    const unsigned char *d;
-    uint8_t *buf = NULL;
-    size_t c;
-    X509 *cert = NULL;
-    EVP_PKEY *cert_pub_key = NULL;
-    const BIGNUM *n;
-    const BIGNUM *e;
-    int modulus_len;
-    unsigned char modulus[OPENSSL_RSA_MAX_MODULUS_BITS/8];
-    int exponent_len;
-    unsigned char exponent[OPENSSL_RSA_MAX_PUBEXP_BITS/8];
-
-    if (der_blob == NULL || der_size == 0) {
-        return EINVAL;
-    }
-
-    d = (const unsigned char *) der_blob;
-
-    cert = d2i_X509(NULL, &d, (int) der_size);
-    if (cert == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "d2i_X509 failed.\n");
-        return EINVAL;
-    }
-
-    /* TODO: verify certificate !!!!! */
-
-    cert_pub_key = X509_get_pubkey(cert);
-    if (cert_pub_key == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "X509_get_pubkey failed.\n");
-        ret = EIO;
-        goto done;
-    }
-
-    if (EVP_PKEY_base_id(cert_pub_key) != EVP_PKEY_RSA) {
-        DEBUG(SSSDBG_CRIT_FAILURE,
-              "Expected RSA public key, found unsupported [%d].\n",
-              EVP_PKEY_base_id(cert_pub_key));
-        ret = EINVAL;
-        goto done;
-    }
-
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-    RSA *rsa_pub_key = NULL;
-    rsa_pub_key = EVP_PKEY_get0_RSA(cert_pub_key);
-    if (rsa_pub_key == NULL) {
-        ret = ENOMEM;
-        goto done;
-    }
-
-    RSA_get0_key(rsa_pub_key, &n, &e, NULL);
-#else
-    n = cert_pub_key->pkey.rsa->n;
-    e = cert_pub_key->pkey.rsa->e;
-#endif
-    modulus_len = BN_bn2bin(n, modulus);
-    exponent_len = BN_bn2bin(e, exponent);
-
-    size = SSH_RSA_HEADER_LEN + 3 * sizeof(uint32_t)
-                + modulus_len
-                + exponent_len
-                + 1; /* see comment about missing 00 below */
-
-    buf = talloc_size(mem_ctx, size);
-    if (buf == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
-        ret = ENOMEM;
-        goto done;
-    }
-
-    c = 0;
-
-    SAFEALIGN_SET_UINT32(buf, htobe32(SSH_RSA_HEADER_LEN), &c);
-    safealign_memcpy(&buf[c], SSH_RSA_HEADER, SSH_RSA_HEADER_LEN, &c);
-    SAFEALIGN_SET_UINT32(&buf[c], htobe32(exponent_len), &c);
-    safealign_memcpy(&buf[c], exponent, exponent_len, &c);
-
-    /* Adding missing 00 which AFAIK is added to make sure
-     * the bigint is handled as positive number */
-    /* TODO: make a better check if 00 must be added or not, e.g. ... & 0x80)
-     */
-    SAFEALIGN_SET_UINT32(&buf[c], htobe32(modulus_len + 1), &c);
-    SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c);
-    safealign_memcpy(&buf[c], modulus, modulus_len, &c);
-
-    *key = buf;
-    *key_size = size;
-
-    ret = EOK;
-
-done:
-    if (ret != EOK)  {
-        talloc_free(buf);
-    }
-    EVP_PKEY_free(cert_pub_key);
-    X509_free(cert);
-
-    return ret;
-}
-
 errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx,
                               const uint8_t *der_blob, size_t der_size,
                               uint8_t **key_blob, size_t *key_size)
diff --git a/src/util/cert/nss/cert.c b/src/util/cert/nss/cert.c
index cb834c426..a8efef818 100644
--- a/src/util/cert/nss/cert.c
+++ b/src/util/cert/nss/cert.c
@@ -223,194 +223,6 @@ errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem,
 #define SSH_RSA_HEADER "ssh-rsa"
 #define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1)
 
-errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db,
-                        const uint8_t *der_blob, size_t der_size,
-                        struct cert_verify_opts *cert_verify_opts,
-                        uint8_t **key, size_t *key_size)
-{
-    CERTCertDBHandle *handle;
-    CERTCertificate *cert = NULL;
-    SECItem der_item;
-    SECKEYPublicKey *cert_pub_key = NULL;
-    int ret;
-    size_t size;
-    uint8_t *buf = NULL;
-    size_t c;
-    NSSInitContext *nss_ctx;
-    NSSInitParameters parameters = { 0 };
-    parameters.length =  sizeof (parameters);
-    SECStatus rv;
-    SECStatus rv_verify;
-    size_t exponent_prefix_len;
-    size_t modulus_prefix_len;
-
-    if (der_blob == NULL || der_size == 0) {
-        return EINVAL;
-    }
-
-    /* initialize NSS with context, we might have already called
-     * NSS_NoDB_Init() but for validation we need to have access to a DB with
-     * the trusted issuer cert. Only NSS_InitContext will really open the DB
-     * in this case. I'm not sure about how long validation might need e.g. if
-     * CRLs or OSCP is enabled, maybe it would be better to run validation in
-     * p11_child? */
-    nss_ctx = NSS_InitContext(ca_db, "", "", SECMOD_DB, &parameters,
-                              NSS_INIT_READONLY);
-    if (nss_ctx == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "NSS_InitContext failed [%d].\n",
-                                 PR_GetError());
-        return EIO;
-    }
-
-    handle = CERT_GetDefaultCertDB();
-
-    if (cert_verify_opts->do_ocsp) {
-        rv = CERT_EnableOCSPChecking(handle);
-        if (rv != SECSuccess) {
-            DEBUG(SSSDBG_OP_FAILURE, "CERT_EnableOCSPChecking failed: [%d].\n",
-                                     PR_GetError());
-            return EIO;
-        }
-
-        if (cert_verify_opts->ocsp_default_responder != NULL
-            && cert_verify_opts->ocsp_default_responder_signing_cert != NULL) {
-            rv = CERT_SetOCSPDefaultResponder(handle,
-                         cert_verify_opts->ocsp_default_responder,
-                         cert_verify_opts->ocsp_default_responder_signing_cert);
-            if (rv != SECSuccess) {
-                DEBUG(SSSDBG_OP_FAILURE,
-                      "CERT_SetOCSPDefaultResponder failed: [%d].\n",
-                      PR_GetError());
-                return EIO;
-            }
-
-            rv = CERT_EnableOCSPDefaultResponder(handle);
-            if (rv != SECSuccess) {
-                DEBUG(SSSDBG_OP_FAILURE,
-                      "CERT_EnableOCSPDefaultResponder failed: [%d].\n",
-                      PR_GetError());
-                return EIO;
-            }
-        }
-    }
-
-    der_item.len = der_size;
-    der_item.data = discard_const(der_blob);
-
-    cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE);
-    if (cert == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n");
-        ret = EINVAL;
-        goto done;
-    }
-
-    if (cert_verify_opts->do_verification) {
-        rv_verify = CERT_VerifyCertificateNow(handle, cert, PR_TRUE,
-                                              certificateUsageSSLClient,
-                                              NULL, NULL);
-
-        /* Disable OCSP default responder so that NSS can shutdown properly */
-        if (cert_verify_opts->do_ocsp
-                && cert_verify_opts->ocsp_default_responder != NULL
-                && cert_verify_opts->ocsp_default_responder_signing_cert
-                                                                      != NULL) {
-            rv = CERT_DisableOCSPDefaultResponder(handle);
-            if (rv != SECSuccess) {
-                DEBUG(SSSDBG_OP_FAILURE,
-                      "CERT_DisableOCSPDefaultResponder failed: [%d].\n",
-                      PR_GetError());
-            }
-        }
-
-        if (rv_verify != SECSuccess) {
-            DEBUG(SSSDBG_CRIT_FAILURE, "CERT_VerifyCertificateNow failed [%d].\n",
-                                       PR_GetError());
-            ret = EACCES;
-            goto done;
-        }
-    }
-
-    cert_pub_key = CERT_ExtractPublicKey(cert);
-    if (cert_pub_key == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "CERT_ExtractPublicKey failed.\n");
-        ret = EIO;
-        goto done;
-    }
-
-    if (cert_pub_key->keyType != rsaKey) {
-        DEBUG(SSSDBG_CRIT_FAILURE,
-              "Expected RSA public key, found unsupported [%d].\n",
-              cert_pub_key->keyType);
-        ret = EINVAL;
-        goto done;
-    }
-
-    /* Looks like nss drops the leading 00 which AFAIK is added to make sure
-     * the bigint is handled as positive number if the leading bit is set. */
-    exponent_prefix_len = 0;
-    if (cert_pub_key->u.rsa.publicExponent.data[0] & 0x80) {
-        exponent_prefix_len = 1;
-    }
-
-    modulus_prefix_len = 0;
-    if (cert_pub_key->u.rsa.modulus.data[0] & 0x80) {
-        modulus_prefix_len = 1;
-    }
-    size = SSH_RSA_HEADER_LEN + 3 * sizeof(uint32_t)
-                + cert_pub_key->u.rsa.modulus.len
-                + cert_pub_key->u.rsa.publicExponent.len
-                + exponent_prefix_len + modulus_prefix_len;
-
-    buf = talloc_size(mem_ctx, size);
-    if (buf == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
-        ret = ENOMEM;
-        goto done;
-    }
-
-    c = 0;
-
-    SAFEALIGN_SET_UINT32(buf, htobe32(SSH_RSA_HEADER_LEN), &c);
-    safealign_memcpy(&buf[c], SSH_RSA_HEADER, SSH_RSA_HEADER_LEN, &c);
-    SAFEALIGN_SET_UINT32(&buf[c],
-                         htobe32(cert_pub_key->u.rsa.publicExponent.len
-                                    + exponent_prefix_len), &c);
-    if (exponent_prefix_len == 1) {
-        SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c);
-    }
-    safealign_memcpy(&buf[c], cert_pub_key->u.rsa.publicExponent.data,
-                     cert_pub_key->u.rsa.publicExponent.len, &c);
-
-    SAFEALIGN_SET_UINT32(&buf[c],
-                         htobe32(cert_pub_key->u.rsa.modulus.len
-                                    + modulus_prefix_len ), &c);
-    if (modulus_prefix_len == 1) {
-        SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c);
-    }
-    safealign_memcpy(&buf[c], cert_pub_key->u.rsa.modulus.data,
-                     cert_pub_key->u.rsa.modulus.len, &c);
-
-    *key = buf;
-    *key_size = size;
-
-    ret = EOK;
-
-done:
-    if (ret != EOK)  {
-        talloc_free(buf);
-    }
-    SECKEY_DestroyPublicKey(cert_pub_key);
-    CERT_DestroyCertificate(cert);
-
-    rv = NSS_ShutdownContext(nss_ctx);
-    if (rv != SECSuccess) {
-        DEBUG(SSSDBG_OP_FAILURE, "NSS_ShutdownContext failed [%d].\n",
-                                 PR_GetError());
-    }
-
-    return ret;
-}
-
 errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx,
                               uint8_t *der_blob, size_t der_size,
                               uint8_t **key_blob, size_t *key_size)

From 2c2abd2788b6bff89e14c5e09f8c052ffb856876 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Tue, 27 Mar 2018 16:32:33 +0200
Subject: [PATCH 10/10] tests: add SSH responder tests

Related to https://pagure.io/SSSD/sssd/issue/3489
---
 Makefile.am                     |  36 +++
 src/tests/cmocka/test_ssh_srv.c | 655 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 691 insertions(+)
 create mode 100644 src/tests/cmocka/test_ssh_srv.c

diff --git a/Makefile.am b/Makefile.am
index df8955d82..f0016fb5c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -275,6 +275,7 @@ if HAVE_CMOCKA
         test_sbus_opath \
         test_fo_srv \
         pam-srv-tests \
+        ssh-srv-tests \
         test_ipa_subdom_util \
         test_tools_colondb \
         test_krb5_wait_queue \
@@ -2431,6 +2432,41 @@ pam_srv_tests_LDADD = \
     libsss_certmap.la \
     $(NULL)
 
+EXTRA_ssh_srv_tests_DEPENDENCIES = \
+    $(ldblib_LTLIBRARIES) \
+    $(NULL)
+if HAVE_NSS
+EXTRA_ssh_srv_tests_DEPENDENCIES += p11_child
+endif
+ssh_srv_tests_SOURCES = \
+    $(TEST_MOCK_RESP_OBJ) \
+    src/tests/cmocka/test_ssh_srv.c \
+    src/responder/ssh/ssh_cmd.c \
+    src/responder/ssh/ssh_known_hosts.c \
+    src/responder/ssh/ssh_protocol.c \
+    src/responder/ssh/ssh_reply.c \
+    src/util/cert/cert_common_p11_child.c \
+    $(NULL)
+ssh_srv_tests_CFLAGS = \
+    -U SSSD_LIBEXEC_PATH -DSSSD_LIBEXEC_PATH=\"$(abs_builddir)\" \
+    -I$(abs_builddir)/src \
+    $(AM_CFLAGS) \
+    $(NULL)
+ssh_srv_tests_LDFLAGS = \
+    -Wl,-wrap,sss_packet_get_body \
+    -Wl,-wrap,sss_packet_get_cmd \
+    -Wl,-wrap,sss_cmd_send_empty \
+    -Wl,-wrap,sss_cmd_done \
+    -Wl,-wrap,ssh_dp_send_req \
+    $(NULL)
+ssh_srv_tests_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(SSSD_LIBS) \
+    $(SSSD_INTERNAL_LTLIBS) \
+    $(SYSTEMD_DAEMON_LIBS) \
+    libsss_test_common.la \
+    $(NULL)
+
 EXTRA_responder_get_domains_tests_DEPENDENCIES = \
      $(ldblib_LTLIBRARIES)
 responder_get_domains_tests_SOURCES = \
diff --git a/src/tests/cmocka/test_ssh_srv.c b/src/tests/cmocka/test_ssh_srv.c
new file mode 100644
index 000000000..dd9a52387
--- /dev/null
+++ b/src/tests/cmocka/test_ssh_srv.c
@@ -0,0 +1,655 @@
+/*
+    Authors:
+        Sumit Bose <sbose@redhat.com>
+
+    Copyright (C) 2015 Red Hat
+
+    SSSD tests: PAM responder tests
+
+    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 <popt.h>
+
+#include "tests/cmocka/common_mock.h"
+#include "tests/cmocka/common_mock_resp.h"
+#include "responder/common/responder_packet.h"
+#include "responder/common/negcache.h"
+#include "responder/ssh/ssh_private.h"
+#include "confdb/confdb.h"
+
+#include "util/crypto/sss_crypto.h"
+#ifdef HAVE_NSS
+#include "util/crypto/nss/nss_util.h"
+#endif
+
+#ifdef HAVE_TEST_CA
+#include "tests/test_CA/SSSD_test_cert_x509_0001.h"
+#include "tests/test_CA/SSSD_test_cert_pubsshkey_0001.h"
+#include "tests/test_CA/SSSD_test_cert_x509_0002.h"
+#include "tests/test_CA/SSSD_test_cert_pubsshkey_0002.h"
+#else
+#define SSSD_TEST_CERT_0001 ""
+#define SSSD_TEST_CERT_SSH_KEY_0001 ""
+#define SSSD_TEST_CERT_0002 ""
+#define SSSD_TEST_CERT_SSH_KEY_0002 ""
+#endif
+
+#define TESTS_PATH "tp_" BASE_FILE_STEM
+#define TEST_CONF_DB "test_ssh_conf.ldb"
+#define TEST_DOM_NAME "ssh_test"
+#define TEST_SUBDOM_NAME "test.subdomain"
+#define TEST_ID_PROVIDER "ldap"
+
+#define TEST_SSH_PUBKEY \
+"AAAAB3NzaC1yc2EAAAADAQABAAABAQC1" \
+"OlYGkYw8JyhKQrlNBGbZC2az9TJhUWNn" \
+"/kS26OOI9hXCZgz4eHyZnCS1bY1/0ptG" \
+"ByQAk2qvF9uYV2plxULoiOUYAWCnnqx/" \
+"bnhQ4SxmCcA5RPy3h8FX2OrxMlQEadH6" \
+"wz3ZTnOvsw57/ZV8yXjzVexJeeO1A59g" \
+"pLD43f3v056zSF/Jo1NwAZUzCJuzpFAy" \
+"Ale6mZ/1rpGN+ah6rN70wz3brwEOi4f2" \
+"HQNbKAL4idVyRYbA7oU+htCLEd6YsSdy" \
+"murxDMAEEQbLeMbF1DXNt1OunoeprXrU" \
+"UE1U9Rxi6xvPt7s3h9NbZiaLRPJU6due" \
+"+nqwn8En7mesd7LnRQST"
+
+struct ssh_test_ctx {
+    struct sss_test_ctx *tctx;
+    struct sss_domain_info *subdom;
+
+    struct resp_ctx *rctx;
+    struct cli_ctx *cctx;
+    struct sss_cmd_table *ssh_cmds;
+    struct ssh_ctx *ssh_ctx;
+
+    int ncache_hits;
+    bool provider_contacted;
+
+    const char *ssh_user_fqdn;
+    const char *wrong_user_fqdn;
+};
+
+/* Must be global because it is needed in some wrappers */
+struct ssh_test_ctx *ssh_test_ctx;
+
+struct ssh_ctx *mock_ssh_ctx(TALLOC_CTX *mem_ctx)
+{
+    struct ssh_ctx *ssh_ctx;
+
+    ssh_ctx = talloc_zero(mem_ctx, struct ssh_ctx);
+    assert_non_null(ssh_ctx);
+
+    return ssh_ctx;
+}
+
+static int add_confdb_params(struct sss_test_conf_param params[],
+                             struct confdb_ctx *cdb, const char *section)
+{
+    const char *val[2];
+    int ret;
+
+    val[1] = NULL;
+
+    for (int i = 0; params[i].key; i++) {
+        val[0] = params[i].value;
+        ret = confdb_add_param(cdb, true, section, params[i].key, val);
+        assert_int_equal(ret, EOK);
+    }
+
+    return EOK;
+}
+
+static int add_ssh_params(struct sss_test_conf_param ssh_params[],
+                          struct confdb_ctx *cdb)
+{
+    return add_confdb_params(ssh_params, cdb, CONFDB_SSH_CONF_ENTRY);
+}
+
+static int add_monitor_params(struct sss_test_conf_param monitor_params[],
+                              struct confdb_ctx *cdb)
+{
+    return add_confdb_params(monitor_params, cdb, CONFDB_MONITOR_CONF_ENTRY);
+}
+
+void test_ssh_setup(struct sss_test_conf_param dom_params[],
+                    struct sss_test_conf_param ssh_params[],
+                    struct sss_test_conf_param monitor_params[],
+                    void **state)
+{
+    struct cli_protocol *prctx;
+    errno_t ret;
+
+    ssh_test_ctx = talloc_zero(NULL, struct ssh_test_ctx);
+    assert_non_null(ssh_test_ctx);
+
+    ssh_test_ctx->tctx = create_dom_test_ctx(ssh_test_ctx, TESTS_PATH,
+                                             TEST_CONF_DB, TEST_DOM_NAME,
+                                             TEST_ID_PROVIDER, dom_params);
+    assert_non_null(ssh_test_ctx->tctx);
+
+    ssh_test_ctx->ssh_cmds = get_ssh_cmds();
+    assert_non_null(ssh_test_ctx->ssh_cmds);
+
+    /* FIXME - perhaps this should be folded into sssd_domain_init or strictly
+     * used together
+     */
+    ret = sss_names_init(ssh_test_ctx, ssh_test_ctx->tctx->confdb,
+                         TEST_DOM_NAME, &ssh_test_ctx->tctx->dom->names);
+    assert_int_equal(ret, EOK);
+
+    /* Initialize the SSH responder */
+    ssh_test_ctx->ssh_ctx = mock_ssh_ctx(ssh_test_ctx);
+    assert_non_null(ssh_test_ctx->ssh_ctx);
+
+    ssh_test_ctx->rctx = mock_rctx(ssh_test_ctx, ssh_test_ctx->tctx->ev,
+                                   ssh_test_ctx->tctx->dom,
+                                   ssh_test_ctx->ssh_ctx);
+    assert_non_null(ssh_test_ctx->rctx);
+    ssh_test_ctx->rctx->cdb = ssh_test_ctx->tctx->confdb;
+    ssh_test_ctx->ssh_ctx->rctx = ssh_test_ctx->rctx;
+
+    ret = add_ssh_params(ssh_params, ssh_test_ctx->rctx->cdb);
+    assert_int_equal(ret, EOK);
+
+    ret = add_monitor_params(monitor_params, ssh_test_ctx->rctx->cdb);
+    assert_int_equal(ret, EOK);
+
+    /* Create client context */
+    ssh_test_ctx->cctx = mock_cctx(ssh_test_ctx, ssh_test_ctx->rctx);
+    assert_non_null(ssh_test_ctx->cctx);
+    ssh_test_ctx->cctx->ev = ssh_test_ctx->tctx->ev;
+
+    prctx = mock_prctx(ssh_test_ctx->cctx);
+    assert_non_null(prctx);
+    ssh_test_ctx->cctx->protocol_ctx = prctx;
+    prctx->cli_protocol_version = register_cli_protocol_version();
+}
+
+static void ssh_test_setup_common(void)
+{
+    errno_t ret;
+
+    ssh_test_ctx->ssh_user_fqdn = \
+                    sss_create_internal_fqname(ssh_test_ctx,
+                                               "sshuser",
+                                               ssh_test_ctx->tctx->dom->name);
+    assert_non_null(ssh_test_ctx->ssh_user_fqdn);
+
+    ssh_test_ctx->wrong_user_fqdn = \
+                    sss_create_internal_fqname(ssh_test_ctx,
+                                               "wrongsshuser",
+                                               ssh_test_ctx->tctx->dom->name);
+    assert_non_null(ssh_test_ctx->wrong_user_fqdn);
+
+    /* Prime the cache with a valid user */
+    ret = sysdb_add_user(ssh_test_ctx->tctx->dom,
+                         ssh_test_ctx->ssh_user_fqdn,
+                         123, 456, "ssh user",
+                         "/home/sshuser", "/bin/sh", NULL,
+                         NULL, 300, 0);
+    assert_int_equal(ret, EOK);
+
+    /* Prime the cache with a user for wrong matches */
+    ret = sysdb_add_user(ssh_test_ctx->tctx->dom,
+                         ssh_test_ctx->wrong_user_fqdn,
+                         321, 654, "wrong ssh user",
+                         "/home/wrongsshuser", "/bin/sh", NULL,
+                         NULL, 300, 0);
+    assert_int_equal(ret, EOK);
+}
+
+static int ssh_test_setup(void **state)
+{
+    struct sss_test_conf_param dom_params[] = {
+        { "enumerate", "false" },
+        { "cache_credentials", "true" },
+        { NULL, NULL },             /* Sentinel */
+    };
+
+    /* When run under valgrind with --trace-children=yes we have to increase
+     * the timeout not because p11_child needs much more time under valgrind
+     * but because of the way valgrind handles the children. */
+    struct sss_test_conf_param ssh_params[] = {
+        { "p11_child_timeout", "40" },
+        { NULL, NULL },             /* Sentinel */
+    };
+
+    struct sss_test_conf_param monitor_params[] = {
+        { "certificate_verification", "no_ocsp"},
+        { NULL, NULL },             /* Sentinel */
+    };
+
+    test_ssh_setup(dom_params, ssh_params, monitor_params, state);
+
+    ssh_test_setup_common();
+    return 0;
+}
+
+static int ssh_test_teardown(void **state)
+{
+    int ret;
+
+    ret = sysdb_delete_user(ssh_test_ctx->tctx->dom,
+                            ssh_test_ctx->ssh_user_fqdn, 0);
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_delete_user(ssh_test_ctx->tctx->dom,
+                            ssh_test_ctx->wrong_user_fqdn, 0);
+    assert_int_equal(ret, EOK);
+
+    talloc_free(ssh_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);
+
+void __wrap_sss_packet_get_body(struct sss_packet *packet,
+                                uint8_t **body, size_t *blen)
+{
+    enum sss_test_wrapper_call wtype = sss_mock_type(enum sss_test_wrapper_call);
+    size_t len;
+
+    if (wtype == WRAP_CALL_REAL) {
+        return __real_sss_packet_get_body(packet, body, blen);
+    }
+
+    *body = sss_mock_ptr_type(uint8_t *);
+    len = sss_mock_type(size_t);
+    if (len == 0) {
+        len = strlen((const char *) *body) + 1;
+    }
+    *blen = len;
+    return;
+}
+
+void __real_sss_packet_get_body(struct sss_packet *packet,
+                                uint8_t **body, size_t *blen);
+
+void __wrap_sss_cmd_done(struct cli_ctx *cctx, void *freectx)
+{
+    struct cli_protocol *prctx;
+    struct sss_packet *packet;
+    uint8_t *body;
+    size_t blen;
+    cmd_cb_fn_t check_cb;
+
+    prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol);
+    packet = prctx->creq->out;
+    assert_non_null(packet);
+
+    check_cb = sss_mock_ptr_type(cmd_cb_fn_t);
+
+    __real_sss_packet_get_body(packet, &body, &blen);
+
+    ssh_test_ctx->tctx->error = check_cb(sss_packet_get_status(packet),
+                                         body, blen);
+    ssh_test_ctx->tctx->done = true;
+}
+
+enum sss_cli_command __wrap_sss_packet_get_cmd(struct sss_packet *packet)
+{
+    return sss_mock_type(enum sss_cli_command);
+}
+
+int __wrap_sss_cmd_send_empty(struct cli_ctx *cctx, TALLOC_CTX *freectx)
+{
+    ssh_test_ctx->tctx->done = true;
+    ssh_test_ctx->tctx->error = ENOENT;
+    return EOK;
+}
+
+static void set_cmd_cb(cmd_cb_fn_t fn)
+{
+    will_return(__wrap_sss_cmd_done, fn);
+}
+
+static void mock_input_user(TALLOC_CTX *mem_ctx, const char *username)
+{
+    uint8_t *buf;
+    size_t len = strlen(username);
+    size_t buf_len = len + 1 + 2 * sizeof(uint32_t);
+
+    buf = talloc_size(mem_ctx, buf_len);
+    SAFEALIGN_SET_UINT32(&buf[0], 0, NULL);
+    SAFEALIGN_SET_UINT32(&buf[4], len + 1, NULL);
+    memcpy(&buf[8], username, len + 1);
+
+    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_len);
+    mock_parse_inp("sshuser", TEST_DOM_NAME, EOK);
+}
+
+static int test_ssh_user_no_pubkeys_check(uint32_t status,
+                                          uint8_t *body, size_t blen)
+{
+    uint32_t val;
+
+    assert_int_equal(status, EOK);
+    assert_int_equal(blen, 8);
+
+    SAFEALIGN_COPY_UINT32(&val, &body[0], NULL);
+    assert_int_equal(val, 0);
+
+    SAFEALIGN_COPY_UINT32(&val, &body[4], NULL);
+    assert_int_equal(val, 0);
+
+    return EOK;
+}
+
+void test_ssh_user_no_pubkeys(void **state) {
+    int ret;
+
+    mock_input_user(ssh_test_ctx, ssh_test_ctx->ssh_user_fqdn);
+    will_return(__wrap_sss_packet_get_cmd, SSS_SSH_GET_USER_PUBKEYS);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_ssh_user_no_pubkeys_check);
+    ret = sss_cmd_execute(ssh_test_ctx->cctx, SSS_SSH_GET_USER_PUBKEYS,
+                          ssh_test_ctx->ssh_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(ssh_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+static int test_ssh_user_one_pubkey_check(uint32_t status,
+                                          uint8_t *body, size_t blen)
+{
+    uint32_t val;
+    size_t exp_len;
+    size_t name_len;
+    size_t key_len;
+    uint8_t *key;
+    size_t rp = 0;
+
+    key = sss_base64_decode(ssh_test_ctx, TEST_SSH_PUBKEY, &key_len);
+    name_len = strlen(ssh_test_ctx->ssh_user_fqdn) + 1;
+
+    exp_len = 5 * sizeof(uint32_t) + name_len + key_len;
+
+    assert_int_equal(status, EOK);
+    assert_int_equal(blen, exp_len);
+
+    SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+    assert_int_equal(val, 1);
+
+    SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+    assert_int_equal(val, 0);
+
+    SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+    assert_int_equal(val, 0);
+
+    SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+    assert_int_equal(val, name_len);
+
+    assert_memory_equal(body + rp, ssh_test_ctx->ssh_user_fqdn, name_len);
+    rp += name_len;
+
+    SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+    assert_int_equal(val, key_len);
+
+    assert_memory_equal(body + rp, key, key_len);
+    rp += key_len;
+
+    assert_int_equal(rp, blen);
+
+    return EOK;
+}
+
+void test_ssh_user_pubkey(void **state)
+{
+    int ret;
+    struct sysdb_attrs *attrs;
+
+    attrs = sysdb_new_attrs(ssh_test_ctx);
+    assert_non_null(attrs);
+    ret = sysdb_attrs_add_string(attrs, SYSDB_SSH_PUBKEY, TEST_SSH_PUBKEY);
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_set_user_attr(ssh_test_ctx->tctx->dom,
+                              ssh_test_ctx->ssh_user_fqdn,
+                              attrs,
+                              LDB_FLAG_MOD_ADD);
+    talloc_free(attrs);
+    assert_int_equal(ret, EOK);
+
+    mock_input_user(ssh_test_ctx, ssh_test_ctx->ssh_user_fqdn);
+    will_return(__wrap_sss_packet_get_cmd, SSS_SSH_GET_USER_PUBKEYS);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_ssh_user_one_pubkey_check);
+    ret = sss_cmd_execute(ssh_test_ctx->cctx, SSS_SSH_GET_USER_PUBKEYS,
+                          ssh_test_ctx->ssh_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(ssh_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+void test_ssh_user_pubkey_cert_disabled(void **state)
+{
+    int ret;
+    struct sysdb_attrs *attrs;
+
+    attrs = sysdb_new_attrs(ssh_test_ctx);
+    assert_non_null(attrs);
+    ret = sysdb_attrs_add_string(attrs, SYSDB_SSH_PUBKEY, TEST_SSH_PUBKEY);
+    assert_int_equal(ret, EOK);
+    ret = sysdb_attrs_add_string(attrs, SYSDB_USER_CERT, SSSD_TEST_CERT_0001);
+    assert_int_equal(ret, EOK);
+    ret = sysdb_attrs_add_string(attrs, SYSDB_USER_CERT, SSSD_TEST_CERT_0002);
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_set_user_attr(ssh_test_ctx->tctx->dom,
+                              ssh_test_ctx->ssh_user_fqdn,
+                              attrs,
+                              LDB_FLAG_MOD_ADD);
+    talloc_free(attrs);
+    assert_int_equal(ret, EOK);
+
+    mock_input_user(ssh_test_ctx, ssh_test_ctx->ssh_user_fqdn);
+    will_return(__wrap_sss_packet_get_cmd, SSS_SSH_GET_USER_PUBKEYS);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_ssh_user_one_pubkey_check);
+    ret = sss_cmd_execute(ssh_test_ctx->cctx, SSS_SSH_GET_USER_PUBKEYS,
+                          ssh_test_ctx->ssh_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(ssh_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+static int test_ssh_user_pubkey_cert_check(uint32_t status,
+                                           uint8_t *body, size_t blen)
+{
+    uint32_t val;
+    size_t exp_len;
+    size_t name_len;
+    size_t key_len[3];
+    uint8_t *key[3];
+    size_t rp = 0;
+    size_t c;
+
+    key[0] = sss_base64_decode(ssh_test_ctx, TEST_SSH_PUBKEY, &key_len[0]);
+    assert_non_null(key[0]);
+
+    key[1] = sss_base64_decode(ssh_test_ctx, SSSD_TEST_CERT_SSH_KEY_0001,
+                               &key_len[1]);
+    assert_non_null(key[1]);
+
+    key[2] = sss_base64_decode(ssh_test_ctx, SSSD_TEST_CERT_SSH_KEY_0002,
+                               &key_len[2]);
+    assert_non_null(key[2]);
+
+    name_len = strlen(ssh_test_ctx->ssh_user_fqdn) + 1;
+
+    exp_len = 2 * sizeof(uint32_t) + 3* 3* sizeof(uint32_t) + 3 * name_len
+                                   + key_len[0] + key_len[1] + key_len[2];
+
+    assert_int_equal(status, EOK);
+    assert_int_equal(blen, exp_len);
+
+    SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+    assert_int_equal(val, 3);
+
+    SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+    assert_int_equal(val, 0);
+
+    for (c = 0; c < 3; c++) {
+        SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+        assert_int_equal(val, 0);
+
+        SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+        assert_int_equal(val, name_len);
+
+        assert_memory_equal(body + rp, ssh_test_ctx->ssh_user_fqdn, name_len);
+        rp += name_len;
+
+        SAFEALIGN_COPY_UINT32(&val, &body[rp], &rp);
+        assert_int_equal(val, key_len[c]);
+
+        assert_memory_equal(body + rp, key[c], key_len[c]);
+        rp += key_len[c];
+    }
+
+    assert_int_equal(rp, blen);
+
+    return EOK;
+}
+
+void test_ssh_user_pubkey_cert(void **state)
+{
+    int ret;
+    struct sysdb_attrs *attrs;
+
+    attrs = sysdb_new_attrs(ssh_test_ctx);
+    assert_non_null(attrs);
+    ret = sysdb_attrs_add_string(attrs, SYSDB_SSH_PUBKEY, TEST_SSH_PUBKEY);
+    assert_int_equal(ret, EOK);
+    ret = sysdb_attrs_add_base64_blob(attrs, SYSDB_USER_CERT,
+                                      SSSD_TEST_CERT_0001);
+    assert_int_equal(ret, EOK);
+    ret = sysdb_attrs_add_base64_blob(attrs, SYSDB_USER_CERT,
+                                      SSSD_TEST_CERT_0002);
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_set_user_attr(ssh_test_ctx->tctx->dom,
+                              ssh_test_ctx->ssh_user_fqdn,
+                              attrs,
+                              LDB_FLAG_MOD_ADD);
+    talloc_free(attrs);
+    assert_int_equal(ret, EOK);
+
+    mock_input_user(ssh_test_ctx, ssh_test_ctx->ssh_user_fqdn);
+    will_return(__wrap_sss_packet_get_cmd, SSS_SSH_GET_USER_PUBKEYS);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    /* Enable certificate support */
+    ssh_test_ctx->ssh_ctx->use_cert_keys = true;
+    ssh_test_ctx->ssh_ctx->ca_db = discard_const("sql:" ABS_BUILD_DIR
+                                                "/src/tests/test_CA/p11_nssdb");
+
+    set_cmd_cb(test_ssh_user_pubkey_cert_check);
+    ret = sss_cmd_execute(ssh_test_ctx->cctx, SSS_SSH_GET_USER_PUBKEYS,
+                          ssh_test_ctx->ssh_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(ssh_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+int main(int argc, const char *argv[])
+{
+    int rv;
+    int no_cleanup = 0;
+    poptContext pc;
+    int opt;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        { "no-cleanup", 'n', POPT_ARG_NONE, &no_cleanup, 0,
+          _("Do not delete the test database after a test run"), NULL },
+        POPT_TABLEEND
+    };
+
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_ssh_user_no_pubkeys,
+                                        ssh_test_setup, ssh_test_teardown),
+        cmocka_unit_test_setup_teardown(test_ssh_user_pubkey,
+                                        ssh_test_setup, ssh_test_teardown),
+#ifdef HAVE_TEST_CA
+        cmocka_unit_test_setup_teardown(test_ssh_user_pubkey_cert_disabled,
+                                        ssh_test_setup, ssh_test_teardown),
+#ifdef HAVE_NSS
+        cmocka_unit_test_setup_teardown(test_ssh_user_pubkey_cert,
+                                        ssh_test_setup, ssh_test_teardown),
+#endif
+#endif
+    };
+
+    /* Set debug level to invalid value so we can decide if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while ((opt = poptGetNextOpt(pc)) != -1) {
+        switch (opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_CLI_INIT(debug_level);
+
+    /* Even though normally the tests should clean up after themselves
+     * they might not after a failed run. Remove the old DB to be sure */
+    tests_set_cwd();
+    test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME);
+    test_dom_suite_setup(TESTS_PATH);
+
+    rv = cmocka_run_group_tests(tests, NULL, NULL);
+    if (rv == 0 && !no_cleanup) {
+        test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME);
+    }
+
+#ifdef HAVE_NSS
+    /* Cleanup NSS and NSPR to make Valgrind happy. */
+    nspr_nss_cleanup();
+#endif
+
+    return rv;
+}
