diff --git a/setup.py b/setup.py index 55f0eec..fadf6ff 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,8 @@ setup( author='Roman Zeyde', author_email='roman.zeyde@gmail.com', url='http://github.com/romanz/trezor-agent', - packages=['trezor_agent'], + packages=['trezor_agent', 'trezor_agent.gpg'], + scripts=['trezor_agent/gpg/trezor-git-gpg-wrapper.sh'], install_requires=['ecdsa>=0.13', 'ed25519>=1.4', 'Cython>=0.23.4', 'protobuf>=3.0.0b2.post2', 'trezor>=0.6.12', 'semver>=2.2'], platforms=['POSIX'], classifiers=[ diff --git a/trezor_agent/gpg/README.md b/trezor_agent/gpg/README.md index 8add8d4..500b7e6 100644 --- a/trezor_agent/gpg/README.md +++ b/trezor_agent/gpg/README.md @@ -1,22 +1,56 @@ -# Generate new stand-alone GPG identity +# Using TREZOR as hardware GPG agent +## Generate new GPG signing key: +First, verify that you have GPG 2.1+ [installed](https://gist.github.com/vt0r/a2f8c0bcb1400131ff51): ``` -$ USER_ID="Satoshi Nakamoto " -$ trezor-gpg create "${USER_ID}" > identity.pub # create new TREZOR-based GPG identity -$ gpg2 --import identity.pub # import into local GPG public keyring -$ gpg2 --edit "${USER_ID}" trust # OPTIONAL: mark the key as trusted +$ gpg2 --version | head -n1 +gpg (GnuPG) 2.1.11 ``` -# Generate new subkey for existing GPG identity +Install the latest development version of `trezor-agent`: ``` -$ USER_ID="Satoshi Nakamoto " -$ gpg2 --list-keys "${USER_ID}" # make sure this identity already exists -$ trezor-gpg create --subkey "${USER_ID}" > identity.pub # create new TREZOR-based GPG public key -$ gpg2 --import identity.pub # append it to existing identity +$ pip install git+https://github.com/romanz/trezor-agent.git@master ``` -# Generate signatures using the TREZOR device +Define your GPG user ID as an environment variable: ``` -$ trezor-gpg sign EXAMPLE > EXAMPLE.sig # confirm signature using the device -$ gpg2 --verify EXAMPLE.sig # verify using standard GPG binary +$ export TREZOR_GPG_USER_ID="John Doe " ``` + +There are two ways to generate TREZOR-based GPG public keys, as described below. + +### (1) create new GPG identity: +``` +$ trezor-gpg create > identity.pub # create new TREZOR-based GPG identity +$ gpg2 --import identity.pub # import into local GPG public keyring +$ gpg2 --list-keys # verify that the new identity is created correctly +$ gpg2 --edit "${TREZOR_GPG_USER_ID}" trust # OPTIONAL: mark the key as trusted +``` +[![asciicast](https://asciinema.org/a/44880.png)](https://asciinema.org/a/44880) + +### (2) create new subkey for an existing GPG identity: +``` +$ gpg2 --list-keys "${TREZOR_GPG_USER_ID}" # make sure this identity already exists +$ trezor-gpg create --subkey > identity.pub # create new TREZOR-based GPG subkey +$ gpg2 --import identity.pub # append it to an existing identity +$ gpg2 --list-keys "${TREZOR_GPG_USER_ID}" # verify that the new subkey is added to keyring +``` +[![subkey](https://asciinema.org/a/8t78s6pqo5yocisaiolqnjp63.png)](https://asciinema.org/a/8t78s6pqo5yocisaiolqnjp63) + +## Generate GPG signatures using a TREZOR device: +``` +$ trezor-gpg sign EXAMPLE # confirm signature using the device +$ gpg2 --verify EXAMPLE.asc # verify using standard GPG binary +``` +[![sign](https://asciinema.org/a/f1unkptesb7anq09i8wugoko6.png)](https://asciinema.org/a/f1unkptesb7anq09i8wugoko6) + +## Git commit & tag signatures: +Git can use GPG to sign and verify commits and tags (see [here](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)): +``` +$ git config --local gpg.program "trezor-git-gpg-wrapper.sh" +$ git commit --gpg-sign # create GPG-signed commit +$ git log --show-signature -1 # verify commit signature +$ git tag --sign "TAG" # create GPG-signed tag +$ git verify-tag "TAG" # verify tag signature +``` +[![asciicast](https://asciinema.org/a/44879.png)](https://asciinema.org/a/44879) diff --git a/trezor_agent/gpg/agent.py b/trezor_agent/gpg/agent.py index e058ac7..a624643 100644 --- a/trezor_agent/gpg/agent.py +++ b/trezor_agent/gpg/agent.py @@ -123,10 +123,11 @@ def sign(sock, keygrip, digest): line = _unescape(line) log.debug('line: %r', line) prefix, sig = line.split(' ', 1) - assert prefix == 'D' + if prefix != 'D': + raise ValueError(line) sig, leftover = _parse(sig) - assert not leftover + assert not leftover, leftover return _parse_sig(sig) diff --git a/trezor_agent/gpg/debug_subkeys.py b/trezor_agent/gpg/debug_subkeys.py deleted file mode 100644 index a9aff80..0000000 --- a/trezor_agent/gpg/debug_subkeys.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python -"""Check GPG v2 signature for a given public key.""" -import argparse -import logging - -from . import decode -from .. import util - -log = logging.getLogger(__name__) - - -def main(): - """Main function.""" - p = argparse.ArgumentParser() - p.add_argument('pubkey') - p.add_argument('-v', '--verbose', action='store_true', default=False) - args = p.parse_args() - logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO, - format='%(asctime)s %(levelname)-10s %(message)s') - stream = open(args.pubkey, 'rb') - parser = decode.parse_packets(util.Reader(stream)) - pubkey, userid, sig1, subkey, sig2 = parser - - digest = decode.digest_packets([pubkey, userid, sig1]) - assert sig1['hash_prefix'] == digest[:2] - decode.verify_digest( - pubkey=pubkey, digest=digest, - signature=sig1['sig'], label='GPG public key (self sig)') - - digest = decode.digest_packets([pubkey, subkey, sig2]) - assert sig2['hash_prefix'] == digest[:2] - decode.verify_digest( - pubkey=pubkey, digest=digest, - signature=sig2['sig'], label='GPG subkey (1st sig)') - - sig3, = sig2['embedded'] - digest = decode.digest_packets([pubkey, subkey, sig3]) - decode.verify_digest( - pubkey=subkey, digest=digest, - signature=sig3['sig'], label='GPG subkey (2nd sig)') - -if __name__ == '__main__': - main() diff --git a/trezor_agent/gpg/demo.sh b/trezor_agent/gpg/demo.sh deleted file mode 100755 index cf44a61..0000000 --- a/trezor_agent/gpg/demo.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -set -x -CREATED=1460731897 # needed for consistent public key creation -NAME="trezor_demo" # will be used as GPG user id and public key name - -echo "Hello GPG World!" > EXAMPLE -# Create, sign and export the public key -trezor-gpg $NAME --time $CREATED -o $NAME.pub - -# Install GPG v2.1 (modern) and import the public key -gpg2 --import $NAME.pub -gpg2 --list-keys $NAME -# gpg2 --edit-key $NAME trust # optional: mark it as trusted - -# Perform actual GPG signature using TREZOR device -trezor-gpg $NAME EXAMPLE - -# Verify signature using GPG2 binary -gpg2 --verify EXAMPLE.sig diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index c672aa8..e3dc32b 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -251,7 +251,7 @@ def _make_signature(signer_func, data_to_sign, public_algo, log.debug('hashing %d bytes', len(data_to_hash)) digest = hashlib.sha256(data_to_hash).digest() - log.info('SHA256 digest to sign: %s', util.hexlify(digest)) + log.info('signing digest: %s', util.hexlify(digest)) sig = signer_func(digest=digest) return bytes(header + hashed + unhashed + diff --git a/trezor_agent/gpg/git_wrapper.py b/trezor_agent/gpg/git_wrapper.py deleted file mode 100755 index 09e1a09..0000000 --- a/trezor_agent/gpg/git_wrapper.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python -"""A simple wrapper for Git commit/tag GPG signing.""" -import logging -import subprocess as sp -import sys - -from . import decode, encode - -log = logging.getLogger(__name__) - - -def main(): - """Main function.""" - logging.basicConfig(level=logging.INFO, - format='%(asctime)s %(levelname)-10s %(message)s') - - log.debug('sys.argv: %s', sys.argv) - args = sys.argv[1:] - if '--verify' in args: - return sp.call(['gpg2'] + args) - else: - command = args[0] - user_id = ' '.join(args[1:]) - assert command == '-bsau' # --detach-sign --sign --armor --local-user - pubkey = decode.load_from_gpg(user_id, use_custom=True) - s = encode.Signer.from_public_key(user_id=user_id, pubkey=pubkey) - - data = sys.stdin.read() - sig = s.sign(data) - sig = encode.armor(sig, 'SIGNATURE') - sys.stdout.write(sig) - s.close() - -if __name__ == '__main__': - main() diff --git a/trezor_agent/gpg/signer.py b/trezor_agent/gpg/signer.py index b8c6014..237d592 100755 --- a/trezor_agent/gpg/signer.py +++ b/trezor_agent/gpg/signer.py @@ -5,6 +5,7 @@ import logging import subprocess as sp import sys import time +import os from . import decode, encode @@ -13,17 +14,18 @@ log = logging.getLogger(__name__) def run_create(args): """Generate a new pubkey for a new/existing GPG identity.""" - s = encode.Signer(user_id=args.user_id, created=args.time, + user_id = os.environ['TREZOR_GPG_USER_ID'] + s = encode.Signer(user_id=user_id, created=args.time, curve_name=args.ecdsa_curve) if args.subkey: subkey = s.subkey() - primary = sp.check_output(['gpg2', '--export', args.user_id]) + primary = sp.check_output(['gpg2', '--export', user_id]) result = primary + subkey else: result = s.export() s.close() - return encode.armor(result, 'PUBLIC KEY BLOCK') + sys.stdout.write(encode.armor(result, 'PUBLIC KEY BLOCK')) def run_sign(args): @@ -39,7 +41,19 @@ def run_sign(args): sig = encode.armor(sig, 'SIGNATURE') decode.verify(pubkey=pubkey, signature=sig, original_data=data) - return sig + + filename = '-' # write to stdout + if args.output: + filename = args.output + elif args.filename: + filename = args.filename + '.asc' + + if filename == '-': + output = sys.stdout + else: + output = open(filename, 'wb') + + output.write(sig) def main(): @@ -49,8 +63,6 @@ def main(): subparsers = p.add_subparsers() create = subparsers.add_parser('create') - create.add_argument('user_id', help='e.g. ' - '"Satoshi Nakamoto "') create.add_argument('-s', '--subkey', action='store_true', default=False) create.add_argument('-e', '--ecdsa-curve', default='nist256p1') create.add_argument('-t', '--time', type=int, default=int(time.time())) @@ -58,13 +70,13 @@ def main(): sign = subparsers.add_parser('sign') sign.add_argument('filename', nargs='?') + sign.add_argument('-o', '--output', default=None) sign.set_defaults(run=run_sign) args = p.parse_args() logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO, format='%(asctime)s %(levelname)-10s %(message)s') - result = args.run(args) - sys.stdout.write(result) + args.run(args) if __name__ == '__main__': diff --git a/trezor_agent/gpg/test.sh b/trezor_agent/gpg/test.sh deleted file mode 100644 index d80c208..0000000 --- a/trezor_agent/gpg/test.sh +++ /dev/null @@ -1,16 +0,0 @@ -# NEVER RUN ON YOUR OWN REAL GPG KEYS!!!!! THEY WILL BE DELETED!!!!! -set -x -e -u -CURVE=ed25519 -#CURVE=nist256p1 -(cd ~/.gnupg && rm -rf openpgp-revocs.d/ private-keys-v1.d/ pubring.kbx* trustdb.gpg /tmp/log *.gpg; killall gpg-agent || true) -gpg2 --full-gen-key --expert -gpg2 --export > romanz.pub -NOW=`date +%s` -USERID="Roman Zeyde " -trezor-gpg -t $NOW -e $CURVE --subkey "$USERID" -o subkey.pub -gpg2 -K -gpg2 -v --import <(cat romanz.pub subkey.pub) -gpg2 -K - -trezor-gpg -t $NOW -e $CURVE "$USERID" EXAMPLE -gpg2 --verify EXAMPLE.sig diff --git a/trezor_agent/gpg/trezor-git-gpg-wrapper.sh b/trezor_agent/gpg/trezor-git-gpg-wrapper.sh index 04a8807..c65bbe9 100755 --- a/trezor_agent/gpg/trezor-git-gpg-wrapper.sh +++ b/trezor_agent/gpg/trezor-git-gpg-wrapper.sh @@ -3,5 +3,5 @@ if [[ "$*" == *"--verify"* ]] then gpg2 $* # verify using GPG2 (for ECDSA and EdDSA keys) else - python -m trezor_agent.gpg.git_wrapper $* # sign using TREZOR + trezor-gpg sign -o- # sign using TREZOR and write the signature to stdout fi