Blob Blame History Raw
From e13d8a12513bfba1531cc867fbf687ff8673241f Mon Sep 17 00:00:00 2001
From: Alexey Tikhonov <atikhono@redhat.com>
Date: Thu, 10 Dec 2020 19:45:08 +0100
Subject: [PATCH] Squashed commit of the following:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

commit 6b3b4b0bf945814e8886b900dcda18de25f38bb4
Author: Sumit Bose <sbose@redhat.com>
Date:   Thu Dec 12 13:10:16 2019 +0100

    certmap: mention special regex characters in man page

    Since some of the matching rules use regular expressions some characters
    must be escaped so that they can be used a ordinary characters in the
    rules.

    Related to https://pagure.io/SSSD/sssd/issue/4127

    Reviewed-by: Michal Židek <mzidek@redhat.com>
    (cherry picked from commit 21cb9fb28db1f2eb4ee770eb029bfe20233e4392)

commit 451410e72514bd68e4b56b1a42c97ade6783e74b
Author: Sumit Bose <sbose@redhat.com>
Date:   Tue Nov 27 16:42:38 2018 +0100

    test: add certificate without KU to certmap tests

    Make sure there is a test for a certificate without key-usage (KU)

    Related to https://bugzilla.redhat.com/show_bug.cgi?id=1660899

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit e1734ba828470d00370c44c95da56822fdcc104d)

commit e7966dfa40b9a7fcde79a07f146ae5283a7bc8e5
Author: Sumit Bose <sbose@redhat.com>
Date:   Mon Nov 26 12:03:13 2018 +0100

    certmap: allow missing KU in OpenSSL version

    Make sure a missing key-usage (KU) is not treated as an error and is
    handled equally in the NSS and OpenSSL implementation

    Related to https://bugzilla.redhat.com/show_bug.cgi?id=1660899

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit aef8e49b7ee2e7743d6981070d61bc89b7c8fcfb)

commit 6e9e6673916b61197df8a809f56c73d8bdbb868c
Author: Alexey Tikhonov <atikhono@redhat.com>
Date:   Mon Jan 7 17:04:34 2019 +0100

    CONFIG: validator rules & test

    Add support of 'certmap' config section to validator rules

    Resolves:
    https://pagure.io/SSSD/sssd/issue/3845

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 8e9e8011ce17860bec67a572e4c11a9178c03b8e)

commit eec9d72a242b2b05369f0eb89c4ebcda26d59802
Author: Sumit Bose <sbose@redhat.com>
Date:   Fri Sep 7 22:26:21 2018 +0200

    intg: add Smartcard authentication tests

    Two test for Smartcard authentication of a local user, i.e. a user
    managed by the files provider, are added. One for a successful
    authentication, the other for a failed authentication with a wrong PIN.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked with fixes from commit 657f3b89bca9adfb13f0867c91f1d76845d2d6dd)

commit cc2840fbb494ac686e9a3ae0016827a44d14769f
Author: Sumit Bose <sbose@redhat.com>
Date:   Fri Sep 7 22:17:47 2018 +0200

    test_ca: set a password/PIN to nss databases

    To make sure the PIN is properly checked during tests the NSS databases
    need a password.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit a45a410dc7fa7cf84bcac541e693ee8781e25431)

commit 0a989c62b4a3b73f23d9b6956ac81afaed9901f7
Author: Sumit Bose <sbose@redhat.com>
Date:   Mon Sep 10 22:03:55 2018 +0200

    test_ca: test library only for readable

    On Debian libraries typically do not have the execute-bit set so it is
    better to only check for readability.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 91aea762d02731193eb66a00b930ff1fe8bc5ab8)

commit 5a47b213b11cbf74dad47594d1826985f6b68f22
Author: Sumit Bose <sbose@redhat.com>
Date:   Fri Sep 7 22:16:50 2018 +0200

    PAM: use better PAM error code for failed Smartcard authentication

    If the user enters a wrong PIN the PAM responder currently returns
    PAM_USER_UNKNOWN better is PAM_AUTH_ERR.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 442ae7b1d0704cdd667d4f1ba4c165ce3f3ffed4)

commit b6907d7cd5ab7568971ddb48f3932f106e86fe06
Author: Sumit Bose <sbose@redhat.com>
Date:   Mon Sep 3 18:38:42 2018 +0200

    doc: add certificate mapping section to man page

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked with fixes from commit 0c739e969a617bdb4c06cdfd63772bf6d283c518)

commit d75b196312c4cec767c196c663ff969b6aebcd6b
Author: Sumit Bose <sbose@redhat.com>
Date:   Mon Jul 9 18:56:26 2018 +0200

    PAM: add certificate matching rules from all domains

    Currently the PAM responder only reads the certificate mapping and
    matching rules from the first domain. To support Smartcard
    authentication for local and remote users all configured domains must be
    taken into account.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit d42f44d54453d3ddb54875374c1b61dc1e7cd821)

commit 167ab7206913c17617a8e5ada7567d91f8ed6e11
Author: Sumit Bose <sbose@redhat.com>
Date:   Mon Jul 9 18:45:21 2018 +0200

    responder: make sure SSS_DP_CERT is passed to files provider

    Currently the files provider is only contacted once in a while to update
    the full cache with fresh data from the passwd file. To allow rule based
    certificate mapping the lookup by certificate request must be always
    send to the file provider so that it can evaluate the rules and add the
    certificate to cached entry of the matching user.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 9fdc5f1d87a133885e6a22810a7eb980c60dcb55)

commit 69def7a3e81313a30ceae937f9cde5d62e999c3d
Author: Sumit Bose <sbose@redhat.com>
Date:   Mon Jul 9 18:37:46 2018 +0200

    files: add support for Smartcard authentication

    To support certificate based authentication the files provider must be
    able to map a certificate to a user during a BE_REQ_BY_CERT request.

    Additionally the authentication request should be handled by the PAM
    responder code which is responsible for the local Smartcard
    authentication. To be consistent with the other backend an authentication
    handler is added to the files provider which unconditionally returns the
    offline error code telling the PAM responder to handle the
    authentication if it has access to the needed credentials.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 275eeed24adc31f3df51cf278f509a4be76a3a3c)

commit e96ba56ea8037d58e1335f7dacd3b19919bc4135
Author: Sumit Bose <sbose@redhat.com>
Date:   Fri Jul 6 15:17:10 2018 +0200

    confdb: add special handling for rules for the files provider

    To make the configuration more simple there are some special assumption
    for local users, i.e. user managed by the files provider.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 9386ef605ffbc03abe2bc273efddbc099441fe3b)

commit d304f5a9e60f7f6eb915a10067ee2e5e5f14c369
Author: Sumit Bose <sbose@redhat.com>
Date:   Tue Jul 3 11:31:12 2018 +0200

    sysdb: sysdb_certmap_add() handle domains more flexible

    sysdb_ldb_msg_attr_to_certmap_info() creates an empty list if there are
    no domains defined, sysdb_certmap_add() should be able to handle both a
    missing or an empty domains list.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 06f7005d38d164879b727708feff80004b422f91)

commit 53befb320c2b60a420a2588425fd5004ceec791a
Author: Sumit Bose <sbose@redhat.com>
Date:   Mon Jul 2 12:20:53 2018 +0200

    AD/LDAP: read certificate mapping rules from config file

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 15301db1dc1e5e2aafc1805a30e3b28756218c9b)

commit 670a1ca6b7b22bb3a1079111528ee7e4aafd97e5
Author: Sumit Bose <sbose@redhat.com>
Date:   Mon Jul 2 10:38:54 2018 +0200

    confdb: add confdb_certmap_to_sysdb()

    Add a function to write certificate mapping and matching rules from the
    config database to the cache of a domain.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked with fixes from commit d9cc38008a51a8a5189904f175e4d10cbde4a974)

commit 14c15cc6db16726419fbf6df76b5c83aec49192a
Author: Sumit Bose <sbose@redhat.com>
Date:   Fri Jun 29 18:13:59 2018 +0200

    sysdb: add attr_map attribute to sysdb_ldb_msg_attr_to_certmap_info()

    Allow more flexible attribute mapping in
    sysdb_ldb_msg_attr_to_certmap_info()

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 0bf709ad348ca115443bd21e4e369abd5d7698c4)

commit f867c2a293651043072afe1dd7a8a78a05e5fe4d
Author: Sumit Bose <sbose@redhat.com>
Date:   Tue Jul 3 11:30:07 2018 +0200

    sysdb_ldb_msg_attr_to_certmap_info: set SSS_CERTMAP_MIN_PRIO

    Make sure that priority is always set.

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit d1dd7f7703b4f40d2fbb830e28969b31b8a1673e)

commit 8ef2cc11008ef86f4dfcbc267c797bf8ee265455
Author: Sumit Bose <sbose@redhat.com>
Date:   Fri Jun 29 17:49:50 2018 +0200

    sysdb: extract sysdb_ldb_msg_attr_to_certmap_info() call

    Related to https://pagure.io/SSSD/sssd/issue/3500

    Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
    (cherry picked from commit 7c619ae08f05a7595d15cf11b64461a7d19cfaa7)
---
 Makefile.am                                   |   2 +
 configure.ac                                  |   1 +
 contrib/sssd.spec.in                          |   1 +
 src/confdb/confdb.c                           | 158 +++++++++++++
 src/confdb/confdb.h                           |  23 ++
 src/config/cfg_rules.ini                      |  10 +
 src/db/sysdb.h                                |   5 +
 src/db/sysdb_certmap.c                        | 223 +++++++++++-------
 src/external/cwrap.m4                         |   5 +
 src/external/intgcheck.m4                     |   1 +
 src/external/test_ca.m4                       |   2 +-
 src/lib/certmap/sss_cert_content_crypto.c     |  22 +-
 src/lib/certmap/sss_cert_content_nss.c        |  21 +-
 src/man/sss-certmap.5.xml                     |   9 +
 src/man/sssd.conf.5.xml                       | 149 ++++++++++++
 src/providers/ad/ad_init.c                    |  16 ++
 src/providers/files/files_auth.c              |  69 ++++++
 src/providers/files/files_certmap.c           | 186 +++++++++++++++
 src/providers/files/files_id.c                |  20 ++
 src/providers/files/files_init.c              |  29 +++
 src/providers/files/files_private.h           |  17 ++
 src/providers/ldap/ldap_init.c                |  16 ++
 src/responder/common/responder_dp.c           |  20 +-
 src/responder/pam/pamsrv.h                    |   2 +-
 src/responder/pam/pamsrv_cmd.c                |   6 +-
 src/responder/pam/pamsrv_p11.c                |  77 +++---
 src/tests/cmocka/test_certmap.c               |  45 ++++
 src/tests/cmocka/test_config_check.c          |  16 +-
 src/tests/intg/test_pam_responder.py          | 109 ++++++++-
 src/tests/test_CA/Makefile.am                 |  24 +-
 src/tests/test_CA/SSSD_test_cert_0004.config  |  11 +
 src/tests/test_CA/SSSD_test_cert_key_0004.pem |  28 +++
 32 files changed, 1170 insertions(+), 153 deletions(-)
 create mode 100644 src/providers/files/files_auth.c
 create mode 100644 src/providers/files/files_certmap.c
 create mode 100644 src/tests/test_CA/SSSD_test_cert_0004.config
 create mode 100644 src/tests/test_CA/SSSD_test_cert_key_0004.pem

