Default config paths; Extrapolates config utils module; further outlines blockchain management

pull/189/head
Kieran Prasch 2018-04-04 19:38:41 -07:00 committed by tuxxy
parent 049153498f
commit 7f7dfe2337
3 changed files with 199 additions and 133 deletions

View File

@ -1,4 +1,4 @@
import base64
import json
import os
import web3
@ -7,60 +7,82 @@ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from nacl.secret import SecretBox
import web3
class Wallet:
def accounts(self):
return web3.personal.listAccounts()
from nkms.config.utils import _derive_wrapping_key_from_master_key, _decrypt_key
class EthAccount:
"""http://eth-account.readthedocs.io/en/latest/eth_account.html#eth-account"""
def __init__(self, address):
self.__address = address
def __del__(self):
self.lock()
@property
def address(self):
return self.__address
@classmethod
def create(self):
pass
def create(self, passphrase):
"""Create a new wallet address"""
@classmethod
def import_existing(self):
pass
def import_existing(self, private_key, passphrase):
"""Instantiate a wallet from an existing wallet address"""
def unlock(self, passphrase, duration):
"""Unlock the account for a specified duration"""
def lock(self):
"""Lock the account and make efforts to remove the key from memory"""
def transact(self, txhash, passphrase):
"""Sign and transact without unlocking"""
class KMSConfig:
"""Warning: This class handles private keys!"""
_default_config_path = None
__root_name = '.nucypher'
__default_key_dir = os.path.join('~', __root_name, 'keys') # TODO: Change by actor
__config_root = os.path.join('~', '.nucypher')
class KMSConfigrationError(Exception):
__default_yaml_path = os.path.join(__config_root, 'conf.yml')
__keys_dir = os.path.join(__config_root, 'keys')
__transacting_key_path = os.path.join('.ethereum')
class KMSConfigurationError(Exception):
pass
def __init__(self, blockchain_address: str, enc_key_path: str=None,
sig_key_path: str=None, config_path: str=None):
if self._default_config_path is None:
pass # TODO: no default config path set
def __init__(self, blockchain_address: str, keys_dir: str=None,
yaml_config_path: str=None):
self.__config_path = config_path or self._default_config_path
self.__enc_key_path = enc_key_path
self.__sig_key_path = sig_key_path
self.__yaml_config_path = yaml_config_path or self.__default_yaml_path
self.__keys_dir = keys_dir
# Blockchain
self.address = blockchain_address
@classmethod
def from_config_file(cls, config_path=None):
def from_yaml_config(cls, config_path=None):
"""Reads the config file and instantiates a KMSConfig instance"""
with open(config_path or cls._default_config_path, 'r') as f:
with open(config_path or cls.__default_yaml_path, 'r') as conf_file:
# Get data from the config file
data = f.read() #TODO: Parse
data = conf_file.read() #TODO: Parse
instance = cls()
return instance
def get_transacting_key(self):
"""
"""
with open(self.transacting_key_path) as keyfile:
def get_transacting_key(self, passphrase):
with open(self.__transacting_key_path) as keyfile:
encrypted_key = keyfile.read()
private_key = web3.eth.account.decrypt(encrypted_key, 'correcthorsebatterystaple')
private_key = web3.eth.account.decrypt(encrypted_key, passphrase)
# WARNING: do not save the key or password anywhere
def get_decrypting_key(self, master_key: bytes=None):
@ -99,22 +121,22 @@ class KMSConfig:
"""
Parses a keyfile and returns key metadata as a dict.
"""
keyfile_path = os.path.join(self.__key_dir, path)
keyfile_path = os.path.join(self.__keys_dir, path)
with open(keyfile_path, 'r') as keyfile:
try:
key_metadata = json.loads(keyfile)
except json.JSONDecodeError:
raise KMSConfigurationError("Invalid data in keyfile {}".format(path))
return key_metadata
except json.JSONDecodeError:
raise self.KMSConfigurationError("Invalid data in keyfile {}".format(path))
else:
return key_metadata
def _save_keyfile(self, path: str, key_data: dict):
"""
Saves key data to a file.
"""
keyfile_path = os.path.join(self.__key_dir, path)
with open(keyfile_path), 'w+') as keyfile:
f.seek(0)
keyfile_path = os.path.join(self.__keys_dir, path)
with open(keyfile_path, 'w+') as keyfile:
keyfile.seek(0)
check_byte = keyfile.read(1)
if len(check_byte) != 0:
raise self.KMSConfigurationError("Keyfile is not empty! Check your key path.")
@ -122,78 +144,3 @@ class KMSConfig:
keyfile.seek(0)
keyfile.write(json.dumps(key_data))
def _derive_master_key_from_passphrase(salt: bytes, passphrase: str):
"""
Uses Scrypt derivation to derive a master key for encrypting key material.
See RFC 7914 for n, r, and p value selections.
This takes around ~5 seconds to perform.
"""
master_key = Scrypt(
salt=salt,
length=32,
n=2**20,
r=8,
p=1,
backend=default_backend()
).derive(passphrase.encode())
return master_key
def _derive_wrapping_key_from_master_key(salt: bytes, master_key: bytes):
"""
Uses HKDF to derive a 32 byte wrapping key to encrypt key material with.
"""
wrapping_key = HKDF(
algorithm=hashes.SHA512(),
length=32,
salt=salt,
info=b'NuCypher-KMS-KeyWrap',
backend=default_backend()
).derive(master_key)
return wrapping_key
def _encrypt_key(wrapping_key: bytes, key_material: bytes):
"""
Encrypts a key with nacl's XSalsa20-Poly1305 algorithm (SecretBox).
Returns an encrypted key as bytes with the nonce appended.
"""
nonce = os.urandom(24)
enc_key = SecretBox(wrapping_key).encrypt(key_material, nonce)
crypto_data = {
'nonce': nonce,
'enc_key': enc_key
}
return crypto_data
# TODO: Handle decryption failures
def _decrypt_key(wrapping_key: bytes, nonce: bytes, enc_key_material: bytes):
"""
Decrypts an encrypted key with nacl's XSalsa20-Poly1305 algorithm (SecretBox).
Returns a decrypted key as bytes.
"""
dec_key = SecretBox(wrapping_key).encrypt(enc_key_material, nonce)
return dec_key
def _generate_encryption_keys():
privkey = UmbralPrivateKey.gen_key()
pubkey = priv_key.get_pubkey()
return (privkey, pubkey)
# TODO: Do we really want to use Umbral keys for signing?
# TODO: Perhaps we can use Curve25519/EdDSA for signatures?
def _generate_signing_keys():
privkey = UmbralPrivateKey.gen_key()
pubkey = priv_key.get_pubkey()
return (privkey, pubkey)

