From 4674f7d97bfcdab906dc152fbf643330e52fb090 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 11 Dec 2017 14:53:13 +1100 Subject: [PATCH 01/19] Add bridge support (obfs4 or user-supplied) --- onionshare/onion.py | 9 ++++ onionshare/settings.py | 5 +- onionshare_gui/settings_dialog.py | 78 ++++++++++++++++++++++++++++++- share/locale/en.json | 4 ++ share/torrc_template-obfs4 | 27 +++++++++++ 5 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 share/torrc_template-obfs4 diff --git a/onionshare/onion.py b/onionshare/onion.py index 2f79719b..d420692e 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -205,6 +205,15 @@ class Onion(object): with open(self.tor_torrc, 'w') as f: f.write(torrc_template) + # Bridge support + if self.settings.get('tor_bridges_use_obfs4'): + f.write('\n') + 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')) + # Execute a tor subprocess start_ts = time.time() if self.system == 'Windows': diff --git a/onionshare/settings.py b/onionshare/settings.py index 408c8bdc..d262aab8 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -60,7 +60,10 @@ class Settings(object): 'systray_notifications': True, 'use_stealth': False, 'use_autoupdate': True, - 'autoupdate_timestamp': None + 'autoupdate_timestamp': None, + 'no_bridges': True, + 'tor_bridges_use_obfs4': False, + 'tor_bridges_use_custom_bridges': '' } self._settings = {} self.fill_in_defaults() diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index df806a06..1280b342 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -194,6 +194,18 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) + # Bridges support + 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) + + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option', True)) + 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_textbox = QtWidgets.QPlainTextEdit() + self.tor_bridges_use_custom_textbox.setPlaceholderText('Bridge [address:port] [identifier]') + self.tor_bridges_use_custom_textbox.hide() + # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) @@ -213,10 +225,19 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_group_layout.addWidget(self.connection_type_socket_file_extras) connection_type_group_layout.addWidget(self.connection_type_socks) connection_type_group_layout.addWidget(self.authenticate_group) - connection_type_group_layout.addWidget(self.connection_type_test_button) connection_type_group = QtWidgets.QGroupBox() 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.tor_bridges_no_bridges_radio) + connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_obfs4_radio) + connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_custom_radio) + connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_custom_textbox) + connection_type_bridges_radio_group_layout.addWidget(self.connection_type_test_button) + connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges", True)) + connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) + # Buttons self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) self.save_button.clicked.connect(self.save_clicked) @@ -245,6 +266,7 @@ class SettingsDialog(QtWidgets.QDialog): right_col_layout = QtWidgets.QVBoxLayout() right_col_layout.addWidget(connection_type_radio_group) right_col_layout.addWidget(connection_type_group) + right_col_layout.addWidget(connection_type_bridges_radio_group) right_col_layout.addWidget(self.tor_status) right_col_layout.addStretch() @@ -315,6 +337,17 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_password_radio.setChecked(True) 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) + self.tor_bridges_use_custom_textbox.setPlainText(self.old_settings.get('tor_bridges_use_custom_bridges')) + def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. @@ -324,6 +357,31 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group.hide() self.connection_type_socks.hide() + 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_obfs4_radio.setEnabled(True) + self.tor_bridges_use_custom_radio.setEnabled(True) + self.tor_bridges_use_custom_textbox.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_radio.setEnabled(False) + self.tor_bridges_use_custom_textbox.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_obfs4_radio.setEnabled(False) + self.tor_bridges_use_custom_textbox.show() + def connection_type_automatic_toggled(self, checked): """ Connection type automatic was toggled. If checked, hide authentication fields. @@ -473,7 +531,9 @@ class SettingsDialog(QtWidgets.QDialog): if changed(settings, self.old_settings, [ 'connection_type', 'control_port_address', '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 @@ -554,6 +614,20 @@ class SettingsDialog(QtWidgets.QDialog): 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) + settings.set('tor_bridges_use_custom_bridges', self.tor_bridges_use_custom_textbox.toPlainText()) + return settings def closeEvent(self, e): diff --git a/share/locale/en.json b/share/locale/en.json index 0756843e..f853295c 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -90,6 +90,10 @@ "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Password", "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_custom_radio_option": "Use custom bridges", "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", diff --git a/share/torrc_template-obfs4 b/share/torrc_template-obfs4 new file mode 100644 index 00000000..306c456c --- /dev/null +++ b/share/torrc_template-obfs4 @@ -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 From 2a52b1a6ac48068f004a68e6e060034f488f8f2f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 11 Dec 2017 16:20:42 +1100 Subject: [PATCH 02/19] Forgot the UseBridges line for custom bridges --- onionshare/onion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionshare/onion.py b/onionshare/onion.py index d420692e..ec98a171 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -213,6 +213,7 @@ class Onion(object): 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 start_ts = time.time() From 304059ddaa0d77333b3c6945b6ed5f4bc226db91 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 11 Dec 2017 16:28:53 +1100 Subject: [PATCH 03/19] fix tests for bridge support --- test/test_onionshare_settings.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 1489c348..387177ab 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -57,7 +57,10 @@ class TestSettings: 'systray_notifications': True, 'use_stealth': False, 'use_autoupdate': True, - 'autoupdate_timestamp': None + 'autoupdate_timestamp': None, + 'no_bridges': True, + 'tor_bridges_use_obfs4': False, + 'tor_bridges_use_custom_bridges': '' } def test_fill_in_defaults(self, settings_obj): @@ -115,6 +118,11 @@ class TestSettings: assert settings_obj.get('use_stealth') is False 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('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): settings_obj.set('version', 'CUSTOM_VERSION') @@ -161,3 +169,7 @@ class TestSettings: monkeypatch.setenv('APPDATA', 'C:') obj = settings.Settings() 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' From b23a4f2bee0273eacc089cb6fe78a661283dd631 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 11 Dec 2017 16:48:26 +1100 Subject: [PATCH 04/19] Improvements to the custom bridges textfield widget, still not perfect when used in conjunction with Tor socket/auth option widgets --- onionshare_gui/settings_dialog.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 1280b342..11dc755f 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -204,7 +204,12 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox.setPlaceholderText('Bridge [address:port] [identifier]') - self.tor_bridges_use_custom_textbox.hide() + tor_bridges_use_custom_textbox_options_layout = QtWidgets.QHBoxLayout() + 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() + self.tor_bridges_use_custom_textbox_options.adjustSize() # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) @@ -233,7 +238,7 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_no_bridges_radio) connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_obfs4_radio) connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_custom_radio) - connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_custom_textbox) + connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_custom_textbox_options) connection_type_bridges_radio_group_layout.addWidget(self.connection_type_test_button) connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges", True)) connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) @@ -364,7 +369,7 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.tor_bridges_use_obfs4_radio.setEnabled(True) self.tor_bridges_use_custom_radio.setEnabled(True) - self.tor_bridges_use_custom_textbox.hide() + self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_obfs4_radio_toggled(self, checked): """ @@ -372,7 +377,7 @@ class SettingsDialog(QtWidgets.QDialog): """ if checked: self.tor_bridges_use_custom_radio.setEnabled(False) - self.tor_bridges_use_custom_textbox.hide() + self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_custom_radio_toggled(self, checked): """ @@ -380,7 +385,7 @@ class SettingsDialog(QtWidgets.QDialog): """ if checked: self.tor_bridges_use_obfs4_radio.setEnabled(False) - self.tor_bridges_use_custom_textbox.show() + self.tor_bridges_use_custom_textbox_options.show() def connection_type_automatic_toggled(self, checked): """ From 3f27b80154226e9356bd545c006f63f437a217f2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 11 Dec 2017 17:58:53 +1100 Subject: [PATCH 05/19] experimenting with adjustSize() which seems to help the widget issue. Also adjust the custom Bridge parsing so that it injects the Bridge prefix automatically (means we can copy/pasta from https://bridges.torproject.org/bridges) --- a | 32 ++++++++++++++++++++ onionshare_gui/settings_dialog.py | 50 +++++++++++++++++++++++++++---- share/locale/en.json | 1 + 3 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 a diff --git a/a b/a new file mode 100644 index 00000000..f45f6335 --- /dev/null +++ b/a @@ -0,0 +1,32 @@ +Old Settings + + + ++ # 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) + + + + ++ # 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') ++ for bridge in bridges: ++ if bridge != '': ++ new_bridges.append(''.join(['Bridge ', bridge, '\n'])) ++ new_bridges = ''.join(new_bridges) ++ settings.set('tor_bridges_use_custom_bridges', new_bridges) + + + ++ "gui_settings_tor_bridges_custom_label": "You can obtain bridges from https://bridges.torproject.org", + diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 11dc755f..3736f31f 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -202,18 +202,27 @@ class SettingsDialog(QtWidgets.QDialog): 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_textbox = QtWidgets.QPlainTextEdit() - self.tor_bridges_use_custom_textbox.setPlaceholderText('Bridge [address:port] [identifier]') - tor_bridges_use_custom_textbox_options_layout = QtWidgets.QHBoxLayout() + self.tor_bridges_use_custom_textbox.setPlaceholderText('[address:port] [identifier]') + self.tor_bridges_use_custom_textbox.adjustSize() + + 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() - self.tor_bridges_use_custom_textbox_options.adjustSize() # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) + connection_type_test_button_layout = QtWidgets.QHBoxLayout() + connection_type_test_button_layout.addWidget(self.connection_type_test_button) + connection_type_test_button_group = QtWidgets.QGroupBox() + connection_type_test_button_group.setLayout(connection_type_test_button_layout) + # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() @@ -239,7 +248,6 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_obfs4_radio) connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_custom_radio) connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_custom_textbox_options) - connection_type_bridges_radio_group_layout.addWidget(self.connection_type_test_button) connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges", True)) connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) @@ -272,6 +280,7 @@ class SettingsDialog(QtWidgets.QDialog): right_col_layout.addWidget(connection_type_radio_group) right_col_layout.addWidget(connection_type_group) right_col_layout.addWidget(connection_type_bridges_radio_group) + right_col_layout.addWidget(connection_type_test_button_group) right_col_layout.addWidget(self.tor_status) right_col_layout.addStretch() @@ -351,7 +360,15 @@ class SettingsDialog(QtWidgets.QDialog): 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) - self.tor_bridges_use_custom_textbox.setPlainText(self.old_settings.get('tor_bridges_use_custom_bridges')) + # 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): """ @@ -386,6 +403,8 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.tor_bridges_use_obfs4_radio.setEnabled(False) self.tor_bridges_use_custom_textbox_options.show() + self.tor_bridges_use_custom_textbox_options.adjustSize() + self.adjustSize() def connection_type_automatic_toggled(self, checked): """ @@ -406,6 +425,10 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() + self.authenticate_group.adjustSize() + self.connection_type_control_port_extras.adjustSize() + self.connection_type_socks.adjustSize() + self.adjustSize() else: self.connection_type_control_port_extras.hide() @@ -420,6 +443,10 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() + self.authenticate_group.adjustSize() + self.connection_type_socket_file_extras.adjustSize() + self.connection_type_socks.adjustSize() + self.adjustSize() else: self.connection_type_socket_file_extras.hide() @@ -437,6 +464,8 @@ class SettingsDialog(QtWidgets.QDialog): common.log('SettingsDialog', 'authenticate_password_toggled') if checked: self.authenticate_password_extras.show() + self.authenticate_password_extras.adjustSize() + self.adjustSize() else: self.authenticate_password_extras.hide() @@ -631,7 +660,16 @@ class SettingsDialog(QtWidgets.QDialog): if self.tor_bridges_use_custom_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_custom_bridges', self.tor_bridges_use_custom_textbox.toPlainText()) + # 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') + for bridge in bridges: + if bridge != '': + new_bridges.append(''.join(['Bridge ', bridge, '\n'])) + new_bridges = ''.join(new_bridges) + settings.set('tor_bridges_use_custom_bridges', new_bridges) return settings diff --git a/share/locale/en.json b/share/locale/en.json index f853295c..6e3afe94 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -94,6 +94,7 @@ "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_custom_radio_option": "Use custom bridges", + "gui_settings_tor_bridges_custom_label": "You can obtain bridges from https://bridges.torproject.org", "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", From ba5356bac77f965b672ecb1e5f99b6b8c25f20b5 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 11 Dec 2017 18:01:14 +1100 Subject: [PATCH 06/19] remove debug file.. --- a | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 a diff --git a/a b/a deleted file mode 100644 index f45f6335..00000000 --- a/a +++ /dev/null @@ -1,32 +0,0 @@ -Old Settings - - - -+ # 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) - - - - -+ # 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') -+ for bridge in bridges: -+ if bridge != '': -+ new_bridges.append(''.join(['Bridge ', bridge, '\n'])) -+ new_bridges = ''.join(new_bridges) -+ settings.set('tor_bridges_use_custom_bridges', new_bridges) - - - -+ "gui_settings_tor_bridges_custom_label": "You can obtain bridges from https://bridges.torproject.org", - From 661cf7f1294d7c6c75f7f49c0f6d92ed8cb303de Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 12 Dec 2017 08:43:12 +1100 Subject: [PATCH 07/19] Fix the Bridges widgets so that they only appear when bundled Tor is chosen. Fixes all the other widget cramming issues. --- onionshare_gui/settings_dialog.py | 93 ++++++++++++++++++------------- share/locale/en.json | 2 +- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 3736f31f..65685ceb 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -119,6 +119,41 @@ class SettingsDialog(QtWidgets.QDialog): if (system == 'Windows' or system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', 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 + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option', True)) + + # 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_textbox = QtWidgets.QPlainTextEdit() + 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 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) @@ -194,27 +229,6 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) - # Bridges support - 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) - - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option', True)) - 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_textbox = QtWidgets.QPlainTextEdit() - self.tor_bridges_use_custom_textbox.setPlaceholderText('[address:port] [identifier]') - self.tor_bridges_use_custom_textbox.adjustSize() - - 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() - # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) @@ -223,7 +237,6 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_test_button_group = QtWidgets.QGroupBox() connection_type_test_button_group.setLayout(connection_type_test_button_layout) - # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio) @@ -244,12 +257,10 @@ class SettingsDialog(QtWidgets.QDialog): # 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.tor_bridges_no_bridges_radio) - connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_obfs4_radio) - connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_custom_radio) - connection_type_bridges_radio_group_layout.addWidget(self.tor_bridges_use_custom_textbox_options) - connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges", True)) - connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) + 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 self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) @@ -279,7 +290,7 @@ class SettingsDialog(QtWidgets.QDialog): right_col_layout = QtWidgets.QVBoxLayout() right_col_layout.addWidget(connection_type_radio_group) right_col_layout.addWidget(connection_type_group) - right_col_layout.addWidget(connection_type_bridges_radio_group) + right_col_layout.addWidget(self.connection_type_bridges_radio_group) right_col_layout.addWidget(connection_type_test_button_group) right_col_layout.addWidget(self.tor_status) right_col_layout.addStretch() @@ -378,6 +389,9 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.authenticate_group.hide() self.connection_type_socks.hide() + self.connection_type_bridges_radio_group.show() + self.connection_type_bridges_radio_group.adjustSize() + self.adjustSize() def tor_bridges_no_bridges_radio_toggled(self, checked): """ @@ -387,6 +401,8 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_obfs4_radio.setEnabled(True) self.tor_bridges_use_custom_radio.setEnabled(True) self.tor_bridges_use_custom_textbox_options.hide() + self.connection_type_bridges_radio_group.adjustSize() + self.adjustSize() def tor_bridges_use_obfs4_radio_toggled(self, checked): """ @@ -395,6 +411,8 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.tor_bridges_use_custom_radio.setEnabled(False) self.tor_bridges_use_custom_textbox_options.hide() + self.connection_type_bridges_radio_group.adjustSize() + self.adjustSize() def tor_bridges_use_custom_radio_toggled(self, checked): """ @@ -403,7 +421,7 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.tor_bridges_use_obfs4_radio.setEnabled(False) self.tor_bridges_use_custom_textbox_options.show() - self.tor_bridges_use_custom_textbox_options.adjustSize() + self.connection_type_bridges_radio_group.adjustSize() self.adjustSize() def connection_type_automatic_toggled(self, checked): @@ -414,6 +432,9 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.authenticate_group.hide() self.connection_type_socks.hide() + self.connection_type_bridges_radio_group.adjustSize() + self.connection_type_bridges_radio_group.hide() + self.adjustSize() def connection_type_control_port_toggled(self, checked): """ @@ -425,9 +446,8 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() - self.authenticate_group.adjustSize() - self.connection_type_control_port_extras.adjustSize() - self.connection_type_socks.adjustSize() + self.connection_type_bridges_radio_group.adjustSize() + self.connection_type_bridges_radio_group.hide() self.adjustSize() else: self.connection_type_control_port_extras.hide() @@ -443,9 +463,8 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() - self.authenticate_group.adjustSize() - self.connection_type_socket_file_extras.adjustSize() - self.connection_type_socks.adjustSize() + self.connection_type_bridges_radio_group.adjustSize() + self.connection_type_bridges_radio_group.hide() self.adjustSize() else: self.connection_type_socket_file_extras.hide() @@ -464,8 +483,6 @@ class SettingsDialog(QtWidgets.QDialog): common.log('SettingsDialog', 'authenticate_password_toggled') if checked: self.authenticate_password_extras.show() - self.authenticate_password_extras.adjustSize() - self.adjustSize() else: self.authenticate_password_extras.hide() diff --git a/share/locale/en.json b/share/locale/en.json index 6e3afe94..fe3baf8d 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -94,7 +94,7 @@ "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_custom_radio_option": "Use custom bridges", - "gui_settings_tor_bridges_custom_label": "You can obtain bridges from https://bridges.torproject.org", + "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", From 890acf9f38ba700b2b655ed3efae071e1c2bcfed Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 14 Dec 2017 17:31:15 +1100 Subject: [PATCH 08/19] Let the Tor connection take a little longer (60s) if bridges are in use --- onionshare/onion.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index ec98a171..e76072ce 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -264,8 +264,13 @@ class Onion(object): break time.sleep(0.2) - # Timeout after 45 seconds - if time.time() - start_ts > 45: + # If using bridges, it might take a bit longer to connect to Tor + if self.settings.get('tor_bridges_use_custom_bridges') or self.settings.get('tor_bridges_use_obfs4'): + connect_timeout = 60 + else: + # Timeout after 45 seconds + connect_timeout = 45 + if time.time() - start_ts > connect_timeout: print("") self.tor_proc.terminate() raise BundledTorTimeout(strings._('settings_error_bundled_tor_timeout')) From 011b635caecaa14ec6af232919722f010110920c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 14 Jan 2018 18:57:52 +1100 Subject: [PATCH 09/19] ensure custom bridges hyperlink opens a browser as per #520 --- 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 092b9341..b3a393a6 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -135,6 +135,8 @@ class SettingsDialog(QtWidgets.QDialog): 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.setPlaceholderText('[address:port] [identifier]') From ed8c4c8302ba889629bc2e3cd7f4bf9b67efeabc Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 14 Jan 2018 20:12:06 +1100 Subject: [PATCH 10/19] ensure hte obfs4 torrc template is included in installers --- install/onionshare.nsi | 2 ++ install/pyinstaller.spec | 1 + 2 files changed, 3 insertions(+) diff --git a/install/onionshare.nsi b/install/onionshare.nsi index d980e0e0..10f459fd 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -146,6 +146,7 @@ Section "install" File "${BINPATH}\share\version.txt" File "${BINPATH}\share\wordlist.txt" File "${BINPATH}\share\torrc_template-windows" + File "${BINPATH}\share\torrc_template-obfs4" SetOutPath "$INSTDIR\share\html" File "${BINPATH}\share\html\404.html" @@ -335,6 +336,7 @@ FunctionEnd Delete "$INSTDIR\share\version.txt" Delete "$INSTDIR\share\wordlist.txt" Delete "$INSTDIR\share\torrc_template-windows" + Delete "$INSTDIR\share\torrc_template-obfs4" Delete "$INSTDIR\share\html\404.html" Delete "$INSTDIR\share\html\denied.html" Delete "$INSTDIR\share\html\index.html" diff --git a/install/pyinstaller.spec b/install/pyinstaller.spec index 0ba359d0..5922991d 100644 --- a/install/pyinstaller.spec +++ b/install/pyinstaller.spec @@ -14,6 +14,7 @@ a = Analysis( ('../share/version.txt', 'share'), ('../share/wordlist.txt', 'share'), ('../share/torrc_template', 'share'), + ('../share/torrc_template-obfs4', 'share'), ('../share/torrc_template-windows', 'share'), ('../share/images/*', 'share/images'), ('../share/locale/*', 'share/locale'), From bf01fcaa6e1232829d1d6b93ae5fc715a3b8e32b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 14 Jan 2018 20:12:24 +1100 Subject: [PATCH 11/19] Validate syntax of custom bridges --- onionshare_gui/settings_dialog.py | 17 +++++++++++++---- share/locale/en.json | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index b3a393a6..b08130d1 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -18,7 +18,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui -import sys, platform, datetime +import sys, platform, datetime, re from onionshare import strings, common from onionshare.settings import Settings @@ -688,11 +688,20 @@ class SettingsDialog(QtWidgets.QDialog): # 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 != '': - new_bridges.append(''.join(['Bridge ', bridge, '\n'])) - new_bridges = ''.join(new_bridges) - settings.set('tor_bridges_use_custom_bridges', new_bridges) + # 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 diff --git a/share/locale/en.json b/share/locale/en.json index cfdcdb6d..9b5fcac6 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -95,6 +95,7 @@ "gui_settings_tor_bridges_obfs4_radio_option": "Use obfs4 pluggable transports", "gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", + "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_cancel": "Cancel", "gui_settings_button_help": "Help", From 55c390eb7f3fca6ad4e2713c745bd3d62af4d34e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 15 Jan 2018 10:49:17 +1100 Subject: [PATCH 12/19] remove the adjustSize stuff which causes more problems than it solves. Revert the change to positioning of the Test Tor button. Set maximum height for custom bridges QPlainTextEdit widget, seems to help with overlap issues --- onionshare_gui/settings_dialog.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index b08130d1..0c5198fc 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -138,6 +138,7 @@ class SettingsDialog(QtWidgets.QDialog): 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() @@ -236,10 +237,6 @@ class SettingsDialog(QtWidgets.QDialog): # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) - connection_type_test_button_layout = QtWidgets.QHBoxLayout() - connection_type_test_button_layout.addWidget(self.connection_type_test_button) - connection_type_test_button_group = QtWidgets.QGroupBox() - connection_type_test_button_group.setLayout(connection_type_test_button_layout) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() @@ -256,6 +253,7 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_group_layout.addWidget(self.connection_type_socket_file_extras) connection_type_group_layout.addWidget(self.connection_type_socks) connection_type_group_layout.addWidget(self.authenticate_group) + connection_type_group_layout.addWidget(self.connection_type_test_button) connection_type_group = QtWidgets.QGroupBox() connection_type_group.setLayout(connection_type_group_layout) @@ -295,7 +293,6 @@ class SettingsDialog(QtWidgets.QDialog): right_col_layout.addWidget(connection_type_radio_group) right_col_layout.addWidget(connection_type_group) right_col_layout.addWidget(self.connection_type_bridges_radio_group) - right_col_layout.addWidget(connection_type_test_button_group) right_col_layout.addWidget(self.tor_status) right_col_layout.addStretch() @@ -394,8 +391,6 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.show() - self.connection_type_bridges_radio_group.adjustSize() - self.adjustSize() def tor_bridges_no_bridges_radio_toggled(self, checked): """ @@ -405,8 +400,6 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_obfs4_radio.setEnabled(True) self.tor_bridges_use_custom_radio.setEnabled(True) self.tor_bridges_use_custom_textbox_options.hide() - self.connection_type_bridges_radio_group.adjustSize() - self.adjustSize() def tor_bridges_use_obfs4_radio_toggled(self, checked): """ @@ -415,8 +408,6 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.tor_bridges_use_custom_radio.setEnabled(False) self.tor_bridges_use_custom_textbox_options.hide() - self.connection_type_bridges_radio_group.adjustSize() - self.adjustSize() def tor_bridges_use_custom_radio_toggled(self, checked): """ @@ -425,8 +416,6 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.tor_bridges_use_obfs4_radio.setEnabled(False) self.tor_bridges_use_custom_textbox_options.show() - self.connection_type_bridges_radio_group.adjustSize() - self.adjustSize() def connection_type_automatic_toggled(self, checked): """ @@ -436,9 +425,7 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.authenticate_group.hide() self.connection_type_socks.hide() - self.connection_type_bridges_radio_group.adjustSize() self.connection_type_bridges_radio_group.hide() - self.adjustSize() def connection_type_control_port_toggled(self, checked): """ @@ -450,9 +437,7 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() - self.connection_type_bridges_radio_group.adjustSize() self.connection_type_bridges_radio_group.hide() - self.adjustSize() else: self.connection_type_control_port_extras.hide() @@ -467,9 +452,7 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() - self.connection_type_bridges_radio_group.adjustSize() self.connection_type_bridges_radio_group.hide() - self.adjustSize() else: self.connection_type_socket_file_extras.hide() From 82b1860dacab66f4b5f7b8e630fb9cc322faa1a2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 15 Jan 2018 11:25:08 +1100 Subject: [PATCH 13/19] add missing 'toggled' connect for obfs4 radio. Remove the enabling/disabling of radios for bridges, it obstructs normal UX --- onionshare_gui/settings_dialog.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 0c5198fc..7b24bf0c 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -129,6 +129,7 @@ class SettingsDialog(QtWidgets.QDialog): # OBFS4 option radio 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)) @@ -397,8 +398,6 @@ class SettingsDialog(QtWidgets.QDialog): 'No bridges' option was toggled. If checked, enable other bridge options. """ if checked: - self.tor_bridges_use_obfs4_radio.setEnabled(True) - self.tor_bridges_use_custom_radio.setEnabled(True) self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_obfs4_radio_toggled(self, checked): @@ -406,7 +405,6 @@ class SettingsDialog(QtWidgets.QDialog): obfs4 bridges option was toggled. If checked, disable custom bridge options. """ if checked: - self.tor_bridges_use_custom_radio.setEnabled(False) self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_custom_radio_toggled(self, checked): @@ -414,7 +412,6 @@ class SettingsDialog(QtWidgets.QDialog): Custom bridges option was toggled. If checked, show custom bridge options. """ if checked: - self.tor_bridges_use_obfs4_radio.setEnabled(False) self.tor_bridges_use_custom_textbox_options.show() def connection_type_automatic_toggled(self, checked): From 284213aa2d9fb4756a90891b43cdd8c974496e23 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 15 Jan 2018 12:49:29 +1100 Subject: [PATCH 14/19] support obfs4 via obfs4proxy on macOS --- install/get-tor-osx.py | 3 +++ onionshare/common.py | 6 +++++- onionshare/onion.py | 4 ++-- onionshare_gui/settings_dialog.py | 5 +++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index b8c17fba..344f5b61 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -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', '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) + # 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 subprocess.call(['diskutil', 'unmount', '/Volumes/Tor Browser']) diff --git a/onionshare/common.py b/onionshare/common.py index 562c71f2..298dd42b 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -94,22 +94,26 @@ def get_tor_paths(): tor_path = '/usr/bin/tor' tor_geo_ip_file_path = '/usr/share/tor/geoip' tor_geo_ipv6_file_path = '/usr/share/tor/geoip6' + obfs4proxy_file_path = '/usr/bin/obfs4proxy' elif p == 'Windows': 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_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') + obfs4proxy_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'obfs4proxy') elif p == 'Darwin': base_path = os.path.dirname(os.path.dirname(os.path.dirname(get_resource_path('')))) 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_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': tor_path = '/usr/local/bin/tor' tor_geo_ip_file_path = '/usr/local/share/tor/geoip' 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(): diff --git a/onionshare/onion.py b/onionshare/onion.py index 61ab128c..0385d019 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -140,7 +140,7 @@ class Onion(object): self.bundle_tor_supported = True # Set the path of the tor binary, for bundled tor - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path) = 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 self.tor_proc = None @@ -207,7 +207,7 @@ class Onion(object): # Bridge support if self.settings.get('tor_bridges_use_obfs4'): - f.write('\n') + 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) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 7b24bf0c..7b1358a6 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -130,6 +130,11 @@ class SettingsDialog(QtWidgets.QDialog): # OBFS4 option radio 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) + # if the obfs4proxy binary is missing, we can't use obfs4 transports + if system != 'Windows' and system != 'Darwin': + (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.setEnabled(False) # Custom bridges radio and textbox self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option', True)) From 3f3d1c751d2b81c8f17b1c501cb868ef907dfac6 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 15 Jan 2018 12:56:24 +1100 Subject: [PATCH 15/19] Fix tests for obfs4proxy --- test/test_onionshare_common.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/test_onionshare_common.py b/test/test_onionshare_common.py index 8d0eb0ea..3775951b 100644 --- a/test/test_onionshare_common.py +++ b/test/test_onionshare_common.py @@ -213,13 +213,15 @@ class TestGetTorPaths: base_path, 'Resources', 'Tor', 'geoip') tor_geo_ipv6_file_path = os.path.join( base_path, 'Resources', 'Tor', 'geoip6') + obfs4proxy_file_path = os.path.join( + base_path, 'Resources', 'Tor', 'obfs4proxy') 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') ? def test_get_tor_paths_linux(self, platform_linux): 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') ? def test_get_tor_paths_windows(self, platform_windows, sys_frozen): @@ -235,8 +237,10 @@ class TestGetTorPaths: tor_geo_ipv6_file_path = os.path.join( os.path.join( os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') + obfs4proxy_file_path = os.path.join( + base_path, 'Data', 'Tor', 'obfs4proxy') 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: From cc9a08c07dd633d36f7c135ef0eff6d29f2acf60 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 16 Jan 2018 15:29:03 +1100 Subject: [PATCH 16/19] Add obfs4proxy as a dependency on Debian/Fedora-like distros, for obfs4 bridge support --- BUILD.md | 4 ++-- install/build_rpm.sh | 2 +- stdeb.cfg | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BUILD.md b/BUILD.md index fc31ce3c..c9224611 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` +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: diff --git a/install/build_rpm.sh b/install/build_rpm.sh index 98cd9c5b..c103262c 100755 --- a/install/build_rpm.sh +++ b/install/build_rpm.sh @@ -9,7 +9,7 @@ VERSION=`cat share/version.txt` rm -r build dist >/dev/null 2>&1 # 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 echo "" diff --git a/stdeb.cfg b/stdeb.cfg index cc811197..334502c0 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [DEFAULT] 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 Suite: xenial X-Python3-Version: >= 3.4 From 40ee114803b2c0f0fcc44fd26106e7834ce0e073 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Jan 2018 12:45:37 +1100 Subject: [PATCH 17/19] Extract tor and obfs4proxy executables from the main TorBrowser executable with 7-zip on Windows --- BUILD.md | 7 +++++- install/get-tor-windows.py | 46 ++++++++++++++++------------------ install/onionshare.nsi | 1 + onionshare/common.py | 4 +-- test/test_onionshare_common.py | 6 ++--- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/BUILD.md b/BUILD.md index c9224611..d1fdcb88 100644 --- a/BUILD.md +++ b/BUILD.md @@ -94,11 +94,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 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\x86` * `C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x86` * `C:\Users\user\AppData\Local\Programs\Python\Python35-32\Lib\site-packages\PyQt5\Qt\bin` +* `C:\Program Files (x86)\7-Zip` If you want to build the installer: diff --git a/install/get-tor-windows.py b/install/get-tor-windows.py index 83b8f2b3..4945ce68 100644 --- a/install/get-tor-windows.py +++ b/install/get-tor-windows.py @@ -24,18 +24,17 @@ In order to avoid a Windows gnupg dependency, I manually verify the signature and hard-code the sha256 hash. """ -import inspect, os, sys, hashlib, zipfile, io, shutil +import inspect, os, sys, hashlib, shutil, subprocess import urllib.request def main(): - zip_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.0.11/tor-win32-0.3.1.9.zip' - zip_filename = 'tor-win32-0.3.1.9.zip' - expected_zip_sha256 = 'faf28efb606455842bda66ca369287a116b6d6e5ad3720ebed9337da0717f1b4' - + exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.0.11/torbrowser-install-7.0.11_en-US.exe' + exe_filename = 'torbrowser-install-7.0.11_en-US.exe' + expected_exe_sha256 = 'a033eb9b9ed2ad389169b36a90946a8af8f05bd0c7bbd3e37678041331096624' # Build paths 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') - 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') # Make sure the working folder exists @@ -43,36 +42,35 @@ def main(): os.makedirs(working_path) # Make sure the zip is downloaded - if not os.path.exists(zip_path): - print("Downloading {}".format(zip_url)) - response = urllib.request.urlopen(zip_url) - zip_data = response.read() - open(zip_path, 'wb').write(zip_data) - zip_sha256 = hashlib.sha256(zip_data).hexdigest() + if not os.path.exists(exe_path): + print("Downloading {}".format(exe_url)) + response = urllib.request.urlopen(exe_url) + exe_data = response.read() + open(exe_path, 'wb').write(exe_data) + exe_sha256 = hashlib.sha256(exe_data).hexdigest() else: - zip_data = open(zip_path, 'rb').read() - zip_sha256 = hashlib.sha256(zip_data).hexdigest() + exe_data = open(exe_path, 'rb').read() + exe_sha256 = hashlib.sha256(exe_data).hexdigest() # Compare the hash - if zip_sha256 != expected_zip_sha256: + if exe_sha256 != expected_exe_sha256: print("ERROR! The sha256 doesn't match:") - print("expected: {}".format(expected_zip_sha256)) - print(" actual: {}".format(zip_sha256)) + print("expected: {}".format(expected_exe_sha256)) + print(" actual: {}".format(exe_sha256)) sys.exit(-1) - # Extract the zip - z = zipfile.ZipFile(io.BytesIO(zip_data)) - z.extractall(working_path) - - # Delete un-used files - os.remove(os.path.join(os.path.join(working_path, 'Tor'), 'tor-gencert.exe')) + # Extract the bits we need from the exe + cmd = ['7z', 'e', exe_path, 'Browser\TorBrowser\Tor', '-o%s' % os.path.join(working_path, 'Tor')] + cmd2 = ['7z', 'e', exe_path, 'Browser\TorBrowser\Data\Tor\geoip*', '-o%s' % os.path.join(working_path, 'Data')] + subprocess.Popen(cmd).wait() + subprocess.Popen(cmd2).wait() # Copy into dist if os.path.exists(dist_path): shutil.rmtree(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, '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__': main() diff --git a/install/onionshare.nsi b/install/onionshare.nsi index 10f459fd..865f5613 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -209,6 +209,7 @@ Section "install" File "${BINPATH}\tor\Tor\libevent-2-0-5.dll" File "${BINPATH}\tor\Tor\libgcc_s_sjlj-1.dll" File "${BINPATH}\tor\Tor\libssp-0.dll" + File "${BINPATH}\tor\Tor\obfs4proxy.exe" File "${BINPATH}\tor\Tor\ssleay32.dll" File "${BINPATH}\tor\Tor\tor.exe" File "${BINPATH}\tor\Tor\zlib1.dll" diff --git a/onionshare/common.py b/onionshare/common.py index 298dd42b..25b901ee 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -97,10 +97,10 @@ def get_tor_paths(): obfs4proxy_file_path = '/usr/bin/obfs4proxy' elif p == 'Windows': 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_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') - obfs4proxy_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'obfs4proxy') elif p == 'Darwin': base_path = os.path.dirname(os.path.dirname(os.path.dirname(get_resource_path('')))) tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor') diff --git a/test/test_onionshare_common.py b/test/test_onionshare_common.py index 3775951b..f574ad7f 100644 --- a/test/test_onionshare_common.py +++ b/test/test_onionshare_common.py @@ -230,15 +230,15 @@ class TestGetTorPaths: os.path.dirname( common.get_resource_path(''))), 'tor') 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( 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') - obfs4proxy_file_path = os.path.join( - base_path, 'Data', 'Tor', 'obfs4proxy') assert (common.get_tor_paths() == (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) From 8375520e100d8b1862504901de22b61db6ecf416 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Jan 2018 13:17:55 +1100 Subject: [PATCH 18/19] Add missing items to the onionshare.nsi --- install/onionshare.nsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/install/onionshare.nsi b/install/onionshare.nsi index 57c2592c..84d54732 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -191,6 +191,7 @@ Section "install" File "${BINPATH}\share\license.txt" File "${BINPATH}\share\torrc_template" File "${BINPATH}\share\torrc_template-windows" + File "${BINPATH}\share\torrc_template-obfs4" File "${BINPATH}\share\version.txt" File "${BINPATH}\share\wordlist.txt" @@ -402,6 +403,7 @@ FunctionEnd Delete "$INSTDIR\share\locale\tr.json" Delete "$INSTDIR\share\torrc_template" Delete "$INSTDIR\share\torrc_template-windows" + Delete "$INSTDIR\share\torrc_template-obfs4" Delete "$INSTDIR\share\version.txt" Delete "$INSTDIR\share\wordlist.txt" Delete "$INSTDIR\sip.pyd" @@ -413,6 +415,7 @@ FunctionEnd Delete "$INSTDIR\tor\Tor\libevent_extra-2-0-5.dll" Delete "$INSTDIR\tor\Tor\libgcc_s_sjlj-1.dll" Delete "$INSTDIR\tor\Tor\libssp-0.dll" + Delete "$INSTDIR\tor\Tor\obfs4proxy.exe" Delete "$INSTDIR\tor\Tor\ssleay32.dll" Delete "$INSTDIR\tor\Tor\tor.exe" Delete "$INSTDIR\tor\Tor\zlib1.dll" From fc2dcafafc7d6d513c24e8aeb90b42e3d6712817 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Jan 2018 16:30:12 +1100 Subject: [PATCH 19/19] Clarify that obfs4 bridges can't be used as custom bridges, and that obfs4proxy is needed for obfs4 bridges --- onionshare_gui/settings_dialog.py | 15 ++++++++------- share/locale/en.json | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 1138b839..1540876e 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -146,14 +146,15 @@ class SettingsDialog(QtWidgets.QDialog): 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 - 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) + # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports - if system != 'Windows' and system != 'Darwin': - (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.setEnabled(False) + (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)) diff --git a/share/locale/en.json b/share/locale/en.json index b8ac38e5..cdd89c1e 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -94,7 +94,8 @@ "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_custom_radio_option": "Use custom bridges", + "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 https://bridges.torproject.org", "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",