diff --git a/install/requirements.txt b/install/requirements.txt index 32ec6887..9c8a4a5a 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -22,6 +22,6 @@ 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 diff --git a/onionshare/onion.py b/onionshare/onion.py index cb73e976..7122c208 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -21,10 +21,10 @@ along with this program. If not, see . 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])) @@ -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(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 diff --git a/onionshare/onionkey.py b/onionshare/onionkey.py deleted file mode 100644 index d2c6ad17..00000000 --- a/onionshare/onionkey.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2017 Micah Lee - -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 . -""" - -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 -