commit badb39f9c13497a7887bac4ac24210ec12be9e39 Author: Ximin Luo infinity0@torproject.org Date: Thu Feb 13 16:45:09 2014 +0000
move common functionality and command-line options into flashproxy-common - move keys.DEFAULT_FACILITATOR_PUBKEY_PEM into new reg module to be with other default-facilitator data --- flashproxy-client | 37 +++-------------- flashproxy-reg-appspot | 97 ++++++--------------------------------------- flashproxy-reg-email | 103 +++++++----------------------------------------- flashproxy-reg-http | 69 ++++---------------------------- flashproxy-reg-url | 65 ++++-------------------------- flashproxy/keys.py | 47 ++++++++++++++-------- flashproxy/reg.py | 59 ++++++++++++++++++++++++++- flashproxy/util.py | 38 ++++++++++++++++++ 8 files changed, 177 insertions(+), 338 deletions(-)
diff --git a/flashproxy-client b/flashproxy-client index 9e198a4..b69d1bf 100755 --- a/flashproxy-client +++ b/flashproxy-client @@ -8,6 +8,7 @@ import BaseHTTPServer import array import base64 import cStringIO +import flashproxy import os import os.path import select @@ -19,7 +20,8 @@ import threading import time import traceback
-from flashproxy.util import parse_addr_spec, addr_family, format_addr +from flashproxy.util import parse_addr_spec, addr_family, format_addr, safe_str, safe_format_addr +from flashproxy.reg import DEFAULT_TRANSPORT
from hashlib import sha1
@@ -35,7 +37,6 @@ DEFAULT_LOCAL_PORT_EXTERNAL = 9001 DEFAULT_REMOTE_PORT = 9000 DEFAULT_REGISTER_METHODS = ["appspot", "email", "http"] DEFAULT_PORT_FORWARDING_HELPER = "tor-fw-helper" -DEFAULT_TRANSPORT = "websocket"
# We will re-register if we have fewer than this many waiting proxies. The # facilitator may choose to ignore our requests. @@ -71,22 +72,12 @@ class options(object): facilitator_url = None facilitator_pubkey_filename = None
-def safe_str(s): - """Return "[scrubbed]" if options.safe_logging is true, and s otherwise.""" - if options.safe_logging: - return "[scrubbed]" - else: - return s - log_lock = threading.Lock() def log(msg): with log_lock: print >> options.log_file, (u"%s %s" % (time.strftime(LOG_DATE_FORMAT), msg)).encode("UTF-8") options.log_file.flush()
-def safe_format_addr(addr): - return safe_str(format_addr(addr)) - def format_sockaddr(sockaddr): host, port = socket.getnameinfo(sockaddr, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV) port = int(port) @@ -1036,19 +1027,8 @@ The -4, -6, --unsafe-logging, --transport and --facilitator-pubkey options are propagated to the child registration helpers. For backwards compatilibility, the --facilitator option is also propagated to the http registration helper. If you need to pass more options, use TODO #9976.""") - # common opts - parser.add_argument("-4", help="name lookups use only IPv4.", - action="store_const", const=socket.AF_INET, dest="address_family") - parser.add_argument("-6", help="name lookups use only IPv6.", - action="store_const", const=socket.AF_INET6, dest="address_family") - parser.add_argument("--unsafe-logging", help="don't scrub IP addresses and " - "other sensitive information from logs.", action="store_true") - parser.add_argument("--facilitator-pubkey", help="encrypt registrations to " - "the given PEM-formatted public key file (default built-in).", - metavar='FILENAME') - parser.add_argument("--transport", - help="register using the given transport, default %(default)s.", - default=DEFAULT_TRANSPORT) + flashproxy.util.add_module_opts(parser) + flashproxy.reg.add_module_opts(parser) parser.add_argument("-f", "--facilitator", metavar="URL", help="register with the facilitator at this URL, default %(default)s. " "This is passed to the http registration ONLY.") @@ -1090,12 +1070,7 @@ If you need to pass more options, use TODO #9976.""")
ns = parser.parse_args(sys.argv[1:]) # set registration options - options.address_family = ns.address_family or socket.AF_UNSPEC - if options.address_family != socket.AF_UNSPEC: - getaddrinfo = socket.getaddrinfo - def getaddrinfo_replacement(host, port, family, *args, **kwargs): - return getaddrinfo(host, port, options.address_family, *args, **kwargs) - socket.getaddrinfo = getaddrinfo_replacement + options.address_family = ns.address_family options.transport = ns.transport options.safe_logging = not ns.unsafe_logging options.facilitator_url = ns.facilitator diff --git a/flashproxy-reg-appspot b/flashproxy-reg-appspot index f0e4714..42aef97 100755 --- a/flashproxy-reg-appspot +++ b/flashproxy-reg-appspot @@ -2,16 +2,16 @@ """Register with a facilitator through Google App Engine."""
import argparse +import flashproxy import httplib -import os import socket -import subprocess import sys import urlparse import urllib2
from flashproxy.keys import PIN_GOOGLE_CA_CERT, PIN_GOOGLE_PUBKEY_SHA1, check_certificate_pin, ensure_M2Crypto, temp_cert -from flashproxy.util import parse_addr_spec, format_addr +from flashproxy.reg import build_reg_b64enc +from flashproxy.util import parse_addr_spec, safe_str, safe_format_addr
try: from M2Crypto import SSL @@ -19,54 +19,11 @@ except ImportError: # Defer the error reporting so that --help works even without M2Crypto. pass
-DEFAULT_REMOTE = ("", 9000) -DEFAULT_TRANSPORT = "websocket" - # The domain to which requests appear to go. FRONT_DOMAIN = "www.google.com" # The value of the Host header within requests. TARGET_DOMAIN = "fp-reg-a.appspot.com"
-FLASHPROXY_REG_URL = "flashproxy-reg-url" - -class options(object): - address_family = socket.AF_UNSPEC - use_certificate_pin = True - facilitator_pubkey_filename = None - transport = DEFAULT_TRANSPORT - safe_logging = True - -def safe_str(s): - """Return "[scrubbed]" if options.safe_logging is true, and s otherwise.""" - if options.safe_logging: - return "[scrubbed]" - else: - return s - -def safe_format_addr(addr): - return safe_str(format_addr(addr)) - -def generate_url(addr): - if getattr(sys, "frozen", False): - script_dir = os.path.dirname(sys.executable) - else: - script_dir = sys.path[0] - if not script_dir: - # Maybe the script was read from stdin; in any case don't guess at the directory. - raise ValueError("Can't find executable directory for registration helpers") - command = [os.path.join(script_dir, FLASHPROXY_REG_URL)] - command += ["-f", urlparse.urlunparse(("https", FRONT_DOMAIN, "/", "", "", ""))] - if options.transport is not None: - command += ["--transport", options.transport] - if options.facilitator_pubkey_filename is not None: - command += ["--facilitator-pubkey", options.facilitator_pubkey_filename] - command.append(format_addr(addr)) - p = subprocess.Popen(command, stdout=subprocess.PIPE) - stdout, stderr = p.communicate() - if p.returncode != 0: - 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): @@ -105,8 +62,7 @@ class PinHTTPSConnection(httplib.HTTPSConnection): self.sock = SSL.Connection(ctx, sock) self.sock.connect((self.host, self.port))
- if options.use_certificate_pin: - check_certificate_pin(self.sock, PIN_GOOGLE_PUBKEY_SHA1) + check_certificate_pin(self.sock, PIN_GOOGLE_PUBKEY_SHA1)
class PinHTTPSHandler(urllib2.HTTPSHandler): def https_open(self, req): @@ -130,40 +86,12 @@ parser = argparse.ArgumentParser( description="Register with a facilitator through a Google App Engine app. " "If only the external port is given, the remote server guesses our " "external address.") -# common opts -parser.add_argument("-4", help="name lookups use only IPv4.", - action="store_const", const=socket.AF_INET, dest="address_family") -parser.add_argument("-6", help="name lookups use only IPv6.", - action="store_const", const=socket.AF_INET6, dest="address_family") -parser.add_argument("--unsafe-logging", help="don't scrub IP addresses and " - "other sensitive information from logs.", action="store_true") -parser.add_argument("--disable-pin", help="disable all certificate pinning " - "checks", action="store_true",) -parser.add_argument("--facilitator-pubkey", help="encrypt registrations to " - "the given PEM-formatted public key file (default built-in).", - metavar='FILENAME') -parser.add_argument("--transport", - help="register using the given transport, default %(default)s.", - default=DEFAULT_TRANSPORT) -# common args -parser.add_argument("remote_addr", - help="remote to register, default %s - the external IP address is guessed." - % format_addr(DEFAULT_REMOTE), - metavar="REMOTE:PORT", default="", nargs="?", - type=lambda x: parse_addr_spec(x, *DEFAULT_REMOTE)) - -ns = parser.parse_args(sys.argv[1:]) -options.address_family = ns.address_family or socket.AF_UNSPEC -if options.address_family != socket.AF_UNSPEC: - getaddrinfo = socket.getaddrinfo - def getaddrinfo_replacement(host, port, family, *args, **kwargs): - return getaddrinfo(host, port, options.address_family, *args, **kwargs) - socket.getaddrinfo = getaddrinfo_replacement -options.safe_logging = not ns.unsafe_logging -options.use_certificate_pin = not ns.disable_pin -options.facilitator_pubkey_filename = ns.facilitator_pubkey -options.transport = ns.transport -remote_addr = ns.remote_addr +flashproxy.util.add_module_opts(parser) +flashproxy.keys.add_module_opts(parser) +flashproxy.reg.add_registration_args(parser) + +options = parser.parse_args(sys.argv[1:]) +remote_addr = options.remote_addr
ensure_M2Crypto()
@@ -186,9 +114,10 @@ if not remote_addr[0]: sys.exit(1)
try: - url = generate_url(remote_addr) + reg = build_reg_b64enc(remote_addr, options.transport, urlsafe=True) + url = urlparse.urljoin(urlparse.urlunparse(("https", FRONT_DOMAIN, "/", "", "", "")), "reg/" + reg) except Exception, e: - print >> sys.stderr, "Error running %s: %s" % (FLASHPROXY_REG_URL, str(e)) + print >> sys.stderr, "Error generating URL: %s" % str(e) sys.exit(1)
try: diff --git a/flashproxy-reg-email b/flashproxy-reg-email index a151efd..ddfc9fa 100755 --- a/flashproxy-reg-email +++ b/flashproxy-reg-email @@ -2,66 +2,30 @@ """Register with a facilitator using the email method."""
import argparse +import flashproxy import os import re import smtplib -import socket import sys -import urllib
-from flashproxy.keys import PIN_GOOGLE_CA_CERT, PIN_GOOGLE_PUBKEY_SHA1, DEFAULT_FACILITATOR_PUBKEY_PEM, check_certificate_pin, ensure_M2Crypto, temp_cert -from flashproxy.util import parse_addr_spec, format_addr +from flashproxy.keys import PIN_GOOGLE_CA_CERT, PIN_GOOGLE_PUBKEY_SHA1, check_certificate_pin, ensure_M2Crypto, temp_cert +from flashproxy.reg import build_reg_b64enc +from flashproxy.util import parse_addr_spec, format_addr, safe_format_addr
try: - from M2Crypto import BIO, RSA, SSL + from M2Crypto import SSL except ImportError: # Defer the error reporting so that --help works even without M2Crypto. pass
-DEFAULT_REMOTE = ("", 9000) DEFAULT_EMAIL_ADDRESS = "flashproxyreg.a@gmail.com" # dig MX gmail.com DEFAULT_SMTP = ("gmail-smtp-in.l.google.com", 25) -DEFAULT_TRANSPORT = "websocket"
# Use this to prevent Python smtplib from guessing and leaking our hostname. EHLO_FQDN = "[127.0.0.1]" FROM_EMAIL_ADDRESS = "nobody@localhost"
-class options(object): - remote_addr = None - - address_family = socket.AF_UNSPEC - debug = False - use_certificate_pin = True - email_addr = None - facilitator_pubkey_filename = None - smtp_addr = None - transport = DEFAULT_TRANSPORT - safe_logging = True - -def safe_str(s): - """Return "[scrubbed]" if options.safe_logging is true, and s otherwise.""" - if options.safe_logging: - return "[scrubbed]" - else: - return s - -def safe_format_addr(addr): - return safe_str(format_addr(addr)) - -def build_reg(addr, transport): - return urllib.urlencode(( - ("client", format_addr(addr)), - ("client-transport", transport), - )) - -def get_facilitator_pubkey(): - if options.facilitator_pubkey_filename is not None: - return RSA.load_pub_key(options.facilitator_pubkey_filename) - else: - return RSA.load_pub_key_bio(BIO.MemoryBuffer(DEFAULT_FACILITATOR_PUBKEY_PEM)) - parser = argparse.ArgumentParser( usage="%(prog)s [OPTIONS] [REMOTE][:PORT]", description="Register with a flash proxy facilitator through email. Makes " @@ -71,27 +35,9 @@ parser = argparse.ArgumentParser( epilog="Using an SMTP server or email address other than the defaults will " "not work unless you have made special arrangements to connect them to a " "facilitator.") -# common opts -parser.add_argument("-4", help="name lookups use only IPv4.", - action="store_const", const=socket.AF_INET, dest="address_family") -parser.add_argument("-6", help="name lookups use only IPv6.", - action="store_const", const=socket.AF_INET6, dest="address_family") -parser.add_argument("--unsafe-logging", help="don't scrub IP addresses and " - "other sensitive information from logs.", action="store_true") -parser.add_argument("--disable-pin", help="disable all certificate pinning " - "checks", action="store_true",) -parser.add_argument("--facilitator-pubkey", help="encrypt registrations to " - "the given PEM-formatted public key file (default built-in).", - metavar='FILENAME') -parser.add_argument("--transport", - help="register using the given transport, default %(default)s.", - default=DEFAULT_TRANSPORT) -# common args -parser.add_argument("remote_addr", - help="remote to register, default %s - the external IP address is guessed." - % format_addr(DEFAULT_REMOTE), - metavar="REMOTE:PORT", default="", nargs="?", - type=lambda x: parse_addr_spec(x, *DEFAULT_REMOTE)) +flashproxy.util.add_module_opts(parser) +flashproxy.keys.add_module_opts(parser) +flashproxy.reg.add_registration_args(parser) # specific opts parser.add_argument("-e", "--email", metavar="ADDRESS", help="send mail to ADDRESS, default %(default)s.", @@ -103,26 +49,11 @@ parser.add_argument("-d", "--debug", help="enable debugging output (Python smtplib messages).", action="store_true")
-ns = parser.parse_args(sys.argv[1:]) -options.address_family = ns.address_family or socket.AF_UNSPEC -if options.address_family != socket.AF_UNSPEC: - getaddrinfo = socket.getaddrinfo - def getaddrinfo_replacement(host, port, family, *args, **kwargs): - return getaddrinfo(host, port, options.address_family, *args, **kwargs) - socket.getaddrinfo = getaddrinfo_replacement -options.safe_logging = not ns.unsafe_logging -options.use_certificate_pin = not ns.disable_pin -options.facilitator_pubkey_filename = ns.facilitator_pubkey -options.transport = ns.transport -options.remote_addr = ns.remote_addr -# specific parsing -options.email_addr = ns.email -options.smtp_addr = ns.smtp -options.debug = ns.debug +options = parser.parse_args(sys.argv[1:])
ensure_M2Crypto()
-smtp = smtplib.SMTP(options.smtp_addr[0], options.smtp_addr[1], EHLO_FQDN) +smtp = smtplib.SMTP(options.smtp[0], options.smtp[1], EHLO_FQDN)
if options.debug: smtp.set_debuglevel(1) @@ -149,8 +80,7 @@ try: smtp.sock.connect_ssl() smtp.file = smtp.sock.makefile()
- if options.use_certificate_pin: - check_certificate_pin(smtp.sock, PIN_GOOGLE_PUBKEY_SHA1) + check_certificate_pin(smtp.sock, PIN_GOOGLE_PUBKEY_SHA1) smtp.ehlo(EHLO_FQDN)
if not options.remote_addr[0]: @@ -164,21 +94,18 @@ try: spec = "[" + spec + "]" options.remote_addr = parse_addr_spec(spec, *options.remote_addr)
- body_plain = build_reg(options.remote_addr, options.transport) - rsa = get_facilitator_pubkey() - body_crypt = rsa.public_encrypt(body_plain, RSA.pkcs1_oaep_padding) - body = body_crypt.encode("base64") + body = build_reg_b64enc(options.remote_addr, options.transport)
# Add a random subject to keep Gmail from threading everything. rand_string = os.urandom(5).encode("hex") - smtp.sendmail(options.email_addr, options.email_addr, """\ + smtp.sendmail(options.email, options.email, """\ To: %(to_addr)s\r From: %(from_addr)s\r Subject: client reg %(rand_string)s\r \r %(body)s """ % { - "to_addr": options.email_addr, + "to_addr": options.email, "from_addr": FROM_EMAIL_ADDRESS, "rand_string": rand_string, "body": body, @@ -188,4 +115,4 @@ except Exception, e: print >> sys.stderr, "Failed to register: %s" % str(e) sys.exit(1)
-print "Registered "%s" with %s." % (safe_format_addr(options.remote_addr), options.email_addr) +print "Registered "%s" with %s." % (safe_format_addr(options.remote_addr), options.email) diff --git a/flashproxy-reg-http b/flashproxy-reg-http index a3ad246..debd2d1 100755 --- a/flashproxy-reg-http +++ b/flashproxy-reg-http @@ -2,82 +2,29 @@ """Register with a facilitator using the HTTP method."""
import argparse -import socket +import flashproxy import sys -import urllib import urllib2
-from flashproxy.util import parse_addr_spec, format_addr - -DEFAULT_REMOTE = ("", 9000) -DEFAULT_FACILITATOR_URL = "https://fp-facilitator.org/" -DEFAULT_TRANSPORT = "websocket" - -class options(object): - remote_addr = None - - address_family = socket.AF_UNSPEC - facilitator_url = None - transport = DEFAULT_TRANSPORT - safe_logging = True - -def safe_str(s): - """Return "[scrubbed]" if options.safe_logging is true, and s otherwise.""" - if options.safe_logging: - return "[scrubbed]" - else: - return s - -def safe_format_addr(addr): - return safe_str(format_addr(addr)) - -def build_reg(addr, transport): - return urllib.urlencode(( - ("client", format_addr(addr)), - ("client-transport", transport), - )) +from flashproxy.util import safe_format_addr +from flashproxy.reg import DEFAULT_FACILITATOR_URL, build_reg
parser = argparse.ArgumentParser( usage="%(prog)s [OPTIONS] [REMOTE][:PORT]", description="Register with a flash proxy facilitator using an HTTP POST. " "If only the external port is given, the remote server guesses our " "external address.") -# common opts -parser.add_argument("-4", help="name lookups use only IPv4.", - action="store_const", const=socket.AF_INET, dest="address_family") -parser.add_argument("-6", help="name lookups use only IPv6.", - action="store_const", const=socket.AF_INET6, dest="address_family") -parser.add_argument("--unsafe-logging", help="don't scrub IP addresses and " - "other sensitive information from logs.", action="store_true") -parser.add_argument("--transport", - help="register using the given transport, default %(default)s.", - default=DEFAULT_TRANSPORT) -# common args -parser.add_argument("remote_addr", - help="remote to register, default %s - the external IP address is guessed." - % format_addr(DEFAULT_REMOTE), - metavar="REMOTE:PORT", default="", nargs="?", - type=lambda x: parse_addr_spec(x, *DEFAULT_REMOTE)) -# specific opts +flashproxy.util.add_module_opts(parser) +flashproxy.reg.add_registration_args(parser, ignore_pubkey=True) parser.add_argument("-f", "--facilitator", metavar="URL", help="register with the given facilitator, default %(default)s.", default=DEFAULT_FACILITATOR_URL)
-ns = parser.parse_args(sys.argv[1:]) -options.address_family = ns.address_family or socket.AF_UNSPEC -if options.address_family != socket.AF_UNSPEC: - getaddrinfo = socket.getaddrinfo - def getaddrinfo_replacement(host, port, family, *args, **kwargs): - return getaddrinfo(host, port, options.address_family, *args, **kwargs) - socket.getaddrinfo = getaddrinfo_replacement -options.safe_logging = not ns.unsafe_logging -options.transport = ns.transport -options.remote_addr = ns.remote_addr -options.facilitator_url = ns.facilitator +options = parser.parse_args(sys.argv[1:])
body = build_reg(options.remote_addr, options.transport) try: - http = urllib2.urlopen(options.facilitator_url, body, 10) + http = urllib2.urlopen(options.facilitator, body, 10) except urllib2.HTTPError, e: print >> sys.stderr, "Status code was %d, not 200" % e.code sys.exit(1) @@ -89,4 +36,4 @@ except Exception, e: sys.exit(1) http.close()
-print "Registered "%s" with %s." % (safe_format_addr(options.remote_addr), options.facilitator_url) +print "Registered "%s" with %s." % (safe_format_addr(options.remote_addr), options.facilitator) diff --git a/flashproxy-reg-url b/flashproxy-reg-url index e8789d5..e73b035 100755 --- a/flashproxy-reg-url +++ b/flashproxy-reg-url @@ -2,78 +2,29 @@ """Register with a facilitator using an indirect URL."""
import argparse -import base64 +import flashproxy import sys -import urllib import urlparse
-from flashproxy.keys import DEFAULT_FACILITATOR_PUBKEY_PEM, ensure_M2Crypto -from flashproxy.util import parse_addr_spec, format_addr - -try: - from M2Crypto import BIO, RSA -except ImportError: - # Defer the error reporting so that --help works even without M2Crypto. - pass - -DEFAULT_REMOTE = ("", 9000) -DEFAULT_FACILITATOR_URL = "https://fp-facilitator.org/" -DEFAULT_TRANSPORT = "websocket" - -class options(object): - facilitator_url = None - facilitator_pubkey_filename = None - transport = DEFAULT_TRANSPORT - -def build_reg(addr, transport): - return urllib.urlencode(( - ("client", format_addr(addr)), - ("client-transport", transport), - )) - -def get_facilitator_pubkey(): - if options.facilitator_pubkey_filename is not None: - return RSA.load_pub_key(options.facilitator_pubkey_filename) - else: - return RSA.load_pub_key_bio(BIO.MemoryBuffer(DEFAULT_FACILITATOR_PUBKEY_PEM)) +from flashproxy.keys import ensure_M2Crypto +from flashproxy.reg import DEFAULT_FACILITATOR_URL, build_reg_b64enc
parser = argparse.ArgumentParser( usage="%(prog)s [OPTIONS] REMOTE[:PORT]", description="Print a URL, which, when retrieved, will cause the input " "client address to be registered with the flash proxy facilitator.") -# common opts -parser.add_argument("--facilitator-pubkey", help="encrypt registrations to " - "the given PEM-formatted public key file (default built-in).", - metavar='FILENAME') -parser.add_argument("--transport", - help="register using the given transport, default %(default)s.", - default=DEFAULT_TRANSPORT) -# common args -parser.add_argument("remote_addr", - help="remote to register, default %s - the external IP address is guessed." - % format_addr(DEFAULT_REMOTE), - metavar="REMOTE:PORT", default="", nargs="?", - type=lambda x: parse_addr_spec(x, *DEFAULT_REMOTE)) -# specific opts +flashproxy.reg.add_registration_args(parser) parser.add_argument("-f", "--facilitator", metavar="URL", help="register with the given facilitator, default %(default)s.", default=DEFAULT_FACILITATOR_URL)
-ns = parser.parse_args(sys.argv[1:]) -options.facilitator_pubkey_filename = ns.facilitator_pubkey -options.transport = ns.transport -remote_addr = ns.remote_addr -options.facilitator_url = ns.facilitator +options = parser.parse_args(sys.argv[1:])
ensure_M2Crypto()
-if not ns.remote_addr[0]: +if not options.remote_addr[0]: print >> sys.stderr, "An IP address (not just a port) is required." sys.exit(1)
-reg_plain = build_reg(remote_addr, options.transport) -rsa = get_facilitator_pubkey() -reg_crypt = rsa.public_encrypt(reg_plain, RSA.pkcs1_oaep_padding) -reg = base64.urlsafe_b64encode(reg_crypt) - -print urlparse.urljoin(options.facilitator_url, "reg/" + reg) +reg = build_reg_b64enc(options.remote_addr, options.transport, urlsafe=True) +print urlparse.urljoin(options.facilitator, "reg/" + reg) diff --git a/flashproxy/keys.py b/flashproxy/keys.py index 1365f07..8c60dc0 100644 --- a/flashproxy/keys.py +++ b/flashproxy/keys.py @@ -1,15 +1,32 @@ +import base64 import errno import os +import sys import tempfile
from hashlib import sha1
try: import M2Crypto + from M2Crypto import BIO, RSA except ImportError: # Defer the error so that the main program gets a chance to print help text M2Crypto = None
+class options(object): + disable_pin = True + +def add_module_opts(parser): + parser.add_argument("--disable-pin", help="disable all certificate pinning " + "checks", action="store_true",) + + old_parse = parser.parse_args + def parse_args(namespace): + options.disable_pin = namespace.disable_pin + return namespace + parser.parse_args = lambda *a, **kw: parse_args(old_parse(*a, **kw)) + + # We trust no other CA certificate than this. # # To find the certificate to copy here, @@ -49,23 +66,8 @@ PIN_GOOGLE_PUBKEY_SHA1 = ( "\x43\xda\xd6\x30\xee\x53\xf8\xa9\x80\xca\x6e\xfd\x85\xf4\x6a\xa3\x79\x90\xe0\xea", )
-# Registrations are encrypted with this public key before being emailed. Only -# the facilitator operators should have the corresponding private key. Given a -# private key in reg-email, get the public key like this: -# openssl rsa -pubout < reg-email > reg-email.pub -DEFAULT_FACILITATOR_PUBKEY_PEM = """\ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA44Mt8c599/4N2fgu6ppN -oatPW1GOgZxxObljFtEy0OWM1eHB35OOn+Kn9MxNHTRxVWwCEi0HYxWNVs2qrXxV -84LmWBz6A65d2qBlgltgLXusiXLrpwxVmJeO+GfmbF8ur0U9JSYxA20cGW/kujNg -XYDGQxO1Gvxq2lHK2LQmBpkfKEE1DMFASmIvlHDQgDj3XBb5lYeOsHZmg16UrGAq -1UH238hgJITPGLXBtwLtJkYbrATJvrEcmvI7QSm57SgYGpaB5ZdCbJL5bag5Pgt6 -M5SDDYYY4xxEPzokjFJfCQv+kcyAnzERNMQ9kR41ePTXG62bpngK5iWGeJ5XdkxG -gwIDAQAB ------END PUBLIC KEY----- -""" - def check_certificate_pin(sock, cert_pubkey): + if options.disable_pin: return found = [] for cert in sock.get_peer_cert_chain(): pubkey_der = cert.get_pubkey().as_der() @@ -105,6 +107,19 @@ class temp_cert(object): def __exit__(self, type, value, traceback): os.unlink(self.path)
+def get_pubkey(defaultkeybytes, overridefn=None): + if overridefn is not None: + return RSA.load_pub_key(overridefn) + else: + return RSA.load_pub_key_bio(BIO.MemoryBuffer(defaultkeybytes)) + +def pubkey_b64enc(plaintext, pubkey, urlsafe=False): + ciphertext = pubkey.public_encrypt(plaintext, RSA.pkcs1_oaep_padding) + if urlsafe: + return base64.urlsafe_b64encode(ciphertext) + else: + return ciphertext.encode("base64") + def ensure_M2Crypto(): if M2Crypto is None: print >> sys.stderr, """\ diff --git a/flashproxy/reg.py b/flashproxy/reg.py index 0551f06..bc292dc 100644 --- a/flashproxy/reg.py +++ b/flashproxy/reg.py @@ -1,6 +1,63 @@ +import urllib from collections import namedtuple
-from flashproxy.util import parse_addr_spec +from flashproxy.keys import get_pubkey, pubkey_b64enc +from flashproxy.util import parse_addr_spec, format_addr + +DEFAULT_REMOTE = ("", 9000) +DEFAULT_FACILITATOR_URL = "https://fp-facilitator.org/" +DEFAULT_TRANSPORT = "websocket" +# Default facilitator pubkey owned by the operator of DEFAULT_FACILITATOR_URL +DEFAULT_FACILITATOR_PUBKEY_PEM = """\ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA44Mt8c599/4N2fgu6ppN +oatPW1GOgZxxObljFtEy0OWM1eHB35OOn+Kn9MxNHTRxVWwCEi0HYxWNVs2qrXxV +84LmWBz6A65d2qBlgltgLXusiXLrpwxVmJeO+GfmbF8ur0U9JSYxA20cGW/kujNg +XYDGQxO1Gvxq2lHK2LQmBpkfKEE1DMFASmIvlHDQgDj3XBb5lYeOsHZmg16UrGAq +1UH238hgJITPGLXBtwLtJkYbrATJvrEcmvI7QSm57SgYGpaB5ZdCbJL5bag5Pgt6 +M5SDDYYY4xxEPzokjFJfCQv+kcyAnzERNMQ9kR41ePTXG62bpngK5iWGeJ5XdkxG +gwIDAQAB +-----END PUBLIC KEY----- +""" +_OPTION_IGNORED = "ignored; for compatibility with other methods" + +class options(object): + transport = DEFAULT_TRANSPORT + facilitator_pubkey = None + +def add_module_opts(parser, ignore_pubkey=False): + parser.add_argument("--transport", metavar="TRANSPORT", + help="register using the given transport, default %(default)s.", + default=DEFAULT_TRANSPORT) + parser.add_argument("--facilitator-pubkey", metavar="FILENAME", + help=(_OPTION_IGNORED if ignore_pubkey else "encrypt registrations to " + "the given PEM-formatted public key file (default built-in).")) + + old_parse = parser.parse_args + def parse_args(namespace): + options.transport = namespace.transport + options.facilitator_pubkey = namespace.facilitator_pubkey + return namespace + parser.parse_args = lambda *a, **kw: parse_args(old_parse(*a, **kw)) + +def add_registration_args(parser, **kwargs): + add_module_opts(parser, **kwargs) + parser.add_argument("remote_addr", metavar="ADDR:PORT", + help="external addr+port to register, default %s" % + format_addr(DEFAULT_REMOTE), default="", nargs="?", + type=lambda x: parse_addr_spec(x, *DEFAULT_REMOTE)) + + +def build_reg(addr, transport): + return urllib.urlencode(( + ("client", format_addr(addr)), + ("client-transport", transport), + )) + +def build_reg_b64enc(addr, transport, urlsafe=False): + pubkey = get_pubkey(DEFAULT_FACILITATOR_PUBKEY_PEM, options.facilitator_pubkey) + return pubkey_b64enc(build_reg(addr, transport), pubkey, urlsafe=urlsafe) +
class Transport(namedtuple("Transport", "inner outer")): @classmethod diff --git a/flashproxy/util.py b/flashproxy/util.py index 13cb5a4..5df15be 100644 --- a/flashproxy/util.py +++ b/flashproxy/util.py @@ -1,6 +1,44 @@ import re import socket
+_old_socket_getaddrinfo = socket.getaddrinfo + +class options(object): + safe_logging = True + address_family = socket.AF_UNSPEC + +def add_module_opts(parser): + parser.add_argument("-4", + help="name lookups use only IPv4.", + action="store_const", const=socket.AF_INET, dest="address_family") + parser.add_argument("-6", + help="name lookups use only IPv6.", + action="store_const", const=socket.AF_INET6, dest="address_family") + parser.add_argument("--unsafe-logging", + help="don't scrub IP addresses and other sensitive information from " + "logs.", action="store_true") + + old_parse = parser.parse_args + def parse_args(namespace): + options.safe_logging = not namespace.unsafe_logging + options.address_family = namespace.address_family or socket.AF_UNSPEC + if options.address_family != socket.AF_UNSPEC: + def getaddrinfo_replacement(host, port, family, *args, **kwargs): + return _old_socket_getaddrinfo(host, port, options.address_family, *args, **kwargs) + socket.getaddrinfo = getaddrinfo_replacement + return namespace + parser.parse_args = lambda *a, **kw: parse_args(old_parse(*a, **kw)) + +def safe_str(s): + """Return "[scrubbed]" if options.safe_logging is true, and s otherwise.""" + if options.safe_logging: + return "[scrubbed]" + else: + return s + +def safe_format_addr(addr): + return safe_str(format_addr(addr)) + def parse_addr_spec(spec, defhost = None, defport = None): """Parse a host:port specification and return a 2-tuple ("host", port) as understood by the Python socket functions.