Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I force Python LDAP to validate/verify an SSL certificate when using .start_tls_s()

I've been trying to use Python-LDAP (version 2.4.19) under MacOS X 10.9.5 and Python 2.7.9

I want to validate my connection to a given LDAP server after I've called the .start_tls_s() (or to have the method raise and exception if the certificate cannot be verified). (I'd also like to check for a CRL, but that's a different matter).

Here's my code:

#!python
#!/usr/bin/env python
import ConfigParser, os, sys
import ldap

CACERTFILE='./ca_ldap.bad'
## CACERTFILE='./ca_ldap.crt'

config = ConfigParser.ConfigParser()
config.read(os.path.expanduser('~/.ssh/creds.ini'))
uid = config.get('LDAP', 'uid')
pwd = config.get('LDAP', 'pwd')
svr = config.get('LDAP', 'svr')
bdn = config.get('LDAP', 'bdn')

ld = ldap.initialize(svr)
ld.protocol_version=ldap.VERSION3
ld.set_option(ldap.OPT_DEBUG_LEVEL, 255 )
ld.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
ld.set_option(ldap.OPT_X_TLS_CACERTFILE, CACERTFILE)
ld.set_option(ldap.OPT_X_TLS_DEMAND, True )
ld.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD)

## From: https://stackoverflow.com/a/7810308/149076
## and : http://python-ldap.cvs.sourceforge.net/viewvc/python-ldap/python-ldap/Demo/initialize.py?revision=1.14&view=markup

ld.start_tls_s()

for each in dir(ldap):
    if 'OPT_X_TLS' in each:
        try:
            print '\t*** %s: %s' % (each, ld.get_option((getattr(ldap, each))))
        except Exception, e:
            print >> sys.stderr, '... Except %s: %s\n' % (each, e)

ld.simple_bind_s(uid, pwd)
results = ld.search_s(bdn, ldap.SCOPE_SUBTREE)

print 'Found %s entries under %s' % (len(results), bdn)
sys.exit()

As noted in the comments I have copied most of this from https://stackoverflow.com/a/7810308/149076 and from http://python-ldap.cvs.sourceforge.net/viewvc/python-ldap/python-ldap/Demo/initialize.py?revision=1.14&view=markup ... though I have tried many variations and sequences of this.

