summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorSumit Bose <sbose@redhat.com>2017-02-02 11:24:02 +0100
committerJakub Hrozek <jhrozek@redhat.com>2017-03-23 17:18:35 +0100
commitdb36dca3d45e6eefbb30042ee65876566f1a6014 (patch)
tree49d8aa07be577934fe557c091d45feb04d1c6e39 /src/lib
parent8b7548f65a0d812a47d26895671ec6f01b6813c1 (diff)
downloadsssd-db36dca3d45e6eefbb30042ee65876566f1a6014.tar.gz
sssd-db36dca3d45e6eefbb30042ee65876566f1a6014.tar.xz
sssd-db36dca3d45e6eefbb30042ee65876566f1a6014.zip
certmap: add new library libsss_certmap
With this library it would be possible to map certificates and users not only by adding the full certificate to the user's LDAP object but by adding e.g. only parts like the issuer and subject name. Additionally the library is also able to flexible select/match certificates based on values in the certificate. Details about mapping and matching rules can be found in the included man page. Related to https://pagure.io/SSSD/sssd/issue/3050 Reviewed-by: Jakub Hrozek <jhrozek@redhat.com> Reviewed-by: Lukáš Slebodník <lslebodn@redhat.com>
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/certmap/sss_cert_content_nss.c1014
-rw-r--r--src/lib/certmap/sss_certmap.c993
-rw-r--r--src/lib/certmap/sss_certmap.doxy.in3
-rw-r--r--src/lib/certmap/sss_certmap.exports13
-rw-r--r--src/lib/certmap/sss_certmap.h155
-rw-r--r--src/lib/certmap/sss_certmap.pc.in11
-rw-r--r--src/lib/certmap/sss_certmap_attr_names.c107
-rw-r--r--src/lib/certmap/sss_certmap_int.h187
-rw-r--r--src/lib/certmap/sss_certmap_krb5_match.c558
-rw-r--r--src/lib/certmap/sss_certmap_ldap_mapping.c367
10 files changed, 3408 insertions, 0 deletions
diff --git a/src/lib/certmap/sss_cert_content_nss.c b/src/lib/certmap/sss_cert_content_nss.c
new file mode 100644
index 000000000..d31828954
--- /dev/null
+++ b/src/lib/certmap/sss_cert_content_nss.c
@@ -0,0 +1,1014 @@
+/*
+ SSSD - certificate handling utils - NSS version
+ The calls defined here should be useable outside of SSSD as well, e.g. in
+ libsss_certmap.
+
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2017
+
+ 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 "config.h"
+
+#include <nss.h>
+#include <cert.h>
+#include <base64.h>
+#include <prerror.h>
+#include <secport.h>
+#include <secerr.h>
+#include <prprf.h>
+#include <prnetdb.h>
+#include <talloc.h>
+
+#include "util/crypto/sss_crypto.h"
+#include "util/crypto/nss/nss_util.h"
+#include "util/cert.h"
+#include "lib/certmap/sss_certmap.h"
+#include "lib/certmap/sss_certmap_int.h"
+
+
+/* The following two functions are copied from NSS's lib/certdb/secname.c
+ * becasue CERT_AddAVA is not exported. I just renamed it and made it static
+ * to avoid issues if the call gets exported some time in future. */
+
+static void **
+AddToArray(PLArenaPool *arena, void **array, void *element)
+{
+ unsigned count;
+ void **ap;
+
+ /* Count up number of slots already in use in the array */
+ count = 0;
+ ap = array;
+ if (ap) {
+ while (*ap++) {
+ count++;
+ }
+ }
+
+ if (array) {
+ array = (void**) PORT_ArenaGrow(arena, array,
+ (count + 1) * sizeof(void *),
+ (count + 2) * sizeof(void *));
+ } else {
+ array = (void**) PORT_ArenaAlloc(arena, (count + 2) * sizeof(void *));
+ }
+ if (array) {
+ array[count] = element;
+ array[count+1] = 0;
+ }
+ return array;
+}
+
+
+static SECStatus
+sss_CERT_AddAVA(PLArenaPool *arena, CERTRDN *rdn, CERTAVA *ava)
+{
+ rdn->avas = (CERTAVA**) AddToArray(arena, (void**) rdn->avas, ava);
+ return rdn->avas ? SECSuccess : SECFailure;
+}
+
+static SECItem *
+cert_get_ext_by_tag(CERTCertificate *cert, SECOidTag tag)
+{
+ SECOidData *oid;
+ int i;
+
+ oid = SECOID_FindOIDByTag(tag);
+ for (i = 0;
+ (cert->extensions != NULL) && (cert->extensions[i] != NULL);
+ i++)
+ if (SECITEM_ItemsAreEqual(&cert->extensions[i]->id, &oid->oid))
+ return &cert->extensions[i]->value;
+ return NULL;
+}
+
+static int get_extended_key_usage_oids(TALLOC_CTX *mem_ctx,
+ CERTCertificate *cert,
+ const char ***_oids)
+{
+ PLArenaPool *pool;
+ SECItem *ext;
+ SECItem **oids = NULL;
+ const char **oids_list = NULL;
+ size_t c;
+ SECStatus rv;
+ char *tmp_str;
+ int ret;
+
+ pool = PORT_NewArena(sizeof(double));
+ ext = cert_get_ext_by_tag(cert, SEC_OID_X509_EXT_KEY_USAGE);
+ if (ext != NULL) {
+ rv = SEC_ASN1DecodeItem(pool, &oids,
+ SEC_ASN1_GET(SEC_SequenceOfObjectIDTemplate),
+ ext);
+ if (rv != SECSuccess) {
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ for (c = 0; (oids != NULL && oids[c] != NULL); c++);
+ oids_list = talloc_zero_array(mem_ctx, const char *, c + 1);
+ if (oids_list == NULL) {
+ return ENOMEM;
+ }
+
+ for (c = 0; (oids != NULL && oids[c] != NULL); c++) {
+ tmp_str = CERT_GetOidString(oids[c]);
+ /* is it expexted that NSS OID strings start with "OID." but we
+ * prefer the plain dotted-decimal version so the prefix is skipped */
+ if (tmp_str == NULL || strncmp(tmp_str, "OID.", 4) != 0) {
+ PR_smprintf_free(tmp_str);
+ ret = EINVAL;
+ goto done;
+ }
+
+ oids_list[c] = talloc_strdup(oids_list, tmp_str + 4);
+ PR_smprintf_free(tmp_str);
+ if(oids_list[c] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ ret = 0;
+
+done:
+ PORT_FreeArena(pool, PR_TRUE);
+ if (ret == 0) {
+ *_oids = oids_list;
+ } else {
+ talloc_free(oids_list);
+ }
+
+ return ret;
+
+}
+
+static int get_rdn_str(TALLOC_CTX *mem_ctx, CERTAVA **avas,
+ const char **rdn_str)
+{
+ size_t c;
+ char *tmp_name = NULL;
+ const char *tmp_str = NULL;
+ int ret;
+ SECStatus rv;
+ CERTRDN rdn = { 0 };
+ CERTName *name = NULL;
+ PLArenaPool *arena = NULL;
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (arena == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+
+ /* Multiple AVAs should be avoided becasue there is no general ordering
+ * rule and the RDN strings are not reproducible */
+ for (c = 0; avas[c] != NULL; c++) {
+ rv = sss_CERT_AddAVA(arena, &rdn, avas[c]);
+ if (rv != SECSuccess) {
+ ret = EIO;
+ goto done;
+ }
+ }
+
+ name = CERT_CreateName(&rdn, NULL);
+ if (name == NULL) {
+ ret = EIO;
+ goto done;
+ }
+
+ tmp_name = CERT_NameToAscii(name);
+ CERT_DestroyName(name);
+ if (tmp_name == NULL) {
+ ret = EIO;
+ goto done;
+ }
+
+ tmp_str = talloc_strdup(mem_ctx, tmp_name);
+ PORT_Free(tmp_name);
+ if (tmp_str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *rdn_str = tmp_str;
+ } else {
+ talloc_free(discard_const(tmp_str));
+ }
+ PORT_FreeArena(arena, PR_FALSE);
+
+ return ret;
+}
+
+static int get_rdn_list(TALLOC_CTX *mem_ctx, CERTRDN **rdns,
+ const char ***rdn_list)
+{
+ int ret;
+ size_t c;
+ const char **list = NULL;
+
+ for (c = 0; rdns[c] != NULL; c++);
+ list = talloc_zero_array(mem_ctx, const char *, c + 1);
+ if (list == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ for (c = 0; rdns[c] != NULL; c++) {
+ ret = get_rdn_str(list, rdns[c]->avas,
+ &(list[c]));
+ if (ret != 0) {
+ goto done;
+ }
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *rdn_list = list;
+ } else {
+ talloc_free(list);
+ }
+
+ return ret;
+}
+
+enum san_opt nss_name_type_to_san_opt(CERTGeneralNameType type)
+{
+ switch (type) {
+ case certOtherName:
+ return SAN_OTHER_NAME;
+ case certRFC822Name:
+ return SAN_RFC822_NAME;
+ case certDNSName:
+ return SAN_DNS_NAME;
+ case certX400Address:
+ return SAN_X400_ADDRESS;
+ case certDirectoryName:
+ return SAN_DIRECTORY_NAME;
+ case certEDIPartyName:
+ return SAN_EDIPART_NAME;
+ case certURI:
+ return SAN_URI;
+ case certIPAddress:
+ return SAN_IP_ADDRESS;
+ case certRegisterID:
+ return SAN_REGISTERED_ID;
+ default:
+ return SAN_INVALID;
+ }
+}
+
+static int add_to_san_list(TALLOC_CTX *mem_ctx, bool is_bin,
+ enum san_opt san_opt, uint8_t *data, size_t len,
+ struct san_list **item)
+{
+ struct san_list *i;
+
+ if (data == NULL || len == 0 || san_opt == SAN_INVALID) {
+ return EINVAL;
+ }
+
+ i = talloc_zero(mem_ctx, struct san_list);
+ if (i == NULL) {
+ return ENOMEM;
+ }
+
+ i->san_opt = san_opt;
+ if (is_bin) {
+ i->bin_val = talloc_memdup(i, data, len);
+ i->bin_val_len = len;
+ } else {
+ i->val = talloc_strndup(i, (char *) data, len);
+ }
+ if (i->val == NULL) {
+ talloc_free(i);
+ return ENOMEM;
+ }
+
+ *item = i;
+
+ return 0;
+}
+
+/* taken from pkinit_crypto_nss.c of MIT Kerberos */
+/* KerberosString: RFC 4120, 5.2.1. */
+static const SEC_ASN1Template kerberos_string_template[] = {
+ {
+ SEC_ASN1_GENERAL_STRING,
+ 0,
+ NULL,
+ sizeof(SECItem),
+ }
+};
+
+/* Realm: RFC 4120, 5.2.2. */
+struct realm {
+ SECItem name;
+};
+static const SEC_ASN1Template realm_template[] = {
+ {
+ SEC_ASN1_GENERAL_STRING,
+ 0,
+ NULL,
+ sizeof(SECItem),
+ }
+};
+
+/* PrincipalName: RFC 4120, 5.2.2. */
+static const SEC_ASN1Template sequence_of_kerberos_string_template[] = {
+ {
+ SEC_ASN1_SEQUENCE_OF,
+ 0,
+ &kerberos_string_template,
+ 0,
+ }
+};
+
+struct principal_name {
+ SECItem name_type;
+ SECItem **name_string;
+};
+static const SEC_ASN1Template principal_name_template[] = {
+ {
+ SEC_ASN1_SEQUENCE,
+ 0,
+ NULL,
+ sizeof(struct principal_name),
+ },
+ {
+ SEC_ASN1_CONTEXT_SPECIFIC | 0 | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT,
+ offsetof(struct principal_name, name_type),
+ &SEC_IntegerTemplate,
+ sizeof(SECItem),
+ },
+ {
+ SEC_ASN1_CONTEXT_SPECIFIC | 1 | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT,
+ offsetof(struct principal_name, name_string),
+ sequence_of_kerberos_string_template,
+ sizeof(struct SECItem **),
+ },
+ {0, 0, NULL, 0},
+};
+
+/* KRB5PrincipalName: RFC 4556, 3.2.2. */
+struct kerberos_principal_name {
+ SECItem realm;
+ struct principal_name principal_name;
+};
+static const SEC_ASN1Template kerberos_principal_name_template[] = {
+ {
+ SEC_ASN1_SEQUENCE,
+ 0,
+ NULL,
+ sizeof(struct kerberos_principal_name),
+ },
+ {
+ SEC_ASN1_CONTEXT_SPECIFIC | 0 | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT,
+ offsetof(struct kerberos_principal_name, realm),
+ &realm_template,
+ sizeof(struct realm),
+ },
+ {
+ SEC_ASN1_CONTEXT_SPECIFIC | 1 | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT,
+ offsetof(struct kerberos_principal_name, principal_name),
+ &principal_name_template,
+ sizeof(struct principal_name),
+ },
+ {0, 0, NULL, 0}
+};
+
+#define PKINIT_OID "1.3.6.1.5.2.2"
+#define NT_PRINCIPAL_OID "1.3.6.1.4.1.311.20.2.3"
+
+static int add_string_other_name_to_san_list(TALLOC_CTX *mem_ctx,
+ enum san_opt san_opt,
+ CERTGeneralName *current,
+ struct san_list **item)
+{
+ struct san_list *i = NULL;
+ int ret;
+ char *tmp_str;
+
+ tmp_str = CERT_GetOidString(&(current->name.OthName.oid));
+ /* is it expexted that NSS OID strings start with "OID." but we
+ * prefer the plain dotted-decimal version so the prefix is skipped */
+ if (tmp_str == NULL || strncmp(tmp_str, "OID.", 4) != 0) {
+ PR_smprintf_free(tmp_str);
+ return EINVAL;
+ }
+
+ i = talloc_zero(mem_ctx, struct san_list);
+ if (i == NULL) {
+ PR_smprintf_free(tmp_str);
+ return ENOMEM;
+ }
+ i->san_opt = san_opt;
+
+ i->other_name_oid = talloc_strdup(i, tmp_str + 4);
+ PR_smprintf_free(tmp_str);
+ if (i->other_name_oid == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ i->bin_val = talloc_memdup(i, current->name.OthName.name.data,
+ current->name.OthName.name.len);
+ if (i->bin_val == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ i->bin_val_len = current->name.OthName.name.len;
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *item = i;
+ } else {
+ talloc_free(i);
+ }
+
+ return ret;
+}
+
+static int get_short_name(TALLOC_CTX *mem_ctx, const char *full_name,
+ char delim, char **short_name)
+{
+ char *at;
+ char *s;
+
+ if (full_name == NULL || delim == '\0' || short_name == NULL) {
+ return EINVAL;
+ }
+
+ at = strchr(full_name, delim);
+ if (at != NULL) {
+ s = talloc_strndup(mem_ctx, full_name, (at - full_name));
+ } else {
+ s = talloc_strdup(mem_ctx, full_name);
+ }
+ if (s == NULL) {
+ return ENOMEM;
+ }
+
+ *short_name = s;
+
+ return 0;
+}
+
+static int add_nt_princ_to_san_list(TALLOC_CTX *mem_ctx,
+ PLArenaPool *pool,
+ enum san_opt san_opt,
+ CERTGeneralName *current,
+ struct san_list **item)
+{
+ struct san_list *i = NULL;
+ SECStatus rv;
+ SECItem tmp_secitem = { 0 };
+ int ret;
+
+ rv = SEC_ASN1DecodeItem(pool, &tmp_secitem,
+ SEC_ASN1_GET(SEC_UTF8StringTemplate),
+ &(current->name.OthName.name));
+ if (rv != SECSuccess) {
+ return EINVAL;
+ }
+
+ i = talloc_zero(mem_ctx, struct san_list);
+ if (i == NULL) {
+ return ENOMEM;
+ }
+ i->san_opt = san_opt;
+
+ i->val = talloc_strndup(i, (char *) tmp_secitem.data,
+ tmp_secitem.len);
+ if (i->val == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = get_short_name(i, i->val, '@', &(i->short_name));
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *item = i;
+ } else {
+ talloc_free(i);
+ }
+
+ return ret;
+}
+
+static int add_pkinit_princ_to_san_list(TALLOC_CTX *mem_ctx,
+ PLArenaPool *pool,
+ enum san_opt san_opt,
+ CERTGeneralName *current,
+ struct san_list **item)
+{
+ struct san_list *i = NULL;
+ SECStatus rv;
+ struct kerberos_principal_name kname;
+ int ret;
+ size_t c;
+
+ rv = SEC_ASN1DecodeItem(pool, &kname,
+ kerberos_principal_name_template,
+ &(current->name.OthName.name));
+ if (rv != SECSuccess) {
+ return EINVAL;
+ }
+
+ i = talloc_zero(mem_ctx, struct san_list);
+ if (i == NULL) {
+ return ENOMEM;
+ }
+ i->san_opt = san_opt;
+
+ if (kname.principal_name.name_string != NULL) {
+ i->val = talloc_strdup(i, "");
+ if (i->val == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ for (c = 0; kname.principal_name.name_string[c] != NULL; c++) {
+ if (c > 0) {
+ i->val = talloc_strdup_append(i->val, "/");
+ if (i->val == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ i->val = talloc_strndup_append(i->val,
+ (char *) kname.principal_name.name_string[c]->data,
+ kname.principal_name.name_string[c]->len);
+ if (i->val == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ i->val = talloc_strndup_append(i->val,
+ (char *) kname.realm.data,
+ kname.realm.len);
+ if (i->val == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = get_short_name(i, i->val, '@', &(i->short_name));
+ if (ret != 0) {
+ goto done;
+ }
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *item = i;
+ } else {
+ talloc_free(i);
+ }
+
+ return ret;
+}
+
+static int add_oid_to_san_list(TALLOC_CTX *mem_ctx,
+ enum san_opt san_opt,
+ SECItem oid,
+ struct san_list **item)
+{
+ struct san_list *i = NULL;
+ char *tmp_str;
+
+ tmp_str = CERT_GetOidString(&oid);
+ /* is it expexted that NSS OID strings start with "OID." but we
+ * prefer the plain dotted-decimal version so the prefix is skipped */
+ if (tmp_str == NULL || strncmp(tmp_str, "OID.", 4) != 0) {
+ PR_smprintf_free(tmp_str);
+ return EINVAL;
+ }
+
+ i = talloc_zero(mem_ctx, struct san_list);
+ if (i == NULL) {
+ PR_smprintf_free(tmp_str);
+ return ENOMEM;
+ }
+ i->san_opt = san_opt;
+
+ i->val = talloc_strdup(i, tmp_str + 4);
+ PR_smprintf_free(tmp_str);
+ if (i->val == NULL) {
+ talloc_free(i);
+ return ENOMEM;
+ }
+
+ *item = i;
+ return 0;
+}
+
+static int add_rdn_list_to_san_list(TALLOC_CTX *mem_ctx,
+ enum san_opt san_opt,
+ CERTName name,
+ struct san_list **item)
+{
+ struct san_list *i = NULL;
+ int ret;
+
+ i = talloc_zero(mem_ctx, struct san_list);
+ if (i == NULL) {
+ return ENOMEM;
+ }
+ i->san_opt = san_opt;
+
+ ret = get_rdn_list(i, name.rdns, &(i->rdn_list));
+ if (ret != 0) {
+ talloc_free(i);
+ return ret;
+ }
+
+ *item = i;
+ return 0;
+}
+
+static int add_ip_to_san_list(TALLOC_CTX *mem_ctx, enum san_opt san_opt,
+ uint8_t *data, size_t len,
+ struct san_list **item)
+{
+ struct san_list *i;
+ PRStatus st;
+ PRNetAddr addr;
+ char addrBuf[80];
+
+ if (data == NULL || len == 0 || san_opt == SAN_INVALID) {
+ return EINVAL;
+ }
+
+ /* taken from secu_PrintIPAddress() */
+ memset(&addr, 0, sizeof addr);
+ if (len == 4) {
+ addr.inet.family = PR_AF_INET;
+ memcpy(&addr.inet.ip, data, len);
+ } else if (len == 16) {
+ addr.ipv6.family = PR_AF_INET6;
+ memcpy(addr.ipv6.ip.pr_s6_addr, data, len);
+ if (PR_IsNetAddrType(&addr, PR_IpAddrV4Mapped)) {
+ /* convert to IPv4. */
+ addr.inet.family = PR_AF_INET;
+ memcpy(&addr.inet.ip, &addr.ipv6.ip.pr_s6_addr[12], 4);
+ memset(&addr.inet.pad[0], 0, sizeof addr.inet.pad);
+ }
+ } else {
+ return EINVAL;
+ }
+
+ st = PR_NetAddrToString(&addr, addrBuf, sizeof addrBuf);
+ if (st != PR_SUCCESS) {
+ return EIO;
+ }
+
+ i = talloc_zero(mem_ctx, struct san_list);
+ if (i == NULL) {
+ return ENOMEM;
+ }
+
+ i->san_opt = san_opt;
+ i->val = talloc_strdup(i, addrBuf);
+ if (i->val == NULL) {
+ talloc_free(i);
+ return ENOMEM;
+ }
+
+ *item = i;
+ return 0;
+}
+static int add_principal_to_san_list(TALLOC_CTX *mem_ctx,
+ enum san_opt san_opt,
+ const char *princ,
+ struct san_list **item)
+{
+ struct san_list *i = NULL;
+ int ret;
+
+ i = talloc_zero(mem_ctx, struct san_list);
+ if (i == NULL) {
+ return ENOMEM;
+ }
+ i->san_opt = san_opt;
+
+ i->val = talloc_strdup(i, princ);
+ if (i->val == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = get_short_name(i, i->val, '@', &(i->short_name));
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *item = i;
+ } else {
+ talloc_free(i);
+ }
+
+ return ret;
+}
+
+static int get_san(TALLOC_CTX *mem_ctx, CERTCertificate *cert,
+ struct san_list **san_list)
+{
+
+ SECItem subAltName = { 0 };
+ SECStatus rv;
+ CERTGeneralName *name_list = NULL;
+ CERTGeneralName *current;
+ PLArenaPool *pool = NULL;
+ int ret;
+ struct san_list *list = NULL;
+ struct san_list *item = NULL;
+ struct san_list *item_s = NULL;
+ struct san_list *item_p = NULL;
+ struct san_list *item_pb = NULL;
+
+ rv = CERT_FindCertExtension(cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &subAltName);
+ if (rv != SECSuccess) {
+ if (rv == SECFailure
+ && PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND) {
+ ret = EOK;
+ } else {
+ ret = EIO;
+ }
+ goto done;
+ }
+
+ pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (pool == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ name_list = CERT_DecodeAltNameExtension(pool, &subAltName);
+ if (name_list == NULL ) {
+ ret = EIO;
+ goto done;
+ }
+
+ current = name_list;
+ do {
+ switch (current->type) {
+ case certOtherName:
+ ret = add_string_other_name_to_san_list(mem_ctx,
+ SAN_STRING_OTHER_NAME,
+ current, &item_s);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(list, item_s);
+
+ item_p = NULL;
+ if (strcmp(item_s->other_name_oid, NT_PRINCIPAL_OID) == 0) {
+ ret = add_nt_princ_to_san_list(mem_ctx, pool, SAN_NT, current,
+ &item_p);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(list, item_p);
+ } else if (strcmp(item_s->other_name_oid, PKINIT_OID) == 0) {
+ ret = add_pkinit_princ_to_san_list(mem_ctx, pool, SAN_PKINIT,
+ current, &item_p);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(list, item_p);
+ }
+
+ if (item_p != NULL) {
+ ret = add_principal_to_san_list(mem_ctx, SAN_PRINCIPAL,
+ item_p->val, &item_pb);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(list, item_pb);
+ }
+
+ break;
+ case certRFC822Name:
+ case certDNSName:
+ case certURI:
+ ret = add_to_san_list(mem_ctx, false,
+ nss_name_type_to_san_opt(current->type),
+ current->name.other.data,
+ current->name.other.len, &item);
+ if (ret != 0) {
+ goto done;
+ }
+
+ if (current->type == certRFC822Name
+ || current->type == certDNSName) {
+ ret = get_short_name(item, item->val,
+ (current->type == certRFC822Name
+ ? '@' : '.'),
+ &(item->short_name));
+ if (ret != 0) {
+ goto done;
+ }
+ }
+
+ DLIST_ADD(list, item);
+ break;
+ case certIPAddress:
+ ret = add_ip_to_san_list(mem_ctx,
+ nss_name_type_to_san_opt(current->type),
+ current->name.other.data,
+ current->name.other.len, &item);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(list, item);
+ break;
+ case certDirectoryName:
+ ret = add_rdn_list_to_san_list(mem_ctx,
+ nss_name_type_to_san_opt(current->type),
+ current->name.directoryName, &item);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(list, item);
+ break;
+ case certRegisterID:
+ ret = add_oid_to_san_list(mem_ctx,
+ nss_name_type_to_san_opt(current->type),
+ current->name.other, &item);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(list, item);
+ break;
+ case certX400Address:
+ case certEDIPartyName:
+ ret = add_to_san_list(mem_ctx, true,
+ nss_name_type_to_san_opt(current->type),
+ current->name.other.data,
+ current->name.other.len, &item);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(list, item);
+ break;
+ default:
+ ret = EINVAL;
+ }
+
+ current = CERT_GetNextGeneralName(current);
+ if (current == NULL) {
+ ret = EIO;
+ goto done;
+ }
+ } while (current != name_list);
+
+done:
+
+ /* Don't free nameList, it's part of the arena. */
+
+ if (pool != NULL) {
+ PORT_FreeArena(pool, PR_FALSE);
+ }
+
+ if (subAltName.data != NULL) {
+ SECITEM_FreeItem(&subAltName, PR_FALSE);
+ }
+
+ if (ret == EOK) {
+ *san_list = list;
+ }
+ return ret;
+}
+
+int sss_cert_get_content(TALLOC_CTX *mem_ctx,
+ const uint8_t *der_blob, size_t der_size,
+ struct sss_cert_content **content)
+{
+ int ret;
+ struct sss_cert_content *cont = NULL;
+ CERTCertDBHandle *handle;
+ CERTCertificate *cert = NULL;
+ SECItem der_item;
+ NSSInitContext *nss_ctx;
+
+ if (der_blob == NULL || der_size == 0) {
+ return EINVAL;
+ }
+
+ nss_ctx = NSS_InitContext("", "", "", "", NULL, NSS_INIT_READONLY
+ | NSS_INIT_NOCERTDB
+ | NSS_INIT_NOMODDB
+ | NSS_INIT_FORCEOPEN
+ | NSS_INIT_NOROOTINIT
+ | NSS_INIT_OPTIMIZESPACE);
+ if (nss_ctx == NULL) {
+ return EIO;
+ }
+
+ cont = talloc_zero(mem_ctx, struct sss_cert_content);
+ if (cont == NULL) {
+ return ENOMEM;
+ }
+
+ 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) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ cont->issuer_str = talloc_strdup(cont, cert->issuerName);
+ if (cont->issuer_str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = get_rdn_list(cont, cert->issuer.rdns, &cont->issuer_rdn_list);
+ if (ret != 0) {
+ goto done;
+ }
+
+ cont->subject_str = talloc_strdup(cont, cert->subjectName);
+ if (cont->subject_str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = get_rdn_list(cont, cert->subject.rdns, &cont->subject_rdn_list);
+ if (ret != 0) {
+ goto done;
+ }
+
+
+ cont->key_usage = cert->keyUsage;
+
+ ret = get_extended_key_usage_oids(cont, cert,
+ &(cont->extended_key_usage_oids));
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = get_san(cont, cert, &(cont->san_list));
+ if (ret != 0) {
+ goto done;
+ }
+
+ cont->cert_der = talloc_memdup(cont, der_blob, der_size);
+ if (cont->cert_der == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cont->cert_der_size = der_size;
+ ret = EOK;
+
+done:
+
+ CERT_DestroyCertificate(cert);
+ NSS_ShutdownContext(nss_ctx);
+
+ if (ret == EOK) {
+ *content = cont;
+ } else {
+ talloc_free(cont);
+ }
+
+ return ret;
+}
diff --git a/src/lib/certmap/sss_certmap.c b/src/lib/certmap/sss_certmap.c
new file mode 100644
index 000000000..080cab8ab
--- /dev/null
+++ b/src/lib/certmap/sss_certmap.c
@@ -0,0 +1,993 @@
+/*
+ SSSD
+
+ Library for rule based certificate to user mapping
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2017 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 "util/util.h"
+#include "util/cert.h"
+#include "util/crypto/sss_crypto.h"
+#include "lib/certmap/sss_certmap.h"
+#include "lib/certmap/sss_certmap_int.h"
+
+int debug_level;
+void sss_debug_fn(const char *file,
+ long line,
+ const char *function,
+ int level,
+ const char *format, ...)
+{
+ return;
+}
+
+static int get_type_prefix(TALLOC_CTX *mem_ctx, const char *match_rule,
+ char **type, const char **rule_start)
+{
+ const char *c;
+ char *delim;
+
+ *type = NULL;
+ *rule_start = match_rule;
+
+ delim = strchr(match_rule, ':');
+ if (delim == NULL) {
+ /* no type prefix found */
+ return 0;
+ }
+
+ /* rule starts with ':', empty type */
+ if (delim == match_rule) {
+ *rule_start = delim + 1;
+ return EOK;
+ }
+
+ for (c = match_rule; c < delim; c++) {
+ /* type prefix may only contain digits and upper-case ASCII characters */
+ if (!(isascii(*c) && (isdigit(*c) || isupper(*c)))) {
+ /* no type prefix found */
+ return 0;
+ }
+ }
+
+ *rule_start = delim + 1;
+ *type = talloc_strndup(mem_ctx, match_rule, (delim - match_rule));
+ if (*type == NULL) {
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+static int parse_match_rule(struct sss_certmap_ctx *ctx, const char *match_rule,
+ struct krb5_match_rule **parsed_match_rule)
+{
+ int ret;
+ char *type;
+ const char *rule_start;
+
+ ret = get_type_prefix(ctx, match_rule, &type, &rule_start);
+ if (ret != EOK) {
+ CM_DEBUG(ctx, "Failed to read rule type.");
+ goto done;
+ }
+
+ if (type == NULL || strcmp(type, "KRB5") == 0) {
+ ret = parse_krb5_match_rule(ctx, rule_start, parsed_match_rule);
+ if (ret != EOK) {
+ CM_DEBUG(ctx, "Failed to parse KRB5 matching rule.");
+ goto done;
+ }
+ } else {
+ CM_DEBUG(ctx, "Unsupported matching rule type.");
+ ret = ESRCH;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(type);
+
+ return ret;
+}
+
+static int parse_mapping_rule(struct sss_certmap_ctx *ctx,
+ const char *mapping_rule,
+ struct ldap_mapping_rule **parsed_mapping_rule)
+{
+ int ret;
+ char *type;
+ const char *rule_start;
+
+ ret = get_type_prefix(ctx, mapping_rule, &type, &rule_start);
+ if (ret != EOK) {
+ CM_DEBUG(ctx, "Failed to read rule type.");
+ goto done;
+ }
+
+ if (type == NULL || strcmp(type, "LDAP") == 0) {
+ ret = parse_ldap_mapping_rule(ctx, rule_start, parsed_mapping_rule);
+ if (ret != EOK) {
+ CM_DEBUG(ctx, "Failed to parse LDAP mapping rule.");
+ goto done;
+ }
+ } else {
+ CM_DEBUG(ctx, "Unsupported mapping rule type.");
+ ret = ESRCH;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(type);
+
+ return ret;
+}
+
+int sss_certmap_add_rule(struct sss_certmap_ctx *ctx,
+ uint32_t priority, const char *match_rule,
+ const char *map_rule, const char **domains)
+{
+ size_t c;
+ int ret;
+ struct match_map_rule *rule;
+ struct TALLOC_CTX *tmp_ctx;
+ struct priority_list *p;
+ struct priority_list *p_new;
+ struct krb5_match_rule *parsed_match_rule;
+ struct ldap_mapping_rule *parsed_mapping_rule;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ rule = talloc_zero(tmp_ctx, struct match_map_rule);
+ if (rule == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ rule->priority = priority;
+
+ if (match_rule == NULL) {
+ match_rule = DEFAULT_MATCH_RULE;
+ }
+ ret = parse_match_rule(ctx, match_rule, &parsed_match_rule);
+ if (ret == 0) {
+ rule->parsed_match_rule = talloc_steal(rule, parsed_match_rule);
+ rule->match_rule = talloc_strdup(rule, match_rule);
+ if (rule->match_rule == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ } else if (ret == ESRCH) {
+ /* report unsupported rules */
+ goto done;
+ } else {
+ goto done;
+ }
+
+ if (map_rule == NULL) {
+ map_rule = DEFAULT_MAP_RULE;
+ }
+ ret = parse_mapping_rule(ctx, map_rule, &parsed_mapping_rule);
+ if (ret == 0) {
+ rule->parsed_mapping_rule = talloc_steal(rule, parsed_mapping_rule);
+ rule->map_rule = talloc_strdup(rule, map_rule);
+ if (rule->map_rule == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ } else if (ret == ESRCH) {
+ /* report unsupported rules */
+ goto done;
+ } else {
+ goto done;
+ }
+
+ if (domains != NULL && *domains != NULL) {
+ for (c = 0; domains[c] != NULL; c++);
+ rule->domains = talloc_zero_array(rule, char *, c + 1);
+ if (rule->domains == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ for (c = 0; domains[c] != NULL; c++) {
+ rule->domains[c] = talloc_strdup(rule->domains, domains[c]);
+ if (rule->domains[c] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ }
+
+ if (ctx->prio_list == NULL) {
+ ctx->prio_list = talloc_zero(ctx, struct priority_list);
+ if (ctx->prio_list == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ctx->prio_list->priority = rule->priority;
+ ctx->prio_list->rule_list = rule;
+ } else {
+ for (p = ctx->prio_list; p != NULL && p->priority < rule->priority;
+ p = p->next);
+ if (p != NULL && p->priority == priority) {
+ DLIST_ADD(p->rule_list, rule);
+ } else {
+ p_new = talloc_zero(ctx, struct priority_list);
+ if (p_new == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ p_new->priority = rule->priority;
+ p_new->rule_list = rule;
+
+ if (p == NULL) {
+ DLIST_ADD_END(ctx->prio_list, p_new, struct priority_list *);
+ } else if (p->prev == NULL) {
+ DLIST_ADD(ctx->prio_list, p_new);
+ } else {
+ DLIST_ADD_AFTER(ctx->prio_list, p_new, p->prev);
+ }
+ }
+ }
+
+ talloc_steal(ctx, rule);
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+static int expand_cert(struct sss_certmap_ctx *ctx,
+ struct parsed_template *parsed_template,
+ struct sss_cert_content *cert_content,
+ char **expanded)
+{
+ int ret;
+ char *tmp_str = NULL;
+
+ if (parsed_template->conversion == NULL
+ || strcmp(parsed_template->conversion, "bin") == 0) {
+ ret = bin_to_ldap_filter_value(ctx, cert_content->cert_der,
+ cert_content->cert_der_size, &tmp_str);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "bin conversion failed.");
+ goto done;
+ }
+ } else if (strcmp(parsed_template->conversion, "base64") == 0) {
+ tmp_str = sss_base64_encode(ctx, cert_content->cert_der,
+ cert_content->cert_der_size);
+ if (tmp_str == NULL) {
+ CM_DEBUG(ctx, "base64 conversion failed.");
+ ret = ENOMEM;
+ goto done;
+ }
+ } else {
+ CM_DEBUG(ctx, "Unsupported conversion.");
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *expanded = tmp_str;
+ } else {
+ talloc_free(tmp_str);
+ }
+
+ return ret;
+}
+
+static int get_dn_str(struct sss_certmap_ctx *ctx, const char *conversion,
+ const char **rdn_list, char **result)
+{
+ char *str = NULL;
+ size_t c;
+ int ret;
+ char *conv = NULL;
+
+ str = talloc_strdup(ctx, "");
+ if (str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ if (conversion == NULL || strcmp(conversion, "nss_ldap") == 0
+ || strcmp(conversion, "nss") == 0) {
+ for (c = 0; rdn_list[c] != NULL; c++);
+ while (c != 0) {
+ c--;
+ str = talloc_asprintf_append(str, "%s%s",
+ (rdn_list[c + 1] == NULL) ? "" : ",",
+ rdn_list[c]);
+ if (str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ };
+ } else if (strcmp(conversion, "ad_ldap") == 0) {
+ for (c = 0; rdn_list[c] != NULL; c++);
+ while (c != 0) {
+ c--;
+ conv = check_ad_attr_name(str, rdn_list[c]);
+ str = talloc_asprintf_append(str, "%s%s",
+ (rdn_list[c + 1] == NULL) ? "" : ",",
+ conv == NULL ? rdn_list[c] : conv);
+ talloc_free(conv);
+ conv = NULL;
+ if (str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ };
+ } else if (strcmp(conversion, "nss_x500") == 0) {
+ for (c = 0; rdn_list[c] != NULL; c++) {
+ str = talloc_asprintf_append(str, "%s%s", (c == 0) ? "" : ",",
+ rdn_list[c]);
+ if (str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ } else if (strcmp(conversion, "ad_x500") == 0
+ || strcmp(conversion, "ad") == 0) {
+ for (c = 0; rdn_list[c] != NULL; c++) {
+ conv = check_ad_attr_name(str, rdn_list[c]);
+ str = talloc_asprintf_append(str, "%s%s",
+ (c == 0) ? "" : ",",
+ conv == NULL ? rdn_list[c] : conv);
+ talloc_free(conv);
+ conv = NULL;
+ if (str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ } else {
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *result = str;
+ } else {
+ talloc_free(str);
+ }
+
+ return ret;
+}
+
+static int expand_san_blob(struct sss_certmap_ctx *ctx, enum san_opt san_opt,
+ struct san_list *san_list, char **expanded)
+{
+ struct san_list *item;
+ char *exp;
+ int ret;
+
+ DLIST_FOR_EACH(item, san_list) {
+ if (item->san_opt == san_opt) {
+ ret = bin_to_ldap_filter_value(ctx, item->bin_val,
+ item->bin_val_len, &exp);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "bin conversion failed.");
+ return ret;
+ }
+
+ *expanded = exp;
+ return 0;
+ }
+ }
+
+ return ENOENT;
+}
+
+static int expand_san_string(struct sss_certmap_ctx *ctx, enum san_opt san_opt,
+ struct san_list *san_list, const char *attr_name,
+ char **expanded)
+{
+ struct san_list *item;
+ char *exp;
+
+ DLIST_FOR_EACH(item, san_list) {
+ if (item->san_opt == san_opt) {
+ if (attr_name == NULL) {
+ exp = talloc_strdup(ctx, item->val);
+ } else if (strcasecmp(attr_name, "short_name") == 0) {
+ exp = talloc_strdup(ctx, item->short_name);
+ } else {
+ CM_DEBUG(ctx, "Unsupported attribute name [%s].", attr_name);
+ return EINVAL;
+ }
+
+ if (exp == NULL) {
+ return ENOMEM;
+ }
+
+ *expanded = exp;
+ return 0;
+ }
+ }
+
+ return ENOENT;
+}
+
+static int expand_san_rdn_list(struct sss_certmap_ctx *ctx,
+ enum san_opt san_opt,
+ struct san_list *san_list,
+ const char *conversion,
+ char **expanded)
+{
+ struct san_list *item;
+ char *exp;
+ int ret;
+
+ DLIST_FOR_EACH(item, san_list) {
+ if (item->san_opt == san_opt) {
+ ret = get_dn_str(ctx, conversion, item->rdn_list, &exp);
+ if (ret != 0) {
+ return ret;
+ }
+
+ *expanded = exp;
+ return 0;
+ }
+ }
+
+ return ENOENT;
+}
+
+
+static int expand_san(struct sss_certmap_ctx *ctx,
+ struct parsed_template *parsed_template,
+ struct san_list *san_list,
+ char **expanded)
+{
+ int ret;
+
+ if (strcmp("subject_rfc822_name", parsed_template->name) == 0) {
+ ret = expand_san_string(ctx, SAN_RFC822_NAME, san_list,
+ parsed_template->attr_name, expanded);
+ } else if (strcmp("subject_dns_name", parsed_template->name) == 0) {
+ ret = expand_san_string(ctx, SAN_DNS_NAME, san_list,
+ parsed_template->attr_name, expanded);
+ } else if (strcmp("subject_x400_address", parsed_template->name) == 0) {
+ ret = expand_san_blob(ctx, SAN_X400_ADDRESS, san_list, expanded);
+ } else if (strcmp("subject_directory_name", parsed_template->name) == 0) {
+ ret = expand_san_rdn_list(ctx, SAN_DIRECTORY_NAME, san_list,
+ parsed_template->conversion, expanded);
+ } else if (strcmp("subject_ediparty_name", parsed_template->name) == 0) {
+ ret = expand_san_blob(ctx, SAN_EDIPART_NAME, san_list, expanded);
+ } else if (strcmp("subject_uri", parsed_template->name) == 0) {
+ ret = expand_san_string(ctx, SAN_URI, san_list,
+ parsed_template->attr_name, expanded);
+ } else if (strcmp("subject_ip_address", parsed_template->name) == 0) {
+ ret = expand_san_string(ctx, SAN_IP_ADDRESS, san_list,
+ parsed_template->attr_name, expanded);
+ } else if (strcmp("subject_registered_id", parsed_template->name) == 0) {
+ ret = expand_san_string(ctx, SAN_REGISTERED_ID, san_list,
+ parsed_template->attr_name, expanded);
+ } else if (strcmp("subject_pkinit_principal", parsed_template->name) == 0) {
+ ret = expand_san_string(ctx, SAN_PKINIT, san_list,
+ parsed_template->attr_name, expanded);
+ } else if (strcmp("subject_nt_principal", parsed_template->name) == 0) {
+ ret = expand_san_string(ctx, SAN_NT, san_list,
+ parsed_template->attr_name, expanded);
+ } else if (strcmp("subject_principal", parsed_template->name) == 0) {
+ ret = expand_san_string(ctx, SAN_PRINCIPAL, san_list,
+ parsed_template->attr_name, expanded);
+ } else {
+ CM_DEBUG(ctx, "Unsupported template name [%s].n",
+ parsed_template->name);
+ ret = EINVAL;
+ }
+
+ return ret;
+}
+
+static int expand_template(struct sss_certmap_ctx *ctx,
+ struct parsed_template *parsed_template,
+ struct sss_cert_content *cert_content,
+ char **expanded)
+{
+ int ret;
+ char *exp = NULL;
+
+ if (strcmp("issuer_dn", parsed_template->name) == 0) {
+ ret = get_dn_str(ctx, parsed_template->conversion,
+ cert_content->issuer_rdn_list, &exp);
+ } else if (strcmp("subject_dn", parsed_template->name) == 0) {
+ ret = get_dn_str(ctx, parsed_template->conversion,
+ cert_content->subject_rdn_list, &exp);
+ } else if (strncmp("subject_", parsed_template->name, 8) == 0) {
+ ret = expand_san(ctx, parsed_template, cert_content->san_list, &exp);
+ } else if (strcmp("cert", parsed_template->name) == 0) {
+ ret = expand_cert(ctx, parsed_template, cert_content, &exp);
+ } else {
+ CM_DEBUG(ctx, "Unsupported template name.");
+ ret = EINVAL;
+ goto done;
+ }
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to expand [%s] template.", parsed_template->name);
+ goto done;
+ }
+
+ if (exp == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *expanded = exp;
+ } else {
+ talloc_free(exp);
+ }
+
+ return ret;
+}
+
+static int get_filter(struct sss_certmap_ctx *ctx,
+ struct ldap_mapping_rule *parsed_mapping_rule,
+ struct sss_cert_content *cert_content,
+ char **filter)
+{
+ struct ldap_mapping_rule_comp *comp;
+ char *result = NULL;
+ char *expanded = NULL;
+ int ret;
+
+ result = talloc_strdup(ctx, "");
+ if (result == NULL) {
+ return ENOMEM;
+ }
+
+ for (comp = parsed_mapping_rule->list; comp != NULL; comp = comp->next) {
+ if (comp->type == comp_string) {
+ result = talloc_strdup_append(result, comp->val);
+ } else if (comp->type == comp_template) {
+ ret = expand_template(ctx, comp->parsed_template, cert_content,
+ &expanded);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to expanded template.");
+ goto done;
+ }
+
+ result = talloc_strdup_append(result, expanded);
+ talloc_free(expanded);
+ expanded = NULL;
+ if (result == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ } else {
+ ret = EINVAL;
+ CM_DEBUG(ctx, "Unsupported component type.");
+ goto done;
+ }
+ }
+
+ ret = 0;
+done:
+ talloc_free(expanded);
+ if (ret == 0) {
+ *filter = result;
+ } else {
+ talloc_free(result);
+ }
+
+ return ret;
+}
+
+static bool check_san_regexp(struct sss_certmap_ctx *ctx,
+ enum san_opt san_opt, regex_t regexp,
+ struct san_list *san_list)
+{
+ struct san_list *item;
+ bool match = false;
+ int ret;
+ char *tmp_str = NULL;
+
+ DLIST_FOR_EACH(item, san_list) {
+ if (item->san_opt == san_opt) {
+ if (item->san_opt == SAN_DIRECTORY_NAME) {
+ /* use LDAP order for matching */
+ ret = get_dn_str(ctx, NULL, item->rdn_list, &tmp_str);
+ if (ret != 0 || tmp_str == NULL) {
+ return false;
+ }
+ match = (regexec(&regexp, tmp_str, 0, NULL, 0) == 0);
+ talloc_free(tmp_str);
+ } else {
+ match = (item->val != NULL
+ && regexec(&regexp, item->val, 0, NULL, 0) == 0);
+ }
+ if (!match) {
+ return false;
+ }
+ }
+ }
+
+ return match;
+}
+
+static bool check_san_blob(enum san_opt san_opt,
+ uint8_t *bin_val, size_t bin_val_len,
+ struct san_list *san_list)
+{
+ struct san_list *item;
+ bool match = false;
+
+ if (bin_val == NULL || bin_val_len == 0) {
+ return false;
+ }
+
+ DLIST_FOR_EACH(item, san_list) {
+ if (item->san_opt == san_opt) {
+ match = (item->bin_val != NULL && item->bin_val_len != 0
+ && memmem(item->bin_val, item->bin_val_len,
+ bin_val, bin_val_len) != NULL);
+ if (!match) {
+ return false;
+ }
+ }
+ }
+
+ return match;
+}
+
+static bool check_san_str_other_name(enum san_opt san_opt,
+ const char *str_other_name_oid,
+ regex_t regexp,
+ struct san_list *san_list)
+{
+ struct san_list *item;
+ bool match = false;
+ char *tmp_str;
+
+ if (str_other_name_oid == NULL) {
+ return false;
+ }
+
+ DLIST_FOR_EACH(item, san_list) {
+ if (item->san_opt == san_opt
+ && strcmp(item->other_name_oid, str_other_name_oid) == 0) {
+ match = false;
+ if (item->bin_val != NULL && item->bin_val_len != 0) {
+ tmp_str = talloc_strndup(item, (char *) item->bin_val,
+ item->bin_val_len);
+ if (tmp_str != NULL) {
+ match = (regexec(&regexp, tmp_str, 0, NULL, 0) == 0);
+ }
+ talloc_free(tmp_str);
+ }
+ if (!match) {
+ return false;
+ }
+ }
+ }
+
+ return match;
+}
+
+static bool do_san_match(struct sss_certmap_ctx *ctx,
+ struct component_list *comp,
+ struct san_list *san_list)
+{
+ switch (comp->san_opt) {
+ case SAN_OTHER_NAME:
+ return check_san_blob(SAN_STRING_OTHER_NAME,
+ comp->bin_val, comp->bin_val_len,
+ san_list);
+ break;
+ case SAN_X400_ADDRESS:
+ case SAN_EDIPART_NAME:
+ return check_san_blob(comp->san_opt, comp->bin_val, comp->bin_val_len,
+ san_list);
+ break;
+ case SAN_RFC822_NAME:
+ case SAN_DNS_NAME:
+ case SAN_DIRECTORY_NAME:
+ case SAN_URI:
+ case SAN_IP_ADDRESS:
+ case SAN_REGISTERED_ID:
+ case SAN_PKINIT:
+ case SAN_NT:
+ case SAN_PRINCIPAL:
+ return check_san_regexp(ctx, comp->san_opt, comp->regexp, san_list);
+ break;
+ case SAN_STRING_OTHER_NAME:
+ return check_san_str_other_name(comp->san_opt, comp->str_other_name_oid,
+ comp->regexp, san_list);
+ break;
+ default:
+ CM_DEBUG(ctx, "Unsupported SAN option [%d].", comp->san_opt);
+ return false;
+ }
+}
+
+static int do_match(struct sss_certmap_ctx *ctx,
+ struct krb5_match_rule *parsed_match_rule,
+ struct sss_cert_content *cert_content)
+{
+ struct component_list *comp;
+ bool match = false;
+ size_t c;
+
+ if (parsed_match_rule == NULL || cert_content == NULL) {
+ return EINVAL;
+ }
+
+ /* Issuer */
+ for (comp = parsed_match_rule->issuer; comp != NULL; comp = comp->next) {
+ match = (cert_content->issuer_str != NULL
+ && regexec(&(comp->regexp), cert_content->issuer_str,
+ 0, NULL, 0) == 0);
+ if (match && parsed_match_rule->r == relation_or) {
+ /* match */
+ return 0;
+ } else if (!match && parsed_match_rule->r == relation_and) {
+ /* no match */
+ return ENOENT;
+ }
+
+ }
+
+ /* Subject */
+ for (comp = parsed_match_rule->subject; comp != NULL; comp = comp->next) {
+ match = (cert_content->subject_str != NULL
+ && regexec(&(comp->regexp), cert_content->subject_str,
+ 0, NULL, 0) == 0);
+ if (match && parsed_match_rule->r == relation_or) {
+ /* match */
+ return 0;
+ } else if (!match && parsed_match_rule->r == relation_and) {
+ /* no match */
+ return ENOENT;
+ }
+
+ }
+
+ /* Key Usage */
+ for (comp = parsed_match_rule->ku; comp != NULL; comp = comp->next) {
+ match = ((cert_content->key_usage & comp->ku) == comp->ku);
+ if (match && parsed_match_rule->r == relation_or) {
+ /* match */
+ return 0;
+ } else if (!match && parsed_match_rule->r == relation_and) {
+ /* no match */
+ return ENOENT;
+ }
+ }
+
+ /* Extended Key Usage */
+ for (comp = parsed_match_rule->eku; comp != NULL; comp = comp->next) {
+ for (c = 0; comp->eku_oid_list[c] != NULL; c++) {
+ match = string_in_list(comp->eku_oid_list[c],
+ discard_const(
+ cert_content->extended_key_usage_oids),
+ true);
+ if (match && parsed_match_rule->r == relation_or) {
+ /* match */
+ return 0;
+ } else if (!match && parsed_match_rule->r == relation_and) {
+ /* no match */
+ return ENOENT;
+ }
+ }
+ }
+
+ /* SAN */
+ for (comp = parsed_match_rule->san; comp != NULL; comp = comp->next) {
+ match = do_san_match(ctx, comp, cert_content->san_list);
+ if (match && parsed_match_rule->r == relation_or) {
+ /* match */
+ return 0;
+ } else if (!match && parsed_match_rule->r == relation_and) {
+ /* no match */
+ return ENOENT;
+ }
+ }
+
+ if (match) {
+ /* match */
+ return 0;
+ }
+
+ /* no match */
+ return ENOENT;
+}
+
+int sss_certmap_match_cert(struct sss_certmap_ctx *ctx,
+ const uint8_t *der_cert, size_t der_size)
+{
+ int ret;
+ struct match_map_rule *r;
+ struct priority_list *p;
+ struct sss_cert_content *cert_content = NULL;
+
+ ret = sss_cert_get_content(ctx, der_cert, der_size, &cert_content);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to get certificate content.");
+ return ret;
+ }
+
+ if (ctx->prio_list == NULL) {
+ /* Match all certificates if there are no rules applied */
+ ret = 0;
+ goto done;
+ }
+
+ for (p = ctx->prio_list; p != NULL; p = p->next) {
+ for (r = p->rule_list; r != NULL; r = r->next) {
+ ret = do_match(ctx, r->parsed_match_rule, cert_content);
+ if (ret == 0) {
+ /* match */
+ goto done;
+ }
+ }
+ }
+
+ ret = ENOENT;
+done:
+ talloc_free(cert_content);
+
+ return ret;
+}
+
+int sss_certmap_get_search_filter(struct sss_certmap_ctx *ctx,
+ const uint8_t *der_cert, size_t der_size,
+ char **_filter, char ***_domains)
+{
+ int ret;
+ struct match_map_rule *r;
+ struct priority_list *p;
+ struct sss_cert_content *cert_content = NULL;
+ char *filter = NULL;
+ char **domains = NULL;
+ size_t c;
+
+ if (_filter == NULL || _domains == NULL) {
+ return EINVAL;
+ }
+
+ ret = sss_cert_get_content(ctx, der_cert, der_size, &cert_content);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to get certificate content [%d].", ret);
+ return ret;
+ }
+
+ if (ctx->prio_list == NULL) {
+ if (ctx->default_mapping_rule == NULL) {
+ CM_DEBUG(ctx, "No matching or mapping rules available.");
+ return EINVAL;
+ }
+
+ ret = get_filter(ctx, ctx->default_mapping_rule, cert_content, &filter);
+ goto done;
+ }
+
+ for (p = ctx->prio_list; p != NULL; p = p->next) {
+ for (r = p->rule_list; r != NULL; r = r->next) {
+ ret = do_match(ctx, r->parsed_match_rule, cert_content);
+ if (ret == 0) {
+ /* match */
+ ret = get_filter(ctx, r->parsed_mapping_rule, cert_content,
+ &filter);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to get filter");
+ goto done;
+ }
+
+ if (r->domains != NULL) {
+ for (c = 0; r->domains[c] != NULL; c++);
+ domains = talloc_zero_array(ctx, char *, c + 1);
+ if (domains == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (c = 0; r->domains[c] != NULL; c++) {
+ domains[c] = talloc_strdup(domains, r->domains[c]);
+ if (domains[c] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ }
+
+ ret = 0;
+ goto done;
+ }
+ }
+ }
+
+ ret = ENOENT;
+
+done:
+ talloc_free(cert_content);
+ if (ret == 0) {
+ *_filter = filter;
+ *_domains = domains;
+ } else {
+ talloc_free(filter);
+ talloc_free(domains);
+ }
+
+ return ret;
+}
+
+int sss_certmap_init(TALLOC_CTX *mem_ctx,
+ sss_certmap_ext_debug *debug, void *debug_priv,
+ struct sss_certmap_ctx **ctx)
+{
+ int ret;
+
+ if (ctx == NULL) {
+ return EINVAL;
+ }
+
+ *ctx = talloc_zero(mem_ctx, struct sss_certmap_ctx);
+ if (*ctx == NULL) {
+ return ENOMEM;
+ }
+
+ (*ctx)->debug = debug;
+ (*ctx)->debug_priv = debug_priv;
+
+ ret = parse_mapping_rule(*ctx, DEFAULT_MAP_RULE,
+ &((*ctx)->default_mapping_rule));
+ if (ret != 0) {
+ CM_DEBUG((*ctx), "Failed to parse default mapping rule.");
+ talloc_free(*ctx);
+ *ctx = NULL;
+ return ret;
+ }
+
+ CM_DEBUG((*ctx), "sss_certmap initialized.");
+ return EOK;
+}
+
+void sss_certmap_free_ctx(struct sss_certmap_ctx *ctx)
+{
+ talloc_free(ctx);
+}
+
+void sss_certmap_free_filter_and_domains(char *filter, char **domains)
+{
+ talloc_free(filter);
+ talloc_free(domains);
+}
diff --git a/src/lib/certmap/sss_certmap.doxy.in b/src/lib/certmap/sss_certmap.doxy.in
new file mode 100644
index 000000000..e8959e209
--- /dev/null
+++ b/src/lib/certmap/sss_certmap.doxy.in
@@ -0,0 +1,3 @@
+PROJECT_NAME = sss_certmap
+OUTPUT_DIRECTORY = certmap_doc
+INPUT = @abs_top_srcdir@/src/lib/certmap/sss_certmap.h
diff --git a/src/lib/certmap/sss_certmap.exports b/src/lib/certmap/sss_certmap.exports
new file mode 100644
index 000000000..8b5d53666
--- /dev/null
+++ b/src/lib/certmap/sss_certmap.exports
@@ -0,0 +1,13 @@
+SSS_CERTMAP_0.0 {
+ global:
+ sss_certmap_init;
+ sss_certmap_free_ctx;
+ sss_certmap_err_msg;
+ sss_certmap_add_rule;
+ sss_certmap_match_cert;
+ sss_certmap_get_search_filter;
+ sss_cert_get_content;
+ sss_certmap_free_filter_and_domains;
+ local:
+ *;
+};
diff --git a/src/lib/certmap/sss_certmap.h b/src/lib/certmap/sss_certmap.h
new file mode 100644
index 000000000..55485cc35
--- /dev/null
+++ b/src/lib/certmap/sss_certmap.h
@@ -0,0 +1,155 @@
+/*
+ SSSD
+
+ Library for rule based certificate to user mapping
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2017 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/>.
+*/
+
+#ifndef _SSS_CERTMAP_H_
+#define _SSS_CERTMAP_H_
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include <talloc.h>
+
+/**
+ * @defgroup sss_certmap Allow rule-based mapping of certificates to users
+ * Libsss_certmap provides a mechanism to map X509 certificate to users based
+ * on rules.
+ * @{
+ */
+
+/**
+ * Opaque type for the idmap context
+ */
+struct sss_certmap_ctx;
+
+/**
+ * Lowest priority of a rule
+ */
+#define SSS_CERTMAP_MIN_PRIO UINT32_MAX
+
+/**
+ * Typedef for external debug callback
+ */
+typedef void (sss_certmap_ext_debug)(void *pvt,
+ const char *file, long line,
+ const char *function,
+ const char *format, ...);
+/**
+ * @brief Initialize certmap context
+ *
+ * @param[in] mem_ctx Talloc memory context, may be NULL
+ * @param[in] debug Callback to handle debug output, may be NULL
+ * @param[in] debug_priv Private data for debugging callback, may be NULL
+ * @param[out] ctx New certmap context
+ *
+ * @return
+ * - 0: success
+ * - ENOMEM: failed to allocate internal Talloc context
+ * - EINVAL: ctx is NULL
+ */
+int sss_certmap_init(TALLOC_CTX *mem_ctx,
+ sss_certmap_ext_debug *debug, void *debug_priv,
+ struct sss_certmap_ctx **ctx);
+
+/**
+ * @brief Free certmap context
+ *
+ * @param[in] ctx certmap context previously initialized with
+ * @ref sss_certmap_init, may be NULL
+ */
+void sss_certmap_free_ctx(struct sss_certmap_ctx *ctx);
+
+/**
+ * @brief Add a rule to the certmap context
+ *
+ * @param[in] ctx certmap context previously initialized with
+ * @ref sss_certmap_init
+ * @param[in] priority priority of the rule, 0 is the hightest priority, the
+ * lowest is SSS_CERTMAP_MIN_PRIO
+ * @param[in] match_rule String with the matching rule
+ * @param[in] map_rule String with the mapping rule
+ * @param[in] domains NULL-terminated string array with a list of domains
+ * the rule should be valid for, i.e. only this domains
+ * should be searched for matching users
+ *
+ * @return
+ * - 0: success
+ */
+int sss_certmap_add_rule(struct sss_certmap_ctx *ctx,
+ uint32_t priority, const char *match_rule,
+ const char *map_rule, const char **domains);
+
+/**
+ * @brief Check if a certificate matches any of the applied rules
+ *
+ * @param[in] ctx certmap context previously initialized with
+ * @ref sss_certmap_init
+ * @param[in] der_cert binary blog with the DER encoded certificate
+ * @param[in] der_size size of the certificate blob
+ *
+ * @return
+ * - 0: certificate matches a rule
+ * - ENOENT: certificate does not match
+ * - EINVAL: internal error
+ */
+int sss_certmap_match_cert(struct sss_certmap_ctx *ctx,
+ const uint8_t *der_cert, size_t der_size);
+
+/**
+ * @brief Get the LDAP filter string for a certificate
+ *
+ * @param[in] ctx certmap context previously initialized with
+ * @ref sss_certmap_init
+ * @param[in] der_cert binary blog with the DER encoded certificate
+ * @param[in] der_size size of the certificate blob
+ * @param[out] filter LDAP filter string, caller should free the data by
+ * calling sss_certmap_free_filter_and_domains
+ * @param[out] domains NULL-terminated array of strings with the domains the
+ * rule applies, caller should free the data by calling
+ * sss_certmap_free_filter_and_domains
+ *
+ * @return
+ * - 0: certificate matches a rule
+ * - ENOENT: certificate does not match
+ * - EINVAL: internal error
+ */
+int sss_certmap_get_search_filter(struct sss_certmap_ctx *ctx,
+ const uint8_t *der_cert, size_t der_size,
+ char **filter, char ***domains);
+
+/**
+ * @brief Free data returned by @ref sss_certmap_get_search_filter
+ *
+ * @param[in] filter LDAP filter strings returned by
+ * sss_certmap_get_search_filter
+ * @param[in] domains string array of domains returned by
+ * sss_certmap_get_search_filter
+ */
+void sss_certmap_free_filter_and_domains(char *filter, char **domains);
+
+/**
+ * @}
+ */
+#endif /* _SSS_CERTMAP_H_ */
diff --git a/src/lib/certmap/sss_certmap.pc.in b/src/lib/certmap/sss_certmap.pc.in
new file mode 100644
index 000000000..f1a4432fc
--- /dev/null
+++ b/src/lib/certmap/sss_certmap.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: sss_certmap
+Description: SSS certificate mapping library
+Version: @VERSION@
+Libs: -L${libdir} -lsss_certmap
+Cflags:
+URL: https://pagure.io/SSSD/sssd/
diff --git a/src/lib/certmap/sss_certmap_attr_names.c b/src/lib/certmap/sss_certmap_attr_names.c
new file mode 100644
index 000000000..a28a46491
--- /dev/null
+++ b/src/lib/certmap/sss_certmap_attr_names.c
@@ -0,0 +1,107 @@
+/*
+ SSSD
+
+ Library for rule based certificate to user mapping - Attribute name
+ mapping for different implementations
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2017 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/>.
+*/
+
+/* NSS data taken from nss-utils:nss/lib/util/secoid.c and
+ * nss:nss/lib/certdb/alg1485.c */
+
+/* AD data taken from
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/aa376556%28v=vs.85%29.aspx
+ * and wine source code dlls/crypt32/oid.c  and include/wincrypt.h . */
+
+#include <stdbool.h>
+#include <string.h>
+#include <talloc.h>
+
+struct oid_attr_name_map {
+ bool nss_ad_differ;
+ const char *oid;
+ const char *nss;
+ const char *ad;
+} oid_attr_name_map[] = {
+ { false, "2.5.4.3", "CN", "CN"},
+ { true, "2.5.4.8", "ST", "S"},
+ { false, "2.5.4.10", "O", "O"},
+ { false, "2.5.4.11", "OU", "OU"},
+ { false, "2.5.4.46", "dnQualifier", "dnQualifier"},
+ { false, "2.5.4.6", "C", "C"},
+ { true, "2.5.4.5", "serialNumber", "SERIALNUMBER"},
+ { false, "2.5.4.7", "L", "L"},
+ { true, "2.5.4.12", "title", "T"},
+ { false, "2.5.4.4", "SN", "SN"},
+ { true, "2.5.4.42", "givenName", "G"},
+ { true, "2.5.4.43", "initials", "I"},
+ { true, "2.5.4.44", "generationQualifier", "OID.2.5.4.44"},
+ { false, "0.9.2342.19200300.100.1.25", "DC", "DC"},
+ { true, "0.9.2342.19200300.100.1.3", "MAIL", "OID,0.9.2342.19200300.100.1.3"},
+ { true, "0.9.2342.19200300.100.1.1", "UID", "OID.0.9.2342.19200300.100.1.1"},
+ { true, "2.5.4.13", "OID.2.5.4.13", "Description"},
+ { true, "2.5.4.16", "postalAddress", "OID.2.5.4.16"},
+ { true, "2.5.4.17", "postalCode", "PostalCode"},
+ { true, "2.5.4.18", "postOfficeBox", "POBox"},
+ { true, "2.5.4.51", "houseIdentifier", "OID.2.5.4.51"},
+ { false, "1.2.840.113549.1.9.1", "E", "E"},
+ { false, "2.5.4.9", "STREET", "STREET"},
+ { true, "2.5.4.65", "pseudonym", "OID.2.5.4.65"},
+ { true, "2.5.4.15", "businessCategory", "OID.2.5.4.15"},
+ { true, "2.5.4.41", "name", "OID.2.5.4.41"},
+
+ { false, NULL, NULL, NULL}
+};
+
+char *check_ad_attr_name(TALLOC_CTX *mem_ctx, const char *rdn)
+{
+ char *p;
+ size_t c;
+ size_t len;
+
+ if (rdn == NULL) {
+ return NULL;
+ }
+
+ p = strchr(rdn, '=');
+ if (p == NULL) {
+ return NULL;
+ }
+
+ len = p - rdn;
+ if (len == 0) {
+ return NULL;
+ }
+
+ for (c = 0; oid_attr_name_map[c].oid != NULL; c++) {
+ if (!oid_attr_name_map[c].nss_ad_differ) {
+ continue;
+ }
+
+ if (strlen(oid_attr_name_map[c].nss) != len
+ || strncmp(rdn, oid_attr_name_map[c].nss, len) != 0) {
+ continue;
+ }
+
+ return talloc_asprintf(mem_ctx, "%s%s", oid_attr_name_map[c].ad, p);
+ }
+
+ return NULL;
+}
diff --git a/src/lib/certmap/sss_certmap_int.h b/src/lib/certmap/sss_certmap_int.h
new file mode 100644
index 000000000..28f1c596c
--- /dev/null
+++ b/src/lib/certmap/sss_certmap_int.h
@@ -0,0 +1,187 @@
+/*
+ SSSD
+
+ Library for rule based certificate to user mapping
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2017 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 <sys/types.h>
+#include <regex.h>
+
+#ifndef __SSS_CERTMAP_INT_H__
+#define __SSS_CERTMAP_INT_H__
+
+#define CM_DEBUG(cm_ctx, format, ...) do { \
+ if (cm_ctx != NULL && cm_ctx->debug != NULL) { \
+ cm_ctx->debug(cm_ctx->debug_priv, __FILE__, __LINE__, __FUNCTION__, \
+ format, ##__VA_ARGS__); \
+ } \
+} while (0)
+
+#define DEFAULT_MATCH_RULE "<KU>digitalSignature<EKU>clientAuth"
+#define DEFAULT_MAP_RULE "LDAP:(userCertificate;binary={cert!bin})"
+
+enum san_opt {
+ SAN_OTHER_NAME = 0,
+ SAN_RFC822_NAME,
+ SAN_DNS_NAME,
+ SAN_X400_ADDRESS,
+ SAN_DIRECTORY_NAME,
+ SAN_EDIPART_NAME,
+ SAN_URI,
+ SAN_IP_ADDRESS,
+ SAN_REGISTERED_ID,
+ SAN_PKINIT,
+ SAN_NT,
+ SAN_PRINCIPAL,
+ SAN_STRING_OTHER_NAME,
+
+ SAN_END,
+ SAN_INVALID
+};
+
+/* KRB5 matching rule */
+enum relation_type {
+ relation_none = 0,
+ relation_and,
+ relation_or
+};
+
+struct component_list {
+ char *val;
+ regex_t regexp;
+ uint32_t ku;
+ const char **eku_oid_list;
+ enum san_opt san_opt;
+ char *str_other_name_oid;
+ uint8_t *bin_val;
+ size_t bin_val_len;
+ struct component_list *prev;
+ struct component_list *next;
+};
+
+struct krb5_match_rule {
+ enum relation_type r;
+ struct component_list *issuer;
+ struct component_list *subject;
+ struct component_list *ku;
+ struct component_list *eku;
+ struct component_list *san;
+};
+
+enum comp_type {
+ comp_none = 0,
+ comp_string,
+ comp_template
+};
+
+struct parsed_template {
+ char *name;
+ char *attr_name;
+ char *conversion;
+};
+
+struct ldap_mapping_rule_comp {
+ enum comp_type type;
+ char *val;
+ struct parsed_template *parsed_template;
+ struct ldap_mapping_rule_comp *prev;
+ struct ldap_mapping_rule_comp *next;
+};
+
+struct ldap_mapping_rule {
+ struct ldap_mapping_rule_comp *list;
+};
+
+struct match_map_rule {
+ uint32_t priority;
+ char *match_rule;
+ struct krb5_match_rule *parsed_match_rule;
+ char *map_rule;
+ struct ldap_mapping_rule *parsed_mapping_rule;
+ char **domains;
+ struct match_map_rule *prev;
+ struct match_map_rule *next;
+};
+
+struct priority_list {
+ uint32_t priority;
+ struct match_map_rule *rule_list;
+ struct priority_list *prev;
+ struct priority_list *next;
+};
+
+struct sss_certmap_ctx {
+ struct priority_list *prio_list;
+ sss_certmap_ext_debug *debug;
+ void *debug_priv;
+ struct ldap_mapping_rule *default_mapping_rule;
+};
+
+struct san_list {
+ enum san_opt san_opt;
+ char *val;
+ uint8_t *bin_val;
+ size_t bin_val_len;
+ char *other_name_oid;
+ char *short_name;
+ const char **rdn_list;
+ struct san_list *prev;
+ struct san_list *next;
+};
+
+/* key usage flags, see RFC 3280 section 4.2.1.3 */
+#define SSS_KU_DIGITAL_SIGNATURE 0x0080
+#define SSS_KU_NON_REPUDIATION 0x0040
+#define SSS_KU_KEY_ENCIPHERMENT 0x0020
+#define SSS_KU_DATA_ENCIPHERMENT 0x0010
+#define SSS_KU_KEY_AGREEMENT 0x0008
+#define SSS_KU_KEY_CERT_SIGN 0x0004
+#define SSS_KU_CRL_SIGN 0x0002
+#define SSS_KU_ENCIPHER_ONLY 0x0001
+#define SSS_KU_DECIPHER_ONLY 0x8000
+
+struct sss_cert_content {
+ const char *issuer_str;
+ const char **issuer_rdn_list;
+ const char *subject_str;
+ const char **subject_rdn_list;
+ uint32_t key_usage;
+ const char **extended_key_usage_oids;
+ struct san_list *san_list;
+
+ uint8_t *cert_der;
+ size_t cert_der_size;
+};
+
+int sss_cert_get_content(TALLOC_CTX *mem_ctx,
+ const uint8_t *der_blob, size_t der_size,
+ struct sss_cert_content **content);
+
+char *check_ad_attr_name(TALLOC_CTX *mem_ctx, const char *rdn);
+
+int parse_krb5_match_rule(struct sss_certmap_ctx *ctx,
+ const char *rule_start,
+ struct krb5_match_rule **match_rule);
+
+int parse_ldap_mapping_rule(struct sss_certmap_ctx *ctx,
+ const char *rule_start,
+ struct ldap_mapping_rule **mapping_rule);
+#endif /* __SSS_CERTMAP_INT_H__ */
diff --git a/src/lib/certmap/sss_certmap_krb5_match.c b/src/lib/certmap/sss_certmap_krb5_match.c
new file mode 100644
index 000000000..e40f17b8a
--- /dev/null
+++ b/src/lib/certmap/sss_certmap_krb5_match.c
@@ -0,0 +1,558 @@
+/*
+ SSSD
+
+ Library for rule based certificate to user mapping - KRB5 matching rules
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2017 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 "util/util.h"
+#include "util/cert.h"
+#include "util/crypto/sss_crypto.h"
+#include "lib/certmap/sss_certmap.h"
+#include "lib/certmap/sss_certmap_int.h"
+
+static bool is_dotted_decimal(const char *s, size_t len)
+{
+ size_t c = 0;
+ bool has_dot = false;
+
+ if (s == NULL || !isdigit(s[c++])) {
+ return false;
+ }
+
+ while ((len == 0 && s[c] != '\0') || (len != 0 && c < len)) {
+ if (s[c] != '.' && !isdigit(s[c])) {
+ return false;
+ }
+ if (!has_dot && s[c] == '.') {
+ has_dot = true;
+ }
+ c++;
+ }
+
+ return (has_dot && isdigit(s[c - 1]));
+}
+
+static int component_list_destructor(void *data)
+{
+ struct component_list *comp = talloc_get_type(data, struct component_list);
+
+ if (comp != NULL) {
+ regfree(&(comp->regexp));
+ }
+
+ return 0;
+}
+
+/*
+ * The syntax of the MIT Kerberos style matching rules is:
+ * [KRB5:][relation-operator]component-rule ...
+ *
+ * where:
+ *
+ * relation-operator
+ * can be either &&, meaning all component rules must match, or ||,
+ * meaning only one component rule must match. The default is &&.
+ *
+ * component-rule
+ * can be one of the following. Note that there is no punctuation or whitespace between component rules.
+ * <SUBJECT>regular-expression
+ * <ISSUER>regular-expression
+ * <SAN>regular-expression
+ * <EKU>extended-key-usage
+ * <KU>key-usage
+ *
+ * see man sss-certmap for more details
+ *
+ */
+
+static int get_comp_value(TALLOC_CTX *mem_ctx,
+ struct sss_certmap_ctx *ctx,
+ const char **cur,
+ struct component_list **_comp)
+
+{
+ struct component_list *comp = NULL;
+ const char *end;
+ int ret;
+
+ comp = talloc_zero(mem_ctx, struct component_list);
+ if (comp == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ talloc_set_destructor((TALLOC_CTX *) comp, component_list_destructor);
+
+ end = strchr(*cur, '<');
+
+ if (end == NULL) {
+ comp->val = talloc_strdup(comp, *cur);
+ } else {
+ comp->val = talloc_strndup(comp, *cur, end - *cur);
+ }
+ if (comp->val == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ if (*(comp->val) == '\0') {
+ CM_DEBUG(ctx, "Missing component value.");
+ ret = EINVAL;
+ goto done;
+ }
+
+ *cur += strlen(comp->val);
+ *_comp = comp;
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ talloc_free(comp);
+ }
+
+ return ret;
+}
+
+static int parse_krb5_get_eku_value(TALLOC_CTX *mem_ctx,
+ struct sss_certmap_ctx *ctx,
+ const char **cur,
+ struct component_list **_comp)
+{
+ struct component_list *comp = NULL;
+ int ret;
+ char **eku_list;
+ size_t c;
+ size_t k;
+ const char *o;
+ size_t e = 0;
+ int eku_list_size;
+
+ struct ext_key_usage {
+ const char *name;
+ const char *oid;
+ } ext_key_usage[] = {
+ /* RFC 3280 section 4.2.1.13 */
+ {"serverAuth", "1.3.6.1.5.5.7.3.1"},
+ {"clientAuth", "1.3.6.1.5.5.7.3.2"},
+ {"codeSigning", "1.3.6.1.5.5.7.3.3"},
+ {"emailProtection", "1.3.6.1.5.5.7.3.4"},
+ {"timeStamping", "1.3.6.1.5.5.7.3.8"},
+ {"OCSPSigning", "1.3.6.1.5.5.7.3.9"},
+
+ /* RFC 4556 section 3.2.2 */
+ {"KPClientAuth", "1.3.6.1.5.2.3.4"},
+ {"pkinit", "1.3.6.1.5.2.3.4"},
+
+ /* https://support.microsoft.com/en-us/help/287547/object-ids-associated-with-microsoft-cryptography*/
+ {"msScLogin", "1.3.6.1.4.1.311.20.2.2"},
+
+ {NULL ,0}
+ };
+
+ ret = get_comp_value(mem_ctx, ctx, cur, &comp);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to parse regexp.");
+ goto done;
+ }
+
+ ret = split_on_separator(mem_ctx, comp->val, ',', true, true,
+ &eku_list, &eku_list_size);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to split list.");
+ goto done;
+ }
+
+ for (c = 0; eku_list[c] != NULL; c++) {
+ for (k = 0; ext_key_usage[k].name != NULL; k++) {
+CM_DEBUG(ctx, "[%s][%s].", eku_list[c], ext_key_usage[k].name);
+ if (strcasecmp(eku_list[c], ext_key_usage[k].name) == 0) {
+ if (comp->eku_oid_list == NULL) {
+ comp->eku_oid_list = talloc_zero_array(comp, const char *,
+ eku_list_size + 1);
+ if (comp->eku_oid_list == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ comp->eku_oid_list[e] = talloc_strdup(comp->eku_oid_list,
+ ext_key_usage[k].oid);
+ if (comp->eku_oid_list[e] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ e++;
+ break;
+ }
+ }
+
+ if (ext_key_usage[k].name == NULL) {
+ /* check for an dotted-decimal OID */
+ if (*(eku_list[c]) != '.') {
+ o = eku_list[c];
+ if (is_dotted_decimal(o, 0)) {
+ /* looks like a OID, only '.' and digits */
+ comp->eku_oid_list[e] = talloc_strdup(comp->eku_oid_list,
+ eku_list[c]);
+ if (comp->eku_oid_list[e] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ e++;
+ continue;
+ }
+ }
+ CM_DEBUG(ctx, "No matching extended key usage found.");
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *_comp = comp;
+ } else {
+ talloc_free(comp);
+ }
+
+ return ret;
+}
+
+static int parse_krb5_get_ku_value(TALLOC_CTX *mem_ctx,
+ struct sss_certmap_ctx *ctx,
+ const char **cur,
+ struct component_list **_comp)
+{
+ struct component_list *comp = NULL;
+ int ret;
+ char **ku_list;
+ size_t c;
+ size_t k;
+
+ struct key_usage {
+ const char *name;
+ uint32_t flag;
+ } key_usage[] = {
+ {"digitalSignature" , SSS_KU_DIGITAL_SIGNATURE},
+ {"nonRepudiation" , SSS_KU_NON_REPUDIATION},
+ {"keyEncipherment" , SSS_KU_KEY_ENCIPHERMENT},
+ {"dataEncipherment" , SSS_KU_DATA_ENCIPHERMENT},
+ {"keyAgreement" , SSS_KU_KEY_AGREEMENT},
+ {"keyCertSign" , SSS_KU_KEY_CERT_SIGN},
+ {"cRLSign" , SSS_KU_CRL_SIGN},
+ {"encipherOnly" , SSS_KU_ENCIPHER_ONLY},
+ {"decipherOnly" , SSS_KU_DECIPHER_ONLY},
+ {NULL ,0}
+ };
+
+
+ ret = get_comp_value(mem_ctx, ctx, cur, &comp);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to get value.");
+ goto done;
+ }
+
+ ret = split_on_separator(mem_ctx, comp->val, ',', true, true,
+ &ku_list, NULL);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to split list.");
+ goto done;
+ }
+
+ for (c = 0; ku_list[c] != NULL; c++) {
+ for (k = 0; key_usage[k].name != NULL; k++) {
+ if (strcasecmp(ku_list[c], key_usage[k].name) == 0) {
+ comp->ku |= key_usage[k].flag;
+ break;
+ }
+ }
+
+ if (key_usage[k].name == NULL) {
+ /* FIXME: add check for numerical ku */
+ CM_DEBUG(ctx, "No matching key usage found.");
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *_comp = comp;
+ } else {
+ talloc_free(comp);
+ }
+
+ return ret;
+}
+
+static int parse_krb5_get_component_value(TALLOC_CTX *mem_ctx,
+ struct sss_certmap_ctx *ctx,
+ const char **cur,
+ struct component_list **_comp)
+{
+ struct component_list *comp = NULL;
+ int ret;
+
+ ret = get_comp_value(mem_ctx, ctx, cur, &comp);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to parse regexp.");
+ goto done;
+ }
+
+ ret = regcomp(&(comp->regexp), comp->val, REG_EXTENDED);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to parse regexp.");
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *_comp = comp;
+ } else {
+ talloc_free(comp);
+ }
+
+ return ret;
+}
+
+struct san_name {
+ const char *name;
+ enum san_opt san_opt;
+ bool is_string;
+} san_names[] = {
+ /* https://www.ietf.org/rfc/rfc3280.txt section 4.2.1.7 */
+ {"otherName", SAN_OTHER_NAME, false},
+ {"rfc822Name", SAN_RFC822_NAME,true},
+ {"dNSName", SAN_DNS_NAME, true},
+ {"x400Address", SAN_X400_ADDRESS, false},
+ {"directoryName", SAN_DIRECTORY_NAME, true},
+ {"ediPartyName", SAN_EDIPART_NAME, false},
+ {"uniformResourceIdentifier", SAN_URI, true},
+ {"iPAddress", SAN_IP_ADDRESS, true},
+ {"registeredID", SAN_REGISTERED_ID, true},
+ /* https://www.ietf.org/rfc/rfc4556.txt section 3.2.2 */
+ {"pkinitSAN", SAN_PKINIT, true},
+ /* https://support.microsoft.com/en-us/help/287547/object-ids-associated-with-microsoft-cryptography */
+ {"ntPrincipalName", SAN_NT, true},
+ /* both previous principal types */
+ {"Principal", SAN_PRINCIPAL, true},
+ {"stringOtherName", SAN_STRING_OTHER_NAME, true},
+ {NULL, SAN_END, false}
+};
+
+static int parse_krb5_get_san_option(TALLOC_CTX *mem_ctx,
+ struct sss_certmap_ctx *ctx,
+ const char **cur,
+ enum san_opt *option,
+ char **str_other_name_oid)
+{
+ char *end;
+ size_t c;
+ size_t len;
+
+ end = strchr(*cur, '>');
+ if (end == NULL) {
+ CM_DEBUG(ctx, "Failed to parse SAN option.");
+ return EINVAL;
+ }
+
+ len = end - *cur;
+
+ if (len == 0) {
+ c= SAN_PRINCIPAL;
+ } else {
+ for (c = 0; san_names[c].name != NULL; c++) {
+ if (strncasecmp(*cur, san_names[c].name, len) == 0) {
+ break;
+ }
+ }
+ if (san_names[c].name == NULL) {
+ if (is_dotted_decimal(*cur, len)) {
+ c = SAN_STRING_OTHER_NAME;
+ *str_other_name_oid = talloc_strndup(mem_ctx, *cur, len);
+ if (*str_other_name_oid == NULL) {
+ CM_DEBUG(ctx, "talloc_strndup failed.");
+ return ENOMEM;
+ }
+ } else {
+ CM_DEBUG(ctx, "Unknown SAN option.");
+ return EINVAL;
+ }
+ }
+ }
+
+ *option = san_names[c].san_opt;
+ *cur = end + 1;
+
+ return 0;
+}
+
+static int parse_krb5_get_san_value(TALLOC_CTX *mem_ctx,
+ struct sss_certmap_ctx *ctx,
+ const char **cur,
+ struct component_list **_comp)
+{
+ struct component_list *comp = NULL;
+ enum san_opt san_opt = SAN_PRINCIPAL;
+ int ret;
+ char *str_other_name_oid = NULL;
+
+ if (*(*cur - 1) == ':') {
+ ret = parse_krb5_get_san_option(mem_ctx, ctx, cur, &san_opt,
+ &str_other_name_oid);
+ if (ret != 0) {
+ goto done;
+ }
+ }
+
+ if (san_names[san_opt].is_string) {
+ ret = parse_krb5_get_component_value(mem_ctx, ctx, cur, &comp);
+ if (ret != 0) {
+ goto done;
+ }
+ } else {
+ ret = get_comp_value(mem_ctx, ctx, cur, &comp);
+ if (ret != 0) {
+ goto done;
+ }
+
+ if (comp->val != NULL) {
+ comp->bin_val = sss_base64_decode(comp, comp->val,
+ &comp->bin_val_len);
+ /* for some reasons the NSS version of sss_base64_decode might
+ * return a non-NULL value on error but len is still 0, so better
+ * check both. */
+ if (comp->bin_val == NULL || comp->bin_val_len == 0) {
+ CM_DEBUG(ctx, "Base64 decode failed.");
+ ret = EINVAL;
+ goto done;
+ }
+ }
+ }
+ comp->san_opt = san_opt;
+
+done:
+ if (ret == 0) {
+ comp->str_other_name_oid = talloc_steal(comp, str_other_name_oid);
+ *_comp = comp;
+ } else {
+ talloc_free(comp);
+ talloc_free(str_other_name_oid);
+ }
+
+ return ret;
+}
+
+int parse_krb5_match_rule(struct sss_certmap_ctx *ctx,
+ const char *rule_start,
+ struct krb5_match_rule **match_rule)
+{
+ const char *cur;
+ struct krb5_match_rule *rule;
+ struct component_list *comp;
+ int ret;
+
+ rule = talloc_zero(ctx, struct krb5_match_rule);
+ if (rule == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cur = rule_start;
+ /* check relation */
+ if (strncmp(cur, "&&", 2) == 0) {
+ rule->r = relation_and;
+ cur += 2;
+ } else if (strncmp(cur, "||", 2) == 0) {
+ rule->r = relation_or;
+ cur += 2;
+ } else {
+ rule->r = relation_and;
+ }
+
+ while (*cur != '\0') {
+ /* new component must start with '<' */
+ if (*cur != '<') {
+ CM_DEBUG(ctx, "Invalid KRB5 matching rule.");
+ ret = EINVAL;
+ goto done;
+ }
+ cur++;
+
+ if (strncmp(cur, "ISSUER>", 7) == 0) {
+ cur += 7;
+ ret = parse_krb5_get_component_value(rule, ctx, &cur, &comp);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(rule->issuer, comp);
+ } else if (strncmp(cur, "SUBJECT>", 8) == 0) {
+ cur += 8;
+ ret = parse_krb5_get_component_value(rule, ctx, &cur, &comp);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(rule->subject, comp);
+ } else if (strncmp(cur, "KU>", 3) == 0) {
+ cur += 3;
+ ret = parse_krb5_get_ku_value(rule, ctx, &cur, &comp);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(rule->ku, comp);
+ } else if (strncmp(cur, "EKU>", 4) == 0) {
+ cur += 4;
+ ret = parse_krb5_get_eku_value(rule, ctx, &cur, &comp);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(rule->eku, comp);
+ } else if (strncmp(cur, "SAN>", 4) == 0
+ || strncmp(cur, "SAN:", 4) == 0) {
+ cur += 4;
+ ret = parse_krb5_get_san_value(rule, ctx, &cur, &comp);
+ if (ret != 0) {
+ goto done;
+ }
+ DLIST_ADD(rule->san, comp);
+ } else {
+ CM_DEBUG(ctx, "Invalid KRB5 matching rule.");
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *match_rule = rule;
+ } else {
+ talloc_free(rule);
+ }
+
+ return ret;
+}
diff --git a/src/lib/certmap/sss_certmap_ldap_mapping.c b/src/lib/certmap/sss_certmap_ldap_mapping.c
new file mode 100644
index 000000000..c64c05b31
--- /dev/null
+++ b/src/lib/certmap/sss_certmap_ldap_mapping.c
@@ -0,0 +1,367 @@
+/*
+ SSSD
+
+ Library for rule based certificate to user mapping - LDAP mapping rules
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2017 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 "util/util.h"
+#include "util/cert.h"
+#include "util/crypto/sss_crypto.h"
+#include "lib/certmap/sss_certmap.h"
+#include "lib/certmap/sss_certmap_int.h"
+struct template_table {
+ const char *name;
+ const char **attr_name;
+ const char **conversion;
+};
+
+const char *empty[] = {NULL};
+const char *name_attr[] = {"short_name", NULL};
+const char *x500_conv[] = {"ad_x500", "ad", "ad_ldap",
+ "nss_x500", "nss", "nss_ldap", NULL};
+const char *bin_conv[] = {"bin", "base64", NULL};
+
+struct template_table template_table[] = {
+ {"issuer_dn", empty, x500_conv},
+ {"subject_dn", empty, x500_conv},
+ {"cert", empty, bin_conv},
+ {"subject_rfc822_name", name_attr, empty},
+ {"subject_dns_name", name_attr, empty},
+ {"subject_x400_address", empty, empty},
+ {"subject_directory_name", empty, empty},
+ {"subject_ediparty_name", empty, empty},
+ {"subject_uri", empty, empty},
+ {"subject_ip_address", empty, empty},
+ {"subject_registered_id", empty, empty},
+ {"subject_pkinit_principal", name_attr, empty},
+ {"subject_nt_principal", name_attr, empty},
+ {"subject_principal", name_attr, empty},
+ {NULL, NULL, NULL}};
+
+static int check_parsed_template(struct sss_certmap_ctx *ctx,
+ struct parsed_template *parsed)
+{
+ size_t n;
+ size_t a;
+ size_t c;
+ bool attr_name_valid = false;
+ bool conversion_valid = false;
+
+ for (n = 0; template_table[n].name != NULL; n++) {
+ if (strcmp(template_table[n].name, parsed->name) != 0) {
+ continue;
+ }
+
+ if (parsed->attr_name != NULL) {
+ for (a = 0; template_table[n].attr_name[a] != NULL; a++) {
+ if (strcmp(template_table[n].attr_name[a],
+ parsed->attr_name) == 0) {
+ attr_name_valid = true;
+ break;
+ }
+ }
+ } else {
+ attr_name_valid = true;
+ }
+
+ if (parsed->conversion != NULL) {
+ for (c = 0; template_table[n].conversion[c] != NULL; c++) {
+ if (strcmp(template_table[n].conversion[c],
+ parsed->conversion) == 0) {
+ conversion_valid = true;
+ break;
+ }
+ }
+ } else {
+ conversion_valid = true;
+ }
+
+ if (attr_name_valid && conversion_valid) {
+ return 0;
+ }
+ }
+
+ return EINVAL;
+}
+
+static int parse_template(TALLOC_CTX *mem_ctx, struct sss_certmap_ctx *ctx,
+ const char *template,
+ struct parsed_template **parsed_template)
+{
+ int ret;
+ struct parsed_template *parsed = NULL;
+ const char *dot;
+ const char *excl;
+ const char *p;
+
+ parsed = talloc_zero(mem_ctx, struct parsed_template);
+ if (parsed == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ dot = strchr(template, '.');
+ if (dot != NULL) {
+ p = strchr(dot + 1, '.');
+ if (p != NULL) {
+ CM_DEBUG(ctx, "Only one '.' allowed in template.");
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (dot == template) {
+ CM_DEBUG(ctx, "Missing name in template.");
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ excl = strchr(template, '!');
+ if (excl != NULL) {
+ p = strchr(excl + 1, '!');
+ if (p != NULL) {
+ CM_DEBUG(ctx, "Only one '!' allowed in template.");
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (excl == template) {
+ CM_DEBUG(ctx, "Missing name in template.");
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ if (excl != NULL && excl[1] != '\0') {
+ parsed->conversion = talloc_strdup(parsed, excl + 1);
+ if (parsed->conversion == NULL) {
+ CM_DEBUG(ctx, "Memory allocation failed.");
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ if (dot != NULL && dot[1] != '\0' && dot[1] != '!') {
+ if (excl == NULL) {
+ parsed->attr_name = talloc_strdup(parsed, dot + 1);
+ } else {
+ parsed->attr_name = talloc_strndup(parsed, dot + 1,
+ (excl - dot - 1));
+ }
+ if (parsed->attr_name == NULL) {
+ CM_DEBUG(ctx, "Memory allocation failed.");
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ if (dot != NULL) {
+ parsed->name = talloc_strndup(parsed, template, (dot - template));
+ } else if (excl != NULL) {
+ parsed->name = talloc_strndup(parsed, template, (excl - template));
+ } else {
+ parsed->name = talloc_strdup(parsed, template);
+ }
+ if (parsed->name == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = check_parsed_template(ctx, parsed);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Parse template invalid.");
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *parsed_template = parsed;
+ } else {
+ talloc_free(parsed);
+ }
+
+ return ret;
+}
+
+static int add_comp(struct sss_certmap_ctx *ctx, struct ldap_mapping_rule *rule,
+ const char *string, enum comp_type type)
+{
+ int ret;
+ struct ldap_mapping_rule_comp *comp;
+
+ comp = talloc_zero(rule, struct ldap_mapping_rule_comp);
+ if (comp == NULL) {
+ return ENOMEM;
+ }
+
+ comp->type = type;
+ comp->val = talloc_strdup(comp, string);
+ if (comp->val == NULL) {
+ talloc_free(comp);
+ return ENOMEM;
+ }
+
+ if (type == comp_template) {
+ ret = parse_template(comp, ctx, string, &comp->parsed_template);
+ if (ret != 0) {
+ talloc_free(comp);
+ return ret;
+ }
+ }
+
+ DLIST_ADD_END(rule->list, comp, struct ldap_mapping_rule_comp *);
+
+ return 0;
+}
+
+static int add_string(struct sss_certmap_ctx *ctx,
+ struct ldap_mapping_rule *rule, const char *string)
+{
+ return add_comp(ctx, rule, string, comp_string);
+}
+
+static int add_template(struct sss_certmap_ctx *ctx,
+ struct ldap_mapping_rule *rule, const char *string)
+{
+ return add_comp(ctx, rule, string, comp_template);
+}
+
+int parse_ldap_mapping_rule(struct sss_certmap_ctx *ctx,
+ const char *rule_start,
+ struct ldap_mapping_rule **mapping_rule)
+{
+ size_t c;
+ const char *cur;
+ char *tmp_string = NULL;
+ size_t tmp_string_size;
+ struct ldap_mapping_rule *rule = NULL;
+ int ret;
+ bool in_template = false;
+
+ rule = talloc_zero(ctx, struct ldap_mapping_rule);
+ if (rule == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tmp_string_size = strlen(rule_start) + 1;
+ tmp_string = talloc_zero_size(ctx, tmp_string_size);
+ if (tmp_string == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cur = rule_start;
+ c = 0;
+
+ while (*cur != '\0') {
+ if (c > tmp_string_size) {
+ CM_DEBUG(ctx, "Cannot parse mapping rule.");
+ ret = EIO;
+ goto done;
+ }
+ switch (*cur) {
+ case '{':
+ if (in_template) {
+ CM_DEBUG(ctx, "'{' not allowed in templates.");
+ ret = EINVAL;
+ goto done;
+ }
+ if (cur[1] == '{') {
+ /* Add only a single '{' to the output */
+ tmp_string[c] = '{';
+ c++;
+ cur += 2;
+ } else {
+ if (c != 0) {
+ ret = add_string(ctx, rule, tmp_string);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to add string.");
+ ret = EINVAL;
+ goto done;
+ }
+ memset(tmp_string, 0, tmp_string_size);
+ c = 0;
+ }
+ cur++;
+ in_template = true;
+ }
+ break;
+ case '}':
+ if (cur[1] == '}') {
+ if (in_template) {
+ CM_DEBUG(ctx, "'}}' not allowed in templates.");
+ ret = EINVAL;
+ goto done;
+ } else {
+ /* Add only a single '}' to the output */
+ tmp_string[c] = '}';
+ c++;
+ cur += 2;
+ }
+ } else {
+ ret = add_template(ctx, rule, tmp_string);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to add template.");
+ ret = EINVAL;
+ goto done;
+ }
+ memset(tmp_string, 0, tmp_string_size);
+ c = 0;
+ cur++;
+ in_template = false;
+ }
+ break;
+ default:
+ tmp_string[c] = *cur;
+ c++;
+ cur++;
+ }
+ }
+ if (in_template) {
+ CM_DEBUG(ctx, "Rule ended inside template.");
+ ret = EINVAL;
+ goto done;
+ }
+ if (c != 0) {
+ ret = add_string(ctx, rule, tmp_string);
+ if (ret != 0) {
+ CM_DEBUG(ctx, "Failed to add string.");
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ ret = 0;
+
+done:
+ if (ret == 0) {
+ *mapping_rule = rule;
+ } else {
+ talloc_free(rule);
+ }
+
+ talloc_free(tmp_string);
+
+ return ret;
+}