mirror of
https://github.com/onionshare/onionshare.git
synced 2024-12-28 16:59:35 -05:00
Merge pull request #806 from micahflee/v3-revisited
Change how v3 onion services work to support Debian Stretch
This commit is contained in:
commit
b2a98fb5e7
@ -3,12 +3,18 @@
|
|||||||
# Check https://circleci.com/docs/2.0/language-python/ for more details
|
# Check https://circleci.com/docs/2.0/language-python/ for more details
|
||||||
#
|
#
|
||||||
version: 2
|
version: 2
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
test:
|
||||||
|
jobs:
|
||||||
|
- test-3.5
|
||||||
|
- test-3.6
|
||||||
|
- test-3.7
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
test-3.5: &test-template
|
||||||
docker:
|
docker:
|
||||||
# specify the version you desire here
|
- image: circleci/python:3.5.6
|
||||||
# use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers`
|
|
||||||
- image: circleci/python:3.6.6
|
|
||||||
|
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
|
|
||||||
@ -18,7 +24,8 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: install dependencies
|
name: install dependencies
|
||||||
command: |
|
command: |
|
||||||
sudo apt-get update && sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy
|
||||||
sudo pip3 install -r install/requirements.txt
|
sudo pip3 install -r install/requirements.txt
|
||||||
sudo pip3 install -r install/requirements-tests.txt
|
sudo pip3 install -r install/requirements-tests.txt
|
||||||
sudo pip3 install pytest-cov flake8
|
sudo pip3 install pytest-cov flake8
|
||||||
@ -37,3 +44,12 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
xvfb-run pytest --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/
|
xvfb-run pytest --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/
|
||||||
|
|
||||||
|
test-3.6:
|
||||||
|
<<: *test-template
|
||||||
|
docker:
|
||||||
|
- image: circleci/python:3.6.6
|
||||||
|
|
||||||
|
test-3.7:
|
||||||
|
<<: *test-template
|
||||||
|
docker:
|
||||||
|
- image: circleci/python:3.7.1
|
||||||
|
12
BUILD.md
12
BUILD.md
@ -11,11 +11,17 @@ cd onionshare
|
|||||||
|
|
||||||
Install the needed dependencies:
|
Install the needed dependencies:
|
||||||
|
|
||||||
For Debian-like distros: `apt install -y python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python`
|
For Debian-like distros:
|
||||||
|
|
||||||
On some older versions of Debian you may need to install pysha3 with `pip3 install pysha3` if python3-sha3 is not available.
|
```
|
||||||
|
apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python
|
||||||
|
```
|
||||||
|
|
||||||
For Fedora-like distros: `dnf install -y python3-flask python3-stem python3-qt5 python3-pynacl python3-cryptography python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build`
|
For Fedora-like distros:
|
||||||
|
|
||||||
|
```
|
||||||
|
dnf install -y python3-flask python3-stem python3-qt5 python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build
|
||||||
|
```
|
||||||
|
|
||||||
After that you can try both the CLI and the GUI version of OnionShare:
|
After that you can try both the CLI and the GUI version of OnionShare:
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ VERSION=`cat share/version.txt`
|
|||||||
rm -r build dist >/dev/null 2>&1
|
rm -r build dist >/dev/null 2>&1
|
||||||
|
|
||||||
# build binary package
|
# build binary package
|
||||||
python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-pynacl, python3-cryptography, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4"
|
python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-cryptography, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4"
|
||||||
|
|
||||||
# install it
|
# install it
|
||||||
echo ""
|
echo ""
|
||||||
|
@ -16,12 +16,11 @@ pefile==2018.8.8
|
|||||||
pycparser==2.18
|
pycparser==2.18
|
||||||
pycryptodome==3.6.6
|
pycryptodome==3.6.6
|
||||||
PyInstaller==3.4
|
PyInstaller==3.4
|
||||||
PyNaCl==1.2.1
|
|
||||||
PyQt5==5.11.2
|
PyQt5==5.11.2
|
||||||
PyQt5-sip==4.19.12
|
PyQt5-sip==4.19.12
|
||||||
PySocks==1.6.8
|
PySocks==1.6.8
|
||||||
requests==2.19.1
|
requests==2.19.1
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
stem==1.6.0
|
stem==1.7.0
|
||||||
urllib3==1.23
|
urllib3==1.23
|
||||||
Werkzeug==0.14.1
|
Werkzeug==0.14.1
|
||||||
|
@ -21,10 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
from stem.control import Controller
|
from stem.control import Controller
|
||||||
from stem import ProtocolError, SocketClosed
|
from stem import ProtocolError, SocketClosed
|
||||||
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
|
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
import base64, 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 distutils.version import LooseVersion as Version
|
||||||
from . import onionkey
|
|
||||||
from . import common, strings
|
from . import common, strings
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
|
|
||||||
@ -441,8 +441,7 @@ class Onion(object):
|
|||||||
|
|
||||||
if self.settings.get('private_key'):
|
if self.settings.get('private_key'):
|
||||||
key_content = self.settings.get('private_key')
|
key_content = self.settings.get('private_key')
|
||||||
# is the key a v2 key?
|
if self.is_v2_key(key_content):
|
||||||
if onionkey.is_v2_key(key_content):
|
|
||||||
key_type = "RSA1024"
|
key_type = "RSA1024"
|
||||||
# The below section is commented out because re-publishing
|
# The below section is commented out because re-publishing
|
||||||
# a pre-prepared v3 private key is currently unstable in Tor.
|
# a pre-prepared v3 private key is currently unstable in Tor.
|
||||||
@ -458,29 +457,33 @@ class Onion(object):
|
|||||||
# key_type = "ED25519-V3"
|
# key_type = "ED25519-V3"
|
||||||
else:
|
else:
|
||||||
raise TorErrorProtocolError(strings._('error_invalid_private_key'))
|
raise TorErrorProtocolError(strings._('error_invalid_private_key'))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
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 Version(self.tor_version) >= Version('0.3.3.1') and not self.settings.get('use_legacy_v2_onions'):
|
if Version(self.tor_version) >= Version('0.3.3.1') and not self.settings.get('use_legacy_v2_onions'):
|
||||||
key_type = "ED25519-V3"
|
key_content = "ED25519-V3"
|
||||||
key_content = onionkey.generate_v3_private_key()[0]
|
|
||||||
else:
|
else:
|
||||||
# fall back to v2 onion services
|
# fall back to v2 onion services
|
||||||
key_type = "RSA1024"
|
key_content = "RSA1024"
|
||||||
key_content = onionkey.generate_v2_private_key()[0]
|
|
||||||
|
|
||||||
# v3 onions don't yet support basic auth. Our ticket:
|
# v3 onions don't yet support basic auth. Our ticket:
|
||||||
# https://github.com/micahflee/onionshare/issues/697
|
# https://github.com/micahflee/onionshare/issues/697
|
||||||
if key_type == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'):
|
if key_type == "NEW" and key_content == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'):
|
||||||
basic_auth = None
|
basic_auth = None
|
||||||
self.stealth = False
|
self.stealth = False
|
||||||
|
|
||||||
self.common.log('Onion', 'start_onion_service', 'key_type={}'.format(key_type))
|
debug_message = 'key_type={}'.format(key_type)
|
||||||
|
if key_type == "NEW":
|
||||||
|
debug_message += ', key_content={}'.format(key_content)
|
||||||
|
self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message))
|
||||||
|
await_publication = True
|
||||||
try:
|
try:
|
||||||
if basic_auth != None:
|
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=await_publication, basic_auth=basic_auth, key_type=key_type, key_content=key_content)
|
||||||
else:
|
else:
|
||||||
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
|
# 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=await_publication, key_type=key_type, key_content=key_content)
|
||||||
|
|
||||||
except ProtocolError as e:
|
except ProtocolError as e:
|
||||||
raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0]))
|
raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0]))
|
||||||
@ -491,7 +494,7 @@ class Onion(object):
|
|||||||
# A new private key was generated and is in the Control port response.
|
# A new private key was generated and is in the Control port response.
|
||||||
if self.settings.get('save_private_key'):
|
if self.settings.get('save_private_key'):
|
||||||
if not self.settings.get('private_key'):
|
if not self.settings.get('private_key'):
|
||||||
self.settings.set('private_key', key_content)
|
self.settings.set('private_key', res.private_key)
|
||||||
|
|
||||||
if self.stealth:
|
if self.stealth:
|
||||||
# Similar to the PrivateKey, the Control port only returns the ClientAuth
|
# Similar to the PrivateKey, the Control port only returns the ClientAuth
|
||||||
@ -575,3 +578,18 @@ class Onion(object):
|
|||||||
return ('127.0.0.1', 9150)
|
return ('127.0.0.1', 9150)
|
||||||
else:
|
else:
|
||||||
return (self.settings.get('socks_address'), self.settings.get('socks_port'))
|
return (self.settings.get('socks_address'), self.settings.get('socks_port'))
|
||||||
|
|
||||||
|
def is_v2_key(self, 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
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
# -*- 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
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
Package3: onionshare
|
Package3: onionshare
|
||||||
Depends3: python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-nacl, python3-socks, python-nautilus, tor, obfs4proxy
|
Depends3: python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy
|
||||||
Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-nacl, python3-socks, python-nautilus, tor, obfs4proxy
|
Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy
|
||||||
Suite: bionic
|
Suite: bionic
|
||||||
X-Python3-Version: >= 3.6
|
X-Python3-Version: >= 3.5.3
|
||||||
|
Loading…
Reference in New Issue
Block a user