async def do_get_claim_requests(self) -> "DeviceGreetInProgress4Ctx": rep = await self._cmds.invite_4_greeter_communicate(token=self.token, payload=b"") if rep["status"] in ("not_found", "already_deleted"): raise InviteNotAvailableError() elif rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError( f"Backend error during step 4 (data exchange): {rep}") if rep["payload"] is None: raise InviteError("Missing InviteDeviceData payload") try: data = InviteDeviceData.decrypt_and_load( rep["payload"], key=self._shared_secret_key) except DataError as exc: raise InviteError( "Invalid InviteDeviceData payload provided by peer") from exc return DeviceGreetInProgress4Ctx( token=self.token, requested_device_label=data.requested_device_label, verify_key=data.verify_key, shared_secret_key=self._shared_secret_key, cmds=self._cmds, )
async def _do_wait_peer(self) -> Tuple[SASCode, SASCode, SecretKey]: claimer_private_key = PrivateKey.generate() rep = await self._cmds.invite_1_claimer_wait_peer( claimer_public_key=claimer_private_key.public_key) if rep["status"] != "ok": raise InviteError(f"Backend error during step 1: {rep}") shared_secret_key = generate_shared_secret_key( our_private_key=claimer_private_key, peer_public_key=rep["greeter_public_key"]) claimer_nonce = generate_nonce() rep = await self._cmds.invite_2a_claimer_send_hashed_nonce( claimer_hashed_nonce=HashDigest.from_data(claimer_nonce)) if rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 2a: {rep}") claimer_sas, greeter_sas = generate_sas_codes( claimer_nonce=claimer_nonce, greeter_nonce=rep["greeter_nonce"], shared_secret_key=shared_secret_key, ) rep = await self._cmds.invite_2b_claimer_send_nonce( claimer_nonce=claimer_nonce) if rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 2b: {rep}") return claimer_sas, greeter_sas, shared_secret_key
async def do_create_new_device(self, author: LocalDevice, device_label: Optional[str]) -> None: device_id = DeviceID(f"{author.user_id}@{DeviceName.new()}") try: now = pendulum_now() device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=now, device_id=device_id, verify_key=self._verify_key, device_label=device_label, ) redacted_device_certificate = device_certificate.evolve( device_label=None) device_certificate = device_certificate.dump_and_sign( author.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( author.signing_key) except DataError as exc: raise InviteError( f"Cannot generate device certificate: {exc}") from exc rep = await self._cmds.device_create( device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, ) if rep["status"] != "ok": raise InviteError(f"Cannot create device: {rep}") try: payload = InviteDeviceConfirmation( device_id=device_id, device_label=device_label, human_handle=author.human_handle, profile=author.profile, private_key=author.private_key, user_manifest_id=author.user_manifest_id, user_manifest_key=author.user_manifest_key, root_verify_key=author.root_verify_key, ).dump_and_encrypt(key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteUserConfirmation payload") from exc rep = await self._cmds.invite_4_greeter_communicate(token=self.token, payload=payload) if rep["status"] in ("not_found", "already_deleted"): raise InviteNotAvailableError() elif rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError( f"Backend error during step 4 (confirmation exchange): {rep}") await self._cmds.invite_delete(token=self.token, reason=InvitationDeletedReason.FINISHED)
async def do_claim_user( self, requested_device_label: Optional[DeviceLabel], requested_human_handle: Optional[HumanHandle], ) -> LocalDevice: # User&device keys are generated here and kept in memory until the end of # the enrollment process. This mean we can lost it if something goes wrong. # This has no impact until step 4 (somewhere between data exchange and # confirmation exchange steps) where greeter upload our certificates in # the server. # This is considered acceptable given 1) the error window is small and # 2) if this occurs the inviter can revoke the user and retry the # enrollment process to fix this private_key = PrivateKey.generate() signing_key = SigningKey.generate() try: payload = InviteUserData( requested_device_label=requested_device_label, requested_human_handle=requested_human_handle, public_key=private_key.public_key, verify_key=signing_key.verify_key, ).dump_and_encrypt(key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteUserData payload") from exc rep = await self._cmds.invite_4_claimer_communicate(payload=payload) _check_rep(rep, step_name="step 4 (data exchange)") rep = await self._cmds.invite_4_claimer_communicate(payload=b"") _check_rep(rep, step_name="step 4 (confirmation exchange)") try: confirmation = InviteUserConfirmation.decrypt_and_load( rep["payload"], key=self._shared_secret_key) except DataError as exc: raise InviteError( "Invalid InviteUserConfirmation payload provided by peer" ) from exc organization_addr = BackendOrganizationAddr.build( backend_addr=self._cmds.addr.get_backend_addr(), organization_id=self._cmds.addr.organization_id, root_verify_key=confirmation.root_verify_key, ) new_device = generate_new_device( organization_addr=organization_addr, device_id=confirmation.device_id, device_label=confirmation.device_label, human_handle=confirmation.human_handle, profile=confirmation.profile, private_key=private_key, signing_key=signing_key, ) return new_device
async def do_claim_user( self, requested_device_label: Optional[str], requested_human_handle: Optional[HumanHandle]) -> LocalDevice: private_key = PrivateKey.generate() signing_key = SigningKey.generate() try: payload = InviteUserData( requested_device_label=requested_device_label, requested_human_handle=requested_human_handle, public_key=private_key.public_key, verify_key=signing_key.verify_key, ).dump_and_encrypt(key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteUserData payload") from exc rep = await self._cmds.invite_4_claimer_communicate(payload=payload) if rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError( f"Backend error during step 4 (data exchange): {rep}") rep = await self._cmds.invite_4_claimer_communicate(payload=b"") if rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError( f"Backend error during step 4 (confirmation exchange): {rep}") try: confirmation = InviteUserConfirmation.decrypt_and_load( rep["payload"], key=self._shared_secret_key) except DataError as exc: raise InviteError( "Invalid InviteUserConfirmation payload provided by peer" ) from exc organization_addr = BackendOrganizationAddr.build( backend_addr=self._cmds.addr, organization_id=self._cmds.addr.organization_id, root_verify_key=confirmation.root_verify_key, ) new_device = generate_new_device( organization_addr=organization_addr, device_id=confirmation.device_id, device_label=confirmation.device_label, human_handle=confirmation.human_handle, profile=confirmation.profile, private_key=private_key, signing_key=signing_key, ) return new_device
async def do_create_new_device(self, author: LocalDevice, device_label: Optional[str]) -> None: device_id = author.user_id.to_device_id(DeviceName.new()) try: now = pendulum_now() device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=now, device_id=device_id, device_label=device_label, verify_key=self._verify_key, ) redacted_device_certificate = device_certificate.evolve( device_label=None) device_certificate = device_certificate.dump_and_sign( author.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( author.signing_key) except DataError as exc: raise InviteError( f"Cannot generate device certificate: {exc}") from exc rep = await self._cmds.device_create( device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, ) _check_rep(rep, step_name="device creation") try: payload = InviteDeviceConfirmation( device_id=device_id, device_label=device_label, human_handle=author.human_handle, profile=author.profile, private_key=author.private_key, user_manifest_id=author.user_manifest_id, user_manifest_key=author.user_manifest_key, root_verify_key=author.root_verify_key, ).dump_and_encrypt(key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteUserConfirmation payload") from exc rep = await self._cmds.invite_4_greeter_communicate(token=self.token, payload=payload) _check_rep(rep, step_name="step 4 (confirmation exchange)") await self._cmds.invite_delete(token=self.token, reason=InvitationDeletedReason.FINISHED)
def _create_new_user_certificates( author: LocalDevice, device_label: Optional[DeviceLabel], human_handle: Optional[HumanHandle], profile: UserProfile, public_key: PublicKey, verify_key: VerifyKey, ) -> Tuple[bytes, bytes, bytes, bytes, InviteUserConfirmation]: """Helper to prepare the creation of a new user.""" device_id = DeviceID.new() try: timestamp = author.timestamp() user_certificate = UserCertificateContent( author=author.device_id, timestamp=timestamp, user_id=device_id.user_id, human_handle=human_handle, public_key=public_key, profile=profile, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=timestamp, device_id=device_id, device_label=device_label, verify_key=verify_key, ) redacted_device_certificate = device_certificate.evolve( device_label=None) user_certificate = user_certificate.dump_and_sign(author.signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( author.signing_key) device_certificate = device_certificate.dump_and_sign( author.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( author.signing_key) except DataError as exc: raise InviteError( f"Cannot generate device certificate: {exc}") from exc invite_user_confirmation = InviteUserConfirmation( device_id=device_id, device_label=device_label, human_handle=human_handle, profile=profile, root_verify_key=author.root_verify_key, ) return ( user_certificate, redacted_user_certificate, device_certificate, redacted_device_certificate, invite_user_confirmation, )
async def do_claim_device( self, requested_device_label: Optional[str]) -> LocalDevice: signing_key = SigningKey.generate() try: payload = InviteDeviceData( requested_device_label=requested_device_label, verify_key=signing_key.verify_key).dump_and_encrypt( key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteDeviceData payload") from exc rep = await self._cmds.invite_4_claimer_communicate(payload=payload) _check_rep(rep, step_name="step 4 (data exchange)") rep = await self._cmds.invite_4_claimer_communicate(payload=b"") _check_rep(rep, step_name="step 4 (confirmation exchange)") try: confirmation = InviteDeviceConfirmation.decrypt_and_load( rep["payload"], key=self._shared_secret_key) except DataError as exc: raise InviteError( "Invalid InviteDeviceConfirmation payload provided by peer" ) from exc organization_addr = BackendOrganizationAddr.build( backend_addr=self._cmds.addr, organization_id=self._cmds.addr.organization_id, root_verify_key=confirmation.root_verify_key, ) return LocalDevice( organization_addr=organization_addr, device_id=confirmation.device_id, device_label=confirmation.device_label, human_handle=confirmation.human_handle, profile=confirmation.profile, private_key=confirmation.private_key, signing_key=signing_key, user_manifest_id=confirmation.user_manifest_id, user_manifest_key=confirmation.user_manifest_key, local_symkey=SecretKey.generate(), )
async def _do_wait_peer(self) -> Tuple[SASCode, SASCode, SecretKey]: greeter_private_key = PrivateKey.generate() rep = await self._cmds.invite_1_greeter_wait_peer( token=self.token, greeter_public_key=greeter_private_key.public_key) if rep["status"] in ("not_found", "already_deleted"): raise InviteNotAvailableError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 1: {rep}") shared_secret_key = generate_shared_secret_key( our_private_key=greeter_private_key, peer_public_key=rep["claimer_public_key"]) greeter_nonce = generate_nonce() rep = await self._cmds.invite_2a_greeter_get_hashed_nonce( token=self.token) if rep["status"] in ("not_found", "already_deleted"): raise InviteNotAvailableError() elif rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 2a: {rep}") claimer_hashed_nonce = rep["claimer_hashed_nonce"] rep = await self._cmds.invite_2b_greeter_send_nonce( token=self.token, greeter_nonce=greeter_nonce) if rep["status"] in ("not_found", "already_deleted"): raise InviteNotAvailableError() elif rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 2b: {rep}") if HashDigest.from_data(rep["claimer_nonce"]) != claimer_hashed_nonce: raise InviteError("Invitee nonce and hashed nonce doesn't match") claimer_sas, greeter_sas = generate_sas_codes( claimer_nonce=rep["claimer_nonce"], greeter_nonce=greeter_nonce, shared_secret_key=shared_secret_key, ) return claimer_sas, greeter_sas, shared_secret_key
def _check_rep(rep, step_name): if rep["status"] == "not_found": raise InviteNotFoundError elif rep["status"] == "already_deleted": raise InviteAlreadyUsedError elif rep["status"] == "invalid_state": raise InvitePeerResetError elif rep["status"] != "ok": raise InviteError(f"Backend error during {step_name}: {rep}")
async def _do_signify_trust(self) -> None: rep = await self._cmds.invite_3b_greeter_signify_trust(token=self.token ) if rep["status"] in ("not_found", "already_deleted"): raise InviteNotAvailableError() elif rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 3a: {rep}")
async def do_get_claim_requests(self) -> "DeviceGreetInProgress4Ctx": rep = await self._cmds.invite_4_greeter_communicate(token=self.token, payload=b"") _check_rep(rep, step_name="step 4 (data exchange)") if rep["payload"] is None: raise InviteError("Missing InviteDeviceData payload") try: data = InviteDeviceData.decrypt_and_load(rep["payload"], key=self._shared_secret_key) except DataError as exc: raise InviteError("Invalid InviteDeviceData payload provided by peer") from exc return DeviceGreetInProgress4Ctx( token=self.token, requested_device_label=data.requested_device_label, verify_key=data.verify_key, shared_secret_key=self._shared_secret_key, cmds=self._cmds, )
def _check_rep(rep, step_name): if rep["status"] == "not_found": raise InviteNotFoundError elif rep["status"] == "already_bootstrapped": raise InviteAlreadyUsedError elif rep["status"] == "invalid_state": raise InvitePeerResetError elif rep["status"] == "invalid_certification" and "timestamp" in rep[ "reason"]: raise InviteTimestampError elif rep["status"] != "ok": raise InviteError(f"Backend error during {step_name}: {rep}")
async def do_create_new_user( self, author: LocalDevice, device_label: Optional[DeviceLabel], human_handle: Optional[HumanHandle], profile: UserProfile, ) -> None: user_certificate, redacted_user_certificate, device_certificate, redacted_device_certificate, invite_user_confirmation = _create_new_user_certificates( author, device_label, human_handle, profile, self._public_key, self._verify_key) rep = await self._cmds.user_create( user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) _check_rep(rep, step_name="step 4 (user certificates upload)") # From now on the user has been created on the server, but greeter # is not aware of it yet. If something goes wrong, we can end up with # the greeter losing it private keys. # This is considered acceptable given 1) the error window is small and # 2) if this occurs the inviter can revoke the user and retry the # enrollment process to fix this try: payload = invite_user_confirmation.dump_and_encrypt( key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteUserConfirmation payload") from exc rep = await self._cmds.invite_4_greeter_communicate(token=self.token, payload=payload) _check_rep(rep, step_name="step 4 (confirmation exchange)") # Invitation deletion is not strictly necessary (enrollment has succeeded # anyway) so it's no big deal if something goes wrong before it can be # done (and it can be manually deleted from invitation list). await self._cmds.invite_delete(token=self.token, reason=InvitationDeletedReason.FINISHED)
async def claimer_retrieve_info( cmds: BackendInvitedCmds ) -> Union["UserClaimInitialCtx", "DeviceClaimInitialCtx"]: rep = await cmds.invite_info() if rep["status"] != "ok": raise InviteError(f"Cannot retrieve invitation informations: {rep}") if rep["type"] == InvitationType.USER: return UserClaimInitialCtx( claimer_email=rep["claimer_email"], greeter_user_id=rep["greeter_user_id"], greeter_human_handle=rep["greeter_human_handle"], cmds=cmds, ) else: return DeviceClaimInitialCtx( greeter_user_id=rep["greeter_user_id"], greeter_human_handle=rep["greeter_human_handle"], cmds=cmds, )
async def do_create_new_device( self, author: LocalDevice, device_label: Optional[DeviceLabel]) -> None: device_id = author.user_id.to_device_id(DeviceName.new()) try: timestamp = author.timestamp() device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=timestamp, device_id=device_id, device_label=device_label, verify_key=self._verify_key, ) redacted_device_certificate = device_certificate.evolve( device_label=None) device_certificate = device_certificate.dump_and_sign( author.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( author.signing_key) except DataError as exc: raise InviteError( f"Cannot generate device certificate: {exc}") from exc rep = await self._cmds.device_create( device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, ) _check_rep(rep, step_name="step 4 (device certificates upload)") # From now on the device has been created on the server, but greeter # is not aware of it yet. If something goes wrong, we can end up with # the greeter losing it private keys. # This is considered acceptable given 1) the error window is small and # 2) if this occurs the inviter can revoke the device and retry the # enrollment process to fix this try: payload = InviteDeviceConfirmation( device_id=device_id, device_label=device_label, human_handle=author.human_handle, profile=author.profile, private_key=author.private_key, user_manifest_id=author.user_manifest_id, user_manifest_key=author.user_manifest_key, root_verify_key=author.root_verify_key, ).dump_and_encrypt(key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteUserConfirmation payload") from exc rep = await self._cmds.invite_4_greeter_communicate(token=self.token, payload=payload) _check_rep(rep, step_name="step 4 (confirmation exchange)") # Invitation deletion is not strictly necessary (enrollment has succeeded # anyway) so it's no big deal if something goes wrong before it can be # done (and it can be manually deleted from invitation list). await self._cmds.invite_delete(token=self.token, reason=InvitationDeletedReason.FINISHED)
async def do_create_new_user( self, author: LocalDevice, device_label: Optional[str], human_handle: Optional[HumanHandle], profile: UserProfile, ) -> None: device_id = DeviceID.new() try: now = pendulum_now() user_certificate = UserCertificateContent( author=author.device_id, timestamp=now, user_id=device_id.user_id, human_handle=human_handle, public_key=self._public_key, profile=profile, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=now, device_id=device_id, device_label=device_label, verify_key=self._verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(author.signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign(author.signing_key) device_certificate = device_certificate.dump_and_sign(author.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( author.signing_key ) except DataError as exc: raise InviteError(f"Cannot generate device certificate: {exc}") from exc rep = await self._cmds.user_create( user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) if rep["status"] != "ok": raise InviteError(f"Cannot create device: {rep}") try: payload = InviteUserConfirmation( device_id=device_id, device_label=device_label, human_handle=human_handle, profile=profile, root_verify_key=author.root_verify_key, ).dump_and_encrypt(key=self._shared_secret_key) except DataError as exc: raise InviteError("Cannot generate InviteUserConfirmation payload") from exc rep = await self._cmds.invite_4_greeter_communicate(token=self.token, payload=payload) _check_rep(rep, step_name="step 4 (confirmation exchange)") await self._cmds.invite_delete(token=self.token, reason=InvitationDeletedReason.FINISHED)
async def _do_wait_peer_trust(self) -> None: rep = await self._cmds.invite_3b_claimer_wait_peer_trust() if rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 3b: {rep}")