[SSSD] [PATCHES] Add support for Smartcard authentication

Sumit Bose sbose at redhat.com
Fri Jul 10 16:40:30 UTC 2015


Hi,

this is the initial version of my patch which add Smartcard
authentication to SSSD. I'm still working on a design page which will
explain everything in more details so I will only add a short version
here.

The main job will be done by a new child process called p11_child. Since
the Smartcard support in GDM is based on NSS I used NSS for the first
version of p11_child as well. But since all PKCS#11 (API to talk to
Smartcards) related code is in this child process adding support for
other PKCS#11 frameworks like p11-kit would be straight forward (in fact
I already started on the p11-kit version). Using NSS here means you have
to add the PKCS#11 module for your Smartcards reader to /etc/pki/nssdb
(the NSS DB GDM uses as well) with modutil or pk11install from the
coolkey package.

The PAM configuration so far must not be changed. pam_sss will do a
pre-auth request similar to the OPT case for find a suitable
authentication method for the user. The pam responder then checks is
Smartcard authentication is enabled (pam_cert_auth = True in the [pam]
section of sssd.conf), if the service is a local one and if there if a
valid certificate can be found which is available in the users LDAP
entry as well. If all this checks pass pam_sss will ask the user for a
PIN and then SSSD tries to validate that PIN, public and private keys
all relate to each other. If no Smartcard is found for the user the
standard password prompt is displayed.

With some valuable input form Christian Heimes I think I found a way to
test the Smartcard support even without real hardware but I still have
to work out some of the details. I will add instructions to the design
page and better and more unit tests.

Any comments and suggestions are welcome.

bye,
Sumit
-------------- next part --------------
From 756e0a204aea681738c540f20e3cfd8dbf18a702 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Mon, 22 Jun 2015 16:36:36 +0200
Subject: [PATCH 1/6] utils: add NSS version of cert utils

---
 Makefile.am                        |  29 +++++-
 configure.ac                       |   4 +-
 contrib/sssd.spec.in               |   1 -
 src/tests/cmocka/test_cert_utils.c |   4 +
 src/util/cert/nss/cert.c           | 201 +++++++++++++++++++++++++++++++++++++
 5 files changed, 233 insertions(+), 6 deletions(-)
 create mode 100644 src/util/cert/nss/cert.c

diff --git a/Makefile.am b/Makefile.am
index b8cbc6df23ded1edb945a709b6dbe1c44eb54017..db06efb10f6dc67aaf7773dbb4fa08d1e50f8f5f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -704,6 +704,17 @@ if HAVE_NSS
                         src/util/crypto/nss/nss_util.c
     SSS_CRYPT_CFLAGS = $(NSS_CFLAGS)
     SSS_CRYPT_LIBS = $(NSS_LIBS)
+
+    SSS_CERT_SOURCES = \
+        src/util/cert/cert_common.c \
+        src/util/cert/nss/cert.c \
+        $(NULL)
+    SSS_CERT_CFLAGS = \
+        $(NSS_CFLAGS) \
+        $(NULL)
+    SSS_CERT_LIBS = \
+        $(NSS_LIBS) \
+        $(NULL)
 else
     SSS_CRYPT_SOURCES = src/util/crypto/libcrypto/crypto_base64.c \
                         src/util/crypto/libcrypto/crypto_hmac_sha1.c \
@@ -711,6 +722,17 @@ else
                         src/util/crypto/libcrypto/crypto_obfuscate.c
     SSS_CRYPT_CFLAGS = $(CRYPTO_CFLAGS)
     SSS_CRYPT_LIBS = $(CRYPTO_LIBS)
+
+    SSS_CERT_SOURCES = \
+        src/util/cert/cert_common.c \
+        src/util/cert/libcrypto/cert.c \
+        $(NULL)
+    SSS_CERT_CFLAGS = \
+        $(CRYPTO_CFLAGS) \
+        $(NULL)
+    SSS_CERT_LIBS = \
+        $(CRYPTO_LIBS) \
+        $(NULL)
 endif
 
 libsss_crypt_la_SOURCES = \
@@ -730,14 +752,13 @@ libsss_crypt_la_LDFLAGS = \
 pkglib_LTLIBRARIES += libsss_cert.la
 
 libsss_cert_la_SOURCES = \
-    src/util/cert/cert_common.c \
-    src/util/cert/libcrypto/cert.c \
+    $(SSS_CERT_SOURCES) \
     $(NULL)
 libsss_cert_la_CFLAGS = \
-    $(CRYPTO_CFLAGS) \
+    $(SSS_CERT_CFLAGS) \
     $(NULL)
 libsss_cert_la_LIBADD = \
-    $(CRYPTO_LIBS) \
+    $(SSS_CERT_LIBS) \
     $(TALLOC_LIBS) \
     libsss_crypt.la \
     libsss_debug.la \
diff --git a/configure.ac b/configure.ac
index 29bedf74db6594b5788d51570514a07e082d5e42..4b4f8f3228bf13c594c86e1e39474a1d02ffd984 100644
--- a/configure.ac
+++ b/configure.ac
@@ -341,7 +341,9 @@ if test x$cryptolib = xnss; then
     AM_CHECK_NSS
 fi
 
-AM_CHECK_LIBCRYPTO
+if test x$cryptolib = xlibcrypto; then
+    AM_CHECK_LIBCRYPTO
+fi
 
 AM_CHECK_INOTIFY
 
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index f475170ed5c235d7e5c3c79463ba9dfdc18852cc..a68f1bdec917cb4af6b4fd31860e642c69ada4de 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -111,7 +111,6 @@ BuildRequires: dbus-libs
 BuildRequires: openldap-devel
 BuildRequires: pam-devel
 BuildRequires: nss-devel
-BuildRequires: openssl-devel
 BuildRequires: nspr-devel
 BuildRequires: pcre-devel
 BuildRequires: libxslt
diff --git a/src/tests/cmocka/test_cert_utils.c b/src/tests/cmocka/test_cert_utils.c
index 5bcbafb27dbdff596c07b8aad10ca15a35ff4ef5..8063b1a65e8692142cbb3cf82fe41afa6567bc91 100644
--- a/src/tests/cmocka/test_cert_utils.c
+++ b/src/tests/cmocka/test_cert_utils.c
@@ -23,7 +23,9 @@
 */
 
 #include <popt.h>
+#ifdef HAVE_LIBCRYPTO
 #include <openssl/objects.h>
+#endif
 
 #include "util/cert.h"
 #include "tests/cmocka/common_mock.h"
@@ -349,7 +351,9 @@ int main(int argc, const char *argv[])
 
     ret = cmocka_run_group_tests(tests, NULL, NULL);
 
+#ifdef HAVE_LIBCRYPTO
     CRYPTO_cleanup_all_ex_data(); /* to make valgrind happy */
+#endif
 
 #ifdef HAVE_NSS
     /* Cleanup NSS and NSPR to make valgrind happy. */
diff --git a/src/util/cert/nss/cert.c b/src/util/cert/nss/cert.c
new file mode 100644
index 0000000000000000000000000000000000000000..d6a471d88a9b1ab3e352dfc6c1b84421e89a59d8
--- /dev/null
+++ b/src/util/cert/nss/cert.c
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+#include "util/util.h"
+
+#include <cert.h>
+#include <base64.h>
+
+#include "util/crypto/nss/nss_util.h"
+
+#define NS_CERT_HEADER "-----BEGIN CERTIFICATE-----"
+#define NS_CERT_TRAILER "-----END CERTIFICATE-----"
+#define NS_CERT_HEADER_LEN  ((sizeof NS_CERT_HEADER) - 1)
+#define NS_CERT_TRAILER_LEN ((sizeof NS_CERT_TRAILER) - 1)
+
+errno_t sss_cert_der_to_pem(TALLOC_CTX *mem_ctx, const uint8_t *der_blob,
+                            size_t der_size, char **pem, size_t *pem_size)
+{
+
+    CERTCertDBHandle *handle;
+    CERTCertificate *cert = NULL;
+    SECItem der_item;
+    char *ascii_crlf = NULL;
+    size_t ascii_crlf_len;
+    char *ascii_lf = NULL;
+    char *pem_cert_str = NULL;
+    int ret;
+    size_t c;
+    size_t d;
+
+    /* initialize NSS if needed */
+    ret = nspr_nss_init();
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "nspr_nss_init failed.\n");
+        return ret;
+    }
+
+    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");
+        return EINVAL;
+    }
+
+    ascii_crlf = BTOA_DataToAscii(cert->derCert.data, cert->derCert.len);
+    if (ascii_crlf == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "BTOA_DataToAscii failed.\n");
+        ret = EIO;
+        goto done;
+    }
+
+    ascii_crlf_len = strlen(ascii_crlf) + 1;
+    ascii_lf = malloc(ascii_crlf_len * sizeof(char));
+    if (ascii_lf == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "malloc failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    d = 0;
+    for (c = 0; c < ascii_crlf_len; c++) {
+        if (ascii_crlf[c] != '\r') {
+            ascii_lf[d++] = ascii_crlf[c];
+        }
+    }
+
+    pem_cert_str = talloc_asprintf(mem_ctx, "%s\n%s\n%s\n", NS_CERT_HEADER,
+                                                            ascii_lf,
+                                                            NS_CERT_TRAILER);
+    if (pem_cert_str == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (pem_size != NULL) {
+        *pem_size = strlen(pem_cert_str);
+    }
+
+    if (pem != NULL) {
+        *pem = pem_cert_str;
+        pem_cert_str = NULL;
+    }
+
+    ret = EOK;
+done:
+    talloc_free(pem_cert_str);
+    free(ascii_crlf);
+    free(ascii_lf);
+    CERT_DestroyCertificate(cert);
+
+    return ret;
+}
+
+errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem,
+                            uint8_t **_der_blob, size_t *_der_size)
+{
+    const char *ps;
+    const char *pe;
+    size_t pem_len;
+    uint8_t *der_blob = NULL;
+    size_t der_size;
+    CERTCertDBHandle *handle;
+    CERTCertificate *cert = NULL;
+    SECItem der_item;
+    int ret;
+    char *b64 = NULL;
+
+    /* initialize NSS if needed */
+    ret = nspr_nss_init();
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "nspr_nss_init failed.\n");
+        return ret;
+    }
+
+    if (pem == NULL || *pem == '\0') {
+        return EINVAL;
+    }
+
+    pem_len = strlen(pem);
+    if (pem_len <= NS_CERT_HEADER_LEN + NS_CERT_TRAILER_LEN) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "PEM data too short.\n");
+        return EINVAL;
+    }
+
+    if (strncmp(pem, NS_CERT_HEADER, NS_CERT_HEADER_LEN) != 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Wrong PEM header.\n");
+        return EINVAL;
+    }
+    if (pem[NS_CERT_HEADER_LEN] != '\n') {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing newline in PEM data.\n");
+        return EINVAL;
+    }
+
+    pe = pem + pem_len - NS_CERT_TRAILER_LEN;
+    if (pem[pem_len - 1] == '\n') {
+        pe--;
+    }
+    if (strncmp(pe, NS_CERT_TRAILER, NS_CERT_TRAILER_LEN) != 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Wrong PEM trailer.\n");
+        return EINVAL;
+    }
+
+    ps = pem + NS_CERT_HEADER_LEN + 1;
+
+    b64 = talloc_strndup(mem_ctx, ps, pe - ps);
+    if(b64 == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    der_blob = ATOB_AsciiToData(b64, &der_size);
+    if (der_blob == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "ATOB_AsciiToData failed.\n");
+        return EIO;
+    }
+
+    handle = CERT_GetDefaultCertDB();
+
+    der_item.len = der_size;
+    der_item.data = 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 (_der_blob != NULL) {
+        *_der_blob = talloc_memdup(mem_ctx, cert->derCert.data,
+                                   cert->derCert.len);
+        if (*_der_blob == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "talloc_memdup failed.\n");
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+
+    if (_der_size != NULL) {
+        *_der_size = cert->derCert.len;
+    }
+done:
+    free(der_blob);
+    talloc_free(b64);
+    CERT_DestroyCertificate(cert);
+
+    return ret;
+}
-- 
2.1.0

-------------- next part --------------
From 98abf452bb9e904aefc716508af9b55af92b4a4f Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Fri, 10 Jul 2015 12:10:53 +0200
Subject: [PATCH 2/6] Add NSS version of p11_child

---
 Makefile.am                   |  25 +-
 src/p11_child/p11_child_nss.c | 624 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 648 insertions(+), 1 deletion(-)
 create mode 100644 src/p11_child/p11_child_nss.c

diff --git a/Makefile.am b/Makefile.am
index db06efb10f6dc67aaf7773dbb4fa08d1e50f8f5f..a95a7f823bf206057677066f61bfaa98272174e7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -146,7 +146,9 @@ endif
 if BUILD_SEMANAGE
 sssdlibexec_PROGRAMS += selinux_child
 endif
-
+if HAVE_NSS
+sssdlibexec_PROGRAMS += p11_child
+endif
 
 if BUILD_PAC_RESPONDER
     sssdlibexec_PROGRAMS += sssd_pac
@@ -3129,6 +3131,23 @@ proxy_child_LDADD = \
     $(SSSD_LIBS) \
     $(SSSD_INTERNAL_LTLIBS)
 
+p11_child_SOURCES = \
+    src/p11_child/p11_child_nss.c \
+    src/util/atomic_io.c \
+    $(NULL)
+p11_child_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(POPT_CFLAGS) \
+    $(NSS_CFLAGS) \
+    $(NULL)
+p11_child_LDADD = \
+    libsss_debug.la \
+    $(TALLOC_LIBS) \
+    $(POPT_LIBS) \
+    $(NSS_LIBS) \
+    libsss_crypt.la \
+    $(NULL)
+
 memberof_la_SOURCES = \
     src/ldb_modules/memberof.c \
     src/util/util.c
@@ -3524,6 +3543,10 @@ if BUILD_SEMANAGE
 	-chgrp $(SSSD_USER) $(DESTDIR)$(sssdlibexecdir)/selinux_child
 	chmod 4750 $(DESTDIR)$(sssdlibexecdir)/selinux_child
 endif
