def _trustchain_data_factory(todo_devices, todo_users):
        data = TrustchainData(coolorg.organization_id, coolorg.root_verify_key)

        def _get_certifier_id_and_key(certifier):
            if not certifier:
                # Certified by root
                certifier_id = None
                certifier_key = coolorg.root_signing_key
            else:
                certifier_ld = data.get_local_device(certifier)
                if not certifier_ld:
                    raise RuntimeError(
                        f"Missing `{certifier}` to sign creation of `{todo_device}`"
                    )
                certifier_id = certifier_ld.device_id
                certifier_key = certifier_ld.signing_key
            return certifier_id, certifier_key

        # First create all the devices
        for todo_device in todo_devices:
            local_device = local_device_factory(todo_device["id"], org=coolorg)
            data.add_local_device(local_device)
            # Generate device certificate
            certifier_id, certifier_key = _get_certifier_id_and_key(
                todo_device.get("certifier"))
            created_on = todo_device.get("created_on", now)
            device_certificate = DeviceCertificateContent(
                author=certifier_id,
                timestamp=created_on,
                device_id=local_device.device_id,
                verify_key=local_device.verify_key,
            )
            data.add_device_certif(
                device_certificate,
                device_certificate.dump_and_sign(certifier_key))

        # Now deal with the users
        for todo_user in todo_users:
            local_user = next((u for u in data.local_devices.values()
                               if str(u.user_id) == todo_user["id"]), None)
            if not local_user:
                raise RuntimeError(
                    f"Missing device for user `{todo_user['id']}`")
            # Generate user certificate
            certifier_id, certifier_key = _get_certifier_id_and_key(
                todo_user.get("certifier"))
            created_on = todo_user.get("created_on", now)
            user_certif = UserCertificateContent(
                author=certifier_id,
                timestamp=created_on,
                user_id=local_user.user_id,
                public_key=local_user.public_key,
                is_admin=todo_user.get("is_admin", False),
            )
            data.add_user_certif(user_certif,
                                 user_certif.dump_and_sign(certifier_key))
            # Generate user revocation certificate if needed
            revoker = todo_user.get("revoker", None)
            if revoker:
                revoked_on = todo_user.get("revoked_on", now)
                revoker_ld = data.get_local_device(revoker)
                if not revoker_ld:
                    raise RuntimeError(
                        f"Missing `{revoker}` to sign revocation of `{todo_user['id']}`"
                    )
                revoked_user_certificate = RevokedUserCertificateContent(
                    author=revoker_ld.device_id,
                    timestamp=revoked_on,
                    user_id=local_user.user_id)
                data.add_revoked_user_certif(
                    revoked_user_certificate,
                    revoked_user_certificate.dump_and_sign(
                        revoker_ld.signing_key),
                )

        return data
Beispiel #2
0
    def load_trustchain(
        self,
        users: List[bytes] = (),
        revoked_users: List[bytes] = (),
        devices: List[bytes] = (),
        now: Pendulum = None,
    ) -> Tuple[List[UserCertificateContent],
               List[RevokedUserCertificateContent],
               List[DeviceCertificateContent], ]:
        now = now or pendulum_now()

        users_states = {}
        devices_states = {}
        revoked_users_states = {}

        # Deserialize the certificates and filter the ones we already have in cache
        try:
            for certif in devices:
                unverified_device = DeviceCertificateContent.unsecure_load(
                    certif)
                verified_device = self.get_device(unverified_device.device_id,
                                                  now)
                if verified_device:
                    devices_states[verified_device.device_id] = CertifState(
                        certif, verified_device, True)
                else:
                    devices_states[unverified_device.device_id] = CertifState(
                        certif, unverified_device, False)

            for certif in users:
                unverified_user = UserCertificateContent.unsecure_load(certif)
                verified_user = self.get_user(unverified_user.user_id, now)
                if verified_user:
                    users_states[verified_user.user_id] = CertifState(
                        certif, verified_user, True)
                else:
                    users_states[unverified_user.user_id] = CertifState(
                        certif, unverified_user, False)

            for certif in revoked_users:
                unverified_revoked_user = RevokedUserCertificateContent.unsecure_load(
                    certif)
                verified_revoked_user = self.get_revoked_user(
                    unverified_revoked_user.user_id, now)
                if verified_revoked_user:
                    revoked_users_states[
                        verified_revoked_user.user_id] = CertifState(
                            certif, verified_revoked_user, True)
                else:
                    revoked_users_states[
                        unverified_revoked_user.user_id] = CertifState(
                            certif, unverified_revoked_user, False)

        except DataError as exc:
            raise TrustchainError(f"Invalid certificate: {exc}") from exc

        def _get_eventually_verified_user(user_id):
            try:
                return users_states[user_id].content
            except KeyError:
                return None

        def _get_eventually_verified_revoked_user(user_id):
            try:
                return revoked_users_states[user_id].content
            except KeyError:
                return None

        def _verify_created_by_root(certif, certif_cls, sign_chain):
            try:
                return certif_cls.verify_and_load(
                    certif,
                    author_verify_key=self.root_verify_key,
                    expected_author=None)

            except DataError as exc:
                path = _build_signature_path(*sign_chain, "<Root Key>")
                raise TrustchainError(
                    f"{path}: Invalid certificate: {exc}") from exc

        def _verify_created_by_device(certif, certif_cls, author_id,
                                      sign_chain):
            author_device = _recursive_verify_device(author_id, sign_chain)
            try:
                verified = certif_cls.verify_and_load(
                    certif,
                    author_verify_key=author_device.verify_key,
                    expected_author=author_device.device_id,
                )

            except DataError as exc:
                path = _build_signature_path(*sign_chain, author_id)
                raise TrustchainError(
                    f"{path}: Invalid certificate: {exc}") from exc

            # Author is either admin or signing one of it own devices
            verified_user_id = (verified.device_id.user_id if isinstance(
                verified, DeviceCertificateContent) else verified.user_id)
            if author_device.device_id.user_id != verified_user_id:
                author_user = _get_eventually_verified_user(author_id.user_id)
                if not author_user:
                    path = _build_signature_path(*sign_chain, author_id)
                    raise TrustchainError(
                        f"{path}: Missing user certificate for {author_id.user_id}"
                    )
                elif not author_user.is_admin:
                    path = _build_signature_path(*sign_chain, author_id)
                    raise TrustchainError(
                        f"{path}:  Invalid signature given {author_user.user_id} is not admin"
                    )
            # Also make sure author wasn't revoked at creation time
            author_revoked_user = _get_eventually_verified_revoked_user(
                author_id.user_id)
            if author_revoked_user and verified.timestamp > author_revoked_user.timestamp:
                path = _build_signature_path(*sign_chain, author_id)
                raise TrustchainError(
                    f"{path}: Signature ({verified.timestamp}) is posterior "
                    f"to user revocation ({author_revoked_user.timestamp})")

            return verified

        def _recursive_verify_device(device_id, signed_children=()):
            if device_id in signed_children:
                path = _build_signature_path(*signed_children, device_id)
                raise TrustchainError(
                    f"{path}: Invalid signature loop detected")

            try:
                state = devices_states[device_id]
            except KeyError:
                path = _build_signature_path(*signed_children, device_id)
                raise TrustchainError(
                    f"{path}: Missing device certificate for {device_id}")

            author = state.content.author
            if author is None:
                verified_device = _verify_created_by_root(
                    state.certif,
                    DeviceCertificateContent,
                    sign_chain=(*signed_children, device_id))
            else:
                verified_device = _verify_created_by_device(
                    state.certif,
                    DeviceCertificateContent,
                    author,
                    sign_chain=(*signed_children, device_id),
                )
            return verified_device

        def _verify_user(unverified_content, certif):
            author = unverified_content.author
            user_id = unverified_content.user_id
            if author is None:
                verified_user = _verify_created_by_root(
                    certif,
                    UserCertificateContent,
                    sign_chain=(f"{user_id}'s creation", ))
            elif author.user_id == user_id:
                raise TrustchainError(
                    f"{user_id}: Invalid self-signed user certificate")
            else:
                verified_user = _verify_created_by_device(
                    certif,
                    UserCertificateContent,
                    author,
                    sign_chain=(f"{user_id}'s creation", ))
            return verified_user

        def _verify_revoked_user(unverified_content, certif):
            author = unverified_content.author
            user_id = unverified_content.user_id
            if author is None:
                verified_revoked_user = _verify_created_by_root(
                    certif,
                    RevokedUserCertificateContent,
                    sign_chain=(f"{user_id}'s revocation", ))
            elif author.user_id == user_id:
                raise TrustchainError(
                    f"{user_id}: Invalid self-signed user revocation certificate"
                )
            else:
                verified_revoked_user = _verify_created_by_device(
                    certif,
                    RevokedUserCertificateContent,
                    author,
                    sign_chain=(f"{user_id}'s revocation", ),
                )
            return verified_revoked_user

        # Verified what need to be and populate the cache with them

        for certif_state in devices_states.values():
            if not certif_state.verified:
                certif_state.content = _recursive_verify_device(
                    certif_state.content.device_id)
        for certif_state in users_states.values():
            if not certif_state.verified:
                certif_state.content = _verify_user(certif_state.content,
                                                    certif_state.certif)
        for certif_state in revoked_users_states.values():
            if not certif_state.verified:
                certif_state.content = _verify_revoked_user(
                    certif_state.content, certif_state.certif)

        # Finally populate the cache
        for certif_state in devices_states.values():
            if not certif_state.verified:
                self._devices_cache[certif_state.content.device_id] = (
                    now, certif_state.content)
        for certif_state in users_states.values():
            if not certif_state.verified:
                self._users_cache[certif_state.content.user_id] = (
                    now, certif_state.content)
        for certif_state in revoked_users_states.values():
            if not certif_state.verified:
                self._revoked_users_cache[certif_state.content.user_id] = (
                    now,
                    certif_state.content,
                )

        return (
            [state.content for state in users_states.values()],
            [state.content for state in revoked_users_states.values()],
            [state.content for state in devices_states.values()],
        )
