Merge branch 'mig5-reuse_private_key'

This commit is contained in:
Micah Lee 2018-01-14 18:36:38 -08:00
commit 1eea734fb5
No known key found for this signature in database
GPG Key ID: 403C2657CD994F73
10 changed files with 144 additions and 22 deletions

View File

@ -23,7 +23,7 @@ import os, sys, time, argparse, threading
from . import strings, common, web from . import strings, common, web
from .onion import * from .onion import *
from .onionshare import OnionShare from .onionshare import OnionShare
from .settings import Settings
def main(cwd=None): def main(cwd=None):
""" """
@ -77,6 +77,10 @@ def main(cwd=None):
if not valid: if not valid:
sys.exit() sys.exit()
settings = Settings(config)
settings.load()
# Start the Onion object # Start the Onion object
onion = Onion() onion = Onion()
try: try:
@ -108,7 +112,7 @@ def main(cwd=None):
print('') print('')
# Start OnionShare http service in new thread # Start OnionShare http service in new thread
t = threading.Thread(target=web.start, args=(app.port, app.stay_open)) t = threading.Thread(target=web.start, args=(app.port, app.stay_open, settings.get('slug')))
t.daemon = True t.daemon = True
t.start() t.start()
@ -120,6 +124,12 @@ def main(cwd=None):
if app.shutdown_timeout > 0: if app.shutdown_timeout > 0:
app.shutdown_timer.start() app.shutdown_timer.start()
# Save the web slug if we are using a persistent private key
if settings.get('save_private_key'):
if not settings.get('slug'):
settings.set('slug', web.slug)
settings.save()
if(stealth): if(stealth):
print(strings._("give_this_url_stealth")) print(strings._("give_this_url_stealth"))
print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))

View File

@ -368,7 +368,7 @@ class Onion(object):
# Do the versions of stem and tor that I'm using support stealth onion services? # Do the versions of stem and tor that I'm using support stealth onion services?
try: try:
res = self.c.create_ephemeral_hidden_service({1:1}, basic_auth={'onionshare':None}, await_publication=False) res = self.c.create_ephemeral_hidden_service({1:1}, basic_auth={'onionshare':None}, await_publication=False)
tmp_service_id = res.content()[0][2].split('=')[1] tmp_service_id = res.service_id
self.c.remove_ephemeral_hidden_service(tmp_service_id) self.c.remove_ephemeral_hidden_service(tmp_service_id)
self.supports_stealth = True self.supports_stealth = True
except: except:
@ -403,16 +403,29 @@ class Onion(object):
print(strings._('using_ephemeral')) print(strings._('using_ephemeral'))
if self.stealth: if self.stealth:
if self.settings.get('hidservauth_string'):
hidservauth_string = self.settings.get('hidservauth_string').split()[2]
basic_auth = {'onionshare':hidservauth_string}
else:
basic_auth = {'onionshare':None} basic_auth = {'onionshare':None}
else: else:
basic_auth = None 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')
else:
key_type = "NEW"
key_content = "RSA1024"
common.log('Onion', 'Starting a hidden service with a new private key')
try: try:
if basic_auth != None : if basic_auth != None:
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth) res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth, key_type = key_type, key_content=key_content)
else : else:
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg # 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) res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, key_type = key_type, key_content=key_content)
except ProtocolError: except ProtocolError:
raise TorErrorProtocolError(strings._('error_tor_protocol_error')) raise TorErrorProtocolError(strings._('error_tor_protocol_error'))
@ -420,10 +433,29 @@ class Onion(object):
self.service_id = res.service_id self.service_id = res.service_id
onion_host = self.service_id + '.onion' onion_host = self.service_id + '.onion'
# A new private key was generated and is in the Control port response.
if self.settings.get('save_private_key'):
if not self.settings.get('private_key'):
self.settings.set('private_key', res.private_key)
if self.stealth: if self.stealth:
auth_cookie = res.content()[2][2].split('=')[1].split(':')[1] # 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]
self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie)
self.settings.set('hidservauth_string', self.auth_string)
else:
auth_cookie = list(res.client_auth.values())[0]
self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie)
self.settings.save()
if onion_host is not None: if onion_host is not None:
return onion_host return onion_host
else: else:

View File

@ -60,7 +60,11 @@ class Settings(object):
'systray_notifications': True, 'systray_notifications': True,
'use_stealth': False, 'use_stealth': False,
'use_autoupdate': True, 'use_autoupdate': True,
'autoupdate_timestamp': None 'autoupdate_timestamp': None,
'save_private_key': False,
'private_key': '',
'slug': '',
'hidservauth_string': ''
} }
self._settings = {} self._settings = {}
self.fill_in_defaults() self.fill_in_defaults()

View File

@ -128,8 +128,11 @@ def add_request(request_type, path, data=None):
slug = None slug = None
def generate_slug(): def generate_slug(persistent_slug=''):
global slug global slug
if persistent_slug:
slug = persistent_slug
else:
slug = common.build_slug() slug = common.build_slug()
download_count = 0 download_count = 0
@ -383,11 +386,11 @@ def force_shutdown():
func() func()
def start(port, stay_open=False): def start(port, stay_open=False, persistent_slug=''):
""" """
Start the flask web server. Start the flask web server.
""" """
generate_slug() generate_slug(persistent_slug)
set_stay_open(stay_open) set_stay_open(stay_open)

View File

@ -69,7 +69,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.file_selection.file_list.add_file(filename) self.file_selection.file_list.add_file(filename)
# Server status # Server status
self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection) self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection, self.settings)
self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.file_selection.server_started)
self.server_status.server_started.connect(self.start_server) self.server_status.server_started.connect(self.start_server)
self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.file_selection.server_stopped)
@ -119,11 +119,17 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Status bar, zip progress bar # Status bar, zip progress bar
self._zip_progress_bar = None self._zip_progress_bar = None
# Persistent URL notification
self.persistent_url_label = QtWidgets.QLabel(strings._('persistent_url_in_use', True))
self.persistent_url_label.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;')
self.persistent_url_label.hide()
# Main layout # Main layout
self.layout = QtWidgets.QVBoxLayout() self.layout = QtWidgets.QVBoxLayout()
self.layout.addLayout(self.file_selection) self.layout.addLayout(self.file_selection)
self.layout.addLayout(self.server_status) self.layout.addLayout(self.server_status)
self.layout.addWidget(self.filesize_warning) self.layout.addWidget(self.filesize_warning)
self.layout.addWidget(self.persistent_url_label)
self.layout.addWidget(self.downloads_container) self.layout.addWidget(self.downloads_container)
central_widget = QtWidgets.QWidget() central_widget = QtWidgets.QWidget()
central_widget.setLayout(self.layout) central_widget.setLayout(self.layout)
@ -272,7 +278,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.app.stay_open = not self.settings.get('close_after_first_download') self.app.stay_open = not self.settings.get('close_after_first_download')
# start onionshare http service in new thread # start onionshare http service in new thread
t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open)) t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.settings.get('slug')))
t.daemon = True t.daemon = True
t.start() t.start()
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash # wait for modules in thread to load, preventing a thread-related cx_Freeze crash
@ -346,6 +352,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.stop_server() self.stop_server()
self.start_server_error(strings._('gui_server_started_after_timeout')) self.start_server_error(strings._('gui_server_started_after_timeout'))
if self.settings.get('save_private_key'):
self.persistent_url_label.show()
def start_server_error(self, error): def start_server_error(self, error):
""" """
If there's an error when trying to start the onion service If there's an error when trying to start the onion service
@ -377,6 +386,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Remove ephemeral service, but don't disconnect from Tor # Remove ephemeral service, but don't disconnect from Tor
self.onion.cleanup(stop_tor=False) self.onion.cleanup(stop_tor=False)
self.filesize_warning.hide() self.filesize_warning.hide()
self.persistent_url_label.hide()
self.stop_server_finished.emit() self.stop_server_finished.emit()
self.set_server_active(False) self.set_server_active(False)

View File

@ -21,7 +21,7 @@ import platform
from .alert import Alert from .alert import Alert
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings, common from onionshare import strings, common, settings
class ServerStatus(QtWidgets.QVBoxLayout): class ServerStatus(QtWidgets.QVBoxLayout):
""" """
@ -36,7 +36,7 @@ class ServerStatus(QtWidgets.QVBoxLayout):
STATUS_WORKING = 1 STATUS_WORKING = 1
STATUS_STARTED = 2 STATUS_STARTED = 2
def __init__(self, qtapp, app, web, file_selection): def __init__(self, qtapp, app, web, file_selection, settings):
super(ServerStatus, self).__init__() super(ServerStatus, self).__init__()
self.status = self.STATUS_STOPPED self.status = self.STATUS_STOPPED
@ -45,6 +45,8 @@ class ServerStatus(QtWidgets.QVBoxLayout):
self.web = web self.web = web
self.file_selection = file_selection self.file_selection = file_selection
self.settings = settings
# Helper boolean as this is used in a few places # Helper boolean as this is used in a few places
self.timer_enabled = False self.timer_enabled = False
# Shutdown timeout layout # Shutdown timeout layout
@ -141,6 +143,11 @@ class ServerStatus(QtWidgets.QVBoxLayout):
self.url_label.show() self.url_label.show()
self.copy_url_button.show() self.copy_url_button.show()
if self.settings.get('save_private_key'):
if not self.settings.get('slug'):
self.settings.set('slug', self.web.slug)
self.settings.save()
if self.app.stealth: if self.app.stealth:
self.copy_hidservauth_button.show() self.copy_hidservauth_button.show()
else: else:

View File

@ -60,10 +60,16 @@ class SettingsDialog(QtWidgets.QDialog):
self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked) self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked)
self.systray_notifications_checkbox.setText(strings._("gui_settings_systray_notifications", True)) self.systray_notifications_checkbox.setText(strings._("gui_settings_systray_notifications", True))
# Whether or not to save the Onion private key for reuse
self.save_private_key_checkbox = QtWidgets.QCheckBox()
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True))
# Sharing options layout # Sharing options layout
sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout = QtWidgets.QVBoxLayout()
sharing_group_layout.addWidget(self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
sharing_group_layout.addWidget(self.systray_notifications_checkbox) sharing_group_layout.addWidget(self.systray_notifications_checkbox)
sharing_group_layout.addWidget(self.save_private_key_checkbox)
sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True))
sharing_group.setLayout(sharing_group_layout) sharing_group.setLayout(sharing_group_layout)
@ -78,10 +84,20 @@ class SettingsDialog(QtWidgets.QDialog):
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True))
hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True))
hidservauth_details.setWordWrap(True)
hidservauth_details.hide()
self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked)
self.hidservauth_copy_button.hide()
# Stealth options layout # Stealth options layout
stealth_group_layout = QtWidgets.QVBoxLayout() stealth_group_layout = QtWidgets.QVBoxLayout()
stealth_group_layout.addWidget(stealth_details) stealth_group_layout.addWidget(stealth_details)
stealth_group_layout.addWidget(self.stealth_checkbox) stealth_group_layout.addWidget(self.stealth_checkbox)
stealth_group_layout.addWidget(hidservauth_details)
stealth_group_layout.addWidget(self.hidservauth_copy_button)
stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True))
stealth_group.setLayout(stealth_group_layout) stealth_group.setLayout(stealth_group_layout)
@ -277,9 +293,18 @@ class SettingsDialog(QtWidgets.QDialog):
else: else:
self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Unchecked) self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Unchecked)
save_private_key = self.old_settings.get('save_private_key')
if save_private_key:
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked)
else:
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
use_stealth = self.old_settings.get('use_stealth') use_stealth = self.old_settings.get('use_stealth')
if use_stealth: if use_stealth:
self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) self.stealth_checkbox.setCheckState(QtCore.Qt.Checked)
if save_private_key:
hidservauth_details.show()
self.hidservauth_copy_button.show()
else: else:
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
@ -379,6 +404,15 @@ class SettingsDialog(QtWidgets.QDialog):
else: else:
self.authenticate_password_extras.hide() self.authenticate_password_extras.hide()
def hidservauth_copy_button_clicked(self):
"""
Toggle the 'Copy HidServAuth' button
to copy the saved HidServAuth to clipboard.
"""
common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard')
clipboard = self.qtapp.clipboard()
clipboard.setText(self.old_settings.get('hidservauth_string'))
def test_tor_clicked(self): def test_tor_clicked(self):
""" """
Test Tor Settings button clicked. With the given settings, see if we can Test Tor Settings button clicked. With the given settings, see if we can
@ -533,7 +567,21 @@ class SettingsDialog(QtWidgets.QDialog):
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
settings.set('systray_notifications', self.systray_notifications_checkbox.isChecked()) settings.set('systray_notifications', self.systray_notifications_checkbox.isChecked())
if self.save_private_key_checkbox.isChecked():
settings.set('save_private_key', True)
settings.set('private_key', self.old_settings.get('private_key'))
settings.set('slug', self.old_settings.get('slug'))
settings.set('hidservauth_string', self.old_settings.get('hidservauth_string'))
else:
settings.set('save_private_key', False)
settings.set('private_key', '')
settings.set('slug', '')
# Also unset the HidServAuth if we are removing our reusable private key
settings.set('hidservauth_string', '')
settings.set('use_stealth', self.stealth_checkbox.isChecked()) settings.set('use_stealth', self.stealth_checkbox.isChecked())
# Always unset the HidServAuth if Stealth mode is unset
if not self.stealth_checkbox.isChecked():
settings.set('hidservauth_string', '')
if self.connection_type_bundled_radio.isChecked(): if self.connection_type_bundled_radio.isChecked():
settings.set('connection_type', 'bundled') settings.set('connection_type', 'bundled')

