Skip to content
Permalink
Browse files
some WIP on HSv3 support
  • Loading branch information
joelanders committed Aug 27, 2019
1 parent 22a604c commit 1d30e6c5076ec2ee17e4b7a2a63ed72d0c32a670
Showing with 83 additions and 26 deletions.
  1. +2 −0 .gitignore
  2. +15 −3 onionbalance/util.py
  3. +2 −1 requirements.txt
  4. +1 −1 setup.cfg
  5. +25 −4 test-requirements.txt
  6. +2 −2 test/scripts/install-chutney.sh
  7. +32 −6 test/test_util.py
  8. +4 −9 tox.ini
@@ -59,3 +59,5 @@ target/

# Coverage files
htmlcov

chutney
@@ -11,6 +11,9 @@

# import Crypto.Util
import Crypto.PublicKey
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.primitives.serialization import PublicFormat

from onionbalance import config

@@ -43,8 +46,13 @@ def calc_permanent_id(rsa_key):
return calc_key_digest(rsa_key)[:10]


def calc_onion_address(rsa_key):
return base64.b32encode(calc_permanent_id(rsa_key)).decode().lower()
def calc_onion_address(key):
if isinstance(key, Ed25519PrivateKey):
pub_key = key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
checksum = hashlib.sha3_256(b".onion checksum" + pub_key + b'\x03').digest()[:2]
address = base32_encode_str(pub_key + checksum + b'\x03')
return address
return base64.b32encode(calc_permanent_id(key)).decode().lower()


