commit 454b735af24797ef639affd69c933d1d28717764 Author: David Fifield david@bamsoftware.com Date: Thu Jul 11 14:33:52 2013 -0700
Use M2Crypto connect in place of Python ssl.wrap_socket.
ssl.wrap_socket doesn't provide enough access to the certificate chain; you can do getpeercert but you can't get at intermediate certificates.
I tried to copy the important OpenSSL calls from newPySSLObject in http://svn.python.org/projects/python/trunk/Modules/_ssl.c, which is what gets called indirectly when you do ssl.wrap_socket. --- facilitator/facilitator-email-poller | 37 +++++++++++++++++++++++++++------ flashproxy-reg-appspot | 38 ++++++++++++++++++++++++++++------ flashproxy-reg-email | 19 +++++++++++------ 3 files changed, 76 insertions(+), 18 deletions(-)
diff --git a/facilitator/facilitator-email-poller b/facilitator/facilitator-email-poller index 2b03799..a2b5cd6 100755 --- a/facilitator/facilitator-email-poller +++ b/facilitator/facilitator-email-poller @@ -19,7 +19,7 @@ import time import fac
from hashlib import sha1 -from M2Crypto import X509 +from M2Crypto import SSL, X509
DEFAULT_IMAP_HOST = "imap.gmail.com" DEFAULT_IMAP_PORT = 993 @@ -83,15 +83,41 @@ class options(object): imaplib_debug = False use_certificate_pin = True
+# Like socket.create_connection in that it tries resolving different address +# families, but doesn't connect the socket. +def create_socket(address, timeout = None, source_address = None): + host, port = address + addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM) + if not addrs: + raise error("getaddrinfo returns an empty list") + err = None + for addr in addrs: + try: + s = socket.socket(addr[0], addr[1], addr[2]) + if timeout is not None and type(timeout) == float: + s.settimeout(timeout) + if source_address is not None: + s.bind(source_address) + return s + except Exception, e: + err = e + raise err + class IMAP4_SSL_REQUIRED(imaplib.IMAP4_SSL): """A subclass of of IMAP4_SSL that uses ssl_version=ssl.PROTOCOL_TLSv1 and cert_reqs=ssl.CERT_REQUIRED.""" def open(self, host = "", port = imaplib.IMAP4_SSL_PORT): + ctx = SSL.Context("tlsv1") + ctx.set_verify(SSL.verify_peer, 2) + ret = ctx.load_verify_locations(self.certfile) + assert ret == 1 + self.host = host self.port = port - self.sock = socket.create_connection((host, port)) - self.sslobj = ssl.wrap_socket(self.sock, ssl_version=ssl.PROTOCOL_TLSv1, - cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.certfile) + self.sock = create_socket((self.host, self.port)) + + self.sslobj = SSL.Connection(ctx, self.sock) + self.sslobj.connect((self.host, self.port)) self.file = self.sslobj.makefile('rb')
def usage(f = sys.stdout): @@ -337,8 +363,7 @@ def imap_login(): ca_certs_file.close()
# Check that the public key is what we expect. - cert_der = imap.ssl().getpeercert(binary_form=True) - cert = X509.load_cert_string(cert_der, format=X509.FORMAT_DER) + cert = imap.ssl().get_peer_cert() pubkey_der = cert.get_pubkey().as_der() pubkey_digest = sha1(pubkey_der).digest()
diff --git a/flashproxy-reg-appspot b/flashproxy-reg-appspot index 16d0e7f..fc960e9 100755 --- a/flashproxy-reg-appspot +++ b/flashproxy-reg-appspot @@ -16,9 +16,10 @@ import urllib2 from hashlib import sha1
try: - from M2Crypto import X509 + from M2Crypto import SSL, X509 except ImportError: # Defer the error reporting so that --help works even without M2Crypto. + SSL = None X509 = None
DEFAULT_REMOTE_ADDRESS = None @@ -187,29 +188,54 @@ def generate_url(addr): raise ValueError("%s exited with status %d" % (FLASHPROXY_REG_URL, p.returncode)) return stdout.strip()
+# Like socket.create_connection in that it tries resolving different address +# families, but doesn't connect the socket. +def create_socket(address, timeout = None, source_address = None): + host, port = address + addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM) + if not addrs: + raise error("getaddrinfo returns an empty list") + err = None + for addr in addrs: + try: + s = socket.socket(addr[0], addr[1], addr[2]) + if timeout is not None and type(timeout) == float: + s.settimeout(timeout) + if source_address is not None: + s.bind(source_address) + return s + except Exception, e: + err = e + raise err + # Certificate validation and pinning for urllib2. Inspired by # http://web.archive.org/web/20110125104752/http://www.muchtooscrawled.com/201....
class PinHTTPSConnection(httplib.HTTPSConnection): def connect(self): - sock = socket.create_connection((self.host, self.port), self.timeout, self.source_address) + sock = create_socket((self.host, self.port), self.timeout, self.source_address) if self._tunnel_host: self.sock = sock self._tunnel()
+ ctx = SSL.Context("tlsv1") + ctx.set_verify(SSL.verify_peer, 2) + ca_certs_fd, ca_certs_path = tempfile.mkstemp(prefix="flashproxy-reg-appspot-", dir=get_state_dir(), suffix=".crt") try: os.write(ca_certs_fd, CA_CERTS) os.close(ca_certs_fd) - self.sock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_TLSv1, - cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_certs_path) + ret = ctx.load_verify_locations(ca_certs_path) + assert ret == 1 finally: os.unlink(ca_certs_path)
+ self.sock = SSL.Connection(ctx, sock) + self.sock.connect((self.host, self.port)) + # Check that the public key is what we expect. - cert_der = self.sock.getpeercert(binary_form=True) - cert = X509.load_cert_string(cert_der, format=X509.FORMAT_DER) + cert = self.sock.get_peer_cert() pubkey_der = cert.get_pubkey().as_der() pubkey_digest = sha1(pubkey_der).digest()
diff --git a/flashproxy-reg-email b/flashproxy-reg-email index 2d3cba9..a3c27ad 100755 --- a/flashproxy-reg-email +++ b/flashproxy-reg-email @@ -13,7 +13,7 @@ import tempfile from hashlib import sha1
try: - from M2Crypto import BIO, RSA, X509 + from M2Crypto import BIO, RSA, SSL, X509 except ImportError: # Defer the error reporting so that --help works even without M2Crypto. X509 = None @@ -257,6 +257,9 @@ if options.debug: smtp.set_debuglevel(1)
try: + ctx = SSL.Context("tlsv1") + ctx.set_verify(SSL.verify_peer, 2) + ca_certs_fd, ca_certs_path = tempfile.mkstemp(prefix="flashproxy-reg-email-", dir=get_state_dir(), suffix=".crt") try: @@ -270,15 +273,19 @@ try: code, msg = smtp.docmd("STARTTLS") if code != 220: raise ValueError("Got code %d after STARTTLS" % code) - smtp.sock = ssl.wrap_socket(smtp.sock, ssl_version=ssl.PROTOCOL_TLSv1, - cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_certs_path) - smtp.file = smtp.sock.makefile() + ret = ctx.load_verify_locations(ca_certs_path) + assert ret == 1 finally: os.unlink(ca_certs_path)
+ smtp.sock = SSL.Connection(ctx, smtp.sock) + smtp.sock.setup_ssl() + smtp.sock.set_connect_state() + smtp.sock.connect_ssl() + smtp.file = smtp.sock.makefile() + # Check that the public key is what we expect. - cert_der = smtp.sock.getpeercert(binary_form=True) - cert = X509.load_cert_string(cert_der, format=X509.FORMAT_DER) + cert = smtp.sock.get_peer_cert() pubkey_der = cert.get_pubkey().as_der() pubkey_digest = sha1(pubkey_der).digest()
tor-commits@lists.torproject.org