Merge pull request #9 from jMyles/rm-crypto

Kits, splitters, new encrypt and decrypt, more
pull/161/head
Tux 2018-02-11 02:15:25 -07:00 committed by GitHub
commit 6aeef2f6ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 214 additions and 196 deletions

View File

@ -13,6 +13,7 @@ from kademlia.network import Server
from kademlia.utils import digest
from sqlalchemy.exc import IntegrityError
from nkms.crypto.kits import MessageKit
from umbral.fragments import KFrag
from umbral.keys import UmbralPublicKey
import umbral
@ -98,7 +99,7 @@ class Character(object):
"""raised when an action appears to amount to malicious conduct."""
@classmethod
def from_public_keys(cls, *powers_and_key_bytes):
def from_public_keys(cls, *powers_and_keys):
"""
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
@ -111,8 +112,13 @@ class Character(object):
"""
crypto_power = CryptoPower()
for power_up, public_key_bytes in powers_and_key_bytes:
crypto_power.consume_power_up(power_up(pubkey_bytes=public_key_bytes))
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))
return cls(is_me=False, crypto_power=crypto_power)
@ -145,7 +151,7 @@ class Character(object):
def learn_about_actor(self, actor):
self._actor_mapping[actor.id()] = actor
def encrypt_for(self, recipient: "Character", cleartext: bytes,
def encrypt_for(self, recipient: "Character", plaintext: bytes,
sign: bool=True, sign_cleartext=True) -> tuple:
"""
Looks up recipient actor, finds that actor's pubkey_enc on our keyring,
@ -153,7 +159,7 @@ class Character(object):
:param recipient: The character whose public key will be used to encrypt
cleartext.
:param cleartext: The secret to be encrypted.
:param plaintext: The secret to be encrypted.
: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.
@ -165,24 +171,24 @@ class Character(object):
if sign:
if sign_cleartext:
signature = self.seal(cleartext)
ciphertext = self._crypto_power.encrypt_for(
actor.public_key(EncryptingPower), signature + cleartext)
signature = self.seal(plaintext)
message_kit = self._crypto_power.encrypt_for(
actor.public_key(EncryptingPower), signature + plaintext)
else:
ciphertext = self._crypto_power.encrypt_for(
actor.public_key(EncryptingPower), cleartext)
signature = self.seal(ciphertext)
message_kit = self._crypto_power.encrypt_for(
actor.public_key(EncryptingPower), plaintext)
signature = self.seal(message_kit)
else:
signature = NOT_SIGNED
ciphertext = self._crypto_power.encrypt_for(
actor.public_key(EncryptingPower), cleartext)
message_kit = self._crypto_power.encrypt_for(
actor.public_key(EncryptingPower), plaintext)
return ciphertext, signature
return message_kit, signature
def verify_from(self,
actor_whom_sender_claims_to_be: "Character", message: bytes,
signature: Signature=None, decrypt=False,
signature_is_on_cleartext=False) -> tuple:
actor_whom_sender_claims_to_be: "Character", message_kit: MessageKit,
signature: Signature=None, decrypt=False,
signature_is_on_cleartext=False) -> tuple:
"""
Inverse of encrypt_for.
@ -205,17 +211,17 @@ class Character(object):
if signature_is_on_cleartext:
if decrypt:
cleartext = self._crypto_power.decrypt(message)
signature, message = BytestringSplitter(Signature)(cleartext,
return_remainder=True)
cleartext_with_sig = self._crypto_power.decrypt(message_kit)
signature, cleartext = BytestringSplitter(Signature)(cleartext_with_sig,
return_remainder=True)
else:
raise ValueError(
"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)
return signature.verify(message, actor.seal), cleartext
return signature.verify(message_kit.ciphertext, message_kit.alice_pubkey), cleartext
def _lookup_actor(self, actor: "Character"):
try:
@ -581,17 +587,15 @@ class Ursula(Character):
"""
from nkms.policy.models import Contract # Avoid circular import
hrac = binascii.unhexlify(hrac_as_hex)
policy_message_kit = MessageKit.from_bytes(request.body)
# group_payload_splitter = BytestringSplitter(PublicKey)
# policy_payload_splitter = BytestringSplitter((KFrag, KFRAG_LENGTH))
group_payload_splitter = BytestringSplitter(PublicKey)
policy_payload_splitter = BytestringSplitter((KFrag, KFRAG_LENGTH))
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, policy_message_kit.alice_pubkey))
self.learn_about_actor(alice)
verified, cleartext = self.verify_from(
alice, payload_encrypted_for_ursula,
alice, policy_message_kit,
decrypt=True, signature_is_on_cleartext=True)
if not verified:

View File

@ -1,12 +1,28 @@
from nkms.crypto.splitters import key_splitter, capsule_splitter
from umbral import umbral
class MessageKit:
class CryptoKit:
return_remainder_when_splitting = True
def __init__(self, ciphertext, capsule, alice_pubkey=None):
@classmethod
def split_bytes(cls, some_bytes):
return cls.splitter(some_bytes,
return_remainder=cls.return_remainder_when_splitting)
@classmethod
def from_bytes(cls, some_bytes):
constituents = cls.split_bytes(some_bytes)
return cls(*constituents)
class MessageKit(CryptoKit):
splitter = capsule_splitter + key_splitter
def __init__(self, capsule, alice_pubkey, ciphertext):
self.ciphertext = ciphertext
self.capsule = capsule
self.alice_pub_key = alice_pubkey
self.alice_pubkey = alice_pubkey
def decrypt(self, privkey):
return umbral.decrypt(
@ -15,9 +31,15 @@ class MessageKit:
self.alice_pubkey
)
def __bytes__(self):
as_bytes = bytes(self.capsule)
if self.alice_pubkey:
as_bytes += bytes(self.alice_pubkey)
as_bytes += self.ciphertext
return as_bytes
class MapKit(MessageKit):
def __init__(self, ciphertext, capsule, treasure_map, alice_pubkey=None):
super().__init__(ciphertext, capsule, alice_pubkey)
self.treasure_map = treasure_map
self.treasure_map = treasure_map

View File

@ -1,11 +1,12 @@
import inspect
from typing import Iterable, List, Tuple
import umbral
from nkms.crypto import api as API
from nkms.crypto.signature import Signature
from nkms.crypto.kits import MessageKit
from nkms.keystore import keypairs
from nkms.keystore.keypairs import SigningKeypair, EncryptingKeypair
from umbral.keys import UmbralPublicKey
from umbral.keys import UmbralPublicKey, UmbralPrivateKey
class PowerUpError(TypeError):
@ -22,7 +23,6 @@ class NoEncryptingPower(PowerUpError):
class CryptoPower(object):
def __init__(self, power_ups=None, generate_keys_if_needed=False):
self._power_ups = {}
# TODO: The keys here will actually be IDs for looking up in a KeyStore.
self.public_keys = {}
@ -89,13 +89,10 @@ class CryptoPower(object):
except KeyError:
raise NoEncryptingPower
def encrypt_for(self, pubkey, cleartext):
try:
encrypting_power = self._power_ups[EncryptingPower]
ciphertext = encrypting_power.encrypt(cleartext, bytes(pubkey))
return ciphertext
except KeyError:
raise NoEncryptingPower
def encrypt_for(self, pubkey, plaintext):
ciphertext, capsule = umbral.umbral.encrypt(pubkey, plaintext)
return MessageKit(ciphertext=ciphertext, capsule=capsule,
alice_pubkey=pubkey)
class CryptoPowerUp(object):
@ -108,10 +105,10 @@ class CryptoPowerUp(object):
class KeyPairBasedPower(CryptoPowerUp):
_keypair_class = keypairs.Keypair
def __init__(self, keypair: keypairs.Keypair = None,
pubkey_bytes: bytes = None,
def __init__(self, keypair: keypairs.Keypair=None,
pubkey: UmbralPublicKey=None,
generate_keys_if_needed=True) -> None:
if keypair and pubkey_bytes:
if keypair and pubkey:
raise ValueError(
"Pass keypair or pubkey_bytes (or neither), but not both.")
elif keypair:
@ -119,9 +116,13 @@ class KeyPairBasedPower(CryptoPowerUp):
else:
# They didn't pass a keypair; we'll make one with the bytes (if any)
# they provided.
if pubkey:
key_to_pass_to_keypair = pubkey
else:
# They didn't even pass pubkey_bytes. We'll generate a keypair.
key_to_pass_to_keypair = UmbralPrivateKey.gen_key()
self.keypair = self._keypair_class(
UmbralPublicKey.from_bytes(pubkey_bytes),
generate_keys_if_needed=generate_keys_if_needed)
umbral_key=key_to_pass_to_keypair)
class SigningPower(KeyPairBasedPower):
@ -232,49 +233,14 @@ class EncryptingPower(KeyPairBasedPower):
keys.append((path_priv, path_pub))
return keys
def encrypt(
self,
data: bytes,
pubkey: bytes = None
) -> Tuple[bytes, bytes]:
"""
Encrypts data with Public key encryption
:param data: Data to encrypt
:param pubkey: publc key to encrypt for
:return: (Encrypted Key, Encrypted data)
"""
pubkey = pubkey or self.pub_key
key, enc_key = API.ecies_encapsulate(pubkey)
enc_data = API.symm_encrypt(key, data)
return (enc_data, API.elliptic_curve.serialize(enc_key.ekey))
def decrypt(
self,
enc_data: Tuple[bytes, bytes],
privkey: bytes = None
message_kit: MessageKit,
) -> bytes:
"""
Decrypts data using ECIES PKE. If no `privkey` is provided, it uses
`self.priv_key`.
cleartext = umbral.umbral.decrypt(message_kit.capsule, self.keypair.privkey,
message_kit.ciphertext, message_kit.alice_pubkey)
:param enc_data: Tuple: (encrypted data, ECIES encapsulated key)
:param privkey: Private key to decapsulate with
:return: Decrypted data
"""
privkey = privkey or self.priv_key
ciphertext, enc_key = enc_data
enc_key = API.elliptic_curve.deserialize(API.PRE.ecgroup, enc_key)
enc_key = API.umbral.EncryptedKey(ekey=enc_key, re_id=None)
dec_key = API.ecies_decapsulate(privkey, enc_key)
return API.symm_decrypt(dec_key, ciphertext)
return cleartext
def public_key(self):
return self.keypair.pubkey

View File

@ -16,7 +16,7 @@ class Signature(object):
def __repr__(self):
return "ECDSA Signature: {}".format(bytes(self).hex()[:15])
def verify(self, message: bytes, pubkey: UmbralPublicKey) -> bool:
def verify(self, message: bytes, pubkey: UmbralPublicKey=None) -> bool:
"""
Verifies that a message's signature was valid.
@ -53,4 +53,6 @@ class Signature(object):
def __add__(self, other):
return bytes(self) + other
__radd__ = __add__
def __radd__(self, other):
return other + bytes(self)

7
nkms/crypto/splitters.py Normal file
View File

@ -0,0 +1,7 @@
from nkms.crypto.constants import PUBLIC_KEY_LENGTH, CAPSULE_LENGTH
from nkms.crypto.utils import BytestringSplitter
from umbral.keys import UmbralPublicKey
from umbral.umbral import Capsule
key_splitter = BytestringSplitter((UmbralPublicKey, PUBLIC_KEY_LENGTH, {"as_b64": False}))
capsule_splitter = BytestringSplitter((Capsule, CAPSULE_LENGTH))

View File

@ -11,24 +11,26 @@ class BytestringSplitter(object):
def __call__(self, splittable, return_remainder=False, msgpack_remainder=False):
if not any((return_remainder, msgpack_remainder)) and len(self) != len(splittable):
raise ValueError(
"Wrong number of bytes to constitute message types {} - need {}, got {} \n Did you mean to return the remainder?".format(
""""Wrong number of bytes to constitute message types {} -
need {}, got {} \n Did you mean to return the remainder?""".format(
self.message_types, len(self), len(splittable)))
if len(self) > len(splittable):
raise ValueError(
"Not enough bytes to constitute message types {} - need {}, got {}".format(self.message_types,
len(self),
len(splittable)))
"""Not enough bytes to constitute
message types {} - need {}, got {}""".format(self.message_types,
len(self),
len(splittable)))
cursor = 0
message_objects = []
for message_type in self.message_types:
message_class, message_length = self.get_message_meta(message_type)
message_class, message_length, kwargs = self.get_message_meta(message_type)
expected_end_of_object_bytes = cursor + message_length
bytes_for_this_object = splittable[cursor:expected_end_of_object_bytes]
try:
message = message_class.from_bytes(bytes_for_this_object)
message = message_class.from_bytes(bytes_for_this_object, **kwargs)
except AttributeError:
message = message_class(bytes_for_this_object)
message = message_class(bytes_for_this_object, **kwargs)
message_objects.append(message)
cursor = expected_end_of_object_bytes
@ -45,17 +47,33 @@ class BytestringSplitter(object):
def __len__(self):
return sum(self.get_message_meta(m)[1] for m in self.message_types)
@staticmethod
def get_message_meta(message_type):
if isinstance(message_type, tuple):
message_meta = message_type
else:
try:
message_meta = message_type, message_type._EXPECTED_LENGTH
except AttributeError:
raise TypeError("No way to know the expected length. Either pass it as the second member of a tuple or set _EXPECTED_LENGTH on the class you're passing.")
try:
message_class = message_type[0]
except TypeError:
message_class = message_type
return message_meta
try:
message_length = message_type[1]
except TypeError:
message_length = message_type._EXPECTED_LENGTH
except AttributeError:
raise TypeError("No way to know the expected length. Either pass it as the second member of a tuple or set _EXPECTED_LENGTH on the class you're passing.")
try:
kwargs = message_type[2]
except (IndexError, TypeError):
kwargs = {}
return message_class, message_length, kwargs
def __add__(self, splitter):
return self.__class__(*self.message_types + splitter.message_types)
def __radd__(self, other):
return other + bytes(self)
class RepeatingBytestringSplitter(BytestringSplitter):

View File

@ -1,10 +1,11 @@
from nkms.crypto.constants import KFRAG_LENGTH
from nkms.keystore.db.models import Key, PolicyContract, Workorder
from nkms.crypto.utils import BytestringSplitter
from nkms.crypto.signature import Signature
from sqlalchemy.orm import sessionmaker
from typing import Union
from sqlalchemy.orm import sessionmaker
from nkms.crypto.constants import KFRAG_LENGTH
from nkms.crypto.signature import Signature
from nkms.crypto.utils import BytestringSplitter
from nkms.keystore.db.models import Key, PolicyContract, Workorder
from umbral.fragments import KFrag
from umbral.keys import UmbralPublicKey
from . import keypairs
@ -34,6 +35,7 @@ class KeyStore(object):
def add_key(self,
keypair: Union[keypairs.EncryptingKeypair,
keypairs.SigningKeypair]) -> Key:
"""
:param keypair: Keypair object to store in the keystore.
@ -49,82 +51,56 @@ class KeyStore(object):
self.session.commit()
return new_key
def get_key(self, fingerprint: bytes) -> Union[keypairs.EncryptingKeypair,
keypairs.SigningKeypair]:
"""
Returns a key from the KeyStore.
:param fingerprint: Fingerprint, in bytes, of key to return
def get_key(self, fingerprint: bytes) -> Union[keypairs.EncryptingKeypair,
keypairs.SigningKeypair]:
"""
Returns a key from the KeyStore.
:return: Keypair of the returned key.
"""
key = self.session.query(Key).filter_by(fingerprint=fingerprint).first()
if not key:
raise NotFound(
"No key with fingerprint {} found.".format(fingerprint))
if key.is_signing:
pubkey = UmbralPublicKey(key.key_data, as_b64=True)
return keypairs.SigningKeypair(pubkey)
:param fingerprint: Fingerprint, in bytes, of key to return
def del_key(self, fingerprint: bytes):
"""
Deletes a key from the KeyStore.
:return: Keypair of the returned key.
"""
key = self.session.query(Key).filter_by(fingerprint=fingerprint).first()
if not key:
raise NotFound(
"No key with fingerprint {} found.".format(fingerprint))
if key.is_signing:
pubkey = UmbralPublicKey(key.key_data, as_b64=True)
return keypairs.SigningKeypair(pubkey)
:param fingerprint: Fingerprint of key to delete
"""
self.session.query(Key).filter_by(fingerprint=fingerprint).delete()
self.session.commit()
def add_policy_contract(self, expiration, deposit, hrac,
alice_pubkey_sig, alice_pubkey_enc,
bob_pubkey_sig, alice_signature) -> PolicyContract:
"""
Creates a PolicyContract to the Keystore.
:return: The newly added PolicyContract object
"""
# TODO: This can be optimized to one commit/write.
alice_pubkey_sig = self.add_key(alice_pubkey_sig)
alice_pubkey_enc = self.add_key(alice_pubkey_enc)
bob_pubkey_sig = self.add_key(bob_pubkey_sig)
def del_key(self, fingerprint: bytes):
"""
Deletes a key from the KeyStore.
new_policy_contract = PolicyContract(
expiration, deposit, hrac, alice_pubkey_sig.id,
alice_pubkey_enc.id, bob_pubkey_sig.id, alice_signature
)
:param fingerprint: Fingerprint of key to delete
"""
self.session.query(Key).filter_by(fingerprint=fingerprint).delete()
self.session.commit()
self.session.add(new_policy_contract)
return new_policy_contract
def add_policy_contract(self, expiration, deposit, hrac,
alice_pubkey_sig, alice_pubkey_enc,
bob_pubkey_sig, alice_signature) -> PolicyContract:
"""
Creates a PolicyContract to the Keystore.
def get_policy_contract(self, hrac: bytes) -> PolicyContract:
"""
Returns the PolicyContract by its HRAC.
:return: The newly added PolicyContract object
"""
# TODO: This can be optimized to one commit/write.
alice_pubkey_sig = self.add_key(alice_pubkey_sig)
alice_pubkey_enc = self.add_key(alice_pubkey_enc)
bob_pubkey_sig = self.add_key(bob_pubkey_sig)
:return: The PolicyContract object
"""
policy_contract = self.session.query(PolicyContract).filter_by(hrac=hrac).first()
if not policy_contract:
raise NotFound("No PolicyContract with {} HRAC found.".format(hrac))
return policy_contract
new_policy_contract = PolicyContract(
expiration, deposit, hrac, alice_pubkey_sig.id,
alice_pubkey_enc.id, bob_pubkey_sig.id, alice_signature
)
def del_policy_contract(self, hrac: bytes):
"""
Deletes a PolicyContract from the Keystore.
"""
self.session.query(PolicyContract).filter_by(hrac=hrac).delete()
self.session.commit()
self.session.add(new_policy_contract)
def add_workorder(self, bob_pubkey_sig, bob_signature, hrac) -> Workorder:
"""
Adds a Workorder to the keystore.
"""
bob_pubkey_sig = self.add_key(bob_pubkey_sig)
new_workorder = Workorder(bob_pubkey_sig.id, bob_signature, hrac)
self.session.add(new_workorder)
self.session.commit()
return new_workorder
return new_policy_contract
def get_workorders(self, hrac: bytes) -> Workorder:
"""

View File

@ -1,6 +1,7 @@
from kademlia.node import Node
from kademlia.protocol import KademliaProtocol
from kademlia.utils import digest
from nkms.crypto.api import keccak_digest
from nkms.crypto.constants import PUBLIC_KEY_LENGTH, KECCAK_DIGEST_LENGTH
from nkms.crypto.signature import Signature
@ -10,7 +11,9 @@ from nkms.network.node import NuCypherNode
from nkms.network.routing import NuCypherRoutingTable
from umbral.keys import UmbralPublicKey
dht_value_splitter = BytestringSplitter(Signature, (UmbralPublicKey, PUBLIC_KEY_LENGTH), (bytes, KECCAK_DIGEST_LENGTH))
dht_value_splitter = BytestringSplitter(Signature, (
UmbralPublicKey, PUBLIC_KEY_LENGTH, {"as_b64": False}),
(bytes, KECCAK_DIGEST_LENGTH))
class NuCypherHashProtocol(KademliaProtocol):
@ -26,7 +29,8 @@ class NuCypherHashProtocol(KademliaProtocol):
return True
def rpc_ping(self, sender, nodeid, node_capabilities=[]):
source = NuCypherNode(nodeid, sender[0], sender[1], capabilities_as_strings=node_capabilities)
source = NuCypherNode(nodeid, sender[0], sender[1],
capabilities_as_strings=node_capabilities)
self.welcomeIfNewNode(source)
return self.sourceNode.id
@ -42,13 +46,17 @@ class NuCypherHashProtocol(KademliaProtocol):
else:
return NODE_HAS_NO_STORAGE, False
def determine_legality_of_dht_key(self, signature, sender_pubkey_sig, message, hrac, dht_key, dht_value):
proper_key = digest(keccak_digest(bytes(sender_pubkey_sig) + bytes(hrac)))
def determine_legality_of_dht_key(self, signature, sender_pubkey_sig,
message, hrac, dht_key, dht_value):
proper_key = digest(
keccak_digest(bytes(sender_pubkey_sig) + bytes(hrac)))
verified = signature.verify(hrac, sender_pubkey_sig)
if not verified or not proper_key == dht_key:
self.log.warning("Got request to store illegal k/v: {} / {}".format(dht_key, dht_value))
self.log.warning(
"Got request to store illegal k/v: {} / {}".format(dht_key,
dht_value))
self.illegal_keys_seen.append(dht_key)
return False
else:
@ -60,13 +68,18 @@ class NuCypherHashProtocol(KademliaProtocol):
self.log.debug("got a store request from %s" % str(sender))
if value.startswith(b"uaddr") or value.startswith(b"trmap"):
signature, sender_pubkey_sig, hrac, message = dht_value_splitter(value[5::], return_remainder=True)
signature, sender_pubkey_sig, hrac, message = dht_value_splitter(
value[5::], return_remainder=True)
# extra_info is a hash of the policy_group.id in the case of a treasure map, or a TTL in the case
# of an Ursula interface. TODO: Decide whether to keep this notion and, if so, use the TTL.
do_store = self.determine_legality_of_dht_key(signature, sender_pubkey_sig, message, hrac, key, value)
do_store = self.determine_legality_of_dht_key(signature,
sender_pubkey_sig,
message, hrac, key,
value)
else:
self.log.info("Got request to store bad k/v: {} / {}".format(key, value))
self.log.info(
"Got request to store bad k/v: {} / {}".format(key, value))
do_store = False
if do_store:

View File

@ -10,6 +10,7 @@ from nkms.crypto.constants import NOT_SIGNED, KECCAK_DIGEST_LENGTH, \
PUBLIC_KEY_LENGTH
from nkms.crypto.powers import SigningPower
from nkms.crypto.signature import Signature
from nkms.crypto.splitters import key_splitter
from nkms.crypto.utils import BytestringSplitter
from umbral.keys import UmbralPublicKey
@ -48,8 +49,7 @@ class Contract(object):
@classmethod
def from_bytes(cls, contract_as_bytes):
contract_splitter = BytestringSplitter(
(UmbralPublicKey, PUBLIC_KEY_LENGTH), (bytes, KECCAK_DIGEST_LENGTH),
contract_splitter = key_splitter + BytestringSplitter((bytes, KECCAK_DIGEST_LENGTH),
(bytes, 26))
alice_pubkey_sig, hrac, expiration_bytes, deposit_bytes = contract_splitter(
contract_as_bytes, return_remainder=True)
@ -194,11 +194,12 @@ class Policy(object):
def enact(self, networky_stuff):
for contract in self._accepted_contracts.values():
policy_payload = contract.encrypt_payload_for_ursula()
full_payload = self.alice.seal + msgpack.dumps(policy_payload)
full_payload = contract.encrypt_payload_for_ursula()
response = networky_stuff.enact_policy(contract.ursula,
self.hrac(),
full_payload) # TODO: Parse response for confirmation.
bytes(full_payload))
# TODO: Parse response for confirmation.
response
# Assuming response is what we hope for
self.treasure_map.add_ursula(contract.ursula)
@ -290,13 +291,14 @@ class WorkOrder(object):
def constructed_by_bob(cls, kfrag_hrac, pfrags, ursula_dht_key, bob):
receipt_bytes = b"wo:" + ursula_dht_key # TODO: represent the pfrags as bytes and hash them as part of the receipt, ie + keccak_digest(b"".join(pfrags)) - See #137
receipt_signature = bob.seal(receipt_bytes)
return cls(bob, kfrag_hrac, pfrags, receipt_bytes, receipt_signature, ursula_dht_key)
return cls(bob, kfrag_hrac, pfrags, receipt_bytes, receipt_signature,
ursula_dht_key)
@classmethod
def from_rest_payload(cls, kfrag_hrac, rest_payload):
payload_splitter = BytestringSplitter(Signature, PublicKey)
signature, bob_pubkey_sig, (receipt_bytes, packed_pfrags) = payload_splitter(rest_payload,
msgpack_remainder=True)
msgpack_remainder=True)
pfrags = [PFrag(p) for p in msgpack.loads(packed_pfrags)]
verified = signature.verify(receipt_bytes, bob_pubkey_sig)
if not verified:
@ -306,7 +308,8 @@ class WorkOrder(object):
def payload(self):
pfrags_as_bytes = [bytes(p) for p in self.pfrags]
packed_receipt_and_pfrags = msgpack.dumps((self.receipt_bytes, msgpack.dumps(pfrags_as_bytes)))
packed_receipt_and_pfrags = msgpack.dumps(
(self.receipt_bytes, msgpack.dumps(pfrags_as_bytes)))
return bytes(self.receipt_signature) + self.bob.seal + packed_receipt_and_pfrags
def complete(self, cfrags):

View File

@ -1,13 +1,13 @@
import datetime
from apistar.test import TestClient
from nkms.characters import Ursula
from nkms.crypto.api import keccak_digest
from nkms.crypto.constants import PUBLIC_KEY_LENGTH
from nkms.crypto.powers import SigningPower, EncryptingPower
from nkms.crypto.utils import BytestringSplitter
from tests.utilities import MockNetworkyStuff
from apistar.test import TestClient
from umbral.keys import UmbralPublicKey
@ -16,7 +16,8 @@ def test_grant(alice, bob, ursulas):
policy_end_datetime = datetime.datetime.now() + datetime.timedelta(days=5)
n = 5
uri = b"this_is_the_path_to_which_access_is_being_granted"
policy = alice.grant(bob, uri, networky_stuff, m=3, n=n, expiration=policy_end_datetime)
policy = alice.grant(bob, uri, networky_stuff, m=3, n=n,
expiration=policy_end_datetime)
# The number of policies is equal to the number of Ursulas we're using (n)
assert len(policy._accepted_contracts) == n
@ -33,7 +34,13 @@ def test_grant(alice, bob, ursulas):
def test_alice_can_get_ursulas_keys_via_rest(alice, ursulas):
mock_client = TestClient(ursulas[0].rest_app)
response = mock_client.get('http://localhost/public_keys')
splitter = BytestringSplitter((UmbralPublicKey, PUBLIC_KEY_LENGTH))
signing_key_bytes, encrypting_key_bytes = splitter(response.content, return_remainder=True)
stranger_ursula_from_public_keys = Ursula.from_public_keys((SigningPower, signing_key_bytes), (EncryptingPower, encrypting_key_bytes))
splitter = BytestringSplitter(
(UmbralPublicKey, PUBLIC_KEY_LENGTH, {"as_b64": False}),
(UmbralPublicKey, PUBLIC_KEY_LENGTH, {"as_b64": False})
)
signing_key, encrypting_key = splitter(response.content)
stranger_ursula_from_public_keys = Ursula.from_public_keys((SigningPower,
signing_key),
(EncryptingPower,
encrypting_key))
assert stranger_ursula_from_public_keys == ursulas[0]