def test_descriptor_creation(self): """ HiddenServiceDescriptorV3 creation. """ # minimal descriptor self.assertTrue(HiddenServiceDescriptorV3.content().startswith( b'hs-descriptor 3\ndescriptor-lifetime 180\n')) self.assertEqual(180, HiddenServiceDescriptorV3.create().lifetime) # specify the parameters desc = HiddenServiceDescriptorV3.create( { 'hs-descriptor': '4', 'descriptor-lifetime': '123', 'descriptor-signing-key-cert': '\n-----BEGIN ED25519 CERT-----\nmalformed block\n-----END ED25519 CERT-----', 'revision-counter': '5', 'superencrypted': '\n-----BEGIN MESSAGE-----\nmalformed block\n-----END MESSAGE-----', 'signature': 'abcde', }, validate=False) self.assertEqual(4, desc.version) self.assertEqual(123, desc.lifetime) self.assertEqual( None, desc.signing_cert ) # malformed cert dropped because validation is disabled self.assertEqual(5, desc.revision_counter) self.assertEqual( '-----BEGIN MESSAGE-----\nmalformed block\n-----END MESSAGE-----', desc.superencrypted) self.assertEqual('abcde', desc.signature) # include introduction points from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey identity_key = Ed25519PrivateKey.generate() onion_address = HiddenServiceDescriptorV3.address_from_identity_key( identity_key) desc = HiddenServiceDescriptorV3.create( identity_key=identity_key, inner_layer=InnerLayer.create(introduction_points=[ IntroductionPointV3.create_for_address('1.1.1.1', 9001), IntroductionPointV3.create_for_address('2.2.2.2', 9001), IntroductionPointV3.create_for_address('3.3.3.3', 9001), ]), ) inner_layer = desc.decrypt(onion_address) self.assertEqual(3, len(inner_layer.introduction_points)) self.assertEqual( '1.1.1.1', inner_layer.introduction_points[0].link_specifiers[0].address)
def load_config_file(self): config_data = util.read_config_data_from_file(self.config_path) logger.debug("Onionbalance config data: %s", config_data) # Do some basic validation if "services" not in config_data: logger.error("Config file is bad. 'services' is missing. Did you make it with onionbalance-config?") sys.exit(1) # More validation for service in config_data["services"]: if "key" not in service: logger.error("Config file is bad. 'key' is missing. Did you make it with onionbalance-config?") sys.exit(1) if "instances" not in service: logger.error("Config file is bad. 'instances' is missing. Did you make it with onionbalance-config?") sys.exit(1) for instance in service["instances"]: if "address" not in instance: logger.error("Config file is wrong. 'address' missing from instance.") sys.exit(1) # Validate that the onion address is legit try: _ = HiddenServiceDescriptorV3.identity_key_from_address(instance["address"]) except ValueError: logger.error("Cannot load instance with address: '%s'.", instance["address"]) logger.error("If you are trying to run onionbalance for v2 onions, please use the --hs-version=v2 switch") sys.exit(1) return config_data
def _load_v3_master_key_from_file(self, master_key_path): """ Load a private key straight from a Tor instance (no OBv3 keys supported) and return the private key and onion address. """ try: with open(master_key_path, 'rb') as handle: pem_key_bytes = handle.read() except EnvironmentError as e: logger.error("Unable to read service private key file ('%s')", e) sys.exit(1) try: master_private_key = tor_ed25519.load_tor_key_from_disk(pem_key_bytes) except ValueError: logger.error("Please provide path to a valid Tor master key") sys.exit(1) identity_pub_key = master_private_key.public_key() identity_pub_key_bytes = identity_pub_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) master_onion_address = HiddenServiceDescriptorV3.address_from_identity_key(identity_pub_key_bytes) # remove the trailing .onion master_onion_address = master_onion_address.replace(".onion", "") self.v3_loaded_key_from_file = True return master_private_key, master_onion_address
def test_outer_layer_creation(self): """ Outer layer creation. """ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey # minimal layer self.assertTrue(OuterLayer.content().startswith(b'desc-auth-type x25519\ndesc-auth-ephemeral-key ')) self.assertEqual('x25519', OuterLayer.create().auth_type) # specify the parameters desc = OuterLayer.create({ 'desc-auth-type': 'foo', 'desc-auth-ephemeral-key': 'bar', 'auth-client': [ 'JNil86N07AA epkaL79NtajmgME/egi8oA qosYH4rXisxda3X7p9b6fw', '1D8VBAh9hdM 6K/uO3sRqBp6URrKC7GB6Q ElwRj5+6SN9kb8bRhiiQvA', ], 'encrypted': '\n-----BEGIN MESSAGE-----\nmalformed block\n-----END MESSAGE-----', }) self.assertEqual('foo', desc.auth_type) self.assertEqual('bar', desc.ephemeral_key) self.assertEqual('-----BEGIN MESSAGE-----\nmalformed block\n-----END MESSAGE-----', desc.encrypted) self.assertEqual({ '1D8VBAh9hdM': AuthorizedClient(id = b'1D8VBAh9hdM', iv = b'6K/uO3sRqBp6URrKC7GB6Q', cookie = b'ElwRj5+6SN9kb8bRhiiQvA'), 'JNil86N07AA': AuthorizedClient(id = b'JNil86N07AA', iv = b'epkaL79NtajmgME/egi8oA', cookie = b'qosYH4rXisxda3X7p9b6fw'), }, desc.clients) self.assertEqual(EXPECTED_OUTER_LAYER, str(desc)) # create an inner layer then decrypt it revision_counter = 5 blinded_key = stem.util._pubkey_bytes(Ed25519PrivateKey.generate()) subcredential = HiddenServiceDescriptorV3._subcredential(Ed25519PrivateKey.generate(), blinded_key) outer_layer = OuterLayer.create( inner_layer = InnerLayer.create( introduction_points = [ IntroductionPointV3.create_for_address('1.1.1.1', 9001), ] ), revision_counter = revision_counter, subcredential = subcredential, blinded_key = blinded_key, ) inner_layer = InnerLayer._decrypt(outer_layer, revision_counter, subcredential, blinded_key) self.assertEqual(1, len(inner_layer.introduction_points)) self.assertEqual('1.1.1.1', inner_layer.introduction_points[0].link_specifiers[0].address)
def test_decryption(self): """ Decrypt our descriptor and validate its content. """ desc = HiddenServiceDescriptorV3.from_str(HS_DESC_STR) inner_layer = desc.decrypt(HS_ADDRESS) self.assertEqual(INNER_LAYER_STR, str(inner_layer)) self.assertEqual(OUTER_LAYER_STR.rstrip('\x00'), str(inner_layer.outer))
def test_identity_key_from_address(self): self.assertEqual( HS_PUBKEY, HiddenServiceDescriptorV3.identity_key_from_address(HS_ADDRESS)) self.assertRaisesWith( ValueError, "'boom.onion' isn't a valid hidden service v3 address", HiddenServiceDescriptorV3.identity_key_from_address, 'boom') self.assertRaisesWith( ValueError, 'Bad checksum (expected def7 but was 842e)', HiddenServiceDescriptorV3.identity_key_from_address, '5' * 56)
def load_v3_master_key(self, master_key_path): if master_key_path: # load key from file return self._load_v3_master_key_from_file(master_key_path) else: # generate new v3 key master_private_key = Ed25519PrivateKey.generate() master_public_key = master_private_key.public_key() master_pub_key_bytes = master_public_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) master_onion_address = HiddenServiceDescriptorV3.address_from_identity_key(master_pub_key_bytes) # cut out the onion since that's what the rest of the code expects master_onion_address = master_onion_address.replace(".onion", "") return master_private_key, master_onion_address
def _load_service_keys(self, service_config_data, config_path): # First of all let's load up the private key key_fname = service_config_data['key'] config_directory = os.path.dirname(config_path) if not os.path.isabs(key_fname): key_fname = os.path.join(config_directory, key_fname) try: with open(key_fname, 'rb') as handle: pem_key_bytes = handle.read() except EnvironmentError as e: logger.critical("Unable to read service private key file ('%s')", e) raise BadServiceInit # Get the service private key # First try with the OBv3 PEM format identity_priv_key = None try: identity_priv_key = serialization.load_pem_private_key( pem_key_bytes, password=None, backend=default_backend()) except ValueError as e: logger.warning( "Service private key not in OBv3 format ('%s'). Trying tor's format...", e) # If the key was not in OBv3 PEM format, try the Tor binary format if not identity_priv_key: try: identity_priv_key = tor_ed25519.load_tor_key_from_disk( pem_key_bytes) self.is_priv_key_in_tor_format = True except ValueError as e: logger.warning( "Service private key not in Tor format either ('%s'). Aborting.", e) raise BadServiceInit # Get onion address identity_pub_key = identity_priv_key.public_key() identity_pub_key_bytes = identity_pub_key.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) onion_address = HiddenServiceDescriptorV3.address_from_identity_key( identity_pub_key_bytes) logger.warning("Loaded onion %s from %s", onion_address, key_fname) return identity_priv_key, onion_address
def load_config_file(self): config_data = util.read_config_data_from_file(self.config_path) logger.debug("Onionbalance config data: %s", config_data) # Do some basic validation if "services" not in config_data: raise ConfigError( "Config file is bad. 'services' is missing. Did you make it with onionbalance-config?" ) # More validation for service in config_data["services"]: if "key" not in service: raise ConfigError( "Config file is bad. 'key' is missing. Did you make it with onionbalance-config?" ) if "instances" not in service: raise ConfigError( "Config file is bad. 'instances' is missing. Did you make it with " "onionbalance-config?") if not service["instances"]: raise ConfigError( "Config file is bad. No backend instances are set. Onionbalance needs at least 1." ) for instance in service["instances"]: if "address" not in instance: raise ConfigError( "Config file is wrong. 'address' missing from instance." ) if not instance["address"]: raise ConfigError( "Config file is bad. Address field is not set.") # Validate that the onion address is legit try: _ = HiddenServiceDescriptorV3.identity_key_from_address( instance["address"]) except ValueError: raise ConfigError( "Cannot load instance with address: '{}'. If you are trying to run onionbalance " "for v2 onions, please use the --hs-version=v2 switch". format(instance["address"])) return config_data
def test_blinding(self): """ Create a descriptor with key blinding. """ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey expected_blinded_key = b'\xb5\xefEA\xfaI\x1a\xd8*p\xcd\x97\x01\x90O\xa8p\xd3\x10\x16\x8e-\x19\xab+\x92\xbc\xf6\xe7\x92\xc2k' desc = HiddenServiceDescriptorV3.create( identity_key = Ed25519PrivateKey.from_private_bytes(b'a' * 32), blinding_nonce = b'a' * 32, ) self.assertEqual(64, len(desc.signing_cert.signature)) self.assertEqual(expected_blinded_key, desc.signing_cert.signing_key())
def test_required_fields(self): """ Check that we require the mandatory fields. """ line_to_attr = { 'hs-descriptor': 'version', 'descriptor-lifetime': 'lifetime', 'descriptor-signing-key-cert': 'signing_cert', 'revision-counter': 'revision_counter', 'superencrypted': 'superencrypted', 'signature': 'signature', } for line in stem.descriptor.hidden_service.REQUIRED_V3_FIELDS: desc_text = HiddenServiceDescriptorV3.content(exclude = (line,)) expect_invalid_attr_for_text(self, desc_text, line_to_attr[line], None)
def __init__(self, onion_address, identity_priv_key, blinding_param, intro_points, is_first_desc): # Timestamp of the last attempt to assemble this descriptor self.last_publish_attempt_ts = None # Timestamp we last uploaded this descriptor self.last_upload_ts = None # Set of responsible HSDirs for last time we uploaded this descriptor self.responsible_hsdirs = None # Start generating descriptor desc_signing_key = Ed25519PrivateKey.generate() # Get the intro points for this descriptor and recertify them! recertified_intro_points = [] for ip in intro_points: recertified_intro_points.append( self._recertify_intro_point(ip, desc_signing_key)) rev_counter = self._get_revision_counter(identity_priv_key, is_first_desc) v3_desc_inner_layer = InnerLayer.create( introduction_points=recertified_intro_points) v3_desc = HiddenServiceDescriptorV3.create( blinding_nonce=blinding_param, identity_key=identity_priv_key, signing_key=desc_signing_key, inner_layer=v3_desc_inner_layer, revision_counter=int(rev_counter), ) # TODO stem should probably initialize it itself so that it has balance # between descriptor creation (where this is not inted) and descriptor # parsing (where this is inited) v3_desc._inner_layer = v3_desc_inner_layer # Check max size is within range if len(str(v3_desc)) > params.MAX_DESCRIPTOR_SIZE: logger.error( "Created descriptor is too big (%d intros). Consider " "relaxing number of instances or intro points per instance " "(see N_INTROS_PER_INSTANCE)") raise BadDescriptor super().__init__(onion_address, v3_desc)
def __init__(self, desc_text, onion_address): """ Parse a descriptor in 'desc_text' and return an ReceivedDescriptor object. Raises BadDescriptor if the descriptor cannot be used. """ try: v3_desc = HiddenServiceDescriptorV3.from_str(desc_text) v3_desc.decrypt(onion_address) except ValueError as err: logger.warning("Descriptor is corrupted (%s).", err) raise BadDescriptor self.received_ts = datetime.datetime.utcnow() logger.debug("Successfuly decrypted descriptor for %s!", onion_address) super().__init__(onion_address, v3_desc)
def test_load_tor_privkey(self): privkey_bytes = binascii.unhexlify(PRIVKEY_FILE_HEX) privkey = tor_ed25519.load_tor_key_from_disk(privkey_bytes) pubkey = privkey.public_key() # Make sure that the fake instances are right self.assertTrue(isinstance(privkey, Ed25519PrivateKey)) self.assertTrue(isinstance(pubkey, Ed25519PublicKey)) # Make sure that the public key matches the onion address onion_addr_pubkey_bytes = HiddenServiceDescriptorV3.identity_key_from_address( ONION_ADDR) self.assertEqual(onion_addr_pubkey_bytes, pubkey.public_bytes()) # Check that signature verification works msg = b"07-04-2020 weird days" msg_sig = privkey.sign(msg) onion_addr_pubkey = tor_ed25519.TorEd25519PublicKey( onion_addr_pubkey_bytes) onion_addr_pubkey.verify(msg_sig, msg) # Now check that it won't just verify any message self.assertRaises(Exception, onion_addr_pubkey.verify, msg_sig, b"another message another day") # Now check that stem will accept this self.assertEqual(stem.util._pubkey_bytes(privkey), pubkey.public_bytes()) self.assertEqual(stem.util._pubkey_bytes(pubkey), pubkey.public_bytes()) # Now check that blinding can work blinded_key_bytes = stem.descriptor.hidden_service._blinded_pubkey( privkey, b"a" * 32) blinded_key = tor_ed25519.TorEd25519PublicKey(blinded_key_bytes) signature = tor_ed25519._blinded_sign_with_tor_key( b"haha", privkey, blinded_key_bytes, b"a" * 32) blinded_key.verify(signature, b"haha")
def _load_service_keys(self, service_config_data, config_path): # First of all let's load up the private key key_fname = service_config_data['key'] config_directory = os.path.dirname(config_path) if not os.path.isabs(key_fname): key_fname = os.path.join(config_directory, key_fname) with open(key_fname, 'rb') as handle: pem_key_bytes = handle.read() identity_priv_key = serialization.load_pem_private_key( pem_key_bytes, password=None, backend=default_backend()) # Get onion address identity_pub_key = identity_priv_key.public_key() identity_pub_key_bytes = identity_pub_key.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) onion_address = HiddenServiceDescriptorV3.address_from_identity_key( identity_pub_key_bytes) logger.warning("Loaded onion %s from %s", onion_address, key_fname) return identity_priv_key, onion_address
def test_address_from_identity_key(self): self.assertEqual(HS_ADDRESS, HiddenServiceDescriptorV3.address_from_identity_key(HS_PUBKEY))