commit 1e541947c32f8d390b5df1cc1c42928e7c058dd5 Author: Ximin Luo infinity0@gmx.com Date: Wed Nov 6 17:32:21 2013 +0000
factor out certificate checking and pinning into flashproxy.keys --- Makefile | 2 +- flashproxy-reg-appspot | 25 ++++--------------------- flashproxy-reg-email | 31 ++++--------------------------- flashproxy/keys.py | 31 +++++++++++++++++++++++++++++++ flashproxy/test/test_keys.py | 22 ++++++++++++++++++++++ setup-common.py | 3 ++- 6 files changed, 64 insertions(+), 50 deletions(-)
diff --git a/Makefile b/Makefile index 2429f33..5623c4f 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ distclean: test: check check: $(MAKE_CLIENT) check - $(PYTHON) setup-common.py check + $(PYTHON) setup-common.py test cd facilitator && ./facilitator-test cd proxy && ./flashproxy-test.js
diff --git a/flashproxy-reg-appspot b/flashproxy-reg-appspot index c47579c..a261f10 100755 --- a/flashproxy-reg-appspot +++ b/flashproxy-reg-appspot @@ -13,9 +13,8 @@ import tempfile import urlparse import urllib2
-from flashproxy.keys import PIN_GOOGLE_CERT, PIN_GOOGLE_PUBKEY_SHA1 +from flashproxy.keys import PIN_GOOGLE_CERT, PIN_GOOGLE_PUBKEY_SHA1, check_certificate_pin, temp_cert from flashproxy.util import parse_addr_spec, format_addr -from hashlib import sha1
try: from M2Crypto import SSL, X509 @@ -142,31 +141,15 @@ class PinHTTPSConnection(httplib.HTTPSConnection): ctx = SSL.Context("tlsv1") ctx.set_verify(SSL.verify_peer, 3)
- ca_certs_fd, ca_certs_path = tempfile.mkstemp(prefix="flashproxy-reg-appspot-", - dir=get_state_dir(), suffix=".crt") - try: - os.write(ca_certs_fd, PIN_GOOGLE_CERT) - os.close(ca_certs_fd) - ret = ctx.load_verify_locations(ca_certs_path) + with temp_cert(PIN_GOOGLE_CERT) as ca_file: + ret = ctx.load_verify_locations(ca_file.name) assert ret == 1 - finally: - os.unlink(ca_certs_path)
self.sock = SSL.Connection(ctx, sock) self.sock.connect((self.host, self.port))
if options.use_certificate_pin: - found = [] - for cert in self.sock.get_peer_cert_chain(): - pubkey_der = cert.get_pubkey().as_der() - pubkey_digest = sha1(pubkey_der).digest() - if pubkey_digest in PIN_GOOGLE_PUBKEY_SHA1: - break - found.append(pubkey_digest) - else: - found = "(" + ", ".join(x.encode("hex") for x in found) + ")" - expected = "(" + ", ".join(x.encode("hex") for x in PIN_GOOGLE_PUBKEY_SHA1) + ")" - raise ValueError("Public key does not match pin: got %s but expected any of %s" % (found, expected)) + check_certificate_pin(self.sock, PIN_GOOGLE_PUBKEY_SHA1)
class PinHTTPSHandler(urllib2.HTTPSHandler): def https_open(self, req): diff --git a/flashproxy-reg-email b/flashproxy-reg-email index 8bc8b6f..4f4599c 100755 --- a/flashproxy-reg-email +++ b/flashproxy-reg-email @@ -11,9 +11,8 @@ import sys import tempfile import urllib
-from flashproxy.keys import PIN_GOOGLE_CERT, PIN_GOOGLE_PUBKEY_SHA1, DEFAULT_FACILITATOR_PUBKEY_PEM +from flashproxy.keys import PIN_GOOGLE_CERT, PIN_GOOGLE_PUBKEY_SHA1, DEFAULT_FACILITATOR_PUBKEY_PEM, check_certificate_pin, temp_cert from flashproxy.util import parse_addr_spec, format_addr -from hashlib import sha1
try: from M2Crypto import BIO, RSA, SSL, X509 @@ -185,11 +184,7 @@ try: ctx = SSL.Context("tlsv1") ctx.set_verify(SSL.verify_peer, 3)
- ca_certs_fd, ca_certs_path = tempfile.mkstemp(prefix="flashproxy-reg-email-", - dir=get_state_dir(), suffix=".crt") - try: - os.write(ca_certs_fd, PIN_GOOGLE_CERT) - os.close(ca_certs_fd) + with temp_cert(PIN_GOOGLE_CERT) as ca_file: # We roll our own initial EHLO/STARTTLS because smtplib.SMTP.starttls # doesn't allow enough certificate validation. code, msg = smtp.docmd("EHLO", EHLO_FQDN) @@ -198,10 +193,8 @@ try: code, msg = smtp.docmd("STARTTLS") if code != 220: raise ValueError("Got code %d after STARTTLS" % code) - ret = ctx.load_verify_locations(ca_certs_path) + ret = ctx.load_verify_locations(ca_file.name) assert ret == 1 - finally: - os.unlink(ca_certs_path)
smtp.sock = SSL.Connection(ctx, smtp.sock) smtp.sock.setup_ssl() @@ -210,23 +203,7 @@ try: smtp.file = smtp.sock.makefile()
if options.use_certificate_pin: - found = [] - for cert in smtp.sock.get_peer_cert_chain(): - pubkey_der = cert.get_pubkey().as_der() - pubkey_digest = sha1(pubkey_der).digest() - if pubkey_digest in PIN_GOOGLE_PUBKEY_SHA1: - break - found.append(pubkey_digest) - else: - found = "(" + ", ".join(x.encode("hex") for x in found) + ")" - expected = "(" + ", ".join(x.encode("hex") for x in PIN_GOOGLE_PUBKEY_SHA1) + ")" - raise ValueError("Public key does not match pin: got %s but expected any of %s" % (found, expected)) - - if options.use_certificate_pin and pubkey_digest not in PIN_GOOGLE_PUBKEY_SHA1: - expected = "(" + ", ".join(x.encode("hex") for x in PIN_GOOGLE_PUBKEY_SHA1) + ")" - raise ValueError("Public key does not match pin: got %s but expected any of %s" % - (pubkey_digest.encode("hex"), expected)) - + check_certificate_pin(smtp.sock, PIN_GOOGLE_PUBKEY_SHA1) smtp.ehlo(EHLO_FQDN)
if not options.remote_addr[0]: diff --git a/flashproxy/keys.py b/flashproxy/keys.py index 71525c8..5b4b9fa 100644 --- a/flashproxy/keys.py +++ b/flashproxy/keys.py @@ -1,3 +1,7 @@ +import tempfile + +from hashlib import sha1 + # We trust no other CA certificate than this. # # To find the certificate to copy here, @@ -52,3 +56,30 @@ M5SDDYYY4xxEPzokjFJfCQv+kcyAnzERNMQ9kR41ePTXG62bpngK5iWGeJ5XdkxG gwIDAQAB -----END PUBLIC KEY----- """ + +def check_certificate_pin(sock, cert_pubkey): + found = [] + for cert in sock.get_peer_cert_chain(): + pubkey_der = cert.get_pubkey().as_der() + pubkey_digest = sha1(pubkey_der).digest() + if pubkey_digest in cert_pubkey: + break + found.append(pubkey_digest) + else: + found = "(" + ", ".join(x.encode("hex") for x in found) + ")" + expected = "(" + ", ".join(x.encode("hex") for x in cert_pubkey) + ")" + raise ValueError("Public key does not match pin: got %s but expected any of %s" % (found, expected)) + +class temp_cert(object): + """Implements a with-statement over raw certificate data.""" + + def __init__(self, certdata): + self.fd = tempfile.NamedTemporaryFile(prefix="fp-cert-temp-", suffix=".crt", delete=True) + self.fd.write(certdata) + self.fd.flush() + + def __enter__(self): + return self.fd + + def __exit__(self, type, value, traceback): + self.fd.close() diff --git a/flashproxy/test/__init__.py b/flashproxy/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flashproxy/test/test_keys.py b/flashproxy/test/test_keys.py new file mode 100644 index 0000000..de22279 --- /dev/null +++ b/flashproxy/test/test_keys.py @@ -0,0 +1,22 @@ +import os.path +import unittest + +from flashproxy.keys import PIN_GOOGLE_CERT, PIN_GOOGLE_PUBKEY_SHA1, DEFAULT_FACILITATOR_PUBKEY_PEM, check_certificate_pin, temp_cert + +class TempCertTest(unittest.TestCase): + def test_temp_cert_success(self): + fn = None + with temp_cert(PIN_GOOGLE_CERT) as ca_file: + fn = ca_file.name + self.assertTrue(os.path.exists(fn)) + self.assertFalse(os.path.exists(fn)) + + def test_temp_cert_raise(self): + fn = None + try: + with temp_cert(PIN_GOOGLE_CERT) as ca_file: + fn = ca_file.name + raise ValueError() + self.fail() + except ValueError: + self.assertFalse(os.path.exists(fn)) diff --git a/setup-common.py b/setup-common.py index 5d98fd9..97d2099 100755 --- a/setup-common.py +++ b/setup-common.py @@ -32,7 +32,8 @@ setup( license = "BSD", keywords = ['tor', 'flashproxy'],
- packages = find_packages(), + packages = find_packages(exclude=['*.test']), + test_suite='flashproxy.test',
version = "1.4",
tor-commits@lists.torproject.org