Beispiel #3
0
    async def api_organization_bootstrap(self, client_ctx, msg):
        msg = organization_bootstrap_serializer.req_load(msg)
        root_verify_key = msg["root_verify_key"]

        try:
            u_data = UserCertificateContent.verify_and_load(
                msg["user_certificate"],
                author_verify_key=root_verify_key,
                expected_author=None)
            d_data = DeviceCertificateContent.verify_and_load(
                msg["device_certificate"],
                author_verify_key=root_verify_key,
                expected_author=None)

        except DataError as exc:
            return {
                "status": "invalid_certification",
                "reason": f"Invalid certification data ({exc}).",
            }

        if u_data.timestamp != d_data.timestamp:
            return {
                "status":
                "invalid_data",
                "reason":
                "Device and user certificates must have the same timestamp.",
            }

        if u_data.user_id != d_data.device_id.user_id:
            return {
                "status": "invalid_data",
                "reason": "Device and user must have the same user ID.",
            }

        now = pendulum.now()
        if not timestamps_in_the_ballpark(u_data.timestamp, now):
            return {
                "status": "invalid_certification",
                "reason": f"Invalid timestamp in certification.",
            }

        user, first_device = new_user_factory(
            device_id=d_data.device_id,
            human_handle=u_data.human_handle,
            is_admin=True,
            certifier=None,
            user_certificate=msg["user_certificate"],
            device_certificate=msg["device_certificate"],
        )
        try:
            await self.bootstrap(
                client_ctx.organization_id,
                user,
                first_device,
                msg["bootstrap_token"],
                root_verify_key,
            )

        except OrganizationAlreadyBootstrappedError:
            return {"status": "already_bootstrapped"}

        except (OrganizationNotFoundError,
                OrganizationInvalidBootstrapTokenError):
            return {"status": "not_found"}

        # Note: we let OrganizationFirstUserCreationError bobbles up given
        # it should not occurs under normal circumstances

        return organization_bootstrap_serializer.rep_dump({"status": "ok"})