diff --git a/Makefile.am b/Makefile.am
index 718041634..77f5faf6b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4094,6 +4094,8 @@ libsss_proxy_la_LDFLAGS = \
 libsss_files_la_SOURCES = \
     src/providers/files/files_init.c \
     src/providers/files/files_id.c \
+    src/providers/files/files_auth.c \
+    src/providers/files/files_certmap.c \
     src/providers/files/files_ops.c \
     src/util/inotify.c \
     $(NULL)
diff --git a/configure.ac b/configure.ac
index 5154918b1..89abddef4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -488,6 +488,7 @@ AM_CONDITIONAL([HAVE_CHECK], [test x$have_check != x])
 AM_CHECK_CMOCKA
 AM_CHECK_UID_WRAPPER
 AM_CHECK_NSS_WRAPPER
+AM_CHECK_PAM_WRAPPER
 AM_CHECK_TEST_CA
 
 # Check if the user wants SSSD to be compiled with systemtap probes
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index 10b5bd56c..52a77ae7a 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -236,6 +236,7 @@ BuildRequires: selinux-policy-targeted
 BuildRequires: libcmocka-devel >= 1.0.0
 BuildRequires: uid_wrapper
 BuildRequires: nss_wrapper
+BuildRequires: pam_wrapper
 
 # Test CA requires openssl independent if SSSD is build with NSS or openssl,
 # openssh is needed for ssh-keygen and NSS builds need nss-tools for certutil.
diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c
index e55f88e4e..97de6d3b1 100644
--- a/src/confdb/confdb.c
+++ b/src/confdb/confdb.c
@@ -2209,3 +2209,161 @@ done:
     talloc_free(tmp_ctx);
     return ret;
 }
+
+static errno_t certmap_local_check(struct ldb_message *msg)
+{
+    const char *rule_name;
+    const char *tmp_str;
+    int ret;
+
+    rule_name = ldb_msg_find_attr_as_string(msg, CONFDB_CERTMAP_NAME, NULL);
+    if (rule_name == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Certficate mapping rule [%s] has no name.",
+                                   ldb_dn_get_linearized(msg->dn));
+        return EINVAL;
+    }
+
+    tmp_str = ldb_msg_find_attr_as_string(msg, CONFDB_CERTMAP_DOMAINS, NULL);
+    if (tmp_str != NULL) {
+        DEBUG(SSSDBG_CONF_SETTINGS,
+              "Option [%s] is ignored for local certmap rules.\n",
+              CONFDB_CERTMAP_DOMAINS);
+    }
+
+    tmp_str = ldb_msg_find_attr_as_string(msg, CONFDB_CERTMAP_MAPRULE, NULL);
+    if (tmp_str != NULL) {
+        if (tmp_str[0] != '(' || tmp_str[strlen(tmp_str) - 1] != ')') {
+            DEBUG(SSSDBG_CONF_SETTINGS,
+                  "Mapping rule must be in braces (...).\n");
+            return EINVAL;
+        }
+        DEBUG(SSSDBG_TRACE_ALL, "Using [%s] mapping rule of [%s].\n",
+                                tmp_str, ldb_dn_get_linearized(msg->dn));
+        return EOK;
+    }
+
+    tmp_str = talloc_asprintf(msg, "(%s)", rule_name);
+    if (tmp_str == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+        return ENOMEM;
+    }
+    ret = ldb_msg_add_string(msg, CONFDB_CERTMAP_MAPRULE, tmp_str);
+    if (ret != LDB_SUCCESS) {
+        talloc_free(discard_const(tmp_str));
+        DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_add_string failed.\n");
+        return EIO;
+    }
+
+    DEBUG(SSSDBG_TRACE_ALL, "Using [%s] as mapping rule for [%s].\n",
+                            tmp_str, ldb_dn_get_linearized(msg->dn));
+
+    return EOK;
+}
+
+static errno_t confdb_get_all_certmaps(TALLOC_CTX *mem_ctx,
+                                       struct confdb_ctx *cdb,
+                                       struct sss_domain_info *dom,
+                                       struct certmap_info ***_certmap_list)
+{
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct ldb_dn *dn = NULL;
+    struct ldb_result *res = NULL;
+    /* The attributte order is important, because it is used in
+     * sysdb_ldb_msg_attr_to_certmap_info and must match
+     * enum certmap_info_member. */
+    static const char *attrs[] = { CONFDB_CERTMAP_NAME,
+                                   CONFDB_CERTMAP_MAPRULE,
+                                   CONFDB_CERTMAP_MATCHRULE,
+                                   CONFDB_CERTMAP_PRIORITY,
+                                   CONFDB_CERTMAP_DOMAINS,
+                                   NULL};
+    struct certmap_info **certmap_list = NULL;
+    size_t c;
+    int ret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    dn = ldb_dn_new_fmt(tmp_ctx, cdb->ldb, "cn=%s,%s", dom->name,
+                                                       CONFDB_CERTMAP_BASEDN);
+    if (dn == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = ldb_search(cdb->ldb, tmp_ctx, &res, dn, LDB_SCOPE_ONELEVEL,
+                     attrs, NULL);
+    if (ret != LDB_SUCCESS) {
+        ret = EIO;
+        goto done;
+    }
+
+    certmap_list = talloc_zero_array(tmp_ctx, struct certmap_info *,
+                                     res->count + 1);
+    if (certmap_list == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (c = 0; c < res->count; c++) {
+        if (is_files_provider(dom)) {
+            ret = certmap_local_check(res->msgs[c]);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_CONF_SETTINGS,
+                      "Invalid certificate mapping [%s] for local user, "
+                      "ignored.\n", ldb_dn_get_linearized(res->msgs[c]->dn));
+                continue;
+            }
+        }
+        ret = sysdb_ldb_msg_attr_to_certmap_info(certmap_list, res->msgs[c],
+                                                 attrs, &certmap_list[c]);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "sysdb_ldb_msg_attr_to_certmap_info failed.\n");
+            goto done;
+        }
+    }
+
+    *_certmap_list = talloc_steal(mem_ctx, certmap_list);
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+int confdb_certmap_to_sysdb(struct confdb_ctx *cdb,
+                            struct sss_domain_info *dom)
+{
+    int ret;
+    TALLOC_CTX *tmp_ctx;
+    struct certmap_info **certmap_list;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+        return ENOMEM;
+    }
+
+    ret = confdb_get_all_certmaps(tmp_ctx, cdb, dom, &certmap_list);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "confdb_get_all_certmaps failed.\n");
+        goto done;
+    }
+
+    ret = sysdb_update_certmap(dom->sysdb, certmap_list, false /* TODO */);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_certmap failed.\n");
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index d3e71be86..b0d52ba49 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -267,6 +267,14 @@
 #define CONFDB_KCM_SOCKET "socket_path"
 #define CONFDB_KCM_DB "ccache_storage" /* Undocumented on purpose */
 
+/* Certificate mapping rules */
+#define CONFDB_CERTMAP_BASEDN "cn=certmap,cn=config"
+#define CONFDB_CERTMAP_NAME "cn"
+#define CONFDB_CERTMAP_MAPRULE "maprule"
+#define CONFDB_CERTMAP_MATCHRULE "matchrule"
+#define CONFDB_CERTMAP_DOMAINS "domains"
+#define CONFDB_CERTMAP_PRIORITY "priority"
+
 /* Prompting */
 #define CONFDB_PC_CONF_ENTRY "config/prompting"
 #define CONFDB_PC_TYPE_PASSWORD "password"
@@ -681,6 +689,21 @@ int confdb_get_sub_sections(TALLOC_CTX *mem_ctx,
                             const char *section,
                             char ***sections,
                             int *num_sections);
+
+/**
+ * @brief Convenience function to write the certificate mapping and matching
+ * rules from the configuration database to the cache of a domain
+ *
+ * @param[in] cdb The connection object to the confdb
+ * @param[in] dom Target domain where to rules should be written to
+ *
+ * @return 0 - Successfully retrieved the entry (or used the default)
+ * @return ENOMEM - There was insufficient memory to complete the operation
+ * @return EINVAL - Typically internal processing error
+ */
+int confdb_certmap_to_sysdb(struct confdb_ctx *cdb,
+                            struct sss_domain_info *dom);
+
 /**
  * @}
  */
diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
index 997ba5aec..79e366875 100644
--- a/src/config/cfg_rules.ini
+++ b/src/config/cfg_rules.ini
@@ -17,6 +17,7 @@ section_re = ^secrets/kcm$
 section_re = ^domain/[^/\@]\+$
 section_re = ^domain/[^/\@]\+/[^/\@]\+$
 section_re = ^application/[^/\@]\+$
+section_re = ^certmap/[^/\@]\+/[^/\@]\+$
 
 
 [rule/allowed_sssd_options]
@@ -765,3 +766,12 @@ option = use_fully_qualified_names
 
 [rule/sssd_checks]
 validator = sssd_checks
+
+[rule/allowed_certmap_options]
+validator = ini_allowed_options
+section_re = ^certmap/[^/\@]\+/[^/\@]\+$
+
+option = matchrule
+option = maprule
+option = priority
+option = domains
diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 018ec22ab..a2bc8ed3b 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -737,6 +737,11 @@ errno_t sysdb_update_certmap(struct sysdb_ctx *sysdb,
                              struct certmap_info **certmaps,
                              bool user_name_hint);
 
