mirror of https://github.com/nucypher/nucypher.git
Merge jmyle's PR fixing stuff
commit
ce8b222197
|
@ -1,5 +1,8 @@
|
|||
from kademlia.network import Server
|
||||
from nkms.crypto.keyring import KeyRing
|
||||
from nkms.crypto.constants import NOT_SIGNED
|
||||
from nkms.crypto import crypto as Crypto
|
||||
from nkms.crypto.hash import signature_hash
|
||||
from nkms.crypto.powers import CryptoPower, SigningKeypair
|
||||
from nkms.network.server import NuCypherDHTServer, NuCypherSeedOnlyDHTServer
|
||||
|
||||
|
||||
|
@ -9,10 +12,50 @@ class Character(object):
|
|||
"""
|
||||
_server = None
|
||||
_server_class = Server
|
||||
_actor_mapping = {}
|
||||
_default_crypto_powerups = None
|
||||
|
||||
class NotFound(KeyError):
|
||||
"""raised when we try to interact with an actor of whom we haven't learned yet."""
|
||||
|
||||
def __init__(self, attach_server=True, crypto_power: CryptoPower = None,
|
||||
crypto_power_ups=[]):
|
||||
"""
|
||||
: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.
|
||||
"""
|
||||
if crypto_power and crypto_power_ups:
|
||||
raise ValueError("Pass crypto_power or crypto_power_ups (or neither), but not both.")
|
||||
|
||||
def __init__(self, attach_server=True):
|
||||
if attach_server:
|
||||
self.attach_server()
|
||||
if crypto_power:
|
||||
self._crypto_power = crypto_power
|
||||
elif crypto_power_ups:
|
||||
self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
|
||||
else:
|
||||
self._crypto_power = CryptoPower(self._default_crypto_powerups)
|
||||
|
||||
class Seal(object):
|
||||
"""
|
||||
Can be called to sign something or used to express the signing public key as bytes.
|
||||
"""
|
||||
|
||||
def __call__(seal_instance, *messages_to_sign):
|
||||
return self._crypto_power.sign(*messages_to_sign)
|
||||
|
||||
def as_bytes(seal_instance):
|
||||
return self._crypto_power.pubkey_sig_bytes()
|
||||
|
||||
def as_tuple(self_instance):
|
||||
return self._crypto_power.pubkey_sig_tuple()
|
||||
|
||||
self.seal = Seal()
|
||||
|
||||
def attach_server(self, ksize=20, alpha=3, id=None, storage=None,
|
||||
*args, **kwargs) -> None:
|
||||
|
@ -25,18 +68,85 @@ class Character(object):
|
|||
else:
|
||||
raise RuntimeError("Server hasn't been attached.")
|
||||
|
||||
def learn_about_actor(self, actor):
|
||||
self._actor_mapping[actor.id()] = actor
|
||||
|
||||
def encrypt_for(self, recipient: str, cleartext: bytes, sign: bool = True,
|
||||
sign_cleartext=True) -> tuple:
|
||||
"""
|
||||
Looks up recipient actor, finds that actor's pubkey_enc on our keyring, and encrypts for them.
|
||||
Optionally signs the message as well.
|
||||
|
||||
:param recipient: The character whose public key will be used to encrypt cleartext.
|
||||
:param cleartext: 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.
|
||||
:return: A tuple, (ciphertext, signature). If sign==False, then signature will be NOT_SIGNED.
|
||||
"""
|
||||
actor = self._lookup_actor(recipient)
|
||||
pubkey_sign_id = actor.seal() # I don't even like this. I prefer .seal(), which
|
||||
ciphertext = self._crypto_power.encrypt_for(pubkey_sign_id, cleartext)
|
||||
|
||||
if sign:
|
||||
if sign_cleartext:
|
||||
signature = self.keyring.sign(cleartext)
|
||||
else:
|
||||
signature = self.keyring.sign(ciphertext)
|
||||
else:
|
||||
signature = NOT_SIGNED
|
||||
|
||||
return ciphertext, signature
|
||||
|
||||
def verify_from(self, actor_whom_sender_claims_to_be: str, signature: bytes,
|
||||
*messages: bytes, decrypt=False,
|
||||
signature_is_on_cleartext=True) -> tuple:
|
||||
"""
|
||||
Inverse of encrypt_for.
|
||||
|
||||
:param actor_that_sender_claims_to_be: The str representation of the actor on this KeyStore
|
||||
that the sender is claiming to be.
|
||||
:param message:
|
||||
:param decrypt:
|
||||
:param signature_is_on_cleartext:
|
||||
:return:
|
||||
"""
|
||||
actor = self._lookup_actor(actor_whom_sender_claims_to_be)
|
||||
signature_pub_key = actor.seal.as_tuple() # TODO: and again, maybe in the real world this looks in KeyStore.
|
||||
msg_digest = b"".join(signature_hash(m) for m in messages) # This does work.
|
||||
return Crypto.verify(signature, msg_digest, signature_pub_key)
|
||||
|
||||
def _lookup_actor(self, actor: "Character"):
|
||||
try:
|
||||
return self._actor_mapping[actor.id()]
|
||||
except KeyError:
|
||||
raise self.NotFound("We haven't learned of an actor with ID {}".format(actor.id()))
|
||||
|
||||
def id(self):
|
||||
return "whatever actor id ends up being - {}".format(id(self))
|
||||
|
||||
|
||||
class Ursula(Character):
|
||||
_server_class = NuCypherDHTServer
|
||||
_default_crypto_powerups = [SigningKeypair]
|
||||
|
||||
|
||||
class Alice(Character):
|
||||
_server_class = NuCypherSeedOnlyDHTServer
|
||||
|
||||
def __init__(self):
|
||||
# TODO: Handle loading keypairs from config
|
||||
self.keyring = KeyRing()
|
||||
_default_crypto_powerups = [SigningKeypair]
|
||||
|
||||
def find_best_ursula(self):
|
||||
# TODO: Right now this just finds the nearest node and returns its ip and port. Make it do something useful.
|
||||
return self.server.bootstrappableNeighbors()[0]
|
||||
|
||||
def generate_re_encryption_keys(self,
|
||||
pubkey_enc_bob,
|
||||
m,
|
||||
n):
|
||||
# TODO: Make this actually work.
|
||||
kfrags = [
|
||||
'sfasdfsd9',
|
||||
'dfasd09fi',
|
||||
'sdfksd3f9',
|
||||
]
|
||||
|
||||
return kfrags
|
||||
|
|
|
@ -4,7 +4,7 @@ import msgpack
|
|||
|
||||
from nkms.crypto import (default_algorithm, pre_from_algorithm,
|
||||
symmetric_from_algorithm)
|
||||
from nkms.crypto.keyring import KeyRing
|
||||
from nkms.crypto.keystore import KeyStore
|
||||
from nkms.network import dummy
|
||||
|
||||
|
||||
|
@ -38,9 +38,9 @@ class Client(object):
|
|||
self._pre = pre_from_algorithm(default_algorithm)
|
||||
self._symm = symmetric_from_algorithm(default_algorithm)
|
||||
|
||||
# TODO: Load existing keys into the KeyRing
|
||||
# TODO: Load existing keys into the KeyStore
|
||||
# TODO: Save newly generated keypair
|
||||
self.keyring = KeyRing()
|
||||
self.keyring = KeyStore()
|
||||
|
||||
def _split_path(self, path):
|
||||
"""
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import importlib
|
||||
from nacl.utils import random # noqa
|
||||
from npre.curves import secp256k1
|
||||
|
||||
default_algorithm = dict(
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
NOT_SIGNED = 445
|
|
@ -1,8 +1,11 @@
|
|||
import msgpack
|
||||
import sha3
|
||||
from random import SystemRandom
|
||||
from npre import umbral
|
||||
from npre import elliptic_curve
|
||||
from nacl.secret import SecretBox
|
||||
from typing import Tuple, Union, List
|
||||
from py_ecc.secp256k1.secp256k1 import ecdsa_raw_recover
|
||||
|
||||
|
||||
PRE = umbral.PRE()
|
||||
|
@ -10,7 +13,7 @@ SYSTEM_RAND = SystemRandom()
|
|||
|
||||
|
||||
def priv_bytes2ec(
|
||||
privkey: bytes
|
||||
privkey: bytes
|
||||
) -> elliptic_curve.ec_element:
|
||||
"""
|
||||
Turns a private key, in bytes, into an elliptic_curve.ec_element.
|
||||
|
@ -23,7 +26,7 @@ def priv_bytes2ec(
|
|||
|
||||
|
||||
def pub_bytes2ec(
|
||||
pubkey: bytes,
|
||||
pubkey: bytes,
|
||||
) -> elliptic_curve.ec_element:
|
||||
"""
|
||||
Turns a public key, in bytes, into an elliptic_curve.ec_element.
|
||||
|
@ -69,9 +72,58 @@ def secure_random_range(
|
|||
return SYSTEM_RAND.randrange(min, max)
|
||||
|
||||
|
||||
def vrs_msgpack_dump(v, r, s):
|
||||
v_bytes = v.to_bytes(1, byteorder='big')
|
||||
r_bytes = r.to_bytes(32, byteorder='big')
|
||||
s_bytes = s.to_bytes(32, byteorder='big')
|
||||
return msgpack.dumps((v_bytes, r_bytes, s_bytes))
|
||||
|
||||
|
||||
def vrs_msgpack_load(msgpack_vrs):
|
||||
sig = msgpack.loads(msgpack_vrs)
|
||||
v = int.from_bytes(sig[0], byteorder='big')
|
||||
r = int.from_bytes(sig[1], byteorder='big')
|
||||
s = int.from_bytes(sig[2], byteorder='big')
|
||||
return (v, r, s)
|
||||
|
||||
|
||||
def digest(*messages):
|
||||
"""
|
||||
Accepts an iterable containing bytes and digests it.
|
||||
|
||||
:param bytes *args: Data to hash
|
||||
|
||||
:rtype: bytes
|
||||
:return: bytestring of digested data
|
||||
"""
|
||||
hash = sha3.keccak_256()
|
||||
for message in messages:
|
||||
hash.update(message)
|
||||
return hash.digest()
|
||||
|
||||
|
||||
def verify(signature, msghash, pubkey=None):
|
||||
"""
|
||||
Takes a msgpacked signature and verifies the message.
|
||||
|
||||
:param bytes msghash: The hashed message to verify
|
||||
:param bytes signature: The msgpacked signature (v, r, and s)
|
||||
:param bytes pubkey: Pubkey to validate signature for
|
||||
Default is the keypair's pub_key.
|
||||
|
||||
:rtype: Boolean
|
||||
:return: Is the signature valid or not?
|
||||
"""
|
||||
sig = vrs_msgpack_load(signature)
|
||||
# Generate the public key from the signature and validate
|
||||
# TODO: Look into fixed processing time functions for comparison
|
||||
verify_sig = ecdsa_raw_recover(msghash, sig)
|
||||
return verify_sig == pubkey
|
||||
|
||||
|
||||
def symm_encrypt(
|
||||
key: bytes,
|
||||
plaintext: bytes
|
||||
key: bytes,
|
||||
plaintext: bytes
|
||||
) -> bytes:
|
||||
"""
|
||||
Performs symmetric encryption using nacl.SecretBox.
|
||||
|
@ -86,8 +138,8 @@ def symm_encrypt(
|
|||
|
||||
|
||||
def symm_decrypt(
|
||||
key: bytes,
|
||||
ciphertext: bytes
|
||||
key: bytes,
|
||||
ciphertext: bytes
|
||||
) -> bytes:
|
||||
"""
|
||||
Decrypts ciphertext performed with nacl.SecretBox.
|
||||
|
@ -102,7 +154,7 @@ def symm_decrypt(
|
|||
|
||||
|
||||
def ecies_gen_priv(
|
||||
to_bytes: bool = True
|
||||
to_bytes: bool = True
|
||||
) -> Union[bytes, elliptic_curve.ec_element]:
|
||||
"""
|
||||
Generates an ECIES private key.
|
||||
|
@ -118,8 +170,8 @@ def ecies_gen_priv(
|
|||
|
||||
|
||||
def ecies_priv2pub(
|
||||
privkey: Union[bytes, elliptic_curve.ec_element],
|
||||
to_bytes: bool = True
|
||||
privkey: Union[bytes, elliptic_curve.ec_element],
|
||||
to_bytes: bool = True
|
||||
) -> Union[bytes, elliptic_curve.ec_element]:
|
||||
"""
|
||||
Takes a private key (secret bytes or an elliptic_curve.ec_element) and
|
||||
|
@ -140,7 +192,7 @@ def ecies_priv2pub(
|
|||
|
||||
|
||||
def ecies_encapsulate(
|
||||
pubkey: Union[bytes, elliptic_curve.ec_element],
|
||||
pubkey: Union[bytes, elliptic_curve.ec_element],
|
||||
) -> Tuple[bytes, umbral.EncryptedKey]:
|
||||
"""
|
||||
Encapsulates an ECIES generated symmetric key for a public key.
|
||||
|
@ -155,8 +207,8 @@ def ecies_encapsulate(
|
|||
|
||||
|
||||
def ecies_decapsulate(
|
||||
privkey: Union[bytes, elliptic_curve.ec_element],
|
||||
enc_key: umbral.EncryptedKey
|
||||
privkey: Union[bytes, elliptic_curve.ec_element],
|
||||
enc_key: umbral.EncryptedKey
|
||||
) -> bytes:
|
||||
"""
|
||||
Decapsulates an ECIES generated encrypted key with a private key.
|
||||
|
@ -172,9 +224,9 @@ def ecies_decapsulate(
|
|||
|
||||
|
||||
def ecies_rekey(
|
||||
privkey_a: Union[bytes, elliptic_curve.ec_element],
|
||||
privkey_b: Union[bytes, elliptic_curve.ec_element],
|
||||
to_bytes: bool = True
|
||||
privkey_a: Union[bytes, elliptic_curve.ec_element],
|
||||
privkey_b: Union[bytes, elliptic_curve.ec_element],
|
||||
to_bytes: bool = True
|
||||
) -> Union[bytes, umbral.RekeyFrag]:
|
||||
"""
|
||||
Generates a re-encryption key from privkey_a to privkey_b.
|
||||
|
@ -197,10 +249,10 @@ def ecies_rekey(
|
|||
|
||||
|
||||
def ecies_split_rekey(
|
||||
privkey_a: Union[bytes, elliptic_curve.ec_element],
|
||||
privkey_b: Union[bytes, elliptic_curve.ec_element],
|
||||
min_shares: int,
|
||||
total_shares: int
|
||||
privkey_a: Union[bytes, elliptic_curve.ec_element],
|
||||
privkey_b: Union[bytes, elliptic_curve.ec_element],
|
||||
min_shares: int,
|
||||
total_shares: int
|
||||
) -> List[umbral.RekeyFrag]:
|
||||
"""
|
||||
Performs a split-key re-encryption key generation where a minimum
|
||||
|
@ -219,11 +271,11 @@ def ecies_split_rekey(
|
|||
if type(privkey_b) == bytes:
|
||||
privkey_b = priv_bytes2ec(privkey_b)
|
||||
return PRE.split_rekey(privkey_a, privkey_b,
|
||||
min_shares, total_shares)
|
||||
min_shares, total_shares)
|
||||
|
||||
|
||||
def ecies_combine(
|
||||
encrypted_keys: List[umbral.EncryptedKey]
|
||||
encrypted_keys: List[umbral.EncryptedKey]
|
||||
) -> umbral.EncryptedKey:
|
||||
"""
|
||||
Combines the encrypted keys together to form a rekey from split_rekey.
|
||||
|
@ -236,8 +288,8 @@ def ecies_combine(
|
|||
|
||||
|
||||
def ecies_reencrypt(
|
||||
rekey: Union[bytes, umbral.RekeyFrag],
|
||||
enc_key: Union[bytes, umbral.EncryptedKey],
|
||||
rekey: Union[bytes, umbral.RekeyFrag],
|
||||
enc_key: Union[bytes, umbral.EncryptedKey],
|
||||
) -> umbral.EncryptedKey:
|
||||
"""
|
||||
Re-encrypts the key provided.
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import msgpack
|
||||
import sha3
|
||||
from random import SystemRandom
|
||||
from py_ecc.secp256k1 import N, privtopub, ecdsa_raw_sign, ecdsa_raw_recover
|
||||
from npre import umbral
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
class EncryptingKeypair(object):
|
||||
|
@ -101,77 +96,3 @@ class EncryptingKeypair(object):
|
|||
"""
|
||||
return self.pre.reencrypt(reenc_key, ciphertext)
|
||||
|
||||
|
||||
class SigningKeypair(object):
|
||||
def __init__(self, privkey_bytes=None):
|
||||
self.secure_rand = SystemRandom()
|
||||
if privkey_bytes:
|
||||
self.priv_key = privkey_bytes
|
||||
else:
|
||||
# Key generation is random([1, N - 1])
|
||||
priv_number = self.secure_rand.randrange(1, N)
|
||||
self.priv_key = priv_number.to_bytes(32, byteorder='big')
|
||||
# Get the public component
|
||||
self.pub_key = privtopub(self.priv_key)
|
||||
|
||||
def pubkey_bytes(self):
|
||||
return b''.join(i.to_bytes(32, 'big') for i in self.pub_key)
|
||||
|
||||
def _vrs_msgpack_dump(self, v, r, s):
|
||||
v_bytes = v.to_bytes(1, byteorder='big')
|
||||
r_bytes = r.to_bytes(32, byteorder='big')
|
||||
s_bytes = s.to_bytes(32, byteorder='big')
|
||||
return msgpack.dumps((v_bytes, r_bytes, s_bytes))
|
||||
|
||||
def _vrs_msgpack_load(self, msgpack_vrs):
|
||||
sig = msgpack.loads(msgpack_vrs)
|
||||
v = int.from_bytes(sig[0], byteorder='big')
|
||||
r = int.from_bytes(sig[1], byteorder='big')
|
||||
s = int.from_bytes(sig[2], byteorder='big')
|
||||
return (v, r, s)
|
||||
|
||||
def digest(self, *messages):
|
||||
"""
|
||||
Accepts an iterable containing bytes and digests it.
|
||||
|
||||
:param bytes *args: Data to hash
|
||||
|
||||
:rtype: bytes
|
||||
:return: bytestring of digested data
|
||||
"""
|
||||
hash = sha3.keccak_256()
|
||||
for message in messages:
|
||||
hash.update(message)
|
||||
return hash.digest()
|
||||
|
||||
def sign(self, msghash):
|
||||
"""
|
||||
Signs a hashed message and returns a msgpack'ed v, r, and s.
|
||||
|
||||
:param bytes msghash: Hash of the message
|
||||
|
||||
:rtype: Bytestring
|
||||
:return: Msgpacked bytestring of v, r, and s (the signature)
|
||||
"""
|
||||
v, r, s = ecdsa_raw_sign(msghash, self.priv_key)
|
||||
return self._vrs_msgpack_dump(v, r, s)
|
||||
|
||||
def verify(self, msghash, signature, pubkey=None):
|
||||
"""
|
||||
Takes a msgpacked signature and verifies the message.
|
||||
|
||||
:param bytes msghash: The hashed message to verify
|
||||
:param bytes signature: The msgpacked signature (v, r, and s)
|
||||
:param bytes pubkey: Pubkey to validate signature for
|
||||
Default is the keypair's pub_key.
|
||||
|
||||
:rtype: Boolean
|
||||
:return: Is the signature valid or not?
|
||||
"""
|
||||
if not pubkey:
|
||||
pubkey = self.pub_key
|
||||
sig = self._vrs_msgpack_load(signature)
|
||||
# Generate the public key from the signature and validate
|
||||
# TODO: Look into fixed processing time functions for comparison
|
||||
verify_sig = ecdsa_raw_recover(msghash, sig)
|
||||
return verify_sig == pubkey
|
||||
|
|
|
@ -1,32 +1,29 @@
|
|||
import sha3
|
||||
import npre.elliptic_curve as ec
|
||||
from nacl.utils import random
|
||||
import sha3
|
||||
from nacl.secret import SecretBox
|
||||
from nkms.crypto.keypairs import SigningKeypair, EncryptingKeypair
|
||||
from nacl.utils import random
|
||||
|
||||
from nkms.crypto.keypairs import EncryptingKeypair
|
||||
from npre import umbral
|
||||
|
||||
|
||||
class KeyRing(object):
|
||||
def __init__(self, sig_privkey=None, enc_privkey=None):
|
||||
class KeyStore(object):
|
||||
"""
|
||||
A vault of cryptographic keys owned by and stored on behalf of a discrete actor.
|
||||
"""
|
||||
|
||||
def __init__(self, enc_privkey=None):
|
||||
"""
|
||||
Initializes a KeyRing object. Uses the private keys to initialize
|
||||
Initializes a KeyStore object. Uses the private keys to initialize
|
||||
their respective objects, if provided. If not, it will generate new
|
||||
keypairs.
|
||||
|
||||
:param bytes sig_privkey: Private key in bytes of ECDSA signing keypair
|
||||
:param bytes enc_privkey: Private key in bytes of encrypting keypair
|
||||
"""
|
||||
self.sig_keypair = SigningKeypair(sig_privkey)
|
||||
self.enc_keypair = EncryptingKeypair(enc_privkey)
|
||||
self.pre = umbral.PRE()
|
||||
|
||||
@property
|
||||
def sig_pubkey(self):
|
||||
return self.sig_keypair.pub_key
|
||||
|
||||
@property
|
||||
def sig_privkey(self):
|
||||
return self.sig_keypair.priv_key
|
||||
self._key_mapping = {}
|
||||
|
||||
@property
|
||||
def enc_pubkey(self):
|
||||
|
@ -87,7 +84,7 @@ class KeyRing(object):
|
|||
for key in path_keys:
|
||||
path_symm_key, enc_symm_key = self.generate_key(pubkey=key)
|
||||
enc_keys.append(
|
||||
(self.symm_encrypt(path_symm_key, plaintext), enc_symm_key))
|
||||
(self.symm_encrypt(path_symm_key, plaintext), enc_symm_key))
|
||||
return enc_keys
|
||||
|
||||
def decrypt(self, enc_path_key, enc_symm_key):
|
||||
|
@ -105,35 +102,6 @@ class KeyRing(object):
|
|||
dec_symm_key = self.decrypt_key(enc_symm_key)
|
||||
return self.symm_decrypt(dec_symm_key, enc_path_key)
|
||||
|
||||
def sign(self, message):
|
||||
"""
|
||||
Signs a message and returns a signature with the keccak hash.
|
||||
|
||||
:param Iterable message: Message to sign in an iterable of bytes
|
||||
|
||||
:rtype: bytestring
|
||||
:return: Signature of message
|
||||
"""
|
||||
msg_digest = self.sig_keypair.digest(message)
|
||||
return self.sig_keypair.sign(msg_digest)
|
||||
|
||||
def verify(self, message, signature, pubkey=None):
|
||||
"""
|
||||
Verifies a signature.
|
||||
|
||||
:param bytes message: Message to check signature for
|
||||
:param bytes signature: Signature to validate
|
||||
:param bytes pubkey: Pubkey to validate signature with
|
||||
Default is the sig_keypair's pub_key
|
||||
|
||||
:rtype: Boolean
|
||||
:return: Is the message signature valid or not?
|
||||
"""
|
||||
if not pubkey:
|
||||
pubkey = self.sig_keypair.pub_key
|
||||
msg_digest = sha3.keccak_256(message).digest()
|
||||
return self.sig_keypair.verify(msg_digest, signature, pubkey=pubkey)
|
||||
|
||||
def generate_key(self, pubkey=None):
|
||||
"""
|
||||
Generates a raw symmetric key and its encrypted counterpart.
|
||||
|
@ -174,7 +142,7 @@ class KeyRing(object):
|
|||
|
||||
# Encrypt ephemeral key with an ECIES generated key
|
||||
symm_key_bob, enc_symm_key_bob = self.enc_keypair.generate_key(
|
||||
pubkey=pubkey_b)
|
||||
pubkey=pubkey_b)
|
||||
enc_priv_e = self.symm_encrypt(symm_key_bob, priv_e_bytes)
|
||||
|
||||
reenc_key = self.enc_keypair.rekey(privkey_a, priv_e)
|
||||
|
@ -247,13 +215,3 @@ class KeyRing(object):
|
|||
cipher = SecretBox(key)
|
||||
return cipher.decrypt(ciphertext)
|
||||
|
||||
def secure_random(self, length):
|
||||
"""
|
||||
Generates a bytestring from a secure random source for keys, etc.
|
||||
|
||||
:params int length: Length of the bytestring to generate.
|
||||
|
||||
:rtype: bytes
|
||||
:return: Secure random generated bytestring of <length> bytes
|
||||
"""
|
||||
return random(length)
|
|
@ -0,0 +1,122 @@
|
|||
from random import SystemRandom
|
||||
from typing import Iterable
|
||||
|
||||
from py_ecc.secp256k1 import N, privtopub, ecdsa_raw_sign, ecdsa_raw_recover
|
||||
|
||||
from nkms.crypto import crypto as Crypto
|
||||
from nkms.crypto.hash import signature_hash
|
||||
from nkms.crypto.keypairs import EncryptingKeypair
|
||||
|
||||
|
||||
class PowerUpError(TypeError):
|
||||
pass
|
||||
|
||||
|
||||
class NoSigningPower(PowerUpError):
|
||||
pass
|
||||
|
||||
|
||||
class NoEncryptingPower(PowerUpError):
|
||||
pass
|
||||
|
||||
|
||||
class CryptoPower(object):
|
||||
def __init__(self, power_ups=[]):
|
||||
self._power_ups = {}
|
||||
self.public_keys = {} # TODO: The keys here will actually be IDs for looking up in a KeyStore.
|
||||
|
||||
if power_ups:
|
||||
for power_up in power_ups:
|
||||
self.consume_power_up(power_up)
|
||||
|
||||
def consume_power_up(self, power_up):
|
||||
if isinstance(power_up, CryptoPowerUp):
|
||||
power_up_class = power_up.__class__
|
||||
power_up_instance = power_up
|
||||
elif CryptoPowerUp in power_up.__bases__:
|
||||
power_up_class = power_up
|
||||
power_up_instance = power_up()
|
||||
else:
|
||||
raise TypeError("power_up must be a subclass of CryptoPowerUp or an instance of a subclass of CryptoPowerUp.")
|
||||
self._power_ups[power_up_class] = power_up_instance
|
||||
|
||||
if power_up.confers_public_key:
|
||||
self.public_keys[power_up_class] = power_up_instance.public_key() # TODO: Make this an ID for later lookup on a KeyStore.
|
||||
|
||||
|
||||
def pubkey_sig_bytes(self):
|
||||
try:
|
||||
return self._power_ups[SigningKeypair].pubkey_bytes() # TODO: Turn this into an ID lookup on a KeyStore.
|
||||
except KeyError:
|
||||
raise NoSigningPower
|
||||
def pubkey_sig_tuple(self):
|
||||
try:
|
||||
return self._power_ups[SigningKeypair].pub_key # TODO: Turn this into an ID lookup on a KeyStore.
|
||||
except KeyError:
|
||||
raise NoSigningPower
|
||||
|
||||
def sign(self, *messages):
|
||||
"""
|
||||
Signs a message and returns a signature with the keccak hash.
|
||||
|
||||
:param Iterable messages: Messages to sign in an iterable of bytes
|
||||
|
||||
:rtype: bytestring
|
||||
:return: Signature of message
|
||||
"""
|
||||
try:
|
||||
sig_keypair = self._power_ups[SigningKeypair]
|
||||
except KeyError:
|
||||
raise NoSigningPower
|
||||
msg_digest = b"".join(signature_hash(m) for m in messages)
|
||||
|
||||
return sig_keypair.sign(msg_digest)
|
||||
|
||||
def encrypt_for(self, pubkey_sign_id, cleartext):
|
||||
try:
|
||||
enc_keypair = self._power_ups[EncryptingKeypair]
|
||||
# TODO: Actually encrypt.
|
||||
except KeyError:
|
||||
raise NoEncryptingPower
|
||||
|
||||
|
||||
class CryptoPowerUp(object):
|
||||
"""
|
||||
Gives you MORE CryptoPower!
|
||||
"""
|
||||
|
||||
|
||||
class SigningKeypair(CryptoPowerUp):
|
||||
|
||||
confers_public_key = True
|
||||
|
||||
def __init__(self, privkey_bytes=None):
|
||||
self.secure_rand = SystemRandom()
|
||||
if privkey_bytes:
|
||||
self.priv_key = privkey_bytes
|
||||
else:
|
||||
# Key generation is random([1, N - 1])
|
||||
priv_number = self.secure_rand.randrange(1, N)
|
||||
self.priv_key = priv_number.to_bytes(32, byteorder='big')
|
||||
# Get the public component
|
||||
self.pub_key = privtopub(self.priv_key)
|
||||
|
||||
def pubkey_bytes(self):
|
||||
return b''.join(i.to_bytes(32, 'big') for i in self.pub_key)
|
||||
|
||||
def sign(self, msghash):
|
||||
"""
|
||||
TODO: Use crypto API sign()
|
||||
|
||||
Signs a hashed message and returns a msgpack'ed v, r, and s.
|
||||
|
||||
:param bytes msghash: Hash of the message
|
||||
|
||||
:rtype: Bytestring
|
||||
:return: Msgpacked bytestring of v, r, and s (the signature)
|
||||
"""
|
||||
v, r, s = ecdsa_raw_sign(msghash, self.priv_key)
|
||||
return Crypto.vrs_msgpack_dump(v, r, s)
|
||||
|
||||
def public_key(self):
|
||||
return self.pub_key
|
|
@ -1,5 +1,5 @@
|
|||
import msgpack
|
||||
from nkms import crypto
|
||||
from nkms.crypto import crypto as Crypto
|
||||
from npre.bbs98 import PRE as BasePRE
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ class PRE(BasePRE):
|
|||
return super(PRE, self).priv2pub(priv)
|
||||
|
||||
def rekey(self, priv1, pub2):
|
||||
priv_to = crypto.random(self.KEY_SIZE)
|
||||
priv_to = Crypto.secure_random(self.KEY_SIZE)
|
||||
rk = super(PRE, self).rekey(
|
||||
convert_priv(priv1), convert_priv(priv_to), dtype=bytes)
|
||||
epriv_to = self.encrypt(pub2, priv_to)
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
# TODO: Make this actually work.
|
||||
def generate_re_encryption_keys(seckey_enc_alice,
|
||||
pubkey_enc_bob,
|
||||
m,
|
||||
n):
|
||||
kfrags = [
|
||||
'sfasdfsd9',
|
||||
'dfasd09fi',
|
||||
'sdfksd3f9',
|
||||
]
|
||||
|
||||
return kfrags
|
|
@ -1,9 +1,8 @@
|
|||
import msgpack
|
||||
|
||||
from nkms.crypto import random
|
||||
from nkms.characters import Alice
|
||||
from nkms.crypto import crypto as Crypto
|
||||
from nkms.crypto.hash import content_hash
|
||||
from nkms.crypto.keyring import KeyRing
|
||||
from nkms.crypto.pre.keygen import generate_re_encryption_keys
|
||||
from nkms.policy.constants import UNKNOWN_KFRAG
|
||||
|
||||
|
||||
|
@ -29,8 +28,8 @@ class PolicyManager(object):
|
|||
|
||||
|
||||
class PolicyManagerForAlice(PolicyManager):
|
||||
def __init__(self, keychain_alice: KeyRing):
|
||||
self.keychain_alice = keychain_alice
|
||||
def __init__(self, owner: Alice):
|
||||
self.owner = owner
|
||||
|
||||
def find_n_ursulas(self, networky_stuff, n, offer: PolicyOffer) -> list:
|
||||
"""
|
||||
|
@ -57,7 +56,7 @@ class PolicyManagerForAlice(PolicyManager):
|
|||
"""
|
||||
Alice dictates a new group of policies.
|
||||
"""
|
||||
re_enc_keys = generate_re_encryption_keys(self.keychain_alice.enc_keypair.priv_key,
|
||||
re_enc_keys = self.owner.generate_re_encryption_keys(
|
||||
pubkey_enc_bob,
|
||||
m,
|
||||
n)
|
||||
|
@ -65,7 +64,7 @@ class PolicyManagerForAlice(PolicyManager):
|
|||
for kfrag_id, rekey in enumerate(re_enc_keys):
|
||||
policy = Policy.from_alice(
|
||||
rekey,
|
||||
self.keychain_alice.sig_keypair.pubkey_bytes(),
|
||||
self.owner.seal.as_bytes(),
|
||||
)
|
||||
policies.append(policy)
|
||||
|
||||
|
@ -125,7 +124,7 @@ class Policy(object):
|
|||
"""
|
||||
self.kfrag = kfrag
|
||||
self.deterministic_id_portion = deterministic_id_portion
|
||||
self.random_id_portion = random(32)
|
||||
self.random_id_portion = Crypto.secure_random(32) # TOOD: Where do we actually want this to live?
|
||||
self.challenge_size = challenge_size
|
||||
self.treasure_map = []
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import logging
|
||||
import sys
|
||||
|
||||
root = logging.getLogger()
|
||||
root.setLevel(logging.WARNING)
|
||||
|
||||
ch = logging.StreamHandler(sys.stdout)
|
||||
ch.setLevel(logging.WARNING)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
root.addHandler(ch)
|
|
@ -0,0 +1,92 @@
|
|||
import pytest
|
||||
|
||||
from nkms.characters import Alice, Ursula, Character
|
||||
from nkms.crypto import crypto as Crypto
|
||||
from nkms.crypto.powers import CryptoPower, SigningKeypair, NoSigningPower, NoEncryptingPower
|
||||
|
||||
|
||||
def test_actor_without_signing_power_cannot_sign():
|
||||
"""
|
||||
We can create a Character with no real CryptoPower to speak of.
|
||||
This Character can't even sign a message.
|
||||
"""
|
||||
cannot_sign = CryptoPower(power_ups=[])
|
||||
non_signer = Character(crypto_power=cannot_sign)
|
||||
|
||||
# The non-signer's seal doesn't work for signing...
|
||||
with pytest.raises(NoSigningPower) as e_info:
|
||||
non_signer.seal("something")
|
||||
|
||||
# ...or as a way to show the public key.
|
||||
with pytest.raises(NoSigningPower) as e_info:
|
||||
non_signer.seal.as_bytes()
|
||||
with pytest.raises(NoSigningPower) as e_info:
|
||||
non_signer.seal.as_tuple()
|
||||
|
||||
|
||||
def test_actor_with_signing_power_can_sign():
|
||||
"""
|
||||
However, simply giving that character a PowerUp bestows the power to sign.
|
||||
|
||||
Instead of having a Character verify the signature, we'll use the lower level API.
|
||||
"""
|
||||
message = b"Llamas."
|
||||
|
||||
signer = Character(crypto_power_ups=[SigningKeypair])
|
||||
seal_of_the_signer = signer.seal
|
||||
|
||||
# We can use the signer's seal to sign a message...
|
||||
signature = seal_of_the_signer(message)
|
||||
|
||||
# ...or to get the signer's public key for verification purposes.
|
||||
verification = Crypto.verify(signature, Crypto.digest(message), seal_of_the_signer.as_tuple())
|
||||
|
||||
assert verification is True
|
||||
|
||||
|
||||
def test_anybody_can_verify():
|
||||
"""
|
||||
In the last example, we used the lower-level Crypto API to verify the signature.
|
||||
|
||||
Here, we show that anybody can do it without needing to directly access Crypto.
|
||||
"""
|
||||
|
||||
# Alice can sign by default, by dint of her _default_crypto_powerups.
|
||||
alice = Alice()
|
||||
|
||||
# So, our story is fairly simple: an everyman meets Alice.
|
||||
somebody = Character()
|
||||
somebody.learn_about_actor(alice)
|
||||
|
||||
# Alice signs a message.
|
||||
message = b"A message for all my friends who can only verify and not sign."
|
||||
signature = alice.seal(message)
|
||||
|
||||
# Our everyman can verify it.
|
||||
verification = somebody.verify_from(alice, signature, message)
|
||||
assert verification is True
|
||||
|
||||
|
||||
def test_signing_only_power_cannot_encrypt():
|
||||
"""
|
||||
Similar to the above with signing, here we show that a Character without the EncryptingKeypair
|
||||
PowerUp can't encrypt.
|
||||
"""
|
||||
|
||||
# Here's somebody who can sign but not encrypt.
|
||||
can_sign_but_not_encrypt = Character(crypto_power_ups=[SigningKeypair])
|
||||
|
||||
# ..and here's Ursula, for whom our Character above wants to encrypt.
|
||||
ursula = Ursula()
|
||||
ursula.pubkey_collection = {'signing': "some_privkey_sig"}
|
||||
|
||||
# They meet.
|
||||
can_sign_but_not_encrypt.learn_about_actor(ursula)
|
||||
|
||||
|
||||
# The Character has the message ready...
|
||||
cleartext = "This is Officer Rod Farva. Come in, Ursula! Come in Ursula!"
|
||||
|
||||
# But without the proper PowerUp, no encryption happens.
|
||||
with pytest.raises(NoEncryptingPower) as e_info:
|
||||
can_sign_but_not_encrypt.encrypt_for(ursula, cleartext)
|
|
@ -2,7 +2,10 @@ import unittest
|
|||
import sha3
|
||||
import msgpack
|
||||
import random
|
||||
from nkms.crypto.keypairs import SigningKeypair, EncryptingKeypair
|
||||
|
||||
from nkms.crypto import crypto as Crypto
|
||||
from nkms.crypto.keypairs import EncryptingKeypair
|
||||
from nkms.crypto.powers import SigningKeypair
|
||||
|
||||
|
||||
class TestSigningKeypair(unittest.TestCase):
|
||||
|
@ -29,13 +32,13 @@ class TestSigningKeypair(unittest.TestCase):
|
|||
self.assertTrue(32, len(sig[1])) # Check r
|
||||
self.assertTrue(32, len(sig[2])) # Check s
|
||||
|
||||
verify_sig = self.keypair_b.verify(msg_digest, signature,
|
||||
verify_sig = Crypto.verify(signature, msg_digest,
|
||||
pubkey=self.keypair_a.pub_key)
|
||||
self.assertTrue(verify_sig)
|
||||
|
||||
def test_digest(self):
|
||||
digest_a = self.keypair_a.digest(b'foo', b'bar')
|
||||
digest_b = self.keypair_a.digest(b'foobar')
|
||||
digest_a = Crypto.digest(b'foo', b'bar')
|
||||
digest_b = Crypto.digest(b'foobar')
|
||||
|
||||
self.assertEqual(digest_a, digest_b)
|
||||
|
||||
|
|
|
@ -1,36 +1,41 @@
|
|||
import unittest
|
||||
import msgpack
|
||||
import random
|
||||
import unittest
|
||||
|
||||
import msgpack
|
||||
import npre.elliptic_curve as ec
|
||||
from nkms.crypto.keyring import KeyRing
|
||||
# from nacl.secret import SecretBox
|
||||
|
||||
from nkms.crypto import crypto as Crypto
|
||||
from nkms.crypto.keystore import KeyStore
|
||||
from nkms.crypto.powers import CryptoPower, SigningKeypair
|
||||
|
||||
|
||||
class TestKeyRing(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.keyring_a = KeyRing()
|
||||
self.keyring_b = KeyRing()
|
||||
self.power_of_signing = CryptoPower(power_ups=[SigningKeypair])
|
||||
self.keyring_a = KeyStore()
|
||||
self.keyring_b = KeyStore()
|
||||
|
||||
self.msg = b'this is a test'
|
||||
|
||||
def test_signing(self):
|
||||
signature = self.keyring_a.sign(self.msg)
|
||||
signature = self.power_of_signing.sign(self.msg)
|
||||
|
||||
sig = msgpack.loads(signature)
|
||||
self.assertTrue(1, len(sig[0])) # Check v
|
||||
self.assertTrue(32, len(sig[1])) # Check r
|
||||
self.assertTrue(32, len(sig[2])) # Check s
|
||||
self.assertTrue(1, len(sig[0])) # Check v
|
||||
self.assertTrue(32, len(sig[1])) # Check r
|
||||
self.assertTrue(32, len(sig[2])) # Check s
|
||||
|
||||
def test_verification(self):
|
||||
signature = self.keyring_a.sign(self.msg)
|
||||
signature = self.power_of_signing.sign(self.msg)
|
||||
|
||||
sig = msgpack.loads(signature)
|
||||
self.assertTrue(1, len(sig[0])) # Check v
|
||||
self.assertTrue(32, len(sig[1])) # Check r
|
||||
self.assertTrue(32, len(sig[2])) # Check s
|
||||
self.assertTrue(1, len(sig[0])) # Check v
|
||||
self.assertTrue(32, len(sig[1])) # Check r
|
||||
self.assertTrue(32, len(sig[2])) # Check s
|
||||
|
||||
is_valid = self.keyring_b.verify(self.msg, signature,
|
||||
pubkey=self.keyring_a.sig_pubkey)
|
||||
msghash = Crypto.digest(self.msg)
|
||||
is_valid = Crypto.verify(signature, msghash,
|
||||
pubkey=self.power_of_signing.pubkey_sig_tuple())
|
||||
self.assertTrue(is_valid)
|
||||
|
||||
def test_key_generation(self):
|
||||
|
@ -53,8 +58,8 @@ class TestKeyRing(unittest.TestCase):
|
|||
|
||||
# Generate the re-encryption key and the ephemeral key data
|
||||
reenc_key, enc_symm_key_bob, enc_priv_e = self.keyring_a.rekey(
|
||||
self.keyring_a.enc_privkey,
|
||||
self.keyring_b.enc_pubkey)
|
||||
self.keyring_a.enc_privkey,
|
||||
self.keyring_b.enc_pubkey)
|
||||
|
||||
# Re-encrypt Alice's symm key
|
||||
enc_symm_key_ab = self.keyring_a.reencrypt(reenc_key,
|
||||
|
@ -98,14 +103,14 @@ class TestKeyRing(unittest.TestCase):
|
|||
self.assertTrue(dec_key == raw_key)
|
||||
|
||||
def test_symm_encryption(self):
|
||||
key = self.keyring_a.secure_random(32)
|
||||
key = Crypto.secure_random(32)
|
||||
self.assertEqual(32, len(key))
|
||||
|
||||
ciphertext = self.keyring_a.symm_encrypt(key, self.msg)
|
||||
self.assertTrue(self.msg not in ciphertext)
|
||||
|
||||
def test_symm_decryption(self):
|
||||
key = self.keyring_a.secure_random(32)
|
||||
key = Crypto.secure_random(32)
|
||||
self.assertEqual(32, len(key))
|
||||
|
||||
ciphertext = self.keyring_a.symm_encrypt(key, self.msg)
|
||||
|
@ -158,14 +163,14 @@ class TestKeyRing(unittest.TestCase):
|
|||
path_priv_a = int.from_bytes(path_priv_a, byteorder='big')
|
||||
|
||||
rk_ab, enc_symm_key_bob, enc_priv_e = self.keyring_a.rekey(
|
||||
path_priv_a, self.keyring_b.enc_pubkey)
|
||||
path_priv_a, self.keyring_b.enc_pubkey)
|
||||
|
||||
enc_path_key, enc_path_symm_key = enc_keys[0]
|
||||
reenc_path_symm_key = self.keyring_a.reencrypt(rk_ab, enc_path_symm_key)
|
||||
|
||||
priv_e = self.keyring_b.decrypt(enc_priv_e, enc_symm_key_bob)
|
||||
priv_e = int.from_bytes(priv_e, byteorder='big')
|
||||
keyring_e = KeyRing(enc_privkey=priv_e)
|
||||
keyring_e = KeyStore(enc_privkey=priv_e)
|
||||
|
||||
dec_key = keyring_e.decrypt(enc_path_key, reenc_path_symm_key)
|
||||
self.assertEqual(plaintext, dec_key)
|
||||
|
@ -180,12 +185,12 @@ class TestKeyRing(unittest.TestCase):
|
|||
|
||||
path_priv_a = self.keyring_a._derive_path_key(b'', is_pub=False)
|
||||
path_priv_a = int.from_bytes(path_priv_a, byteorder='big')
|
||||
keyring_a_path = KeyRing(enc_privkey=path_priv_a)
|
||||
keyring_a_path = KeyStore(enc_privkey=path_priv_a)
|
||||
|
||||
dec_key = keyring_a_path.decrypt(*enc_keys[0])
|
||||
self.assertEqual(plaintext, dec_key)
|
||||
|
||||
def test_secure_random(self):
|
||||
length = random.randrange(1, 100)
|
||||
rand_bytes = self.keyring_a.secure_random(length)
|
||||
rand_bytes = Crypto.secure_random(length)
|
||||
self.assertEqual(length, len(rand_bytes))
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from nkms.crypto import default_algorithm
|
||||
from nkms.crypto import symmetric_from_algorithm
|
||||
from nkms.crypto import pre_from_algorithm
|
||||
from nkms import crypto
|
||||
from nkms.crypto import crypto as Crypto
|
||||
|
||||
|
||||
def test_symmetric():
|
||||
Cipher = symmetric_from_algorithm(default_algorithm)
|
||||
key = crypto.random(Cipher.KEY_SIZE)
|
||||
key = Crypto.secure_random(Cipher.KEY_SIZE)
|
||||
cipher = Cipher(key)
|
||||
data = b'Hello world' * 10
|
||||
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
import asyncio
|
||||
import unittest
|
||||
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from nkms import crypto
|
||||
from nkms.characters import Ursula, Alice
|
||||
from nkms.characters import Ursula, Alice, Character
|
||||
from nkms.crypto import crypto as Crypto
|
||||
from nkms.crypto.encrypting_keypair import EncryptingKeypair
|
||||
from nkms.crypto.keyring import KeyRing
|
||||
from nkms.crypto.keystore import KeyStore
|
||||
from nkms.policy.constants import NON_PAYMENT
|
||||
from nkms.policy.models import PolicyManagerForAlice, PolicyOffer, TreasureMap, PolicyGroup
|
||||
|
||||
|
||||
class MockEncryptingPair(object):
|
||||
|
||||
def encrypt(self, cleartext):
|
||||
pass
|
||||
|
||||
|
@ -50,9 +48,9 @@ def test_complete_treasure_map_flow():
|
|||
Shows that Alice can share a TreasureMap with Ursula and that Bob can receive and decrypt it.
|
||||
"""
|
||||
|
||||
keyring_alice = KeyRing()
|
||||
keyring_alice = KeyStore()
|
||||
bob_encrypting_keypair = EncryptingKeypair()
|
||||
keyring_ursula = KeyRing()
|
||||
keyring_ursula = KeyStore()
|
||||
|
||||
alice, ursula, event_loop = test_alice_finds_ursula()
|
||||
|
||||
|
@ -60,7 +58,7 @@ def test_complete_treasure_map_flow():
|
|||
|
||||
treasure_map = TreasureMap()
|
||||
for i in range(50):
|
||||
treasure_map.nodes.append(crypto.random(50))
|
||||
treasure_map.nodes.append(Crypto.secure_random(50))
|
||||
|
||||
encrypted_treasure_map = bob_encrypting_keypair.encrypt(treasure_map.packed_payload())
|
||||
signature = "THIS IS A SIGNATURE"
|
||||
|
@ -72,14 +70,15 @@ def test_complete_treasure_map_flow():
|
|||
event_loop.run_until_complete(setter)
|
||||
|
||||
treasure_map_as_set_on_network = list(ursula.server.storage.items())[0][1]
|
||||
treasure_map_as_decrypted_by_bob = bob_encrypting_keypair.decrypt(treasure_map_as_set_on_network)
|
||||
treasure_map_as_decrypted_by_bob = bob_encrypting_keypair.decrypt(
|
||||
treasure_map_as_set_on_network)
|
||||
assert treasure_map_as_decrypted_by_bob == treasure_map.packed_payload()
|
||||
|
||||
|
||||
def test_alice_has_ursulas_public_key_and_uses_it_to_encode_policy_payload():
|
||||
keychain_alice = KeyRing()
|
||||
keychain_bob = KeyRing()
|
||||
keychain_ursula = KeyRing()
|
||||
alice = Alice()
|
||||
keychain_bob = KeyStore()
|
||||
keychain_ursula = KeyStore()
|
||||
|
||||
# For example, a hashed path.
|
||||
resource_id = b"as098duasdlkj213098asf"
|
||||
|
@ -92,7 +91,7 @@ def test_alice_has_ursulas_public_key_and_uses_it_to_encode_policy_payload():
|
|||
|
||||
# Now, Alice needs to find N Ursulas to whom to make the offer.
|
||||
networky_stuff = MockNetworkyStuff()
|
||||
policy_manager = PolicyManagerForAlice(keychain_alice)
|
||||
policy_manager = PolicyManagerForAlice(alice)
|
||||
|
||||
policy_group = policy_manager.create_policy_group(
|
||||
keychain_bob.enc_keypair.pub_key,
|
||||
|
@ -124,3 +123,19 @@ def test_alice_finds_ursula():
|
|||
_discovered_ursula_ip, discovered_ursula_port = alice.find_best_ursula()
|
||||
assert ursula_port == ursula_port
|
||||
return alice, ursula, event_loop
|
||||
|
||||
|
||||
def test_trying_to_find_unknown_actor_raises_not_found():
|
||||
terry = Character()
|
||||
alice = Alice()
|
||||
|
||||
message = b"some_message"
|
||||
signature = alice.seal(message)
|
||||
|
||||
# Terry can't reference Alice...
|
||||
with pytest.raises(Character.NotFound):
|
||||
verification = terry.verify_from(alice, signature, message)
|
||||
|
||||
# ...before learning about Alice.
|
||||
terry.learn_about_actor(alice)
|
||||
verification = terry.verify_from(alice, signature, message)
|
||||
|
|
Loading…
Reference in New Issue