From 63a77be2d3e6a100e6e41d36ca99ceea068fc6a2 Mon Sep 17 00:00:00 2001 From: Keith Mukai Date: Sat, 19 Oct 2019 18:37:02 -0500 Subject: [PATCH] Initial Tor integration option --- .env_example | 3 ++ .flaskenv | 9 ++++++ .gitignore | 6 +++- server.py | 15 +++++++++- tor/README.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ tor/tor_util.py | 37 +++++++++++++++++++++++ 6 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 .env_example create mode 100644 .flaskenv create mode 100644 tor/README.md create mode 100644 tor/tor_util.py diff --git a/.env_example b/.env_example new file mode 100644 index 000000000..94b2763f8 --- /dev/null +++ b/.env_example @@ -0,0 +1,3 @@ +# The cleartext password that was entered into: +# $ tor --hash-password "your-tor-passphrase" +TOR_PASSWORD=your-tor-passphrase diff --git a/.flaskenv b/.flaskenv new file mode 100644 index 000000000..0d4a639a6 --- /dev/null +++ b/.flaskenv @@ -0,0 +1,9 @@ +PORT=25441 + +# If you want to serve over a Tor hidden service, also set FLASK_ENV=production. +# (The autoreloading in 'development' mode causes problems with the Tor connector) +CONNECT_TOR=False + +#FLASK_ENV=production +FLASK_ENV=development + diff --git a/.gitignore b/.gitignore index 7e99e367f..e21b8e885 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -*.pyc \ No newline at end of file +__pycache__ +*.pyc + +.env +.tor_service_key diff --git a/server.py b/server.py index 70f346647..eb2450146 100644 --- a/server.py +++ b/server.py @@ -15,6 +15,12 @@ from specter import Specter, purposes, addrtypes from datetime import datetime import urllib +from pathlib import Path +env_path = Path('.') / '.flaskenv' +from dotenv import load_dotenv +load_dotenv(env_path) + + if getattr(sys, 'frozen', False): template_folder = os.path.join(sys._MEIPASS, 'templates') static_folder = os.path.join(sys._MEIPASS, 'static') @@ -483,4 +489,11 @@ if __name__ == '__main__': if os.path.isfile(filename): extra_files.append(filename) - app.run(port=25441, debug=debug, extra_files=extra_files) + if os.getenv('CONNECT_TOR', False): + from tor import tor_util + tor_util.run_on_hidden_service(app, port=os.getenv('PORT'), debug=debug, extra_files=extra_files) + else: + app.run(port=os.getenv('PORT'), debug=debug, extra_files=extra_files) + + + diff --git a/tor/README.md b/tor/README.md new file mode 100644 index 000000000..396ff45ab --- /dev/null +++ b/tor/README.md @@ -0,0 +1,80 @@ +## Running Specter Desktop over a Tor hidden service +Specter Desktop protects your security and privacy by running on a local server that only talks to your own bitcoin node. But what if you need to check a wallet balance or generate and sign transactions when you're away from your home network? + +Configuring your router to let you VPN into your home network is probably the easiest solution. + +But you can also make Specter Desktop available outside of your network via a Tor hidden service. A hidden service generates a secret .onion address that only you know and which can be accessed from a Tor browser from anywhere in the world. This does not require any port forwarding on your router. + +### Security note +Tor support, like Specter Desktop as a whole, should be treated as a work-in-progress that is not yet vetted as being fully secure. + +### Install Tor service +Install Tor on the same server that you'll be running Specter Desktop: +* [Debian / Ubuntu](https://2019.www.torproject.org/docs/debian.html.en) +* [macOS](https://2019.www.torproject.org/docs/tor-doc-osx.html.en) + +### Configure Tor authentication +``` +$ tor --hash-password "your-tor-passphrase" +``` +That returns a password hash such as: +``` +16:CE9058DA89498A4160373C70FF7FFF70CC2E20B6788FC48F5C35B2E85B +``` +Update your `torrc` config file (usually `/etc/tor/torrc` or `/usr/local/etc/tor/torrc` on macOS Homebrew installs). Uncomment the `ControlPort` line as well as the `HashedControlPassword` line. Remember to paste in your own hashed password result from above. +``` +## The port on which Tor will listen for local connections from Tor +## controller applications, as documented in control-spec.txt. +ControlPort 9051 +## If you enable the controlport, be sure to enable one of these +## authentication methods, to prevent attackers from accessing it. +HashedControlPassword 16:CE9058DA89498A4160373C70FF7FFF70CC2E20B6788FC48F5C35B2E85B +#CookieAuthentication 1 +``` + +Restart the Tor service: +* `sudo /etc/init.d/tor restart` on linux +* `brew services restart tor` on macOS Homebrew installs + + +### Configure Specter Desktop to connect to Tor +Update the `.flaskenv` file in the project root. Set `CONNECT_TOR` to 'True' and set `FLASK_ENV` to 'production': +``` +PORT=25441 + +# If you want to serve over a Tor hidden service, also set FLASK_ENV=production. +# (The autoreloading in 'development' mode causes problems with the Tor connector) +CONNECT_TOR=False + +FLASK_ENV=production +#FLASK_ENV=development +``` + +### Specify Tor secrets +The Tor password that we hashed above will need to be shared with Specter Desktop. + +Copy the example `.env_example` file: +``` +$ cp .env_example .env +``` + +And then edit `.env` and specify `TOR_PASSWORD`: +``` +# The cleartext password that was entered into: +# $ tor --hash-password "your-tor-passphrase" +TOR_PASSWORD=your-tor-passphrase +``` + +### Launch with Tor +Now just start Specter Desktop as usual: +``` +$ python server.py +``` + +Amongst the startup output you'll see: +``` + * Connecting to tor +Started a new hidden service with the address of abcd1234efgh5678.onion +``` + +Point a Tor browser at that onion address and you will have (reasonably?) secure access to your Specter Desktop from anywhere in the world! diff --git a/tor/tor_util.py b/tor/tor_util.py new file mode 100644 index 000000000..93b0ebe87 --- /dev/null +++ b/tor/tor_util.py @@ -0,0 +1,37 @@ +import os +import stem +from stem.control import Controller + +from dotenv import load_dotenv +load_dotenv() # Load the secrets from .env + + +def run_on_hidden_service(app, port, debug, extra_files): + with Controller.from_port() as controller: + print(' * Connecting to tor') + controller.authenticate(os.getenv('TOR_PASSWORD')) + + key_path = os.path.expanduser('.tor_service_key') + tor_service_id = None + + if not os.path.exists(key_path): + service = controller.create_ephemeral_hidden_service({80: port}, await_publication = True) + tor_service_id = service.service_id + print("Started a new hidden service with the address of %s.onion" % tor_service_id) + + with open(key_path, 'w') as key_file: + key_file.write('%s:%s' % (service.private_key_type, service.private_key)) + else: + with open(key_path) as key_file: + key_type, key_content = key_file.read().split(':', 1) + + service = controller.create_ephemeral_hidden_service({80: port}, key_type = key_type, key_content = key_content, await_publication = True) + tor_service_id = service.service_id + print("Resumed %s.onion" % tor_service_id) + + try: + app.run(port=port, debug=debug, extra_files=extra_files) + finally: + if tor_service_id: + print(" * Shutting down our hidden service") + controller.remove_ephemeral_hidden_service(tor_service_id)