Rewrite gpg-init Bash script in Python

nistp521
Roman Zeyde 7 years ago
parent 44cdeed024
commit 0f85ae6e2c
No known key found for this signature in database
GPG Key ID: 87CAE5FA46917CBB

@ -1,2 +1,5 @@
[MESSAGES CONTROL] [MESSAGES CONTROL]
disable=invalid-name, missing-docstring, locally-disabled, unbalanced-tuple-unpacking,no-else-return disable=invalid-name, missing-docstring, locally-disabled, unbalanced-tuple-unpacking,no-else-return
[SIMILARITIES]
min-similarity-lines=5

@ -13,11 +13,14 @@ import contextlib
import functools import functools
import logging import logging
import os import os
import re
import subprocess
import sys import sys
import time import time
import semver import semver
from . import agent, client, encode, keyring, protocol from . import agent, client, encode, keyring, protocol
from .. import device, formats, server, util from .. import device, formats, server, util
@ -73,23 +76,111 @@ def export_public_key(device_type, args):
subkey=subkey, subkey=subkey,
signer_func=signer_func) signer_func=signer_func)
sys.stdout.write(protocol.armor(result, 'PUBLIC KEY BLOCK')) return protocol.armor(result, 'PUBLIC KEY BLOCK')
def verify_gpg_version():
"""Make sure that the installed GnuPG is not too old."""
existing_gpg = keyring.gpg_version().decode('ascii')
required_gpg = '>=2.1.11'
msg = 'Existing GnuPG has version "{}" ({} required)'.format(existing_gpg,
required_gpg)
assert semver.match(existing_gpg, required_gpg), msg
def check_output(args):
"""Runs command and returns the output as string."""
log.debug('run: %s', args)
out = subprocess.check_output(args=args).decode('utf-8')
log.debug('out: %r', out)
return out
def run_create(device_type, args): def check_call(args, stdin=None, env=None):
"""Export public GPG key.""" """Runs command and verifies its success."""
log.debug('run: %s', args)
subprocess.check_call(args=args, stdin=stdin, env=env)
def write_file(path, data):
"""Writes data to specified path."""
with open(path, 'w') as f:
log.debug('setting %s contents:\n%s', path, data)
f.write(data)
return f
def run_init(device_type, args):
"""Initialize hardware-based GnuPG identity."""
util.setup_logging(verbosity=args.verbose) util.setup_logging(verbosity=args.verbose)
log.warning('This GPG tool is still in EXPERIMENTAL mode, ' log.warning('This GPG tool is still in EXPERIMENTAL mode, '
'so please note that the API and features may ' 'so please note that the API and features may '
'change without backwards compatibility!') 'change without backwards compatibility!')
existing_gpg = keyring.gpg_version().decode('ascii') verify_gpg_version()
required_gpg = '>=2.1.11'
if semver.match(existing_gpg, required_gpg): # Prepare new GPG home directory for hardware-based identity
export_public_key(device_type, args) device_name = os.path.basename(sys.argv[0]).rsplit('-', 1)[0]
else: log.info('device name: %s', device_name)
log.error('Existing gpg2 has version "%s" (%s required)', homedir = os.path.expanduser('~/.gnupg/{}'.format(device_name))
existing_gpg, required_gpg) log.info('GPG home directory: %s', homedir)
check_call(['rm', '-rf', homedir])
check_call(['mkdir', '-p', homedir])
check_call(['chmod', '700', homedir])
# Generate new GPG identity and import into GPG keyring
pubkey = write_file(os.path.join(homedir, 'pubkey.asc'),
export_public_key(device_type, args))
gpg_binary = keyring.get_gnupg_binary()
check_call([gpg_binary, '--homedir', homedir, '--quiet',
'--import', pubkey.name])
check_call(['rm', '-f', os.path.join(homedir, 'S.gpg-agent')])
# (otherwise, our agent won't be started automatically)
# Make new GPG identity with "ultimate" trust (via its fingerprint)
out = check_output([gpg_binary, '--homedir', homedir, '--list-public-keys',
'--with-fingerprint', '--with-colons'])
fpr = re.findall('fpr:::::::::([0-9A-F]+):', out)[0]
f = write_file(os.path.join(homedir, 'ownertrust.txt'), fpr + ':6\n')
check_call([gpg_binary, '--homedir', homedir,
'--import-ownertrust', f.name])
agent_path = check_output(['which', '{}-gpg-agent'.format(device_name)])
agent_path = agent_path.strip()
# Prepare GPG configuration file
with open(os.path.join(homedir, 'gpg.conf'), 'w') as f:
f.write("""# Hardware-based GPG configuration
agent-program {0}
personal-digest-preferences SHA512
default-key \"{1}\"
""".format(agent_path, args.user_id))
# Prepare GPG agent configuration file
with open(os.path.join(homedir, 'gpg-agent.conf'), 'w') as f:
f.write("""# Hardware-based GPG agent emulator
log-file {0}/gpg-agent.log
verbosity 2
""".format(homedir))
# Prepare a helper script for setting up the new identity
with open(os.path.join(homedir, 'env'), 'w') as f:
f.write("""#!/bin/bash
set -eu
export GNUPGHOME={0}
COMMAND=$*
if [ -z "${{COMMAND}}" ]
then
${{SHELL}}
else
${{COMMAND}}
fi
""".format(homedir))
check_call(['chmod', 'u+x', f.name])
# Load agent and make sure it responds with the new identity
check_call([gpg_binary, '--list-secret-keys'], env={'GNUPGHOME': homedir})
def run_unlock(device_type, args): def run_unlock(device_type, args):
@ -133,13 +224,14 @@ def main(device_type):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers() subparsers = parser.add_subparsers()
p = subparsers.add_parser('create', help='Export public GPG key') p = subparsers.add_parser('init',
help='Initialize hardware-based GnuPG identity')
p.add_argument('user_id') p.add_argument('user_id')
p.add_argument('-e', '--ecdsa-curve', default='nist256p1') p.add_argument('-e', '--ecdsa-curve', default='nist256p1')
p.add_argument('-t', '--time', type=int, default=int(time.time())) p.add_argument('-t', '--time', type=int, default=int(time.time()))
p.add_argument('-v', '--verbose', default=0, action='count') p.add_argument('-v', '--verbose', default=0, action='count')
p.add_argument('-s', '--subkey', default=False, action='store_true') p.add_argument('-s', '--subkey', default=False, action='store_true')
p.set_defaults(func=run_create) p.set_defaults(func=run_init)
p = subparsers.add_parser('unlock', help='Unlock the hardware device') p = subparsers.add_parser('unlock', help='Unlock the hardware device')
p.add_argument('-v', '--verbose', default=0, action='count') p.add_argument('-v', '--verbose', default=0, action='count')