+if HAVE_NSS
+	-chgrp $(SSSD_USER) $(DESTDIR)$(sssdlibexecdir)/p11_child
+	chmod 4750 $(DESTDIR)$(sssdlibexecdir)/p11_child
+endif
 endif
 
 install-data-hook:
diff --git a/src/p11_child/p11_child_nss.c b/src/p11_child/p11_child_nss.c
new file mode 100644
index 0000000000000000000000000000000000000000..eb2db692188535d92c36249ecc0c0a4c123a5902
--- /dev/null
+++ b/src/p11_child/p11_child_nss.c
@@ -0,0 +1,624 @@
+/*
+    SSSD
+
+    Helper child to commmunicate with SamrtCard via NSS
+
+    Authors:
+        Sumit Bose <sbose at redhat.com>
+
+    Copyright (C) 2015 Red Hat
+
+    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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <popt.h>
+
+#include "util/util.h"
+
+#include <nss.h>
+#include <base64.h>
+#include <cryptohi.h>
+#include <secmod.h>
+#include <cert.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <prerror.h>
+
+#include "util/child_common.h"
+#include "providers/dp_backend.h"
+#include "util/crypto/sss_crypto.h"
+
+enum op_mode {
+    OP_NONE,
+    OP_AUTH,
+    OP_PREAUTH
+};
+
+enum pin_mode {
+    PIN_NONE,
+    PIN_STDIN,
+    PIN_KEYPAD
+};
+
+static char *password_passthrough(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+  /* give up if 1) no password was supplied, or 2) the password has already
+ *    * been rejected once by this token. */
+  if (retry || (arg == NULL)) {
+    return NULL;
+  }
+  return PL_strdup((char *)arg);
+}
+
+
+
+int do_work(TALLOC_CTX *mem_ctx, const char *slot_name_in, enum op_mode mode,
+            const char *pin, char **cert, char **token_name_out)
+{
+    int ret;
+    SECStatus rv;
+    NSSInitContext *nss_ctx;
+    SECMODModuleList *mod_list;
+    SECMODModuleList *mod_list_item;
+    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;
+    CERTCertList *cert_list = NULL;
+    CERTCertListNode *cert_list_node;
+    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;
+    SECItem signed_random_value = {0};
+    SECKEYPublicKey *pub_key;
+    CERTCertificate *found_cert = NULL;
+    PK11SlotList *list = NULL;
+    PK11SlotListElement *le;
+
+
+    nss_ctx = NSS_InitContext("/etc/pki/nssdb", "", "", SECMOD_DB,
+                              &parameters, flags);
+    if (nss_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "NSS_InitContext failed [%d].\n",
+                                 PR_GetError());
+        return EIO;
+    }
+
+    PK11_SetPasswordFunc(password_passthrough);
+
+    DEBUG(SSSDBG_TRACE_ALL, "Default Module List:\n");
+    mod_list = SECMOD_GetDefaultModuleList();
+    for (mod_list_item = mod_list; mod_list_item != NULL;
+                                   mod_list_item = mod_list_item->next) {
+        DEBUG(SSSDBG_TRACE_ALL, "common name: [%s].\n",
+                                mod_list_item->module->commonName);
+        DEBUG(SSSDBG_TRACE_ALL, "dll name: [%s].\n",
+                                mod_list_item->module->dllName);
+    }
+
+    DEBUG(SSSDBG_TRACE_ALL, "Dead Module List:\n");
+    mod_list = SECMOD_GetDeadModuleList();
+    for (mod_list_item = mod_list; mod_list_item != NULL;
+                                   mod_list_item = mod_list_item->next) {
+        DEBUG(SSSDBG_TRACE_ALL, "common name: [%s].\n",
+                                mod_list_item->module->commonName);
+        DEBUG(SSSDBG_TRACE_ALL, "dll name: [%s].\n",
+                                mod_list_item->module->dllName);
+    }
+
+    DEBUG(SSSDBG_TRACE_ALL, "DB Module List:\n");
+    mod_list = SECMOD_GetDeadModuleList();
+    for (mod_list_item = mod_list; mod_list_item != NULL;
+                                   mod_list_item = mod_list_item->next) {
+        DEBUG(SSSDBG_TRACE_ALL, "common name: [%s].\n",
+                                mod_list_item->module->commonName);
+        DEBUG(SSSDBG_TRACE_ALL, "dll name: [%s].\n",
+                                mod_list_item->module->dllName);
+    }
+
+    if (slot_name_in != NULL) {
+        slot = PK11_FindSlotByName(slot_name_in);
+        if (slot == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "PK11_FindSlotByName failed for [%s]: [%d].\n",
+                                     slot_name_in, PR_GetError());
+            return EIO;
+        }
+    } else {
+
+        list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_TRUE,
+                                 NULL);
+        if (list == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "PK11_GetAllTokens failed.\n");
+            return EIO;
+        }
+
+        for (le = list->head; le; le = le->next) {
+            CK_SLOT_INFO slInfo;
+
+            slInfo.flags = 0;
+            rv = PK11_GetSlotInfo(le->slot, &slInfo);
+            DEBUG(SSSDBG_TRACE_ALL,
+                  "Description [%s] Manufacturer [%s] flags [%lu].\n",
+                  slInfo.slotDescription, slInfo.manufacturerID, slInfo.flags);
+            if (rv == SECSuccess && (slInfo.flags & CKF_REMOVABLE_DEVICE)) {
+                slot = PK11_ReferenceSlot(le->slot);
+                break;
+           }
+        }
+        PK11_FreeSlotList(list);
+        if (slot == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "No removable slots found.\n");
+            return EIO;
+        }
+    }
+
+
+    slot_id = PK11_GetSlotID(slot);
+    module_id = PK11_GetModuleID(slot);
+    slot_name = PK11_GetSlotName(slot);
+    token_name = PK11_GetTokenName(slot);
+    DEBUG(SSSDBG_TRACE_ALL, "Found [%s] in slot [%s][%d] of module [%d].\n",
+          token_name, slot_name, (int) slot_id, (int) module_id);
+
+    if (PK11_IsFriendly(slot)) {
+        DEBUG(SSSDBG_TRACE_ALL, "Token is friendly.\n");
+    } else {
+        DEBUG(SSSDBG_TRACE_ALL,
+              "Token is NOT friendly.\n");
+        if (mode == OP_PREAUTH) {
+            DEBUG(SSSDBG_TRACE_ALL, "Trying to switch to friendly to read certificate.\n");
+            rv = PK11_UpdateSlotAttribute(slot, &friendly_attr, PR_TRUE);
+            if (rv != SECSuccess) {
+                DEBUG(SSSDBG_OP_FAILURE,
+                      "PK11_UpdateSlotAttribute failed, continue.\n");
+            }
+        }
+    }
+
+    /* TODO: check  PK11_ProtectedAuthenticationPath() and return the result */
+    if (mode == OP_AUTH || PK11_NeedLogin(slot)) {
+        DEBUG(SSSDBG_TRACE_ALL, "Login required.\n");
+        if (pin != NULL) {
+            rv = PK11_Authenticate(slot, PR_FALSE, discard_const(pin));
+            if (rv !=  SECSuccess) {
+                DEBUG(SSSDBG_OP_FAILURE, "PK11_Authenticate failed: [%d].\n",
+                                         PR_GetError());
+                return EIO;
+            }
+        } else {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Login required but no pin available, continue.\n");
+        }
+    } else {
+        DEBUG(SSSDBG_TRACE_ALL, "Login NOT required.\n");
+    }
+
+    cert_list = PK11_ListCertsInSlot(slot);
+    if (cert_list == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "PK11_ListCertsInSlot failed: [%d].\n",
+                                 PR_GetError());
+        return EIO;
+    }
+
+    for (cert_list_node = CERT_LIST_HEAD(cert_list);
+                !CERT_LIST_END(cert_list_node, cert_list);
+                cert_list_node = CERT_LIST_NEXT(cert_list_node)) {
+        if (cert_list_node->cert) {
+            DEBUG(SSSDBG_TRACE_ALL, "found cert[%s][%s]\n",
+                             cert_list_node->cert->nickname,
+                             cert_list_node->cert->subjectName);
+        } else {
+            DEBUG(SSSDBG_TRACE_ALL, "--- empty cert list node ---\n");
+        }
+    }
+
+    rv = CERT_FilterCertListByUsage(cert_list, certUsageSSLClient, PR_FALSE);
+    if (rv != SECSuccess) {
+        DEBUG(SSSDBG_OP_FAILURE, "CERT_FilterCertListByUsage failed: [%d].\n",
+                                 PR_GetError());
+        return EIO;
+    }
+
+    rv = CERT_FilterCertListForUserCerts(cert_list);
+    if (rv != SECSuccess) {
+        DEBUG(SSSDBG_OP_FAILURE, "CERT_FilterCertListForUserCerts failed: [%d].\n",
+                                 PR_GetError());
+        return EIO;
+    }
+
+
+    handle = CERT_GetDefaultCertDB();
+    if (handle == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "CERT_GetDefaultCertDB failed: [%d].\n",
+                                 PR_GetError());
+        return EIO;
+    }
+
+
+    found_cert = NULL;
+    DEBUG(SSSDBG_TRACE_ALL, "Filtered certificates:\n");
+    for (cert_list_node = CERT_LIST_HEAD(cert_list);
+                !CERT_LIST_END(cert_list_node, cert_list);
+                cert_list_node = CERT_LIST_NEXT(cert_list_node)) {
+        if (cert_list_node->cert) {
+            DEBUG(SSSDBG_TRACE_ALL, "found cert[%s][%s]\n",
+                             cert_list_node->cert->nickname,
+                             cert_list_node->cert->subjectName);
+
+            if (found_cert == NULL) {
+                found_cert = cert_list_node->cert;
+            } else {
+                DEBUG(SSSDBG_TRACE_ALL, "More than one certificate found, " \
+                                        "using just the first one.\n");
+            }
+        } else {
+            DEBUG(SSSDBG_TRACE_ALL, "--- empty cert list node ---\n");
+        }
+    }
+
+    if (found_cert == NULL) {
+        DEBUG(SSSDBG_TRACE_ALL, "No certificate found.\n");
+        *cert = NULL;
+        *token_name_out = NULL;
+        ret = EOK;
+        goto done;
+    }
+
+    rv = CERT_VerifyCertificateNow(handle, found_cert, PR_TRUE,
+                                   certificateUsageSSLClient, NULL, NULL);
+    if (rv != SECSuccess) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "CERT_VerifyCertificateNow failed [%d].\n",
+              PR_GetError());
+        ret = EIO;
+        goto done;
+    }
+
+    if (mode == OP_AUTH) {
+        rv = PK11_GenerateRandom(random_value, sizeof(random_value));
+        if (rv != SECSuccess) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "PK11_GenerateRandom failed [%d].\n", PR_GetError());
+            return EIO;
+        }
+
+        priv_key = PK11_FindPrivateKeyFromCert(slot, found_cert, NULL);
+        if (priv_key == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "PK11_FindPrivateKeyFromCert failed [%d]." \
+                  "Maybe pin is missing.\n", PR_GetError());
+            ret = EIO;
+            goto done;
+        }
+
+        algtag = SEC_GetSignatureAlgorithmOidTag(priv_key->keyType,
+                                                  SEC_OID_SHA1);
+        if (algtag == SEC_OID_UNKNOWN) {
+            SECKEY_DestroyPrivateKey(priv_key);
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "SEC_GetSignatureAlgorithmOidTag failed [%d].",
+                  PR_GetError());
+            ret = EIO;
+            goto done;
+        }
+
+        rv = SEC_SignData(&signed_random_value,
+                          random_value, sizeof(random_value),
+                          priv_key, algtag);
+        SECKEY_DestroyPrivateKey(priv_key);
+        if (rv != SECSuccess) {
+            DEBUG(SSSDBG_OP_FAILURE, "SEC_SignData failed [%d].",
+                                     PR_GetError());
+            ret = EIO;
+            goto done;
+        }
+
+        pub_key = CERT_ExtractPublicKey(found_cert);
+        if (pub_key == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "CERT_ExtractPublicKey failed [%d].", PR_GetError());
+            ret = EIO;
+            goto done;
+        }
+
+        rv = VFY_VerifyData(random_value, sizeof(random_value),
+                            pub_key, &signed_random_value, algtag,
+                            NULL);
+        SECKEY_DestroyPublicKey(pub_key);
+        if (rv != SECSuccess) {
+            DEBUG(SSSDBG_OP_FAILURE, "VFY_VerifyData failed [%d].",
+                                     PR_GetError());
+            ret = EACCES;
+            goto done;
+        }
+
+        DEBUG(SSSDBG_TRACE_ALL,
+              "Certificate verified and validated.\n");
+    }
+
+    *cert = sss_base64_encode(mem_ctx, found_cert->derCert.data,
+                                       found_cert->derCert.len);
+    if (*cert == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n");
+        ret = ENOMEM;
+    }
+
+    *token_name_out = talloc_strdup(mem_ctx, token_name);
+    if (*token_name_out == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy slot name.\n");
+        ret = ENOMEM;
+    }
+
+    ret = EOK;
+
+done:
+    if (slot != NULL) {
+        PK11_FreeSlot(slot);
+    }
+
+    if (cert_list != NULL) {
+        CERT_DestroyCertList(cert_list);
+    }
+
+    PORT_Free(signed_random_value.data);
+
+    rv = NSS_ShutdownContext(nss_ctx);
+    if (rv != SECSuccess) {
+        DEBUG(SSSDBG_OP_FAILURE, "NSS_ShutdownContext failed [%d].\n",
+                                 PR_GetError());
+    }
+
+    return ret;
+}
+
+static errno_t p11c_recv_data(TALLOC_CTX *mem_ctx, int fd, char **pin)
+{
+    uint8_t buf[IN_BUF_SIZE];
+    ssize_t len;
+    errno_t ret;
+    char *str;
+
+    errno = 0;
+    len = sss_atomic_read_s(fd, buf, IN_BUF_SIZE);
+    if (len == -1) {
+        ret = errno;
+        ret = (ret == 0) ? EINVAL: ret;
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "read failed [%d][%s].\n", ret, strerror(ret));
+        return ret;
+    }
+
+    if (len == 0 || buf == NULL || *buf == '\0') {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n");
+        return EINVAL;
+    }
+
+    str = talloc_strndup(mem_ctx, (char *) buf, len);
+    if (str == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+        return ENOMEM;
+    }
+
+    if (strlen(str) != len) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Input contains additional data, only PIN expected.\n");
+        talloc_free(str);
+        return EINVAL;
+    }
+
+    *pin = str;
+
+    return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+    int opt;
+    poptContext pc;
+    int debug_fd = -1;
+    errno_t ret;
+    TALLOC_CTX *main_ctx = NULL;
+    char *cert;
+    enum op_mode mode = OP_NONE;
+    enum pin_mode pin_mode = PIN_NONE;
+    char *pin = NULL;
+    char *slot_name_in = NULL;
+    char *token_name_out = NULL;
+
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0,
+         _("Debug level"), NULL},
+        {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0,
+         _("Add debug timestamps"), NULL},
+        {"debug-microseconds", 0, POPT_ARG_INT, &debug_microseconds, 0,
+         _("Show timestamps with microseconds"), NULL},
+        {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0,
+         _("An open file descriptor for the debug logs"), NULL},
+        {"debug-to-stderr", 0, POPT_ARG_NONE | POPT_ARGFLAG_DOC_HIDDEN,
+         &debug_to_stderr, 0,
+         _("Send the debug output to stderr directly."), NULL },
+        {"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},
+        {"pin", 0, POPT_ARG_NONE, NULL, 'i', _("Expect PIN on stdin"), NULL},
+        {"keypad", 0, POPT_ARG_NONE, NULL, 'k', _("Expect PIN on keypad"),
+         NULL},
+        POPT_TABLEEND
+    };
+
+    /* 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) {
+        case 'a':
+            if (mode != OP_NONE) {
+                fprintf(stderr,
+                        "\n--auth and --pre are mutually exclusive and " \
+                        "should be only used once.\n\n");
+                poptPrintUsage(pc, stderr, 0);
+                _exit(-1);
+            }
+            mode = OP_AUTH;
+            break;
+        case 'p':
+            if (mode != OP_NONE) {
+                fprintf(stderr,
+                        "\n--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 'i':
+            if (pin_mode != PIN_NONE) {
+                fprintf(stderr, "\n--pin and --keypad are mutually exclusive " \
+                                "and should be only used once.\n\n");
+                poptPrintUsage(pc, stderr, 0);
+                _exit(-1);
+            }
+            pin_mode = PIN_STDIN;
+            break;
+        case 'k':
+            if (pin_mode != PIN_NONE) {
+                fprintf(stderr, "\n--pin and --keypad are mutually exclusive " \
+                                "and should be only used once.\n\n");
+                poptPrintUsage(pc, stderr, 0);
+                _exit(-1);
+            }
+            pin_mode = PIN_KEYPAD;
+            break;
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                  poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            _exit(-1);
+        }
+    }
+
+    if (mode == OP_NONE) {
+        fprintf(stderr, "\nMissing operation mode, " \
+                        "either --auth or --pre must be specified.\n\n");
+        poptPrintUsage(pc, stderr, 0);
+        _exit(-1);
+    } else if (mode == OP_AUTH && pin_mode == PIN_NONE) {
+        fprintf(stderr, "\nMissing pin mode for authentication, " \
+                        "either --pin or --keypad must be specified.\n");
+        poptPrintUsage(pc, stderr, 0);
+        _exit(-1);
+    }
+
+    poptFreeContext(pc);
+
+    DEBUG_INIT(debug_level);
+
+    debug_prg_name = talloc_asprintf(NULL, "[sssd[p11_child[%d]]]", getpid());
+    if (debug_prg_name == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+        goto fail;
+    }
+
+    if (debug_fd != -1) {
+        ret = set_debug_file_from_fd(debug_fd);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "set_debug_file_from_fd failed.\n");
+        }
+    }
+
+    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 with effective IDs: [%"SPRIuid"][%"SPRIgid"].\n",
+          geteuid(), getegid());
+
+    if (getuid() != 0) {
+        ret = setuid(0);
+        if (ret == -1) {
+            ret = errno;
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "setuid failed: %d, p11_child might not work!\n", ret);
+        }
+    }
+
+    if (getgid() != 0) {
+        ret = setgid(0);
+        if (ret == -1) {
+            ret = errno;
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "setgid failed: %d, p11_child might not work!\n", ret);
+        }
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL,
+          "Running with real IDs [%"SPRIuid"][%"SPRIgid"].\n",
+          getuid(), getgid());
+
+    main_ctx = talloc_new(NULL);
+    if (main_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n");
+        talloc_free(discard_const(debug_prg_name));
+        goto fail;
+    }
+    talloc_steal(main_ctx, debug_prg_name);
+
+
+    if (mode == OP_AUTH && pin_mode == PIN_STDIN) {
+        ret = p11c_recv_data(main_ctx, STDIN_FILENO, &pin);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_FATAL_FAILURE, "Failed to read pin.\n");
+            goto fail;
+        }
+    }
+
+    ret = do_work(main_ctx, slot_name_in, mode, pin, &cert, &token_name_out);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "do_work failed.\n");
+        goto fail;
+    }
+
+    if (cert != NULL) {
+        fprintf(stdout, "%s\n", token_name_out);
+        fprintf(stdout, "%s\n", cert);
+    }
+
+    talloc_free(main_ctx);
+    return EXIT_SUCCESS;
+fail:
+    DEBUG(SSSDBG_CRIT_FAILURE, "p11_child failed!\n");
+    close(STDOUT_FILENO);
+    talloc_free(main_ctx);
+    return EXIT_FAILURE;
+}
-- 
2.1.0

-------------- next part --------------
From c327c8c2ad72bcec8742b302670ecda181b102a8 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Fri, 26 Jun 2015 14:45:21 +0200
Subject: [PATCH 3/6] pack_message_v3: allow empty name

---
 src/sss_client/pam_message.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/sss_client/pam_message.c b/src/sss_client/pam_message.c
index b8104c680d0e733b713c665e6206dc4b0d379237..b239f6f53da54054c52e484bdd076193709cb003 100644
--- a/src/sss_client/pam_message.c
+++ b/src/sss_client/pam_message.c
@@ -107,9 +107,10 @@ int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer)
     uint8_t *buf;
     size_t rp;
 
-    len = sizeof(uint32_t) +
-          2*sizeof(uint32_t) + pi->pam_user_size +
-          sizeof(uint32_t);
+    len = sizeof(uint32_t) + sizeof(uint32_t);
+
+    len +=  *pi->pam_user != '\0' ?
+                2*sizeof(uint32_t) + pi->pam_user_size : 0;
     len += *pi->pam_service != '\0' ?
                 2*sizeof(uint32_t) + pi->pam_service_size : 0;
     len += *pi->pam_tty != '\0' ?
-- 
2.1.0

-------------- next part --------------
From f4f19dffed19efa6b10a8a8ff6fbdae4dda0d282 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Fri, 26 Jun 2015 17:55:23 +0200
Subject: [PATCH 4/6] authok: add support for Smart Card related authtokens

---
 src/sss_client/sss_cli.h        |  7 ++++
 src/tests/cmocka/test_authtok.c | 75 +++++++++++++++++++++++++++++++++++++++++
 src/util/authtok.c              | 64 +++++++++++++++++++++++++++++++++++
 src/util/authtok.h              | 41 ++++++++++++++++++++++
 4 files changed, 187 insertions(+)

diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 0dfb525bacba5f6928e8ece76e05f60d7f2eebd5..3c4e938ae37c042879b1ae26fe389fa37cef682c 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -308,6 +308,13 @@ enum sss_authtok_type {
     SSS_AUTHTOK_TYPE_2FA =       0x0003, /**< Authentication token has two
                                           * factors, they may or may no contain
                                           * a trailing \\0 */
