Merge pull request #8 from jMyles/rm-crypto

Rm crypto
pull/157/head
Tux 2018-02-10 20:58:26 -07:00 committed by GitHub
commit 6922739fd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 117 additions and 80 deletions

View File

@ -15,6 +15,7 @@ from sqlalchemy.exc import IntegrityError
from umbral.fragments import KFrag
from umbral.keys import UmbralPublicKey
import umbral
from nkms.crypto import api as API
from nkms.crypto.api import secure_random, keccak_digest
@ -238,7 +239,7 @@ class Alice(Character):
_server_class = NuCypherSeedOnlyDHTServer
_default_crypto_powerups = [SigningPower, EncryptingPower]
def generate_rekey_frags(self, alice_privkey, bob, m, n):
def generate_kfrags(self, bob, m, n):
"""
Generates re-encryption key frags and returns the frags and encrypted
ephemeral key data.
@ -250,9 +251,10 @@ class Alice(Character):
:return: Tuple(kfrags, eph_key_data)
"""
kfrags, eph_key_data = API.ecies_ephemeral_split_rekey(
alice_privkey, bytes(bob.seal.without_metabytes()), m, n)
return (kfrags, eph_key_data)
# TODO: Is this how we want to access Alice's private key?
alice_priv_enc = self._crypto_power._power_ups[EncryptingPower].keypair.privkey
k_frags, _v_keys = umbral.umbral.split_rekey(alice_priv_enc, bob.public_key(EncryptingPower), m, n)
return k_frags
def create_policy(self,
bob: "Bob",
@ -263,17 +265,13 @@ class Alice(Character):
"""
Alice dictates a new group of policies.
"""
##### Temporary until we decide on an API for private key access
alice_priv_enc = self._crypto_power._power_ups[EncryptingPower].priv_key
kfrags, pfrag = self.generate_rekey_frags(alice_priv_enc, bob, m, n)
kfrags = self.generate_kfrags(bob, m, n)
# TODO: Access Alice's private key inside this method.
from nkms.policy.models import Policy
policy = Policy.from_alice(
alice=self,
bob=bob,
kfrags=kfrags,
pfrag=pfrag,
uri=uri,
)

View File

@ -8,7 +8,7 @@ from cryptography.exceptions import InvalidSignature
from py_ecc.secp256k1 import ecdsa_raw_recover
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
from nkms.crypto.signature import Signature
SYSTEM_RAND = SystemRandom()
@ -75,8 +75,8 @@ def ecdsa_sign(message: bytes, privkey: UmbralPrivateKey) -> bytes:
:return: signature
"""
cryptography_priv_key = privkey.bn_key.to_cryptography_priv_key()
signature_bytes = cryptography_priv_key.sign(message, ec.ECDSA(hashes.BLAKE2b(64)))
return Signature(signature_bytes)
signature_der_bytes = cryptography_priv_key.sign(message, ec.ECDSA(hashes.BLAKE2b(64)))
return signature_der_bytes
def ecdsa_verify(

View File

@ -1,10 +1,10 @@
# TODO: Turn these into classes?
HASH_DIGEST_LENGTH = 64
BLAKE2B_DIGEST_LENGTH = 64
KECCAK_DIGEST_LENGTH = 32
NOT_SIGNED = 445
NO_DECRYPTION_PERFORMED = 455
# These lengths are centric to secp256k1
# These lengths are specific to secp256k1
KFRAG_LENGTH = 194
CFRAG_LENGTH = 131
CAPSULE_LENGTH = 98

View File

@ -21,15 +21,18 @@ class NoEncryptingPower(PowerUpError):
class CryptoPower(object):
def __init__(self, power_ups=[], generate_keys_if_needed=False):
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 = {}
self.generate_keys = generate_keys_if_needed
if power_ups:
if power_ups is not None:
for power_up in power_ups:
self.consume_power_up(power_up)
else:
power_ups = [] # default
def consume_power_up(self, power_up):
if isinstance(power_up, CryptoPowerUp):
@ -47,14 +50,13 @@ class CryptoPower(object):
if power_up.confers_public_key:
# TODO: Make this an ID for later lookup on a KeyStore.
self.public_keys[
power_up_class] = power_up_instance.public_key()
self.public_keys[power_up_class] = power_up_instance.public_key()
def pubkey_sig_bytes(self):
try:
# TODO: Turn this into an ID lookup on a KeyStore.
return self._power_ups[
SigningPower].pub_key
pubkey_sig = self._power_ups[SigningPower].public_key()
return bytes(pubkey_sig)
except KeyError:
raise NoSigningPower
@ -78,8 +80,7 @@ class CryptoPower(object):
except KeyError as e:
raise NoSigningPower(e)
msg_digest = b"".join(API.keccak_digest(m) for m in messages)
return Signature(sig_keypair.sign(msg_digest))
return sig_keypair.sign(msg_digest)
def decrypt(self, ciphertext):
try:
@ -118,8 +119,8 @@ class KeyPairBasedPower(CryptoPowerUp):
else:
# They didn't pass a keypair; we'll make one with the bytes (if any)
# they provided.
self.keypair = self._keypair_class.load_key(
UmbralPublicKey(pubkey_bytes),
self.keypair = self._keypair_class(
UmbralPublicKey.from_bytes(pubkey_bytes),
generate_keys_if_needed=generate_keys_if_needed)
@ -127,15 +128,11 @@ class SigningPower(KeyPairBasedPower):
confers_public_key = True
_keypair_class = SigningKeypair
def sign(self, msghash):
def sign(self, message):
"""
Signs a hashed message and returns a signature.
:param msghash: The hashed message to sign
:return: Signature in bytes
Signs a message message and returns a Signature.
"""
return self.keypair.sign(msghash)
return self.keypair.sign(message)
def public_key(self):
return self.keypair.pubkey

View File

@ -1,23 +1,20 @@
from nkms.crypto import api as API
from umbral.keys import UmbralPublicKey
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature
class Signature(bytes):
class Signature(object):
"""
The Signature object allows signatures to be made and verified.
"""
_EXPECTED_LENGTH = 70
_EXPECTED_LENGTH = 64 # With secp256k1 and BLAKE2b(64).
def __init__(self, sig_as_bytes: bytes):
"""
Initializes a Signature object.
:param sig_as_bytes: Cryptography.io signature as bytes.
:return: Signature object
"""
self.sig_as_bytes = sig_as_bytes
def __init__(self, r: int, s: int):
self.r = r
self.s = s
def __repr__(self):
return "ECDSA Signature: {}".format(sig_as_bytes.decode())
return "ECDSA Signature: {}".format(bytes(self).hex()[:15])
def verify(self, message: bytes, pubkey: UmbralPublicKey) -> bool:
"""
@ -28,11 +25,32 @@ class Signature(bytes):
:return: True if valid, False if invalid
"""
return API.ecdsa_verify(message, self.sig_as_bytes, pubkey)
return API.ecdsa_verify(message, self._der_encoded_bytes(), pubkey)
@classmethod
def from_bytes(cls, signature_as_bytes, der_encoded=False):
if der_encoded:
r, s = decode_dss_signature(signature_as_bytes)
else:
if not len(signature_as_bytes) == 64:
raise ValueError("Looking for exactly 64 bytes if you call from_bytes with der_encoded=False.")
else:
r = int.from_bytes(signature_as_bytes[:32], "big")
s = int.from_bytes(signature_as_bytes[32:], "big")
return cls(r, s)
def _der_encoded_bytes(self):
return encode_dss_signature(self.r, self.s)
def __bytes__(self):
"""
Implements the __bytes__ call for Signature to transform into a
transportable mode.
"""
return self.sig_as_bytes
# A couple quick assertions to be sure this is OK. Remove these at some point.
assert self.r.to_bytes(33, "big")[0] == 0, "Is 32 bytes enough?"
assert self.s.to_bytes(33, "big")[0] == 0, "Is 32 bytes enough?"
return self.r.to_bytes(32, "big") + self.s.to_bytes(32, "big")
def __len__(self):
return len(bytes(self))
def __add__(self, other):
return bytes(self) + other
__radd__ = __add__

View File

@ -47,7 +47,15 @@ class BytestringSplitter(object):
@staticmethod
def get_message_meta(message_type):
return message_type if isinstance(message_type, tuple) else (message_type, message_type._EXPECTED_LENGTH)
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.")
return message_meta
class RepeatingBytestringSplitter(BytestringSplitter):

View File

@ -6,6 +6,7 @@ from nkms.crypto import api as API
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
from umbral import umbral
from nkms.crypto.kits import MessageKit
from nkms.crypto.signature import Signature
class Keypair(object):
@ -22,7 +23,7 @@ class Keypair(object):
:param generate_keys_if_needed: Generate keys or not?
"""
try:
self.pubkey = umbral_key.get_pub_key()
self.pubkey = umbral_key.get_pubkey()
self.privkey = umbral_key
except NotImplementedError:
self.pubkey = umbral_key
@ -30,7 +31,7 @@ class Keypair(object):
# They didn't pass anything we recognize as a valid key.
if generate_keys_if_needed:
self.privkey = UmbralPrivateKey.gen_key()
self.pubkey = self.priv_key.get_pub_key()
self.pubkey = self.privkey.get_pub_key()
else:
raise ValueError("Either pass a valid key as umbral_key or, if you want to generate keys, set generate_keys_if_needed to True.")
@ -79,8 +80,9 @@ class SigningKeypair(Keypair):
"""
Signs a hashed message and returns a signature.
:param msghash: The hashed message to sign
:param message: The message to sign
:return: Signature in bytes
"""
return API.ecdsa_sign(message, self.privkey)
signature_der_bytes = API.ecdsa_sign(message, self.privkey)
return Signature.from_bytes(signature_der_bytes, der_encoded=True)

View File

@ -2,7 +2,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 HASH_DIGEST_LENGTH, PUBLIC_KEY_LENGTH
from nkms.crypto.constants import PUBLIC_KEY_LENGTH, KECCAK_DIGEST_LENGTH
from nkms.crypto.signature import Signature
from nkms.crypto.utils import BytestringSplitter
from nkms.network.constants import NODE_HAS_NO_STORAGE
@ -10,7 +10,7 @@ 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, HASH_DIGEST_LENGTH))
dht_value_splitter = BytestringSplitter(Signature, (UmbralPublicKey, PUBLIC_KEY_LENGTH), (bytes, KECCAK_DIGEST_LENGTH))
class NuCypherHashProtocol(KademliaProtocol):

