Remove the whole onionkey module, and generate onion keys using stem 1.7.0

This commit is contained in:
Micah Lee 2018-10-11 19:38:05 -07:00
parent 48654895ea
commit 1be53c633a
No known key found for this signature in database
GPG Key ID: 403C2657CD994F73
3 changed files with 30 additions and 141 deletions

View File

@ -22,6 +22,6 @@ 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

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.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]))
@ -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(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