+    SSS_AUTHTOK_TYPE_SC_PIN =    0x0004, /**< Authentication token is a Smart
+                                          * Card pin, it may or may no contain
+                                          * a trailing \\0 */
+    SSS_AUTHTOK_TYPE_SC_KEYPAD = 0x0005, /**< Authentication token indicates
+                                          * Smart Card authentication is used
+                                          * and that the pin will be entered
+                                          * at the card reader. */
 };
 
 /**
diff --git a/src/tests/cmocka/test_authtok.c b/src/tests/cmocka/test_authtok.c
index 5aa47c7b6b8c955666a9c73d5f9627d6378d13e0..30dcc9c8401103a275bd592fe8afd2c2f396ffb1 100644
--- a/src/tests/cmocka/test_authtok.c
+++ b/src/tests/cmocka/test_authtok.c
@@ -488,6 +488,77 @@ void test_sss_authtok_2fa_blobs_missing_null(void **state)
     MISSING_NULL_CHECK;
 }
 
+void test_sss_authtok_sc_keypad(void **state)
+{
+    struct test_state *ts;
+
+    ts = talloc_get_type_abort(*state, struct test_state);
+
+    sss_authtok_set_sc_keypad(NULL);
+
+    sss_authtok_set_sc_keypad(ts->authtoken);
+    assert_int_equal(sss_authtok_get_type(ts->authtoken),
+                     SSS_AUTHTOK_TYPE_SC_KEYPAD);
+    assert_int_equal(sss_authtok_get_size(ts->authtoken), 0);
+    assert_null(sss_authtok_get_data(ts->authtoken));
+}
+
+void test_sss_authtok_sc_pin(void **state)
+{
+    struct test_state *ts;
+    int ret;
+    size_t size;
+    const char *pin;
+    size_t len;
+
+    ts = talloc_get_type_abort(*state, struct test_state);
+
+    ret = sss_authtok_set_sc_pin(NULL, NULL, 0);
+    assert_int_equal(ret, EFAULT);
+
+    ret = sss_authtok_set_sc_pin(ts->authtoken, NULL, 0);
+    assert_int_equal(ret, EINVAL);
+
+    ret = sss_authtok_set_sc_pin(ts->authtoken, "12345678", 0);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(sss_authtok_get_type(ts->authtoken),
+                     SSS_AUTHTOK_TYPE_SC_PIN);
+    size = sss_authtok_get_size(ts->authtoken);
+    assert_int_equal(size, 9);
+    assert_memory_equal(sss_authtok_get_data(ts->authtoken), "12345678\0",
+                                             size);
+
+    ret = sss_authtok_set_sc_pin(ts->authtoken, "12345678", 5);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(sss_authtok_get_type(ts->authtoken),
+                     SSS_AUTHTOK_TYPE_SC_PIN);
+    size = sss_authtok_get_size(ts->authtoken);
+    assert_int_equal(size, 6);
+    assert_memory_equal(sss_authtok_get_data(ts->authtoken), "12345\0",
+                                             size);
+
+    ret = sss_authtok_get_sc_pin(ts->authtoken, &pin, &len);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(len, 5);
+    assert_string_equal(pin, "12345");
+
+    sss_authtok_set_empty(ts->authtoken);
+
+    ret = sss_authtok_get_sc_pin(ts->authtoken, &pin, &len);
+    assert_int_equal(ret, ENOENT);
+
+    ret = sss_authtok_set_password(ts->authtoken, "12345", 0);
+    assert_int_equal(ret, EOK);
+
+    ret = sss_authtok_get_sc_pin(ts->authtoken, &pin, &len);
+    assert_int_equal(ret, EACCES);
+
+    sss_authtok_set_empty(ts->authtoken);
+
+    ret = sss_authtok_get_sc_pin(NULL, &pin, &len);
+    assert_int_equal(ret, EFAULT);
+}
+
 int main(int argc, const char *argv[])
 {
     poptContext pc;
@@ -517,6 +588,10 @@ int main(int argc, const char *argv[])
                                         setup, teardown),
         cmocka_unit_test_setup_teardown(test_sss_authtok_2fa_blobs_missing_null,
                                         setup, teardown),
+        cmocka_unit_test_setup_teardown(test_sss_authtok_sc_keypad,
+                                        setup, teardown),
+        cmocka_unit_test_setup_teardown(test_sss_authtok_sc_pin,
+                                        setup, teardown),
     };
 
     /* Set debug level to invalid value so we can deside if -d 0 was used. */
diff --git a/src/util/authtok.c b/src/util/authtok.c
index 45761df80175fded8a6c6e5dac8a90180b11d225..6062cd875ce2c6b541ef237e7f7bdddac80366c5 100644
--- a/src/util/authtok.c
+++ b/src/util/authtok.c
@@ -39,6 +39,8 @@ size_t sss_authtok_get_size(struct sss_auth_token *tok)
     case SSS_AUTHTOK_TYPE_PASSWORD:
     case SSS_AUTHTOK_TYPE_CCFILE:
     case SSS_AUTHTOK_TYPE_2FA:
+    case SSS_AUTHTOK_TYPE_SC_PIN:
+    case SSS_AUTHTOK_TYPE_SC_KEYPAD:
         return tok->length;
     case SSS_AUTHTOK_TYPE_EMPTY:
         return 0;
@@ -72,6 +74,8 @@ errno_t sss_authtok_get_password(struct sss_auth_token *tok,
         return EOK;
     case SSS_AUTHTOK_TYPE_CCFILE:
     case SSS_AUTHTOK_TYPE_2FA:
+    case SSS_AUTHTOK_TYPE_SC_PIN:
+    case SSS_AUTHTOK_TYPE_SC_KEYPAD:
         return EACCES;
     }
 
@@ -95,6 +99,8 @@ errno_t sss_authtok_get_ccfile(struct sss_auth_token *tok,
         return EOK;
     case SSS_AUTHTOK_TYPE_PASSWORD:
     case SSS_AUTHTOK_TYPE_2FA:
+    case SSS_AUTHTOK_TYPE_SC_PIN:
+    case SSS_AUTHTOK_TYPE_SC_KEYPAD:
         return EACCES;
     }
 
@@ -144,9 +150,11 @@ void sss_authtok_set_empty(struct sss_auth_token *tok)
         return;
     case SSS_AUTHTOK_TYPE_PASSWORD:
     case SSS_AUTHTOK_TYPE_2FA:
