Merge pull request #157 from jMyles/cfrags-with-correctness-proof

Attaching CFrags to a Capsule now requires correctness check and KFrag signature validation.
pull/160/head
Justin Holmes 2018-05-31 00:44:45 -04:00 committed by GitHub
commit 0ae4210367
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 215 additions and 33 deletions

View File

@ -0,0 +1,104 @@
import os
import pytest
from umbral import pre
from umbral.curvebn import CurveBN
from umbral.fragments import CapsuleFrag
from umbral.keys import UmbralPrivateKey
from umbral.point import Point
from umbral.pre import Capsule
from umbral.signing import Signer
def test_cannot_attach_cfrag_without_keys():
"""
We need the proper keys to verify the correctness of CFrags
in order to attach them to a Capsule.
"""
capsule = Capsule(point_e=Point.gen_rand(),
point_v=Point.gen_rand(),
bn_sig=CurveBN.gen_rand())
cfrag = CapsuleFrag(point_e1=Point.gen_rand(),
point_v1=Point.gen_rand(),
kfrag_id=os.urandom(10),
point_noninteractive=Point.gen_rand(),
point_xcoord=Point.gen_rand(),
)
with pytest.raises(TypeError):
capsule.attach_cfrag(cfrag)
def test_set_correctness_keys(alices_keys, bobs_keys):
"""
If the three keys do appear together, along with the capsule,
we can attach them all at once.
"""
delegating_privkey, signing_privkey = alices_keys
signer = Signer(signing_privkey)
priv_key_bob, pub_key_bob = bobs_keys
plain_data = b'peace at dawn'
ciphertext, capsule = pre.encrypt(delegating_privkey.get_pubkey(), plain_data)
capsule.set_correctness_keys(delegating_privkey.get_pubkey(),
pub_key_bob,
signing_privkey.get_pubkey()
)
kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, 2, 2)
for kfrag in kfrags:
cfrag = pre.reencrypt(kfrag, capsule)
capsule.attach_cfrag(cfrag)
def test_cannot_attach_cfrag_without_proof():
"""
However, even when properly attaching keys, we can't attach the CFrag
if it is unproven.
"""
capsule = Capsule(point_e=Point.gen_rand(),
point_v=Point.gen_rand(),
bn_sig=CurveBN.gen_rand())
cfrag = CapsuleFrag(point_e1=Point.gen_rand(),
point_v1=Point.gen_rand(),
kfrag_id=os.urandom(10),
point_noninteractive=Point.gen_rand(),
point_xcoord=Point.gen_rand(),
)
key_details = capsule.set_correctness_keys(
UmbralPrivateKey.gen_key().get_pubkey(),
UmbralPrivateKey.gen_key().get_pubkey(),
UmbralPrivateKey.gen_key().get_pubkey())
delegating_details, encrypting_details, verifying_details = key_details
assert all((delegating_details, encrypting_details, verifying_details))
with pytest.raises(cfrag.NoProofProvided):
capsule.attach_cfrag(cfrag)
def test_cannot_set_different_keys():
"""
Once a key is set on a Capsule, it can't be changed to a different key.
"""
capsule = Capsule(point_e=Point.gen_rand(),
point_v=Point.gen_rand(),
bn_sig=CurveBN.gen_rand())
capsule.set_correctness_keys(delegating=UmbralPrivateKey.gen_key().get_pubkey(),
encrypting=UmbralPrivateKey.gen_key().get_pubkey(),
verifying=UmbralPrivateKey.gen_key().get_pubkey())
with pytest.raises(ValueError):
capsule.set_correctness_keys(delegating=UmbralPrivateKey.gen_key().get_pubkey())
with pytest.raises(ValueError):
capsule.set_correctness_keys(encrypting=UmbralPrivateKey.gen_key().get_pubkey())
with pytest.raises(ValueError):
capsule.set_correctness_keys(verifying=UmbralPrivateKey.gen_key().get_pubkey())

View File

@ -1,6 +1,6 @@
import pytest
from umbral import pre, keys
from umbral import pre
from umbral.curvebn import CurveBN
from umbral.point import Point
from umbral.pre import Capsule
@ -73,15 +73,20 @@ def test_capsule_as_dict_key(alices_keys):
# TODO: This test is a little weird - why activate a Capsule from alice to alice? Let's get bob involved.
delegating_privkey, signing_privkey = alices_keys
signer_alice = Signer(signing_privkey)
encrypting_key = delegating_privkey.get_pubkey()
plain_data = b'peace at dawn'
ciphertext, capsule = pre.encrypt(delegating_privkey.get_pubkey(), plain_data)
ciphertext, capsule = pre.encrypt(encrypting_key, plain_data)
capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
encrypting=encrypting_key,
verifying=signing_privkey.get_pubkey())
# We can use the capsule as a key, and successfully lookup using it.
some_dict = {capsule: "Thing that Bob wants to try per-Capsule"}
assert some_dict[capsule] == "Thing that Bob wants to try per-Capsule"
kfrags = pre.split_rekey(delegating_privkey, signer_alice, delegating_privkey.get_pubkey(), 1, 2)
kfrags = pre.split_rekey(delegating_privkey, signer_alice, encrypting_key , 1, 2)
cfrag = pre.reencrypt(kfrags[0], capsule)
capsule.attach_cfrag(cfrag)

