Skip to content

Commit

Permalink
Fix load and migrate not working
Browse files Browse the repository at this point in the history
The load method did not get a decryption key of the previous stash to use for decrypting the keys.
  • Loading branch information
nir0s committed Apr 2, 2017
1 parent 0ccf177 commit d56b00b
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 27 deletions.
51 changes: 28 additions & 23 deletions ghost.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,7 @@ def assert_value_provided_for_new_key(value, existing_key):
# from the API.
if add:
value = self._update_existing_key(key, value)
if encrypt:
new_key['value'] = self._encrypt(value)
new_key['value'] = self._encrypt(value) if encrypt else value
else:
new_key['value'] = key.get('value')

Expand Down Expand Up @@ -484,47 +483,52 @@ def export(self, output_path=None, decrypt=False):
else:
raise GhostError('There are no keys to export')

def load(self, keys=None, key_file=None, encrypt=False):
def load(self, origin_passphrase, keys=None, key_file=None):
"""Import keys to the stash from either a list of keys or a file
`keys` is a list of dictionaries created by `self.export`
`stash_path` is a path to a file created by `self.export`
If `force` is true, existing keys will be overwriten.
"""
# TODO: Handle keys not dict or key_file not json
self._assert_valid_stash()

if not keys and not key_file or (keys and key_file):
# Check if both or none are provided (ahh, the mighty xor)
if not (bool(keys) ^ bool(key_file)):
raise GhostError(
'You must either provide a path to an exported stash file '
'or a list of key dicts to import')
if key_file:
with open(key_file) as stash_file:
keys = json.loads(stash_file.read())

# If the passphrases are the same, there's no reason to decrypt
# and re-encrypt. We can simply pass the value.
decrypt = origin_passphrase != self.passphrase
if decrypt:
# TODO: The fact that we need to create a stub stash just to
# decrypt means we should probably have some encryptor class.
stub = Stash(TinyDBStorage('stub'), origin_passphrase)
# TODO: Handle existing keys when loading
for key in keys:
self.put(
name=key['name'],
value=key['value'],
value=stub._decrypt(key['value']) if decrypt else key['value'],
metadata=key['metadata'],
description=key['description'],
lock=key.get('lock'),
key_type=key.get('type'),
encrypt=encrypt)
encrypt=decrypt)

@property
def key(self):
if self._key is None:
passphrase = self.passphrase.encode('utf-8')
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=b'ghost',
iterations=self._iterations,
backend=default_backend())
self._key = base64.urlsafe_b64encode(kdf.derive(passphrase))
passphrase = self.passphrase.encode('utf-8')
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=b'ghost',
iterations=self._iterations,
backend=default_backend())
self._key = base64.urlsafe_b64encode(kdf.derive(passphrase))
return self._key

@property
Expand Down Expand Up @@ -586,11 +590,10 @@ def migrate(src_path,
dst_storage = STORAGE_MAPPING[dst_backend](**_parse_path_string(dst_path))
src_stash = Stash(src_storage, src_passphrase)
dst_stash = Stash(dst_storage, dst_passphrase)
# TODO: Test that re-encryption does not occur on similiar
# TODO: Test that re-encryption does not occur on similar
# passphrases
similiar_passphrase = src_passphrase == dst_passphrase
keys = src_stash.export(decrypt=not similiar_passphrase)
dst_stash.load(keys=keys, encrypt=not similiar_passphrase)
keys = src_stash.export()
dst_stash.load(src_passphrase, keys=keys)


class TinyDBStorage(object):
Expand Down Expand Up @@ -1509,18 +1512,20 @@ def export_keys(output_path, stash, passphrase, backend):

@main.command(name='load')
@click.argument('KEY_FILE')
@click.option('--origin-passphrase',
help='The passphrase of the origin stash')
@stash_option
@passphrase_option
@backend_option
def load_keys(key_file, stash, passphrase, backend):
def load_keys(key_file, origin_passphrase, stash, passphrase, backend):
"""Load all keys from an exported key file to the stash
`KEY_FILE` is the exported stash file to load keys from
"""
stash = _get_stash(backend, stash, passphrase)

click.echo('Importing all keys from {0}...'.format(key_file))
stash.load(key_file=key_file)
stash.load(origin_passphrase, key_file=key_file)
click.echo('Import complete!')


Expand Down
15 changes: 11 additions & 4 deletions tests/test_ghost.py
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ def test_load(self, test_stash):
keys = test_stash.export()
assert keys[0]['name'] == 'aws'
test_stash.purge(force=True)
test_stash.load(keys, encrypt=False)
test_stash.load(test_stash.passphrase, keys)
key_list = test_stash.list()
assert len(key_list) == 1
assert 'aws' in key_list
Expand All @@ -1091,14 +1091,14 @@ def test_load_from_file(self, test_stash, temp_file_path):
test_stash.purge(force=True)
keys = test_stash.list()
assert len(keys) == 0
test_stash.load(key_file=temp_file_path)
test_stash.load(test_stash.passphrase, key_file=temp_file_path)
key_list = test_stash.list()
assert len(key_list) == 1
assert 'aws' in key_list

def test_load_no_keys_no_file_provided(self, test_stash):
with pytest.raises(ghost.GhostError) as ex:
test_stash.load()
test_stash.load('stub_passphrase')
assert 'You must either provide a path to an exported' in str(ex.value)

def test_migrate(self, test_stash, temp_file_path):
Expand All @@ -1114,6 +1114,12 @@ def test_migrate(self, test_stash, temp_file_path):
assert 'aws' in destination_stash.list()
assert 'gcp' in destination_stash.list()
assert 'openstack' in destination_stash.list()
# Verify that not only were the keys loaded, their values are correct.
# To understand why the two indices of the lists below make sense,
# see _create_migration_env where the keys are put.
example_src_key = test_stash.get(test_stash.list()[0])
example_dst_key = destination_stash.get(destination_stash.list()[1])
assert example_src_key['value'] == example_dst_key['value']

# TODO: Test lock here also
def test_lock_an_already_locked_key(self, test_stash):
Expand Down Expand Up @@ -1483,7 +1489,8 @@ def test_load(self, test_cli_stash, temp_file_path):
_invoke('purge_stash -f')
result = _invoke('list_keys -j')
assert json.loads(result.output.strip('\n')) == []
_invoke('load_keys "{0}"'.format(temp_file_path))
_invoke('load_keys "{0}" --origin-passphrase {1}'.format(
temp_file_path, test_cli_stash.passphrase))
result = _invoke('list_keys -j')
assert json.loads(result.output) == key_list

Expand Down

0 comments on commit d56b00b

Please sign in to comment.