+    case SSS_AUTHTOK_TYPE_SC_PIN:
         safezero(tok->data, tok->length);
         break;
     case SSS_AUTHTOK_TYPE_CCFILE:
+    case SSS_AUTHTOK_TYPE_SC_KEYPAD:
         break;
     }
 
@@ -187,6 +195,11 @@ errno_t sss_authtok_set(struct sss_auth_token *tok,
         return sss_authtok_set_ccfile(tok, (const char *)data, len);
     case SSS_AUTHTOK_TYPE_2FA:
         return sss_authtok_set_2fa_from_blob(tok, data, len);
+    case SSS_AUTHTOK_TYPE_SC_PIN:
+        return sss_authtok_set_sc_pin(tok, (const char*)data, len);
+    case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+        sss_authtok_set_sc_keypad(tok);
+        return EOK;
     case SSS_AUTHTOK_TYPE_EMPTY:
         sss_authtok_set_empty(tok);
         return EOK;
@@ -411,3 +424,54 @@ errno_t sss_authtok_set_2fa(struct sss_auth_token *tok,
 
     return EOK;
 }
+
+errno_t sss_authtok_set_sc_pin(struct sss_auth_token *tok, const char *pin,
+                               size_t len)
+{
+    if (tok == NULL) {
+        return EFAULT;
+    }
+    if (pin == NULL) {
+        return EINVAL;
+    }
+
+    sss_authtok_set_empty(tok);
+
+    return sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_SC_PIN,
+                                  "sc_pin", pin, len);
+}
+
+errno_t sss_authtok_get_sc_pin(struct sss_auth_token *tok, const char **pin,
+                               size_t *len)
+{
+    if (!tok) {
+        return EFAULT;
+    }
+    switch (tok->type) {
+    case SSS_AUTHTOK_TYPE_EMPTY:
+        return ENOENT;
+    case SSS_AUTHTOK_TYPE_SC_PIN:
+        *pin = (const char *)tok->data;
+        if (len) {
+            *len = tok->length - 1;
+        }
+        return EOK;
+    case SSS_AUTHTOK_TYPE_PASSWORD:
+    case SSS_AUTHTOK_TYPE_CCFILE:
+    case SSS_AUTHTOK_TYPE_2FA:
+    case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+        return EACCES;
+    }
+
+    return EINVAL;
+}
+
+void sss_authtok_set_sc_keypad(struct sss_auth_token *tok)
+{
+    if (!tok) {
+        return;
+    }
+    sss_authtok_set_empty(tok);
+
+    tok->type = SSS_AUTHTOK_TYPE_SC_KEYPAD;
+}
diff --git a/src/util/authtok.h b/src/util/authtok.h
index cb366270832852281a222018f8e27feb1500ff01..a4523ddca403f64aba7daf35fdaf20b0fbf1dc04 100644
--- a/src/util/authtok.h
+++ b/src/util/authtok.h
@@ -223,4 +223,45 @@ errno_t sss_authtok_set_2fa(struct sss_auth_token *tok,
 errno_t sss_authtok_get_2fa(struct sss_auth_token *tok,
                             const char **fa1, size_t *fa1_len,
                             const char **fa2, size_t *fa2_len);
+
+/**
+ * @brief Set a Smart Card pin into a an auth token, replacing any previous data
+ *
+ * @param tok        A pointer to a sss_auth_token structure to change, also
+ *                   used as a memory context to allocate the internal data.
+ * @param pin        A string
+ * @param len        The length of the string or, if 0 is passed,
+ *                   then strlen(password) will be used internally.
+ *
+ * @return       EOK on success
+ *               ENOMEM on error
+ */
+errno_t sss_authtok_set_sc_pin(struct sss_auth_token *tok, const char *pin,
+                               size_t len);
+
+/**
+ * @brief Returns a Smart Card pin as const string if the auth token is of
+ *        type SSS_AUTHTOK_TYPE_SC_PIN, otherwise it returns an error
+ *
+ * @param tok    A pointer to an sss_auth_token
+ * @param pwd    A pointer to a const char *, that will point to a null
+ *               terminated string
+ * @param len    The length of the pin string
+ *
+ * @return       EOK on success
+ *               ENOENT if the token is empty
+ *               EACCESS if the token is not a Smart Card pin token
+ */
+errno_t sss_authtok_get_sc_pin(struct sss_auth_token *tok, const char **pin,
+                               size_t *len);
+
+/**
+ * @brief Sets an auth token to type SSS_AUTHTOK_TYPE_SC_KEYPAD, replacing any
+ *        previous data
+ *
+ * @param tok        A pointer to a sss_auth_token structure to change, also
+ *                   used as a memory context to allocate the internal data.
+ */
+void sss_authtok_set_sc_keypad(struct sss_auth_token *tok);
+
 #endif /*  __AUTHTOK_H__ */
-- 
2.1.0

-------------- next part --------------
From e6740a092c50cc132ee64b6e006e6b6d045edb47 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Fri, 10 Jul 2015 17:54:07 +0200
Subject: [PATCH 5/6] PAM: add certificate support to PAM (pre-)auth requests

---
 Makefile.am                     |   4 +
 src/confdb/confdb.h             |   1 +
 src/responder/pam/pamsrv.c      |  23 ++
 src/responder/pam/pamsrv.h      |  24 ++
 src/responder/pam/pamsrv_cmd.c  | 321 ++++++++++++++++++++++---
 src/responder/pam/pamsrv_p11.c  | 502 ++++++++++++++++++++++++++++++++++++++++
 src/sss_client/sss_cli.h        |   1 +
 src/tests/cmocka/test_pam_srv.c | 485 +++++++++++++++++++++++++++++++++++++-
 src/util/util_errors.c          |   1 +
 src/util/util_errors.h          |   1 +
 10 files changed, 1324 insertions(+), 39 deletions(-)
 create mode 100644 src/responder/pam/pamsrv_p11.c

diff --git a/Makefile.am b/Makefile.am
index a95a7f823bf206057677066f61bfaa98272174e7..d59ddf83c6b39133455728b4c8b65a6eed9207e4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1081,6 +1081,7 @@ sssd_pam_SOURCES = \
     src/responder/pam/pam_LOCAL_domain.c \
     src/responder/pam/pamsrv.c \
     src/responder/pam/pamsrv_cmd.c \
+    src/responder/pam/pamsrv_p11.c \
     src/responder/pam/pamsrv_dp.c \
     src/responder/pam/pam_helpers.c \
     $(SSSD_RESPONDER_OBJ)
@@ -1860,11 +1861,13 @@ pam_srv_tests_SOURCES = \
     src/tests/cmocka/test_pam_srv.c \
     src/sss_client/pam_message.c \
     src/responder/pam/pamsrv_cmd.c \
+    src/responder/pam/pamsrv_p11.c \
     src/responder/pam/pam_helpers.c \
     src/responder/pam/pamsrv_dp.c \
     src/responder/pam/pam_LOCAL_domain.c \
     $(NULL)
 pam_srv_tests_CFLAGS = \
+    -U SSSD_LIBEXEC_PATH -DSSSD_LIBEXEC_PATH=\"$(abs_builddir)\" \
     $(AM_CFLAGS) \
     $(NULL)
 pam_srv_tests_LDFLAGS = \
@@ -1873,6 +1876,7 @@ pam_srv_tests_LDFLAGS = \
     -Wl,-wrap,sss_cmd_send_empty \
     -Wl,-wrap,sss_cmd_done \
     -Wl,-wrap,pam_dp_send_req \
+    -Wl,-wrap,read_pipe_recv \
     $(NULL)
 pam_srv_tests_LDADD = \
     $(CMOCKA_LIBS) \
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index b2ec2e0b98a9be2d50009df524a1072e9b1c15c7..b28e14e0a33eaac892f4b8d042f7e5bd9dd4794c 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -115,6 +115,7 @@
 #define CONFDB_PAM_TRUSTED_USERS "pam_trusted_users"
 #define CONFDB_PAM_PUBLIC_DOMAINS "pam_public_domains"
 #define CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE "pam_account_expired_message"
+#define CONFDB_PAM_CERT_AUTH "pam_cert_auth"
 
 /* SUDO */
 #define CONFDB_SUDO_CONF_ENTRY "config/sudo"
diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c
index aa0d2796b1357d2457a7545415960bd0aaf241bf..c528b7d17810053a0ba59ce428f15114bf4c5444 100644
--- a/src/responder/pam/pamsrv.c
+++ b/src/responder/pam/pamsrv.c
@@ -50,6 +50,7 @@
 #define ALL_DOMAIMS_ARE_PUBLIC "all"
 #define NO_DOMAIMS_ARE_PUBLIC "none"
 #define DEFAULT_ALLOWED_UIDS ALL_UIDS_ALLOWED
+#define DEFAULT_PAM_CERT_AUTH false
 
 struct mon_cli_iface monitor_pam_methods = {
     { &mon_cli_iface_meta, 0 },
@@ -302,6 +303,28 @@ static int pam_process_init(TALLOC_CTX *mem_ctx,
         goto done;
     }
 
+    /* Check if certificate based authentication is enabled */
+    ret = confdb_get_bool(pctx->rctx->cdb,
+                          CONFDB_PAM_CONF_ENTRY,
+                          CONFDB_PAM_CERT_AUTH,
+                          DEFAULT_PAM_CERT_AUTH,
+                          &pctx->cert_auth);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to determine if certificate based authentication is " \
+              "enabled or not.\n");
+        goto done;
+    }
+
+    pctx->p11_child_debug_fd = -1;
+    if (pctx->cert_auth) {
+        ret = p11_child_init(pctx);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_FATAL_FAILURE, "p11_child_init failed.\n");
+            goto done;
+        }
+    }
+
     ret = EOK;
 
 done:
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 0278006467a41b28a1d47a8777074f265c5c478e..bd46e4be8c200b55bb26e1d2bcdcd7022d3e98c9 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -43,6 +43,9 @@ struct pam_ctx {
     /* List of domains that are accessible even for untrusted users. */
     char **public_domains;
     int public_domains_count;
+
+    bool cert_auth;
+    int p11_child_debug_fd;
 };
 
 struct pam_auth_dp_req {
@@ -65,6 +68,9 @@ struct pam_auth_req {
     bool cached_auth_failed;
 
     struct pam_auth_dp_req *dpreq_spy;
+
+    struct ldb_message *cert_user_obj;
+    char *token_name;
 };
 
 struct sss_cmd_table *get_pam_cmds(void);
@@ -73,4 +79,22 @@ int pam_dp_send_req(struct pam_auth_req *preq, int timeout);
 
 int LOCAL_pam_handler(struct pam_auth_req *preq);
 
+errno_t p11_child_init(struct pam_ctx *pctx);
+
+struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       int child_debug_fd,
+                                       time_t timeout,
+                                       struct pam_data *pd);
+errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                            uint8_t **buf, ssize_t *buf_len);
+
+errno_t parse_p11_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf,
+                                 ssize_t buf_len, char **_cert,
+                                 char **_token_name);
+
+errno_t add_pam_cert_response(struct pam_data *pd, const char *user,
+                              const char *token_name);
+
+bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd);
 #endif /* __PAMSRV_H__ */
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 9c32f40ff4ee24fdc8d0507efe6205b72ffbcb28..3f4f4b4d47cf78000a2627cc37f89b5f151cf1f5 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -31,6 +31,7 @@
 #include "providers/data_provider.h"
 #include "responder/pam/pamsrv.h"
 #include "responder/pam/pam_helpers.h"
+#include "responder/common/responder_cache_req.h"
 #include "db/sysdb.h"
 
 enum pam_verbosity {
@@ -49,6 +50,7 @@ static errno_t
 pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain,
                                          const char *name,
                                          uint64_t *_value);
+
 static void pam_reply(struct pam_auth_req *preq);
 
 static errno_t pack_user_info_account_expired(TALLOC_CTX *mem_ctx,
@@ -154,6 +156,13 @@ static int extract_authtok_v2(struct sss_auth_token *tok,
         ret = sss_authtok_set(tok, SSS_AUTHTOK_TYPE_2FA,
                               auth_token_data, auth_token_length);
         break;
+    case SSS_AUTHTOK_TYPE_SC_PIN:
+        ret = sss_authtok_set_sc_pin(tok, (const char *) auth_token_data,
+                                     auth_token_length);
+        break;
+    case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+        sss_authtok_set_sc_keypad(tok);
+        break;
     default:
         return EINVAL;
     }
@@ -892,6 +901,7 @@ static void pam_handle_cached_login(struct pam_auth_req *preq, int ret,
 }
 
 static void pam_forwarder_cb(struct tevent_req *req);
+static void pam_forwarder_cert_cb(struct tevent_req *req);
 static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min,
                                        const char *err_msg, void *ptr);
 static int pam_check_user_search(struct pam_auth_req *preq);
@@ -939,9 +949,22 @@ static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *p
         goto done;
     }
 
-    ret = sss_parse_name_for_domains(pd, cctx->rctx->domains,
-                                     cctx->rctx->default_domain, pd->logon_name,
-                                     &pd->domain, &pd->user);
+    if (pd->logon_name != NULL) {
+        ret = sss_parse_name_for_domains(pd, cctx->rctx->domains,
+                                         cctx->rctx->default_domain,
+                                         pd->logon_name,
+                                         &pd->domain, &pd->user);
+    } else {
+        /* Only SSS_PAM_PREAUTH request may have a missing name, e.g. if the
+         * name is determined with the help of a certificate */
+        if (pd->cmd == SSS_PAM_PREAUTH) {
+            ret = EOK;
+        } else {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n");
+            ret = EINVAL;
+            goto done;
+        }
+    }
 
     DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd);
 
@@ -1052,49 +1075,66 @@ static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd)
         goto done;
     }
 
