Merge pull request #806 from micahflee/v3-revisited

Change how v3 onion services work to support Debian Stretch
This commit is contained in:
Miguel Jacq 2018-11-26 08:26:33 +11:00 committed by GitHub
commit b2a98fb5e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 65 additions and 155 deletions

View File

@ -3,12 +3,18 @@
# Check https://circleci.com/docs/2.0/language-python/ for more details
#
version: 2
workflows:
version: 2
test:
jobs:
build:
- test-3.5
- test-3.6
- test-3.7
jobs:
test-3.5: &test-template
docker:
# specify the version you desire here
# use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers`
- image: circleci/python:3.6.6
- image: circleci/python:3.5.6
working_directory: ~/repo
@ -18,7 +24,8 @@ jobs:
- run:
name: install dependencies
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-tests.txt
sudo pip3 install pytest-cov flake8
@ -37,3 +44,12 @@ jobs:
command: |
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

View File

@ -11,11 +11,17 @@ cd onionshare
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:

View File

@ -9,7 +9,7 @@ VERSION=`cat share/version.txt`
rm -r build dist >/dev/null 2>&1
# 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
echo ""

View File

@ -16,12 +16,11 @@ pefile==2018.8.8
pycparser==2.18
pycryptodome==3.6.6
PyInstaller==3.4
PyNaCl==1.2.1
PyQt5==5.11.2
PyQt5-sip==4.19.12
PySocks==1.6.8
requests==2.19.1
six==1.11.0
stem==1.6.0
stem==1.7.0
urllib3==1.23
Werkzeug==0.14.1

View File

@ -21,10 +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
from Crypto.PublicKey import RSA
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
@ -441,8 +441,7 @@ class Onion(object):
if self.settings.get('private_key'):
key_content = self.settings.get('private_key')
# is the key a v2 key?
if onionkey.is_v2_key(key_content):
if self.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.
@ -458,29 +457,33 @@ class Onion(object):
# key_type = "ED25519-V3"
else:
raise TorErrorProtocolError(strings._('error_invalid_private_key'))
else:
key_type = "NEW"
# 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'):
key_type = "ED25519-V3"
key_content = onionkey.generate_v3_private_key()[0]
key_content = "ED25519-V3"
else:
# fall back to v2 onion services
key_type = "RSA1024"
key_content = onionkey.generate_v2_private_key()[0]
key_content = "RSA1024"
# 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'):
if key_type == "NEW" and key_content == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'):
basic_auth = None
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:
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:
# 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:
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.
if self.settings.get('save_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:
# Similar to the PrivateKey, the Control port only returns the ClientAuth
@ -575,3 +578,18 @@ class Onion(object):
return ('127.0.0.1', 9150)
else:
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

View File

@ -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

View File

@ -1,6 +1,6 @@
[DEFAULT]
Package3: onionshare
Depends3: 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-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-crypto, python3-socks, python-nautilus, tor, obfs4proxy
Suite: bionic
X-Python3-Version: >= 3.6
X-Python3-Version: >= 3.5.3