Created
August 20, 2012 01:33
-
-
Save Caligatio/3399114 to your computer and use it in GitHub Desktop.
Certificate validating HTTPSHandler class, VerifiedHTTPSHandler, for Python 2.7
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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