-    /* now check user is valid */
-    if (pd->domain) {
-        preq->domain = responder_get_domain(cctx->rctx, pd->domain);
-        if (!preq->domain) {
-            ret = ENOENT;
-            goto done;
-        }
-
-        ncret = sss_ncache_check_user(pctx->ncache, pctx->neg_timeout,
-                                      preq->domain, pd->user);
-        if (ncret == EEXIST) {
-            /* User found in the negative cache */
-            ret = ENOENT;
-            goto done;
-        }
-    } else {
-        for (dom = preq->cctx->rctx->domains;
-             dom;
-             dom = get_next_domain(dom, false)) {
-            if (dom->fqnames) continue;
+    if (pd->user != NULL) {
+        /* now check user is valid */
+        if (pd->domain) {
+            preq->domain = responder_get_domain(cctx->rctx, pd->domain);
+            if (!preq->domain) {
+                ret = ENOENT;
+                goto done;
+            }
 
             ncret = sss_ncache_check_user(pctx->ncache, pctx->neg_timeout,
-                                          dom, pd->user);
-            if (ncret == ENOENT) {
-                /* User not found in the negative cache
-                 * Proceed with PAM actions
-                 */
-                break;
+                                          preq->domain, pd->user);
+            if (ncret == EEXIST) {
+                /* User found in the negative cache */
+                ret = ENOENT;
+                goto done;
             }
+        } else {
+            for (dom = preq->cctx->rctx->domains;
+                 dom;
+                 dom = get_next_domain(dom, false)) {
+                if (dom->fqnames) continue;
 
-            /* Try the next domain */
-            DEBUG(SSSDBG_TRACE_FUNC,
-                  "User [%s@%s] filtered out (negative cache). "
-                   "Trying next domain.\n", pd->user, dom->name);
+                ncret = sss_ncache_check_user(pctx->ncache, pctx->neg_timeout,
+                                              dom, pd->user);
+                if (ncret == ENOENT) {
+                    /* User not found in the negative cache
+                     * Proceed with PAM actions
+                     */
+                    break;
+                }
+
+                /* Try the next domain */
+                DEBUG(SSSDBG_TRACE_FUNC,
+                      "User [%s@%s] filtered out (negative cache). "
+                       "Trying next domain.\n", pd->user, dom->name);
+            }
+
+            if (!dom) {
+                ret = ENOENT;
+                goto done;
+            }
+            preq->domain = dom;
         }
+    }
 
-        if (!dom) {
-            ret = ENOENT;
-            goto done;
+    if (may_do_cert_auth(pctx, pd)) {
+        req = pam_check_cert_send(cctx, cctx->ev, pctx->p11_child_debug_fd,10,
+                                  pd);
+        if (req == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n");
+            ret = ENOMEM;
+        } else {
+            tevent_req_set_callback(req, pam_forwarder_cert_cb, preq);
+            ret = EAGAIN;
         }
-        preq->domain = dom;
+
+        goto done;
     }
 
+
     if (preq->domain->provider == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE,
               "Domain [%s] has no auth provider.\n", preq->domain->name);
@@ -1113,6 +1153,151 @@ done:
     return pam_check_user_done(preq, ret);
 }
 
+static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req);
+static void pam_forwarder_cert_cb(struct tevent_req *req)
+{
+    struct pam_auth_req *preq = tevent_req_callback_data(req,
+                                                         struct pam_auth_req);
+    struct cli_ctx *cctx = preq->cctx;
+    struct pam_data *pd;
+    errno_t ret = EOK;
+    uint8_t *buf;
+    ssize_t buf_len;
+    char *cert;
+    struct pam_ctx *pctx =
+            talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
+
+    ret = pam_check_cert_recv(req, preq, &buf, &buf_len);
+    talloc_free(req);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n");
+        goto done;
+    }
+
+    ret = parse_p11_child_response(preq, buf, buf_len, &cert,
+                                   &preq->token_name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "parse_p11_child_respose failed.\n");
+        goto done;
+    }
+
+    pd = preq->pd;
+
+    if (cert == NULL) {
+        if (pd->logon_name == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "No certificate found and no logon name given, " \
+                  "authentication not possible.\n");;
+            ret = ENOENT;
+        } else {
+            if (pd->cmd == SSS_PAM_AUTHENTICATE) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "No certificate returned, authentication failed.\n");
+                ret = ENOENT;
+            } else {
+                ret = pam_check_user_search(preq);
+                if (ret == EOK) {
+                    pam_dom_forwarder(preq);
+                }
+            }
+
+        }
+        goto done;
+    }
+
+
+    req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx,
+                                      pctx->ncache, pctx->neg_timeout,
+                                      0, NULL, cert);
+    if (req == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+    tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq);
+    return;
+
+done:
+    pam_check_user_done(preq, ret);
+}
+
+static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
+{
+    int ret;
+    struct ldb_result *res;
+    struct sss_domain_info *domain;
+    struct pam_auth_req *preq = tevent_req_callback_data(req,
+                                                         struct pam_auth_req);
+    const char *cert_user;
+
+
+    ret = cache_req_user_by_cert_recv(preq, req, &res, &domain, NULL);
+    talloc_zfree(req);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n");
+        goto done;
+    }
+
+    if (ret == EOK && res->count > 1) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Search by certificate returned more than one result.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    if (ret == EOK) {
+        if (preq->domain == NULL) {
+            preq->domain = domain;
+        }
+
+        preq->cert_user_obj = talloc_steal(preq, res->msgs[0]);
+
+        if (preq->pd->logon_name == NULL) {
+            cert_user = ldb_msg_find_attr_as_string(preq->cert_user_obj,
+                                                    SYSDB_NAME, NULL);
+            if (cert_user == NULL) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "Certificate user object has not name.\n");
+                ret = ENOENT;
+                goto done;
+            }
+
+            DEBUG(SSSDBG_FUNC_DATA, "Found certificate user [%s].\n",
+                                    cert_user);
+
+            ret = add_pam_cert_response(preq->pd, cert_user, preq->token_name);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n");
+            }
+
+            preq->pd->domain = talloc_strdup(preq->pd, domain->name);
+            if (preq->pd->domain == NULL) {
+                DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+                ret = ENOMEM;
+                goto done;
+            }
+            preq->pd->pam_status = PAM_SUCCESS;
+            pam_reply(preq);
+            return;
+        }
+    } else {
+        if (preq->pd->logon_name == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Missing logon name and no certificate user found.\n");
+            ret = ENOENT;
+            goto done;
+        }
+    }
+
+    ret = pam_check_user_search(preq);
+    if (ret == EOK) {
+        pam_dom_forwarder(preq);
+    }
+
+done:
+    pam_check_user_done(preq, ret);
+}
+
 static void pam_forwarder_cb(struct tevent_req *req)
 {
     struct pam_auth_req *preq = tevent_req_callback_data(req,
@@ -1120,6 +1305,8 @@ static void pam_forwarder_cb(struct tevent_req *req)
     struct cli_ctx *cctx = preq->cctx;
     struct pam_data *pd;
     errno_t ret = EOK;
+    struct pam_ctx *pctx =
+            talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
 
     ret = sss_dp_get_domains_recv(req);
     talloc_free(req);
@@ -1158,6 +1345,20 @@ static void pam_forwarder_cb(struct tevent_req *req)
         }
     }
 
