Skip to content

Commit

Permalink
Merge pull request #241 from cygnusv/invalidtag
Browse files Browse the repository at this point in the history
1st Chimney anniversary
  • Loading branch information
cygnusv committed May 21, 2019
2 parents 17c1990 + f2598db commit 41bbdc4
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 48 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ workflows:
requires:
- mypy_type_check_36
- doctests_36
- notebook_tests_36
filters:
tags:
only: /v[0-9]+.*/
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/umbral_simple_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
fail_decrypted_data = pre.decrypt(ciphertext=ciphertext,
capsule=bob_capsule,
decrypting_key=bobs_private_key)
except:
except pre.UmbralDecryptionError:
print("Decryption failed! Bob doesn't has access granted yet.")

#8
Expand Down
2 changes: 1 addition & 1 deletion docs/notebooks/pyUmbral Simple API.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
" fail_decrypted_data = pre.decrypt(ciphertext=ciphertext, \n",
" capsule=capsule, \n",
" decrypting_key=bobs_private_key)\n",
"except:\n",
"except pre.UmbralDecryptionError:\n",
" print(\"Decryption failed! Bob doesn't has access granted yet.\")\n"
]
},
Expand Down
15 changes: 11 additions & 4 deletions docs/source/using_pyumbral.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@ Alice can open the capsule and decrypt the ciphertext with her private key.

.. doctest:: capsule_story

>>> cleartext = pre.decrypt(ciphertext=ciphertext, capsule=capsule, decrypting_key=alices_private_key)
>>> cleartext = pre.decrypt(ciphertext=ciphertext,
... capsule=capsule,
... decrypting_key=alices_private_key)


Threshold Re-encryption
Threshold Re-Encryption
==================================

Bob Exists
Expand Down Expand Up @@ -140,7 +142,7 @@ or re-encrypted for him by Ursula, he will not be able to open it.
... decrypting_key=bobs_private_key)
Traceback (most recent call last):
...
cryptography.exceptions.InvalidTag
umbral.pre.UmbralDecryptionError


Ursulas perform re-encryption
Expand Down Expand Up @@ -178,6 +180,9 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
>>> assert len(cfrags) == 10


Decryption
==================================

Bob attaches cfrags to the capsule
----------------------------------
Bob attaches at least ``threshold`` cfrags to the capsule,
Expand All @@ -201,7 +206,9 @@ Finally, Bob decrypts the re-encrypted ciphertext using the activated capsule.

.. doctest:: capsule_story

>>> cleartext = pre.decrypt(ciphertext=ciphertext, capsule=capsule, decrypting_key=bobs_private_key)
>>> cleartext = pre.decrypt(ciphertext=ciphertext,
... capsule=capsule,
... decrypting_key=bobs_private_key)

.. doctest:: capsule_story
:hide:
Expand Down
45 changes: 32 additions & 13 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@

set_default_curve(SECP256K1)

MockKeyPair = namedtuple('TestKeyPair', 'priv pub')

parameters = (
# (N, M)
(1, 1),
Expand All @@ -56,18 +54,19 @@
(True, True), (True, False), (False, True), (False, False)
)

@pytest.fixture(scope='function')

@pytest.fixture
def alices_keys():
delegating_priv = keys.UmbralPrivateKey.gen_key()
signing_priv = keys.UmbralPrivateKey.gen_key()
return delegating_priv, signing_priv


@pytest.fixture(scope='function')
@pytest.fixture
def bobs_keys():
priv = keys.UmbralPrivateKey.gen_key()
pub = priv.get_pubkey()
return MockKeyPair(priv, pub)
return priv, pub


@pytest.fixture()
Expand All @@ -90,24 +89,44 @@ def random_ec_curvebn2():
yield CurveBN.gen_rand()


@pytest.fixture(scope='function')
def capsule(alices_keys):


@pytest.fixture(scope='session')
def message():
message = b"dnunez [9:30 AM]" \
b"@Tux we had this super fruitful discussion last night with @jMyles @michwill @KPrasch" \
b"to sum up: the symmetric ciphertext is now called the 'Chimney'." \
b"the chimney of the capsule, of course" \
b"tux [9:32 AM]" \
b"wat"
return message


@pytest.fixture
def ciphertext_and_capsule(alices_keys, message):
delegating_privkey, _signing_privkey = alices_keys
_sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
return capsule
# See nucypher's issue #183
chimney, capsule = pre.encrypt(delegating_privkey.get_pubkey(), message)
return chimney, capsule


