diff -up ./doc/pam_pkcs11.xml.uid-attribute ./doc/pam_pkcs11.xml --- ./doc/pam_pkcs11.xml.uid-attribute 2008-08-28 12:12:46.000000000 -0700 +++ ./doc/pam_pkcs11.xml 2015-07-06 19:24:37.485287137 -0700 @@ -1679,6 +1679,8 @@ ldap_mapper configuration file shows lik base = "ou=People,o=example,c=com"; attribute = "userCertificate"; filter = "" + uid_attribute = "uid"; + attribute_map = "", ""; # SSL/TLS-Settings ssl = tls # tls_randfile = ... @@ -1757,6 +1759,43 @@ attribute. All certificates are utilized + +uid_attribute +The attribute in an LDAP entry which contains the +user's login name. When the module needs to determine the user's +login name from the information in the certificate, if this +option is set, the module can search for an LDAP entry which +contains the certificate that was read from the card in the +attribute named by the setting and read the +user's login name from this attribute. If not, the module must +iterate through all known users, checking for each user if the +certificate on the card matches that user. + + + + +attribute_map +A list of sets of conditions which can specify how the +module should locate an entry which corresponds to the user. +Each list item consists of a set of one or more conditions +consisting of an LDAP attribute and a certificate attribute, with +conditions in the same set being separated by a +character. An LDAP entry is considered to match a user if all of +the conditions are met. The recognized certificate attributes include + +"cn" a commonName attribute value from the certificate's subject +"subject" the certificate's entire subject name +"kpn" a Kerberos principal name subjectAltName value +"email" an rfc822Name subjectAltName value +"upn" a Microsoft Universal Principal Name subjectAltName value +"uid" a UID attribute value from the certificate's subject +"cert" the entire certificate + +If no is specified, the default method +is to search for entries which contain the card certificate in +the attribute named by the setting. + + filter diff -up ./doc/README.ldap_mapper.uid-attribute ./doc/README.ldap_mapper --- ./doc/README.ldap_mapper.uid-attribute 2008-08-28 12:12:46.000000000 -0700 +++ ./doc/README.ldap_mapper 2015-07-06 19:24:37.485287137 -0700 @@ -27,6 +27,10 @@ pam_pkcs11.conf: attribute = "userCertificate"; # Searchfilter for user entry. Must only let pass user entry for the login user. filter = "(&(objectClass=posixAccount)(uid=%s))" + # Attribute of user entry which contains the user's login name (optional) + uid_attribute = "uid"; + # List of sets of ldap attribute / cert attribute pairs (optional) + attribute_map = "uid=uid&mail=email", "krbprincipalname=upn", "userCertificate;binary=cert"; } (...) diff -up ./src/mappers/ldap_mapper.c.uid-attribute ./src/mappers/ldap_mapper.c --- ./src/mappers/ldap_mapper.c.uid-attribute 2015-07-06 19:24:37.479287249 -0700 +++ ./src/mappers/ldap_mapper.c 2015-07-06 20:13:01.578027801 -0700 @@ -101,14 +101,12 @@ static const char *binddn=""; static const char *passwd=""; static const char *base="ou=People,o=example,c=com"; static const char *attribute="userCertificate"; +static const char *uid_attribute; +static const scconf_list *attribute_map; static const char *filter="(&(objectClass=posixAccount)(uid=%s)"; static int searchtimeout=20; static int ignorecase=0; -#ifdef HAVE_NSS -static X509 **ldap_x509; -#else -static const X509 **ldap_x509; -#endif +static char *uid_attribute_value; static int certcnt=0; static ldap_ssl_options_t ssl_on = SSL_OFF; @@ -586,31 +584,335 @@ static int ldap_add_uri (char **uris, co return 0; } +/* Build a filter suitable for locating the entry for the named user. */ +static void +ldap_x509_as_binary(X509 *x509, unsigned char **der, size_t *der_len) +{ +#ifdef HAVE_NSS + *der_len = 0; + *der = malloc(x509->derCert.len); + if (*der != NULL) { + *der_len = x509->derCert.len; + memcpy(*der, x509->derCert.data, *der_len); + } +#else + unsigned char *p = NULL, *q; + int len; + + *der_len = 0; + *der = NULL; + len = i2d_X509(x509, NULL); + if (len > 0) { + p = malloc(len); + if (p != NULL) { + q = p; + if (i2d_X509(x509, &p) == len) { + *der = q; + *der_len = p - q; + } + } + } +#endif +} + +/* Encode anything that isn't printable in the binary string as a hex escape, + * and return the result as a NUL-terminated string. */ +static char * +ldap_encode_escapes(const unsigned char *binary, size_t length) +{ + char *ret; + unsigned int i, j; + + ret = malloc(length * 3 + 1); + if (ret == NULL) { + DBG("ldap_encode_escapes(): out of memory"); + return NULL; + } + for (i = 0, j = 0; i < length; i++) { + if (((binary[i] >= '0') && (binary[i] <= '9')) || + ((binary[i] >= 'a') && (binary[i] <= 'z')) || + ((binary[i] >= 'A') && (binary[i] <= 'Z'))) { + ret[j++] = binary[i]; + } else { + ret[j++] = '\\'; + ret[j++] = "0123456789abcdef"[(binary[i] >> 4) & 0x0f]; + ret[j++] = "0123456789abcdef"[binary[i] & 0x0f]; + } + } + ret[j] = '\0'; + return ret; +} + +/* Build a subfilter for matching the passed-in certificate against the + * configured attribute. */ +static char * +ldap_build_default_cert_filter(X509 *x509) +{ + char *buf, *cert; + unsigned char *der; + size_t buf_len, der_len; + + ldap_x509_as_binary(x509, &der, &der_len); + if (der == NULL) { + DBG("ldap_build_cert_filter(): failed to encode certificate"); + return NULL; + } + cert = ldap_encode_escapes(der, der_len); + free(der); + if (cert == NULL) { + DBG("ldap_build_cert_filter(): failed to escape certificate"); + return NULL; + } + buf_len = 1 + strlen(attribute) + 1 + strlen(cert) + 2; + buf = malloc(buf_len); + if (buf == NULL) { + DBG("ldap_build_cert_filter(): out of memory"); + free(cert); + return NULL; + } + snprintf(buf, buf_len, "(%s=%s)", attribute, cert); + free(cert); + return buf; +} + +/* Build a subfilter for matching the passed-in certificate using the mapping + * information, or against the configured attribute. */ +static char * +ldap_build_partial_cert_filter(const char *map, X509 *x509) +{ + char *buf, *certs[2] = {NULL, NULL}, **values = NULL; + unsigned char *der; + const char *p, *q; + size_t buf_len, der_len, len, n; + int i; + + p = strchr(map, '='); + if (p == NULL) { + DBG1("ldap_build_cert_filter(): error parsing filter '%s'", + map); + return NULL; + } + q = p + strcspn(p, "&"); + if (strncmp(p + 1, "cn", q - p - 1) == 0) { + values = cert_info(x509, CERT_CN, ALGORITHM_NULL); + } else + if (strncmp(p + 1, "subject", q - p - 1) == 0) { + values = cert_info(x509, CERT_SUBJECT, ALGORITHM_NULL); + } else + if (strncmp(p + 1, "kpn", q - p - 1) == 0) { + values = cert_info(x509, CERT_KPN, ALGORITHM_NULL); + } else + if (strncmp(p + 1, "email", q - p - 1) == 0) { + values = cert_info(x509, CERT_EMAIL, ALGORITHM_NULL); + } else + if (strncmp(p + 1, "upn", q - p - 1) == 0) { + values = cert_info(x509, CERT_UPN, ALGORITHM_NULL); + } else + if (strncmp(p + 1, "uid", q - p - 1) == 0) { + values = cert_info(x509, CERT_UID, ALGORITHM_NULL); + } else + if (strncmp(p + 1, "cert", q - p - 1) == 0) { + ldap_x509_as_binary(x509, &der, &der_len); + if (der == NULL) { + DBG("ldap_build_cert_filter(): error encoding " + "certificate"); + return NULL; + } + certs[0] = ldap_encode_escapes(der, der_len); + free(der); + if (certs[0] == NULL) { + DBG("ldap_build_cert_filter(): error escaping " + "certificate"); + return NULL; + } + values = certs; + } else { + DBG2("ldap_build_cert_filter(): unrecognized certificate " + "attribute '%.*s'", q - p - 1, p + 1); + return NULL; + } + if (values == NULL) { + DBG2("ldap_build_cert_filter(): no values for certificate " + "attribute '%.*s'", q - p - 1, p + 1); + return NULL; + } + DBG4("ldap_build_cert_filter(): building subfilter '%.*s'='%.*s'", + p - map, map, q - p - 1, p + 1); + for (n = 0, buf_len = 0; values[n] != NULL; n++) { + buf_len++; + buf_len += (p - map); + buf_len++; + buf_len += strlen(values[n]); + buf_len++; + } + buf_len += (n > 1) ? 4 : 1; + buf = malloc(buf_len); + if (buf == NULL) { + DBG("ldap_build_cert_filter(): out of memory"); + free(certs[0]); + return NULL; + } + i = 0; + if (n > 1) { + strcpy(buf, "(|"); + i += 2; + } + for (n = 0; values[n] != NULL; n++) { + buf[i++] = '('; + memcpy(buf + i, map, p - map); + i += (p - map); + buf[i++] = '='; + len = strlen(values[n]); + memcpy(buf + i, values[n], len); + i += len; + buf[i++] = ')'; + } + if (n > 1) { + buf[i++] = ')'; + } + buf[i] = '\0'; + free(certs[0]); + return buf; +} + +/* Build a filter for matching the passed-in certificate using the mapping + * information, or against the configured attribute. */ +static char * +ldap_build_cert_filter(const char *map, X509 *x509) +{ + char *buf = NULL, *tmp, *sub; + const char *p; + size_t length, n; + + if (map == NULL) { + DBG("ldap_build_cert_filter(): building default filter"); + return ldap_build_default_cert_filter(x509); + } + DBG1("ldap_build_cert_filter(): building filter '%s'", map); + p = map; + n = 0; + while (*p != '\0') { + sub = ldap_build_partial_cert_filter(p, x509); + if (sub == NULL) { + free(buf); + return NULL; + } + if (buf != NULL) { + length = strlen(buf) + strlen(sub) + 1; + tmp = malloc(length); + if (tmp == NULL) { + free(buf); + free(sub); + return NULL; + } + snprintf(tmp, length, "%s%s", buf, sub); + free(buf); + free(sub); + buf = tmp; + } else { + buf = sub; + } + n++; + p += strcspn(p, "&"); + p += strspn(p, "&"); + } + if (n > 1) { + length = strlen(buf) + 4; + tmp = malloc(length); + if (tmp == NULL) { + free(buf); + return NULL; + } + snprintf(tmp, length, "(&%s)", buf); + free(buf); + buf = tmp; + } + return buf; +} + +/* Build a filter suitable for locating the entry for the named user. */ +static char * +ldap_build_filter(const char *filter, const char *login, const char *map, + X509 *x509) +{ + char *buf, *user_filter, *escaped, *cert_filter; + unsigned char *der; + size_t buf_len, user_filter_len, der_len; + unsigned int i; + + /* If no user name is specified, this is a search across all users. */ + if (login != NULL) { + escaped = ldap_encode_escapes(login, strlen(login)); + } else { + escaped = strdup("*"); + } + if (escaped == NULL) { + DBG1("ldap_build_filter(): error escaping user name '%s'", + login); + return NULL; + } + + /* Build a user filter using the supplied filter and user name. */ + user_filter_len = strlen(filter) + strlen(escaped) + 1; + user_filter = malloc(user_filter_len); + if (user_filter == NULL) { + DBG("ldap_build_filter(): out of memory for user filter"); + free(escaped); + return NULL; + } + snprintf(user_filter, user_filter_len, filter, escaped); + free(escaped); + + /* Build the part of the filter that's specific to the certificate. */ + cert_filter = ldap_build_cert_filter(map, x509); + if (cert_filter == NULL) { + DBG("ldap_build_filter(): error building certificate filter"); + free(user_filter); + return NULL; + } + + /* Build a filter combining the user filter and the certificate. */ + buf_len = 3 + strlen(user_filter) + 2 + 2 + strlen(cert_filter) + 2; + buf = malloc(buf_len); + if (buf != NULL) { + if (filter[0] == '(') { + snprintf(buf, buf_len, "(&%s%s)", user_filter, + cert_filter); + } else { + snprintf(buf, buf_len, "(&(%s)%s)", user_filter, + cert_filter); + } + } else { + DBG("ldap_build_filter(): out of memory"); + } + + free(user_filter); + free(cert_filter); + return buf; +} + /** * Get certificate from LDAP-Server. */ -static int ldap_get_certificate(const char *login) { +static int ldap_get_certificate(const char *login, X509 *x509) { LDAP *ldap_connection; int ret, entries; LDAPMessage *res; LDAPMessage *entry; - struct berval **bvals = NULL; + struct berval **bvals = NULL, *bv; BerElement *ber = NULL; - char *name = NULL; - char filter_str[100]; - char *attrs[2]; + char *filter_str; + char *attrs[3]; int rv; -#ifndef HAVE_NSS - void *bv_val; -#endif char uri[4096]; char uribuf[4096]; char *uris[LDAP_CONFIG_URI_MAX + 1]; const char *p; int current_uri = 0, start_uri = 0; + const scconf_list *mapping; char *buffer; size_t buflen; @@ -618,14 +920,16 @@ static int ldap_get_certificate(const ch uris[0] = NULL; attrs[0] = (char *)attribute; - attrs[1] = NULL; - - DBG1("ldap_get_certificate(): begin login = %s", login); - - /* Put the login to the %s in Filterstring */ - snprintf(filter_str, sizeof(filter_str), filter, login); + attrs[1] = (char *)uid_attribute; + attrs[2] = NULL; + free((char *)uid_attribute_value); + uid_attribute_value = NULL; - DBG1("ldap_get_certificate(): filter_str = %s", filter_str); + if (login != NULL) { + DBG1("ldap_get_certificate(): begin login = %s", login); + } else { + DBG("ldap_get_certificate(): begin login unknown"); + } /* parse and split URI config entry */ buffer = uribuf; @@ -709,14 +1013,55 @@ static int ldap_get_certificate(const ch server again. Perhaps create a state file/smem/etc. ? */ - rv = ldap_search_s( - ldap_connection, - base, - sscope[scope], - filter_str, - attrs, - 0, - &res); + /* Search for matching entries. */ + for (mapping = attribute_map;; mapping = mapping->next) { + /* Walk the list of mappings, and if we're out of those, let + * the ldap_build_filter() function just build one that uses + * the certificate. */ + if (mapping != NULL) { + DBG1("ldap_get_certificate(): building filter_str " + "from template '%s'", mapping->data); + } else { + DBG("ldap_get_certificate(): building default " + "filter_str"); + } + filter_str = ldap_build_filter(filter, login, + mapping ? mapping->data : NULL, + x509); + if (filter_str == NULL) { + DBG("ldap_get_certificate(): error building filter_str"); + if (mapping) { + continue; + } + break; + } + DBG1("ldap_get_certificate(): searching with filter_str = %s", + filter_str); + rv = ldap_search_s(ldap_connection, + base, + sscope[scope], + filter_str, + attrs, + 0, + &res); + free(filter_str); + /* The first successful search means we're done. */ + if ((rv == LDAP_SUCCESS) && + (ldap_count_entries(ldap_connection, res) > 0)) { + DBG("ldap_get_certificate(): found an entry"); + break; + } + DBG("ldap_get_certificate(): no matching entries"); + /* If this was the fallback (cert-only) search, we're done. */ + if (mapping == NULL) { + break; + } + } + if (filter_str == NULL) { + DBG("ldap_get_certificate(): unable to build any filter_str"); + return(-8); + } + if ( rv != LDAP_SUCCESS ) { DBG1("ldap_search_s() failed: %s", ldap_err2string(rv)); ldap_unbind_s(ldap_connection); @@ -742,65 +1087,35 @@ static int ldap_get_certificate(const ch return(-4); } - /* Only first attribute is used. See comment above... */ - if ( NULL == (name = ldap_first_attribute(ldap_connection, res, &ber))){ - DBG("ldap_first_attribute() failed (rc=%d)"); - ldap_unbind_s(ldap_connection); - return(-5); - } - DBG1("attribute name = %s", name); - - bvals = ldap_get_values_len(ldap_connection, entry, name); + /* Count the number of certificates in the entry. */ + DBG1("attribute name = %s", attribute); + bvals = ldap_get_values_len(ldap_connection, entry, attribute); certcnt = ldap_count_values_len(bvals); - DBG1("number of user certificates = %d", certcnt); + ldap_value_free_len(bvals); - ldap_x509 = malloc(sizeof(X509*) * certcnt ); - if (NULL == ldap_x509) - { - DBG("not enough memory"); - return(-7); - } + if (uid_attribute != NULL) { + /* Try to retrieve the user's login name from the + * specified attribute in the entry. */ + bvals = ldap_get_values_len(ldap_connection, entry, + uid_attribute); + DBG2("number of user names ('%s' values) = %d", + uid_attribute, ldap_count_values_len(bvals)); + if (ldap_count_values_len(bvals) == 1) { + bv = bvals[0]; + uid_attribute_value = malloc(bv->bv_len + 1); + if (uid_attribute_value != NULL) { + memcpy(uid_attribute_value, bv->bv_val, + bv->bv_len); + uid_attribute_value[bv->bv_len] = '\0'; + } + } + ldap_value_free_len(bvals); + } rv = 0; - while(rv < certcnt ) - { - /* SaW: not nifty, but otherwise gcc doesn't optimize */ -#ifdef HAVE_NSS - { - SECItem derdata; - derdata.data = bvals[rv]->bv_val; - derdata.len = bvals[rv]->bv_len; - ldap_x509[rv] = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), - &derdata, NULL, 0, 1); - } -#else - bv_val = &bvals[rv]->bv_val; - ldap_x509[rv] = d2i_X509(NULL, ((const unsigned char **) bv_val), bvals[rv]->bv_len); -#endif - if (NULL == ldap_x509[rv]) { - DBG1("d2i_X509() failed for certificate %d", rv); - free(ldap_x509); -#ifdef HAVE_NSS - { - for (rv=0; rvnext) { + DBG1("attribute_map = %s", attribute_map->data); + } DBG1("filter = %s", filter); DBG1("searchtimeout = %d", searchtimeout); DBG1("ssl_on = %d", ssl_on); @@ -895,35 +1217,16 @@ static int ldap_mapper_match_user(X509 * int match_found = 0; int i=0; - if ( 1 != ldap_get_certificate(login)){ + if ( 1 != ldap_get_certificate(login, x509)){ DBG("ldap_get_certificate() failed"); match_found = 0; } else { - /* TODO: maybe compare public keys instead of hashes */ - while( i