Merge pull request #133 from jMyles/apistar

REST relationship for Bob and Ursula, with complete PRE using all three Fragment classes.
pull/145/head
Justin Holmes 2017-12-07 00:34:26 -08:00 committed by GitHub
commit 761553b26b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 388 additions and 72 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ __pycache__
/.idea
.coverage
_temp_test_datastore
.mypy_cache

View File

@ -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):

View File

@ -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)

119
nkms/crypto/fragments.py Normal file
View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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