def calc_descriptor_id(permanent_id, secret_id_part):
@@ -120,9 +128,13 @@ def key_decrypt_prompt(key_file, retries=3):
"""

key_passphrase = None
with open(key_file, 'rt') as handle:
with open(key_file, 'rb') as handle:
pem_key = handle.read()
hsv3_tag = b"== ed25519v1-secret: type0 =="
if pem_key.startswith(hsv3_tag):
return Ed25519PrivateKey.from_private_bytes(pem_key[32:64])

pem_key = pem_key.decode('utf-8')
for retries in range(0, retries):
if "Proc-Type: 4,ENCRYPTED" in pem_key: # Key looks encrypted
key_passphrase = getpass.getpass(
@@ -1,6 +1,7 @@
stem==1.4.1b
PyYAML==3.11
PyYAML
pycrypto==2.6.1
future==0.14.3
sphinxcontrib-autoprogram==0.1.2
setproctitle==1.1.9
cryptography
@@ -1,4 +1,4 @@
[pytest]
[tool:pytest]
norecursedirs = .tox _build tor chutney

[bdist_wheel]
@@ -1,6 +1,27 @@
pytest
alabaster
appdirs
Babel
coverage
coveralls
docopt
docutils
imagesize
Jinja2
MarkupSafe
mock
pytest-mock
OnionBalance
packaging
pbr
pexpect
coveralls==1.1
pytest-cov==2.2.1
ptyprocess
py
Pygments
pyparsing
pytest==3.10.1
pytest-cov
pytest-mock
pytz
requests
six
snowballstemmer
Sphinx
@@ -16,8 +16,8 @@ do
output=$(./chutney verify networks/hs)
# Check if chutney output included 'Transmission: Success'.
if [[ $output == *"Transmission: Success"* ]]; then
hs_address=$(echo $output | grep -Po "([a-z2-7]{16}.onion:\d{2,5})")
client_address=$(echo $output | grep -Po -m 1 "(localhost:\d{2,5})" | head -n1)
hs_address=$(echo $output | ggrep -Po "([a-z2-7]{16}.onion:\d{2,5})")
client_address=$(echo $output | ggrep -Po -m 1 "(localhost:\d{2,5})" | head -n1)
echo "HS system running with service available at $hs_address"
export CHUTNEY_ONION_ADDRESS="$hs_address"
export CHUTNEY_CLIENT_PORT="$client_address"
@@ -6,12 +6,20 @@
import sys

import Crypto.PublicKey.RSA
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

import pytest
from .util import builtin

from onionbalance.util import *

ED25519_SECRET_KEY = b''.join([
b"== ed25519v1-secret: type0 ==\x00\x00\x00\xd0d\xe96\x10\xbb",
b"\x96u`\xd21u\xe7\xe9\xc2I\t\x8a\xd4\x0e\xdc\x94(\xcb\xd7\xde",
b"\xf7\xac0E[YyY\xf5\xbcU\xba \x04\x06\x134\xf91\x8e(\xa3\xf4",
b"\xa4,\xba\x01a\xbb\x96_,\x8b\xe4b|\x0b\xad",
])


PEM_PRIVATE_KEY = u'\n'.join([
"-----BEGIN RSA PRIVATE KEY-----",
@@ -118,6 +126,17 @@ def test_calc_onion_address():
assert calc_onion_address(PRIVATE_KEY) == u'jyvfq5umznvka34v'


def test_calc_onion_address_hsv3():
key = Ed25519PrivateKey.from_private_bytes(ED25519_SECRET_KEY[32:64]) # XXX guess
assert calc_onion_address(key) == u'd2gtlwwcxmkpdin2d3ya5gsjccmqwbscavxj7bcyfukeduyxwka7zvid'

def test_calc_fucking_onion_address_hsv3():
pub_key = b'\x1e\x8d5\xda\xc2\xbb\x14\xf1\xa1\xba\x1e\xf0\x0e\x9aI\x10\x99\x0b\x06B\x05n\x9f\x84X-\x14A\xd3\x17\xb2\x81'
checksum = hashlib.sha3_256(b".onion checksum" + pub_key + b'\x03').digest()[:2]
address = base32_encode_str(pub_key + checksum + b'\x03')
assert address == u'd2gtlwwcxmkpdin2d3ya5gsjccmqwbscavxj7bcyfukeduyxwka7zvid'


def test_get_time_period():
time_period = get_time_period(
time=UNIX_TIMESTAMP,
@@ -220,24 +239,31 @@ def test_base32_encode_str_not_byte_string():

def test_key_decrypt_prompt(mocker):
# Valid private PEM key
mocker.patch(builtin('open'), lambda *_: io.StringIO(PEM_PRIVATE_KEY))
mocker.patch(builtin('open'), lambda *_: io.BytesIO(PEM_PRIVATE_KEY.encode('utf-8')))
key = key_decrypt_prompt('private.key')
assert isinstance(key, Crypto.PublicKey.RSA._RSAobj)
assert key.has_private()


def test_key_decrypt_prompt_hsv3(mocker):
# Valid secret ed25519 key
mocker.patch(builtin('open'), lambda *_: io.BytesIO(ED25519_SECRET_KEY))
key = key_decrypt_prompt('private.key')
assert isinstance(key, Ed25519PrivateKey)


def test_key_decrypt_prompt_public_key(mocker):
# Valid public PEM key
private_key = Crypto.PublicKey.RSA.importKey(PEM_PRIVATE_KEY)
private_key = Crypto.PublicKey.RSA.importKey(PEM_PRIVATE_KEY.encode('utf-8'))
pem_public_key = private_key.publickey().exportKey().decode('utf-8')
mocker.patch(builtin('open'), lambda *_: io.StringIO(pem_public_key))
mocker.patch(builtin('open'), lambda *_: io.BytesIO(pem_public_key.encode('utf-8')))

with pytest.raises(ValueError):
key_decrypt_prompt('public.key')


def test_key_decrypt_prompt_malformed_key(mocker):
mocker.patch(builtin('open'), lambda *_: io.StringIO(PEM_INVALID_KEY))
mocker.patch(builtin('open'), lambda *_: io.BytesIO(PEM_INVALID_KEY.encode('utf-8')))
with pytest.raises(ValueError):
key_decrypt_prompt('private.key')

@@ -246,13 +272,13 @@ def test_key_decrypt_prompt_incorrect_size(mocker):
# Key which is not 1024 bits
private_key_1280 = Crypto.PublicKey.RSA.generate(1280)
pem_key_1280 = private_key_1280.exportKey().decode('utf-8')
mocker.patch(builtin('open'), lambda *_: io.StringIO(pem_key_1280))
mocker.patch(builtin('open'), lambda *_: io.BytesIO(pem_key_1280.encode('utf-8')))
with pytest.raises(ValueError):
key_decrypt_prompt('512-bit-private.key')


def test_key_decrypt_prompt_encrypted(mocker):
mocker.patch(builtin('open'), lambda *_: io.StringIO(PEM_ENCRYPTED))
mocker.patch(builtin('open'), lambda *_: io.BytesIO(PEM_ENCRYPTED.encode('utf-8')))

# Load with correct password
mocker.patch('getpass.getpass', lambda *_: u'password')
13 tox.ini
@@ -4,7 +4,7 @@
# and then run "tox" from this directory.

[tox]
envlist = style, py27, py35, docs
envlist = style, py37, docs

[testenv]
deps = -rrequirements.txt
@@ -13,17 +13,12 @@ deps = -rrequirements.txt
passenv = CHUTNEY_ONION_ADDRESS CHUTNEY_CLIENT_PORT TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
commands = py.test --cov-report=term-missing --cov=onionbalance {posargs}

[testenv:docs]
basepython=python
changedir=docs
deps=sphinx
sphinxcontrib-autoprogram==0.1.2
commands=
sphinx-build -W -b html -d {envtmpdir}/docs . {envtmpdir}/html

[testenv:style]
basepython=python
deps=pylint
flake8
commands=pylint onionbalance {posargs: -E}
flake8 onionbalance

[pytest]
addopts = -p no:warnings

0 comments on commit 1d30e6c

Please sign in to comment.