2017-11-03 21:50:04 +00:00
import asyncio
2017-11-21 03:49:43 +00:00
import binascii
2017-11-28 04:26:06 +00:00
from binascii import hexlify
2017-11-21 17:38:04 +00:00
from logging import getLogger
2017-10-28 04:32:32 +00:00
import msgpack
2017-11-18 01:36:08 +00:00
from apistar import http
2017-11-19 19:58:33 +00:00
from apistar . core import Route
from apistar . frameworks . wsgi import WSGIApp as App
2017-12-05 20:48:40 +00:00
from apistar . http import Response
2017-09-28 00:07:36 +00:00
from kademlia . network import Server
2017-11-03 04:54:58 +00:00
from kademlia . utils import digest
2017-12-10 01:19:30 +00:00
from sqlalchemy . exc import IntegrityError
2017-10-19 20:13:41 +00:00
from nkms . crypto import api as API
2017-11-10 10:04:01 +00:00
from nkms . crypto . api import secure_random , keccak_digest
2017-10-13 02:11:07 +00:00
from nkms . crypto . constants import NOT_SIGNED , NO_DECRYPTION_PERFORMED
2017-10-17 21:01:25 +00:00
from nkms . crypto . powers import CryptoPower , SigningPower , EncryptingPower
2017-11-22 06:02:26 +00:00
from nkms . crypto . signature import Signature
from nkms . crypto . utils import BytestringSplitter
2017-11-16 23:36:31 +00:00
from nkms . keystore . keypairs import Keypair
2017-11-03 21:50:04 +00:00
from nkms . network import blockchain_client
2017-11-12 01:46:27 +00:00
from nkms . network . protocols import dht_value_splitter
2017-09-28 00:07:36 +00:00
from nkms . network . server import NuCypherDHTServer , NuCypherSeedOnlyDHTServer
2017-12-10 01:21:08 +00:00
from nkms . policy . constants import NOT_FROM_ALICE , NON_PAYMENT
2017-11-18 06:19:53 +00:00
2017-10-28 04:32:32 +00:00
2017-09-28 00:07:36 +00:00
class Character ( object ) :
"""
A base - class for any character in our cryptography protocol narrative .
"""
_server = None
_server_class = Server
2017-10-07 02:29:07 +00:00
_default_crypto_powerups = None
2017-11-03 04:54:58 +00:00
_seal = None
2017-09-28 00:07:36 +00:00
2017-10-07 02:29:07 +00:00
def __init__ ( self , attach_server = True , crypto_power : CryptoPower = None ,
2017-11-22 06:08:02 +00:00
crypto_power_ups = [ ] , is_me = True ) - > None :
2017-10-07 02:29:07 +00:00
"""
: param attach_server : Whether to attach a Server when this Character is born .
: param crypto_power : A CryptoPower object ; if provided , this will be the character ' s CryptoPower.
: param crypto_power_ups : If crypto_power is not provided , a new CryptoPower will be made and
will consume all of the CryptoPowerUps in this list .
If neither crypto_power nor crypto_power_ups are provided , we give this Character all CryptoPowerUps
listed in their _default_crypto_powerups attribute .
2017-11-22 04:20:15 +00:00
: param is_me : Set this to True when you want this Character to represent the owner of the configuration under
which the program is being run . A Character who is_me can do things that other Characters can ' t, like run
servers , sign messages , and decrypt messages which are encrypted for them . Typically this will be True
for exactly one Character , but there are scenarios in which its imaginable to be represented by zero Characters
or by more than one Character .
2017-10-07 02:29:07 +00:00
"""
2017-11-21 17:38:04 +00:00
self . log = getLogger ( " characters " )
2017-11-07 22:25:03 +00:00
if crypto_power and crypto_power_ups :
raise ValueError ( " Pass crypto_power or crypto_power_ups (or neither), but not both. " )
if crypto_power :
self . _crypto_power = crypto_power
elif crypto_power_ups :
self . _crypto_power = CryptoPower ( power_ups = crypto_power_ups )
else :
self . _crypto_power = CryptoPower ( self . _default_crypto_powerups )
2017-11-07 20:51:30 +00:00
if is_me :
self . _actor_mapping = { }
2017-10-07 02:29:07 +00:00
2017-11-07 20:51:30 +00:00
self . _seal = Seal ( self )
2017-11-03 04:54:58 +00:00
2017-11-07 22:25:03 +00:00
if attach_server :
self . attach_server ( )
else :
self . _seal = StrangerSeal ( self )
2017-09-28 00:07:36 +00:00
2017-12-05 20:48:40 +00:00
def __eq__ ( self , other ) :
return bytes ( self . seal ) == bytes ( other . seal )
def __hash__ ( self ) :
return int . from_bytes ( self . seal , byteorder = " big " )
2017-11-22 04:20:15 +00:00
class NotFound ( KeyError ) :
""" raised when we try to interact with an actor of whom we haven ' t learned yet. """
2017-11-19 03:17:52 +00:00
@classmethod
def from_pubkey_sig_bytes ( cls , pubkey_sig_bytes ) :
return cls ( is_me = False , crypto_power_ups = [ SigningPower ( keypair = Keypair . deserialize_key ( pubkey_sig_bytes ) ) ] )
2017-09-28 00:07:36 +00:00
def attach_server ( self , ksize = 20 , alpha = 3 , id = None , storage = None ,
* args , * * kwargs ) - > None :
self . _server = self . _server_class ( ksize , alpha , id , storage , * args , * * kwargs )
2017-11-03 04:54:58 +00:00
@property
def seal ( self ) :
if not self . _seal :
raise AttributeError ( " Seal has not been set up yet. " )
else :
return self . _seal
2017-09-28 00:07:36 +00:00
@property
def server ( self ) - > Server :
if self . _server :
return self . _server
else :
raise RuntimeError ( " Server hasn ' t been attached. " )
2017-10-14 22:07:10 +00:00
@property
def name ( self ) :
return self . __class__ . __name__
2017-11-10 18:47:07 +00:00
def hash ( self , message ) :
return keccak_digest ( message )
2017-10-07 02:29:07 +00:00
def learn_about_actor ( self , actor ) :
self . _actor_mapping [ actor . id ( ) ] = actor
2017-10-05 19:20:53 +00:00
2017-11-22 06:09:29 +00:00
def encrypt_for ( self , recipient : " Character " , cleartext : bytes , sign : bool = True ,
2017-10-13 21:06:33 +00:00
sign_cleartext = True ) - > tuple :
2017-10-05 19:20:53 +00:00
"""
Looks up recipient actor , finds that actor ' s pubkey_enc on our keyring, and encrypts for them.
Optionally signs the message as well .
: param recipient : The character whose public key will be used to encrypt cleartext .
2017-10-06 00:26:41 +00:00
: param cleartext : The secret to be encrypted .
2017-10-05 19:20:53 +00:00
: param sign : Whether or not to sign the message .
: param sign_cleartext : When signing , the cleartext is signed if this is True , Otherwise , the resulting ciphertext is signed .
: return : A tuple , ( ciphertext , signature ) . If sign == False , then signature will be NOT_SIGNED .
"""
actor = self . _lookup_actor ( recipient )
2017-10-13 02:11:07 +00:00
2017-10-05 19:20:53 +00:00
if sign :
if sign_cleartext :
2017-10-11 05:39:25 +00:00
signature = self . seal ( cleartext )
2017-11-22 06:02:26 +00:00
ciphertext = self . _crypto_power . encrypt_for ( actor . public_key ( EncryptingPower ) ,
signature + cleartext )
2017-10-05 19:20:53 +00:00
else :
2017-11-22 06:02:26 +00:00
ciphertext = self . _crypto_power . encrypt_for ( actor . public_key ( EncryptingPower ) ,
cleartext )
2017-10-11 05:39:25 +00:00
signature = self . seal ( ciphertext )
2017-10-05 19:20:53 +00:00
else :
signature = NOT_SIGNED
2017-11-22 06:02:26 +00:00
ciphertext = self . _crypto_power . encrypt_for ( actor . public_key ( EncryptingPower ) ,
cleartext )
2017-10-05 19:20:53 +00:00
return ciphertext , signature
2017-11-22 06:02:26 +00:00
def verify_from ( self , actor_whom_sender_claims_to_be : " Character " , message : bytes , signature : Signature = None ,
decrypt = False ,
2017-10-13 21:06:33 +00:00
signature_is_on_cleartext = False ) - > tuple :
2017-10-05 19:20:53 +00:00
"""
Inverse of encrypt_for .
2017-10-13 04:31:03 +00:00
: param actor_that_sender_claims_to_be : A Character instance representing the actor whom the sender claims to be . We check the public key owned by this Character instance to verify .
: param messages : The messages to be verified .
: param decrypt : Whether or not to decrypt the messages .
: param signature_is_on_cleartext : True if we expect the signature to be on the cleartext . Otherwise , we presume that the ciphertext is what is signed .
: return : ( Whether or not the signature is valid , the decrypted plaintext or NO_DECRYPTION_PERFORMED )
2017-10-05 19:20:53 +00:00
"""
2017-11-22 06:02:26 +00:00
if not signature and not signature_is_on_cleartext :
raise ValueError ( " You need to either provide the Signature or decrypt and find it on the cleartext. " )
2017-10-13 02:11:07 +00:00
cleartext = NO_DECRYPTION_PERFORMED
2017-11-22 06:02:26 +00:00
2017-10-13 02:11:07 +00:00
if signature_is_on_cleartext :
if decrypt :
2017-10-17 03:13:38 +00:00
cleartext = self . _crypto_power . decrypt ( message )
2017-11-22 06:02:26 +00:00
signature , message = BytestringSplitter ( Signature ) ( cleartext , return_remainder = True )
2017-10-13 02:11:07 +00:00
else :
raise ValueError (
" Can ' t look for a signature on the cleartext if we ' re not decrypting. " )
2017-10-05 19:20:53 +00:00
actor = self . _lookup_actor ( actor_whom_sender_claims_to_be )
2017-10-13 02:11:07 +00:00
2017-11-11 07:36:21 +00:00
return signature . verify ( message , actor . seal ) , cleartext
2017-10-05 19:20:53 +00:00
2017-10-07 02:29:07 +00:00
def _lookup_actor ( self , actor : " Character " ) :
2017-10-05 19:20:53 +00:00
try :
2017-10-07 02:29:07 +00:00
return self . _actor_mapping [ actor . id ( ) ]
2017-10-06 00:26:41 +00:00
except KeyError :
2017-10-09 21:03:16 +00:00
raise self . NotFound ( " We haven ' t learned of an actor with ID {} " . format ( actor . id ( ) ) )
2017-10-07 02:29:07 +00:00
def id ( self ) :
2017-11-28 04:26:06 +00:00
return hexlify ( bytes ( self . seal ) )
2017-10-05 19:20:53 +00:00
2017-10-17 04:45:43 +00:00
def public_key ( self , key_class ) :
2017-10-17 03:13:38 +00:00
try :
2017-10-17 04:45:43 +00:00
return self . _crypto_power . public_keys [ key_class ]
2017-10-17 03:13:38 +00:00
except KeyError :
2017-10-17 04:45:43 +00:00
raise # TODO: Does it make sense to have a specialized exception here? Probably.
2017-10-17 03:13:38 +00:00
2017-09-28 00:07:36 +00:00
class Alice ( Character ) :
_server_class = NuCypherSeedOnlyDHTServer
2017-10-17 21:01:25 +00:00
_default_crypto_powerups = [ SigningPower , EncryptingPower ]
2017-09-28 00:07:36 +00:00
2017-12-10 01:19:30 +00:00
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
from nkms . policy . models import PolicyManagerForAlice # Avoid circular import
self . policy_manager = PolicyManagerForAlice ( self )
2017-09-28 00:07:36 +00:00
def find_best_ursula ( self ) :
2017-11-03 04:54:58 +00:00
# TODO: This just finds *some* Ursula - let's have it find a particularly good one.
2017-11-03 21:50:04 +00:00
return list_all_ursulas ( ) [ 1 ]
2017-10-06 00:26:41 +00:00
2017-10-24 00:28:10 +00:00
def generate_rekey_frags ( self , alice_privkey , bob , m , n ) :
2017-10-19 20:13:41 +00:00
"""
Generates re - encryption key frags and returns the frags and encrypted
ephemeral key data .
: param alice_privkey : Alice ' s private key
: param bob_pubkey : Bob ' s public key
: param m : Minimum number of rekey shares needed to rebuild ciphertext
: param n : Total number of rekey shares to generate
: return : Tuple ( kfrags , eph_key_data )
"""
kfrags , eph_key_data = API . ecies_ephemeral_split_rekey (
2017-11-18 21:11:27 +00:00
alice_privkey , bytes ( bob . seal . without_metabytes ( ) ) , m , n )
2017-10-19 20:13:41 +00:00
return ( kfrags , eph_key_data )
2017-10-12 22:30:17 +00:00
2017-11-10 10:04:01 +00:00
def publish_treasure_map ( self , policy_group ) :
encrypted_treasure_map , signature_for_bob = self . encrypt_for ( policy_group . bob ,
policy_group . treasure_map . packed_payload ( ) )
2017-11-12 01:46:27 +00:00
signature_for_ursula = self . seal ( policy_group . hrac ( ) ) # TODO: Great use-case for Ciphertext class
2017-11-10 10:04:01 +00:00
# In order to know this is safe to propagate, Ursula needs to see a signature, our public key,
2017-11-10 18:47:07 +00:00
# and, reasons explained in treasure_map_dht_key above, the uri_hash.
2017-11-12 01:46:27 +00:00
dht_value = signature_for_ursula + self . seal + policy_group . hrac ( ) + msgpack . dumps (
encrypted_treasure_map ) # TODO: Ideally, this is a Ciphertext object instead of msgpack (see #112)
2017-11-10 18:47:07 +00:00
dht_key = policy_group . treasure_map_dht_key ( )
2017-11-10 10:04:01 +00:00
setter = self . server . set ( dht_key , b " trmap " + dht_value )
return setter , encrypted_treasure_map , dht_value , signature_for_bob , signature_for_ursula
2017-12-11 22:43:51 +00:00
def grant ( self , bob , uri , networky_stuff , m = None , n = None , expiration = None , deposit = None ) :
2017-12-10 01:21:08 +00:00
if not m :
# TODO: get m from config
raise NotImplementedError
if not n :
# TODO: get n from config
raise NotImplementedError
if not expiration :
# TODO: check default duration in config
raise NotImplementedError
2017-12-11 22:43:51 +00:00
if not deposit :
2017-12-12 00:55:00 +00:00
default_deposit = None # TODO: Check default deposit in config.
2017-12-11 22:43:51 +00:00
if not default_deposit :
deposit = networky_stuff . get_competitive_rate ( )
if deposit == NotImplemented :
deposit = NON_PAYMENT
2017-12-10 01:21:08 +00:00
policy_group = self . policy_manager . create_policy_group ( bob , uri , m , n )
offer = policy_group . craft_offer ( deposit , expiration )
policy_group . find_n_ursulas ( networky_stuff , offer )
policy_group . enact_policies ( networky_stuff ) # REST call happens here, as does population of TreasureMap.
return policy_group
2017-10-12 22:30:17 +00:00
class Bob ( Character ) :
2017-10-28 04:32:32 +00:00
_server_class = NuCypherSeedOnlyDHTServer
2017-10-17 21:01:25 +00:00
_default_crypto_powerups = [ SigningPower , EncryptingPower ]
2017-10-12 22:30:17 +00:00
2017-12-05 20:49:14 +00:00
def __init__ ( self , alice = None , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
2017-11-07 22:25:03 +00:00
self . _ursulas = { }
2017-12-12 00:55:00 +00:00
self . treasure_maps = { }
2017-10-28 04:32:32 +00:00
if alice :
self . alice = alice
2017-12-08 04:35:38 +00:00
self . _saved_work_orders = { }
2017-10-28 04:32:32 +00:00
@property
def alice ( self ) :
if not self . _alice :
raise Alice . NotFound
else :
return self . _alice
2017-10-28 02:26:23 +00:00
2017-10-28 04:32:32 +00:00
@alice.setter
def alice ( self , alice_object ) :
self . learn_about_actor ( alice_object )
self . _alice = alice_object
2017-12-12 00:55:35 +00:00
def follow_treasure_map ( self , pfrag ) :
for ursula_interface_id in self . treasure_maps [ pfrag ] :
# TODO: perform this part concurrently.
2017-11-07 20:51:30 +00:00
getter = self . server . get ( ursula_interface_id )
loop = asyncio . get_event_loop ( )
value = loop . run_until_complete ( getter )
2017-11-12 02:00:32 +00:00
signature , ursula_pubkey_sig , hrac , ( port , interface , ttl ) = dht_value_splitter ( value . lstrip ( b " uaddr " ) ,
msgpack_remainder = True )
# TODO: If we're going to implement TTL, it will be here.
2017-11-10 10:04:01 +00:00
self . _ursulas [ ursula_interface_id ] = Ursula . as_discovered_on_network ( port = port , interface = interface ,
pubkey_sig_bytes = ursula_pubkey_sig )
2017-11-07 20:51:30 +00:00
2017-11-22 06:02:26 +00:00
def get_treasure_map ( self , policy_group ) :
2017-11-10 10:04:01 +00:00
2017-11-10 18:47:07 +00:00
dht_key = policy_group . treasure_map_dht_key ( )
2017-11-10 10:04:01 +00:00
ursula_coro = self . server . get ( dht_key )
2017-10-28 02:26:23 +00:00
event_loop = asyncio . get_event_loop ( )
2017-10-28 17:53:56 +00:00
packed_encrypted_treasure_map = event_loop . run_until_complete ( ursula_coro )
2017-11-12 02:00:32 +00:00
_signature_for_ursula , pubkey_sig_alice , hrac , encrypted_treasure_map = dht_value_splitter (
packed_encrypted_treasure_map [ 5 : : ] , msgpack_remainder = True )
2017-11-22 06:02:26 +00:00
verified , cleartext = self . verify_from ( self . alice , encrypted_treasure_map ,
2017-12-05 20:49:14 +00:00
signature_is_on_cleartext = True , decrypt = True )
2017-11-22 06:02:26 +00:00
alices_signature , packed_node_list = BytestringSplitter ( Signature ) ( cleartext , return_remainder = True )
2017-10-28 04:32:32 +00:00
if not verified :
return NOT_FROM_ALICE
else :
from nkms . policy . models import TreasureMap
2017-12-12 00:56:00 +00:00
self . treasure_maps [ policy_group . hrac ] = TreasureMap ( msgpack . loads ( packed_node_list ) )
return self . treasure_maps [ policy_group . hrac ]
2017-10-12 22:30:17 +00:00
2017-12-12 00:58:40 +00:00
def generate_work_orders ( self , kfrag_hrac , * pfrags , num_ursulas = None ) :
2017-12-01 20:57:51 +00:00
from nkms . policy . models import WorkOrder # Prevent circular import
2017-12-01 23:17:18 +00:00
generated_work_orders = { }
for ursula_dht_key , ursula in self . _ursulas . items ( ) :
2017-12-08 04:35:38 +00:00
completed_work_orders_for_this_ursula = self . _saved_work_orders . setdefault ( ursula_dht_key , [ ] )
pfrags_to_include = [ ]
for pfrag in pfrags :
if not pfrag in sum ( [ wo . pfrags for wo in completed_work_orders_for_this_ursula ] , [ ] ) : # TODO: This is inane - probably push it down into a WorkOrderHistory concept.
pfrags_to_include . append ( pfrag )
if pfrags_to_include :
work_order = WorkOrder . constructed_by_bob ( policy_group . hrac ( ) , pfrags_to_include , ursula_dht_key , self )
generated_work_orders [ ursula_dht_key ] = work_order
2017-12-01 23:17:18 +00:00
if num_ursulas is not None :
if num_ursulas == len ( generated_work_orders ) :
break
return generated_work_orders
2017-12-08 04:36:09 +00:00
def get_reencrypted_c_frags ( self , networky_stuff , work_order ) :
2017-12-05 20:51:01 +00:00
cfrags = networky_stuff . reencrypt ( work_order )
2017-12-08 04:36:09 +00:00
if not len ( work_order ) == len ( cfrags ) :
raise ValueError ( " Ursula gave back the wrong number of cfrags. She ' s up to something. " )
for counter , pfrag in enumerate ( work_order . pfrags ) :
# TODO: Ursula is actually supposed to sign this. See #141.
self . _saved_work_orders [ work_order . ursula_id ] . append ( work_order )
2017-12-05 20:51:01 +00:00
return cfrags
def get_ursula ( self , ursula_id ) :
return self . _ursulas [ ursula_id ]
2017-12-01 20:57:51 +00:00
2017-11-03 04:54:58 +00:00
2017-10-12 22:30:17 +00:00
class Ursula ( Character ) :
_server_class = NuCypherDHTServer
2017-10-17 21:01:25 +00:00
_default_crypto_powerups = [ SigningPower , EncryptingPower ]
2017-10-27 01:39:13 +00:00
2017-11-03 04:54:58 +00:00
port = None
interface = None
2017-11-10 23:55:54 +00:00
interface_ttl = 0
2017-11-03 04:54:58 +00:00
2017-11-16 23:36:31 +00:00
def __init__ ( self , urulsas_keystore = None , * args , * * kwargs ) :
2017-11-16 23:14:43 +00:00
super ( ) . __init__ ( * args , * * kwargs )
2017-11-21 20:04:33 +00:00
self . keystore = urulsas_keystore
2017-11-16 23:14:43 +00:00
2017-11-19 19:58:33 +00:00
self . _rest_app = None
2017-12-05 20:51:44 +00:00
self . _work_orders = [ ]
2017-11-19 19:58:33 +00:00
@property
def rest_app ( self ) :
if not self . _rest_app :
2017-11-21 17:38:04 +00:00
raise AttributeError (
" This Ursula doesn ' t have a REST app attached. If you want one, init with is_me and attach_server. " )
2017-11-19 19:58:33 +00:00
else :
return self . _rest_app
2017-11-07 20:51:30 +00:00
@staticmethod
def as_discovered_on_network ( port , interface , pubkey_sig_bytes ) :
2017-11-19 03:17:52 +00:00
ursula = Ursula . from_pubkey_sig_bytes ( pubkey_sig_bytes )
2017-11-07 20:51:30 +00:00
ursula . port = port
ursula . interface = interface
2017-11-07 22:25:03 +00:00
return ursula
2017-11-07 20:51:30 +00:00
2017-11-03 04:54:58 +00:00
def attach_server ( self , ksize = 20 , alpha = 3 , id = None , storage = None ,
* args , * * kwargs ) :
if not id :
2017-12-01 23:21:36 +00:00
id = digest ( secure_random ( 32 ) ) # TODO: Network-wide deterministic ID generation (ie, auction or whatever) #136.
2017-11-03 04:54:58 +00:00
super ( ) . attach_server ( ksize , alpha , id , storage )
2017-11-19 19:58:33 +00:00
routes = [
2017-11-21 03:49:43 +00:00
Route ( ' /kFrag/ {hrac_as_hex} ' , ' POST ' , self . set_policy ) ,
2017-12-02 00:01:07 +00:00
Route ( ' /kFrag/ {hrac_as_hex} /reencrypt ' , ' POST ' , self . reencrypt_via_rest ) ,
2017-11-19 19:58:33 +00:00
]
self . _rest_app = App ( routes = routes )
2017-11-03 04:54:58 +00:00
def listen ( self , port , interface ) :
self . port = port
self . interface = interface
return self . server . listen ( port , interface )
2017-12-01 20:58:30 +00:00
def dht_interface_info ( self ) :
2017-11-12 01:46:27 +00:00
return self . port , self . interface , self . interface_ttl
2017-11-10 10:04:01 +00:00
def interface_dht_key ( self ) :
2017-11-12 01:46:27 +00:00
return self . hash ( self . seal + self . interface_hrac ( ) )
2017-11-10 10:04:01 +00:00
def interface_dht_value ( self ) :
2017-11-12 01:46:27 +00:00
signature = self . seal ( self . interface_hrac ( ) )
2017-12-01 20:58:30 +00:00
return b " uaddr " + signature + self . seal + self . interface_hrac ( ) + msgpack . dumps ( self . dht_interface_info ( ) )
2017-11-12 01:46:27 +00:00
def interface_hrac ( self ) :
2017-12-01 20:58:30 +00:00
return self . hash ( msgpack . dumps ( self . dht_interface_info ( ) ) )
2017-11-10 10:04:01 +00:00
2017-11-03 04:54:58 +00:00
def publish_interface_information ( self ) :
if not self . port and self . interface :
raise RuntimeError ( " Must listen before publishing interface information. " )
2017-11-06 03:04:04 +00:00
2017-11-10 10:04:01 +00:00
dht_key = self . interface_dht_key ( )
value = self . interface_dht_value ( )
setter = self . server . set ( key = dht_key , value = value )
blockchain_client . _ursulas_on_blockchain . append ( dht_key )
2017-11-03 04:54:58 +00:00
loop = asyncio . get_event_loop ( )
loop . run_until_complete ( setter )
2017-11-21 03:49:43 +00:00
def set_policy ( self , hrac_as_hex , request : http . Request ) :
2017-11-16 23:07:12 +00:00
"""
REST endpoint for setting a kFrag .
2017-11-18 01:36:08 +00:00
TODO : Instead of taking a Request , use the apistar typing system to type a payload and validate / split it .
TODO : Validate that the kfrag being saved is pursuant to an approved Policy ( see #121).
2017-11-16 23:07:12 +00:00
"""
2017-11-18 06:19:53 +00:00
from nkms . policy . models import Policy # Avoid circular import
2017-11-21 03:49:43 +00:00
hrac = binascii . unhexlify ( hrac_as_hex )
2017-11-18 06:19:53 +00:00
policy = Policy . from_ursula ( request . body , self )
2017-11-19 03:19:39 +00:00
2017-11-17 00:21:48 +00:00
try :
2017-11-21 03:49:43 +00:00
self . keystore . add_kfrag ( hrac , policy . kfrag , policy . alices_signature )
2017-11-21 03:19:18 +00:00
except IntegrityError :
2017-11-17 00:21:48 +00:00
raise
2017-11-19 03:19:39 +00:00
# Do something appropriately RESTful (ie, 4xx).
2017-11-17 00:21:48 +00:00
2017-12-02 00:01:07 +00:00
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 )
2017-12-02 01:09:02 +00:00
kfrag = self . keystore . get_kfrag ( hrac ) # Careful! :-)
2017-12-05 01:17:56 +00:00
cfrag_byte_stream = b " "
2017-12-02 01:09:02 +00:00
for pfrag in work_order . pfrags :
2017-12-08 04:36:36 +00:00
# TODO: Sign the result of this. See #141.
2017-12-05 20:51:44 +00:00
cfrag_byte_stream + = API . ecies_reencrypt ( kfrag , pfrag . encrypted_key )
2017-12-02 01:09:02 +00:00
2017-12-05 20:51:44 +00:00
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
2017-11-16 23:07:12 +00:00
2017-11-07 20:51:30 +00:00
class Seal ( object ) :
"""
Can be called to sign something or used to express the signing public key as bytes .
"""
2017-11-07 22:25:03 +00:00
2017-11-07 20:51:30 +00:00
def __init__ ( self , character ) :
self . character = character
def __call__ ( self , * args , * * kwargs ) :
return self . character . _crypto_power . sign ( * args , * * kwargs )
def _as_tuple ( self ) :
return self . character . _crypto_power . pubkey_sig_tuple ( )
def __iter__ ( seal ) :
yield from seal . _as_tuple ( )
def __bytes__ ( self ) :
return self . character . _crypto_power . pubkey_sig_bytes ( )
def __eq__ ( self , other ) :
return other == self . _as_tuple ( ) or other == bytes ( self )
2017-11-11 23:49:15 +00:00
def __add__ ( self , other ) :
return bytes ( self ) + other
def __radd__ ( self , other ) :
return other + bytes ( self )
def __len__ ( self ) :
return len ( bytes ( self ) )
2017-11-18 21:11:27 +00:00
def without_metabytes ( self ) :
return self . character . _crypto_power . pubkey_sig_bytes ( ) . without_metabytes ( )
2017-11-07 22:25:03 +00:00
class StrangerSeal ( Seal ) :
"""
Seal of a stranger ( ie , can only be used to glean public key , not to sign )
"""
def __call__ ( self , * args , * * kwargs ) :
raise TypeError (
" This isn ' t your Seal; it belongs to {} (a Stranger). You can ' t sign with it. " . format ( self . character ) )
2017-11-07 20:51:30 +00:00
def congregate ( * characters ) :
2017-10-27 01:39:13 +00:00
for character in characters :
for newcomer in characters :
2017-11-03 04:54:58 +00:00
character . learn_about_actor ( newcomer )