packages feed

magic-wormhole-0.3.4: tests/python/version_exchange.py

#!/usr/bin/env python

"""Exchange SPAKE2 keys and versions.

This is a fake magic-wormhole client that receives messages from STDIN and
sends them on STDOUT. Each 'message' is a JSON value terminated by a newline.

It handles the following messages in order:

1. SEND `pake` - mailbox message containing our SPAKE2 info
2. RECEIVE `pake` - mailbox message with the other side's SPAKE2 info
3. SEND `version` - mailbox message containing our version information,
   encrypted with the negotiated SPAKE2 info
4. RECEIVE `version` - mailbox message containing their version information,
   encrypted with the negotiated SPAKE2 info
"""
import argparse
import attr
import json
import sys

try:
    import wormhole
except ImportError as e:
    print("Could not find Magic Wormhole: %s" % (e,))
    sys.exit(0)

from wormhole._key import decrypt_data, encrypt_data, derive_phase_key
from wormhole import util
from spake2 import SPAKE2_Symmetric

wormhole  # Be still, pyflakes


def main():
    parser = argparse.ArgumentParser(prog='version_exchange')
    parser.add_argument(
        '--code', dest='code', type=str,
        help='Password to use to connect to other side')
    parser.add_argument(
        '--side', dest='side', type=str,
        help='Identifier for this side of the exchange')
    parser.add_argument(
        '--app-id', dest='app_id', type=str,
        help='Identifier for the application')
    params = parser.parse_args(sys.argv[1:])
    transport = Transport(input_stream=sys.stdin, output_stream=sys.stdout)
    run_exchange(transport, params.code, params.app_id, params.side)


def run_exchange(transport, code, app_id, side):
    # Send the SPAKE2 message
    spake = SPAKE2_Symmetric(
        util.to_bytes(code), idSymmetric=util.to_bytes(app_id))
    outbound = spake.start()
    transport.send_json({
        'phase': 'pake',
        'body': util.bytes_to_hexstr(
            util.dict_to_bytes({
                'pake_v1': util.bytes_to_hexstr(outbound),
            })
        ),
        'side': side,
        'type': 'message',
    })

    # Receive SPAKE2 message
    pake_msg = transport.receive_json()
    inbound = util.hexstr_to_bytes(
        util.bytes_to_dict(
            util.hexstr_to_bytes(pake_msg['body'])
        )['pake_v1']
    )
    spake_key = spake.finish(inbound)

    # Send the versions message
    version_phase = 'version'
    transport.send_json({
        'phase': version_phase,
        'body': util.bytes_to_hexstr(
            encrypt_data(
                derive_phase_key(spake_key, side, version_phase),
                util.dict_to_bytes({'app_versions': {}})
            )
        ),
        'side': side,
        'type': 'message',
    })

    # Receive the versions message
    versions = transport.receive_json()
    their_versions = util.bytes_to_dict(
        decrypt_data(
            derive_phase_key(spake_key, versions['side'], versions['phase']),
            util.hexstr_to_bytes(
                versions['body']
            ),
        ),
    )
    return their_versions


@attr.s
class Transport(object):
    # XXX: Duplicated with spake2_exchange.py
    input_stream = attr.ib()
    output_stream = attr.ib()

    def send_json(self, json_value):
        self.output_stream.write(json.dumps(json_value))
        self.output_stream.write('\n')
        self.output_stream.flush()

    def receive_json(self):
        line = self.input_stream.readline()
        return json.loads(line.strip())


if __name__ == '__main__':
    main()