Beispiel #4
0
    async def api_organization_bootstrap(self, client_ctx, msg):
        msg = apiv1_organization_bootstrap_serializer.req_load(msg)
        bootstrap_token = msg["bootstrap_token"]
        root_verify_key = msg["root_verify_key"]

        try:
            u_data = UserCertificateContent.verify_and_load(
                msg["user_certificate"],
                author_verify_key=root_verify_key,
                expected_author=None)
            d_data = DeviceCertificateContent.verify_and_load(
                msg["device_certificate"],
                author_verify_key=root_verify_key,
                expected_author=None)

            ru_data = rd_data = None
            if "redacted_user_certificate" in msg:
                ru_data = UserCertificateContent.verify_and_load(
                    msg["redacted_user_certificate"],
                    author_verify_key=root_verify_key,
                    expected_author=None,
                )
            if "redacted_device_certificate" in msg:
                rd_data = DeviceCertificateContent.verify_and_load(
                    msg["redacted_device_certificate"],
                    author_verify_key=root_verify_key,
                    expected_author=None,
                )

        except DataError as exc:
            return {
                "status": "invalid_certification",
                "reason": f"Invalid certification data ({exc}).",
            }
        if u_data.profile != UserProfile.ADMIN:
            return {
                "status": "invalid_data",
                "reason": "Bootstrapping user must have admin profile.",
            }

        if u_data.timestamp != d_data.timestamp:
            return {
                "status":
                "invalid_data",
                "reason":
                "Device and user certificates must have the same timestamp.",
            }

        if u_data.user_id != d_data.device_id.user_id:
            return {
                "status": "invalid_data",
                "reason": "Device and user must have the same user ID.",
            }

        now = pendulum.now()
        if not timestamps_in_the_ballpark(u_data.timestamp, now):
            return {
                "status": "invalid_certification",
                "reason": f"Invalid timestamp in certification.",
            }

        if ru_data:
            if ru_data.evolve(human_handle=u_data.human_handle) != u_data:
                return {
                    "status":
                    "invalid_data",
                    "reason":
                    "Redacted User certificate differs from User certificate.",
                }
            if ru_data.human_handle:
                return {
                    "status":
                    "invalid_data",
                    "reason":
                    "Redacted User certificate must not contain a human_handle field.",
                }

        if rd_data:
            if rd_data.evolve(device_label=d_data.device_label) != d_data:
                return {
                    "status":
                    "invalid_data",
                    "reason":
                    "Redacted Device certificate differs from Device certificate.",
                }
            if rd_data.device_label:
                return {
                    "status":
                    "invalid_data",
                    "reason":
                    "Redacted Device certificate must not contain a device_label field.",
                }

        if (rd_data and not ru_data) or (ru_data and not rd_data):
            return {
                "status":
                "invalid_data",
                "reason":
                "Redacted user&device certificate muste be provided together",
            }

        user = User(
            user_id=u_data.user_id,
            human_handle=u_data.human_handle,
            profile=u_data.profile,
            user_certificate=msg["user_certificate"],
            redacted_user_certificate=msg.get("redacted_user_certificate",
                                              msg["user_certificate"]),
            user_certifier=u_data.author,
            created_on=u_data.timestamp,
        )
        first_device = Device(
            device_id=d_data.device_id,
            device_label=d_data.device_label,
            device_certificate=msg["device_certificate"],
            redacted_device_certificate=msg.get("redacted_device_certificate",
                                                msg["device_certificate"]),
            device_certifier=d_data.author,
            created_on=d_data.timestamp,
        )
        try:
            await self.bootstrap(client_ctx.organization_id, user,
                                 first_device, bootstrap_token,
                                 root_verify_key)

        except OrganizationAlreadyBootstrappedError:
            return {"status": "already_bootstrapped"}

        except (OrganizationNotFoundError,
                OrganizationInvalidBootstrapTokenError):
            return {"status": "not_found"}

        # Note: we let OrganizationFirstUserCreationError bobbles up given
        # it should not occurs under normal circumstances

        # Finally notify webhook
        await self.webhooks.on_organization_bootstrap(
            organization_id=client_ctx.organization_id,
            device_id=first_device.device_id,
            device_label=first_device.device_label,
            human_email=user.human_handle.email if user.human_handle else None,
            human_label=user.human_handle.label if user.human_handle else None,
        )

        return apiv1_organization_bootstrap_serializer.rep_dump(
            {"status": "ok"})
Beispiel #5
0
async def test_organization_bootstrap_bad_data(
    apiv1_backend_sock_factory, organization_factory, local_device_factory, backend, coolorg
):
    neworg = organization_factory("NewOrg")
    newalice = local_device_factory("alice@dev1", neworg)
    await backend.organization.create(
        id=neworg.organization_id, bootstrap_token=neworg.bootstrap_token
    )

    bad_organization_id = coolorg.organization_id
    good_organization_id = neworg.organization_id

    root_signing_key = neworg.root_signing_key
    bad_root_signing_key = coolorg.root_signing_key

    good_bootstrap_token = neworg.bootstrap_token
    bad_bootstrap_token = coolorg.bootstrap_token

    good_rvk = neworg.root_verify_key
    bad_rvk = coolorg.root_verify_key

    good_device_id = newalice.device_id

    good_user_id = newalice.user_id
    bad_user_id = UserID("dummy")

    public_key = newalice.public_key
    verify_key = newalice.verify_key

    now = pendulum.now()
    bad_now = now.subtract(seconds=1)

    good_cu = UserCertificateContent(
        author=None,
        timestamp=now,
        user_id=good_user_id,
        public_key=public_key,
        profile=UserProfile.ADMIN,
        human_handle=newalice.human_handle,
    )
    good_redacted_cu = good_cu.evolve(human_handle=None)
    good_cd = DeviceCertificateContent(
        author=None,
        timestamp=now,
        device_id=good_device_id,
        device_label=newalice.device_label,
        verify_key=verify_key,
    )
    good_redacted_cd = good_cd.evolve(device_label=None)
    bad_now_cu = good_cu.evolve(timestamp=bad_now)
    bad_now_cd = good_cd.evolve(timestamp=bad_now)
    bad_now_redacted_cu = good_redacted_cu.evolve(timestamp=bad_now)
    bad_now_redacted_cd = good_redacted_cd.evolve(timestamp=bad_now)
    bad_id_cu = good_cu.evolve(user_id=bad_user_id)
    bad_not_admin_cu = good_cu.evolve(profile=UserProfile.STANDARD)

    bad_key_cu = good_cu.dump_and_sign(bad_root_signing_key)
    bad_key_cd = good_cd.dump_and_sign(bad_root_signing_key)

    good_cu = good_cu.dump_and_sign(root_signing_key)
    good_redacted_cu = good_redacted_cu.dump_and_sign(root_signing_key)
    good_cd = good_cd.dump_and_sign(root_signing_key)
    good_redacted_cd = good_redacted_cd.dump_and_sign(root_signing_key)
    bad_now_cu = bad_now_cu.dump_and_sign(root_signing_key)
    bad_now_cd = bad_now_cd.dump_and_sign(root_signing_key)
    bad_now_redacted_cu = bad_now_redacted_cu.dump_and_sign(root_signing_key)
    bad_now_redacted_cd = bad_now_redacted_cd.dump_and_sign(root_signing_key)
    bad_id_cu = bad_id_cu.dump_and_sign(root_signing_key)
    bad_not_admin_cu = bad_not_admin_cu.dump_and_sign(root_signing_key)

    for i, (status, organization_id, *params) in enumerate(
        [
            ("not_found", good_organization_id, bad_bootstrap_token, good_cu, good_cd, good_rvk),
            (
                "already_bootstrapped",
                bad_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                good_rvk,
            ),
            (
                "invalid_certification",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                bad_rvk,
            ),
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                bad_now_cu,
                good_cd,
                good_rvk,
            ),
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                bad_id_cu,
                good_cd,
                good_rvk,
            ),
            (
                "invalid_certification",
                good_organization_id,
                good_bootstrap_token,
                bad_key_cu,
                good_cd,
                good_rvk,
            ),
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                bad_now_cd,
                good_rvk,
            ),
            (
                "invalid_certification",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                bad_key_cd,
                good_rvk,
            ),
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                bad_not_admin_cu,
                good_cd,
                good_rvk,
            ),
            # Tests with redacted certificates
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                good_rvk,
                good_cu,  # Not redacted !
                good_redacted_cd,
            ),
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                good_rvk,
                good_redacted_cu,
                good_cd,  # Not redacted !
            ),
            (
                "bad_message",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                good_rvk,
                None,  # None not allowed
                good_redacted_cd,
            ),
            (
                "bad_message",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                good_rvk,
                good_redacted_cu,
                None,  # None not allowed
            ),
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                good_rvk,
                bad_now_redacted_cu,
                good_redacted_cd,
            ),
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                good_rvk,
                good_redacted_cu,
                bad_now_redacted_cd,
            ),
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                good_rvk,
                good_redacted_cu,
                _missing,  # Must proved redacted_device if redacted user is present
            ),
            (
                "invalid_data",
                good_organization_id,
                good_bootstrap_token,
                good_cu,
                good_cd,
                good_rvk,
                _missing,  # Must proved redacted_device if redacted user is present
                good_redacted_cd,
            ),
        ]
    ):
        print(f"sub test {i}")
        async with apiv1_backend_sock_factory(backend, organization_id) as sock:
            rep = await organization_bootstrap(sock, *params)
        assert rep["status"] == status

    # Finally cheap test to make sure our "good" data were really good
    async with apiv1_backend_sock_factory(backend, good_organization_id) as sock:
        rep = await organization_bootstrap(
            sock,
            good_bootstrap_token,
            good_cu,
            good_cd,
            good_rvk,
            good_redacted_cu,
            good_redacted_cd,
        )
    assert rep["status"] == "ok"
