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
|
2018-02-24 05:38:46 +00:00
|
|
|
from typing import Union, List
|
2017-11-21 17:38:04 +00:00
|
|
|
|
2017-10-28 04:32:32 +00:00
|
|
|
import msgpack
|
2018-02-06 07:08:55 +00:00
|
|
|
import requests
|
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-11-10 10:04:01 +00:00
|
|
|
from nkms.crypto.api import secure_random, keccak_digest
|
2018-02-24 05:37:41 +00:00
|
|
|
from nkms.crypto.constants import NOT_SIGNED, NO_DECRYPTION_PERFORMED
|
|
|
|
from nkms.crypto.kits import MessageKit
|
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-03 21:50:04 +00:00
|
|
|
from nkms.network import blockchain_client
|
2018-02-24 05:37:41 +00:00
|
|
|
from nkms.network.constants import BYTESTRING_IS_URSULA_IFACE_INFO, BYTESTRING_IS_TREASURE_MAP
|
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
|
2018-02-24 05:37:41 +00:00
|
|
|
from umbral import pre
|
|
|
|
from umbral.fragments import KFrag
|
|
|
|
from umbral.keys import UmbralPublicKey
|
2017-10-28 04:32:32 +00:00
|
|
|
|
2017-12-21 01:21:00 +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
|
2018-02-24 06:39:10 +00:00
|
|
|
_stamp = 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
|
|
|
"""
|
2018-02-10 04:15:50 +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.
|
|
|
|
|
|
|
|
: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.")
|
|
|
|
|
2017-11-07 20:51:30 +00:00
|
|
|
if is_me:
|
2018-02-24 06:39:10 +00:00
|
|
|
self._stamp = SignatureStamp(self)
|
2017-11-03 04:54:58 +00:00
|
|
|
|
2017-11-07 22:25:03 +00:00
|
|
|
if attach_server:
|
|
|
|
self.attach_server()
|
|
|
|
else:
|
2018-02-24 06:39:10 +00:00
|
|
|
self._stamp = StrangerStamp(self)
|
2017-09-28 00:07:36 +00:00
|
|
|
|
2018-02-05 19:05:28 +00:00
|
|
|
if crypto_power:
|
|
|
|
self._crypto_power = crypto_power
|
|
|
|
elif crypto_power_ups:
|
2018-02-10 04:15:50 +00:00
|
|
|
self._crypto_power = CryptoPower(power_ups=crypto_power_ups,
|
|
|
|
generate_keys_if_needed=is_me)
|
2018-02-05 19:05:28 +00:00
|
|
|
else:
|
2018-02-10 04:15:50 +00:00
|
|
|
self._crypto_power = CryptoPower(self._default_crypto_powerups,
|
|
|
|
generate_keys_if_needed=is_me)
|
2018-02-05 19:05:28 +00:00
|
|
|
|
2017-12-05 20:48:40 +00:00
|
|
|
def __eq__(self, other):
|
2018-02-24 06:39:10 +00:00
|
|
|
return bytes(self.stamp) == bytes(other.stamp)
|
2017-12-05 20:48:40 +00:00
|
|
|
|
|
|
|
def __hash__(self):
|
2018-02-24 06:39:10 +00:00
|
|
|
return int.from_bytes(self.stamp, byteorder="big")
|
2017-12-05 20:48:40 +00:00
|
|
|
|
2017-11-22 04:20:15 +00:00
|
|
|
class NotFound(KeyError):
|
2018-02-10 04:15:50 +00:00
|
|
|
"""raised when we try to interact with an actor of whom we haven't \
|
|
|
|
learned yet."""
|
2017-11-22 04:20:15 +00:00
|
|
|
|
2017-12-16 05:46:14 +00:00
|
|
|
class SuspiciousActivity(RuntimeError):
|
|
|
|
"""raised when an action appears to amount to malicious conduct."""
|
|
|
|
|
2017-11-19 03:17:52 +00:00
|
|
|
@classmethod
|
2018-02-11 08:50:22 +00:00
|
|
|
def from_public_keys(cls, *powers_and_keys):
|
2018-02-07 01:15:53 +00:00
|
|
|
"""
|
2018-02-10 04:15:50 +00:00
|
|
|
Sometimes we discover a Character and, at the same moment, learn one or
|
|
|
|
more of their public keys. Here, we take a collection of tuples
|
|
|
|
(powers_and_key_bytes) in the following format:
|
2018-02-07 01:15:53 +00:00
|
|
|
(CryptoPowerUp class, public_key_bytes)
|
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
Each item in the collection will have the CryptoPowerUp instantiated
|
|
|
|
with the public_key_bytes, and the resulting CryptoPowerUp instance
|
|
|
|
consumed by the Character.
|
2018-02-07 01:15:53 +00:00
|
|
|
"""
|
2018-02-05 19:25:17 +00:00
|
|
|
crypto_power = CryptoPower()
|
2018-02-07 01:15:53 +00:00
|
|
|
|
2018-02-11 08:50:22 +00:00
|
|
|
for power_up, public_key in powers_and_keys:
|
|
|
|
try:
|
|
|
|
umbral_key = UmbralPublicKey(public_key)
|
|
|
|
except TypeError:
|
|
|
|
umbral_key = public_key
|
|
|
|
|
|
|
|
crypto_power.consume_power_up(power_up(pubkey=umbral_key))
|
2018-02-07 01:15:53 +00:00
|
|
|
|
2018-02-05 19:25:17 +00:00
|
|
|
return cls(is_me=False, crypto_power=crypto_power)
|
2017-11-19 03:17:52 +00:00
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
def attach_server(self, ksize=20, alpha=3, id=None,
|
|
|
|
storage=None, *args, **kwargs) -> None:
|
|
|
|
self._server = self._server_class(
|
2018-02-24 05:37:41 +00:00
|
|
|
ksize, alpha, id, storage, *args, **kwargs)
|
2017-09-28 00:07:36 +00:00
|
|
|
|
2017-11-03 04:54:58 +00:00
|
|
|
@property
|
2018-02-24 06:39:10 +00:00
|
|
|
def stamp(self):
|
|
|
|
if not self._stamp:
|
|
|
|
raise AttributeError("SignatureStamp has not been set up yet.")
|
2017-11-03 04:54:58 +00:00
|
|
|
else:
|
2018-02-24 06:39:10 +00:00
|
|
|
return self._stamp
|
2017-11-03 04:54:58 +00:00
|
|
|
|
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__
|
|
|
|
|
2018-02-14 08:07:55 +00:00
|
|
|
def encrypt_for(self,
|
|
|
|
recipient: "Character",
|
|
|
|
plaintext: bytes,
|
|
|
|
sign: bool=True,
|
|
|
|
sign_plaintext=True,
|
|
|
|
) -> tuple:
|
2017-10-05 19:20:53 +00:00
|
|
|
"""
|
2018-02-24 08:48:30 +00:00
|
|
|
Encrypts plaintext for recipient actor. Optionally signs the message as well.
|
2017-10-05 19:20:53 +00:00
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
:param recipient: The character whose public key will be used to encrypt
|
|
|
|
cleartext.
|
2018-02-11 08:51:21 +00:00
|
|
|
:param plaintext: The secret to be encrypted.
|
2017-10-05 19:20:53 +00:00
|
|
|
:param sign: Whether or not to sign the message.
|
2018-02-14 08:07:55 +00:00
|
|
|
:param sign_plaintext: When signing, the cleartext is signed if this is
|
2018-02-10 04:15:50 +00:00
|
|
|
True, Otherwise, the resulting ciphertext is signed.
|
|
|
|
|
|
|
|
:return: A tuple, (ciphertext, signature). If sign==False,
|
|
|
|
then signature will be NOT_SIGNED.
|
2017-10-05 19:20:53 +00:00
|
|
|
"""
|
2018-02-24 08:48:30 +00:00
|
|
|
recipient_pubkey_enc = recipient.public_key(EncryptingPower)
|
2017-10-05 19:20:53 +00:00
|
|
|
if sign:
|
2018-02-14 08:07:55 +00:00
|
|
|
if sign_plaintext:
|
2018-02-24 08:48:56 +00:00
|
|
|
# Sign first, encrypt second.
|
2018-02-24 06:39:10 +00:00
|
|
|
signature = self.stamp(plaintext)
|
2018-02-24 08:48:56 +00:00
|
|
|
ciphertext, capsule = pre.encrypt(recipient_pubkey_enc, signature + plaintext)
|
2017-10-05 19:20:53 +00:00
|
|
|
else:
|
2018-02-24 08:48:56 +00:00
|
|
|
# Encrypt first, sign second.
|
|
|
|
ciphertext, capsule = pre.encrypt(recipient_pubkey_enc, plaintext)
|
|
|
|
signature = self.stamp(ciphertext)
|
2017-10-05 19:20:53 +00:00
|
|
|
else:
|
2018-02-24 08:48:56 +00:00
|
|
|
# Don't sign.
|
2017-10-05 19:20:53 +00:00
|
|
|
signature = NOT_SIGNED
|
2018-02-24 08:48:56 +00:00
|
|
|
ciphertext, capsule = pre.encrypt(recipient_pubkey_enc, plaintext)
|
2017-10-05 19:20:53 +00:00
|
|
|
|
2018-02-24 08:48:56 +00:00
|
|
|
|
|
|
|
message_kit = MessageKit(ciphertext=ciphertext, capsule=capsule)
|
|
|
|
message_kit.alice_pubkey = self.public_key(SigningPower)
|
2018-02-11 08:51:21 +00:00
|
|
|
return message_kit, signature
|
2017-10-05 19:20:53 +00:00
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
def verify_from(self,
|
2018-02-24 05:41:54 +00:00
|
|
|
actor_whom_sender_claims_to_be: "Character",
|
|
|
|
message_kit: Union[MessageKit, bytes],
|
|
|
|
signature: Signature=None,
|
|
|
|
decrypt=False,
|
|
|
|
signature_is_on_cleartext=False) -> tuple:
|
2017-10-05 19:20:53 +00:00
|
|
|
"""
|
|
|
|
Inverse of encrypt_for.
|
|
|
|
|
2018-02-10 04:15:50 +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.
|
2017-10-13 04:31:03 +00:00
|
|
|
:param messages: The messages to be verified.
|
|
|
|
:param decrypt: Whether or not to decrypt the messages.
|
2018-02-10 04:15:50 +00:00
|
|
|
: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
|
|
|
"""
|
2018-02-14 08:11:15 +00:00
|
|
|
# TODO: In this flow we now essentially have two copies of the public key.
|
|
|
|
# One from the actor (first arg) and one from the MessageKit.
|
|
|
|
# Which do we use in which cases?
|
|
|
|
|
|
|
|
# if not signature and not signature_is_on_cleartext:
|
2018-02-24 05:41:54 +00:00
|
|
|
# TODO: Since a signature can now be in a MessageKit, this might not be accurate anymore.
|
|
|
|
# raise ValueError("You need to either provide the Signature or \
|
|
|
|
# decrypt and find it on the cleartext.")
|
2017-11-22 06:02:26 +00:00
|
|
|
|
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:
|
2018-02-14 08:11:15 +00:00
|
|
|
cleartext_with_sig = self.decrypt(message_kit)
|
2018-02-11 08:52:10 +00:00
|
|
|
signature, cleartext = BytestringSplitter(Signature)(cleartext_with_sig,
|
2018-02-24 05:41:54 +00:00
|
|
|
return_remainder=True)
|
2018-02-12 20:58:10 +00:00
|
|
|
message_kit.signature = signature # TODO: Obviously this is the wrong way to do this. Let's make signature a property.
|
2017-10-13 02:11:07 +00:00
|
|
|
else:
|
|
|
|
raise ValueError(
|
2018-02-10 04:15:50 +00:00
|
|
|
"Can't look for a signature on the cleartext if we're not \
|
|
|
|
decrypting.")
|
2018-02-14 08:11:15 +00:00
|
|
|
message = cleartext
|
2018-02-13 19:07:39 +00:00
|
|
|
alice_pubkey = message_kit.alice_pubkey
|
|
|
|
else:
|
2018-02-14 08:11:15 +00:00
|
|
|
# The signature is on the ciphertext. We might not even need to decrypt it.
|
|
|
|
if decrypt:
|
|
|
|
message = message_kit.ciphertext
|
|
|
|
cleartext = self.decrypt(message_kit)
|
|
|
|
# TODO: Fully deprecate actor lookup flow?
|
|
|
|
else:
|
|
|
|
message = bytes(message_kit)
|
2018-02-13 19:07:39 +00:00
|
|
|
alice_pubkey = actor_whom_sender_claims_to_be.public_key(SigningPower)
|
2017-10-13 02:11:07 +00:00
|
|
|
|
2018-02-14 08:11:34 +00:00
|
|
|
if signature:
|
|
|
|
is_valid = signature.verify(message, alice_pubkey)
|
|
|
|
else:
|
|
|
|
# Meh, we didn't even get a signature. Not much we can do.
|
|
|
|
is_valid = False
|
|
|
|
|
|
|
|
return is_valid, cleartext
|
|
|
|
|
2018-02-24 09:43:54 +00:00
|
|
|
"""
|
|
|
|
Next we have decrypt() and sign() - these two functions use the private keys of their respective powers;
|
|
|
|
any character who has these powers can use these functions.
|
|
|
|
|
|
|
|
If they don't have the correct Power, the appropriate PowerUpError is raised.
|
|
|
|
"""
|
|
|
|
|
2018-02-14 08:11:34 +00:00
|
|
|
def decrypt(self, message_kit):
|
2018-02-24 09:43:54 +00:00
|
|
|
return self._crypto_power.power_ups(EncryptingPower).decrypt(message_kit)
|
2017-10-05 19:20:53 +00:00
|
|
|
|
2018-02-24 09:22:23 +00:00
|
|
|
def sign(self, message):
|
|
|
|
return self._crypto_power.power_ups(SigningPower).sign(message)
|
|
|
|
|
2018-02-24 09:13:40 +00:00
|
|
|
def public_key(self, power_up_class):
|
|
|
|
power_up = self._crypto_power.power_ups(power_up_class)
|
|
|
|
return power_up.public_key()
|
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
|
|
|
|
2018-02-24 05:38:46 +00:00
|
|
|
def generate_kfrags(self, bob, m, n) -> List:
|
2017-10-19 20:13:41 +00:00
|
|
|
"""
|
2018-02-24 05:38:46 +00:00
|
|
|
Generates re-encryption key frags ("KFrags") and returns them.
|
|
|
|
|
|
|
|
These KFrags can be used by Ursula to re-encrypt a Capsule for Bob so
|
|
|
|
that he can activate the Capsule.
|
2017-10-19 20:13:41 +00:00
|
|
|
|
2018-02-24 08:49:40 +00:00
|
|
|
:param bob: Bob's public key
|
2018-02-24 05:38:46 +00:00
|
|
|
:param m: Minimum number of KFrags needed to rebuild ciphertext
|
2017-10-19 20:13:41 +00:00
|
|
|
:param n: Total number of rekey shares to generate
|
|
|
|
"""
|
2018-02-24 08:49:40 +00:00
|
|
|
bob_pubkey_enc = bob.public_key(EncryptingPower)
|
2018-02-24 09:44:14 +00:00
|
|
|
return self._crypto_power.power_ups(EncryptingPower).generate_kfrags(bob_pubkey_enc, m, n)
|
2017-10-12 22:30:17 +00:00
|
|
|
|
2017-12-16 05:47:02 +00:00
|
|
|
def create_policy(self,
|
2018-02-05 19:25:17 +00:00
|
|
|
bob: "Bob",
|
|
|
|
uri: bytes,
|
|
|
|
m: int,
|
|
|
|
n: int,
|
|
|
|
):
|
2017-12-16 05:47:02 +00:00
|
|
|
"""
|
2018-02-24 05:41:54 +00:00
|
|
|
Create a Policy to share uri with bob.
|
|
|
|
Generates KFrags and attaches them.
|
2017-12-16 05:47:02 +00:00
|
|
|
"""
|
2018-02-11 02:19:32 +00:00
|
|
|
kfrags = self.generate_kfrags(bob, m, n)
|
2018-02-10 04:15:50 +00:00
|
|
|
# TODO: Access Alice's private key inside this method.
|
2017-12-16 05:47:02 +00:00
|
|
|
from nkms.policy.models import Policy
|
|
|
|
policy = Policy.from_alice(
|
|
|
|
alice=self,
|
|
|
|
bob=bob,
|
|
|
|
kfrags=kfrags,
|
|
|
|
uri=uri,
|
2017-12-21 01:21:00 +00:00
|
|
|
m=m,
|
2017-12-16 05:47:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
return policy
|
|
|
|
|
2018-02-10 04:15:50 +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
|
|
|
|
2017-12-16 05:47:02 +00:00
|
|
|
policy = self.create_policy(bob, uri, m, n)
|
2017-12-10 01:21:08 +00:00
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
# We'll find n Ursulas by default. It's possible to "play the field"
|
|
|
|
# by trying differet
|
2017-12-15 04:31:54 +00:00
|
|
|
# deposits and expirations on a limited number of Ursulas.
|
|
|
|
# Users may decide to inject some market strategies here.
|
2018-02-24 05:41:54 +00:00
|
|
|
found_ursulas = policy.find_ursulas(networky_stuff, deposit,
|
2018-02-10 04:15:50 +00:00
|
|
|
expiration, num_ursulas=n)
|
2017-12-15 05:18:25 +00:00
|
|
|
policy.match_kfrags_to_found_ursulas(found_ursulas)
|
2018-02-10 04:15:50 +00:00
|
|
|
# REST call happens here, as does population of TreasureMap.
|
2018-02-24 05:41:54 +00:00
|
|
|
policy.enact(networky_stuff)
|
2017-12-15 04:31:54 +00:00
|
|
|
|
|
|
|
return policy
|
2017-12-10 01:21:08 +00:00
|
|
|
|
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-21 01:21:00 +00:00
|
|
|
from nkms.policy.models import WorkOrderHistory # Need a bigger strategy to avoid circulars.
|
|
|
|
self._saved_work_orders = WorkOrderHistory()
|
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._alice = alice_object
|
|
|
|
|
2017-12-15 05:18:50 +00:00
|
|
|
def follow_treasure_map(self, hrac):
|
|
|
|
for ursula_interface_id in self.treasure_maps[hrac]:
|
2017-12-12 00:55:35 +00:00
|
|
|
# TODO: perform this part concurrently.
|
2018-02-19 01:31:50 +00:00
|
|
|
value = self.server.get_now(ursula_interface_id)
|
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
# TODO: Make this much prettier
|
2018-02-19 01:31:50 +00:00
|
|
|
header, signature, ursula_pubkey_sig, _hrac, (port, interface, ttl) = dht_value_splitter(value, msgpack_remainder=True)
|
2018-02-15 20:30:43 +00:00
|
|
|
|
|
|
|
if header != BYTESTRING_IS_URSULA_IFACE_INFO:
|
|
|
|
raise TypeError("Unknown DHT value. How did this get on the network?")
|
2017-11-12 02:00:32 +00:00
|
|
|
|
|
|
|
# TODO: If we're going to implement TTL, it will be here.
|
2018-02-10 04:15:50 +00:00
|
|
|
self._ursulas[ursula_interface_id] =\
|
|
|
|
Ursula.as_discovered_on_network(
|
|
|
|
dht_port=port,
|
|
|
|
dht_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)
|
2018-02-10 04:15:50 +00:00
|
|
|
|
|
|
|
# TODO: Make this prettier
|
2018-02-15 20:30:43 +00:00
|
|
|
header, _signature_for_ursula, pubkey_sig_alice, hrac, encrypted_treasure_map =\
|
|
|
|
dht_value_splitter(packed_encrypted_treasure_map, return_remainder=True)
|
2018-02-14 08:19:26 +00:00
|
|
|
tmap_messaage_kit = MessageKit.from_bytes(encrypted_treasure_map)
|
2018-02-15 20:30:43 +00:00
|
|
|
|
|
|
|
if header != BYTESTRING_IS_TREASURE_MAP:
|
|
|
|
raise TypeError("Unknown DHT value. How did this get on the network?")
|
|
|
|
|
2018-02-14 08:19:26 +00:00
|
|
|
verified, packed_node_list = self.verify_from(
|
|
|
|
self.alice, tmap_messaage_kit,
|
2018-02-10 04:15:50 +00:00
|
|
|
signature_is_on_cleartext=True, decrypt=True
|
|
|
|
)
|
|
|
|
|
2017-10-28 04:32:32 +00:00
|
|
|
if not verified:
|
|
|
|
return NOT_FROM_ALICE
|
|
|
|
else:
|
|
|
|
from nkms.policy.models import TreasureMap
|
2018-02-10 04:15:50 +00:00
|
|
|
self.treasure_maps[policy_group.hrac] = TreasureMap(
|
|
|
|
msgpack.loads(packed_node_list)
|
|
|
|
)
|
2017-12-12 00:56:00 +00:00
|
|
|
return self.treasure_maps[policy_group.hrac]
|
2017-10-12 22:30:17 +00:00
|
|
|
|
2018-02-13 20:21:25 +00:00
|
|
|
def generate_work_orders(self, kfrag_hrac, *capsules, 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
|
|
|
|
2017-12-12 01:00:36 +00:00
|
|
|
try:
|
|
|
|
treasure_map_to_use = self.treasure_maps[kfrag_hrac]
|
|
|
|
except KeyError:
|
2018-02-10 04:15:50 +00:00
|
|
|
raise KeyError(
|
|
|
|
"Bob doesn't have a TreasureMap matching the hrac {}".format(kfrag_hrac))
|
2017-12-12 01:00:36 +00:00
|
|
|
|
2017-12-01 23:17:18 +00:00
|
|
|
generated_work_orders = {}
|
|
|
|
|
2017-12-12 01:02:45 +00:00
|
|
|
if not treasure_map_to_use:
|
2018-02-24 05:41:54 +00:00
|
|
|
raise ValueError(
|
|
|
|
"Bob doesn't have a TreasureMap to match any of these capsules: {}".format(
|
|
|
|
capsules))
|
2017-12-12 01:02:45 +00:00
|
|
|
|
|
|
|
for ursula_dht_key in treasure_map_to_use:
|
|
|
|
ursula = self._ursulas[ursula_dht_key]
|
2017-12-08 04:35:38 +00:00
|
|
|
|
2018-02-13 20:21:25 +00:00
|
|
|
capsules_to_include = []
|
|
|
|
for capsule in capsules:
|
2017-12-21 01:57:04 +00:00
|
|
|
if not capsule in self._saved_work_orders[ursula_dht_key]:
|
2018-02-13 20:21:25 +00:00
|
|
|
capsules_to_include.append(capsule)
|
2017-12-08 04:35:38 +00:00
|
|
|
|
2018-02-13 20:21:25 +00:00
|
|
|
if capsules_to_include:
|
2018-02-15 22:08:01 +00:00
|
|
|
work_order = WorkOrder.construct_by_bob(
|
2018-02-24 05:41:54 +00:00
|
|
|
kfrag_hrac, capsules_to_include, ursula_dht_key, self)
|
2017-12-08 04:35:38 +00:00
|
|
|
generated_work_orders[ursula_dht_key] = work_order
|
2018-02-19 23:18:32 +00:00
|
|
|
self._saved_work_orders[work_order.ursula_id][capsule] = 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.")
|
2018-02-13 20:21:25 +00:00
|
|
|
for counter, capsule in enumerate(work_order.capsules):
|
2017-12-08 04:36:09 +00:00
|
|
|
# TODO: Ursula is actually supposed to sign this. See #141.
|
2018-02-19 23:18:32 +00:00
|
|
|
# TODO: Maybe just update the work order here instead of setting it anew.
|
|
|
|
work_orders_by_ursula = self._saved_work_orders[work_order.ursula_id]
|
|
|
|
work_orders_by_ursula[capsule] = 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
|
|
|
|
2018-02-05 07:39:40 +00:00
|
|
|
dht_port = None
|
|
|
|
dht_interface = None
|
|
|
|
dht_ttl = 0
|
|
|
|
rest_address = None
|
|
|
|
rest_port = None
|
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
|
|
|
|
|
2018-02-05 07:39:40 +00:00
|
|
|
@classmethod
|
2018-02-10 04:15:50 +00:00
|
|
|
def as_discovered_on_network(cls, dht_port, dht_interface, pubkey_sig_bytes,
|
|
|
|
rest_address=None, rest_port=None):
|
2018-02-05 19:25:17 +00:00
|
|
|
# TODO: We also need the encrypting public key here.
|
2018-02-07 01:15:53 +00:00
|
|
|
ursula = cls.from_public_keys((SigningPower, pubkey_sig_bytes))
|
2018-02-05 07:39:40 +00:00
|
|
|
ursula.dht_port = dht_port
|
|
|
|
ursula.dht_interface = dht_interface
|
|
|
|
ursula.rest_address = rest_address
|
|
|
|
ursula.rest_port = rest_port
|
2017-11-07 22:25:03 +00:00
|
|
|
return ursula
|
2017-11-07 20:51:30 +00:00
|
|
|
|
2018-02-06 07:09:21 +00:00
|
|
|
@classmethod
|
|
|
|
def from_rest_url(cls, url):
|
|
|
|
response = requests.get(url)
|
|
|
|
if not response.status_code == 200:
|
|
|
|
raise RuntimeError("Got a bad response: {}".format(response))
|
2018-02-24 05:41:54 +00:00
|
|
|
signing_key_bytes, encrypting_key_bytes = \
|
|
|
|
BytestringSplitter(PublicKey)(response.content,
|
|
|
|
return_remainder=True)
|
2018-02-10 04:15:50 +00:00
|
|
|
stranger_ursula_from_public_keys = cls.from_public_keys(
|
2018-02-24 05:41:54 +00:00
|
|
|
signing=signing_key_bytes, encrypting=encrypting_key_bytes)
|
2018-02-06 07:09:21 +00:00
|
|
|
return stranger_ursula_from_public_keys
|
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
def attach_server(self, ksize=20, alpha=3, id=None,
|
|
|
|
storage=None, *args, **kwargs):
|
|
|
|
# TODO: Network-wide deterministic ID generation (ie, auction or
|
|
|
|
# whatever) See #136.
|
2017-11-03 04:54:58 +00:00
|
|
|
if not id:
|
2018-02-24 05:41:54 +00:00
|
|
|
id = digest(secure_random(32))
|
2017-11-03 04:54:58 +00:00
|
|
|
|
|
|
|
super().attach_server(ksize, alpha, id, storage)
|
|
|
|
|
2017-11-19 19:58:33 +00:00
|
|
|
routes = [
|
2018-02-10 04:15:50 +00:00
|
|
|
Route('/kFrag/{hrac_as_hex}',
|
|
|
|
'POST',
|
|
|
|
self.set_policy),
|
|
|
|
Route('/kFrag/{hrac_as_hex}/reencrypt',
|
|
|
|
'POST',
|
|
|
|
self.reencrypt_via_rest),
|
2018-02-24 05:41:54 +00:00
|
|
|
Route('/public_keys', 'GET',
|
2018-02-10 04:15:50 +00:00
|
|
|
self.get_signing_and_encrypting_public_keys),
|
|
|
|
Route('/consider_contract',
|
|
|
|
'POST',
|
|
|
|
self.consider_contract),
|
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):
|
2018-02-07 01:16:21 +00:00
|
|
|
self.dht_port = port
|
|
|
|
self.dht_interface = interface
|
2017-11-03 04:54:58 +00:00
|
|
|
return self.server.listen(port, interface)
|
|
|
|
|
2017-12-01 20:58:30 +00:00
|
|
|
def dht_interface_info(self):
|
2018-02-06 11:08:23 +00:00
|
|
|
return self.dht_port, self.dht_interface, self.dht_ttl
|
2017-11-10 10:04:01 +00:00
|
|
|
|
2017-12-21 01:57:55 +00:00
|
|
|
class InterfaceDHTKey:
|
2018-02-24 06:39:10 +00:00
|
|
|
def __init__(self, stamp, interface_hrac):
|
|
|
|
self.pubkey_sig_bytes = bytes(stamp)
|
2017-12-21 01:57:55 +00:00
|
|
|
self.interface_hrac = interface_hrac
|
|
|
|
|
|
|
|
def __bytes__(self):
|
2018-02-24 09:53:20 +00:00
|
|
|
return keccak_digest(self.pubkey_sig_bytes + self.interface_hrac)
|
2017-12-21 01:57:55 +00:00
|
|
|
|
2017-12-21 05:49:24 +00:00
|
|
|
def __add__(self, other):
|
|
|
|
return bytes(self) + other
|
|
|
|
|
|
|
|
def __radd__(self, other):
|
|
|
|
return other + bytes(self)
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return int.from_bytes(self, byteorder="big")
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
return bytes(self) == bytes(other)
|
|
|
|
|
2017-11-10 10:04:01 +00:00
|
|
|
def interface_dht_key(self):
|
2018-02-24 06:39:10 +00:00
|
|
|
return self.InterfaceDHTKey(self.stamp, self.interface_hrac())
|
2017-11-10 10:04:01 +00:00
|
|
|
|
|
|
|
def interface_dht_value(self):
|
2018-02-24 06:39:10 +00:00
|
|
|
signature = self.stamp(self.interface_hrac())
|
2018-02-10 04:15:50 +00:00
|
|
|
return (
|
2018-02-24 06:39:10 +00:00
|
|
|
BYTESTRING_IS_URSULA_IFACE_INFO + signature + self.stamp + self.interface_hrac()
|
2018-02-10 04:15:50 +00:00
|
|
|
+ msgpack.dumps(self.dht_interface_info())
|
|
|
|
)
|
2017-11-12 01:46:27 +00:00
|
|
|
|
|
|
|
def interface_hrac(self):
|
2018-02-24 09:53:20 +00:00
|
|
|
return keccak_digest(msgpack.dumps(self.dht_interface_info()))
|
2017-11-10 10:04:01 +00:00
|
|
|
|
2018-02-07 01:16:21 +00:00
|
|
|
def publish_dht_information(self):
|
|
|
|
if not self.dht_port and self.dht_interface:
|
2017-11-03 04:54:58 +00:00
|
|
|
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)
|
|
|
|
|
2018-02-06 07:09:46 +00:00
|
|
|
def get_signing_and_encrypting_public_keys(self):
|
|
|
|
"""
|
|
|
|
REST endpoint for getting both signing and encrypting public keys.
|
|
|
|
"""
|
2018-02-10 04:15:50 +00:00
|
|
|
return Response(
|
2018-02-24 06:39:10 +00:00
|
|
|
content=bytes(self.stamp) + bytes(self.public_key(EncryptingPower)),
|
2018-02-10 04:15:50 +00:00
|
|
|
content_type="application/octet-stream")
|
2018-02-06 07:09:46 +00:00
|
|
|
|
|
|
|
def consider_contract(self, hrac_as_hex, request: http.Request):
|
2018-02-10 04:15:50 +00:00
|
|
|
# TODO: This actually needs to be a REST endpoint, with the payload
|
|
|
|
# carrying the kfrag hash separately.
|
2018-02-06 07:09:46 +00:00
|
|
|
from nkms.policy.models import Contract
|
2018-02-24 05:41:54 +00:00
|
|
|
contract, deposit_as_bytes = \
|
2018-02-10 04:15:50 +00:00
|
|
|
BytestringSplitter(Contract)(request.body, return_remainder=True)
|
2018-02-06 07:09:46 +00:00
|
|
|
contract.deposit = deposit_as_bytes
|
|
|
|
|
2018-02-13 19:07:56 +00:00
|
|
|
# contract_to_store = { # TODO: This needs to be a datastore - see #127.
|
|
|
|
# "alice_pubkey_sig":
|
|
|
|
# "deposit": contract.deposit,
|
|
|
|
# # TODO: Whatever type "deposit" ends up being, we'll need to
|
|
|
|
# # serialize it here. See #148.
|
|
|
|
# "expiration": contract.expiration,
|
|
|
|
# }
|
|
|
|
self.keystore.add_policy_contract(contract.expiration.datetime(),
|
|
|
|
contract.deposit,
|
|
|
|
hrac=contract.hrac.hex().encode(),
|
2018-02-24 06:39:10 +00:00
|
|
|
alice_pubkey_sig=contract.alice.stamp
|
2018-02-13 19:07:56 +00:00
|
|
|
)
|
2018-02-10 04:15:50 +00:00
|
|
|
# TODO: Make the rest of this logic actually work - do something here
|
|
|
|
# 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")
|
2018-02-06 07:09:46 +00:00
|
|
|
|
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.
|
2018-02-10 04:15:50 +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-21 03:49:43 +00:00
|
|
|
hrac = binascii.unhexlify(hrac_as_hex)
|
2018-02-11 08:52:25 +00:00
|
|
|
policy_message_kit = MessageKit.from_bytes(request.body)
|
|
|
|
# group_payload_splitter = BytestringSplitter(PublicKey)
|
|
|
|
# policy_payload_splitter = BytestringSplitter((KFrag, KFRAG_LENGTH))
|
2017-12-16 05:47:52 +00:00
|
|
|
|
2018-02-11 08:52:25 +00:00
|
|
|
alice = Alice.from_public_keys((SigningPower, policy_message_kit.alice_pubkey))
|
2017-12-16 05:47:52 +00:00
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
verified, cleartext = self.verify_from(
|
2018-02-24 05:41:54 +00:00
|
|
|
alice, policy_message_kit,
|
|
|
|
decrypt=True, signature_is_on_cleartext=True)
|
2017-12-16 05:47:52 +00:00
|
|
|
|
|
|
|
if not verified:
|
|
|
|
# TODO: What do we do if the Policy isn't signed properly?
|
|
|
|
pass
|
2018-02-12 20:58:38 +00:00
|
|
|
#
|
|
|
|
# alices_signature, policy_payload =\
|
|
|
|
# BytestringSplitter(Signature)(cleartext, return_remainder=True)
|
2017-12-16 05:47:52 +00:00
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
# TODO: If we're not adding anything else in the payload, stop using the
|
|
|
|
# splitter here.
|
2018-02-12 20:58:38 +00:00
|
|
|
# kfrag = policy_payload_splitter(policy_payload)[0]
|
|
|
|
kfrag = KFrag.from_bytes(cleartext)
|
2017-12-16 05:47:52 +00:00
|
|
|
|
|
|
|
# TODO: Query stored Contract and reconstitute
|
2018-02-13 19:08:18 +00:00
|
|
|
policy_contract = self.keystore.get_policy_contract(hrac_as_hex.encode())
|
|
|
|
# contract_details = self._contracts[hrac.hex()]
|
2017-12-16 05:47:52 +00:00
|
|
|
|
2018-02-24 06:39:10 +00:00
|
|
|
if policy_contract.alice_pubkey_sig.key_data != alice.stamp:
|
2017-12-16 05:47:52 +00:00
|
|
|
raise Alice.SuspiciousActivity
|
|
|
|
|
2018-02-13 19:08:18 +00:00
|
|
|
# contract = Contract(alice=alice, hrac=hrac,
|
|
|
|
# kfrag=kfrag, expiration=policy_contract.expiration)
|
2017-11-19 03:19:39 +00:00
|
|
|
|
2017-11-17 00:21:48 +00:00
|
|
|
try:
|
2018-02-13 19:08:18 +00:00
|
|
|
# TODO: Obviously we do this lower-level.
|
|
|
|
policy_contract.k_frag = bytes(kfrag)
|
|
|
|
self.keystore.session.commit()
|
|
|
|
|
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-16 05:47:52 +00:00
|
|
|
return # TODO: Return A 200, with whatever policy metadata.
|
2017-12-02 00:01:07 +00:00
|
|
|
|
|
|
|
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)
|
2018-02-13 23:10:25 +00:00
|
|
|
kfrag_bytes = self.keystore.get_policy_contract(hrac.hex().encode()).k_frag # Careful! :-)
|
|
|
|
# TODO: Push this to a lower level.
|
|
|
|
kfrag = KFrag.from_bytes(kfrag_bytes)
|
2017-12-05 01:17:56 +00:00
|
|
|
cfrag_byte_stream = b""
|
2017-12-02 01:09:02 +00:00
|
|
|
|
2018-02-13 20:21:25 +00:00
|
|
|
for capsule in work_order.capsules:
|
2017-12-08 04:36:36 +00:00
|
|
|
# TODO: Sign the result of this. See #141.
|
2018-02-24 05:41:54 +00:00
|
|
|
cfrag_byte_stream += bytes(pre.reencrypt(kfrag, capsule))
|
2017-12-02 01:09:02 +00:00
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
# TODO: Put this in Ursula's datastore
|
2018-02-24 05:41:54 +00:00
|
|
|
self._work_orders.append(work_order)
|
2017-12-05 20:51:44 +00:00
|
|
|
|
2018-02-10 04:15:50 +00:00
|
|
|
return Response(content=cfrag_byte_stream,
|
|
|
|
content_type="application/octet-stream")
|
2017-12-05 20:51:44 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
2018-02-24 06:39:10 +00:00
|
|
|
class SignatureStamp(object):
|
2017-11-07 20:51:30 +00:00
|
|
|
"""
|
2018-02-10 04:15:50 +00:00
|
|
|
Can be called to sign something or used to express the signing public
|
|
|
|
key as bytes.
|
2017-11-07 20:51:30 +00:00
|
|
|
"""
|
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):
|
2018-02-24 09:22:23 +00:00
|
|
|
return self.character.sign(*args, **kwargs)
|
2017-11-07 20:51:30 +00:00
|
|
|
|
|
|
|
def __bytes__(self):
|
2018-02-24 09:13:40 +00:00
|
|
|
return bytes(self.character.public_key(SigningPower))
|
2017-11-07 20:51:30 +00:00
|
|
|
|
|
|
|
def __eq__(self, other):
|
2018-02-12 20:58:55 +00:00
|
|
|
return other == bytes(self)
|
2017-11-07 20:51:30 +00:00
|
|
|
|
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))
|
|
|
|
|
2018-02-13 23:29:35 +00:00
|
|
|
def as_umbral_pubkey(self):
|
|
|
|
return self.character.public_key(SigningPower)
|
2017-11-18 21:11:27 +00:00
|
|
|
|
2018-02-12 20:59:31 +00:00
|
|
|
def fingerprint(self):
|
|
|
|
"""
|
|
|
|
Hashes the key using keccak-256 and returns the hexdigest in bytes.
|
|
|
|
|
|
|
|
:return: Hexdigest fingerprint of key (keccak-256) in bytes
|
|
|
|
"""
|
|
|
|
return keccak_digest(bytes(self)).hex().encode()
|
|
|
|
|
2017-11-07 22:25:03 +00:00
|
|
|
|
2018-02-24 06:39:10 +00:00
|
|
|
class StrangerStamp(SignatureStamp):
|
2017-11-07 22:25:03 +00:00
|
|
|
"""
|
2018-02-24 06:39:10 +00:00
|
|
|
SignatureStamp of a stranger (ie, can only be used to glean public key, not to sign)
|
2017-11-07 22:25:03 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
raise TypeError(
|
2018-02-24 06:39:10 +00:00
|
|
|
"This isn't your SignatureStamp; it belongs to {} (a Stranger). You can't sign with it.".format(self.character))
|