Clean slate

pull/263/head
Bogdan Opanchuk 2021-03-15 19:44:24 -07:00
parent 2ef43a6df5
commit bbf168e08b
45 changed files with 0 additions and 5780 deletions

View File

@ -1,16 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""

View File

@ -1,139 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from collections import namedtuple
from umbral import keys
from umbral.curve import SECP256K1, SECP384R1, SECP256R1
from umbral.curvebn import CurveBN
from umbral.config import set_default_curve
from umbral.point import Point
from umbral.signing import Signer
from umbral import pre
set_default_curve(SECP256K1)
parameters = (
# (N, M)
(1, 1),
(6, 1),
(6, 4),
(6, 6),
(50, 30)
)
wrong_parameters = (
# (N, M)
(-1, -1), (-1, 0), (-1, 5),
(0, -1), (0, 0), (0, 5),
(1, -1), (1, 0), (1, 5),
(5, -1), (5, 0), (5, 10)
)
other_supported_curves = (
SECP384R1,
SECP256R1
)
kfrag_signing_modes = (
(True, True), (True, False), (False, True), (False, False)
)
@pytest.fixture
def alices_keys():
delegating_priv = keys.UmbralPrivateKey.gen_key()
signing_priv = keys.UmbralPrivateKey.gen_key()
return delegating_priv, signing_priv
@pytest.fixture
def bobs_keys():
priv = keys.UmbralPrivateKey.gen_key()
pub = priv.get_pubkey()
return priv, pub
@pytest.fixture()
def random_ec_point1():
yield Point.gen_rand()
@pytest.fixture()
def random_ec_point2():
yield Point.gen_rand()
@pytest.fixture()
def random_ec_curvebn1():
yield CurveBN.gen_rand()
@pytest.fixture()
def random_ec_curvebn2():
yield CurveBN.gen_rand()
@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
# 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, capsule):
delegating_privkey, signing_privkey = alices_keys
_receiving_privkey, receiving_pubkey = bobs_keys
capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
receiving=receiving_pubkey,
verifying=signing_privkey.get_pubkey())
return capsule
@pytest.fixture
def kfrags(alices_keys, bobs_keys):
delegating_privkey, signing_privkey = alices_keys
signer_alice = Signer(signing_privkey)
receiving_privkey, receiving_pubkey = bobs_keys
yield pre.generate_kfrags(delegating_privkey=delegating_privkey,
signer=signer_alice,
receiving_pubkey=receiving_pubkey,
threshold=6, N=10)

View File

@ -1,16 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""

View File

@ -1,156 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral import pre
from umbral.point import Point
from umbral.signing import Signer
from umbral.cfrags import CapsuleFrag
def test_cheating_ursula_replays_old_reencryption(alices_keys, bobs_keys,
kfrags, prepared_capsule):
delegating_privkey, signing_privkey = alices_keys
delegating_pubkey = delegating_privkey.get_pubkey()
receiving_privkey, receiving_pubkey = bobs_keys
capsule_alice1 = prepared_capsule
_unused_key2, capsule_alice2 = pre._encapsulate(delegating_pubkey)
capsule_alice2.set_correctness_keys(delegating=delegating_pubkey,
receiving=receiving_pubkey,
verifying=signing_privkey.get_pubkey())
cfrags = []
for i, kfrag in enumerate(kfrags):
# Example of potential metadata to describe the re-encryption request
metadata_i = "This is an example of metadata for re-encryption request #{}"
metadata_i = metadata_i.format(i).encode()
if i == 0:
# Let's put the re-encryption of a different Alice ciphertext
cfrag = pre.reencrypt(kfrag, capsule_alice2, metadata=metadata_i)
else:
cfrag = pre.reencrypt(kfrag, capsule_alice1, metadata=metadata_i)
cfrags.append(cfrag)
#  CFrag 0 is not valid ...
assert not cfrags[0].verify_correctness(capsule_alice1)
# ... and trying to attach it raises an error.
with pytest.raises(pre.UmbralCorrectnessError) as exception_info:
capsule_alice1.attach_cfrag(cfrags[0])
correctness_error = exception_info.value
assert cfrags[0] in correctness_error.offending_cfrags
assert len(correctness_error.offending_cfrags) == 1
# The rest of CFrags should be correct:
correct_cases = 0
for cfrag_i in cfrags[1:]:
assert cfrag_i.verify_correctness(capsule_alice1)
capsule_alice1.attach_cfrag(cfrag_i)
correct_cases += 1
assert correct_cases == len(cfrags[1:])
def test_cheating_ursula_sends_garbage(kfrags, prepared_capsule):
capsule_alice = prepared_capsule
cfrags = []
for i, kfrag in enumerate(kfrags):
# Example of potential metadata to describe the re-encryption request
metadata_i = "This is an example of metadata for re-encryption request #{}"
metadata_i = metadata_i.format(i).encode()
cfrag = pre.reencrypt(kfrag, capsule_alice, metadata=metadata_i)
cfrags.append(cfrag)
# Let's put random garbage in one of the cfrags
cfrags[0].point_e1 = Point.gen_rand()
cfrags[0].point_v1 = Point.gen_rand()
#  Of course, this CFrag is not valid ...
assert not cfrags[0].verify_correctness(capsule_alice)
# ... and trying to attach it raises an error.
with pytest.raises(pre.UmbralCorrectnessError) as exception_info:
capsule_alice.attach_cfrag(cfrags[0])
correctness_error = exception_info.value
assert cfrags[0] in correctness_error.offending_cfrags
assert len(correctness_error.offending_cfrags) == 1
# The response of cheating Ursula is in cfrags[0],
# so the rest of CFrags should be correct:
for cfrag_i in cfrags[1:]:
assert cfrag_i.verify_correctness(capsule_alice)
capsule_alice.attach_cfrag(cfrag_i)
def test_cfrag_with_missing_proof_cannot_be_attached(kfrags, prepared_capsule):
capsule = prepared_capsule
cfrags = []
for kfrag in kfrags:
cfrag = pre.reencrypt(kfrag, capsule)
cfrags.append(cfrag)
# If the proof is lost (e.g., it is chopped off a serialized CFrag or similar),
#  then the CFrag cannot be attached.
cfrags[0].proof = None
with pytest.raises(CapsuleFrag.NoProofProvided):
capsule.attach_cfrag(cfrags[0])
# The remaining CFrags are fine, so they can be attached correctly
for cfrag in cfrags[1:]:
capsule.attach_cfrag(cfrag)
def test_kfrags_signed_without_correctness_keys(alices_keys, bobs_keys, capsule):
delegating_privkey, signing_privkey = alices_keys
delegating_pubkey = delegating_privkey.get_pubkey()
verifying_key = signing_privkey.get_pubkey()
receiving_privkey, receiving_pubkey = bobs_keys
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
signer=Signer(signing_privkey),
receiving_pubkey=receiving_pubkey,
threshold=6,
N=10,
sign_delegating_key=False,
sign_receiving_key=False)
for kfrag in kfrags:
# You can verify the KFrag specifying only the verifying key
assert kfrag.verify(signing_pubkey=verifying_key)
# ... or if it is set in the capsule, using the capsule
capsule.set_correctness_keys(verifying=verifying_key)
assert kfrag.verify_for_capsule(capsule)
# It should even work when other keys are set in the capsule
assert kfrag.verify(signing_pubkey=verifying_key,
delegating_pubkey=delegating_pubkey,
receiving_pubkey=receiving_pubkey)

View File

@ -1,81 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral import pre
from umbral.keys import UmbralPrivateKey
from umbral.kfrags import KFrag
def test_set_correctness_keys(alices_keys, bobs_keys, capsule, kfrags):
"""
If the three keys do appear together, along with the capsule,
we can attach them all at once.
"""
delegating_privkey, signing_privkey = alices_keys
_receiving_privkey, receiving_pubkey = bobs_keys
capsule.set_correctness_keys(delegating_privkey.get_pubkey(),
receiving_pubkey,
signing_privkey.get_pubkey()
)
for kfrag in kfrags:
cfrag = pre.reencrypt(kfrag, capsule)
capsule.attach_cfrag(cfrag)
def test_setting_one_correctness_keys(alices_keys, capsule):
# The capsule doesn't have any correctness keys set initially
assert capsule.get_correctness_keys()['delegating'] is None
assert capsule.get_correctness_keys()['receiving'] is None
assert capsule.get_correctness_keys()['verifying'] is None
# Let's set only one of them, e.g., the delegating key
delegating_privkey, _signing_privkey = alices_keys
delegating_pubkey = delegating_privkey.get_pubkey()
details = capsule.set_correctness_keys(delegating=delegating_pubkey)
# Since we are only setting the first key ("delegating"),
# the other keys are not set
assert details == (True, False, False)
assert capsule.get_correctness_keys()['delegating'] == delegating_pubkey
assert capsule.get_correctness_keys()['receiving'] is None
assert capsule.get_correctness_keys()['verifying'] is None
def test_set_invalid_correctness_keys(alices_keys, capsule, kfrags):
"""
If the three keys do appear together, along with the capsule,
we can attach them all at once.
"""
delegating_privkey, signing_privkey = alices_keys
unrelated_receiving_pubkey = UmbralPrivateKey.gen_key().get_pubkey()
capsule.set_correctness_keys(delegating_privkey.get_pubkey(),
unrelated_receiving_pubkey,
signing_privkey.get_pubkey()
)
for kfrag in kfrags:
with pytest.raises(KFrag.NotValid):
cfrag = pre.reencrypt(kfrag, capsule)

View File

@ -1,56 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral import pre
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
signer = Signer(signing_privkey)
_receiving_privkey, receiving_pubkey = bobs_keys
with pytest.raises(ValueError):
_kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
signer=signer,
receiving_pubkey=receiving_pubkey,
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)

View File

@ -1,190 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import json
import os
from umbral.curvebn import CurveBN
from umbral.point import Point
from umbral.keys import UmbralPublicKey
from umbral.config import default_params
from umbral.kfrags import KFrag
from umbral.cfrags import CapsuleFrag
from umbral.random_oracles import hash_to_curvebn, unsafe_hash_to_point, kdf
from umbral import pre
def test_curvebn_operations():
vector_file = os.path.join('vectors', 'vectors_curvebn_operations.json')
try:
with open(vector_file) as f:
vector_suite = json.load(f)
except OSError:
raise
bn1 = CurveBN.from_bytes(bytes.fromhex(vector_suite['first operand']))
bn2 = CurveBN.from_bytes(bytes.fromhex(vector_suite['second operand']))
expected = dict()
for op_result in vector_suite['vectors']:
result = bytes.fromhex(op_result['result'])
expected[op_result['operation']] = CurveBN.from_bytes(result)
test = [('Addition', bn1 + bn2),
('Subtraction', bn1 - bn2),
('Multiplication', bn1 * bn2),
('Division', bn1 / bn2),
('Pow', bn1 ** bn2),
('Mod', bn1 % bn2),
('Inverse', ~bn1),
('Neg', -bn1),
]
for (operation, result) in test:
assert result == expected[operation], 'Error in {}'.format(operation)
def test_curvebn_hash():
vector_file = os.path.join('vectors', 'vectors_curvebn_hash.json')
try:
with open(vector_file) as f:
vector_suite = json.load(f)
except OSError:
raise
params = default_params()
for vector in vector_suite['vectors']:
hash_input = [bytes.fromhex(item['bytes']) for item in vector['input']]
expected = CurveBN.from_bytes(bytes.fromhex(vector['output']))
assert hash_to_curvebn(*hash_input, params=params) == expected
def test_point_operations():
vector_file = os.path.join('vectors', 'vectors_point_operations.json')
try:
with open(vector_file) as f:
vector_suite = json.load(f)
except OSError:
raise
point1 = Point.from_bytes(bytes.fromhex(vector_suite['first Point operand']))
point2 = Point.from_bytes(bytes.fromhex(vector_suite['second Point operand']))
bn1 = CurveBN.from_bytes(bytes.fromhex(vector_suite['CurveBN operand']))
expected = dict()
for op_result in vector_suite['vectors']:
expected[op_result['operation']] = bytes.fromhex(op_result['result'])
test = [('Addition', point1 + point2),
('Subtraction', point1 - point2),
('Multiplication', bn1 * point1),
('Inversion', -point1),
]
for (operation, result) in test:
assert result == Point.from_bytes(expected[operation]), 'Error in {}'.format(operation)
test = [('To_affine.X', point1.to_affine()[0]),
('To_affine.Y', point1.to_affine()[1]),
]
for (operation, result) in test:
assert result == int.from_bytes(expected[operation], 'big'), 'Error in {}'.format(operation)
assert kdf(point1, pre.DEM_KEYSIZE) == expected['kdf']
def test_unsafe_hash_to_point():
vector_file = os.path.join('vectors', 'vectors_unsafe_hash_to_point.json')
try:
with open(vector_file) as f:
vector_suite = json.load(f)
except OSError:
raise
params = default_params()
for item in vector_suite['vectors']:
data = bytes.fromhex(item['data'])
label = bytes.fromhex(item['label'])
expected = Point.from_bytes(bytes.fromhex(item['point']))
assert expected == unsafe_hash_to_point(label=label, data=data, params=params)
def test_kfrags():
vector_file = os.path.join('vectors', 'vectors_kfrags.json')
try:
with open(vector_file) as f:
vector_suite = json.load(f)
except OSError:
raise
verifying_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_key']))
delegating_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_key']))
receiving_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_key']))
for json_kfrag in vector_suite['vectors']:
kfrag = KFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag']))
assert kfrag.verify(signing_pubkey=verifying_key,
delegating_pubkey=delegating_key,
receiving_pubkey=receiving_key), \
'Invalid KFrag {}'.format(kfrag.to_bytes().hex())
def test_cfrags():
vector_file = os.path.join('vectors', 'vectors_cfrags.json')
try:
with open(vector_file) as f:
vector_suite = json.load(f)
except OSError:
raise
params = default_params()
capsule = pre.Capsule.from_bytes(bytes.fromhex(vector_suite['capsule']),
params=params)
verifying_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_key']))
delegating_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_key']))
receiving_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_key']))
kfrags_n_cfrags = [(KFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag'])),
CapsuleFrag.from_bytes(bytes.fromhex(json_kfrag['cfrag'])))
for json_kfrag in vector_suite['vectors']]
capsule.set_correctness_keys(delegating=delegating_key,
receiving=receiving_key,
verifying=verifying_key)
for kfrag, cfrag in kfrags_n_cfrags:
assert kfrag.verify(signing_pubkey=verifying_key,
delegating_pubkey=delegating_key,
receiving_pubkey=receiving_key), \
'Invalid KFrag {}'.format(kfrag.to_bytes().hex())
new_cfrag = pre.reencrypt(kfrag, capsule, provide_proof=False)
assert new_cfrag.point_e1 == cfrag.point_e1
assert new_cfrag.point_v1 == cfrag.point_v1
assert new_cfrag.kfrag_id == cfrag.kfrag_id
assert new_cfrag.point_precursor == cfrag.point_precursor
assert new_cfrag.proof is None
assert cfrag.to_bytes() == new_cfrag.to_bytes()

View File

@ -1,140 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import sys
import os
import time
sys.path.append(os.path.abspath(os.getcwd()))
import pytest
from umbral import keys, pre
from umbral.config import default_curve
from umbral.params import UmbralParameters
from umbral.signing import Signer
#
# Setup
#
CURVE = default_curve()
PARAMS = UmbralParameters(curve=CURVE)
# Faster
# (M, N) # |
FRAG_VALUES = ((1, 1), # |
(2, 3), # |
(5, 8), # |
(6, 10), # |
(10, 30), # |
# (20, 30), # | # FIXME: CircleCi build killed
# (10, 100) # |
# |
) # |
# Slower
def __standard_encryption_api() -> tuple:
delegating_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
delegating_pubkey = delegating_privkey.get_pubkey()
signing_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
signer = Signer(signing_privkey)
receiving_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
receiving_pubkey = receiving_privkey.get_pubkey()
plain_data = os.urandom(32)
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
capsule.set_correctness_keys(delegating=delegating_pubkey,
receiving=receiving_pubkey,
verifying=signing_privkey.get_pubkey())
return delegating_privkey, signer, receiving_pubkey, ciphertext, capsule
#
# KFrag Generation Benchmarks
#
@pytest.mark.benchmark(group="Reencryption Key Generation Performance",
disable_gc=True,
warmup=True,
warmup_iterations=10)
@pytest.mark.parametrize("m, n", FRAG_VALUES)
def test_generate_kfrags_performance(benchmark, m: int, n: int) -> None:
def __setup():
delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
args = (delegating_privkey, receiving_pubkey)
kwargs = {"threshold": m, "N": n, "signer": signer}
return args, kwargs
print("\nBenchmarking {function} with M:{M} of N:{N}...".format(function="pre.generate_kfrags", M=m, N=n))
benchmark.pedantic(pre.generate_kfrags, setup=__setup, rounds=1000)
assert True # ensure function finishes and succeeds.
#
# Reencryption Benchmarks
#
@pytest.mark.benchmark(group="Reencryption Performance",
timer=time.perf_counter,
disable_gc=True,
warmup=True,
warmup_iterations=10)
@pytest.mark.parametrize("m, n", ((6, 10), ))
def test_random_frag_reencryption_performance(benchmark, m: int, n: int) -> None:
def __setup():
delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
one_kfrag, *remaining_kfrags = kfrags
args, kwargs = tuple(), {"kfrag": one_kfrag, "capsule": capsule},
return args, kwargs
print("\nBenchmarking {} with randomly created fragments...".format("pre.reencrypt"))
benchmark.pedantic(pre.reencrypt, setup=__setup, rounds=1000)
assert True # ensure function finishes and succeeds.
@pytest.mark.benchmark(group="Reencryption Performance",
timer=time.perf_counter,
disable_gc=True,
min_time=0.00005,
max_time=0.005,
min_rounds=7,
warmup=True,
warmup_iterations=10)
@pytest.mark.parametrize("m, n", ((6, 10), ))
def test_single_frag_reencryption_performance(benchmark, m: int, n: int) -> None:
delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
one_kfrag, *remaining_kfrags = kfrags
args, kwargs = tuple(), {"kfrag": one_kfrag, "capsule": capsule},
print("\nBenchmarking {} with the same fragment({M} of {N}) repeatedly...".format("pre.reencrypt", M=m, N=n))
benchmark.pedantic(pre.reencrypt, args=args, kwargs=kwargs, iterations=20, rounds=100)
assert True # ensure function finishes and succeeds.

View File

@ -1,81 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import sys
from typing import Tuple, List
sys.path.append(os.path.abspath(os.getcwd()))
from umbral.kfrags import KFrag
from umbral.pre import Capsule
from umbral import keys, pre
from umbral.config import default_curve
from umbral.params import UmbralParameters
from umbral.signing import Signer
CURVE = default_curve()
PARAMS = UmbralParameters(curve=CURVE)
REENCRYPTIONS = 1000
def __produce_kfrags_and_capsule(m: int, n: int) -> Tuple[List[KFrag], Capsule]:
delegating_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
delegating_pubkey = delegating_privkey.get_pubkey()
signing_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
signer = Signer(signing_privkey)
receiving_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
receiving_pubkey = receiving_privkey.get_pubkey()
plain_data = os.urandom(32)
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
capsule.set_correctness_keys(delegating=delegating_pubkey,
receiving=receiving_pubkey,
verifying=signing_privkey.get_pubkey())
return kfrags, capsule
def firehose(m: int=6, n: int=10) -> None:
print("Making kfrags...")
kfrags, capsule = __produce_kfrags_and_capsule(m=m, n=n)
one_kfrag, *remaining_kfrags = kfrags
print('Re-encrypting...')
successful_reencryptions = 0
for iteration in range(int(REENCRYPTIONS)):
_cfrag = pre.reencrypt(one_kfrag, capsule) # <<< REENCRYPTION HAPPENS HERE
successful_reencryptions += 1
if iteration % 20 == 0:
print('Performed {} Re-encryptions...'.format(iteration))
failure_message = "A Reencryption failed. {} of {} succeeded".format(successful_reencryptions, REENCRYPTIONS)
assert successful_reencryptions == REENCRYPTIONS, failure_message
print("Successfully performed {} reencryptions".format(successful_reencryptions), end='\n')
if __name__ == "__main__":
firehose() # do

View File

@ -1,16 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""

View File

@ -1,167 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral import pre
from umbral.cfrags import CapsuleFrag
from umbral.kfrags import KFrag
from umbral.config import default_curve
from umbral.params import UmbralParameters
from umbral.signing import Signer
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
from ..conftest import parameters, other_supported_curves, kfrag_signing_modes
@pytest.mark.parametrize("N, M", parameters)
@pytest.mark.parametrize("signing_mode", kfrag_signing_modes)
def test_lifecycle_with_serialization(N, M, signing_mode, curve=default_curve()):
"""
This test is a variant of test_simple_api, but with intermediate
serialization/deserialization steps, modeling how pyUmbral artifacts
(such as keys, ciphertexts, etc) will actually be used.
These intermediate steps are in between the different 'usage domains'
in NuCypher, namely, key generation, delegation, encryption, decryption by
Alice, re-encryption by Ursula, and decryption by Bob.
Manually injects UmbralParameters for multi-curve testing.
"""
# Convenience method to avoid replicating key generation code
def new_keypair_bytes():
privkey = UmbralPrivateKey.gen_key(params=params)
return privkey.to_bytes(), privkey.get_pubkey().to_bytes()
## SETUP
params = UmbralParameters(curve=curve)
delegating_privkey_bytes, delegating_pubkey_bytes = new_keypair_bytes()
signing_privkey_bytes, signing_pubkey_bytes = new_keypair_bytes()
receiving_privkey_bytes, receiving_pubkey_bytes = new_keypair_bytes()
## DELEGATION DOMAIN:
## Alice delegates decryption rights to some Bob by generating a set of
## KFrags, using her delegating private key and Bob's receiving public key
delegating_privkey = UmbralPrivateKey.from_bytes(delegating_privkey_bytes, params=params)
signing_privkey = UmbralPrivateKey.from_bytes(signing_privkey_bytes, params=params)
receiving_pubkey = UmbralPublicKey.from_bytes(receiving_pubkey_bytes, params=params)
signer = Signer(signing_privkey)
sign_delegating_key, sign_receiving_key = signing_mode
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
receiving_pubkey=receiving_pubkey,
threshold=M,
N=N,
signer=signer,
sign_delegating_key=sign_delegating_key,
sign_receiving_key=sign_receiving_key)
kfrags_bytes = tuple(map(bytes, kfrags))
del kfrags
del signer
del delegating_privkey
del signing_privkey
del receiving_pubkey
del params
## ENCRYPTION DOMAIN ##
params = UmbralParameters(curve=curve)
delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
plain_data = b'peace at dawn'
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
capsule_bytes = bytes(capsule)
del capsule
del delegating_pubkey
del params
## DECRYPTION BY ALICE ##
params = UmbralParameters(curve=curve)
delegating_privkey = UmbralPrivateKey.from_bytes(delegating_privkey_bytes, params=params)
capsule = pre.Capsule.from_bytes(capsule_bytes, params)
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
assert cleartext == plain_data
del delegating_privkey
del capsule
del params
## RE-ENCRYPTION DOMAIN (i.e., Ursula's side)
cfrags_bytes = list()
for kfrag_bytes in kfrags_bytes:
params = UmbralParameters(curve=curve)
delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
signing_pubkey = UmbralPublicKey.from_bytes(signing_pubkey_bytes, params)
receiving_pubkey = UmbralPublicKey.from_bytes(receiving_pubkey_bytes, params)
capsule = pre.Capsule.from_bytes(capsule_bytes, params)
capsule.set_correctness_keys(delegating=delegating_pubkey,
receiving=receiving_pubkey,
verifying=signing_pubkey)
# TODO: use params instead of curve?
kfrag = KFrag.from_bytes(kfrag_bytes, params.curve)
assert kfrag.verify(signing_pubkey, delegating_pubkey, receiving_pubkey, params)
cfrag_bytes = bytes(pre.reencrypt(kfrag, capsule))
cfrags_bytes.append(cfrag_bytes)
del capsule
del kfrag
del params
del delegating_pubkey
del signing_pubkey
del receiving_pubkey
## DECRYPTION DOMAIN (i.e., Bob's side)
params = UmbralParameters(curve=curve)
capsule = pre.Capsule.from_bytes(capsule_bytes, params)
delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
signing_pubkey = UmbralPublicKey.from_bytes(signing_pubkey_bytes, params)
receiving_privkey = UmbralPrivateKey.from_bytes(receiving_privkey_bytes, params=params)
receiving_pubkey = receiving_privkey.get_pubkey()
capsule.set_correctness_keys(delegating=delegating_pubkey,
receiving=receiving_pubkey,
verifying=signing_pubkey)
for cfrag_bytes in cfrags_bytes:
# TODO: use params instead of curve?
cfrag = CapsuleFrag.from_bytes(cfrag_bytes, params.curve)
capsule.attach_cfrag(cfrag)
reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
assert reenc_cleartext == plain_data
@pytest.mark.parametrize("curve", other_supported_curves)
@pytest.mark.parametrize("N, M", parameters)
@pytest.mark.parametrize("signing_mode", kfrag_signing_modes)
def test_lifecycle_with_serialization_on_multiple_curves(N, M, signing_mode, curve):
test_lifecycle_with_serialization(N, M, signing_mode, curve)

View File

@ -1,98 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral import pre
from umbral.config import default_curve
from umbral.params import UmbralParameters
from umbral.signing import Signer
from umbral.keys import UmbralPrivateKey
from ..conftest import parameters, other_supported_curves
@pytest.mark.parametrize("N, M", parameters)
def test_simple_api(N, M, curve=default_curve()):
"""
This test models the main interactions between NuCypher actors (i.e., Alice,
Bob, Data Source, and Ursulas) and artifacts (i.e., public and private keys,
ciphertexts, capsules, KFrags, CFrags, etc).
The test covers all the main stages of data sharing with NuCypher:
key generation, delegation, encryption, decryption by
Alice, re-encryption by Ursula, and decryption by Bob.
Manually injects umbralparameters for multi-curve testing."""
# Generation of global parameters
params = UmbralParameters(curve=curve)
# Key Generation (Alice)
delegating_privkey = UmbralPrivateKey.gen_key(params=params)
delegating_pubkey = delegating_privkey.get_pubkey()
signing_privkey = UmbralPrivateKey.gen_key(params=params)
signing_pubkey = signing_privkey.get_pubkey()
signer = Signer(signing_privkey)
# Key Generation (Bob)
receiving_privkey = UmbralPrivateKey.gen_key(params=params)
receiving_pubkey = receiving_privkey.get_pubkey()
# Encryption by an unnamed data source
plain_data = b'peace at dawn'
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
# Decryption by Alice
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
assert cleartext == plain_data
# Split Re-Encryption Key Generation (aka Delegation)
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, M, N, signer)
# Capsule preparation (necessary before re-encryotion and activation)
capsule.set_correctness_keys(delegating=delegating_pubkey,
receiving=receiving_pubkey,
verifying=signing_pubkey)
# Bob requests re-encryption to some set of M ursulas
cfrags = list()
for kfrag in kfrags[:M]:
# Ursula checks that the received kfrag is valid
assert kfrag.verify(signing_pubkey, delegating_pubkey, receiving_pubkey, params)
# Re-encryption by an Ursula
cfrag = pre.reencrypt(kfrag, capsule)
# Bob collects the result
cfrags.append(cfrag)
# Capsule activation (by Bob)
for cfrag in cfrags:
capsule.attach_cfrag(cfrag)
# Decryption by Bob
reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
assert reenc_cleartext == plain_data
@pytest.mark.parametrize("curve", other_supported_curves)
@pytest.mark.parametrize("N, M", parameters)
def test_simple_api_on_multiple_curves(N, M, curve):
test_simple_api(N, M, curve)

View File