+    if (may_do_cert_auth(pctx, pd)) {
+        req = pam_check_cert_send(cctx, cctx->ev, pctx->p11_child_debug_fd, 10,
+                                  pd);
+        if (req == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n");
+            ret = ENOMEM;
+        } else {
+            tevent_req_set_callback(req, pam_forwarder_cert_cb, preq);
+            ret = EAGAIN;
+        }
+
+        goto done;
+    }
+
     ret = pam_check_user_search(preq);
     if (ret == EOK) {
         pam_dom_forwarder(preq);
@@ -1542,6 +1743,7 @@ static void pam_dom_forwarder(struct pam_auth_req *preq)
     int ret;
     struct pam_ctx *pctx =
             talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
+    const char *cert_user;
 
     if (!preq->pd->domain) {
         preq->pd->domain = preq->domain->name;
@@ -1579,6 +1781,51 @@ static void pam_dom_forwarder(struct pam_auth_req *preq)
         return;
     }
 
+    if (may_do_cert_auth(pctx, preq->pd) && preq->cert_user_obj != NULL) {
+        /* Check if user matches certificate user */
+        cert_user = ldb_msg_find_attr_as_string(preq->cert_user_obj, SYSDB_NAME,
+                                                NULL);
+        if (cert_user == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Certificate user object has not name.\n");
+            preq->pd->pam_status = PAM_USER_UNKNOWN;
+            pam_reply(preq);
+            return;
+        }
+
+        /* pam_check_user_search() calls pd_set_primary_name() is the search
+         * was successful, so pd->user contains the canonical name as well */
+        if (strcmp(cert_user, preq->pd->user) == 0) {
+
+            preq->pd->pam_status = PAM_SUCCESS;
+
+            if (preq->pd->cmd == SSS_PAM_PREAUTH) {
+                ret = add_pam_cert_response(preq->pd, cert_user,
+                                            preq->token_name);
+                if (ret != EOK) {
+                    DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n");
+                    preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+                }
+            }
+
+            preq->callback = pam_reply;
+            pam_reply(preq);
+            return;
+        } else {
+            if (preq->pd->cmd == SSS_PAM_PREAUTH) {
+                DEBUG(SSSDBG_TRACE_FUNC,
+                      "User and certificate user do not match, " \
+                      "continue with other authentication methods.\n");
+            } else {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "User and certificate user do not match.\n");
+                preq->pd->pam_status = PAM_AUTH_ERR;
+                pam_reply(preq);
+                return;
+            }
+        }
+    }
+
     if (!NEED_CHECK_PROVIDER(preq->domain->provider) ) {
         preq->callback = pam_reply;
         ret = LOCAL_pam_handler(preq);
diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c
new file mode 100644
index 0000000000000000000000000000000000000000..03ca27629e5c50609727a92d404113c667a0c45d
--- /dev/null
+++ b/src/responder/pam/pamsrv_p11.c
@@ -0,0 +1,502 @@
+/*
+   SSSD
+
+   PAM Responder - certificate realted requests
+
+   Copyright (C) Sumit Bose <sbose at redhat.com> 2015
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <time.h>
+
+#include "util/util.h"
+#include "providers/data_provider.h"
+#include "util/child_common.h"
+#include "util/strtonum.h"
+#include "responder/pam/pamsrv.h"
+
+
+#ifndef SSSD_LIBEXEC_PATH
+#error "SSSD_LIBEXEC_PATH not defined"
+#endif  /* SSSD_LIBEXEC_PATH */
+
+#define P11_CHILD_LOG_FILE "p11_child"
+#define P11_CHILD_PATH SSSD_LIBEXEC_PATH"/p11_child"
+
+errno_t p11_child_init(struct pam_ctx *pctx)
+{
+    return child_debug_init(P11_CHILD_LOG_FILE, &pctx->p11_child_debug_fd);
+}
+
+bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd)
+{
+    size_t c;
+    const char *sc_services[] = { "login", "su", "su-l", "gdm-smartcard",
+                                  "gdm-password", "kdm", "sudo", "sudo-i",
+                                  NULL };
+    if (!pctx->cert_auth) {
+        return false;
+    }
+
+    if (pd->cmd != SSS_PAM_PREAUTH && pd->cmd != SSS_PAM_AUTHENTICATE) {
+        return false;
+    }
+
+    if (pd->cmd == SSS_PAM_AUTHENTICATE
+           && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_SC_PIN
+           && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_SC_KEYPAD) {
+        return false;
+    }
+
+    /* TODO: make services configurable */
+    if (pd->service == NULL || *pd->service == '\0') {
+        return false;
+    }
+    for (c = 0; sc_services[c] != NULL; c++) {
+        if (strcmp(pd->service, sc_services[c]) == 0) {
+            break;
+        }
+    }
+    if  (sc_services[c] == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Smartcard authentication for service [%s] not supported.\n",
+              pd->service);
+        return false;
+    }
+
+    return true;
+}
+
+static errno_t get_p11_child_write_buffer(TALLOC_CTX *mem_ctx,
+                                          struct pam_data *pd,
+                                          uint8_t **_buf, size_t *_len)
+{
+    int ret;
+    uint8_t *buf;
+    size_t len;
+    const char *pin = NULL;
+
+    if (pd == NULL || pd->authtok == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n");
+        return EINVAL;
+    }
+
+    switch (sss_authtok_get_type(pd->authtok)) {
+    case SSS_AUTHTOK_TYPE_SC_PIN:
+        ret = sss_authtok_get_sc_pin(pd->authtok, &pin, &len);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc_pin failed.\n");
+            return ret;
+        }
+        if (pin == NULL || len == 0) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n");
+            return EINVAL;
+        }
+
+        buf = talloc_size(mem_ctx, len);
+        if (buf == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+            return ENOMEM;
+        }
+
+        safealign_memcpy(buf, pin, len, NULL);
+
+        break;
+    case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+        /* Nothing to do */
+        break;
+    default:
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n",
+                                   sss_authtok_get_type(pd->authtok));
+        return EINVAL;
+    }
+
+    *_len = len;
+    *_buf = buf;
+
+    return EOK;
+}
+
+struct pam_check_cert_state {
+    int child_status;
+    struct sss_child_ctx_old *child_ctx;
+    struct tevent_timer *timeout_handler;
+    struct tevent_context *ev;
+
+    int write_to_child_fd;
+    int read_from_child_fd;
+    uint8_t *buf;
+    ssize_t len;
+};
+
+static void p11_child_write_done(struct tevent_req *subreq);
+static void p11_child_done(struct tevent_req *subreq);
+static void p11_child_timeout(struct tevent_context *ev,
+                              struct tevent_timer *te,
+                              struct timeval tv, void *pvt);
+
+struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       int child_debug_fd,
+                                       time_t timeout,
+                                       struct pam_data *pd)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct pam_check_cert_state *state;
+    pid_t child_pid;
+    struct timeval tv;
+    int pipefd_to_child[2];
+    int pipefd_from_child[2];
+    const char *extra_args[3] = {NULL, NULL, NULL};
+    uint8_t *write_buf = NULL;
+    size_t write_buf_len = 0;
+
+    req = tevent_req_create(mem_ctx, &state, struct pam_check_cert_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    if (pd->cmd == SSS_PAM_AUTHENTICATE) {
+        extra_args[0] = "--auth";
+        switch (sss_authtok_get_type(pd->authtok)) {
+        case SSS_AUTHTOK_TYPE_SC_PIN:
+            extra_args[1] = "--pin";
+            break;
+        case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+            extra_args[1] = "--keypad";
+            break;
+        default:
+            DEBUG(SSSDBG_OP_FAILURE, "Unsupported authtok type.\n");
+            ret = EINVAL;
+            goto done;
+        }
+    } else if (pd->cmd == SSS_PAM_PREAUTH) {
+        extra_args[0] = "--pre";
+    } else {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected PAM command [%d}.\n", pd->cmd);
+        ret = EINVAL;
+        goto done;
+    }
+
+    state->ev = ev;
+    state->child_status = EFAULT;
+    state->read_from_child_fd = -1;
+    state->write_to_child_fd = -1;
+
+    ret = pipe(pipefd_from_child);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "pipe failed [%d][%s].\n", ret, strerror(ret));
+        goto done;
+    }
+    ret = pipe(pipefd_to_child);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "pipe failed [%d][%s].\n", ret, strerror(ret));
+        goto done;
+    }
+
+    if (child_debug_fd == -1) {
+        child_debug_fd = STDERR_FILENO;
+    }
+
+    child_pid = fork();
+    if (child_pid == 0) { /* child */
+        ret = exec_child_ex(state, pipefd_to_child, pipefd_from_child,
+                            P11_CHILD_PATH, child_debug_fd, extra_args,
+                            STDIN_FILENO, STDOUT_FILENO);
+
+    } else if (child_pid > 0) { /* parent */
+
+        state->read_from_child_fd = pipefd_from_child[0];
+        close(pipefd_from_child[1]);
+        sss_fd_nonblocking(state->read_from_child_fd);
+
+        state->write_to_child_fd = pipefd_to_child[1];
+        close(pipefd_to_child[0]);
+        sss_fd_nonblocking(state->write_to_child_fd);
+
+        /* Set up SIGCHLD handler */
+        ret = child_handler_setup(ev, child_pid, NULL, NULL, &state->child_ctx);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n",
+                ret, sss_strerror(ret));
+            ret = ERR_P11_CHILD;
+            goto done;
+        }
+
+        /* Set up timeout handler */
+        tv = tevent_timeval_current_ofs(timeout, 0);
+        state->timeout_handler = tevent_add_timer(ev, req, tv,
+                                                  p11_child_timeout, req);
+        if(state->timeout_handler == NULL) {
+            ret = ERR_P11_CHILD;
+            goto done;
+        }
+
+        if (pd->cmd == SSS_PAM_AUTHENTICATE) {
+            ret = get_p11_child_write_buffer(state, pd, &write_buf,
+                                             &write_buf_len);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_OP_FAILURE,
+                      "get_p11_child_write_buffer failed.\n");
+                goto done;
+            }
+        }
+
+        if (write_buf_len != 0) {
+            subreq = write_pipe_send(state, ev, write_buf, write_buf_len,
+                                     state->write_to_child_fd);
+            if (subreq == NULL) {
+                DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n");
+                ret = ERR_P11_CHILD;
+                goto done;
+            }
+            tevent_req_set_callback(subreq, p11_child_write_done, req);
+        } else {
+            subreq = read_pipe_send(state, ev, state->read_from_child_fd);
+            if (subreq == NULL) {
+                DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n");
+                ret = ERR_P11_CHILD;
+                goto done;
+            }
+            tevent_req_set_callback(subreq, p11_child_done, req);
+        }
+
+        /* Now either wait for the timeout to fire or the child
+         * to finish
+         */
+    } else { /* error */
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n",
+                                   ret, sss_strerror(ret));
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        tevent_req_post(req, ev);
+    }
+    return req;
+}
+
+static void p11_child_write_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct pam_check_cert_state *state = tevent_req_data(req,
+                                                   struct pam_check_cert_state);
+    int ret;
+
+    ret = write_pipe_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    close(state->write_to_child_fd);
+    state->write_to_child_fd = -1;
+
+    subreq = read_pipe_send(state, state->ev, state->read_from_child_fd);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, p11_child_done, req);
+}
+
+static void p11_child_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct pam_check_cert_state *state = tevent_req_data(req,
+                                                   struct pam_check_cert_state);
+    int ret;
+
+    talloc_zfree(state->timeout_handler);
+
+    ret = read_pipe_recv(subreq, state, &state->buf, &state->len);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    close(state->read_from_child_fd);
+    state->read_from_child_fd = -1;
+
+    tevent_req_done(req);
+    return;
+}
+
+static void p11_child_timeout(struct tevent_context *ev,
+                              struct tevent_timer *te,
+                              struct timeval tv, void *pvt)
+{
+    struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+    struct pam_check_cert_state *state =
+                              tevent_req_data(req, struct pam_check_cert_state);
+
+    DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for p11_child.\n");
+    child_handler_destroy(state->child_ctx);
+    state->child_ctx = NULL;
+    state->child_status = ETIMEDOUT;
+    tevent_req_error(req, ERR_P11_CHILD);
+}
+
+errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                            uint8_t **buf, ssize_t *buf_len)
+{
+    struct pam_check_cert_state *state =
+                              tevent_req_data(req, struct pam_check_cert_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (buf_len != NULL) {
+        *buf_len = state->len;
+    }
+
+    if (buf != NULL) {
+        *buf = talloc_steal(mem_ctx, state->buf);
+    }
+
+    return EOK;
+}
+
+errno_t parse_p11_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf,
+                                 ssize_t buf_len, char **_cert,
+                                 char **_token_name)
+{
+    int ret;
+    TALLOC_CTX *tmp_ctx = NULL;
+    uint8_t *p;
+    uint8_t *pn;
+    char *endptr;
+    size_t count = 0;
+    char *cert = NULL;
+    char *str;
+    char *token_name = NULL;
+    size_t c;
+
+    if (buf_len < 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Error occured while reading data from p11_child.\n");
+        return EIO;
+    }
+
+    if (buf_len == 0) {
+        DEBUG(SSSDBG_TRACE_LIBS, "No certificate found.\n");
+        ret = EOK;
+        goto done;
+    }
+
+    p = memchr(buf, '\n', buf_len);
+    if (p == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "Missing new-line in p11_child response.\n");
+        return EINVAL;
+    }
+    if (p == buf) {
+        DEBUG(SSSDBG_OP_FAILURE, "Missing counter in p11_child response.\n");
+        return EINVAL;
+    }
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+        return ENOMEM;
+    }
+
+    token_name = talloc_strndup(tmp_ctx, (char*) buf, (p - buf));
+    if (token_name == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+        return ENOMEM;
+    }
+
+    p++;
+    pn = memchr(p, '\n', buf_len - (p - buf));
+    if (pn == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Missing new-line in p11_child response.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    if (pn == p) {
+        DEBUG(SSSDBG_OP_FAILURE, "Missing cert in p11_child response.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    cert = talloc_strndup(tmp_ctx, (char *) p, (pn - p));
+    if(cert == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+    DEBUG(SSSDBG_TRACE_ALL, "Found cert [%s].\n", cert);
+
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        *_token_name = talloc_steal(mem_ctx, token_name);
+        *_cert = talloc_steal(mem_ctx, cert);
+    }
+
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
+
+errno_t add_pam_cert_response(struct pam_data *pd, const char *user,
+                              const char *token_name)
+{
+    uint8_t *msg = NULL;
+    size_t user_len;
+    size_t msg_len;
+    size_t slot_len;
+    int ret;
+
+    if (user == NULL || token_name == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing mandatory user or slot name.\n");
+        return EINVAL;
+    }
+
+    user_len = strlen(user) + 1;
+    slot_len = strlen(token_name) + 1;
+    msg_len = user_len + slot_len;
+
+    msg = talloc_zero_size(pd, msg_len);
+    if (msg == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n");
+        return ENOMEM;
+    }
+
+    memcpy(msg, user, user_len);
+    memcpy(msg + user_len, token_name, slot_len);
+
+    ret = pam_add_response(pd, SSS_PAM_CERT_INFO, msg_len, msg);
+    talloc_free(msg);
+
+    return ret;
+}
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 3c4e938ae37c042879b1ae26fe389fa37cef682c..f39ceba5e401b742b27a012ea0ef3059cb19aecc 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -417,6 +417,7 @@ enum response_type {
                           * @param Three zero terminated strings, if one of the
                           * strings is missing the message will contain only
                           * an empty string (\0) for that component. */
+    SSS_PAM_CERT_INFO,
     SSS_OTP,             /**< Indicates that the autotok was a OTP, so don't
                           * cache it. There is no message.
                           * @param None. */
diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c
index 0f6c272dfd7499c529d313bf16b8e3f5e289c6e0..344733ebf1d4025f39dfe971924d81da35b6d004 100644
--- a/src/tests/cmocka/test_pam_srv.c
+++ b/src/tests/cmocka/test_pam_srv.c
@@ -40,6 +40,100 @@
 #define TEST_SUBDOM_NAME "test.subdomain"
 #define TEST_ID_PROVIDER "ldap"
 
+
+
+const uint8_t test_cert_der[] = {
+0x30, 0x82, 0x04, 0x09, 0x30, 0x82, 0x02, 0xf1, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x09,
+0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30,
+0x34, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x49, 0x50, 0x41, 0x2e,
+0x44, 0x45, 0x56, 0x45, 0x4c, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x15,
+0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x35, 0x30, 0x34, 0x32, 0x38, 0x31,
+0x30, 0x32, 0x31, 0x31, 0x31, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x30, 0x34, 0x32, 0x38, 0x31, 0x30,
+0x32, 0x31, 0x31, 0x31, 0x5a, 0x30, 0x32, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a,
+0x0c, 0x09, 0x49, 0x50, 0x41, 0x2e, 0x44, 0x45, 0x56, 0x45, 0x4c, 0x31, 0x1c, 0x30, 0x1a, 0x06,
+0x03, 0x55, 0x04, 0x03, 0x0c, 0x13, 0x69, 0x70, 0x61, 0x2d, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x2e,
+0x69, 0x70, 0x61, 0x2e, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f,
+0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb2, 0x32, 0x92, 0xab, 0x47, 0xb8,
+0x0c, 0x13, 0x54, 0x4a, 0x1f, 0x1e, 0x29, 0x06, 0xff, 0xd0, 0x50, 0xcb, 0xf7, 0x5f, 0x79, 0x91,
+0x65, 0xb1, 0x39, 0x01, 0x83, 0x6a, 0xad, 0x9e, 0x77, 0x3b, 0xf3, 0x0d, 0xd7, 0xb9, 0xf6, 0xdc,
+0x9e, 0x4a, 0x49, 0xa7, 0xd0, 0x66, 0x72, 0xcc, 0xbf, 0x77, 0xd6, 0xde, 0xa9, 0xfe, 0x67, 0x96,
+0xcc, 0x49, 0xf1, 0x37, 0x23, 0x2e, 0xc4, 0x50, 0xf4, 0xeb, 0xba, 0x62, 0xd4, 0x23, 0x4d, 0xf3,
+0x37, 0x38, 0x82, 0xee, 0x3b, 0x3f, 0x2c, 0xd0, 0x80, 0x9b, 0x17, 0xaa, 0x9b, 0xeb, 0xa6, 0xdd,
+0xf6, 0x15, 0xff, 0x06, 0xb2, 0xce, 0xff, 0xdf, 0x8a, 0x9e, 0x95, 0x85, 0x49, 0x1f, 0x84, 0xfd,
+0x81, 0x26, 0xce, 0x06, 0x32, 0x0d, 0x36, 0xca, 0x7c, 0x15, 0x81, 0x68, 0x6b, 0x8f, 0x3e, 0xb3,
+0xa2, 0xfc, 0xae, 0xaf, 0xc2, 0x44, 0x58, 0x15, 0x95, 0x40, 0xfc, 0x56, 0x19, 0x91, 0x80, 0xed,
+0x42, 0x11, 0x66, 0x04, 0xef, 0x3c, 0xe0, 0x76, 0x33, 0x4b, 0x83, 0xfa, 0x7e, 0xb4, 0x47, 0xdc,
+0xfb, 0xed, 0x46, 0xa5, 0x8d, 0x0a, 0x66, 0x87, 0xa5, 0xef, 0x7b, 0x74, 0x62, 0xac, 0xbe, 0x73,
+0x36, 0xc9, 0xb4, 0xfe, 0x20, 0xc4, 0x81, 0xf3, 0xfe, 0x78, 0x19, 0xa8, 0xd0, 0xaf, 0x7f, 0x81,
+0x72, 0x24, 0x61, 0xd9, 0x76, 0x93, 0xe3, 0x0b, 0xd2, 0x4f, 0x19, 0x17, 0x33, 0x57, 0xd4, 0x82,
+0xb0, 0xf1, 0xa8, 0x03, 0xf6, 0x01, 0x99, 0xa9, 0xb8, 0x8c, 0x83, 0xc9, 0xba, 0x19, 0x87, 0xea,
+0xd6, 0x3b, 0x06, 0xeb, 0x4c, 0xf7, 0xf1, 0xe5, 0x28, 0xa9, 0x10, 0xb6, 0x46, 0xde, 0xe1, 0xe1,
+0x3f, 0xc1, 0xcc, 0x72, 0xbe, 0x2a, 0x43, 0xc6, 0xf6, 0xd0, 0xb5, 0xa0, 0xc4, 0x24, 0x6e, 0x4f,
+0xbd, 0xec, 0x22, 0x8a, 0x07, 0x11, 0x3d, 0xf9, 0xd3, 0x15, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+0x82, 0x01, 0x26, 0x30, 0x82, 0x01, 0x22, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+0x30, 0x16, 0x80, 0x14, 0xf2, 0x9d, 0x42, 0x4e, 0x0f, 0xc4, 0x48, 0x25, 0x58, 0x2f, 0x1c, 0xce,
+0x0f, 0xa1, 0x3f, 0x22, 0xc8, 0x55, 0xc8, 0x91, 0x30, 0x3b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+0x05, 0x07, 0x01, 0x01, 0x04, 0x2f, 0x30, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+0x05, 0x07, 0x30, 0x01, 0x86, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x69, 0x70, 0x61,
+0x2d, 0x63, 0x61, 0x2e, 0x69, 0x70, 0x61, 0x2e, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x2f, 0x63, 0x61,
+0x2f, 0x6f, 0x63, 0x73, 0x70, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+0x04, 0x03, 0x02, 0x04, 0xf0, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14,
+0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+0x05, 0x07, 0x03, 0x02, 0x30, 0x74, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x6d, 0x30, 0x6b, 0x30,
+0x69, 0xa0, 0x31, 0xa0, 0x2f, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x69, 0x70,
+0x61, 0x2d, 0x63, 0x61, 0x2e, 0x69, 0x70, 0x61, 0x2e, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x2f, 0x69,
+0x70, 0x61, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x52, 0x4c,
+0x2e, 0x62, 0x69, 0x6e, 0xa2, 0x34, 0xa4, 0x32, 0x30, 0x30, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03,
+0x55, 0x04, 0x0a, 0x0c, 0x05, 0x69, 0x70, 0x61, 0x63, 0x61, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03,
+0x55, 0x04, 0x03, 0x0c, 0x15, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
+0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+0x0e, 0x04, 0x16, 0x04, 0x14, 0x2d, 0x2b, 0x3f, 0xcb, 0xf5, 0xb2, 0xff, 0x32, 0x2c, 0xa8, 0xc2,
+0x1c, 0xdd, 0xbd, 0x8c, 0x80, 0x1e, 0xdd, 0x31, 0x82, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x9a, 0x47, 0x2e,
+0x50, 0xa7, 0x4d, 0x1d, 0x53, 0x0f, 0xc9, 0x71, 0x42, 0x0c, 0xe5, 0xda, 0x7d, 0x49, 0x64, 0xe7,
+0xab, 0xc8, 0xdf, 0xdf, 0x02, 0xc1, 0x87, 0xd1, 0x5b, 0xde, 0xda, 0x6f, 0x2b, 0xe4, 0xf0, 0xbe,
+0xba, 0x09, 0xdf, 0x02, 0x85, 0x0b, 0x8a, 0xe6, 0x9b, 0x06, 0x7d, 0x69, 0x38, 0x6c, 0x72, 0xff,
+0x4c, 0x7b, 0x2a, 0x0d, 0x3f, 0x23, 0x2f, 0x16, 0x46, 0xff, 0x05, 0x93, 0xb0, 0xea, 0x24, 0x28,
+0xd7, 0x12, 0xa1, 0x57, 0xb8, 0x59, 0x19, 0x25, 0xf3, 0x43, 0x0a, 0xd3, 0xfd, 0x0f, 0x37, 0x8d,
+0xb8, 0xca, 0x15, 0xe7, 0x48, 0x8a, 0xa0, 0xc7, 0xc7, 0x4b, 0x7f, 0x01, 0x3c, 0x58, 0xd7, 0x37,
+0xe5, 0xff, 0x7d, 0x2b, 0x01, 0xac, 0x0d, 0x9f, 0x51, 0x6a, 0xe5, 0x40, 0x24, 0xe6, 0x5e, 0x55,
+0x0d, 0xf7, 0xb8, 0x2f, 0x42, 0xac, 0x6d, 0xe5, 0x29, 0x6b, 0xc6, 0x0b, 0xa4, 0xbf, 0x19, 0xbd,
+0x39, 0x27, 0xee, 0xfe, 0xc5, 0xb3, 0xdb, 0x62, 0xd4, 0xbe, 0xd2, 0x47, 0xba, 0x96, 0x30, 0x5a,
+0xfd, 0x62, 0x00, 0xb8, 0x27, 0x5d, 0x2f, 0x3a, 0x94, 0x0b, 0x95, 0x35, 0x85, 0x40, 0x2c, 0xbc,
+0x67, 0xdf, 0x8a, 0xf9, 0xf1, 0x7b, 0x19, 0x96, 0x3e, 0x42, 0x48, 0x13, 0x23, 0x04, 0x95, 0xa9,
+0x6b, 0x11, 0x33, 0x81, 0x47, 0x5a, 0x83, 0x72, 0xf6, 0x20, 0xfa, 0x8e, 0x41, 0x7b, 0x8f, 0x77,
+0x47, 0x7c, 0xc7, 0x5d, 0x46, 0xf4, 0x4f, 0xfd, 0x81, 0x0a, 0xae, 0x39, 0x27, 0xb6, 0x6a, 0x26,
+0x63, 0xb1, 0xd3, 0xbf, 0x55, 0x83, 0x82, 0x9b, 0x36, 0x6c, 0x33, 0x64, 0x0f, 0x50, 0xc0, 0x55,
+0x94, 0x13, 0xc3, 0x85, 0xf4, 0xd5, 0x71, 0x65, 0xd0, 0xc0, 0xdd, 0xfc, 0xe6, 0xec, 0x9c, 0x5b,
+0xf0, 0x11, 0xb5, 0x2c, 0xf3, 0x48, 0xc1, 0x36, 0x8c, 0xa2, 0x96, 0x48, 0x84};
+#define TEST_CERT_DERB64 \
+"MIIECTCCAvGgAwIBAgIBCTANBgkqhkiG9w0BAQsFADA0MRIwEAYDVQQKDAlJUEEu" \
+"REVWRUwxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNTA0Mjgx" \
+"MDIxMTFaFw0xNzA0MjgxMDIxMTFaMDIxEjAQBgNVBAoMCUlQQS5ERVZFTDEcMBoG" \
+"A1UEAwwTaXBhLWRldmVsLmlwYS5kZXZlbDCCASIwDQYJKoZIhvcNAQEBBQADggEP" \
+"ADCCAQoCggEBALIykqtHuAwTVEofHikG/9BQy/dfeZFlsTkBg2qtnnc78w3Xufbc" \
+"nkpJp9Bmcsy/d9beqf5nlsxJ8TcjLsRQ9Ou6YtQjTfM3OILuOz8s0ICbF6qb66bd" \
+"9hX/BrLO/9+KnpWFSR+E/YEmzgYyDTbKfBWBaGuPPrOi/K6vwkRYFZVA/FYZkYDt" \
+"QhFmBO884HYzS4P6frRH3PvtRqWNCmaHpe97dGKsvnM2ybT+IMSB8/54GajQr3+B" \
+"ciRh2XaT4wvSTxkXM1fUgrDxqAP2AZmpuIyDyboZh+rWOwbrTPfx5SipELZG3uHh" \
+"P8HMcr4qQ8b20LWgxCRuT73sIooHET350xUCAwEAAaOCASYwggEiMB8GA1UdIwQY" \
+"MBaAFPKdQk4PxEglWC8czg+hPyLIVciRMDsGCCsGAQUFBwEBBC8wLTArBggrBgEF" \
+"BQcwAYYfaHR0cDovL2lwYS1jYS5pcGEuZGV2ZWwvY2Evb2NzcDAOBgNVHQ8BAf8E" \
+"BAMCBPAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHQGA1UdHwRtMGsw" \
+"aaAxoC+GLWh0dHA6Ly9pcGEtY2EuaXBhLmRldmVsL2lwYS9jcmwvTWFzdGVyQ1JM" \
+"LmJpbqI0pDIwMDEOMAwGA1UECgwFaXBhY2ExHjAcBgNVBAMMFUNlcnRpZmljYXRl" \
+"IEF1dGhvcml0eTAdBgNVHQ4EFgQULSs/y/Wy/zIsqMIc3b2MgB7dMYIwDQYJKoZI" \
+"hvcNAQELBQADggEBAJpHLlCnTR1TD8lxQgzl2n1JZOeryN/fAsGH0Vve2m8r5PC+" \
+"ugnfAoULiuabBn1pOGxy/0x7Kg0/Iy8WRv8Fk7DqJCjXEqFXuFkZJfNDCtP9DzeN" \
+"uMoV50iKoMfHS38BPFjXN+X/fSsBrA2fUWrlQCTmXlUN97gvQqxt5Slrxgukvxm9" \
+"OSfu/sWz22LUvtJHupYwWv1iALgnXS86lAuVNYVALLxn34r58XsZlj5CSBMjBJWp" \
+"axEzgUdag3L2IPqOQXuPd0d8x11G9E/9gQquOSe2aiZjsdO/VYOCmzZsM2QPUMBV" \
+"lBPDhfTVcWXQwN385uycW/ARtSzzSME2jKKWSIQ="
+
+#define P11_CHILD_RESPONSE "token_name\n" TEST_CERT_DERB64 "\n"
+
 struct pam_test_ctx {
     struct sss_test_ctx *tctx;
     struct sss_domain_info *subdom;
@@ -113,6 +207,7 @@ void test_pam_setup(struct sss_test_conf_param params[],
     assert_non_null(pam_test_ctx->cctx);
 
     pam_test_ctx->cctx->cli_protocol_version = register_cli_protocol_version();
+    pam_test_ctx->cctx->ev = pam_test_ctx->tctx->ev;
 }
 
 static int pam_test_setup(void **state)
@@ -141,6 +236,22 @@ static int pam_test_setup(void **state)
                                discard_const("pamuser"),
                                pam_test_ctx->pctx->id_timeout);
     assert_int_equal(ret, EOK);
+
+    /* Prime the cache with a user for wrong matches */
+    ret = sysdb_add_user(pam_test_ctx->tctx->dom,
+                         "wronguser", 321, 654, "wrong user",
+                         "/home/wringuser", "/bin/sh", NULL,
+                         NULL, 300, 0);
+    assert_int_equal(ret, EOK);
+
+    /* Add entry to the initgr cache to make sure no initgr request is sent to
+     * the backend */
+    ret = pam_initgr_cache_set(pam_test_ctx->pctx->rctx->ev,
+                               pam_test_ctx->pctx->id_table,
+                               discard_const("wronguser"),
+                               pam_test_ctx->pctx->id_timeout);
+    assert_int_equal(ret, EOK);
+
     return 0;
 }
 
@@ -151,12 +262,39 @@ static int pam_test_teardown(void **state)
     ret = sysdb_delete_user(pam_test_ctx->tctx->dom, "pamuser", 0);
     assert_int_equal(ret, EOK);
 
+    ret = sysdb_delete_user(pam_test_ctx->tctx->dom, "wronguser", 0);
+    assert_int_equal(ret, EOK);
+
     talloc_free(pam_test_ctx);
     return 0;
 }
 
 typedef int (*cmd_cb_fn_t)(uint32_t, uint8_t *, size_t);
 
+
+int __real_read_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                          uint8_t **buf, ssize_t *len);
+
+int __wrap_read_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                          uint8_t **buf, ssize_t *len)
+{
+    enum sss_test_wrapper_call wtype = sss_mock_type(enum sss_test_wrapper_call);
+    uint8_t *mock_buf;
+
+    if (wtype == WRAP_CALL_REAL) {
+        return __real_read_pipe_recv(req, mem_ctx, buf, len);
+    }
+
+    mock_buf = sss_mock_ptr_type(uint8_t *);
+    *len = sss_mock_type(size_t);
+    *buf = NULL;
+    if (mock_buf != NULL) {
+        *buf = talloc_memdup(mem_ctx, mock_buf, *len);
+        assert_non_null(*buf);
+    }
+    return EOK;
+}
+
 void __real_sss_packet_get_body(struct sss_packet *packet,
                                 uint8_t **body, size_t *blen);
 
