summaryrefslogtreecommitdiff
path: root/onionshare/onion.py
diff options
context:
space:
mode:
Diffstat (limited to 'onionshare/onion.py')
-rw-r--r--onionshare/onion.py171
1 files changed, 104 insertions, 67 deletions
diff --git a/onionshare/onion.py b/onionshare/onion.py
index 4d159d94..ed4fde7b 100644
--- a/onionshare/onion.py
+++ b/onionshare/onion.py
@@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
-Copyright (C) 2018 Micah Lee <micah@micahflee.com>
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,9 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from stem.control import Controller
from stem import ProtocolError, SocketClosed
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
-import os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex
+from Crypto.PublicKey import RSA
+import base64, os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex
-from . import socks
+from distutils.version import LooseVersion as Version
from . import common, strings
from .settings import Settings
@@ -125,38 +126,40 @@ class Onion(object):
call this function and pass in a status string while connecting to tor. This
is necessary for status updates to reach the GUI.
"""
- def __init__(self):
- common.log('Onion', '__init__')
+ def __init__(self, common):
+ self.common = common
+
+ self.common.log('Onion', '__init__')
self.stealth = False
self.service_id = None
- self.system = common.get_platform()
-
# Is bundled tor supported?
- if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
+ if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
self.bundle_tor_supported = False
else:
self.bundle_tor_supported = True
# Set the path of the tor binary, for bundled tor
- (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
+ (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
# The tor process
self.tor_proc = None
+ # The Tor controller
+ self.c = None
+
# Start out not connected to Tor
self.connected_to_tor = False
- def connect(self, settings=False, config=False, tor_status_update_func=None):
- common.log('Onion', 'connect')
+ def connect(self, custom_settings=False, config=False, tor_status_update_func=None):
+ self.common.log('Onion', 'connect')
- # Either use settings that are passed in, or load them from disk
- if settings:
- self.settings = settings
+ # Either use settings that are passed in, or use them from common
+ if custom_settings:
+ self.settings = custom_settings
else:
- self.settings = Settings(config)
- self.settings.load()
+ self.settings = self.common.settings
# The Tor controller
self.c = None
@@ -166,34 +169,35 @@ class Onion(object):
raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported'))
# Create a torrc for this session
- self.tor_data_directory = tempfile.TemporaryDirectory()
+ self.tor_data_directory = tempfile.TemporaryDirectory(dir=self.common.build_data_dir())
+ self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name))
- if self.system == 'Windows':
- # Windows needs to use network ports, doesn't support unix sockets
- torrc_template = open(common.get_resource_path('torrc_template-windows')).read()
+ # Create the torrc
+ with open(self.common.get_resource_path('torrc_template')) as f:
+ torrc_template = f.read()
+ self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
+ try:
+ self.tor_socks_port = self.common.get_available_port(1000, 65535)
+ except:
+ raise OSError(strings._('no_available_port'))
+ self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
+
+ if self.common.platform == 'Windows' or self.common.platform == "Darwin":
+ # Windows doesn't support unix sockets, so it must use a network port.
+ # macOS can't use unix sockets either because socket filenames are limited to
+ # 100 chars, and the macOS sandbox forces us to put the socket file in a place
+ # with a really long path.
+ torrc_template += 'ControlPort {{control_port}}\n'
try:
- self.tor_control_port = common.get_available_port(1000, 65535)
+ self.tor_control_port = self.common.get_available_port(1000, 65535)
except:
raise OSError(strings._('no_available_port'))
self.tor_control_socket = None
- self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
- try:
- self.tor_socks_port = common.get_available_port(1000, 65535)
- except:
- raise OSError(strings._('no_available_port'))
- self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
else:
- # Linux, Mac and BSD can use unix sockets
- with open(common.get_resource_path('torrc_template')) as f:
- torrc_template = f.read()
+ # Linux and BSD can use unix sockets
+ torrc_template += 'ControlSocket {{control_socket}}\n'
self.tor_control_port = None
self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket')
- self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
- try:
- self.tor_socks_port = common.get_available_port(1000, 65535)
- except:
- raise OSError(strings._('no_available_port'))
- self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name)
torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port))
@@ -202,23 +206,19 @@ class Onion(object):
torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path)
torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path)
torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port))
+
with open(self.tor_torrc, 'w') as f:
f.write(torrc_template)
# Bridge support
if self.settings.get('tor_bridges_use_obfs4'):
f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
- with open(common.get_resource_path('torrc_template-obfs4')) as o:
- for line in o:
- f.write(line)
- elif self.settings.get('tor_bridges_use_meek_lite_amazon'):
- f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
- with open(common.get_resource_path('torrc_template-meek_lite_amazon')) as o:
+ with open(self.common.get_resource_path('torrc_template-obfs4')) as o:
for line in o:
f.write(line)
elif self.settings.get('tor_bridges_use_meek_lite_azure'):
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
- with open(common.get_resource_path('torrc_template-meek_lite_azure')) as o:
+ with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o:
for line in o:
f.write(line)
@@ -232,7 +232,7 @@ class Onion(object):
# Execute a tor subprocess
start_ts = time.time()
- if self.system == 'Windows':
+ if self.common.platform == 'Windows':
# In Windows, hide console window when opening tor.exe subprocess
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
@@ -245,14 +245,14 @@ class Onion(object):
# Connect to the controller
try:
- if self.system == 'Windows':
+ if self.common.platform == 'Windows' or self.common.platform == "Darwin":
self.c = Controller.from_port(port=self.tor_control_port)
self.c.authenticate()
else:
self.c = Controller.from_socket_file(path=self.tor_control_socket)
self.c.authenticate()
except Exception as e:
- raise BundledTorBroken(strings._('settings_error_bundled_tor_broken', True).format(e.args[0]))
+ raise BundledTorBroken(strings._('settings_error_bundled_tor_broken').format(e.args[0]))
while True:
try:
@@ -270,7 +270,7 @@ class Onion(object):
if callable(tor_status_update_func):
if not tor_status_update_func(progress, summary):
# If the dialog was canceled, stop connecting to Tor
- common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor')
+ self.common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor')
print()
return False
@@ -282,7 +282,6 @@ class Onion(object):
# If using bridges, it might take a bit longer to connect to Tor
if self.settings.get('tor_bridges_use_custom_bridges') or \
self.settings.get('tor_bridges_use_obfs4') or \
- self.settings.get('tor_bridges_use_meek_lite_amazon') or \
self.settings.get('tor_bridges_use_meek_lite_azure'):
connect_timeout = 150
else:
@@ -322,7 +321,7 @@ class Onion(object):
socket_file_path = ''
if not found_tor:
try:
- if self.system == 'Darwin':
+ if self.common.platform == 'Darwin':
socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket')
self.c = Controller.from_socket_file(path=socket_file_path)
@@ -334,11 +333,11 @@ class Onion(object):
# guessing the socket file name next
if not found_tor:
try:
- if self.system == 'Linux' or self.system == 'BSD':
+ if self.common.platform == 'Linux' or self.common.platform == 'BSD':
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
- elif self.system == 'Darwin':
+ elif self.common.platform == 'Darwin':
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
- elif self.system == 'Windows':
+ elif self.common.platform == 'Windows':
# Windows doesn't support unix sockets
raise TorErrorAutomatic(strings._('settings_error_automatic'))
@@ -393,6 +392,7 @@ class Onion(object):
# Get the tor version
self.tor_version = self.c.get_version().version_str
+ self.common.log('Onion', 'connect', 'Connected to tor {}'.format(self.tor_version))
# Do the versions of stem and tor that I'm using support ephemeral onion services?
list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None)
@@ -408,6 +408,10 @@ class Onion(object):
# ephemeral stealth onion services are not supported
self.supports_stealth = False
+ # Does this version of Tor support next-gen ('v3') onions?
+ # Note, this is the version of Tor where this bug was fixed:
+ # https://trac.torproject.org/projects/tor/ticket/28619
+ self.supports_v3_onions = self.tor_version >= Version('0.3.5.7')
def is_authenticated(self):
"""
@@ -424,7 +428,7 @@ class Onion(object):
Start a onion service on port 80, pointing to the given port, and
return the onion hostname.
"""
- common.log('Onion', 'start_onion_service')
+ self.common.log('Onion', 'start_onion_service')
self.auth_string = None
if not self.supports_ephemeral:
@@ -433,7 +437,6 @@ class Onion(object):
raise TorTooOld(strings._('error_stealth_not_supported'))
print(strings._("config_onion_service").format(int(port)))
- print(strings._('using_ephemeral'))
if self.stealth:
if self.settings.get('hidservauth_string'):
@@ -445,23 +448,42 @@ class Onion(object):
basic_auth = None
if self.settings.get('private_key'):
- key_type = "RSA1024"
key_content = self.settings.get('private_key')
- common.log('Onion', 'Starting a hidden service with a saved private key')
+ if self.is_v2_key(key_content):
+ key_type = "RSA1024"
+ else:
+ # Assume it was a v3 key. Stem will throw an error if it's something illegible
+ key_type = "ED25519-V3"
+
else:
key_type = "NEW"
- key_content = "RSA1024"
- common.log('Onion', 'Starting a hidden service with a new private key')
+ # Work out if we can support v3 onion services, which are preferred
+ if self.supports_v3_onions and not self.settings.get('use_legacy_v2_onions'):
+ key_content = "ED25519-V3"
+ else:
+ # fall back to v2 onion services
+ key_content = "RSA1024"
+
+ # v3 onions don't yet support basic auth. Our ticket:
+ # https://github.com/micahflee/onionshare/issues/697
+ if key_type == "NEW" and key_content == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'):
+ basic_auth = None
+ self.stealth = False
+ debug_message = 'key_type={}'.format(key_type)
+ if key_type == "NEW":
+ debug_message += ', key_content={}'.format(key_content)
+ self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message))
+ await_publication = True
try:
if basic_auth != None:
- res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth, key_type = key_type, key_content=key_content)
+ res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, basic_auth=basic_auth, key_type=key_type, key_content=key_content)
else:
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
- res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, key_type = key_type, key_content=key_content)
+ res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, key_type=key_type, key_content=key_content)
- except ProtocolError:
- raise TorErrorProtocolError(strings._('error_tor_protocol_error'))
+ except ProtocolError as e:
+ raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0]))
self.service_id = res.service_id
onion_host = self.service_id + '.onion'
@@ -492,23 +514,23 @@ class Onion(object):
self.settings.save()
return onion_host
else:
- raise TorErrorProtocolError(strings._('error_tor_protocol_error'))
+ raise TorErrorProtocolError(strings._('error_tor_protocol_error_unknown'))
def cleanup(self, stop_tor=True):
"""
Stop onion services that were created earlier. If there's a tor subprocess running, kill it.
"""
- common.log('Onion', 'cleanup')
+ self.common.log('Onion', 'cleanup')
# Cleanup the ephemeral onion services, if we have any
try:
onions = self.c.list_ephemeral_hidden_services()
for onion in onions:
try:
- common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion))
+ self.common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion))
self.c.remove_ephemeral_hidden_service(onion)
except:
- common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion))
+ self.common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion))
pass
except:
pass
@@ -545,7 +567,7 @@ class Onion(object):
"""
Returns a (address, port) tuple for the Tor SOCKS port
"""
- common.log('Onion', 'get_tor_socks_port')
+ self.common.log('Onion', 'get_tor_socks_port')
if self.settings.get('connection_type') == 'bundled':
return ('127.0.0.1', self.tor_socks_port)
@@ -553,3 +575,18 @@ class Onion(object):
return ('127.0.0.1', 9150)
else:
return (self.settings.get('socks_address'), self.settings.get('socks_port'))
+
+ def is_v2_key(self, key):
+ """
+ Helper function for determining if a key is RSA1024 (v2) or not.
+ """
+ try:
+ # Import the key
+ key = RSA.importKey(base64.b64decode(key))
+ # Is this a v2 Onion key? (1024 bits) If so, we should keep using it.
+ if key.n.bit_length() == 1024:
+ return True
+ else:
+ return False
+ except:
+ return False