def new_user_and_device(self, is_admin, certifier_id, certifier_key): device_id = self.next_device_id() local_device = local_device_factory(device_id, org=coolorg) self.local_devices[device_id] = local_device user = UserCertificateContent( author=certifier_id, timestamp=pendulum_now(), user_id=local_device.user_id, public_key=local_device.public_key, is_admin=is_admin, ) self.users_content[device_id.user_id] = user self.users_certifs[device_id.user_id] = user.dump_and_sign( certifier_key) device = DeviceCertificateContent( author=certifier_id, timestamp=pendulum_now(), device_id=local_device.device_id, verify_key=local_device.verify_key, ) self.devices_content[local_device.device_id] = device self.devices_certifs[ local_device.device_id] = device.dump_and_sign(certifier_key) return device_id
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, )
def new_user_and_device(self, is_admin, certifier_id, certifier_key): device_id = self.next_device_id() local_device = local_device_factory(device_id, org=coolorg) self.local_devices[device_id] = local_device user = UserCertificateContent( author=certifier_id, timestamp=pendulum_now(), user_id=local_device.user_id, human_handle=local_device.human_handle, public_key=local_device.public_key, profile=UserProfile.ADMIN if is_admin else UserProfile.STANDARD, ) self.users_content[device_id.user_id] = user self.users_certifs[device_id.user_id] = user.dump_and_sign( certifier_key) device = DeviceCertificateContent( author=certifier_id, timestamp=pendulum_now(), device_id=local_device.device_id, device_label=local_device.device_label, verify_key=local_device.verify_key, ) self.devices_content[local_device.device_id] = device self.devices_certifs[ local_device.device_id] = device.dump_and_sign(certifier_key) return device_id
async def api_device_create(self, client_ctx, msg): msg = device_create_serializer.req_load(msg) try: data = DeviceCertificateContent.verify_and_load( msg["device_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) redacted_data = None if msg["redacted_device_certificate"]: redacted_data = DeviceCertificateContent.verify_and_load( msg["redacted_device_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) except DataError as exc: return { "status": "invalid_certification", "reason": f"Invalid certification data ({exc}).", } if not timestamps_in_the_ballpark(data.timestamp, pendulum.now()): return { "status": "invalid_certification", "reason": f"Invalid timestamp in certification.", } if data.device_id.user_id != client_ctx.user_id: return {"status": "bad_user_id", "reason": "Device must be handled by it own user."} if redacted_data: if redacted_data.evolve(device_label=data.device_label) != data: return { "status": "invalid_data", "reason": "Redacted Device certificate differs from Device certificate.", } if redacted_data.device_label: return { "status": "invalid_data", "reason": "Redacted Device certificate must not contain a device_label field.", } try: device = Device( device_id=data.device_id, device_label=data.device_label, device_certificate=msg["device_certificate"], redacted_device_certificate=msg["redacted_device_certificate"] or msg["device_certificate"], device_certifier=data.author, created_on=data.timestamp, ) await self.create_device(client_ctx.organization_id, device) except UserAlreadyExistsError as exc: return {"status": "already_exists", "reason": str(exc)} return device_create_serializer.rep_dump({"status": "ok"})
async def bootstrap_organization( cmds: APIV1_BackendAnonymousCmds, human_handle: Optional[HumanHandle], device_label: Optional[str], ) -> LocalDevice: root_signing_key = SigningKey.generate() root_verify_key = root_signing_key.verify_key organization_addr = BackendOrganizationAddr.build( backend_addr=cmds.addr, organization_id=cmds.addr.organization_id, root_verify_key=root_verify_key, ) device = generate_new_device( organization_addr=organization_addr, profile=UserProfile.ADMIN, human_handle=human_handle, device_label=device_label, ) now = pendulum_now() user_certificate = UserCertificateContent( author=None, timestamp=now, user_id=device.user_id, human_handle=device.human_handle, public_key=device.public_key, profile=device.profile, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=None, timestamp=now, device_id=device.device_id, device_label=device.device_label, verify_key=device.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(root_signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( root_signing_key) device_certificate = device_certificate.dump_and_sign(root_signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( root_signing_key) rep = await cmds.organization_bootstrap( organization_id=cmds.addr.organization_id, bootstrap_token=cmds.addr.token, root_verify_key=root_verify_key, 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="organization bootstrap") return device
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 bootstrap_organization( addr: BackendOrganizationBootstrapAddr, human_handle: Optional[HumanHandle], device_label: Optional[DeviceLabel], ) -> LocalDevice: root_signing_key = SigningKey.generate() root_verify_key = root_signing_key.verify_key organization_addr = BackendOrganizationAddr.build( backend_addr=addr.get_backend_addr(), organization_id=addr.organization_id, root_verify_key=root_verify_key, ) device = generate_new_device( organization_addr=organization_addr, profile=UserProfile.ADMIN, human_handle=human_handle, device_label=device_label, ) timestamp = device.timestamp() user_certificate = UserCertificateContent( author=None, timestamp=timestamp, user_id=device.user_id, human_handle=device.human_handle, public_key=device.public_key, profile=device.profile, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=None, timestamp=timestamp, device_id=device.device_id, device_label=device.device_label, verify_key=device.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(root_signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign(root_signing_key) device_certificate = device_certificate.dump_and_sign(root_signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign(root_signing_key) rep = await failsafe_organization_bootstrap( addr=addr, root_verify_key=root_verify_key, 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="organization bootstrap") return device
async def test_redacted_certificates_cannot_contain_sensitive_data( alice_backend_sock, alice, mallory): now = pendulum.now() user_certificate = UserCertificateContent( author=alice.device_id, timestamp=now, user_id=mallory.user_id, public_key=mallory.public_key, profile=UserProfile.STANDARD, human_handle=mallory.human_handle, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=mallory.device_id, verify_key=mallory.verify_key, device_label=mallory.device_label, ) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(alice.signing_key) device_certificate = device_certificate.dump_and_sign(alice.signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( alice.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( alice.signing_key) with freeze_time(now): rep = await user_create( alice_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=user_certificate, redacted_device_certificate=redacted_device_certificate, ) assert rep == { "status": "invalid_data", "reason": "Redacted User certificate must not contain a human_handle field.", } rep = await user_create( alice_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=device_certificate, ) assert rep == { "status": "invalid_data", "reason": "Redacted Device certificate must not contain a device_label field.", }
async def test_user_create_nok_active_users_limit_reached( backend, backend_data_binder_factory, backend_sock_factory, coolorg, alice, mallory ): # Ensure there is only one user in the organization... binder = backend_data_binder_factory(backend) await binder.bind_organization(coolorg, alice) # ...so our active user limit has just been reached await backend.organization.update(alice.organization_id, active_users_limit=1) now = pendulum.now() user_certificate = UserCertificateContent( author=alice.device_id, timestamp=now, user_id=mallory.user_id, human_handle=None, public_key=mallory.public_key, profile=UserProfile.STANDARD, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=mallory.device_id, device_label=None, verify_key=mallory.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(alice.signing_key) device_certificate = device_certificate.dump_and_sign(alice.signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign(alice.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign(alice.signing_key) async with backend_sock_factory(backend, alice) as sock: rep = await user_create( sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) assert rep == {"status": "active_users_limit_reached"} # Now correct the limit, and ensure the user can be created await backend.organization.update(alice.organization_id, active_users_limit=2) rep = await user_create( sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) assert rep == {"status": "ok"}
async def test_device_claim_invalid_returned_certificate( running_backend, backend, alice, bob, backend_claim_response_hook): hooks = backend_claim_response_hook device_count = 0 async def _do_test(): nonlocal device_count device_count += 1 new_device_id = DeviceID(f"{alice.user_id}@newdev{device_count}") token = generate_invitation_token() exception_occured = False async def _from_alice(): await invite_and_create_device(alice, new_device_id.device_name, token=token) async def _from_new_device(): nonlocal exception_occured with pytest.raises(InviteClaimCryptoError): await claim_device(alice.organization_addr, new_device_id, token=token) exception_occured = True await _invite_and_claim(running_backend, _from_alice, _from_new_device, event_name="device.claimed") assert exception_occured # Invalid data hooks["device_certificate"] = lambda x: b"dummy" await _do_test() # Certificate author differs from invitation creator def bob_sign(certif): return certif.evolve(author=bob.device_id).dump_and_sign( author_signkey=bob.signing_key) hooks["device_certificate"] = lambda raw: bob_sign( DeviceCertificateContent.unsecure_load(raw)) await _do_test() # Certificate info doesn't correspond to created user hooks["device_certificate"] = ( lambda raw: DeviceCertificateContent.unsecure_load(raw).evolve( device_id=bob.device_id).dump_and_sign(author_signkey=alice. signing_key)) await _do_test()
async def _bootstrap_organization(debug, device_id, organization_bootstrap_addr, config_dir, force, password): root_signing_key = SigningKey.generate() root_verify_key = root_signing_key.verify_key organization_addr = organization_bootstrap_addr.generate_organization_addr( root_verify_key) device_display = click.style(device_id, fg="yellow") device = generate_new_device(device_id, organization_addr, profile=UserProfile.ADMIN) with operation(f"Creating locally {device_display}"): save_device_with_password(config_dir, device, password, force=force) now = pendulum.now() user_certificate = UserCertificateContent( author=None, timestamp=now, user_id=device.user_id, public_key=device.public_key, profile=device.profile, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent(author=None, timestamp=now, device_id=device_id, verify_key=device.verify_key) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(root_signing_key) device_certificate = device_certificate.dump_and_sign(root_signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( root_signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( root_signing_key) async with spinner(f"Sending {device_display} to server"): async with apiv1_backend_anonymous_cmds_factory( organization_bootstrap_addr) as cmds: await cmds.organization_bootstrap( organization_id=organization_bootstrap_addr.organization_id, bootstrap_token=organization_bootstrap_addr.token, root_verify_key=root_verify_key, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) organization_addr_display = click.style(organization_addr.to_url(), fg="yellow") click.echo(f"Organization url: {organization_addr_display}")
async def test_device_create_ok(backend, backend_sock_factory, alice_backend_sock, alice, alice_nd, with_labels): now = pendulum.now() device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=alice_nd.device_id, device_label=alice_nd.device_label, verify_key=alice_nd.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) if not with_labels: device_certificate = redacted_device_certificate device_certificate = device_certificate.dump_and_sign(alice.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( alice.signing_key) with backend.event_bus.listen() as spy: rep = await device_create( alice_backend_sock, device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, ) assert rep == {"status": "ok"} # No guarantees this event occurs before the command's return await spy.wait_with_timeout( BackendEvent.DEVICE_CREATED, { "organization_id": alice_nd.organization_id, "device_id": alice_nd.device_id, "device_certificate": device_certificate, "encrypted_answer": b"", }, ) # Make sure the new device can connect now async with backend_sock_factory(backend, alice_nd) as sock: rep = await ping(sock, ping="Hello world !") assert rep == {"status": "ok", "pong": "Hello world !"} # Check the resulting data in the backend _, backend_device = await backend.user.get_user_with_device( alice_nd.organization_id, alice_nd.device_id) assert backend_device == Device( device_id=alice_nd.device_id, device_label=alice_nd.device_label if with_labels else None, device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, device_certifier=alice.device_id, created_on=now, )
def local_device_to_backend_user( device: LocalDevice, certifier: Union[LocalDevice, OrganizationFullData] ) -> Tuple[BackendUser, BackendDevice]: if isinstance(certifier, OrganizationFullData): certifier_id = None certifier_signing_key = certifier.root_signing_key else: certifier_id = certifier.device_id certifier_signing_key = certifier.signing_key timestamp = device.timestamp() user_certificate = UserCertificateContent( author=certifier_id, timestamp=timestamp, user_id=device.user_id, public_key=device.public_key, profile=device.profile, human_handle=device.human_handle, ) device_certificate = DeviceCertificateContent( author=certifier_id, timestamp=timestamp, device_id=device.device_id, device_label=device.device_label, verify_key=device.verify_key, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) redacted_device_certificate = device_certificate.evolve(device_label=None) user = BackendUser( user_id=device.user_id, human_handle=device.human_handle, profile=device.profile, user_certificate=user_certificate.dump_and_sign(certifier_signing_key), redacted_user_certificate=redacted_user_certificate.dump_and_sign( certifier_signing_key), user_certifier=certifier_id, created_on=timestamp, ) first_device = BackendDevice( device_id=device.device_id, device_label=device.device_label, device_certificate=device_certificate.dump_and_sign( certifier_signing_key), redacted_device_certificate=redacted_device_certificate.dump_and_sign( certifier_signing_key), device_certifier=certifier_id, created_on=timestamp, ) return user, first_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 new_device(self, user, author_user, author_device_rand): author = self.get_device(author_user, author_device_rand) device_id = self.next_device_id(user) note(f"new device: {device_id} (author: {author.device_id})") local_device = local_device_factory(device_id, org=coolorg) device = DeviceCertificateContent( author=author.device_id, timestamp=pendulum_now(), device_id=local_device.device_id, verify_key=local_device.verify_key, ) self.devices_content[local_device.device_id] = device self.devices_certifs[local_device.device_id] = device.dump_and_sign(author.signing_key)
async def _register_new_user( cmds: BackendAuthenticatedCmds, author: LocalDevice, device_label: Optional[str], human_handle: Optional[HumanHandle], profile: UserProfile, ) -> LocalDevice: new_device = generate_new_device( organization_addr=cmds.addr, device_label=device_label, human_handle=human_handle, profile=profile, ) now = pendulum_now() user_certificate = UserCertificateContent( author=author.device_id, timestamp=now, user_id=new_device.device_id.user_id, human_handle=new_device.human_handle, public_key=new_device.public_key, profile=new_device.profile, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=now, device_id=new_device.device_id, device_label=new_device.device_label, verify_key=new_device.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) rep = await 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 RuntimeError(f"Cannot create user: {rep}") return new_device
async def test_user_create_invalid_certificate(backend, apiv1_backend_sock_factory, alice, bob, mallory): now = pendulum.now() good_user_certificate = UserCertificateContent( author=alice.device_id, timestamp=now, user_id=mallory.user_id, human_handle=None, public_key=mallory.public_key, profile=UserProfile.STANDARD, ).dump_and_sign(alice.signing_key) good_device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=mallory.device_id, device_label=None, verify_key=mallory.verify_key, ).dump_and_sign(alice.signing_key) bad_user_certificate = UserCertificateContent( author=bob.device_id, timestamp=now, user_id=mallory.user_id, human_handle=None, public_key=mallory.public_key, profile=UserProfile.STANDARD, ).dump_and_sign(bob.signing_key) bad_device_certificate = DeviceCertificateContent( author=bob.device_id, timestamp=now, device_id=mallory.device_id, device_label=None, verify_key=mallory.verify_key, ).dump_and_sign(bob.signing_key) async with apiv1_backend_sock_factory(backend, alice) as sock: for cu, cd in [ (good_user_certificate, bad_device_certificate), (bad_user_certificate, good_device_certificate), (bad_user_certificate, bad_device_certificate), ]: rep = await user_create(sock, user_certificate=cu, device_certificate=cd) assert rep == { "status": "invalid_certification", "reason": "Invalid certification data (Signature was forged or corrupt).", }
async def _create_new_device_for_self( original_device: LocalDevice, new_device_label: DeviceLabel) -> LocalDevice: """ Raises: BackendConnectionError """ new_device = LocalDevice( organization_addr=original_device.organization_addr, device_id=DeviceID(f"{original_device.user_id}@{DeviceName.new()}"), device_label=new_device_label, human_handle=original_device.human_handle, profile=original_device.profile, private_key=original_device.private_key, signing_key=SigningKey.generate(), user_manifest_id=original_device.user_manifest_id, user_manifest_key=original_device.user_manifest_key, local_symkey=SecretKey.generate(), ) now = pendulum_now() device_certificate = DeviceCertificateContent( author=original_device.device_id, timestamp=now, device_id=new_device.device_id, device_label=new_device.device_label, verify_key=new_device.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) device_certificate = device_certificate.dump_and_sign( original_device.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( original_device.signing_key) async with backend_authenticated_cmds_factory( addr=original_device.organization_addr, device_id=original_device.device_id, signing_key=original_device.signing_key, ) as cmds: rep = await cmds.device_create( device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, ) if rep["status"] != "ok": raise BackendConnectionError(f"Cannot create recovery device: {rep}") return new_device
def validate_new_device_certificate( expected_author: DeviceID, author_verify_key: VerifyKey, device_certificate: bytes, redacted_device_certificate: bytes, ) -> Device: try: data = DeviceCertificateContent.verify_and_load( device_certificate, author_verify_key=author_verify_key, expected_author=expected_author) redacted_data = DeviceCertificateContent.verify_and_load( redacted_device_certificate, author_verify_key=author_verify_key, expected_author=expected_author, ) except DataError as exc: raise CertificateValidationError( "invalid_certification", f"Invalid certification data ({exc}).") if not timestamps_in_the_ballpark(data.timestamp, pendulum.now()): raise CertificateValidationError( "invalid_certification", f"Invalid timestamp in certification.") if data.device_id.user_id != expected_author.user_id: raise CertificateValidationError( "bad_user_id", "Device must be handled by it own user.") if redacted_data.evolve(device_label=data.device_label) != data: raise CertificateValidationError( "invalid_data", "Redacted Device certificate differs from Device certificate.") if redacted_data.device_label: raise CertificateValidationError( "invalid_data", "Redacted Device certificate must not contain a device_label field." ) return Device( device_id=data.device_id, device_label=data.device_label, device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, device_certifier=data.author, created_on=data.timestamp, )
async def test_device_create_ok(backend, backend_sock_factory, alice_backend_sock, alice, alice_nd): now = pendulum.now() device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=alice_nd.device_id, verify_key=alice_nd.verify_key, ).dump_and_sign(alice.signing_key) with backend.event_bus.listen() as spy: rep = await device_create(alice_backend_sock, device_certificate=device_certificate, encrypted_answer=b"<good>") assert rep == {"status": "ok"} # No guarantees this event occurs before the command's return await spy.wait_with_timeout( "device.created", { "organization_id": alice_nd.organization_id, "device_id": alice_nd.device_id, "device_certificate": device_certificate, "encrypted_answer": b"<good>", }, ) # Make sure the new device can connect now async with backend_sock_factory(backend, alice_nd) as sock: await sock.send(packb({"cmd": "ping", "ping": "Hello world !"})) raw_rep = await sock.recv() rep = ping_serializer.rep_loads(raw_rep) assert rep == {"status": "ok", "pong": "Hello world !"}
async def _alice_invite(): rep = await alice_backend_cmds.user_invite(mallory.user_id) assert rep["status"] == "ok" claim = UserClaimContent.decrypt_and_load_for( rep["encrypted_claim"], recipient_privkey=alice.private_key ) assert claim.token == token now = pendulum.now() user_certificate = UserCertificateContent( author=alice.device_id, timestamp=now, user_id=claim.device_id.user_id, public_key=claim.public_key, is_admin=False, ).dump_and_sign(alice.signing_key) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=claim.device_id, verify_key=claim.verify_key, ).dump_and_sign(alice.signing_key) with trio.fail_after(1): rep = await alice_backend_cmds.user_create(user_certificate, device_certificate) assert rep["status"] == "ok"
async def test_user_create_certificate_too_old(alice_backend_sock, alice, mallory): too_old = pendulum.datetime(2000, 1, 1) now = too_old.add(seconds=INVITATION_VALIDITY + 1) user_certificate = UserCertificateContent( author=alice.device_id, timestamp=too_old, user_id=mallory.user_id, human_handle=mallory.human_handle, public_key=mallory.public_key, profile=UserProfile.STANDARD, ).dump_and_sign(alice.signing_key) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=too_old, device_id=mallory.device_id, device_label=mallory.device_label, verify_key=mallory.verify_key, ).dump_and_sign(alice.signing_key) with freeze_time(now): rep = await user_create( alice_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=user_certificate, redacted_device_certificate=device_certificate, ) assert rep == { "status": "invalid_certification", "reason": "Invalid timestamp in certificate.", }
async def test_user_create_not_matching_certified_on(alice_backend_sock, alice, mallory): date1 = pendulum.datetime(2000, 1, 1) date2 = date1.add(seconds=1) user_certificate = UserCertificateContent( author=alice.device_id, timestamp=date1, user_id=mallory.user_id, human_handle=mallory.human_handle, public_key=mallory.public_key, profile=UserProfile.STANDARD, ).dump_and_sign(alice.signing_key) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=date2, device_id=mallory.device_id, device_label=mallory.device_label, verify_key=mallory.verify_key, ).dump_and_sign(alice.signing_key) with freeze_time(date1): rep = await user_create( alice_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=user_certificate, redacted_device_certificate=device_certificate, ) assert rep == { "status": "invalid_data", "reason": "Device and User certificates must have the same timestamp.", }
async def test_user_create_already_exists(alice_backend_sock, alice, bob): now = pendulum.now() user_certificate = UserCertificateContent( author=alice.device_id, timestamp=now, user_id=bob.user_id, human_handle=None, public_key=bob.public_key, profile=UserProfile.STANDARD, ).dump_and_sign(alice.signing_key) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=bob.device_id, device_label=None, verify_key=bob.verify_key, ).dump_and_sign(alice.signing_key) rep = await user_create( alice_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=user_certificate, redacted_device_certificate=device_certificate, ) assert rep == {"status": "already_exists", "reason": f"User `{bob.user_id}` already exists"}
async def test_user_create_not_matching_user_device(alice_backend_sock, alice, bob, mallory): now = pendulum.now() user_certificate = UserCertificateContent( author=alice.device_id, timestamp=now, user_id=mallory.user_id, human_handle=mallory.human_handle, public_key=mallory.public_key, profile=UserProfile.STANDARD, ).dump_and_sign(alice.signing_key) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=bob.device_id, device_label=mallory.device_label, verify_key=mallory.verify_key, ).dump_and_sign(alice.signing_key) rep = await user_create( alice_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=user_certificate, redacted_device_certificate=device_certificate, ) assert rep == { "status": "invalid_data", "reason": "Device and User must have the same user ID.", }
async def test_user_create_human_handle_with_revoked_previous_one( alice_backend_sock, alice, bob, backend_data_binder): # First revoke bob await backend_data_binder.bind_revocation(user_id=bob.user_id, certifier=alice) # Now recreate another user with bob's human handle now = pendulum.now() bob2_device_id = DeviceID("bob2@dev1") user_certificate = UserCertificateContent( author=alice.device_id, timestamp=now, user_id=bob2_device_id.user_id, public_key=bob.public_key, is_admin=False, human_handle=bob.human_handle, ).dump_and_sign(alice.signing_key) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=bob2_device_id, verify_key=bob.verify_key).dump_and_sign(alice.signing_key) rep = await user_create(alice_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate) assert rep == {"status": "ok"}
async def test_user_create_not_matching_certified_on(alice_backend_sock, alice, mallory): date1 = pendulum.Pendulum(2000, 1, 1) date2 = date1.add(seconds=1) cu = UserCertificateContent( author=alice.device_id, timestamp=date1, user_id=mallory.user_id, public_key=mallory.public_key, is_admin=False, ).dump_and_sign(alice.signing_key) cd = DeviceCertificateContent( author=alice.device_id, timestamp=date2, device_id=mallory.device_id, verify_key=mallory.verify_key, ).dump_and_sign(alice.signing_key) with freeze_time(date1): rep = await user_create(alice_backend_sock, user_certificate=cu, device_certificate=cd) assert rep == { "status": "invalid_data", "reason": "Device and User certifications must have the same timestamp.", }
async def test_user_create_certify_too_old(alice_backend_sock, alice, mallory): too_old = pendulum.Pendulum(2000, 1, 1) now = too_old.add(seconds=INVITATION_VALIDITY + 1) cu = UserCertificateContent( author=alice.device_id, timestamp=too_old, user_id=mallory.user_id, public_key=mallory.public_key, is_admin=False, ).dump_and_sign(alice.signing_key) cd = DeviceCertificateContent( author=alice.device_id, timestamp=too_old, device_id=mallory.device_id, verify_key=mallory.verify_key, ).dump_and_sign(alice.signing_key) with freeze_time(now): rep = await user_create(alice_backend_sock, user_certificate=cu, device_certificate=cd) assert rep == { "status": "invalid_certification", "reason": "Invalid timestamp in certification.", }
async def test_user_create_author_not_admin(backend, bob_backend_sock, bob, mallory): # Unlike alice, bob is not admin now = pendulum.now() user_certificate = UserCertificateContent( author=bob.device_id, timestamp=now, user_id=mallory.user_id, public_key=mallory.public_key, is_admin=False, ).dump_and_sign(bob.signing_key) device_certificate = DeviceCertificateContent( author=bob.device_id, timestamp=now, device_id=mallory.device_id, verify_key=mallory.verify_key, ).dump_and_sign(bob.signing_key) rep = await user_create(bob_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate) assert rep == { "status": "not_allowed", "reason": "User `bob` is not admin" }
def local_device_to_backend_user( device: LocalDevice, certifier: Union[LocalDevice, OrganizationFullData] ) -> Tuple[BackendUser, BackendDevice]: if isinstance(certifier, OrganizationFullData): certifier_id = None certifier_signing_key = certifier.root_signing_key else: certifier_id = certifier.device_id certifier_signing_key = certifier.signing_key now = pendulum.now() user_certificate = UserCertificateContent( author=certifier_id, timestamp=now, user_id=device.user_id, public_key=device.public_key, is_admin=device.is_admin, human_handle=device.human_handle, ).dump_and_sign(certifier_signing_key) device_certificate = DeviceCertificateContent( author=certifier_id, timestamp=now, device_id=device.device_id, verify_key=device.verify_key).dump_and_sign(certifier_signing_key) return new_backend_user_factory( device_id=device.device_id, human_handle=device.human_handle, is_admin=device.is_admin, certifier=certifier_id, user_certificate=user_certificate, device_certificate=device_certificate, )