Merge jmyle's PR fixing stuff

pull/74/head
tuxxy 2017-10-10 15:03:06 -06:00
commit ce8b222197
18 changed files with 509 additions and 233 deletions

View File

@ -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

View File

@ -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):
"""

View File

@ -1,5 +1,4 @@
import importlib
from nacl.utils import random # noqa
from npre.curves import secp256k1
default_algorithm = dict(

1
nkms/crypto/constants.py Normal file
View File

@ -0,0 +1 @@
NOT_SIGNED = 445

View File

@ -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.

View File

@ -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

View File

@ -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)

122
nkms/crypto/powers.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 = []

View File

@ -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)

View File

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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)