From 5252d5d94a808fd478b1ac9d0206d19bd38db483 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 21 Aug 2018 19:31:02 +1000 Subject: [PATCH 01/25] Introduce v3 onion support --- .travis.yml | 2 +- BUILD.md | 4 +- install/requirements-windows.txt | 4 + install/requirements.txt | 4 + onionshare/onion.py | 51 +++++++++--- onionshare/onionkey.py | 126 ++++++++++++++++++++++++++++++ onionshare_gui/server_status.py | 11 ++- onionshare_gui/settings_dialog.py | 9 ++- 8 files changed, 195 insertions(+), 16 deletions(-) create mode 100644 onionshare/onionkey.py diff --git a/.travis.yml b/.travis.yml index 9010e77a..301f87a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ python: - "nightly" # command to install dependencies install: - - pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls flake8 + - pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls flake8 pycrypto pynacl cryptography pysha3 before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics diff --git a/BUILD.md b/BUILD.md index e6e54951..57c83438 100644 --- a/BUILD.md +++ b/BUILD.md @@ -11,9 +11,9 @@ cd onionshare Install the needed dependencies: -For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy` +For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy python3-cryptography python3-crypto python3-nacl python3-pip; pip3 install pysha3` -For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4` +For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4 python3-pynacl python3-cryptography python3-crypto python3-pip; pip3 install pysha3` After that you can try both the CLI and the GUI version of OnionShare: diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt index 32b8da4a..611edf3c 100644 --- a/install/requirements-windows.txt +++ b/install/requirements-windows.txt @@ -1,4 +1,5 @@ click==6.7 +cryptography==2.1.4 Flask==0.12.2 future==0.16.0 itsdangerous==0.24 @@ -8,6 +9,9 @@ pefile==2017.11.5 PyInstaller==3.3.1 PyQt5==5.9.2 PySocks==1.6.7 +pynacl==1.2.1 +pycrypto==2.6.1 +pysha3==1.0.2 sip==4.19.6 stem==1.6.0 Werkzeug==0.14.1 diff --git a/install/requirements.txt b/install/requirements.txt index c7828080..964030e8 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,4 +1,5 @@ click==6.7 +cryptography==2.1.4 Flask==0.12.2 itsdangerous==0.24 Jinja2==2.10 @@ -6,6 +7,9 @@ MarkupSafe==1.0 PyInstaller==3.3.1 PyQt5==5.9.2 PySocks==1.6.7 +pycrypto==2.6.1 +pynacl==1.2.1 +pysha3==1.0.2 sip==4.19.6 stem==1.6.0 Werkzeug==0.14.1 diff --git a/onionshare/onion.py b/onionshare/onion.py index 4812842a..73c94600 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -21,8 +21,10 @@ along with this program. If not, see . 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 +import base64, os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex +from distutils.version import LooseVersion as Version +from . import onionkey from . import socks from . import common, strings from .settings import Settings @@ -444,20 +446,49 @@ class Onion(object): basic_auth = None if self.settings.get('private_key'): - key_type = "RSA1024" - key_content = self.settings.get('private_key') - self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a saved private key') + try: + # is the key a v2 key? + key = onionkey.is_v2_key(self.settings.get('private_key')) + key_type = "RSA1024" + key_content = self.settings.get('private_key') + # The below section is commented out because re-publishing + # a pre-prepared v3 private key is currently unstable in Tor. + # This is fixed upstream but won't reach stable until 0.3.5 + # (expected in December 2018) + # See https://trac.torproject.org/projects/tor/ticket/25552 + # Until then, we will deliberately not work with 'persistent' + # v3 onions, which should not be possible via the GUI settings + # anyway. + # Our ticket: https://github.com/micahflee/onionshare/issues/677 + except: + pass + # Assume it was a v3 key + # key_type = "ED25519-V3" + # key_content = self.settings.get('private_key') + self.common.log('Onion', 'Starting a hidden service with a saved private key') else: - key_type = "NEW" - key_content = "RSA1024" - self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a new private key') + # Work out if we can support v3 onion services, which are preferred + if Version(self.tor_version) >= Version('0.3.3'): + key_type = "ED25519-V3" + key_content = onionkey.generate_v3_private_key()[0] + else: + # fall back to v2 onion services + key_type = "RSA1024" + key_content = onionkey.generate_v2_private_key()[0] + self.common.log('Onion', 'Starting a hidden service with a new private key') + + # v3 onions don't yet support basic auth. Our ticket: + # https://github.com/micahflee/onionshare/issues/697 + if key_type == "ED25519-V3": + basic_auth = None + self.stealth = False 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=True, 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=True, key_type=key_type, key_content=key_content) except ProtocolError: raise TorErrorProtocolError(strings._('error_tor_protocol_error')) @@ -468,7 +499,7 @@ class Onion(object): # 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) + self.settings.set('private_key', key_content) if self.stealth: # Similar to the PrivateKey, the Control port only returns the ClientAuth diff --git a/onionshare/onionkey.py b/onionshare/onionkey.py new file mode 100644 index 00000000..89c781ab --- /dev/null +++ b/onionshare/onionkey.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2017 Micah Lee + +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 +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import os +import sys + +import base64 +import hashlib +# Need sha3 if python version is older than 3.6, otherwise +# we can't use hashlib.sha3_256 +if sys.version_info < (3, 6): + import sha3 + +import nacl.signing + +from Crypto.PublicKey import RSA + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa + + +b = 256 + +def bit(h, i): + return (h[i // 8] >> (i % 8)) & 1 + + +def encodeint(y): + bits = [(y >> i) & 1 for i in range(b)] + return b''.join([bytes([(sum([bits[i * 8 + j] << j for j in range(8)]))]) for i in range(b // 8)]) + + +def H(m): + return hashlib.sha512(m).digest() + + +def expandSK(sk): + h = H(sk) + a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) + k = b''.join([bytes([h[i]]) for i in range(b // 8, b // 4)]) + assert len(k) == 32 + return encodeint(a) + k + + +def onion_url_from_private_key(private_key): + """ + Derives the public key (.onion hostname) from a v3-style + Onion private key. + """ + private_key = nacl.signing.SigningKey(seed=private_key) + pubkey = bytes(private_key.verify_key) + version = b'\x03' + checksum = hashlib.sha3_256(b".onion checksum" + pubkey + version).digest()[:2] + onion_address = "http://{}.onion".format(base64.b32encode(pubkey + checksum + version).decode().lower()) + return onion_address + + +def generate_v3_private_key(): + """ + Generates a private and public key for use with v3 style Onions. + Returns both the private key as well as the public key (.onion hostname) + """ + secretKey = os.urandom(32) + expandedSecretKey = expandSK(secretKey) + private_key = base64.b64encode(expandedSecretKey).decode() + return (private_key, onion_url_from_private_key(secretKey)) + +def generate_v2_private_key(): + """ + Generates a private and public key for use with v2 style Onions. + Returns both the serialized private key (compatible with Stem) + as well as the public key (.onion hostname) + """ + # Generate v2 Onion Service private key + private_key = rsa.generate_private_key(public_exponent=65537, + key_size=1024, + backend=default_backend()) + hs_public_key = private_key.public_key() + + # Pre-generate the public key (.onion hostname) + der_format = hs_public_key.public_bytes(encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.PKCS1) + + onion_url = base64.b32encode(hashlib.sha1(der_format).digest()[:-10]).lower().decode() + + # Generate Stem-compatible key content + pem_format = private_key.private_bytes(encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption()) + serialized_key = ''.join(pem_format.decode().split('\n')[1:-2]) + + return (serialized_key, onion_url) + +def is_v2_key(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 + diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 1562ee10..46310605 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ import platform +import textwrap from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings @@ -88,7 +89,7 @@ class ServerStatus(QtWidgets.QWidget): self.url = QtWidgets.QLabel() self.url.setFont(url_font) self.url.setWordWrap(True) - self.url.setMinimumHeight(60) + self.url.setMinimumHeight(65) self.url.setMinimumSize(self.url.sizeHint()) self.url.setStyleSheet(self.common.css['server_status_url']) @@ -162,7 +163,13 @@ class ServerStatus(QtWidgets.QWidget): else: self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) - self.url.setText(self.get_url()) + # Wrap the Onion URL if it's a big v3 one + url_length=len(self.get_url()) + if url_length > 60: + wrapped_onion_url = textwrap.fill(self.get_url(), 50) + self.url.setText(wrapped_onion_url) + else: + self.url.setText(self.get_url()) self.url.show() self.copy_url_button.show() diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 94480205..a41226f6 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -19,6 +19,7 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui import sys, platform, datetime, re +from distutils.version import LooseVersion as Version from onionshare import strings, common from onionshare.settings import Settings @@ -64,7 +65,7 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) - # Whether or not to save the Onion private key for reuse + # Whether or not to save the Onion private key for reuse (persistent URLs) 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)) @@ -421,6 +422,9 @@ class SettingsDialog(QtWidgets.QDialog): self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) + # Using persistent URLs with v3 onions is not yet stable + if Version(self.onion.tor_version) >= Version('0.3.2.9'): + self.save_private_key_checkbox.hide() downloads_dir = self.old_settings.get('downloads_dir') self.downloads_dir_lineedit.setText(downloads_dir) @@ -445,6 +449,9 @@ class SettingsDialog(QtWidgets.QDialog): self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) + # Using Client Auth with v3 onions is not yet possible + if Version(self.onion.tor_version) >= Version('0.3.2.9'): + stealth_group.hide() use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: From 8c31505b90a30b5b5966821f3965a39c32e5d5a5 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 22 Aug 2018 11:45:08 +1000 Subject: [PATCH 02/25] Rather than hide persistence/stealth mode altogether if the Tor version is high enough for v3, give the user the option to 'use legacy v2 onions' in Settings dialog, so that they may continue to use persistence etc --- onionshare/onion.py | 4 +- onionshare/settings.py | 1 + onionshare_gui/settings_dialog.py | 88 ++++++++++++++++++++++++++++--- 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 73c94600..18ebeb5d 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -468,7 +468,7 @@ class Onion(object): self.common.log('Onion', 'Starting a hidden service with a saved private key') else: # Work out if we can support v3 onion services, which are preferred - if Version(self.tor_version) >= Version('0.3.3'): + if Version(self.tor_version) >= Version('0.3.2.9') and not self.settings.get('use_legacy_v2_onions'): key_type = "ED25519-V3" key_content = onionkey.generate_v3_private_key()[0] else: @@ -479,7 +479,7 @@ class Onion(object): # v3 onions don't yet support basic auth. Our ticket: # https://github.com/micahflee/onionshare/issues/697 - if key_type == "ED25519-V3": + if key_type == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'): basic_auth = None self.stealth = False diff --git a/onionshare/settings.py b/onionshare/settings.py index 6d551ca0..9968a828 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -68,6 +68,7 @@ class Settings(object): 'tor_bridges_use_meek_lite_amazon': False, 'tor_bridges_use_meek_lite_azure': False, 'tor_bridges_use_custom_bridges': '', + 'use_legacy_v2_onions': False, 'save_private_key': False, 'private_key': '', 'slug': '', diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index a41226f6..8574bc6e 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -65,15 +65,25 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) + # Whether or not to use legacy v2 onions + self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() + self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox", True)) + self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_clicked) + if Version(self.onion.tor_version) < Version('0.3.2.9'): + self.use_legacy_v2_onions_checkbox.hide() + # Whether or not to save the Onion private key for reuse (persistent URLs) 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)) + self.save_private_key_checkbox.clicked.connect(self.save_private_key_checkbox_clicked) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.shutdown_timeout_checkbox) + sharing_group_layout.addWidget(self.use_legacy_v2_onions_checkbox) sharing_group_layout.addWidget(self.save_private_key_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) @@ -118,6 +128,7 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) + self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details.setWordWrap(True) @@ -134,8 +145,8 @@ class SettingsDialog(QtWidgets.QDialog): 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.setLayout(stealth_group_layout) + self.stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) + self.stealth_group.setLayout(stealth_group_layout) # Automatic updates options @@ -380,7 +391,7 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) - left_col_layout.addWidget(stealth_group) + left_col_layout.addWidget(self.stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() @@ -417,15 +428,25 @@ class SettingsDialog(QtWidgets.QDialog): else: self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked) + use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') + save_private_key = self.old_settings.get('save_private_key') if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) + # Legacy v2 mode is forced on if persistence is enabled + self.use_legacy_v2_onions_checkbox.setEnabled(False) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.use_legacy_v2_onions_checkbox.setEnabled(True) # Using persistent URLs with v3 onions is not yet stable - if Version(self.onion.tor_version) >= Version('0.3.2.9'): + if Version(self.onion.tor_version) >= Version('0.3.2.9') and not use_legacy_v2_onions: self.save_private_key_checkbox.hide() + if use_legacy_v2_onions or save_private_key: + self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) + self.save_private_key_checkbox.show() + self.stealth_group.show() + downloads_dir = self.old_settings.get('downloads_dir') self.downloads_dir_lineedit.setText(downloads_dir) @@ -444,14 +465,17 @@ class SettingsDialog(QtWidgets.QDialog): use_stealth = self.old_settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) + # Legacy v2 mode is forced on if Stealth is enabled + self.use_legacy_v2_onions_checkbox.setEnabled(False) if save_private_key: hidservauth_details.show() self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.use_legacy_v2_onions_checkbox.setEnabled(True) # Using Client Auth with v3 onions is not yet possible - if Version(self.onion.tor_version) >= Version('0.3.2.9'): - stealth_group.hide() + if not use_legacy_v2_onions: + self.stealth_group.hide() use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: @@ -627,6 +651,37 @@ class SettingsDialog(QtWidgets.QDialog): clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) + def use_legacy_v2_onions_clicked(self, checked): + """ + Show the persistent and stealth options since we're using legacy onions. + """ + if checked: + self.save_private_key_checkbox.show() + self.stealth_group.show() + else: + self.save_private_key_checkbox.hide() + self.stealth_group.hide() + + def save_private_key_checkbox_clicked(self, checked): + """ + Prevent the v2 legacy mode being switched off if persistence is enabled + """ + if checked: + self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) + self.use_legacy_v2_onions_checkbox.setEnabled(False) + else: + self.use_legacy_v2_onions_checkbox.setEnabled(True) + + def stealth_checkbox_clicked_connect(self, checked): + """ + Prevent the v2 legacy mode being switched off if stealth is enabled + """ + if checked: + self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) + self.use_legacy_v2_onions_checkbox.setEnabled(False) + else: + self.use_legacy_v2_onions_checkbox.setEnabled(True) + def downloads_button_clicked(self): """ Browse for a new downloads directory @@ -813,7 +868,16 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) settings.set('shutdown_timeout', self.shutdown_timeout_checkbox.isChecked()) + + # Complicated logic here to force v2 onion mode on or off depending on other settings + if self.use_legacy_v2_onions_checkbox.isChecked(): + use_legacy_v2_onions = True + else: + use_legacy_v2_onions = False + if self.save_private_key_checkbox.isChecked(): + # force the legacy mode on + use_legacy_v2_onions = True settings.set('save_private_key', True) settings.set('private_key', self.old_settings.get('private_key')) settings.set('slug', self.old_settings.get('slug')) @@ -824,6 +888,18 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('slug', '') # Also unset the HidServAuth if we are removing our reusable private key settings.set('hidservauth_string', '') + + if use_legacy_v2_onions: + settings.set('use_legacy_v2_onions', True) + else: + settings.set('use_legacy_v2_onions', False) + # If we are not using legacy mode, but we previously had persistence turned on, force it off! + 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('downloads_dir', self.downloads_dir_lineedit.text()) settings.set('receive_allow_receiver_shutdown', self.receive_allow_receiver_shutdown_checkbox.isChecked()) settings.set('receive_public_mode', self.receive_public_mode_checkbox.isChecked()) From a3c89bdf6721a427caee36d4b4f07bbc1eb97f35 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 22 Aug 2018 11:50:16 +1000 Subject: [PATCH 03/25] Fix tests --- test/test_onionshare_settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 3942ab8c..19851db5 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -60,6 +60,7 @@ class TestSettings: 'tor_bridges_use_meek_lite_amazon': False, 'tor_bridges_use_meek_lite_azure': False, 'tor_bridges_use_custom_bridges': '', + 'use_legacy_v2_onions': False, 'save_private_key': False, 'private_key': '', 'slug': '', From a5cb8385afbc52e377004d1430f7e3cddf1618d1 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 22 Aug 2018 17:03:15 +1000 Subject: [PATCH 04/25] Add missing locale key for legacy v2 onions --- share/locale/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/share/locale/en.json b/share/locale/en.json index b1d247d9..8205a29a 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -153,6 +153,7 @@ "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.", "share_via_onionshare": "Share via OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Use legacy (v2) .onion addresses?", "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", From 1e70df4f22b8ed67d8c3f4901bb855c206476da9 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 23 Aug 2018 11:02:28 +1000 Subject: [PATCH 05/25] Don't try and parse Tor version in order whether or not to show v2-only features. Just note in the QLabel what is v2-only. Still force v2 legacy mode on when using persistence or stealth. --- onionshare_gui/settings_dialog.py | 35 +++++++------------------------ share/locale/en.json | 4 ++-- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 8574bc6e..b2515a69 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -19,7 +19,6 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui import sys, platform, datetime, re -from distutils.version import LooseVersion as Version from onionshare import strings, common from onionshare.settings import Settings @@ -69,9 +68,6 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox", True)) - self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_clicked) - if Version(self.onion.tor_version) < Version('0.3.2.9'): - self.use_legacy_v2_onions_checkbox.hide() # Whether or not to save the Onion private key for reuse (persistent URLs) self.save_private_key_checkbox = QtWidgets.QCheckBox() @@ -145,8 +141,8 @@ class SettingsDialog(QtWidgets.QDialog): stealth_group_layout.addWidget(self.stealth_checkbox) stealth_group_layout.addWidget(hidservauth_details) stealth_group_layout.addWidget(self.hidservauth_copy_button) - self.stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) - self.stealth_group.setLayout(stealth_group_layout) + stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) + stealth_group.setLayout(stealth_group_layout) # Automatic updates options @@ -391,7 +387,7 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) - left_col_layout.addWidget(self.stealth_group) + left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() @@ -438,14 +434,9 @@ class SettingsDialog(QtWidgets.QDialog): else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setEnabled(True) - # Using persistent URLs with v3 onions is not yet stable - if Version(self.onion.tor_version) >= Version('0.3.2.9') and not use_legacy_v2_onions: - self.save_private_key_checkbox.hide() if use_legacy_v2_onions or save_private_key: self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) - self.save_private_key_checkbox.show() - self.stealth_group.show() downloads_dir = self.old_settings.get('downloads_dir') self.downloads_dir_lineedit.setText(downloads_dir) @@ -473,9 +464,6 @@ class SettingsDialog(QtWidgets.QDialog): else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setEnabled(True) - # Using Client Auth with v3 onions is not yet possible - if not use_legacy_v2_onions: - self.stealth_group.hide() use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: @@ -651,17 +639,6 @@ class SettingsDialog(QtWidgets.QDialog): clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) - def use_legacy_v2_onions_clicked(self, checked): - """ - Show the persistent and stealth options since we're using legacy onions. - """ - if checked: - self.save_private_key_checkbox.show() - self.stealth_group.show() - else: - self.save_private_key_checkbox.hide() - self.stealth_group.hide() - def save_private_key_checkbox_clicked(self, checked): """ Prevent the v2 legacy mode being switched off if persistence is enabled @@ -670,7 +647,8 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) self.use_legacy_v2_onions_checkbox.setEnabled(False) else: - self.use_legacy_v2_onions_checkbox.setEnabled(True) + if not self.stealth_checkbox.isChecked(): + self.use_legacy_v2_onions_checkbox.setEnabled(True) def stealth_checkbox_clicked_connect(self, checked): """ @@ -680,7 +658,8 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) self.use_legacy_v2_onions_checkbox.setEnabled(False) else: - self.use_legacy_v2_onions_checkbox.setEnabled(True) + if not self.save_private_key_checkbox.isChecked(): + self.use_legacy_v2_onions_checkbox.setEnabled(True) def downloads_button_clicked(self): """ diff --git a/share/locale/en.json b/share/locale/en.json index 8205a29a..d73fb369 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -84,7 +84,7 @@ "gui_settings_window_title": "Settings", "gui_settings_stealth_label": "Stealth (advanced)", "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.
More information.", + "gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to.