Beispiel #6
0
async def test_user_create_ok(
    backend, backend_sock_factory, alice_backend_sock, alice, mallory, profile, with_labels
):
    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=profile,
    )
    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=mallory.device_label,
        verify_key=mallory.verify_key,
    )
    redacted_device_certificate = device_certificate.evolve(device_label=None)
    if not with_labels:
        user_certificate = redacted_user_certificate
        device_certificate = redacted_device_certificate

    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)

    rep = await user_create(
        alice_backend_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"}

    # Make sure mallory can connect now
    async with backend_sock_factory(backend, mallory) as sock:
        rep = await user_get(sock, user_id=mallory.user_id)
        assert rep["status"] == "ok"

    # Check the resulting data in the backend
    backend_user, backend_device = await backend.user.get_user_with_device(
        mallory.organization_id, mallory.device_id
    )
    assert backend_user == User(
        user_id=mallory.user_id,
        human_handle=mallory.human_handle if with_labels else None,
        profile=profile,
        user_certificate=user_certificate,
        redacted_user_certificate=redacted_user_certificate,
        user_certifier=alice.device_id,
        created_on=now,
    )
    assert backend_device == Device(
        device_id=mallory.device_id,
        device_label=mallory.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,
    )
Beispiel #7
0
def validate_new_user_certificates(
    expected_author: DeviceID,
    author_verify_key: VerifyKey,
    user_certificate: bytes,
    device_certificate: bytes,
    redacted_user_certificate: bytes,
    redacted_device_certificate: bytes,
) -> Tuple[User, Device]:
    """
    Raises:
        CertificateValidationError
        UserInvalidCertificationError
    """
    try:
        d_data = DeviceCertificateContent.verify_and_load(
            device_certificate,
            author_verify_key=author_verify_key,
            expected_author=expected_author)
        u_data = UserCertificateContent.verify_and_load(
            user_certificate,
            author_verify_key=author_verify_key,
            expected_author=expected_author)
        ru_data = UserCertificateContent.verify_and_load(
            redacted_user_certificate,
            author_verify_key=author_verify_key,
            expected_author=expected_author,
        )
        rd_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 u_data.timestamp != d_data.timestamp:
        raise CertificateValidationError(
            "invalid_data",
            "Device and User certificates must have the same timestamp.")

    now = pendulum.now()
    if not timestamps_in_the_ballpark(u_data.timestamp, now):
        raise CertificateValidationError("invalid_certification",
                                         "Invalid timestamp in certificate.")

    if u_data.user_id != d_data.device_id.user_id:
        raise CertificateValidationError(
            "invalid_data", "Device and User must have the same user ID.")

    if ru_data.evolve(human_handle=u_data.human_handle) != u_data:
        raise CertificateValidationError(
            "invalid_data",
            "Redacted User certificate differs from User certificate.")

    if ru_data.human_handle:
        raise CertificateValidationError(
            "invalid_data",
            "Redacted User certificate must not contain a human_handle field.")

    if rd_data.evolve(device_label=d_data.device_label) != d_data:
        raise CertificateValidationError(
            "invalid_data",
            "Redacted Device certificate differs from Device certificate.")

    if rd_data.device_label:
        raise CertificateValidationError(
            "invalid_data",
            "Redacted Device certificate must not contain a device_label field."
        )

    user = User(
        user_id=u_data.user_id,
        human_handle=u_data.human_handle,
        profile=u_data.profile,
        user_certificate=user_certificate,
        redacted_user_certificate=redacted_user_certificate,
        user_certifier=u_data.author,
        created_on=u_data.timestamp,
    )
    first_device = Device(
        device_id=d_data.device_id,
        device_label=d_data.device_label,
        device_certificate=device_certificate,
        redacted_device_certificate=redacted_device_certificate,
        device_certifier=d_data.author,
        created_on=d_data.timestamp,
    )

    return user, first_device
Beispiel #8
0
async def test_user_claim_invalid_returned_certificates(
        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"user{device_count}@dev")
        token = generate_invitation_token()
        exception_occured = False

        async def _from_alice():
            await invite_and_create_user(alice,
                                         new_device_id.user_id,
                                         token=token,
                                         is_admin=False)

        async def _from_new_device():
            nonlocal exception_occured
            with pytest.raises(InviteClaimCryptoError):
                await claim_user(alice.organization_addr,
                                 new_device_id,
                                 token=token)
            exception_occured = True

        await _invite_and_claim(running_backend, _from_alice, _from_new_device)
        assert exception_occured

    # Invalid data

    hooks["user_certificate"] = lambda x: b"dummy"
    hooks["device_certificate"] = None
    await _do_test()

    hooks["user_certificate"] = None
    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["user_certificate"] = lambda raw: bob_sign(
        UserCertificateContent.unsecure_load(raw))
    hooks["device_certificate"] = None
    await _do_test()

    hooks["user_certificate"] = None
    hooks["device_certificate"] = lambda raw: bob_sign(
        DeviceCertificateContent.unsecure_load(raw))
    await _do_test()

    # Certificate info doesn't correspond to created user

    hooks["user_certificate"] = (
        lambda raw: UserCertificateContent.unsecure_load(raw).evolve(
            user_id=bob.user_id).dump_and_sign(author_signkey=alice.signing_key
                                               ))
    hooks["device_certificate"] = None
    await _do_test()

    hooks["user_certificate"] = None
    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()
Beispiel #9
0
    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,
                verify_key=self._verify_key,
                device_label=device_label,
            )
            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)
        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)
Beispiel #10
0
    async def api_user_create(self, client_ctx, msg):
        if not client_ctx.is_admin:
            return {
                "status": "not_allowed",
                "reason": f"User `{client_ctx.device_id.user_id}` is not admin",
            }

        msg = user_create_serializer.req_load(msg)

        try:
            d_data = DeviceCertificateContent.verify_and_load(
                msg["device_certificate"],
                author_verify_key=client_ctx.verify_key,
                expected_author=client_ctx.device_id,
            )
            u_data = UserCertificateContent.verify_and_load(
                msg["user_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 u_data.timestamp != d_data.timestamp:
            return {
                "status": "invalid_data",
                "reason": "Device and User certifications must have the same timestamp.",
            }

        now = pendulum.now()
        if not timestamps_in_the_ballpark(u_data.timestamp, now):
            return {
                "status": "invalid_certification",
                "reason": f"Invalid timestamp in certification.",
            }

        if u_data.user_id != d_data.device_id.user_id:
            return {
                "status": "invalid_data",
                "reason": "Device and User must have the same user ID.",
            }

        try:
            user = User(
                user_id=u_data.user_id,
                human_handle=u_data.human_handle,
                is_admin=u_data.is_admin,
                user_certificate=msg["user_certificate"],
                user_certifier=u_data.author,
                created_on=u_data.timestamp,
            )
            first_devices = Device(
                device_id=d_data.device_id,
                device_certificate=msg["device_certificate"],
                device_certifier=d_data.author,
                created_on=d_data.timestamp,
            )
            await self.create_user(client_ctx.organization_id, user, first_devices)

        except UserAlreadyExistsError as exc:
            return {"status": "already_exists", "reason": str(exc)}

        return user_create_serializer.rep_dump({"status": "ok"})
async def _do_bootstrap_organization(
    config_dir,
    password: str,
    password_check: str,
    user_id: str,
    device_name: str,
    bootstrap_addr: BackendOrganizationBootstrapAddr,
):
    if password != password_check:
        raise JobResultError("password-mismatch")
    if len(password) < 8:
        raise JobResultError("password-size")

    try:
        device_id = DeviceID(f"{user_id}@{device_name}")
    except ValueError as exc:
        raise JobResultError("bad-device_name") from exc

    root_signing_key = SigningKey.generate()
    root_verify_key = root_signing_key.verify_key
    organization_addr = bootstrap_addr.generate_organization_addr(
        root_verify_key)

    try:
        device = generate_new_device(device_id,
                                     organization_addr,
                                     is_admin=True)
        save_device_with_password(config_dir, device, password)

    except LocalDeviceAlreadyExistsError as exc:
        raise JobResultError("user-exists") from exc

    now = pendulum.now()
    user_certificate = UserCertificateContent(
        author=None,
        timestamp=now,
        user_id=device.user_id,
        public_key=device.public_key,
        is_admin=device.is_admin,
    ).dump_and_sign(root_signing_key)
    device_certificate = DeviceCertificateContent(
        author=None,
        timestamp=now,
        device_id=device_id,
        verify_key=device.verify_key).dump_and_sign(root_signing_key)

    try:
        async with backend_anonymous_cmds_factory(bootstrap_addr) as cmds:
            rep = await cmds.organization_bootstrap(
                bootstrap_addr.organization_id,
                bootstrap_addr.token,
                root_verify_key,
                user_certificate,
                device_certificate,
            )

            if rep["status"] == "already_bootstrapped":
                raise JobResultError("already-bootstrapped", info=str(rep))
            elif rep["status"] == "not_found":
                raise JobResultError("invalid-url", info=str(rep))
            elif rep["status"] != "ok":
                raise JobResultError("refused-by-backend", info=str(rep))
        return device, password
    except BackendConnectionRefused as exc:
        raise JobResultError("invalid-url", info=str(exc)) from exc

    except BackendNotAvailable as exc:
        raise JobResultError("backend-offline", info=str(exc)) from exc

    except BackendConnectionError as exc:
        raise JobResultError("refused-by-backend", info=str(exc)) from exc
async def test_user_create_invalid_certificate(alice_backend_sock, alice, bob,
                                               mallory):
    now = pendulum.now()
    good_user_certificate = UserCertificateContent(
        author=alice.device_id,
        timestamp=now,
        user_id=mallory.user_id,
        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,
        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,
        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,
        verify_key=mallory.verify_key,
    ).dump_and_sign(bob.signing_key)

    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(
            alice_backend_sock,
            user_certificate=cu,
            device_certificate=cd,
            redacted_user_certificate=good_user_certificate,
            redacted_device_certificate=good_device_certificate,
        )
        assert rep == {
            "status":
            "invalid_certification",
            "reason":
            "Invalid certification data (Signature was forged or corrupt).",
        }

    # Same thing for the redacted part
    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(
            alice_backend_sock,
            user_certificate=good_user_certificate,
            device_certificate=good_device_certificate,
            redacted_user_certificate=cu,
            redacted_device_certificate=cd,
        )
        assert rep == {
            "status":
            "invalid_certification",
            "reason":
            "Invalid certification data (Signature was forged or corrupt).",
        }
async def test_user_create_bad_redacted_user_certificate(
        alice_backend_sock, alice, mallory):
    now = pendulum.now()
    device_certificate = DeviceCertificateContent(
        author=alice.device_id,
        timestamp=now,
        device_id=mallory.device_id,
        verify_key=mallory.verify_key,
    ).dump_and_sign(alice.signing_key)
    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,
    )
    good_redacted_user_certificate = user_certificate.evolve(human_handle=None)
    user_certificate = user_certificate.dump_and_sign(alice.signing_key)
    for bad_redacted_user_certificate in (
            good_redacted_user_certificate.evolve(timestamp=now.add(
                seconds=1)),
            good_redacted_user_certificate.evolve(user_id=alice.user_id),
            good_redacted_user_certificate.evolve(public_key=alice.public_key),
            good_redacted_user_certificate.evolve(
                profile=UserProfile.OUTSIDER),
    ):
        rep = await user_create(
            alice_backend_sock,
            user_certificate=user_certificate,
            device_certificate=device_certificate,
            redacted_user_certificate=bad_redacted_user_certificate.
            dump_and_sign(alice.signing_key),
            redacted_device_certificate=device_certificate,
        )
        assert rep == {
            "status": "invalid_data",
            "reason":
            "Redacted User certificate differs from User certificate.",
        }

    # Missing redacted certificate is not allowed as well
    rep = await user_create(
        alice_backend_sock,
        user_certificate=user_certificate,
        device_certificate=device_certificate,
        redacted_user_certificate=None,
        redacted_device_certificate=device_certificate,
    )
    assert rep == {
        "status": "bad_message",
        "reason": "Invalid message.",
        "errors": {
            "redacted_user_certificate": ["Missing data for required field."]
        },
    }

    # Finally just make sure good was really good
    rep = await user_create(
        alice_backend_sock,
        user_certificate=user_certificate,
        device_certificate=device_certificate,
        redacted_user_certificate=good_redacted_user_certificate.dump_and_sign(
            alice.signing_key),
        redacted_device_certificate=device_certificate,
    )
    assert rep == {"status": "ok"}
Beispiel #14
0
async def invite_and_create_user(
    device: LocalDevice,
    user_id: UserID,
    token: str,
    is_admin: bool,
    keepalive: Optional[int] = None,
) -> DeviceID:
    """
    Raises:
        InviteClaimError
        InviteClaimBackendOfflineError
        InviteClaimValidationError
        InviteClaimPackingError
        InviteClaimCryptoError
        InviteClaimInvalidTokenError
    """
    try:
        async with backend_authenticated_cmds_factory(
                device.organization_addr,
                device.device_id,
                device.signing_key,
                keepalive=keepalive) as cmds:
            try:
                rep = await cmds.user_invite(user_id)
                if rep["status"] == "timeout":
                    raise InviteClaimTimeoutError()
                elif rep["status"] != "ok":
                    raise InviteClaimError(f"Cannot invite user: {rep}")

                try:
                    claim = UserClaimContent.decrypt_and_load_for(
                        rep["encrypted_claim"],
                        recipient_privkey=device.private_key)

                except DataError as exc:
                    raise InviteClaimCryptoError(
                        f"Cannot decrypt user claim info: {exc}") from exc

                if claim.token != token:
                    raise InviteClaimInvalidTokenError(
                        f"Invalid claim token provided by peer: `{claim.token}`"
                        f" (was expecting `{token}`)")

                device_id = claim.device_id
                now = pendulum.now()
                try:

                    user_certificate = UserCertificateContent(
                        author=device.device_id,
                        timestamp=now,
                        user_id=device_id.user_id,
                        public_key=claim.public_key,
                        is_admin=is_admin,
                    ).dump_and_sign(device.signing_key)
                    device_certificate = DeviceCertificateContent(
                        author=device.device_id,
                        timestamp=now,
                        device_id=device_id,
                        verify_key=claim.verify_key,
                    ).dump_and_sign(device.signing_key)

                except DataError as exc:
                    raise InviteClaimError(
                        f"Cannot generate user&first device certificates: {exc}"
                    ) from exc

            except:
                # Cancel the invitation to prevent the claiming peer from
                # waiting us until timeout
                deadline = trio.current_time() + CANCEL_INVITATION_MAX_WAIT
                with trio.CancelScope(shield=True, deadline=deadline):
                    try:
                        await cmds.user_cancel_invitation(user_id)
                    except BackendConnectionError:
                        pass
                raise

            rep = await cmds.user_create(user_certificate, device_certificate)
            if rep["status"] != "ok":
                raise InviteClaimError(f"Cannot create user: {rep}")

    except BackendNotAvailable as exc:
        raise InviteClaimBackendOfflineError(str(exc)) from exc

    except BackendConnectionError as exc:
        raise InviteClaimError(f"Cannot create user: {exc}") from exc

    return device_id
Beispiel #15
0
async def test_user_create_ok(backend, apiv1_backend_sock_factory, alice,
                              mallory, profile):
    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=profile,
    ).dump_and_sign(alice.signing_key)
    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)

    with backend.event_bus.listen() as spy:
        async with apiv1_backend_sock_factory(backend, alice) as sock:
            rep = await user_create(sock,
                                    user_certificate=user_certificate,
                                    device_certificate=device_certificate)
        assert rep == {"status": "ok"}

        # No guarantees this event occurs before the command's return
        await spy.wait_with_timeout(
            BackendEvent.USER_CREATED,
            {
                "organization_id": alice.organization_id,
                "user_id": mallory.user_id,
                "first_device_id": mallory.device_id,
                "user_certificate": user_certificate,
                "first_device_certificate": device_certificate,
            },
        )

    # Make sure mallory can connect now
    async with apiv1_backend_sock_factory(backend, mallory) as sock:
        rep = await user_get(sock, user_id=mallory.user_id)
        assert rep["status"] == "ok"

    # Check the resulting data in the backend
    backend_user, backend_device = await backend.user.get_user_with_device(
        mallory.organization_id, mallory.device_id)
    assert backend_user == User(
        user_id=mallory.user_id,
        human_handle=None,
        profile=profile,
        user_certificate=user_certificate,
        redacted_user_certificate=user_certificate,
        user_certifier=alice.device_id,
        created_on=now,
    )
    assert backend_device == Device(
        device_id=mallory.device_id,
        device_label=None,
        device_certificate=device_certificate,
        redacted_device_certificate=device_certificate,
        device_certifier=alice.device_id,
        created_on=now,
    )
