Early support for ClientAuth with v3 onions

This commit is contained in:
Miguel Jacq 2021-05-04 10:02:02 +10:00
parent 34554414e9
commit b48848eb04
No known key found for this signature in database
GPG key ID: EEA4341C6D97A0B6
11 changed files with 455 additions and 156 deletions

View file

@ -132,7 +132,14 @@ def main(cwd=None):
action="store_true", action="store_true",
dest="client_auth", dest="client_auth",
default=False, default=False,
help="Use client authorization (requires --legacy)", help="Use V2 client authorization (requires --legacy)",
)
parser.add_argument(
"--client-auth-v3",
action="store_true",
dest="client_auth_v3",
default=False,
help="Use V3 client authorization",
) )
# Share args # Share args
parser.add_argument( parser.add_argument(
@ -196,6 +203,7 @@ def main(cwd=None):
autostop_timer = int(args.autostop_timer) autostop_timer = int(args.autostop_timer)
legacy = bool(args.legacy) legacy = bool(args.legacy)
client_auth = bool(args.client_auth) client_auth = bool(args.client_auth)
client_auth_v3 = bool(args.client_auth_v3)
autostop_sharing = not bool(args.no_autostop_sharing) autostop_sharing = not bool(args.no_autostop_sharing)
data_dir = args.data_dir data_dir = args.data_dir
webhook_url = args.webhook_url webhook_url = args.webhook_url
@ -217,7 +225,14 @@ def main(cwd=None):
# client_auth can only be set if legacy is also set # client_auth can only be set if legacy is also set
if client_auth and not legacy: if client_auth and not legacy:
print( print(
"Client authentication (--client-auth) is only supported with with legacy onion services (--legacy)" "Client authentication (--client-auth) is only supported with legacy onion services (--legacy)"
)
sys.exit()
# client_auth_v3 and legacy cannot be both set
if client_auth_v3 and legacy:
print(
"V3 Client authentication (--client-auth-v3) cannot be used with legacy onion services (--legacy)"
) )
sys.exit() sys.exit()
@ -243,6 +258,7 @@ def main(cwd=None):
mode_settings.set("general", "autostop_timer", autostop_timer) mode_settings.set("general", "autostop_timer", autostop_timer)
mode_settings.set("general", "legacy", legacy) mode_settings.set("general", "legacy", legacy)
mode_settings.set("general", "client_auth", client_auth) mode_settings.set("general", "client_auth", client_auth)
mode_settings.set("general", "client_auth_v3", client_auth_v3)
if mode == "share": if mode == "share":
mode_settings.set("share", "autostop_sharing", autostop_sharing) mode_settings.set("share", "autostop_sharing", autostop_sharing)
if mode == "receive": if mode == "receive":
@ -364,9 +380,14 @@ def main(cwd=None):
print("") print("")
if mode_settings.get("general", "client_auth"): if mode_settings.get("general", "client_auth"):
print( print(
f"Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}" f"Give this address and HidServAuth line to your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
) )
print(app.auth_string) print(app.auth_string)
elif mode_settings.get("general", "client_auth_v3"):
print(
f"Give this address and ClientAuth line to your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
)
print(app.auth_string_v3)
else: else:
print( print(
f"Give this address to your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}" f"Give this address to your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
@ -377,6 +398,11 @@ def main(cwd=None):
f"Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}" f"Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
) )
print(app.auth_string) print(app.auth_string)
elif mode_settings.get("general", "client_auth_v3"):
print(
f"Give this address and ClientAuth line to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
)
print(app.auth_string_v3)
else: else:
print( print(
f"Give this address to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}" f"Give this address to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
@ -461,6 +487,10 @@ def main(cwd=None):
print("Give this address and HidServAuth to the sender:") print("Give this address and HidServAuth to the sender:")
print(url) print(url)
print(app.auth_string) print(app.auth_string)
elif mode_settings.get("general", "client_auth_v3"):
print("Give this address and ClientAuth to the sender:")
print(url)
print(app.auth_string_v3)
else: else:
print("Give this address to the sender:") print("Give this address to the sender:")
print(url) print(url)
@ -469,6 +499,10 @@ def main(cwd=None):
print("Give this address and HidServAuth line to the recipient:") print("Give this address and HidServAuth line to the recipient:")
print(url) print(url)
print(app.auth_string) print(app.auth_string)
elif mode_settings.get("general", "client_auth_v3"):
print("Give this address and ClientAuth line to the recipient:")
print(url)
print(app.auth_string_v3)
else: else:
print("Give this address to the recipient:") print("Give this address to the recipient:")
print(url) print(url)

View file

@ -39,6 +39,8 @@ class ModeSettings:
"private_key": None, "private_key": None,
"hidservauth_string": None, "hidservauth_string": None,
"password": None, "password": None,
"client_auth_v3_priv_key": None,
"client_auth_v3_pub_key": None,
}, },
"persistent": {"mode": None, "enabled": False}, "persistent": {"mode": None, "enabled": False},
"general": { "general": {
@ -48,6 +50,7 @@ class ModeSettings:
"autostop_timer": False, "autostop_timer": False,
"legacy": False, "legacy": False,
"client_auth": False, "client_auth": False,
"client_auth_v3": False,
"service_id": None, "service_id": None,
}, },
"share": {"autostop_sharing": True, "filenames": []}, "share": {"autostop_sharing": True, "filenames": []},

View file

@ -23,6 +23,7 @@ from stem import ProtocolError, SocketClosed
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
import base64 import base64
import nacl.public
import os import os
import tempfile import tempfile
import subprocess import subprocess
@ -166,10 +167,25 @@ class Onion(object):
# Assigned later if we are using stealth mode # Assigned later if we are using stealth mode
self.auth_string = None self.auth_string = None
self.auth_string_v3 = None
# Keep track of onions where it's important to gracefully close to prevent truncated downloads # Keep track of onions where it's important to gracefully close to prevent truncated downloads
self.graceful_close_onions = [] self.graceful_close_onions = []
def key_str(self, key):
"""
Returns a base32 decoded string of a key.
"""
# bytes to base 32
key_bytes = bytes(key)
key_b32 = base64.b32encode(key_bytes)
# strip trailing ====
assert key_b32[-4:] == b'===='
key_b32 = key_b32[:-4]
# change from b'ASDF' to ASDF
s = key_b32.decode('utf-8')
return s
def connect( def connect(
self, self,
custom_settings=None, custom_settings=None,
@ -570,7 +586,7 @@ class Onion(object):
callable(list_ephemeral_hidden_services) and self.tor_version >= "0.2.7.1" callable(list_ephemeral_hidden_services) and self.tor_version >= "0.2.7.1"
) )
# Do the versions of stem and tor that I'm using support stealth onion services? # Do the versions of stem and tor that I'm using support v2 stealth onion services?
try: try:
res = self.c.create_ephemeral_hidden_service( res = self.c.create_ephemeral_hidden_service(
{1: 1}, {1: 1},
@ -586,11 +602,33 @@ class Onion(object):
# ephemeral stealth onion services are not supported # ephemeral stealth onion services are not supported
self.supports_stealth = False self.supports_stealth = False
# Do the versions of stem and tor that I'm using support v3 stealth onion services?
try:
res = self.c.create_ephemeral_hidden_service(
{1: 1},
basic_auth=None,
await_publication=False,
key_type="NEW",
key_content="ED25519-V3",
client_auth_v3="E2GOT5LTUTP3OAMRCRXO4GSH6VKJEUOXZQUC336SRKAHTTT5OVSA",
)
tmp_service_id = res.service_id
self.c.remove_ephemeral_hidden_service(tmp_service_id)
self.supports_stealth_v3 = True
except:
# ephemeral v3 stealth onion services are not supported
self.supports_stealth_v3 = False
# Does this version of Tor support next-gen ('v3') onions? # Does this version of Tor support next-gen ('v3') onions?
# Note, this is the version of Tor where this bug was fixed: # Note, this is the version of Tor where this bug was fixed:
# https://trac.torproject.org/projects/tor/ticket/28619 # https://trac.torproject.org/projects/tor/ticket/28619
self.supports_v3_onions = self.tor_version >= Version("0.3.5.7") self.supports_v3_onions = self.tor_version >= Version("0.3.5.7")
# Does this version of Tor support legacy ('v2') onions?
# v2 onions have been phased out as of Tor 0.4.6.1.
self.supports_v2_onions = self.tor_version < Version("0.4.6.1")
def is_authenticated(self): def is_authenticated(self):
""" """
Returns True if the Tor connection is still working, or False otherwise. Returns True if the Tor connection is still working, or False otherwise.
@ -618,6 +656,12 @@ class Onion(object):
) )
raise TorTooOldStealth() raise TorTooOldStealth()
if mode_settings.get("general", "client_auth_v3") and not self.supports_stealth_v3:
print(
"Your version of Tor is too old, stealth v3 onion services are not supported"
)
raise TorTooOldStealth()
auth_cookie = None auth_cookie = None
if mode_settings.get("general", "client_auth"): if mode_settings.get("general", "client_auth"):
if mode_settings.get("onion", "hidservauth_string"): if mode_settings.get("onion", "hidservauth_string"):
@ -633,10 +677,11 @@ class Onion(object):
else: else:
# Not using client auth at all # Not using client auth at all
basic_auth = None basic_auth = None
client_auth_v3_pub_key = None
if mode_settings.get("onion", "private_key"): if mode_settings.get("onion", "private_key"):
key_content = mode_settings.get("onion", "private_key") key_content = mode_settings.get("onion", "private_key")
if self.is_v2_key(key_content): if self.is_v2_key(key_content) and self.supports_v2_onions:
key_type = "RSA1024" key_type = "RSA1024"
else: else:
# Assume it was a v3 key. Stem will throw an error if it's something illegible # Assume it was a v3 key. Stem will throw an error if it's something illegible
@ -644,19 +689,35 @@ class Onion(object):
else: else:
key_type = "NEW" key_type = "NEW"
# Work out if we can support v3 onion services, which are preferred # Work out if we can support v3 onion services, which are preferred
if self.supports_v3_onions and not mode_settings.get("general", "legacy"): if self.supports_v3_onions and not mode_settings.get("general", "legacy") and not self.supports_v2_onions:
key_content = "ED25519-V3" key_content = "ED25519-V3"
else: else:
# fall back to v2 onion services # fall back to v2 onion services
key_content = "RSA1024" key_content = "RSA1024"
# v3 onions don't yet support basic auth. Our ticket:
# https://github.com/micahflee/onionshare/issues/697
if ( if (
key_type == "NEW" (key_type == "ED25519-V3"
and key_content == "ED25519-V3" or key_content == "ED25519-V3")
and not mode_settings.get("general", "legacy") and mode_settings.get("general", "client_auth_v3")
): ):
if key_type == "NEW" or not mode_settings.get("onion", "client_auth_v3_priv_key"):
# Generate a new key pair for Client Auth on new onions, or if
# it's a persistent onion but for some reason we don't them
client_auth_v3_priv_key_raw = nacl.public.PrivateKey.generate()
client_auth_v3_priv_key = self.key_str(client_auth_v3_priv_key_raw)
client_auth_v3_pub_key = self.key_str(client_auth_v3_priv_key_raw.public_key)
else:
# These should have been saved in settings from the previous run of a persistent onion
client_auth_v3_priv_key = mode_settings.get("onion", "client_auth_v3_priv_key")
client_auth_v3_pub_key = mode_settings.get("onion", "client_auth_v3_pub_key")
self.common.log(
"Onion", "start_onion-service", f"ClientAuthV3 private key (for Tor Browser: {client_auth_v3_priv_key}"
)
self.common.log(
"Onion", "start_onion-service", f"ClientAuthV3 public key (for Onion service: {client_auth_v3_pub_key}"
)
# basic_auth is only for v2 onions
basic_auth = None basic_auth = None
debug_message = f"key_type={key_type}" debug_message = f"key_type={key_type}"
@ -670,6 +731,7 @@ class Onion(object):
basic_auth=basic_auth, basic_auth=basic_auth,
key_type=key_type, key_type=key_type,
key_content=key_content, key_content=key_content,
client_auth_v3=client_auth_v3_pub_key,
) )
except ProtocolError as e: except ProtocolError as e:
@ -695,6 +757,19 @@ class Onion(object):
self.auth_string = f"HidServAuth {onion_host} {auth_cookie}" self.auth_string = f"HidServAuth {onion_host} {auth_cookie}"
mode_settings.set("onion", "hidservauth_string", self.auth_string) mode_settings.set("onion", "hidservauth_string", self.auth_string)
# If using V3 onions and Client Auth, save both the private and public key
# because we need to send the public key to ADD_ONION, and the private key
# to the other user for their Tor Browser.
if mode_settings.get("general", "client_auth_v3"):
mode_settings.set("onion", "client_auth_v3_priv_key", client_auth_v3_priv_key)
mode_settings.set("onion", "client_auth_v3_pub_key", client_auth_v3_pub_key)
# If we were pasting the client auth directly into the filesystem behind a Tor client,
# it would need to be in the format below. However, let's just set the private key
# by itself, as this can be pasted directly into Tor Browser, which is likely to
# be the most common use case.
# self.auth_string_v3 = f"{onion_host}:x25519:{client_auth_v3_priv_key}"
self.auth_string_v3 = client_auth_v3_priv_key
return onion_host return onion_host
def stop_onion_service(self, mode_settings): def stop_onion_service(self, mode_settings):