@ -1,62 +0,0 @@
#!/bin/bash
set -eu
USER_ID="${1}"
shift
ARGS="$*"
DEVICE=${DEVICE:="trezor"} # or "ledger"
CURVE=${CURVE:="nist256p1"} # or "ed25519"
TIMESTAMP=${TIMESTAMP:=`date +%s`} # key creation timestamp
HOMEDIR=~/.gnupg/${DEVICE}
# NOTE: starting from GnuPG 2.2, gpg2 -> gpg
GPG_BINARY=$(python -c "import libagent.gpg.keyring as k; print(k.get_gnupg_binary())")
${GPG_BINARY} --version # verify that GnuPG 2.1+ is installed
# Prepare new GPG home directory for hardware-based identity
rm -rf "${HOMEDIR}"
mkdir -p "${HOMEDIR}"
chmod 700 "${HOMEDIR}"
# Generate new GPG identity and import into GPG keyring
$DEVICE-gpg create -v "${USER_ID}" -t "${TIMESTAMP}" -e "${CURVE}" ${ARGS} > "${HOMEDIR}/pubkey.asc"
${GPG_BINARY} --homedir "${HOMEDIR}" -q --import < "${HOMEDIR}/pubkey.asc"
rm -f "${HOMEDIR}/S.gpg-agent" # (otherwise, our agent won't be started automatically)
# Make new GPG identity with "ultimate" trust (via its fingerprint)
FINGERPRINT=$(${GPG_BINARY} --homedir "${HOMEDIR}" --list-public-keys --with-fingerprint --with-colons | sed -n -E 's/^fpr:::::::::([0-9A-F]+):$/\1/p' | head -n1)
echo "${FINGERPRINT}:6" | ${GPG_BINARY} --homedir "${HOMEDIR}" --import-ownertrust 2> /dev/null
AGENT_PATH="$(which ${DEVICE}-gpg-agent)"
# Prepare GPG configuration file
echo "# Hardware-based GPG configuration
agent-program ${AGENT_PATH}
personal-digest-preferences SHA512
default-key \"${USER_ID}\"
" > "${HOMEDIR}/gpg.conf"
# Prepare GPG agent configuration file
echo "# Hardware-based GPG agent emulator
log-file ${HOMEDIR}/gpg-agent.log
verbosity 2
" > "${HOMEDIR}/gpg-agent.conf"
# Prepare a helper script for setting up the new identity
echo "#!/bin/bash
set -eu
export GNUPGHOME=${HOMEDIR}
COMMAND=\$*
if [ -z \"\${COMMAND}\" ]
then
\${SHELL}
else
\${COMMAND}
fi
" > "${HOMEDIR}/env"
chmod u+x "${HOMEDIR}/env"
echo "Starting ${DEVICE}-gpg-agent at ${HOMEDIR}..."
# Load agent and make sure it responds with the new identity
GNUPGHOME="${HOMEDIR}" ${GPG_BINARY} -K 2> /dev/null
Loading…
Cancel
Save