Skip to content

Instantly share code, notes, and snippets.

@Caligatio
Created August 20, 2012 01:33
Show Gist options
  • Save Caligatio/3399114 to your computer and use it in GitHub Desktop.
Save Caligatio/3399114 to your computer and use it in GitHub Desktop.
Certificate validating HTTPSHandler class, VerifiedHTTPSHandler, for Python 2.7
from httplib import HTTPSConnection
import urllib2
import socket
import ssl
class VerifiedHTTPSConnection(HTTPSConnection):
'''
Modified version of the httplib.HTTPSConnection class that forces server
certificate validation
'''
def __init__(self, host, port=None, key_file=None, cert_file=None,
strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None,
ca_file=None):
HTTPSConnection.__init__(self, host, port, key_file,
cert_file, strict, timeout, source_address)
self.ca_file = ca_file
def connect(self):
sock = socket.create_connection(
(self.host, self.port),
self.timeout, self.source_address
)
if self._tunnel_host:
self.sock = sock
self._tunnel()
if (None != self.ca_file):
# Wrap the socket using verification with the root certs, note the hardcoded path
self.sock = ssl.wrap_socket(
sock,
self.key_file,
self.cert_file,
cert_reqs = ssl.CERT_REQUIRED, # NEW: Require certificate validation
ca_certs = self.ca_file # NEW: Path to trusted CA file
)
self.checkHostname()
else:
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
def checkHostname(self):
'''
checkHostname()
Checks the hostname being accessed against the various hostnames present
in the remote certificate
'''
hostnames = set()
cert = self.sock.getpeercert()
for subject in cert['subject']:
if ('commonName' == subject[0][0]):
hostnames.add(subject[0][1].encode('utf-8'))
# Get the subject alternative names out of the certificate
try:
sans = (x for x in cert['subjectAltName'] if x[0] == 'DNS')
for san in sans:
hostnames.add(san[1])
except (KeyError) as e:
pass
if (self.host not in hostnames):
raise ssl.SSLError("hostname '%s' doesn't match certificate name(s) '%s'" %
(self.host, ', '.join(hostnames)))
class VerifiedHTTPSHandler(urllib2.HTTPSHandler):
'''
Modified version of the urllib2.HTTPSHandler class that uses the
VerifiedHTTPSConnection HTTPSConnection class
'''
def __init__(self, debuglevel=0, key_file=None, cert_file=None, ca_file=None):
urllib2.HTTPSHandler.__init__(self, debuglevel)
self.key_file = key_file
self.cert_file = cert_file
self.ca_file = ca_file
def https_open(self, req):
return self.do_open(self.get_connection, req)
def get_connection(self, host, timeout = socket._GLOBAL_DEFAULT_TIMEOUT):
return VerifiedHTTPSConnection(host, timeout = timeout, ca_file = self.ca_file)
def main():
# ca_file needs to be a PEM-formatted listing of certificate roots
# See http://curl.haxx.se/ca/cacert.pem for an example
# Or https://raw.github.com/Caligatio/nss-root-converter/master/nss-coverter-py27.py
opener = urllib2.build_opener(VerifiedHTTPSHandler(ca_file = 'cacert.pem'))
urllib2.install_opener(opener)
request = urllib2.Request('https://www.google.com')
sock = urllib2.urlopen(request)
data = sock.read()
print(data)
if ('__main__' == __name__):
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment