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()}