mirror of
https://github.com/onionshare/onionshare.git
synced 2024-10-01 01:35:40 -04:00
Fix conflict
This commit is contained in:
commit
c3755389dd
@ -10,7 +10,7 @@ python:
|
||||
- "nightly"
|
||||
# command to install dependencies
|
||||
install:
|
||||
- pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls flake8
|
||||
- pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls flake8 pycrypto pynacl cryptography pysha3
|
||||
before_script:
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
|
||||
|
6
BUILD.md
6
BUILD.md
@ -11,9 +11,11 @@ cd onionshare
|
||||
|
||||
Install the needed dependencies:
|
||||
|
||||
For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-socks python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy`
|
||||
For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy python3-cryptography python3-crypto python3-nacl python3-pip python3-socks python3-sha3`
|
||||
|
||||
For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4 rpm-build`
|
||||
On some older versions of Debian you may need to install pysha3 with `pip3 install pysha3` if python3-sha3 is not available.
|
||||
|
||||
For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4 python3-pynacl python3-cryptography python3-crypto python3-pip python3-pysocks`
|
||||
|
||||
After that you can try both the CLI and the GUI version of OnionShare:
|
||||
|
||||
|
@ -1,14 +1,26 @@
|
||||
altgraph==0.16.1
|
||||
certifi==2018.8.24
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
Flask==0.12.2
|
||||
Flask==1.0.2
|
||||
future==0.16.0
|
||||
idna==2.7
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
macholib==1.11
|
||||
MarkupSafe==1.0
|
||||
pefile==2017.11.5
|
||||
PyInstaller==3.3.1
|
||||
PyQt5==5.9.2
|
||||
PySocks==1.6.7
|
||||
packaging==17.1
|
||||
pefile==2018.8.8
|
||||
PyInstaller==3.4
|
||||
pyparsing==2.2.0
|
||||
pypiwin32==223
|
||||
PyQt5==5.11.2
|
||||
PySocks==1.6.8
|
||||
python-dateutil==2.7.3
|
||||
pywin32==223
|
||||
requests==2.19.1
|
||||
sip==4.19.6
|
||||
sip==4.19.8
|
||||
six==1.11.0
|
||||
stem==1.6.0
|
||||
urllib3==1.23
|
||||
Werkzeug==0.14.1
|
||||
|
@ -1,12 +1,21 @@
|
||||
altgraph==0.16.1
|
||||
certifi==2018.8.24
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
Flask==0.12.2
|
||||
Flask==1.0.2
|
||||
future==0.16.0
|
||||
idna==2.7
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
macholib==1.11
|
||||
MarkupSafe==1.0
|
||||
PyInstaller==3.3.1
|
||||
PyQt5==5.9.2
|
||||
PySocks==1.6.7
|
||||
pefile==2018.8.8
|
||||
PyInstaller==3.4
|
||||
PyQt5==5.11.2
|
||||
PyQt5-sip==4.19.12
|
||||
PySocks==1.6.8
|
||||
requests==2.19.1
|
||||
sip==4.19.6
|
||||
sip==4.19.8
|
||||
stem==1.6.0
|
||||
urllib3==1.23
|
||||
Werkzeug==0.14.1
|
||||
|
@ -346,6 +346,11 @@ class Common(object):
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 10px;
|
||||
}""",
|
||||
|
||||
'settings_whats_this': """
|
||||
QLabel {
|
||||
font-size: 12px;
|
||||
}"""
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
from stem.control import Controller
|
||||
from stem import ProtocolError, SocketClosed
|
||||
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
|
||||
import os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex
|
||||
import base64, os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex
|
||||
|
||||
from distutils.version import LooseVersion as Version
|
||||
from . import onionkey
|
||||
from . import common, strings
|
||||
from .settings import Settings
|
||||
|
||||
@ -437,20 +439,48 @@ class Onion(object):
|
||||
basic_auth = None
|
||||
|
||||
if self.settings.get('private_key'):
|
||||
key_type = "RSA1024"
|
||||
key_content = self.settings.get('private_key')
|
||||
self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a saved private key')
|
||||
# is the key a v2 key?
|
||||
if onionkey.is_v2_key(key_content):
|
||||
key_type = "RSA1024"
|
||||
# The below section is commented out because re-publishing
|
||||
# a pre-prepared v3 private key is currently unstable in Tor.
|
||||
# This is fixed upstream but won't reach stable until 0.3.5
|
||||
# (expected in December 2018)
|
||||
# See https://trac.torproject.org/projects/tor/ticket/25552
|
||||
# Until then, we will deliberately not work with 'persistent'
|
||||
# v3 onions, which should not be possible via the GUI settings
|
||||
# anyway.
|
||||
# Our ticket: https://github.com/micahflee/onionshare/issues/677
|
||||
#
|
||||
# Assume it was a v3 key
|
||||
# key_type = "ED25519-V3"
|
||||
else:
|
||||
raise TorErrorProtocolError(strings._('error_invalid_private_key'))
|
||||
self.common.log('Onion', 'Starting a hidden service with a saved private key')
|
||||
else:
|
||||
key_type = "NEW"
|
||||
key_content = "RSA1024"
|
||||
self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a new private key')
|
||||
# Work out if we can support v3 onion services, which are preferred
|
||||
if Version(self.tor_version) >= Version('0.3.2.9') and not self.settings.get('use_legacy_v2_onions'):
|
||||
key_type = "ED25519-V3"
|
||||
key_content = onionkey.generate_v3_private_key()[0]
|
||||
else:
|
||||
# fall back to v2 onion services
|
||||
key_type = "RSA1024"
|
||||
key_content = onionkey.generate_v2_private_key()[0]
|
||||
self.common.log('Onion', 'Starting a hidden service with a new private key')
|
||||
|
||||
# v3 onions don't yet support basic auth. Our ticket:
|
||||
# https://github.com/micahflee/onionshare/issues/697
|
||||
if key_type == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'):
|
||||
basic_auth = None
|
||||
self.stealth = False
|
||||
|
||||
try:
|
||||
if basic_auth != None:
|
||||
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth, key_type = key_type, key_content=key_content)
|
||||
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth, key_type=key_type, key_content=key_content)
|
||||
else:
|
||||
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
|
||||
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, key_type = key_type, key_content=key_content)
|
||||
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, key_type=key_type, key_content=key_content)
|
||||
|
||||
except ProtocolError:
|
||||
raise TorErrorProtocolError(strings._('error_tor_protocol_error'))
|
||||
@ -461,7 +491,7 @@ class Onion(object):
|
||||
# A new private key was generated and is in the Control port response.
|
||||
if self.settings.get('save_private_key'):
|
||||
if not self.settings.get('private_key'):
|
||||
self.settings.set('private_key', res.private_key)
|
||||
self.settings.set('private_key', key_content)
|
||||
|
||||
if self.stealth:
|
||||
# Similar to the PrivateKey, the Control port only returns the ClientAuth
|
||||
|
129
onionshare/onionkey.py
Normal file
129
onionshare/onionkey.py
Normal file
@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
OnionShare | https://onionshare.org/
|
||||
|
||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
# Need sha3 if python version is older than 3.6, otherwise
|
||||
# we can't use hashlib.sha3_256
|
||||
if sys.version_info < (3, 6):
|
||||
import sha3
|
||||
|
||||
import nacl.signing
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
|
||||
def stem_compatible_base64_blob_from_private_key(private_key_seed: bytes) -> str:
|
||||
"""
|
||||
Provides a base64-encoded private key for v3-style Onions.
|
||||
"""
|
||||
b = 256
|
||||
|
||||
def bit(h: bytes, i: int) -> int:
|
||||
return (h[i // 8] >> (i % 8)) & 1
|
||||
|
||||
def encode_int(y: int) -> bytes:
|
||||
bits = [(y >> i) & 1 for i in range(b)]
|
||||
return b''.join([bytes([(sum([bits[i * 8 + j] << j for j in range(8)]))]) for i in range(b // 8)])
|
||||
|
||||
def expand_private_key(sk: bytes) -> bytes:
|
||||
h = hashlib.sha512(sk).digest()
|
||||
a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2))
|
||||
k = b''.join([bytes([h[i]]) for i in range(b // 8, b // 4)])
|
||||
assert len(k) == 32
|
||||
return encode_int(a) + k
|
||||
|
||||
expanded_private_key = expand_private_key(private_key_seed)
|
||||
return base64.b64encode(expanded_private_key).decode()
|
||||
|
||||
|
||||
def onion_url_from_private_key(private_key_seed: bytes) -> str:
|
||||
"""
|
||||
Derives the public key (.onion hostname) from a v3-style
|
||||
Onion private key.
|
||||
"""
|
||||
signing_key = nacl.signing.SigningKey(seed=private_key_seed)
|
||||
public_key = bytes(signing_key.verify_key)
|
||||
version = b'\x03'
|
||||
checksum = hashlib.sha3_256(b".onion checksum" + public_key + version).digest()[:2]
|
||||
onion_address = "http://{}.onion".format(base64.b32encode(public_key + checksum + version).decode().lower())
|
||||
return onion_address
|
||||
|
||||
|
||||
def generate_v3_private_key():
|
||||
"""
|
||||
Generates a private and public key for use with v3 style Onions.
|
||||
Returns both the private key as well as the public key (.onion hostname)
|
||||
"""
|
||||
private_key_seed = os.urandom(32)
|
||||
private_key = stem_compatible_base64_blob_from_private_key(private_key_seed)
|
||||
onion_url = onion_url_from_private_key(private_key_seed)
|
||||
return (private_key, onion_url)
|
||||
|
||||
|
||||
def generate_v2_private_key():
|
||||
"""
|
||||
Generates a private and public key for use with v2 style Onions.
|
||||
Returns both the serialized private key (compatible with Stem)
|
||||
as well as the public key (.onion hostname)
|
||||
"""
|
||||
# Generate v2 Onion Service private key
|
||||
private_key = rsa.generate_private_key(public_exponent=65537,
|
||||
key_size=1024,
|
||||
backend=default_backend())
|
||||
hs_public_key = private_key.public_key()
|
||||
|
||||
# Pre-generate the public key (.onion hostname)
|
||||
der_format = hs_public_key.public_bytes(encoding=serialization.Encoding.DER,
|
||||
format=serialization.PublicFormat.PKCS1)
|
||||
|
||||
onion_url = base64.b32encode(hashlib.sha1(der_format).digest()[:-10]).lower().decode()
|
||||
|
||||
# Generate Stem-compatible key content
|
||||
pem_format = private_key.private_bytes(encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption())
|
||||
serialized_key = ''.join(pem_format.decode().split('\n')[1:-2])
|
||||
|
||||
return (serialized_key, onion_url)
|
||||
|
||||
|
||||
def is_v2_key(key):
|
||||
"""
|
||||
Helper function for determining if a key is RSA1024 (v2) or not.
|
||||
"""
|
||||
try:
|
||||
# Import the key
|
||||
key = RSA.importKey(base64.b64decode(key))
|
||||
# Is this a v2 Onion key? (1024 bits) If so, we should keep using it.
|
||||
if key.n.bit_length() == 1024:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
@ -67,6 +67,7 @@ class Settings(object):
|
||||
'tor_bridges_use_obfs4': False,
|
||||
'tor_bridges_use_meek_lite_azure': False,
|
||||
'tor_bridges_use_custom_bridges': '',
|
||||
'use_legacy_v2_onions': False,
|
||||
'save_private_key': False,
|
||||
'private_key': '',
|
||||
'public_mode': False,
|
||||
|
@ -33,6 +33,7 @@ from distutils.version import LooseVersion as Version
|
||||
from urllib.request import urlopen
|
||||
from datetime import datetime
|
||||
|
||||
import flask
|
||||
from flask import (
|
||||
Flask, Response, Request, request, render_template, abort, make_response,
|
||||
flash, redirect, __version__ as flask_version
|
||||
@ -42,6 +43,15 @@ from werkzeug.utils import secure_filename
|
||||
from . import strings
|
||||
from .common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable
|
||||
|
||||
|
||||
# Stub out flask's show_server_banner function, to avoiding showing warnings that
|
||||
# are not applicable to OnionShare
|
||||
def stubbed_show_server_banner(env, debug, app_import_path, eager_loading):
|
||||
pass
|
||||
|
||||
flask.cli.show_server_banner = stubbed_show_server_banner
|
||||
|
||||
|
||||
class Web(object):
|
||||
"""
|
||||
The Web object is the OnionShare web server, powered by flask
|
||||
|
@ -205,7 +205,7 @@ class ReceiveMode(Mode):
|
||||
else:
|
||||
image = self.common.get_resource_path('images/share_completed.png')
|
||||
self.info_completed_uploads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.uploads_completed))
|
||||
self.info_completed_uploads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.uploads_completed))
|
||||
self.info_completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.uploads_completed))
|
||||
|
||||
def update_uploads_in_progress(self):
|
||||
"""
|
||||
@ -217,7 +217,7 @@ class ReceiveMode(Mode):
|
||||
image = self.common.get_resource_path('images/share_in_progress.png')
|
||||
self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_green.png')))
|
||||
self.info_in_progress_uploads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.uploads_in_progress))
|
||||
self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.uploads_in_progress))
|
||||
self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.uploads_in_progress))
|
||||
|
||||
def update_primary_action(self):
|
||||
self.common.log('ReceiveMode', 'update_primary_action')
|
||||
|
@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import platform
|
||||
import textwrap
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare import strings
|
||||
@ -56,6 +57,8 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
|
||||
self.web = None
|
||||
|
||||
self.resizeEvent(None)
|
||||
|
||||
# Shutdown timeout layout
|
||||
self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True))
|
||||
self.shutdown_timeout = QtWidgets.QDateTimeEdit()
|
||||
@ -88,7 +91,7 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
self.url = QtWidgets.QLabel()
|
||||
self.url.setFont(url_font)
|
||||
self.url.setWordWrap(True)
|
||||
self.url.setMinimumHeight(60)
|
||||
self.url.setMinimumHeight(65)
|
||||
self.url.setMinimumSize(self.url.sizeHint())
|
||||
self.url.setStyleSheet(self.common.css['server_status_url'])
|
||||
|
||||
@ -128,6 +131,24 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
|
||||
self.update()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""
|
||||
When the widget is resized, try and adjust the display of a v3 onion URL.
|
||||
"""
|
||||
try:
|
||||
self.get_url()
|
||||
url_length=len(self.get_url())
|
||||
if url_length > 60:
|
||||
width = self.frameGeometry().width()
|
||||
if width < 530:
|
||||
wrapped_onion_url = textwrap.fill(self.get_url(), 50)
|
||||
self.url.setText(wrapped_onion_url)
|
||||
else:
|
||||
self.url.setText(self.get_url())
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def shutdown_timeout_reset(self):
|
||||
"""
|
||||
Reset the timeout in the UI after stopping a share
|
||||
|
@ -54,20 +54,111 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
|
||||
# General options
|
||||
|
||||
# Whether or not to save the Onion private key for reuse (persistent URL mode)
|
||||
self.save_private_key_checkbox = QtWidgets.QCheckBox()
|
||||
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True))
|
||||
|
||||
# Use a slug
|
||||
# Use a slug or not ('public mode')
|
||||
self.public_mode_checkbox = QtWidgets.QCheckBox()
|
||||
self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True))
|
||||
public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Public-Mode"))
|
||||
public_mode_label.setStyleSheet(self.common.css['settings_whats_this'])
|
||||
public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
public_mode_label.setOpenExternalLinks(True)
|
||||
public_mode_label.setMinimumSize(public_mode_label.sizeHint())
|
||||
public_mode_layout = QtWidgets.QHBoxLayout()
|
||||
public_mode_layout.addWidget(self.public_mode_checkbox)
|
||||
public_mode_layout.addWidget(public_mode_label)
|
||||
public_mode_layout.addStretch()
|
||||
public_mode_layout.setContentsMargins(0,0,0,0)
|
||||
self.public_mode_widget = QtWidgets.QWidget()
|
||||
self.public_mode_widget.setLayout(public_mode_layout)
|
||||
|
||||
# Whether or not to use a shutdown ('auto-stop') timer
|
||||
self.shutdown_timeout_checkbox = QtWidgets.QCheckBox()
|
||||
self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True))
|
||||
shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer"))
|
||||
shutdown_timeout_label.setStyleSheet(self.common.css['settings_whats_this'])
|
||||
shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
shutdown_timeout_label.setOpenExternalLinks(True)
|
||||
shutdown_timeout_label.setMinimumSize(public_mode_label.sizeHint())
|
||||
shutdown_timeout_layout = QtWidgets.QHBoxLayout()
|
||||
shutdown_timeout_layout.addWidget(self.shutdown_timeout_checkbox)
|
||||
shutdown_timeout_layout.addWidget(shutdown_timeout_label)
|
||||
shutdown_timeout_layout.addStretch()
|
||||
shutdown_timeout_layout.setContentsMargins(0,0,0,0)
|
||||
self.shutdown_timeout_widget = QtWidgets.QWidget()
|
||||
self.shutdown_timeout_widget.setLayout(shutdown_timeout_layout)
|
||||
|
||||
# Whether or not to use legacy v2 onions
|
||||
self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox()
|
||||
self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox", True))
|
||||
self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked)
|
||||
use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses"))
|
||||
use_legacy_v2_onions_label.setStyleSheet(self.common.css['settings_whats_this'])
|
||||
use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
use_legacy_v2_onions_label.setOpenExternalLinks(True)
|
||||
use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout()
|
||||
use_legacy_v2_onions_layout.addWidget(self.use_legacy_v2_onions_checkbox)
|
||||
use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label)
|
||||
use_legacy_v2_onions_layout.addStretch()
|
||||
use_legacy_v2_onions_layout.setContentsMargins(0,0,0,0)
|
||||
self.use_legacy_v2_onions_widget = QtWidgets.QWidget()
|
||||
self.use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout)
|
||||
|
||||
# Whether or not to save the Onion private key for reuse (persistent URL mode)
|
||||
self.save_private_key_checkbox = QtWidgets.QCheckBox()
|
||||
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True))
|
||||
self.save_private_key_checkbox.clicked.connect(self.save_private_key_checkbox_clicked)
|
||||
save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL"))
|
||||
save_private_key_label.setStyleSheet(self.common.css['settings_whats_this'])
|
||||
save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
save_private_key_label.setOpenExternalLinks(True)
|
||||
save_private_key_layout = QtWidgets.QHBoxLayout()
|
||||
save_private_key_layout.addWidget(self.save_private_key_checkbox)
|
||||
save_private_key_layout.addWidget(save_private_key_label)
|
||||
save_private_key_layout.addStretch()
|
||||
save_private_key_layout.setContentsMargins(0,0,0,0)
|
||||
self.save_private_key_widget = QtWidgets.QWidget()
|
||||
self.save_private_key_widget.setLayout(save_private_key_layout)
|
||||
|
||||
# Stealth
|
||||
self.stealth_checkbox = QtWidgets.QCheckBox()
|
||||
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True))
|
||||
self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect)
|
||||
use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services"))
|
||||
use_stealth_label.setStyleSheet(self.common.css['settings_whats_this'])
|
||||
use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
use_stealth_label.setOpenExternalLinks(True)
|
||||
use_stealth_label.setMinimumSize(use_stealth_label.sizeHint())
|
||||
use_stealth_layout = QtWidgets.QHBoxLayout()
|
||||
use_stealth_layout.addWidget(self.stealth_checkbox)
|
||||
use_stealth_layout.addWidget(use_stealth_label)
|
||||
use_stealth_layout.addStretch()
|
||||
use_stealth_layout.setContentsMargins(0,0,0,0)
|
||||
self.use_stealth_widget = QtWidgets.QWidget()
|
||||
self.use_stealth_widget.setLayout(use_stealth_layout)
|
||||
|
||||
hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True))
|
||||
hidservauth_details.setWordWrap(True)
|
||||
hidservauth_details.setMinimumSize(hidservauth_details.sizeHint())
|
||||
hidservauth_details.hide()
|
||||
|
||||
self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
|
||||
self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked)
|
||||
self.hidservauth_copy_button.hide()
|
||||
|
||||
# General options layout
|
||||
general_group_layout = QtWidgets.QVBoxLayout()
|
||||
general_group_layout.addWidget(self.save_private_key_checkbox)
|
||||
general_group_layout.addWidget(self.public_mode_checkbox)
|
||||
general_group_layout.addWidget(self.public_mode_widget)
|
||||
general_group_layout.addWidget(self.shutdown_timeout_widget)
|
||||
general_group_layout.addWidget(self.use_legacy_v2_onions_widget)
|
||||
general_group_layout.addWidget(self.save_private_key_widget)
|
||||
general_group_layout.addWidget(self.use_stealth_widget)
|
||||
general_group_layout.addWidget(hidservauth_details)
|
||||
general_group_layout.addWidget(self.hidservauth_copy_button)
|
||||
|
||||
general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label", True))
|
||||
general_group.setLayout(general_group_layout)
|
||||
|
||||
@ -78,15 +169,9 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option", True))
|
||||
|
||||
# Whether or not to use a shutdown timer
|
||||
self.shutdown_timeout_checkbox = QtWidgets.QCheckBox()
|
||||
self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True))
|
||||
|
||||
# Sharing options layout
|
||||
sharing_group_layout = QtWidgets.QVBoxLayout()
|
||||
sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
|
||||
sharing_group_layout.addWidget(self.shutdown_timeout_checkbox)
|
||||
sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True))
|
||||
sharing_group.setLayout(sharing_group_layout)
|
||||
|
||||
@ -107,36 +192,6 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True))
|
||||
receiving_group.setLayout(receiving_group_layout)
|
||||
|
||||
# Stealth options
|
||||
|
||||
# Stealth
|
||||
stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True))
|
||||
stealth_details.setWordWrap(True)
|
||||
stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
stealth_details.setOpenExternalLinks(True)
|
||||
stealth_details.setMinimumSize(stealth_details.sizeHint())
|
||||
self.stealth_checkbox = QtWidgets.QCheckBox()
|
||||
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True))
|
||||
|
||||
hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True))
|
||||
hidservauth_details.setWordWrap(True)
|
||||
hidservauth_details.setMinimumSize(hidservauth_details.sizeHint())
|
||||
hidservauth_details.hide()
|
||||
|
||||
self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
|
||||
self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked)
|
||||
self.hidservauth_copy_button.hide()
|
||||
|
||||
# Stealth options layout
|
||||
stealth_group_layout = QtWidgets.QVBoxLayout()
|
||||
stealth_group_layout.addWidget(stealth_details)
|
||||
stealth_group_layout.addWidget(self.stealth_checkbox)
|
||||
stealth_group_layout.addWidget(hidservauth_details)
|
||||
stealth_group_layout.addWidget(self.hidservauth_copy_button)
|
||||
stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True))
|
||||
stealth_group.setLayout(stealth_group_layout)
|
||||
|
||||
# Automatic updates options
|
||||
|
||||
# Autoupdate
|
||||
@ -369,7 +424,6 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
left_col_layout.addWidget(general_group)
|
||||
left_col_layout.addWidget(sharing_group)
|
||||
left_col_layout.addWidget(receiving_group)
|
||||
left_col_layout.addWidget(stealth_group)
|
||||
left_col_layout.addWidget(autoupdate_group)
|
||||
left_col_layout.addStretch()
|
||||
|
||||
@ -406,11 +460,26 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
else:
|
||||
self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions')
|
||||
|
||||
if use_legacy_v2_onions:
|
||||
self.save_private_key_widget.show()
|
||||
self.use_stealth_widget.show()
|
||||
else:
|
||||
self.save_private_key_widget.hide()
|
||||
self.use_stealth_widget.hide()
|
||||
|
||||
save_private_key = self.old_settings.get('save_private_key')
|
||||
if save_private_key:
|
||||
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
# Legacy v2 mode is forced on if persistence is enabled
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(False)
|
||||
else:
|
||||
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(True)
|
||||
|
||||
if use_legacy_v2_onions or save_private_key:
|
||||
self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
|
||||
downloads_dir = self.old_settings.get('downloads_dir')
|
||||
self.downloads_dir_lineedit.setText(downloads_dir)
|
||||
@ -424,11 +493,15 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
use_stealth = self.old_settings.get('use_stealth')
|
||||
if use_stealth:
|
||||
self.stealth_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
if save_private_key:
|
||||
# Legacy v2 mode is forced on if Stealth is enabled
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(False)
|
||||
if save_private_key and self.old_settings.get('hidservauth_string') != "":
|
||||
hidservauth_details.show()
|
||||
self.hidservauth_copy_button.show()
|
||||
else:
|
||||
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
if not save_private_key:
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(True)
|
||||
|
||||
use_autoupdate = self.old_settings.get('use_autoupdate')
|
||||
if use_autoupdate:
|
||||
@ -592,6 +665,39 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
clipboard = self.qtapp.clipboard()
|
||||
clipboard.setText(self.old_settings.get('hidservauth_string'))
|
||||
|
||||
def use_legacy_v2_onions_checkbox_clicked(self, checked):
|
||||
"""
|
||||
Show the legacy settings if the legacy mode is enabled.
|
||||
"""
|
||||
if checked:
|
||||
self.save_private_key_widget.show()
|
||||
self.use_stealth_widget.show()
|
||||
else:
|
||||
self.save_private_key_widget.hide()
|
||||
self.use_stealth_widget.hide()
|
||||
|
||||
def save_private_key_checkbox_clicked(self, checked):
|
||||
"""
|
||||
Prevent the v2 legacy mode being switched off if persistence is enabled
|
||||
"""
|
||||
if checked:
|
||||
self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(False)
|
||||
else:
|
||||
if not self.stealth_checkbox.isChecked():
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(True)
|
||||
|
||||
def stealth_checkbox_clicked_connect(self, checked):
|
||||
"""
|
||||
Prevent the v2 legacy mode being switched off if stealth is enabled
|
||||
"""
|
||||
if checked:
|
||||
self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(False)
|
||||
else:
|
||||
if not self.save_private_key_checkbox.isChecked():
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(True)
|
||||
|
||||
def downloads_button_clicked(self):
|
||||
"""
|
||||
Browse for a new downloads directory
|
||||
@ -778,7 +884,16 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
|
||||
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
|
||||
settings.set('shutdown_timeout', self.shutdown_timeout_checkbox.isChecked())
|
||||
|
||||
# Complicated logic here to force v2 onion mode on or off depending on other settings
|
||||
if self.use_legacy_v2_onions_checkbox.isChecked():
|
||||
use_legacy_v2_onions = True
|
||||
else:
|
||||
use_legacy_v2_onions = False
|
||||
|
||||
if self.save_private_key_checkbox.isChecked():
|
||||
# force the legacy mode on
|
||||
use_legacy_v2_onions = True
|
||||
settings.set('save_private_key', True)
|
||||
settings.set('private_key', self.old_settings.get('private_key'))
|
||||
settings.set('slug', self.old_settings.get('slug'))
|
||||
@ -789,6 +904,18 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
settings.set('slug', '')
|
||||
# Also unset the HidServAuth if we are removing our reusable private key
|
||||
settings.set('hidservauth_string', '')
|
||||
|
||||
if use_legacy_v2_onions:
|
||||
settings.set('use_legacy_v2_onions', True)
|
||||
else:
|
||||
settings.set('use_legacy_v2_onions', False)
|
||||
# If we are not using legacy mode, but we previously had persistence turned on, force it off!
|
||||
settings.set('save_private_key', False)
|
||||
settings.set('private_key', '')
|
||||
settings.set('slug', '')
|
||||
# Also unset the HidServAuth if we are removing our reusable private key
|
||||
settings.set('hidservauth_string', '')
|
||||
|
||||
settings.set('downloads_dir', self.downloads_dir_lineedit.text())
|
||||
settings.set('public_mode', self.public_mode_checkbox.isChecked())
|
||||
settings.set('use_stealth', self.stealth_checkbox.isChecked())
|
||||
|
@ -72,17 +72,16 @@
|
||||
"error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.",
|
||||
"error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.",
|
||||
"gui_settings_window_title": "Settings",
|
||||
"gui_settings_stealth_label": "Stealth (advanced)",
|
||||
"gui_settings_stealth_option": "Create stealth onion services",
|
||||
"gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">More information</a>.",
|
||||
"gui_settings_whats_this": "<a href='{0:s}'>what's this?</a>",
|
||||
"gui_settings_stealth_option": "Create stealth onion services (legacy)",
|
||||
"gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.",
|
||||
"gui_settings_autoupdate_label": "Check for upgrades",
|
||||
"gui_settings_autoupdate_option": "Notify me when upgrades are available",
|
||||
"gui_settings_autoupdate_timestamp": "Last checked: {}",
|
||||
"gui_settings_autoupdate_timestamp_never": "Never",
|
||||
"gui_settings_autoupdate_check_button": "Check For Upgrades",
|
||||
"gui_settings_general_label": "General options",
|
||||
"gui_settings_sharing_label": "Sharing options",
|
||||
"gui_settings_general_label": "General settings",
|
||||
"gui_settings_sharing_label": "Sharing settings",
|
||||
"gui_settings_close_after_first_download_option": "Stop sharing after first download",
|
||||
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
|
||||
"gui_settings_connection_type_bundled_option": "Use the Tor version that is bundled with OnionShare",
|
||||
@ -93,7 +92,7 @@
|
||||
"gui_settings_control_port_label": "Control port",
|
||||
"gui_settings_socket_file_label": "Socket file",
|
||||
"gui_settings_socks_label": "SOCKS port",
|
||||
"gui_settings_authenticate_label": "Tor authentication options",
|
||||
"gui_settings_authenticate_label": "Tor authentication settings",
|
||||
"gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication",
|
||||
"gui_settings_authenticate_password_option": "Password",
|
||||
"gui_settings_password_label": "Password",
|
||||
@ -125,6 +124,7 @@
|
||||
"settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}",
|
||||
"settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}",
|
||||
"error_tor_protocol_error": "Could not communicate with the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.",
|
||||
"error_invalid_private_key": "This private key type is unsupported",
|
||||
"connecting_to_tor": "Connecting to the Tor network",
|
||||
"update_available": "A new version of OnionShare is available. <a href='{}'>Click here</a> to download it.<br><br>Installed version: {}<br>Latest version: {}",
|
||||
"update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.",
|
||||
@ -139,7 +139,8 @@
|
||||
"gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.",
|
||||
"gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.",
|
||||
"share_via_onionshare": "Share via OnionShare",
|
||||
"gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)",
|
||||
"gui_use_legacy_v2_onions_checkbox": "Use legacy addresses",
|
||||
"gui_save_private_key_checkbox": "Use a persistent address (legacy)",
|
||||
"gui_share_url_description": "<b>Anyone</b> with this link can <b>download</b> your files using the <b>Tor Browser</b>: <img src='{}' />",
|
||||
"gui_receive_url_description": "<b>Anyone</b> with this link can <b>upload</b> files to your computer using the <b>Tor Browser</b>: <img src='{}' />",
|
||||
"gui_url_label_persistent": "This share will not expire automatically unless a timer is set.<br><br>Every share will have the same address (to use one-time addresses, disable persistence in Settings)",
|
||||
@ -156,6 +157,8 @@
|
||||
"gui_file_info_single": "{} File, {}",
|
||||
"info_in_progress_downloads_tooltip": "{} download(s) in progress",
|
||||
"info_completed_downloads_tooltip": "{} download(s) completed",
|
||||
"info_in_progress_uploads_tooltip": "{} upload(s) in progress",
|
||||
"info_completed_uploads_tooltip": "{} upload(s) completed",
|
||||
"error_cannot_create_downloads_dir": "Error creating downloads folder: {}",
|
||||
"error_downloads_dir_not_writable": "The downloads folder isn't writable: {}",
|
||||
"receive_mode_downloads_dir": "Files people send you will appear in this folder: {}",
|
||||
@ -165,10 +168,10 @@
|
||||
"receive_mode_received_file": "Received file: {}",
|
||||
"gui_mode_share_button": "Share Files",
|
||||
"gui_mode_receive_button": "Receive Files",
|
||||
"gui_settings_receiving_label": "Receiving options",
|
||||
"gui_settings_receiving_label": "Receiving settings",
|
||||
"gui_settings_downloads_label": "Save files to",
|
||||
"gui_settings_downloads_button": "Browse",
|
||||
"gui_settings_public_mode_checkbox": "OnionShare is open to the public\n(don't prevent people from guessing the OnionShare address)",
|
||||
"gui_settings_public_mode_checkbox": "Public mode",
|
||||
"systray_close_server_title": "OnionShare Server Closed",
|
||||
"systray_close_server_message": "A user closed the server",
|
||||
"systray_page_loaded_title": "OnionShare Page Loaded",
|
||||
|
@ -2,5 +2,5 @@
|
||||
Package3: onionshare
|
||||
Depends3: python3-flask, python3-stem, python3-pyqt5, python-nautilus, tor, obfs4proxy
|
||||
Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5
|
||||
Suite: xenial
|
||||
X-Python3-Version: >= 3.4
|
||||
Suite: bionic
|
||||
X-Python3-Version: >= 3.6
|
||||
|
@ -59,6 +59,7 @@ class TestSettings:
|
||||
'tor_bridges_use_obfs4': False,
|
||||
'tor_bridges_use_meek_lite_azure': False,
|
||||
'tor_bridges_use_custom_bridges': '',
|
||||
'use_legacy_v2_onions': False,
|
||||
'save_private_key': False,
|
||||
'private_key': '',
|
||||
'slug': '',
|
||||
|
Loading…
Reference in New Issue
Block a user