NOTE: currently this only works with v2 onions.
More information.", "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 upgrades", "gui_settings_autoupdate_option": "Notify me when upgrades are available", @@ -154,7 +154,7 @@ "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", "gui_use_legacy_v2_onions_checkbox": "Use legacy (v2) .onion addresses?", - "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)", + "gui_save_private_key_checkbox": "Use a persistent (v2 only) address\n(unchecking will delete any saved addresses)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

Every share will have the same address (to use one-time addresses, disable persistence in Settings)", From 2aa6a72ba45ab7f0a985d7398f87218462d35db7 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 28 Aug 2018 09:33:49 +1000 Subject: [PATCH 06/25] Only wrap the v3 onion if the window is too small to show it unwrapped --- onionshare_gui/server_status.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 46310605..c0409749 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -57,6 +57,8 @@ class ServerStatus(QtWidgets.QWidget): self.web = None + self.resizeEvent(None) + # Shutdown timeout layout self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.shutdown_timeout = QtWidgets.QDateTimeEdit() @@ -129,6 +131,24 @@ class ServerStatus(QtWidgets.QWidget): self.update() + def resizeEvent(self, event): + """ + When the widget is resized, try and adjust the display of a v3 onion URL. + """ + try: + self.get_url() + url_length=len(self.get_url()) + if url_length > 60: + width = self.frameGeometry().width() + if width < 530: + wrapped_onion_url = textwrap.fill(self.get_url(), 50) + self.url.setText(wrapped_onion_url) + else: + self.url.setText(self.get_url()) + except: + pass + + def shutdown_timeout_reset(self): """ Reset the timeout in the UI after stopping a share @@ -163,13 +183,7 @@ class ServerStatus(QtWidgets.QWidget): else: self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) - # Wrap the Onion URL if it's a big v3 one - url_length=len(self.get_url()) - if url_length > 60: - wrapped_onion_url = textwrap.fill(self.get_url(), 50) - self.url.setText(wrapped_onion_url) - else: - self.url.setText(self.get_url()) + self.url.setText(self.get_url()) self.url.show() self.copy_url_button.show() From 15835f1c380b10f680cbff20e152b3aaa9d514e1 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 11:35:28 +1000 Subject: [PATCH 07/25] Update cryptography dependency to 2.3.1 --- install/requirements-windows.txt | 2 +- install/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt index 611edf3c..f016c523 100644 --- a/install/requirements-windows.txt +++ b/install/requirements-windows.txt @@ -1,5 +1,5 @@ click==6.7 -cryptography==2.1.4 +cryptography==2.3.1 Flask==0.12.2 future==0.16.0 itsdangerous==0.24 diff --git a/install/requirements.txt b/install/requirements.txt index 964030e8..15e9a108 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,5 +1,5 @@ click==6.7 -cryptography==2.1.4 +cryptography==2.3.1 Flask==0.12.2 itsdangerous==0.24 Jinja2==2.10 From 4a55b7a13b54da47857a1539702b9100592015cf Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 12:21:38 +1000 Subject: [PATCH 08/25] Refactor the onionkey stuff to be more like @maqp's revised version (thanks) --- onionshare/onionkey.py | 60 ++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/onionshare/onionkey.py b/onionshare/onionkey.py index 89c781ab..e9dcc9c4 100644 --- a/onionshare/onionkey.py +++ b/onionshare/onionkey.py @@ -37,39 +37,40 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa -b = 256 +def stem_compatible_base64_blob_from_private_key(private_key_seed: bytes) -> str: + """ + Provides a base64-encoded private key for v3-style Onions. + """ + b = 256 -def bit(h, i): - return (h[i // 8] >> (i % 8)) & 1 + def bit(h: bytes, i: int) -> int: + return (h[i // 8] >> (i % 8)) & 1 + + def encode_int(y: int) -> bytes: + bits = [(y >> i) & 1 for i in range(b)] + return b''.join([bytes([(sum([bits[i * 8 + j] << j for j in range(8)]))]) for i in range(b // 8)]) + + def expand_private_key(sk: bytes) -> bytes: + h = hashlib.sha512(sk).digest() + a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) + k = b''.join([bytes([h[i]]) for i in range(b // 8, b // 4)]) + assert len(k) == 32 + return encode_int(a) + k + + expanded_private_key = expand_private_key(private_key_seed) + return base64.b64encode(expanded_private_key).decode() -def encodeint(y): - bits = [(y >> i) & 1 for i in range(b)] - return b''.join([bytes([(sum([bits[i * 8 + j] << j for j in range(8)]))]) for i in range(b // 8)]) - - -def H(m): - return hashlib.sha512(m).digest() - - -def expandSK(sk): - h = H(sk) - a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) - k = b''.join([bytes([h[i]]) for i in range(b // 8, b // 4)]) - assert len(k) == 32 - return encodeint(a) + k - - -def onion_url_from_private_key(private_key): +def onion_url_from_private_key(private_key_seed: bytes) -> str: """ Derives the public key (.onion hostname) from a v3-style Onion private key. """ - private_key = nacl.signing.SigningKey(seed=private_key) - pubkey = bytes(private_key.verify_key) + signing_key = nacl.signing.SigningKey(seed=private_key_seed) + public_key = bytes(signing_key.verify_key) version = b'\x03' - checksum = hashlib.sha3_256(b".onion checksum" + pubkey + version).digest()[:2] - onion_address = "http://{}.onion".format(base64.b32encode(pubkey + checksum + version).decode().lower()) + checksum = hashlib.sha3_256(b".onion checksum" + public_key + version).digest()[:2] + onion_address = "http://{}.onion".format(base64.b32encode(public_key + checksum + version).decode().lower()) return onion_address @@ -78,10 +79,10 @@ def generate_v3_private_key(): Generates a private and public key for use with v3 style Onions. Returns both the private key as well as the public key (.onion hostname) """ - secretKey = os.urandom(32) - expandedSecretKey = expandSK(secretKey) - private_key = base64.b64encode(expandedSecretKey).decode() - return (private_key, onion_url_from_private_key(secretKey)) + private_key_seed = os.urandom(32) + private_key = stem_compatible_base64_blob_from_private_key(private_key_seed) + return (private_key, onion_url_from_private_key(private_key_seed)) + def generate_v2_private_key(): """ @@ -109,6 +110,7 @@ def generate_v2_private_key(): return (serialized_key, onion_url) + def is_v2_key(key): """ Helper function for determining if a key is RSA1024 (v2) or not. From 81db9d3264e086a99c162474718d9732acc5fb87 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 12:29:48 +1000 Subject: [PATCH 09/25] More clarity for the returned values in generate_v3_private_key(), also more consistent with generate_v2_private_key() --- onionshare/onionkey.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionshare/onionkey.py b/onionshare/onionkey.py index e9dcc9c4..d2c6ad17 100644 --- a/onionshare/onionkey.py +++ b/onionshare/onionkey.py @@ -81,7 +81,8 @@ def generate_v3_private_key(): """ private_key_seed = os.urandom(32) private_key = stem_compatible_base64_blob_from_private_key(private_key_seed) - return (private_key, onion_url_from_private_key(private_key_seed)) + onion_url = onion_url_from_private_key(private_key_seed) + return (private_key, onion_url) def generate_v2_private_key(): From ab80440a3ce4ae8e8effc6ca5c9d973ce9af0370 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 16:35:24 +1000 Subject: [PATCH 10/25] Fixing a future check for persistent v3 onions (still disabled for now) --- onionshare/onion.py | 17 ++++++++--------- share/locale/en.json | 1 + 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 18ebeb5d..031f418a 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -446,11 +446,10 @@ class Onion(object): basic_auth = None if self.settings.get('private_key'): - try: - # is the key a v2 key? - key = onionkey.is_v2_key(self.settings.get('private_key')) + key_content = self.settings.get('private_key') + # is the key a v2 key? + if onionkey.is_v2_key(key_content): key_type = "RSA1024" - key_content = self.settings.get('private_key') # The below section is commented out because re-publishing # a pre-prepared v3 private key is currently unstable in Tor. # This is fixed upstream but won't reach stable until 0.3.5 @@ -460,11 +459,11 @@ class Onion(object): # v3 onions, which should not be possible via the GUI settings # anyway. # Our ticket: https://github.com/micahflee/onionshare/issues/677 - except: - pass - # Assume it was a v3 key - # key_type = "ED25519-V3" - # key_content = self.settings.get('private_key') + # + # Assume it was a v3 key + # key_type = "ED25519-V3" + else: + raise TorErrorProtocolError(strings._('error_invalid_private_key')) self.common.log('Onion', 'Starting a hidden service with a saved private key') else: # Work out if we can support v3 onion services, which are preferred diff --git a/share/locale/en.json b/share/locale/en.json index d73fb369..06301e80 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -139,6 +139,7 @@ "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", "error_tor_protocol_error": "Could not communicate with the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", + "error_invalid_private_key": "This private key type is unsupported", "connecting_to_tor": "Connecting to the Tor network", "update_available": "A new version of OnionShare is available. Click here to download it.

