From a517ad3aeed385a0fec3adae505215064d6600a4 Mon Sep 17 00:00:00 2001 From: Kevin F Date: Fri, 23 Sep 2022 17:31:34 -0400 Subject: [PATCH] Mostly finished with wot command processing --- requirements.in => base-requirements.in | 5 - scripts/passphrase-generator.py | 21 ---- src/onionrcommands/pubkeymanager.py | 95 ------------------- src/onionrutils/stringvalidators.py | 42 -------- src/utils/sizeutils.py | 1 - .../wot/wot/identity/__init__.py | 3 - .../wot/identity/processidentityannounce.py | 28 ------ .../wot/wot/identityprocessing/__init__.py | 4 + .../processidentityannounce.py | 17 ++++ .../processrevokeidentity.py | 21 ++++ .../processrevokesignature.py | 0 .../processtrustsignature.py | 1 + .../wot/test_process_announce_identity.py | 17 ++-- .../wot/test_process_identity_revoke.py | 68 +++++++++++++ .../wot/test_process_revoke_signature.py | 6 +- 15 files changed, 122 insertions(+), 207 deletions(-) rename requirements.in => base-requirements.in (52%) delete mode 100755 scripts/passphrase-generator.py delete mode 100755 src/onionrcommands/pubkeymanager.py delete mode 100644 src/onionrutils/stringvalidators.py delete mode 100644 static-data/default-plugins/wot/wot/identity/processidentityannounce.py create mode 100644 static-data/default-plugins/wot/wot/identityprocessing/__init__.py create mode 100644 static-data/default-plugins/wot/wot/identityprocessing/processidentityannounce.py create mode 100644 static-data/default-plugins/wot/wot/identityprocessing/processrevokeidentity.py rename static-data/default-plugins/wot/wot/{identity => identityprocessing}/processrevokesignature.py (100%) rename static-data/default-plugins/wot/wot/{identity => identityprocessing}/processtrustsignature.py (98%) create mode 100644 tests/default-plugin-tests/wot/test_process_identity_revoke.py diff --git a/requirements.in b/base-requirements.in similarity index 52% rename from requirements.in rename to base-requirements.in index 4aa5fd2d..0f0531e0 100644 --- a/requirements.in +++ b/base-requirements.in @@ -1,13 +1,8 @@ PyNaCl==1.5.0 -PySocks==1.7.1 -stem==1.8.0 -unpaddedbase32==0.2.0 -niceware==0.2.1 psutil==5.9.1 filenuke==0.0.0 ujson==5.4.0 cffi==1.15.1 onionrblocks==7.0.0 ordered-set==4.1.0 -cherrypy==18.8.0 json-rpc==1.13.0 \ No newline at end of file diff --git a/scripts/passphrase-generator.py b/scripts/passphrase-generator.py deleted file mode 100755 index d0d98dad..00000000 --- a/scripts/passphrase-generator.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 - -"""Generate a 16 word passphase with 256 bits of entropy. - -Specify true to reduce to 128 bits""" - - -import sys - -import niceware - -byte_count = 32 # 256 bits of entropy with niceware - -arg = False -try: - arg = sys.argv[1].lower() - if arg == 'true': - byte_count = 16 -except IndexError: pass - -print(' '.join(niceware.generate_passphrase(byte_count))) diff --git a/src/onionrcommands/pubkeymanager.py b/src/onionrcommands/pubkeymanager.py deleted file mode 100755 index 523c648a..00000000 --- a/src/onionrcommands/pubkeymanager.py +++ /dev/null @@ -1,95 +0,0 @@ -"""Onionr - Private P2P Communication. - -This module defines user ID-related CLI commands -""" -import sys -import getpass - -import unpaddedbase32 -import niceware - -import logger -import onionrexceptions -from onionrutils import stringvalidators, bytesconverter -import config -import keymanager -import onionrcrypto -import onionrvalues -""" -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 . -""" - - -DETERMINISTIC_REQUIREMENT = onionrvalues.PASSWORD_LENGTH - - -def add_ID(): - """Command to create a new user ID key pair.""" - key_manager = keymanager.KeyManager() - pw = "" - try: - sys.argv[2] # pylint: disable=W0104 - if not sys.argv[2].lower() == 'true': - raise ValueError - except (IndexError, ValueError): - newID = key_manager.addKey()[0] - else: - pw = "-".join(niceware.generate_passphrase(32)) - newID, privKey = onionrcrypto.generate_deterministic(pw) - try: - key_manager.addKey(pubKey=newID, - privKey=privKey) - except ValueError: - logger.error( - 'That ID is already available, you can change to it ' + - 'with the change-id command.', terminal=True) - return - if pw: - print("Phrase to restore ID:", pw) - logger.info('Added ID: %s' % - (bytesconverter.bytes_to_str(newID.replace('=', '')),), terminal=True) - - -add_ID.onionr_help = "If the first argument is true, " # type: ignore -add_ID.onionr_help += "Onionr will show a deterministic " # type: ignore -add_ID.onionr_help += "generation prompt. Otherwise it will " # type: ignore -add_ID.onionr_help += "generate & save a new random key pair." # type: ignore - - -def change_ID(): - """Command to change active ID from argv or stdin.""" - key_manager = keymanager.KeyManager() - try: - key = sys.argv[2] - key = unpaddedbase32.repad(key.encode()).decode() - except IndexError: - logger.warn('Specify pubkey to use', terminal=True) - else: - if stringvalidators.validate_pub_key(key): - key_list = key_manager.getPubkeyList() - if key in key_list or key.replace('=', '') in key_list: - config.set('general.public_key', key) - config.save() - logger.info('Set active key to: %s' % (key,), terminal=True) - logger.info('Restart Onionr if it is running.', terminal=True) - else: - logger.warn('That key does not exist', terminal=True) - else: - logger.warn('Invalid key %s' % (key,), terminal=True) - - -change_ID.onionr_help = ": Switches Onionr to " # type: ignore -change_ID.onionr_help += "use a different user ID key. " # type: ignore -change_ID.onionr_help += "You should immediately restart " # type: ignore -change_ID.onionr_help += "Onionr if it is running." # type: ignore diff --git a/src/onionrutils/stringvalidators.py b/src/onionrutils/stringvalidators.py deleted file mode 100644 index 8dbc3173..00000000 --- a/src/onionrutils/stringvalidators.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Onionr - Private P2P Communication - -validate various string data types -""" -import base64 -import string -import unpaddedbase32, nacl.signing, nacl.encoding -from onionrutils import bytesconverter -""" -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 . -""" - - -def validate_pub_key(key): - """Validate if a string is a valid base32 encoded Ed25519 key""" - if type(key) is type(None): - return False - # Accept keys that have no = padding - key = unpaddedbase32.repad(bytesconverter.str_to_bytes(key)) - - retVal = False - try: - nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) - except nacl.exceptions.ValueError: - pass - except base64.binascii.Error as _: - pass - else: - retVal = True - return retVal diff --git a/src/utils/sizeutils.py b/src/utils/sizeutils.py index 1199f2b1..3ecf08a4 100644 --- a/src/utils/sizeutils.py +++ b/src/utils/sizeutils.py @@ -3,7 +3,6 @@ size related utilities """ import os -from onionrutils import stringvalidators """ 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 diff --git a/static-data/default-plugins/wot/wot/identity/__init__.py b/static-data/default-plugins/wot/wot/identity/__init__.py index 668b8ba9..f4344184 100644 --- a/static-data/default-plugins/wot/wot/identity/__init__.py +++ b/static-data/default-plugins/wot/wot/identity/__init__.py @@ -7,9 +7,6 @@ from nacl.signing import SigningKey, VerifyKey from nacl.encoding import Base32Encoder from nacl.exceptions import BadSignatureError -from wot.identity.processtrustsignature import process_trust_signature -from wot.identity.processrevokesignature import process_revoke_signature -from wot.identity.processidentityannounce import process_identity_announce from wot.identity.name import IdentityName from wot.identity.name import max_len as max_name_len from wot.identity.identityset import IdentitySet, identities diff --git a/static-data/default-plugins/wot/wot/identity/processidentityannounce.py b/static-data/default-plugins/wot/wot/identity/processidentityannounce.py deleted file mode 100644 index 5fc9f304..00000000 --- a/static-data/default-plugins/wot/wot/identity/processidentityannounce.py +++ /dev/null @@ -1,28 +0,0 @@ -import logger - -from nacl.signing import VerifyKey - -from wot.blockprocessingevent import WotCommand -from wot.identity.identityset import identities - -def process_identity_announce(identity_announce_payload): - if len(identity_announce_payload) != 97: - logger.warn( - f'Identity announce signature size is invalid', - terminal=True) - - # verify that this is a signature for an announce command - if identity_announce_payload[0] != WotCommand.ANNOUNCE: - logger.warn( - f'Invalid command in signature' , terminal=True) - return - # signer is first 32 bytes - signer = identity_announce_payload[1:33] - # signature is last 64 bytes - signature = identity_announce_payload[33:] - - # If bad signature, it raises nacl.exceptions.BadSignatureError - VerifyKey(signer).verify(identity_announce_payload[0] + signer, signature) - - # noop if already announced - identities.add \ No newline at end of file diff --git a/static-data/default-plugins/wot/wot/identityprocessing/__init__.py b/static-data/default-plugins/wot/wot/identityprocessing/__init__.py new file mode 100644 index 00000000..daa036c8 --- /dev/null +++ b/static-data/default-plugins/wot/wot/identityprocessing/__init__.py @@ -0,0 +1,4 @@ +from .processidentityannounce import process_identity_announce +from .processrevokesignature import process_revoke_signature +from .processtrustsignature import process_trust_signature +from .processrevokeidentity import process_identity_revoke \ No newline at end of file diff --git a/static-data/default-plugins/wot/wot/identityprocessing/processidentityannounce.py b/static-data/default-plugins/wot/wot/identityprocessing/processidentityannounce.py new file mode 100644 index 00000000..f1406f47 --- /dev/null +++ b/static-data/default-plugins/wot/wot/identityprocessing/processidentityannounce.py @@ -0,0 +1,17 @@ +import logger + +from nacl.signing import VerifyKey + +from wot.blockprocessingevent import WotCommand +from wot.identity import Identity +from wot.identity.identityset import identities + +def process_identity_announce(identity_announce_payload): + + # verify that this is a signature for an announce command + if identity_announce_payload[0] != WotCommand.ANNOUNCE: + logger.warn( + f'Invalid command in signature' , terminal=True) + return + iden = Identity.deserialize(identity_announce_payload[1:]) + identities.add(iden) \ No newline at end of file diff --git a/static-data/default-plugins/wot/wot/identityprocessing/processrevokeidentity.py b/static-data/default-plugins/wot/wot/identityprocessing/processrevokeidentity.py new file mode 100644 index 00000000..d538405a --- /dev/null +++ b/static-data/default-plugins/wot/wot/identityprocessing/processrevokeidentity.py @@ -0,0 +1,21 @@ +import logger + +from nacl.signing import VerifyKey + +from wot.blockprocessingevent import WotCommand +from wot.identity import Identity +from wot.identity.identityset import identities + +def process_identity_revoke(revoke_payload: bytes): + wot_cmd = revoke_payload[0].to_bytes(1, 'big') + if revoke_payload[0] != WotCommand.REVOKE: + logger.warn( + f'Invalid command in signature', terminal=True) + return + revoked_identity = revoke_payload[1:33] + signature = revoke_payload[33:] + + # raises nacl.exceptions.BadSignatureError if bad signature + VerifyKey(revoked_identity).verify(wot_cmd + revoked_identity, signature) + + identities.remove(Identity(revoked_identity, "etc")) diff --git a/static-data/default-plugins/wot/wot/identity/processrevokesignature.py b/static-data/default-plugins/wot/wot/identityprocessing/processrevokesignature.py similarity index 100% rename from static-data/default-plugins/wot/wot/identity/processrevokesignature.py rename to static-data/default-plugins/wot/wot/identityprocessing/processrevokesignature.py diff --git a/static-data/default-plugins/wot/wot/identity/processtrustsignature.py b/static-data/default-plugins/wot/wot/identityprocessing/processtrustsignature.py similarity index 98% rename from static-data/default-plugins/wot/wot/identity/processtrustsignature.py rename to static-data/default-plugins/wot/wot/identityprocessing/processtrustsignature.py index f76babbd..c07ffc8a 100644 --- a/static-data/default-plugins/wot/wot/identity/processtrustsignature.py +++ b/static-data/default-plugins/wot/wot/identityprocessing/processtrustsignature.py @@ -11,6 +11,7 @@ def process_trust_signature(sig_payload: bytes): if len(sig_payload) != 129: logger.warn( f'Signature size is invalid for a signed identity') + return # verify that this is a signature for a trust command if sig_payload[0] != WotCommand.TRUST: diff --git a/tests/default-plugin-tests/wot/test_process_announce_identity.py b/tests/default-plugin-tests/wot/test_process_announce_identity.py index 54013599..7343da38 100644 --- a/tests/default-plugin-tests/wot/test_process_announce_identity.py +++ b/tests/default-plugin-tests/wot/test_process_announce_identity.py @@ -15,10 +15,11 @@ os.environ["ONIONR_HOME"] = TEST_DIR import unittest import sys sys.path.append(".") -sys.path.append('static-data/default-plugins/wot/wot') +sys.path.append('static-data/default-plugins/wot/') sys.path.append("src/") -import identity -from identity.identityset import identities +from wot.identityprocessing import process_identity_announce +from wot import identity +from wot.identity.identityset import identities class WotCommand(IntEnum): @@ -34,16 +35,14 @@ class TestAnnounceIdentityPayload(unittest.TestCase): identities.clear() signing_key = SigningKey.generate() - main_iden = identity.Identity(signing_key.verify_key, "test") + main_iden = identity.Identity(signing_key, "test") wot_cmd = int(WotCommand.ANNOUNCE).to_bytes(1, 'big') - announce_signature = signing_key.sign(wot_cmd + bytes(main_iden)) - announce_signature_payload = wot_cmd + bytes(signing_key.verify_key) + \ - bytes(announce_signature) + serialized_iden = wot_cmd + main_iden.serialize() - identity.process_identity_announce(announce_signature_payload) + process_identity_announce(serialized_iden) - self.assertEqual(main_iden, identities[0]) + self.assertEqual(bytes(main_iden.key), bytes(list(identities)[0].key)) self.assertEqual(len(identities), 1) self.assertEqual(len(main_iden.trusted), 0) diff --git a/tests/default-plugin-tests/wot/test_process_identity_revoke.py b/tests/default-plugin-tests/wot/test_process_identity_revoke.py new file mode 100644 index 00000000..555d0b16 --- /dev/null +++ b/tests/default-plugin-tests/wot/test_process_identity_revoke.py @@ -0,0 +1,68 @@ +import os, uuid +from random import randint +from time import sleep +from enum import IntEnum, auto +from nacl.signing import SigningKey, VerifyKey +from nacl.exceptions import BadSignatureError +import nacl +import secrets +import onionrblocks + + +TEST_DIR = 'testdata/%s-%s' % (str(uuid.uuid4())[:6], os.path.basename(__file__)) + '/' +print("Test directory:", TEST_DIR) +os.environ["ONIONR_HOME"] = TEST_DIR + +import unittest +import sys +sys.path.append(".") +sys.path.append('static-data/default-plugins/wot/') +sys.path.append("src/") +from wot.identityprocessing import process_identity_revoke +from wot import identity +from wot.identity.identityset import identities + + +class WotCommand(IntEnum): + TRUST = 1 + REVOKE_TRUST = auto() + ANNOUNCE = auto() + REVOKE = auto() + + +class TestRevokeIdentityPayload(unittest.TestCase): + + def test_revoke_identity_invalid(self): + identities.clear() + + signing_key = SigningKey.generate() + main_iden = identity.Identity(signing_key, "test") + identities.add(main_iden) + + wot_cmd = int(WotCommand.REVOKE).to_bytes(1, 'big') + + signed = signing_key.sign(wot_cmd + bytes(main_iden.key)) + revoke_payload = wot_cmd + bytes(signing_key.verify_key) + signed.signature + + self.assertRaises(nacl.exceptions.Inv process_identity_revoke(revoke_payload) + + self.assertEqual(len(identities), 1) + + def test_revoke_identity_payload(self): + identities.clear() + + signing_key = SigningKey.generate() + main_iden = identity.Identity(signing_key, "test") + identities.add(main_iden) + + wot_cmd = int(WotCommand.REVOKE).to_bytes(1, 'big') + + signed = signing_key.sign(wot_cmd + bytes(main_iden.key)) + revoke_payload = wot_cmd + bytes(signing_key.verify_key) + signed.signature + + process_identity_revoke(revoke_payload) + + self.assertEqual(len(identities), 0) + + +unittest.main() diff --git a/tests/default-plugin-tests/wot/test_process_revoke_signature.py b/tests/default-plugin-tests/wot/test_process_revoke_signature.py index f9feec5b..1025baff 100644 --- a/tests/default-plugin-tests/wot/test_process_revoke_signature.py +++ b/tests/default-plugin-tests/wot/test_process_revoke_signature.py @@ -15,10 +15,10 @@ os.environ["ONIONR_HOME"] = TEST_DIR import unittest import sys sys.path.append(".") -sys.path.append('static-data/default-plugins/wot/wot') +sys.path.append('static-data/default-plugins/wot/') sys.path.append("src/") -import identity -from identity.identityset import identities +from wot import identity +from wot.identity.identityset import identities class WotCommand(IntEnum):