mirror of
https://github.com/onionshare/onionshare.git
synced 2024-10-01 01:35:40 -04:00
Introduce v3 onion support
This commit is contained in:
parent
f5ccfcf2cc
commit
2de9359629
@ -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
|
||||
|
4
BUILD.md
4
BUILD.md
@ -11,9 +11,9 @@ cd onionshare
|
||||
|
||||
Install the needed dependencies:
|
||||
|
||||
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`
|
||||
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; pip3 install pysha3`
|
||||
|
||||
For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4`
|
||||
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; pip3 install pysha3`
|
||||
|
||||
After that you can try both the CLI and the GUI version of OnionShare:
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
click==6.7
|
||||
cryptography==2.1.4
|
||||
Flask==0.12.2
|
||||
future==0.16.0
|
||||
itsdangerous==0.24
|
||||
@ -8,6 +9,9 @@ pefile==2017.11.5
|
||||
PyInstaller==3.3.1
|
||||
PyQt5==5.9.2
|
||||
PySocks==1.6.7
|
||||
pynacl==1.2.1
|
||||
pycrypto==2.6.1
|
||||
pysha3==1.0.2
|
||||
sip==4.19.6
|
||||
stem==1.6.0
|
||||
Werkzeug==0.14.1
|
||||
|
@ -1,4 +1,5 @@
|
||||
click==6.7
|
||||
cryptography==2.1.4
|
||||
Flask==0.12.2
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
@ -6,6 +7,9 @@ MarkupSafe==1.0
|
||||
PyInstaller==3.3.1
|
||||
PyQt5==5.9.2
|
||||
PySocks==1.6.7
|
||||
pycrypto==2.6.1
|
||||
pynacl==1.2.1
|
||||
pysha3==1.0.2
|
||||
sip==4.19.6
|
||||
stem==1.6.0
|
||||
Werkzeug==0.14.1
|
||||
|
@ -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 socks
|
||||
from . import common, strings
|
||||
from .settings import Settings
|
||||
@ -444,20 +446,49 @@ 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')
|
||||
try:
|
||||
# is the key a v2 key?
|
||||
key = onionkey.is_v2_key(self.settings.get('private_key'))
|
||||
key_type = "RSA1024"
|
||||
key_content = self.settings.get('private_key')
|
||||
# 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
|
||||
except:
|
||||
pass
|
||||
# Assume it was a v3 key
|
||||
# key_type = "ED25519-V3"
|
||||
# key_content = self.settings.get('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.3'):
|
||||
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":
|
||||
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'))
|
||||
@ -468,7 +499,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
|
||||
|
126
onionshare/onionkey.py
Normal file
126
onionshare/onionkey.py
Normal file
@ -0,0 +1,126 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
b = 256
|
||||
|
||||
def bit(h, i):
|
||||
return (h[i // 8] >> (i % 8)) & 1
|
||||
|
||||
|
||||
def encodeint(y):
|
||||
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 H(m):
|
||||
return hashlib.sha512(m).digest()
|
||||
|
||||
|
||||
def expandSK(sk):
|
||||
h = H(sk)
|
||||
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 encodeint(a) + k
|
||||
|
||||
|
||||
def onion_url_from_private_key(private_key):
|
||||
"""
|
||||
Derives the public key (.onion hostname) from a v3-style
|
||||
Onion private key.
|
||||
"""
|
||||
private_key = nacl.signing.SigningKey(seed=private_key)
|
||||
pubkey = bytes(private_key.verify_key)
|
||||
version = b'\x03'
|
||||
checksum = hashlib.sha3_256(b".onion checksum" + pubkey + version).digest()[:2]
|
||||
onion_address = "http://{}.onion".format(base64.b32encode(pubkey + 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)
|
||||
"""
|
||||
secretKey = os.urandom(32)
|
||||
expandedSecretKey = expandSK(secretKey)
|
||||
private_key = base64.b64encode(expandedSecretKey).decode()
|
||||
return (private_key, onion_url_from_private_key(secretKey))
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
@ -88,7 +89,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'])
|
||||
|
||||
@ -162,7 +163,13 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
else:
|
||||
self.url_description.setToolTip(strings._('gui_url_label_stay_open', True))
|
||||
|
||||
self.url.setText(self.get_url())
|
||||
# Wrap the Onion URL if it's a big v3 one
|
||||
url_length=len(self.get_url())
|
||||
if url_length > 60:
|
||||
wrapped_onion_url = textwrap.fill(self.get_url(), 50)
|
||||
self.url.setText(wrapped_onion_url)
|
||||
else:
|
||||
self.url.setText(self.get_url())
|
||||
self.url.show()
|
||||
|
||||
self.copy_url_button.show()
|
||||
|
@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
import sys, platform, datetime, re
|
||||
from distutils.version import LooseVersion as Version
|
||||
|
||||
from onionshare import strings, common
|
||||
from onionshare.settings import Settings
|
||||
@ -64,7 +65,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True))
|
||||
|
||||
# Whether or not to save the Onion private key for reuse
|
||||
# Whether or not to save the Onion private key for reuse (persistent URLs)
|
||||
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))
|
||||
@ -421,6 +422,9 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
# Using persistent URLs with v3 onions is not yet stable
|
||||
if Version(self.onion.tor_version) >= Version('0.3.2.9'):
|
||||
self.save_private_key_checkbox.hide()
|
||||
|
||||
downloads_dir = self.old_settings.get('downloads_dir')
|
||||
self.downloads_dir_lineedit.setText(downloads_dir)
|
||||
@ -445,6 +449,9 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
self.hidservauth_copy_button.show()
|
||||
else:
|
||||
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
# Using Client Auth with v3 onions is not yet possible
|
||||
if Version(self.onion.tor_version) >= Version('0.3.2.9'):
|
||||
stealth_group.hide()
|
||||
|
||||
use_autoupdate = self.old_settings.get('use_autoupdate')
|
||||
if use_autoupdate:
|
||||
|
Loading…
Reference in New Issue
Block a user