PEP8 changes to characters

pull/157/head
tuxxy 2018-02-09 21:15:50 -07:00
parent 7f7c1207ef
commit 5b73db801e
1 changed files with 180 additions and 98 deletions

View File

@ -22,7 +22,6 @@ from nkms.crypto.constants import NOT_SIGNED, NO_DECRYPTION_PERFORMED, KFRAG_LEN
from nkms.crypto.powers import CryptoPower, SigningPower, EncryptingPower from nkms.crypto.powers import CryptoPower, SigningPower, EncryptingPower
from nkms.crypto.signature import Signature from nkms.crypto.signature import Signature
from nkms.crypto.utils import BytestringSplitter from nkms.crypto.utils import BytestringSplitter
from nkms.keystore.keypairs import PublicKey
from nkms.network import blockchain_client from nkms.network import blockchain_client
from nkms.network.protocols import dht_value_splitter from nkms.network.protocols import dht_value_splitter
from nkms.network.server import NuCypherDHTServer, NuCypherSeedOnlyDHTServer from nkms.network.server import NuCypherDHTServer, NuCypherSeedOnlyDHTServer
@ -41,19 +40,25 @@ class Character(object):
def __init__(self, attach_server=True, crypto_power: CryptoPower = None, def __init__(self, attach_server=True, crypto_power: CryptoPower = None,
crypto_power_ups=[], is_me=True) -> None: crypto_power_ups=[], is_me=True) -> None:
""" """
:param attach_server: Whether to attach a Server when this Character is born. :param attach_server: Whether to attach a Server when this Character is
:param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower. born.
:param crypto_power_ups: If crypto_power is not provided, a new CryptoPower will be made and :param crypto_power: A CryptoPower object; if provided, this will be the
will consume all of the CryptoPowerUps in this list. 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 If neither crypto_power nor crypto_power_ups are provided, we give this
listed in their _default_crypto_powerups attribute. Character all CryptoPowerUps listed in their _default_crypto_powerups
attribute.
:param is_me: Set this to True when you want this Character to represent the owner of the configuration under :param is_me: Set this to True when you want this Character to represent
which the program is being run. A Character who is_me can do things that other Characters can't, like run the owner of the configuration under which the program is being run.
servers, sign messages, and decrypt messages which are encrypted for them. Typically this will be True A Character who is_me can do things that other Characters can't,
for exactly one Character, but there are scenarios in which its imaginable to be represented by zero Characters like run servers, sign messages, and decrypt messages which are
or by more than one Character. 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.
""" """
self.log = getLogger("characters") self.log = getLogger("characters")
if crypto_power and crypto_power_ups: if crypto_power and crypto_power_ups:
@ -72,9 +77,11 @@ class Character(object):
if crypto_power: if crypto_power:
self._crypto_power = crypto_power self._crypto_power = crypto_power
elif crypto_power_ups: elif crypto_power_ups:
self._crypto_power = CryptoPower(power_ups=crypto_power_ups, generate_keys_if_needed=is_me) self._crypto_power = CryptoPower(power_ups=crypto_power_ups,
generate_keys_if_needed=is_me)
else: else:
self._crypto_power = CryptoPower(self._default_crypto_powerups, generate_keys_if_needed=is_me) self._crypto_power = CryptoPower(self._default_crypto_powerups,
generate_keys_if_needed=is_me)
def __eq__(self, other): def __eq__(self, other):
return bytes(self.seal) == bytes(other.seal) return bytes(self.seal) == bytes(other.seal)
@ -83,7 +90,8 @@ class Character(object):
return int.from_bytes(self.seal, byteorder="big") return int.from_bytes(self.seal, byteorder="big")
class NotFound(KeyError): class NotFound(KeyError):
"""raised when we try to interact with an actor of whom we haven't learned yet.""" """raised when we try to interact with an actor of whom we haven't \
learned yet."""
class SuspiciousActivity(RuntimeError): class SuspiciousActivity(RuntimeError):
"""raised when an action appears to amount to malicious conduct.""" """raised when an action appears to amount to malicious conduct."""
@ -91,12 +99,14 @@ class Character(object):
@classmethod @classmethod
def from_public_keys(cls, *powers_and_key_bytes): def from_public_keys(cls, *powers_and_key_bytes):
""" """
Sometimes we discover a Character and, at the same moment, learn one or more of their public keys. Sometimes we discover a Character and, at the same moment, learn one or
Here, we take a collection of tuples (powers_and_key_bytes) in the following format: more of their public keys. Here, we take a collection of tuples
(powers_and_key_bytes) in the following format:
(CryptoPowerUp class, public_key_bytes) (CryptoPowerUp class, public_key_bytes)
Each item in the collection will have the CryptoPowerUp instantiated with the public_key_bytes, and the resulting Each item in the collection will have the CryptoPowerUp instantiated
CryptoPowerUp instance consumed by the Character. with the public_key_bytes, and the resulting CryptoPowerUp instance
consumed by the Character.
""" """
crypto_power = CryptoPower() crypto_power = CryptoPower()
@ -105,9 +115,10 @@ class Character(object):
return cls(is_me=False, crypto_power=crypto_power) return cls(is_me=False, crypto_power=crypto_power)
def attach_server(self, ksize=20, alpha=3, id=None, storage=None, def attach_server(self, ksize=20, alpha=3, id=None,
*args, **kwargs) -> None: storage=None, *args, **kwargs) -> None:
self._server = self._server_class(ksize, alpha, id, storage, *args, **kwargs) self._server = self._server_class(
ksize, alpha, id, storage, *args, **kwargs)
@property @property
def seal(self): def seal(self):
@ -133,60 +144,73 @@ class Character(object):
def learn_about_actor(self, actor): def learn_about_actor(self, actor):
self._actor_mapping[actor.id()] = actor self._actor_mapping[actor.id()] = actor
def encrypt_for(self, recipient: "Character", cleartext: bytes, sign: bool = True, def encrypt_for(self, recipient: "Character", cleartext: bytes,
sign_cleartext=True) -> tuple: sign: bool=True, sign_cleartext=True) -> tuple:
""" """
Looks up recipient actor, finds that actor's pubkey_enc on our keyring, and encrypts for them. Looks up recipient actor, finds that actor's pubkey_enc on our keyring,
Optionally signs the message as well. and encrypts for them. Optionally signs the message as well.
:param recipient: The character whose public key will be used to encrypt cleartext. :param recipient: The character whose public key will be used to encrypt
:param cleartext: The secret to be encrypted. cleartext.
:param cleartext: The secret to be encrypted.
:param sign: Whether or not to sign the message. :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. :param sign_cleartext: When signing, the cleartext is signed if this is
:return: A tuple, (ciphertext, signature). If sign==False, then signature will be NOT_SIGNED. 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) actor = self._lookup_actor(recipient)
if sign: if sign:
if sign_cleartext: if sign_cleartext:
signature = self.seal(cleartext) signature = self.seal(cleartext)
ciphertext = self._crypto_power.encrypt_for(actor.public_key(EncryptingPower), ciphertext = self._crypto_power.encrypt_for(
signature + cleartext) actor.public_key(EncryptingPower), signature + cleartext)
else: else:
ciphertext = self._crypto_power.encrypt_for(actor.public_key(EncryptingPower), ciphertext = self._crypto_power.encrypt_for(
cleartext) actor.public_key(EncryptingPower), cleartext)
signature = self.seal(ciphertext) signature = self.seal(ciphertext)
else: else:
signature = NOT_SIGNED signature = NOT_SIGNED
ciphertext = self._crypto_power.encrypt_for(actor.public_key(EncryptingPower), ciphertext = self._crypto_power.encrypt_for(
cleartext) actor.public_key(EncryptingPower), cleartext)
return ciphertext, signature return ciphertext, signature
def verify_from(self, actor_whom_sender_claims_to_be: "Character", message: bytes, signature: Signature = None, def verify_from(self,
decrypt=False, actor_whom_sender_claims_to_be: "Character", message: bytes,
signature_is_on_cleartext=False) -> tuple: signature: Signature=None, decrypt=False,
signature_is_on_cleartext=False) -> tuple:
""" """
Inverse of encrypt_for. Inverse of encrypt_for.
: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 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 messages: The messages to be verified.
:param decrypt: Whether or not to decrypt the messages. :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. :param signature_is_on_cleartext: True if we expect the signature to be
:return: (Whether or not the signature is valid, the decrypted plaintext or NO_DECRYPTION_PERFORMED) 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
""" """
if not signature and not signature_is_on_cleartext: 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.") raise ValueError("You need to either provide the Signature or \
decrypt and find it on the cleartext.")
cleartext = NO_DECRYPTION_PERFORMED cleartext = NO_DECRYPTION_PERFORMED
if signature_is_on_cleartext: if signature_is_on_cleartext:
if decrypt: if decrypt:
cleartext = self._crypto_power.decrypt(message) cleartext = self._crypto_power.decrypt(message)
signature, message = BytestringSplitter(Signature)(cleartext, return_remainder=True) signature, message = BytestringSplitter(Signature)(cleartext,
return_remainder=True)
else: else:
raise ValueError( raise ValueError(
"Can't look for a signature on the cleartext if we're not decrypting.") "Can't look for a signature on the cleartext if we're not \
decrypting.")
actor = self._lookup_actor(actor_whom_sender_claims_to_be) actor = self._lookup_actor(actor_whom_sender_claims_to_be)
@ -196,16 +220,18 @@ class Character(object):
try: try:
return self._actor_mapping[actor.id()] return self._actor_mapping[actor.id()]
except KeyError: except KeyError:
raise self.NotFound("We haven't learned of an actor with ID {}".format(actor.id())) raise self.NotFound(
"We haven't learned of an actor with ID {}".format(actor.id()))
def id(self): def id(self):
return hexlify(bytes(self.seal)) return hexlify(bytes(self.seal))
def public_key(self, key_class): def public_key(self, key_class):
# TODO: Does it make sense to have a specialized exception here? Probably.
try: try:
return self._crypto_power.public_keys[key_class] return self._crypto_power.public_keys[key_class]
except KeyError: except KeyError:
raise # TODO: Does it make sense to have a specialized exception here? Probably. raise
class Alice(Character): class Alice(Character):
@ -240,8 +266,8 @@ class Alice(Character):
##### Temporary until we decide on an API for private key access ##### Temporary until we decide on an API for private key access
alice_priv_enc = self._crypto_power._power_ups[EncryptingPower].priv_key alice_priv_enc = self._crypto_power._power_ups[EncryptingPower].priv_key
kfrags, pfrag = self.generate_rekey_frags(alice_priv_enc, bob, m, kfrags, pfrag = self.generate_rekey_frags(alice_priv_enc, bob, m, n)
n) # TODO: Access Alice's private key inside this method. # TODO: Access Alice's private key inside this method.
from nkms.policy.models import Policy from nkms.policy.models import Policy
policy = Policy.from_alice( policy = Policy.from_alice(
alice=self, alice=self,
@ -253,7 +279,8 @@ class Alice(Character):
return policy return policy
def grant(self, bob, uri, networky_stuff, m=None, n=None, expiration=None, deposit=None): def grant(self, bob, uri, networky_stuff,
m=None, n=None, expiration=None, deposit=None):
if not m: if not m:
# TODO: get m from config # TODO: get m from config
raise NotImplementedError raise NotImplementedError
@ -272,12 +299,15 @@ class Alice(Character):
policy = self.create_policy(bob, uri, m, n) policy = self.create_policy(bob, uri, m, n)
# We'll find n Ursulas by default. It's possible to "play the field" by trying differet # We'll find n Ursulas by default. It's possible to "play the field"
# by trying differet
# deposits and expirations on a limited number of Ursulas. # deposits and expirations on a limited number of Ursulas.
# Users may decide to inject some market strategies here. # Users may decide to inject some market strategies here.
found_ursulas = policy.find_ursulas(networky_stuff, deposit, expiration, num_ursulas=n) found_ursulas = policy.find_ursulas(networky_stuff, deposit,
expiration, num_ursulas=n)
policy.match_kfrags_to_found_ursulas(found_ursulas) policy.match_kfrags_to_found_ursulas(found_ursulas)
policy.enact(networky_stuff) # REST call happens here, as does population of TreasureMap. # REST call happens here, as does population of TreasureMap.
policy.enact(networky_stuff)
return policy return policy
@ -312,12 +342,18 @@ class Bob(Character):
getter = self.server.get(ursula_interface_id) getter = self.server.get(ursula_interface_id)
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
value = loop.run_until_complete(getter) value = loop.run_until_complete(getter)
signature, ursula_pubkey_sig, hrac, (port, interface, ttl) = dht_value_splitter(value.lstrip(b"uaddr"),
msgpack_remainder=True) # TODO: Make this much prettier
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. # TODO: If we're going to implement TTL, it will be here.
self._ursulas[ursula_interface_id] = Ursula.as_discovered_on_network(dht_port=port, dht_interface=interface, self._ursulas[ursula_interface_id] =\
pubkey_sig_bytes=ursula_pubkey_sig) Ursula.as_discovered_on_network(
dht_port=port,
dht_interface=interface,
pubkey_sig_bytes=ursula_pubkey_sig
)
def get_treasure_map(self, policy_group): def get_treasure_map(self, policy_group):
@ -326,16 +362,26 @@ class Bob(Character):
ursula_coro = self.server.get(dht_key) ursula_coro = self.server.get(dht_key)
event_loop = asyncio.get_event_loop() event_loop = asyncio.get_event_loop()
packed_encrypted_treasure_map = event_loop.run_until_complete(ursula_coro) packed_encrypted_treasure_map = event_loop.run_until_complete(ursula_coro)
_signature_for_ursula, pubkey_sig_alice, hrac, encrypted_treasure_map = dht_value_splitter(
packed_encrypted_treasure_map[5::], msgpack_remainder=True) # TODO: Make this prettier
verified, cleartext = self.verify_from(self.alice, encrypted_treasure_map, _signature_for_ursula, pubkey_sig_alice, hrac, encrypted_treasure_map =\
signature_is_on_cleartext=True, decrypt=True) dht_value_splitter(
alices_signature, packed_node_list = BytestringSplitter(Signature)(cleartext, return_remainder=True) 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
)
alices_signature, packed_node_list =\
BytestringSplitter(Signature)(cleartext, return_remainder=True)
if not verified: if not verified:
return NOT_FROM_ALICE return NOT_FROM_ALICE
else: else:
from nkms.policy.models import TreasureMap from nkms.policy.models import TreasureMap
self.treasure_maps[policy_group.hrac] = TreasureMap(msgpack.loads(packed_node_list)) self.treasure_maps[policy_group.hrac] = TreasureMap(
msgpack.loads(packed_node_list)
)
return self.treasure_maps[policy_group.hrac] return self.treasure_maps[policy_group.hrac]
def generate_work_orders(self, kfrag_hrac, *pfrags, num_ursulas=None): def generate_work_orders(self, kfrag_hrac, *pfrags, num_ursulas=None):
@ -344,7 +390,8 @@ class Bob(Character):
try: try:
treasure_map_to_use = self.treasure_maps[kfrag_hrac] treasure_map_to_use = self.treasure_maps[kfrag_hrac]
except KeyError: except KeyError:
raise KeyError("Bob doesn't have a TreasureMap matching the hrac {}".format(kfrag_hrac)) raise KeyError(
"Bob doesn't have a TreasureMap matching the hrac {}".format(kfrag_hrac))
generated_work_orders = {} generated_work_orders = {}
@ -354,7 +401,8 @@ class Bob(Character):
for ursula_dht_key in treasure_map_to_use: for ursula_dht_key in treasure_map_to_use:
ursula = self._ursulas[ursula_dht_key] ursula = self._ursulas[ursula_dht_key]
completed_work_orders_for_this_ursula = self._saved_work_orders.setdefault(ursula_dht_key, []) completed_work_orders_for_this_ursula =\
self._saved_work_orders.setdefault(ursula_dht_key, [])
pfrags_to_include = [] pfrags_to_include = []
for pfrag in pfrags: for pfrag in pfrags:
@ -363,7 +411,8 @@ class Bob(Character):
pfrags_to_include.append(pfrag) pfrags_to_include.append(pfrag)
if pfrags_to_include: if pfrags_to_include:
work_order = WorkOrder.constructed_by_bob(kfrag_hrac, pfrags_to_include, ursula_dht_key, self) work_order = WorkOrder.constructed_by_bob(
kfrag_hrac, pfrags_to_include, ursula_dht_key, self)
generated_work_orders[ursula_dht_key] = work_order generated_work_orders[ursula_dht_key] = work_order
if num_ursulas is not None: if num_ursulas is not None:
@ -401,7 +450,8 @@ class Ursula(Character):
self._rest_app = None self._rest_app = None
self._work_orders = [] self._work_orders = []
self._contracts = {} # TODO: This needs to actually be a persistent data store. See #127. # TODO: This needs to actually be a persistent data store. See #127.
self._contracts = {}
@property @property
def rest_app(self): def rest_app(self):
@ -412,7 +462,8 @@ class Ursula(Character):
return self._rest_app return self._rest_app
@classmethod @classmethod
def as_discovered_on_network(cls, dht_port, dht_interface, pubkey_sig_bytes, rest_address=None, rest_port=None): def as_discovered_on_network(cls, dht_port, dht_interface, pubkey_sig_bytes,
rest_address=None, rest_port=None):
# TODO: We also need the encrypting public key here. # TODO: We also need the encrypting public key here.
ursula = cls.from_public_keys((SigningPower, pubkey_sig_bytes)) ursula = cls.from_public_keys((SigningPower, pubkey_sig_bytes))
ursula.dht_port = dht_port ursula.dht_port = dht_port
@ -426,25 +477,35 @@ class Ursula(Character):
response = requests.get(url) response = requests.get(url)
if not response.status_code == 200: if not response.status_code == 200:
raise RuntimeError("Got a bad response: {}".format(response)) raise RuntimeError("Got a bad response: {}".format(response))
signing_key_bytes, encrypting_key_bytes = BytestringSplitter(PublicKey)(response.content, return_remainder=True) signing_key_bytes, encrypting_key_bytes =\
stranger_ursula_from_public_keys = cls.from_public_keys(signing=signing_key_bytes, BytestringSplitter(PublicKey)(response.content,
encrypting=encrypting_key_bytes) return_remainder=True)
stranger_ursula_from_public_keys = cls.from_public_keys(
signing=signing_key_bytes, encrypting=encrypting_key_bytes)
return stranger_ursula_from_public_keys return stranger_ursula_from_public_keys
def attach_server(self, ksize=20, alpha=3, id=None, storage=None, def attach_server(self, ksize=20, alpha=3, id=None,
*args, **kwargs): storage=None, *args, **kwargs):
# TODO: Network-wide deterministic ID generation (ie, auction or
# whatever) See #136.
if not id: if not id:
id = digest( id = digest(secure_random(32))
secure_random(32)) # TODO: Network-wide deterministic ID generation (ie, auction or whatever) #136.
super().attach_server(ksize, alpha, id, storage) super().attach_server(ksize, alpha, id, storage)
routes = [ routes = [
Route('/kFrag/{hrac_as_hex}', 'POST', self.set_policy), Route('/kFrag/{hrac_as_hex}',
Route('/kFrag/{hrac_as_hex}/reencrypt', 'POST', self.reencrypt_via_rest), 'POST',
Route('/public_keys', 'GET', self.get_signing_and_encrypting_public_keys), self.set_policy),
Route('/consider_contract', 'POST', self.consider_contract), Route('/kFrag/{hrac_as_hex}/reencrypt',
'POST',
self.reencrypt_via_rest),
Route('/public_keys', 'GET',
self.get_signing_and_encrypting_public_keys),
Route('/consider_contract',
'POST',
self.consider_contract),
] ]
self._rest_app = App(routes=routes) self._rest_app = App(routes=routes)
@ -462,7 +523,10 @@ class Ursula(Character):
def interface_dht_value(self): def interface_dht_value(self):
signature = self.seal(self.interface_hrac()) signature = self.seal(self.interface_hrac())
return b"uaddr" + signature + self.seal + self.interface_hrac() + msgpack.dumps(self.dht_interface_info()) return (
b"uaddr" + signature + self.seal + self.interface_hrac()
+ msgpack.dumps(self.dht_interface_info())
)
def interface_hrac(self): def interface_hrac(self):
return self.hash(msgpack.dumps(self.dht_interface_info())) return self.hash(msgpack.dumps(self.dht_interface_info()))
@ -482,31 +546,40 @@ class Ursula(Character):
""" """
REST endpoint for getting both signing and encrypting public keys. REST endpoint for getting both signing and encrypting public keys.
""" """
return Response(content=bytes(self.seal) + bytes(self.public_key(EncryptingPower)), return Response(
content_type="application/octet-stream") content=bytes(self.seal) + bytes(self.public_key(EncryptingPower)),
content_type="application/octet-stream")
def consider_contract(self, hrac_as_hex, request: http.Request): def consider_contract(self, hrac_as_hex, request: http.Request):
# TODO: This actually needs to be a REST endpoint, with the payload carrying the kfrag hash separately. # TODO: This actually needs to be a REST endpoint, with the payload
# carrying the kfrag hash separately.
from nkms.policy.models import Contract from nkms.policy.models import Contract
contract, deposit_as_bytes = BytestringSplitter(Contract)(request.body, return_remainder=True) contract, deposit_as_bytes =\
BytestringSplitter(Contract)(request.body, return_remainder=True)
contract.deposit = deposit_as_bytes contract.deposit = deposit_as_bytes
contract_to_store = { # TODO: This needs to be a datastore - see #127. contract_to_store = { # TODO: This needs to be a datastore - see #127.
"alice_pubkey_sig": bytes(contract.alice.seal), "alice_pubkey_sig": bytes(contract.alice.seal),
"deposit": contract.deposit, "deposit": contract.deposit,
# TODO: Whatever type "deposit" ends up being, we'll need to serialize it here. See #148. # TODO: Whatever type "deposit" ends up being, we'll need to
# serialize it here. See #148.
"expiration": contract.expiration, "expiration": contract.expiration,
} }
self._contracts[contract.hrac] = contract_to_store self._contracts[contract.hrac] = contract_to_store
# TODO: Make the rest of this logic actually work - do something here to decide if this Contract is worth accepting. # TODO: Make the rest of this logic actually work - do something here
return Response(b"This will eventually be an actual acceptance of the contract.", content_type="application/octet-stream") # to decide if this Contract is worth accepting.
return Response(
b"This will eventually be an actual acceptance of the contract.",
content_type="application/octet-stream")
def set_policy(self, hrac_as_hex, request: http.Request): def set_policy(self, hrac_as_hex, request: http.Request):
""" """
REST endpoint for setting a kFrag. REST endpoint for setting a kFrag.
TODO: Instead of taking a Request, use the apistar typing system to type a payload and validate / split it. TODO: Instead of taking a Request, use the apistar typing system to type
TODO: Validate that the kfrag being saved is pursuant to an approved Policy (see #121). a payload and validate / split it.
TODO: Validate that the kfrag being saved is pursuant to an approved
Policy (see #121).
""" """
from nkms.policy.models import Contract # Avoid circular import from nkms.policy.models import Contract # Avoid circular import
hrac = binascii.unhexlify(hrac_as_hex) hrac = binascii.unhexlify(hrac_as_hex)
@ -514,20 +587,25 @@ class Ursula(Character):
group_payload_splitter = BytestringSplitter(PublicKey) group_payload_splitter = BytestringSplitter(PublicKey)
policy_payload_splitter = BytestringSplitter((KFrag, KFRAG_LENGTH)) policy_payload_splitter = BytestringSplitter((KFrag, KFRAG_LENGTH))
alice_pubkey_sig, payload_encrypted_for_ursula = group_payload_splitter(request.body, msgpack_remainder=True) alice_pubkey_sig, payload_encrypted_for_ursula =\
group_payload_splitter(request.body, msgpack_remainder=True)
alice = Alice.from_public_keys((SigningPower, alice_pubkey_sig)) alice = Alice.from_public_keys((SigningPower, alice_pubkey_sig))
self.learn_about_actor(alice) self.learn_about_actor(alice)
verified, cleartext = self.verify_from(alice, payload_encrypted_for_ursula, verified, cleartext = self.verify_from(
decrypt=True, signature_is_on_cleartext=True) alice, payload_encrypted_for_ursula,
decrypt=True, signature_is_on_cleartext=True)
if not verified: if not verified:
# TODO: What do we do if the Policy isn't signed properly? # TODO: What do we do if the Policy isn't signed properly?
pass pass
alices_signature, policy_payload = BytestringSplitter(Signature)(cleartext, return_remainder=True) alices_signature, policy_payload =\
BytestringSplitter(Signature)(cleartext, return_remainder=True)
kfrag = policy_payload_splitter(policy_payload)[0] # TODO: If we're not adding anything else in the payload, stop using the splitter here. # TODO: If we're not adding anything else in the payload, stop using the
# splitter here.
kfrag = policy_payload_splitter(policy_payload)[0]
# TODO: Query stored Contract and reconstitute # TODO: Query stored Contract and reconstitute
contract_details = self._contracts[hrac] contract_details = self._contracts[hrac]
@ -536,7 +614,8 @@ class Ursula(Character):
if stored_alice_pubkey_sig != alice_pubkey_sig: if stored_alice_pubkey_sig != alice_pubkey_sig:
raise Alice.SuspiciousActivity raise Alice.SuspiciousActivity
contract = Contract(alice=alice, hrac=hrac, kfrag=kfrag, **contract_details) contract = Contract(alice=alice, hrac=hrac,
kfrag=kfrag, **contract_details)
try: try:
self.keystore.add_kfrag(hrac, contract.kfrag, alices_signature) self.keystore.add_kfrag(hrac, contract.kfrag, alices_signature)
@ -557,9 +636,11 @@ class Ursula(Character):
# TODO: Sign the result of this. See #141. # TODO: Sign the result of this. See #141.
cfrag_byte_stream += API.ecies_reencrypt(kfrag, pfrag.encrypted_key) cfrag_byte_stream += API.ecies_reencrypt(kfrag, pfrag.encrypted_key)
self._work_orders.append(work_order) # TODO: Put this in Ursula's datastore # TODO: Put this in Ursula's datastore
self._work_orders.append(work_order)
return Response(content=cfrag_byte_stream, content_type="application/octet-stream") return Response(content=cfrag_byte_stream,
content_type="application/octet-stream")
def work_orders(self, bob=None): def work_orders(self, bob=None):
""" """
@ -577,7 +658,8 @@ class Ursula(Character):
class Seal(object): class Seal(object):
""" """
Can be called to sign something or used to express the signing public key as bytes. Can be called to sign something or used to express the signing public
key as bytes.
""" """
def __init__(self, character): def __init__(self, character):