@ -1,104 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import pytest
from umbral.curvebn import CurveBN
from umbral.cfrags import CapsuleFrag
from umbral.keys import UmbralPrivateKey
from umbral.point import Point
from umbral.pre import Capsule
from umbral.config import default_params
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.
"""
params = default_params()
capsule = Capsule(params,
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_precursor=Point.gen_rand(),
)
with pytest.raises(TypeError):
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.
"""
params = default_params()
capsule = Capsule(params,
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_precursor=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, receiving_details, verifying_details = key_details
assert all((delegating_details, receiving_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.
"""
params = default_params()
capsule = Capsule(params,
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(),
receiving=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(receiving=UmbralPrivateKey.gen_key().get_pubkey())
with pytest.raises(ValueError):
capsule.set_correctness_keys(verifying=UmbralPrivateKey.gen_key().get_pubkey())

View File

@ -1,135 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral import pre
from umbral.curvebn import CurveBN
from umbral.point import Point
from umbral.pre import Capsule
from umbral.signing import Signer
from umbral.keys import UmbralPrivateKey
from umbral.config import default_params
def test_capsule_creation(alices_keys):
params = default_params()
with pytest.raises(TypeError):
rare_capsule = Capsule(params) # Alice cannot make a capsule this way.
# Some users may create capsules their own way.
custom_capsule = Capsule(params,
point_e=Point.gen_rand(),
point_v=Point.gen_rand(),
bn_sig=CurveBN.gen_rand())
assert isinstance(custom_capsule, Capsule)
# Typical Alice, constructing a typical capsule
delegating_privkey, _signing_key = alices_keys
plaintext = b'peace at dawn'
ciphertext, typical_capsule = pre.encrypt(delegating_privkey.get_pubkey(), plaintext)
assert isinstance(typical_capsule, Capsule)
def test_capsule_equality():
params = default_params()
one_capsule = Capsule(params,
point_e=Point.gen_rand(),
point_v=Point.gen_rand(),
bn_sig=CurveBN.gen_rand())
another_capsule = Capsule(params,
point_e=Point.gen_rand(),
point_v=Point.gen_rand(),
bn_sig=CurveBN.gen_rand())
assert one_capsule != another_capsule
def test_decapsulation_by_alice(alices_keys):
params = default_params()
delegating_privkey, _signing_privkey = alices_keys
sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
assert len(sym_key) == 32
# The symmetric key sym_key is perhaps used for block cipher here in a real-world scenario.
sym_key_2 = pre._decapsulate_original(delegating_privkey, capsule)
assert sym_key_2 == sym_key
def test_bad_capsule_fails_reencryption(kfrags):
params = default_params()
bollocks_capsule = Capsule(params,
point_e=Point.gen_rand(),
point_v=Point.gen_rand(),
bn_sig=CurveBN.gen_rand())
for kfrag in kfrags:
with pytest.raises(Capsule.NotValid):
pre.reencrypt(kfrag, bollocks_capsule)
def test_capsule_as_dict_key(alices_keys, bobs_keys):
delegating_privkey, signing_privkey = alices_keys
signer_alice = Signer(signing_privkey)
delegating_pubkey = delegating_privkey.get_pubkey()
signing_pubkey = signing_privkey.get_pubkey()
receiving_privkey, receiving_pubkey = bobs_keys
plain_data = b'peace at dawn'
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
# 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"
# And if we change the value for this key, all is still well.
some_dict[capsule] = "Bob has changed his mind."
assert some_dict[capsule] == "Bob has changed his mind."
assert len(some_dict.keys()) == 1
def test_capsule_length(prepared_capsule, kfrags):
capsule = prepared_capsule
for counter, kfrag in enumerate(kfrags):
assert len(capsule) == counter
cfrag = pre.reencrypt(kfrag, capsule)
capsule.attach_cfrag(cfrag)
def test_capsule_clear(prepared_capsule, kfrags):
capsule = prepared_capsule
for counter, kfrag in enumerate(kfrags):
assert len(capsule) == counter
cfrag = pre.reencrypt(kfrag, capsule)
capsule.attach_cfrag(cfrag)
capsule.clear_cfrags()
assert len(capsule) == 0

View File

@ -1,63 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral.pre import Capsule
from umbral.curvebn import CurveBN
from umbral.point import Point
def test_capsule_serialization(capsule: Capsule):
params = capsule.params
capsule_bytes = capsule.to_bytes()
capsule_bytes_casted = bytes(capsule)
assert capsule_bytes == capsule_bytes_casted
# A Capsule can be represented as the 98 total bytes of two Points (33 each) and a CurveBN (32).
assert len(capsule_bytes) == Capsule.expected_bytes_length()
new_capsule = Capsule.from_bytes(capsule_bytes, params)
# Three ways to think about equality.
# First, the public approach for the Capsule. Simply:
assert new_capsule == capsule
# Second, we show that the original components (which is all we have here since we haven't activated) are the same:
assert new_capsule.components() == capsule.components()
# Third, we can directly compare the private original component attributes
# (though this is not a supported approach):
assert new_capsule.point_e == capsule.point_e
assert new_capsule.point_v == capsule.point_v
assert new_capsule.bn_sig == capsule.bn_sig
def test_cannot_create_capsule_from_bogus_material(alices_keys):
params = alices_keys[0].params
with pytest.raises(TypeError):
_capsule_of_questionable_parentage = Capsule(params,
point_e=Point.gen_rand(),
point_v=42,
bn_sig=CurveBN.gen_rand())
with pytest.raises(TypeError):
_capsule_of_questionable_parentage = Capsule(params,
point_e=Point.gen_rand(),
point_v=Point.gen_rand(),
bn_sig=42)

View File

@ -1,126 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from umbral import pre
from umbral.cfrags import CapsuleFrag, CorrectnessProof
def test_cfrag_serialization_with_proof_and_metadata(prepared_capsule, kfrags):
# Example of potential metadata to describe the re-encryption request
metadata = b'This is an example of metadata for re-encryption request'
for kfrag in kfrags:
cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=True, metadata=metadata)
cfrag_bytes = cfrag.to_bytes()
proof = cfrag.proof
assert proof is not None
assert proof.metadata is not None
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
assert new_cfrag.point_e1 == cfrag.point_e1
assert new_cfrag.point_v1 == cfrag.point_v1
assert new_cfrag.kfrag_id == cfrag.kfrag_id
assert new_cfrag.point_precursor == cfrag.point_precursor
new_proof = new_cfrag.proof
assert new_proof is not None
assert new_proof.point_e2 == proof.point_e2
assert new_proof.point_v2 == proof.point_v2
assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
assert new_proof.bn_sig == proof.bn_sig
assert new_proof.metadata == metadata
assert new_proof.metadata == proof.metadata
def test_cfrag_serialization_with_proof_but_no_metadata(prepared_capsule, kfrags):
for kfrag in kfrags:
cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=True)
cfrag_bytes = cfrag.to_bytes()
proof = cfrag.proof
assert proof is not None
assert proof.metadata is None
# A CFrag can be represented as the 131 total bytes of three Points (33 each) and a CurveBN (32).
# TODO: Figure out final size for CFrags with proofs
# assert len(cfrag_bytes) == 33 + 33 + 33 + 32 == 131
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
assert new_cfrag.point_e1 == cfrag.point_e1
assert new_cfrag.point_v1 == cfrag.point_v1
assert new_cfrag.kfrag_id == cfrag.kfrag_id
assert new_cfrag.point_precursor == cfrag.point_precursor
new_proof = new_cfrag.proof
assert new_proof is not None
assert new_proof.point_e2 == proof.point_e2
assert new_proof.point_v2 == proof.point_v2
assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
assert new_proof.bn_sig == proof.bn_sig
assert new_proof.metadata is None
def test_cfrag_serialization_no_proof_no_metadata(prepared_capsule, kfrags):
for kfrag in kfrags:
cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=False)
cfrag_bytes = cfrag.to_bytes()
proof = cfrag.proof
assert proof is None
assert len(cfrag_bytes) == CapsuleFrag.expected_bytes_length()
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
assert new_cfrag.point_e1 == cfrag.point_e1
assert new_cfrag.point_v1 == cfrag.point_v1
assert new_cfrag.kfrag_id == cfrag.kfrag_id
assert new_cfrag.point_precursor == cfrag.point_precursor
new_proof = new_cfrag.proof
assert new_proof is None
def test_correctness_proof_serialization(prepared_capsule, kfrags):
# Example of potential metadata to describe the re-encryption request
metadata = b"This is an example of metadata for re-encryption request"
for kfrag in kfrags:
cfrag = pre.reencrypt(kfrag, prepared_capsule, metadata=metadata)
proof = cfrag.proof
proof_bytes = proof.to_bytes()
# A CorrectnessProof can be represented as
# the 228 total bytes of four Points (33 each) and three BigNums (32 each).
# TODO: Figure out final size for CorrectnessProofs
# assert len(proof_bytes) == (33 * 4) + (32 * 3) == 228
new_proof = CorrectnessProof.from_bytes(proof_bytes)
assert new_proof.point_e2 == proof.point_e2
assert new_proof.point_v2 == proof.point_v2
assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
assert new_proof.bn_sig == proof.bn_sig
assert new_proof.kfrag_signature == proof.kfrag_signature
assert new_proof.metadata == proof.metadata

View File

@ -1,86 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import importlib
import pytest
import warnings
from umbral.config import _CONFIG
from umbral.curve import SECP256K1, SECP256R1
def _copy_config_for_testing():
"""
NEVER do this. This is for testing only.
This is absolutely not a thing to actually do in production code. At all. Ever.
"""
config_module_spec = importlib.util.find_spec("umbral.config")
config_copy = importlib.util.module_from_spec(config_module_spec)
config_module_spec.loader.exec_module(config_copy)
assert hasattr(config_copy, "default_curve")
assert config_copy is not _CONFIG
return config_copy
def test_try_to_use_curve_with_no_default_curve():
config = _copy_config_for_testing()
# No curve is set.
assert config._CONFIG._CONFIG__curve is None
# Getting the default curve if we haven't set one yet sets one and gives us a warning.
with warnings.catch_warnings(record=True) as caught_warnings:
assert len(caught_warnings) == 0
config.default_curve()
assert len(caught_warnings) == 1
assert caught_warnings[0].message.args[0] == config._CONFIG._CONFIG__WARNING_IF_NO_DEFAULT_SET
assert caught_warnings[0].category == RuntimeWarning
# Now, a default curve has been set.
assert config._CONFIG._CONFIG__curve == SECP256K1
def test_try_to_use_default_params_with_no_default_curve():
config = _copy_config_for_testing()
# Again, no curve is set.
assert config._CONFIG._CONFIG__curve is None
# This time, we'll try to use default_params() and get the same warning as above.
with warnings.catch_warnings(record=True) as caught_warnings:
assert len(caught_warnings) == 0
config.default_params()
assert len(caught_warnings) == 1
assert caught_warnings[0].message.args[0] == config._CONFIG._CONFIG__WARNING_IF_NO_DEFAULT_SET
assert caught_warnings[0].category == RuntimeWarning
# Now, a default curve has been set.
assert config._CONFIG._CONFIG__curve == SECP256K1
def test_cannot_set_default_curve_twice():
config = _copy_config_for_testing()
# pyumbral even supports NIST curves!
config.set_default_curve(SECP256R1)
# Our default curve has been set...
assert config.default_curve() == SECP256R1
# ...but once set, you can't set the default curve again, even if you've found a better one.
with pytest.raises(config._CONFIG.UmbralConfigurationError):
config.set_default_curve(SECP256K1)

View File

@ -1,82 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral.curve import Curve
def test_supported_curves():
# Ensure we have the correct number opf supported curves hardcoded
number_of_supported_curves = 3
assert len(Curve._supported_curves) == number_of_supported_curves
# Manually ensure the `_supported curves` dict contains only valid supported curves
assert Curve._supported_curves[415] == 'secp256r1'
assert Curve._supported_curves[714] == 'secp256k1'
assert Curve._supported_curves[715] == 'secp384r1'
nid, name = 714, 'secp256k1'
#
# Create by NID
#
# supported
_curve_714 = Curve(nid=nid)
assert _curve_714.curve_nid == nid
assert _curve_714.name == name
# unsuported
with pytest.raises(NotImplementedError):
_ = Curve(711)
#
# Create by Name
#
# Supported
_curve_secp256k1 = Curve.from_name(name)
assert _curve_secp256k1.name == name
assert _curve_secp256k1.curve_nid == nid
# Unsupported
with pytest.raises(NotImplementedError):
_ = Curve.from_name('abcd123e4')
# Import curve constants
from umbral.curve import SECP256R1, SECP256K1, SECP384R1
test_p256 = SECP256R1
test_secp256k1 = SECP256K1
test_p384 = SECP384R1
# Test the hardcoded curve NIDs are correct:
assert test_p256.curve_nid == 415
assert test_secp256k1.curve_nid == 714
assert test_p384.curve_nid == 715
# Ensure every curve constant is in the CURVES collection
from umbral.curve import CURVES
assert len(CURVES) == number_of_supported_curves
# Ensure all supported curves can be initialized
for nid, name in Curve._supported_curves.items():
_curve_nid, _curve_name = Curve(nid=nid), Curve.from_name(name)
assert _curve_nid.name == name
assert _curve_name.curve_nid == nid

View File

@ -1,77 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
import os
from umbral.dem import UmbralDEM, DEM_KEYSIZE, DEM_NONCE_SIZE
from cryptography.exceptions import InvalidTag
def test_encrypt_decrypt():
key = os.urandom(32)
dem = UmbralDEM(key)
plaintext = b'peace at dawn'
ciphertext0 = dem.encrypt(plaintext)
ciphertext1 = dem.encrypt(plaintext)
assert ciphertext0 != plaintext
assert ciphertext1 != plaintext
# Ciphertext should be different even with same plaintext.
assert ciphertext0 != ciphertext1
# Nonce should be different
assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE]
cleartext0 = dem.decrypt(ciphertext0)
cleartext1 = dem.decrypt(ciphertext1)
assert cleartext0 == plaintext
assert cleartext1 == plaintext
def test_encrypt_decrypt_associated_data():
key = os.urandom(32)
aad = b'secret code 1234'
dem = UmbralDEM(key)
plaintext = b'peace at dawn'
ciphertext0 = dem.encrypt(plaintext, authenticated_data=aad)
ciphertext1 = dem.encrypt(plaintext, authenticated_data=aad)
assert ciphertext0 != plaintext
assert ciphertext1 != plaintext
assert ciphertext0 != ciphertext1
assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE]
cleartext0 = dem.decrypt(ciphertext0, authenticated_data=aad)
cleartext1 = dem.decrypt(ciphertext1, authenticated_data=aad)
assert cleartext0 == plaintext
assert cleartext1 == plaintext
# Attempt decryption with invalid associated data
with pytest.raises(InvalidTag):
cleartext2 = dem.decrypt(ciphertext0, authenticated_data=b'wrong data')

View File

@ -1,67 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from umbral.kfrags import KFrag
def test_kfrag_serialization(alices_keys, bobs_keys, kfrags):
delegating_privkey, signing_privkey = alices_keys
_receiving_privkey, receiving_pubkey = bobs_keys
for kfrag in kfrags:
kfrag_bytes = kfrag.to_bytes()
assert len(kfrag_bytes) == KFrag.expected_bytes_length()
new_kfrag = KFrag.from_bytes(kfrag_bytes)
assert new_kfrag.id == kfrag.id
assert new_kfrag.bn_key == kfrag.bn_key
assert new_kfrag.point_precursor == kfrag.point_precursor
assert new_kfrag.point_commitment == kfrag.point_commitment
assert new_kfrag.keys_in_signature == kfrag.keys_in_signature
assert new_kfrag.signature_for_proxy == kfrag.signature_for_proxy
assert new_kfrag.signature_for_bob == kfrag.signature_for_bob
assert new_kfrag.verify(signing_pubkey=signing_privkey.get_pubkey(),
delegating_pubkey=delegating_privkey.get_pubkey(),
receiving_pubkey=receiving_pubkey)
assert new_kfrag == kfrag
def test_kfrag_verify_for_capsule(prepared_capsule, kfrags):
for kfrag in kfrags:
assert kfrag.verify_for_capsule(prepared_capsule)
# If we alter some element, the verification fails
previous_id, kfrag.id = kfrag.id, bytes(32)
assert not kfrag.verify_for_capsule(prepared_capsule)
# Let's restore the KFrag, and alter the re-encryption key instead
kfrag.id = previous_id
kfrag.bn_key += kfrag.bn_key
assert not kfrag.verify_for_capsule(prepared_capsule)
def test_kfrag_as_dict_key(kfrags):
dict_with_kfrags_as_keys = dict()
dict_with_kfrags_as_keys[kfrags[0]] = "Some llamas. Definitely some llamas."
dict_with_kfrags_as_keys[kfrags[1]] = "No llamas here. Definitely not."
assert dict_with_kfrags_as_keys[kfrags[0]] != dict_with_kfrags_as_keys[kfrags[1]]

View File

@ -1,167 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import contextlib
import pytest
from cryptography.hazmat.backends.openssl import backend
from umbral.curvebn import CurveBN
from umbral.point import Point
@pytest.fixture()
def mock_openssl(mocker, random_ec_point1: Point, random_ec_curvebn1: CurveBN, random_ec_curvebn2: CurveBN):
"""
Patches openssl backend methods for testing.
For all functions, 1 is returned for success, 0 on error.
"""
actual_backend = {
# Point
'EC_POINT_mul': backend._lib.EC_POINT_mul,
'EC_POINT_cmp': backend._lib.EC_POINT_cmp,
'EC_POINT_add': backend._lib.EC_POINT_add,
'EC_POINT_invert': backend._lib.EC_POINT_invert,
# Bignum
'BN_cmp': backend._lib.BN_cmp,
'BN_mod_exp': backend._lib.BN_mod_exp,
'BN_mod_mul': backend._lib.BN_mod_mul,
'BN_mod_inverse': backend._lib.BN_mod_inverse,
'BN_mod_add': backend._lib.BN_mod_add,
'BN_mod_sub': backend._lib.BN_mod_sub,
'BN_nnmod': backend._lib.BN_nnmod,
}
def check_curvebn_ctypes(*curvebns):
for bn in curvebns:
assert 'BIGNUM' in str(bn)
assert bn.__class__.__name__ == 'CDataGCP'
def check_point_ctypes(*ec_points):
for point in ec_points:
assert 'EC_POINT' in str(point)
assert point.__class__.__name__ == 'CDataGCP'
@contextlib.contextmanager
def mocked_openssl_backend():
def mocked_ec_point_equality(group, ec_point, other_point, context):
check_point_ctypes(ec_point, other_point)
assert 'BN_CTX' in str(context)
assert 'EC_GROUP' in str(group)
assert random_ec_point1.curve.ec_group == group
assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
result = actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, other_point, context)
assert not bool(result)
return result
def mocked_ec_point_addition(group, sum, ec_point, other_point, context):
check_point_ctypes(sum, other_point)
assert 'BN_CTX' in str(context)
assert random_ec_point1.group == group
assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
return actual_backend['EC_POINT_add'](group, sum, ec_point, other_point, context)
def mocked_ec_point_multiplication(group, product, null, ec_point, curvebn, context):
check_point_ctypes(ec_point, product)
assert 'BN_CTX' in str(context)
assert 'EC_GROUP' in str(group)
assert 'NULL' in str(null)
assert random_ec_point1.group == group
assert random_ec_curvebn1.curve_nid == random_ec_point1.curve_nid
assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
return actual_backend['EC_POINT_mul'](group, product, null, ec_point, curvebn, context)
def mocked_ec_point_inversion(group, inverse, context):
check_point_ctypes(inverse)
assert 'BN_CTX' in str(context)
assert random_ec_point1.group == group
return actual_backend['EC_POINT_invert'](group, inverse, context)
def mocked_bn_compare(curvebn, other):
check_curvebn_ctypes(curvebn, other)
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
return actual_backend['BN_cmp'](curvebn, other)
def mocked_bn_mod_exponent(power, curvebn, other, order, context):
check_curvebn_ctypes(curvebn, other, power, order)
assert 'BN_CTX' in str(context)
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
return actual_backend['BN_mod_exp'](power, curvebn, other, order, context)
def mocked_bn_mod_multiplication(product, curvebn, other, order, context):
check_curvebn_ctypes(curvebn, other, product, order)
assert 'BN_CTX' in str(context)
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
return actual_backend['BN_mod_mul'](product, curvebn, other, order, context)
def mocked_bn_inverse(null, curvebn, order, context):
check_curvebn_ctypes(curvebn, order)
assert 'BN_CTX' in str(context)
assert 'NULL' in str(null)
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
return actual_backend['BN_mod_inverse'](null, curvebn, order, context)
def mocked_bn_addition(sum, curvebn, other, order, context):
check_curvebn_ctypes(curvebn, other, sum, order)
assert 'BN_CTX' in str(context)
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
return actual_backend['BN_mod_add'](sum, curvebn, other, order, context)
def mocked_bn_subtraction(diff, curvebn, other, order, context):
check_curvebn_ctypes(curvebn, other, diff, order)
assert 'BN_CTX' in str(context)
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
return actual_backend['BN_mod_sub'](diff, curvebn, other, order, context)
def mocked_bn_nmodulus(rem, curvebn, other, context):
check_curvebn_ctypes(curvebn, other, rem)
assert 'BN_CTX' in str(context)
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
return actual_backend['BN_nnmod'](rem, curvebn, other, context)
mock_load = {
# Point
'EC_POINT_mul': mocked_ec_point_multiplication,
'EC_POINT_cmp': mocked_ec_point_equality,
'EC_POINT_add': mocked_ec_point_addition,
'EC_POINT_invert': mocked_ec_point_inversion,
# Bignum
'BN_cmp': mocked_bn_compare,
'BN_mod_exp': mocked_bn_mod_exponent,
'BN_mod_mul': mocked_bn_mod_multiplication,
'BN_mod_inverse': mocked_bn_inverse,
'BN_mod_add': mocked_bn_addition,
'BN_mod_sub': mocked_bn_subtraction,
'BN_nnmod': mocked_bn_nmodulus,
}
with contextlib.ExitStack() as stack:
for method, patch in mock_load.items():
stack.enter_context(mocker.mock_module.patch.object(backend._lib, method, patch))
yield
return mocked_openssl_backend

View File

@ -1,64 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from cryptography.hazmat.backends.openssl import backend
from umbral.curvebn import CurveBN
def test_mocked_openssl_curvebn_arithmetic(mock_openssl, random_ec_curvebn1, random_ec_curvebn2):
operations_that_construct = (
random_ec_curvebn1 * random_ec_curvebn2, # __mul__
random_ec_curvebn1 ** random_ec_curvebn2, # __pow__
random_ec_curvebn1 ** int(random_ec_curvebn2), # __pow__ (as int)
random_ec_curvebn1 + random_ec_curvebn2, # __add__
random_ec_curvebn1 - random_ec_curvebn2, # __sub__
-random_ec_curvebn1, # __neg__
random_ec_curvebn1 % random_ec_curvebn2, # __mod__
random_ec_curvebn1 % int(random_ec_curvebn2), # __mod__ (as int)
~random_ec_curvebn1, # __invert__
random_ec_curvebn1 / random_ec_curvebn2 # __truediv__
)
with mock_openssl():
assert random_ec_curvebn1 == random_ec_curvebn1 # __eq__
for operator_result in operations_that_construct:
assert operator_result
assert isinstance(operator_result, CurveBN)
order = backend._bn_to_int(random_ec_curvebn1.curve.order)
random_ec_curvebn1 = int(random_ec_curvebn1)
random_ec_curvebn2 = int(random_ec_curvebn2)
# For simplicity, we test these two cases separately
assert (int(operations_that_construct[-2]) * random_ec_curvebn1) % order == 1
assert (int(operations_that_construct[-1]) * random_ec_curvebn2) % order == random_ec_curvebn1
# The remaining cases can be tested in bulk
expected_results = (
(random_ec_curvebn1 * random_ec_curvebn2) % order, # __mul__
pow(random_ec_curvebn1, random_ec_curvebn2, order), # __pow__
pow(random_ec_curvebn1, random_ec_curvebn2, order), # __pow__ (as int)
(random_ec_curvebn1 + random_ec_curvebn2) % order, # __add__
(random_ec_curvebn1 - random_ec_curvebn2) % order, # __sub__
(-random_ec_curvebn1) % order, # __neg__
random_ec_curvebn1 % random_ec_curvebn2, # __mod__
random_ec_curvebn1 % int(random_ec_curvebn2), # __mod__ (as int)
)
for (result, expected) in zip(operations_that_construct[:-2], expected_results):
assert result == expected

View File

@ -1,39 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from umbral.curvebn import CurveBN
from umbral.random_oracles import hash_to_curvebn
import pytest
def test_cast_curvebn_to_int():
x = CurveBN.gen_rand()
x_as_int_from_dunder = x.__int__()
x_as_int_type_caster = int(x)
assert x_as_int_from_dunder == x_as_int_type_caster
x = x_as_int_type_caster
y = CurveBN.from_int(x)
assert x == y
def test_cant_hash_arbitrary_object_into_bignum():
whatever = object()
with pytest.raises(TypeError):
hash_to_curvebn(whatever)

View File

@ -1,62 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral.curvebn import CurveBN
from umbral.curve import CURVES
from cryptography.hazmat.backends import default_backend
@pytest.mark.parametrize("curve", CURVES)
def test_serialization_rotations_of_1(curve):
size_in_bytes = CurveBN.expected_bytes_length(curve)
for i in range(size_in_bytes):
lonely_one = 1 << i
bn = CurveBN.from_int(lonely_one, curve)
lonely_one_in_bytes = lonely_one.to_bytes(size_in_bytes, 'big')
# Check serialization
assert bn.to_bytes() == lonely_one_in_bytes
# Check deserialization
assert CurveBN.from_bytes(lonely_one_in_bytes, curve) == bn
@pytest.mark.parametrize("curve", CURVES)
def test_invalid_deserialization(curve):
size_in_bytes = CurveBN.expected_bytes_length(curve)
# All-zeros bytestring are invalid (i.e., 0 < bn < order of the curve)
zero_bytes = bytes(size_in_bytes)
with pytest.raises(ValueError):
_bn = CurveBN.from_bytes(zero_bytes, curve)
# All-ones bytestring is invalid too (since it's greater than order)
lots_of_ones = 2**(8*size_in_bytes) - 1
lots_of_ones = lots_of_ones.to_bytes(size_in_bytes, 'big')
with pytest.raises(ValueError):
_bn = CurveBN.from_bytes(lots_of_ones, curve)
# Serialization of `order` is invalid since it's not strictly lower than
# the order of the curve
order = default_backend()._bn_to_int(curve.order)
with pytest.raises(ValueError):
_bn = CurveBN.from_bytes(order.to_bytes(size_in_bytes, 'big'), curve)
# On the other hand, serialization of `order - 1` is valid
order -= 1
_bn = CurveBN.from_bytes(order.to_bytes(size_in_bytes, 'big'), curve)

View File

@ -1,59 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from umbral.curvebn import CurveBN
from umbral.point import Point
def test_mocked_openssl_point_arithmetic(mock_openssl, random_ec_point1, random_ec_point2, random_ec_curvebn1):
operations_that_construct = (
random_ec_point1 * random_ec_curvebn1, # __mul__
random_ec_point1 + random_ec_point2, # __add__
random_ec_point1 - random_ec_point2, # __sub__
-random_ec_point1 # __neg__
)
with mock_openssl():
assert random_ec_point1 == random_ec_point1 # __eq__
for operator_result in operations_that_construct:
assert operator_result
assert isinstance(operator_result, Point)
def test_point_curve_multiplication_regression():
k256_point_bytes = b'\x03\xe0{\x1bQ\xbf@\x1f\x95\x8d\xe1\x17\xa7\xbe\x9e-G`T\xbf\xd7\x9e\xa7\x10\xc8uA\xc0z$\xc0\x92\x8a'
k256_bn_bytes = b'4u\xd70-\xa0h\xdeG\xf0\x143\x06!\x91\x05{\xe4jC\n\xf1h\xed7a\xf8\x9d\xec^\x19\x8c'
k256_point = Point.from_bytes(k256_point_bytes)
k256_bn = CurveBN.from_bytes(k256_bn_bytes)
product_with_star_operator = k256_point * k256_bn
# Make sure we have instantiated a new, unequal point in the same curve and group
assert isinstance(product_with_star_operator, Point), "Point.__mul__ did not return a point instance"
assert k256_point != product_with_star_operator
assert k256_point.curve == product_with_star_operator.curve
product_bytes = b'\x03\xc9\xda\xa2\x88\xe2\xa0+\xb1N\xb6\xe6\x1c\xa5(\xe6\xe0p\xf6\xf4\xa9\xfc\xb1\xfaUV\xd3\xb3\x0e4\x94\xbe\x12'
product_point = Point.from_bytes(product_bytes)
assert product_with_star_operator.to_bytes() == product_bytes
assert product_point == product_with_star_operator
# Repeating the operation, should return the same result.
product_with_star_operator_again = k256_point * k256_bn
assert product_with_star_operator == product_with_star_operator_again

View File

@ -1,165 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from cryptography.exceptions import InternalError
from umbral.curve import SECP256K1, SECP256R1
from umbral.point import Point
def generate_test_points_bytes(quantity=2):
points_bytes = [
(SECP256K1, 714, b'\x02x{DR\x94\x8f\x17\xb8\xa2\x14t\x11\xdb\xb1VK\xdb\xc2\xa0T\x97iCK\x8cz~\xea\xa3\xb7AJ'),
]
for _ in range(quantity):
args = (SECP256K1, 714, Point.gen_rand(curve=SECP256K1).to_bytes())
points_bytes.append(args)
return points_bytes
def generate_test_points_affine(quantity=2):
points_affine = [
(SECP256K1, 714, (54495335564072000415434275044935054036617226655045445809732056033758606213450,
26274482902044210718566767736429706729731617411738990314884135712590488065008)),
]
for _ in range(quantity):
args = (SECP256K1, 714, Point.gen_rand(curve=SECP256K1).to_affine())
points_affine.append(args)
return points_affine
def test_generate_random_points():
for _ in range(10):
point = Point.gen_rand()
another_point = Point.gen_rand()
assert isinstance(point, Point)
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
assert isinstance(point_with_curve, Point)
the_same_point_bytes = point_with_curve.to_bytes()
assert point_bytes == the_same_point_bytes
representations = (point_bytes, # Compressed representation
point_with_curve.to_bytes(is_compressed=False)) # Uncompressed
for point_representation in representations:
malformed_point_bytes = point_representation + b'0x'
with pytest.raises(InternalError):
_ = Point.from_bytes(malformed_point_bytes)
malformed_point_bytes = point_representation[1:]
with pytest.raises(InternalError):
_ = Point.from_bytes(malformed_point_bytes)
malformed_point_bytes = point_representation[:-1]
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
assert isinstance(point, Point)
point_affine2 = point.to_affine()
assert point_affine == point_affine2
def test_invalid_points(random_ec_point2):
point_bytes = bytearray(random_ec_point2.to_bytes(is_compressed=False))
point_bytes[-1] = point_bytes[-1] ^ 0x01 # Flips last bit
point_bytes = bytes(point_bytes)
with pytest.raises(InternalError) as e:
_point = Point.from_bytes(point_bytes)
# We want to catch specific InternalExceptions:
# - Point not in the curve (code 107)
# - Invalid compressed point (code 110)
# https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228
assert e.value.err_code[0].reason in (107, 110)
def test_generator_point():
"""http://www.secg.org/SEC2-Ver-1.0.pdf Section 2.7.1"""
g1 = Point.get_generator_from_curve()
g_compressed = 0x0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
g_uncompressed = 0x0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
g_compressed = g_compressed.to_bytes(32+1, byteorder='big')
g_uncompressed = g_uncompressed.to_bytes(64+1, byteorder='big')
g2 = Point.from_bytes(g_compressed)
assert g1 == g2
g3 = Point.from_bytes(g_uncompressed)
assert g1 == g3
assert g2 == g3
def test_point_not_on_curve():
"""
We want to be unable to create a Point that's not on the curve.
When we try, we get cryptography.exceptions.InternalError - is that specifically because it isn't
on the curve? It seems to be reliably raised in the event of the Point being off the curve.
The OpenSSL docs don't explicitly say that they raise an error for this reason:
https://www.openssl.org/docs/man1.1.0/crypto/EC_GFp_simple_method.html
"""
point_on_koblitz256_but_not_P256 = Point.from_bytes(b'\x03%\x98Dk\x88\xe2\x97\xab?\xabZ\xef\xd4' \
b'\x9e\xaa\xc6\xb3\xa4\xa3\x89\xb2\xd7b.\x8f\x16Ci_&\xe0\x7f', curve=SECP256K1)
from cryptography.exceptions import InternalError
with pytest.raises(InternalError):
Point.from_bytes(point_on_koblitz256_but_not_P256.to_bytes(), curve=SECP256R1)
def test_serialize_point_at_infinity():
p = Point.gen_rand()
point_at_infinity = p - p
bytes_point_at_infinity = point_at_infinity.to_bytes()
assert bytes_point_at_infinity == b'\x00'
def test_coords_with_special_characteristics():
# Testing that a point with x coordinate greater than the curve order is still valid.
# In particular, we will test the last valid point from the default curve (secp256k1)
# whose x coordinate is `field_order - 3` and is greater than the order of the curve
field_order = 2**256 - 0x1000003D1
compressed = b'\x02' + (field_order-3).to_bytes(32, 'big')
last_point = Point.from_bytes(compressed)
# The same point, but obtained through the from_affine method
coords = (115792089237316195423570985008687907853269984665640564039457584007908834671660,
109188863561374057667848968960504138135859662956057034999983532397866404169138)
assert last_point == Point.from_affine(coords)

View File

@ -1,115 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from cryptography.hazmat.backends.openssl import backend
from hypothesis import HealthCheck, given, settings, unlimited
from hypothesis.strategies import binary, booleans, integers, tuples
from umbral.config import default_curve
from umbral.curvebn import CurveBN
from umbral.cfrags import CorrectnessProof
from umbral.kfrags import KFrag
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
from umbral.params import UmbralParameters
from umbral.point import Point
from umbral.random_oracles import unsafe_hash_to_point
from umbral.pre import Capsule
# test parameters
max_examples = 1000
# crypto constants
curve = default_curve()
params = UmbralParameters(curve)
bn_size = curve.group_order_size_in_bytes
# generators
bns = integers(min_value=1, max_value=backend._bn_to_int(curve.order)).map(
lambda x: CurveBN.from_int(x))
points = binary(min_size=1).map(
lambda x: unsafe_hash_to_point(x, label=b'hypothesis', params=params))
signatures = tuples(integers(min_value=1, max_value=backend._bn_to_int(curve.order)),
integers(min_value=1, max_value=backend._bn_to_int(curve.order))).map(
lambda tup: tup[0].to_bytes(bn_size, 'big') + tup[1].to_bytes(bn_size, 'big'))
# # utility
def assert_kfrag_eq(k0, k1):
assert(all([ k0.id == k1.id
, k0.bn_key == k1.bn_key
, k0.point_precursor == k1.point_precursor
, k0.point_commitment == k1.point_commitment
, k0.signature_for_bob == k1.signature_for_bob
, k0.signature_for_proxy == k1.signature_for_proxy
]))
def assert_cp_eq(c0, c1):
assert(all([ c0.point_e2 == c1.point_e2
, c0.point_v2 == c1.point_v2
, c0.point_kfrag_commitment == c1.point_kfrag_commitment
, c0.point_kfrag_pok == c1.point_kfrag_pok
, c0.kfrag_signature == c1.kfrag_signature
, c0.bn_sig == c1.bn_sig
, c0.metadata == c1.metadata
]))
# tests
@given(bns)
@settings(max_examples=max_examples, timeout=unlimited)
def test_bn_roundtrip(bn):
assert(bn == CurveBN.from_bytes(bn.to_bytes()))
@given(points, booleans())
@settings(max_examples=max_examples, timeout=unlimited)
def test_point_roundtrip(p, c):
assert(p == Point.from_bytes(p.to_bytes(is_compressed=c)))
@given(binary(min_size=bn_size, max_size=bn_size), bns, points, points, signatures, signatures)
@settings(max_examples=max_examples, timeout=unlimited)
def test_kfrag_roundtrip(d, b0, p0, p1, sig_proxy, sig_bob):
k = KFrag(identifier=d, bn_key=b0, point_commitment=p0, point_precursor=p1,
signature_for_proxy=sig_proxy, signature_for_bob=sig_bob)
assert_kfrag_eq(k, KFrag.from_bytes(k.to_bytes()))
@given(points, points, bns)
@settings(max_examples=max_examples, timeout=unlimited)
def test_capsule_roundtrip_0(p0, p1, b):
c = Capsule(params=params, point_e=p0, point_v=p1, bn_sig=b)
assert(c == Capsule.from_bytes(c.to_bytes(), params=params))
@given(points, points, points, points, bns, signatures)
@settings(max_examples=max_examples, timeout=unlimited)
def test_cp_roundtrip(p0, p1, p2, p3, b0, sig):
c = CorrectnessProof(p0, p1, p2, p3, b0, sig)
assert_cp_eq(c, CorrectnessProof.from_bytes(c.to_bytes()))
@given(points)
@settings(max_examples=max_examples, timeout=unlimited)
def test_pubkey_roundtrip(p):
k = UmbralPublicKey(p, params)
assert(k == UmbralPublicKey.from_bytes(k.to_bytes(), params=params))
@given(binary(min_size=1))
@settings(max_examples=20, timeout=unlimited, suppress_health_check=[HealthCheck.hung_test])
def test_privkey_roundtrip(p):
insecure_scrypt_cost = 5 # This is deliberately insecure, just to make it faster
k = UmbralPrivateKey.gen_key()
rt = UmbralPrivateKey.from_bytes(k.to_bytes(password=p, _scrypt_cost=insecure_scrypt_cost),
password=p,
_scrypt_cost=insecure_scrypt_cost)
assert(k.get_pubkey() == rt.get_pubkey())

View File

@ -1,62 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from cryptography.hazmat.backends.openssl import backend
from cryptography.hazmat.primitives import hashes
from umbral.keys import UmbralPrivateKey
from umbral.signing import Signer, Signature, DEFAULT_HASH_ALGORITHM
@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
def test_sign_and_verify(execution_number):
privkey = UmbralPrivateKey.gen_key()
pubkey = privkey.get_pubkey()
signer = Signer(private_key=privkey)
message = b"peace at dawn"
signature = signer(message=message)
# Basic signature verification
assert signature.verify(message, pubkey)
assert not signature.verify(b"another message", pubkey)
another_pubkey = UmbralPrivateKey.gen_key().pubkey
assert not signature.verify(message, another_pubkey)
# Signature serialization
sig_bytes = bytes(signature)
assert len(sig_bytes) == Signature.expected_bytes_length()
restored_signature = Signature.from_bytes(sig_bytes)
assert restored_signature == signature
assert restored_signature.verify(message, pubkey)
@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
def test_prehashed_message(execution_number):
privkey = UmbralPrivateKey.gen_key()
pubkey = privkey.get_pubkey()
signer = Signer(private_key=privkey)
message = b"peace at dawn"
hash_function = hashes.Hash(DEFAULT_HASH_ALGORITHM(), backend=backend)
hash_function.update(message)
prehashed_message = hash_function.finalize()
signature = signer(message=prehashed_message, is_prehashed=True)
assert signature.verify(message=prehashed_message,
verifying_key=pubkey,
is_prehashed=True)

View File

@ -1,223 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import base64
import os
import pytest
import string
from umbral.config import default_params
from umbral.keys import UmbralPublicKey, UmbralPrivateKey, UmbralKeyingMaterial
from umbral.point import Point
def test_gen_key():
# Pass in the parameters to test that manual param selection works
umbral_priv_key = UmbralPrivateKey.gen_key()
assert type(umbral_priv_key) == UmbralPrivateKey
umbral_pub_key = umbral_priv_key.get_pubkey()
assert type(umbral_pub_key) == UmbralPublicKey
def test_derive_key_from_label():
umbral_keying_material = UmbralKeyingMaterial()
label = b"my_healthcare_information"
priv_key1 = umbral_keying_material.derive_privkey_by_label(label)
assert type(priv_key1) == UmbralPrivateKey
pub_key1 = priv_key1.get_pubkey()
assert type(pub_key1) == UmbralPublicKey
# Check that key derivation is reproducible
priv_key2 = umbral_keying_material.derive_privkey_by_label(label)
pub_key2 = priv_key2.get_pubkey()
assert priv_key1.bn_key == priv_key2.bn_key
assert pub_key1 == pub_key2
# A salt can be used too, but of course it affects the derived key
salt = b"optional, randomly generated salt"
priv_key3 = umbral_keying_material.derive_privkey_by_label(label, salt=salt)
assert priv_key3.bn_key != priv_key1.bn_key
# Different labels on the same master secret create different keys
label = b"my_tax_information"
priv_key4 = umbral_keying_material.derive_privkey_by_label(label)
pub_key4 = priv_key4.get_pubkey()
assert priv_key1.bn_key != priv_key4.bn_key
def test_private_key_serialization(random_ec_curvebn1):
priv_key = random_ec_curvebn1
umbral_key = UmbralPrivateKey(priv_key, default_params())
encoded_key = umbral_key.to_bytes()
decoded_key = UmbralPrivateKey.from_bytes(encoded_key)
assert priv_key == decoded_key.bn_key
def test_private_key_serialization_with_encryption(random_ec_curvebn1):
priv_key = random_ec_curvebn1
umbral_key = UmbralPrivateKey(priv_key, default_params())
insecure_cost = 15 # This is deliberately insecure, just to make the tests faster
encoded_key = umbral_key.to_bytes(password=b'test',
_scrypt_cost=insecure_cost)
decoded_key = UmbralPrivateKey.from_bytes(encoded_key,
password=b'test',
_scrypt_cost=insecure_cost)
assert priv_key == decoded_key.bn_key
def test_public_key_serialization(random_ec_curvebn1):
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_point == decoded_key.point_key
def test_public_key_to_compressed_bytes(random_ec_curvebn1):
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):
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)
def test_key_encoder_decoder(random_ec_curvebn1):
priv_key = random_ec_curvebn1
umbral_key = UmbralPrivateKey(priv_key, default_params())
encoded_key = umbral_key.to_bytes(encoder=base64.urlsafe_b64encode)
decoded_key = UmbralPrivateKey.from_bytes(encoded_key,
decoder=base64.urlsafe_b64decode)
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()
crypto_privkey = umbral_priv_key.to_cryptography_privkey()
assert int(umbral_priv_key.bn_key) == crypto_privkey.private_numbers().private_value
crypto_pubkey = umbral_pub_key.to_cryptography_pubkey()
umbral_affine = umbral_pub_key.point_key.to_affine()
x, y = crypto_pubkey.public_numbers().x, crypto_pubkey.public_numbers().y
assert umbral_affine == (x, y)
def test_keying_material_serialization():
umbral_keying_material = UmbralKeyingMaterial()
encoded_keying_material = umbral_keying_material.to_bytes()
decoded_keying_material = UmbralKeyingMaterial.from_bytes(encoded_keying_material)
label = os.urandom(32)
privkey_bytes = umbral_keying_material.derive_privkey_by_label(label).to_bytes()
assert privkey_bytes == decoded_keying_material.derive_privkey_by_label(label).to_bytes()
def test_keying_material_serialization_with_encryption():
umbral_keying_material = UmbralKeyingMaterial()
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)
decoded_keying_material = UmbralKeyingMaterial.from_bytes(encoded_keying_material,
password=b'test',
_scrypt_cost=insecure_cost)
label = os.urandom(32)
privkey_bytes = umbral_keying_material.derive_privkey_by_label(label).to_bytes()
assert privkey_bytes == decoded_keying_material.derive_privkey_by_label(label).to_bytes()
def test_umbral_public_key_equality():
umbral_priv_key = UmbralPrivateKey.gen_key()
umbral_pub_key = umbral_priv_key.get_pubkey()
as_bytes = bytes(umbral_pub_key)
assert umbral_pub_key == as_bytes
reconstructed = UmbralPublicKey.from_bytes(as_bytes)
assert reconstructed == umbral_pub_key
assert not umbral_pub_key == b"some whatever bytes"
another_umbral_priv_key = UmbralPrivateKey.gen_key()
another_umbral_pub_key = another_umbral_priv_key.get_pubkey()
assert not umbral_pub_key == another_umbral_pub_key
# Also not equal to a totally disparate type.
assert not umbral_pub_key == 107
def test_umbral_public_key_as_dict_key():
umbral_priv_key = UmbralPrivateKey.gen_key()
umbral_pub_key = umbral_priv_key.get_pubkey()
d = {umbral_pub_key: 19}
assert d[umbral_pub_key] == 19
another_umbral_priv_key = UmbralPrivateKey.gen_key()
another_umbral_pub_key = another_umbral_priv_key.get_pubkey()
with pytest.raises(KeyError):
_ = d[another_umbral_pub_key]
d[another_umbral_pub_key] = False
assert d[umbral_pub_key] == 19
d[umbral_pub_key] = 20
assert d[umbral_pub_key] == 20
assert d[another_umbral_pub_key] is False

