Why does ldapsearch fail to accept Subject Alternative Name of LDAP server SSL certificate?

Solution Verified - Updated -

Environment

  • Red Hat Enterprise Linux 6.

Issue

  • A LDAP server can be reached by multiple DNS names (e.g. ldaptest.example.com and ldapprod.example.com).

  • The SSL certificate for the LDAP server includes Subject Alternative Name (subjectAltName) extension using the * wildcard character for a partial match of the left-most DNS label (e.g. ldap*.example.com).

$ echo "" | openssl s_client -CAfile ExampleRootCA.pem -connect 1.2.3.4:636 2>/dev/null | openssl x509 -text -noout | egrep -A 1 "(Subject Alternative Name|Subject:)"
        Subject: CN=ldap.example.com, C=COM, O=EXAMPLE
        Subject Public Key Info:
--
            X509v3 Subject Alternative Name: 
                DNS:ldap.example.com, DNS:ldap*.example.com, DNS:ldap*
  • ldapsearch using SSL (LDAPS) fails to reach LDAP server when providing a hostname included in the Subject Alternative Name of the SSL certificate:
# LDAPTLS_CACERT=ExampleRootCA.pem ldapsearch -x -H ldaps://ldaptest.example.com:636 -d 1 -b ou=exampleuser,o=example,c=com cn=m123456 displayname | grep displayname
ldap_url_parse_ext(ldaps://ldaptest.example.com:636)
ldap_create
ldap_url_parse_ext(ldaps://ldaptest.example.com:636/??base)
ldap_sasl_bind
ldap_send_initial_request
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP ldaptest.example.com:636
ldap_new_socket: 3
ldap_prepare_socket: 3
ldap_connect_to_host: Trying 1.2.3.4:636
ldap_pvt_connect: fd: 3 tm: -1 async: 0
TLS: certdb config: configDir='/etc/openldap' tokenDescription='ldap(0)' certPrefix='cacerts' keyPrefix='cacerts' flags=readOnly
TLS: cannot open certdb '/etc/openldap', error -8018:Unknown PKCS #11 error.
TLS: loaded CA certificate file ExampleRootCA.pem.
TLS: could not get info about the CA certificate directory /etc/openldap/cacerts - error -5950:File not found.
TLS: certificate [O=EXAMPLE,C=COM,CN=ldap.example.com] is valid
TLS certificate verification: subject: O=EXAMPLE,C=COM,CN=ldap.example.com, issuer: O=EXAMPLE,C=COM,CN=Services CA, cipher: AES-256, security level: high, secret key bits: 256, total key bits: 256, cache hits: 0, cache misses: 0, cache not reusable: 0
TLS: hostname (ldaptest.example.com) does not match common name in certificate (ldap.example.com).
TLS: can't connect: TLS: hostname does not match CN in peer certificate.
ldap_err2string
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)

Resolution

The * wildcard character can only be used to match the left-most DNS label entirely (no partial matching).
Consequently *.example.com is a valid usage of the * wildcard character but ldap*.example.com is not.

Please consider either:

  • specifying the full list of all hostnames:
            X509v3 Subject Alternative Name: 
                DNS:ldaptest.example.com, DNS:ldapprod.example.com
  • adding an additional domain level such as .ldap.example.com thus allowing matching of all hostnames test.ldap.example.com and prod.ldap.example.com:
            X509v3 Subject Alternative Name: 
                DNS:*.ldap.example.com

Root Cause

   The '*' (ASCII 42) wildcard character is allowed in subjectAltName
   values of type dNSName, and then only as the left-most (least
   significant) DNS label in that value.  This wildcard matches any
   left-most DNS label in the server name.  That is, the subject
   *.example.com matches the server names a.example.com and
   b.example.com, but does not match example.com or a.b.example.com.
  • Source code of OpenLDAP reveals it expects the wildcard to be a match for a full subdomain rather than a partial one:
2746                                 /* is this an exact match? */
2747                                 if ( nlen == hlen && !strncasecmp( name, host, nlen )) {
2748                                         ret = LDAP_SUCCESS;
2749                                         break;
2750                                 }
2751
2752                                 /* is this a wildcard match? */
2753                                 if ( domain && host[0] == '*' && host[1] == '.' &&
2754                                         dlen == hlen-1 && !strncasecmp( domain, host+1, dlen )) {
2755                                         ret = LDAP_SUCCESS;
2756                                         break;
2757                                 }

Line 2753 reveals OpenLDAP expects first character in host (0) to be the wildcard "*" and next character (1) to be a dot "." so the * wildcard must be the first character in the subjectAltName entry and must be followed by a dot "." thus matching the entire left-most DNS label.

  • The same applies to the subject CN (as per line 2800):
2798                                 if ( av->len == nlen && !strncasecmp( name, (char *)av->data, nlen )) {
2799                                         ret = LDAP_SUCCESS;
2800                                 } else if ( av->data[0] == '*' && av->data[1] == '.' &&
2801                                         domain && dlen == av->len - 1 && !strncasecmp( domain,
2802                                                 (char *)(av->data+1), dlen )) {
2803                                         ret = LDAP_SUCCESS;
2804                                 } else {
2805                                         int len = av->len;
2806                                         if ( len >= sizeof(buf) )
2807                                                 len = sizeof(buf)-1;
2808                                         memcpy( buf, av->data, len );
2809                                         buf[len] = '\0';
2810                                 }

This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.

Comments