View file

@ -83,6 +83,9 @@ class OnionShare(object):
if mode_settings.get("general", "client_auth"): if mode_settings.get("general", "client_auth"):
self.auth_string = self.onion.auth_string self.auth_string = self.onion.auth_string
if mode_settings.get("general", "client_auth_v3"):
self.auth_string_v3 = self.onion.auth_string_v3
def stop_onion_service(self, mode_settings): def stop_onion_service(self, mode_settings):
""" """
Stop the onion service Stop the onion service

393
cli/poetry.lock generated
View file

@ -1,32 +1,33 @@
[[package]] [[package]]
name = "atomicwrites"
version = "1.4.0"
description = "Atomic file writes."
category = "dev" category = "dev"
description = "Atomic file writes."
marker = "sys_platform == \"win32\""
name = "atomicwrites"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.4.0"
[[package]] [[package]]
name = "attrs"
version = "20.3.0"
description = "Classes Without Boilerplate"
category = "dev" category = "dev"
description = "Classes Without Boilerplate"
name = "attrs"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "20.3.0"
[package.extras] [package.extras]
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"]
docs = ["furo", "sphinx", "zope.interface"] docs = ["furo", "sphinx", "zope.interface"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
[[package]] [[package]]
name = "bidict"
version = "0.21.2"
description = "The bidirectional mapping library for Python."
category = "main" category = "main"
description = "The bidirectional mapping library for Python."
name = "bidict"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
version = "0.21.2"
[package.extras] [package.extras]
coverage = ["coverage (<6)", "pytest-cov (<3)"] coverage = ["coverage (<6)", "pytest-cov (<3)"]
@ -36,56 +37,68 @@ precommit = ["pre-commit (<3)"]
test = ["hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] test = ["hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)"]
[[package]] [[package]]
name = "certifi"
version = "2020.12.5"
description = "Python package for providing Mozilla's CA Bundle."
category = "main" category = "main"
description = "Python package for providing Mozilla's CA Bundle."
name = "certifi"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2020.12.5"
[[package]] [[package]]
name = "chardet" category = "main"
version = "4.0.0" description = "Foreign Function Interface for Python calling C code."
name = "cffi"
optional = false
python-versions = "*"
version = "1.14.5"
[package.dependencies]
pycparser = "*"
[[package]]
category = "main"
description = "Universal encoding detector for Python 2 and 3" description = "Universal encoding detector for Python 2 and 3"
category = "main" name = "chardet"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "4.0.0"
[[package]] [[package]]
name = "click" category = "main"
version = "7.1.2"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
category = "main" name = "click"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "7.1.2"
[[package]] [[package]]
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
category = "dev" category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.4"
[[package]] [[package]]
name = "dnspython"
version = "1.16.0"
description = "DNS toolkit"
category = "main" category = "main"
description = "DNS toolkit"
name = "dnspython"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.16.0"
[package.extras] [package.extras]
DNSSEC = ["pycryptodome", "ecdsa (>=0.13)"] DNSSEC = ["pycryptodome", "ecdsa (>=0.13)"]
IDNA = ["idna (>=2.1)"] IDNA = ["idna (>=2.1)"]
[[package]] [[package]]
name = "eventlet"
version = "0.30.2"
description = "Highly concurrent networking library"
category = "main" category = "main"
description = "Highly concurrent networking library"
name = "eventlet"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.30.2"
[package.dependencies] [package.dependencies]
dnspython = ">=1.15.0,<2.0.0" dnspython = ">=1.15.0,<2.0.0"
@ -93,18 +106,18 @@ greenlet = ">=0.3"
six = ">=1.10.0" six = ">=1.10.0"
[[package]] [[package]]
name = "flask"
version = "1.1.2"
description = "A simple framework for building complex web applications."
category = "main" category = "main"
description = "A simple framework for building complex web applications."
name = "flask"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.1.2"
[package.dependencies] [package.dependencies]
click = ">=5.1"
itsdangerous = ">=0.24"
Jinja2 = ">=2.10.1" Jinja2 = ">=2.10.1"
Werkzeug = ">=0.15" Werkzeug = ">=0.15"
click = ">=5.1"
itsdangerous = ">=0.24"
[package.extras] [package.extras]
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
@ -112,86 +125,90 @@ docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-
dotenv = ["python-dotenv"] dotenv = ["python-dotenv"]
[[package]] [[package]]
name = "flask-httpauth"
version = "4.2.0"
description = "Basic and Digest HTTP authentication for Flask routes"
category = "main" category = "main"
description = "Basic and Digest HTTP authentication for Flask routes"
name = "flask-httpauth"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "4.2.0"
[package.dependencies] [package.dependencies]
Flask = "*" Flask = "*"
[[package]] [[package]]
name = "flask-socketio"
version = "5.0.1"
description = "Socket.IO integration for Flask applications"
category = "main" category = "main"
description = "Socket.IO integration for Flask applications"
name = "flask-socketio"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "5.0.1"
[package.dependencies] [package.dependencies]
Flask = ">=0.9" Flask = ">=0.9"
python-socketio = ">=5.0.2" python-socketio = ">=5.0.2"
[[package]] [[package]]
name = "greenlet"
version = "1.0.0"
description = "Lightweight in-process concurrent programming"
category = "main" category = "main"
description = "Lightweight in-process concurrent programming"
name = "greenlet"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "1.0.0"
[package.extras] [package.extras]
docs = ["sphinx"] docs = ["sphinx"]
[[package]] [[package]]
name = "idna"
version = "2.10"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main" category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.10"
[[package]] [[package]]
name = "importlib-metadata"
version = "3.10.0"
description = "Read metadata from Python packages"
category = "dev" category = "dev"
description = "Read metadata from Python packages"
marker = "python_version < \"3.8\""
name = "importlib-metadata"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
version = "3.10.0"
[package.dependencies] [package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5" zipp = ">=0.5"
[package.dependencies.typing-extensions]
python = "<3.8"
version = ">=3.6.4"
[package.extras] [package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
[[package]] [[package]]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev" category = "dev"
description = "iniconfig: brain-dead simple config-ini parsing"
name = "iniconfig"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.1.1"
[[package]] [[package]]
name = "itsdangerous"
version = "1.1.0"
description = "Various helpers to pass data to untrusted environments and back."
category = "main" category = "main"
description = "Various helpers to pass data to untrusted environments and back."
name = "itsdangerous"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.1.0"
[[package]] [[package]]
name = "jinja2"
version = "2.11.3"
description = "A very fast and expressive template engine."
category = "main" category = "main"
description = "A very fast and expressive template engine."
name = "jinja2"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.11.3"
[package.dependencies] [package.dependencies]
MarkupSafe = ">=0.23" MarkupSafe = ">=0.23"
@ -200,122 +217,151 @@ MarkupSafe = ">=0.23"
i18n = ["Babel (>=0.8)"] i18n = ["Babel (>=0.8)"]
[[package]] [[package]]
name = "markupsafe"
version = "1.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main" category = "main"
description = "Safely add untrusted strings to HTML/XML markup."
name = "markupsafe"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "1.1.1"
[[package]] [[package]]
name = "packaging"
version = "20.9"
description = "Core utilities for Python packages"
category = "dev" category = "dev"
description = "Core utilities for Python packages"
name = "packaging"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "20.9"
[package.dependencies] [package.dependencies]
pyparsing = ">=2.0.2" pyparsing = ">=2.0.2"
[[package]] [[package]]
name = "pluggy"
version = "0.13.1"
description = "plugin and hook calling mechanisms for python"
category = "dev" category = "dev"
description = "plugin and hook calling mechanisms for python"
name = "pluggy"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.13.1"
[package.dependencies] [package.dependencies]
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.extras] [package.extras]
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
[[package]] [[package]]
name = "psutil"
version = "5.8.0"
description = "Cross-platform lib for process and system monitoring in Python."
category = "main" category = "main"
description = "Cross-platform lib for process and system monitoring in Python."
name = "psutil"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "5.8.0"
[package.extras] [package.extras]
test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"]
[[package]] [[package]]
name = "py"
version = "1.10.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev" category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.10.0"
[[package]] [[package]]
name = "pycryptodome"
version = "3.10.1"
description = "Cryptographic library for Python"
category = "main" category = "main"
description = "C parser in Python"
name = "pycparser"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.20"
[[package]]
category = "main"
description = "Cryptographic library for Python"
name = "pycryptodome"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "3.10.1"
[[package]] [[package]]
name = "pyparsing"
version = "2.4.7"
description = "Python parsing module"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "pysocks"
version = "1.7.1"
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
category = "main" category = "main"
description = "Python binding to the Networking and Cryptography (NaCl) library"
name = "pynacl"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.4.0"
[[package]]
name = "pytest"
version = "6.2.3"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies] [package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} cffi = ">=1.4.1"
six = "*"
[package.extras]
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)", "hypothesis (>=3.27.0)"]
[[package]]
category = "dev"
description = "Python parsing module"
name = "pyparsing"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.7"
[[package]]
category = "main"
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
name = "pysocks"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.7.1"
[[package]]
category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=3.6"
version = "6.2.3"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=19.2.0" attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""} colorama = "*"
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*" iniconfig = "*"
packaging = "*" packaging = "*"
pluggy = ">=0.12,<1.0.0a1" pluggy = ">=0.12,<1.0.0a1"
py = ">=1.8.2" py = ">=1.8.2"
toml = "*" toml = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.extras] [package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]] [[package]]
name = "python-engineio"
version = "4.0.1"
description = "Engine.IO server"
category = "main" category = "main"
description = "Engine.IO server"
name = "python-engineio"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "4.0.1"
[package.extras] [package.extras]
asyncio_client = ["aiohttp (>=3.4)"] asyncio_client = ["aiohttp (>=3.4)"]
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
[[package]] [[package]]
name = "python-socketio"
version = "5.1.0"
description = "Socket.IO server"
category = "main" category = "main"
description = "Socket.IO server"
name = "python-socketio"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "5.1.0"
[package.dependencies] [package.dependencies]
bidict = ">=0.21.0" bidict = ">=0.21.0"
@ -326,105 +372,109 @@ asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"]
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
[[package]] [[package]]
name = "requests"
version = "2.25.1"
description = "Python HTTP for Humans."
category = "main" category = "main"
description = "Python HTTP for Humans."
name = "requests"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.25.1"
[package.dependencies] [package.dependencies]
certifi = ">=2017.4.17" certifi = ">=2017.4.17"
chardet = ">=3.0.2,<5" chardet = ">=3.0.2,<5"
idna = ">=2.5,<3" idna = ">=2.5,<3"
PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7", optional = true, markers = "extra == \"socks\""}
urllib3 = ">=1.21.1,<1.27" urllib3 = ">=1.21.1,<1.27"
[package.dependencies.PySocks]
optional = true
version = ">=1.5.6,<1.5.7 || >1.5.7"
[package.extras] [package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
[[package]] [[package]]
name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "main" category = "main"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.15.0"
[[package]] [[package]]
name = "stem"
version = "1.8.0"
description = "Stem is a Python controller library that allows applications to interact with Tor (https://www.torproject.org/)."
category = "main" category = "main"
description = "Stem is a Python controller library that allows applications to interact with Tor (https://www.torproject.org/)."
name = "stem"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.8.0"
[[package]] [[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev" category = "dev"
description = "Python Library for Tom's Obvious, Minimal Language"
name = "toml"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "0.10.2"
[[package]] [[package]]
name = "typing-extensions"
version = "3.7.4.3"
description = "Backported and Experimental Type Hints for Python 3.5+"
category = "dev" category = "dev"
description = "Backported and Experimental Type Hints for Python 3.5+"
marker = "python_version < \"3.8\""
name = "typing-extensions"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "3.7.4.3"
[[package]] [[package]]
name = "unidecode"
version = "1.2.0"
description = "ASCII transliterations of Unicode text"
category = "main" category = "main"
description = "ASCII transliterations of Unicode text"
name = "unidecode"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.2.0"
[[package]] [[package]]
name = "urllib3"
version = "1.26.4"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main" category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "1.26.4"
[package.extras] [package.extras]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
brotli = ["brotlipy (>=0.6.0)"] brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
[[package]] [[package]]
name = "werkzeug"
version = "1.0.1"
description = "The comprehensive WSGI web application library."
category = "main" category = "main"
description = "The comprehensive WSGI web application library."
name = "werkzeug"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.0.1"
[package.extras] [package.extras]
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
watchdog = ["watchdog"] watchdog = ["watchdog"]
[[package]] [[package]]
name = "zipp"
version = "3.4.1"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev" category = "dev"
description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version < \"3.8\""
name = "zipp"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
version = "3.4.1"
[package.extras] [package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
[metadata] [metadata]
lock-version = "1.1" content-hash = "ace423d1b657b80c33a6fddb308d7d2a458847cfb14630c17da256c9e50f1f1d"
python-versions = "^3.6" python-versions = "^3.6"
content-hash = "27f9680e537bbe672c9dc3e65a88e3d9f19c4f849135153580a4209773bbced5"
[metadata.files] [metadata.files]
atomicwrites = [ atomicwrites = [
@ -443,6 +493,45 @@ certifi = [
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
] ]
cffi = [
{file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"},
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"},
{file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"},
{file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"},
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"},
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"},
{file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"},
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"},
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"},
{file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"},
{file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"},
{file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"},
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"},
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"},
{file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"},
{file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"},
{file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"},
{file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"},
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"},
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"},
{file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"},
{file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"},
{file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"},
{file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"},
{file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"},
{file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"},
{file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"},
{file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"},
{file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"},
{file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"},
{file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"},
{file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"},
{file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"},
{file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"},
{file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"},
{file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"},
]
chardet = [ chardet = [
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
@ -617,6 +706,10 @@ py = [
{file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
{file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
] ]
pycparser = [
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
]
pycryptodome = [ pycryptodome = [
{file = "pycryptodome-3.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06"}, {file = "pycryptodome-3.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06"},
{file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c"}, {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c"},
@ -649,6 +742,26 @@ pycryptodome = [
{file = "pycryptodome-3.10.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713"}, {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713"},
{file = "pycryptodome-3.10.1.tar.gz", hash = "sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673"}, {file = "pycryptodome-3.10.1.tar.gz", hash = "sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673"},
] ]
pynacl = [
{file = "PyNaCl-1.4.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff"},
{file = "PyNaCl-1.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514"},
{file = "PyNaCl-1.4.0-cp27-cp27m-win32.whl", hash = "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574"},
{file = "PyNaCl-1.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"},
{file = "PyNaCl-1.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7"},
{file = "PyNaCl-1.4.0-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122"},
{file = "PyNaCl-1.4.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d"},
{file = "PyNaCl-1.4.0-cp35-abi3-win32.whl", hash = "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634"},
{file = "PyNaCl-1.4.0-cp35-abi3-win_amd64.whl", hash = "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6"},
{file = "PyNaCl-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4"},
{file = "PyNaCl-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25"},
{file = "PyNaCl-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4"},
{file = "PyNaCl-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6"},
{file = "PyNaCl-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f"},
{file = "PyNaCl-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f"},
{file = "PyNaCl-1.4.0-cp38-cp38-win32.whl", hash = "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96"},
{file = "PyNaCl-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420"},
{file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"},
]
pyparsing = [ pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},

View file

@ -30,6 +30,7 @@ unidecode = "*"
urllib3 = "*" urllib3 = "*"
eventlet = "*" eventlet = "*"
setuptools = "*" setuptools = "*"
pynacl = "^1.4.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "*" pytest = "*"

View file

@ -25,11 +25,14 @@
"gui_receive_flatpak_data_dir": "Because you installed OnionShare using Flatpak, you must save files to a folder in ~/OnionShare.", "gui_receive_flatpak_data_dir": "Because you installed OnionShare using Flatpak, you must save files to a folder in ~/OnionShare.",
"gui_copy_url": "Copy Address", "gui_copy_url": "Copy Address",
"gui_copy_hidservauth": "Copy HidServAuth", "gui_copy_hidservauth": "Copy HidServAuth",
"gui_copy_client_auth_v3": "Copy ClientAuth",
"gui_canceled": "Canceled", "gui_canceled": "Canceled",
"gui_copied_url_title": "Copied OnionShare Address", "gui_copied_url_title": "Copied OnionShare Address",
"gui_copied_url": "OnionShare address copied to clipboard", "gui_copied_url": "OnionShare address copied to clipboard",
"gui_copied_hidservauth_title": "Copied HidServAuth", "gui_copied_hidservauth_title": "Copied HidServAuth",
"gui_copied_hidservauth": "HidServAuth line copied to clipboard", "gui_copied_hidservauth": "HidServAuth line copied to clipboard",
"gui_copied_client_auth_v3_title": "Copied ClientAuth",
"gui_copied_client_auth_v3": "ClientAuth private key copied to clipboard",
"gui_show_url_qr_code": "Show QR Code", "gui_show_url_qr_code": "Show QR Code",
"gui_qr_code_dialog_title": "OnionShare QR Code", "gui_qr_code_dialog_title": "OnionShare QR Code",
"gui_waiting_to_start": "Scheduled to start in {}. Click to cancel.", "gui_waiting_to_start": "Scheduled to start in {}. Click to cancel.",
@ -68,7 +71,7 @@
"gui_settings_button_save": "Save", "gui_settings_button_save": "Save",
"gui_settings_button_cancel": "Cancel", "gui_settings_button_cancel": "Cancel",
"gui_settings_button_help": "Help", "gui_settings_button_help": "Help",
"settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.\nSupports next-gen .onion addresses: {}.", "settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports legacy .onion addresses: {}.\nSupports v2 client authentication: {}.\nSupports next-gen .onion addresses: {}.\nSupports next-gen client authentication: {}.",
"connecting_to_tor": "Connecting to the Tor network", "connecting_to_tor": "Connecting to the Tor network",
"update_available": "New OnionShare out. <a href='{}'>Click here</a> to get it.<br><br>You are using {} and the latest is {}.", "update_available": "New OnionShare out. <a href='{}'>Click here</a> to get it.<br><br>You are using {} and the latest is {}.",
"update_error_invalid_latest_version": "Could not check for new version: The OnionShare website is saying the latest version is the unrecognizable '{}'…", "update_error_invalid_latest_version": "Could not check for new version: The OnionShare website is saying the latest version is the unrecognizable '{}'…",

View file

@ -695,8 +695,10 @@ class SettingsDialog(QtWidgets.QDialog):
strings._("settings_test_success").format( strings._("settings_test_success").format(
onion.tor_version, onion.tor_version,
onion.supports_ephemeral, onion.supports_ephemeral,
onion.supports_v2_onions,
onion.supports_stealth, onion.supports_stealth,
onion.supports_v3_onions, onion.supports_v3_onions,
onion.supports_stealth_v3,
), ),
) )

View file

@ -139,7 +139,7 @@ class ModeSettingsWidget(QtWidgets.QWidget):
else: else:
self.legacy_checkbox.setCheckState(QtCore.Qt.Unchecked) self.legacy_checkbox.setCheckState(QtCore.Qt.Unchecked)
# Client auth # Client auth (v2)
self.client_auth_checkbox = QtWidgets.QCheckBox() self.client_auth_checkbox = QtWidgets.QCheckBox()
self.client_auth_checkbox.clicked.connect(self.client_auth_checkbox_clicked) self.client_auth_checkbox.clicked.connect(self.client_auth_checkbox_clicked)
self.client_auth_checkbox.clicked.connect(self.update_ui) self.client_auth_checkbox.clicked.connect(self.update_ui)
@ -151,6 +151,18 @@ class ModeSettingsWidget(QtWidgets.QWidget):
else: else:
self.client_auth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.client_auth_checkbox.setCheckState(QtCore.Qt.Unchecked)
# Client auth (v3)
self.client_auth_v3_checkbox = QtWidgets.QCheckBox()
self.client_auth_v3_checkbox.clicked.connect(self.client_auth_v3_checkbox_clicked)
self.client_auth_v3_checkbox.clicked.connect(self.update_ui)
self.client_auth_v3_checkbox.setText(
strings._("mode_settings_client_auth_checkbox")
)
if self.settings.get("general", "client_auth_v3"):
self.client_auth_v3_checkbox.setCheckState(QtCore.Qt.Checked)
else:
self.client_auth_v3_checkbox.setCheckState(QtCore.Qt.Unchecked)
# Toggle advanced settings # Toggle advanced settings
self.toggle_advanced_button = QtWidgets.QPushButton() self.toggle_advanced_button = QtWidgets.QPushButton()
self.toggle_advanced_button.clicked.connect(self.toggle_advanced_clicked) self.toggle_advanced_button.clicked.connect(self.toggle_advanced_clicked)
@ -167,6 +179,7 @@ class ModeSettingsWidget(QtWidgets.QWidget):
advanced_layout.addLayout(autostop_timer_layout) advanced_layout.addLayout(autostop_timer_layout)
advanced_layout.addWidget(self.legacy_checkbox) advanced_layout.addWidget(self.legacy_checkbox)
advanced_layout.addWidget(self.client_auth_checkbox) advanced_layout.addWidget(self.client_auth_checkbox)
advanced_layout.addWidget(self.client_auth_v3_checkbox)
self.advanced_widget = QtWidgets.QWidget() self.advanced_widget = QtWidgets.QWidget()
self.advanced_widget.setLayout(advanced_layout) self.advanced_widget.setLayout(advanced_layout)
self.advanced_widget.hide() self.advanced_widget.hide()
@ -192,16 +205,19 @@ class ModeSettingsWidget(QtWidgets.QWidget):
strings._("mode_settings_advanced_toggle_show") strings._("mode_settings_advanced_toggle_show")
) )
# Client auth is only a legacy option # v2 client auth is only a legacy option
if self.client_auth_checkbox.isChecked(): if self.client_auth_checkbox.isChecked():
self.legacy_checkbox.setChecked(True) self.legacy_checkbox.setChecked(True)
self.legacy_checkbox.setEnabled(False) self.legacy_checkbox.setEnabled(False)
self.client_auth_v3_checkbox.hide()
else: else:
self.legacy_checkbox.setEnabled(True) self.legacy_checkbox.setEnabled(True)
if self.legacy_checkbox.isChecked(): if self.legacy_checkbox.isChecked():
self.client_auth_checkbox.show() self.client_auth_checkbox.show()
self.client_auth_v3_checkbox.hide()
else: else:
self.client_auth_checkbox.hide() self.client_auth_checkbox.hide()
self.client_auth_v3_checkbox.show()
# If the server has been started in the past, prevent changing legacy option # If the server has been started in the past, prevent changing legacy option
if self.settings.get("onion", "private_key"): if self.settings.get("onion", "private_key"):
@ -209,10 +225,12 @@ class ModeSettingsWidget(QtWidgets.QWidget):
# If using legacy, disable legacy and client auth options # If using legacy, disable legacy and client auth options
self.legacy_checkbox.setEnabled(False) self.legacy_checkbox.setEnabled(False)
self.client_auth_checkbox.setEnabled(False) self.client_auth_checkbox.setEnabled(False)
self.client_auth_v3_checkbox.hide()
else: else:
# If using v3, hide legacy and client auth options # If using v3, hide legacy and client auth options, show v3 client auth option
self.legacy_checkbox.hide() self.legacy_checkbox.hide()
self.client_auth_checkbox.hide() self.client_auth_checkbox.hide()
self.client_auth_v3_checkbox.show()
def title_editing_finished(self): def title_editing_finished(self):
if self.title_lineedit.text().strip() == "": if self.title_lineedit.text().strip() == "":
@ -283,6 +301,11 @@ class ModeSettingsWidget(QtWidgets.QWidget):
"general", "client_auth", self.client_auth_checkbox.isChecked() "general", "client_auth", self.client_auth_checkbox.isChecked()
) )
def client_auth_v3_checkbox_clicked(self):
self.settings.set(
"general", "client_auth_v3", self.client_auth_v3_checkbox.isChecked()
)
def toggle_advanced_clicked(self): def toggle_advanced_clicked(self):
if self.advanced_widget.isVisible(): if self.advanced_widget.isVisible():
self.advanced_widget.hide() self.advanced_widget.hide()

View file

@ -39,6 +39,7 @@ class ServerStatus(QtWidgets.QWidget):
button_clicked = QtCore.Signal() button_clicked = QtCore.Signal()
url_copied = QtCore.Signal() url_copied = QtCore.Signal()
hidservauth_copied = QtCore.Signal() hidservauth_copied = QtCore.Signal()
client_auth_v3_copied = QtCore.Signal()
STATUS_STOPPED = 0 STATUS_STOPPED = 0
STATUS_WORKING = 1 STATUS_WORKING = 1
@ -98,6 +99,9 @@ class ServerStatus(QtWidgets.QWidget):
self.copy_hidservauth_button = QtWidgets.QPushButton( self.copy_hidservauth_button = QtWidgets.QPushButton(
strings._("gui_copy_hidservauth") strings._("gui_copy_hidservauth")
) )
self.copy_client_auth_v3_button = QtWidgets.QPushButton(
strings._("gui_copy_client_auth_v3")
)
self.show_url_qr_code_button = QtWidgets.QPushButton( self.show_url_qr_code_button = QtWidgets.QPushButton(
strings._("gui_show_url_qr_code") strings._("gui_show_url_qr_code")
) )
@ -113,10 +117,15 @@ class ServerStatus(QtWidgets.QWidget):
self.common.gui.css["server_status_url_buttons"] self.common.gui.css["server_status_url_buttons"]
) )
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
self.copy_client_auth_v3_button.setStyleSheet(
self.common.gui.css["server_status_url_buttons"]
)
self.copy_client_auth_v3_button.clicked.connect(self.copy_client_auth_v3)
url_buttons_layout = QtWidgets.QHBoxLayout() url_buttons_layout = QtWidgets.QHBoxLayout()
url_buttons_layout.addWidget(self.copy_url_button) url_buttons_layout.addWidget(self.copy_url_button)
url_buttons_layout.addWidget(self.show_url_qr_code_button) url_buttons_layout.addWidget(self.show_url_qr_code_button)
url_buttons_layout.addWidget(self.copy_hidservauth_button) url_buttons_layout.addWidget(self.copy_hidservauth_button)
url_buttons_layout.addWidget(self.copy_client_auth_v3_button)
url_buttons_layout.addStretch() url_buttons_layout.addStretch()
url_layout = QtWidgets.QVBoxLayout() url_layout = QtWidgets.QVBoxLayout()
@ -218,6 +227,11 @@ class ServerStatus(QtWidgets.QWidget):
else: else:
self.copy_hidservauth_button.hide() self.copy_hidservauth_button.hide()
if self.settings.get("general", "client_auth_v3"):
self.copy_client_auth_v3_button.show()
else:
self.copy_client_auth_v3_button.hide()
def update(self): def update(self):
""" """
Update the GUI elements based on the current state. Update the GUI elements based on the current state.
@ -247,6 +261,7 @@ class ServerStatus(QtWidgets.QWidget):
self.url.hide() self.url.hide()
self.copy_url_button.hide() self.copy_url_button.hide()
self.copy_hidservauth_button.hide() self.copy_hidservauth_button.hide()
self.copy_client_auth_v3_button.hide()
self.show_url_qr_code_button.hide() self.show_url_qr_code_button.hide()
self.mode_settings_widget.update_ui() self.mode_settings_widget.update_ui()
@ -454,6 +469,15 @@ class ServerStatus(QtWidgets.QWidget):
self.hidservauth_copied.emit() self.hidservauth_copied.emit()
def copy_client_auth_v3(self):
"""
Copy the ClientAuth v3 private key line to the clipboard.
"""
clipboard = self.qtapp.clipboard()
clipboard.setText(self.app.auth_string_v3)
self.client_auth_v3_copied.emit()
def get_url(self): def get_url(self):
""" """
Returns the OnionShare URL. Returns the OnionShare URL.

View file

@ -276,6 +276,7 @@ class Tab(QtWidgets.QWidget):
self.share_mode.server_status.button_clicked.connect(self.clear_message) self.share_mode.server_status.button_clicked.connect(self.clear_message)
self.share_mode.server_status.url_copied.connect(self.copy_url) self.share_mode.server_status.url_copied.connect(self.copy_url)
self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
self.share_mode.server_status.client_auth_v3_copied.connect(self.copy_client_auth_v3)
self.change_title.emit(self.tab_id, strings._("gui_tab_name_share")) self.change_title.emit(self.tab_id, strings._("gui_tab_name_share"))
@ -313,6 +314,9 @@ class Tab(QtWidgets.QWidget):
self.receive_mode.server_status.hidservauth_copied.connect( self.receive_mode.server_status.hidservauth_copied.connect(
self.copy_hidservauth self.copy_hidservauth
) )
self.receive_mode.server_status.client_auth_v3_copied.connect(
self.copy_client_auth_v3
)
self.change_title.emit(self.tab_id, strings._("gui_tab_name_receive")) self.change_title.emit(self.tab_id, strings._("gui_tab_name_receive"))
@ -350,6 +354,9 @@ class Tab(QtWidgets.QWidget):
self.website_mode.server_status.hidservauth_copied.connect( self.website_mode.server_status.hidservauth_copied.connect(
self.copy_hidservauth self.copy_hidservauth
) )
self.website_mode.server_status.client_auth_v3_copied.connect(
self.copy_client_auth_v3
)
self.change_title.emit(self.tab_id, strings._("gui_tab_name_website")) self.change_title.emit(self.tab_id, strings._("gui_tab_name_website"))
@ -383,6 +390,7 @@ class Tab(QtWidgets.QWidget):
self.chat_mode.server_status.button_clicked.connect(self.clear_message) self.chat_mode.server_status.button_clicked.connect(self.clear_message)
self.chat_mode.server_status.url_copied.connect(self.copy_url) self.chat_mode.server_status.url_copied.connect(self.copy_url)
self.chat_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.chat_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
self.chat_mode.server_status.client_auth_v3_copied.connect(self.copy_client_auth_v3)
self.change_title.emit(self.tab_id, strings._("gui_tab_name_chat")) self.change_title.emit(self.tab_id, strings._("gui_tab_name_chat"))
@ -604,6 +612,16 @@ class Tab(QtWidgets.QWidget):
strings._("gui_copied_hidservauth"), strings._("gui_copied_hidservauth"),
) )
def copy_client_auth_v3(self):
"""
When the v3 onion service ClientAuth private key gets copied to the clipboard, display this in the status bar.
"""
self.common.log("Tab", "copy_client_auth_v3")
self.system_tray.showMessage(
strings._("gui_copied_client_auth_v3_title"),
strings._("gui_copied_client_auth_v3"),
)
def clear_message(self): def clear_message(self):
""" """
Clear messages from the status bar. Clear messages from the status bar.