mirror of https://github.com/nucypher/nucypher.git
Merge pull request #133 from jMyles/apistar
REST relationship for Bob and Ursula, with complete PRE using all three Fragment classes.pull/145/head
commit
761553b26b
|
@ -10,3 +10,4 @@ __pycache__
|
|||
/.idea
|
||||
.coverage
|
||||
_temp_test_datastore
|
||||
.mypy_cache
|
|
@ -4,11 +4,12 @@ from binascii import hexlify
|
|||
from logging import getLogger
|
||||
|
||||
import msgpack
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from apistar import http
|
||||
from apistar.core import Route
|
||||
from apistar.frameworks.wsgi import WSGIApp as App
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from apistar.http import Response
|
||||
from kademlia.network import Server
|
||||
from kademlia.utils import digest
|
||||
from nkms.crypto import api as API
|
||||
|
@ -71,6 +72,12 @@ class Character(object):
|
|||
else:
|
||||
self._seal = StrangerSeal(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
return bytes(self.seal) == bytes(other.seal)
|
||||
|
||||
def __hash__(self):
|
||||
return int.from_bytes(self.seal, byteorder="big")
|
||||
|
||||
class NotFound(KeyError):
|
||||
"""raised when we try to interact with an actor of whom we haven't learned yet."""
|
||||
|
||||
|
@ -224,11 +231,12 @@ class Bob(Character):
|
|||
_server_class = NuCypherSeedOnlyDHTServer
|
||||
_default_crypto_powerups = [SigningPower, EncryptingPower]
|
||||
|
||||
def __init__(self, alice=None):
|
||||
super().__init__()
|
||||
def __init__(self, alice=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._ursulas = {}
|
||||
if alice:
|
||||
self.alice = alice
|
||||
self._work_orders = {}
|
||||
|
||||
@property
|
||||
def alice(self):
|
||||
|
@ -265,7 +273,7 @@ class Bob(Character):
|
|||
_signature_for_ursula, pubkey_sig_alice, hrac, encrypted_treasure_map = dht_value_splitter(
|
||||
packed_encrypted_treasure_map[5::], msgpack_remainder=True)
|
||||
verified, cleartext = self.verify_from(self.alice, encrypted_treasure_map,
|
||||
signature_is_on_cleartext=True, decrypt=True)
|
||||
signature_is_on_cleartext=True, decrypt=True)
|
||||
alices_signature, packed_node_list = BytestringSplitter(Signature)(cleartext, return_remainder=True)
|
||||
if not verified:
|
||||
return NOT_FROM_ALICE
|
||||
|
@ -273,6 +281,34 @@ class Bob(Character):
|
|||
from nkms.policy.models import TreasureMap
|
||||
return TreasureMap(msgpack.loads(packed_node_list))
|
||||
|
||||
def generate_work_orders(self, policy_group, *pfrags, num_ursulas=None):
|
||||
# TODO: Perhaps instead of taking a policy_group, it makes more sense for Bob to reconstruct one with the TreasureMap.
|
||||
from nkms.policy.models import WorkOrder # Prevent circular import
|
||||
|
||||
# existing_work_orders = self._work_orders.get(pfrags, {}) # TODO: lookup whether we've done this reencryption before - see #137.
|
||||
existing_work_orders = {}
|
||||
generated_work_orders = {}
|
||||
|
||||
for ursula_dht_key, ursula in self._ursulas.items():
|
||||
if ursula_dht_key in existing_work_orders:
|
||||
continue
|
||||
else:
|
||||
work_order = WorkOrder.constructed_by_bob(policy_group.hrac(), pfrags, ursula_dht_key, self)
|
||||
existing_work_orders[ursula_dht_key] = generated_work_orders[ursula_dht_key] = work_order
|
||||
|
||||
if num_ursulas is not None:
|
||||
if num_ursulas == len(generated_work_orders):
|
||||
break
|
||||
|
||||
return generated_work_orders
|
||||
|
||||
def get_reencrypted_c_frag(self, networky_stuff, work_order):
|
||||
cfrags = networky_stuff.reencrypt(work_order)
|
||||
return cfrags
|
||||
|
||||
def get_ursula(self, ursula_id):
|
||||
return self._ursulas[ursula_id]
|
||||
|
||||
|
||||
class Ursula(Character):
|
||||
_server_class = NuCypherDHTServer
|
||||
|
@ -287,6 +323,7 @@ class Ursula(Character):
|
|||
self.keystore = urulsas_keystore
|
||||
|
||||
self._rest_app = None
|
||||
self._work_orders = []
|
||||
|
||||
@property
|
||||
def rest_app(self):
|
||||
|
@ -307,12 +344,13 @@ class Ursula(Character):
|
|||
*args, **kwargs):
|
||||
|
||||
if not id:
|
||||
id = digest(secure_random(32)) # TODO: Network-wide deterministic ID generation (ie, auction or whatever)
|
||||
id = digest(secure_random(32)) # TODO: Network-wide deterministic ID generation (ie, auction or whatever) #136.
|
||||
|
||||
super().attach_server(ksize, alpha, id, storage)
|
||||
|
||||
routes = [
|
||||
Route('/kFrag/{hrac_as_hex}', 'POST', self.set_policy),
|
||||
Route('/kFrag/{hrac_as_hex}/reencrypt', 'POST', self.reencrypt_via_rest),
|
||||
]
|
||||
|
||||
self._rest_app = App(routes=routes)
|
||||
|
@ -322,7 +360,7 @@ class Ursula(Character):
|
|||
self.interface = interface
|
||||
return self.server.listen(port, interface)
|
||||
|
||||
def interface_info(self):
|
||||
def dht_interface_info(self):
|
||||
return self.port, self.interface, self.interface_ttl
|
||||
|
||||
def interface_dht_key(self):
|
||||
|
@ -330,10 +368,10 @@ class Ursula(Character):
|
|||
|
||||
def interface_dht_value(self):
|
||||
signature = self.seal(self.interface_hrac())
|
||||
return b"uaddr" + signature + self.seal + self.interface_hrac() + msgpack.dumps(self.interface_info())
|
||||
return b"uaddr" + signature + self.seal + self.interface_hrac() + msgpack.dumps(self.dht_interface_info())
|
||||
|
||||
def interface_hrac(self):
|
||||
return self.hash(msgpack.dumps(self.interface_info()))
|
||||
return self.hash(msgpack.dumps(self.dht_interface_info()))
|
||||
|
||||
def publish_interface_information(self):
|
||||
if not self.port and self.interface:
|
||||
|
@ -362,7 +400,34 @@ class Ursula(Character):
|
|||
raise
|
||||
# Do something appropriately RESTful (ie, 4xx).
|
||||
|
||||
return # A 200, which whatever policy metadata.
|
||||
return # A 200, with whatever policy metadata.
|
||||
|
||||
def reencrypt_via_rest(self, hrac_as_hex, request: http.Request):
|
||||
from nkms.policy.models import WorkOrder # Avoid circular import
|
||||
hrac = binascii.unhexlify(hrac_as_hex)
|
||||
work_order = WorkOrder.from_rest_payload(hrac, request.body)
|
||||
kfrag = self.keystore.get_kfrag(hrac) # Careful! :-)
|
||||
cfrag_byte_stream = b""
|
||||
|
||||
for pfrag in work_order.pfrags:
|
||||
cfrag_byte_stream += API.ecies_reencrypt(kfrag, pfrag.encrypted_key)
|
||||
|
||||
self._work_orders.append(work_order) # TODO: Put this in Ursula's datastore
|
||||
|
||||
return Response(content=cfrag_byte_stream, content_type="application/octet-stream")
|
||||
|
||||
def work_orders(self, bob=None):
|
||||
"""
|
||||
TODO: This is better written as a model method for Ursula's datastore.
|
||||
"""
|
||||
if not bob:
|
||||
return self._work_orders
|
||||
else:
|
||||
work_orders_from_bob = []
|
||||
for work_order in self._work_orders:
|
||||
if work_order.bob == bob:
|
||||
work_orders_from_bob.append(work_order)
|
||||
return work_orders_from_bob
|
||||
|
||||
|
||||
class Seal(object):
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from random import SystemRandom
|
||||
from typing import Tuple, Union, List
|
||||
|
||||
import sha3
|
||||
from nacl.secret import SecretBox
|
||||
from py_ecc.secp256k1 import N, privtopub, ecdsa_raw_recover, ecdsa_raw_sign
|
||||
from random import SystemRandom
|
||||
|
||||
from nkms.crypto import _internal
|
||||
from nkms.crypto.fragments import KFrag, PFrag, CFrag
|
||||
from nkms.keystore.constants import SIG_KEYPAIR_BYTE, PUB_KEY_BYTE
|
||||
from npre import elliptic_curve
|
||||
from npre import umbral
|
||||
|
@ -378,8 +379,9 @@ def ecies_split_rekey(
|
|||
privkey_a = priv_bytes2ec(privkey_a)
|
||||
if type(privkey_b) == bytes:
|
||||
privkey_b = priv_bytes2ec(privkey_b)
|
||||
return PRE.split_rekey(privkey_a, privkey_b,
|
||||
min_shares, total_shares)
|
||||
umbral_rekeys = PRE.split_rekey(privkey_a, privkey_b,
|
||||
min_shares, total_shares)
|
||||
return [KFrag(umbral_kfrag=u) for u in umbral_rekeys]
|
||||
|
||||
|
||||
def ecies_ephemeral_split_rekey(
|
||||
|
@ -402,14 +404,15 @@ def ecies_ephemeral_split_rekey(
|
|||
:return: A tuple containing a list of rekey frags, and a tuple of the
|
||||
encrypted ephemeral key data (enc_symm_key, enc_eph_privkey)
|
||||
"""
|
||||
eph_privkey, enc_eph_data = _internal._ecies_gen_ephemeral_key(pubkey_b)
|
||||
frags = ecies_split_rekey(privkey_a, eph_privkey, min_shares, total_shares)
|
||||
eph_privkey, (encrypted_key, encrypted_message) = _internal._ecies_gen_ephemeral_key(pubkey_b)
|
||||
kfrags = ecies_split_rekey(privkey_a, eph_privkey, min_shares, total_shares)
|
||||
pfrag = PFrag(ephemeral_data_as_bytes=None, encrypted_key=encrypted_key, encrypted_message=encrypted_message)
|
||||
|
||||
return (frags, enc_eph_data)
|
||||
return (kfrags, pfrag)
|
||||
|
||||
|
||||
def ecies_combine(
|
||||
encrypted_keys: List[umbral.EncryptedKey]
|
||||
cfrags: List[CFrag]
|
||||
) -> umbral.EncryptedKey:
|
||||
"""
|
||||
Combines the encrypted keys together to form a rekey from split_rekey.
|
||||
|
@ -418,7 +421,7 @@ def ecies_combine(
|
|||
|
||||
:return: The combined EncryptedKey of the rekey
|
||||
"""
|
||||
return PRE.combine(encrypted_keys)
|
||||
return PRE.combine([cfrag.encrypted_key for cfrag in cfrags])
|
||||
|
||||
|
||||
def ecies_reencrypt(
|
||||
|
@ -437,4 +440,5 @@ def ecies_reencrypt(
|
|||
rekey = umbral.RekeyFrag(None, priv_bytes2ec(rekey))
|
||||
if type(enc_key) == bytes:
|
||||
enc_key = umbral.EncryptedKey(priv_bytes2ec(enc_key), None)
|
||||
return PRE.reencrypt(rekey, enc_key)
|
||||
reencrypted_data = PRE.reencrypt(rekey, enc_key)
|
||||
return CFrag(reencrypted_data=reencrypted_data)
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
from nkms.crypto.utils import BytestringSplitter
|
||||
from npre.constants import UNKNOWN_KFRAG
|
||||
from npre.umbral import RekeyFrag, EncryptedKey
|
||||
|
||||
|
||||
class PFrag(object):
|
||||
_key_length = 34
|
||||
_message_length = 72
|
||||
_EXPECTED_LENGTH = _key_length + _message_length
|
||||
|
||||
splitter = BytestringSplitter((bytes, _key_length), (bytes, _message_length))
|
||||
|
||||
def __init__(self, ephemeral_data_as_bytes=None, encrypted_key=None, encrypted_message=None):
|
||||
from nkms.crypto.api import PRE # Avoid circular import
|
||||
if ephemeral_data_as_bytes and encrypted_key:
|
||||
raise ValueError("Pass either the ephemeral data as bytes or the encrypted key and message. Not both.")
|
||||
elif ephemeral_data_as_bytes:
|
||||
encrypted_key, self.encrypted_message = self.splitter(ephemeral_data_as_bytes)
|
||||
self.encrypted_key = EncryptedKey(ekey=PRE.load_key(encrypted_key), re_id=None)
|
||||
elif encrypted_key and encrypted_message:
|
||||
self.encrypted_key = encrypted_key
|
||||
self.encrypted_message = encrypted_message
|
||||
else:
|
||||
assert False # What do we do if all the values were None? Perhaps have an "UNKNOWN_PFRAG" concept?
|
||||
|
||||
def __bytes__(self):
|
||||
from nkms.crypto.api import PRE # Avoid circular import
|
||||
encrypted_key_bytes = PRE.save_key(self.encrypted_key.ekey)
|
||||
return encrypted_key_bytes + self.encrypted_message
|
||||
|
||||
def __len__(self):
|
||||
return len(bytes(self))
|
||||
|
||||
def deserialized(self):
|
||||
return self.encrypted_key, self.encrypted_message
|
||||
|
||||
|
||||
class KFrag(object):
|
||||
|
||||
_EXPECTED_LENGTH = 66
|
||||
_is_unknown_kfrag = False
|
||||
|
||||
def __init__(self, id_plus_key_as_bytes=None, umbral_kfrag=None):
|
||||
if all((id_plus_key_as_bytes, umbral_kfrag)):
|
||||
raise ValueError("Pass either the id/key or an umbral_kfrag (or neither for UNKNOWN_KFRAG). Not both.")
|
||||
elif id_plus_key_as_bytes:
|
||||
self._umbral_kfrag = RekeyFrag.from_bytes(id_plus_key_as_bytes)
|
||||
elif umbral_kfrag:
|
||||
self._umbral_kfrag = umbral_kfrag
|
||||
else:
|
||||
self._is_unknown_kfrag = True
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(self._umbral_kfrag)
|
||||
|
||||
def __eq__(self, other_kfrag):
|
||||
if other_kfrag is UNKNOWN_KFRAG:
|
||||
return bool(self._is_unknown_kfrag)
|
||||
else:
|
||||
return bytes(self) == bytes(other_kfrag)
|
||||
|
||||
def __add__(self, other):
|
||||
return bytes(self) + other
|
||||
|
||||
def __radd__(self, other):
|
||||
return other + bytes(self)
|
||||
|
||||
def __getitem__(self, slice):
|
||||
return bytes(self)[slice]
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return self._umbral_kfrag.key
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._umbral_kfrag.id
|
||||
|
||||
|
||||
class CFrag(object):
|
||||
_EXPECTED_LENGTH = 67
|
||||
_key_element_length = 34
|
||||
_re_id_length = 33
|
||||
|
||||
def __init__(self, encrypted_key_as_bytes=None, reencrypted_data=None):
|
||||
from nkms.crypto.api import PRE # Avoid circular import
|
||||
if encrypted_key_as_bytes and reencrypted_data:
|
||||
raise ValueError("Pass the bytes or the EncryptedKey, not both.")
|
||||
elif encrypted_key_as_bytes:
|
||||
if not len(encrypted_key_as_bytes) == self._EXPECTED_LENGTH:
|
||||
raise ValueError("Got {} bytes; need {} for a proper cFrag.".format(len(encrypted_key_as_bytes)),
|
||||
self._EXPECTED_LENGTH)
|
||||
key_element = PRE.load_key(encrypted_key_as_bytes[:self._key_element_length])
|
||||
re_id = PRE.load_key(encrypted_key_as_bytes[self._key_element_length:])
|
||||
self.encrypted_key = EncryptedKey(ekey=key_element, re_id=re_id)
|
||||
elif reencrypted_data:
|
||||
self.encrypted_key = reencrypted_data
|
||||
else:
|
||||
assert False # Again, do we want a concept of an "empty" CFrag?
|
||||
|
||||
def __bytes__(self):
|
||||
from nkms.crypto.api import PRE # Avoid circular import
|
||||
as_bytes = PRE.save_key(self.encrypted_key.ekey) + PRE.save_key(self.encrypted_key.re_id)
|
||||
if len(as_bytes) != self._EXPECTED_LENGTH:
|
||||
raise TypeError("Something went crazy wrong here. This CFrag serialized to {} bytes.".format(len(as_bytes)))
|
||||
else:
|
||||
return as_bytes
|
||||
|
||||
def __len__(self):
|
||||
return len(bytes(self))
|
||||
|
||||
def __add__(self, other):
|
||||
return bytes(self) + other
|
||||
|
||||
def __radd__(self, other):
|
||||
return other + bytes(self)
|
||||
|
||||
def __eq__(self, other_cfrag):
|
||||
return bytes(self) == bytes(other_cfrag)
|
|
@ -45,3 +45,15 @@ class BytestringSplitter(object):
|
|||
@staticmethod
|
||||
def get_message_meta(message_type):
|
||||
return message_type if isinstance(message_type, tuple) else (message_type, message_type._EXPECTED_LENGTH)
|
||||
|
||||
|
||||
class RepeatingBytestringSplitter(BytestringSplitter):
|
||||
|
||||
def __call__(self, splittable):
|
||||
remainder = True
|
||||
messages = []
|
||||
while remainder:
|
||||
message, remainder = super().__call__(splittable, return_remainder=True)
|
||||
messages.append(message)
|
||||
splittable = remainder
|
||||
return messages
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import sha3
|
||||
|
||||
from nkms.crypto.fragments import KFrag
|
||||
from nkms.keystore import keypairs, constants
|
||||
from nkms.keystore.db.models import Key, KeyFrag
|
||||
from nkms.crypto.utils import BytestringSplitter
|
||||
|
@ -20,11 +22,7 @@ class KeyStore(object):
|
|||
A storage class of cryptographic keys.
|
||||
"""
|
||||
|
||||
kFrag_splitter = BytestringSplitter(
|
||||
Signature,
|
||||
(bytes, constants.REKEY_FRAG_ID_LEN),
|
||||
(bytes, constants.REKEY_FRAG_KEY_LEN)
|
||||
)
|
||||
kfrag_splitter = BytestringSplitter(Signature, KFrag)
|
||||
|
||||
def __init__(self, sqlalchemy_engine=None):
|
||||
"""
|
||||
|
@ -105,12 +103,11 @@ class KeyStore(object):
|
|||
.format(hrac)
|
||||
)
|
||||
# TODO: Make this use a class
|
||||
sig, id, key = self.kFrag_splitter(kfrag.key_frag)
|
||||
sig, kfrag = self.kfrag_splitter(kfrag.key_frag)
|
||||
|
||||
kFrag = RekeyFrag(id=id, key=key)
|
||||
if get_sig:
|
||||
return (kFrag, sig)
|
||||
return kFrag
|
||||
return (kfrag, sig)
|
||||
return kfrag
|
||||
|
||||
def add_key(self,
|
||||
keypair: Union[keypairs.EncryptingKeypair,
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import binascii
|
||||
|
||||
import msgpack
|
||||
|
||||
from nkms.characters import Alice, Bob, Ursula
|
||||
from nkms.crypto import api
|
||||
from nkms.crypto.api import keccak_digest
|
||||
from nkms.crypto.constants import HASH_DIGEST_LENGTH, NOT_SIGNED
|
||||
from nkms.crypto.constants import NOT_SIGNED
|
||||
from nkms.crypto.fragments import KFrag, PFrag
|
||||
from nkms.crypto.powers import EncryptingPower
|
||||
from nkms.crypto.signature import Signature
|
||||
from nkms.crypto.utils import BytestringSplitter
|
||||
from nkms.keystore.keypairs import PublicKey
|
||||
from npre.constants import UNKNOWN_KFRAG
|
||||
from npre.umbral import RekeyFrag
|
||||
|
||||
group_payload_splitter = BytestringSplitter(PublicKey)
|
||||
policy_payload_splitter = BytestringSplitter((bytes, 66)) # TODO: I wish ReKeyFrag worked with this interface.
|
||||
policy_payload_splitter = BytestringSplitter(KFrag)
|
||||
|
||||
|
||||
class PolicyOffer(object):
|
||||
|
@ -59,15 +61,15 @@ class PolicyManagerForAlice(PolicyManager):
|
|||
re_enc_keys, encrypted_key = self.owner.generate_rekey_frags(alice_priv_enc, bob, m,
|
||||
n) # TODO: Access Alice's private key inside this method.
|
||||
policies = []
|
||||
for kfrag_id, rekey in enumerate(re_enc_keys):
|
||||
for kfrag_id, kfrag in enumerate(re_enc_keys):
|
||||
policy = Policy.from_alice(
|
||||
alice=self.owner,
|
||||
bob=bob,
|
||||
kfrag=rekey,
|
||||
kfrag=kfrag,
|
||||
)
|
||||
policies.append(policy)
|
||||
|
||||
return PolicyGroup(uri, self.owner, bob, policies)
|
||||
return PolicyGroup(uri, self.owner, bob, encrypted_key, policies)
|
||||
|
||||
|
||||
class PolicyGroup(object):
|
||||
|
@ -77,10 +79,11 @@ class PolicyGroup(object):
|
|||
|
||||
_id = None
|
||||
|
||||
def __init__(self, uri: bytes, alice: Alice, bob: Bob, policies=None) -> None:
|
||||
def __init__(self, uri: bytes, alice: Alice, bob: Bob, pfrag, policies=None) -> None:
|
||||
self.policies = policies or []
|
||||
self.alice = alice
|
||||
self.bob = bob
|
||||
self.pfrag = pfrag
|
||||
self.uri = uri
|
||||
self.treasure_map = TreasureMap()
|
||||
|
||||
|
@ -137,7 +140,6 @@ class PolicyGroup(object):
|
|||
self.hrac(),
|
||||
full_payload) # TODO: Parse response for confirmation.
|
||||
|
||||
|
||||
# Assuming response is what we hope for
|
||||
self.treasure_map.add_ursula(policy.ursula)
|
||||
|
||||
|
@ -219,7 +221,7 @@ class Policy(object):
|
|||
alice = Alice.from_pubkey_sig_bytes(alice_pubkey_sig)
|
||||
ursula.learn_about_actor(alice)
|
||||
verified, cleartext = ursula.verify_from(alice, payload_encrypted_for_ursula,
|
||||
decrypt=True, signature_is_on_cleartext=True)
|
||||
decrypt=True, signature_is_on_cleartext=True)
|
||||
|
||||
if not verified:
|
||||
# TODO: What do we do if it's not signed properly?
|
||||
|
@ -227,8 +229,7 @@ class Policy(object):
|
|||
|
||||
alices_signature, policy_payload = BytestringSplitter(Signature)(cleartext, return_remainder=True)
|
||||
|
||||
kfrag_bytes, encrypted_challenge_pack = policy_payload_splitter(policy_payload, return_remainder=True)
|
||||
kfrag = RekeyFrag.from_bytes(kfrag_bytes)
|
||||
kfrag, encrypted_challenge_pack = policy_payload_splitter(policy_payload, return_remainder=True)
|
||||
policy = Policy(alice=alice, alices_signature=alices_signature, kfrag=kfrag,
|
||||
encrypted_challenge_pack=encrypted_challenge_pack)
|
||||
|
||||
|
@ -288,3 +289,44 @@ class TreasureMap(object):
|
|||
|
||||
def __iter__(self):
|
||||
return iter(self.ids)
|
||||
|
||||
|
||||
class WorkOrder(object):
|
||||
def __init__(self, bob, kfrag_hrac, pfrags, receipt_bytes, receipt_signature, ursula_id=None):
|
||||
self.bob = bob
|
||||
self.kfrag_hrac = kfrag_hrac
|
||||
self.pfrags = pfrags
|
||||
self.receipt_bytes = receipt_bytes
|
||||
self.receipt_signature = receipt_signature
|
||||
self.ursula_id = ursula_id # TODO: We may still need a more elegant system for ID'ing Ursula. See #136.
|
||||
|
||||
def __repr__(self):
|
||||
return "WorkOrder (pfrags: {}) {} for {}".format([binascii.hexlify(bytes(p))[:6] for p in self.pfrags],
|
||||
binascii.hexlify(self.receipt_bytes)[:6],
|
||||
binascii.hexlify(self.ursula_id)[:6])
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.receipt_bytes, self.receipt_signature) == (other.receipt_bytes, other.receipt_signature)
|
||||
|
||||
@classmethod
|
||||
def constructed_by_bob(cls, kfrag_hrac, pfrags, ursula_dht_key, bob):
|
||||
receipt_bytes = b"wo:" + ursula_dht_key # TODO: represent the pfrags as bytes and hash them as part of the receipt, ie + keccak_digest(b"".join(pfrags)) - See #137
|
||||
receipt_signature = bob.seal(receipt_bytes)
|
||||
return cls(bob, kfrag_hrac, pfrags, receipt_bytes, receipt_signature, ursula_dht_key)
|
||||
|
||||
@classmethod
|
||||
def from_rest_payload(cls, kfrag_hrac, rest_payload):
|
||||
payload_splitter = BytestringSplitter(Signature, PublicKey)
|
||||
signature, bob_pubkey_sig, (receipt_bytes, packed_pfrags) = payload_splitter(rest_payload,
|
||||
msgpack_remainder=True)
|
||||
pfrags = [PFrag(p) for p in msgpack.loads(packed_pfrags)]
|
||||
verified = signature.verify(receipt_bytes, bob_pubkey_sig)
|
||||
if not verified:
|
||||
raise ValueError("This doesn't appear to be from Bob.")
|
||||
bob = Bob.from_pubkey_sig_bytes(bob_pubkey_sig)
|
||||
return cls(bob, kfrag_hrac, pfrags, receipt_bytes, signature)
|
||||
|
||||
def payload(self):
|
||||
pfrags_as_bytes = [bytes(p) for p in self.pfrags]
|
||||
packed_receipt_and_pfrags = msgpack.dumps((self.receipt_bytes, msgpack.dumps(pfrags_as_bytes)))
|
||||
return bytes(self.receipt_signature) + self.bob.seal + packed_receipt_and_pfrags
|
||||
|
|
|
@ -5,6 +5,7 @@ import sha3
|
|||
from nacl.utils import EncryptedMessage
|
||||
|
||||
from nkms.crypto import api
|
||||
from nkms.crypto.fragments import PFrag
|
||||
from nkms.keystore.keypairs import PublicKey
|
||||
from npre import elliptic_curve as ec
|
||||
from npre import umbral
|
||||
|
@ -297,10 +298,9 @@ class TestCrypto(unittest.TestCase):
|
|||
self.assertEqual(list, type(frags))
|
||||
self.assertEqual(4, len(frags))
|
||||
|
||||
self.assertEqual(tuple, type(enc_eph_data))
|
||||
self.assertEqual(2, len(enc_eph_data))
|
||||
self.assertEqual(umbral.EncryptedKey, type(enc_eph_data[0]))
|
||||
self.assertEqual(EncryptedMessage, type(enc_eph_data[1]))
|
||||
self.assertEqual(PFrag._EXPECTED_LENGTH, len(enc_eph_data))
|
||||
self.assertEqual(umbral.EncryptedKey, type(enc_eph_data.deserialized()[0]))
|
||||
self.assertEqual(EncryptedMessage, type(enc_eph_data.deserialized()[1]))
|
||||
|
||||
def test_ecies_combine(self):
|
||||
eph_priv = self.pre.gen_priv()
|
||||
|
@ -320,7 +320,6 @@ class TestCrypto(unittest.TestCase):
|
|||
shares = [api.ecies_reencrypt(rk_frag, enc_key) for rk_frag in rk_selected]
|
||||
self.assertEqual(list, type(shares))
|
||||
self.assertEqual(6, len(shares))
|
||||
[self.assertEqual(umbral.EncryptedKey, type(share)) for share in shares]
|
||||
|
||||
e_b = api.ecies_combine(shares)
|
||||
self.assertEqual(umbral.EncryptedKey, type(e_b))
|
||||
|
@ -345,6 +344,6 @@ class TestCrypto(unittest.TestCase):
|
|||
self.assertEqual(umbral.RekeyFrag, type(rk_eb))
|
||||
self.assertEqual(ec.ec_element, type(rk_eb.key))
|
||||
|
||||
reenc_key = api.ecies_reencrypt(rk_eb, enc_key)
|
||||
dec_key = api.ecies_decapsulate(self.privkey_b, reenc_key)
|
||||
cfrag = api.ecies_reencrypt(rk_eb, enc_key)
|
||||
dec_key = api.ecies_decapsulate(self.privkey_b, cfrag.encrypted_key)
|
||||
self.assertEqual(plain_key, dec_key)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import pytest
|
||||
|
||||
from nkms.crypto import api
|
||||
from nkms.crypto.api import secure_random
|
||||
from nkms.crypto.fragments import KFrag
|
||||
from nkms.crypto.signature import Signature
|
||||
from nkms.crypto.utils import BytestringSplitter
|
||||
|
||||
|
@ -22,10 +24,22 @@ def test_split_signature_from_arbitrary_bytes():
|
|||
some_bytes = secure_random(how_many_bytes)
|
||||
splitter = BytestringSplitter(Signature, (bytes, how_many_bytes))
|
||||
|
||||
|
||||
rebuilt_signature, rebuilt_bytes = splitter(signature + some_bytes)
|
||||
|
||||
|
||||
def test_split_kfrag_from_arbitrary_bytes():
|
||||
rand_id = b'\x00' + api.secure_random(32)
|
||||
rand_key = b'\x00' + api.secure_random(32)
|
||||
kfrag = KFrag(rand_id + rand_key)
|
||||
|
||||
how_many_bytes = 10
|
||||
some_bytes = secure_random(how_many_bytes)
|
||||
|
||||
splitter = BytestringSplitter(KFrag, (bytes, how_many_bytes))
|
||||
rebuilt_kfrag, rebuilt_bytes = splitter(kfrag + some_bytes)
|
||||
assert kfrag == rebuilt_kfrag
|
||||
|
||||
|
||||
def test_trying_to_extract_too_many_bytes_raises_typeerror():
|
||||
how_many_bytes = 10
|
||||
too_many_bytes = 11
|
||||
|
|
|
@ -36,7 +36,7 @@ def enacted_policy_group(alices_policy_group, ursulas):
|
|||
|
||||
networky_stuff = MockNetworkyStuff(ursulas)
|
||||
alices_policy_group.find_n_ursulas(networky_stuff, offer)
|
||||
alices_policy_group.enact_policies(networky_stuff)
|
||||
alices_policy_group.enact_policies(networky_stuff) # REST call happens here.
|
||||
|
||||
return alices_policy_group
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import unittest
|
||||
import sha3
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from nkms.crypto.fragments import KFrag
|
||||
from nkms.keystore.db import Base
|
||||
from nkms.keystore import keystore, keypairs
|
||||
from npre.umbral import RekeyFrag
|
||||
|
@ -72,19 +74,24 @@ class TestKeyStore(unittest.TestCase):
|
|||
key = self.ks.get_key(fingerprint_priv)
|
||||
|
||||
def test_keyfrag_sqlite(self):
|
||||
kfrag_component_length = 32
|
||||
rand_sig = API.secure_random(65)
|
||||
rand_id = b'\x00' + API.secure_random(32)
|
||||
rand_key = b'\x00' + API.secure_random(32)
|
||||
rand_id = b'\x00' + API.secure_random(kfrag_component_length)
|
||||
rand_key = b'\x00' + API.secure_random(kfrag_component_length)
|
||||
rand_hrac = API.secure_random(32)
|
||||
|
||||
kfrag = RekeyFrag.from_bytes(rand_id+rand_key)
|
||||
kfrag = KFrag(rand_id+rand_key)
|
||||
self.ks.add_kfrag(rand_hrac, kfrag, sig=rand_sig)
|
||||
|
||||
# Check that kfrag was added
|
||||
kfrag, signature = self.ks.get_kfrag(rand_hrac, get_sig=True)
|
||||
kfrag_from_datastore, signature = self.ks.get_kfrag(rand_hrac, get_sig=True)
|
||||
self.assertEqual(rand_sig, signature)
|
||||
self.assertEqual(kfrag.id, rand_id)
|
||||
self.assertEqual(kfrag.key, rand_key)
|
||||
|
||||
# De/serialization happens here, by dint of the slicing interface, which casts the kfrag to bytes.
|
||||
# The +1 is to account for the metabyte.
|
||||
self.assertEqual(kfrag_from_datastore[:kfrag_component_length + 1], rand_id)
|
||||
self.assertEqual(kfrag_from_datastore[kfrag_component_length + 1:], rand_key)
|
||||
self.assertEqual(kfrag_from_datastore, kfrag)
|
||||
|
||||
# Check that kfrag gets deleted
|
||||
self.ks.del_kfrag(rand_hrac)
|
||||
|
|
|
@ -107,16 +107,6 @@ def test_alice_creates_policy_group_with_correct_hrac(alices_policy_group):
|
|||
bytes(alice.seal) + bytes(bob.seal) + alice.__resource_id)
|
||||
|
||||
|
||||
def test_alice_enacts_policies_in_policy_group_via_rest(enacted_policy_group):
|
||||
"""
|
||||
Now that Alice has made a PolicyGroup, she can enact its policies, using Ursula's Public Key to encrypt each offer
|
||||
and transmitting them via REST.
|
||||
"""
|
||||
ursula = enacted_policy_group.policies[0].ursula
|
||||
kfrag_that_was_set = ursula.keystore.get_kfrag(enacted_policy_group.hrac())
|
||||
assert bool(kfrag_that_was_set) # TODO: This can be a more poignant assertion.
|
||||
|
||||
|
||||
def test_alice_sets_treasure_map_on_network(enacted_policy_group, ursulas):
|
||||
"""
|
||||
Having enacted all the policies of a PolicyGroup, Alice creates a TreasureMap and sends it to Ursula via the DHT.
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
from tests.utilities import EVENT_LOOP
|
||||
from nkms.crypto import api
|
||||
from tests.utilities import EVENT_LOOP, MockNetworkyStuff
|
||||
|
||||
|
||||
def test_bob_can_follow_treasure_map(enacted_policy_group, ursulas):
|
||||
def test_alice_enacts_policies_in_policy_group_via_rest(enacted_policy_group):
|
||||
"""
|
||||
Now that Alice has made a PolicyGroup, she can enact its policies, using Ursula's Public Key to encrypt each offer
|
||||
and transmitting them via REST.
|
||||
"""
|
||||
ursula = enacted_policy_group.policies[0].ursula
|
||||
kfrag_that_was_set = ursula.keystore.get_kfrag(enacted_policy_group.hrac())
|
||||
assert bool(kfrag_that_was_set) # TODO: This can be a more poignant assertion.
|
||||
|
||||
|
||||
def test_bob_can_follow_treasure_map(enacted_policy_group, ursulas, alice, bob):
|
||||
"""
|
||||
Upon receiving a TreasureMap, Bob populates his list of Ursulas with the correct number.
|
||||
"""
|
||||
alice = enacted_policy_group.alice
|
||||
bob = enacted_policy_group.bob
|
||||
assert len(bob._ursulas) == 0
|
||||
|
||||
setter, encrypted_treasure_map, packed_encrypted_treasure_map, signature_for_bob, signature_for_ursula = alice.publish_treasure_map(
|
||||
|
@ -15,3 +24,41 @@ def test_bob_can_follow_treasure_map(enacted_policy_group, ursulas):
|
|||
|
||||
bob.follow_treasure_map(enacted_policy_group.treasure_map)
|
||||
assert len(bob._ursulas) == len(ursulas)
|
||||
|
||||
|
||||
def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_policy_group, alice, bob, ursulas):
|
||||
"""
|
||||
Now that Bob has his list of Ursulas, he can issue a WorkOrder to one. Upon receiving the WorkOrder, Ursula
|
||||
saves it and responds by re-encrypting and giving Bob a cFrag.
|
||||
|
||||
This is a multipart test; it shows proper relations between the Characters Ursula and Bob and also proper
|
||||
interchange between a KFrag, PFrag, and CFrag object in the context of REST-driven proxy re-encryption.
|
||||
"""
|
||||
|
||||
# We pick up our story with Bob already having followed the treasure map above, ie:
|
||||
assert len(bob._ursulas) == len(ursulas)
|
||||
|
||||
the_pfrag = enacted_policy_group.pfrag
|
||||
|
||||
# We'll test against just a single Ursula - here, we made a WorkOrder for just one.
|
||||
work_orders = bob.generate_work_orders(enacted_policy_group, the_pfrag, num_ursulas=1)
|
||||
assert len(work_orders) == 1
|
||||
|
||||
networky_stuff = MockNetworkyStuff(ursulas)
|
||||
|
||||
ursula_dht_key, work_order = list(work_orders.items())[0]
|
||||
cfrags = bob.get_reencrypted_c_frag(networky_stuff, work_order)
|
||||
|
||||
the_cfrag = cfrags[0] # We only gave one pFrag, so we only got one cFrag.
|
||||
|
||||
# Wow, Bob has his cFrag! Let's make sure everything went properly. First, we'll show that it is in fact
|
||||
# the correct cFrag (ie, that Ursula performed reencryption properly).
|
||||
ursula = networky_stuff.get_ursula_by_id(work_order.ursula_id)
|
||||
the_kfrag = ursula.keystore.get_kfrag(work_order.kfrag_hrac)
|
||||
the_correct_cfrag = api.ecies_reencrypt(the_kfrag, the_pfrag.encrypted_key)
|
||||
assert the_cfrag == the_correct_cfrag # It's the correct cfrag!
|
||||
|
||||
# Now we'll show that Ursula saved the correct WorkOrder.
|
||||
work_orders_from_bob = ursula.work_orders(bob=bob)
|
||||
assert len(work_orders_from_bob) == 1
|
||||
assert work_orders_from_bob[0] == work_order
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import asyncio
|
||||
|
||||
from apistar.test import TestClient
|
||||
import pytest
|
||||
from sqlalchemy.engine import create_engine
|
||||
|
||||
from nkms.characters import Ursula, Alice, Bob
|
||||
from apistar.test import TestClient
|
||||
from nkms.characters import Ursula
|
||||
from nkms.crypto.fragments import CFrag
|
||||
from nkms.crypto.utils import RepeatingBytestringSplitter
|
||||
from nkms.keystore import keystore
|
||||
from nkms.keystore.db import Base
|
||||
from nkms.network.node import NetworkyStuff
|
||||
|
||||
|
||||
|
||||
NUMBER_OF_URSULAS_IN_NETWORK = 6
|
||||
|
||||
EVENT_LOOP = asyncio.get_event_loop()
|
||||
|
@ -52,6 +53,7 @@ class MockPolicyOfferResponse(object):
|
|||
|
||||
class MockNetworkyStuff(NetworkyStuff):
|
||||
def __init__(self, ursulas):
|
||||
self._ursulas = {u.interface_dht_key(): u for u in ursulas}
|
||||
self.ursulas = iter(ursulas)
|
||||
|
||||
def go_live_with_policy(self, ursula, policy_offer):
|
||||
|
@ -70,3 +72,20 @@ class MockNetworkyStuff(NetworkyStuff):
|
|||
mock_client = TestClient(ursula.rest_app)
|
||||
response = mock_client.post('http://localhost/kFrag/{}'.format(hrac.hex()), payload)
|
||||
return True, ursula.interface_dht_key()
|
||||
|
||||
def get_ursula_by_id(self, ursula_id):
|
||||
print(self._ursulas)
|
||||
try:
|
||||
ursula = self._ursulas[ursula_id]
|
||||
except KeyError:
|
||||
pytest.fail("No Ursula with ID {}".format(ursula_id))
|
||||
return ursula
|
||||
|
||||
def reencrypt(self, work_order):
|
||||
print(work_order)
|
||||
ursula = self.get_ursula_by_id(work_order.ursula_id)
|
||||
mock_client = TestClient(ursula.rest_app)
|
||||
payload = work_order.payload()
|
||||
response = mock_client.post('http://localhost/kFrag/{}/reencrypt'.format(work_order.kfrag_hrac.hex()), payload)
|
||||
cfrags = RepeatingBytestringSplitter(CFrag)(response.content)
|
||||
return cfrags
|
||||
|
|
Loading…
Reference in New Issue