Built-in bridge refactoring.

Store the built-in bridges in OnionShare settings as a dict, and try writing those into the torrc if present.

If they aren't present in OnionShare settings, use the hardcoded templates instead.

Fetch the latest built-in bridges over Tor, once connected to Tor.

If we can't fetch the bridges over Tor, fall back to Meek (domain-fronting) and try again.

Then write those as the dict to the OnionShare settings, to take precedence next time.
This commit is contained in:
Miguel Jacq 2021-11-24 17:55:47 +11:00
parent f8dd9547cd
commit 06a3599fe1
No known key found for this signature in database
GPG Key ID: EEA4341C6D97A0B6
10 changed files with 169 additions and 70 deletions

View File

@ -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
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",
"Trying to automatically obtain built-in bridges via Meek",
"Wrote in the built-in bridges from OnionShare settings",
)
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")
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:
# 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 = 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 for this bridge type via Meek",
"Wrote in the built-in bridges from torrc templates",
)
raise TorErrorGettingBridges()
else:
self.common.log(
"Onion",
"connect",
"Error getting built-in bridges via Meek",
)
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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
Bridge snowflake 0.0.3.0:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72

View File

@ -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,

View File

@ -34,6 +34,7 @@ class TestSettings:
"bridges_builtin_pt": "obfs4",
"bridges_moat": "",
"bridges_custom": "",
"bridges_builtin": {},
"persistent_tabs": [],
"theme": 0,
}

View File

@ -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

View File

@ -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",

View File

@ -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)

View File

@ -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(