@@ -239,8 +377,13 @@ static void mock_input_pam(TALLOC_CTX *mem_ctx, const char *name,
     size_t needed_size;
     uint8_t *authtok;
 
-    pi.pam_user = name;
-    pi.pam_user_size = strlen(pi.pam_user) + 1;
+    if (name != NULL) {
+        pi.pam_user = name;
+        pi.pam_user_size = strlen(pi.pam_user) + 1;
+    } else {
+        pi.pam_user = "";
+        pi.pam_user_size = 0;
+    }
 
     if (pwd != NULL) {
         if (fa2 != NULL) {
@@ -287,6 +430,52 @@ static void mock_input_pam(TALLOC_CTX *mem_ctx, const char *name,
     will_return(__wrap_sss_packet_get_body, buf_size);
 }
 
+static void mock_input_pam_cert(TALLOC_CTX *mem_ctx, const char *name,
+                                const char *pin)
+{
+    size_t buf_size;
+    uint8_t *m_buf;
+    uint8_t *buf;
+    struct pam_items pi = { 0 };
+    int ret;
+
+    if (name != NULL) {
+        pi.pam_user = name;
+        pi.pam_user_size = strlen(pi.pam_user) + 1;
+    } else {
+        pi.pam_user = "";
+        pi.pam_user_size = 0;
+    }
+
+    if (pin != NULL) {
+        pi.pam_authtok = discard_const(pin);
+        pi.pam_authtok_size = strlen(pi.pam_authtok) + 1;
+        pi.pam_authtok_type = SSS_AUTHTOK_TYPE_SC_PIN;
+    }
+
+    pi.pam_service = "login";
+    pi.pam_service_size = strlen(pi.pam_service) + 1;
+    pi.pam_tty = "/dev/tty";
+    pi.pam_tty_size = strlen(pi.pam_tty) + 1;
+    pi.pam_ruser = "remuser";
+    pi.pam_ruser_size = strlen(pi.pam_ruser) + 1;
+    pi.pam_rhost = "remhost";
+    pi.pam_rhost_size = strlen(pi.pam_rhost) + 1;
+    pi.requested_domains = "";
+    pi.cli_pid = 12345;
+
+    ret = pack_message_v3(&pi, &buf_size, &m_buf);
+    assert_int_equal(ret, 0);
+
+    buf = talloc_memdup(mem_ctx, m_buf, buf_size);
+    free(m_buf);
+    assert_non_null(buf);
+
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_WRAPPER);
+    will_return(__wrap_sss_packet_get_body, buf);
+    will_return(__wrap_sss_packet_get_body, buf_size);
+}
+
 static int test_pam_simple_check(uint32_t status, uint8_t *body, size_t blen)
 {
     size_t rp = 0;
@@ -312,6 +501,46 @@ static int test_pam_simple_check(uint32_t status, uint8_t *body, size_t blen)
     return EOK;
 }
 
+static int test_pam_cert_check(uint32_t status, uint8_t *body, size_t blen)
+{
+    size_t rp = 0;
+    uint32_t val;
+
+    assert_int_equal(status, 0);
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, pam_test_ctx->exp_pam_status);
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, 2);
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, SSS_PAM_DOMAIN_NAME);
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, 9);
+
+    assert_int_equal(*(body + rp + val - 1), 0);
+    assert_string_equal(body + rp, TEST_DOM_NAME);
+    rp += val;
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, SSS_PAM_CERT_INFO);
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, (sizeof("pamuser") + sizeof("token_name")));
+
+    assert_int_equal(*(body + rp + sizeof("pamuser") - 1), 0);
+    assert_string_equal(body + rp, "pamuser");
+    rp += sizeof("pamuser");
+
+    assert_int_equal(*(body + rp + sizeof("token_name") - 1), 0);
+    assert_string_equal(body + rp, "token_name");
+
+    return EOK;
+}
+
+
 static int test_pam_offline_chauthtok_check(uint32_t status,
                                             uint8_t *body, size_t blen)
 {
@@ -372,6 +601,23 @@ static int test_pam_wrong_pw_offline_auth_check(uint32_t status,
     return test_pam_simple_check(status, body, blen);
 }
 
+static int test_pam_user_unknown_check(uint32_t status,
+                                       uint8_t *body, size_t blen)
+{
+    size_t rp = 0;
+    uint32_t val;
+
+    assert_int_equal(status, 0);
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, PAM_USER_UNKNOWN);
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, 0);
+
+    return EOK;
+}
+
 void test_pam_authenticate(void **state)
 {
     int ret;
@@ -859,6 +1105,226 @@ void test_pam_offline_chauthtok(void **state)
     assert_int_equal(ret, EOK);
 }
 