Beispiel #16
0
async def claim_user(
    organization_addr: BackendOrganizationAddr,
    new_device_id: DeviceID,
    token: str,
    keepalive: Optional[int] = None,
) -> LocalDevice:
    """
    Raises:
        InviteClaimError
        InviteClaimBackendOfflineError
        InviteClaimValidationError
        InviteClaimPackingError
        InviteClaimCryptoError
    """
    new_device = generate_new_device(new_device_id, organization_addr)

    try:
        async with backend_anonymous_cmds_factory(organization_addr,
                                                  keepalive=keepalive) as cmds:
            # 1) Retrieve invitation creator
            try:
                invitation_creator_user, invitation_creator_device = await get_user_invitation_creator(
                    cmds, new_device.root_verify_key, new_device.user_id)

            except RemoteDevicesManagerBackendOfflineError as exc:
                raise InviteClaimBackendOfflineError(str(exc)) from exc

            except RemoteDevicesManagerError as exc:
                raise InviteClaimError(
                    f"Cannot retrieve invitation creator: {exc}") from exc

            # 2) Generate claim info for invitation creator
            try:
                encrypted_claim = UserClaimContent(
                    device_id=new_device_id,
                    token=token,
                    public_key=new_device.public_key,
                    verify_key=new_device.verify_key,
                ).dump_and_encrypt_for(
                    recipient_pubkey=invitation_creator_user.public_key)

            except DataError as exc:
                raise InviteClaimError(
                    f"Cannot generate user claim message: {exc}") from exc

            # 3) Send claim
            rep = await cmds.user_claim(new_device_id.user_id, encrypted_claim)
            if rep["status"] != "ok":
                raise InviteClaimError(f"Cannot claim user: {rep}")

            # 4) Verify user&device certificates and check admin status
            try:
                user = UserCertificateContent.verify_and_load(
                    rep["user_certificate"],
                    author_verify_key=invitation_creator_device.verify_key,
                    expected_author=invitation_creator_device.device_id,
                    expected_user=new_device_id.user_id,
                )

                DeviceCertificateContent.verify_and_load(
                    rep["device_certificate"],
                    author_verify_key=invitation_creator_device.verify_key,
                    expected_author=invitation_creator_device.device_id,
                    expected_device=new_device_id,
                )

                new_device = new_device.evolve(is_admin=user.is_admin)

            except DataError as exc:
                raise InviteClaimCryptoError(str(exc)) from exc

    except BackendNotAvailable as exc:
        raise InviteClaimBackendOfflineError(str(exc)) from exc

    except BackendConnectionError as exc:
        raise InviteClaimError(f"Cannot claim user: {exc}") from exc

    return new_device
Beispiel #17
0
 def public_key(self) -> PublicKey:
     return UserCertificateContent.unsecure_load(
         self.user_certificate).public_key
Beispiel #18
0
def test_unsecure_read_user_certificate_bad_data():
    with pytest.raises(DataError):
        UserCertificateContent.unsecure_load(b"dummy")
Beispiel #19
0
    async def _api_user_create(self, client_ctx, msg):
        try:
            d_data = DeviceCertificateContent.verify_and_load(
                msg["device_certificate"],
                author_verify_key=client_ctx.verify_key,
                expected_author=client_ctx.device_id,
            )
            u_data = UserCertificateContent.verify_and_load(
                msg["user_certificate"],
                author_verify_key=client_ctx.verify_key,
                expected_author=client_ctx.device_id,
            )
            ru_data = UserCertificateContent.verify_and_load(
                msg["redacted_user_certificate"],
                author_verify_key=client_ctx.verify_key,
                expected_author=client_ctx.device_id,
            )
            rd_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 u_data.timestamp != d_data.timestamp:
            return {
                "status":
                "invalid_data",
                "reason":
                "Device and User certificates must have the same timestamp.",
            }

        now = pendulum.now()
        if not timestamps_in_the_ballpark(u_data.timestamp, now):
            return {
                "status": "invalid_certification",
                "reason": f"Invalid timestamp in certificate.",
            }

        if u_data.user_id != d_data.device_id.user_id:
            return {
                "status": "invalid_data",
                "reason": "Device and User must have the same user ID.",
            }

        if ru_data.evolve(human_handle=u_data.human_handle) != u_data:
            return {
                "status":
                "invalid_data",
                "reason":
                "Redacted User certificate differs from User certificate.",
            }
        if ru_data.human_handle:
            return {
                "status":
                "invalid_data",
                "reason":
                "Redacted User certificate must not contain a human_handle field.",
            }

        if rd_data.evolve(device_label=d_data.device_label) != d_data:
            return {
                "status":
                "invalid_data",
                "reason":
                "Redacted Device certificate differs from Device certificate.",
            }
        if rd_data.device_label:
            return {
                "status":
                "invalid_data",
                "reason":
                "Redacted Device certificate must not contain a device_label field.",
            }

        try:
            user = User(
                user_id=u_data.user_id,
                human_handle=u_data.human_handle,
                profile=u_data.profile,
                user_certificate=msg["user_certificate"],
                redacted_user_certificate=msg["redacted_user_certificate"]
                or msg["user_certificate"],
                user_certifier=u_data.author,
                created_on=u_data.timestamp,
            )
            first_device = Device(
                device_id=d_data.device_id,
                device_label=d_data.device_label,
                device_certificate=msg["device_certificate"],
                redacted_device_certificate=msg["redacted_device_certificate"]
                or msg["device_certificate"],
                device_certifier=d_data.author,
                created_on=d_data.timestamp,
            )
            await self.create_user(client_ctx.organization_id, user,
                                   first_device)

        except UserAlreadyExistsError as exc:
            return {"status": "already_exists", "reason": str(exc)}

        return {"status": "ok"}
Beispiel #20
0
async def test_organization_bootstrap_bad_data(
    backend_data_binder,
    backend_sock_factory,
    organization_factory,
    local_device_factory,
    backend,
    coolorg,
    alice,
):
    neworg = organization_factory("NewOrg")
    newalice = local_device_factory("alice@dev1", neworg)
    await backend_data_binder.bind_organization(neworg)

    bad_organization_id = coolorg.organization_id
    good_organization_id = neworg.organization_id

    root_signing_key = neworg.root_signing_key
    bad_root_signing_key = coolorg.root_signing_key

    good_bootstrap_token = neworg.bootstrap_token
    bad_bootstrap_token = coolorg.bootstrap_token

    good_rvk = neworg.root_verify_key
    bad_rvk = coolorg.root_verify_key

    good_device_id = newalice.device_id

    good_user_id = newalice.user_id
    bad_user_id = UserID("dummy")

    public_key = newalice.public_key
    verify_key = newalice.verify_key

    now = pendulum.now()
    good_cu = UserCertificateContent(
        author=None,
        timestamp=now,
        user_id=good_user_id,
        public_key=public_key,
        is_admin=False).dump_and_sign(root_signing_key)
    good_cd = DeviceCertificateContent(
        author=None,
        timestamp=now,
        device_id=good_device_id,
        verify_key=verify_key).dump_and_sign(root_signing_key)

    bad_now = now - pendulum.interval(seconds=1)
    bad_now_cu = UserCertificateContent(
        author=None,
        timestamp=bad_now,
        user_id=good_user_id,
        public_key=public_key,
        is_admin=False).dump_and_sign(root_signing_key)
    bad_now_cd = DeviceCertificateContent(
        author=None,
        timestamp=bad_now,
        device_id=good_device_id,
        verify_key=verify_key).dump_and_sign(root_signing_key)
    bad_id_cu = UserCertificateContent(
        author=None,
        timestamp=now,
        user_id=bad_user_id,
        public_key=public_key,
        is_admin=False).dump_and_sign(root_signing_key)
    bad_key_cu = UserCertificateContent(
        author=None,
        timestamp=now,
        user_id=good_user_id,
        public_key=public_key,
        is_admin=False).dump_and_sign(bad_root_signing_key)
    bad_key_cd = DeviceCertificateContent(
        author=None,
        timestamp=now,
        device_id=good_device_id,
        verify_key=verify_key).dump_and_sign(bad_root_signing_key)

    for i, (status, organization_id, *params) in enumerate([
        ("not_found", good_organization_id, bad_bootstrap_token, good_cu,
         good_cd, good_rvk),
        (
            "already_bootstrapped",
            bad_organization_id,
            bad_bootstrap_token,
            bad_key_cu,
            bad_key_cd,
            bad_rvk,
        ),
        (
            "invalid_certification",
            good_organization_id,
            good_bootstrap_token,
            good_cu,
            good_cd,
            bad_rvk,
        ),
        (
            "invalid_data",
            good_organization_id,
            good_bootstrap_token,
            bad_now_cu,
            good_cd,
            good_rvk,
        ),
        (
            "invalid_data",
            good_organization_id,
            good_bootstrap_token,
            bad_id_cu,
            good_cd,
            good_rvk,
        ),
        (
            "invalid_certification",
            good_organization_id,
            good_bootstrap_token,
            bad_key_cu,
            good_cd,
            good_rvk,
        ),
        (
            "invalid_data",
            good_organization_id,
            good_bootstrap_token,
            good_cu,
            bad_now_cd,
            good_rvk,
        ),
        (
            "invalid_certification",
            good_organization_id,
            good_bootstrap_token,
            good_cu,
            bad_key_cd,
            good_rvk,
        ),
    ]):
        async with backend_sock_factory(backend, organization_id) as sock:
            rep = await organization_bootstrap(sock, *params)
        assert rep["status"] == status

    # Finally cheap test to make sure our "good" data were really good
    async with backend_sock_factory(backend, good_organization_id) as sock:
        rep = await organization_bootstrap(sock, good_bootstrap_token, good_cu,
                                           good_cd, good_rvk)
    assert rep["status"] == "ok"
Beispiel #21
0
async def initialize_test_organization(
    backend_address,
    organization_id,
    alice_device_id,
    bob_device_id,
    other_device_name,
    alice_workspace,
    bob_workspace,
    password,
    administration_token,
    force,
):

    configure_logging("WARNING")

    config_dir = get_default_config_dir(os.environ)
    alice_slugid = f"{organization_id}:{alice_device_id}"
    bob_slugid = f"{organization_id}:{bob_device_id}"

    # Create organization
    async with backend_administration_cmds_factory(
        backend_address, administration_token) as cmds:

        rep = await cmds.organization_create(organization_id)
        assert rep["status"] == "ok"
        bootstrap_token = rep["bootstrap_token"]

        organization_bootstrap_addr = BackendOrganizationBootstrapAddr.build(
            backend_address, organization_id, bootstrap_token)

    # Bootstrap organization and Alice user

    async with backend_anonymous_cmds_factory(
            organization_bootstrap_addr) as cmds:
        root_signing_key = SigningKey.generate()
        root_verify_key = root_signing_key.verify_key
        organization_addr = organization_bootstrap_addr.generate_organization_addr(
            root_verify_key)

        alice_device = generate_new_device(alice_device_id, organization_addr,
                                           True)

        save_device_with_password(config_dir,
                                  alice_device,
                                  password,
                                  force=force)

        now = pendulum.now()
        user_certificate = UserCertificateContent(
            author=None,
            timestamp=now,
            user_id=alice_device.user_id,
            public_key=alice_device.public_key,
            is_admin=True,
        ).dump_and_sign(author_signkey=root_signing_key)
        device_certificate = DeviceCertificateContent(
            author=None,
            timestamp=now,
            device_id=alice_device.device_id,
            verify_key=alice_device.verify_key,
        ).dump_and_sign(author_signkey=root_signing_key)

        rep = await cmds.organization_bootstrap(
            organization_bootstrap_addr.organization_id,
            organization_bootstrap_addr.token,
            root_verify_key,
            user_certificate,
            device_certificate,
        )
        assert rep["status"] == "ok"

    # Create a workspace for Alice

    config = load_config(config_dir, debug="DEBUG" in os.environ)
    async with logged_core_factory(config, alice_device) as core:
        alice_ws_id = await core.user_fs.workspace_create(f"{alice_workspace}")
        await core.user_fs.sync()

    # Register a new device for Alice

    token = generate_invitation_token()
    other_alice_device_id = DeviceID(
        f"{alice_device.user_id}@{other_device_name}")
    other_alice_slugid = f"{organization_id}:{other_alice_device_id}"

    async def invite_task():
        await invite_and_create_device(alice_device, other_device_name, token)

    other_alice_device = None

    async def claim_task():
        nonlocal other_alice_device
        other_alice_device = await retry_claim(claim_device,
                                               alice_device.organization_addr,
                                               other_alice_device_id, token)
        save_device_with_password(config_dir,
                                  other_alice_device,
                                  password,
                                  force=force)

    async with trio.open_service_nursery() as nursery:
        nursery.start_soon(invite_task)
        nursery.start_soon(claim_task)

    # Invite Bob in

    token = generate_invitation_token()
    bob_device = None

    async def invite_task():
        await invite_and_create_user(alice_device,
                                     bob_device_id.user_id,
                                     token,
                                     is_admin=False)

    async def claim_task():
        nonlocal bob_device
        bob_device = await retry_claim(claim_user,
                                       alice_device.organization_addr,
                                       bob_device_id, token)
        save_device_with_password(config_dir,
                                  bob_device,
                                  password,
                                  force=force)

    async with trio.open_service_nursery() as nursery:
        nursery.start_soon(invite_task)
        nursery.start_soon(claim_task)

    # Create bob workspace and share with Alice

    async with logged_core_factory(config, bob_device) as core:
        bob_ws_id = await core.user_fs.workspace_create(f"{bob_workspace}")
        await core.user_fs.workspace_share(bob_ws_id, alice_device_id.user_id,
                                           WorkspaceRole.MANAGER)

    # Share Alice workspace with bob

    async with logged_core_factory(config, alice_device) as core:
        await core.user_fs.workspace_share(alice_ws_id, bob_device_id.user_id,
                                           WorkspaceRole.MANAGER)

    # Synchronize every device
    for device in (alice_device, other_alice_device, bob_device):
        async with logged_core_factory(config, device) as core:
            await core.user_fs.process_last_messages()
            await core.user_fs.sync()

    return alice_slugid, other_alice_slugid, bob_slugid