View File

@ -7,10 +7,12 @@ from npre.constants import UNKNOWN_KFRAG
from nkms.characters import Alice, Bob, Ursula
from nkms.crypto.api import keccak_digest
from nkms.crypto.constants import NOT_SIGNED, HASH_DIGEST_LENGTH
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.utils import BytestringSplitter
from umbral.keys import UmbralPublicKey
class Contract(object):
@ -19,7 +21,8 @@ class Contract(object):
"""
_EXPECTED_LENGTH = 124
def __init__(self, alice, hrac, expiration, deposit=None, ursula=None, kfrag=UNKNOWN_KFRAG, alices_signature=None):
def __init__(self, alice, hrac, expiration, deposit=None, ursula=None,
kfrag=UNKNOWN_KFRAG, alices_signature=None):
"""
:param deposit: Funds which will pay for the timeframe of this Contract (not the actual re-encryptions);
a portion will be locked for each Ursula that accepts.
@ -40,12 +43,17 @@ class Contract(object):
self.ursula = ursula
def __bytes__(self):
return bytes(self.alice.seal) + bytes(self.hrac) + self.expiration.isoformat().encode() + bytes(self.deposit)
return bytes(self.alice.seal) + bytes(
self.hrac) + self.expiration.isoformat().encode() + bytes(
self.deposit)
@classmethod
def from_bytes(cls, contract_as_bytes):
contract_splitter = BytestringSplitter(PublicKey, (bytes, HASH_DIGEST_LENGTH), (bytes, 26))
alice_pubkey_sig, hrac, expiration_bytes = contract_splitter(contract_as_bytes)
contract_splitter = BytestringSplitter(
(UmbralPublicKey, PUBLIC_KEY_LENGTH), (bytes, KECCAK_DIGEST_LENGTH),
(bytes, 26))
alice_pubkey_sig, hrac, expiration_bytes, deposit_bytes = contract_splitter(
contract_as_bytes, return_remainder=True)
expiration = maya.parse(expiration_bytes.decode())
alice = Alice.from_public_keys((SigningPower, alice_pubkey_sig))
return cls(alice=alice, hrac=hrac, expiration=expiration)
@ -59,7 +67,8 @@ class Contract(object):
"""
Craft an offer to send to Ursula.
"""
return self.alice.encrypt_for(self.ursula, self.payload())[0] # We don't need the signature separately.
# We don't need the signature separately.
return self.alice.encrypt_for(self.ursula, self.payload())[0]
def payload(self):
# TODO: Ship the expiration again? Or some other way of alerting Ursula to recall her previous dialogue regarding this Contract. Update: We'll probably have her store the Contract by hrac. See #127.
@ -82,10 +91,9 @@ class Policy(object):
Once Alice has secured agreement with n Ursulas to enact a Policy, she sends each a KFrag,
and generates a TreasureMap for the Policy, recording which Ursulas got a KFrag.
"""
_ursula = None
hashed_part = None
def __init__(self, alice, bob=None, kfrags=(UNKNOWN_KFRAG,), pfrag=None, uri=None, alices_signature=NOT_SIGNED):
def __init__(self, alice, bob=None, kfrags=(UNKNOWN_KFRAG,), uri=None,
alices_signature=NOT_SIGNED):
"""
:param kfrags: A list of KFrags to distribute per this Policy.
:param pfrag: The input ciphertext which Bob will give to Ursula to re-encrypt.
@ -94,7 +102,6 @@ class Policy(object):
self.alice = alice
self.bob = bob
self.kfrags = kfrags
self.pfrag = pfrag
self.uri = uri
self.treasure_map = TreasureMap()
self._accepted_contracts = {}
@ -125,13 +132,12 @@ class Policy(object):
@staticmethod
def from_alice(kfrags,
pfrag,
alice,
bob,
uri,
):
# TODO: What happened to Alice's signature - don't we include it here?
policy = Policy(alice, bob, kfrags, pfrag, uri)
policy = Policy(alice, bob, kfrags, uri)
return policy
@ -143,7 +149,6 @@ class Policy(object):
@staticmethod
def hrac_for(alice, bob, uri):
"""
The "hashed resource authentication code".
@ -172,9 +177,10 @@ class Policy(object):
return self.hash(bytes(self.alice.seal) + self.hrac())
def publish_treasure_map(self):
encrypted_treasure_map, signature_for_bob = self.alice.encrypt_for(self.bob,
self.treasure_map.packed_payload())
signature_for_ursula = self.alice.seal(self.hrac()) # TODO: Great use-case for Ciphertext class
encrypted_treasure_map, signature_for_bob = self.alice.encrypt_for(
self.bob,
self.treasure_map.packed_payload())
signature_for_ursula = self.alice.seal(self.hrac())
# In order to know this is safe to propagate, Ursula needs to see a signature, our public key,
# and, reasons explained in treasure_map_dht_key above, the uri_hash.
@ -188,7 +194,6 @@ class Policy(object):
return encrypted_treasure_map, dht_value, signature_for_bob, signature_for_ursula
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)
@ -200,9 +205,11 @@ class Policy(object):
self.treasure_map.add_ursula(contract.ursula)
def draw_up_contract(self, deposit, expiration):
return Contract(self.alice, self.hrac(), expiration=expiration, deposit=deposit)
return Contract(self.alice, self.hrac(), expiration=expiration,
deposit=deposit)
def find_ursulas(self, networky_stuff, deposit, expiration, num_ursulas=None):
def find_ursulas(self, networky_stuff, deposit, expiration,
num_ursulas=None):
# TODO: This is a number mismatch - we need not one contract, but n contracts.
"""
:param networky_stuff: A compliant interface (maybe a Client instance) to be used to engage the DHT swarm.
@ -258,7 +265,8 @@ class TreasureMap(object):
class WorkOrder(object):
def __init__(self, bob, kfrag_hrac, pfrags, receipt_bytes, receipt_signature, ursula_id=None):
def __init__(self, bob, kfrag_hrac, pfrags, receipt_bytes,
receipt_signature, ursula_id=None):
self.bob = bob
self.kfrag_hrac = kfrag_hrac
self.pfrags = pfrags
@ -267,12 +275,14 @@ class WorkOrder(object):
self.ursula_id = ursula_id # TODO: We may still need a more elegant system for ID'ing Ursula. See #136.
def __repr__(self):
return "WorkOrder (pfrags: {}) {} for {}".format([binascii.hexlify(bytes(p))[:6] for p in self.pfrags],
binascii.hexlify(self.receipt_bytes)[:6],
binascii.hexlify(self.ursula_id)[:6])
return "WorkOrder (pfrags: {}) {} for {}".format(
[binascii.hexlify(bytes(p))[:6] for p in self.pfrags],
binascii.hexlify(self.receipt_bytes)[:6],
binascii.hexlify(self.ursula_id)[:6])
def __eq__(self, other):
return (self.receipt_bytes, self.receipt_signature) == (other.receipt_bytes, other.receipt_signature)
return (self.receipt_bytes, self.receipt_signature) == (
other.receipt_bytes, other.receipt_signature)
def __len__(self):
return len(self.pfrags)

View File

@ -2,11 +2,14 @@ import datetime
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
def test_grant(alice, bob, ursulas):
networky_stuff = MockNetworkyStuff(ursulas)
@ -30,6 +33,7 @@ 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')
signing_key_bytes, encrypting_key_bytes = BytestringSplitter(PublicKey)(response.content, return_remainder=True)
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))
assert stranger_ursula_from_public_keys == ursulas[0]