As shown I have two files which represent a bad certificate and one which should work (it's actually taken from one of our systems which is configured to run sssd (System Security Services Daemon) which is presumed to be checking this correctly.

In the "bad" copy I've simply replaced the first character of each key line with the letter 'x' on the assumption that this would corrupt the CA key and cause any code attempting to verify a chain of signatures to fail.

However, it seems that the Python LDAP code ignores this; even if I set it to /dev/null or an entirely bogus path my code still runs, still binds to the LDAP server and still completes my search request.

So the question is, how do I get this to "fail" as intended (or, more broadly, how do I prevent my code from being vulnerable to MITM (Mallory) attacks?

If it's of any consequence in this discussion here's my OpenSSL version:

$ openssl version
OpenSSL 0.9.8za 5 Jun 2014

The LDAP server is running OpenLDAP, but I don't know any details about its version nor configuration.

Here's sample output from my code:

    *** OPT_X_TLS: 0
    *** OPT_X_TLS_ALLOW: 0
    *** OPT_X_TLS_CACERTDIR: None
    *** OPT_X_TLS_CACERTFILE: /bogus/null
    *** OPT_X_TLS_CERTFILE: None
    *** OPT_X_TLS_CIPHER_SUITE: None
    *** OPT_X_TLS_CRLCHECK: 0
    *** OPT_X_TLS_CRLFILE: None
    *** OPT_X_TLS_CRL_ALL: 1
    *** OPT_X_TLS_CRL_NONE: {'info_version': 1, 'extensions': ('X_OPENLDAP', 'THREAD_SAFE', 'SESSION_THREAD_SAFE', 'OPERATION_THREAD_SAFE', 'X_OPENLDAP_THREAD_SAFE'), 'vendor_version': 20428, 'protocol_version': 3, 'vendor_name': 'OpenLDAP', 'api_version': 3001}
    *** OPT_X_TLS_CRL_PEER: 3
... Except OPT_X_TLS_CTX: unknown option 24577

    *** OPT_X_TLS_DEMAND: 1
    *** OPT_X_TLS_DHFILE: None
    *** OPT_X_TLS_HARD: 3
    *** OPT_X_TLS_KEYFILE: None
    *** OPT_X_TLS_NEVER: {'info_version': 1, 'extensions': ('X_OPENLDAP', 'THREAD_SAFE', 'SESSION_THREAD_SAFE', 'OPERATION_THREAD_SAFE', 'X_OPENLDAP_THREAD_SAFE'), 'vendor_version': 20428, 'protocol_version': 3, 'vendor_name': 'OpenLDAP', 'api_version': 3001}
... Except OPT_X_TLS_NEWCTX: unknown option 24591

    *** OPT_X_TLS_PACKAGE: OpenSSL
    *** OPT_X_TLS_PROTOCOL_MIN: 0
    *** OPT_X_TLS_RANDOM_FILE: None
    *** OPT_X_TLS_REQUIRE_CERT: 1
    *** OPT_X_TLS_TRY: 0

Found 883 entries under [... redacted ...]
like image 778
Jim Dennis Avatar asked Apr 09 '15 02:04

Jim Dennis


People also ask

How do I verify an SSL certificate?

Chrome has made it simple for any site visitor to get certificate information with just a few clicks: Click the padlock icon in the address bar for the website. Click on Certificate (Valid) in the pop-up. Check the Valid from dates to validate the SSL certificate is current.


1 Answers

Your code works for me as expected. Actually, I had exactly the opposite problem, when I executed your code for the first time. It always said 'certificate verify failed'. Adding the following lines fixed this:

# Force libldap to create a new SSL context (must be last TLS option!)
ld.set_option(ldap.OPT_X_TLS_NEWCTX, 0)

Now when I use the wrong CA certificate or one that has been modified as you described it, the result is this error message:

Traceback (most recent call last):
  File "ldap_ssl.py", line 28, in <module>
    ld.start_tls_s()
  File "/Library/Python/2.7/site-packages/python_ldap-2.4.19-py2.7-macosx-10.10-intel.egg/ldap/ldapobject.py", line 571, in start_tls_s
    return self._ldap_call(self._l.start_tls_s)
  File "/Library/Python/2.7/site-packages/python_ldap-2.4.19-py2.7-macosx-10.10-intel.egg/ldap/ldapobject.py", line 106, in _ldap_call
    result = func(*args,**kwargs)
ldap.CONNECT_ERROR: {'info': 'error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (unable to get local issuer certificate)', 'desc': 'Connect error'}

When I use the right CA certificate, the output is like yours.


Now the interesting question is: What are the differences between our setups and especially which difference causes this strange behaviour on your machine?

My setup is:

  • Mac OS X 10.10
  • Python 2.7.6
  • python-ldap 2.4.19 (manual installation)
  • OpenLDAP 2.4.39 (via Homebrew)
  • OpenSSL 1.0.1l (via Homebrew)

I have a local OpenLDAP running, installed with Homebrew:

brew install homebrew/dupes/openldap --with-berkeley-db

On Yosemite python-ldap is quite buggy when installed with pip (see Python-ldap set_option not working on Yosemite), therefore I had to download the tarball and compile/install it, which was fortunately pretty easy, because I already had the OpenLDAP installation with current libs/headers:

First edit the [_ldap] section in setup.cfg like this:

[_ldap]
library_dirs = /usr/local/opt/openldap/lib /usr/lib /usr/local/lib
include_dirs = /usr/local/opt/openldap/include /usr/include/sasl /usr/include /usr/local/include
extra_compile_args = -g -arch x86_64
extra_objects = 
libs = ldap_r lber sasl2 ssl crypto

Some header files are in Mac OS SDK, link the directory (change the path according to your version) to /usr/include:

sudo ln -s /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/ /usr/include

Then build and install:

python setup.py build
sudo python setup.py install

The output of otool shows that python-ldap is now linked to the libraries of OpenLDAP 2.4.39 and OpenSSL 0.9.8:

$ otool -L /Library/Python/2.7/site-packages/python_ldap-2.4.19-py2.7-macosx-10.10-intel.egg/_ldap.so
/Library/Python/2.7/site-packages/python_ldap-2.4.19-py2.7-macosx-10.10-intel.egg/_ldap.so:
    /usr/local/lib/libldap_r-2.4.2.dylib (compatibility version 13.0.0, current version 13.2.0)
    /usr/local/lib/liblber-2.4.2.dylib (compatibility version 13.0.0, current version 13.2.0)
    /usr/lib/libsasl2.2.dylib (compatibility version 3.0.0, current version 3.15.0)
    /usr/lib/libssl.0.9.8.dylib (compatibility version 0.9.8, current version 0.9.8)
    /usr/lib/libcrypto.0.9.8.dylib (compatibility version 0.9.8, current version 0.9.8)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)

An alternative approach for building python-ldap is to install only the OpenLDAP libraries and headers needed for building: http://projects.skurfer.com/posts/2011/python_ldap_lion/

All these steps should work under Mavericks as well and I assume using the latest OpenLDAP and OpenSSL libraries will solve your problem.

like image 51
Omikron Avatar answered Oct 07 '22 04:10

Omikron