View File

@ -67,6 +67,7 @@
"gui_settings_stealth_label": "Stealth (advanced)", "gui_settings_stealth_label": "Stealth (advanced)",
"gui_settings_stealth_option": "Create stealth onion services", "gui_settings_stealth_option": "Create stealth onion services",
"gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to it.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">More information</a>.", "gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to it.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">More information</a>.",
"gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.",
"gui_settings_autoupdate_label": "Check for updates", "gui_settings_autoupdate_label": "Check for updates",
"gui_settings_autoupdate_option": "Notify me when updates are available", "gui_settings_autoupdate_option": "Notify me when updates are available",
"gui_settings_autoupdate_timestamp": "Last checked: {}", "gui_settings_autoupdate_timestamp": "Last checked: {}",
@ -122,5 +123,7 @@
"gui_tor_connection_lost": "Disconnected from Tor.", "gui_tor_connection_lost": "Disconnected from Tor.",
"gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.", "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.",
"gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.",
"share_via_onionshare": "Share via OnionShare" "share_via_onionshare": "Share via OnionShare",
"gui_save_private_key_checkbox": "Use a persistent URL\n(unchecking will delete any saved URL)",
"persistent_url_in_use": "This share is using a persistent URL"
} }

View File

@ -27,6 +27,7 @@ from onionshare import OnionShare
class MyOnion: class MyOnion:
def __init__(self, stealth=False): def __init__(self, stealth=False):
self.auth_string = 'TestHidServAuth' self.auth_string = 'TestHidServAuth'
self.private_key = ''
self.stealth = stealth self.stealth = stealth
@staticmethod @staticmethod

View File

@ -57,7 +57,11 @@ class TestSettings:
'systray_notifications': True, 'systray_notifications': True,
'use_stealth': False, 'use_stealth': False,
'use_autoupdate': True, 'use_autoupdate': True,
'autoupdate_timestamp': None 'autoupdate_timestamp': None,
'save_private_key': False,
'private_key': '',
'slug': '',
'hidservauth_string': ''
} }
def test_fill_in_defaults(self, settings_obj): def test_fill_in_defaults(self, settings_obj):