Skip to content

Instantly share code, notes, and snippets.

@paulegan
Last active December 14, 2015 06:29
Show Gist options
  • Save paulegan/5042672 to your computer and use it in GitHub Desktop.
Save paulegan/5042672 to your computer and use it in GitHub Desktop.
Script for sharing secure data with colleagues by encrypting ini-format file with gpg keys.Import others' public gpg keys to your keyring and then use `ACCESS` section to specify which users or groups can decrypt which ini sections ("*" for everything). On edit/save, a separate encrypted file is created for each user or group. Commit the resulti…
[ACCESS]
myusername = *
email@address = host1 site1
GPG_GROUP = sectionA
[host1]
username = user1
password = xxx
[site1]
url = http://example.com
email = xxx@abc.com
challenge1 = xxx
[sectionA]
data = sensitive
#!/usr/bin/env python
import sys
import os
import gpgme
import subprocess
import warnings
from io import BytesIO
from argparse import ArgumentParser
from collections import OrderedDict
from ConfigParser import RawConfigParser
from tempfile import NamedTemporaryFile
INIT_TEMPLATE = '''
[ACCESS]
%s = *
[myhost]
username = me
password = xxx
'''
# global gpgme context
gpg = None
def find_key(id, keys):
"""Check for id string in keyid or email."""
for key in keys:
for subkey in key.subkeys:
if id in subkey.keyid:
return key
for uid in key.uids:
if id in uid.email:
return key
def copy_ini(src, sections=None):
dst = RawConfigParser()
dst._sections = OrderedDict((s, OrderedDict(src.items(s)))
for s in src.sections()
if not sections or
s in sections or
s.split('.', 1)[0] in sections)
return dst
def decrypt(sections=None):
keyid = None
for key in gpg.keylist(None, True):
for subkey in key.subkeys:
if os.path.exists(subkey.keyid):
keyid = subkey.keyid
break
assert keyid, 'Unable to find matching key file'
buf = BytesIO()
with open(keyid) as f:
gpg.decrypt(f, buf)
if sections is not None:
buf.seek(0)
src = RawConfigParser()
src.readfp(buf)
buf = BytesIO()
tmp = copy_ini(src, sections)
tmp.write(buf)
return buf.getvalue().strip()
def encrypt(fp):
keylist = list(gpg.keylist())
src = RawConfigParser()
src.readfp(fp)
for user, sections in src.items('ACCESS'):
key = find_key(user, keylist)
if not key:
warnings.warn('No key found for %s' % user)
continue
user_ini = copy_ini(src, sections.split() if sections != '*' else None)
if not user_ini.sections():
warnings.warn('No sections for %s' % user)
continue
buf = BytesIO()
user_ini.write(buf)
buf.seek(0)
with open(key.subkeys[0].keyid, 'w') as f:
gpg.encrypt([key], 0, buf, f)
def edit(text):
assert '[ACCESS]' in text, 'No edit permissions'
with NamedTemporaryFile() as tmp:
tmp.write(text)
tmp.seek(0)
editor = os.environ.get('EDITOR', 'vi')
subprocess.check_call((editor, tmp.name))
tmp.seek(0)
encrypt(tmp)
def init():
# use email from first private key as username
key = gpg.keylist(None, True).next()
email = key.uids[0].email
edit(INIT_TEMPLATE.strip() % email.split('@', 1)[0])
if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('-i', '--init', action='store_true', help='Initialise a new password db')
parser.add_argument('-e', '--edit', action='store_true', help='Edit a password db')
parser.add_argument('section', nargs='*', help='Return only the specified db sections')
args = parser.parse_args()
try:
gpg = gpgme.Context()
if args.init:
init()
elif args.edit:
edit(decrypt())
else:
print decrypt(args.section)
except Exception, e:
raise
print >>sys.stderr, 'ERROR:', getattr(e, 'message', None) or str(e)
sys.exit(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment