Description of problem: During tracking down, why a LDAP enabled postfix cannot lookup via TLS enabled LDAP client I found that openssl function SSL_add_dir_cert_subjects_to_stack is very optimistic relating to the files found in a specified directory. Version-Release number of selected component (if applicable): openssl-0.9.7a-43.4 also openssl-0.9.7.i (latest 0.9.7 release) also openssl-0.9.8.a (latest 0.9.8 release) in conjunction with openldap-2.2.13-4 also openldap-2.2.30 (latest 2.2 release) also openldap-2.3.20 (latest 2.3 release) How reproducible: Always Steps to Reproduce: 1. Create a directory for local PKI storage, e.g. /etc/pki 2. Store local CA, local server certificates and local keys into this directory 3. Set proper permissions to keys, e.g. chmod o-rwx *.key.pem # ll /etc/pki/ total 120 lrwxrwxrwx 1 root root 23 Sep 14 15:42 592fcc04.0 -> ca.crt -r--r--r-- 1 root root 1834 Sep 14 15:39 ca.crt -r--r--r-- 1 root root 2529 Sep 14 15:39 AE-CA-Class4-2005-A.crt -r-------- 1 root root 5875 Sep 14 15:45 ca+cert+key.pem -r--r--r-- 1 root root 4196 Sep 14 16:07 ca+cert.pem -r--r--r-- 1 root root 2362 Sep 14 15:37 cert.crt -r--r----- 1 root root 4041 Sep 14 15:50 cert+key.pem -r--r----- 1 root ldap 1679 Sep 14 15:37 key.pem (note: group=ldap for LDAP server, which reads key file for server TLS after changing the user to "ldap") 4. Configure /etc/openldap/slapd.conf for TLS like TLSCACertificateFile /etc/pki/ca.crt TLSCACertificatePath /etc/pki TLSCertificateFile /etc/pki/crt.crt TLSCertificateKeyFile /etc/pki/key.pem 5. Configure /etc/openldap/ldap.conf related URI ldaps://ldapserver/ #URI ldap://ldapserver/ BASE dc=example,dc=com TLS_CACERTDIR /etc/pki # <- important! 6. Try ldapsearch usig TLS as root: ldapsearch -x -H ldaps://ldapserver/ Result: working 7. Try ldapsearch using TLS as normal user: $ ldapsearch -x -v -H ldaps://ldapserver Result: not working, message: ldap_initialize( ldaps://ldapserver ) ldap_bind: Can't contact LDAP server (-1) Using strace shows me: open("/etc/pki/key.pem", O_RDONLY) = -1 EACCES (Permission denied) close(4) = 0 write(2, "ldap_bind: Can\'t contact LDAP server (-1)\n", 42ldap_bind: Can't contact LDAP server (-1) ) = 42 exit_group(1) = ? Process 15652 detached Ooops, why will ldapsearch open this key file??? Ok, thanks to opensource we are now now digging through the code: A) openldap: openldap-2.2.13/libraries/libldap/tls.c (same code in newer/latest versions) static STACK_OF(X509_NAME) * get_ca_list( char * bundle, char * dir ) { STACK_OF(X509_NAME) *ca_list = NULL; if ( bundle ) { ca_list = SSL_load_client_CA_file( bundle ); } #if defined(HAVE_DIRENT_H) || defined(dirent) if ( dir ) { int freeit = 0; if ( !ca_list ) { ca_list = sk_X509_NAME_new_null(); freeit = 1; } if ( !SSL_add_dir_cert_subjects_to_stack( ca_list, dir ) && freeit ) { sk_X509_NAME_free( ca_list ); ca_list = NULL; } } #endif return ca_list; } Ok, openldap calls an openssl functions to read all certificates in "dir" and add to "ca_list". Note that here is also an opportunity for improvements to be more graceful, if SSL_load_client_CA_file works but SSL_add_dir_cert_subjects_to_stack fails. Currently, the whole ca_list ist dropped. B) openssl: openssl-0.9.7a/ssl/ssl_cert.c (same code in newer/latest versions) /*! * Add a directory of certs to a stack. * \param stack the stack to append to. * \param dir the directory to append from. All files in this directory will be * examined as potential certs. Any that are acceptable to * SSL_add_dir_cert_subjects_to_stack() that are not already in the stack will be * included. * \return 1 for success, 0 for failure. Note that in the case of failure some * certs may have been added to \c stack. */ #ifndef OPENSSL_SYS_WIN32 #ifndef OPENSSL_SYS_VMS /* XXXX This may be fixed in the future */ #ifndef OPENSSL_SYS_MACINTOSH_CLASSIC /* XXXXX: Better scheme needed! */ int SSL_add_dir_cert_subjects_to_stack(STACK_OF(X509_NAME) *stack, const char *dir) { DIR *d; struct dirent *dstruct; int ret = 0; CRYPTO_w_lock(CRYPTO_LOCK_READDIR); d = opendir(dir); /* Note that a side effect is that the CAs will be sorted by name */ if(!d) { SYSerr(SYS_F_OPENDIR, get_last_sys_error()); ERR_add_error_data(3, "opendir('", dir, "')"); SSLerr(SSL_F_SSL_ADD_DIR_CERT_SUBJECTS_TO_STACK, ERR_R_SYS_LIB); goto err; } while((dstruct=readdir(d))) { char buf[1024]; int r; if(strlen(dir)+strlen(dstruct->d_name)+2 > sizeof buf) { SSLerr(SSL_F_SSL_ADD_DIR_CERT_SUBJECTS_TO_STACK,SSL_R_PATH_TOO_LONG); goto err; } r = BIO_snprintf(buf,sizeof buf,"%s/%s",dir,dstruct->d_name); if (r <= 0 || r >= sizeof buf) goto err; if(!SSL_add_file_cert_subjects_to_stack(stack,buf)) goto err; } ret = 1; err: if (d) closedir(d); CRYPTO_w_unlock(CRYPTO_LOCK_READDIR); return ret; } #endif #endif Oooppppssss!!!! Looks like developers were very optimistic that after reading the directory into "d", all files are readable by the user which executes the program. Only a test whether path is too long was made. Afterwards, file is given to BIO_snprintf for reading, which fails on non-readable files. There is an additional check missing! Additional info: It looks like that a simple # touch /path/to/my/pki/certs/dummy # chmod go-rwx /path/to/my/pki/certs/dummy would stop the use of TLS enabled clients executed by non-root users (e.g. here: postfix) at all - that's bad! BTW: If I temporary change permissions to 444 to all files in this directory, ldapsearch works for non-root users TLS enabled. Please notify openldap and openssl developers for a) improve the code and b) be more graceful in some circumstances...
Note: as a temporary workaround I created /etc/pki/certs and put softlinks to certificates and hashes into and specify this in ldap.conf. Now it will work.
This is wrong usage of the TLS_CACERTDIR directive. It should point to a directory with CA certificates only. All of them should be readable files (no private keys and so on). I don't think it is a problem to return error if some file is not readable in such directory. If you disagree use OpenSSL request tracker to report this upstream. http://www.openssl.org/support/rt2.html On the other hand OpenLDAP server should report somehow (in syslog?) that SSL_add_dir_cert_subjects_to_stack failed thus allowing user to find out what's wrong with his configuration.
As I wrote, this is a problen caused by use of the LDAP *client* library. LDAP server isn't involved in this case (but has also a problem: if PEM file with secret key can't be readed, slapd, will silently fail to start without any major error notice). So this library has to return a proper error to application or be more graceful. I will open a ticket on OpenSSL, because I think an additional check is for no cost but can solve "strange" problems, which can't be detect easily without using strace.
If you use TLS_CACERTDIR directive, you must put only certificates to the specified directory. These certificates must be obviously readable by all ldap clients, otherwise you cannot make TLS connections. SSL library (instructed by ldap client lib.) opens all files in the TLS_CACERTDIR directory, assuming that these files are certificates and are readable. The library correctly fails, if any of these files are not readable - it cannot fulfill your request to use the certificates. Maybe the error produced by ldap client library is not easily understandable, but with some patience (and the right log level) you can find following messages in the log: TLS: could not load client CA list (file:`',dir:`/etc/openldap/cacerts'). TLS: error:0200100D:system library:fopen:Permission denied bss_file.c:259 I do not know if postfix stores somewhere ldap/openssl logs, openldap client application do (e.g. ldapsearch -d 7 -ZZ ...) Regarding ldap server: init script will now display proper information in case of permission problems with keys/certificates, see bug #171165.