+errno_t sysdb_ldb_msg_attr_to_certmap_info(TALLOC_CTX *mem_ctx,
+                                           struct ldb_message *msg,
+                                           const char **attr_map,
+                                           struct certmap_info **certmap);
+
 errno_t sysdb_get_certmap(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb,
                           struct certmap_info ***certmaps,
                           bool *user_name_hint);
diff --git a/src/db/sysdb_certmap.c b/src/db/sysdb_certmap.c
index eda20f5a7..4cc5b5b41 100644
--- a/src/db/sysdb_certmap.c
+++ b/src/db/sysdb_certmap.c
@@ -22,6 +22,7 @@
 
 #include "util/util.h"
 #include "db/sysdb_private.h"
+#include "lib/certmap/sss_certmap.h"
 
 static errno_t sysdb_create_certmap_container(struct sysdb_ctx *sysdb,
                                               bool user_name_hint)
@@ -153,7 +154,7 @@ static errno_t sysdb_certmap_add(struct sysdb_ctx *sysdb,
         }
     }
 
-    if (certmap->domains != NULL) {
+    if (certmap->domains != NULL && certmap->domains[0] != NULL) {
         for (c = 0; certmap->domains[c] != NULL; c++);
         el = talloc_zero(tmp_ctx, struct ldb_message_element);
         if (el == NULL) {
@@ -285,28 +286,151 @@ done:
     return ret;
 }
 
+enum certmap_info_member {
+    SSS_CMIM_NAME = 0,
+    SSS_CMIM_MAPPING_RULE,
+    SSS_CMIM_MATCHING_RULE,
+    SSS_CMIM_PRIORITY,
+    SSS_CMIM_DOMAINS,
+
+    SSS_CMIM_SENTINEL
+};
+
+errno_t sysdb_ldb_msg_attr_to_certmap_info(TALLOC_CTX *mem_ctx,
+                                           struct ldb_message *msg,
+                                           const char **attr_map,
+                                           struct certmap_info **certmap)
+{
+    int ret;
+    size_t d;
+    size_t num_values;
+    struct certmap_info *map = NULL;
+    const char *tmp_str;
+    uint64_t tmp_uint;
+    struct ldb_message_element *tmp_el;
+
+    if (msg == NULL || attr_map == NULL || certmap == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input.\n");
+        return EINVAL;
+    }
+
+    for (d = 0; d < SSS_CMIM_SENTINEL; d++) {
+        if (attr_map[d] == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Invalid attribute map");
+            return EINVAL;
+        }
+    }
+
+    map = talloc_zero(mem_ctx, struct certmap_info);
+    if (map == NULL) {
+        return ENOMEM;
+    }
+
+    tmp_str = ldb_msg_find_attr_as_string(msg, attr_map[SSS_CMIM_NAME], NULL);
+    if (tmp_str == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "The object [%s] doesn't have a name.\n",
+                                    ldb_dn_get_linearized(msg->dn));
+        ret = EINVAL;
+        goto done;
+    }
+
+    map->name = talloc_strdup(map, tmp_str);
+    if (map->name == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    tmp_str = ldb_msg_find_attr_as_string(msg, attr_map[SSS_CMIM_MAPPING_RULE],
+                                          NULL);
+    if (tmp_str != NULL) {
+        map->map_rule = talloc_strdup(map, tmp_str);
+        if (map->map_rule == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+
+    tmp_str = ldb_msg_find_attr_as_string(msg, attr_map[SSS_CMIM_MATCHING_RULE],
+                                          NULL);
+    if (tmp_str != NULL) {
+        map->match_rule = talloc_strdup(map, tmp_str);
+        if (map->match_rule == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+
+    tmp_uint = ldb_msg_find_attr_as_uint64(msg, attr_map[SSS_CMIM_PRIORITY],
+                                           (uint64_t) -1);
+    if (tmp_uint != (uint64_t) -1) {
+        if (tmp_uint > UINT32_MAX) {
+            DEBUG(SSSDBG_OP_FAILURE, "Priority value [%lu] too large.\n",
+                                     (unsigned long) tmp_uint);
+            ret = EINVAL;
+            goto done;
+        }
+
+        map->priority = (uint32_t) tmp_uint;
+    } else {
+        map->priority = SSS_CERTMAP_MIN_PRIO;
+    }
+
+    tmp_el = ldb_msg_find_element(msg, attr_map[SSS_CMIM_DOMAINS]);
+    if (tmp_el != NULL) {
+        num_values = tmp_el->num_values;
+    } else {
+        num_values = 0;
+    }
+
+    map->domains = talloc_zero_array(map, const char *, num_values + 1);
+    if (map->domains == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (d = 0; d < num_values; d++) {
+        map->domains[d] = talloc_strndup(map->domains,
+                                         (char *) tmp_el->values[d].data,
+                                         tmp_el->values[d].length);
+        if (map->domains[d] == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+
+    *certmap = map;
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        talloc_free(map);
+    }
+
+    return ret;
+}
+
 errno_t sysdb_get_certmap(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb,
                           struct certmap_info ***certmaps, bool *user_name_hint)
 {
     size_t c;
-    size_t d;
     struct ldb_dn *container_dn = NULL;
     int ret;
     struct certmap_info **maps = NULL;
     TALLOC_CTX *tmp_ctx = NULL;
     struct ldb_result *res;
-    const char *tmp_str;
-    uint64_t tmp_uint;
-    struct ldb_message_element *tmp_el;
     const char *attrs[] = {SYSDB_NAME,
-                           SYSDB_CERTMAP_PRIORITY,
-                           SYSDB_CERTMAP_MATCHING_RULE,
                            SYSDB_CERTMAP_MAPPING_RULE,
+                           SYSDB_CERTMAP_MATCHING_RULE,
+                           SYSDB_CERTMAP_PRIORITY,
                            SYSDB_CERTMAP_DOMAINS,
                            NULL};
     const char *config_attrs[] = {SYSDB_CERTMAP_USER_NAME_HINT,
                                   NULL};
-    size_t num_values;
     bool hint = false;
 
     tmp_ctx = talloc_new(NULL);
@@ -355,86 +479,13 @@ errno_t sysdb_get_certmap(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb,
     }
 
     for (c = 0; c < res->count; c++) {
-        maps[c] = talloc_zero(maps, struct certmap_info);
-        if (maps[c] == NULL) {
-            ret = ENOMEM;
-            goto done;
-        }
-        tmp_str = ldb_msg_find_attr_as_string(res->msgs[c], SYSDB_NAME, NULL);
-        if (tmp_str == NULL) {
-            DEBUG(SSSDBG_MINOR_FAILURE, "The object [%s] doesn't have a name.\n",
-                                       ldb_dn_get_linearized(res->msgs[c]->dn));
-            ret = EINVAL;
-            goto done;
-        }
-
-        maps[c]->name = talloc_strdup(maps, tmp_str);
-        if (maps[c]->name == NULL) {
-            ret = ENOMEM;
-            goto done;
-        }
-
-        tmp_str = ldb_msg_find_attr_as_string(res->msgs[c],
-                                              SYSDB_CERTMAP_MAPPING_RULE, NULL);
-        if (tmp_str != NULL) {
-            maps[c]->map_rule = talloc_strdup(maps, tmp_str);
-            if (maps[c]->map_rule == NULL) {
-                DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
-                ret = ENOMEM;
-                goto done;
-            }
-        }
-
-        tmp_str = ldb_msg_find_attr_as_string(res->msgs[c],
-                                              SYSDB_CERTMAP_MATCHING_RULE, NULL);
-        if (tmp_str != NULL) {
-            maps[c]->match_rule = talloc_strdup(maps, tmp_str);
-            if (maps[c]->match_rule == NULL) {
-                DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
-                ret = ENOMEM;
-                goto done;
-            }
-        }
-
-        tmp_uint = ldb_msg_find_attr_as_uint64(res->msgs[c],
-                                               SYSDB_CERTMAP_PRIORITY,
-                                               (uint64_t) -1);
-        if (tmp_uint != (uint64_t) -1) {
-            if (tmp_uint > UINT32_MAX) {
-                DEBUG(SSSDBG_OP_FAILURE, "Priority value [%lu] too large.\n",
-                                         (unsigned long) tmp_uint);
-                ret = EINVAL;
-                goto done;
-            }
-
-            maps[c]->priority = (uint32_t) tmp_uint;
-        }
-
-        tmp_el = ldb_msg_find_element(res->msgs[c], SYSDB_CERTMAP_DOMAINS);
-        if (tmp_el != NULL) {
-            num_values = tmp_el->num_values;
-        } else {
-            num_values = 0;
-        }
-
-        maps[c]->domains = talloc_zero_array(maps[c], const char *,
-                                             num_values + 1);
-        if (maps[c]->domains == NULL) {
-            DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n");
-            ret = ENOMEM;
+        ret = sysdb_ldb_msg_attr_to_certmap_info(maps, res->msgs[c], attrs,
+                                                 &maps[c]);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "sysdb_ldb_msg_attr_to_certmap_info failed.\n");
             goto done;
         }
-
-        for (d = 0; d < num_values; d++) {
-            maps[c]->domains[d] = talloc_strndup(maps[c]->domains,
-                                            (char *) tmp_el->values[d].data,
-                                            tmp_el->values[d].length);
-            if (maps[c]->domains[d] == NULL) {
-                DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
-                ret = ENOMEM;
-                goto done;
-            }
-        }
     }
 
     ret = EOK;
diff --git a/src/external/cwrap.m4 b/src/external/cwrap.m4
index b8489cc76..6e3487c13 100644
--- a/src/external/cwrap.m4
+++ b/src/external/cwrap.m4
@@ -28,3 +28,8 @@ AC_DEFUN([AM_CHECK_NSS_WRAPPER],
 [
     AM_CHECK_WRAPPER(nss_wrapper, HAVE_NSS_WRAPPER)
 ])
+
+AC_DEFUN([AM_CHECK_PAM_WRAPPER],
+[
+    AM_CHECK_WRAPPER(pam_wrapper, HAVE_PAM_WRAPPER)
+])
diff --git a/src/external/intgcheck.m4 b/src/external/intgcheck.m4
index ef81ec718..d73c5dfb1 100644
--- a/src/external/intgcheck.m4
+++ b/src/external/intgcheck.m4
@@ -75,6 +75,7 @@ AC_DEFUN([SSS_ENABLE_INTGCHECK_REQS], [
     if test x"$enable_intgcheck_reqs" = xyes; then
         SSS_INTGCHECK_REQ([HAVE_UID_WRAPPER], [uid_wrapper])
         SSS_INTGCHECK_REQ([HAVE_NSS_WRAPPER], [nss_wrapper])
+        SSS_INTGCHECK_REQ([HAVE_PAM_WRAPPER], [pam_wrapper])
         SSS_INTGCHECK_REQ([HAVE_SLAPD], [slapd])
         SSS_INTGCHECK_REQ([HAVE_LDAPMODIFY], [ldapmodify])
         SSS_INTGCHECK_REQ([HAVE_FAKEROOT], [fakeroot])
diff --git a/src/external/test_ca.m4 b/src/external/test_ca.m4
index 2cdb3c750..bb4872698 100644
--- a/src/external/test_ca.m4
+++ b/src/external/test_ca.m4
@@ -58,7 +58,7 @@ AC_DEFUN([AM_CHECK_TEST_CA],
             AC_MSG_NOTICE([Could not find p11tool])
         fi
 
-        AM_CONDITIONAL([BUILD_TEST_CA], [test -x "$OPENSSL" -a -x "$SSH_KEYGEN" -a -x "$SOFTHSM2_PATH" -a -x "$SOFTHSM2_UTIL" -a -x "$P11TOOL"])
+        AM_CONDITIONAL([BUILD_TEST_CA], [test -x "$OPENSSL" -a -x "$SSH_KEYGEN" -a -r "$SOFTHSM2_PATH" -a -x "$SOFTHSM2_UTIL" -a -x "$P11TOOL"])
     fi
 
     AM_COND_IF([BUILD_TEST_CA],
diff --git a/src/lib/certmap/sss_cert_content_crypto.c b/src/lib/certmap/sss_cert_content_crypto.c
index ee9aec208..b01830aec 100644
--- a/src/lib/certmap/sss_cert_content_crypto.c
+++ b/src/lib/certmap/sss_cert_content_crypto.c
@@ -771,11 +771,25 @@ int sss_cert_get_content(TALLOC_CTX *mem_ctx,
         ret = EIO;
         goto done;
     }
-    if (!(X509_get_extension_flags(cert) & EXFLAG_KUSAGE)) {
-        ret = EINVAL;
-        goto done;
+    if ((X509_get_extension_flags(cert) & EXFLAG_KUSAGE)) {
+        cont->key_usage = X509_get_key_usage(cert);
+    } else {
+        /* According to X.509 https://www.itu.int/rec/T-REC-X.509-201610-I
+         * section 13.3.2 "Certificate match" "keyUsage matches if all of the
+         * bits set in the presented value are also set in the key usage
+         * extension in the stored attribute value, or if there is no key
+         * usage extension in the stored attribute value;". So we set all bits
+         * in our key_usage to make sure everything matches is keyUsage is not
+         * set in the certificate.
+         *
+         * Please note that NSS currently
+         * (https://bugzilla.mozilla.org/show_bug.cgi?id=549952) does not
+         * support 'decipherOnly' and will only use 0xff in this case. To have
+         * a consistent behavior with both libraries we will use UINT32_MAX
+         * for NSS as well. Since comparisons should be always done with a
+         * bit-wise and-operation the difference should not matter. */
+        cont->key_usage = UINT32_MAX;
     }
-    cont->key_usage = X509_get_key_usage(cert);
 
     ret = get_extended_key_usage_oids(cont, cert,
                                       &(cont->extended_key_usage_oids));
diff --git a/src/lib/certmap/sss_cert_content_nss.c b/src/lib/certmap/sss_cert_content_nss.c
index ed7ce24c4..cef1efc26 100644
--- a/src/lib/certmap/sss_cert_content_nss.c
+++ b/src/lib/certmap/sss_cert_content_nss.c
@@ -887,8 +887,25 @@ int sss_cert_get_content(TALLOC_CTX *mem_ctx,
         goto done;
     }
 
-
-    cont->key_usage = cert->keyUsage;
+    /* According to X.509 https://www.itu.int/rec/T-REC-X.509-201610-I
+     * section 13.3.2 "Certificate match" "keyUsage matches if all of the
+     * bits set in the presented value are also set in the key usage
+     * extension in the stored attribute value, or if there is no key
+     * usage extension in the stored attribute value;". So we set all bits
+     * in our key_usage to make sure everything matches is keyUsage is not
+     * set in the certificate.
+     *
+     * Please note that NSS currently
+     * (https://bugzilla.mozilla.org/show_bug.cgi?id=549952) does not
+     * support 'decipherOnly' and will only use 0xff in this case. To have
+     * a consistent behavior with both libraries we will use UINT32_MAX
+     * for NSS as well. Since comparisons should be always done with a
+     * bit-wise and-operation the difference should not matter. */
+    if (cert->keyUsagePresent == PR_FALSE) {
+        cont->key_usage = UINT32_MAX;
+    } else {
+        cont->key_usage = cert->keyUsage;
+    }
 
     ret = get_extended_key_usage_oids(cont, cert,
                                       &(cont->extended_key_usage_oids));
diff --git a/src/man/sss-certmap.5.xml b/src/man/sss-certmap.5.xml
index db258d14a..10343625e 100644
--- a/src/man/sss-certmap.5.xml
+++ b/src/man/sss-certmap.5.xml
@@ -92,6 +92,15 @@
                     <para>
                         Example: &lt;SUBJECT&gt;.*,DC=MY,DC=DOMAIN
                     </para>
+                    <para>
+                        Please note that the characters "^.[$()|*+?{\" have a
+                        special meaning in regular expressions and must be
+                        escaped with the help of the '\' character so that they
+                        are matched as ordinary characters.
+                    </para>
+                    <para>
+                        Example: &lt;SUBJECT&gt;^CN=.* \(Admin\),DC=MY,DC=DOMAIN$
+                    </para>
                     </listitem>
                 </varlistentry>
                 <varlistentry>
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index 0e1a97a31..8adbb8de9 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -3452,6 +3452,135 @@ ldap_user_extra_attrs = phone:telephoneNumber
         </para>
     </refsect1>
 
+    <refsect1 id='certmap'>
+        <title>CERTIFICATE MAPPING SECTION</title>
+        <para>
+            To allow authentication with Smartcards and certificates SSSD must
+            be able to map certificates to users. This can be done by adding the
+            full certificate to the LDAP object of the user or to a local
+            override. While using the full certificate is required to use the
+            Smartcard authentication feature of SSH (see
+                <citerefentry>
+                    <refentrytitle>sss_ssh_authorizedkeys</refentrytitle>
+                    <manvolnum>8</manvolnum>
+                </citerefentry>
+            for details) it might be cumbersome or not even possible to do this
+            for the general case where local services use PAM for
+            authentication.
+        </para>
+        <para>
+            To make the mapping more flexible mapping and matching rules were
+            added to SSSD (see
+                <citerefentry>
+                    <refentrytitle>sss-certmap</refentrytitle>
+                    <manvolnum>5</manvolnum>
+                </citerefentry>
+            for details).
+        </para>
+        <para>
+            A mapping and matching rule can be added to the SSSD configuration
+            in a section on its own with a name like
+            <quote>[certmap/<replaceable>DOMAIN_NAME</replaceable>/<replaceable>RULE_NAME</replaceable>]</quote>.
+            In this section the following options are allowed:
+        </para>
+        <variablelist>
+            <varlistentry>
+                <term>matchrule (string)</term>
+                <listitem>
+                    <para>
+                        Only certificates from the Smartcard which matches this
+                        rule will be processed, all others are ignored.
+                    </para>
+                    <para>
+                        Default: KRB5:&lt;EKU&gt;clientAuth, i.e. only
+                        certificates which have the Extended Key Usage
+                        <quote>clientAuth</quote>
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>maprule (string)</term>
+                <listitem>
+                    <para>
+                        Defines how the user is found for a given certificate.
+                    </para>
+                    <para>
+                        Default:
+                        <itemizedlist>
+                            <listitem>
+                                <para>LDAP:(userCertificate;binary={cert!bin})
+                                for LDAP based providers like
+                                <quote>ldap</quote>, <quote>AD</quote> or
+                                <quote>ipa</quote>.</para>
+                            </listitem>
+                            <listitem>
+                                <para>The RULE_NAME for the <quote>files</quote>
+                                provider which tries to find a user with the
+                                same name.</para>
+                            </listitem>
+                        </itemizedlist>
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>domains (string)</term>
+                <listitem>
+                    <para>
+                        Comma separated list of domain names the rule should be
+                        applied. By default a rule is only valid in the domain
+                        configured in sssd.conf. If the provider supports
+                        subdomains this option can be used to add the rule to
+                        subdomains as well.
+                    </para>
+                    <para>
+                        Default: the configured domain in sssd.conf
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>priority (integer)</term>
+                <listitem>
+                    <para>
+                        Unsigned integer value defining the priority of the
+                        rule. The higher the number the lower the priority.
+                        <quote>0</quote> stands for the highest priority while
+                        <quote>4294967295</quote> is the lowest.
+                    </para>
+                    <para>
+                        Default: the lowest priority
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+        <para>
+            To make the configuration simple and reduce the amount of
+            configuration options the <quote>files</quote> provider has some
+            special properties:
+            <itemizedlist>
+                <listitem>
+                    <para>
+                        if maprule is not set the RULE_NAME name is assumed to
+                        be the name of the matching user
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        if a maprule is used both a single user name or a
+                        template like
+                        <quote>{subject_rfc822_name.short_name}</quote> must
+                        be in braces like e.g. <quote>(username)</quote> or
+                        <quote>({subject_rfc822_name.short_name})</quote>
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        the <quote>domains</quote> option is ignored
+                    </para>
+                </listitem>
+            </itemizedlist>
+        </para>
+    </refsect1>
+
     <refsect1 id='example'>
         <title>EXAMPLES</title>
         <para>
@@ -3494,6 +3623,26 @@ enumerate = False
 <programlisting>
 [domain/ipa.com/child.ad.com]
 use_fully_qualified_names = false
+</programlisting>
+        </para>
+        <para>
+            3. The following example shows the configuration for two certificate
+            mapping rules. The first is valid for the configured domain
+            <quote>my.domain</quote> and additionally for the subdomains
+            <quote>your.domain</quote> and uses the full certificate in the
+            search filter. The second example is valid for the domain
+            <quote>files</quote> where it is assumed the files provider is used
+            for this domain and contains a matching rule for the local user
+            <quote>myname</quote>.
+<programlisting>
+[certmap/my.domain/rule_name]
+matchrule = &lt;ISSUER&gt;^CN=My-CA,DC=MY,DC=DOMAIN$
+maprule = (userCertificate;binary={cert!bin})
+domains = my.domain, your.domain
+priority = 10
+
+[certmap/files/myname]
+matchrule = &lt;ISSUER&gt;^CN=My-CA,DC=MY,DC=DOMAIN$&lt;SUBJECT&gt;^CN=User.Name,DC=MY,DC=DOMAIN$
 </programlisting>
         </para>
     </refsect1>
diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c
index 09397852b..fb24a28e1 100644
--- a/src/providers/ad/ad_init.c
+++ b/src/providers/ad/ad_init.c
@@ -427,6 +427,22 @@ static errno_t ad_init_misc(struct be_ctx *be_ctx,
         return ret;
     }
 
+    ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to initialize certificate mapping rules. "
+              "Authentication with certificates/Smartcards might not work "
+              "as expected.\n");
+        /* not fatal, ignored */
+    }
+
+    ret = sdap_init_certmap(sdap_id_ctx, sdap_id_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to initialized certificate mapping.\n");
+        return ret;
+    }
+
     return EOK;
 }
 
diff --git a/src/providers/files/files_auth.c b/src/providers/files/files_auth.c
new file mode 100644
index 000000000..b71de6971
--- /dev/null
+++ b/src/providers/files/files_auth.c
@@ -0,0 +1,69 @@
+/*
+    SSSD
+
+    files_auth.c - PAM operations on the files provider
+
+    Copyright (C) 2018 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 <security/pam_modules.h>
+
+#include "providers/data_provider/dp.h"
+#include "providers/data_provider.h"
+#include "providers/files/files_private.h"
+#include "util/cert.h"
+
+struct files_auth_ctx {
+    struct pam_data *pd;
+};
+
+struct tevent_req *
+files_auth_handler_send(TALLOC_CTX *mem_ctx,
+                        void *unused,
+                        struct pam_data *pd,
+                        struct dp_req_params *params)
+{
+    struct files_auth_ctx *state;
+    struct tevent_req *req;
+
+    req = tevent_req_create(mem_ctx, &state, struct files_auth_ctx);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+        return NULL;
+    }
+
+    state->pd = pd;
+    state->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    tevent_req_done(req);
+    tevent_req_post(req, params->ev);
+    return req;
+}
+
+errno_t files_auth_handler_recv(TALLOC_CTX *mem_ctx,
+                                struct tevent_req *req,
+                                struct pam_data **_data)
+{
+    struct files_auth_ctx *state = NULL;
+
+    state = tevent_req_data(req, struct files_auth_ctx);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *_data = talloc_steal(mem_ctx, state->pd);
+
+    return EOK;
+}
diff --git a/src/providers/files/files_certmap.c b/src/providers/files/files_certmap.c
new file mode 100644
index 000000000..7d90a1fec
--- /dev/null
+++ b/src/providers/files/files_certmap.c
@@ -0,0 +1,186 @@
+/*
+    SSSD
+
+    files_init.c - Initialization of the files provider
+
+    Copyright (C) 2018 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 "providers/files/files_private.h"
+#include "util/util.h"
+#include "util/cert.h"
+#include "lib/certmap/sss_certmap.h"
+
+struct priv_sss_debug {
+    int level;
+};
+
+static void ext_debug(void *private, const char *file, long line,
+                      const char *function, const char *format, ...)
+{
+    va_list ap;
+    struct priv_sss_debug *data = private;
+    int level = SSSDBG_OP_FAILURE;
+
+    if (data != NULL) {
+        level = data->level;
+    }
+
+    if (DEBUG_IS_SET(level)) {
+        va_start(ap, format);
+        sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED,
+                      format, ap);
+        va_end(ap);
+    }
+}
+
+errno_t files_init_certmap(TALLOC_CTX *mem_ctx, struct files_id_ctx *id_ctx)
+{
+    int ret;
+    bool hint;
+    struct certmap_info **certmap_list = NULL;
+    size_t c;
+
+    ret = sysdb_get_certmap(mem_ctx, id_ctx->be->domain->sysdb,
+                            &certmap_list, &hint);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n");
+        goto done;
+    }
+
+    if (certmap_list == NULL || *certmap_list == NULL) {
+        DEBUG(SSSDBG_TRACE_ALL, "No certmap data, nothing to do.\n");
+        ret = EOK;
+        goto done;
+    }
+
+    ret = sss_certmap_init(mem_ctx, ext_debug, NULL, &id_ctx->sss_certmap_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n");
+        goto done;
+    }
+
+    for (c = 0; certmap_list[c] != NULL; c++) {
+        DEBUG(SSSDBG_TRACE_ALL, "Trying to add rule [%s][%d][%s][%s].\n",
+                                certmap_list[c]->name,
+                                certmap_list[c]->priority,
+                                certmap_list[c]->match_rule,
+                                certmap_list[c]->map_rule);
+
+        ret = sss_certmap_add_rule(id_ctx->sss_certmap_ctx,
+                                   certmap_list[c]->priority,
+                                   certmap_list[c]->match_rule,
+                                   certmap_list[c]->map_rule,
+                                   certmap_list[c]->domains);
+        if (ret != 0) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "sss_certmap_add_rule failed for rule [%s] "
+                  "with error [%d][%s], skipping. "
+                  "Please check for typos and if rule syntax is supported.\n",
+                  certmap_list[c]->name, ret, sss_strerror(ret));
+            continue;
+        }
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(certmap_list);
+
+    return ret;
+}
+
+errno_t files_map_cert_to_user(struct files_id_ctx *id_ctx,
+                               struct dp_id_data *data)
+{
+    errno_t ret;
+    char *filter;
+    char *user;
+    struct ldb_message *msg = NULL;
+    struct sysdb_attrs *attrs = NULL;
+    TALLOC_CTX *tmp_ctx;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+        return ENOMEM;
+    }
+
+    ret = sss_cert_derb64_to_ldap_filter(tmp_ctx, data->filter_value, "",
+                                         id_ctx->sss_certmap_ctx,
+                                         id_ctx->domain, &filter);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "sss_cert_derb64_to_ldap_filter failed.\n");
+        goto done;
+    }
+    if (filter == NULL || filter[0] != '('
+                       || filter[strlen(filter) - 1] != ')') {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "sss_cert_derb64_to_ldap_filter returned bad filter [%s].\n",
+              filter);
+        ret = EINVAL;
+        goto done;
+    }
+
+    filter[strlen(filter) - 1] = '\0';
+    user = sss_create_internal_fqname(tmp_ctx, &filter[1],
+                                      id_ctx->domain->name);
+    if (user == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+    DEBUG(SSSDBG_TRACE_ALL, "Certificate mapped to user: [%s].\n", user);
+
+    ret = sysdb_search_user_by_name(tmp_ctx, id_ctx->domain, user, NULL, &msg);
+    if (ret == EOK) {
+        attrs = sysdb_new_attrs(tmp_ctx);
+        if (attrs == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n");
+            ret = ENOMEM;
+            goto done;
+        }
+
+        ret = sysdb_attrs_add_base64_blob(attrs, SYSDB_USER_MAPPED_CERT,
+                                          data->filter_value);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_base64_blob failed.\n");
+            goto done;
+        }
+
+        ret = sysdb_set_entry_attr(id_ctx->domain->sysdb, msg->dn, attrs,
+                                   SYSDB_MOD_ADD);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed.\n");
+            goto done;
+        }
+    } else if (ret == ENOENT) {
+        DEBUG(SSSDBG_TRACE_ALL, "Mapped user [%s] not found.\n", user);
+        ret = EOK;
+        goto done;
+    } else {
+        DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n");
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
diff --git a/src/providers/files/files_id.c b/src/providers/files/files_id.c
index 41314c66b..f6f8c7311 100644
--- a/src/providers/files/files_id.c
+++ b/src/providers/files/files_id.c
@@ -87,6 +87,26 @@ files_account_info_handler_send(TALLOC_CTX *mem_ctx,
                        ? true \
                        : false;
         break;
+    case BE_REQ_BY_CERT:
+        if (data->filter_type != BE_FILTER_CERT) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Unexpected filter type for lookup by cert: %d\n",
+                  data->filter_type);
+            ret = EINVAL;
+            goto immediate;
+        }
+        if (id_ctx->sss_certmap_ctx == NULL) {
+            DEBUG(SSSDBG_TRACE_ALL, "Certificate mapping not configured.\n");
+            ret = EOK;
+            goto immediate;
+        }
+
+        ret = files_map_cert_to_user(id_ctx, data);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "files_map_cert_to_user failed");
+        }
+        goto immediate;
+        break;
     default:
         DEBUG(SSSDBG_CRIT_FAILURE,
               "Unexpected entry type: %d\n", data->entry_type & BE_REQ_TYPE_MASK);
diff --git a/src/providers/files/files_init.c b/src/providers/files/files_init.c
index 746c04af1..1ce4bcf27 100644
--- a/src/providers/files/files_init.c
+++ b/src/providers/files/files_init.c
@@ -189,6 +189,23 @@ int sssm_files_init(TALLOC_CTX *mem_ctx,
         goto done;
     }
 
+    ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to initialize certificate mapping rules. "
+              "Authentication with certificates/Smartcards might not work "
+              "as expected.\n");
+        /* not fatal, ignored */
+    } else {
+        ret = files_init_certmap(ctx, ctx);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "files_init_certmap failed. "
+                  "Authentication with certificates/Smartcards might not work "
+                  "as expected.\n");
+            /* not fatal, ignored */
+        }
+    }
+
     *_module_data = ctx;
     ret = EOK;
 done:
@@ -224,3 +241,15 @@ int sssm_files_id_init(TALLOC_CTX *mem_ctx,
 
     return EOK;
 }
+
+int sssm_files_auth_init(TALLOC_CTX *mem_ctx,
+                         struct be_ctx *be_ctx,
+                         void *module_data,
+                         struct dp_method *dp_methods)
+{
+    dp_set_method(dp_methods, DPM_AUTH_HANDLER,
+                  files_auth_handler_send, files_auth_handler_recv, NULL, void,
+                  struct pam_data, struct pam_data *);
+
+    return EOK;
+}
diff --git a/src/providers/files/files_private.h b/src/providers/files/files_private.h
index f44e6d458..fd1781930 100644
--- a/src/providers/files/files_private.h
+++ b/src/providers/files/files_private.h
@@ -38,6 +38,7 @@ struct files_id_ctx {
     struct be_ctx *be;
     struct sss_domain_info *domain;
     struct files_ctx *fctx;
+    struct sss_certmap_ctx *sss_certmap_ctx;
 
     const char **passwd_files;
     const char **group_files;
@@ -71,4 +72,20 @@ errno_t files_account_info_handler_recv(TALLOC_CTX *mem_ctx,
 void files_account_info_finished(struct files_id_ctx *id_ctx,
                                  int req_type,
                                  errno_t ret);
+
+/* files_auth.c */
+struct tevent_req *files_auth_handler_send(TALLOC_CTX *mem_ctx,
+                                           void *unused,
+                                           struct pam_data *pd,
+                                           struct dp_req_params *params);
+
+errno_t files_auth_handler_recv(TALLOC_CTX *mem_ctx,
+                                struct tevent_req *req,
+                                struct pam_data **_data);
+
+/* files_certmap.c */
+errno_t files_init_certmap(TALLOC_CTX *mem_ctx, struct files_id_ctx *id_ctx);
+
+errno_t files_map_cert_to_user(struct files_id_ctx *id_ctx,
+                               struct dp_id_data *data);
 #endif /* __FILES_PRIVATE_H_ */
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
index 3714d5d79..7998cc8ff 100644
--- a/src/providers/ldap/ldap_init.c
+++ b/src/providers/ldap/ldap_init.c
@@ -439,6 +439,22 @@ static errno_t ldap_init_misc(struct be_ctx *be_ctx,
               "[%d]: %s\n", ret, sss_strerror(ret));
     }
 
+    ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to initialize certificate mapping rules. "
+              "Authentication with certificates/Smartcards might not work "
+              "as expected.\n");
+        /* not fatal, ignored */
+    }
+
+    ret = sdap_init_certmap(id_ctx, id_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to initialized certificate mapping.\n");
+        return ret;
+    }
+
     return EOK;
 }
 
diff --git a/src/responder/common/responder_dp.c b/src/responder/common/responder_dp.c
index 208c415ac..a49b31528 100644
--- a/src/responder/common/responder_dp.c
+++ b/src/responder/common/responder_dp.c
@@ -598,15 +598,17 @@ static int sss_dp_account_files_params(struct sss_domain_info *dom,
                                        enum sss_dp_acct_type *_type_out,
                                        const char **_opt_name_out)
 {
-    if (sss_domain_get_state(dom) != DOM_INCONSISTENT) {
+    if (type_in != SSS_DP_CERT) {
+        if (sss_domain_get_state(dom) != DOM_INCONSISTENT) {
+            DEBUG(SSSDBG_TRACE_INTERNAL,
+                  "The entries in the files domain are up-to-date\n");
+            return EOK;
+        }
+
         DEBUG(SSSDBG_TRACE_INTERNAL,
-              "The entries in the files domain are up-to-date\n");
-        return EOK;
+              "Domain files is not consistent, issuing update\n");
     }
 
-    DEBUG(SSSDBG_TRACE_INTERNAL,
-          "Domain files is not consistent, issuing update\n");
-
     switch(type_in) {
     case SSS_DP_USER:
     case SSS_DP_GROUP:
@@ -620,12 +622,16 @@ static int sss_dp_account_files_params(struct sss_domain_info *dom,
         *_type_out = type_in;
         *_opt_name_out = DP_REQ_OPT_FILES_INITGR;
         return EAGAIN;
+    case SSS_DP_CERT:
+        /* Let the backend handle certificate mapping for local users */
+        *_type_out = type_in;
+        *_opt_name_out = opt_name_in;
+        return EAGAIN;
     /* These are not handled by the files provider, just fall back */
     case SSS_DP_NETGR:
     case SSS_DP_SERVICES:
     case SSS_DP_SECID:
     case SSS_DP_USER_AND_GROUP:
-    case SSS_DP_CERT:
     case SSS_DP_WILDCARD_USER:
     case SSS_DP_WILDCARD_GROUP:
         return EOK;
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 319362a95..ccb2037b1 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -123,7 +123,7 @@ errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
 bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd);
 
 errno_t p11_refresh_certmap_ctx(struct pam_ctx *pctx,
-                                struct certmap_info **certmap_list);
+                                struct sss_domain_info *domains);
 
 errno_t
 pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain,
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 00302be75..139f4260e 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -1461,7 +1461,9 @@ static void pam_forwarder_cert_cb(struct tevent_req *req)
             if (pd->cmd == SSS_PAM_AUTHENTICATE) {
                 DEBUG(SSSDBG_CRIT_FAILURE,
                       "No certificate returned, authentication failed.\n");
-                ret = ENOENT;
+                preq->pd->pam_status = PAM_AUTH_ERR;
+                pam_reply(preq);
+                return;
             } else {
                 ret = pam_check_user_search(preq);
             }
@@ -1762,7 +1764,7 @@ static void pam_forwarder_cb(struct tevent_req *req)
         goto done;
     }
 
-    ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains->certmaps);
+    ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE,
               "p11_refresh_certmap_ctx failed, "
diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c
index c7e57be17..c58a8bedd 100644
--- a/src/responder/pam/pamsrv_p11.c
+++ b/src/responder/pam/pamsrv_p11.c
@@ -141,11 +141,14 @@ static void ext_debug(void *private, const char *file, long line,
 }
 
 errno_t p11_refresh_certmap_ctx(struct pam_ctx *pctx,
-                                struct certmap_info **certmap_list)
+                                struct sss_domain_info *domains)
 {
     int ret;
     struct sss_certmap_ctx *sss_certmap_ctx = NULL;
     size_t c;
+    struct sss_domain_info *dom;
+    bool certmap_found = false;
+    struct certmap_info **certmap_list;
 
     ret = sss_certmap_init(pctx, ext_debug, NULL, &sss_certmap_ctx);
     if (ret != EOK) {
@@ -153,7 +156,15 @@ errno_t p11_refresh_certmap_ctx(struct pam_ctx *pctx,
         goto done;
     }
 
-    if (certmap_list == NULL || *certmap_list == NULL) {
+    DLIST_FOR_EACH(dom, domains) {
+        certmap_list = dom->certmaps;
+        if (certmap_list != NULL && *certmap_list != NULL) {
+            certmap_found = true;
+            break;
+        }
+    }
+
+    if (!certmap_found) {
         /* Try to add default matching rule */
         ret = sss_certmap_add_rule(sss_certmap_ctx, SSS_CERTMAP_MIN_PRIO,
                                    CERT_AUTH_DEFAULT_MATCHING_RULE, NULL, NULL);
@@ -165,24 +176,32 @@ errno_t p11_refresh_certmap_ctx(struct pam_ctx *pctx,
         goto done;
     }
 
-    for (c = 0; certmap_list[c] != NULL; c++) {
-        DEBUG(SSSDBG_TRACE_ALL,
-              "Trying to add rule [%s][%d][%s][%s].\n",
-              certmap_list[c]->name, certmap_list[c]->priority,
-              certmap_list[c]->match_rule, certmap_list[c]->map_rule);
-
-        ret = sss_certmap_add_rule(sss_certmap_ctx, certmap_list[c]->priority,
-                                   certmap_list[c]->match_rule,
-                                   certmap_list[c]->map_rule,
-                                   certmap_list[c]->domains);
-        if (ret != 0) {
-            DEBUG(SSSDBG_CRIT_FAILURE,
-                  "sss_certmap_add_rule failed for rule [%s] "
-                  "with error [%d][%s], skipping. "
-                  "Please check for typos and if rule syntax is supported.\n",
-                  certmap_list[c]->name, ret, sss_strerror(ret));
+    DLIST_FOR_EACH(dom, domains) {
+        certmap_list = dom->certmaps;
+        if (certmap_list == NULL || *certmap_list == NULL) {
             continue;
         }
+
+        for (c = 0; certmap_list[c] != NULL; c++) {
+            DEBUG(SSSDBG_TRACE_ALL,
+                  "Trying to add rule [%s][%d][%s][%s].\n",
+                  certmap_list[c]->name, certmap_list[c]->priority,
+                  certmap_list[c]->match_rule, certmap_list[c]->map_rule);
+
+            ret = sss_certmap_add_rule(sss_certmap_ctx,
+                                       certmap_list[c]->priority,
+                                       certmap_list[c]->match_rule,
+                                       certmap_list[c]->map_rule,
+                                       certmap_list[c]->domains);
+            if (ret != 0) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "sss_certmap_add_rule failed for rule [%s] "
+                      "with error [%d][%s], skipping. "
+                      "Please check for typos and if rule syntax is supported.\n",
+                      certmap_list[c]->name, ret, sss_strerror(ret));
+                continue;
+            }
+        }
     }
 
     ret = EOK;
@@ -203,19 +222,21 @@ errno_t p11_child_init(struct pam_ctx *pctx)
     int ret;
     struct certmap_info **certmaps;
     bool user_name_hint;
-    struct sss_domain_info *dom = pctx->rctx->domains;
+    struct sss_domain_info *dom;
 
-    ret = sysdb_get_certmap(dom, dom->sysdb, &certmaps, &user_name_hint);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n");
-        return ret;
-    }
+    DLIST_FOR_EACH(dom, pctx->rctx->domains) {
+        ret = sysdb_get_certmap(dom, dom->sysdb, &certmaps, &user_name_hint);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n");
+            return ret;
+        }
 
-    dom->user_name_hint = user_name_hint;
-    talloc_free(dom->certmaps);
-    dom->certmaps = certmaps;
+        dom->user_name_hint = user_name_hint;
+        talloc_free(dom->certmaps);
+        dom->certmaps = certmaps;
+    }
 
-    ret = p11_refresh_certmap_ctx(pctx, dom->certmaps);
+    ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "p11_refresh_certmap_ctx failed.\n");
         return ret;
diff --git a/src/tests/cmocka/test_certmap.c b/src/tests/cmocka/test_certmap.c
index 3091e1abe..c882202a0 100644
--- a/src/tests/cmocka/test_certmap.c
+++ b/src/tests/cmocka/test_certmap.c
@@ -46,8 +46,10 @@
 
 #ifdef HAVE_TEST_CA
 #include "tests/test_CA/SSSD_test_cert_x509_0003.h"
+#include "tests/test_CA/SSSD_test_cert_x509_0004.h"
 #else
 #define SSSD_TEST_CERT_0003 ""
+#define SSSD_TEST_CERT_0004 ""
 #endif
 
 struct priv_sss_debug {
@@ -960,6 +962,48 @@ void test_sss_cert_get_content_test_cert_0003(void **state)
     talloc_free(content);
 }
 
+void test_sss_cert_get_content_test_cert_0004(void **state)
+{
+    int ret;
+    uint8_t *der;
+    size_t der_size;
+    struct sss_cert_content *content;
+
+    der = sss_base64_decode(NULL, SSSD_TEST_CERT_0004, &der_size);
+    assert_non_null(der);
+
+    ret = sss_cert_get_content(NULL, der, der_size, &content);
+    assert_int_equal(ret, 0);
+    assert_non_null(content);
+    assert_non_null(content->issuer_str);
+    assert_string_equal(content->issuer_str,
+                        "CN=SSSD test CA,OU=SSSD test,O=SSSD");
+
+    assert_non_null(content->issuer_rdn_list);
+    assert_string_equal(content->issuer_rdn_list[0], "O=SSSD");
+    assert_string_equal(content->issuer_rdn_list[1], "OU=SSSD test");
+    assert_string_equal(content->issuer_rdn_list[2], "CN=SSSD test CA");
+    assert_null(content->issuer_rdn_list[3]);
+
+    assert_non_null(content->subject_str);
+    assert_string_equal(content->subject_str,
+                        "CN=SSSD test cert 0004,OU=SSSD test,O=SSSD");
+
+    assert_non_null(content->subject_rdn_list);
+    assert_string_equal(content->issuer_rdn_list[0], "O=SSSD");
+    assert_string_equal(content->issuer_rdn_list[1], "OU=SSSD test");
+    assert_string_equal(content->subject_rdn_list[2], "CN=SSSD test cert 0004");
+    assert_null(content->subject_rdn_list[3]);
+
+    assert_int_equal(content->key_usage, UINT32_MAX);
+
+    assert_non_null(content->extended_key_usage_oids);
+    assert_null(content->extended_key_usage_oids[0]);
+
+    assert_null(content->san_list);
+
+    talloc_free(content);
+}
 
 static void test_sss_certmap_match_cert(void **state)
 {
@@ -1572,6 +1616,7 @@ int main(int argc, const char *argv[])
         cmocka_unit_test(test_sss_cert_get_content_2),
 #ifdef HAVE_TEST_CA
         cmocka_unit_test(test_sss_cert_get_content_test_cert_0003),
+        cmocka_unit_test(test_sss_cert_get_content_test_cert_0004),
 #endif
         cmocka_unit_test(test_sss_certmap_match_cert),
         cmocka_unit_test(test_sss_certmap_add_mapping_rule),
diff --git a/src/tests/cmocka/test_config_check.c b/src/tests/cmocka/test_config_check.c
index a2958de63..f1fdbc8eb 100644
--- a/src/tests/cmocka/test_config_check.c
+++ b/src/tests/cmocka/test_config_check.c
@@ -213,6 +213,18 @@ void config_check_test_bad_subdom_option_name(void **state)
     config_check_test_common(cfg_str, 1, expected_errors);
 }
 
+void config_check_test_bad_certmap_option_name(void **state)
+{
+    char cfg_str[] = "[certmap/files/testuser]\n"
+                     "debug_level = 10\n";
+    const char *expected_errors[] = {
+        "[rule/allowed_certmap_options]: Attribute 'debug_level' is not "
+        "allowed in section 'certmap/files/testuser'. Check for typos.",
+    };
+
+    config_check_test_common(cfg_str, 1, expected_errors);
+}
+
 void config_check_test_good_sections(void **state)
 {
     char cfg_str[] = "[sssd]\n"
@@ -225,7 +237,8 @@ void config_check_test_good_sections(void **state)
                      "[secrets/users/1000]\n"
                      "[ssh]\n"
                      "[ifp]\n"
-                     "[pac]\n";
+                     "[pac]\n"
+                     "[certmap/files/testuser]\n";
     const char *expected_errors[] = { NULL };
 
     config_check_test_common(cfg_str, 0, expected_errors);
@@ -272,6 +285,7 @@ int main(int argc, const char *argv[])
         cmocka_unit_test(config_check_test_bad_ifp_option_name),
         cmocka_unit_test(config_check_test_bad_appdomain_option_name),
         cmocka_unit_test(config_check_test_bad_subdom_option_name),
+        cmocka_unit_test(config_check_test_bad_certmap_option_name),
         cmocka_unit_test(config_check_test_good_sections),
         cmocka_unit_test(config_check_test_inherit_from_in_normal_dom),
         cmocka_unit_test(config_check_test_inherit_from_in_app_dom),
diff --git a/src/tests/intg/test_pam_responder.py b/src/tests/intg/test_pam_responder.py
index 7e5828dde..e97bd409b 100644
--- a/src/tests/intg/test_pam_responder.py
+++ b/src/tests/intg/test_pam_responder.py
@@ -27,12 +27,9 @@ import signal
 import errno
 import subprocess
 import time
-import pytest
-
-import config
 import shutil
-from util import unindent
 
+import config
 import intg.ds_openldap
 
 import pytest
@@ -109,24 +106,35 @@ def format_basic_conf(ldap_conn):
     """).format(**locals())
 
 
-def format_pam_cert_auth_conf():
+USER1 = dict(name='user1', passwd='x', uid=10001, gid=20001,
+             gecos='User for tests',
+             dir='/home/user1',
+             shell='/bin/bash')
+
+
+def format_pam_cert_auth_conf(config):
     """Format a basic SSSD configuration"""
     return unindent("""\
         [sssd]
+        debug_level = 10
         domains = auth_only
-        services = pam
+        services = pam, nss
 
         [nss]
+        debug_level = 10
 
         [pam]
         pam_cert_auth = True
+        pam_p11_allowed_services = +pam_sss_service
+        pam_cert_db_path = {config.PAM_CERT_DB_PATH}
         debug_level = 10
 
         [domain/auth_only]
-        id_provider = ldap
-        auth_provider = ldap
-        chpass_provider = ldap
-        access_provider = ldap
+        debug_level = 10
+        id_provider = files
+
+        [certmap/auth_only/user1]
+        matchrule = <SUBJECT>.*CN=SSSD test cert 0001.*
     """).format(**locals())
 
 
@@ -193,12 +201,42 @@ def create_sssd_fixture(request):
     request.addfinalizer(cleanup_sssd_process)
 
 
+def create_nssdb():
+    os.mkdir(config.SYSCONFDIR + "/pki")
+    os.mkdir(config.SYSCONFDIR + "/pki/nssdb")
+    if subprocess.call(["certutil", "-N", "-d",
+                        "sql:" + config.SYSCONFDIR + "/pki/nssdb/",
+                        "--empty-password"]) != 0:
+        raise Exception("certutil failed")
+
+    pkcs11_txt = open(config.SYSCONFDIR + "/pki/nssdb/pkcs11.txt", "w")
+    pkcs11_txt.write("library=libsoftokn3.so\nname=soft\n" +
+                     "parameters=configdir='sql:" + config.ABS_BUILDDIR +
+                     "/../test_CA/p11_nssdb' " +
+                     "dbSlotDescription='SSSD Test Slot' " +
+                     "dbTokenDescription='SSSD Test Token' " +
+                     "secmod='secmod.db' flags=readOnly)\n\n")
+    pkcs11_txt.close()
+
+
+def cleanup_nssdb():
+    shutil.rmtree(config.SYSCONFDIR + "/pki")
+
+
+def create_nssdb_fixture(request):
+    create_nssdb()
+    request.addfinalizer(cleanup_nssdb)
+
+
 @pytest.fixture
-def simple_pam_cert_auth(request):
+def simple_pam_cert_auth(request, passwd_ops_setup):
     """Setup SSSD with pam_cert_auth=True"""
-    conf = format_pam_cert_auth_conf()
+    config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+    conf = format_pam_cert_auth_conf(config)
     create_conf_fixture(request, conf)
     create_sssd_fixture(request)
+    create_nssdb_fixture(request)
+    passwd_ops_setup.useradd(**USER1)
     return None
 
 
@@ -281,3 +319,50 @@ def env_for_sssctl(request):
     env_for_sssctl['LD_PRELOAD'] += ':' + os.environ['PAM_WRAPPER_PATH']
 
     return env_for_sssctl
+
+
+def test_sc_auth_wrong_pin(simple_pam_cert_auth, env_for_sssctl):
+
+    sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+                               "--action=auth", "--service=pam_sss_service"],
+                              universal_newlines=True,
+                              env=env_for_sssctl, stdin=subprocess.PIPE,
+                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+    try:
+        out, err = sssctl.communicate(input="111")
+    except:
+        sssctl.kill()
+        out, err = sssctl.communicate()
+
+    sssctl.stdin.close()
+    sssctl.stdout.close()
+
+    if sssctl.wait() != 0:
+        raise Exception("sssctl failed")
+
+    assert err.find("pam_authenticate for user [user1]: " +
+                    "Authentication failure") != -1
+
+
+def test_sc_auth(simple_pam_cert_auth, env_for_sssctl):
+
+    sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+                               "--action=auth", "--service=pam_sss_service"],
+                              universal_newlines=True,
+                              env=env_for_sssctl, stdin=subprocess.PIPE,
+                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+    try:
+        out, err = sssctl.communicate(input="123456")
+    except:
+        sssctl.kill()
+        out, err = sssctl.communicate()
+
+    sssctl.stdin.close()
+    sssctl.stdout.close()
+
+    if sssctl.wait() != 0:
+        raise Exception("sssctl failed")
+
+    assert err.find("pam_authenticate for user [user1]: Success") != -1
diff --git a/src/tests/test_CA/Makefile.am b/src/tests/test_CA/Makefile.am
index 0c7099390..19772d150 100644
--- a/src/tests/test_CA/Makefile.am
+++ b/src/tests/test_CA/Makefile.am
@@ -4,9 +4,11 @@ dist_noinst_DATA = \
     SSSD_test_cert_0001.config \
     SSSD_test_cert_0002.config \
     SSSD_test_cert_0003.config \
+    SSSD_test_cert_0004.config \
     SSSD_test_cert_key_0001.pem \
     SSSD_test_cert_key_0002.pem \
     SSSD_test_cert_key_0003.pem \
+    SSSD_test_cert_key_0004.pem \
     $(NULL)
 
 openssl_ca_config = $(srcdir)/SSSD_test_CA.config
@@ -33,14 +35,18 @@ endif
 ca_all: clean serial SSSD_test_CA.pem $(certs) $(certs_h) $(pubkeys) $(pubkeys_h) $(pkcs12) $(extra)
 
 $(pwdfile):
-	@echo "12345678" > $@
+	@echo "123456" > $@
 
 SSSD_test_CA.pem: $(openssl_ca_key) $(openssl_ca_config) serial
 	$(OPENSSL) req -batch -config ${openssl_ca_config} -x509 -new -nodes -key $< -sha256 -days 1024 -set_serial 0 -extensions v3_ca -out $@
 
 
 SSSD_test_cert_req_%.pem: $(srcdir)/SSSD_test_cert_key_%.pem $(srcdir)/SSSD_test_cert_%.config
-	$(OPENSSL) req -new -nodes -key $< -reqexts req_exts -config $(srcdir)/SSSD_test_cert_$*.config -out $@
+	if [ $(shell grep -c req_exts $(srcdir)/SSSD_test_cert_$*.config) -eq 0 ]; then \
+		$(OPENSSL) req -new -nodes -key $< -config $(srcdir)/SSSD_test_cert_$*.config -out $@ ; \
+	else \
+		$(OPENSSL) req -new -nodes -key $< -reqexts req_exts -config $(srcdir)/SSSD_test_cert_$*.config -out $@ ; \
+	fi
 
 SSSD_test_cert_x509_%.pem: SSSD_test_cert_req_%.pem $(openssl_ca_config) SSSD_test_CA.pem
 	$(OPENSSL) ca -config ${openssl_ca_config} -batch -notext -keyfile $(openssl_ca_key) -in $< -days 200 -extensions usr_cert -out $@
@@ -65,18 +71,18 @@ SSSD_test_cert_pubsshkey_%.h: SSSD_test_cert_pubsshkey_%.pub
 # - src/tests/cmocka/test_pam_srv.c
 p11_nssdb: SSSD_test_cert_pkcs12_0001.pem SSSD_test_CA.pem $(pwdfile)
 	mkdir $@
-	$(CERTUTIL) -d sql:./$@ -N --empty-password
-	$(CERTUTIL) -d sql:./$@ -A -n 'SSSD test CA' -t CT,CT,CT -a -i SSSD_test_CA.pem
-	$(PK12UTIL) -d sql:./$@ -i SSSD_test_cert_pkcs12_0001.pem -w $(pwdfile)
+	$(CERTUTIL) -d sql:./$@ -N -f $(pwdfile)
+	$(CERTUTIL) -d sql:./$@ -A -n 'SSSD test CA' -t CT,CT,CT -a -i SSSD_test_CA.pem -f $(pwdfile)
+	$(PK12UTIL) -d sql:./$@ -i SSSD_test_cert_pkcs12_0001.pem -w $(pwdfile) -k $(pwdfile)
 
 # This nss db is used in
 # - src/tests/cmocka/test_pam_srv.c
 p11_nssdb_2certs: SSSD_test_cert_pkcs12_0001.pem SSSD_test_cert_pkcs12_0002.pem SSSD_test_CA.pem $(pwdfile)
 	mkdir $@
-	$(CERTUTIL) -d sql:./$@ -N --empty-password
-	$(CERTUTIL) -d sql:./$@ -A -n 'SSSD test CA' -t CT,CT,CT -a -i SSSD_test_CA.pem
-	$(PK12UTIL) -d sql:./$@ p11_nssdb -i SSSD_test_cert_pkcs12_0001.pem -w $(pwdfile)
-	$(PK12UTIL) -d sql:./$@ p11_nssdb -i SSSD_test_cert_pkcs12_0002.pem -w $(pwdfile)
+	$(CERTUTIL) -d sql:./$@ -N -f $(pwdfile)
+	$(CERTUTIL) -d sql:./$@ -A -n 'SSSD test CA' -t CT,CT,CT -a -i SSSD_test_CA.pem -f $(pwdfile)
+	$(PK12UTIL) -d sql:./$@ -i SSSD_test_cert_pkcs12_0001.pem -w $(pwdfile) -k $(pwdfile)
+	$(PK12UTIL) -d sql:./$@ -i SSSD_test_cert_pkcs12_0002.pem -w $(pwdfile) -k $(pwdfile)
 
 # The softhsm2 PKCS#11 setups are used in
 # - src/tests/cmocka/test_pam_srv.c
diff --git a/src/tests/test_CA/SSSD_test_cert_0004.config b/src/tests/test_CA/SSSD_test_cert_0004.config
new file mode 100644
index 000000000..cb61b43ce
--- /dev/null
+++ b/src/tests/test_CA/SSSD_test_cert_0004.config
@@ -0,0 +1,11 @@
+# This certificate is used in
+# - test_sss_cert_get_content_test_cert_0004
+# as an example for a simple certificate without KU, EKU and SAN extensions
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+O = SSSD
+OU = SSSD test
+CN = SSSD test cert 0004
diff --git a/src/tests/test_CA/SSSD_test_cert_key_0004.pem b/src/tests/test_CA/SSSD_test_cert_key_0004.pem
new file mode 100644
index 000000000..e7e1b1de7
--- /dev/null
+++ b/src/tests/test_CA/SSSD_test_cert_key_0004.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCdue/OaH/8xyzm
+PUXFJeVJe0GOLZv3Pv/wPuIlNjAU1JNSDQUUKBlv7SOJr7KZ+4se6RyTU22G0KMN
+0qswY5hlpOtCbRlH2fp5zaYakDVbAv00UBvllPuLetQA9hjCxvz3DZLfLC/N954N
+ZKJIrO2fqTDvJwKqhw7gvp4p1vZcpAcsDf/AYzPgw1oX3yZyzQhfQwQ5Gu3U0Fwc
+nQL/l++mDFD2faDBfn9CFu7hPHKMQvjITsK/RwuFqZa1BzU80x0iyRMhYyDuUDWD
+2tDgzgt4qlMIHJb0ACHuSZFNIiqCF2xkM63PP3is2w7DUpRSu26FR4JacoKq7v5g
+yhtw9y9HAgMBAAECggEAVpNKKy03G4QkhBib5HRBoAz01dr5IkTFbZTGwxA0Yiqw
+1rfo0sCT/djXyerUCSuGmKfyFHgVxYteBOdfKgdxDlHxBJwn5UWj9BnKlAgWEWfZ
+nk5eka0uSchY+FIdE0Twc5dSyAdUEiVZ7xYO8f9hy2KuRodOMlZB92EKJgMlZYGS
+/hYOfZYmz3c4LFWO+UEiXyKKjENtnp5CpOw+Vcrwlu77PbFiT4Y12dOOwDRP4a/a
+ddXQBUaApOMDBA6gpB4jaysq5EBnrLTL5fzHNpATVKFnAL7icPuIfefjU2kxQMoo
+siUL7RzZnLlx0mN37DIzTv4uGltvGzzqhkIA5X/TsQKBgQDNBt3+dih7YbDt/4cC
+HtuApUAbwYDYhETzU8Zt+WRiFZdOgBcm/bacxOqBWGJ1WCYnegPYmk4WVThH1zrF
+Pr2EN2sOn2KJzQlLIOR7hvtTXgLx1hqc9XVBq/8JKhvCPfk/RoCjt+GmkoLHdrWI
+w1kd2milRcFs09UCV8LGa/vYSQKBgQDE8JUXD8x77IP/CBXLBmFq6tSwYkRWh4jC
+l8HB75VWXrknzgjv4Iqx4FEm2T0Mp0QFZLF6WCoclUcsGiCTz7jm20eoRL+5dX1d
+yASi00GSpS1p2Q9eTTU4FHVg1nD1B5F1kQB8uqBj0oSjeQLLPngaIftxTGNrkw3J
+4mk5kVdrDwKBgF0iA3F1pwn05HQYIPHbpoYXirmQ+sBfxRprMbX/FZRgjmzATsQN
+eAhagtPinEcFlb9U865O2a3XZEtt/2peB6Spr93ilNZX5yLTfDaIqF3EVL4aLdii
+v3LneGBnWliv4irWEdVM0Bnkb7e/utK3OiIPdn2s5CJVT2tTBk0v/CTRAoGAIe04
+IeLs3SRfkN25s2IEAkE2JrSnBSkQHEW8cUZuuZRT3VGXJIvQGNiF4mVmKPnfs/Ym
+xObPSmFFA4n0tsIAHnUEIS7GwJJG6JL+iXZPQ44FBskH5rzyQBj2J5qJlwyYuGIk
+bVhRLSElDGxaWN0IH6hfAqOgNPX+WBsS+YHaR20CgYAVUwTRA9kQgPZJfg+mepFG
+zw9Tx7/TSwILZDlL0AU/i12xn0RA7sweLW8cPEDx1OnTbv+/pqSZ46eeZDzTrlu7
+ASy844law96NdhpKuTyz/jEl6aj0RLp1wzQZLSQkV0nv3f2Qlknhz83uShhxmxJv
+FqS4fShRFJNoQDwEUvE7ZA==
+-----END PRIVATE KEY-----
-- 
2.21.3