async def _alice_nd_claim():
        async with apiv1_backend_anonymous_cmds_factory(alice.organization_addr) as cmds:
            ret = await cmds.device_get_invitation_creator(nd_id)
            assert ret["status"] == "ok"
            assert ret["trustchain"] == {"devices": [], "revoked_users": [], "users": []}
            creator = UserCertificateContent.unsecure_load(ret["user_certificate"])
            creator_device = DeviceCertificateContent.unsecure_load(ret["device_certificate"])
            assert creator_device.device_id.user_id == creator.user_id

            answer_private_key = PrivateKey.generate()
            encrypted_claim = APIV1_DeviceClaimContent(
                token=token,
                device_id=nd_id,
                verify_key=nd_signing_key.verify_key,
                answer_public_key=answer_private_key.public_key,
            ).dump_and_encrypt_for(recipient_pubkey=creator.public_key)
            with trio.fail_after(1):
                ret = await cmds.device_claim(nd_id, encrypted_claim)
                assert ret["status"] == "ok"

            assert ret["device_certificate"] == device_certificate
            answer = APIV1_DeviceClaimAnswerContent.decrypt_and_load_for(
                ret["encrypted_answer"], recipient_privkey=answer_private_key
            )
            assert answer == APIV1_DeviceClaimAnswerContent(
                private_key=alice.private_key,
                user_manifest_id=alice.user_manifest_id,
                user_manifest_key=alice.user_manifest_key,
            )
示例#2
0
def test_build_user_certificate(alice, bob, mallory):
    now = pendulum_now()
    certif = UserCertificateContent(
        author=alice.device_id,
        timestamp=now,
        user_id=bob.user_id,
        human_handle=bob.human_handle,
        public_key=bob.public_key,
        profile=UserProfile.ADMIN,
    ).dump_and_sign(alice.signing_key)
    assert isinstance(certif, bytes)

    unsecure = UserCertificateContent.unsecure_load(certif)
    assert isinstance(unsecure, UserCertificateContent)
    assert unsecure.user_id == bob.user_id
    assert unsecure.public_key == bob.public_key
    assert unsecure.timestamp == now
    assert unsecure.author == alice.device_id
    assert unsecure.profile == UserProfile.ADMIN

    verified = UserCertificateContent.verify_and_load(
        certif,
        author_verify_key=alice.verify_key,
        expected_author=alice.device_id)
    assert verified == unsecure

    with pytest.raises(DataError) as exc:
        UserCertificateContent.verify_and_load(
            certif,
            author_verify_key=alice.verify_key,
            expected_author=mallory.device_id)
    assert str(exc.value
               ) == "Invalid author: expected `mallory@dev1`, got `alice@dev1`"

    with pytest.raises(DataError) as exc:
        UserCertificateContent.verify_and_load(
            certif,
            author_verify_key=mallory.verify_key,
            expected_author=alice.device_id)
    assert str(exc.value) == "Signature was forged or corrupt"

    with pytest.raises(DataError) as exc:
        UserCertificateContent.verify_and_load(
            certif,
            author_verify_key=alice.verify_key,
            expected_author=alice.device_id,
            expected_user=mallory.user_id,
        )
    assert str(exc.value) == "Invalid user ID: expected `mallory`, got `bob`"
示例#3
0
    async def _mallory_claim():
        async with backend_anonymous_cmds_factory(mallory.organization_addr) as cmds:
            rep = await cmds.user_get_invitation_creator(mallory.user_id)
            assert rep["trustchain"] == {"devices": [], "revoked_users": [], "users": []}
            creator = UserCertificateContent.unsecure_load(rep["user_certificate"])
            creator_device = DeviceCertificateContent.unsecure_load(rep["device_certificate"])
            assert creator_device.device_id.user_id == creator.user_id

            encrypted_claim = UserClaimContent(
                device_id=mallory.device_id,
                token=token,
                public_key=mallory.public_key,
                verify_key=mallory.verify_key,
            ).dump_and_encrypt_for(recipient_pubkey=creator.public_key)
            with trio.fail_after(1):
                rep = await cmds.user_claim(mallory.user_id, encrypted_claim)
                assert rep["status"] == "ok"
示例#4
0
    def load_trustchain(
        self,
        users: Sequence[bytes] = (),
        revoked_users: Sequence[bytes] = (),
        devices: Sequence[bytes] = (),
        now: DateTime = 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 author_user.profile != UserProfile.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()],
        )
示例#5
0
 def public_key(self) -> PublicKey:
     return UserCertificateContent.unsecure_load(self.user_certificate).public_key
示例#6
0
def test_unsecure_read_user_certificate_bad_data():
    with pytest.raises(DataError):
        UserCertificateContent.unsecure_load(b"dummy")
示例#7
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()