@pytest.fixture
def capsule(ciphertext_and_capsule):
ciphertext, capsule = ciphertext_and_capsule
return capsule


@pytest.fixture
def prepared_capsule(alices_keys, bobs_keys):
def prepared_capsule(alices_keys, bobs_keys, capsule):
delegating_privkey, signing_privkey = alices_keys
_receiving_privkey, receiving_pubkey = bobs_keys

_sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
receiving=receiving_pubkey,
verifying=signing_privkey.get_pubkey())
return capsule

@pytest.fixture(scope='function')

@pytest.fixture
def kfrags(alices_keys, bobs_keys):
delegating_privkey, signing_privkey = alices_keys
signer_alice = Signer(signing_privkey)
Expand Down
13 changes: 13 additions & 0 deletions tests/functional/test_pre_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
from umbral.signing import Signer
from ..conftest import wrong_parameters


def test_public_key_encryption(alices_keys):
delegating_privkey, _ = alices_keys
plain_data = b'peace at dawn'
ciphertext, capsule = pre.encrypt(delegating_privkey.get_pubkey(), plain_data)
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
assert cleartext == plain_data


@pytest.mark.parametrize("N, M", wrong_parameters)
def test_wrong_N_M_in_split_rekey(N, M, alices_keys, bobs_keys):
delegating_privkey, signing_privkey = alices_keys
Expand All @@ -41,3 +43,14 @@ def test_wrong_N_M_in_split_rekey(N, M, alices_keys, bobs_keys):
threshold=M,
N=N)


def test_decryption_error(alices_keys, bobs_keys, ciphertext_and_capsule, message):
delegating_privkey, _signing_privkey = alices_keys
receiving_privkey, _receiving_pubkey = bobs_keys
ciphertext, capsule = ciphertext_and_capsule

cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
assert message == cleartext

with pytest.raises(pre.UmbralDecryptionError) as e:
_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
2 changes: 2 additions & 0 deletions tests/unit/test_primitives/test_point_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def test_generate_random_points():
assert isinstance(another_point, Point)
assert point != another_point


@pytest.mark.parametrize("curve, nid, point_bytes", generate_test_points_bytes())
def test_bytes_serializers(point_bytes, nid, curve):
point_with_curve = Point.from_bytes(point_bytes, curve=curve) # from curve
Expand All @@ -76,6 +77,7 @@ def test_bytes_serializers(point_bytes, nid, curve):
with pytest.raises(InternalError):
_ = Point.from_bytes(malformed_point_bytes)


@pytest.mark.parametrize("curve, nid, point_affine", generate_test_points_affine())
def test_affine(point_affine, nid, curve):
point = Point.from_affine(point_affine, curve=curve) # from curve
Expand Down
51 changes: 29 additions & 22 deletions tests/unit/test_umbral_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import base64
import os
import pytest
import string

from umbral.config import default_params
from umbral.keys import UmbralPublicKey, UmbralPrivateKey, UmbralKeyingMaterial
Expand Down Expand Up @@ -87,37 +88,23 @@ def test_private_key_serialization_with_encryption(random_ec_curvebn1):


def test_public_key_serialization(random_ec_curvebn1):
priv_key = random_ec_curvebn1

params = default_params()
pub_key = priv_key * params.g

umbral_key = UmbralPublicKey(pub_key, params)
umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
pub_point = umbral_key.point_key

encoded_key = umbral_key.to_bytes()

decoded_key = UmbralPublicKey.from_bytes(encoded_key)
assert pub_key == decoded_key.point_key
assert pub_point == decoded_key.point_key


def test_public_key_to_compressed_bytes(random_ec_curvebn1):
priv_key = random_ec_curvebn1

params = default_params()
pub_key = priv_key * params.g

umbral_key = UmbralPublicKey(pub_key, params)
umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
key_bytes = bytes(umbral_key)
assert len(key_bytes) == Point.expected_bytes_length(is_compressed=True)


def test_public_key_to_uncompressed_bytes(random_ec_curvebn1):
priv_key = random_ec_curvebn1

params = default_params()
pub_key = priv_key * params.g

umbral_key = UmbralPublicKey(pub_key, params)
umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
key_bytes = umbral_key.to_bytes(is_compressed=False)
assert len(key_bytes) == Point.expected_bytes_length(is_compressed=False)

Expand All @@ -133,6 +120,26 @@ def test_key_encoder_decoder(random_ec_curvebn1):
assert decoded_key.to_bytes() == umbral_key.to_bytes()


