[tor-commits] [flashproxy/master] Certificate pinning for flashproxy-reg-appspot.

dcf at torproject.org dcf at torproject.org
Sun May 19 16:11:39 UTC 2013


commit 844344efa6c975daa9ef4378eff2966adf623058
Author: David Fifield <david at bamsoftware.com>
Date:   Sun May 19 00:34:03 2013 -0700

    Certificate pinning for flashproxy-reg-appspot.
---
 flashproxy-reg-appspot |  105 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 104 insertions(+), 1 deletion(-)

diff --git a/flashproxy-reg-appspot b/flashproxy-reg-appspot
index b9bc621..9764937 100755
--- a/flashproxy-reg-appspot
+++ b/flashproxy-reg-appspot
@@ -1,14 +1,25 @@
 #!/usr/bin/env python
 
 import getopt
+import httplib
 import re
 import os
 import socket
+import ssl
 import subprocess
 import sys
+import tempfile
 import urlparse
 import urllib2
 
+from hashlib import sha1
+
+try:
+    from M2Crypto import X509
+except ImportError:
+    # Defer the error reporting so that --help works even without M2Crypto.
+    X509 = None
+
 DEFAULT_REMOTE_ADDRESS = None
 DEFAULT_REMOTE_PORT = 9000
 
@@ -17,6 +28,39 @@ FRONT_DOMAIN = "www.google.com"
 # The value of the Host header within requests.
 TARGET_DOMAIN = "flashproxy-reg.appspot.com"
 
+# We trust no other CA certificate than this.
+#
+# To find the certificate to copy here,
+# $ strace openssl s_client -connect FRONT_DOMAIN:443 -verify 10 -CApath /etc/ssl/certs 2>&1 | grep /etc/ssl/certs
+# stat("/etc/ssl/certs/XXXXXXXX.0", {st_mode=S_IFREG|0644, st_size=YYYY, ...}) = 0
+CA_CERTS = """\
+subject=/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
+issuer=/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
+UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
+dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
+MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
+dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
+BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
+cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
+AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
+MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
+aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
+ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
+IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
+MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
+A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
+7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
+1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
+-----END CERTIFICATE-----
+"""
+# SHA-1 digest of expected public key. See
+# http://www.imperialviolet.org/2011/05/04/pinning.html for the reason behind
+# hashing the public key, not the entire certificate.
+PUBKEY_SHA1 = "c70ccd442ff4528c603aefef85206fd693990e09".decode("hex")
+
 def get_external_ip():
     f = urlopen(urlparse.urlunparse(("https", FRONT_DOMAIN, "/ip", "", "", "")))
     try:
@@ -96,6 +140,19 @@ def format_addr(addr):
         result += u":%d" % port
     return result
 
+def get_state_dir():
+    """Get a directory where we can put temporary files. Returns None if any
+    suitable temporary directory will do."""
+    pt_dir = os.environ.get("TOR_PT_STATE_LOCATION")
+    if pt_dir is None:
+        return None
+    try:
+        os.makedirs(pt_dir)
+    except OSError, e:
+        if e.errno != errno.EEXIST:
+            raise
+    return pt_dir
+
 def generate_url(addr):
     if getattr(sys, "frozen", False):
         script_dir = os.path.dirname(sys.executable)
@@ -113,10 +170,45 @@ def generate_url(addr):
     stdout, stderr = p.communicate()
     return stdout.strip()
 
+# Certificate validation and pinning for urllib2. Inspired by
+# http://web.archive.org/web/20110125104752/http://www.muchtooscrawled.com/2010/03/https-certificate-verification-in-python-with-urllib2/.
+
+class PinHTTPSConnection(httplib.HTTPSConnection):
+    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()
+
+        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)
+        finally:
+            os.unlink(ca_certs_path)
+
+        # 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)
+        pubkey_der = cert.get_pubkey().as_der()
+        pubkey_digest = sha1(pubkey_der).digest()
+
+        if pubkey_digest != PUBKEY_SHA1:
+            raise ValueError("Public key does not match pin: got %s but expected %s" %
+                (pubkey_digest.encode("hex"), PUBKEY_SHA1.encode("hex")))
+
+class PinHTTPSHandler(urllib2.HTTPSHandler):
+    def https_open(self, req):
+        return self.do_open(PinHTTPSConnection, req)
+
 def urlopen(url):
     req = urllib2.Request(url)
     req.add_header("Host", TARGET_DOMAIN)
-    return urllib2.urlopen(req)
+    opener = urllib2.build_opener(PinHTTPSHandler())
+    return opener.open(req)
 
 opt, args = getopt.gnu_getopt(sys.argv[1:], "46h", ["facilitator-pubkey=", "help"])
 for o, a in opt:
@@ -138,6 +230,17 @@ else:
     usage(sys.stderr)
     sys.exit(1)
 
+if X509 is None:
+    print >> sys.stderr, """\
+This program requires the M2Crypto library, which is not installed.
+
+You can install it using one of the packages at
+http://chandlerproject.org/Projects/MeTooCrypto#Downloads.
+
+On Debian-like systems, use the command "apt-get install python-m2crypto".\
+"""
+    sys.exit(1)
+
 if options.address_family != socket.AF_UNSPEC:
     getaddrinfo = socket.getaddrinfo
     def getaddrinfo_replacement(host, port, family, *args, **kwargs):





More information about the tor-commits mailing list