From bf0f83b6ea8503a2ae1ead33071741b5a3822756 Mon Sep 17 00:00:00 2001 From: jMyles Date: Tue, 10 Oct 2017 22:39:25 -0700 Subject: [PATCH] Moving EncryptingKeypair over to powers. --- nkms/characters.py | 4 +- nkms/crypto/keypairs.py | 98 -------------- nkms/crypto/keystore.py | 2 +- nkms/crypto/powers.py | 120 ++++++++++++++++-- ...test_crypto_characters_and_their_powers.py | 32 ++++- tests/crypto/test_keypairs.py | 23 ++-- 6 files changed, 155 insertions(+), 124 deletions(-) diff --git a/nkms/characters.py b/nkms/characters.py index d8154b8b9..c92a04501 100644 --- a/nkms/characters.py +++ b/nkms/characters.py @@ -88,9 +88,9 @@ class Character(object): if sign: if sign_cleartext: - signature = self.keyring.sign(cleartext) + signature = self.seal(cleartext) else: - signature = self.keyring.sign(ciphertext) + signature = self.seal(ciphertext) else: signature = NOT_SIGNED diff --git a/nkms/crypto/keypairs.py b/nkms/crypto/keypairs.py index 5125dcb58..e69de29bb 100644 --- a/nkms/crypto/keypairs.py +++ b/nkms/crypto/keypairs.py @@ -1,98 +0,0 @@ -from npre import umbral - - -class EncryptingKeypair(object): - KEYSIZE = 32 - - def __init__(self, privkey=None): - self.pre = umbral.PRE() - - if not privkey: - self.priv_key = self.pre.gen_priv() - else: - self.priv_key = privkey - self._pub_key = None - - @property - def pub_key(self): - if self._pub_key is None: - self._pub_key = self.pre.priv2pub(self.priv_key) - return self._pub_key - - def generate_key(self, pubkey=None): - """ - Generate a raw symmetric key and its encrypted counterpart. - - :rtype: Tuple(bytes, bytes) - :return: Tuple of the raw encrypted key and the encrypted key - """ - pubkey = pubkey or self.pub_key - symm_key, enc_symm_key = self.pre.encapsulate(pubkey) - return (symm_key, enc_symm_key) - - def decrypt_key(self, enc_key, privkey=None): - """ - Decrypts an ECIES encrypted symmetric key. - - :param int enc_key: The ECIES encrypted key as an integer - :param bytes privkey: The privkey to decapsulate from - - :rtype: int - :return: Decrypted key as an integer - """ - priv_key = privkey or self.priv_key - return self.pre.decapsulate(priv_key, enc_key) - - def rekey(self, privkey_a, privkey_b): - """ - Generates a re-encryption key in interactive mode. - - :param bytes privkey_a: Alice's private key - :param bytes privkey_b: Bob's private key (or an ephemeral privkey) - - :rtype: bytes - :return: Bytestring of a re-encryption key - """ - return self.pre.rekey(privkey_a, privkey_b) - - def split_rekey(self, privkey_a, privkey_b, min_shares, num_shares): - """ - Generates key shares that can be used to re-encrypt data. Requires - `min_shares` to be able to successfully combine data for full key. - - :param int privkey_a: Alice's private key - :param int privkey_b: Bob's private key (or an ephemeral privkey) - :param int min_shares: Threshold of shares needed to reconstruct key - :param int num_shares: Total number of shares to generate - - :rtype: List(RekeyFrag) - :return: List of `num_shares` RekeyFrags - """ - return self.pre.split_rekey(privkey_a, privkey_b, min_shares, - num_shares) - - def combine(self, shares): - """ - Reconstructs a secret from the given shares. - - :param list shares: List of secret share fragments. - - :rtype: EncryptedKey - :return: EncryptedKey from `shares` - """ - # TODO: What to do if not enough shares, or invalid? - return self.pre.combine(shares) - - def reencrypt(self, reenc_key, ciphertext): - """ - Re-encrypts the provided ciphertext for the recipient of the generated - re-encryption key. - - :param bytes reenc_key: The re-encryption key from the proxy to Bob - :param bytes ciphertext: The ciphertext to re-encrypt to Bob - - :rtype: bytes - :return: Re-encrypted ciphertext - """ - return self.pre.reencrypt(reenc_key, ciphertext) - diff --git a/nkms/crypto/keystore.py b/nkms/crypto/keystore.py index c1f7fef6b..8896ec38b 100644 --- a/nkms/crypto/keystore.py +++ b/nkms/crypto/keystore.py @@ -3,7 +3,7 @@ import sha3 from nacl.secret import SecretBox from nacl.utils import random -from nkms.crypto.keypairs import EncryptingKeypair +from nkms.crypto.powers import EncryptingKeypair from npre import umbral diff --git a/nkms/crypto/powers.py b/nkms/crypto/powers.py index f04993908..601965345 100644 --- a/nkms/crypto/powers.py +++ b/nkms/crypto/powers.py @@ -1,10 +1,10 @@ from random import SystemRandom from typing import Iterable -from py_ecc.secp256k1 import N, privtopub, ecdsa_raw_sign, ecdsa_raw_recover +from py_ecc.secp256k1 import N, privtopub from nkms.crypto import api -from nkms.crypto.keypairs import EncryptingKeypair +from npre import umbral class PowerUpError(TypeError): @@ -36,21 +36,25 @@ class CryptoPower(object): 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.") + 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. - + 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. + 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. + return self._power_ups[ + SigningKeypair].pub_key # TODO: Turn this into an ID lookup on a KeyStore. except KeyError: raise NoSigningPower @@ -83,10 +87,10 @@ class CryptoPowerUp(object): """ Gives you MORE CryptoPower! """ + confers_public_key = False class SigningKeypair(CryptoPowerUp): - confers_public_key = True def __init__(self, privkey_bytes=None): @@ -119,3 +123,103 @@ class SigningKeypair(CryptoPowerUp): def public_key(self): return self.pub_key + + +class EncryptingKeypair(CryptoPowerUp): + KEYSIZE = 32 + confers_public_key = True + + def __init__(self, privkey=None): + self.pre = umbral.PRE() + + if not privkey: + self.priv_key = self.pre.gen_priv() + else: + self.priv_key = privkey + self._pub_key = None + + @property + def pub_key(self): + if self._pub_key is None: + self._pub_key = self.pre.priv2pub(self.priv_key) + return self._pub_key + + def generate_key(self, pubkey=None): + """ + Generate a raw symmetric key and its encrypted counterpart. + + :rtype: Tuple(bytes, bytes) + :return: Tuple of the raw encrypted key and the encrypted key + """ + pubkey = pubkey or self.pub_key + symm_key, enc_symm_key = self.pre.encapsulate(pubkey) + return (symm_key, enc_symm_key) + + def decrypt_key(self, enc_key, privkey=None): + """ + Decrypts an ECIES encrypted symmetric key. + + :param int enc_key: The ECIES encrypted key as an integer + :param bytes privkey: The privkey to decapsulate from + + :rtype: int + :return: Decrypted key as an integer + """ + priv_key = privkey or self.priv_key + return self.pre.decapsulate(priv_key, enc_key) + + def rekey(self, privkey_a, privkey_b): + """ + Generates a re-encryption key in interactive mode. + + :param bytes privkey_a: Alice's private key + :param bytes privkey_b: Bob's private key (or an ephemeral privkey) + + :rtype: bytes + :return: Bytestring of a re-encryption key + """ + return self.pre.rekey(privkey_a, privkey_b) + + def split_rekey(self, privkey_a, privkey_b, min_shares, num_shares): + """ + Generates key shares that can be used to re-encrypt data. Requires + `min_shares` to be able to successfully combine data for full key. + + :param int privkey_a: Alice's private key + :param int privkey_b: Bob's private key (or an ephemeral privkey) + :param int min_shares: Threshold of shares needed to reconstruct key + :param int num_shares: Total number of shares to generate + + :rtype: List(RekeyFrag) + :return: List of `num_shares` RekeyFrags + """ + return self.pre.split_rekey(privkey_a, privkey_b, min_shares, + num_shares) + + def combine(self, shares): + """ + Reconstructs a secret from the given shares. + + :param list shares: List of secret share fragments. + + :rtype: EncryptedKey + :return: EncryptedKey from `shares` + """ + # TODO: What to do if not enough shares, or invalid? + return self.pre.combine(shares) + + def reencrypt(self, reenc_key, ciphertext): + """ + Re-encrypts the provided ciphertext for the recipient of the generated + re-encryption key. + + :param bytes reenc_key: The re-encryption key from the proxy to Bob + :param bytes ciphertext: The ciphertext to re-encrypt to Bob + + :rtype: bytes + :return: Re-encrypted ciphertext + """ + return self.pre.reencrypt(reenc_key, ciphertext) + + def public_key(self): + return self.pub_key diff --git a/tests/characters/test_crypto_characters_and_their_powers.py b/tests/characters/test_crypto_characters_and_their_powers.py index d99809985..57c117a37 100644 --- a/tests/characters/test_crypto_characters_and_their_powers.py +++ b/tests/characters/test_crypto_characters_and_their_powers.py @@ -2,7 +2,13 @@ import pytest from nkms.characters import Alice, Ursula, Character from nkms.crypto import api -from nkms.crypto.powers import CryptoPower, SigningKeypair, NoSigningPower, NoEncryptingPower +from nkms.crypto.constants import NOT_SIGNED +from nkms.crypto.powers import CryptoPower, SigningKeypair, NoSigningPower, NoEncryptingPower, \ + EncryptingKeypair + +""" +SIGNING +""" def test_actor_without_signing_power_cannot_sign(): @@ -68,6 +74,11 @@ def test_anybody_can_verify(): assert verification is True +""" +ENCRYPTION +""" + + def test_signing_only_power_cannot_encrypt(): """ Similar to the above with signing, here we show that a Character without the EncryptingKeypair @@ -79,15 +90,30 @@ def test_signing_only_power_cannot_encrypt(): # ..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) + + +def test_character_with_encrypting_power_can_encrypt(): + """ + Now, a Character *with* EncryptingKeyPair can encrypt. + """ + can_sign_and_encrypt = Character(crypto_power_ups=[SigningKeypair, EncryptingKeypair]) + ursula = Ursula() + can_sign_and_encrypt.learn_about_actor(ursula) + + cleartext = b"This is Officer Rod Farva. Come in, Ursula! Come in Ursula!" + + # TODO: Make encrypt_for actually encrypt. + ciphertext, signature = can_sign_and_encrypt.encrypt_for(ursula, cleartext, sign=False) + assert signature == NOT_SIGNED + + assert ciphertext is not None # annnd fail. diff --git a/tests/crypto/test_keypairs.py b/tests/crypto/test_keypairs.py index 35d375981..e78edeba8 100644 --- a/tests/crypto/test_keypairs.py +++ b/tests/crypto/test_keypairs.py @@ -1,11 +1,10 @@ -import unittest -import sha3 -import msgpack import random +import unittest + +import sha3 from nkms.crypto import api -from nkms.crypto.keypairs import EncryptingKeypair -from nkms.crypto.powers import SigningKeypair +from nkms.crypto.powers import SigningKeypair, EncryptingKeypair class TestSigningKeypair(unittest.TestCase): @@ -20,21 +19,21 @@ class TestSigningKeypair(unittest.TestCase): sig = api.ecdsa_load_sig(signature) self.assertEqual(tuple, type(sig)) - self.assertEqual(int, type(sig[0])) # Check v - self.assertEqual(int, type(sig[1])) # Check r - self.assertEqual(int, type(sig[2])) # Check s + self.assertEqual(int, type(sig[0])) # Check v + self.assertEqual(int, type(sig[1])) # Check r + self.assertEqual(int, type(sig[2])) # Check s def test_verification(self): msg_digest = sha3.keccak_256(self.msg).digest() signature = self.keypair_a.sign(msg_digest) sig = api.ecdsa_load_sig(signature) - self.assertEqual(int, type(sig[0])) # Check v - self.assertEqual(int, type(sig[1])) # Check r - self.assertEqual(int, type(sig[2])) # Check s + self.assertEqual(int, type(sig[0])) # Check v + self.assertEqual(int, type(sig[1])) # Check r + self.assertEqual(int, type(sig[2])) # Check s verify_sig = api.ecdsa_verify(*sig, msg_digest, - self.keypair_a.pub_key) + self.keypair_a.pub_key) self.assertTrue(verify_sig) def test_digest(self):