Start refactoring Onion to allow for managing a separate onion service for each tab

This commit is contained in:
Micah Lee 2019-11-10 17:32:34 -08:00
parent 81584e12ff
commit 28bc37d16f
No known key found for this signature in database
GPG key ID: 403C2657CD994F73
5 changed files with 82 additions and 114 deletions

View file

@ -36,7 +36,7 @@ class ModeSettings:
"enabled": False, "enabled": False,
"mode": None, "mode": None,
"private_key": None, "private_key": None,
"hidservauth": None, "hidservauth_string": None,
"password": None, "password": None,
}, },
"general": { "general": {
@ -45,6 +45,7 @@ class ModeSettings:
"autostop_timer": False, "autostop_timer": False,
"legacy": False, "legacy": False,
"client_auth": False, "client_auth": False,
"service_id": None,
}, },
"share": {"autostop_sharing": True, "filenames": []}, "share": {"autostop_sharing": True, "filenames": []},
"receive": {"data_dir": self.build_default_receive_data_dir()}, "receive": {"data_dir": self.build_default_receive_data_dir()},

View file

@ -152,14 +152,8 @@ class Onion(object):
def __init__(self, common): def __init__(self, common):
self.common = common self.common = common
self.common.log("Onion", "__init__") self.common.log("Onion", "__init__")
self.stealth = False
self.service_id = None
self.scheduled_key = None
self.scheduled_auth_cookie = None
# Is bundled tor supported? # Is bundled tor supported?
if ( if (
self.common.platform == "Windows" or self.common.platform == "Darwin" self.common.platform == "Windows" or self.common.platform == "Darwin"
@ -570,48 +564,32 @@ class Onion(object):
else: else:
return False return False
def start_onion_service(self, port, await_publication, save_scheduled_key=False): def start_onion_service(
self, mode_settings, port, await_publication, save_scheduled_key=False
):
""" """
Start a onion service on port 80, pointing to the given port, and Start a onion service on port 80, pointing to the given port, and
return the onion hostname. return the onion hostname.
""" """
self.common.log("Onion", "start_onion_service") self.common.log("Onion", "start_onion_service", f"port={port}")
# Settings may have changed in the frontend but not updated in our settings object,
# such as persistence. Reload the settings now just to be sure.
self.settings.load()
self.auth_string = None
if not self.supports_ephemeral: if not self.supports_ephemeral:
raise TorTooOld(strings._("error_ephemeral_not_supported")) raise TorTooOld(strings._("error_ephemeral_not_supported"))
if self.stealth and not self.supports_stealth: if mode_settings.get("general", "client_auth") and not self.supports_stealth:
raise TorTooOld(strings._("error_stealth_not_supported")) raise TorTooOld(strings._("error_stealth_not_supported"))
if not save_scheduled_key: if mode_settings.get("general", "client_auth") and mode_settings.get(
print(f"Setting up onion service on port {port}.") "general", "hidservauth_string"
):
if self.stealth: auth_cookie = mode_settings.get("persistent", "hidservauth_string").split()[
if self.settings.get("hidservauth_string"): 2
hidservauth_string = self.settings.get("hidservauth_string").split()[2] ]
basic_auth = {"onionshare": hidservauth_string} basic_auth = {"onionshare": auth_cookie}
else:
if self.scheduled_auth_cookie:
basic_auth = {"onionshare": self.scheduled_auth_cookie}
else:
basic_auth = {"onionshare": None}
else: else:
basic_auth = None basic_auth = None
if self.settings.get("private_key"): if mode_settings.get("persistent", "private_key"):
key_content = self.settings.get("private_key") key_content = mode_settings.get("persistent", "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"
elif self.scheduled_key:
key_content = self.scheduled_key
if self.is_v2_key(key_content): if self.is_v2_key(key_content):
key_type = "RSA1024" key_type = "RSA1024"
else: else:
@ -621,9 +599,7 @@ class Onion(object):
else: else:
key_type = "NEW" key_type = "NEW"
# Work out if we can support v3 onion services, which are preferred # Work out if we can support v3 onion services, which are preferred
if self.supports_v3_onions and not self.settings.get( if self.supports_v3_onions and not mode_settings.get("general", "legacy"):
"use_legacy_v2_onions"
):
key_content = "ED25519-V3" key_content = "ED25519-V3"
else: else:
# fall back to v2 onion services # fall back to v2 onion services
@ -634,17 +610,15 @@ class Onion(object):
if ( if (
key_type == "NEW" key_type == "NEW"
and key_content == "ED25519-V3" and key_content == "ED25519-V3"
and not self.settings.get("use_legacy_v2_onions") and not mode_settings.get("general", "legacy")
): ):
basic_auth = None basic_auth = None
self.stealth = False
debug_message = f"key_type={key_type}" debug_message = f"key_type={key_type}"
if key_type == "NEW": if key_type == "NEW":
debug_message += f", key_content={key_content}" debug_message += f", key_content={key_content}"
self.common.log("Onion", "start_onion_service", debug_message) self.common.log("Onion", "start_onion_service", debug_message)
try: try:
if basic_auth != None:
res = self.c.create_ephemeral_hidden_service( res = self.c.create_ephemeral_hidden_service(
{80: port}, {80: port},
await_publication=await_publication, await_publication=await_publication,
@ -652,69 +626,46 @@ class Onion(object):
key_type=key_type, key_type=key_type,
key_content=key_content, 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=await_publication,
key_type=key_type,
key_content=key_content,
)
except ProtocolError as e: except ProtocolError as e:
raise TorErrorProtocolError( raise TorErrorProtocolError(
strings._("error_tor_protocol_error").format(e.args[0]) strings._("error_tor_protocol_error").format(e.args[0])
) )
self.service_id = res.service_id onion_host = res.service_id + ".onion"
onion_host = self.service_id + ".onion"
# A new private key was generated and is in the Control port response. # Save the service_id
if self.settings.get("save_private_key"): if not mode_settings.get("general", "service_id"):
if not self.settings.get("private_key"): mode_settings.set("general", "service_id", res.service_id)
self.settings.set("private_key", res.private_key)
# If we were scheduling a future share, register the private key for later re-use # Save the private key and hidservauth string if persistence is enabled
if save_scheduled_key: if mode_settings.get("persistent", "enabled"):
self.scheduled_key = res.private_key if not mode_settings.get("persistent", "private_key"):
else: mode_settings.set("persistent", "private_key", res.private_key)
self.scheduled_key = None if mode_settings.get("general", "client_auth") and not mode_settings.get(
"persistent", "hidservauth_string"
if self.stealth: ):
# Similar to the PrivateKey, the Control port only returns the ClientAuth
# in the response if it was responsible for creating the basic_auth password
# in the first place.
# If we sent the basic_auth (due to a saved hidservauth_string in the settings),
# there is no response here, so use the saved value from settings.
if self.settings.get("save_private_key"):
if self.settings.get("hidservauth_string"):
self.auth_string = self.settings.get("hidservauth_string")
else:
auth_cookie = list(res.client_auth.values())[0] auth_cookie = list(res.client_auth.values())[0]
self.auth_string = f"HidServAuth {onion_host} {auth_cookie}" auth_string = f"HidServAuth {onion_host} {auth_cookie}"
self.settings.set("hidservauth_string", self.auth_string) mode_settings.set("persistent", "hidservauth_string", auth_string)
else:
if not self.scheduled_auth_cookie:
auth_cookie = list(res.client_auth.values())[0]
self.auth_string = f"HidServAuth {onion_host} {auth_cookie}"
if save_scheduled_key:
# Register the HidServAuth for the scheduled share
self.scheduled_auth_cookie = auth_cookie
else:
self.scheduled_auth_cookie = None
else:
self.auth_string = (
f"HidServAuth {onion_host} {self.scheduled_auth_cookie}"
)
if not save_scheduled_key:
# We've used the scheduled share's HidServAuth. Reset it to None for future shares
self.scheduled_auth_cookie = None
if onion_host is not None:
self.settings.save()
return onion_host return onion_host
else:
raise TorErrorProtocolError(strings._("error_tor_protocol_error_unknown")) def stop_onion_service(self, mode_settings):
"""
Stop a specific onion service
"""
onion_host = mode_settings.get("general", "service_id")
self.common.log("Onion", "stop_onion_service", f"onion host: {onion_host}")
try:
self.c.remove_ephemeral_hidden_service(
mode_settings.get("general", "service_id")
)
except:
self.common.log(
"Onion", "stop_onion_service", f"failed to remove {onion_host}"
)
def cleanup(self, stop_tor=True): def cleanup(self, stop_tor=True):
""" """
@ -725,22 +676,20 @@ class Onion(object):
# Cleanup the ephemeral onion services, if we have any # Cleanup the ephemeral onion services, if we have any
try: try:
onions = self.c.list_ephemeral_hidden_services() onions = self.c.list_ephemeral_hidden_services()
for onion in onions: for service_id in onions:
onion_host = f"{service_id}.onion"
try: try:
self.common.log( self.common.log(
"Onion", "cleanup", f"trying to remove onion {onion}" "Onion", "cleanup", f"trying to remove onion {onion_host}"
) )
self.c.remove_ephemeral_hidden_service(onion) self.c.remove_ephemeral_hidden_service(service_id)
except: except:
self.common.log( self.common.log(
"Onion", "Onion", "cleanup", f"failed to remove onion {onion_host}"
"cleanup",
f"could not remove onion {onion}.. moving on anyway",
) )
pass pass
except: except:
pass pass
self.service_id = None
if stop_tor: if stop_tor:
# Stop tor process # Stop tor process

View file

@ -82,12 +82,18 @@ class OnionShare(object):
return return
self.onion_host = self.onion.start_onion_service( self.onion_host = self.onion.start_onion_service(
self.port, await_publication, save_scheduled_key mode_settings, self.port, await_publication, save_scheduled_key
) )
if mode_settings.get("general", "client_auth"): if mode_settings.get("general", "client_auth"):
self.auth_string = self.onion.auth_string self.auth_string = self.onion.auth_string
def stop_onion_service(self, mode_settings):
"""
Stop the onion service
"""
self.onion.stop_onion_service(mode_settings)
def cleanup(self): def cleanup(self):
""" """
Shut everything down and clean up temporary files, etc. Shut everything down and clean up temporary files, etc.

View file

@ -362,7 +362,7 @@ class Tab(QtWidgets.QWidget):
def stop_server_finished(self): def stop_server_finished(self):
# When the server stopped, cleanup the ephemeral onion service # When the server stopped, cleanup the ephemeral onion service
self.common.gui.onion.cleanup(stop_tor=False) self.get_mode().app.stop_onion_service(self.settings)
def timer_callback(self): def timer_callback(self):
""" """

View file

@ -20,7 +20,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import time import time
from PyQt5 import QtCore from PyQt5 import QtCore
from onionshare.onion import * from onionshare import strings
from onionshare.onion import (
TorTooOld,
TorErrorInvalidSetting,
TorErrorAutomatic,
TorErrorSocketPort,
TorErrorSocketFile,
TorErrorMissingPassword,
TorErrorUnreadableCookieFile,
TorErrorAuthError,
TorErrorProtocolError,
BundledTorTimeout,
)
class OnionThread(QtCore.QThread): class OnionThread(QtCore.QThread):
@ -64,7 +76,7 @@ class OnionThread(QtCore.QThread):
time.sleep(0.2) time.sleep(0.2)
self.success_early.emit() self.success_early.emit()
# Unregister the onion so we can use it in the next OnionThread # Unregister the onion so we can use it in the next OnionThread
self.mode.app.onion.cleanup(False) self.mode.app.start_onion_service(self.mode.settings)
else: else:
self.mode.app.start_onion_service( self.mode.app.start_onion_service(
self.mode.settings, await_publication=True self.mode.settings, await_publication=True