From 0ddfa230def4a269241d92258a7f64f212bdb52a Mon Sep 17 00:00:00 2001 From: tuxxy Date: Thu, 15 Nov 2018 00:42:06 -0700 Subject: [PATCH] Refactor to use new RevocationKit --- nucypher/characters/lawful.py | 35 +++++++++++-------- nucypher/network/server.py | 26 ++++++++++---- nucypher/policy/models.py | 3 ++ nucypher/utilities/sandbox/middleware.py | 15 ++++++++ .../test_alice_can_grant_and_revoke.py | 1 + 5 files changed, 58 insertions(+), 22 deletions(-) diff --git a/nucypher/characters/lawful.py b/nucypher/characters/lawful.py index b31a3bb9a..440b1fdd6 100644 --- a/nucypher/characters/lawful.py +++ b/nucypher/characters/lawful.py @@ -170,22 +170,27 @@ class Alice(Character, PolicyAuthor): Parses the treasure map and revokes arrangements in it. If any arrangements can't be revoked (other than a 404 status), then the node_id and arrangement_id are added to a list and returned. - - TODO: How do we tell the user that a revocation was successful or not? """ - failed_revocations = list() - for node_id, arrangement_id in policy.treasure_map: - # TODO: What about nodes we don't know about? - ursula = self._known_nodes[node_id] - try: - self.network_middleware.revoke_arrangement(ursula, arrangement_id) - except RuntimeError as e: - # Check the status code of the response to determine what to do - # TODO: What do we do in the event of a 404? Is there are way - # to check if it's a real 404 and not SuspiciousActivity? - if e[1] != 404: - failed_revocations.append((node_id, arrangement_id)) - continue + # Sign the revocations in the RevocationKit in preparation for + # sending to Ursula. + policy.revocation_kit.sign_revocations(self.stamp) + + try: + # Wait for a revocation threshold of nodes to be known ((n - m) + 1) + revocation_threshold = ((policy.n - policy.treasure_map.m) + 1) + self.block_until_specific_nodes_are_known( + policy.revocation_kit.revokable_addresses, + allow_missing=revocation_threshold) + except self.NotEnoughTeachers as e: + raise e + else: + failed_revocations = list() + for node_id in policy.revocation_kit.revokable_addresses: + ursula = self.known_nodes[node_id] + revocation = policy.revocation_kit[node_id] + response = self.network_middleware.revoke_arrangement(ursula, revocation) + if response.status_code != 200: + failed_revocations.append(notice) return failed_revocations diff --git a/nucypher/network/server.py b/nucypher/network/server.py index 2913d658d..351e0721b 100644 --- a/nucypher/network/server.py +++ b/nucypher/network/server.py @@ -261,15 +261,27 @@ class ProxyRESTRoutes: TODO: How do we want to verify that this request comes from Alice? What/How is she going to sign? """ - arrangement_id = request.body + from nucypher.crypto.kits import RevocationKit + + revocation = RevocationKit.revocation_from_bytes(request.body) try: with ThreadedSession(self.db_engine) as session: - self.datastore.del_policy_arrangement( - arrangement_id, - session=session) - except NotFound: - return 404 # TODO: Should we 404 or do something else? - return 200 + # Verify the Notice was signed by Alice + policy_arrangement = self.datastore.get_policy_arrangement( + id_as_hex.encode(), session=session) + alice_pubkey = UmbralPublicKey.from_bytes( + policy_arrangement.alice_pubkey_sig.key_data) + + # Check that the request is the same for the provided revocation + if not id_as_hex.encode() == revocation.arrangement_id: + return 400 + elif RevocationKit.verify_revocation(revocation, alice_pubkey): + self.datastore.del_policy_arrangement( + id_as_hex.encode(), session=session) + except (NotFound, InvalidSignature): + return 404 + else: + return 200 def reencrypt_via_rest(self, id_as_hex, request: http.Request): from nucypher.policy.models import WorkOrder # Avoid circular import diff --git a/nucypher/policy/models.py b/nucypher/policy/models.py index 6933d671f..93c4670aa 100644 --- a/nucypher/policy/models.py +++ b/nucypher/policy/models.py @@ -248,6 +248,9 @@ class Policy: self.treasure_map.add_arrangement(arrangement) else: # ...After *all* the policies are enacted + # Create Alice's revocation kit + self.revocation_kit = RevocationKit(self.treasure_map) + if publish is True: return self.publish(network_middleware) diff --git a/nucypher/utilities/sandbox/middleware.py b/nucypher/utilities/sandbox/middleware.py index 45b311a80..29b7e2f77 100644 --- a/nucypher/utilities/sandbox/middleware.py +++ b/nucypher/utilities/sandbox/middleware.py @@ -14,9 +14,12 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with nucypher. If not, see . """ + + from apistar import TestClient from nucypher.characters.lawful import Ursula +from nucypher.crypto.kits import RevocationKit from nucypher.network.middleware import RestMiddleware from nucypher.utilities.sandbox.constants import TEST_KNOWN_URSULAS_CACHE @@ -107,6 +110,18 @@ class MockRestMiddleware(RestMiddleware): data=map_payload, verify=certificate_filepath) return response + def revoke_arrangement(self, ursula, revocation): + mock_client = self._get_mock_client_by_ursula(ursula) + response = mock_client.delete('http://localhost/kFrag/{}'.format( + revocation.arrangement_id.hex()), + data=RevocationKit.revocation_to_bytes(revocation)) + + if response.status_code != 200: + if response.status_code == 404: + raise RuntimeError("KFrag doesn't exist to revoke with id {}".format(arrangement_id)) + raise RuntimeError("Bad response: {}".format(response.status_code)) + return response + class EvilMiddleWare(MockRestMiddleware): """ diff --git a/tests/characters/test_alice_can_grant_and_revoke.py b/tests/characters/test_alice_can_grant_and_revoke.py index da7f6574c..3d444e2d4 100644 --- a/tests/characters/test_alice_can_grant_and_revoke.py +++ b/tests/characters/test_alice_can_grant_and_revoke.py @@ -27,6 +27,7 @@ from nucypher.characters.lawful import Bob, Ursula from nucypher.config.characters import AliceConfiguration from nucypher.config.storages import LocalFileBasedNodeStorage from nucypher.crypto.api import keccak_digest +from nucypher.crypto.kits import RevocationKit from nucypher.crypto.powers import SigningPower, DelegatingPower, EncryptingPower from nucypher.utilities.sandbox.constants import TEST_URSULA_INSECURE_DEVELOPMENT_PASSWORD from nucypher.utilities.sandbox.middleware import MockRestMiddleware