View File

@ -1,20 +1,3 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published b
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import absolute_import, division, print_function
__all__ = [

View File

@ -1,20 +1,3 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published b
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from umbral.__about__ import (
__author__, __license__, __summary__, __title__, __version__, __copyright__, __email__, __url__
)

View File

@ -1,287 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from typing import Optional, Any
from bytestring_splitter import BytestringSplitter
from umbral.config import default_curve
from umbral.curvebn import CurveBN
from umbral.point import Point
from umbral.signing import Signature
from umbral.curve import Curve
from umbral.random_oracles import hash_to_curvebn, ExtendedKeccak
class CorrectnessProof:
def __init__(self, point_e2: Point, point_v2: Point, point_kfrag_commitment: Point,
point_kfrag_pok: Point, bn_sig: CurveBN, kfrag_signature: Signature,
metadata: Optional[bytes] = None) -> None:
self.point_e2 = point_e2
self.point_v2 = point_v2
self.point_kfrag_commitment = point_kfrag_commitment
self.point_kfrag_pok = point_kfrag_pok
self.bn_sig = bn_sig
self.metadata = metadata
self.kfrag_signature = kfrag_signature
@classmethod
def expected_bytes_length(cls, curve: Optional[Curve] = None):
"""
Returns the size (in bytes) of a CorrectnessProof without the metadata.
If no curve is given, it will use the default curve.
"""
curve = curve if curve is not None else default_curve()
bn_size = CurveBN.expected_bytes_length(curve=curve)
point_size = Point.expected_bytes_length(curve=curve)
return (bn_size * 3) + (point_size * 4)
@classmethod
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CorrectnessProof':
"""
Instantiate CorrectnessProof from serialized data.
"""
curve = curve if curve is not None else default_curve()
bn_size = CurveBN.expected_bytes_length(curve)
point_size = Point.expected_bytes_length(curve)
arguments = {'curve': curve}
splitter = BytestringSplitter(
(Point, point_size, arguments), # point_e2
(Point, point_size, arguments), # point_v2
(Point, point_size, arguments), # point_kfrag_commitment
(Point, point_size, arguments), # point_kfrag_pok
(CurveBN, bn_size, arguments), # bn_sig
(Signature, Signature.expected_bytes_length(curve), arguments), # kfrag_signature
)
components = splitter(data, return_remainder=True)
components.append(components.pop() or None)
return cls(*components)
def to_bytes(self) -> bytes:
"""
Serialize the CorrectnessProof to a bytestring.
"""
e2 = self.point_e2.to_bytes()
v2 = self.point_v2.to_bytes()
kfrag_commitment = self.point_kfrag_commitment.to_bytes()
kfrag_pok = self.point_kfrag_pok.to_bytes()
result = e2 \
+ v2 \
+ kfrag_commitment \
+ kfrag_pok \
+ self.bn_sig.to_bytes() \
+ self.kfrag_signature
result += self.metadata or b''
return result
def __bytes__(self):
return self.to_bytes()
class CapsuleFrag:
def __init__(self,
point_e1: Point,
point_v1: Point,
kfrag_id: bytes,
point_precursor: Point,
proof: Optional[CorrectnessProof] = None) -> None:
self.point_e1 = point_e1
self.point_v1 = point_v1
self.kfrag_id = kfrag_id
self.point_precursor = point_precursor
self.proof = proof
class NoProofProvided(TypeError):
"""
Raised when a cfrag is assessed for correctness, but no proof is attached.
"""
@classmethod
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
"""
Returns the size (in bytes) of a CapsuleFrag given the curve without
the CorrectnessProof.
If no curve is provided, it will use the default curve.
"""
curve = curve if curve is not None else default_curve()
bn_size = CurveBN.expected_bytes_length(curve)
point_size = Point.expected_bytes_length(curve)
return (bn_size * 1) + (point_size * 3)
@classmethod
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CapsuleFrag':
"""
Instantiates a CapsuleFrag object from the serialized data.
"""
curve = curve if curve is not None else default_curve()
bn_size = CurveBN.expected_bytes_length(curve)
point_size = Point.expected_bytes_length(curve)
arguments = {'curve': curve}
splitter = BytestringSplitter(
(Point, point_size, arguments), # point_e1
(Point, point_size, arguments), # point_v1
bn_size, # kfrag_id
(Point, point_size, arguments), # point_precursor
)
components = splitter(data, return_remainder=True)
proof = components.pop() or None
components.append(CorrectnessProof.from_bytes(proof, curve) if proof else None)
return cls(*components)
def to_bytes(self) -> bytes:
"""
Serialize the CapsuleFrag into a bytestring.
"""
e1 = self.point_e1.to_bytes()
v1 = self.point_v1.to_bytes()
precursor = self.point_precursor.to_bytes()
serialized_cfrag = e1 + v1 + self.kfrag_id + precursor
if self.proof is not None:
serialized_cfrag += self.proof.to_bytes()
return serialized_cfrag
def prove_correctness(self,
capsule,
kfrag,
metadata: Optional[bytes] = None):
params = capsule.params
# Check correctness of original ciphertext
if not capsule.verify():
raise capsule.NotValid("Capsule verification failed.")
rk = kfrag.bn_key
t = CurveBN.gen_rand(params.curve)
####
# Here are the formulaic constituents shared with `verify_correctness`.
####
e = capsule.point_e
v = capsule.point_v
e1 = self.point_e1
v1 = self.point_v1
u = params.u
u1 = kfrag.point_commitment
e2 = t * e # type: Any
v2 = t * v # type: Any
u2 = t * u # type: Any
hash_input = [e, e1, e2, v, v1, v2, u, u1, u2]
if metadata is not None:
hash_input.append(metadata)
h = hash_to_curvebn(*hash_input, params=params, hash_class=ExtendedKeccak)
########
z3 = t + h * rk
self.attach_proof(e2, v2, u1, u2, metadata=metadata, z3=z3, kfrag_signature=kfrag.signature_for_bob)
def verify_correctness(self, capsule) -> bool:
if self.proof is None:
raise CapsuleFrag.NoProofProvided
correctness_keys = capsule.get_correctness_keys()
delegating_pubkey = correctness_keys['delegating']
signing_pubkey = correctness_keys['verifying']
receiving_pubkey = correctness_keys['receiving']
params = capsule.params
####
# Here are the formulaic constituents shared with `prove_correctness`.
####
e = capsule.point_e
v = capsule.point_v
e1 = self.point_e1
v1 = self.point_v1
u = params.u
u1 = self.proof.point_kfrag_commitment
e2 = self.proof.point_e2
v2 = self.proof.point_v2
u2 = self.proof.point_kfrag_pok
hash_input = [e, e1, e2, v, v1, v2, u, u1, u2]
if self.proof.metadata is not None:
hash_input.append(self.proof.metadata)
h = hash_to_curvebn(*hash_input, params=params, hash_class=ExtendedKeccak)
########
precursor = self.point_precursor
kfrag_id = self.kfrag_id
validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, precursor)
kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
valid_kfrag_signature = self.proof.kfrag_signature.verify(kfrag_validity_message, signing_pubkey)
z3 = self.proof.bn_sig
correct_reencryption_of_e = z3 * e == e2 + (h * e1)
correct_reencryption_of_v = z3 * v == v2 + (h * v1)
correct_rk_commitment = z3 * u == u2 + (h * u1)
return valid_kfrag_signature \
& correct_reencryption_of_e \
& correct_reencryption_of_v \
& correct_rk_commitment
def attach_proof(self,
e2: Point,
v2: Point,
u1: Point,
u2: Point,
z3: CurveBN,
kfrag_signature: Signature,
metadata: Optional[bytes]) -> None:
self.proof = CorrectnessProof(point_e2=e2,
point_v2=v2,
point_kfrag_commitment=u1,
point_kfrag_pok=u2,
bn_sig=z3,
kfrag_signature=kfrag_signature,
metadata=metadata,
)
def __bytes__(self) -> bytes:
return self.to_bytes()
def __repr__(self):
return "CFrag:{}".format(self.point_e1.to_bytes().hex()[2:17])