Installed version: {}
Latest version: {}", "update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.", From dfe18d10fa4a285d2798e373c5823539c4fd4832 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 15 Sep 2018 16:07:08 +1000 Subject: [PATCH 11/25] Fix bug where lack of stealth mode re-enabled v2 legacy checkbox even if persistence was still enabled --- onionshare_gui/settings_dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index b2515a69..24fea800 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -463,7 +463,8 @@ class SettingsDialog(QtWidgets.QDialog): self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.use_legacy_v2_onions_checkbox.setEnabled(True) + if not save_private_key: + self.use_legacy_v2_onions_checkbox.setEnabled(True) use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: From 1af531eab2c3a12f3cbfc27c3573b5995de9b2ba Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 20:33:12 -0700 Subject: [PATCH 12/25] Update all pip dependency versions --- install/requirements-windows.txt | 12 ++++++++++++ install/requirements.txt | 19 ++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt index 6e03f6e8..5d88fec7 100644 --- a/install/requirements-windows.txt +++ b/install/requirements-windows.txt @@ -1,14 +1,26 @@ +altgraph==0.15 +certifi==2018.4.16 +chardet==3.0.4 click==6.7 Flask==0.12.2 future==0.16.0 +idna==2.7 itsdangerous==0.24 Jinja2==2.10 +macholib==1.9 MarkupSafe==1.0 +packaging==17.1 pefile==2017.11.5 PyInstaller==3.3.1 +pyparsing==2.2.0 +pypiwin32==223 PyQt5==5.9.2 PySocks==1.6.7 +python-dateutil==2.7.3 +pywin32==223 requests==2.19.1 sip==4.19.6 +six==1.11.0 stem==1.6.0 +urllib3==1.23 Werkzeug==0.14.1 diff --git a/install/requirements.txt b/install/requirements.txt index ed83b995..16179eb7 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,12 +1,21 @@ +altgraph==0.16.1 +certifi==2018.8.24 +chardet==3.0.4 click==6.7 -Flask==0.12.2 +Flask==1.0.2 +future==0.16.0 +idna==2.7 itsdangerous==0.24 Jinja2==2.10 +macholib==1.11 MarkupSafe==1.0 -PyInstaller==3.3.1 -PyQt5==5.9.2 -PySocks==1.6.7 +pefile==2018.8.8 +PyInstaller==3.4 +PyQt5==5.11.2 +PyQt5-sip==4.19.12 +PySocks==1.6.8 requests==2.19.1 -sip==4.19.6 +sip==4.19.8 stem==1.6.0 +urllib3==1.23 Werkzeug==0.14.1 From 5443b6e63d5a78a90059a09d9b21291b086f2b09 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 13:54:47 +1000 Subject: [PATCH 13/25] Move stealth to general options, and add hyperlinks for more info for this and legacy addresses --- onionshare_gui/settings_dialog.py | 62 +++++++++++++++---------------- share/locale/en.json | 10 ++--- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 5b052375..856ca993 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -58,6 +58,10 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox", True)) + use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_use_legacy_v2_onions_label", True)) + use_legacy_v2_onions_label.setWordWrap(True) + use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + use_legacy_v2_onions_label.setOpenExternalLinks(True) # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() @@ -70,11 +74,37 @@ class SettingsDialog(QtWidgets.QDialog): self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) + # Stealth + self.stealth_checkbox = QtWidgets.QCheckBox() + self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) + self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) + stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) + stealth_details.setWordWrap(True) + stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + stealth_details.setOpenExternalLinks(True) + stealth_details.setMinimumSize(stealth_details.sizeHint()) + + hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) + hidservauth_details.setWordWrap(True) + hidservauth_details.setMinimumSize(hidservauth_details.sizeHint()) + 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() + # General options layout general_group_layout = QtWidgets.QVBoxLayout() general_group_layout.addWidget(self.use_legacy_v2_onions_checkbox) + general_group_layout.addWidget(use_legacy_v2_onions_label) general_group_layout.addWidget(self.save_private_key_checkbox) general_group_layout.addWidget(self.public_mode_checkbox) + general_group_layout.addWidget(self.stealth_checkbox) + general_group_layout.addWidget(stealth_details) + general_group_layout.addWidget(hidservauth_details) + general_group_layout.addWidget(self.hidservauth_copy_button) + general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label", True)) general_group.setLayout(general_group_layout) @@ -120,37 +150,6 @@ class SettingsDialog(QtWidgets.QDialog): receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True)) receiving_group.setLayout(receiving_group_layout) - # Stealth options - - # Stealth - stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) - stealth_details.setWordWrap(True) - stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) - stealth_details.setOpenExternalLinks(True) - stealth_details.setMinimumSize(stealth_details.sizeHint()) - self.stealth_checkbox = QtWidgets.QCheckBox() - self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) - self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - - hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) - hidservauth_details.setWordWrap(True) - hidservauth_details.setMinimumSize(hidservauth_details.sizeHint()) - 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_group_layout = QtWidgets.QVBoxLayout() - stealth_group_layout.addWidget(stealth_details) - 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.setLayout(stealth_group_layout) - # Automatic updates options # Autoupdate @@ -383,7 +382,6 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout.addWidget(general_group) left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) - left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() diff --git a/share/locale/en.json b/share/locale/en.json index e8649106..0239037a 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -71,9 +71,8 @@ "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", "gui_settings_window_title": "Settings", - "gui_settings_stealth_label": "Stealth (advanced)", - "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.

