def test_good_authenticated_handshake(alice): sh = ServerHandshake() ch = AuthenticatedClientHandshake( alice.organization_id, alice.device_id, alice.signing_key, alice.root_verify_key ) assert sh.state == "stalled" challenge_req = sh.build_challenge_req() assert sh.state == "challenge" answer_req = ch.process_challenge_req(challenge_req) sh.process_answer_req(answer_req) assert sh.state == "answer" assert sh.answer_type == HandshakeType.AUTHENTICATED assert sh.answer_data == { "answer": ANY, "client_api_version": API_V2_VERSION, "organization_id": alice.organization_id, "device_id": alice.device_id, "rvk": alice.root_verify_key, } result_req = sh.build_result_req(alice.verify_key) assert sh.state == "result" ch.process_result_req(result_req) assert sh.client_api_version == API_V2_VERSION
async def _backend_sock_factory(backend, auth_as: LocalDevice, freeze_on_transport_error=True): async with backend_raw_transport_factory( backend, freeze_on_transport_error=freeze_on_transport_error ) as transport: # Handshake ch = AuthenticatedClientHandshake( auth_as.organization_id, auth_as.device_id, auth_as.signing_key, auth_as.root_verify_key, ) challenge_req = await transport.recv() answer_req = ch.process_challenge_req(challenge_req) await transport.send(answer_req) result_req = await transport.recv() ch.process_result_req(result_req) yield transport
async def test_authenticated_handshake_bad_rvk(backend, server_factory, alice, otherorg): ch = AuthenticatedClientHandshake( organization_id=alice.organization_id, device_id=alice.device_id, user_signkey=alice.signing_key, root_verify_key=otherorg.root_verify_key, ) async with server_factory(backend.handle_client) as server: stream = await server.connection_factory() transport = await Transport.init_for_client(stream, "127.0.0.1") challenge_req = await transport.recv() answer_req = ch.process_challenge_req(challenge_req) await transport.send(answer_req) result_req = await transport.recv() with pytest.raises(HandshakeRVKMismatch): ch.process_result_req(result_req)
async def test_authenticated_handshake_unknown_device(backend, server_factory, mallory): ch = AuthenticatedClientHandshake( organization_id=mallory.organization_id, device_id=mallory.device_id, user_signkey=mallory.signing_key, root_verify_key=mallory.root_verify_key, ) async with server_factory(backend.handle_client) as server: stream = await server.connection_factory() transport = await Transport.init_for_client(stream, "127.0.0.1") challenge_req = await transport.recv() answer_req = ch.process_challenge_req(challenge_req) await transport.send(answer_req) result_req = await transport.recv() with pytest.raises(HandshakeBadIdentity): ch.process_result_req(result_req)
async def test_authenticated_handshake_good(backend, server_factory, alice): ch = AuthenticatedClientHandshake( organization_id=alice.organization_id, device_id=alice.device_id, user_signkey=alice.signing_key, root_verify_key=alice.root_verify_key, ) async with server_factory(backend.handle_client) as server: stream = await server.connection_factory() transport = await Transport.init_for_client(stream, "127.0.0.1") challenge_req = await transport.recv() answer_req = ch.process_challenge_req(challenge_req) await transport.send(answer_req) result_req = await transport.recv() ch.process_result_req(result_req) assert ch.client_api_version == API_VERSION assert ch.backend_api_version == API_VERSION
async def test_handle_client_coroutine_destroyed_on_client_left( backend, alice, close_on, clean_close, recwarn): # For this test we want to use a real TCP socket (instead of relying on # the `tcp_stream_spy` mock fixture) test the backend on outcome = None outcome_available = trio.Event() async def _handle_client_with_captured_outcome(stream): nonlocal outcome try: ret = await backend.handle_client(stream) except BaseException as exc: outcome = ("exception", exc) outcome_available.set() raise else: outcome = ("return", ret) outcome_available.set() return ret async with trio.open_nursery() as nursery: try: # Start server listeners = await nursery.start( trio.serve_tcp, _handle_client_with_captured_outcome, 0) # Client connect to the server client_stream = await open_stream_to_socket_listener(listeners[0]) async def _do_close_client(): if clean_close: await client_stream.aclose() else: # Reset the tcp socket instead of regular clean close # See https://stackoverflow.com/a/54065411 l_onoff = 1 l_linger = 0 client_stream.setsockopt( socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", l_onoff, l_linger)) client_stream.socket.close() with trio.fail_after(1): await outcome_available.wait() if close_on == "tcp_ready": await _do_close_client() else: if close_on == "before_http_request": # Send the beginning of an http request await client_stream.send_all(b"GET / HTTP/1.1\r\n") await _do_close_client() elif close_on in ("after_http_request"): # Send an entire http request await client_stream.send_all(b"GET / HTTP/1.0\r\n\r\n") # Peer will realize connection is closed after having sent # the answer for the previous request await _do_close_client() else: # First request doing websocket negotiation hostname = f"127.0.0.1:{listeners[0].socket.getsockname()}" transport = await Transport.init_for_client( client_stream, hostname) if close_on == "websocket_ready": await _do_close_client() else: # Client do the handshake ch = AuthenticatedClientHandshake( alice.organization_id, alice.device_id, alice.signing_key, alice.root_verify_key, ) challenge_req = await transport.recv() answer_req = ch.process_challenge_req(challenge_req) if close_on == "handshake_started": await _do_close_client() else: await transport.send(answer_req) result_req = await transport.recv() ch.process_result_req(result_req) assert close_on == "handshake_done" # Sanity check await _do_close_client() # Outcome should aways be the same assert outcome == ("return", None) finally: nursery.cancel_scope.cancel()