From 673b1df648bce3b8fe30b1528311a5f0c3f37e40 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 28 Apr 2016 21:31:01 +0300 Subject: [PATCH 01/24] 1st try --- trezor_agent/gpg/encode.py | 23 ++++++++++++++++++++--- trezor_agent/gpg/signer.py | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 3b09c2e..9f6824c 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -145,14 +145,12 @@ class AgentSigner(object): class Signer(object): """Performs GPG signing operations.""" - SignerType = AgentSigner - def __init__(self, user_id, created, curve_name): """Construct and loads a public key from the device.""" self.user_id = user_id assert curve_name in formats.SUPPORTED_CURVES - self.conn = self.SignerType(user_id, curve_name=curve_name) + self.conn = HardwareSigner(user_id, curve_name=curve_name) self.verifying_key = self.conn.pubkey() self.created = int(created) @@ -225,9 +223,27 @@ class Signer(object): return pubkey_packet + user_id_packet + sign_packet def subkey(self, user_id): + subkey_packet = packet(tag=14, blob=self._pubkey_data()) primary = decode.load_from_gpg(user_id) keygrip = agent.get_keygrip(user_id) log.info('adding as subkey to %s (%s)', user_id, keygrip) + data_to_sign = primary['_to_hash'] + self._pubkey_data_to_hash() + hashed_subpackets = [ + subpacket_time(self.created), # signature creaion time + subpacket_byte(0x1B, 2)] # key flags (certify & sign) + + _conn = self.conn + self.conn = AgentSigner(user_id, curve_name=formats.CURVE_NIST256) + self.key_id = lambda: primary['key_id'] + signature = self._make_signature(visual='Add subkey', + data_to_sign=data_to_sign, + sig_type=0x18, # Subkey Binding Signature + hashed_subpackets=hashed_subpackets) + self.conn = _conn + + sign_packet = packet(tag=2, blob=signature) + return subkey_packet + sign_packet + def sign(self, msg, sign_time=None): """Sign GPG message at specified time.""" @@ -251,6 +267,7 @@ class Signer(object): curve_info['algo_id'], 8) # hash_alg (SHA256) hashed = subpackets(*hashed_subpackets) + log.info('key_id: %s', util.hexlify(self.key_id())) unhashed = subpackets( subpacket(16, self.key_id()) # issuer key id ) diff --git a/trezor_agent/gpg/signer.py b/trezor_agent/gpg/signer.py index 7654251..1cffcc0 100755 --- a/trezor_agent/gpg/signer.py +++ b/trezor_agent/gpg/signer.py @@ -44,7 +44,7 @@ def main(): if not args.filename: s = encode.Signer(user_id=user_id, created=args.time, curve_name=args.ecdsa_curve) - pubkey = s.export() + pubkey = s.subkey(user_id='romanz') ext = '.pub' if args.armor: pubkey = encode.armor(pubkey, 'PUBLIC KEY BLOCK') From 1d3ba7e9b7aa4aa3fe8eb1dff4c772c058ab890d Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 28 Apr 2016 22:10:40 +0300 Subject: [PATCH 02/24] subkey: add backsig --- trezor_agent/gpg/encode.py | 16 ++++++++++++---- trezor_agent/gpg/test.sh | 7 +++++++ 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 trezor_agent/gpg/test.sh diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 9f6824c..0ab3df8 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -228,17 +228,24 @@ class Signer(object): keygrip = agent.get_keygrip(user_id) log.info('adding as subkey to %s (%s)', user_id, keygrip) data_to_sign = primary['_to_hash'] + self._pubkey_data_to_hash() + hashed_subpackets = [ + subpacket_time(self.created)] # signature creaion time + back_sign = self._make_signature(visual='Add subkey', + data_to_sign=data_to_sign, + sig_type=0x19, # Primary Key Binding Signature + hashed_subpackets=hashed_subpackets) + log.info('back_sign: %r', back_sign) hashed_subpackets = [ subpacket_time(self.created), # signature creaion time subpacket_byte(0x1B, 2)] # key flags (certify & sign) - _conn = self.conn self.conn = AgentSigner(user_id, curve_name=formats.CURVE_NIST256) self.key_id = lambda: primary['key_id'] signature = self._make_signature(visual='Add subkey', data_to_sign=data_to_sign, sig_type=0x18, # Subkey Binding Signature - hashed_subpackets=hashed_subpackets) + hashed_subpackets=hashed_subpackets, + unhashed=[subpacket(32, bytes(back_sign))]) self.conn = _conn sign_packet = packet(tag=2, blob=signature) @@ -259,7 +266,7 @@ class Signer(object): return packet(tag=2, blob=blob) def _make_signature(self, visual, data_to_sign, - hashed_subpackets, sig_type=0): + hashed_subpackets, sig_type=0, unhashed=()): curve_info = SUPPORTED_CURVES[self.conn.curve_name] header = struct.pack('>BBBB', 4, # version @@ -269,7 +276,8 @@ class Signer(object): hashed = subpackets(*hashed_subpackets) log.info('key_id: %s', util.hexlify(self.key_id())) unhashed = subpackets( - subpacket(16, self.key_id()) # issuer key id + subpacket(16, self.key_id()), # issuer key id + *unhashed ) tail = b'\x04\xff' + struct.pack('>L', len(header) + len(hashed)) data_to_hash = data_to_sign + header + hashed + tail diff --git a/trezor_agent/gpg/test.sh b/trezor_agent/gpg/test.sh new file mode 100644 index 0000000..fd7975d --- /dev/null +++ b/trezor_agent/gpg/test.sh @@ -0,0 +1,7 @@ +set -x +(cd ~/.gnupg && rm -r openpgp-revocs.d/ private-keys-v1.d/ pubring.kbx* trustdb.gpg /tmp/log *.gpg; killall gpg-agent) +gpg2 --full-gen-key --expert +gpg2 --export > romanz.pub +NOW=`date +%s`; trezor-gpg -t $NOW "romanz" -o subkey.pub +gpg2 -vv --import <(cat romanz.pub subkey.pub) +gpg2 -k From a45c6c1300c31b391bae4cd10966b6572d6c3df6 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 28 Apr 2016 22:17:08 +0300 Subject: [PATCH 03/24] horrible hack - but IT WORKS!!! --- trezor_agent/gpg/decode.py | 10 ++++++++-- trezor_agent/gpg/test.sh | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/trezor_agent/gpg/decode.py b/trezor_agent/gpg/decode.py index d57bd2c..ebe96b3 100644 --- a/trezor_agent/gpg/decode.py +++ b/trezor_agent/gpg/decode.py @@ -115,6 +115,7 @@ def _parse_signature(stream): p['unhashed_subpackets'] = parse_subpackets(stream) embedded = list(_parse_embedded_signatures(p['unhashed_subpackets'])) if embedded: + log.info('embedded sigs: %s', embedded) p['embedded'] = embedded p['hash_prefix'] = stream.readfmt('2s') @@ -248,13 +249,18 @@ def digest_packets(packets): def load_public_key(stream): """Parse and validate GPG public key from an input stream.""" packets = list(parse_packets(util.Reader(stream))) - pubkey, userid, signature = packets[:3] + subkey = subsig = None + if len(packets) == 5: + pubkey, userid, signature, subkey, subsig = packets + else: + pubkey, userid, signature = packets + digest = digest_packets([pubkey, userid, signature]) assert signature['hash_prefix'] == digest[:2] log.debug('loaded public key "%s"', userid['value']) verify_digest(pubkey=pubkey, digest=digest, signature=signature['sig'], label='GPG public key') - return pubkey + return subkey or pubkey def load_signature(stream, original_data): diff --git a/trezor_agent/gpg/test.sh b/trezor_agent/gpg/test.sh index fd7975d..4f21a02 100644 --- a/trezor_agent/gpg/test.sh +++ b/trezor_agent/gpg/test.sh @@ -1,7 +1,12 @@ +# NEVER RUN ON YOUR OWN REAL GPG KEYS!!!!! THEY WILL BE DELETED!!!!! set -x (cd ~/.gnupg && rm -r openpgp-revocs.d/ private-keys-v1.d/ pubring.kbx* trustdb.gpg /tmp/log *.gpg; killall gpg-agent) gpg2 --full-gen-key --expert gpg2 --export > romanz.pub -NOW=`date +%s`; trezor-gpg -t $NOW "romanz" -o subkey.pub +NOW=`date +%s` +trezor-gpg -t $NOW "romanz" -o subkey.pub gpg2 -vv --import <(cat romanz.pub subkey.pub) -gpg2 -k +gpg2 -K + +trezor-gpg -t $NOW "romanz" EXAMPLE +gpg2 --verify EXAMPLE.sig From 32984d2d3f333a7e6298897a9ccf0c0b199ed70b Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 29 Apr 2016 10:16:58 +0300 Subject: [PATCH 04/24] agent: add support for gpg passphrase entry --- trezor_agent/gpg/agent.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/trezor_agent/gpg/agent.py b/trezor_agent/gpg/agent.py index 34a5dcf..2b43dfb 100644 --- a/trezor_agent/gpg/agent.py +++ b/trezor_agent/gpg/agent.py @@ -82,9 +82,18 @@ def sign(sock, keygrip, digest): assert len(digest) == 32 assert _communicate(sock, 'RESET').startswith('OK') + + ttyname = sp.check_output('tty').strip() + options = ['ttyname={}'.format(ttyname)] # set TTY for passphrase entry + for opt in options: + assert _communicate(sock, 'OPTION {}'.format(opt)) == 'OK' + assert _communicate(sock, 'SIGKEY {}'.format(keygrip)) == 'OK' assert _communicate(sock, 'SETHASH {} {}'.format(hash_algo, _hex(digest))) == 'OK' + assert _communicate(sock, 'SETKEYDESC ' + 'Please+enter+the+passphrase+to+unlock+the+OpenPGP%0A' + 'secret+key,+to+sign+a+new+TREZOR-based+subkey') == 'OK' assert _communicate(sock, 'PKSIGN') == 'OK' line = _recvline(sock).strip() From 12d640c66bd26ce2d2b70a92d54421c0294044be Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 29 Apr 2016 10:25:46 +0300 Subject: [PATCH 05/24] fixup pep8 --- trezor_agent/gpg/agent.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/trezor_agent/gpg/agent.py b/trezor_agent/gpg/agent.py index 2b43dfb..bc196f2 100644 --- a/trezor_agent/gpg/agent.py +++ b/trezor_agent/gpg/agent.py @@ -91,9 +91,10 @@ def sign(sock, keygrip, digest): assert _communicate(sock, 'SIGKEY {}'.format(keygrip)) == 'OK' assert _communicate(sock, 'SETHASH {} {}'.format(hash_algo, _hex(digest))) == 'OK' - assert _communicate(sock, 'SETKEYDESC ' - 'Please+enter+the+passphrase+to+unlock+the+OpenPGP%0A' - 'secret+key,+to+sign+a+new+TREZOR-based+subkey') == 'OK' + + desc = ('Please+enter+the+passphrase+to+unlock+the+OpenPGP%0A' + 'secret+key,+to+sign+a+new+TREZOR-based+subkey') + assert _communicate(sock, 'SETKEYDESC {}'.format(desc)) == 'OK' assert _communicate(sock, 'PKSIGN') == 'OK' line = _recvline(sock).strip() @@ -122,8 +123,9 @@ def get_keygrip(user_id): def _main(): - logging.basicConfig(level=logging.INFO, - format='%(asctime)s %(levelname)-10s %(message)s') + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(levelname)-10s %(message)-100s ' + '%(filename)s:%(lineno)d') p = argparse.ArgumentParser() p.add_argument('user_id') From f3b49ff55381eec3b96bd1764c6b8e3714eb6d1c Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 29 Apr 2016 11:14:27 +0300 Subject: [PATCH 06/24] gpg: use strict bash mode for demo --- trezor_agent/gpg/test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trezor_agent/gpg/test.sh b/trezor_agent/gpg/test.sh index 4f21a02..9a5618f 100644 --- a/trezor_agent/gpg/test.sh +++ b/trezor_agent/gpg/test.sh @@ -1,6 +1,6 @@ # NEVER RUN ON YOUR OWN REAL GPG KEYS!!!!! THEY WILL BE DELETED!!!!! -set -x -(cd ~/.gnupg && rm -r openpgp-revocs.d/ private-keys-v1.d/ pubring.kbx* trustdb.gpg /tmp/log *.gpg; killall gpg-agent) +set -x -e -u +(cd ~/.gnupg && rm -rf openpgp-revocs.d/ private-keys-v1.d/ pubring.kbx* trustdb.gpg /tmp/log *.gpg; killall gpg-agent) gpg2 --full-gen-key --expert gpg2 --export > romanz.pub NOW=`date +%s` From ac2d12b3541244eb463d7a0e1fa5abd3049bb63b Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 29 Apr 2016 17:45:16 +0300 Subject: [PATCH 07/24] It works again! --- trezor_agent/gpg/agent.py | 19 ++-- trezor_agent/gpg/decode.py | 3 + trezor_agent/gpg/encode.py | 175 +++++++++++++++++++++---------------- trezor_agent/gpg/test.sh | 1 + 4 files changed, 115 insertions(+), 83 deletions(-) diff --git a/trezor_agent/gpg/agent.py b/trezor_agent/gpg/agent.py index bc196f2..2e1ad93 100644 --- a/trezor_agent/gpg/agent.py +++ b/trezor_agent/gpg/agent.py @@ -76,6 +76,16 @@ def _parse(s): return _parse_term(s) +def _parse_ecdsa_sig(sig): + data, (algo, (r, sig_r), (s, sig_s)) = sig + assert data == 'sig-val' + assert algo == 'ecdsa' + assert r == 'r' + assert s == 's' + return (util.bytes2num(sig_r), + util.bytes2num(sig_s)) + + def sign(sock, keygrip, digest): """Sign a digest using specified key using GPG agent.""" hash_algo = 8 # SHA256 @@ -105,14 +115,7 @@ def sign(sock, keygrip, digest): sig, leftover = _parse(sig) assert not leftover - - data, (algo, (r, sig_r), (s, sig_s)) = sig - assert data == 'sig-val' - assert algo == 'ecdsa' - assert r == 'r' - assert s == 's' - return (util.bytes2num(sig_r), - util.bytes2num(sig_s)) + return _parse_ecdsa_sig(sig) def get_keygrip(user_id): diff --git a/trezor_agent/gpg/decode.py b/trezor_agent/gpg/decode.py index ebe96b3..fda6e45 100644 --- a/trezor_agent/gpg/decode.py +++ b/trezor_agent/gpg/decode.py @@ -252,6 +252,9 @@ def load_public_key(stream): subkey = subsig = None if len(packets) == 5: pubkey, userid, signature, subkey, subsig = packets + # TODO: refactor this out! + log.debug('subkey: %s', subkey) + log.debug('subsig: %s', subsig) else: pubkey, userid, signature = packets diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 0ab3df8..b0c2a04 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -135,6 +135,7 @@ class AgentSigner(object): return self.public_key['verifying_key'] def sign(self, digest, visual): + log.info('signing %r using gpg-agent', visual) r, s = agent.sign(sock=self.sock, keygrip=self.keygrip, digest=digest) return mpi(r) + mpi(s) @@ -142,37 +143,14 @@ class AgentSigner(object): self.sock.close() -class Signer(object): - """Performs GPG signing operations.""" - - def __init__(self, user_id, created, curve_name): - """Construct and loads a public key from the device.""" - self.user_id = user_id - assert curve_name in formats.SUPPORTED_CURVES - - self.conn = HardwareSigner(user_id, curve_name=curve_name) - self.verifying_key = self.conn.pubkey() - +class PublicKey(object): + def __init__(self, curve_name, created, verifying_key): + self.curve_name = str(curve_name) self.created = int(created) - log.info('%s GPG public key %s created at %s', curve_name, - self.hex_short_key_id(), util.time_format(self.created)) - - @classmethod - def from_public_key(cls, pubkey, user_id): - """ - Create from an existing GPG public key. - - `pubkey` should be loaded via `decode.load_from_gpg(user_id)` - from the local GPG keyring. - """ - s = Signer(user_id=user_id, - created=pubkey['created'], - curve_name=_find_curve_by_algo_id(pubkey['algo'])) - assert s.key_id() == pubkey['key_id'] - return s + self.verifying_key = verifying_key def _pubkey_data(self): - curve_info = SUPPORTED_CURVES[self.conn.curve_name] + curve_info = SUPPORTED_CURVES[self.curve_name] header = struct.pack('>BLB', 4, # version self.created, # creation @@ -195,63 +173,108 @@ class Signer(object): """Short (8 hexadecimal digits) GPG key ID.""" return util.hexlify(self.key_id()[-4:]) + +class Signer(object): + """Performs GPG signing operations.""" + + def __init__(self, user_id, created, curve_name): + """Construct and loads a public key from the device.""" + self.user_id = user_id + assert curve_name in formats.SUPPORTED_CURVES + + self.conn = HardwareSigner(user_id, curve_name=curve_name) + self.pubkey = PublicKey( + curve_name=curve_name, created=created, + verifying_key=self.conn.pubkey()) + + log.info('%s GPG public key %s created at %s', curve_name, + self.pubkey.hex_short_key_id(), util.time_format(self.pubkey.created)) + + @classmethod + def from_public_key(cls, pubkey, user_id): + """ + Create from an existing GPG public key. + + `pubkey` should be loaded via `decode.load_from_gpg(user_id)` + from the local GPG keyring. + """ + s = Signer(user_id=user_id, + created=pubkey['created'], + curve_name=_find_curve_by_algo_id(pubkey['algo'])) + assert s.pubkey.key_id() == pubkey['key_id'] + return s + def close(self): """Close connection and turn off the screen of the device.""" self.conn.close() def export(self): """Export GPG public key, ready for "gpg2 --import".""" - pubkey_packet = packet(tag=6, blob=self._pubkey_data()) + pubkey_packet = packet(tag=6, blob=self.pubkey._pubkey_data()) user_id_packet = packet(tag=13, blob=self.user_id) - data_to_sign = (self._pubkey_data_to_hash() + + data_to_sign = (self.pubkey._pubkey_data_to_hash() + user_id_packet[:1] + util.prefix_len('>L', self.user_id)) log.info('signing public key "%s"', self.user_id) hashed_subpackets = [ - subpacket_time(self.created), # signature creaion time + subpacket_time(self.pubkey.created), # signature creaion time subpacket_byte(0x1B, 1 | 2), # key flags (certify & sign) subpacket_byte(0x15, 8), # preferred hash (SHA256) subpacket_byte(0x16, 0), # preferred compression (none) subpacket_byte(0x17, 0x80)] # key server prefs (no-modify) - signature = self._make_signature(visual=self.hex_short_key_id(), - data_to_sign=data_to_sign, - sig_type=0x13, # user id & public key - hashed_subpackets=hashed_subpackets) + unhashed_subpackets = [ + subpacket(16, self.pubkey.key_id())] # issuer key id + + signature = _make_signature( + pubkey=self.pubkey, conn=self.conn, + data_to_sign=data_to_sign, + sig_type=0x13, # user id & public key + hashed_subpackets=hashed_subpackets, + unhashed_subpackets=unhashed_subpackets) sign_packet = packet(tag=2, blob=signature) return pubkey_packet + user_id_packet + sign_packet def subkey(self, user_id): - subkey_packet = packet(tag=14, blob=self._pubkey_data()) + subkey_packet = packet(tag=14, blob=self.pubkey._pubkey_data()) primary = decode.load_from_gpg(user_id) keygrip = agent.get_keygrip(user_id) log.info('adding as subkey to %s (%s)', user_id, keygrip) - data_to_sign = primary['_to_hash'] + self._pubkey_data_to_hash() + data_to_sign = primary['_to_hash'] + self.pubkey._pubkey_data_to_hash() hashed_subpackets = [ - subpacket_time(self.created)] # signature creaion time - back_sign = self._make_signature(visual='Add subkey', - data_to_sign=data_to_sign, - sig_type=0x19, # Primary Key Binding Signature - hashed_subpackets=hashed_subpackets) + subpacket_time(self.pubkey.created)] # signature creaion time + unhashed_subpackets = [ + subpacket(16, self.pubkey.key_id())] # issuer key id + + # Primary Key Binding Signature + back_sign = _make_signature(pubkey=self.pubkey, conn=self.conn, + data_to_sign=data_to_sign, + sig_type=0x19, + hashed_subpackets=hashed_subpackets, + unhashed_subpackets=unhashed_subpackets) log.info('back_sign: %r', back_sign) hashed_subpackets = [ - subpacket_time(self.created), # signature creaion time + subpacket_time(self.pubkey.created), # signature creaion time subpacket_byte(0x1B, 2)] # key flags (certify & sign) + unhashed_subpackets = [ + subpacket(16, primary['key_id']), # issuer key id + subpacket(32, back_sign)] + _conn = self.conn self.conn = AgentSigner(user_id, curve_name=formats.CURVE_NIST256) - self.key_id = lambda: primary['key_id'] - signature = self._make_signature(visual='Add subkey', - data_to_sign=data_to_sign, - sig_type=0x18, # Subkey Binding Signature - hashed_subpackets=hashed_subpackets, - unhashed=[subpacket(32, bytes(back_sign))]) + + # Subkey Binding Signature + signature = _make_signature(pubkey=self.pubkey, conn=self.conn, + data_to_sign=data_to_sign, + sig_type=0x18, + hashed_subpackets=hashed_subpackets, + unhashed_subpackets=unhashed_subpackets) self.conn = _conn sign_packet = packet(tag=2, blob=signature) return subkey_packet + sign_packet - def sign(self, msg, sign_time=None): """Sign GPG message at specified time.""" if sign_time is None: @@ -260,36 +283,38 @@ class Signer(object): log.info('signing %d byte message at %s', len(msg), util.time_format(sign_time)) hashed_subpackets = [subpacket_time(sign_time)] - blob = self._make_signature( - visual=self.hex_short_key_id(), - data_to_sign=msg, hashed_subpackets=hashed_subpackets) + unhashed_subpackets = [ + subpacket(16, self.pubkey.key_id())] # issuer key id + + blob = _make_signature( + pubkey=self.pubkey, conn=self.conn, data_to_sign=msg, + hashed_subpackets=hashed_subpackets, + unhashed_subpackets=unhashed_subpackets) return packet(tag=2, blob=blob) - def _make_signature(self, visual, data_to_sign, - hashed_subpackets, sig_type=0, unhashed=()): - curve_info = SUPPORTED_CURVES[self.conn.curve_name] - header = struct.pack('>BBBB', - 4, # version - sig_type, # rfc4880 (section-5.2.1) - curve_info['algo_id'], - 8) # hash_alg (SHA256) - hashed = subpackets(*hashed_subpackets) - log.info('key_id: %s', util.hexlify(self.key_id())) - unhashed = subpackets( - subpacket(16, self.key_id()), # issuer key id - *unhashed - ) - tail = b'\x04\xff' + struct.pack('>L', len(header) + len(hashed)) - data_to_hash = data_to_sign + header + hashed + tail - log.debug('hashing %d bytes', len(data_to_hash)) - digest = hashlib.sha256(data_to_hash).digest() +def _make_signature(pubkey, conn, data_to_sign, + hashed_subpackets, unhashed_subpackets, sig_type=0): + curve_info = SUPPORTED_CURVES[pubkey.curve_name] + header = struct.pack('>BBBB', + 4, # version + sig_type, # rfc4880 (section-5.2.1) + curve_info['algo_id'], + 8) # hash_alg (SHA256) + hashed = subpackets(*hashed_subpackets) + unhashed = subpackets(*unhashed_subpackets) + tail = b'\x04\xff' + struct.pack('>L', len(header) + len(hashed)) + data_to_hash = data_to_sign + header + hashed + tail - sig = self.conn.sign(digest=digest, visual=visual) + log.debug('hashing %d bytes', len(data_to_hash)) + digest = hashlib.sha256(data_to_hash).digest() - return (header + hashed + unhashed + - digest[:2] + # used for decoder's sanity check - sig) # actual ECDSA signature + visual = pubkey.hex_short_key_id() + sig = conn.sign(digest=digest, visual=visual) + + return bytes(header + hashed + unhashed + + digest[:2] + # used for decoder's sanity check + sig) # actual ECDSA signature def _split_lines(body, size): diff --git a/trezor_agent/gpg/test.sh b/trezor_agent/gpg/test.sh index 9a5618f..6a31a35 100644 --- a/trezor_agent/gpg/test.sh +++ b/trezor_agent/gpg/test.sh @@ -5,6 +5,7 @@ gpg2 --full-gen-key --expert gpg2 --export > romanz.pub NOW=`date +%s` trezor-gpg -t $NOW "romanz" -o subkey.pub +gpg2 -K gpg2 -vv --import <(cat romanz.pub subkey.pub) gpg2 -K From dcc7ef26001f6091449e8b8df921bd1f8599cc02 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 29 Apr 2016 22:10:04 +0300 Subject: [PATCH 08/24] minor fixes --- trezor_agent/gpg/encode.py | 9 ++++----- trezor_agent/gpg/test.sh | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index b0c2a04..538c868 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -188,7 +188,8 @@ class Signer(object): verifying_key=self.conn.pubkey()) log.info('%s GPG public key %s created at %s', curve_name, - self.pubkey.hex_short_key_id(), util.time_format(self.pubkey.created)) + self.pubkey.hex_short_key_id(), + util.time_format(self.pubkey.created)) @classmethod def from_public_key(cls, pubkey, user_id): @@ -261,16 +262,14 @@ class Signer(object): subpacket(16, primary['key_id']), # issuer key id subpacket(32, back_sign)] - _conn = self.conn - self.conn = AgentSigner(user_id, curve_name=formats.CURVE_NIST256) + conn = AgentSigner(user_id, curve_name=formats.CURVE_NIST256) # Subkey Binding Signature - signature = _make_signature(pubkey=self.pubkey, conn=self.conn, + signature = _make_signature(pubkey=self.pubkey, conn=conn, data_to_sign=data_to_sign, sig_type=0x18, hashed_subpackets=hashed_subpackets, unhashed_subpackets=unhashed_subpackets) - self.conn = _conn sign_packet = packet(tag=2, blob=signature) return subkey_packet + sign_packet diff --git a/trezor_agent/gpg/test.sh b/trezor_agent/gpg/test.sh index 6a31a35..e063cde 100644 --- a/trezor_agent/gpg/test.sh +++ b/trezor_agent/gpg/test.sh @@ -1,6 +1,6 @@ # NEVER RUN ON YOUR OWN REAL GPG KEYS!!!!! THEY WILL BE DELETED!!!!! set -x -e -u -(cd ~/.gnupg && rm -rf openpgp-revocs.d/ private-keys-v1.d/ pubring.kbx* trustdb.gpg /tmp/log *.gpg; killall gpg-agent) +(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` From 169ff39b1aae7acf0b458cba420fb5a3419725cd Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 29 Apr 2016 22:23:12 +0300 Subject: [PATCH 09/24] gpg: remove visual keyword for now --- trezor_agent/gpg/encode.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 538c868..e81c744 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -108,11 +108,11 @@ class HardwareSigner(object): pubkey=public_node.node.public_key, curve_name=self.curve_name) - def sign(self, digest, visual): + def sign(self, digest): result = self.client_wrapper.connection.sign_identity( identity=self.identity, challenge_hidden=digest, - challenge_visual=visual, + challenge_visual=util.hexlify(digest), ecdsa_curve_name=self.curve_name) assert result.signature[:1] == b'\x00' sig = result.signature[1:] @@ -134,8 +134,7 @@ class AgentSigner(object): def pubkey(self): return self.public_key['verifying_key'] - def sign(self, digest, visual): - log.info('signing %r using gpg-agent', visual) + def sign(self, digest): r, s = agent.sign(sock=self.sock, keygrip=self.keygrip, digest=digest) return mpi(r) + mpi(s) @@ -308,8 +307,7 @@ def _make_signature(pubkey, conn, data_to_sign, log.debug('hashing %d bytes', len(data_to_hash)) digest = hashlib.sha256(data_to_hash).digest() - visual = pubkey.hex_short_key_id() - sig = conn.sign(digest=digest, visual=visual) + sig = conn.sign(digest=digest) return bytes(header + hashed + unhashed + digest[:2] + # used for decoder's sanity check From cc326b1f7dc166bac30cc122ff13a5e433a82a81 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 29 Apr 2016 22:25:08 +0300 Subject: [PATCH 10/24] gpg: pubkey is not needed for make_signature --- trezor_agent/gpg/encode.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index e81c744..0305e1a 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -227,7 +227,7 @@ class Signer(object): subpacket(16, self.pubkey.key_id())] # issuer key id signature = _make_signature( - pubkey=self.pubkey, conn=self.conn, + conn=self.conn, data_to_sign=data_to_sign, sig_type=0x13, # user id & public key hashed_subpackets=hashed_subpackets, @@ -248,7 +248,7 @@ class Signer(object): subpacket(16, self.pubkey.key_id())] # issuer key id # Primary Key Binding Signature - back_sign = _make_signature(pubkey=self.pubkey, conn=self.conn, + back_sign = _make_signature(conn=self.conn, data_to_sign=data_to_sign, sig_type=0x19, hashed_subpackets=hashed_subpackets, @@ -264,7 +264,7 @@ class Signer(object): conn = AgentSigner(user_id, curve_name=formats.CURVE_NIST256) # Subkey Binding Signature - signature = _make_signature(pubkey=self.pubkey, conn=conn, + signature = _make_signature(conn=conn, data_to_sign=data_to_sign, sig_type=0x18, hashed_subpackets=hashed_subpackets, @@ -285,15 +285,15 @@ class Signer(object): subpacket(16, self.pubkey.key_id())] # issuer key id blob = _make_signature( - pubkey=self.pubkey, conn=self.conn, data_to_sign=msg, + conn=self.conn, data_to_sign=msg, hashed_subpackets=hashed_subpackets, unhashed_subpackets=unhashed_subpackets) return packet(tag=2, blob=blob) -def _make_signature(pubkey, conn, data_to_sign, +def _make_signature(conn, data_to_sign, hashed_subpackets, unhashed_subpackets, sig_type=0): - curve_info = SUPPORTED_CURVES[pubkey.curve_name] + curve_info = SUPPORTED_CURVES[conn.curve_name] header = struct.pack('>BBBB', 4, # version sig_type, # rfc4880 (section-5.2.1) From 492285de1b4ed8ab5232513442f4419985b30ce8 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 29 Apr 2016 22:28:41 +0300 Subject: [PATCH 11/24] gpg: rename pubkey methods --- trezor_agent/gpg/encode.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 0305e1a..2e72b6b 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -148,7 +148,8 @@ class PublicKey(object): self.created = int(created) self.verifying_key = verifying_key - def _pubkey_data(self): + def data(self): + """Data for packet creation.""" curve_info = SUPPORTED_CURVES[self.curve_name] header = struct.pack('>BLB', 4, # version @@ -158,15 +159,14 @@ class PublicKey(object): blob = curve_info['serialize'](self.verifying_key) return header + oid + blob - def _pubkey_data_to_hash(self): - return b'\x99' + util.prefix_len('>H', self._pubkey_data()) - - def _fingerprint(self): - return hashlib.sha1(self._pubkey_data_to_hash()).digest() + def data_to_hash(self): + """Data for digest computation.""" + return b'\x99' + util.prefix_len('>H', self.data()) def key_id(self): """Short (8 byte) GPG key ID.""" - return self._fingerprint()[-8:] + fingerprint = hashlib.sha1(self.data_to_hash()).digest() + return fingerprint[-8:] def hex_short_key_id(self): """Short (8 hexadecimal digits) GPG key ID.""" @@ -210,10 +210,10 @@ class Signer(object): def export(self): """Export GPG public key, ready for "gpg2 --import".""" - pubkey_packet = packet(tag=6, blob=self.pubkey._pubkey_data()) + pubkey_packet = packet(tag=6, blob=self.pubkey.data()) user_id_packet = packet(tag=13, blob=self.user_id) - data_to_sign = (self.pubkey._pubkey_data_to_hash() + + data_to_sign = (self.pubkey.data_to_hash() + user_id_packet[:1] + util.prefix_len('>L', self.user_id)) log.info('signing public key "%s"', self.user_id) @@ -237,11 +237,11 @@ class Signer(object): return pubkey_packet + user_id_packet + sign_packet def subkey(self, user_id): - subkey_packet = packet(tag=14, blob=self.pubkey._pubkey_data()) + subkey_packet = packet(tag=14, blob=self.pubkey.data()) primary = decode.load_from_gpg(user_id) keygrip = agent.get_keygrip(user_id) log.info('adding as subkey to %s (%s)', user_id, keygrip) - data_to_sign = primary['_to_hash'] + self.pubkey._pubkey_data_to_hash() + data_to_sign = primary['_to_hash'] + self.pubkey.data_to_hash() hashed_subpackets = [ subpacket_time(self.pubkey.created)] # signature creaion time unhashed_subpackets = [ From b8eba72d0b780e4bda500526f8ae0d65ccb5ce53 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 29 Apr 2016 22:45:52 +0300 Subject: [PATCH 12/24] gpg: fixup subkey/export handling --- trezor_agent/gpg/encode.py | 25 ++++++++++++++----------- trezor_agent/gpg/signer.py | 9 +++++++-- trezor_agent/gpg/test.sh | 4 ++-- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 2e72b6b..05ea87d 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -163,14 +163,18 @@ class PublicKey(object): """Data for digest computation.""" return b'\x99' + util.prefix_len('>H', self.data()) + def _fingerprint(self): + return hashlib.sha1(self.data_to_hash()).digest() + def key_id(self): """Short (8 byte) GPG key ID.""" - fingerprint = hashlib.sha1(self.data_to_hash()).digest() - return fingerprint[-8:] + return self._fingerprint()[-8:] - def hex_short_key_id(self): + def __repr__(self): """Short (8 hexadecimal digits) GPG key ID.""" - return util.hexlify(self.key_id()[-4:]) + return '<{}>'.format(util.hexlify(self.key_id())) + + __str__ = __repr__ class Signer(object): @@ -187,8 +191,7 @@ class Signer(object): verifying_key=self.conn.pubkey()) log.info('%s GPG public key %s created at %s', curve_name, - self.pubkey.hex_short_key_id(), - util.time_format(self.pubkey.created)) + self.pubkey, util.time_format(self.pubkey.created)) @classmethod def from_public_key(cls, pubkey, user_id): @@ -236,11 +239,11 @@ class Signer(object): sign_packet = packet(tag=2, blob=signature) return pubkey_packet + user_id_packet + sign_packet - def subkey(self, user_id): + def subkey(self): subkey_packet = packet(tag=14, blob=self.pubkey.data()) - primary = decode.load_from_gpg(user_id) - keygrip = agent.get_keygrip(user_id) - log.info('adding as subkey to %s (%s)', user_id, keygrip) + primary = decode.load_from_gpg(self.user_id) + keygrip = agent.get_keygrip(self.user_id) + log.info('adding as subkey to %s (%s)', self.user_id, keygrip) data_to_sign = primary['_to_hash'] + self.pubkey.data_to_hash() hashed_subpackets = [ subpacket_time(self.pubkey.created)] # signature creaion time @@ -261,7 +264,7 @@ class Signer(object): subpacket(16, primary['key_id']), # issuer key id subpacket(32, back_sign)] - conn = AgentSigner(user_id, curve_name=formats.CURVE_NIST256) + conn = AgentSigner(self.user_id, curve_name=formats.CURVE_NIST256) # Subkey Binding Signature signature = _make_signature(conn=conn, diff --git a/trezor_agent/gpg/signer.py b/trezor_agent/gpg/signer.py index 1cffcc0..c9b424a 100755 --- a/trezor_agent/gpg/signer.py +++ b/trezor_agent/gpg/signer.py @@ -31,6 +31,7 @@ def main(): p.add_argument('-t', '--time', type=int, default=int(time.time())) p.add_argument('-a', '--armor', action='store_true', default=False) p.add_argument('-v', '--verbose', action='store_true', default=False) + p.add_argument('-s', '--subkey', action='store_true', default=False) p.add_argument('-e', '--ecdsa-curve', default='nist256p1') p.add_argument('-o', '--output', help='Output file name for the results. ' @@ -44,12 +45,16 @@ def main(): if not args.filename: s = encode.Signer(user_id=user_id, created=args.time, curve_name=args.ecdsa_curve) - pubkey = s.subkey(user_id='romanz') + if args.subkey: + pubkey = s.subkey() + else: + pubkey = s.export() + ext = '.pub' if args.armor: pubkey = encode.armor(pubkey, 'PUBLIC KEY BLOCK') ext = '.asc' - filename = args.output or (s.hex_short_key_id() + ext) + filename = args.output or '-' # use stdout if no file specified if filename == 'GPG': log.info('importing public key to local keyring') _call_with_input(['gpg2', '--import'], pubkey) diff --git a/trezor_agent/gpg/test.sh b/trezor_agent/gpg/test.sh index e063cde..9a25bb0 100644 --- a/trezor_agent/gpg/test.sh +++ b/trezor_agent/gpg/test.sh @@ -4,10 +4,10 @@ set -x -e -u gpg2 --full-gen-key --expert gpg2 --export > romanz.pub NOW=`date +%s` -trezor-gpg -t $NOW "romanz" -o subkey.pub +trezor-gpg -t $NOW -v --subkey "romanz" -o subkey.pub gpg2 -K gpg2 -vv --import <(cat romanz.pub subkey.pub) gpg2 -K -trezor-gpg -t $NOW "romanz" EXAMPLE +trezor-gpg -t $NOW -v "romanz" EXAMPLE gpg2 --verify EXAMPLE.sig From 7dfa3ab255504dc0cae055a92f761eac8cbe2b32 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 09:29:04 +0300 Subject: [PATCH 13/24] gpg: replace PublicKey.curve_name attribute --- trezor_agent/gpg/encode.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 05ea87d..deae7a4 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -143,20 +143,22 @@ class AgentSigner(object): class PublicKey(object): + """GPG representation for public key packets.""" + def __init__(self, curve_name, created, verifying_key): - self.curve_name = str(curve_name) - self.created = int(created) + """Contruct using a ECDSA VerifyingKey object.""" + self.curve_info = SUPPORTED_CURVES[curve_name] + self.created = int(created) # time since Epoch self.verifying_key = verifying_key def data(self): """Data for packet creation.""" - curve_info = SUPPORTED_CURVES[self.curve_name] header = struct.pack('>BLB', 4, # version self.created, # creation - curve_info['algo_id']) - oid = util.prefix_len('>B', curve_info['oid']) - blob = curve_info['serialize'](self.verifying_key) + self.curve_info['algo_id']) + oid = util.prefix_len('>B', self.curve_info['oid']) + blob = self.curve_info['serialize'](self.verifying_key) return header + oid + blob def data_to_hash(self): From 5d007260e1c82d353176f8200de6a309ab7724f4 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 10:04:44 +0300 Subject: [PATCH 14/24] gpg: add docstrings --- trezor_agent/gpg/encode.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index deae7a4..0c4260c 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -92,7 +92,10 @@ def _find_curve_by_algo_id(algo_id): class HardwareSigner(object): + """Sign messages and get public keys from a hardware device.""" + def __init__(self, user_id, curve_name): + """Connect to the device and retrieve required public key.""" self.client_wrapper = factory.load() self.identity = self.client_wrapper.identity_type() self.identity.proto = 'gpg' @@ -100,6 +103,7 @@ class HardwareSigner(object): self.curve_name = curve_name def pubkey(self): + """Return public key as VerifyingKey object.""" addr = client.get_address(self.identity) public_node = self.client_wrapper.connection.get_public_node( n=addr, ecdsa_curve_name=self.curve_name) @@ -109,6 +113,7 @@ class HardwareSigner(object): curve_name=self.curve_name) def sign(self, digest): + """Sign the digest and return an ECDSA signature.""" result = self.client_wrapper.connection.sign_identity( identity=self.identity, challenge_hidden=digest, @@ -119,12 +124,16 @@ class HardwareSigner(object): return mpi(util.bytes2num(sig[:32])) + mpi(util.bytes2num(sig[32:])) def close(self): + """Close the connection to the device.""" self.client_wrapper.connection.clear_session() self.client_wrapper.connection.close() class AgentSigner(object): + """Sign messages and get public keys using gpg-agent tool.""" + def __init__(self, user_id, curve_name): + """Connect to the agent and retrieve required public key.""" self.sock = agent.connect() assert curve_name == formats.CURVE_NIST256 self.curve_name = curve_name @@ -132,13 +141,16 @@ class AgentSigner(object): self.public_key = decode.load_from_gpg(user_id) def pubkey(self): + """Return public key as VerifyingKey object.""" return self.public_key['verifying_key'] def sign(self, digest): + """Sign the digest and return an ECDSA signature.""" r, s = agent.sign(sock=self.sock, keygrip=self.keygrip, digest=digest) return mpi(r) + mpi(s) def close(self): + """Close the connection to gpg-agent.""" self.sock.close() @@ -242,6 +254,7 @@ class Signer(object): return pubkey_packet + user_id_packet + sign_packet def subkey(self): + """Export a subkey to `self.user_id` GPG primary key.""" subkey_packet = packet(tag=14, blob=self.pubkey.data()) primary = decode.load_from_gpg(self.user_id) keygrip = agent.get_keygrip(self.user_id) From 9ed978149631a8128c37f7997b814aa0e3f8acc8 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 10:56:15 +0300 Subject: [PATCH 15/24] gpg: support RSA decode and verify --- trezor_agent/gpg/agent.py | 12 ++++++++-- trezor_agent/gpg/decode.py | 46 +++++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/trezor_agent/gpg/agent.py b/trezor_agent/gpg/agent.py index 2e1ad93..a03fc61 100644 --- a/trezor_agent/gpg/agent.py +++ b/trezor_agent/gpg/agent.py @@ -86,7 +86,15 @@ def _parse_ecdsa_sig(sig): util.bytes2num(sig_s)) -def sign(sock, keygrip, digest): +def _parse_rsa_sig(sig): + data, (algo, (s, sig_s)) = sig + assert data == 'sig-val' + assert algo == 'rsa' + assert s == 's' + return (util.bytes2num(sig_s),) + + +def sign(sock, keygrip, digest, algo='ecdsa'): """Sign a digest using specified key using GPG agent.""" hash_algo = 8 # SHA256 assert len(digest) == 32 @@ -115,7 +123,7 @@ def sign(sock, keygrip, digest): sig, leftover = _parse(sig) assert not leftover - return _parse_ecdsa_sig(sig) + return {'ecdsa': _parse_ecdsa_sig, 'rsa': _parse_rsa_sig}[algo](sig) def get_keygrip(user_id): diff --git a/trezor_agent/gpg/decode.py b/trezor_agent/gpg/decode.py index fda6e45..96eb3da 100644 --- a/trezor_agent/gpg/decode.py +++ b/trezor_agent/gpg/decode.py @@ -68,11 +68,27 @@ def _parse_ed25519_verifier(mpi): return _ed25519_verify, vk +def _create_rsa_verifier(n, e): + def verifier(signature, digest): + s, = signature + size = n.bit_length() + result = pow(s, e, n) % (2 ** 256) + digest = util.bytes2num(digest) + if result == digest: + log.debug('RSA-%d signature is OK', size) + return True + else: + raise ValueError('invalid RSA signature') + + return verifier + SUPPORTED_CURVES = { b'\x2A\x86\x48\xCE\x3D\x03\x01\x07': _parse_nist256p1_verifier, b'\x2B\x06\x01\x04\x01\xDA\x47\x0F\x01': _parse_ed25519_verifier, } +ECDSA_ALGO_IDS = (19, 22) # (nist256, ed25519) + def _parse_literal(stream): """See https://tools.ietf.org/html/rfc4880#section-5.9 for details.""" @@ -119,7 +135,11 @@ def _parse_signature(stream): p['embedded'] = embedded p['hash_prefix'] = stream.readfmt('2s') - p['sig'] = (parse_mpi(stream), parse_mpi(stream)) + if p['pubkey_alg'] in ECDSA_ALGO_IDS: + p['sig'] = (parse_mpi(stream), parse_mpi(stream)) + else: # RSA + p['sig'] = (parse_mpi(stream),) + assert not stream.read() return p @@ -132,16 +152,23 @@ def _parse_pubkey(stream): p['version'] = stream.readfmt('B') p['created'] = stream.readfmt('>L') p['algo'] = stream.readfmt('B') + if p['algo'] in ECDSA_ALGO_IDS: + # https://tools.ietf.org/html/rfc6637#section-11 + oid_size = stream.readfmt('B') + oid = stream.read(oid_size) + assert oid in SUPPORTED_CURVES, util.hexlify(oid) + parser = SUPPORTED_CURVES[oid] - # https://tools.ietf.org/html/rfc6637#section-11 - oid_size = stream.readfmt('B') - oid = stream.read(oid_size) - assert oid in SUPPORTED_CURVES - parser = SUPPORTED_CURVES[oid] + mpi = parse_mpi(stream) + log.debug('mpi: %x (%d bits)', mpi, mpi.bit_length()) + p['verifier'], p['verifying_key'] = parser(mpi) + else: # RSA + n = parse_mpi(stream) + e = parse_mpi(stream) + log.debug('n: %x (%d bits)', n, n.bit_length()) + log.debug('e: %x (%d bits)', e, e.bit_length()) + p['verifier'] = _create_rsa_verifier(n, e) - mpi = parse_mpi(stream) - log.debug('mpi: %x (%d bits)', mpi, mpi.bit_length()) - p['verifier'], p['verifying_key'] = parser(mpi) assert not stream.read() # https://tools.ietf.org/html/rfc4880#section-12.2 @@ -252,7 +279,6 @@ def load_public_key(stream): subkey = subsig = None if len(packets) == 5: pubkey, userid, signature, subkey, subsig = packets - # TODO: refactor this out! log.debug('subkey: %s', subkey) log.debug('subsig: %s', subsig) else: From f35b5be3ac1bf60aaeb7df55678811bb3951d912 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 11:40:02 +0300 Subject: [PATCH 16/24] gpg: 1st try for RSA primary key support --- trezor_agent/gpg/agent.py | 2 +- trezor_agent/gpg/encode.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/trezor_agent/gpg/agent.py b/trezor_agent/gpg/agent.py index a03fc61..89c61c2 100644 --- a/trezor_agent/gpg/agent.py +++ b/trezor_agent/gpg/agent.py @@ -94,7 +94,7 @@ def _parse_rsa_sig(sig): return (util.bytes2num(sig_s),) -def sign(sock, keygrip, digest, algo='ecdsa'): +def sign(sock, keygrip, digest, algo='rsa'): """Sign a digest using specified key using GPG agent.""" hash_algo = 8 # SHA256 assert len(digest) == 32 diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 0c4260c..2bd93f9 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -146,8 +146,8 @@ class AgentSigner(object): def sign(self, digest): """Sign the digest and return an ECDSA signature.""" - r, s = agent.sign(sock=self.sock, keygrip=self.keygrip, digest=digest) - return mpi(r) + mpi(s) + s, = agent.sign(sock=self.sock, keygrip=self.keygrip, digest=digest) + return mpi(s) def close(self): """Close the connection to gpg-agent.""" @@ -286,7 +286,8 @@ class Signer(object): data_to_sign=data_to_sign, sig_type=0x18, hashed_subpackets=hashed_subpackets, - unhashed_subpackets=unhashed_subpackets) + unhashed_subpackets=unhashed_subpackets, + public_algo=1) sign_packet = packet(tag=2, blob=signature) return subkey_packet + sign_packet @@ -310,12 +311,13 @@ class Signer(object): def _make_signature(conn, data_to_sign, - hashed_subpackets, unhashed_subpackets, sig_type=0): + hashed_subpackets, unhashed_subpackets, sig_type=0, + public_algo=None): curve_info = SUPPORTED_CURVES[conn.curve_name] header = struct.pack('>BBBB', 4, # version sig_type, # rfc4880 (section-5.2.1) - curve_info['algo_id'], + public_algo or curve_info['algo_id'], 8) # hash_alg (SHA256) hashed = subpackets(*hashed_subpackets) unhashed = subpackets(*unhashed_subpackets) From d486c1ee7b4ded80593373b3aceef3a1aa7703f8 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 12:33:01 +0300 Subject: [PATCH 17/24] gpg: refactor agent rsa/ecdsa signature parsing --- trezor_agent/gpg/agent.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/trezor_agent/gpg/agent.py b/trezor_agent/gpg/agent.py index 89c61c2..a32ba17 100644 --- a/trezor_agent/gpg/agent.py +++ b/trezor_agent/gpg/agent.py @@ -76,24 +76,28 @@ def _parse(s): return _parse_term(s) -def _parse_ecdsa_sig(sig): - data, (algo, (r, sig_r), (s, sig_s)) = sig - assert data == 'sig-val' - assert algo == 'ecdsa' +def _parse_ecdsa_sig(args): + (r, sig_r), (s, sig_s) = args assert r == 'r' assert s == 's' return (util.bytes2num(sig_r), util.bytes2num(sig_s)) -def _parse_rsa_sig(sig): - data, (algo, (s, sig_s)) = sig - assert data == 'sig-val' - assert algo == 'rsa' +def _parse_rsa_sig(args): + (s, sig_s), = args assert s == 's' return (util.bytes2num(sig_s),) +def _parse_sig(sig): + label, sig = sig + assert label == 'sig-val' + algo_name = sig[0] + parser = {'rsa': _parse_rsa_sig, 'ecdsa': _parse_ecdsa_sig}[algo_name] + return parser(args=sig[1:]) + + def sign(sock, keygrip, digest, algo='rsa'): """Sign a digest using specified key using GPG agent.""" hash_algo = 8 # SHA256 @@ -123,7 +127,7 @@ def sign(sock, keygrip, digest, algo='rsa'): sig, leftover = _parse(sig) assert not leftover - return {'ecdsa': _parse_ecdsa_sig, 'rsa': _parse_rsa_sig}[algo](sig) + return _parse_sig(sig) def get_keygrip(user_id): From a7ef2639548ddc4a5618c9c3b434498b6819d0f3 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 13:01:14 +0300 Subject: [PATCH 18/24] gpg: generalize RSA/ECDSA signatures --- trezor_agent/gpg/agent.py | 2 +- trezor_agent/gpg/encode.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/trezor_agent/gpg/agent.py b/trezor_agent/gpg/agent.py index a32ba17..e058ac7 100644 --- a/trezor_agent/gpg/agent.py +++ b/trezor_agent/gpg/agent.py @@ -98,7 +98,7 @@ def _parse_sig(sig): return parser(args=sig[1:]) -def sign(sock, keygrip, digest, algo='rsa'): +def sign(sock, keygrip, digest): """Sign a digest using specified key using GPG agent.""" hash_algo = 8 # SHA256 assert len(digest) == 32 diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 2bd93f9..e67a1c5 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -13,6 +13,7 @@ log = logging.getLogger(__name__) def packet(tag, blob): """Create small GPG packet.""" + # TODO: allow larger sizes. assert len(blob) < 256 length_type = 0 # : 1 byte for length leading_byte = 0x80 | (tag << 2) | (length_type) @@ -50,13 +51,13 @@ def mpi(value): """Serialize multipresicion integer using GPG format.""" bits = value.bit_length() data_size = (bits + 7) // 8 - data_bytes = [0] * data_size + data_bytes = bytearray(data_size) for i in range(data_size): data_bytes[i] = value & 0xFF value = value >> 8 data_bytes.reverse() - return struct.pack('>H', bits) + bytearray(data_bytes) + return struct.pack('>H', bits) + bytes(data_bytes) def _serialize_nist256(vk): @@ -113,7 +114,7 @@ class HardwareSigner(object): curve_name=self.curve_name) def sign(self, digest): - """Sign the digest and return an ECDSA signature.""" + """Sign the digest and return a serialized signature.""" result = self.client_wrapper.connection.sign_identity( identity=self.identity, challenge_hidden=digest, @@ -146,8 +147,8 @@ class AgentSigner(object): def sign(self, digest): """Sign the digest and return an ECDSA signature.""" - s, = agent.sign(sock=self.sock, keygrip=self.keygrip, digest=digest) - return mpi(s) + params = agent.sign(sock=self.sock, keygrip=self.keygrip, digest=digest) + return b''.join(mpi(p) for p in params) def close(self): """Close the connection to gpg-agent.""" From 131c30accacf29924e070b0d1affca582f1062a7 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 13:20:06 +0300 Subject: [PATCH 19/24] gpg: use explicit public key algo_id --- trezor_agent/gpg/encode.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index e67a1c5..3d0d201 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -133,11 +133,9 @@ class HardwareSigner(object): class AgentSigner(object): """Sign messages and get public keys using gpg-agent tool.""" - def __init__(self, user_id, curve_name): + def __init__(self, user_id): """Connect to the agent and retrieve required public key.""" self.sock = agent.connect() - assert curve_name == formats.CURVE_NIST256 - self.curve_name = curve_name self.keygrip = agent.get_keygrip(user_id) self.public_key = decode.load_from_gpg(user_id) @@ -163,13 +161,14 @@ class PublicKey(object): self.curve_info = SUPPORTED_CURVES[curve_name] self.created = int(created) # time since Epoch self.verifying_key = verifying_key + self.algo_id = self.curve_info['algo_id'] def data(self): """Data for packet creation.""" header = struct.pack('>BLB', 4, # version self.created, # creation - self.curve_info['algo_id']) + self.algo_id) # public key algorithm ID oid = util.prefix_len('>B', self.curve_info['oid']) blob = self.curve_info['serialize'](self.verifying_key) return header + oid + blob @@ -245,7 +244,8 @@ class Signer(object): subpacket(16, self.pubkey.key_id())] # issuer key id signature = _make_signature( - conn=self.conn, + signer_func=self.conn.sign, + public_algo=self.pubkey.algo_id, data_to_sign=data_to_sign, sig_type=0x13, # user id & public key hashed_subpackets=hashed_subpackets, @@ -267,8 +267,9 @@ class Signer(object): subpacket(16, self.pubkey.key_id())] # issuer key id # Primary Key Binding Signature - back_sign = _make_signature(conn=self.conn, + back_sign = _make_signature(signer_func=self.conn.sign, data_to_sign=data_to_sign, + public_algo=self.pubkey.algo_id, sig_type=0x19, hashed_subpackets=hashed_subpackets, unhashed_subpackets=unhashed_subpackets) @@ -280,10 +281,9 @@ class Signer(object): subpacket(16, primary['key_id']), # issuer key id subpacket(32, back_sign)] - conn = AgentSigner(self.user_id, curve_name=formats.CURVE_NIST256) - # Subkey Binding Signature - signature = _make_signature(conn=conn, + gpg_agent = AgentSigner(self.user_id) + signature = _make_signature(signer_func=gpg_agent.sign, data_to_sign=data_to_sign, sig_type=0x18, hashed_subpackets=hashed_subpackets, @@ -305,20 +305,19 @@ class Signer(object): subpacket(16, self.pubkey.key_id())] # issuer key id blob = _make_signature( - conn=self.conn, data_to_sign=msg, + signer_func=self.conn.sign, data_to_sign=msg, + public_algo=self.pubkey.algo_id, hashed_subpackets=hashed_subpackets, unhashed_subpackets=unhashed_subpackets) return packet(tag=2, blob=blob) -def _make_signature(conn, data_to_sign, - hashed_subpackets, unhashed_subpackets, sig_type=0, - public_algo=None): - curve_info = SUPPORTED_CURVES[conn.curve_name] +def _make_signature(signer_func, data_to_sign, public_algo, + hashed_subpackets, unhashed_subpackets, sig_type=0): header = struct.pack('>BBBB', 4, # version sig_type, # rfc4880 (section-5.2.1) - public_algo or curve_info['algo_id'], + public_algo, 8) # hash_alg (SHA256) hashed = subpackets(*hashed_subpackets) unhashed = subpackets(*unhashed_subpackets) @@ -328,7 +327,7 @@ def _make_signature(conn, data_to_sign, log.debug('hashing %d bytes', len(data_to_hash)) digest = hashlib.sha256(data_to_hash).digest() - sig = conn.sign(digest=digest) + sig = signer_func(digest=digest) return bytes(header + hashed + unhashed + digest[:2] + # used for decoder's sanity check From 2d2d6efa935d4359811c60dc5b15a27084365110 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 13:25:14 +0300 Subject: [PATCH 20/24] gpg: small refactoring --- trezor_agent/gpg/encode.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 3d0d201..cad7b6d 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -261,12 +261,12 @@ class Signer(object): keygrip = agent.get_keygrip(self.user_id) log.info('adding as subkey to %s (%s)', self.user_id, keygrip) data_to_sign = primary['_to_hash'] + self.pubkey.data_to_hash() + + # Primary Key Binding Signature hashed_subpackets = [ subpacket_time(self.pubkey.created)] # signature creaion time unhashed_subpackets = [ subpacket(16, self.pubkey.key_id())] # issuer key id - - # Primary Key Binding Signature back_sign = _make_signature(signer_func=self.conn.sign, data_to_sign=data_to_sign, public_algo=self.pubkey.algo_id, @@ -274,22 +274,21 @@ class Signer(object): hashed_subpackets=hashed_subpackets, unhashed_subpackets=unhashed_subpackets) log.info('back_sign: %r', back_sign) + + # Subkey Binding Signature hashed_subpackets = [ subpacket_time(self.pubkey.created), # signature creaion time subpacket_byte(0x1B, 2)] # key flags (certify & sign) unhashed_subpackets = [ subpacket(16, primary['key_id']), # issuer key id subpacket(32, back_sign)] - - # Subkey Binding Signature gpg_agent = AgentSigner(self.user_id) signature = _make_signature(signer_func=gpg_agent.sign, data_to_sign=data_to_sign, + public_algo=primary['algo'], sig_type=0x18, hashed_subpackets=hashed_subpackets, - unhashed_subpackets=unhashed_subpackets, - public_algo=1) - + unhashed_subpackets=unhashed_subpackets) sign_packet = packet(tag=2, blob=signature) return subkey_packet + sign_packet @@ -304,11 +303,11 @@ class Signer(object): unhashed_subpackets = [ subpacket(16, self.pubkey.key_id())] # issuer key id - blob = _make_signature( - signer_func=self.conn.sign, data_to_sign=msg, - public_algo=self.pubkey.algo_id, - hashed_subpackets=hashed_subpackets, - unhashed_subpackets=unhashed_subpackets) + blob = _make_signature(signer_func=self.conn.sign, + data_to_sign=msg, + public_algo=self.pubkey.algo_id, + hashed_subpackets=hashed_subpackets, + unhashed_subpackets=unhashed_subpackets) return packet(tag=2, blob=blob) From 5c04d17c43de4edfa13fad55d0d7ae7f7640f8c2 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 13:32:20 +0300 Subject: [PATCH 21/24] gpg: demo with ed25519 TREZOR-based keys --- trezor_agent/gpg/test.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/trezor_agent/gpg/test.sh b/trezor_agent/gpg/test.sh index 9a25bb0..27131a9 100644 --- a/trezor_agent/gpg/test.sh +++ b/trezor_agent/gpg/test.sh @@ -1,13 +1,14 @@ # NEVER RUN ON YOUR OWN REAL GPG KEYS!!!!! THEY WILL BE DELETED!!!!! set -x -e -u +CURVE=ed25519 (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` -trezor-gpg -t $NOW -v --subkey "romanz" -o subkey.pub +trezor-gpg -t $NOW -v -e $CURVE --subkey "romanz" -o subkey.pub gpg2 -K gpg2 -vv --import <(cat romanz.pub subkey.pub) gpg2 -K -trezor-gpg -t $NOW -v "romanz" EXAMPLE +trezor-gpg -t $NOW -v -e $CURVE "romanz" EXAMPLE gpg2 --verify EXAMPLE.sig From c3d23ea7f5adb948cbe45e4efd6fdcc02a7e0ef7 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 14:47:32 +0300 Subject: [PATCH 22/24] gpg: allow longer packets --- trezor_agent/gpg/encode.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index cad7b6d..bfb95a4 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -13,11 +13,9 @@ log = logging.getLogger(__name__) def packet(tag, blob): """Create small GPG packet.""" - # TODO: allow larger sizes. - assert len(blob) < 256 - length_type = 0 # : 1 byte for length + length_type = 1 # : 2 bytes for length leading_byte = 0x80 | (tag << 2) | (length_type) - return struct.pack('>B', leading_byte) + util.prefix_len('>B', blob) + return struct.pack('>B', leading_byte) + util.prefix_len('>H', blob) def subpacket(subpacket_type, fmt, *values): From 87ca33c1048a99ffc2b28959573e8e2c0552cac4 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 15:34:18 +0300 Subject: [PATCH 23/24] gpg: fixup encoding for large packets --- trezor_agent/gpg/encode.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index bfb95a4..8aa88b8 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -13,9 +13,18 @@ log = logging.getLogger(__name__) def packet(tag, blob): """Create small GPG packet.""" - length_type = 1 # : 2 bytes for length + assert len(blob) < 2**32 + + if len(blob) < 2**8: + length_type = 0 + elif len(blob) < 2**16: + length_type = 1 + else: + length_type = 2 + + fmt = ['>B', '>H', '>L'][length_type] leading_byte = 0x80 | (tag << 2) | (length_type) - return struct.pack('>B', leading_byte) + util.prefix_len('>H', blob) + return struct.pack('>B', leading_byte) + util.prefix_len(fmt, blob) def subpacket(subpacket_type, fmt, *values): From 31c3686fa469564f9e45fef87f9e21a05c60ba01 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sat, 30 Apr 2016 15:39:32 +0300 Subject: [PATCH 24/24] gpg: small fixes --- trezor_agent/gpg/encode.py | 20 +++++++++++--------- trezor_agent/gpg/test.sh | 1 + 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/trezor_agent/gpg/encode.py b/trezor_agent/gpg/encode.py index 8aa88b8..3be6a43 100644 --- a/trezor_agent/gpg/encode.py +++ b/trezor_agent/gpg/encode.py @@ -152,7 +152,8 @@ class AgentSigner(object): def sign(self, digest): """Sign the digest and return an ECDSA signature.""" - params = agent.sign(sock=self.sock, keygrip=self.keygrip, digest=digest) + params = agent.sign(sock=self.sock, + keygrip=self.keygrip, digest=digest) return b''.join(mpi(p) for p in params) def close(self): @@ -274,13 +275,13 @@ class Signer(object): subpacket_time(self.pubkey.created)] # signature creaion time unhashed_subpackets = [ subpacket(16, self.pubkey.key_id())] # issuer key id - back_sign = _make_signature(signer_func=self.conn.sign, - data_to_sign=data_to_sign, - public_algo=self.pubkey.algo_id, - sig_type=0x19, - hashed_subpackets=hashed_subpackets, - unhashed_subpackets=unhashed_subpackets) - log.info('back_sign: %r', back_sign) + embedded_sig = _make_signature(signer_func=self.conn.sign, + data_to_sign=data_to_sign, + public_algo=self.pubkey.algo_id, + sig_type=0x19, + hashed_subpackets=hashed_subpackets, + unhashed_subpackets=unhashed_subpackets) + log.info('embedded signature: %r', embedded_sig) # Subkey Binding Signature hashed_subpackets = [ @@ -288,7 +289,7 @@ class Signer(object): subpacket_byte(0x1B, 2)] # key flags (certify & sign) unhashed_subpackets = [ subpacket(16, primary['key_id']), # issuer key id - subpacket(32, back_sign)] + subpacket(32, embedded_sig)] gpg_agent = AgentSigner(self.user_id) signature = _make_signature(signer_func=gpg_agent.sign, data_to_sign=data_to_sign, @@ -320,6 +321,7 @@ class Signer(object): def _make_signature(signer_func, data_to_sign, public_algo, hashed_subpackets, unhashed_subpackets, sig_type=0): + # pylint: disable=too-many-arguments header = struct.pack('>BBBB', 4, # version sig_type, # rfc4880 (section-5.2.1) diff --git a/trezor_agent/gpg/test.sh b/trezor_agent/gpg/test.sh index 27131a9..de1cba4 100644 --- a/trezor_agent/gpg/test.sh +++ b/trezor_agent/gpg/test.sh @@ -1,6 +1,7 @@ # 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