[tor-commits] [flashproxy/master] move common functionality and command-line options into flashproxy-common

infinity0 at torproject.org infinity0 at torproject.org
Mon Jul 7 08:55:34 UTC 2014


commit badb39f9c13497a7887bac4ac24210ec12be9e39
Author: Ximin Luo <infinity0 at 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 at 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 at 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.





More information about the tor-commits mailing list