NOTE: currently this only works with v2 onions.
More information.", + "gui_settings_stealth_option": "Create stealth onion services (legacy)", + "gui_settings_stealth_option_details": "(what's this?)", "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 upgrades", "gui_settings_autoupdate_option": "Notify me when upgrades are available", @@ -139,8 +138,9 @@ "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.", "share_via_onionshare": "Share via OnionShare", - "gui_use_legacy_v2_onions_checkbox": "Use legacy (v2) .onion addresses?", - "gui_save_private_key_checkbox": "Use a persistent (v2 only) address\n(unchecking will delete any saved addresses)", + "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", + "gui_use_legacy_v2_onions_label": "(what's this?)", + "gui_save_private_key_checkbox": "Use a persistent address (legacy)\n(unchecking will delete any saved addresses)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

Every share will have the same address (to use one-time addresses, disable persistence in Settings)", From 3b16b15b4cb4fdb27162b7dbbb248feb4532781b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 14:00:41 +1000 Subject: [PATCH 14/25] Move the hyperlink labels into HBox layouts with the checkboxes --- onionshare_gui/settings_dialog.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 856ca993..2897b944 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -62,6 +62,12 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions_label.setWordWrap(True) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setOpenExternalLinks(True) + use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout() + use_legacy_v2_onions_layout.addWidget(self.use_legacy_v2_onions_checkbox) + use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label) + use_legacy_v2_onions_layout.addStretch() + use_legacy_v2_onions_widget = QtWidgets.QWidget() + use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() @@ -79,11 +85,18 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) - stealth_details.setWordWrap(True) - stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) - stealth_details.setOpenExternalLinks(True) - stealth_details.setMinimumSize(stealth_details.sizeHint()) + use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) + use_stealth_label.setWordWrap(True) + use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + use_stealth_label.setOpenExternalLinks(True) + use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) + + use_stealth_layout = QtWidgets.QHBoxLayout() + use_stealth_layout.addWidget(self.stealth_checkbox) + use_stealth_layout.addWidget(use_stealth_label) + use_stealth_layout.addStretch() + use_stealth_widget = QtWidgets.QWidget() + use_stealth_widget.setLayout(use_stealth_layout) hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details.setWordWrap(True) @@ -96,12 +109,10 @@ class SettingsDialog(QtWidgets.QDialog): # General options layout general_group_layout = QtWidgets.QVBoxLayout() - general_group_layout.addWidget(self.use_legacy_v2_onions_checkbox) - general_group_layout.addWidget(use_legacy_v2_onions_label) + general_group_layout.addWidget(use_legacy_v2_onions_widget) general_group_layout.addWidget(self.save_private_key_checkbox) general_group_layout.addWidget(self.public_mode_checkbox) - general_group_layout.addWidget(self.stealth_checkbox) - general_group_layout.addWidget(stealth_details) + general_group_layout.addWidget(use_stealth_widget) general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(self.hidservauth_copy_button) From 57f1d3b2ec7aaad00242af057e29bcbe0ebdd19b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 14:06:55 +1000 Subject: [PATCH 15/25] Fix margins on HBoxLayouts in settings --- onionshare_gui/settings_dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 2897b944..f8201fea 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -66,6 +66,7 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions_layout.addWidget(self.use_legacy_v2_onions_checkbox) use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label) use_legacy_v2_onions_layout.addStretch() + use_legacy_v2_onions_layout.setContentsMargins(0,0,0,0) use_legacy_v2_onions_widget = QtWidgets.QWidget() use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) @@ -95,6 +96,7 @@ class SettingsDialog(QtWidgets.QDialog): use_stealth_layout.addWidget(self.stealth_checkbox) use_stealth_layout.addWidget(use_stealth_label) use_stealth_layout.addStretch() + use_stealth_layout.setContentsMargins(0,0,0,0) use_stealth_widget = QtWidgets.QWidget() use_stealth_widget.setLayout(use_stealth_layout) From e4d5a74cca1241c1b8d7c120816a436105ff6307 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 22:04:08 -0700 Subject: [PATCH 16/25] Monkeypatch flask to suppress output that isn't applicable to OnionShare --- onionshare/web.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/onionshare/web.py b/onionshare/web.py index f67a4b16..68b8fbc2 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -32,6 +32,7 @@ import io from distutils.version import LooseVersion as Version from urllib.request import urlopen +import flask from flask import ( Flask, Response, Request, request, render_template, abort, make_response, flash, redirect, __version__ as flask_version @@ -40,6 +41,15 @@ from werkzeug.utils import secure_filename from . import strings, common + +# Stub out flask's show_server_banner function, to avoiding showing warnings that +# are not applicable to OnionShare +def stubbed_show_server_banner(env, debug, app_import_path, eager_loading): + pass + +flask.cli.show_server_banner = stubbed_show_server_banner + + class Web(object): """ The Web object is the OnionShare web server, powered by flask From 244696afc9e7ab34ca9c8a3bf764001dca06208c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 15:41:50 +1000 Subject: [PATCH 17/25] Bump windows versions of pip packages to match those of OS X --- install/requirements-windows.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt index 5d88fec7..005acc9f 100644 --- a/install/requirements-windows.txt +++ b/install/requirements-windows.txt @@ -1,25 +1,25 @@ -altgraph==0.15 -certifi==2018.4.16 +altgraph==0.16.1 +certifi==2018.8.24 chardet==3.0.4 click==6.7 -Flask==0.12.2 +Flask==1.0.2 future==0.16.0 idna==2.7 itsdangerous==0.24 Jinja2==2.10 -macholib==1.9 +macholib==1.11 MarkupSafe==1.0 packaging==17.1 -pefile==2017.11.5 -PyInstaller==3.3.1 +pefile==2018.8.8 +PyInstaller==3.4 pyparsing==2.2.0 pypiwin32==223 -PyQt5==5.9.2 -PySocks==1.6.7 +PyQt5==5.11.2 +PySocks==1.6.8 python-dateutil==2.7.3 pywin32==223 requests==2.19.1 -sip==4.19.6 +sip==4.19.8 six==1.11.0 stem==1.6.0 urllib3==1.23 From 5a8f3b4f5c03072b7210d4765b32bce86575ac1f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 08:43:19 +1000 Subject: [PATCH 18/25] Use 'settings' rather than 'options' in the SettingsDialog labels --- share/locale/en.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index 0239037a..df4d03e6 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -79,8 +79,8 @@ "gui_settings_autoupdate_timestamp": "Last checked: {}", "gui_settings_autoupdate_timestamp_never": "Never", "gui_settings_autoupdate_check_button": "Check For Upgrades", - "gui_settings_general_label": "General options", - "gui_settings_sharing_label": "Sharing options", + "gui_settings_general_label": "General settings", + "gui_settings_sharing_label": "Sharing settings", "gui_settings_close_after_first_download_option": "Stop sharing after first download", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_bundled_option": "Use the Tor version that is bundled with OnionShare", @@ -91,7 +91,7 @@ "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", "gui_settings_socks_label": "SOCKS port", - "gui_settings_authenticate_label": "Tor authentication options", + "gui_settings_authenticate_label": "Tor authentication settings", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", "gui_settings_password_label": "Password", @@ -166,7 +166,7 @@ "receive_mode_received_file": "Received file: {}", "gui_mode_share_button": "Share Files", "gui_mode_receive_button": "Receive Files", - "gui_settings_receiving_label": "Receiving options", + "gui_settings_receiving_label": "Receiving settings", "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", From 5f31767a7b47056a02c531975a85259096a63ee0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 08:44:56 +1000 Subject: [PATCH 19/25] Reorder the general settings --- onionshare_gui/settings_dialog.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index f8201fea..cee119f4 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -81,6 +81,11 @@ class SettingsDialog(QtWidgets.QDialog): self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) + # Whether or not to use a shutdown timer + self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() + self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) + self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) + # Stealth self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) @@ -111,9 +116,10 @@ class SettingsDialog(QtWidgets.QDialog): # General options layout general_group_layout = QtWidgets.QVBoxLayout() + general_group_layout.addWidget(self.public_mode_checkbox) + general_group_layout.addWidget(self.shutdown_timeout_checkbox) general_group_layout.addWidget(use_legacy_v2_onions_widget) general_group_layout.addWidget(self.save_private_key_checkbox) - general_group_layout.addWidget(self.public_mode_checkbox) general_group_layout.addWidget(use_stealth_widget) general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(self.hidservauth_copy_button) @@ -128,15 +134,9 @@ class SettingsDialog(QtWidgets.QDialog): self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option", True)) - # Whether or not to use a shutdown timer - self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() - self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) - self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) - # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) - sharing_group_layout.addWidget(self.shutdown_timeout_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) From e9721aa243862452515855d13c5b24144fe5ff8a Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:01:30 +1000 Subject: [PATCH 20/25] Add 'what's this' labels to each General Setting --- onionshare_gui/settings_dialog.py | 60 +++++++++++++++++++++++-------- share/locale/en.json | 5 ++- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index cee119f4..b33aa94c 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -54,12 +54,43 @@ class SettingsDialog(QtWidgets.QDialog): # General options + # Use a slug or not ('public mode') + self.public_mode_checkbox = QtWidgets.QCheckBox() + self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) + public_mode_label = QtWidgets.QLabel(strings._("gui_settings_public_mode_details", True)) + public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + public_mode_label.setOpenExternalLinks(True) + public_mode_label.setMinimumSize(public_mode_label.sizeHint()) + public_mode_layout = QtWidgets.QHBoxLayout() + public_mode_layout.addWidget(self.public_mode_checkbox) + public_mode_layout.addWidget(public_mode_label) + public_mode_layout.addStretch() + public_mode_layout.setContentsMargins(0,0,0,0) + public_mode_widget = QtWidgets.QWidget() + public_mode_widget.setLayout(public_mode_layout) + + # Whether or not to use a shutdown ('auto-stop') timer + self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() + self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) + self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) + shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_shutdown_timeout_details", True)) + shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + shutdown_timeout_label.setOpenExternalLinks(True) + shutdown_timeout_label.setMinimumSize(public_mode_label.sizeHint()) + shutdown_timeout_layout = QtWidgets.QHBoxLayout() + shutdown_timeout_layout.addWidget(self.shutdown_timeout_checkbox) + shutdown_timeout_layout.addWidget(shutdown_timeout_label) + shutdown_timeout_layout.addStretch() + shutdown_timeout_layout.setContentsMargins(0,0,0,0) + shutdown_timeout_widget = QtWidgets.QWidget() + shutdown_timeout_widget.setLayout(shutdown_timeout_layout) + # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox", True)) use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_use_legacy_v2_onions_label", True)) - use_legacy_v2_onions_label.setWordWrap(True) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setOpenExternalLinks(True) use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout() @@ -75,16 +106,16 @@ class SettingsDialog(QtWidgets.QDialog): self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True)) self.save_private_key_checkbox.clicked.connect(self.save_private_key_checkbox_clicked) - - # Use a slug - self.public_mode_checkbox = QtWidgets.QCheckBox() - self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) - - # Whether or not to use a shutdown timer - self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() - self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) - self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) + save_private_key_label = QtWidgets.QLabel(strings._("gui_save_private_key_label", True)) + save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + save_private_key_label.setOpenExternalLinks(True) + save_private_key_layout = QtWidgets.QHBoxLayout() + save_private_key_layout.addWidget(self.save_private_key_checkbox) + save_private_key_layout.addWidget(save_private_key_label) + save_private_key_layout.addStretch() + save_private_key_layout.setContentsMargins(0,0,0,0) + save_private_key_widget = QtWidgets.QWidget() + save_private_key_widget.setLayout(save_private_key_layout) # Stealth self.stealth_checkbox = QtWidgets.QCheckBox() @@ -92,7 +123,6 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) - use_stealth_label.setWordWrap(True) use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) @@ -116,10 +146,10 @@ class SettingsDialog(QtWidgets.QDialog): # General options layout general_group_layout = QtWidgets.QVBoxLayout() - general_group_layout.addWidget(self.public_mode_checkbox) - general_group_layout.addWidget(self.shutdown_timeout_checkbox) + general_group_layout.addWidget(public_mode_widget) + general_group_layout.addWidget(shutdown_timeout_widget) general_group_layout.addWidget(use_legacy_v2_onions_widget) - general_group_layout.addWidget(self.save_private_key_checkbox) + general_group_layout.addWidget(save_private_key_widget) general_group_layout.addWidget(use_stealth_widget) general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(self.hidservauth_copy_button) diff --git a/share/locale/en.json b/share/locale/en.json index df4d03e6..9c7501cf 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -109,6 +109,7 @@ "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer", + "gui_settings_shutdown_timeout_details": "(what's this?)", "gui_settings_shutdown_timeout": "Stop the share at:", "settings_saved": "Settings saved to {}", "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.", @@ -141,6 +142,7 @@ "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_use_legacy_v2_onions_label": "(what's this?)", "gui_save_private_key_checkbox": "Use a persistent address (legacy)\n(unchecking will delete any saved addresses)", + "gui_save_private_key_label": "(what's this?)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