+void test_pam_preauth_cert_nocert(void **state)
+{
+    int ret;
+
+    pam_test_ctx->pctx->cert_auth = true;
+
+    mock_input_pam_cert(pam_test_ctx, "pamuser", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_read_pipe_recv, WRAP_CALL_WRAPPER);
+    will_return(__wrap_read_pipe_recv, NULL);
+    will_return(__wrap_read_pipe_recv, 0);
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+static int test_lookup_by_cert_cb(void *pvt)
+{
+    int ret;
+    struct sysdb_attrs *attrs;
+
+    if (pvt != NULL) {
+        attrs = sysdb_new_attrs(pam_test_ctx);
+        assert_non_null(attrs);
+
+        ret = sysdb_attrs_add_mem(attrs, SYSDB_USER_CERT, test_cert_der,
+                                  sizeof(test_cert_der));
+        assert_int_equal(ret, EOK);
+
+        ret = sysdb_set_user_attr(pam_test_ctx->tctx->dom, "pamuser", attrs,
+                                  LDB_FLAG_MOD_ADD);
+        assert_int_equal(ret, EOK);
+    }
+
+    return EOK;
+}
+
+static int test_lookup_by_cert_wrong_user_cb(void *pvt)
+{
+    int ret;
+    struct sysdb_attrs *attrs;
+
+    if (pvt != NULL) {
+        attrs = sysdb_new_attrs(pam_test_ctx);
+        assert_non_null(attrs);
+
+        ret = sysdb_attrs_add_mem(attrs, SYSDB_USER_CERT, test_cert_der,
+                                  sizeof(test_cert_der));
+        assert_int_equal(ret, EOK);
+
+        ret = sysdb_set_user_attr(pam_test_ctx->tctx->dom, "wronguser", attrs,
+                                  LDB_FLAG_MOD_ADD);
+        assert_int_equal(ret, EOK);
+    }
+
+    return EOK;
+}
+
+
+void test_pam_preauth_cert_nomatch(void **state)
+{
+    int ret;
+
+    pam_test_ctx->pctx->cert_auth = true;
+
+    mock_input_pam_cert(pam_test_ctx, "pamuser", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_read_pipe_recv, WRAP_CALL_WRAPPER);
+    will_return(__wrap_read_pipe_recv, P11_CHILD_RESPONSE);
+    will_return(__wrap_read_pipe_recv, sizeof(P11_CHILD_RESPONSE));
+    mock_account_recv(0, 0, NULL, test_lookup_by_cert_cb, NULL);
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+void test_pam_preauth_cert_match(void **state)
+{
+    int ret;
+
+    pam_test_ctx->pctx->cert_auth = true;
+
+    mock_input_pam_cert(pam_test_ctx, "pamuser", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_read_pipe_recv, WRAP_CALL_WRAPPER);
+    will_return(__wrap_read_pipe_recv, P11_CHILD_RESPONSE);
+    will_return(__wrap_read_pipe_recv, sizeof(P11_CHILD_RESPONSE));
+    mock_account_recv(0, 0, NULL, test_lookup_by_cert_cb,
+                      discard_const(TEST_CERT_DERB64));
+
+    set_cmd_cb(test_pam_cert_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+void test_pam_preauth_cert_match_wrong_user(void **state)
+{
+    int ret;
+
+    pam_test_ctx->pctx->cert_auth = true;
+
+    mock_input_pam_cert(pam_test_ctx, "pamuser", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_read_pipe_recv, WRAP_CALL_WRAPPER);
+    will_return(__wrap_read_pipe_recv, P11_CHILD_RESPONSE);
+    will_return(__wrap_read_pipe_recv, sizeof(P11_CHILD_RESPONSE));
+    mock_account_recv(0, 0, NULL, test_lookup_by_cert_wrong_user_cb,
+                      discard_const(TEST_CERT_DERB64));
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+
+void test_pam_preauth_cert_no_logon_name(void **state)
+{
+    int ret;
+
+    pam_test_ctx->pctx->cert_auth = true;
+
+    mock_input_pam_cert(pam_test_ctx, NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_read_pipe_recv, WRAP_CALL_WRAPPER);
+    will_return(__wrap_read_pipe_recv, P11_CHILD_RESPONSE);
+    will_return(__wrap_read_pipe_recv, sizeof(P11_CHILD_RESPONSE));
+    mock_account_recv(0, 0, NULL, test_lookup_by_cert_cb,
+                      discard_const(TEST_CERT_DERB64));
+
+    set_cmd_cb(test_pam_cert_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+void test_pam_preauth_no_cert_no_logon_name(void **state)
+{
+    int ret;
+
+    pam_test_ctx->pctx->cert_auth = true;
+
+    mock_input_pam_cert(pam_test_ctx, NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_read_pipe_recv, WRAP_CALL_WRAPPER);
+    will_return(__wrap_read_pipe_recv, NULL);
+    will_return(__wrap_read_pipe_recv, 0);
+
+    set_cmd_cb(test_pam_user_unknown_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+void test_pam_preauth_cert_no_logon_name_no_match(void **state)
+{
+    int ret;
+
+    pam_test_ctx->pctx->cert_auth = true;
+
+    mock_input_pam_cert(pam_test_ctx, NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+    will_return(__wrap_read_pipe_recv, WRAP_CALL_WRAPPER);
+    will_return(__wrap_read_pipe_recv, P11_CHILD_RESPONSE);
+    will_return(__wrap_read_pipe_recv, sizeof(P11_CHILD_RESPONSE));
+    mock_account_recv(0, 0, NULL, test_lookup_by_cert_cb, NULL);
+
+    set_cmd_cb(test_pam_user_unknown_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
 int main(int argc, const char *argv[])
 {
     int rv;
@@ -925,6 +1391,21 @@ int main(int argc, const char *argv[])
                                         pam_test_setup, pam_test_teardown),
         cmocka_unit_test_setup_teardown(test_pam_offline_chauthtok,
                                         pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_preauth_cert_nocert,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_preauth_cert_nomatch,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_preauth_cert_match,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_preauth_cert_match_wrong_user,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_preauth_cert_no_logon_name,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_preauth_no_cert_no_logon_name,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(
+                                   test_pam_preauth_cert_no_logon_name_no_match,
+                                   pam_test_setup, pam_test_teardown),
     };
 
     /* Set debug level to invalid value so we can deside if -d 0 was used. */
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index 61818c9fc3be49358eebd5b38e8d21cb6e9ab3db..735f6dcfc7af33edcc886fd106cb3655bcc9566a 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -78,6 +78,7 @@ struct err_string error_to_str[] = {
     { "Unsupported trust direction" }, /* ERR_TRUST_NOT_SUPPORTED */
     { "Retrieving keytab failed" }, /* ERR_IPA_GETKEYTAB_FAILED */
     { "Trusted forest root unknown" }, /* ERR_TRUST_FOREST_UNKNOWN */
+    { "p11_child failed" }, /* ERR_P11_CHILD */
     { "ERR_LAST" } /* ERR_LAST */
 };
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 7e03f00e899ee718a35bfaa9340d0b11565862db..fbfbdef334be1fb8a525b78ab6336d616b31a189 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -100,6 +100,7 @@ enum sssd_errors {
     ERR_TRUST_NOT_SUPPORTED,
     ERR_IPA_GETKEYTAB_FAILED,
     ERR_TRUST_FOREST_UNKNOWN,
+    ERR_P11_CHILD,
     ERR_LAST            /* ALWAYS LAST */
 };
 
-- 
2.1.0

-------------- next part --------------
From 8a7e2923fac2149990f2f0d35c37c3a25a547e92 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Fri, 3 Jul 2015 14:05:11 +0200
Subject: [PATCH 6/6] pam_sss: add sc support

---
 src/sss_client/pam_message.h |  3 ++
 src/sss_client/pam_sss.c     | 93 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 95 insertions(+), 1 deletion(-)

diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h
index 3b3841a2c66b46d78855164099684ef2ac98ed77..f0a7a076cf38a4efc8befcc2fb835ae26e9415a4 100644
--- a/src/sss_client/pam_message.h
+++ b/src/sss_client/pam_message.h
@@ -56,6 +56,9 @@ struct pam_items {
     char *otp_token_id;
     char *otp_challenge;
     char *first_factor;
+
+    char *cert_user;
+    char *token_name;
 };
 
 int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer);
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index e4fa83e12c71bb05dd329686cf2d2df6323ff3bd..926be5af3bc10af7890e063536759d7d851bcbfe 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -155,6 +155,12 @@ static void overwrite_and_free_pam_items(struct pam_items *pi)
 
     free(pi->otp_challenge);
     pi->otp_challenge = NULL;
+
+    free(pi->cert_user);
+    pi->cert_user = NULL;
+
+    free(pi->token_name);
+    pi->token_name = NULL;
 }
 
 static int null_strcmp(const char *s1, const char *s2) {
@@ -922,7 +928,7 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
                 break;
             case SSS_PAM_OTP_INFO:
                 if (buf[p + (len - 1)] != '\0') {
-                    D(("system info does not end with \\0."));
+                    D(("otp info does not end with \\0."));
                     break;
                 }
 
@@ -959,6 +965,32 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
                 }
 
                 break;
+            case SSS_PAM_CERT_INFO:
+                if (buf[p + (len - 1)] != '\0') {
+                    D(("cert info does not end with \\0."));
+                    break;
+                }
+
+                pi->cert_user = strdup((char *) &buf[p]);
+                if (pi->cert_user == NULL) {
+                    D(("strdup failed"));
+                }
+
+                offset = strlen(pi->cert_user) + 1;
+                if (offset >= len) {
+                    D(("Cert message size mismatch"));
+                    free(pi->otp_vendor);
+                    pi->cert_user = NULL;
+                    break;
+                }
+                pi->token_name = strdup((char *) &buf[p + offset]);
+                if (pi->token_name == NULL) {
+                    D(("strdup failed"));
+                    break;
+                }
+                D(("cert user: [%s] token name: [%s]", pi->cert_user,
+                                                       pi->token_name));
+                break;
             default:
                 D(("Unknown response type [%d]", type));
         }
@@ -1039,6 +1071,9 @@ static int get_pam_items(pam_handle_t *pamh, struct pam_items *pi)
     pi->otp_token_id = NULL;
     pi->otp_challenge = NULL;
 
+    pi->cert_user = NULL;
+    pi->token_name = NULL;
+
     return PAM_SUCCESS;
 }
 
@@ -1345,6 +1380,60 @@ done:
     return ret;
 }
 
+#define SC_PROMPT_FMT "PIN for %s for user %s"
+static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi)
+{
+    int ret;
+    char *answer = NULL;
+    char *prompt;
+    size_t size;
+
+    if (pi->token_name == NULL || *pi->token_name == '\0'
+            || pi->cert_user == NULL || *pi->cert_user == '\0') {
+        return EINVAL;
+    }
+
+    size = sizeof(SC_PROMPT_FMT) + strlen(pi->token_name) +
+           strlen(pi->cert_user);
+    prompt = malloc(size);
+    if (prompt == NULL) {
+        D(("malloc failed."));
+        return ENOMEM;
+    }
+
+    ret = snprintf(prompt, size, SC_PROMPT_FMT, pi->token_name, pi->cert_user);
+    if (ret < 0 || ret >= size) {
+        D(("snprintf failed."));
+        free(prompt);
+        return EFAULT;
+    }
+
+    ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, &answer);
+    free(prompt);
+    if (ret != PAM_SUCCESS) {
+        D(("do_pam_conversation failed."));
+        return ret;
+    }
+
+    if (answer == NULL) {
+        pi->pam_authtok = NULL;
+        pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY;
+        pi->pam_authtok_size=0;
+    } else {
+        pi->pam_authtok = strdup(answer);
+        _pam_overwrite((void *)answer);
+        free(answer);
+        answer=NULL;
+        if (pi->pam_authtok == NULL) {
+            return PAM_BUF_ERR;
+        }
+        pi->pam_authtok_type = SSS_AUTHTOK_TYPE_SC_PIN;
+        pi->pam_authtok_size=strlen(pi->pam_authtok);
+    }
+
+    return PAM_SUCCESS;
+}
+
 static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi)
 {
     int ret;
@@ -1458,6 +1547,8 @@ static int get_authtok_for_authentication(pam_handle_t *pamh,
                         && pi->otp_challenge != NULL)) {
             ret = prompt_2fa(pamh, pi, _("First Factor: "),
                              _("Second Factor: "));
+        } else if (pi->cert_user != NULL) {
+            ret = prompt_sc_pin(pamh, pi);
         } else {
             ret = prompt_password(pamh, pi, _("Password: "));
         }
-- 
2.1.0



More information about the sssd-devel mailing list