Merge branch '435_support_bridges' of https://github.com/mig5/onionshare into mig5-435_support_bridges

This commit is contained in:
Micah Lee 2018-01-17 16:19:30 -08:00
commit 713e45084a
No known key found for this signature in database
GPG key ID: 403C2657CD994F73
15 changed files with 720 additions and 506 deletions

View file

@ -11,9 +11,9 @@ cd onionshare
Install the needed dependencies: 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` 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 Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor` For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4`
After that you can try both the CLI and the GUI version of OnionShare: After that you can try both the CLI and the GUI version of OnionShare:
@ -98,11 +98,16 @@ These instructions include adding folders to the path in Windows. To do this, go
Download and install the 32-bit [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-US/download/details.aspx?id=48145). I downloaded `vc_redist.x86.exe`. Download and install the 32-bit [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-US/download/details.aspx?id=48145). I downloaded `vc_redist.x86.exe`.
Download and install the standalone [Windows 10 SDK](https://dev.windows.com/en-us/downloads/windows-10-sdk). Note that you may not need this if you already have Visual Studio. Add the following directories to the path: Download and install 7-Zip from http://70zip.org/download.html. I downloaded 7z1800.exe.
Download and install the standalone [Windows 10 SDK](https://dev.windows.com/en-us/downloads/windows-10-sdk). Note that you may not need this if you already have Visual Studio.
Add the following directories to the path:
* `C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x86` * `C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x86`
* `C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x86` * `C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x86`
* `C:\Users\user\AppData\Local\Programs\Python\Python36-32\Lib\site-packages\PyQt5\Qt\bin` * `C:\Users\user\AppData\Local\Programs\Python\Python36-32\Lib\site-packages\PyQt5\Qt\bin`
* `C:\Program Files (x86)\7-Zip`
If you want to build the installer: If you want to build the installer:

View file

@ -9,7 +9,7 @@ VERSION=`cat share/version.txt`
rm -r build dist >/dev/null 2>&1 rm -r build dist >/dev/null 2>&1
# build binary package # build binary package
python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, nautilus-python, tor" python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, nautilus-python, tor, obfs4"
# install it # install it
echo "" echo ""

View file

@ -84,6 +84,9 @@ def main():
shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'tor.real'), os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real')) shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'tor.real'), os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'))
shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'libevent-2.0.5.dylib'), os.path.join(dist_path, 'MacOS', 'Tor', 'libevent-2.0.5.dylib')) shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'libevent-2.0.5.dylib'), os.path.join(dist_path, 'MacOS', 'Tor', 'libevent-2.0.5.dylib'))
os.chmod(os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'), 0o755) os.chmod(os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'), 0o755)
# obfs4proxy binary
shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'PluggableTransports', 'obfs4proxy'), os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy'))
os.chmod(os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy'), 0o755)
# Unmount dmg # Unmount dmg
subprocess.call(['diskutil', 'unmount', '/Volumes/Tor Browser']) subprocess.call(['diskutil', 'unmount', '/Volumes/Tor Browser'])

View file

@ -24,18 +24,17 @@ In order to avoid a Windows gnupg dependency, I manually verify the signature
and hard-code the sha256 hash. and hard-code the sha256 hash.
""" """
import inspect, os, sys, hashlib, zipfile, io, shutil import inspect, os, sys, hashlib, shutil, subprocess
import urllib.request import urllib.request
def main(): def main():
zip_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.0.11/tor-win32-0.3.1.9.zip' exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.0.11/torbrowser-install-7.0.11_en-US.exe'
zip_filename = 'tor-win32-0.3.1.9.zip' exe_filename = 'torbrowser-install-7.0.11_en-US.exe'
expected_zip_sha256 = 'faf28efb606455842bda66ca369287a116b6d6e5ad3720ebed9337da0717f1b4' expected_exe_sha256 = 'a033eb9b9ed2ad389169b36a90946a8af8f05bd0c7bbd3e37678041331096624'
# Build paths # Build paths
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
working_path = os.path.join(os.path.join(root_path, 'build'), 'tor') working_path = os.path.join(os.path.join(root_path, 'build'), 'tor')
zip_path = os.path.join(working_path, zip_filename) exe_path = os.path.join(working_path, exe_filename)
dist_path = os.path.join(os.path.join(os.path.join(root_path, 'dist'), 'onionshare'), 'tor') dist_path = os.path.join(os.path.join(os.path.join(root_path, 'dist'), 'onionshare'), 'tor')
# Make sure the working folder exists # Make sure the working folder exists
@ -43,36 +42,35 @@ def main():
os.makedirs(working_path) os.makedirs(working_path)
# Make sure the zip is downloaded # Make sure the zip is downloaded
if not os.path.exists(zip_path): if not os.path.exists(exe_path):
print("Downloading {}".format(zip_url)) print("Downloading {}".format(exe_url))
response = urllib.request.urlopen(zip_url) response = urllib.request.urlopen(exe_url)
zip_data = response.read() exe_data = response.read()
open(zip_path, 'wb').write(zip_data) open(exe_path, 'wb').write(exe_data)
zip_sha256 = hashlib.sha256(zip_data).hexdigest() exe_sha256 = hashlib.sha256(exe_data).hexdigest()
else: else:
zip_data = open(zip_path, 'rb').read() exe_data = open(exe_path, 'rb').read()
zip_sha256 = hashlib.sha256(zip_data).hexdigest() exe_sha256 = hashlib.sha256(exe_data).hexdigest()
# Compare the hash # Compare the hash
if zip_sha256 != expected_zip_sha256: if exe_sha256 != expected_exe_sha256:
print("ERROR! The sha256 doesn't match:") print("ERROR! The sha256 doesn't match:")
print("expected: {}".format(expected_zip_sha256)) print("expected: {}".format(expected_exe_sha256))
print(" actual: {}".format(zip_sha256)) print(" actual: {}".format(exe_sha256))
sys.exit(-1) sys.exit(-1)
# Extract the zip # Extract the bits we need from the exe
z = zipfile.ZipFile(io.BytesIO(zip_data)) cmd = ['7z', 'e', exe_path, 'Browser\TorBrowser\Tor', '-o%s' % os.path.join(working_path, 'Tor')]
z.extractall(working_path) cmd2 = ['7z', 'e', exe_path, 'Browser\TorBrowser\Data\Tor\geoip*', '-o%s' % os.path.join(working_path, 'Data')]
subprocess.Popen(cmd).wait()
# Delete un-used files subprocess.Popen(cmd2).wait()
os.remove(os.path.join(os.path.join(working_path, 'Tor'), 'tor-gencert.exe'))
# Copy into dist # Copy into dist
if os.path.exists(dist_path): if os.path.exists(dist_path):
shutil.rmtree(dist_path) shutil.rmtree(dist_path)
os.makedirs(dist_path) os.makedirs(dist_path)
shutil.copytree( os.path.join(working_path, 'Tor'), os.path.join(dist_path, 'Tor') ) shutil.copytree( os.path.join(working_path, 'Tor'), os.path.join(dist_path, 'Tor') )
shutil.copytree( os.path.join(working_path, 'Data'), os.path.join(dist_path, 'Data') ) shutil.copytree( os.path.join(working_path, 'Data'), os.path.join(dist_path, 'Data', 'Tor') )
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View file

@ -191,6 +191,7 @@ Section "install"
File "${BINPATH}\share\license.txt" File "${BINPATH}\share\license.txt"
File "${BINPATH}\share\torrc_template" File "${BINPATH}\share\torrc_template"
File "${BINPATH}\share\torrc_template-windows" File "${BINPATH}\share\torrc_template-windows"
File "${BINPATH}\share\torrc_template-obfs4"
File "${BINPATH}\share\version.txt" File "${BINPATH}\share\version.txt"
File "${BINPATH}\share\wordlist.txt" File "${BINPATH}\share\wordlist.txt"
@ -235,6 +236,7 @@ Section "install"
File "${BINPATH}\tor\Tor\libevent_extra-2-0-5.dll" File "${BINPATH}\tor\Tor\libevent_extra-2-0-5.dll"
File "${BINPATH}\tor\Tor\libgcc_s_sjlj-1.dll" File "${BINPATH}\tor\Tor\libgcc_s_sjlj-1.dll"
File "${BINPATH}\tor\Tor\libssp-0.dll" File "${BINPATH}\tor\Tor\libssp-0.dll"
File "${BINPATH}\tor\Tor\obfs4proxy.exe"
File "${BINPATH}\tor\Tor\ssleay32.dll" File "${BINPATH}\tor\Tor\ssleay32.dll"
File "${BINPATH}\tor\Tor\tor.exe" File "${BINPATH}\tor\Tor\tor.exe"
File "${BINPATH}\tor\Tor\zlib1.dll" File "${BINPATH}\tor\Tor\zlib1.dll"
@ -401,6 +403,7 @@ FunctionEnd
Delete "$INSTDIR\share\locale\tr.json" Delete "$INSTDIR\share\locale\tr.json"
Delete "$INSTDIR\share\torrc_template" Delete "$INSTDIR\share\torrc_template"
Delete "$INSTDIR\share\torrc_template-windows" Delete "$INSTDIR\share\torrc_template-windows"
Delete "$INSTDIR\share\torrc_template-obfs4"
Delete "$INSTDIR\share\version.txt" Delete "$INSTDIR\share\version.txt"
Delete "$INSTDIR\share\wordlist.txt" Delete "$INSTDIR\share\wordlist.txt"
Delete "$INSTDIR\sip.pyd" Delete "$INSTDIR\sip.pyd"
@ -412,6 +415,7 @@ FunctionEnd
Delete "$INSTDIR\tor\Tor\libevent_extra-2-0-5.dll" Delete "$INSTDIR\tor\Tor\libevent_extra-2-0-5.dll"
Delete "$INSTDIR\tor\Tor\libgcc_s_sjlj-1.dll" Delete "$INSTDIR\tor\Tor\libgcc_s_sjlj-1.dll"
Delete "$INSTDIR\tor\Tor\libssp-0.dll" Delete "$INSTDIR\tor\Tor\libssp-0.dll"
Delete "$INSTDIR\tor\Tor\obfs4proxy.exe"
Delete "$INSTDIR\tor\Tor\ssleay32.dll" Delete "$INSTDIR\tor\Tor\ssleay32.dll"
Delete "$INSTDIR\tor\Tor\tor.exe" Delete "$INSTDIR\tor\Tor\tor.exe"
Delete "$INSTDIR\tor\Tor\zlib1.dll" Delete "$INSTDIR\tor\Tor\zlib1.dll"

View file

@ -14,6 +14,7 @@ a = Analysis(
('../share/version.txt', 'share'), ('../share/version.txt', 'share'),
('../share/wordlist.txt', 'share'), ('../share/wordlist.txt', 'share'),
('../share/torrc_template', 'share'), ('../share/torrc_template', 'share'),
('../share/torrc_template-obfs4', 'share'),
('../share/torrc_template-windows', 'share'), ('../share/torrc_template-windows', 'share'),
('../share/images/*', 'share/images'), ('../share/images/*', 'share/images'),
('../share/locale/*', 'share/locale'), ('../share/locale/*', 'share/locale'),

View file

@ -94,9 +94,11 @@ def get_tor_paths():
tor_path = '/usr/bin/tor' tor_path = '/usr/bin/tor'
tor_geo_ip_file_path = '/usr/share/tor/geoip' tor_geo_ip_file_path = '/usr/share/tor/geoip'
tor_geo_ipv6_file_path = '/usr/share/tor/geoip6' tor_geo_ipv6_file_path = '/usr/share/tor/geoip6'
obfs4proxy_file_path = '/usr/bin/obfs4proxy'
elif p == 'Windows': elif p == 'Windows':
base_path = os.path.join(os.path.dirname(os.path.dirname(get_resource_path(''))), 'tor') base_path = os.path.join(os.path.dirname(os.path.dirname(get_resource_path(''))), 'tor')
tor_path = os.path.join(os.path.join(base_path, 'Tor'), "tor.exe") tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe')
obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe')
tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip') tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip')
tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
elif p == 'Darwin': elif p == 'Darwin':
@ -104,12 +106,14 @@ def get_tor_paths():
tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor') tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor')
tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip') tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip')
tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6') tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6')
obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy')
elif p == 'OpenBSD' or p == 'FreeBSD': elif p == 'OpenBSD' or p == 'FreeBSD':
tor_path = '/usr/local/bin/tor' tor_path = '/usr/local/bin/tor'
tor_geo_ip_file_path = '/usr/local/share/tor/geoip' tor_geo_ip_file_path = '/usr/local/share/tor/geoip'
tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6' tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6'
obfs4proxy_file_path = '/usr/local/bin/obfs4proxy'
return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path) return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
def get_version(): def get_version():

View file

@ -140,7 +140,7 @@ class Onion(object):
self.bundle_tor_supported = True self.bundle_tor_supported = True
# Set the path of the tor binary, for bundled tor # Set the path of the tor binary, for bundled tor
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path) = common.get_tor_paths() (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
# The tor process # The tor process
self.tor_proc = None self.tor_proc = None
@ -205,6 +205,16 @@ class Onion(object):
with open(self.tor_torrc, 'w') as f: with open(self.tor_torrc, 'w') as f:
f.write(torrc_template) f.write(torrc_template)
# Bridge support
if self.settings.get('tor_bridges_use_obfs4'):
f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
with open(common.get_resource_path('torrc_template-obfs4')) as o:
for line in o:
f.write(line)
if self.settings.get('tor_bridges_use_custom_bridges'):
f.write(self.settings.get('tor_bridges_use_custom_bridges'))
f.write('\nUseBridges 1')
# Execute a tor subprocess # Execute a tor subprocess
start_ts = time.time() start_ts = time.time()
if self.system == 'Windows': if self.system == 'Windows':
@ -254,8 +264,13 @@ class Onion(object):
break break
time.sleep(0.2) time.sleep(0.2)
# Timeout after 90 seconds # If using bridges, it might take a bit longer to connect to Tor
if time.time() - start_ts > 90: if self.settings.get('tor_bridges_use_custom_bridges') or self.settings.get('tor_bridges_use_obfs4'):
connect_timeout = 150
else:
# Timeout after 120 seconds
connect_timeout = 120
if time.time() - start_ts > connect_timeout:
print("") print("")
self.tor_proc.terminate() self.tor_proc.terminate()
raise BundledTorTimeout(strings._('settings_error_bundled_tor_timeout')) raise BundledTorTimeout(strings._('settings_error_bundled_tor_timeout'))

View file

@ -61,6 +61,9 @@ class Settings(object):
'use_stealth': False, 'use_stealth': False,
'use_autoupdate': True, 'use_autoupdate': True,
'autoupdate_timestamp': None, 'autoupdate_timestamp': None,
'no_bridges': True,
'tor_bridges_use_obfs4': False,
'tor_bridges_use_custom_bridges': '',
'save_private_key': False, 'save_private_key': False,
'private_key': '', 'private_key': '',
'slug': '', 'slug': '',

View file

@ -18,7 +18,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
import sys, platform, datetime import sys, platform, datetime, re
from onionshare import strings, common from onionshare import strings, common
from onionshare.settings import Settings from onionshare.settings import Settings
@ -140,6 +140,51 @@ class SettingsDialog(QtWidgets.QDialog):
if (system == 'Windows' or system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): if (system == 'Windows' or system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
self.connection_type_bundled_radio.setEnabled(False) self.connection_type_bundled_radio.setEnabled(False)
# Bridge options for bundled tor
# No bridges option radio
self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option', True))
self.tor_bridges_no_bridges_radio.toggled.connect(self.tor_bridges_no_bridges_radio_toggled)
# obfs4 option radio
# if the obfs4proxy binary is missing, we can't use obfs4 transports
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
if not os.path.isfile(self.obfs4proxy_file_path):
self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True))
self.tor_bridges_use_obfs4_radio.setEnabled(False)
else:
self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option', True))
self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled)
# Custom bridges radio and textbox
self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option', True))
self.tor_bridges_use_custom_radio.toggled.connect(self.tor_bridges_use_custom_radio_toggled)
self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label', True))
self.tor_bridges_use_custom_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
self.tor_bridges_use_custom_label.setOpenExternalLinks(True)
self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit()
self.tor_bridges_use_custom_textbox.setMaximumHeight(200)
self.tor_bridges_use_custom_textbox.setPlaceholderText('[address:port] [identifier]')
tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout()
tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_label)
tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_textbox)
self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget()
self.tor_bridges_use_custom_textbox_options.setLayout(tor_bridges_use_custom_textbox_options_layout)
self.tor_bridges_use_custom_textbox_options.hide()
# Bridges layout/widget
bridges_layout = QtWidgets.QVBoxLayout()
bridges_layout.addWidget(self.tor_bridges_no_bridges_radio)
bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio)
bridges_layout.addWidget(self.tor_bridges_use_custom_radio)
bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options)
self.bridges = QtWidgets.QWidget()
self.bridges.setLayout(bridges_layout)
# Automatic # Automatic
self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True)) self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True))
self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled) self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled)
@ -238,6 +283,13 @@ class SettingsDialog(QtWidgets.QDialog):
connection_type_group = QtWidgets.QGroupBox() connection_type_group = QtWidgets.QGroupBox()
connection_type_group.setLayout(connection_type_group_layout) connection_type_group.setLayout(connection_type_group_layout)
# The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges)
connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout()
connection_type_bridges_radio_group_layout.addWidget(self.bridges)
self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges", True))
self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout)
self.connection_type_bridges_radio_group.hide()
# Buttons # Buttons
self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True))
self.save_button.clicked.connect(self.save_clicked) self.save_button.clicked.connect(self.save_clicked)
@ -266,6 +318,7 @@ class SettingsDialog(QtWidgets.QDialog):
right_col_layout = QtWidgets.QVBoxLayout() right_col_layout = QtWidgets.QVBoxLayout()
right_col_layout.addWidget(connection_type_radio_group) right_col_layout.addWidget(connection_type_radio_group)
right_col_layout.addWidget(connection_type_group) right_col_layout.addWidget(connection_type_group)
right_col_layout.addWidget(self.connection_type_bridges_radio_group)
right_col_layout.addWidget(self.tor_status) right_col_layout.addWidget(self.tor_status)
right_col_layout.addStretch() right_col_layout.addStretch()
@ -345,6 +398,25 @@ class SettingsDialog(QtWidgets.QDialog):
self.authenticate_password_radio.setChecked(True) self.authenticate_password_radio.setChecked(True)
self.authenticate_password_extras_password.setText(self.old_settings.get('auth_password')) self.authenticate_password_extras_password.setText(self.old_settings.get('auth_password'))
if self.old_settings.get('no_bridges'):
self.tor_bridges_no_bridges_radio.setChecked(True)
self.tor_bridges_use_obfs4_radio.setChecked(False)
self.tor_bridges_use_custom_radio.setChecked(False)
else:
self.tor_bridges_no_bridges_radio.setChecked(False)
self.tor_bridges_use_obfs4_radio.setChecked(self.old_settings.get('tor_bridges_use_obfs4'))
if self.old_settings.get('tor_bridges_use_custom_bridges'):
self.tor_bridges_use_custom_radio.setChecked(True)
# Remove the 'Bridge' lines at the start of each bridge.
# They are added automatically to provide compatibility with
# copying/pasting bridges provided from https://bridges.torproject.org
new_bridges = []
bridges = self.old_settings.get('tor_bridges_use_custom_bridges').split('Bridge ')
for bridge in bridges:
new_bridges.append(bridge)
new_bridges = ''.join(new_bridges)
self.tor_bridges_use_custom_textbox.setPlainText(new_bridges)
def connection_type_bundled_toggled(self, checked): def connection_type_bundled_toggled(self, checked):
""" """
Connection type bundled was toggled. If checked, hide authentication fields. Connection type bundled was toggled. If checked, hide authentication fields.
@ -353,6 +425,28 @@ class SettingsDialog(QtWidgets.QDialog):
if checked: if checked:
self.authenticate_group.hide() self.authenticate_group.hide()
self.connection_type_socks.hide() self.connection_type_socks.hide()
self.connection_type_bridges_radio_group.show()
def tor_bridges_no_bridges_radio_toggled(self, checked):
"""
'No bridges' option was toggled. If checked, enable other bridge options.
"""
if checked:
self.tor_bridges_use_custom_textbox_options.hide()
def tor_bridges_use_obfs4_radio_toggled(self, checked):
"""
obfs4 bridges option was toggled. If checked, disable custom bridge options.
"""
if checked:
self.tor_bridges_use_custom_textbox_options.hide()
def tor_bridges_use_custom_radio_toggled(self, checked):
"""
Custom bridges option was toggled. If checked, show custom bridge options.
"""
if checked:
self.tor_bridges_use_custom_textbox_options.show()
def connection_type_automatic_toggled(self, checked): def connection_type_automatic_toggled(self, checked):
""" """
@ -362,6 +456,7 @@ class SettingsDialog(QtWidgets.QDialog):
if checked: if checked:
self.authenticate_group.hide() self.authenticate_group.hide()
self.connection_type_socks.hide() self.connection_type_socks.hide()
self.connection_type_bridges_radio_group.hide()
def connection_type_control_port_toggled(self, checked): def connection_type_control_port_toggled(self, checked):
""" """
@ -373,6 +468,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.authenticate_group.show() self.authenticate_group.show()
self.connection_type_control_port_extras.show() self.connection_type_control_port_extras.show()
self.connection_type_socks.show() self.connection_type_socks.show()
self.connection_type_bridges_radio_group.hide()
else: else:
self.connection_type_control_port_extras.hide() self.connection_type_control_port_extras.hide()
@ -387,6 +483,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.authenticate_group.show() self.authenticate_group.show()
self.connection_type_socket_file_extras.show() self.connection_type_socket_file_extras.show()
self.connection_type_socks.show() self.connection_type_socks.show()
self.connection_type_bridges_radio_group.hide()
else: else:
self.connection_type_socket_file_extras.hide() self.connection_type_socket_file_extras.hide()
@ -513,7 +610,9 @@ class SettingsDialog(QtWidgets.QDialog):
if changed(settings, self.old_settings, [ if changed(settings, self.old_settings, [
'connection_type', 'control_port_address', 'connection_type', 'control_port_address',
'control_port_port', 'socks_address', 'socks_port', 'control_port_port', 'socks_address', 'socks_port',
'socket_file_path', 'auth_type', 'auth_password']): 'socket_file_path', 'auth_type', 'auth_password',
'no_bridges', 'tor_bridges_use_obfs4',
'tor_bridges_use_custom_bridges']):
reboot_onion = True reboot_onion = True
@ -614,6 +713,38 @@ class SettingsDialog(QtWidgets.QDialog):
settings.set('auth_password', self.authenticate_password_extras_password.text()) settings.set('auth_password', self.authenticate_password_extras_password.text())
# Whether we use bridges
if self.tor_bridges_no_bridges_radio.isChecked():
settings.set('no_bridges', True)
settings.set('tor_bridges_use_obfs4', False)
settings.set('tor_bridges_use_custom_bridges', '')
if self.tor_bridges_use_obfs4_radio.isChecked():
settings.set('no_bridges', False)
settings.set('tor_bridges_use_obfs4', True)
settings.set('tor_bridges_use_custom_bridges', '')
if self.tor_bridges_use_custom_radio.isChecked():
settings.set('no_bridges', False)
settings.set('tor_bridges_use_obfs4', False)
# Insert a 'Bridge' line at the start of each bridge.
# This makes it easier to copy/paste a set of bridges
# provided from https://bridges.torproject.org
new_bridges = []
bridges = self.tor_bridges_use_custom_textbox.toPlainText().split('\n')
bridges_valid = False
for bridge in bridges:
if bridge != '':
# Check the syntax of the custom bridge to make sure it looks legitimate
pattern = re.compile("[0-9.]+:[0-9]+\s[A-Z0-9]+$")
if pattern.match(bridge):
new_bridges.append(''.join(['Bridge ', bridge, '\n']))
bridges_valid = True
if bridges_valid:
new_bridges = ''.join(new_bridges)
settings.set('tor_bridges_use_custom_bridges', new_bridges)
else:
Alert(strings._('gui_settings_tor_bridges_invalid', True))
settings.set('no_bridges', True)
return settings return settings
def closeEvent(self, e): def closeEvent(self, e):

View file

@ -91,6 +91,13 @@
"gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_authenticate_cookie_option": "Cookie",
"gui_settings_password_label": "Password", "gui_settings_password_label": "Password",
"gui_settings_cookie_label": "Cookie path", "gui_settings_cookie_label": "Cookie path",
"gui_settings_tor_bridges": "Tor Bridge support",
"gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges",
"gui_settings_tor_bridges_obfs4_radio_option": "Use obfs4 pluggable transports",
"gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use obfs4 pluggable transports (requires obfs4proxy)",
"gui_settings_tor_bridges_custom_radio_option": "Use custom bridges (non-pluggable transports)",
"gui_settings_tor_bridges_custom_label": "You can get bridges from <a href=\"https://bridges.torproject.org/bridges\">https://bridges.torproject.org</a>",
"gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to be valid, so they've been ignored.\nPlease try again with valid bridges.",
"gui_settings_button_save": "Save", "gui_settings_button_save": "Save",
"gui_settings_button_cancel": "Cancel", "gui_settings_button_cancel": "Cancel",
"gui_settings_button_help": "Help", "gui_settings_button_help": "Help",

View file

@ -0,0 +1,27 @@
Bridge obfs4 154.35.22.10:80 8FB9F4319E89E5C6223052AA525A192AFBC85D55 cert=GGGS1TX4R81m3r0HBl79wKy1OtPPNR2CZUIrHjkRg65Vc2VR8fOyo64f9kmT1UAFG7j0HQ iat-mode=0
Bridge obfs4 83.212.101.3:50002 A09D536DD1752D542E1FBB3C9CE4449D51298239 cert=lPRQ/MXdD1t5SRZ9MquYQNT9m5DV757jtdXdlePmRCudUU9CFUOX1Tm7/meFSyPOsud7Cw iat-mode=0
Bridge obfs4 109.105.109.165:10527 8DFCD8FB3285E855F5A55EDDA35696C743ABFC4E cert=Bvg/itxeL4TWKLP6N1MaQzSOC6tcRIBv6q57DYAZc3b2AzuM+/TfB7mqTFEfXILCjEwzVA iat-mode=1
Bridge obfs4 154.35.22.11:80 A832D176ECD5C7C6B58825AE22FC4C90FA249637 cert=YPbQqXPiqTUBfjGFLpm9JYEFTBvnzEJDKJxXG5Sxzrr/v2qrhGU4Jls9lHjLAhqpXaEfZw iat-mode=0
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 154.35.22.9:443 C73ADBAC8ADFDBF0FC0F3F4E8091C0107D093716 cert=gEGKc5WN/bSjFa6UkG9hOcft1tuK+cV8hbZ0H6cqXiMPLqSbCh2Q3PHe5OOr6oMVORhoJA iat-mode=0
Bridge obfs4 154.35.22.11:443 A832D176ECD5C7C6B58825AE22FC4C90FA249637 cert=YPbQqXPiqTUBfjGFLpm9JYEFTBvnzEJDKJxXG5Sxzrr/v2qrhGU4Jls9lHjLAhqpXaEfZw iat-mode=0
Bridge obfs4 154.35.22.13:443 FE7840FE1E21FE0A0639ED176EDA00A3ECA1E34D cert=fKnzxr+m+jWXXQGCaXe4f2gGoPXMzbL+bTBbXMYXuK0tMotd+nXyS33y2mONZWU29l81CA iat-mode=0
Bridge obfs4 154.35.22.10:443 8FB9F4319E89E5C6223052AA525A192AFBC85D55 cert=GGGS1TX4R81m3r0HBl79wKy1OtPPNR2CZUIrHjkRg65Vc2VR8fOyo64f9kmT1UAFG7j0HQ iat-mode=0
Bridge obfs4 154.35.22.9:80 C73ADBAC8ADFDBF0FC0F3F4E8091C0107D093716 cert=gEGKc5WN/bSjFa6UkG9hOcft1tuK+cV8hbZ0H6cqXiMPLqSbCh2Q3PHe5OOr6oMVORhoJA iat-mode=0
Bridge obfs4 192.99.11.54:443 7B126FAB960E5AC6A629C729434FF84FB5074EC2 cert=VW5f8+IBUWpPFxF+rsiVy2wXkyTQG7vEd+rHeN2jV5LIDNu8wMNEOqZXPwHdwMVEBdqXEw iat-mode=0
Bridge obfs4 154.35.22.13:16815 FE7840FE1E21FE0A0639ED176EDA00A3ECA1E34D cert=fKnzxr+m+jWXXQGCaXe4f2gGoPXMzbL+bTBbXMYXuK0tMotd+nXyS33y2mONZWU29l81CA iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
Bridge obfs4 154.35.22.11:16488 A832D176ECD5C7C6B58825AE22FC4C90FA249637 cert=YPbQqXPiqTUBfjGFLpm9JYEFTBvnzEJDKJxXG5Sxzrr/v2qrhGU4Jls9lHjLAhqpXaEfZw iat-mode=0
Bridge obfs4 154.35.22.9:12166 C73ADBAC8ADFDBF0FC0F3F4E8091C0107D093716 cert=gEGKc5WN/bSjFa6UkG9hOcft1tuK+cV8hbZ0H6cqXiMPLqSbCh2Q3PHe5OOr6oMVORhoJA iat-mode=0
Bridge obfs4 109.105.109.147:13764 BBB28DF0F201E706BE564EFE690FE9577DD8386D cert=KfMQN/tNMFdda61hMgpiMI7pbwU1T+wxjTulYnfw+4sgvG0zSH7N7fwT10BI8MUdAD7iJA iat-mode=2
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 [2001:470:b381:bfff:216:3eff:fe23:d6c3]:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
Bridge obfs4 85.17.30.79:443 FC259A04A328A07FED1413E9FC6526530D9FD87A cert=RutxZlu8BtyP+y0NX7bAVD41+J/qXNhHUrKjFkRSdiBAhIHIQLhKQ2HxESAKZprn/lR3KA iat-mode=0
Bridge obfs4 154.35.22.10:15937 8FB9F4319E89E5C6223052AA525A192AFBC85D55 cert=GGGS1TX4R81m3r0HBl79wKy1OtPPNR2CZUIrHjkRg65Vc2VR8fOyo64f9kmT1UAFG7j0HQ iat-mode=0
Bridge obfs4 37.218.240.34:40035 88CD36D45A35271963EF82E511C8827A24730913 cert=eGXYfWODcgqIdPJ+rRupg4GGvVGfh25FWaIXZkit206OSngsp7GAIiGIXOJJROMxEqFKJg iat-mode=1
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
Bridge obfs4 154.35.22.12:80 00DC6C4FA49A65BD1472993CF6730D54F11E0DBB cert=N86E9hKXXXVz6G7w2z8wFfhIDztDAzZ/3poxVePHEYjbKDWzjkRDccFMAnhK75fc65pYSg iat-mode=0
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
Bridge obfs4 154.35.22.12:4304 00DC6C4FA49A65BD1472993CF6730D54F11E0DBB cert=N86E9hKXXXVz6G7w2z8wFfhIDztDAzZ/3poxVePHEYjbKDWzjkRDccFMAnhK75fc65pYSg iat-mode=0
UseBridges 1

View file

@ -1,6 +1,6 @@
[DEFAULT] [DEFAULT]
Package3: onionshare Package3: onionshare
Depends3: python3-flask, python3-stem, python3-pyqt5, python-nautilus, tor Depends3: python3-flask, python3-stem, python3-pyqt5, python-nautilus, tor, obfs4proxy
Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5 Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5
Suite: xenial Suite: xenial
X-Python3-Version: >= 3.4 X-Python3-Version: >= 3.4

View file

@ -213,13 +213,15 @@ class TestGetTorPaths:
base_path, 'Resources', 'Tor', 'geoip') base_path, 'Resources', 'Tor', 'geoip')
tor_geo_ipv6_file_path = os.path.join( tor_geo_ipv6_file_path = os.path.join(
base_path, 'Resources', 'Tor', 'geoip6') base_path, 'Resources', 'Tor', 'geoip6')
obfs4proxy_file_path = os.path.join(
base_path, 'Resources', 'Tor', 'obfs4proxy')
assert (common.get_tor_paths() == assert (common.get_tor_paths() ==
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path)) (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
# @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ? # @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ?
def test_get_tor_paths_linux(self, platform_linux): def test_get_tor_paths_linux(self, platform_linux):
assert (common.get_tor_paths() == assert (common.get_tor_paths() ==
('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6')) ('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6', '/usr/bin/obfs4proxy'))
# @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ? # @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ?
def test_get_tor_paths_windows(self, platform_windows, sys_frozen): def test_get_tor_paths_windows(self, platform_windows, sys_frozen):
@ -228,7 +230,9 @@ class TestGetTorPaths:
os.path.dirname( os.path.dirname(
common.get_resource_path(''))), 'tor') common.get_resource_path(''))), 'tor')
tor_path = os.path.join( tor_path = os.path.join(
os.path.join(base_path, 'Tor'), "tor.exe") os.path.join(base_path, 'Tor'), 'tor.exe')
obfs4proxy_file_path = os.path.join(
os.path.join(base_path, 'Tor'), 'obfs4proxy.exe')
tor_geo_ip_file_path = os.path.join( tor_geo_ip_file_path = os.path.join(
os.path.join( os.path.join(
os.path.join(base_path, 'Data'), 'Tor'), 'geoip') os.path.join(base_path, 'Data'), 'Tor'), 'geoip')
@ -236,7 +240,7 @@ class TestGetTorPaths:
os.path.join( os.path.join(
os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
assert (common.get_tor_paths() == assert (common.get_tor_paths() ==
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path)) (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
class TestGetVersion: class TestGetVersion:

View file

@ -58,6 +58,9 @@ class TestSettings:
'use_stealth': False, 'use_stealth': False,
'use_autoupdate': True, 'use_autoupdate': True,
'autoupdate_timestamp': None, 'autoupdate_timestamp': None,
'no_bridges': True,
'tor_bridges_use_obfs4': False,
'tor_bridges_use_custom_bridges': '',
'save_private_key': False, 'save_private_key': False,
'private_key': '', 'private_key': '',
'slug': '', 'slug': '',
@ -119,6 +122,11 @@ class TestSettings:
assert settings_obj.get('use_stealth') is False assert settings_obj.get('use_stealth') is False
assert settings_obj.get('use_autoupdate') is True assert settings_obj.get('use_autoupdate') is True
assert settings_obj.get('autoupdate_timestamp') is None assert settings_obj.get('autoupdate_timestamp') is None
assert settings_obj.get('autoupdate_timestamp') is None
assert settings_obj.get('no_bridges') is True
assert settings_obj.get('tor_bridges_use_obfs4') is False
assert settings_obj.get('tor_bridges_use_custom_bridges') == ''
def test_set_version(self, settings_obj): def test_set_version(self, settings_obj):
settings_obj.set('version', 'CUSTOM_VERSION') settings_obj.set('version', 'CUSTOM_VERSION')
@ -165,3 +173,7 @@ class TestSettings:
monkeypatch.setenv('APPDATA', 'C:') monkeypatch.setenv('APPDATA', 'C:')
obj = settings.Settings() obj = settings.Settings()
assert obj.filename == 'C:\\OnionShare\\onionshare.json' assert obj.filename == 'C:\\OnionShare\\onionshare.json'
def test_set_custom_bridge(self, settings_obj):
settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E')
assert settings_obj._settings['tor_bridges_use_custom_bridges'] == 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E'