HTTPS httplib Client Connection with Certificate Validation « Python recipes « ActiveState Code (original) (raw)

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.

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 SSL Page

SSL Page

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 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