Welcome, guest | Sign In | My Account | Store | Cart

Despite httplib.HTTPSConnection lets the programmer specify the client's pair of certificates, it doesn't force the underlying SSL library to check the server certificate against the client keys (from the client point of view).

This class allows to force this check, to ensure the python client is connecting to the right server.

Python, 49 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#-*- coding: utf-8 -*-

import socket
import ssl
import httplib

class HTTPSClientAuthConnection(httplib.HTTPSConnection):
    """ Class to make a HTTPS connection, with support for full client-based SSL Authentication"""

    def __init__(self, host, port, key_file, cert_file, ca_file, timeout=None):
        httplib.HTTPSConnection.__init__(self, host, key_file=key_file, cert_file=cert_file)
        self.key_file = key_file
        self.cert_file = cert_file
        self.ca_file = ca_file
        self.timeout = timeout

    def connect(self):
        """ Connect to a host on a given (SSL) port.
            If ca_file is pointing somewhere, use it to check Server Certificate.

            Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
            This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to ssl.wrap_socket(),
            which forces SSL to check server certificate against our client certificate.
        """
        sock = socket.create_connection((self.host, self.port), self.timeout)
        if self._tunnel_host:
            self.sock = sock
            self._tunnel()
        # If there's no CA File, don't force Server Certificate Check
        if self.ca_file:
            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ca_certs=self.ca_file, cert_reqs=ssl.CERT_REQUIRED)
        else:
            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, cert_reqs=ssl.CERT_NONE)

if __name__ == '__main__':
    # Little test-case of our class
    import sys
    if len(sys.argv) != 6:
        print 'usage: python https_auth_handler.py host port key_file cert_file ca_file'
        sys.exit(1)
    else:
        host, port, key_file, cert_file, ca_file = sys.argv[1:]
    conn = HTTPSClientAuthConnection(host, port, key_file=key_file, cert_file=cert_file, ca_file=ca_file)
    conn.request('GET', '/')
    response = conn.getresponse()
    print response.status, response.reason
    data = response.read()
    print data
    conn.close()

You can run this script from console and pass arguments to it. This is a OK example:

marcelo@jupiter:~/src/py_https$ python https_auth_handler.py mysslserver.com 443 key_file.pkey cert_file.cert CA_file.cert 200 OK <html lang="en"> <head> <meta name="ROBOTS" content="NOINDEX, NOFOLLOW"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>SSL Page</title> <link rel="stylesheet" title="Default" type="text/css" href="css/default.css"> <link rel="alternate stylesheet" title="Debug" type="text/css" href="css/debug.css"> </head> <body> <h1>SSL Page</h1> </body> </html>

And this is a failed server-client certificate check:

marcelo@jupiter:~/src/py_https$ python https_auth_handler.py mysslserver.com 443 key_file.pkey cert_file.cert CA_file.cert Traceback (most recent call last): File "https_auth.py", line 8, in <module> conn.request('GET', '/') File "/usr/lib/python2.6/httplib.py", line 914, in request self._send_request(method, url, body, headers) File "/usr/lib/python2.6/httplib.py", line 951, in _send_request self.endheaders() File "/usr/lib/python2.6/httplib.py", line 908, in endheaders self._send_output() File "/usr/lib/python2.6/httplib.py", line 780, in _send_output self.send(msg) File "/usr/lib/python2.6/httplib.py", line 739, in send self.connect() File "/home/marcelo/src/py_https/https_auth_handler.py", line 31, in connect self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ca_certs=self.ca_file, cert_reqs=ssl.CERT_REQUIRED) File "/usr/lib/python2.6/ssl.py", line 338, in wrap_socket suppress_ragged_eofs=suppress_ragged_eofs) File "/usr/lib/python2.6/ssl.py", line 120, in __init__ self.do_handshake() File "/usr/lib/python2.6/ssl.py", line 279, in do_handshake self._sslobj.do_handshake() ssl.SSLError: [Errno 1] _ssl.c:490: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

1 comment

Garel Alex 12 years, 3 months ago  # | flag

Here is how I did, however your versions is better

class SecureHTTPSConnection(httplib.HTTPSConnection):

    def __init__(self, host, port=None, key_file=None, cert_file=None,
                 strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
                 remote_cert=None):
        httplib.HTTPSConnection.__init__(self,
                        host, port, key_file, cert_file, strict, timeout)
        self.remote_cert = remote_cert

    def connect(self):
        """Connect to a host on a given (SSL) port.
        and verify certificate
        """
        # Note : there is also : http://code.activestate.com/recipes/577548-https-httplib-client-connection-with-certificate-v/
        httplib.HTTPSConnection.connect(self)
        # verify cert
        if self.remote_cert is not None:
            cert = self.sock.getpeercert(True)
            with open(self.remote_cert) as certfile:
                remote_cert = certfile.read()
            if remote_cert[:10] == '-----BEGIN':  # we got a pem
                cert = ssl.DER_cert_to_PEM_cert(cert)
            remote_cert = remote_cert.replace('\r', '').replace('\n', '')
            cert = cert.replace('\r', '').replace('\n', '')
            if remote_cert != cert:
                raise AssertionError("Remote cert does not match provided one")