View File

@ -1,32 +1,49 @@
import curses
from curses.textpad import rectangle, Textbox
import sys
from .configs import validate_passphrase, KMSConfig
# Configurator Text #
title = "NuCypher KMS Configurator"
welcome = "Welcome to the NuCypher KMS Config Tool"
description = "Use this tool to manage keypairs node operation."
newlines = '\n' * 2
press_any = "Press any key to continue..."
enter_new_passphrase = 'Enter new passphrase: '
confirm_passphrase = "Confirm passphrase"
did_not_match = "Passwords did not match"
loading = "loading..."
keygen_success = "Keys generated and written to keyfile!"
class ConfigText:
title = "NuCypher KMS Configurator"
welcome = "Welcome to the NuCypher KMS Config Tool"
description = "Use this tool to manage keypairs node operation."
loading = "loading..."
keygen_success = "Keys generated and written to keyfile!"
def close():
sys.exit()
def main(screen):
def gather_passphrase():
user_passphrase = input(enter_new_passphrase)
height, width = 40, 1
editwin = curses.newwin(width, height, 2, 1)
textarea = rectangle(screen, 1, 0, 1+width+1, 1+height+1) # 1s for padding
try: # Validate
validate_passphrase(user_passphrase)
except KMSConfig.KMSConfigrationError:
close()
else:
confirm_passphrase = input(enter_new_passphrase)
if user_passphrase != confirm_passphrase:
print(did_not_match)
del user_passphrase
del confirm_passphrase
gather_passphrase()
screen.addstr(1, 1, ConfigText.welcome)
screen.refresh()
screen.addstr(1, 1, ConfigText.title, curses.A_BOLD)
screen.refresh()
box = Textbox(editwin)
box.edit() # Let the user edit until Ctrl-G is struck.
message = box.gather() # Get resulting contents
return user_passphrase
if __name__ == "__main__":
curses.wrapper(main)
curses.beep()
def start():
print(title, welcome, description, newlines, sep='\n')
input(press_any)
gather_passphrase()
start()

102
nkms/config/utils.py Normal file
View File

@ -0,0 +1,102 @@
import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from nacl.secret import SecretBox
from umbral.keys import UmbralPrivateKey
from nkms.config.config import KMSConfig
def validate_passphrase(passphrase):
"""Validate a passphrase and return it or raise"""
rules = (
(len(passphrase) >= 16, 'Too short'),
)
for rule, failure_message in rules:
if not rule:
raise KMSConfig.KMSConfigurationError(failure_message)
else:
return passphrase
def _derive_master_key_from_passphrase(salt: bytes, passphrase: str):
"""
Uses Scrypt derivation to derive a master key for encrypting key material.
See RFC 7914 for n, r, and p value selections.
This takes around ~5 seconds to perform.
"""
master_key = Scrypt(
salt=salt,
length=32,
n=2**20,
r=8,
p=1,
backend=default_backend()
).derive(passphrase.encode())
return master_key
def _derive_wrapping_key_from_master_key(salt: bytes, master_key: bytes):
"""
Uses HKDF to derive a 32 byte wrapping key to encrypt key material with.
"""
wrapping_key = HKDF(
algorithm=hashes.SHA512(),
length=32,
salt=salt,
info=b'NuCypher-KMS-KeyWrap',
backend=default_backend()
).derive(master_key)
return wrapping_key
def _encrypt_key(wrapping_key: bytes, key_material: bytes):
"""
Encrypts a key with nacl's XSalsa20-Poly1305 algorithm (SecretBox).
Returns an encrypted key as bytes with the nonce appended.
"""
nonce = os.urandom(24)
enc_key = SecretBox(wrapping_key).encrypt(key_material, nonce)
crypto_data = {
'nonce': nonce,
'enc_key': enc_key
}
return crypto_data
# TODO: Handle decryption failures
def _decrypt_key(wrapping_key: bytes, nonce: bytes, enc_key_material: bytes):
"""
Decrypts an encrypted key with nacl's XSalsa20-Poly1305 algorithm (SecretBox).
Returns a decrypted key as bytes.
"""
dec_key = SecretBox(wrapping_key).encrypt(enc_key_material, nonce)
return dec_key
def _generate_encryption_keys():
privkey = UmbralPrivateKey.gen_key()
pubkey = privkey.get_pubkey()
return privkey, pubkey
# TODO: Do we really want to use Umbral keys for signing?
# TODO: Perhaps we can use Curve25519/EdDSA for signatures?
def _generate_signing_keys():
privkey = UmbralPrivateKey.gen_key()
pubkey = privkey.get_pubkey()
return privkey, pubkey