mirror of https://github.com/nucypher/nucypher.git
commit
523ea089f4
|
@ -17,11 +17,12 @@ COPY ./nucypher/blockchain/eth/sol/__conf__.py /install/nucypher/blockchain/eth/
|
|||
COPY scripts/installation/install_solc.py /install/scripts/installation/
|
||||
COPY dev-requirements.txt /install
|
||||
COPY requirements.txt /install
|
||||
COPY docs-requirements.txt /install
|
||||
COPY dev/docker/scripts/install/entrypoint.sh /install
|
||||
|
||||
# install reqs and solc
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip3 install -r /install/dev-requirements.txt --src /usr/local/src
|
||||
RUN pip3 install .[dev] --src /usr/local/src
|
||||
RUN pip3 install ipdb
|
||||
|
||||
# puts the nucypher executable in bin path
|
||||
|
|
|
@ -29,7 +29,7 @@ The NuCypher network does not store or handle an application's data; instead - i
|
|||
Management of encrypted secrets and public keys tends to be highly domain-specific - The surrounding architecture
|
||||
will vary greatly depending on the throughput, sensitivity, and sharing cadence of application secrets.
|
||||
In all cases, NuCypher must be integrated with a storage and transport layer in order to function properly.
|
||||
Along with the transport of ciphertexts, a nucypher application also needs to include channels for Alice and Bob
|
||||
Along with the transport of ciphertexts, a nucypher application also needs to include channels for Alice and Bob
|
||||
to discover each other's public keys, and provide policy encrypting information to Bob and Enrico.
|
||||
|
||||
Side Channel Application Data
|
||||
|
@ -93,7 +93,7 @@ Connecting Nucypher to an Ethereum Provider
|
|||
Ursula: Untrusted Re-Encryption Proxies
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When initializing an ``Alice``\ , ``Bob``\ , or ``Ursula``\ , an initial "Stranger-\ ``Ursula``\ " is needed to perform
|
||||
When initializing an ``Alice``\ , ``Bob``\ , or ``Ursula``\ , an initial "Stranger-\ ``Ursula``\ " is needed to perform
|
||||
the role of a ``Teacher``\ , or "seednode":
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -151,7 +151,7 @@ Create a NuCypher Keyring
|
|||
alice.start_learning_loop(now=True)
|
||||
|
||||
|
||||
Alice needs to know about Bob in order to grant access by acquiring Bob's public key's through
|
||||
Alice needs to know about Bob in order to grant access by acquiring Bob's public key's through
|
||||
the application side channel:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -201,7 +201,7 @@ Encrypt
|
|||
from nucypher.characters.lawful import Enrico
|
||||
|
||||
enrico = Enrico(policy_encrypting_key=policy_encrypting_key)
|
||||
ciphertext, signature = enrico.encrypt_message(message=b'Peace at dawn.')
|
||||
ciphertext, signature = enrico.encrypt_message(plaintext=b'Peace at dawn.')
|
||||
|
||||
|
||||
The ciphertext can then be sent to Bob via the application side channel.
|
||||
|
|
|
@ -55,7 +55,7 @@ def mario_box_cli(plaintext_dir, alice_config, label, outfile):
|
|||
encoded_plaintext = base64.b64encode(plaintext)
|
||||
|
||||
enrico = Enrico(policy_encrypting_key=policy_encrypting_key)
|
||||
message_kit, _signature = enrico.encrypt_message(message=encoded_plaintext)
|
||||
message_kit, _signature = enrico.encrypt_message(plaintext=encoded_plaintext)
|
||||
base64_message_kit = base64.b64encode(bytes(message_kit)).decode()
|
||||
|
||||
# Collect Bob Retrieve JSON Requests
|
||||
|
|
|
@ -60,7 +60,6 @@ class CharacterControllerBase(ABC):
|
|||
method = getattr(self.interface, action, None)
|
||||
serializer = method._schema
|
||||
params = serializer.load(request) # input validation will occur here.
|
||||
|
||||
response = method(**params) # < ---- INLET
|
||||
|
||||
response_data = serializer.dump(response)
|
||||
|
@ -139,10 +138,11 @@ class CLIController(CharacterControlServer):
|
|||
def test_client(self):
|
||||
return
|
||||
|
||||
def handle_request(self, method_name, request):
|
||||
def handle_request(self, method_name, request) -> dict:
|
||||
start = maya.now()
|
||||
response = self._perform_action(action=method_name, request=request)
|
||||
return self.emitter.ipc(response=response, request_id=start.epoch, duration=maya.now() - start)
|
||||
self.emitter.ipc(response=response, request_id=start.epoch, duration=maya.now() - start)
|
||||
return response
|
||||
|
||||
|
||||
class JSONRPCController(CharacterControlServer):
|
||||
|
|
|
@ -24,6 +24,7 @@ from nucypher.characters.control.specifications import alice, bob, enrico
|
|||
from nucypher.crypto.kits import UmbralMessageKit
|
||||
from nucypher.crypto.powers import DecryptingPower, SigningPower
|
||||
from nucypher.crypto.utils import construct_policy_id
|
||||
from nucypher.datastore.datastore import NotFound
|
||||
|
||||
|
||||
def attach_schema(schema):
|
||||
|
@ -241,11 +242,11 @@ class BobInterface(CharacterPublicInterface):
|
|||
class EnricoInterface(CharacterPublicInterface):
|
||||
|
||||
@attach_schema(enrico.EncryptMessage)
|
||||
def encrypt_message(self, message: str):
|
||||
def encrypt_message(self, plaintext: Union[str, bytes]):
|
||||
"""
|
||||
Character control endpoint for encrypting data for a policy and
|
||||
receiving the messagekit (and signature) to give to Bob.
|
||||
"""
|
||||
message_kit, signature = self.character.encrypt_message(bytes(message, encoding='utf-8'))
|
||||
message_kit, signature = self.character.encrypt_message(plaintext=plaintext)
|
||||
response_data = {'message_kit': message_kit, 'signature': signature}
|
||||
return response_data
|
||||
|
|
|
@ -16,25 +16,52 @@
|
|||
"""
|
||||
|
||||
import click
|
||||
from marshmallow import post_load
|
||||
|
||||
from nucypher.characters.control.specifications import fields
|
||||
from nucypher.characters.control.specifications.base import BaseSchema
|
||||
from nucypher.characters.control.specifications import fields, exceptions
|
||||
from nucypher.cli import options
|
||||
from nucypher.cli.types import EXISTING_READABLE_FILE
|
||||
from nucypher.characters.control.specifications.base import BaseSchema
|
||||
|
||||
|
||||
class EncryptMessage(BaseSchema):
|
||||
|
||||
# input
|
||||
message = fields.Cleartext(
|
||||
required=True, load_only=True,
|
||||
load_only=True,
|
||||
allow_none=True,
|
||||
click=click.option('--message', help="A unicode message to encrypt for a policy")
|
||||
)
|
||||
|
||||
file = fields.FileField(
|
||||
load_only=True,
|
||||
allow_none=True,
|
||||
click=click.option('--file', help="Filepath to plaintext file to encrypt", type=EXISTING_READABLE_FILE)
|
||||
)
|
||||
|
||||
policy_encrypting_key = fields.Key(
|
||||
required=False,
|
||||
load_only=True,
|
||||
click=options.option_policy_encrypting_key())
|
||||
click=options.option_policy_encrypting_key()
|
||||
)
|
||||
|
||||
@post_load()
|
||||
def format_method_arguments(self, data, **kwargs):
|
||||
"""
|
||||
input can be through either the file input or a raw message,
|
||||
we output one of them as the "plaintext" arg to enrico.encrypt_message
|
||||
"""
|
||||
|
||||
if data.get('message') and data.get('file'):
|
||||
raise exceptions.InvalidArgumentCombo("choose either a message or a filepath but not both.")
|
||||
|
||||
if data.get('message'):
|
||||
data = bytes(data['message'], encoding='utf-8')
|
||||
else:
|
||||
data = data['file']
|
||||
|
||||
return {"plaintext": data}
|
||||
|
||||
# output
|
||||
message_kit = fields.UmbralMessageKit(dump_only=True)
|
||||
signature = fields.String(dump_only=True) # maybe we need a signature field?
|
||||
signature = fields.UmbralSignature(dump_only=True)
|
||||
|
|
|
@ -22,3 +22,5 @@ from nucypher.characters.control.specifications.fields.datetime import *
|
|||
from nucypher.characters.control.specifications.fields.label import *
|
||||
from nucypher.characters.control.specifications.fields.cleartext import *
|
||||
from nucypher.characters.control.specifications.fields.misc import *
|
||||
from nucypher.characters.control.specifications.fields.file import *
|
||||
from nucypher.characters.control.specifications.fields.signature import *
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
from marshmallow import fields
|
||||
|
||||
from nucypher.characters.control.specifications.exceptions import InvalidInputData, InvalidNativeDataTypes
|
||||
from nucypher.characters.control.specifications.fields.base import BaseField
|
||||
|
||||
|
||||
class FileField(BaseField, fields.String):
|
||||
|
||||
def _deserialize(self, value, attr, data, **kwargs):
|
||||
with open(value, 'rb') as plaintext_file:
|
||||
plaintext = plaintext_file.read() # TODO: #2106 Handle large files
|
||||
return plaintext
|
||||
|
||||
def _validate(self, value):
|
||||
return os.path.exists(value) and os.path.isfile(value)
|
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from base64 import b64decode, b64encode
|
||||
|
||||
from marshmallow import fields
|
||||
from umbral.signing import Signature
|
||||
|
||||
from nucypher.characters.control.specifications.exceptions import InvalidInputData, InvalidNativeDataTypes
|
||||
from nucypher.characters.control.specifications.fields.base import BaseField
|
||||
|
||||
|
||||
class UmbralSignature(BaseField, fields.Field):
|
||||
|
||||
def _serialize(self, value: Signature, attr, obj, **kwargs):
|
||||
return b64encode(bytes(value)).decode()
|
||||
|
||||
def _deserialize(self, value, attr, data, **kwargs):
|
||||
if isinstance(value, bytes):
|
||||
return value
|
||||
try:
|
||||
return Signature.from_bytes(b64decode(value))
|
||||
except InvalidNativeDataTypes as e:
|
||||
raise InvalidInputData(f"Could not parse {self.name}: {e}")
|
||||
|
||||
def _validate(self, value):
|
||||
try:
|
||||
Signature.from_bytes(value)
|
||||
except InvalidNativeDataTypes as e:
|
||||
raise InvalidInputData(f"Could not parse {self.name}: {e}")
|
|
@ -791,15 +791,15 @@ class Bob(Character):
|
|||
self.get_reencrypted_cfrags(work_order, retain_cfrags=retain_cfrags)
|
||||
except NodeSeemsToBeDown as e:
|
||||
# TODO: What to do here? Ursula isn't supposed to be down. NRN
|
||||
self.log.info(
|
||||
f"Ursula ({work_order.ursula}) seems to be down while trying to complete WorkOrder: {work_order}")
|
||||
self.log.info(f"Ursula ({work_order.ursula}) seems to be down while trying to complete WorkOrder: {work_order}")
|
||||
continue
|
||||
except self.network_middleware.NotFound:
|
||||
# This Ursula claims not to have a matching KFrag. Maybe this has been revoked?
|
||||
# TODO: What's the thing to do here? Do we want to track these Ursulas in some way in case they're lying? 567
|
||||
self.log.warn(
|
||||
f"Ursula ({work_order.ursula}) claims not to have the KFrag to complete WorkOrder: {work_order}. Has accessed been revoked?")
|
||||
self.log.warn(f"Ursula ({work_order.ursula}) claims not to have the KFrag to complete WorkOrder: {work_order}. Has accessed been revoked?")
|
||||
continue
|
||||
except self.network_middleware.UnexpectedResponse:
|
||||
raise # TODO: Handle this
|
||||
|
||||
for capsule, pre_task in work_order.tasks.items():
|
||||
try:
|
||||
|
@ -1551,11 +1551,10 @@ class Enrico(Character):
|
|||
self.log = Logger(f'{self.__class__.__name__}-{bytes(self.public_keys(SigningPower)).hex()[:6]}')
|
||||
self.log.info(self.banner.format(policy_encrypting_key))
|
||||
|
||||
def encrypt_message(self,
|
||||
message: bytes
|
||||
) -> Tuple[UmbralMessageKit, Signature]:
|
||||
def encrypt_message(self, plaintext: bytes) -> Tuple[UmbralMessageKit, Signature]:
|
||||
# TODO: #2107 Rename to "encrypt"
|
||||
message_kit, signature = encrypt_and_sign(self.policy_pubkey,
|
||||
plaintext=message,
|
||||
plaintext=plaintext,
|
||||
signer=self.stamp)
|
||||
message_kit.policy_pubkey = self.policy_pubkey # TODO: We can probably do better here. NRN
|
||||
return message_kit, signature
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import base64
|
||||
|
||||
import click
|
||||
import ipfshttpclient
|
||||
|
||||
from nucypher.characters.control.emitters import StdoutEmitter
|
||||
from nucypher.characters.control.interfaces import BobInterface
|
||||
|
@ -286,13 +288,15 @@ def public_keys(general_config, character_options, config_file):
|
|||
@option_config_file
|
||||
@BobInterface.connect_cli('retrieve')
|
||||
@group_general_config
|
||||
@click.option('--ipfs', help="Download an encrypted message from IPFS at the specified gateway URI")
|
||||
def retrieve(general_config,
|
||||
character_options,
|
||||
config_file,
|
||||
label,
|
||||
policy_encrypting_key,
|
||||
alice_verifying_key,
|
||||
message_kit):
|
||||
message_kit,
|
||||
ipfs):
|
||||
"""Obtain plaintext from encrypted data, if access was granted."""
|
||||
|
||||
# Setup
|
||||
|
@ -305,6 +309,15 @@ def retrieve(general_config,
|
|||
required_fields = ', '.join(input_specification)
|
||||
raise click.BadArgumentUsage(f'{required_fields} are required flags to retrieve')
|
||||
|
||||
if ipfs:
|
||||
# TODO: #2108
|
||||
emitter.message(f"Connecting to IPFS Gateway {ipfs}")
|
||||
ipfs_client = ipfshttpclient.connect(ipfs)
|
||||
cid = message_kit # Understand the message kit value as an IPFS hash.
|
||||
raw_message_kit = ipfs_client.cat(cid) # cat the contents at the hash reference
|
||||
emitter.message(f"Downloaded message kit from IPFS (CID {cid})", color='green')
|
||||
message_kit = raw_message_kit.decode() # cast to utf-8
|
||||
|
||||
# Request
|
||||
bob_request_data = {
|
||||
'label': label,
|
||||
|
|
|
@ -17,6 +17,7 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
||||
|
||||
import click
|
||||
import ipfshttpclient
|
||||
from umbral.keys import UmbralPublicKey
|
||||
|
||||
from nucypher.characters.control.interfaces import EnricoInterface
|
||||
|
@ -58,13 +59,29 @@ def run(general_config, policy_encrypting_key, dry_run, http_port):
|
|||
|
||||
@enrico.command()
|
||||
@EnricoInterface.connect_cli('encrypt_message')
|
||||
@click.option('--ipfs', help="Upload the encrypted message to IPFS at the specified gateway URI")
|
||||
@group_general_config
|
||||
def encrypt(general_config, policy_encrypting_key, message):
|
||||
def encrypt(general_config, policy_encrypting_key, message, file, ipfs):
|
||||
"""Encrypt a message under a given policy public key."""
|
||||
|
||||
# Setup
|
||||
emitter = setup_emitter(general_config=general_config, banner=policy_encrypting_key)
|
||||
ENRICO = _create_enrico(emitter, policy_encrypting_key)
|
||||
encryption_request = {'message': message}
|
||||
if not (bool(message) ^ bool(file)):
|
||||
emitter.error(f'Pass either --message or --file. Got {message}, {file}.')
|
||||
raise click.Abort
|
||||
|
||||
# Encryption Request
|
||||
encryption_request = {'policy_encrypting_key': policy_encrypting_key, 'message': message, 'file': file}
|
||||
response = ENRICO.controller.encrypt_message(request=encryption_request)
|
||||
|
||||
# Handle Ciphertext
|
||||
# TODO: This might be crossing the bridge of being application code
|
||||
if ipfs:
|
||||
emitter.message(f"Connecting to IPFS Gateway {ipfs}")
|
||||
ipfs_client = ipfshttpclient.connect(ipfs)
|
||||
cid = ipfs_client.add_str(response['message_kit'])
|
||||
emitter.message(f"Uploaded message kit to IPFS (CID {cid})", color='green')
|
||||
return response
|
||||
|
||||
|
||||
|
|
|
@ -235,10 +235,13 @@ class RestMiddleware:
|
|||
def send_work_order_payload_to_ursula(self, work_order):
|
||||
payload = work_order.payload()
|
||||
id_as_hex = work_order.arrangement_id.hex()
|
||||
return self.client.post(
|
||||
response = self.client.post(
|
||||
node_or_sprout=work_order.ursula,
|
||||
path=f"kFrag/{id_as_hex}/reencrypt",
|
||||
data=payload, timeout=2)
|
||||
data=payload,
|
||||
timeout=2
|
||||
)
|
||||
return response
|
||||
|
||||
def check_rest_availability(self, initiator, responder):
|
||||
response = self.client.post(node_or_sprout=responder,
|
||||
|
|
|
@ -36,9 +36,12 @@ from nucypher.utilities.prometheus.collector import (
|
|||
import json
|
||||
from typing import List
|
||||
|
||||
from prometheus_client.core import Timestamp
|
||||
from prometheus_client.registry import CollectorRegistry, REGISTRY
|
||||
from prometheus_client.utils import floatToGoString
|
||||
try:
|
||||
from prometheus_client.core import Timestamp
|
||||
from prometheus_client.registry import CollectorRegistry, REGISTRY
|
||||
from prometheus_client.utils import floatToGoString
|
||||
except ImportError:
|
||||
raise DevelopmentInstallationRequired(importable_name='prometheus_client')
|
||||
from twisted.internet import reactor, task
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
|
|
|
@ -39,11 +39,9 @@ def get_fields(interface, method_name):
|
|||
|
||||
|
||||
def validate_json_rpc_response_data(response, method_name, interface):
|
||||
|
||||
required_output_fileds = get_fields(interface, method_name)[-1]
|
||||
|
||||
required_output_fields = get_fields(interface, method_name)[-1]
|
||||
assert 'jsonrpc' in response.data
|
||||
for output_field in required_output_fileds:
|
||||
for output_field in required_output_fields:
|
||||
assert output_field in response.content
|
||||
return True
|
||||
|
||||
|
|
|
@ -571,7 +571,7 @@ def test_collect_rewards_integration(click_runner,
|
|||
|
||||
# Encrypt
|
||||
random_data = os.urandom(random.randrange(20, 100))
|
||||
message_kit, signature = enrico.encrypt_message(message=random_data)
|
||||
message_kit, signature = enrico.encrypt_message(plaintext=random_data)
|
||||
|
||||
# Decrypt
|
||||
cleartexts = blockchain_bob.retrieve(message_kit,
|
||||
|
|
|
@ -473,7 +473,7 @@ def test_collect_rewards_integration(click_runner,
|
|||
|
||||
# Encrypt
|
||||
random_data = os.urandom(random.randrange(20, 100))
|
||||
ciphertext, signature = enrico.encrypt_message(message=random_data)
|
||||
ciphertext, signature = enrico.encrypt_message(plaintext=random_data)
|
||||
|
||||
# Decrypt
|
||||
cleartexts = blockchain_bob.retrieve(ciphertext,
|
||||
|
|
|
@ -230,7 +230,6 @@ def test_bob_web_character_control_retrieve_again(bob_web_controller_test_client
|
|||
def test_enrico_web_character_control_encrypt_message(enrico_web_controller_test_client, encrypt_control_request):
|
||||
method_name, params = encrypt_control_request
|
||||
endpoint = f'/{method_name}'
|
||||
|
||||
response = enrico_web_controller_test_client.post(endpoint, data=json.dumps(params))
|
||||
assert response.status_code == 200
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ def test_alice_can_decrypt(federated_alice):
|
|||
enrico = Enrico(policy_encrypting_key=policy_pubkey)
|
||||
|
||||
message = b"boring test message"
|
||||
message_kit, signature = enrico.encrypt_message(message=message)
|
||||
message_kit, signature = enrico.encrypt_message(plaintext=message)
|
||||
|
||||
# Interesting thing: if Alice wants to decrypt, she needs to provide the label directly.
|
||||
cleartext = federated_alice.verify_from(stranger=enrico,
|
||||
|
|
|
@ -65,7 +65,7 @@ def test_message_kit_serialization_via_enrico(enacted_federated_policy, federate
|
|||
plaintext_bytes = bytes(message, encoding='utf-8')
|
||||
|
||||
# Create
|
||||
message_kit, signature = enrico.encrypt_message(message=plaintext_bytes)
|
||||
message_kit, signature = enrico.encrypt_message(plaintext=plaintext_bytes)
|
||||
|
||||
# Serialize
|
||||
message_kit_bytes = message_kit.to_bytes()
|
||||
|
|
Loading…
Reference in New Issue