Last active
December 14, 2015 06:29
-
-
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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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