View File

@ -41,6 +41,11 @@ def test_activated_capsule_serialization(alices_keys, bobs_keys):
priv_key_bob, pub_key_bob = bobs_keys
_unused_key, capsule = pre._encapsulate(pub_key_bob.point_key)
capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
encrypting=pub_key_bob,
verifying=signing_privkey.get_pubkey())
kfrags = pre.split_rekey(delegating_privkey, signer_alice, pub_key_bob, 1, 2)
cfrag = pre.reencrypt(kfrags[0], capsule)

View File

@ -21,9 +21,6 @@ def test_correctness_proof_serialization(alices_keys):
metadata = b"This is an example of metadata for re-encryption request"
cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata)
capsule.attach_cfrag(cfrag)
proof = cfrag.proof
proof_bytes = proof.to_bytes()
@ -53,6 +50,10 @@ def test_cheating_ursula_replays_old_reencryption(N, M, alices_keys):
sym_key_alice1, capsule_alice1 = pre._encapsulate(delegating_privkey.get_pubkey().point_key)
sym_key_alice2, capsule_alice2 = pre._encapsulate(delegating_privkey.get_pubkey().point_key)
capsule_alice1.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
encrypting=pub_key_bob,
verifying=signing_privkey.get_pubkey())
kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N)
cfrags, metadata = [], []
@ -125,6 +126,11 @@ def test_cheating_ursula_sends_garbage(N, M, alices_keys):
pub_key_bob = priv_key_bob.get_pubkey()
sym_key, capsule_alice = pre._encapsulate(delegating_privkey.get_pubkey().point_key)
capsule_alice.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
encrypting=pub_key_bob,
verifying=signing_privkey.get_pubkey())
kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N)
cfrags, metadata = [], []
@ -169,7 +175,7 @@ def test_cheating_ursula_sends_garbage(N, M, alices_keys):
# We should get an exception with an attached list of incorrect cfrags
with pytest.raises(pre.UmbralCorrectnessError) as exception_info:
_decapsulated_key = pre._open_capsule(capsule_alice, priv_key_bob, delegating_privkey.get_pubkey(),
signing_privkey.get_pubkey())
signing_privkey.get_pubkey())
correctness_error = exception_info.value
assert cfrags[0] in correctness_error.offending_cfrags
assert len(correctness_error.offending_cfrags) == 1
@ -186,15 +192,24 @@ def test_decryption_fails_when_it_expects_a_proof_and_there_isnt(N, M, alices_ke
plain_data = b'peace at dawn'
ciphertext, capsule = pre.encrypt(delegating_privkey.get_pubkey(), plain_data)
capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
encrypting=pub_key_bob,
verifying=signing_privkey.get_pubkey())
kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N)
for kfrag in kfrags:
cfrag = pre.reencrypt(kfrag, capsule, provide_proof=False)
cfrag = pre.reencrypt(kfrag, capsule)
capsule.attach_cfrag(cfrag)
# Even thought we can successfully attach a CFrag, if the proof is lost
# (for example, it is chopped off a serialized CFrag or similar), then decrypt
# will still fail.
cfrag.proof = None
with pytest.raises(cfrag.NoProofProvided):
_cleartext = pre.decrypt(ciphertext, capsule, priv_key_bob,
delegating_privkey.get_pubkey(),
signing_privkey.get_pubkey())
delegating_privkey.get_pubkey(),
signing_privkey.get_pubkey())
@pytest.mark.parametrize("N, M", parameters)
@ -205,6 +220,11 @@ def test_m_of_n(N, M, alices_keys, bobs_keys):
priv_key_bob, pub_key_bob = bobs_keys
sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey().point_key)
capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
encrypting=pub_key_bob,
verifying=signing_privkey.get_pubkey())
kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N)
for kfrag in kfrags:
@ -222,5 +242,6 @@ def test_m_of_n(N, M, alices_keys, bobs_keys):
delegating_privkey.get_pubkey(), signing_privkey.get_pubkey(), pub_key_bob,
)
sym_key_from_capsule = pre._open_capsule(capsule, priv_key_bob, delegating_privkey.get_pubkey(), signing_privkey.get_pubkey())
sym_key_from_capsule = pre._open_capsule(capsule, priv_key_bob, delegating_privkey.get_pubkey(),
signing_privkey.get_pubkey())
assert sym_key == sym_key_from_capsule

