mirror of
https://github.com/onionshare/onionshare.git
synced 2025-09-18 03:24:48 -04:00
ClientAuthV3 fixes
* Remove Client Auth as an explicit option (it's on by default). * Update wording about Public mode * Fix tuple error when raising TorTooOldStealth exception in CLI * Move Private Key button next to URL button in GUI * Replace visual references of ClientAuth to Private Key * Remove HTTPAuth Flask dependency and remove a lot of code to do with password generation, 401 auth triggers/invalid password rate limit detection etc * Test updates * Remove obsolete locale keys
This commit is contained in:
parent
07fb95c04a
commit
5d9554438f
78 changed files with 112 additions and 612 deletions
|
@ -38,14 +38,6 @@ from .onionshare import OnionShare
|
|||
from .mode_settings import ModeSettings
|
||||
|
||||
|
||||
def build_url(mode_settings, app, web):
|
||||
# Build the URL
|
||||
if mode_settings.get("general", "public"):
|
||||
return f"http://{app.onion_host}"
|
||||
else:
|
||||
return f"http://onionshare:{web.password}@{app.onion_host}"
|
||||
|
||||
|
||||
def main(cwd=None):
|
||||
"""
|
||||
The main() function implements all of the logic that the command-line version of
|
||||
|
@ -113,7 +105,7 @@ def main(cwd=None):
|
|||
action="store_true",
|
||||
dest="public",
|
||||
default=False,
|
||||
help="Don't use a password",
|
||||
help="Don't use a private key",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--auto-start-timer",
|
||||
|
@ -129,13 +121,6 @@ def main(cwd=None):
|
|||
default=0,
|
||||
help="Stop onion service at schedule time (N seconds from now)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--client-auth",
|
||||
action="store_true",
|
||||
dest="client_auth",
|
||||
default=False,
|
||||
help="Use client authorization",
|
||||
)
|
||||
# Share args
|
||||
parser.add_argument(
|
||||
"--no-autostop-sharing",
|
||||
|
@ -208,7 +193,6 @@ def main(cwd=None):
|
|||
public = bool(args.public)
|
||||
autostart_timer = int(args.autostart_timer)
|
||||
autostop_timer = int(args.autostop_timer)
|
||||
client_auth = bool(args.client_auth)
|
||||
autostop_sharing = not bool(args.no_autostop_sharing)
|
||||
data_dir = args.data_dir
|
||||
webhook_url = args.webhook_url
|
||||
|
@ -249,7 +233,6 @@ def main(cwd=None):
|
|||
mode_settings.set("general", "public", public)
|
||||
mode_settings.set("general", "autostart_timer", autostart_timer)
|
||||
mode_settings.set("general", "autostop_timer", autostop_timer)
|
||||
mode_settings.set("general", "client_auth", client_auth)
|
||||
if mode == "share":
|
||||
mode_settings.set("share", "autostop_sharing", autostop_sharing)
|
||||
if mode == "receive":
|
||||
|
@ -336,11 +319,6 @@ def main(cwd=None):
|
|||
try:
|
||||
common.settings.load()
|
||||
|
||||
if mode_settings.get("general", "public"):
|
||||
web.password = None
|
||||
else:
|
||||
web.generate_password(mode_settings.get("onion", "password"))
|
||||
|
||||
# Receive mode needs to know the tor proxy details for webhooks
|
||||
if mode == "receive":
|
||||
if local_only:
|
||||
|
@ -365,7 +343,7 @@ def main(cwd=None):
|
|||
sys.exit()
|
||||
|
||||
app.start_onion_service(mode, mode_settings, False)
|
||||
url = build_url(mode_settings, app, web)
|
||||
url = f"http://{app.onion_host}"
|
||||
schedule = datetime.now() + timedelta(seconds=autostart_timer)
|
||||
if mode == "receive":
|
||||
print(
|
||||
|
@ -378,21 +356,21 @@ def main(cwd=None):
|
|||
"what you are doing."
|
||||
)
|
||||
print("")
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
if not mode_settings.get("general", "public"):
|
||||
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')}"
|
||||
f"Give this address and private key to your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
|
||||
)
|
||||
print(f"ClientAuth: {app.auth_string}")
|
||||
print(f"Private key: {app.auth_string}")
|
||||
else:
|
||||
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')}"
|
||||
)
|
||||
else:
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
if not mode_settings.get("general", "public"):
|
||||
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')}"
|
||||
f"Give this address and private key to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
|
||||
)
|
||||
print(f"ClientAuth: {app.auth_string}")
|
||||
print(f"Private key: {app.auth_string}")
|
||||
else:
|
||||
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')}"
|
||||
|
@ -410,7 +388,6 @@ def main(cwd=None):
|
|||
sys.exit()
|
||||
except (TorTooOldEphemeral, TorTooOldStealth, TorErrorProtocolError) as e:
|
||||
print("")
|
||||
print(e.args[0])
|
||||
sys.exit()
|
||||
|
||||
if mode == "website":
|
||||
|
@ -442,21 +419,14 @@ def main(cwd=None):
|
|||
t.start()
|
||||
|
||||
try: # Trap Ctrl-C
|
||||
# Wait for web.generate_password() to finish running
|
||||
time.sleep(0.2)
|
||||
|
||||
# start auto-stop timer thread
|
||||
if app.autostop_timer > 0:
|
||||
app.autostop_timer_thread.start()
|
||||
|
||||
# Save the web password if we are using a persistent private key
|
||||
if mode_settings.get("persistent", "enabled"):
|
||||
if not mode_settings.get("onion", "password"):
|
||||
mode_settings.set("onion", "password", web.password)
|
||||
# mode_settings.save()
|
||||
|
||||
# Build the URL
|
||||
url = build_url(mode_settings, app, web)
|
||||
url = f"http://{app.onion_host}"
|
||||
|
||||
print("")
|
||||
if autostart_timer > 0:
|
||||
|
@ -474,21 +444,21 @@ def main(cwd=None):
|
|||
)
|
||||
print("")
|
||||
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
print("Give this address and ClientAuth to the sender:")
|
||||
print(url)
|
||||
print(f"ClientAuth: {app.auth_string}")
|
||||
else:
|
||||
if mode_settings.get("general", "public"):
|
||||
print("Give this address to the sender:")
|
||||
print(url)
|
||||
else:
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
print("Give this address and ClientAuth line to the recipient:")
|
||||
print(url)
|
||||
print(f"ClientAuth: {app.auth_string}")
|
||||
else:
|
||||
print("Give this address and private key to the sender:")
|
||||
print(url)
|
||||
print(f"Private key: {app.auth_string}")
|
||||
else:
|
||||
if mode_settings.get("general", "public"):
|
||||
print("Give this address to the recipient:")
|
||||
print(url)
|
||||
else:
|
||||
print("Give this address and private key to the recipient:")
|
||||
print(url)
|
||||
print(f"Private key: {app.auth_string}")
|
||||
print("")
|
||||
print("Press Ctrl+C to stop the server")
|
||||
|
||||
|
|
|
@ -37,8 +37,6 @@ class ModeSettings:
|
|||
self.default_settings = {
|
||||
"onion": {
|
||||
"private_key": None,
|
||||
"hidservauth_string": None,
|
||||
"password": None,
|
||||
"client_auth_priv_key": None,
|
||||
"client_auth_pub_key": None,
|
||||
},
|
||||
|
@ -48,7 +46,6 @@ class ModeSettings:
|
|||
"public": False,
|
||||
"autostart_timer": False,
|
||||
"autostop_timer": False,
|
||||
"client_auth": False,
|
||||
"service_id": None,
|
||||
},
|
||||
"share": {"autostop_sharing": True, "filenames": []},
|
||||
|
|
|
@ -640,7 +640,10 @@ class Onion(object):
|
|||
debug_message += f", key_content={key_content}"
|
||||
self.common.log("Onion", "start_onion_service", debug_message)
|
||||
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
if mode_settings.get("general", "public"):
|
||||
client_auth_priv_key = None
|
||||
client_auth_pub_key = None
|
||||
else:
|
||||
if not self.supports_stealth:
|
||||
print(
|
||||
"Your version of Tor is too old, stealth onion services are not supported"
|
||||
|
@ -657,9 +660,6 @@ class Onion(object):
|
|||
# These should have been saved in settings from the previous run of a persistent onion
|
||||
client_auth_priv_key = mode_settings.get("onion", "client_auth_priv_key")
|
||||
client_auth_pub_key = mode_settings.get("onion", "client_auth_pub_key")
|
||||
else:
|
||||
client_auth_priv_key = None
|
||||
client_auth_pub_key = None
|
||||
|
||||
try:
|
||||
if not self.supports_stealth:
|
||||
|
@ -701,7 +701,7 @@ class Onion(object):
|
|||
# because we need to send the public key to ADD_ONION (if we restart this
|
||||
# same share at a later date), and the private key to the other user for
|
||||
# their Tor Browser.
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
if not mode_settings.get("general", "public"):
|
||||
mode_settings.set("onion", "client_auth_priv_key", client_auth_priv_key)
|
||||
mode_settings.set("onion", "client_auth_pub_key", client_auth_pub_key)
|
||||
# If we were pasting the client auth directly into the filesystem behind a Tor client,
|
||||
|
|
|
@ -74,7 +74,7 @@ class OnionShare(object):
|
|||
|
||||
if self.local_only:
|
||||
self.onion_host = f"127.0.0.1:{self.port}"
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
if not mode_settings.get("general", "public"):
|
||||
self.auth_string = "E2GOT5LTUTP3OAMRCRXO4GSH6VKJEUOXZQUC336SRKAHTTT5OVSA"
|
||||
return
|
||||
|
||||
|
@ -82,7 +82,7 @@ class OnionShare(object):
|
|||
mode, mode_settings, self.port, await_publication
|
||||
)
|
||||
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
if not mode_settings.get("general", "public"):
|
||||
self.auth_string = self.onion.auth_string
|
||||
|
||||
def stop_onion_service(self, mode_settings):
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>OnionShare: 401 Unauthorized Access</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="info-wrapper">
|
||||
<div class="info">
|
||||
<p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
|
||||
<p class="info-header">401 Unauthorized Access</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -34,7 +34,6 @@ from flask import (
|
|||
send_file,
|
||||
__version__ as flask_version,
|
||||
)
|
||||
from flask_httpauth import HTTPBasicAuth
|
||||
from flask_socketio import SocketIO
|
||||
|
||||
from .share_mode import ShareModeWeb
|
||||
|
@ -75,7 +74,6 @@ class Web:
|
|||
REQUEST_INDIVIDUAL_FILE_CANCELED = 12
|
||||
REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 13
|
||||
REQUEST_OTHER = 14
|
||||
REQUEST_INVALID_PASSWORD = 15
|
||||
|
||||
def __init__(self, common, is_gui, mode_settings, mode="share"):
|
||||
self.common = common
|
||||
|
@ -92,8 +90,6 @@ class Web:
|
|||
)
|
||||
self.app.secret_key = self.common.random_string(8)
|
||||
self.generate_static_url_path()
|
||||
self.auth = HTTPBasicAuth()
|
||||
self.auth.error_handler(self.error401)
|
||||
|
||||
# Verbose mode?
|
||||
if self.common.verbose:
|
||||
|
@ -132,9 +128,6 @@ class Web:
|
|||
]
|
||||
|
||||
self.q = queue.Queue()
|
||||
self.password = None
|
||||
|
||||
self.reset_invalid_passwords()
|
||||
|
||||
self.done = False
|
||||
|
||||
|
@ -199,28 +192,6 @@ class Web:
|
|||
Common web app routes between all modes.
|
||||
"""
|
||||
|
||||
@self.auth.get_password
|
||||
def get_pw(username):
|
||||
if username == "onionshare":
|
||||
return self.password
|
||||
else:
|
||||
return None
|
||||
|
||||
@self.app.before_request
|
||||
def conditional_auth_check():
|
||||
# Allow static files without basic authentication
|
||||
if request.path.startswith(self.static_url_path + "/"):
|
||||
return None
|
||||
|
||||
# If public mode is disabled, require authentication
|
||||
if not self.settings.get("general", "public"):
|
||||
|
||||
@self.auth.login_required
|
||||
def _check_login():
|
||||
return None
|
||||
|
||||
return _check_login()
|
||||
|
||||
@self.app.errorhandler(404)
|
||||
def not_found(e):
|
||||
mode = self.get_mode()
|
||||
|
@ -260,31 +231,6 @@ class Web:
|
|||
f"{self.common.get_resource_path('static')}/img/favicon.ico"
|
||||
)
|
||||
|
||||
def error401(self):
|
||||
auth = request.authorization
|
||||
if auth:
|
||||
if (
|
||||
auth["username"] == "onionshare"
|
||||
and auth["password"] not in self.invalid_passwords
|
||||
):
|
||||
print(f"Invalid password guess: {auth['password']}")
|
||||
self.add_request(Web.REQUEST_INVALID_PASSWORD, data=auth["password"])
|
||||
|
||||
self.invalid_passwords.append(auth["password"])
|
||||
self.invalid_passwords_count += 1
|
||||
|
||||
if self.invalid_passwords_count == 20:
|
||||
self.add_request(Web.REQUEST_RATE_LIMIT)
|
||||
self.force_shutdown()
|
||||
print(
|
||||
"Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share."
|
||||
)
|
||||
|
||||
r = make_response(
|
||||
render_template("401.html", static_url_path=self.static_url_path), 401
|
||||
)
|
||||
return self.add_security_headers(r)
|
||||
|
||||
def error403(self):
|
||||
self.add_request(Web.REQUEST_OTHER, request.path)
|
||||
r = make_response(
|
||||
|
@ -362,21 +308,6 @@ class Web:
|
|||
"""
|
||||
self.q.put({"type": request_type, "path": path, "data": data})
|
||||
|
||||
def generate_password(self, saved_password=None):
|
||||
self.common.log("Web", "generate_password", f"saved_password={saved_password}")
|
||||
if saved_password is not None and saved_password != "":
|
||||
self.password = saved_password
|
||||
self.common.log(
|
||||
"Web",
|
||||
"generate_password",
|
||||
f'saved_password sent, so password is: "{self.password}"',
|
||||
)
|
||||
else:
|
||||
self.password = self.common.build_password()
|
||||
self.common.log(
|
||||
"Web", "generate_password", f'built random password: "{self.password}"'
|
||||
)
|
||||
|
||||
def verbose_mode(self):
|
||||
"""
|
||||
Turn on verbose mode, which will log flask errors to a file.
|
||||
|
@ -386,10 +317,6 @@ class Web:
|
|||
log_handler.setLevel(logging.WARNING)
|
||||
self.app.logger.addHandler(log_handler)
|
||||
|
||||
def reset_invalid_passwords(self):
|
||||
self.invalid_passwords_count = 0
|
||||
self.invalid_passwords = []
|
||||
|
||||
def force_shutdown(self):
|
||||
"""
|
||||
Stop the flask web server, from the context of the flask app.
|
||||
|
@ -446,18 +373,9 @@ class Web:
|
|||
# To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown
|
||||
# (We're putting the shutdown_password in the path as well to make routing simpler)
|
||||
if self.running:
|
||||
if self.password:
|
||||
requests.get(
|
||||
f"http://127.0.0.1:{port}/{self.shutdown_password}/shutdown",
|
||||
auth=requests.auth.HTTPBasicAuth("onionshare", self.password),
|
||||
)
|
||||
else:
|
||||
requests.get(
|
||||
f"http://127.0.0.1:{port}/{self.shutdown_password}/shutdown"
|
||||
)
|
||||
|
||||
# Reset any password that was in use
|
||||
self.password = None
|
||||
requests.get(
|
||||
f"http://127.0.0.1:{port}/{self.shutdown_password}/shutdown"
|
||||
)
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue