Skip to content

Commit

Permalink
Merge pull request #13 from micimize/master
Browse files Browse the repository at this point in the history
Keystore updates & disabling
  • Loading branch information
peerchemist authored Aug 9, 2017
2 parents b841281 + 5343035 commit 30b3d19
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 86 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,6 @@ ENV/

# Rope project settings
.ropeproject

# vim swap files
.*.sw*
52 changes: 32 additions & 20 deletions pacli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import json
import logging

from pacli.keystore import read_keystore, write_keystore, KeyedProvider
from pacli.keystore import GpgKeystore, as_local_key_provider

conf_dir = user_config_dir("pacli")
conf_file = os.path.join(conf_dir, "pacli.conf")
Expand Down Expand Up @@ -56,7 +56,7 @@ def set_up(provider):
if not Settings.production:
if not provider.listtransactions("PATEST"):
pa.pautils.load_p2th_privkeys_into_local_node(provider, prod=False)
else:
elif Settings.provider != 'holy':
pa.pautils.load_p2th_privkeys_into_local_node(provider,keyfile)

def default_account_utxo(provider, amount):
Expand Down Expand Up @@ -335,7 +335,7 @@ def new_deck(provider, deck, broadcast):
'''
Spawn a new PeerAssets deck.
pacli deck -new '{"name": "test", "number_of_decimals": 1, "issue_mode": "ONCE"}'
pacli deck --new '{"name": "test", "number_of_decimals": 1, "issue_mode": "ONCE"}'
Will return deck span txid.
'''
Expand Down Expand Up @@ -780,7 +780,8 @@ def cli():
parser.add_argument("--newaddress", action="store_true",
help="generate a new address and import to wallet")
parser.add_argument("--status", action="store_true", help="show pacli status")
parser.add_argument("--addressbalance", action="store", nargs=2, help="check card balance of the address")
parser.add_argument("--addressbalance", action="store",
metavar=('DECK_ID', 'ADDRESS'), nargs=2, help="check card balance of the address")

deck = subparsers.add_parser('deck', help='Deck manipulation.')
deck.add_argument("--list", action="store_true", help="list decks")
Expand All @@ -807,28 +808,37 @@ def cli():

return parser.parse_args()

def main():

first_run()
def configured_provider(Settings):
" resolve settings into configured provider "

try:
load_conf()
except:
raise
if Settings.provider.lower() == "rpcnode":
Provider = pa.RpcNode
kwargs = dict(testnet=Settings.testnet)

mypg = None
password = None
mykeys = ""
mykeys = read_keystore(Settings,keyfile)
elif Settings.provider.lower() == "holy":
Provider = pa.Holy
kwargs = dict(network=Settings.network)

if Settings.provider.lower() == "rpcnode":
provider = pa.RpcNode(testnet=Settings.testnet)
if Settings.provider.lower() == "holy":
provider = pa.Holy(network=Settings.network)
else: raise Exception('invalid provider')

if Settings.keystore.lower() == "gnupg":
Provider = as_local_key_provider(Provider)
kwargs['keystore'] = keystore = GpgKeystore(Settings, keyfile)

provider = KeyedProvider(provider,keysJson=mykeys)
provider = Provider(**kwargs)
set_up(provider)

return provider


def main():

first_run()
load_conf()

provider = configured_provider(Settings)

args = cli()

if args.status:
Expand Down Expand Up @@ -878,7 +888,9 @@ def main():
if args.info:
vote_info(provider, args.info)

write_keystore(Settings,keyfile,provider.dumpprivkeys())
if (hasattr(provider, 'keystore')):
# could possibly make this direct behavior of dumprivkeys
provider.keystore.write(provider.dumpprivkeys())

if __name__ == "__main__":
main()
25 changes: 14 additions & 11 deletions pacli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@ def write_default_config(conf_file=None):
with open(conf_file, 'w') as configfile:
config.write(configfile)

optional = {
"keystore": "none",
"gnupgdir": "",
"gnupgagent": "",
"gnupgkey": ""
}

required = { "network", "production", "loglevel", "change" }

def read_conf(conf_file):
config = configparser.ConfigParser()
config.read(conf_file)
try:
settings = {
"network": config["settings"]["network"],
"production": config["settings"]["production"],
"loglevel": config["settings"]["loglevel"],
"change": config["settings"]["change"],
"provider": config["settings"]["provider"],
"keystore": config["settings"]["keystore"],
"gnupgdir": config["settings"]["gnupgdir"],
"gnupgagent": config["settings"]["gnupgagent"],
"gnupgkey": config["settings"]["gnupgkey"]
}
settings = dict(config["settings"])
assert set(settings.keys()).issuperset(required)
for k, v in optional.items():
settings[k] = settings.get(k, v)

