2016-12-16 03:16:48 -05:00
|
|
|
import os
|
2016-12-17 01:07:50 -05:00
|
|
|
from io import BytesIO
|
2016-12-16 03:16:48 -05:00
|
|
|
import subprocess
|
|
|
|
import datetime
|
|
|
|
import jinja2
|
|
|
|
import gpgme
|
|
|
|
import psutil
|
|
|
|
|
|
|
|
def genGPG(conf):
|
|
|
|
# https://media.readthedocs.org/pdf/pygpgme/latest/pygpgme.pdf
|
|
|
|
build = conf['build']
|
2016-12-26 13:34:34 -05:00
|
|
|
dlpath = build['dlpath']
|
2016-12-17 01:07:50 -05:00
|
|
|
bdisk = conf['bdisk']
|
2016-12-16 03:16:48 -05:00
|
|
|
gpghome = conf['gpg']['mygpghome']
|
2017-03-07 18:38:20 -05:00
|
|
|
distkeys = []
|
2017-03-06 03:21:23 -05:00
|
|
|
gpgkeyserver = []
|
|
|
|
for a in conf['build']['arch']:
|
|
|
|
keysrv = conf['src'][a]['gpgkeyserver']
|
2017-03-07 18:38:20 -05:00
|
|
|
distkey = conf['src'][a]['gpgkey']
|
2017-03-06 03:21:23 -05:00
|
|
|
if keysrv and (keysrv not in gpgkeyserver):
|
|
|
|
gpgkeyserver.append(keysrv)
|
2017-04-11 09:06:19 -04:00
|
|
|
if distkey and(distkey not in distkeys):
|
2017-03-07 18:38:20 -05:00
|
|
|
distkeys.append(distkey)
|
2016-12-16 03:16:48 -05:00
|
|
|
templates_dir = '{0}/extra/templates'.format(build['basedir'])
|
|
|
|
mykey = False
|
|
|
|
pkeys = []
|
2016-12-18 02:55:40 -05:00
|
|
|
killStaleAgent(conf)
|
2016-12-16 03:16:48 -05:00
|
|
|
if conf['gpg']['mygpgkey'] != '':
|
|
|
|
mykey = conf['gpg']['mygpgkey']
|
|
|
|
if gpghome == '':
|
|
|
|
# Let's try the default.
|
|
|
|
gpghome = '{0}/.gnupg'.format(os.path.expanduser("~"))
|
|
|
|
else:
|
|
|
|
# No key ID was specified.
|
|
|
|
if gpghome == '':
|
|
|
|
# We'll generate a key if we can't find one here.
|
|
|
|
gpghome = build['dlpath'] + '/.gnupg'
|
2017-04-11 09:06:19 -04:00
|
|
|
killStaleAgent(conf)
|
2016-12-16 03:16:48 -05:00
|
|
|
os.environ['GNUPGHOME'] = gpghome
|
|
|
|
gpg = gpgme.Context()
|
2016-12-16 04:28:53 -05:00
|
|
|
# do we need to add a keyserver?
|
2017-03-06 03:21:23 -05:00
|
|
|
if len(gpgkeyserver) != 0:
|
2016-12-16 04:28:53 -05:00
|
|
|
dirmgr = '{0}/dirmngr.conf'.format(gpghome)
|
2017-03-06 03:21:23 -05:00
|
|
|
for s in gpgkeyserver:
|
|
|
|
if os.path.isfile(dirmgr):
|
|
|
|
with open(dirmgr, 'r+') as f:
|
|
|
|
findme = any(s in line for line in f)
|
|
|
|
if not findme:
|
|
|
|
f.seek(0, os.SEEK_END)
|
|
|
|
f.write("\n# Added by {0}.\nkeyserver {1}\n".format(
|
|
|
|
bdisk['pname'],
|
|
|
|
s))
|
2016-12-16 03:16:48 -05:00
|
|
|
if mykey:
|
|
|
|
try:
|
2017-05-06 06:53:28 -04:00
|
|
|
pkeys.append(gpg.get_key(mykey, True))
|
2016-12-16 03:16:48 -05:00
|
|
|
except:
|
|
|
|
exit('{0}: ERROR: You specified using {1} but we have no secret key for that ID!'.format(
|
|
|
|
datetime.datetime.now(),
|
|
|
|
mykey))
|
|
|
|
else:
|
2017-05-06 06:53:28 -04:00
|
|
|
for key in gpg.keylist(None, True):
|
2016-12-16 03:16:48 -05:00
|
|
|
if key.can_sign:
|
|
|
|
pkeys.append(key)
|
|
|
|
break
|
|
|
|
if len(pkeys) == 0:
|
|
|
|
print("{0}: [GPG] Generating a GPG key...".format(datetime.datetime.now()))
|
|
|
|
loader = jinja2.FileSystemLoader(templates_dir)
|
|
|
|
env = jinja2.Environment(loader = loader)
|
|
|
|
tpl = env.get_template('GPG.j2')
|
|
|
|
tpl_out = tpl.render(build = build, bdisk = bdisk)
|
|
|
|
privkey = gpg.get_key(gpg.genkey(tpl_out).fpr, True)
|
|
|
|
pkeys.append(privkey)
|
2016-12-16 04:28:53 -05:00
|
|
|
# do we need to add a keyserver? this is for the freshly-generated GNUPGHOME
|
2017-03-06 03:21:23 -05:00
|
|
|
if len(gpgkeyserver) != 0:
|
2016-12-16 04:28:53 -05:00
|
|
|
dirmgr = '{0}/dirmngr.conf'.format(gpghome)
|
2017-03-06 03:21:23 -05:00
|
|
|
for s in gpgkeyserver:
|
|
|
|
with open(dirmgr, 'r+') as f:
|
|
|
|
findme = any(s in line for line in f)
|
|
|
|
if not findme:
|
|
|
|
f.seek(0, os.SEEK_END)
|
|
|
|
f.write("\n# Added by {0}.\nkeyserver {1}\n".format(
|
|
|
|
bdisk['pname'],
|
|
|
|
s))
|
2016-12-16 04:28:53 -05:00
|
|
|
gpg.signers = pkeys
|
2016-12-16 03:16:48 -05:00
|
|
|
# Now we try to find and add the key for the base image.
|
2016-12-17 01:07:50 -05:00
|
|
|
gpg.keylist_mode = gpgme.KEYLIST_MODE_EXTERN # remote (keyserver)
|
2017-03-07 18:38:20 -05:00
|
|
|
if len(distkeys) > 0: # testing
|
|
|
|
for k in distkeys:
|
|
|
|
key = gpg.get_key(k)
|
|
|
|
importkey = key.subkeys[0].fpr
|
|
|
|
gpg.keylist_mode = gpgme.KEYLIST_MODE_LOCAL # local keyring (default)
|
|
|
|
DEVNULL = open(os.devnull, 'w')
|
|
|
|
print('{0}: [GPG] Importing {1} and signing it for verification purposes...'.format(
|
|
|
|
datetime.datetime.now(),
|
|
|
|
distkey))
|
|
|
|
cmd = ['/usr/bin/gpg',
|
|
|
|
'--recv-keys',
|
|
|
|
'--batch',
|
|
|
|
'--yes',
|
|
|
|
'0x{0}'.format(importkey)]
|
|
|
|
subprocess.call(cmd, stdout = DEVNULL, stderr = subprocess.STDOUT)
|
|
|
|
sigkeys = []
|
|
|
|
for i in gpg.get_key(importkey).subkeys:
|
|
|
|
sigkeys.append(i.fpr)
|
|
|
|
cmd = ['/usr/bin/gpg',
|
|
|
|
'--batch',
|
|
|
|
'--yes',
|
|
|
|
'--lsign-key',
|
|
|
|
'0x{0}'.format(importkey)]
|
|
|
|
subprocess.call(cmd, stdout = DEVNULL, stderr = subprocess.STDOUT)
|
2016-12-26 13:34:34 -05:00
|
|
|
# We need to expose this key to the chroots, too, so we need to export it.
|
|
|
|
with open('{0}/gpgkey.pub'.format(dlpath), 'wb') as f:
|
|
|
|
gpg.export(pkeys[0].subkeys[0].keyid, f)
|
2016-12-16 04:28:53 -05:00
|
|
|
return(gpg)
|
2016-12-16 03:16:48 -05:00
|
|
|
|
2016-12-16 04:28:53 -05:00
|
|
|
def killStaleAgent(conf):
|
2016-12-16 03:16:48 -05:00
|
|
|
# Kill off any stale GPG agents running.
|
|
|
|
# Probably not even needed, but good to have.
|
2016-12-16 04:28:53 -05:00
|
|
|
chrootdir = conf['build']['chrootdir']
|
2017-04-11 09:06:19 -04:00
|
|
|
gpgpath = conf['gpg']['mygpghome']
|
2016-12-16 03:16:48 -05:00
|
|
|
procs = psutil.process_iter()
|
|
|
|
plst = []
|
|
|
|
for p in procs:
|
2016-12-16 04:28:53 -05:00
|
|
|
if (p.name() in ('gpg-agent', 'dirmngr') and p.uids()[0] == os.getuid()):
|
2016-12-16 03:16:48 -05:00
|
|
|
pd = psutil.Process(p.pid).as_dict()
|
2017-04-11 09:06:19 -04:00
|
|
|
for d in (chrootdir, gpgpath):
|
2016-12-16 04:28:53 -05:00
|
|
|
if pd['cwd'].startswith('{0}'.format(d)):
|
|
|
|
plst.append(p.pid)
|
2016-12-16 03:16:48 -05:00
|
|
|
if len(plst) >= 1:
|
|
|
|
for p in plst:
|
|
|
|
psutil.Process(p).terminate()
|
|
|
|
|
|
|
|
def signIMG(path, conf):
|
2017-03-06 03:21:23 -05:00
|
|
|
if conf['build']['sign']:
|
2016-12-17 01:07:50 -05:00
|
|
|
# Do we want to kill off any stale gpg-agents? (So we spawn a new one)
|
|
|
|
# Requires further testing.
|
|
|
|
#killStaleAgent()
|
|
|
|
gpg = conf['gpgobj']
|
|
|
|
print('{0}: [GPG] Signing {1}...'.format(
|
2016-12-16 03:16:48 -05:00
|
|
|
datetime.datetime.now(),
|
2016-12-17 01:07:50 -05:00
|
|
|
path))
|
|
|
|
# May not be necessary; further testing necessary
|
|
|
|
#if os.getenv('GPG_AGENT_INFO'):
|
|
|
|
# del os.environ['GPG_AGENT_INFO']
|
|
|
|
gpg = conf['gpgobj']
|
|
|
|
# ASCII-armor (.asc)
|
|
|
|
gpg.armor = True
|
2016-12-16 03:16:48 -05:00
|
|
|
data_in = open(path, 'rb')
|
2016-12-17 01:07:50 -05:00
|
|
|
sigbuf = BytesIO()
|
|
|
|
sig = gpg.sign(data_in, sigbuf, gpgme.SIG_MODE_DETACH)
|
|
|
|
_ = sigbuf.seek(0)
|
|
|
|
_ = data_in.seek(0)
|
2016-12-16 03:16:48 -05:00
|
|
|
data_in.close()
|
2016-12-17 01:07:50 -05:00
|
|
|
with open('{0}.asc'.format(path), 'wb') as f:
|
|
|
|
f.write(sigbuf.read())
|
|
|
|
print('{0}: [GPG] Wrote {1}.asc (ASCII-armored signature).'.format(
|
|
|
|
datetime.datetime.now(),
|
|
|
|
path))
|
|
|
|
# Binary signature (.sig)
|
|
|
|
gpg.armor = False
|
|
|
|
data_in = open(path, 'rb')
|
|
|
|
sigbuf = BytesIO()
|
|
|
|
sig = gpg.sign(data_in, sigbuf, gpgme.SIG_MODE_DETACH)
|
|
|
|
_ = sigbuf.seek(0)
|
|
|
|
_ = data_in.seek(0)
|
|
|
|
data_in.close()
|
|
|
|
with open('{0}.sig'.format(path), 'wb') as f:
|
|
|
|
f.write(sigbuf.read())
|
|
|
|
print('{0}: [GPG] Wrote {1}.sig (binary signature).'.format(
|
|
|
|
datetime.datetime.now(),
|
|
|
|
path))
|
2016-12-16 03:16:48 -05:00
|
|
|
|
|
|
|
def gpgVerify(sigfile, datafile, conf):
|
2016-12-17 01:07:50 -05:00
|
|
|
gpg = conf['gpgobj']
|
|
|
|
fullkeys = []
|
|
|
|
print('{0}: [GPG] Verifying {1} with {2}...'.format(
|
|
|
|
datetime.datetime.now(),
|
|
|
|
datafile,
|
|
|
|
sigfile))
|
|
|
|
keylst = gpg.keylist()
|
|
|
|
for k in keylst:
|
|
|
|
fullkeys.append(k.subkeys[0].fpr)
|
|
|
|
with open(sigfile,'rb') as s:
|
|
|
|
with open(datafile, 'rb') as f:
|
|
|
|
sig = gpg.verify(s, f, None)
|
|
|
|
for x in sig:
|
|
|
|
if x.validity <= 1:
|
|
|
|
if not x.validity_reason:
|
|
|
|
reason = 'we require a signature trust of 2 or higher'
|
|
|
|
else:
|
|
|
|
reason = x.validity_reason
|
|
|
|
print('{0}: [GPG] Key {1} failed to verify: {2}'.format(
|
|
|
|
datetime.datetime.now(),
|
|
|
|
x.fpr,
|
|
|
|
reason))
|
|
|
|
verified = False
|
|
|
|
skeys = []
|
|
|
|
for k in sig:
|
|
|
|
skeys.append(k.fpr)
|
|
|
|
if k.fpr in fullkeys:
|
|
|
|
verified = True
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
pass
|
|
|
|
if verified:
|
|
|
|
print('{0}: [GPG] {1} verified (success).'.format(
|
|
|
|
datetime.datetime.now(),
|
|
|
|
datafile))
|
|
|
|
else:
|
|
|
|
print('{0}: [GPG] {1} failed verification!'.format(
|
|
|
|
datetime.datetime.now(),
|
|
|
|
datafile))
|
|
|
|
return(verified)
|
2016-12-16 04:28:53 -05:00
|
|
|
|
|
|
|
def delTempKeys(conf):
|
2016-12-17 01:07:50 -05:00
|
|
|
# Create a config option to delete these.
|
|
|
|
# It's handy to keep these keys, but I'd understand if
|
|
|
|
# people didn't want to use them.
|
|
|
|
gpg = conf['gpgobj']
|
|
|
|
if conf['gpg']:
|
|
|
|
keys = []
|
|
|
|
if conf['gpgkey'] != '':
|
|
|
|
keys.append(gpg.get_key(conf['gpgkey']))
|
|
|
|
if conf['mygpghome'] == '':
|
|
|
|
keys.append(gpg.get_key(None, True)) # this is safe; we generated our own
|
|
|
|
for k in keys:
|
|
|
|
gpg.delete(k)
|
2016-12-16 04:28:53 -05:00
|
|
|
killStaleAgent(conf)
|