View File

@ -1,77 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from typing import Optional, Type
from warnings import warn
from umbral.curve import Curve, SECP256K1
from umbral.params import UmbralParameters
class _CONFIG:
__curve = None
__params = None
__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER = SECP256K1
__WARNING_IF_NO_DEFAULT_SET = "No default curve has been set. " \
"Using SECP256K1. " \
"A slight performance penalty has been " \
"incurred for only this call. Set a default " \
"curve with umbral.config.set_default_curve()."
class UmbralConfigurationError(RuntimeError):
"""Raised when somebody does something dumb re: configuration."""
@classmethod
def __set_curve_by_default(cls):
warn(cls.__WARNING_IF_NO_DEFAULT_SET, RuntimeWarning)
cls.set_curve(cls.__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER)
@classmethod
def params(cls) -> UmbralParameters:
if not cls.__params:
cls.__set_curve_by_default()
return cls.__params # type: ignore
@classmethod
def curve(cls) -> Curve:
if not cls.__curve:
cls.__set_curve_by_default()
return cls.__curve # type: ignore
@classmethod
def set_curve(cls, curve: Optional[Curve] = None) -> None:
if cls.__curve:
raise cls.UmbralConfigurationError(
"You can only set the default curve once. Do it once and then leave it alone.")
else:
from umbral.params import UmbralParameters
if curve is None:
curve = _CONFIG.__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER
cls.__curve = curve
cls.__params = UmbralParameters(curve)
def set_default_curve(curve: Optional[Curve] = None) -> None:
return _CONFIG.set_curve(curve)
def default_curve() -> Curve:
return _CONFIG.curve()
def default_params() -> UmbralParameters:
return _CONFIG.params()

View File

@ -1,135 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from cryptography.hazmat.backends import default_backend
from umbral import openssl
class Curve:
"""
Acts as a container to store constant variables such as the OpenSSL
curve_nid, the EC_GROUP struct, and the order of the curve.
Contains a whitelist of supported elliptic curves used in pyUmbral.
"""
_supported_curves = {
415: 'secp256r1',
714: 'secp256k1',
715: 'secp384r1'
}
def __init__(self, nid: int) -> None:
"""
Instantiates an OpenSSL curve with the provided curve_nid and derives
the proper EC_GROUP struct and order. You can _only_ instantiate curves
with supported nids (see `Curve.supported_curves`).
"""
try:
self.__curve_name = self._supported_curves[nid]
except KeyError:
raise NotImplementedError("Curve NID {} is not supported.".format(nid))
# set only once
self.__curve_nid = nid
self.__ec_group = openssl._get_ec_group_by_curve_nid(self.__curve_nid)
self.__order = openssl._get_ec_order_by_group(self.ec_group)
self.__generator = openssl._get_ec_generator_by_group(self.ec_group)
# Init cache
self.__field_order_size_in_bytes = 0
self.__group_order_size_in_bytes = 0
@classmethod
def from_name(cls, name: str) -> 'Curve':
"""
Alternate constructor to generate a curve instance by its name.
Raises NotImplementedError if the name cannot be mapped to a known
supported curve NID.
"""
name = name.casefold() # normalize
for supported_nid, supported_name in cls._supported_curves.items():
if name == supported_name:
instance = cls(nid=supported_nid)
break
else:
message = "{} is not supported curve name.".format(name)
raise NotImplementedError(message)
return instance
def __eq__(self, other):
return self.__curve_nid == other.curve_nid
def __repr__(self):
return "<OpenSSL Curve(nid={}, name={})>".format(self.__curve_nid, self.__curve_name)
#
# Immutable Curve Data
#
@property
def field_order_size_in_bytes(self) -> int:
if not self.__field_order_size_in_bytes:
size_in_bits = openssl._get_ec_group_degree(self.__ec_group)
self.__field_order_size_in_bytes = (size_in_bits + 7) // 8
return self.__field_order_size_in_bytes
@property
def group_order_size_in_bytes(self) -> int:
if not self.__group_order_size_in_bytes:
BN_num_bytes = default_backend()._lib.BN_num_bytes
self.__group_order_size_in_bytes = BN_num_bytes(self.order)
return self.__group_order_size_in_bytes
@property
def curve_nid(self) -> int:
return self.__curve_nid
@property
def name(self) -> str:
return self.__curve_name
@property
def ec_group(self):
return self.__ec_group
@property
def order(self):
return self.__order
@property
def generator(self):
return self.__generator
#
# Global Curve Instances
#
SECP256R1 = Curve.from_name('secp256r1')
SECP256K1 = Curve.from_name('secp256k1')
SECP384R1 = Curve.from_name('secp384r1')
CURVES = (SECP256K1, SECP256R1, SECP384R1)

View File

@ -1,272 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from typing import Optional, Union, cast
from cryptography.hazmat.backends.openssl import backend
from umbral import openssl
from umbral.config import default_curve
from umbral.curve import Curve
class CurveBN:
"""
Represents an OpenSSL Bignum modulo the order of a curve. Some of these
operations will only work with prime numbers
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
constant time operations.
"""
def __init__(self, bignum, curve: Curve) -> None:
on_curve = openssl._bn_is_on_curve(bignum, curve)
if not on_curve:
raise ValueError("The provided BIGNUM is not on the provided curve.")
self.bignum = bignum
self.curve = curve
@classmethod
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
"""
Returns the size (in bytes) of a CurveBN given the curve,
which comes from the size of the order of the generated group.
If no curve is provided, it uses the default.
"""
curve = curve if curve is not None else default_curve()
return curve.group_order_size_in_bytes
@classmethod
def gen_rand(cls, curve: Optional[Curve] = None) -> 'CurveBN':
"""
Returns a CurveBN object with a cryptographically secure OpenSSL BIGNUM
based on the given curve.
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
constant time operations.
"""
curve = curve if curve is not None else default_curve()
new_rand_bn = openssl._get_new_BN()
rand_res = backend._lib.BN_rand_range(new_rand_bn, curve.order)
backend.openssl_assert(rand_res == 1)
if not openssl._bn_is_on_curve(new_rand_bn, curve):
new_rand_bn = cls.gen_rand(curve=curve)
return new_rand_bn
return cls(new_rand_bn, curve)
@classmethod
def from_int(cls, num: int, curve: Optional[Curve] = None) -> 'CurveBN':
"""
Returns a CurveBN object from a given integer on a curve.
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
constant time operations.
"""
curve = curve if curve is not None else default_curve()
conv_bn = openssl._int_to_bn(num, curve)
return cls(conv_bn, curve)
@classmethod
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CurveBN':
"""
Returns a CurveBN object from the given byte data that's within the size
of the provided curve's order.
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
constant time operations.
"""
curve = curve if curve is not None else default_curve()
size = backend._lib.BN_num_bytes(curve.order)
if len(data) != size:
raise ValueError("Expected {} B for CurveBNs".format(size))
bignum = openssl._bytes_to_bn(data)
return cls(bignum, curve)
def to_bytes(self) -> bytes:
"""
Returns the CurveBN as bytes.
"""
size = backend._lib.BN_num_bytes(self.curve.order)
return openssl._bn_to_bytes(self.bignum, size)
def __int__(self) -> int:
"""
Converts the CurveBN to a Python int.
"""
return backend._bn_to_int(self.bignum)
def __eq__(self, other) -> bool:
"""
Compares the two BIGNUMS or int.
"""
# TODO: Should this stay in or not?
if type(other) == int:
other = openssl._int_to_bn(other)
other = CurveBN(other, self.curve)
# -1 less than, 0 is equal to, 1 is greater than
return not bool(backend._lib.BN_cmp(self.bignum, other.bignum))
def __pow__(self, other: Union[int, 'CurveBN']) -> 'CurveBN':
"""
Performs a BN_mod_exp on two BIGNUMS.
WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
"""
# TODO: Should this stay in or not?
if type(other) == int:
other = openssl._int_to_bn(other)
other = CurveBN(other, self.curve)
other = cast('CurveBN', other) # This is just for mypy
power = openssl._get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx, openssl._tmp_bn_mont_ctx(self.curve.order) as bn_mont_ctx:
res = backend._lib.BN_mod_exp_mont(
power, self.bignum, other.bignum, self.curve.order, bn_ctx, bn_mont_ctx
)
backend.openssl_assert(res == 1)
return CurveBN(power, self.curve)
def __mul__(self, other) -> 'CurveBN':
"""
Performs a BN_mod_mul between two BIGNUMS.
"""
if type(other) != CurveBN:
return NotImplemented
product = openssl._get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.BN_mod_mul(
product, self.bignum, other.bignum, self.curve.order, bn_ctx
)
backend.openssl_assert(res == 1)
return CurveBN(product, self.curve)
def __truediv__(self, other: 'CurveBN') -> 'CurveBN':
"""
Performs a BN_div on two BIGNUMs (modulo the order of the curve).
WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
"""
product = openssl._get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
inv_other = backend._lib.BN_mod_inverse(
backend._ffi.NULL, other.bignum, self.curve.order, bn_ctx
)
backend.openssl_assert(inv_other != backend._ffi.NULL)
inv_other = backend._ffi.gc(inv_other, backend._lib.BN_clear_free)
res = backend._lib.BN_mod_mul(
product, self.bignum, inv_other, self.curve.order, bn_ctx
)
backend.openssl_assert(res == 1)
return CurveBN(product, self.curve)
def __add__(self, other : Union[int, 'CurveBN']) -> 'CurveBN':
"""
Performs a BN_mod_add on two BIGNUMs.
"""
if type(other) == int:
other = openssl._int_to_bn(other)
other = CurveBN(other, self.curve)
other = cast('CurveBN', other) # This is just for mypy
op_sum = openssl._get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.BN_mod_add(
op_sum, self.bignum, other.bignum, self.curve.order, bn_ctx
)
backend.openssl_assert(res == 1)
return CurveBN(op_sum, self.curve)
def __sub__(self, other : Union[int, 'CurveBN']) -> 'CurveBN':
"""
Performs a BN_mod_sub on two BIGNUMS.
"""
if type(other) == int:
other = openssl._int_to_bn(other)
other = CurveBN(other, self.curve)
other = cast('CurveBN', other) # This is just for mypy
diff = openssl._get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.BN_mod_sub(
diff, self.bignum, other.bignum, self.curve.order, bn_ctx
)
backend.openssl_assert(res == 1)
return CurveBN(diff, self.curve)
def __invert__(self) -> 'CurveBN':
"""
Performs a BN_mod_inverse.
WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
"""
with backend._tmp_bn_ctx() as bn_ctx:
inv = backend._lib.BN_mod_inverse(
backend._ffi.NULL, self.bignum, self.curve.order, bn_ctx
)
backend.openssl_assert(inv != backend._ffi.NULL)
inv = backend._ffi.gc(inv, backend._lib.BN_clear_free)
return CurveBN(inv, self.curve)
def __neg__(self) -> 'CurveBN':
"""
Computes the modular opposite (i.e., additive inverse) of a BIGNUM
"""
zero = backend._int_to_bn(0)
zero = backend._ffi.gc(zero, backend._lib.BN_clear_free)
the_opposite = openssl._get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.BN_mod_sub(
the_opposite, zero, self.bignum, self.curve.order, bn_ctx
)
backend.openssl_assert(res == 1)
return CurveBN(the_opposite, self.curve)
def __mod__(self, other: Union[int, 'CurveBN']) -> 'CurveBN':
"""
Performs a BN_nnmod on two BIGNUMS.
"""
if type(other) == int:
other = openssl._int_to_bn(other)
other = CurveBN(other, self.curve)
other = cast('CurveBN', other) # This is just for mypy
rem = openssl._get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.BN_nnmod(
rem, self.bignum, other.bignum, bn_ctx
)
backend.openssl_assert(res == 1)
return CurveBN(rem, self.curve)

View File

