def _mock_ursula_reencrypts(ursula): delegating_privkey = SecretKey.random() capsule, _ciphertext = encrypt(delegating_privkey.public_key(), b'unused') signing_privkey = SecretKey.random() signing_pubkey = signing_privkey.public_key() signer = Signer(signing_privkey) priv_key_bob = SecretKey.random() pub_key_bob = priv_key_bob.public_key() kfrags = generate_kfrags(delegating_sk=delegating_privkey, signer=signer, receiving_pk=pub_key_bob, threshold=2, num_kfrags=4, sign_delegating_key=False, sign_receiving_key=False) ursula_pubkey = ursula.stamp.as_umbral_pubkey() alice_address = canonical_address_from_umbral_key(signing_pubkey) blockhash = bytes(32) specification = b''.join((bytes(capsule), bytes(ursula_pubkey), bytes(ursula.decentralized_identity_evidence), alice_address, blockhash)) bobs_signer = Signer(priv_key_bob) task_signature = bytes(bobs_signer.sign(specification)) cfrag = reencrypt(capsule, kfrags[0]) cfrag_signature = ursula.stamp(bytes(cfrag)) bob = Bob.from_public_keys(verifying_key=pub_key_bob) return WorkOrder.PRETask(capsule, task_signature, cfrag, cfrag_signature)
def _mock_ursula_reencrypts(ursula, corrupt_cfrag: bool = False): delegating_privkey = UmbralPrivateKey.gen_key() _symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey()) signing_privkey = UmbralPrivateKey.gen_key() signing_pubkey = signing_privkey.get_pubkey() signer = Signer(signing_privkey) priv_key_bob = UmbralPrivateKey.gen_key() pub_key_bob = priv_key_bob.get_pubkey() kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey, signer=signer, receiving_pubkey=pub_key_bob, threshold=2, N=4, sign_delegating_key=False, sign_receiving_key=False) capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_pubkey) ursula_pubkey = ursula.stamp.as_umbral_pubkey() alice_address = canonical_address_from_umbral_key(signing_pubkey) blockhash = bytes(32) specification = b''.join((bytes(capsule), bytes(ursula_pubkey), bytes(ursula.decentralized_identity_evidence), alice_address, blockhash)) bobs_signer = Signer(priv_key_bob) task_signature = bytes(bobs_signer(specification)) metadata = bytes(ursula.stamp(task_signature)) cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata) if corrupt_cfrag: cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) cfrag_signature = bytes(ursula.stamp(bytes(cfrag))) bob = Bob.from_public_keys(verifying_key=pub_key_bob) task = WorkOrder.Task(capsule, task_signature, cfrag, cfrag_signature) work_order = WorkOrder(bob, None, alice_address, [task], None, ursula, blockhash) evidence = IndisputableEvidence(task, work_order) return evidence
def test_pre_task(mock_ursula_reencrypts, ursula, get_random_checksum_address): identity_evidence = ursula.decentralized_identity_evidence task = mock_ursula_reencrypts(ursula) cfrag = task.cfrag capsule = task.capsule capsule_bytes = bytes(capsule) signature = ursula.stamp(capsule_bytes) task = WorkOrder.PRETask(capsule=capsule, signature=signature) assert capsule == task.capsule assert signature == task.signature task_bytes = bytes(task) assert bytes(capsule) + bytes(signature) == task_bytes deserialized_task = WorkOrder.PRETask.from_bytes(task_bytes) assert capsule == deserialized_task.capsule assert signature == deserialized_task.signature # Attaching cfrags to the task cfrag_bytes = bytes(cfrag) cfrag_signature = ursula.stamp(cfrag_bytes) task.attach_work_result(cfrag, cfrag_signature) assert capsule == task.capsule assert signature == task.signature assert cfrag == task.cfrag assert cfrag_signature == task.cfrag_signature task_bytes = bytes(task) assert bytes(capsule) + bytes(signature) + cfrag_bytes + bytes( cfrag_signature) == task_bytes deserialized_task = WorkOrder.PRETask.from_bytes(task_bytes) assert capsule == deserialized_task.capsule assert signature == deserialized_task.signature assert bytes(cfrag) == bytes( deserialized_task.cfrag ) # We compare bytes as there's no CapsuleFrag.__eq__ assert cfrag_signature == deserialized_task.cfrag_signature # Task specification alice_address = to_canonical_address(get_random_checksum_address()) blockhash = os.urandom(ETH_HASH_BYTE_LENGTH) specification = task.get_specification(bytes(ursula.stamp), alice_address, blockhash, identity_evidence) expected_specification = bytes(capsule) + bytes( ursula.stamp) + identity_evidence + alice_address + blockhash assert expected_specification == specification with pytest.raises( ValueError, match=f"blockhash must be of length {ETH_HASH_BYTE_LENGTH}"): task.get_specification(bytes(ursula.stamp), alice_address, os.urandom(42), identity_evidence)
def reencrypt_via_rest(id_as_hex): from nucypher.policy.collections import WorkOrder # Avoid circular import arrangement_id = binascii.unhexlify(id_as_hex) try: with ThreadedSession(db_engine) as session: policy_arrangement = datastore.get_policy_arrangement( arrangement_id=id_as_hex.encode(), session=session) except NotFound: return Response(response=arrangement_id, status=404) kfrag_bytes = policy_arrangement.kfrag # Careful! :-) verifying_key_bytes = policy_arrangement.alice_verifying_key.key_data # TODO: Push this to a lower level. Perhaps to Ursula character? #619 kfrag = KFrag.from_bytes(kfrag_bytes) alices_verifying_key = UmbralPublicKey.from_bytes(verifying_key_bytes) alices_address = canonical_address_from_umbral_key( alices_verifying_key) work_order = WorkOrder.from_rest_payload(arrangement_id=arrangement_id, rest_payload=request.data, ursula=this_node, alice_address=alices_address) log.info( f"Work Order from {work_order.bob}, signed {work_order.receipt_signature}" ) cfrag_byte_stream = b"" for task in work_order.tasks: # Ursula signs on top of Bob's signature of each task. # Now both are committed to the same task. See #259. reencryption_metadata = bytes( this_node.stamp(bytes(task.signature))) capsule = task.capsule capsule.set_correctness_keys(verifying=alices_verifying_key) cfrag = pre.reencrypt(kfrag, capsule, metadata=reencryption_metadata) log.info(f"Re-encrypting for {capsule}, made {cfrag}.") # Finally, Ursula commits to her result reencryption_signature = this_node.stamp(bytes(cfrag)) cfrag_byte_stream += VariableLengthBytestring( cfrag) + reencryption_signature # TODO: Put this in Ursula's datastore this_node._work_orders.append(work_order) headers = {'Content-Type': 'application/octet-stream'} return Response(response=cfrag_byte_stream, headers=headers)
def reencrypt_via_rest(id_as_hex): # Get Policy Arrangement try: arrangement_id = binascii.unhexlify(id_as_hex) except (binascii.Error, TypeError): return Response(response=b'Invalid arrangement ID', status=405) try: with ThreadedSession(db_engine) as session: arrangement = datastore.get_policy_arrangement( arrangement_id=id_as_hex.encode(), session=session) except NotFound: return Response(response=arrangement_id, status=404) # Get KFrag # TODO: Yeah, well, what if this arrangement hasn't been enacted? 1702 kfrag = KFrag.from_bytes(arrangement.kfrag) # Get Work Order from nucypher.policy.collections import WorkOrder # Avoid circular import alice_verifying_key_bytes = arrangement.alice_verifying_key.key_data alice_verifying_key = UmbralPublicKey.from_bytes( alice_verifying_key_bytes) alice_address = canonical_address_from_umbral_key(alice_verifying_key) work_order_payload = request.data work_order = WorkOrder.from_rest_payload( arrangement_id=arrangement_id, rest_payload=work_order_payload, ursula=this_node, alice_address=alice_address) log.info( f"Work Order from {work_order.bob}, signed {work_order.receipt_signature}" ) # Re-encrypt response = this_node._reencrypt( kfrag=kfrag, work_order=work_order, alice_verifying_key=alice_verifying_key) # Now, Ursula saves this workorder to her database... with ThreadedSession(db_engine): this_node.datastore.save_workorder( bob_verifying_key=bytes(work_order.bob.stamp), bob_signature=bytes(work_order.receipt_signature), arrangement_id=work_order.arrangement_id) headers = {'Content-Type': 'application/octet-stream'} return Response(headers=headers, response=response)
def reencrypt_via_rest(id_as_hex): # Get Policy Arrangement try: arrangement_id = binascii.unhexlify(id_as_hex) except (binascii.Error, TypeError): return Response(response=b'Invalid arrangement ID', status=405) try: # Get KFrag # TODO: Yeah, well, what if this arrangement hasn't been enacted? 1702 with datastore.describe(PolicyArrangement, id_as_hex) as policy_arrangement: kfrag = policy_arrangement.kfrag alice_verifying_key = policy_arrangement.alice_verifying_key except RecordNotFound: return Response(response=arrangement_id, status=404) # Get Work Order from nucypher.policy.collections import WorkOrder # Avoid circular import alice_address = canonical_address_from_umbral_key(alice_verifying_key) work_order_payload = request.data work_order = WorkOrder.from_rest_payload( arrangement_id=arrangement_id, rest_payload=work_order_payload, ursula=this_node, alice_address=alice_address) log.info( f"Work Order from {work_order.bob}, signed {work_order.receipt_signature}" ) # Re-encrypt response = this_node._reencrypt( kfrag=kfrag, work_order=work_order, alice_verifying_key=alice_verifying_key) # Now, Ursula saves this workorder to her database... # Note: we give the work order a random ID to store it under. with datastore.describe(Workorder, str(uuid.uuid4()), writeable=True) as new_workorder: new_workorder.arrangement_id = work_order.arrangement_id new_workorder.bob_verifying_key = work_order.bob.stamp.as_umbral_pubkey( ) new_workorder.bob_signature = work_order.receipt_signature headers = {'Content-Type': 'application/octet-stream'} return Response(headers=headers, response=response)
def test_work_order_with_multiple_capsules(mock_ursula_reencrypts, ursula, get_random_checksum_address, federated_bob, federated_alice, number): tasks = [mock_ursula_reencrypts(ursula) for _ in range(number)] material = [(task.capsule, task.signature, task.cfrag, task.cfrag_signature) for task in tasks] capsules, signatures, cfrags, cfrag_signatures = zip(*material) arrangement_id = os.urandom(Arrangement.ID_LENGTH) alice_address = canonical_address_from_umbral_key(federated_alice.stamp) blockhash = b'\0' * ETH_HASH_BYTE_LENGTH # TODO: Prove freshness of work order - #259 identity_evidence = ursula.decentralized_identity_evidence # Test construction of WorkOrders by Bob work_order = WorkOrder.construct_by_bob( arrangement_id=arrangement_id, bob=federated_bob, alice_verifying=federated_alice.stamp.as_umbral_pubkey(), ursula=ursula, capsules=capsules) receipt_input = WorkOrder.HEADER + bytes(ursula.stamp) + b''.join( map(bytes, capsules)) bob_verifying_pubkey = federated_bob.stamp.as_umbral_pubkey() assert work_order.bob == federated_bob assert work_order.arrangement_id == arrangement_id assert work_order.alice_address == alice_address assert len(work_order.tasks) == len(work_order) == number for capsule in capsules: assert work_order.tasks[capsule].capsule == capsule task = WorkOrder.PRETask(capsule, signature=None) specification = task.get_specification(ursula.stamp, alice_address, blockhash, identity_evidence) assert work_order.tasks[capsule].signature.verify( bob_verifying_pubkey, specification) assert work_order.receipt_signature.verify(bob_verifying_pubkey, receipt_input) assert work_order.ursula == ursula assert work_order.blockhash == blockhash assert not work_order.completed # Test WorkOrders' payload serialization and deserialization tasks_bytes = b''.join(map(bytes, work_order.tasks.values())) expected_payload = bytes(work_order.receipt_signature) + bytes( federated_bob.stamp) + blockhash + tasks_bytes payload = work_order.payload() assert expected_payload == payload same_work_order = WorkOrder.from_rest_payload( arrangement_id=arrangement_id, rest_payload=payload, ursula=ursula, alice_address=alice_address) assert same_work_order.bob == federated_bob assert same_work_order.arrangement_id == arrangement_id assert same_work_order.alice_address == alice_address assert len(same_work_order.tasks) == len(same_work_order) == number for capsule in capsules: assert same_work_order.tasks[capsule].capsule == capsule assert same_work_order.tasks[capsule].signature == work_order.tasks[ capsule].signature assert same_work_order.receipt_signature == work_order.receipt_signature assert same_work_order.ursula == ursula assert same_work_order.blockhash == blockhash assert not same_work_order.completed tampered_payload = bytearray(payload) somewhere_over_the_blockhash = 64 + 33 + 5 tampered_payload[somewhere_over_the_blockhash] = 255 - payload[ somewhere_over_the_blockhash] with pytest.raises(InvalidSignature): _ = WorkOrder.from_rest_payload(arrangement_id=arrangement_id, rest_payload=bytes(tampered_payload), ursula=ursula, alice_address=alice_address) # Testing WorkOrder.complete() # Let's use the original task signatures in our WorkOrder, instead for capsule, task_signature in zip(capsules, signatures): work_order.tasks[capsule].signature = task_signature # Now, complete() works as intended good_cfrags = work_order.complete(list(zip(cfrags, cfrag_signatures))) assert work_order.completed assert len(good_cfrags) == number # Testing some additional expected exceptions with pytest.raises(ValueError, match="Ursula gave back the wrong number of cfrags"): work_order.complete(list(zip(cfrags, cfrag_signatures))[1:]) bad_cfrag_signature = ursula.stamp(os.urandom(10)) with pytest.raises(InvalidSignature, match=f"{cfrags[0]} is not properly signed by Ursula."): work_order.complete( list( zip(cfrags, [bad_cfrag_signature] + list(cfrag_signatures[1:]))))