mirror of https://github.com/nucypher/nucypher.git
Final compartmentalization of serialization logic. Fixes #172.
parent
00286e043a
commit
4c0f2009e7
|
@ -54,22 +54,30 @@ class NucypherHashProtocol(KademliaProtocol):
|
|||
self.welcomeIfNewNode(source)
|
||||
self.log.debug("got a store request from %s" % str(sender))
|
||||
|
||||
# TODO: Why is this logic here? This is madness. See #172.
|
||||
if value.startswith(bytes(constants.BYTESTRING_IS_URSULA_IFACE_INFO)):
|
||||
header, signature, sender_pubkey_sig,\
|
||||
public_address, rest_info, dht_info = ursula_interface_splitter(value)
|
||||
header, payload = default_constant_splitter(value, return_remainder=True)
|
||||
|
||||
# TODO: TTL?
|
||||
hrac = public_address + rest_info + dht_info
|
||||
do_store = self.determine_legality_of_dht_key(signature, sender_pubkey_sig,
|
||||
hrac, key, value)
|
||||
elif value.startswith(bytes(constants.BYTESTRING_IS_TREASURE_MAP)):
|
||||
header, signature, sender_pubkey_sig, hrac, message = dht_with_hrac_splitter(
|
||||
value, return_remainder=True)
|
||||
if header == constants.BYTESTRING_IS_URSULA_IFACE_INFO:
|
||||
from nucypher.characters import Ursula
|
||||
stranger_ursula = Ursula.from_bytes(payload, federated_only=True) # TODO: Is federated_only the right thing here?
|
||||
|
||||
# TODO: TTL?
|
||||
do_store = self.determine_legality_of_dht_key(signature, sender_pubkey_sig,
|
||||
hrac, key, value)
|
||||
if stranger_ursula.verify_interface() and key == digest(stranger_ursula.canonical_public_address):
|
||||
self.sourceNode._node_storage[key] = stranger_ursula # TODO: 340
|
||||
return True
|
||||
else:
|
||||
self.log.warning("Got request to store invalid node: {} / {}".format(key, value))
|
||||
self.illegal_keys_seen.append(key)
|
||||
return False
|
||||
elif header == constants.BYTESTRING_IS_TREASURE_MAP:
|
||||
from nucypher.policy.models import TreasureMap
|
||||
try:
|
||||
treasure_map = TreasureMap.from_bytes(payload)
|
||||
self.log.info("Storing TreasureMap: {} / {}".format(key, value))
|
||||
self.sourceNode._treasure_maps[treasure_map.public_id()] = value
|
||||
return True
|
||||
except TreasureMap.InvalidPublicSignature:
|
||||
self.log.warning("Got request to store invalid TreasureMap: {} / {}".format(key, value))
|
||||
self.illegal_keys_seen.append(key)
|
||||
return False
|
||||
else:
|
||||
self.log.info(
|
||||
"Got request to store bad k/v: {} / {}".format(key, value))
|
||||
|
|
|
@ -6,23 +6,23 @@ from typing import ClassVar
|
|||
import kademlia
|
||||
from apistar import http, Route, App
|
||||
from apistar.http import Response
|
||||
from bytestring_splitter import VariableLengthBytestring, BytestringSplitter
|
||||
from constant_sorrow import constants
|
||||
from kademlia.crawling import NodeSpiderCrawl
|
||||
from kademlia.network import Server
|
||||
from kademlia.utils import digest
|
||||
from umbral import pre
|
||||
from umbral.fragments import KFrag
|
||||
|
||||
from bytestring_splitter import VariableLengthBytestring, BytestringSplitter
|
||||
from nucypher.config.configs import NetworkConfiguration
|
||||
from nucypher.crypto.constants import PUBLIC_ADDRESS_LENGTH, PUBLIC_KEY_LENGTH
|
||||
from nucypher.crypto.kits import UmbralMessageKit
|
||||
from nucypher.crypto.powers import EncryptingPower, SigningPower
|
||||
from nucypher.keystore.threading import ThreadedSession
|
||||
from nucypher.network.protocols import NucypherSeedOnlyProtocol, NucypherHashProtocol, \
|
||||
dht_with_hrac_splitter, InterfaceInfo
|
||||
from nucypher.network.protocols import NucypherSeedOnlyProtocol, NucypherHashProtocol, InterfaceInfo
|
||||
from nucypher.network.storage import SeedOnlyStorage
|
||||
from umbral import pre
|
||||
from umbral.fragments import KFrag
|
||||
from umbral.keys import UmbralPublicKey
|
||||
from umbral.signing import Signature
|
||||
from nucypher.crypto.constants import PUBLIC_ADDRESS_LENGTH, PUBLIC_KEY_LENGTH
|
||||
|
||||
|
||||
class NucypherDHTServer(Server):
|
||||
|
@ -73,6 +73,10 @@ class NucypherDHTServer(Server):
|
|||
loop = asyncio.get_event_loop()
|
||||
return loop.run_until_complete(self.get(bytes(key)))
|
||||
|
||||
def set_now(self, key, value):
|
||||
loop = asyncio.get_event_loop()
|
||||
return loop.run_until_complete(self.set(key, value))
|
||||
|
||||
async def set(self, key, value):
|
||||
"""
|
||||
Set the given string key to the given value in the network.
|
||||
|
@ -91,7 +95,6 @@ class NucypherSeedOnlyDHTServer(NucypherDHTServer):
|
|||
|
||||
|
||||
class ProxyRESTServer:
|
||||
|
||||
public_information_splitter = BytestringSplitter(Signature,
|
||||
(UmbralPublicKey, int(PUBLIC_KEY_LENGTH)),
|
||||
(UmbralPublicKey, int(PUBLIC_KEY_LENGTH)),
|
||||
|
@ -104,7 +107,7 @@ class ProxyRESTServer:
|
|||
self._rest_app = None
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, network_config: NetworkConfiguration=None):
|
||||
def from_config(cls, network_config: NetworkConfiguration = None):
|
||||
"""Create a server object from config values, or from a config file."""
|
||||
# if network_config is None:
|
||||
# NetworkConfiguration._load()
|
||||
|
@ -114,7 +117,7 @@ class ProxyRESTServer:
|
|||
"""Implemented on Ursula"""
|
||||
raise NotImplementedError
|
||||
|
||||
def public_address(self):
|
||||
def canonical_public_address(self):
|
||||
"""Implemented on Ursula"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -138,10 +141,10 @@ class ProxyRESTServer:
|
|||
Route('/consider_arrangement',
|
||||
'POST',
|
||||
self.consider_arrangement),
|
||||
Route('/treasure_map/{treasure_map_id_as_hex}',
|
||||
Route('/treasure_map/{treasure_map_id}',
|
||||
'GET',
|
||||
self.provide_treasure_map),
|
||||
Route('/treasure_map/{treasure_map_id_as_hex}',
|
||||
Route('/treasure_map/{treasure_map_id}',
|
||||
'POST',
|
||||
self.receive_treasure_map),
|
||||
]
|
||||
|
@ -166,7 +169,6 @@ class ProxyRESTServer:
|
|||
def rest_url(self):
|
||||
return "{}:{}".format(self.ip_address, self.rest_port)
|
||||
|
||||
|
||||
#####################################
|
||||
# Actual REST Endpoints and utilities
|
||||
#####################################
|
||||
|
@ -178,7 +180,8 @@ class ProxyRESTServer:
|
|||
|
||||
headers = {'Content-Type': 'application/octet-stream'}
|
||||
# TODO: Calling public_address() works here because this is mixed in with Character, but it's not really right.
|
||||
message = bytes(self.public_key(SigningPower)) + bytes(self.public_key(EncryptingPower)) + self.public_address
|
||||
message = bytes(self.public_key(SigningPower)) + bytes(
|
||||
self.public_key(EncryptingPower)) + self.canonical_public_address
|
||||
signature = self.stamp(message)
|
||||
|
||||
response = Response(
|
||||
|
@ -187,11 +190,11 @@ class ProxyRESTServer:
|
|||
|
||||
return response
|
||||
|
||||
def list_all_active_nodes_about_which_we_know(self):
|
||||
def list_all_active_nodes_about_which_we_know(self, request: http.Request):
|
||||
headers = {'Content-Type': 'application/octet-stream'}
|
||||
# TODO: mm hmmph *slowly exhales* fffff. Some 227 right here.
|
||||
ursulas_as_bytes = bytes().join(self.dht_server.protocol.ursulas.values())
|
||||
ursulas_as_bytes += self.interface_info_with_metadata()
|
||||
ursulas_as_bytes = bytes().join(bytes(n) for n in self._known_nodes.values())
|
||||
ursulas_as_bytes += bytes(self)
|
||||
signature = self.stamp(ursulas_as_bytes)
|
||||
return Response(bytes(signature) + ursulas_as_bytes, headers=headers)
|
||||
|
||||
|
@ -223,8 +226,6 @@ class ProxyRESTServer:
|
|||
"""
|
||||
hrac = binascii.unhexlify(hrac_as_hex)
|
||||
policy_message_kit = UmbralMessageKit.from_bytes(request.body)
|
||||
# group_payload_splitter = BytestringSplitter(PublicKey)
|
||||
# policy_payload_splitter = BytestringSplitter((KFrag, KFRAG_LENGTH))
|
||||
|
||||
alice = self._alice_class.from_public_keys({SigningPower: policy_message_kit.sender_pubkey_sig})
|
||||
|
||||
|
|
|
@ -161,41 +161,32 @@ class Policy:
|
|||
"""
|
||||
return keccak_digest(bytes(self.alice.stamp) + bytes(self.bob.stamp) + self.label)
|
||||
|
||||
def treasure_map_dht_key(self):
|
||||
"""
|
||||
We need a key that Bob can glean from knowledge he already has *and* which Ursula can verify came from us.
|
||||
Ursula will refuse to propagate this key if it she can't prove that our public key, which is included in it,
|
||||
was used to sign the payload.
|
||||
|
||||
Our public key (which everybody knows) and the hrac above.
|
||||
"""
|
||||
return keccak_digest(bytes(self.alice.stamp) + self.hrac())
|
||||
|
||||
def publish_treasure_map(self, network_middleare=None, use_dht=False):
|
||||
if network_middleare is None and use_dht is False:
|
||||
raise ValueError("Can't engage the REST swarm without networky stuff.")
|
||||
tmap_message_kit, signature_for_bob = self.alice.encrypt_for(
|
||||
self.bob,
|
||||
self.treasure_map.packed_payload())
|
||||
signature_for_ursula = self.alice.stamp(bytes(self.alice.stamp) + self.hrac())
|
||||
|
||||
# In order to know this is safe to propagate, Ursula needs to see a signature, our public key,
|
||||
# and, reasons explained in treasure_map_dht_key above, the uri_hash.
|
||||
# TODO: Clean this up. See #172.
|
||||
map_payload = signature_for_ursula + self.alice.stamp + self.alice.public_address + self.hrac() + tmap_message_kit.to_bytes()
|
||||
map_id = self.treasure_map_dht_key()
|
||||
|
||||
def publish_treasure_map(self, network_middleare):
|
||||
self.treasure_map.prepare_for_publication(self.bob.public_key(EncryptingPower),
|
||||
self.bob.public_key(SigningPower),
|
||||
self.alice.stamp,
|
||||
self.label
|
||||
)
|
||||
if not self.alice._known_nodes:
|
||||
# TODO: Optionally block.
|
||||
raise RuntimeError("Alice hasn't learned of any nodes. Thus, she can't push the TreasureMap.")
|
||||
|
||||
for node in self.alice._known_nodes.values():
|
||||
response = network_middleare.push_treasure_map_to_node(node, map_id,
|
||||
constants.BYTESTRING_IS_TREASURE_MAP + map_payload)
|
||||
# TODO: Do something here based on success or failure
|
||||
if response.status_code == 204:
|
||||
pass
|
||||
responses = {}
|
||||
|
||||
return tmap_message_kit, map_payload, signature_for_bob, signature_for_ursula
|
||||
for node in self.alice._known_nodes.values():
|
||||
# TODO: It's way overkill to push this to every node we know about. Come up with a system. 342
|
||||
response = network_middleare.put_treasure_map_on_node(node,
|
||||
self.treasure_map.public_id(),
|
||||
bytes(self.treasure_map)
|
||||
)
|
||||
if response.status_code == 202:
|
||||
responses[node] = response
|
||||
# TODO: Handle response wherein node already had a copy of this TreasureMap. 341
|
||||
else:
|
||||
# TODO: Do something useful here.
|
||||
raise RuntimeError
|
||||
|
||||
return responses
|
||||
|
||||
def publish(self, network_middleware) -> None:
|
||||
"""Spread word of this Policy far and wide."""
|
||||
|
@ -313,28 +304,141 @@ class FederatedPolicy(Policy):
|
|||
raise self.MoreKFragsThanArrangements
|
||||
|
||||
|
||||
class TreasureMap(object):
|
||||
def __init__(self, m, ursula_interface_ids=None):
|
||||
self.m = m
|
||||
self.ids = set(ursula_interface_ids or set())
|
||||
class TreasureMap:
|
||||
splitter = BytestringSplitter(Signature,
|
||||
(bytes, KECCAK_DIGEST_LENGTH), # hrac
|
||||
(UmbralMessageKit, VariableLengthBytestring)
|
||||
)
|
||||
node_id_splitter = BytestringSplitter(PUBLIC_ADDRESS_LENGTH)
|
||||
|
||||
def packed_payload(self):
|
||||
return msgpack.dumps(self.nodes_as_bytes() + [self.m])
|
||||
class InvalidPublicSignature(Exception):
|
||||
"""Raised when the public signature (typically intended for Ursula) is not valid."""
|
||||
|
||||
def __init__(self,
|
||||
m=None,
|
||||
node_ids=None,
|
||||
message_kit=None,
|
||||
public_signature=None,
|
||||
hrac=None):
|
||||
|
||||
if m is not None:
|
||||
if m > 255:
|
||||
raise ValueError(
|
||||
"Largest allowed value for m is 255. Why the heck are you trying to make it larger than that anyway? That's too big.")
|
||||
self.m = m
|
||||
self.node_ids = node_ids or set()
|
||||
else:
|
||||
self.m = constants.NO_DECRYPTION_PERFORMED
|
||||
self.node_ids = constants.NO_DECRYPTION_PERFORMED
|
||||
|
||||
self.message_kit = message_kit
|
||||
self._signature_for_bob = None
|
||||
self._public_signature = public_signature
|
||||
self._hrac = hrac
|
||||
self._payload = None
|
||||
|
||||
def prepare_for_publication(self, bob_encrypting_key, bob_verifying_key, alice_stamp, label):
|
||||
plaintext = self.m.to_bytes(1, "big") + self.nodes_as_bytes()
|
||||
|
||||
self.message_kit, _signature_for_bob = encrypt_and_sign(bob_encrypting_key,
|
||||
plaintext=plaintext,
|
||||
signer=alice_stamp,
|
||||
)
|
||||
"""
|
||||
Here's our "hashed resource authentication code".
|
||||
|
||||
A hash of:
|
||||
* Alice's public key
|
||||
* Bob's public key
|
||||
* the uri
|
||||
|
||||
Alice and Bob have all the information they need to construct this.
|
||||
Ursula does not, so we share it with her.
|
||||
|
||||
This way, Bob can generate it and use it to find the TreasureMap.
|
||||
"""
|
||||
self._hrac = keccak_digest(bytes(alice_stamp) + bytes(bob_verifying_key) + label)
|
||||
self._public_signature = alice_stamp(bytes(alice_stamp) + self._hrac)
|
||||
self._set_payload()
|
||||
|
||||
def _set_payload(self):
|
||||
self._payload = self._public_signature + self._hrac + bytes(
|
||||
VariableLengthBytestring(self.message_kit.to_bytes()))
|
||||
|
||||
def __bytes__(self):
|
||||
if self._payload is None:
|
||||
self._set_payload()
|
||||
|
||||
return self._payload
|
||||
|
||||
@property
|
||||
def _verifying_key(self):
|
||||
return self.message_kit.sender_pubkey_sig
|
||||
|
||||
def nodes_as_bytes(self):
|
||||
return [bytes(ursula_id) for ursula_id in self.ids]
|
||||
if self.node_ids == constants.NO_DECRYPTION_PERFORMED:
|
||||
return constants.NO_DECRYPTION_PERFORMED
|
||||
else:
|
||||
return bytes().join(bytes(ursula_id) for ursula_id in self.node_ids)
|
||||
|
||||
def add_ursula(self, ursula):
|
||||
self.ids.add(bytes(ursula.stamp))
|
||||
if self.node_ids == constants.NO_DECRYPTION_PERFORMED:
|
||||
raise TypeError("This TreasureMap is encrypted. You can't add another node without decrypting it.")
|
||||
self.node_ids.add(ursula.canonical_public_address)
|
||||
|
||||
def public_id(self):
|
||||
"""
|
||||
We need an ID that Bob can glean from knowledge he already has *and* which Ursula can verify came from Alice.
|
||||
Ursula will refuse to propagate this if it she can't prove the payload is signed by Alice's public key,
|
||||
which is included in it,
|
||||
"""
|
||||
return keccak_digest(bytes(self._verifying_key) + bytes(self._hrac)).hex()
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, bytes_representation, verify=True):
|
||||
signature, hrac, tmap_message_kit = \
|
||||
cls.splitter(bytes_representation)
|
||||
|
||||
treasure_map = cls(
|
||||
message_kit=tmap_message_kit,
|
||||
public_signature=signature,
|
||||
hrac=hrac,
|
||||
)
|
||||
|
||||
if verify:
|
||||
treasure_map.public_verify()
|
||||
|
||||
return treasure_map
|
||||
|
||||
def public_verify(self):
|
||||
message = bytes(self._verifying_key) + self._hrac
|
||||
verified = self._public_signature.verify(message, self._verifying_key)
|
||||
|
||||
if verified:
|
||||
return True
|
||||
else:
|
||||
raise self.InvalidPublicSignature("This TreasureMap is not properly publicly signed by Alice.")
|
||||
|
||||
def orient(self, compass):
|
||||
"""
|
||||
When Bob receives the TreasureMap, he'll pass a compass (a callable which can verify and decrypt the
|
||||
payload message kit).
|
||||
"""
|
||||
verified, map_in_the_clear = compass(message_kit=self.message_kit)
|
||||
if verified:
|
||||
self.m = map_in_the_clear[0]
|
||||
self.node_ids = self.node_id_splitter.repeat(map_in_the_clear[1:], as_set=True)
|
||||
else:
|
||||
raise self.InvalidPublicSignature("This TreasureMap does not contain the correct signature from Alice to Bob.")
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.ids == other.ids
|
||||
return self.node_ids == other.node_ids
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.ids)
|
||||
return iter(self.node_ids)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.ids)
|
||||
return len(self.node_ids)
|
||||
|
||||
|
||||
class WorkOrder(object):
|
||||
|
|
Loading…
Reference in New Issue