View File

@ -27,6 +27,10 @@ def test_simple_api(alices_keys, N, M, curve=default_curve()):
plain_data = b'peace at dawn'
ciphertext, capsule = pre.encrypt(delegating_privkey.get_pubkey(), plain_data, params=params)
capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
encrypting=decrypting_key.get_pubkey(),
verifying=signing_privkey.get_pubkey())
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey, params=params)
assert cleartext == plain_data

View File

@ -42,7 +42,7 @@ def prove_cfrag_correctness(cfrag: "CapsuleFrag",
# Check correctness of original ciphertext (check nº 2) at the end
# to avoid timing oracles
if not capsule.verify(params):
if not capsule.verify():
raise capsule.NotValid("Capsule verification failed.")

View File

@ -225,6 +225,9 @@ class CapsuleFrag(object):
signing_pubkey: UmbralPublicKey,
encrypting_pubkey: UmbralPublicKey,
params: UmbralParameters = None):
if not all((delegating_pubkey, signing_pubkey, encrypting_pubkey)):
raise TypeError("Need all three keys to verify correctness.")
pubkey_a_point = delegating_pubkey.point_key
pubkey_b_point = encrypting_pubkey.point_key

View File

@ -5,7 +5,7 @@ from cryptography.hazmat.backends.openssl import backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from umbral._pre import prove_cfrag_correctness
from umbral._pre import prove_cfrag_correctness, assess_cfrag_correctness
from umbral.curvebn import CurveBN
from umbral.config import default_params, default_curve
from umbral.dem import UmbralDEM
@ -37,7 +37,13 @@ class Capsule(object):
bn_sig=None,
point_e_prime=None,
point_v_prime=None,
point_noninteractive=None):
point_noninteractive=None,
delegating_pubkey: UmbralPublicKey = None,
encrypting_pubkey: UmbralPublicKey = None,
verifying_pubkey: UmbralPublicKey = None,
params: UmbralParameters = None):
self._umbral_params = params if params is not None else default_params()
if isinstance(point_e, Point):
if not isinstance(point_v, Point) or not isinstance(bn_sig, CurveBN):
@ -50,6 +56,10 @@ class Capsule(object):
"Need proper Points and/or CurveBNs to make a Capsule. Pass either Alice's data or Bob's. " \
"Passing both is also fine.")
self._cfrag_correctness_keys = {"delegating": delegating_pubkey,
"encrypting": encrypting_pubkey,
"verifying": verifying_pubkey}
self._point_e = point_e
self._point_v = point_v
self._bn_sig = bn_sig
@ -108,6 +118,31 @@ class Capsule(object):
components = splitter(capsule_bytes)
return cls(*components)
def _set_cfrag_correctness_key(self, key_type, key: UmbralPublicKey):
current_key = self._cfrag_correctness_keys[key_type]
if current_key is None:
if key is None:
raise TypeError("The Delegating Key is not set and you didn't pass one.")
else:
self._cfrag_correctness_keys[key_type] = key
return True
elif key in (None, self._cfrag_correctness_keys[key_type]):
return False
else:
raise ValueError("The Delegating Key is already set; you can't set it again.")
def set_correctness_keys(self,
delegating: UmbralPublicKey = None,
encrypting: UmbralPublicKey = None,
verifying: UmbralPublicKey = None
):
delegating_key_details = self._set_cfrag_correctness_key("delegating", delegating)
encrypting_key_details = self._set_cfrag_correctness_key("encrypting", encrypting)
verifying_key_details = self._set_cfrag_correctness_key("verifying", verifying)
return delegating_key_details, encrypting_key_details, verifying_key_details
def _original_to_bytes(self) -> bytes:
return bytes().join(c.to_bytes() for c in self.original_components())
@ -120,19 +155,27 @@ class Capsule(object):
bytes_representation += bytes().join(c.to_bytes() for c in self.activated_components())
return bytes_representation
def verify(self, params: UmbralParameters = None) -> bool:
params = params if params is not None else default_params()
def verify(self) -> bool:
e = self._point_e
v = self._point_v
s = self._bn_sig
h = CurveBN.hash(e, v, params=params)
h = CurveBN.hash(e, v, params=self._umbral_params)
return s * params.g == v + (h * e)
return s * self._umbral_params.g == v + (h * e)
def attach_cfrag(self, cfrag: CapsuleFrag) -> None:
self.verify_cfrag(cfrag)
self._attached_cfrags.append(cfrag)
def verify_cfrag(self, cfrag):
return cfrag.verify_correctness(self,
self._cfrag_correctness_keys["delegating"],
self._cfrag_correctness_keys["encrypting"],
self._cfrag_correctness_keys["verifying"],
self._umbral_params
)
def original_components(self) -> Tuple[Point, Point, CurveBN]:
return self._point_e, self._point_v, self._bn_sig
@ -140,12 +183,9 @@ class Capsule(object):
return self._point_e_prime, self._point_v_prime, self._point_noninteractive
def _reconstruct_shamirs_secret(self,
priv_b: Union[UmbralPrivateKey, CurveBN],
params: UmbralParameters = None) -> None:
params = params if params is not None else default_params()
g = params.g
priv_b: Union[UmbralPrivateKey, CurveBN]
) -> None:
g = self._umbral_params.g
if isinstance(priv_b, UmbralPrivateKey):
pub_b = priv_b.get_pubkey()
@ -167,9 +207,9 @@ class Capsule(object):
hashed_dh_tuple = blake2b.finalize()
if len(self._attached_cfrags) > 1:
xs = [CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=params)
xs = [CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=self._umbral_params)
for cfrag in self._attached_cfrags]
x_0 = CurveBN.hash(id_0, hashed_dh_tuple, params=params)
x_0 = CurveBN.hash(id_0, hashed_dh_tuple, params=self._umbral_params)
lambda_0 = lambda_coeff(x_0, xs)
e = lambda_0 * cfrag_0._point_e1
v = lambda_0 * cfrag_0._point_v1
@ -178,7 +218,7 @@ class Capsule(object):
if (ni, xcoord) != (cfrag._point_noninteractive, cfrag._point_xcoord):
raise ValueError("Attached CFrags are not pairwise consistent")
x_i = CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=params)
x_i = CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=self._umbral_params)
lambda_i = lambda_coeff(x_i, xs)
e = e + (lambda_i * cfrag._point_e1)
v = v + (lambda_i * cfrag._point_v1)
@ -230,7 +270,7 @@ def split_rekey(privkey_a_bn: Union[UmbralPrivateKey, CurveBN],
params: UmbralParameters = None) -> List[KFrag]:
"""
Creates a re-encryption key from Alice to Bob and splits it in KFrags,
using Shamir's Secret Sharing. Requires a threshold number of KFrags
using Shamir's Secret Sharing. Requires a threshold number of KFrags
out of N to guarantee correctness of re-encryption.
Returns a list of KFrags.
@ -262,7 +302,7 @@ def split_rekey(privkey_a_bn: Union[UmbralPrivateKey, CurveBN],
# 'xcoord' stands for 'X coordinate'.
# This point is used as an ephemeral public key in a DH key exchange,
# and the resulting shared secret 'dh_xcoord' contributes to prevent
# and the resulting shared secret 'dh_xcoord' contributes to prevent
# reconstruction of the re-encryption key without Bob's intervention
priv_xcoord = CurveBN.gen_rand(params.curve)
xcoord = priv_xcoord * g
@ -304,7 +344,7 @@ def reencrypt(kfrag: KFrag, capsule: Capsule, params: UmbralParameters = None,
if params is None:
params = default_params()
if not capsule.verify(params):
if not capsule.verify():
raise capsule.NotValid
rk = kfrag._bn_key
@ -342,7 +382,7 @@ def _encapsulate(alice_pub_key: Point, key_length=32,
# Key to be used for symmetric encryption
key = kdf(shared_key, key_length)
return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s)
return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s, params=params)
def _decapsulate_original(priv_key: CurveBN, capsule: Capsule, key_length=32,
@ -353,7 +393,7 @@ def _decapsulate_original(priv_key: CurveBN, capsule: Capsule, key_length=32,
shared_key = priv_key * (capsule._point_e + capsule._point_v)
key = kdf(shared_key, key_length)
if not capsule.verify(params):
if not capsule.verify():
# Check correctness of original ciphertext
# (check nº 2) at the end to avoid timing oracles
raise capsule.NotValid("Capsule verification failed.")
@ -439,7 +479,7 @@ def _open_capsule(capsule: Capsule,
error_msg = "Decryption error: Some CFrags are not correct"
raise UmbralCorrectnessError(error_msg, offending_cfrags)
capsule._reconstruct_shamirs_secret(priv_b, params=params)
capsule._reconstruct_shamirs_secret(priv_b)
key = _decapsulate_reencrypted(bob_pubkey.point_key, priv_b, delegating_pubkey.point_key, capsule, params=params)
return key