@ -1,58 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import os
from typing import Optional
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
DEM_KEYSIZE = 32
DEM_NONCE_SIZE = 12
class UmbralDEM:
def __init__(self, symm_key: bytes) -> None:
"""
Initializes an UmbralDEM object. Requires a key to perform
ChaCha20-Poly1305.
"""
if len(symm_key) != DEM_KEYSIZE:
raise ValueError(
"Invalid key size, must be {} bytes".format(DEM_KEYSIZE)
)
self.cipher = ChaCha20Poly1305(symm_key)
def encrypt(self, data: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
"""
Encrypts data using ChaCha20-Poly1305 with optional authenticated data.
"""
nonce = os.urandom(DEM_NONCE_SIZE)
enc_data = self.cipher.encrypt(nonce, data, authenticated_data)
# Ciphertext will be a 12 byte nonce, the ciphertext, and a 16 byte tag.
return nonce + enc_data
def decrypt(self, ciphertext: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
"""
Decrypts data using ChaCha20-Poly1305 and validates the provided
authenticated data.
"""
nonce = ciphertext[:DEM_NONCE_SIZE]
ciphertext = ciphertext[DEM_NONCE_SIZE:]
cleartext = self.cipher.decrypt(nonce, ciphertext, authenticated_data)
return cleartext

View File

@ -1,472 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import os
from typing import Callable, Optional, Any
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey, _EllipticCurvePublicKey
from cryptography.exceptions import InternalError
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt as CryptographyScrypt
from nacl.secret import SecretBox
from umbral import openssl
from umbral.config import default_params
from umbral.curvebn import CurveBN
from umbral.params import UmbralParameters
from umbral.point import Point
from umbral.curve import Curve
from umbral.random_oracles import hash_to_curvebn
__SALT_SIZE = 32
class Scrypt:
__DEFAULT_SCRYPT_COST = 20
def __call__(self,
password: bytes,
salt: bytes,
**kwargs) -> bytes:
"""
Derives a symmetric encryption key from a pair of password and salt.
It also accepts an additional _scrypt_cost argument.
WARNING: RFC7914 recommends that you use a 2^20 cost value for sensitive
files. It is NOT recommended to change the `_scrypt_cost` value unless
you know what you are doing.
:param password: byte-encoded password used to derive a symmetric key
:param salt: cryptographic salt added during key derivation
:return:
"""
_scrypt_cost = kwargs.get('_scrypt_cost', Scrypt.__DEFAULT_SCRYPT_COST)
try:
derived_key = CryptographyScrypt(
salt=salt,
length=SecretBox.KEY_SIZE,
n=2 ** _scrypt_cost,
r=8,
p=1,
backend=default_backend()
).derive(password)
except InternalError as e:
required_memory = 128 * 2**_scrypt_cost * 8 // (10**6)
if e.err_code[0].reason == 65:
raise MemoryError(
"Scrypt key derivation requires at least {} MB of memory. "
"Please free up some memory and try again.".format(required_memory)
)
else:
raise e
else:
return derived_key
def derive_key_from_password(password: bytes,
salt: bytes,
**kwargs) -> bytes:
"""
Derives a symmetric encryption key from a pair of password and salt.
It uses Scrypt by default.
"""
kdf = kwargs.get('kdf', Scrypt)()
derived_key = kdf(password, salt, **kwargs)
return derived_key
def wrap_key(key_to_wrap: bytes,
wrapping_key: Optional[bytes] = None,
password: Optional[bytes] = None,
**kwargs) -> bytes:
"""
Wraps a key using a provided wrapping key. Alternatively, it can derive
the wrapping key from a password.
:param key_to_wrap:
:param wrapping_key:
:param password:
:return:
"""
if not(bool(password) ^ bool(wrapping_key)):
raise ValueError("Either password or wrapping_key must be passed")
wrapped_key = b''
if password:
salt = os.urandom(__SALT_SIZE)
wrapping_key = derive_key_from_password(password=password,
salt=salt,
**kwargs)
wrapped_key = salt
wrapped_key += SecretBox(wrapping_key).encrypt(key_to_wrap)
return wrapped_key
def unwrap_key(wrapped_key: bytes,
wrapping_key: Optional[bytes] = None,
password: Optional[bytes] = None,
**kwargs) -> bytes:
"""
Unwraps a key using a provided wrapping key. Alternatively, it can derive
the wrapping key from a password.
:param wrapped_key:
:param wrapping_key:
:param password:
:return:
"""
if all((password, wrapping_key)) or not any((password, wrapping_key)):
raise ValueError("Either password or wrapping_key must be passed")
if password:
salt = wrapped_key[:__SALT_SIZE]
wrapped_key = wrapped_key[__SALT_SIZE:]
wrapping_key = derive_key_from_password(password=password,
salt=salt,
**kwargs)
key = SecretBox(wrapping_key).decrypt(wrapped_key)
return key
class UmbralPrivateKey:
def __init__(self, bn_key: CurveBN, params: UmbralParameters) -> None:
"""
Initializes an Umbral private key.
"""
self.params = params
self.bn_key = bn_key
self.pubkey = UmbralPublicKey(self.bn_key * params.g, params=params) # type: ignore
@classmethod
def gen_key(cls, params: Optional[UmbralParameters] = None) -> 'UmbralPrivateKey':
"""
Generates a private key and returns it.
"""
if params is None:
params = default_params()
bn_key = CurveBN.gen_rand(params.curve)
return cls(bn_key, params)
@classmethod
def from_bytes(cls,
key_bytes: bytes,
wrapping_key: Optional[bytes] = None,
password: Optional[bytes] = None,
params: Optional[UmbralParameters] = None,
decoder: Optional[Callable] = None,
**kwargs) -> 'UmbralPrivateKey':
"""
Loads an Umbral private key from bytes.
Optionally, allows a decoder function to be passed as a param to decode
the data provided before converting to an Umbral key.
Optionally, uses a wrapping key to unwrap an encrypted Umbral private key.
Alternatively, if a password is provided it will derive the wrapping key
from it.
"""
if params is None:
params = default_params()
if decoder:
key_bytes = decoder(key_bytes)
if any((wrapping_key, password)):
key_bytes = unwrap_key(wrapped_key=key_bytes,
wrapping_key=wrapping_key,
password=password,
**kwargs)
bn_key = CurveBN.from_bytes(key_bytes, params.curve)
return cls(bn_key, params)
def to_bytes(self,
wrapping_key: Optional[bytes] = None,
password: Optional[bytes] = None,
encoder: Optional[Callable] = None,
**kwargs) -> bytes:
"""
Returns an UmbralPrivateKey as bytes with optional symmetric
encryption via nacl's Salsa20-Poly1305.
If a password is provided instead of a wrapping key, it will use
Scrypt for key derivation.
Optionally, allows an encoder to be passed in as a param to encode the
data before returning it.
"""
key_bytes = self.bn_key.to_bytes()
if wrapping_key or password:
key_bytes = wrap_key(key_to_wrap=key_bytes,
wrapping_key=wrapping_key,
password=password,
**kwargs)
if encoder:
key_bytes = encoder(key_bytes)
return key_bytes
def get_pubkey(self) -> 'UmbralPublicKey':
"""
Calculates and returns the public key of the private key.
"""
return self.pubkey
def to_cryptography_privkey(self) -> _EllipticCurvePrivateKey:
"""
Returns a cryptography.io EllipticCurvePrivateKey from the Umbral key.
"""
backend = default_backend()
backend.openssl_assert(self.bn_key.curve.ec_group != backend._ffi.NULL)
backend.openssl_assert(self.bn_key.bignum != backend._ffi.NULL)
ec_key = backend._lib.EC_KEY_new()
backend.openssl_assert(ec_key != backend._ffi.NULL)
ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
set_group_result = backend._lib.EC_KEY_set_group(
ec_key, self.bn_key.curve.ec_group
)
backend.openssl_assert(set_group_result == 1)
set_privkey_result = backend._lib.EC_KEY_set_private_key(
ec_key, self.bn_key.bignum
)
backend.openssl_assert(set_privkey_result == 1)
# Get public key
point = openssl._get_new_EC_POINT(self.params.curve)
with backend._tmp_bn_ctx() as bn_ctx:
mult_result = backend._lib.EC_POINT_mul(
self.bn_key.curve.ec_group, point, self.bn_key.bignum,
backend._ffi.NULL, backend._ffi.NULL, bn_ctx
)
backend.openssl_assert(mult_result == 1)
set_pubkey_result = backend._lib.EC_KEY_set_public_key(ec_key, point)
backend.openssl_assert(set_pubkey_result == 1)
evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
return _EllipticCurvePrivateKey(backend, ec_key, evp_pkey)
class UmbralPublicKey:
def __init__(self, point_key: Point, params: UmbralParameters) -> None:
"""
Initializes an Umbral public key.
"""
self.params = params
if not isinstance(point_key, Point):
raise TypeError("point_key can only be a Point. Don't pass anything else.")
self.point_key = point_key
@classmethod
def from_bytes(cls,
key_bytes: bytes,
params: Optional[UmbralParameters] = None,
decoder: Optional[Callable] = None) -> 'UmbralPublicKey':
"""
Loads an Umbral public key from bytes.
Optionally, if an decoder function is provided it will be used to decode
the data before returning it as an Umbral key.
"""
if params is None:
params = default_params()
if decoder:
key_bytes = decoder(key_bytes)
point_key = Point.from_bytes(key_bytes, params.curve)
return cls(point_key, params)
@classmethod
def expected_bytes_length(cls, curve: Optional[Curve] = None,
is_compressed: bool = True) -> int:
"""
Returns the size (in bytes) of an UmbralPublicKey given a curve.
If no curve is provided, it uses the default curve.
By default, it assumes compressed representation (is_compressed = True).
"""
return Point.expected_bytes_length(curve=curve, is_compressed=is_compressed)
def to_bytes(self, encoder: Callable = None, is_compressed: bool = True) -> bytes:
"""
Returns an Umbral public key as bytes.
Optionally, if an encoder function is provided it will be used to encode
the data before returning it.
"""
umbral_pubkey = self.point_key.to_bytes(is_compressed=is_compressed)
if encoder:
umbral_pubkey = encoder(umbral_pubkey)
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.
"""
backend = default_backend()
backend.openssl_assert(self.point_key.curve.ec_group != backend._ffi.NULL)
backend.openssl_assert(self.point_key.ec_point != backend._ffi.NULL)
ec_key = backend._lib.EC_KEY_new()
backend.openssl_assert(ec_key != backend._ffi.NULL)
ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
set_group_result = backend._lib.EC_KEY_set_group(
ec_key, self.point_key.curve.ec_group
)
backend.openssl_assert(set_group_result == 1)
set_pubkey_result = backend._lib.EC_KEY_set_public_key(
ec_key, self.point_key.ec_point
)
backend.openssl_assert(set_pubkey_result == 1)
evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
return _EllipticCurvePublicKey(backend, ec_key, evp_pkey)
def __bytes__(self) -> bytes:
"""
Returns an Umbral Public key as a bytestring.
"""
return self.point_key.to_bytes()
def __repr__(self):
return "{}:{}".format(self.__class__.__name__, self.point_key.to_bytes().hex()[:15])
def __eq__(self, other: Any) -> bool:
if type(other) == bytes:
is_eq = bytes(other) == bytes(self)
elif hasattr(other, "point_key") and hasattr(other, "params"):
is_eq = (self.point_key, self.params) == (other.point_key, other.params)
else:
is_eq = False
return is_eq
def __hash__(self) -> int:
return int.from_bytes(self.to_bytes(), byteorder="big")
class UmbralKeyingMaterial:
"""
This class handles keying material for Umbral, by allowing deterministic
derivation of UmbralPrivateKeys based on labels.
Don't use this key material directly as a key.
"""
def __init__(self, keying_material: Optional[bytes] = None) -> None:
"""
Initializes an UmbralKeyingMaterial.
"""
if keying_material:
if len(keying_material) < 32:
raise ValueError("UmbralKeyingMaterial must have size at least 32 bytes.")
self.__keying_material = keying_material
else:
self.__keying_material = os.urandom(64)
def derive_privkey_by_label(self,
label: bytes,
salt: Optional[bytes] = None,
params: Optional[UmbralParameters] = None) -> UmbralPrivateKey:
"""
Derives an UmbralPrivateKey using a KDF from this instance of
UmbralKeyingMaterial, a label, and an optional salt.
"""
params = params if params is not None else default_params()
key_material = HKDF(
algorithm=hashes.BLAKE2b(64),
length=64,
salt=salt,
info=b"NuCypher/KeyDerivation/"+label,
backend=default_backend()
).derive(self.__keying_material)
bn_key = hash_to_curvebn(key_material, params=params)
return UmbralPrivateKey(bn_key, params)
@classmethod
def from_bytes(cls,
key_bytes: bytes,
wrapping_key: Optional[bytes] = None,
password: Optional[bytes] = None,
decoder: Optional[Callable] = None,
**kwargs) -> 'UmbralKeyingMaterial':
"""
Loads an UmbralKeyingMaterial from bytes.
Optionally, allows a decoder function to be passed as a param to decode
the data provided before converting to an Umbral key.
Optionally, uses a wrapping key to unwrap an encrypted UmbralKeyingMaterial.
Alternatively, if a password is provided it will derive the wrapping key
from it.
"""
if decoder:
key_bytes = decoder(key_bytes)
if any((password, wrapping_key)):
key_bytes = unwrap_key(wrapped_key=key_bytes,
wrapping_key=wrapping_key,
password=password,
**kwargs)
return cls(keying_material=key_bytes)
def to_bytes(self,
wrapping_key: Optional[bytes] = None,
password: Optional[bytes] = None,
encoder: Optional[Callable] = None,
**kwargs) -> bytes:
"""
Returns an UmbralKeyingMaterial as bytes with optional symmetric
encryption via nacl's Salsa20-Poly1305.
If a password is provided instead of a wrapping key, it will use
Scrypt for key derivation.
Optionally, allows an encoder to be passed in as a param to encode the
data before returning it.
"""
key_bytes = self.__keying_material
if any((password, wrapping_key)):
key_bytes = wrap_key(key_to_wrap=key_bytes,
wrapping_key=wrapping_key,
password=password,
**kwargs)
if encoder:
key_bytes = encoder(key_bytes)
return key_bytes

View File

@ -1,199 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import hmac
from typing import Optional
from bytestring_splitter import BytestringSplitter
from umbral.config import default_curve, default_params
from umbral.curvebn import CurveBN
from umbral.keys import UmbralPublicKey
from umbral.point import Point
from umbral.signing import Signature
from umbral.params import UmbralParameters
from umbral.curve import Curve
NO_KEY = b'\x00'
DELEGATING_ONLY = b'\x01'
RECEIVING_ONLY = b'\x02'
DELEGATING_AND_RECEIVING = b'\x03'
class KFrag:
def __init__(self,
identifier: bytes,
bn_key: CurveBN,
point_commitment: Point,
point_precursor: Point,
signature_for_proxy: Signature,
signature_for_bob: Signature,
keys_in_signature=DELEGATING_AND_RECEIVING,
) -> None:
self.id = identifier
self.bn_key = bn_key
self.point_commitment = point_commitment
self.point_precursor = point_precursor
self.signature_for_proxy = signature_for_proxy
self.signature_for_bob = signature_for_bob
self.keys_in_signature = keys_in_signature
class NotValid(ValueError):
"""
raised if the KFrag does not pass verification.
"""
@classmethod
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
"""
Returns the size (in bytes) of a KFrag given the curve.
If no curve is provided, it will use the default curve.
"""
curve = curve if curve is not None else default_curve()
bn_size = CurveBN.expected_bytes_length(curve)
point_size = Point.expected_bytes_length(curve)
# self.id --> 1 bn_size
# self.bn_key --> 1 bn_size
# self.point_commitment --> 1 point_size
# self.point_precursor --> 1 point_size
# self.signature_for_proxy --> 2 bn_size
# self.signature_for_bob --> 2 bn_size
# self.keys_in_signature --> 1
return bn_size * 6 + point_size * 2 + 1
@classmethod
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'KFrag':
"""
Instantiate a KFrag object from the serialized data.
"""
curve = curve if curve is not None else default_curve()
bn_size = CurveBN.expected_bytes_length(curve)
point_size = Point.expected_bytes_length(curve)
signature_size = Signature.expected_bytes_length(curve)
arguments = {'curve': curve}
splitter = BytestringSplitter(
bn_size, # id
(CurveBN, bn_size, arguments), # bn_key
(Point, point_size, arguments), # point_commitment
(Point, point_size, arguments), # point_precursor
1, # keys_in_signature
(Signature, signature_size, arguments), # signature_for_proxy
(Signature, signature_size, arguments), # signature_for_bob
)
components = splitter(data)
return cls(identifier=components[0],
bn_key=components[1],
point_commitment=components[2],
point_precursor=components[3],
keys_in_signature=components[4],
signature_for_proxy=components[5],
signature_for_bob=components[6])
def to_bytes(self) -> bytes:
"""
Serialize the KFrag into a bytestring.
"""
key = self.bn_key.to_bytes()
commitment = self.point_commitment.to_bytes()
precursor = self.point_precursor.to_bytes()
signature_for_proxy = bytes(self.signature_for_proxy)
signature_for_bob = bytes(self.signature_for_bob)
mode = bytes(self.keys_in_signature)
return self.id + key + commitment + precursor \
+ mode + signature_for_proxy + signature_for_bob
def verify(self,
signing_pubkey: UmbralPublicKey,
delegating_pubkey: Optional[UmbralPublicKey] = None,
receiving_pubkey: Optional[UmbralPublicKey] = None,
params: Optional[UmbralParameters] = None,
) -> bool:
if params is None:
params = default_params()
if signing_pubkey is None:
raise ValueError("The verifying pubkey is required to verify this KFrag.")
if self.delegating_key_in_signature():
if delegating_pubkey is None:
raise ValueError("The delegating pubkey is required to verify this KFrag.")
elif delegating_pubkey.params != params:
raise ValueError("The delegating pubkey has different UmbralParameters.")
if self.receiving_key_in_signature():
if receiving_pubkey is None:
raise ValueError("The receiving pubkey is required to verify this KFrag.")
elif receiving_pubkey.params != params:
raise ValueError("The receiving pubkey has different UmbralParameters.")
u = params.u
kfrag_id = self.id
key = self.bn_key
commitment = self.point_commitment
precursor = self.point_precursor
#  We check that the commitment is well-formed
correct_commitment = commitment == key * u
validity_input = [kfrag_id, commitment, precursor, self.keys_in_signature]
if self.delegating_key_in_signature():
validity_input.append(delegating_pubkey)
if self.receiving_key_in_signature():
validity_input.append(receiving_pubkey)
kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
valid_kfrag_signature = self.signature_for_proxy.verify(kfrag_validity_message, signing_pubkey)
return correct_commitment & valid_kfrag_signature
def verify_for_capsule(self, capsule) -> bool:
correctness_keys = capsule.get_correctness_keys()
return self.verify(params=capsule.params,
signing_pubkey=correctness_keys["verifying"],
delegating_pubkey=correctness_keys["delegating"],
receiving_pubkey=correctness_keys["receiving"])
def delegating_key_in_signature(self):
return self.keys_in_signature == DELEGATING_ONLY or \
self.keys_in_signature == DELEGATING_AND_RECEIVING
def receiving_key_in_signature(self):
return self.keys_in_signature == RECEIVING_ONLY or \
self.keys_in_signature == DELEGATING_AND_RECEIVING
def __bytes__(self) -> bytes:
return self.to_bytes()
def __eq__(self, other):
return hmac.compare_digest(bytes(self), bytes(other))
def __hash__(self):
return hash(bytes(self.id))
def __repr__(self):
return "{}:{}".format(self.__class__.__name__, self.id.hex()[:15])

View File

@ -1,218 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import typing
from contextlib import contextmanager
from cryptography.hazmat.backends.openssl import backend
@typing.no_type_check
def _get_new_BN(set_consttime_flag=True):
"""
Returns a new and initialized OpenSSL BIGNUM.
The set_consttime_flag is set to True by default. When this instance of a
CurveBN object has BN_FLG_CONSTTIME set, OpenSSL will use constant time
operations whenever this CurveBN is passed.
"""
new_bn = backend._lib.BN_new()
backend.openssl_assert(new_bn != backend._ffi.NULL)
new_bn = backend._ffi.gc(new_bn, backend._lib.BN_clear_free)
if set_consttime_flag:
backend._lib.BN_set_flags(new_bn, backend._lib.BN_FLG_CONSTTIME)
return new_bn
@typing.no_type_check
def _get_ec_group_by_curve_nid(curve_nid: int):
"""
Returns the group of a given curve via its OpenSSL nid. This must be freed
after each use otherwise it leaks memory.
"""
group = backend._lib.EC_GROUP_new_by_curve_name(curve_nid)
backend.openssl_assert(group != backend._ffi.NULL)
return group
@typing.no_type_check
def _get_ec_order_by_group(ec_group):
"""
Returns the order of a given curve via its OpenSSL EC_GROUP.
"""
ec_order = _get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_GROUP_get_order(ec_group, ec_order, bn_ctx)
backend.openssl_assert(res == 1)
return ec_order
@typing.no_type_check
def _get_ec_generator_by_group(ec_group):
"""
Returns the generator point of a given curve via its OpenSSL EC_GROUP.
"""
generator = backend._lib.EC_GROUP_get0_generator(ec_group)
backend.openssl_assert(generator != backend._ffi.NULL)
generator = backend._ffi.gc(generator, backend._lib.EC_POINT_clear_free)
return generator
@typing.no_type_check
def _get_ec_group_degree(ec_group):
"""
Returns the number of bits needed to represent the order of the finite
field upon the curve is based.
"""
return backend._lib.EC_GROUP_get_degree(ec_group)
@typing.no_type_check
def _bn_is_on_curve(check_bn, curve: 'Curve'):
"""
Checks if a given OpenSSL BIGNUM is within the provided curve's order.
Returns True if the provided BN is on the curve, or False if the BN is zero
or not on the curve.
"""
zero = backend._int_to_bn(0)
zero = backend._ffi.gc(zero, backend._lib.BN_clear_free)
check_sign = backend._lib.BN_cmp(check_bn, zero)
range_check = backend._lib.BN_cmp(check_bn, curve.order)
return check_sign == 1 and range_check == -1
@typing.no_type_check
def _int_to_bn(py_int: int, curve: 'Curve'=None, set_consttime_flag=True):
"""
Converts the given Python int to an OpenSSL BIGNUM. If a curve is
provided, it will check if the Python integer is within the order of that
curve. If it's not within the order, it will raise a ValueError.
If set_consttime_flag is set to True, OpenSSL will use constant time
operations when using this CurveBN.
"""
conv_bn = backend._int_to_bn(py_int)
conv_bn = backend._ffi.gc(conv_bn, backend._lib.BN_clear_free)
if curve:
on_curve = _bn_is_on_curve(conv_bn, curve)
if not on_curve:
raise ValueError("The Python integer given is not on the provided curve.")
if set_consttime_flag:
backend._lib.BN_set_flags(conv_bn, backend._lib.BN_FLG_CONSTTIME)
return conv_bn
@typing.no_type_check
def _bytes_to_bn(bytes_seq: bytes, set_consttime_flag=True):
"""
Converts the given byte sequence to an OpenSSL BIGNUM.
If set_consttime_flag is set to True, OpenSSL will use constant time
operations when using this BIGNUM.
"""
bn = _get_new_BN(set_consttime_flag)
backend._lib.BN_bin2bn(bytes_seq, len(bytes_seq), bn)
backend.openssl_assert(bn != backend._ffi.NULL)
return bn
@typing.no_type_check
def _bn_to_bytes(bignum, length : int = None):
"""
Converts the given OpenSSL BIGNUM into a Python bytes sequence.
If length is given, the return bytes will have such length.
If the BIGNUM doesn't fit, it raises a ValueError.
"""
if bignum is None or bignum == backend._ffi.NULL:
raise ValueError("Input BIGNUM must have a value")
bn_num_bytes = backend._lib.BN_num_bytes(bignum)
if length is None:
length = bn_num_bytes
elif bn_num_bytes > length:
raise ValueError("Input BIGNUM doesn't fit in {} B".format(length))
bin_ptr = backend._ffi.new("unsigned char []", length)
bin_len = backend._lib.BN_bn2bin(bignum, bin_ptr)
return bytes.rjust(backend._ffi.buffer(bin_ptr, bin_len)[:], length, b'\0')
@typing.no_type_check
def _get_new_EC_POINT(curve: 'Curve'):
"""
Returns a new and initialized OpenSSL EC_POINT given the group of a curve.
If __curve_nid is provided, it retrieves the group from the curve provided.
"""
new_point = backend._lib.EC_POINT_new(curve.ec_group)
backend.openssl_assert(new_point != backend._ffi.NULL)
new_point = backend._ffi.gc(new_point, backend._lib.EC_POINT_clear_free)
return new_point
@typing.no_type_check
def _get_EC_POINT_via_affine(affine_x, affine_y, curve: 'Curve'):
"""
Returns an EC_POINT given the group of a curve and the affine coordinates
provided.
"""
new_point = _get_new_EC_POINT(curve)
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_POINT_set_affine_coordinates_GFp(
curve.ec_group, new_point, affine_x, affine_y, bn_ctx
)
backend.openssl_assert(res == 1)
return new_point
@typing.no_type_check
def _get_affine_coords_via_EC_POINT(ec_point, curve: 'Curve'):
"""
Returns the affine coordinates of a given point on the provided ec_group.
"""
affine_x = _get_new_BN()
affine_y = _get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_POINT_get_affine_coordinates_GFp(
curve.ec_group, ec_point, affine_x, affine_y, bn_ctx
)
backend.openssl_assert(res == 1)
return (affine_x, affine_y)
@typing.no_type_check
@contextmanager
def _tmp_bn_mont_ctx(modulus):
"""
Initializes and returns a BN_MONT_CTX for Montgomery ops.
Requires a modulus to place in the Montgomery structure.
"""
bn_mont_ctx = backend._lib.BN_MONT_CTX_new()
backend.openssl_assert(bn_mont_ctx != backend._ffi.NULL)
# Don't set the garbage collector. Only free it when the context is done
# or else you'll get a null pointer error.
try:
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.BN_MONT_CTX_set(bn_mont_ctx, modulus, bn_ctx)
backend.openssl_assert(res == 1)
yield bn_mont_ctx
finally:
backend._lib.BN_MONT_CTX_free(bn_mont_ctx)

View File

@ -1,41 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from umbral.curve import Curve
class UmbralParameters:
def __init__(self, curve: Curve) -> None:
from umbral.point import Point
from umbral.random_oracles import unsafe_hash_to_point
self.curve = curve
self.CURVE_KEY_SIZE_BYTES = self.curve.field_order_size_in_bytes
self.g = Point.get_generator_from_curve(curve=curve)
g_bytes = self.g.to_bytes()
parameters_seed = b'NuCypher/UmbralParameters/'
self.u = unsafe_hash_to_point(g_bytes, self, parameters_seed + b'u')
def __eq__(self, other) -> bool:
# TODO: This is not comparing the order, which currently is an OpenSSL pointer
self_attributes = self.curve, self.g, self.CURVE_KEY_SIZE_BYTES, self.u
others_attributes = other.curve, other.g, other.CURVE_KEY_SIZE_BYTES, other.u
return self_attributes == others_attributes

View File

@ -1,211 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from typing import Optional, Tuple
from cryptography.hazmat.backends.openssl import backend
from umbral import openssl
from umbral.config import default_curve
from umbral.curve import Curve
from umbral.curvebn import CurveBN
class Point:
"""
Represents an OpenSSL EC_POINT except more Pythonic
"""
def __init__(self, ec_point, curve: Curve) -> None:
self.ec_point = ec_point
self.curve = curve
@classmethod
def expected_bytes_length(cls, curve: Optional[Curve] = None,
is_compressed: bool = True):
"""
Returns the size (in bytes) of a Point given a curve.
If no curve is provided, it uses the default curve.
By default, it assumes compressed representation (is_compressed = True).
"""
curve = curve if curve is not None else default_curve()
coord_size = curve.field_order_size_in_bytes
if is_compressed:
return 1 + coord_size
else:
return 1 + 2 * coord_size
@classmethod
def gen_rand(cls, curve: Optional[Curve] = None) -> 'Point':
"""
Returns a Point object with a cryptographically secure EC_POINT based
on the provided curve.
"""
curve = curve if curve is not None else default_curve()
rand_point = openssl._get_new_EC_POINT(curve)
rand_bn = CurveBN.gen_rand(curve).bignum
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_POINT_mul(
curve.ec_group, rand_point, backend._ffi.NULL, curve.generator,
rand_bn, bn_ctx
)
backend.openssl_assert(res == 1)
return cls(rand_point, curve)
@classmethod
def from_affine(cls, coords: Tuple[int, int], curve: Optional[Curve] = None) -> 'Point':
"""
Returns a Point object from the given affine coordinates in a tuple in
the format of (x, y) and a given curve.
"""
curve = curve if curve is not None else default_curve()
affine_x, affine_y = coords
if type(affine_x) == int:
affine_x = openssl._int_to_bn(affine_x, curve=None)
if type(affine_y) == int:
affine_y = openssl._int_to_bn(affine_y, curve=None)
ec_point = openssl._get_EC_POINT_via_affine(affine_x, affine_y, curve)
return cls(ec_point, curve)
def to_affine(self):
"""
Returns a tuple of Python ints in the format of (x, y) that represents
the point in the curve.
"""
affine_x, affine_y = openssl._get_affine_coords_via_EC_POINT(
self.ec_point, self.curve)
return (backend._bn_to_int(affine_x), backend._bn_to_int(affine_y))
@classmethod
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'Point':
"""
Returns a Point object from the given byte data on the curve provided.
"""
curve = curve if curve is not None else default_curve()
point = openssl._get_new_EC_POINT(curve)
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_POINT_oct2point(
curve.ec_group, point, data, len(data), bn_ctx);
backend.openssl_assert(res == 1)
return cls(point, curve)
def to_bytes(self, is_compressed: bool=True) -> bytes:
"""
Returns the Point serialized as bytes. It will return a compressed form
if is_compressed is set to True.
"""
length = self.expected_bytes_length(self.curve, is_compressed)
if is_compressed:
point_conversion_form = backend._lib.POINT_CONVERSION_COMPRESSED
else:
point_conversion_form = backend._lib.POINT_CONVERSION_UNCOMPRESSED
bin_ptr = backend._ffi.new("unsigned char[]", length)
with backend._tmp_bn_ctx() as bn_ctx:
bin_len = backend._lib.EC_POINT_point2oct(
self.curve.ec_group, self.ec_point, point_conversion_form,
bin_ptr, length, bn_ctx
)
backend.openssl_assert(bin_len != 0)
return bytes(backend._ffi.buffer(bin_ptr, bin_len)[:])
@classmethod
def get_generator_from_curve(cls, curve: Optional[Curve] = None) -> 'Point':
"""
Returns the generator Point from the given curve as a Point object.
"""
curve = curve if curve is not None else default_curve()
return cls(curve.generator, curve)
def __eq__(self, other):
"""
Compares two EC_POINTS for equality.
"""
with backend._tmp_bn_ctx() as bn_ctx:
is_equal = backend._lib.EC_POINT_cmp(
self.curve.ec_group, self.ec_point, other.ec_point, bn_ctx
)
backend.openssl_assert(is_equal != -1)
# 1 is not-equal, 0 is equal, -1 is error
return not bool(is_equal)
def __mul__(self, other: CurveBN) -> 'Point':
"""
Performs an EC_POINT_mul on an EC_POINT and a BIGNUM.
"""
# TODO: Check that both points use the same curve.
prod = openssl._get_new_EC_POINT(self.curve)
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_POINT_mul(
self.curve.ec_group, prod, backend._ffi.NULL,
self.ec_point, other.bignum, bn_ctx
)
backend.openssl_assert(res == 1)
return Point(prod, self.curve)
__rmul__ = __mul__
def __add__(self, other) -> 'Point':
"""
Performs an EC_POINT_add on two EC_POINTS.
"""
op_sum = openssl._get_new_EC_POINT(self.curve)
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_POINT_add(
self.curve.ec_group, op_sum, self.ec_point, other.ec_point, bn_ctx
)
backend.openssl_assert(res == 1)
return Point(op_sum, self.curve)
def __sub__(self, other):
"""
Performs subtraction by adding the inverse of the `other` to the point.
"""
return (self + (-other))
def __neg__(self) -> 'Point':
"""
Computes the additive inverse of a Point, by performing an
EC_POINT_invert on itself.
"""
inv = backend._lib.EC_POINT_dup(self.ec_point, self.curve.ec_group)
backend.openssl_assert(inv != backend._ffi.NULL)
inv = backend._ffi.gc(inv, backend._lib.EC_POINT_clear_free)
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_POINT_invert(
self.curve.ec_group, inv, bn_ctx
)
backend.openssl_assert(res == 1)
return Point(inv, self.curve)
def __bytes__(self) -> bytes:
return self.to_bytes()

View File

@ -1,518 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import typing
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.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.utils import poly_eval, lambda_coeff
class GenericUmbralError(Exception):
pass
class UmbralCorrectnessError(GenericUmbralError):
def __init__(self, message: str, offending_cfrags: List[CapsuleFrag]) -> None:
super().__init__(message)
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,
point_e: Point,
point_v: Point,
bn_sig: CurveBN,
) -> None:
self.params = params
if not all((isinstance(point_e, Point),
isinstance(point_v, Point),
isinstance(bn_sig, CurveBN))):
raise TypeError("Need valid point_e, point_v, and bn_sig to make a Capsule.")
self.point_e = point_e
self.point_v = point_v
self.bn_sig = bn_sig
self._attached_cfrags = set() # type: set
self._cfrag_correctness_keys = {
'delegating': None, 'receiving': None, 'verifying': None
} # type: dict
class NotValid(ValueError):
"""
raised if the capsule does not pass verification.
"""
@classmethod
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
"""
Returns the size (in bytes) of a Capsule given the curve.
If no curve is provided, it will use the default curve.
"""
curve = curve if curve is not None else default_curve()
bn_size = CurveBN.expected_bytes_length(curve)
point_size = Point.expected_bytes_length(curve)
return (bn_size * 1) + (point_size * 2)
@classmethod
def from_bytes(cls, capsule_bytes: bytes, params: UmbralParameters) -> 'Capsule':
"""
Instantiates a Capsule object from the serialized data.
"""
curve = params.curve
bn_size = CurveBN.expected_bytes_length(curve)
point_size = Point.expected_bytes_length(curve)
arguments = {'curve': curve}
if len(capsule_bytes) == cls.expected_bytes_length(curve):
splitter = BytestringSplitter(
(Point, point_size, arguments), # point_e
(Point, point_size, arguments), # point_v
(CurveBN, bn_size, arguments) # bn_sig
)
else:
raise ValueError("Byte string does not have a valid length for a Capsule")
components = splitter(capsule_bytes)
return cls(params, *components)
def _set_cfrag_correctness_key(self, key_type: str, key: Optional[UmbralPublicKey]) -> bool:
if key_type not in ("delegating", "receiving", "verifying"):
raise ValueError("You can only set 'delegating', 'receiving' or 'verifying' keys.")
current_key = self._cfrag_correctness_keys[key_type]
if current_key is None:
if key is None:
return False
elif self.params != key.params:
raise TypeError("You are trying to set a key with different UmbralParameters.")
else:
self._cfrag_correctness_keys[key_type] = key
return True
elif key in (None, current_key):
return False
else:
raise ValueError("The {} key is already set; you can't set it again.".format(key_type))
def get_correctness_keys(self) -> Dict[str, Union[UmbralPublicKey, None]]:
return dict(self._cfrag_correctness_keys)
def set_correctness_keys(self,
delegating: Optional[UmbralPublicKey] = None,
receiving: Optional[UmbralPublicKey] = None,
verifying: Optional[UmbralPublicKey] = None,
) -> Tuple[bool, bool, bool]:
delegating_key_details = self._set_cfrag_correctness_key(key_type="delegating", key=delegating)
receiving_key_details = self._set_cfrag_correctness_key(key_type="receiving", key=receiving)
verifying_key_details = self._set_cfrag_correctness_key(key_type="verifying", key=verifying)
return delegating_key_details, receiving_key_details, verifying_key_details
def to_bytes(self) -> bytes:
"""
Serialize the Capsule into a bytestring.
"""
e, v, s = self.components()
return e.to_bytes() + v.to_bytes() + s.to_bytes()
def verify(self) -> bool:
g = self.params.g
e, v, s = self.components()
h = hash_to_curvebn(e, v, params=self.params)
result = s * g == v + (h * e) # type: bool
return result
def attach_cfrag(self, cfrag: CapsuleFrag) -> None:
if cfrag.verify_correctness(self):
self._attached_cfrags.add(cfrag)
else:
error_msg = "CFrag is not correct and cannot be attached to the Capsule"
raise UmbralCorrectnessError(error_msg, [cfrag])
def clear_cfrags(self):
self._attached_cfrags = set()
def first_cfrag(self):
try:
return list(self._attached_cfrags)[0]
except IndexError:
raise TypeError("This Capsule doesn't have any CFrags attached. Ergo, you can't get the first one.")
def components(self) -> Tuple[Point, Point, CurveBN]:
return self.point_e, self.point_v, self.bn_sig
def __bytes__(self) -> bytes:
return self.to_bytes()
def __contains__(self, cfrag):
return cfrag in self._attached_cfrags
def __eq__(self, other) -> bool:
"""
Each component is compared to its counterpart in constant time per the __eq__ of Point and CurveBN.
"""
return hasattr(other, "components") and self.components() == other.components() and all(self.components())
@typing.no_type_check
def __hash__(self) -> int:
# In case this isn't obvious, don't use this as a secure hash. Use BLAKE2b or something.
component_bytes = tuple(component.to_bytes() for component in self.components())
return hash(component_bytes)
def __len__(self) -> int:
return len(self._attached_cfrags)
def __repr__(self):
return "{}:{}".format(self.__class__.__name__, hex(int(self.bn_sig))[2:17])
def generate_kfrags(delegating_privkey: UmbralPrivateKey,
receiving_pubkey: UmbralPublicKey,
threshold: int,
N: int,
signer: Signer,
sign_delegating_key: Optional[bool] = True,
sign_receiving_key: Optional[bool] = True,
) -> List[KFrag]:
"""
Creates a re-encryption key from Alice's delegating public key to Bob's
receiving public key, and splits it in KFrags, using Shamir's Secret Sharing.
Requires a threshold number of KFrags out of N.
Returns a list of N KFrags
"""
if threshold <= 0 or threshold > N:
raise ValueError('Arguments threshold and N must satisfy 0 < threshold <= N')
if delegating_privkey.params != receiving_pubkey.params:
raise ValueError("Keys must have the same parameter set.")
params = delegating_privkey.params
g = params.g
delegating_pubkey = delegating_privkey.get_pubkey()
bob_pubkey_point = receiving_pubkey.point_key
# The precursor point is used as an ephemeral public key in a DH key exchange,
# and the resulting shared secret 'dh_point' is used to derive other secret values
private_precursor = CurveBN.gen_rand(params.curve)
precursor = private_precursor * g # type: Any
dh_point = private_precursor * bob_pubkey_point
# Secret value 'd' allows to make Umbral non-interactive
d = hash_to_curvebn(precursor,
bob_pubkey_point,
dh_point,
bytes(constants.NON_INTERACTIVE),
params=params)
# Coefficients of the generating polynomial
coefficients = [delegating_privkey.bn_key * (~d)]
coefficients += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)]
bn_size = CurveBN.expected_bytes_length(params.curve)
kfrags = list()
for _ in range(N):
kfrag_id = os.urandom(bn_size)
# The index of the re-encryption key share (which in Shamir's Secret
# Sharing corresponds to x in the tuple (x, f(x)), with f being the
# generating polynomial), is used to prevent reconstruction of the
# re-encryption key without Bob's intervention
share_index = hash_to_curvebn(precursor,
bob_pubkey_point,
dh_point,
bytes(constants.X_COORDINATE),
kfrag_id,
params=params)
# The re-encryption key share is the result of evaluating the generating
# polynomial for the index value
rk = poly_eval(coefficients, share_index)
commitment = rk * params.u # type: Any
validity_message_for_bob = (kfrag_id,
delegating_pubkey,
receiving_pubkey,
commitment,
precursor,
) # type: Any
validity_message_for_bob = bytes().join(bytes(item) for item in validity_message_for_bob)
signature_for_bob = signer(validity_message_for_bob)
if sign_delegating_key and sign_receiving_key:
mode = DELEGATING_AND_RECEIVING
elif sign_delegating_key:
mode = DELEGATING_ONLY
elif sign_receiving_key:
mode = RECEIVING_ONLY
else:
mode = NO_KEY
validity_message_for_proxy = [kfrag_id, commitment, precursor, mode] # type: Any
if sign_delegating_key:
validity_message_for_proxy.append(delegating_pubkey)
if sign_receiving_key:
validity_message_for_proxy.append(receiving_pubkey)
validity_message_for_proxy = bytes().join(bytes(item) for item in validity_message_for_proxy)
signature_for_proxy = signer(validity_message_for_proxy)
kfrag = KFrag(identifier=kfrag_id,
bn_key=rk,
point_commitment=commitment,
point_precursor=precursor,
signature_for_proxy=signature_for_proxy,
signature_for_bob=signature_for_bob,
keys_in_signature=mode,
)
kfrags.append(kfrag)
return kfrags
def reencrypt(kfrag: KFrag,
capsule: Capsule,
provide_proof: bool = True,
metadata: Optional[bytes] = None,
verify_kfrag: bool = True) -> CapsuleFrag:
if not isinstance(capsule, Capsule) or not capsule.verify():
raise Capsule.NotValid
if verify_kfrag:
if not isinstance(kfrag, KFrag) or not kfrag.verify_for_capsule(capsule):
raise KFrag.NotValid
rk = kfrag.bn_key
e1 = rk * capsule.point_e # type: Any
v1 = rk * capsule.point_v # type: Any
cfrag = CapsuleFrag(point_e1=e1, point_v1=v1, kfrag_id=kfrag.id,
point_precursor=kfrag.point_precursor)
if provide_proof:
cfrag.prove_correctness(capsule, kfrag, metadata)
return cfrag
def _encapsulate(alice_pubkey: UmbralPublicKey,
key_length: int = DEM_KEYSIZE) -> Tuple[bytes, Capsule]:
"""Generates a symmetric key and its associated KEM ciphertext"""
params = alice_pubkey.params
g = params.g
priv_r = CurveBN.gen_rand(params.curve)
pub_r = priv_r * g # type: Any
priv_u = CurveBN.gen_rand(params.curve)
pub_u = priv_u * g # type: Any
h = hash_to_curvebn(pub_r, pub_u, params=params)
s = priv_u + (priv_r * h)
shared_key = (priv_r + priv_u) * alice_pubkey.point_key # type: Any
# 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, params=params)
def _decapsulate_original(private_key: UmbralPrivateKey,
capsule: Capsule,
key_length: int = DEM_KEYSIZE) -> bytes:
"""Derive the same symmetric key"""
if not capsule.verify():
# Check correctness of original ciphertext
raise capsule.NotValid("Capsule verification failed.")
shared_key = private_key.bn_key * (capsule.point_e + capsule.point_v) # type: Any
key = kdf(shared_key, key_length)
return key
def _decapsulate_reencrypted(receiving_privkey: UmbralPrivateKey, capsule: Capsule,
key_length: int = DEM_KEYSIZE) -> bytes:
"""Derive the same symmetric encapsulated_key"""
params = capsule.params
pub_key = receiving_privkey.get_pubkey().point_key
priv_key = receiving_privkey.bn_key
precursor = capsule.first_cfrag().point_precursor
dh_point = priv_key * precursor
# Combination of CFrags via Shamir's Secret Sharing reconstruction
xs = list()
for cfrag in capsule._attached_cfrags:
x = hash_to_curvebn(precursor,
pub_key,
dh_point,
bytes(constants.X_COORDINATE),
cfrag.kfrag_id,
params=params)
xs.append(x)
e_summands, v_summands = list(), list()
for cfrag, x in zip(capsule._attached_cfrags, xs):
if precursor != cfrag.point_precursor:
raise ValueError("Attached CFrags are not pairwise consistent")
lambda_i = lambda_coeff(x, xs)
e_summands.append(lambda_i * cfrag.point_e1)
v_summands.append(lambda_i * cfrag.point_v1)
e_prime = sum(e_summands[1:], e_summands[0])
v_prime = sum(v_summands[1:], v_summands[0])
# Secret value 'd' allows to make Umbral non-interactive
d = hash_to_curvebn(precursor,
pub_key,
dh_point,
bytes(constants.NON_INTERACTIVE),
params=params)
e, v, s = capsule.components()
h = hash_to_curvebn(e, v, params=params)
orig_pub_key = capsule.get_correctness_keys()['delegating'].point_key # type: ignore
if not (s / d) * orig_pub_key == (h * e_prime) + v_prime:
raise GenericUmbralError()
shared_key = d * (e_prime + v_prime)
encapsulated_key = kdf(shared_key, key_length)
return encapsulated_key
def encrypt(alice_pubkey: UmbralPublicKey, plaintext: bytes) -> Tuple[bytes, Capsule]:
"""
Performs an encryption using the UmbralDEM object and encapsulates a key
for the sender using the public key provided.
Returns the ciphertext and the KEM Capsule.
"""
key, capsule = _encapsulate(alice_pubkey, DEM_KEYSIZE)
capsule_bytes = bytes(capsule)
dem = UmbralDEM(key)
ciphertext = dem.encrypt(plaintext, authenticated_data=capsule_bytes)
return ciphertext, capsule
def _open_capsule(capsule: Capsule, receiving_privkey: UmbralPrivateKey,
check_proof: bool = True) -> bytes:
"""
Activates the Capsule from the attached CFrags,
opens the Capsule and returns what is inside.
This will often be a symmetric key.
"""
if check_proof:
offending_cfrags = []
for cfrag in capsule._attached_cfrags:
if not cfrag.verify_correctness(capsule):
offending_cfrags.append(cfrag)
if offending_cfrags:
error_msg = "Decryption error: Some CFrags are not correct"
raise UmbralCorrectnessError(error_msg, offending_cfrags)
key = _decapsulate_reencrypted(receiving_privkey, capsule)
return key
def decrypt(ciphertext: bytes, capsule: Capsule, decrypting_key: UmbralPrivateKey,
check_proof: bool = True) -> bytes:
"""
Opens the capsule and gets what's inside.
We hope that's a symmetric key, which we use to decrypt the ciphertext
and return the resulting cleartext.
"""
if not isinstance(ciphertext, bytes) or len(ciphertext) < DEM_NONCE_SIZE:
raise ValueError("Input ciphertext must be a bytes object of length >= {}".format(DEM_NONCE_SIZE))
elif not isinstance(capsule, Capsule) or not capsule.verify():
raise Capsule.NotValid
elif not isinstance(decrypting_key, UmbralPrivateKey):
raise TypeError("The decrypting key is not an UmbralPrivateKey")
if capsule._attached_cfrags:
# Since there are cfrags attached, we assume this is Bob opening the Capsule.
# (i.e., this is a re-encrypted capsule)
encapsulated_key = _open_capsule(capsule, decrypting_key, check_proof=check_proof)
else:
# Since there aren't cfrags attached, we assume this is Alice opening the Capsule.
# (i.e., this is an original capsule)
encapsulated_key = _decapsulate_original(decrypting_key, capsule)
dem = UmbralDEM(encapsulated_key)
try:
cleartext = dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
except InvalidTag as e:
raise UmbralDecryptionError() from e
return cleartext

View File

@ -1,214 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from abc import abstractmethod, ABC
from typing import Optional, Type
from cryptography.hazmat.backends.openssl import backend
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.exceptions import InternalError
import sha3
from umbral import openssl
from umbral.curvebn import CurveBN
from umbral.point import Point
from umbral.params import UmbralParameters
from umbral.config import default_params
class Hash(ABC):
CUSTOMIZATION_STRING_LENGTH = 64
CUSTOMIZATION_STRING_PAD = b'\x00'
@abstractmethod
def __init__(self, customization_string: bytes = b''):
if len(customization_string) > Hash.CUSTOMIZATION_STRING_LENGTH:
raise ValueError("The maximum length of the customization string is "
"{} bytes".format(Hash.CUSTOMIZATION_STRING_LENGTH))
self.customization_string = customization_string.ljust(
Hash.CUSTOMIZATION_STRING_LENGTH,
Hash.CUSTOMIZATION_STRING_PAD
)
self.update(self.customization_string)
@abstractmethod
def update(self, data: bytes) -> None:
raise NotImplementedError
@abstractmethod
def copy(self) -> 'Hash':
raise NotImplementedError
@abstractmethod
def finalize(self) -> bytes:
raise NotImplementedError
class Blake2b(Hash):
def __init__(self, customization_string: bytes = b''):
# TODO: use a Blake2b implementation that supports personalization (see #155)
self._blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend)
super().__init__(customization_string)
def update(self, data: bytes) -> None:
self._blake2b.update(data)
def copy(self) -> 'Blake2b':
replica = type(self)()
replica._blake2b = self._blake2b.copy()
return replica
def finalize(self) -> bytes:
return self._blake2b.finalize()
class ExtendedKeccak(Hash):
_UPPER_PREFIX = b'\x00'
_LOWER_PREFIX = b'\x01'
def __init__(self, customization_string: bytes = b''):
self._upper = sha3.keccak_256()
self._lower = sha3.keccak_256()
self._upper.update(self._UPPER_PREFIX)
self._lower.update(self._LOWER_PREFIX)
super().__init__(customization_string)
def update(self, data: bytes) -> None:
self._upper.update(data)
self._lower.update(data)
def copy(self) -> 'ExtendedKeccak':
replica = type(self)()
replica._upper = self._upper.copy()
replica._lower = self._lower.copy()
return replica
def finalize(self) -> bytes:
return self._upper.digest() + self._lower.digest()
def kdf(ecpoint: Point,
key_length: int,
salt: Optional[bytes] = None,
info: Optional[bytes] = None,
) -> bytes:
data = ecpoint.to_bytes(is_compressed=True)
hkdf = HKDF(algorithm=hashes.BLAKE2b(64),
length=key_length,
salt=salt,
info=info,
backend=default_backend())
return hkdf.derive(data)
# TODO: Common API for all hash_to_curvebn functions.
# TODO: ^ It should check the correct number and type args, instead of current approach.
def hash_to_curvebn(*crypto_items,
params: UmbralParameters,
customization_string: bytes = b'',
hash_class: Type[Hash] = Blake2b) -> CurveBN:
customization_string = b'hash_to_curvebn' + customization_string
hash_function = hash_class(customization_string=customization_string)
for item in crypto_items:
try:
item_bytes = item.to_bytes()
except AttributeError:
if isinstance(item, bytes):
item_bytes = item
else:
raise TypeError("Input with type {} not accepted".format(type(item)))
hash_function.update(item_bytes)
hash_digest = openssl._bytes_to_bn(hash_function.finalize())
one = backend._lib.BN_value_one()
order_minus_1 = openssl._get_new_BN()
res = backend._lib.BN_sub(order_minus_1, params.curve.order, one)
backend.openssl_assert(res == 1)
bignum = openssl._get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.BN_mod(bignum, hash_digest, order_minus_1, bn_ctx)
backend.openssl_assert(res == 1)
res = backend._lib.BN_add(bignum, bignum, one)
backend.openssl_assert(res == 1)
return CurveBN(bignum, params.curve)
def unsafe_hash_to_point(data: bytes = b'',
params: UmbralParameters = None,
label: bytes = b'',
hash_class = Blake2b,
) -> 'Point':
"""
Hashes arbitrary data into a valid EC point of the specified curve,
using the try-and-increment method.
It admits an optional label as an additional input to the hash function.
It uses BLAKE2b (with a digest size of 64 bytes) as the internal hash function.
WARNING: Do not use when the input data is secret, as this implementation is not
in constant time, and hence, it is not safe with respect to timing attacks.
"""
params = params if params is not None else default_params()
len_data = len(data).to_bytes(4, byteorder='big')
len_label = len(label).to_bytes(4, byteorder='big')
label_data = len_label + label + len_data + data
# We use an internal 32-bit counter as additional input
i = 0
while i < 2**32:
ibytes = i.to_bytes(4, byteorder='big')
hash_function = hash_class()
hash_function.update(label_data + ibytes)
hash_digest = hash_function.finalize()[:1 + params.CURVE_KEY_SIZE_BYTES]
sign = b'\x02' if hash_digest[0] & 1 == 0 else b'\x03'
compressed_point = sign + hash_digest[1:]
try:
return Point.from_bytes(compressed_point, params.curve)
except InternalError as e:
# We want to catch specific InternalExceptions:
# - Point not in the curve (code 107)
# - Invalid compressed point (code 110)
# https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228
if e.err_code[0].reason in (107, 110):
pass
else:
# Any other exception, we raise it
raise e
i += 1
# Only happens with probability 2^(-32)
raise ValueError('Could not hash input into the curve')

View File

@ -1,151 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published b
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import hmac
from typing import Optional, Type
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.hashes import HashAlgorithm, SHA256
from cryptography.hazmat.primitives.asymmetric import utils
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
from umbral.config import default_curve
from umbral.curve import Curve
from umbral.curvebn import CurveBN
from umbral.keys import UmbralPublicKey, UmbralPrivateKey
DEFAULT_HASH_ALGORITHM = SHA256
class Signature:
"""
Wrapper for ECDSA signatures.
We store signatures as r and s; this class allows interoperation
between (r, s) and DER formatting.
"""
def __init__(self,
r: CurveBN,
s: CurveBN,
hash_algorithm: Type[HashAlgorithm] = DEFAULT_HASH_ALGORITHM) -> None:
self.r = r
self.s = s
self.hash_algorithm = hash_algorithm
def __repr__(self):
return "ECDSA Signature: {}".format(bytes(self).hex()[:15])
@classmethod
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
curve = curve if curve is not None else default_curve()
return 2 * curve.group_order_size_in_bytes
def verify(self, message: bytes,
verifying_key: UmbralPublicKey,
is_prehashed: bool = False) -> bool:
"""
Verifies that a message's signature was valid.
:param message: The message to verify
:param verifying_key: UmbralPublicKey of the signer
:param is_prehashed: True if the message has been prehashed previously
:return: True if valid, False if invalid
"""
cryptography_pub_key = verifying_key.to_cryptography_pubkey()
if is_prehashed:
signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
else:
signature_algorithm = ECDSA(self.hash_algorithm())
# TODO: Raise error instead of returning boolean
try:
cryptography_pub_key.verify(
signature=self._der_encoded_bytes(),
data=message,
signature_algorithm=signature_algorithm
)
except InvalidSignature:
return False
return True
@classmethod
def from_bytes(cls,
signature_as_bytes: bytes,
der_encoded: bool = False,
curve: Optional[Curve] = None) -> 'Signature':
curve = curve if curve is not None else default_curve()
if der_encoded:
r, s = utils.decode_dss_signature(signature_as_bytes)
else:
expected_len = cls.expected_bytes_length(curve)
if not len(signature_as_bytes) == expected_len:
raise ValueError("Looking for exactly {} bytes if you call from_bytes \
with der_encoded=False and curve={}.".format(expected_len, curve))
else:
r = int.from_bytes(signature_as_bytes[:(expected_len//2)], "big")
s = int.from_bytes(signature_as_bytes[(expected_len//2):], "big")
return cls(CurveBN.from_int(r, curve), CurveBN.from_int(s, curve))
def _der_encoded_bytes(self) -> bytes:
return utils.encode_dss_signature(int(self.r), int(self.s))
def __bytes__(self) -> bytes:
return self.r.to_bytes() + self.s.to_bytes()
def __len__(self):
return len(bytes(self))
def __add__(self, other):
return bytes(self) + other
def __radd__(self, other: bytes) -> bytes:
return other + bytes(self)
def __eq__(self, other) -> bool:
simple_bytes_match = hmac.compare_digest(bytes(self), bytes(other))
der_encoded_match = hmac.compare_digest(self._der_encoded_bytes(), bytes(other))
return simple_bytes_match or der_encoded_match
class Signer:
"""Callable wrapping ECDSA signing with UmbralPrivateKeys"""
def __init__(self,
private_key: UmbralPrivateKey,
hash_algorithm: Type[HashAlgorithm] = DEFAULT_HASH_ALGORITHM) -> None:
self.__cryptography_private_key = private_key.to_cryptography_privkey()
self.curve = private_key.params.curve
self.hash_algorithm = hash_algorithm
def __call__(self, message: bytes, is_prehashed: bool = False) -> Signature:
"""
Signs the message with this instance's private key.
:param message: Message to hash and sign
:param is_prehashed: True if the message has been prehashed previously
:return: signature
"""
if is_prehashed:
signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
else:
signature_algorithm = ECDSA(self.hash_algorithm())
signature_der_bytes = self.__cryptography_private_key.sign(message, signature_algorithm)
return Signature.from_bytes(signature_der_bytes, der_encoded=True, curve=self.curve)

View File

@ -1,41 +0,0 @@
"""
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published b
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
from typing import List
from umbral.curvebn import CurveBN
def lambda_coeff(id_i: CurveBN, selected_ids: List[CurveBN]) -> CurveBN:
ids = [x for x in selected_ids if x != id_i]
if not ids:
return CurveBN.from_int(1, id_i.curve)
result = ids[0] / (ids[0] - id_i)
for id_j in ids[1:]:
result = result * id_j / (id_j - id_i)
return result
def poly_eval(coeff: List[CurveBN], x: CurveBN) -> CurveBN:
result = coeff[-1]
for i in range(-2, -len(coeff) - 1, -1):
result = (result * x) + coeff[i]
return result