async def _apiv1_process_anonymous_answer( backend, transport: Transport, handshake: ServerHandshake ) -> Tuple[Optional[BaseClientContext], bytes, Optional[Dict]]: organization_id = handshake.answer_data["organization_id"] expected_rvk = handshake.answer_data["rvk"] def _make_error_infos(reason): return { "reason": reason, "handshake_type": APIV1_HandshakeType.ANONYMOUS, "organization_id": organization_id, } try: organization = await backend.organization.get(organization_id) except OrganizationNotFoundError: result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos("Bad organization") if organization.expiration_date is not None and organization.expiration_date <= pendulum_now(): result_req = handshake.build_organization_expired_result_req() return None, result_req, _make_error_infos("Expired organization") if expected_rvk and organization.root_verify_key != expected_rvk: result_req = handshake.build_rvk_mismatch_result_req() return None, result_req, _make_error_infos("Bad root verify key") context = APIV1_AnonymousClientContext(transport, handshake, organization_id=organization_id) result_req = handshake.build_result_req() return context, result_req, None
async def _apiv1_process_administration_answer( backend, transport: Transport, handshake: ServerHandshake ) -> Tuple[Optional[BaseClientContext], bytes, Optional[Dict]]: if handshake.answer_data["token"] != backend.config.administration_token: result_req = handshake.build_bad_administration_token_result_req() error_infos = {"reason": "Bad token", "handshake_type": APIV1_HandshakeType.ADMINISTRATION} return None, result_req, error_infos context = APIV1_AdministrationClientContext(transport, handshake) result_req = handshake.build_result_req() return context, result_req, None
async def poorly_serve_client(stream): nonlocal client_answered transport = await Transport.init_for_server(stream) handshake = ServerHandshake() await transport.send(handshake.build_challenge_req()) await transport.recv() # Close connection during handshake await stream.aclose() client_answered = True
async def _do_process_authenticated_answer( backend, transport: Transport, handshake: ServerHandshake, handshake_type ) -> Tuple[Optional[BaseClientContext], bytes, Optional[Dict]]: organization_id = cast(OrganizationID, handshake.answer_data["organization_id"]) device_id = cast(DeviceID, handshake.answer_data["device_id"]) expected_rvk = handshake.answer_data["rvk"] def _make_error_infos(reason): return { "reason": reason, "handshake_type": handshake_type, "organization_id": organization_id, "device_id": device_id, } try: organization = await backend.organization.get(organization_id) user, device = await backend.user.get_user_with_device( organization_id, device_id) except (OrganizationNotFoundError, UserNotFoundError, KeyError) as exc: result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos(str(exc)) if organization.root_verify_key != expected_rvk: result_req = handshake.build_rvk_mismatch_result_req() return None, result_req, _make_error_infos("Bad root verify key") if organization.is_expired: result_req = handshake.build_organization_expired_result_req() return None, result_req, _make_error_infos("Expired organization") if user.revoked_on and user.revoked_on <= pendulum_now(): result_req = handshake.build_revoked_device_result_req() return None, result_req, _make_error_infos("Revoked device") context = AuthenticatedClientContext( transport=transport, api_version=handshake.backend_api_version, organization_id=organization_id, device_id=device_id, human_handle=user.human_handle, device_label=device.device_label, profile=user.profile, public_key=user.public_key, verify_key=device.verify_key, ) result_req = handshake.build_result_req(device.verify_key) return context, result_req, None
async def _process_invited_answer( backend, transport: Transport, handshake: ServerHandshake ) -> Tuple[Optional[BaseClientContext], bytes, Optional[Dict]]: organization_id = handshake.answer_data["organization_id"] invitation_type = handshake.answer_data["invitation_type"] token = handshake.answer_data["token"] def _make_error_infos(reason): return { "reason": reason, "handshake_type": HandshakeType.INVITED, "organization_id": organization_id, "invitation_type": invitation_type, "token": token, } try: organization = await backend.organization.get(organization_id) except OrganizationNotFoundError: result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos("Bad organization") if organization.expiration_date is not None and organization.expiration_date <= pendulum_now(): result_req = handshake.build_organization_expired_result_req() return None, result_req, _make_error_infos("Expired organization") try: invitation = await backend.invite.info( organization_id, token=handshake.answer_data["token"] ) except InvitationError: result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos("Bad invitation") if handshake.answer_data["invitation_type"] == InvitationType.USER: expected_invitation_type = UserInvitation else: # Device expected_invitation_type = DeviceInvitation if not isinstance(invitation, expected_invitation_type): result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos("Bad invitation") context = InvitedClientContext( transport, handshake, organization_id=organization_id, invitation=invitation ) result_req = handshake.build_result_req() return context, result_req, None
async def _apiv1_process_anonymous_answer( backend, transport: Transport, handshake: ServerHandshake ) -> Tuple[Optional[BaseClientContext], bytes, Optional[Dict]]: organization_id = cast(OrganizationID, handshake.answer_data["organization_id"]) expected_rvk = handshake.answer_data["rvk"] def _make_error_infos(reason): return { "reason": reason, "handshake_type": APIV1_HandshakeType.ANONYMOUS, "organization_id": organization_id, } try: organization = await backend.organization.get(organization_id) except OrganizationNotFoundError: if backend.config.organization_spontaneous_bootstrap: # Lazy creation of the organization with always the same empty token try: await backend.organization.create(id=organization_id, bootstrap_token="") except OrganizationAlreadyExistsError: pass organization = await backend.organization.get(organization_id) else: result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos("Bad organization") if organization.is_expired: result_req = handshake.build_organization_expired_result_req() return None, result_req, _make_error_infos("Expired organization") if expected_rvk and organization.root_verify_key != expected_rvk: result_req = handshake.build_rvk_mismatch_result_req() return None, result_req, _make_error_infos("Bad root verify key") context = APIV1_AnonymousClientContext( transport, api_version=handshake.backend_api_version, organization_id=organization_id) result_req = handshake.build_result_req() return context, result_req, None
async def do_handshake( backend, transport: Transport ) -> Tuple[Optional[BaseClientContext], Optional[Dict]]: try: handshake = ServerHandshake() challenge_req = handshake.build_challenge_req() await transport.send(challenge_req) answer_req = await transport.recv() handshake.process_answer_req(answer_req) if handshake.answer_type == HandshakeType.AUTHENTICATED: context, result_req, error_infos = await _process_authenticated_answer( backend, transport, handshake) elif handshake.answer_type == HandshakeType.INVITED: context, result_req, error_infos = await _process_invited_answer( backend, transport, handshake) else: assert handshake.answer_type == APIV1_HandshakeType.ANONYMOUS context, result_req, error_infos = await _apiv1_process_anonymous_answer( backend, transport, handshake) except ProtocolError as exc: context = None result_req = handshake.build_bad_protocol_result_req(str(exc)) error_infos = { "reason": str(exc), "handshake_type": handshake.answer_type } await transport.send(result_req) return context, error_infos
def test_good_invited_handshake(coolorg, invitation_type): organization_id = OrganizationID("Org") token = uuid4() sh = ServerHandshake() ch = InvitedClientHandshake( organization_id=organization_id, invitation_type=invitation_type, token=token ) 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.INVITED assert sh.answer_data == { "client_api_version": API_V2_VERSION, "organization_id": organization_id, "invitation_type": invitation_type, "token": token, } result_req = sh.build_result_req() assert sh.state == "result" ch.process_result_req(result_req) assert sh.client_api_version == API_V2_VERSION
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
def test_build_result_req_bad_challenge(alice): sh = ServerHandshake() sh.build_challenge_req() answer = { "handshake": "answer", "type": HandshakeType.AUTHENTICATED.value, "client_api_version": API_V2_VERSION, "organization_id": alice.organization_id, "device_id": alice.device_id, "rvk": alice.root_verify_key.encode(), "answer": alice.signing_key.sign(sh.challenge + b"-dummy"), } sh.process_answer_req(packb(answer)) with pytest.raises(HandshakeFailedChallenge): sh.build_result_req(alice.verify_key)
def test_process_answer_req_bad_format(req, alice): for key, good_value in [ ("organization_id", alice.organization_id), ("device_id", alice.device_id), ("rvk", alice.root_verify_key.encode()), ("token", uuid4()), ]: if req.get(key) == "<good>": req[key] = good_value req["client_api_version"] = API_V2_VERSION sh = ServerHandshake() sh.build_challenge_req() with pytest.raises(InvalidMessageError): sh.process_answer_req(packb(req))
def test_build_bad_outcomes(alice, method, expected_result): sh = ServerHandshake() sh.build_challenge_req() answer = { "handshake": "answer", "type": HandshakeType.AUTHENTICATED.value, "client_api_version": API_V2_VERSION, "organization_id": alice.organization_id, "device_id": alice.device_id, "rvk": alice.root_verify_key.encode(), "answer": alice.signing_key.sign(sh.challenge), } sh.process_answer_req(packb(answer)) req = getattr(sh, method)() assert unpackb(req) == {"handshake": "result", "result": expected_result, "help": ANY}
async def _process_invited_answer( backend, transport: Transport, handshake: ServerHandshake ) -> Tuple[Optional[BaseClientContext], bytes, Optional[Dict]]: organization_id = cast(OrganizationID, handshake.answer_data["organization_id"]) invitation_type = cast(InvitationType, handshake.answer_data["invitation_type"]) token = handshake.answer_data["token"] def _make_error_infos(reason): return { "reason": reason, "handshake_type": HandshakeType.INVITED, "organization_id": organization_id, "invitation_type": invitation_type, "token": token, } try: organization = await backend.organization.get(organization_id) except OrganizationNotFoundError: result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos("Bad organization") if organization.is_expired: result_req = handshake.build_organization_expired_result_req() return None, result_req, _make_error_infos("Expired organization") try: invitation = await backend.invite.info( organization_id, token=handshake.answer_data["token"] ) except InvitationAlreadyDeletedError: result_req = handshake.build_bad_identity_result_req( help="Invalid handshake: Invitation already deleted" ) return None, result_req, _make_error_infos("Bad invitation") except InvitationNotFoundError: result_req = handshake.build_bad_identity_result_req( help="Invalid handshake: Invitation not found" ) return None, result_req, _make_error_infos("Bad invitation") except InvitationError: result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos("Bad invitation") expected_invitation_type: Type = ( UserInvitation if handshake.answer_data["invitation_type"] == InvitationType.USER else DeviceInvitation ) if not isinstance(invitation, expected_invitation_type): result_req = handshake.build_bad_identity_result_req() return None, result_req, _make_error_infos("Bad invitation") context = InvitedClientContext( transport, handshake, organization_id=organization_id, invitation=invitation ) result_req = handshake.build_result_req() return context, result_req, None
async def _do_handshake(self, transport): context = None error_infos = None try: handshake = transport.handshake = ServerHandshake() challenge_req = handshake.build_challenge_req() await transport.send(challenge_req) answer_req = await transport.recv() handshake.process_answer_req(answer_req) if handshake.answer_type == "authenticated": organization_id = handshake.answer_data["organization_id"] device_id = handshake.answer_data["device_id"] expected_rvk = handshake.answer_data["rvk"] try: organization = await self.organization.get(organization_id) user, device = await self.user.get_user_with_device( organization_id, device_id) except (OrganizationNotFoundError, UserNotFoundError, KeyError) as exc: result_req = handshake.build_bad_identity_result_req() error_infos = { "reason": str(exc), "handshake_type": "authenticated", "organization_id": organization_id, "device_id": device_id, } else: if organization.root_verify_key != expected_rvk: result_req = handshake.build_rvk_mismatch_result_req() error_infos = { "reason": "Bad root verify key", "handshake_type": "authenticated", "organization_id": organization_id, "device_id": device_id, } elif (organization.expiration_date is not None and organization.expiration_date <= pendulum_now()): result_req = handshake.build_organization_expired_result_req( ) error_infos = { "reason": "Expired organization", "handshake_type": "authenticated", "organization_id": organization_id, "device_id": device_id, } elif user.revoked_on and user.revoked_on <= pendulum_now(): result_req = handshake.build_revoked_device_result_req( ) error_infos = { "reason": "Revoked device", "handshake_type": "authenticated", "organization_id": organization_id, "device_id": device_id, } else: context = LoggedClientContext( transport, organization_id, user.is_admin, device_id, user.public_key, device.verify_key, ) result_req = handshake.build_result_req( device.verify_key) elif handshake.answer_type == "anonymous": organization_id = handshake.answer_data["organization_id"] expected_rvk = handshake.answer_data["rvk"] try: organization = await self.organization.get(organization_id) except OrganizationNotFoundError: result_req = handshake.build_bad_identity_result_req() error_infos = { "reason": "Bad organization", "handshake_type": "anonymous", "organization_id": organization_id, } else: if expected_rvk and organization.root_verify_key != expected_rvk: result_req = handshake.build_rvk_mismatch_result_req() error_infos = { "reason": "Bad root verify key", "handshake_type": "anonymous", "organization_id": organization_id, } elif (organization.expiration_date is not None and organization.expiration_date <= pendulum_now()): result_req = handshake.build_organization_expired_result_req( ) error_infos = { "reason": "Expired organization", "handshake_type": "anonymous", "organization_id": organization_id, } else: context = AnonymousClientContext( transport, organization_id) result_req = handshake.build_result_req() elif handshake.answer_type == "administration": if handshake.answer_data[ "token"] == self.config.administration_token: context = AdministrationClientContext(transport) result_req = handshake.build_result_req() else: result_req = handshake.build_bad_administration_token_result_req( ) error_infos = { "reason": "Bad token", "handshake_type": "administration" } else: assert False except ProtocolError as exc: result_req = handshake.build_bad_protocol_result_req(str(exc)) error_infos = { "reason": str(exc), "handshake_type": handshake.answer_type } await transport.send(result_req) return context, error_infos