Every share will have the same address (to use one-time addresses, disable persistence in Settings)", @@ -170,7 +172,8 @@ "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", - "gui_settings_public_mode_checkbox": "OnionShare is open to the public\n(don't prevent people from guessing the OnionShare address)", + "gui_settings_public_mode_checkbox": "Public mode", + "gui_settings_public_mode_details": "(what's this?)", "systray_close_server_title": "OnionShare Server Closed", "systray_close_server_message": "A user closed the server", "systray_page_loaded_title": "OnionShare Page Loaded", From 47a6d94ef3d789b11e852cf34dc20a06a1d63c8c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:02:35 +1000 Subject: [PATCH 21/25] reduce verbosity of persistent mode label --- share/locale/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/en.json b/share/locale/en.json index 9c7501cf..07a01464 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -141,7 +141,7 @@ "share_via_onionshare": "Share via OnionShare", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_use_legacy_v2_onions_label": "(what's this?)", - "gui_save_private_key_checkbox": "Use a persistent address (legacy)\n(unchecking will delete any saved addresses)", + "gui_save_private_key_checkbox": "Use a persistent address (legacy)", "gui_save_private_key_label": "(what's this?)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", From 73508f380fcfc7443c7d02a2a226c4e3a7089b57 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:12:13 +1000 Subject: [PATCH 22/25] Hide the legacy settings if legacy mode is not enabled. Fix unrelated bug regarding displaying the HidServAuth copy button/label --- onionshare_gui/settings_dialog.py | 52 +++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index b33aa94c..895cde13 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -67,8 +67,8 @@ class SettingsDialog(QtWidgets.QDialog): public_mode_layout.addWidget(public_mode_label) public_mode_layout.addStretch() public_mode_layout.setContentsMargins(0,0,0,0) - public_mode_widget = QtWidgets.QWidget() - public_mode_widget.setLayout(public_mode_layout) + self.public_mode_widget = QtWidgets.QWidget() + self.public_mode_widget.setLayout(public_mode_layout) # Whether or not to use a shutdown ('auto-stop') timer self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() @@ -83,13 +83,14 @@ class SettingsDialog(QtWidgets.QDialog): shutdown_timeout_layout.addWidget(shutdown_timeout_label) shutdown_timeout_layout.addStretch() shutdown_timeout_layout.setContentsMargins(0,0,0,0) - shutdown_timeout_widget = QtWidgets.QWidget() - shutdown_timeout_widget.setLayout(shutdown_timeout_layout) + self.shutdown_timeout_widget = QtWidgets.QWidget() + self.shutdown_timeout_widget.setLayout(shutdown_timeout_layout) # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox", True)) + self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked) use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_use_legacy_v2_onions_label", True)) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setOpenExternalLinks(True) @@ -98,8 +99,8 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label) use_legacy_v2_onions_layout.addStretch() use_legacy_v2_onions_layout.setContentsMargins(0,0,0,0) - use_legacy_v2_onions_widget = QtWidgets.QWidget() - use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) + self.use_legacy_v2_onions_widget = QtWidgets.QWidget() + self.use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() @@ -114,8 +115,8 @@ class SettingsDialog(QtWidgets.QDialog): save_private_key_layout.addWidget(save_private_key_label) save_private_key_layout.addStretch() save_private_key_layout.setContentsMargins(0,0,0,0) - save_private_key_widget = QtWidgets.QWidget() - save_private_key_widget.setLayout(save_private_key_layout) + self.save_private_key_widget = QtWidgets.QWidget() + self.save_private_key_widget.setLayout(save_private_key_layout) # Stealth self.stealth_checkbox = QtWidgets.QCheckBox() @@ -126,14 +127,13 @@ class SettingsDialog(QtWidgets.QDialog): use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) - use_stealth_layout = QtWidgets.QHBoxLayout() use_stealth_layout.addWidget(self.stealth_checkbox) use_stealth_layout.addWidget(use_stealth_label) use_stealth_layout.addStretch() use_stealth_layout.setContentsMargins(0,0,0,0) - use_stealth_widget = QtWidgets.QWidget() - use_stealth_widget.setLayout(use_stealth_layout) + self.use_stealth_widget = QtWidgets.QWidget() + self.use_stealth_widget.setLayout(use_stealth_layout) hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details.setWordWrap(True) @@ -146,11 +146,11 @@ class SettingsDialog(QtWidgets.QDialog): # General options layout general_group_layout = QtWidgets.QVBoxLayout() - general_group_layout.addWidget(public_mode_widget) - general_group_layout.addWidget(shutdown_timeout_widget) - general_group_layout.addWidget(use_legacy_v2_onions_widget) - general_group_layout.addWidget(save_private_key_widget) - general_group_layout.addWidget(use_stealth_widget) + general_group_layout.addWidget(self.public_mode_widget) + general_group_layout.addWidget(self.shutdown_timeout_widget) + general_group_layout.addWidget(self.use_legacy_v2_onions_widget) + general_group_layout.addWidget(self.save_private_key_widget) + general_group_layout.addWidget(self.use_stealth_widget) general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(self.hidservauth_copy_button) @@ -463,6 +463,13 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') + if use_legacy_v2_onions: + self.save_private_key_widget.show() + self.use_stealth_widget.show() + else: + self.save_private_key_widget.hide() + self.use_stealth_widget.hide() + save_private_key = self.old_settings.get('save_private_key') if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) @@ -495,7 +502,7 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) # Legacy v2 mode is forced on if Stealth is enabled self.use_legacy_v2_onions_checkbox.setEnabled(False) - if save_private_key: + if save_private_key and self.old_settings.get('hidservauth_string') != "": hidservauth_details.show() self.hidservauth_copy_button.show() else: @@ -665,6 +672,17 @@ class SettingsDialog(QtWidgets.QDialog): clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) + def use_legacy_v2_onions_checkbox_clicked(self, checked): + """ + Show the legacy settings if the legacy mode is enabled. + """ + if checked: + self.save_private_key_widget.show() + self.use_stealth_widget.show() + else: + self.save_private_key_widget.hide() + self.use_stealth_widget.hide() + def save_private_key_checkbox_clicked(self, checked): """ Prevent the v2 legacy mode being switched off if persistence is enabled From 94cc76729c7660848e38f786f39d372084d0a2c2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:16:11 +1000 Subject: [PATCH 23/25] Update stdeb.cfg to depend on bionic and Python 3.6 --- stdeb.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdeb.cfg b/stdeb.cfg index 334502c0..e190fe8b 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -2,5 +2,5 @@ Package3: onionshare Depends3: python3-flask, python3-stem, python3-pyqt5, python-nautilus, tor, obfs4proxy Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5 -Suite: xenial -X-Python3-Version: >= 3.4 +Suite: bionic +X-Python3-Version: >= 3.6 From 2896f1e3a82b89090224744c307db2002e2679a8 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 18:48:22 +1000 Subject: [PATCH 24/25] Use the term 'upload' rather than 'download' in the Receive mode tooltip icons --- onionshare_gui/receive_mode/__init__.py | 4 ++-- share/locale/en.json | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 623d3986..6a8d3835 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -194,7 +194,7 @@ class ReceiveMode(Mode): else: image = self.common.get_resource_path('images/share_completed.png') self.info_completed_uploads_count.setText(' {1:d}'.format(image, self.uploads_completed)) - self.info_completed_uploads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.uploads_completed)) + self.info_completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.uploads_completed)) def update_uploads_in_progress(self): """ @@ -206,7 +206,7 @@ class ReceiveMode(Mode): image = self.common.get_resource_path('images/share_in_progress.png') self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_green.png'))) self.info_in_progress_uploads_count.setText(' {1:d}'.format(image, self.uploads_in_progress)) - self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.uploads_in_progress)) + self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.uploads_in_progress)) def update_primary_action(self): self.common.log('ReceiveMode', 'update_primary_action') diff --git a/share/locale/en.json b/share/locale/en.json index cc39bba7..a3a7f3df 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -155,6 +155,8 @@ "gui_file_info_single": "{} File, {}", "info_in_progress_downloads_tooltip": "{} download(s) in progress", "info_completed_downloads_tooltip": "{} download(s) completed", + "info_in_progress_uploads_tooltip": "{} upload(s) in progress", + "info_completed_uploads_tooltip": "{} upload(s) completed", "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}", "receive_mode_downloads_dir": "Files people send you will appear in this folder: {}", From 2c80a74467341ec13ea550dc93800e7a7e81421d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 16:11:52 -0700 Subject: [PATCH 25/25] Make what's this links use the same string, and change their style --- onionshare/common.py | 5 +++++ onionshare_gui/settings_dialog.py | 15 ++++++++++----- share/locale/en.json | 6 +----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 61663f23..0ce411e8 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -346,6 +346,11 @@ class Common(object): background-color: #ffffff; color: #000000; padding: 10px; + }""", + + 'settings_whats_this': """ + QLabel { + font-size: 12px; }""" } diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 895cde13..c31d4630 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -58,7 +58,8 @@ class SettingsDialog(QtWidgets.QDialog): self.public_mode_checkbox = QtWidgets.QCheckBox() self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) - public_mode_label = QtWidgets.QLabel(strings._("gui_settings_public_mode_details", True)) + public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Public-Mode")) + public_mode_label.setStyleSheet(self.common.css['settings_whats_this']) public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) public_mode_label.setOpenExternalLinks(True) public_mode_label.setMinimumSize(public_mode_label.sizeHint()) @@ -74,7 +75,8 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) - shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_shutdown_timeout_details", True)) + shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer")) + shutdown_timeout_label.setStyleSheet(self.common.css['settings_whats_this']) shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) shutdown_timeout_label.setOpenExternalLinks(True) shutdown_timeout_label.setMinimumSize(public_mode_label.sizeHint()) @@ -91,7 +93,8 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox", True)) self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked) - use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_use_legacy_v2_onions_label", True)) + use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses")) + use_legacy_v2_onions_label.setStyleSheet(self.common.css['settings_whats_this']) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setOpenExternalLinks(True) use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout() @@ -107,7 +110,8 @@ class SettingsDialog(QtWidgets.QDialog): self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True)) self.save_private_key_checkbox.clicked.connect(self.save_private_key_checkbox_clicked) - save_private_key_label = QtWidgets.QLabel(strings._("gui_save_private_key_label", True)) + save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL")) + save_private_key_label.setStyleSheet(self.common.css['settings_whats_this']) save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) save_private_key_label.setOpenExternalLinks(True) save_private_key_layout = QtWidgets.QHBoxLayout() @@ -123,7 +127,8 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) + use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services")) + use_stealth_label.setStyleSheet(self.common.css['settings_whats_this']) use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) diff --git a/share/locale/en.json b/share/locale/en.json index c70ca2eb..4e7143d3 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -71,8 +71,8 @@ "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", "gui_settings_window_title": "Settings", + "gui_settings_whats_this": "what's this?", "gui_settings_stealth_option": "Create stealth onion services (legacy)", - "gui_settings_stealth_option_details": "(what's this?)", "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 upgrades", "gui_settings_autoupdate_option": "Notify me when upgrades are available", @@ -109,7 +109,6 @@ "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer", - "gui_settings_shutdown_timeout_details": "(what's this?)", "gui_settings_shutdown_timeout": "Stop the share at:", "settings_saved": "Settings saved to {}", "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.", @@ -140,9 +139,7 @@ "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", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", - "gui_use_legacy_v2_onions_label": "(what's this?)", "gui_save_private_key_checkbox": "Use a persistent address (legacy)", - "gui_save_private_key_label": "(what's this?)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

Every share will have the same address (to use one-time addresses, disable persistence in Settings)", @@ -175,7 +172,6 @@ "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", "gui_settings_public_mode_checkbox": "Public mode", - "gui_settings_public_mode_details": "(what's this?)", "systray_close_server_title": "OnionShare Server Closed", "systray_close_server_message": "A user closed the server", "systray_page_loaded_title": "OnionShare Page Loaded",