diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index 65b9e2bb..c0a4e6e1 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -26,11 +26,12 @@ from stem.connection import MissingPassword, UnreadableCookieFile, Authenticatio import base64 import nacl.public import os -import tempfile -import subprocess -import time -import shlex import psutil +import requests +import shlex +import subprocess +import tempfile +import time import traceback from distutils.version import LooseVersion as Version @@ -127,13 +128,6 @@ class PortNotAvailable(Exception): """ -class TorErrorGettingBridges(Exception): - """ - This exception is raised if onionshare tried to fetch bridges from the Tor - CensorshipCircumvention API, but failed to retrieve valid bridges for some reason. - """ - - class Onion(object): """ Onion is an abstraction layer for connecting to the Tor control port and @@ -324,71 +318,63 @@ class Onion(object): ) with open(self.tor_torrc, "w") as f: + self.common.log("Onion", "connect", "Writing torrc template file") f.write(torrc_template) # Bridge support if self.settings.get("bridges_enabled"): f.write("\nUseBridges 1\n") if self.settings.get("bridges_type") == "built-in": - # Use the CensorshipCircumvention API to fetch the latest built-in bridges - self.common.log( - "Onion", - "connect", - "Trying to automatically obtain built-in bridges via Meek", - ) - meek = Meek(self.common) - meek.start() - self.censorship_circumvention = CensorshipCircumvention( - self.common, meek - ) - builtin_bridges = ( - self.censorship_circumvention.request_builtin_bridges() - ) - meek.cleanup() - if builtin_bridges: - self.common.log( - "Onion", - "connect", - f"Obtained bridges: {builtin_bridges}", - ) - if ( - self.settings.get("bridges_builtin_pt") == "obfs4" - and "obfs4" in builtin_bridges - ): - for line in builtin_bridges["obfs4"]: - f.write(f"Bridge {line}\n") - elif ( - self.settings.get("bridges_builtin_pt") == "meek-azure" - and "meek" in builtin_bridges - ): - for line in builtin_bridges["meek"]: - # Meek bridge needs to be defined as "meek_lite", not "meek" - line = line.replace("meek", "meek_lite") - f.write(f"Bridge {line}\n") - elif ( - self.settings.get("bridges_builtin_pt") == "snowflake" - and "snowflake" in builtin_bridges - ): - for line in builtin_bridges["snowflake"]: - f.write(f"Bridge {line}\n") - else: - # Either this is a weird bridge type saved to settings (how?) - # or there were no bridges for this bridge type returned from - # the API. + use_torrc_bridge_templates = False + builtin_bridge_type = self.settings.get("bridges_builtin_pt") + # Use built-inbridges stored in settings, if they are there already. + # They are probably newer than that of our hardcoded copies. + if self.settings.get("bridges_builtin"): + try: + for line in self.settings.get("bridges_builtin")[ + builtin_bridge_type + ]: + if line.strip() != "": + f.write(f"Bridge {line}\n") self.common.log( "Onion", "connect", - "Error getting built-in bridges for this bridge type via Meek", + "Wrote in the built-in bridges from OnionShare settings", ) - raise TorErrorGettingBridges() + except KeyError: + # Somehow we had built-in bridges in our settings, but + # not for this bridge type. Fall back to using the hard- + # coded templates. + use_torrc_bridge_templates = True else: + use_torrc_bridge_templates = True + if use_torrc_bridge_templates: + if builtin_bridge_type == "obfs4": + with open( + self.common.get_resource_path( + "torrc_template-obfs4" + ) + ) as o: + f.write(o.read()) + elif builtin_bridge_type == "meek-azure": + with open( + self.common.get_resource_path( + "torrc_template-meek_lite_azure" + ) + ) as o: + f.write(o.read()) + elif builtin_bridge_type == "snowflake": + with open( + self.common.get_resource_path( + "torrc_template-snowflake" + ) + ) as o: + f.write(o.read()) self.common.log( "Onion", "connect", - "Error getting built-in bridges via Meek", + "Wrote in the built-in bridges from torrc templates", ) - raise TorErrorGettingBridges() - elif self.settings.get("bridges_type") == "moat": for line in self.settings.get("bridges_moat").split("\n"): if line.strip() != "": @@ -671,6 +657,14 @@ class Onion(object): # https://trac.torproject.org/projects/tor/ticket/28619 self.supports_v3_onions = self.tor_version >= Version("0.3.5.7") + # Now that we are connected to Tor, if we are using built-in bridges, + # update them with the latest copy available from the Tor API + if ( + self.settings.get("bridges_enabled") + and self.settings.get("bridges_type") == "built-in" + ): + self.update_builtin_bridges() + def is_authenticated(self): """ Returns True if the Tor connection is still working, or False otherwise. @@ -914,3 +908,96 @@ class Onion(object): return ("127.0.0.1", 9150) else: return (self.settings.get("socks_address"), self.settings.get("socks_port")) + + def update_builtin_bridges(self): + """ + Use the CensorshipCircumvention API to fetch the latest built-in bridges + and update them in settings. + """ + got_builtin_bridges = False + # Try obtaining bridges over Tor, if we're connected to it. + if self.is_authenticated: + self.common.log( + "Onion", + "update_builtin_bridges", + "Updating the built-in bridges. Trying over Tor first", + ) + (socks_address, socks_port) = self.get_tor_socks_port() + tor_proxies = { + "http": f"socks5h://{socks_address}:{socks_port}", + "https": f"socks5h://{socks_address}:{socks_port}", + } + # Request a bridge + r = requests.post( + "https://bridges.torproject.org/moat/circumvention/builtin", + headers={"Content-Type": "application/vnd.api+json"}, + proxies=tor_proxies, + ) + if r.status_code != 200: + self.common.log( + "Onion", + "update_builtin_bridges", + f"Trying over Tor failed: status_code={r.status_code}", + ) + + try: + builtin_bridges = r.json() + if "errors" in builtin_bridges: + self.common.log( + "Onion", + "update_builtin_bridges", + f"Trying over Tor failed: errors={builtin_bridges['errors']}", + ) + else: + got_builtin_bridges = builtin_bridges + except Exception as e: + self.common.log( + "Onion", + "update_builtin_bridges", + f"Hit exception when trying over Tor: {e}", + ) + + if not got_builtin_bridges: + # Fall back to using Meek, without Tor + self.common.log( + "Onion", + "update_builtin_bridges", + "Updating the built-in bridges. Trying via Meek (no Tor)", + ) + meek = Meek(self.common) + meek.start() + self.censorship_circumvention = CensorshipCircumvention(self.common, meek) + got_builtin_bridges = ( + self.censorship_circumvention.request_builtin_bridges() + ) + meek.cleanup() + + # If we got to this point, we have bridges + if got_builtin_bridges: + self.common.log( + "Onion", + "update_builtin_bridges", + f"Obtained bridges: {got_builtin_bridges}", + ) + if got_builtin_bridges["meek"]: + # Meek bridge needs to be defined as "meek_lite", not "meek", + # for it to work with obfs4proxy. + # We also refer to this bridge type as 'meek-azure' in our settings. + # So first, rename the key in the dict + got_builtin_bridges["meek-azure"] = got_builtin_bridges.pop("meek") + new_meek_bridges = [] + # Now replace the values. They also need the url/front params appended + for item in got_builtin_bridges["meek-azure"]: + newline = item.replace("meek", "meek_lite") + new_meek_bridges.append( + f"{newline} url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com" + ) + got_builtin_bridges["meek-azure"] = new_meek_bridges + # Save the new settings + self.settings.set("bridges_builtin", got_builtin_bridges) + self.settings.save() + else: + self.common.log( + "Onion", "update_builtin_bridges", "Error getting built-in bridges" + ) + return False diff --git a/cli/onionshare_cli/resources/torrc_template-meek_lite_azure b/cli/onionshare_cli/resources/torrc_template-meek_lite_azure new file mode 100644 index 00000000..cbc5a9ee --- /dev/null +++ b/cli/onionshare_cli/resources/torrc_template-meek_lite_azure @@ -0,0 +1,2 @@ +# Enable built-in meek-azure bridge +Bridge meek_lite 0.0.2.0:3 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com diff --git a/cli/onionshare_cli/resources/torrc_template-obfs4 b/cli/onionshare_cli/resources/torrc_template-obfs4 new file mode 100644 index 00000000..4ea90134 --- /dev/null +++ b/cli/onionshare_cli/resources/torrc_template-obfs4 @@ -0,0 +1,16 @@ +# Enable built-in obfs4-bridge +Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1 +Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0 +Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0 +Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0 +Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0 +Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0 +Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0 +Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0 +Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1 +Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0 +Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0 +Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0 +Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0 +Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1 +Bridge obfs4 [2a0c:4d80:42:702::1]:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0 diff --git a/cli/onionshare_cli/resources/torrc_template-snowflake b/cli/onionshare_cli/resources/torrc_template-snowflake new file mode 100644 index 00000000..20efe28c --- /dev/null +++ b/cli/onionshare_cli/resources/torrc_template-snowflake @@ -0,0 +1 @@ +Bridge snowflake 0.0.3.0:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72 diff --git a/cli/onionshare_cli/settings.py b/cli/onionshare_cli/settings.py index c7d74a70..8a4a9939 100644 --- a/cli/onionshare_cli/settings.py +++ b/cli/onionshare_cli/settings.py @@ -110,6 +110,7 @@ class Settings(object): "bridges_builtin_pt": "obfs4", # "obfs4", "meek-azure", or "snowflake" "bridges_moat": "", "bridges_custom": "", + "bridges_builtin": {}, "persistent_tabs": [], "locale": None, # this gets defined in fill_in_defaults() "theme": 0, diff --git a/cli/tests/test_cli_settings.py b/cli/tests/test_cli_settings.py index 9513b013..a149b283 100644 --- a/cli/tests/test_cli_settings.py +++ b/cli/tests/test_cli_settings.py @@ -34,6 +34,7 @@ class TestSettings: "bridges_builtin_pt": "obfs4", "bridges_moat": "", "bridges_custom": "", + "bridges_builtin": {}, "persistent_tabs": [], "theme": 0, } diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index 486a3578..2a0bae4d 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -38,7 +38,6 @@ from onionshare_cli.onion import ( TorTooOldEphemeral, TorTooOldStealth, PortNotAvailable, - TorErrorGettingBridges, ) @@ -508,7 +507,4 @@ class GuiCommon: return strings._("error_stealth_not_supported") elif type(e) is PortNotAvailable: return strings._("error_port_not_available") - elif type(e) is TorErrorGettingBridges: - return strings._("error_getting_bridges") - return None diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 73b35b2e..5a44cbb2 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -223,7 +223,6 @@ "error_port_not_available": "OnionShare port not available", "history_receive_read_message_button": "Read Message", "error_tor_protocol_error": "There was an error with Tor: {}", - "error_getting_bridges": "Could not obtain bridges from the Tor API", "moat_contact_label": "Contacting BridgeDB...", "moat_captcha_label": "Solve the CAPTCHA to request a bridge.", "moat_captcha_placeholder": "Enter the characters from the image", diff --git a/desktop/src/onionshare/threads.py b/desktop/src/onionshare/threads.py index c5f24017..b02c6f21 100644 --- a/desktop/src/onionshare/threads.py +++ b/desktop/src/onionshare/threads.py @@ -37,7 +37,6 @@ from onionshare_cli.onion import ( TorTooOldEphemeral, TorTooOldStealth, PortNotAvailable, - TorErrorGettingBridges, ) from . import strings @@ -105,7 +104,6 @@ class OnionThread(QtCore.QThread): TorTooOldEphemeral, TorTooOldStealth, PortNotAvailable, - TorErrorGettingBridges, ) as e: message = self.mode.common.gui.get_translated_tor_error(e) self.error.emit(message) diff --git a/desktop/src/onionshare/tor_connection.py b/desktop/src/onionshare/tor_connection.py index 77218c1a..2cc599c4 100644 --- a/desktop/src/onionshare/tor_connection.py +++ b/desktop/src/onionshare/tor_connection.py @@ -36,7 +36,6 @@ from onionshare_cli.onion import ( TorTooOldEphemeral, TorTooOldStealth, PortNotAvailable, - TorErrorGettingBridges, ) from . import strings @@ -311,7 +310,6 @@ class TorConnectionThread(QtCore.QThread): TorTooOldEphemeral, TorTooOldStealth, PortNotAvailable, - TorErrorGettingBridges, ) as e: message = self.common.gui.get_translated_tor_error(e) self.common.log(