diff --git a/bdisk/GPG.py b/bdisk/GPG.py index 72a7f80..ad10df8 100644 --- a/bdisk/GPG.py +++ b/bdisk/GPG.py @@ -1,3 +1,4 @@ +import datetime import gpg import os import psutil @@ -14,6 +15,12 @@ _algmaps = {#'cv': 'cv{keysize}', # DISABLED, can't sign (only encrypt). Curren 'rsa': 'rsa{keysize}', # Variable (1024 <> 4096), but we only support 1024, 2048, 4096 'dsa': 'dsa{keysize}'} # Variable (768 <> 3072), but we only support 768, 2048, 3072 +# This is just a helper function to get a delta from a unix epoch. +def _epoch_helper(epoch): + d = datetime.datetime.utcfromtimestamp(epoch) - datetime.datetime.utcnow() + return(abs(int(d.total_seconds()))) # Returns a positive integer even if negative... + #return(int(d.total_seconds())) + # http://files.au.adversary.org/crypto/GPGMEpythonHOWTOen.html # https://www.gnupg.org/documentation/manuals/gpgme.pdf # Support ECC? https://www.gnupg.org/faq/whats-new-in-2.1.html#ecc @@ -125,11 +132,62 @@ class GPGHandler(object): # for p in plst: # psutil.Process(p).terminate() - def CreateKey(self, params): # TODO: explicit params + def CreateKey(self, name, algo, keysize, email = None, comment = None, passwd = None, key = None, expiry = None): + algo = _algmaps[algo].format(keysize = keysize) + userid = name + userid += ' ({0})'.format(comment) if comment else '' + userid += ' <{0}>'.format(email) if email else '' + if not expiry: + expires = False + else: + expires = True + self.ctx.create_key(userid, + algorithm = algo, + expires = expires, + expires_in = _epoch_helper(expiry), + sign = True) + # Even if expires is False, it still parses the expiry... + # except OverflowError: # Only trips if expires is True and a negative expires occurred. + # raise ValueError(('Expiration epoch must be 0 (to disable) or a future time! ' + # 'The specified epoch ({0}, {1}) is in the past ' + # '(current time is {2}, {3}).').format(expiry, + # str(datetime.datetime.utcfromtimestamp(expiry)), + # datetime.datetime.utcnow().timestamp(), + # str(datetime.datetime.utcnow()))) + return(k) # We can't use self.ctx.create_key; it's a little limiting. # It's a fairly thin wrapper to .op_createkey() (the C GPGME API gpgme_op_createkey) anyways. - - pass + flags = (gpg.constants.create.SIGN | + gpg.constants.create.CERT) + if not expiry: + flags = (flags | gpg.constants.create.NOEXPIRE) + if not passwd: + flags = (flags | gpg.constants.create.NOPASSWD) + else: + # Thanks, gpg/core.py#Context.create_key()! + sys_pinentry = gpg.constants.PINENTRY_MODE_DEFAULT + old_pass_cb = getattr(self, '_passphrase_cb', None) + self.ctx.pinentry_mode = gpg.constants.PINENTRY_MODE_LOOPBACK + def passphrase_cb(hint, desc, prev_bad, hook = None): + return(passwd) + self.ctx.set_passphrase_cb(passphrase_cb) + try: + if not key: + try: + self.ctx.op_createkey(userid, algo, 0, 0, flags) + k = self.ctx.get_key(self.ctx.op_genkey_result().fpr, secret = True) + else: + if not isinstance(key, gpg.gpgme._gpgme_key): + key = self.ctx.get_key(key) + if not key: + raise ValueError('Key {0} does not exist'.format()) + #self.ctx.op_createsubkey(key, ) + finally: + if not passwd: + self.ctx.pinentry_mode = sys_pinentry + if old_pass_cb: + self.ctx.set_passphrase_cb(*old_pass_cb[1:]) + return(k) def GetSigs(self, data_in): key_ids = [] @@ -153,3 +211,5 @@ class GPGHandler(object): def CheckSigs(self, keys, sig_data): try: self.ctx.verify(sig_data) + except: + pass # TODO diff --git a/bdisk/bdisk.xsd b/bdisk/bdisk.xsd index 9485a01..4f7ab91 100644 --- a/bdisk/bdisk.xsd +++ b/bdisk/bdisk.xsd @@ -665,6 +665,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bdisk/confparse.py b/bdisk/confparse.py index 6ba6616..56abec9 100644 --- a/bdisk/confparse.py +++ b/bdisk/confparse.py @@ -1,3 +1,4 @@ +import copy import os import pprint import re @@ -210,16 +211,24 @@ class Conf(object): for attr in elem.xpath('./@*'): self.cfg['gpg'][attr.attrname] = transform.xml2py(attr) for key in elem.xpath('./key'): - _key = {'algo': 'rsa', - 'keysize': '4096', - 'expire': '0', - 'name': None, - 'email': None, - 'comment': None} + _keytpl = {'algo': 'rsa', + 'keysize': '4096'} + _key = copy.deepcopy(_keytpl) + _key['name'] = None + _key['email'] = None + _key['comment'] = None for attr in key.xpath('./@*'): _key[attr.attrname] = transform.xml2py(attr) for param in key.xpath('./*'): - _key[param.tag] = transform.xml2py(param.text, attrib = False) + if param.tag == 'subkey': + # We only support one subkey (for key generation). + if 'subkey' not in _key: + _key['subkey'] = copy.deepcopy(_keytpl) + for attr in param.xpath('./@*'): + _key['subkey'][attr.attrname] = transform.xml2py(attr) + print(_key) + else: + _key[param.tag] = transform.xml2py(param.text, attrib = False) self.cfg['gpg']['keys'].append(_key) return() diff --git a/bdisk/utils.py b/bdisk/utils.py index fd82060..5280ee1 100644 --- a/bdisk/utils.py +++ b/bdisk/utils.py @@ -135,7 +135,8 @@ class detect(object): return(salt) def remote_files(self, url_base, ptrn = None, flags = []): - soup = BeautifulSoup(Download(url_base, progress = False).bytes_obj, 'lxml') + soup = BeautifulSoup(Download(url_base, progress = False).fetch().decode('utf-8'), + 'lxml') urls = [] if 'regex' in flags: if not isinstance(ptrn, str): diff --git a/docs/examples/multi_profile.xml b/docs/examples/multi_profile.xml index bb9dcb2..6f734cd 100644 --- a/docs/examples/multi_profile.xml +++ b/docs/examples/multi_profile.xml @@ -136,13 +136,15 @@ - {xpath%../../../meta/dev/author/text()} {xpath%../../../meta/dev/email/text()} + + for {xpath%../../../meta/names/pname/text()} [autogenerated] | {xpath%../../../meta/uri/text()} | {xpath%../../../meta/desc/text()}