def test_character_card(character_class, capsys): character = character_class(federated_only=True, start_learning_now=False, network_middleware=MockRestMiddleware()) character_card = character.get_card() same_card = Card.from_character(character) assert character_card == same_card with pytest.raises(TypeError): # only cards can be compared to other cards _ = character_card == same_card.verifying_key # Bob's Keys assert character_card.verifying_key == character.public_keys(SigningPower) assert character_card.encrypting_key == character.public_keys( DecryptingPower) # Card Serialization # bytes card_bytes = bytes(character_card) assert Card.from_bytes(card_bytes) == character_card == same_card # hex hex_bob = character_card.to_hex() assert Card.from_hex(hex_bob) == character_card == same_card # base64 base64_bob = character_card.to_base64() assert Card.from_base64(base64_bob) == character_card == same_card # qr code echo character_card.to_qr_code() captured = capsys.readouterr() qr_code_padding = '\xa0' * 21 # min length for qr code version 1 assert captured.out.startswith(qr_code_padding) assert captured.out.endswith(f'{qr_code_padding}\n') # filepath without nickname assert character_card.id.hex() in str(character_card.filepath) # nicknames original_checksum = character_card.id nickname = 'Wilson the Great' expected_nickname = nickname.replace(' ', '_') character_card.set_nickname(nickname) restored = Card.from_bytes(bytes(character_card)) restored_checksum = restored.id assert restored.nickname == expected_nickname assert original_checksum == restored_checksum == same_card.id # filepath with nickname assert f'{expected_nickname}.{character_card.id.hex()}' in str( character_card.filepath)
def paint_single_card(emitter, card: Card, qrcode: bool = False) -> None: emitter.echo('*' * 90) emitter.message( f'{(card.nickname or str(card.character.__name__)).capitalize()}\'s Card ({card.id.hex()})' ) emitter.echo(f'Verifying Key - {card.verifying_key.hex()}') if card.character is Bob: emitter.echo(f'Encrypting Key - {card.encrypting_key.hex()}') if qrcode: card.to_qr_code() emitter.echo('*' * 90)
def create(character_flag, verifying_key, encrypting_key, nickname, force): """Store a new character card""" emitter = StdoutEmitter() # Validate if not all((character_flag, verifying_key, encrypting_key)) and force: emitter.error( f'--verifying-key, --encrypting-key, and --type are required with --force enabled.' ) # Card type from constant_sorrow.constants import ALICE, BOB flags = {'a': ALICE, 'b': BOB} if not character_flag: choice = click.prompt('Enter Card Type - (A)lice or (B)ob', type=click.Choice(['a', 'b'], case_sensitive=False)) character_flag = flags[choice] else: character_flag = flags[character_flag] # Verifying Key if not verifying_key: verifying_key = click.prompt('Enter Verifying Key', type=UMBRAL_PUBLIC_KEY_HEX) verifying_key = bytes.fromhex(verifying_key) # TODO: Move / Validate # Encrypting Key if character_flag is BOB: if not encrypting_key: encrypting_key = click.prompt('Enter Encrypting Key', type=UMBRAL_PUBLIC_KEY_HEX) encrypting_key = bytes.fromhex(encrypting_key) # TODO: Move / Validate # Init new_card = Card(character_flag=character_flag, verifying_key=verifying_key, encrypting_key=encrypting_key, nickname=nickname) # Nickname if not force and not nickname: card_id_hex = new_card.id.hex() nickname = click.prompt('Enter nickname for card', default=card_id_hex) if nickname != card_id_hex: # not the default nickname = nickname.strip() new_card.nickname = nickname # Save new_card.save() emitter.message(f'Saved new card {new_card}', color='green') paint_single_card(emitter=emitter, card=new_card)
def select_card(emitter, card_identifier: str) -> Card: if not card_identifier: cards = [] for filename in os.listdir(Card.CARD_DIR): filepath = Card.CARD_DIR / filename card = Card.load(filepath=filepath) cards.append(card) paint_cards(emitter=emitter, cards=cards, as_table=True) selection = click.prompt('Select card', type=click.IntRange(0, len(cards))) card = cards[selection] else: card = Card.load(identifier=card_identifier) return card
def make_card(general_config, character_options, config_file, nickname): emitter = setup_emitter(general_config) BOB = character_options.create_character(emitter, config_file, json_ipc=False) card = Card.from_character(BOB) if nickname: card.nickname = nickname card.save(overwrite=True) emitter.message(f"Saved new character card to {card.filepath}", color='green') paint_single_card(card=card, emitter=emitter)
def test_character_card(character_class): character = character_class(federated_only=True, start_learning_now=False, network_middleware=MockRestMiddleware()) character_card = character.get_card() same_card = Card.from_character(character) assert character_card == same_card with pytest.raises(TypeError): # only cards can be compared to other cards _ = character_card == same_card.verifying_key # Bob's Keys assert character_card.verifying_key == character.public_keys(SigningPower) assert character_card.encrypting_key == character.public_keys( DecryptingPower) # Card Serialization # bytes card_bytes = bytes(character_card) assert Card.from_bytes(card_bytes) == character_card == same_card # hex hex_bob = character_card.to_hex() assert Card.from_hex(hex_bob) == character_card == same_card # base64 base64_bob = character_card.to_base64() assert Card.from_base64(base64_bob) == character_card == same_card # qr code echo character_card.to_qr_code() # TODO: Examine system output here? # nicknames original_checksum = character_card.id nickname = 'Wilson' character_card.set_nickname(nickname) restored = Card.from_bytes(bytes(character_card)) restored_checksum = restored.id assert restored.nickname == nickname assert original_checksum == restored_checksum == same_card.id
def make_card(general_config, character_options, config_file, nickname): """Create a character card file for public key sharing""" emitter = setup_emitter(general_config) ALICE = character_options.create_character(emitter, config_file, general_config.json_ipc, load_seednodes=False) card = Card.from_character(ALICE) if nickname: card.nickname = nickname card.save(overwrite=True) emitter.message(f"Saved new character card to {card.filepath}", color='green') paint_single_card(card=card, emitter=emitter)
def retrieve(general_config, character_options, config_file, label, policy_encrypting_key, alice_verifying_key, message_kit, ipfs, alice, force): """Obtain plaintext from encrypted data, if access was granted.""" # Setup emitter = setup_emitter(general_config) BOB = character_options.create_character(emitter, config_file) if ipfs: import ipfshttpclient # 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 if not alice_verifying_key: if alice: # from storage card = Card.load(identifier=alice) if card.character is not Alice: emitter.error('Grantee card is not an Alice.') raise click.Abort alice_verifying_key = card.verifying_key.hex() emitter.message(f'{card.nickname or ("Alice #"+card.id.hex())}\n' f'Verifying Key | {card.verifying_key.hex()}', color='green') if not force: click.confirm('Is this the correct Granter (Alice)?', abort=True) else: # interactive alice_verifying_key = click.prompt("Enter Alice's verifying key") # Request bob_request_data = { 'label': label, 'policy_encrypting_key': policy_encrypting_key, 'alice_verifying_key': alice_verifying_key, 'message_kit': message_kit, } response = BOB.controller.retrieve(request=bob_request_data) return response
def _list(): """Show all character cards""" emitter = StdoutEmitter() if not Card.CARD_DIR.is_dir(): Card.CARD_DIR.mkdir() card_filepaths = list(Card.CARD_DIR.iterdir()) if not card_filepaths: emitter.error( f'No cards found at {Card.CARD_DIR}. ' f"To create one run 'nucypher {contacts.name} {create.name}'.") cards = list() for filename in card_filepaths: card = Card.load(filepath=Card.CARD_DIR / filename) cards.append(card) paint_cards(emitter=emitter, cards=cards, as_table=True)
def _list(): """Show all character cards""" emitter = StdoutEmitter() card_directory = Card.CARD_DIR try: card_filepaths = os.listdir(card_directory) except FileNotFoundError: os.mkdir(Card.CARD_DIR) card_filepaths = os.listdir(card_directory) if not card_filepaths: emitter.error( f'No cards found at {card_directory}. ' f"To create one run 'nucypher {contacts.name} {create.name}'.") cards = list() for filename in card_filepaths: card = Card.load(filepath=Card.CARD_DIR / filename) cards.append(card) paint_cards(emitter=emitter, cards=cards, as_table=True)
def collect_keys_from_card(emitter: StdoutEmitter, card_identifier: str, force: bool): emitter.message(f"Searching contacts for {card_identifier}\n", color='yellow') card = Card.load(identifier=card_identifier) if card.character is not Bob: emitter.error('Grantee card is not a Bob.') raise click.Abort paint_single_card(emitter=emitter, card=card) if not force: click.confirm('Is this the correct grantee (Bob)?', abort=True) bob_encrypting_key = bytes(card.encrypting_key).hex() bob_verifying_key = bytes(card.verifying_key).hex() public_keys = PublicKeys(encrypting_key=bob_encrypting_key, verifying_key=bob_verifying_key) return public_keys
def retrieve_and_decrypt(general_config, character_options, config_file, alice_verifying_key, treasure_map, message_kit, ipfs, alice, decode, force): """Obtain plaintext from encrypted data, if access was granted.""" # 'message_kit' is a required and a "multiple" value click option - the option name was kept singular so that # it makes sense when specifying many of them i.e. `--message-kit <message_kit_1> --message-kit <message_kit_2> ...` message_kits = list(message_kit) # Setup emitter = setup_emitter(general_config) BOB = character_options.create_character(emitter, config_file, json_ipc=general_config.json_ipc) if not (bool(alice_verifying_key) ^ bool(alice)): message = f"Pass either '--alice_verifying_key' or '--alice'; " \ f"got {'both' if alice_verifying_key else 'neither'}" raise click.BadOptionUsage( option_name='--alice_verifying_key, --alice', message=message) if not alice_verifying_key: if alice: # from storage card = Card.load(identifier=alice) if card.character is not Alice: emitter.error('Grantee card is not an Alice.') raise click.Abort alice_verifying_key = bytes(card.verifying_key).hex() emitter.message( f'{card.nickname or ("Alice #"+card.id.hex())}\n' f'Verifying Key | {bytes(card.verifying_key).hex()}', color='green') if not force: click.confirm('Is this the correct Granter (Alice)?', abort=True) if ipfs: # '--message_kit' option was repurposed to specify ipfs cids (#2098) cids = [] for cid in message_kits: cids.append(cid) # populate message_kits list with actual message_kits message_kits = [] import ipfshttpclient # TODO: #2108 emitter.message(f"Connecting to IPFS Gateway {ipfs}") ipfs_client = ipfshttpclient.connect(ipfs) for cid in cids: 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 message_kits.append(message_kit) # Request bob_request_data = { 'alice_verifying_key': alice_verifying_key, 'message_kits': message_kits, 'encrypted_treasure_map': treasure_map } response = BOB.controller.retrieve_and_decrypt(request=bob_request_data) if decode: messages = list( [b64decode(r).decode() for r in response['cleartexts']]) emitter.echo('----------Messages----------') for message in messages: emitter.echo(message) return response
def retrieve(general_config, character_options, config_file, label, policy_encrypting_key, alice_verifying_key, message_kit, ipfs, alice, decode, force): """Obtain plaintext from encrypted data, if access was granted.""" # Setup emitter = setup_emitter(general_config) BOB = character_options.create_character(emitter, config_file, json_ipc=general_config.json_ipc) if not message_kit: if ipfs: prompt = "Enter IPFS CID for encrypted data" else: prompt = "Enter encrypted data (base64)" message_kit = click.prompt(prompt, type=click.STRING) if ipfs: import ipfshttpclient # 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 if not alice_verifying_key: if alice: # from storage card = Card.load(identifier=alice) if card.character is not Alice: emitter.error('Grantee card is not an Alice.') raise click.Abort alice_verifying_key = bytes(card.verifying_key).hex() emitter.message(f'{card.nickname or ("Alice #"+card.id.hex())}\n' f'Verifying Key | {bytes(card.verifying_key).hex()}', color='green') if not force: click.confirm('Is this the correct Granter (Alice)?', abort=True) else: # interactive alice_verifying_key = click.prompt("Enter Alice's verifying key", click.STRING) if not force: if not policy_encrypting_key: policy_encrypting_key = click.prompt("Enter policy public key", type=click.STRING) if not label: label = click.prompt("Enter label to retrieve", type=click.STRING) # Request bob_request_data = { 'label': label, 'policy_encrypting_key': policy_encrypting_key, 'alice_verifying_key': alice_verifying_key, 'message_kit': message_kit, } response = BOB.controller.retrieve(request=bob_request_data) if decode: messages = list([b64decode(r).decode() for r in response['cleartexts']]) emitter.echo('----------Messages----------') for message in messages: emitter.echo(message) return response
def grant(general_config, bob, bob_encrypting_key, bob_verifying_key, label, value, rate, expiration, m, n, character_options, config_file, force): """Create and enact an access policy for some Bob. """ # Setup emitter = setup_emitter(general_config) ALICE = character_options.create_character(emitter, config_file, general_config.json_ipc) # Policy option validation if ALICE.federated_only: if any((value, rate)): message = "Can't use --value or --rate with a federated Alice." raise click.BadOptionUsage(option_name="--value, --rate", message=message) elif bool(value) and bool(rate): raise click.BadOptionUsage(option_name="--rate", message="Can't use --value if using --rate") # Grantee selection if bob and any((bob_encrypting_key, bob_verifying_key)): message = '--bob cannot be used with --bob-encrypting-key or --bob-veryfying key' raise click.BadOptionUsage(option_name='--bob', message=message) if bob: card = Card.load(identifier=bob) if card.character is not Bob: emitter.error('Grantee card is not a Bob.') raise click.Abort paint_single_card(emitter=emitter, card=card) if not force: click.confirm('Is this the correct grantee (Bob)?', abort=True) bob_encrypting_key = card.encrypting_key.hex() bob_verifying_key = card.verifying_key.hex() # Interactive collection follows: # TODO: Extricate to support modules # - Disclaimer # - Label # - Expiration Date & Time # - M of N # - Policy Value (ETH) # Policy Expiration # TODO: Remove this line when the time is right. paint_probationary_period_disclaimer(emitter) # Label if not label: label = click.prompt(f'Enter label to grant Bob {bob_verifying_key[:8]}', type=click.STRING) if not force and not expiration: if ALICE.duration_periods: # TODO: use a default in days or periods? expiration = maya.now() + timedelta(days=ALICE.duration_periods) # default if not click.confirm(f'Use default policy duration (expires {expiration})?'): expiration = click.prompt('Enter policy expiration datetime', type=click.DateTime()) else: # No policy duration default default available; Go interactive expiration = click.prompt('Enter policy expiration datetime', type=click.DateTime()) # TODO: Remove this line when the time is right. enforce_probationary_period(emitter=emitter, expiration=expiration) # Policy Threshold and Shares if not n: n = ALICE.n if not force and not click.confirm(f'Use default value for N ({n})?', default=True): n = click.prompt('Enter total number of shares (N)', type=click.INT) if not m: m = ALICE.m if not force and not click.confirm(f'Use default value for M ({m})?', default=True): m = click.prompt('Enter threshold (M)', type=click.IntRange(1, n)) # Policy Value policy_value_provided = bool(value) or bool(rate) if not ALICE.federated_only and not policy_value_provided: rate = ALICE.default_rate # TODO #1709 - Fine tuning and selection of default rates if not force: default_gwei = Web3.fromWei(rate, 'gwei') prompt = "Confirm rate of {node_rate} gwei ({total_rate} gwei per period)?" if not click.confirm(prompt.format(node_rate=default_gwei, total_rate=default_gwei*n), default=True): interactive_rate = click.prompt('Enter rate per period in gwei', type=GWEI) # TODO: Validate interactively collected rate (#1709) click.confirm(prompt.format(node_rate=rate, total_rate=rate*n), default=True, abort=True) rate = Web3.toWei(interactive_rate, 'gwei') # Request grant_request = { 'bob_encrypting_key': bob_encrypting_key, 'bob_verifying_key': bob_verifying_key, 'label': label, 'm': m, 'n': n, 'expiration': expiration, } if not ALICE.federated_only: if value: grant_request['value'] = value elif rate: grant_request['rate'] = rate # in wei if not force and not general_config.json_ipc: confirm_staged_grant(emitter=emitter, grant_request=grant_request) emitter.echo(f'Granting Access to {bob_verifying_key[:8]}', color='yellow') return ALICE.controller.grant(request=grant_request)