Bug 185080 - SSL_add_dir_cert_subjects_to_stack does not check for read access of file, breaking TLS enabled LDAP clients
Summary: SSL_add_dir_cert_subjects_to_stack does not check for read access of file, br...
Keywords:
Status: CLOSED NOTABUG
Alias: None
Product: Red Hat Enterprise Linux 4
Classification: Red Hat
Component: openldap
Version: 4.0
Hardware: All
OS: Linux
medium
medium
Target Milestone: ---
: ---
Assignee: Jan Safranek
QA Contact: Jay Turner
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2006-03-10 13:58 UTC by Peter Bieringer
Modified: 2015-01-08 00:12 UTC (History)
3 users (show)

Fixed In Version:
Doc Type: Bug Fix
Doc Text:
Clone Of:
Environment:
Last Closed: 2007-07-20 10:13:42 UTC
Target Upstream Version:
Embargoed:


Attachments (Terms of Use)

Description Peter Bieringer 2006-03-10 13:58:33 UTC
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...

Comment 1 Peter Bieringer 2006-03-10 14:44:05 UTC
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.

Comment 2 Tomas Mraz 2006-03-13 16:56:52 UTC
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.


Comment 3 Peter Bieringer 2006-03-14 06:51:50 UTC
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.

Comment 4 Jan Safranek 2007-07-20 10:13:42 UTC
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.


Note You need to log in before you can comment on or make changes to this bug.