def test_public_key_as_hex(random_ec_curvebn1):
pubkey = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
hex_string = pubkey.hex()

assert set(hex_string).issubset(set(string.hexdigits))
assert len(hex_string) == 2 * UmbralPublicKey.expected_bytes_length()

decoded_pubkey = UmbralPublicKey.from_hex(hex_string)

assert pubkey == decoded_pubkey

hex_string = pubkey.hex(is_compressed=False)

assert set(hex_string).issubset(set(string.hexdigits))
assert len(hex_string) == 2 * UmbralPublicKey.expected_bytes_length(is_compressed=False)

decoded_pubkey = UmbralPublicKey.from_hex(hex_string)
assert pubkey == decoded_pubkey


def test_umbral_key_to_cryptography_keys():
umbral_priv_key = UmbralPrivateKey.gen_key()
umbral_pub_key = umbral_priv_key.get_pubkey()
Expand Down Expand Up @@ -163,11 +170,11 @@ def test_keying_material_serialization_with_encryption():

insecure_cost = 15 # This is deliberately insecure, just to make the tests faster
encoded_keying_material = umbral_keying_material.to_bytes(password=b'test',
_scrypt_cost=insecure_cost)
_scrypt_cost=insecure_cost)

decoded_keying_material = UmbralKeyingMaterial.from_bytes(encoded_keying_material,
password=b'test',
_scrypt_cost=insecure_cost)
password=b'test',
_scrypt_cost=insecure_cost)

label = os.urandom(32)
privkey_bytes = umbral_keying_material.derive_privkey_by_label(label).to_bytes()
Expand Down
10 changes: 10 additions & 0 deletions umbral/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,16 @@ def to_bytes(self, encoder: Callable = None, is_compressed: bool = True) -> byte

return umbral_pubkey

def hex(self, is_compressed: bool = True) -> str:
"""
Returns an Umbral public key as hex string.
"""
return self.to_bytes(is_compressed=is_compressed).hex()

@classmethod
def from_hex(cls, hex_string) -> 'UmbralPublicKey':
return cls.from_bytes(key_bytes=hex_string, decoder=bytes.fromhex)

def to_cryptography_pubkey(self) -> _EllipticCurvePublicKey:
"""
Returns a cryptography.io EllipticCurvePublicKey from the Umbral key.
Expand Down
26 changes: 19 additions & 7 deletions umbral/pre.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@
from typing import Dict, List, Optional, Tuple, Union, Any

from bytestring_splitter import BytestringSplitter
from cryptography.exceptions import InvalidTag
from constant_sorrow import constants

from umbral.cfrags import CapsuleFrag
from umbral.config import default_curve
from umbral.curve import Curve
from umbral.curvebn import CurveBN
from umbral.dem import UmbralDEM, DEM_KEYSIZE, DEM_NONCE_SIZE
from umbral.cfrags import CapsuleFrag
from umbral.kfrags import KFrag, NO_KEY, DELEGATING_ONLY, RECEIVING_ONLY, DELEGATING_AND_RECEIVING
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
from umbral.kfrags import KFrag, NO_KEY, DELEGATING_ONLY, RECEIVING_ONLY, DELEGATING_AND_RECEIVING
from umbral.params import UmbralParameters
from umbral.point import Point
from umbral.random_oracles import kdf, hash_to_curvebn
from umbral.signing import Signer
from umbral.curve import Curve
from umbral.utils import poly_eval, lambda_coeff
from umbral.random_oracles import kdf, hash_to_curvebn

from constant_sorrow import constants


class GenericUmbralError(Exception):
Expand All @@ -46,6 +47,13 @@ def __init__(self, message: str, offending_cfrags: List[CapsuleFrag]) -> None:
self.offending_cfrags = offending_cfrags


class UmbralDecryptionError(GenericUmbralError):
def __init__(self) -> None:
super().__init__("Decryption of ciphertext failed: "
"either someone tampered with the ciphertext or "
"you are using an incorrect decryption key.")


class Capsule:
def __init__(self,
params: UmbralParameters,
Expand Down Expand Up @@ -489,5 +497,9 @@ def decrypt(ciphertext: bytes, capsule: Capsule, decrypting_key: UmbralPrivateKe
encapsulated_key = _decapsulate_original(decrypting_key, capsule)

dem = UmbralDEM(encapsulated_key)
cleartext = dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
try:
cleartext = dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
except InvalidTag as e:
raise UmbralDecryptionError() from e

return cleartext

0 comments on commit 41bbdc4

Please sign in to comment.