mirror of https://github.com/nucypher/nucypher.git
Merge pull request #9 from jMyles/rm-crypto
Kits, splitters, new encrypt and decrypt, morepull/161/head
commit
6aeef2f6ec
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
"""
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue