diff --git a/trezor_agent/gpg/keyring.py b/trezor_agent/gpg/keyring.py index 9440b0f..427c42c 100644 --- a/trezor_agent/gpg/keyring.py +++ b/trezor_agent/gpg/keyring.py @@ -44,11 +44,8 @@ def _recvline(sock): return result -def _hex(data): - return binascii.hexlify(data).upper().decode('ascii') - - -def _unescape(s): +def unescape(s): + """Unescape ASSUAN message.""" s = bytearray(s) i = 0 while i < len(s): @@ -60,23 +57,25 @@ def _unescape(s): return bytes(s) -def _parse_term(s): +def parse_term(s): + """Parse single s-expr term from bytes.""" size, s = s.split(b':', 1) size = int(size) return s[:size], s[size:] -def _parse(s): +def parse(s): + """Parse full s-expr from bytes.""" if s.startswith(b'('): s = s[1:] - name, s = _parse_term(s) + name, s = parse_term(s) values = [name] while not s.startswith(b')'): - value, s = _parse(s) + value, s = parse(s) values.append(value) return values, s[1:] else: - return _parse_term(s) + return parse_term(s) def _parse_ecdsa_sig(args): @@ -86,6 +85,7 @@ def _parse_ecdsa_sig(args): return (util.bytes2num(sig_r), util.bytes2num(sig_s)) +# DSA happens to have the same structure as ECDSA signatures _parse_dsa_sig = _parse_ecdsa_sig @@ -95,7 +95,8 @@ def _parse_rsa_sig(args): return (util.bytes2num(sig_s),) -def _parse_sig(sig): +def parse_sig(sig): + """Parse signature integer values from s-expr.""" label, sig = sig assert label == b'sig-val' algo_name = sig[0] @@ -123,23 +124,24 @@ def sign_digest(sock, keygrip, digest): assert _communicate(sock, 'OPTION {}'.format(opt)) == b'OK' assert _communicate(sock, 'SIGKEY {}'.format(keygrip)) == b'OK' + hex_digest = binascii.hexlify(digest).upper().decode('ascii') assert _communicate(sock, 'SETHASH {} {}'.format(hash_algo, - _hex(digest))) == b'OK' + hex_digest)) == b'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)) == b'OK' assert _communicate(sock, 'PKSIGN') == b'OK' line = _recvline(sock).strip() - line = _unescape(line) + line = unescape(line) log.debug('unescaped: %r', line) prefix, sig = line.split(b' ', 1) if prefix != b'D': raise ValueError(prefix) - sig, leftover = _parse(sig) + sig, leftover = parse(sig) assert not leftover, leftover - return _parse_sig(sig) + return parse_sig(sig) def get_keygrip(user_id): diff --git a/trezor_agent/gpg/tests/test_keyring.py b/trezor_agent/gpg/tests/test_keyring.py new file mode 100644 index 0000000..b312a5b --- /dev/null +++ b/trezor_agent/gpg/tests/test_keyring.py @@ -0,0 +1,37 @@ +from .. import keyring + + +def test_unescape_short(): + assert keyring.unescape(b'abc%0AX%0D %25;.-+()') == b'abc\nX\r %;.-+()' + + +def test_unescape_long(): + escaped = (b'D (7:sig-val(3:dsa(1:r32:\x1d\x15.\x12\xe8h\x19\xd9O\xeb\x06' + b'yD?a:/\xae\xdb\xac\x93\xa6\x86\xcbs\xb8\x03\xf1\xcb\x89\xc7' + b'\x1f)(1:s32:%25\xb5\x04\x94\xc7\xc4X\xc7\xe0%0D\x08\xbb%0DuN' + b'\x9c6}[\xc2=t\x8c\xfdD\x81\xe8\xdd\x86=\xe2\xa9)))') + unescaped = (b'D (7:sig-val(3:dsa(1:r32:\x1d\x15.\x12\xe8h\x19\xd9O\xeb' + b'\x06yD?a:/\xae\xdb\xac\x93\xa6\x86\xcbs\xb8\x03\xf1\xcb\x89' + b'\xc7\x1f)(1:s32:%\xb5\x04\x94\xc7\xc4X\xc7\xe0\r\x08\xbb\ru' + b'N\x9c6}[\xc2=t\x8c\xfdD\x81\xe8\xdd\x86=\xe2\xa9)))') + assert keyring.unescape(escaped) == unescaped + + +def test_parse_term(): + assert keyring.parse(b'4:abcdXXX') == (b'abcd', b'XXX') + + +def test_parse_ecdsa(): + sig, rest = keyring.parse(b'(7:sig-val(5:ecdsa' + b'(1:r2:\x01\x02)(1:s2:\x03\x04)))') + values = [[b'r', b'\x01\x02'], [b's', b'\x03\x04']] + assert sig == [b'sig-val', [b'ecdsa'] + values] + assert rest == b'' + assert keyring.parse_sig(sig) == (0x102, 0x304) + + +def test_parse_rsa(): + sig, rest = keyring.parse(b'(7:sig-val(3:rsa(1:s4:\x01\x02\x03\x04)))') + assert sig == [b'sig-val', [b'rsa', [b's', b'\x01\x02\x03\x04']]] + assert rest == b'' + assert keyring.parse_sig(sig) == (0x1020304,)