except:
print("config is outdated, saving current default config to",conf_file+".sample")
write_default_config(conf_file+".sample")
Expand Down
116 changes: 63 additions & 53 deletions pacli/keystore.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,87 @@
import gnupg
import getpass
import sys
import sys, pickle
from binascii import hexlify, unhexlify
import gnupg, getpass
from pypeerassets.kutil import Kutil

mypg = None
class GpgKeystore:
"""
Implements reading and writing from gpg encrypted file.
python-gnupg is a wrapper around the gpg cli, which makes it inherently fragile
Uses pickle because private keys are binary
"""

def __init__(self, Settings, keyfile):
assert Settings.keystore == "gnupg"

def read_keystore(Settings,keyfile) -> str:
mykeys = ""
self._key = Settings.gnupgkey
self._keyfile = keyfile

if Settings.keystore == "gnupg" and Settings.provider != "rpcnode":
mypg = gnupg.GPG(binary='/usr/bin/gpg',homedir=Settings.gnupgdir,use_agent=bool(Settings.gnupgagent=="True"),keyring='pubring.gpg',secring='secring.gpg')
self._init_settings = dict(
homedir=Settings.gnupgdir,
use_agent=bool(Settings.gnupgagent == "True"),
keyring='pubring.gpg',
secring='secring.gpg')
self.gpg = gnupg.GPG(**self._init_settings)

def read(self) -> dict:
password = getpass.getpass("Input gpg key password:")
fd = open(keyfile)
data = fd.read()
contents = open(self._keyfile, 'rb').read()

if len(data)>0:
mykeys = str(mypg.decrypt(data,passphrase=password))
fd.close()
else:
print("using rpcnode")
if not len(contents):
return {}

return mykeys
decrypted = self.gpg.decrypt(contents, passphrase=password)

def write_keystore(Settings,keyfile,keys):
assert decrypted.ok, decrypted.status
return pickle.loads(unhexlify(str(decrypted).encode()))

if mypg:
data = str(mypg.encrypt(str(keys),Settings.gnupgkey))
fd = open(keyfile,"w")
fd.write(data)
fd.close()
def write(self, data: dict) -> str:
encrypted = self.gpg.encrypt(hexlify(pickle.dumps(data)).decode(), self._key)
assert encrypted.ok, encrypted.status
keyfile = open(self._keyfile, "w")
keyfile.write(str(encrypted))
keyfile.close()

class KeyedProvider:

def as_local_key_provider(Provider):
"""
Keystore class
factory for subclassing Providers,
allowing for local key management and isinstance checks
"""

@classmethod
def __init__(self, provider, keysJson: str=""):
class LocalKeyProvider(Provider):

"""
:
Wraps a provider, shadowing it's private key management and deferring other logic.
Uses an in-memory store to handle
importprivkey, getaddressesbyaccount, listaccounts, and dumpprivkeys
"""
self.provider = provider

if keysJson != "":
self.privkeys = eval(keysJson)
else:
self.privkeys = {}
def __init__(self, keystore: GpgKeystore, **kwargs):
super(Provider, self).__init__(**kwargs)
self.keystore = keystore
self.privkeys = keystore.read()

def importprivkey(self, privkey: str, label: str) -> int:
"""import <privkey> with <label>"""
mykey = Kutil(network=self.network, wif=privkey)

@classmethod
def __getattr__(self, name):
return getattr(self.provider, name)
if label not in self.privkeys.keys():
self.privkeys[label] = []

@classmethod
def importprivkey(self, privkey: str, label: str) -> int:
"""import <privkey> with <label>"""
mykey = Kutil(wif=privkey)
if mykey.privkey not in [key['privkey'] for key in self.privkeys[label]]:
self.privkeys[label].append({ "privkey": mykey.privkey,
"address": mykey.address })

if label not in self.privkeys.keys():
self.privkeys[label] = []
def getaddressesbyaccount(self, label: str) -> list:
if label in self.privkeys.keys():
return [key["address"] for key in self.privkeys[label]]

if mykey._privkey not in [key['privkey'] for key in self.privkeys[label]]:
self.privkeys[label].append({"privkey":mykey._privkey,"address":mykey.address})
def listaccounts(self) -> dict:
return {key:0 for key in self.privkeys.keys()}

@classmethod
def getaddressesbyaccount(self, label: str) -> list:
if label in self.privkeys.keys():
return [key["address"] for key in self.privkeys[label]]
def dumpprivkeys(self) -> dict:
return self.privkeys

@classmethod
def listaccounts(self) -> dict:
return {key:0 for key in self.privkeys.keys()}
return LocalKeyProvider

@classmethod
def dumpprivkeys(self) -> dict:
return self.privkeys
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pypeerassets>=0.1.1
pypeerassets>=0.2.1
terminaltables>=3.1.0
gnupg>=2.1
appdirs

0 comments on commit 30b3d19

Please sign in to comment.