Example #1
0
def test_device_certificate():
    from parsec.api.data.certif import (
        _RsDeviceCertificateContent,
        DeviceCertificateContent,
        _PyDeviceCertificateContent,
    )

    assert DeviceCertificateContent is _RsDeviceCertificateContent

    def _assert_device_certificate_eq(py, rs):
        assert py.author == rs.author
        assert py.timestamp == rs.timestamp
        assert py.device_id == rs.device_id
        assert py.device_label == rs.device_label
        assert py.verify_key == rs.verify_key

    kwargs = {
        "author": DeviceID.new(),
        "timestamp": pendulum.now(),
        "device_id": DeviceID("bob@dev1"),
        "device_label": DeviceLabel("dev machine"),
        "verify_key": SigningKey.generate().verify_key,
    }

    py_dc = _PyDeviceCertificateContent(**kwargs)
    rs_dc = DeviceCertificateContent(**kwargs)
    _assert_device_certificate_eq(py_dc, rs_dc)

    kwargs = {
        "author": DeviceID.new(),
        "timestamp": pendulum.now(),
        "device_id": DeviceID("alice@dev1"),
        "device_label": None,
        "verify_key": SigningKey.generate().verify_key,
    }

    py_dc = py_dc.evolve(**kwargs)
    rs_dc = rs_dc.evolve(**kwargs)
    _assert_device_certificate_eq(py_dc, rs_dc)

    sign_key = SigningKey.generate()
    py_data = py_dc.dump_and_sign(sign_key)
    rs_data = rs_dc.dump_and_sign(sign_key)

    py_dc = _PyDeviceCertificateContent.verify_and_load(
        rs_data, sign_key.verify_key, expected_author=py_dc.author, expected_device=py_dc.device_id
    )
    rs_dc = DeviceCertificateContent.verify_and_load(
        py_data, sign_key.verify_key, expected_author=rs_dc.author, expected_device=rs_dc.device_id
    )
    _assert_device_certificate_eq(py_dc, rs_dc)

    py_dc = _PyDeviceCertificateContent.unsecure_load(rs_data)
    rs_dc = DeviceCertificateContent.unsecure_load(py_data)
    _assert_device_certificate_eq(py_dc, rs_dc)
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)

    with operation(f"Creating locally {device_display}"):
        save_device_with_password(config_dir, device, password, force=force)

    now = pendulum.now()
    certified_user = certify_user(None, root_signing_key, device.user_id, device.public_key, now)
    certified_device = certify_device(None, root_signing_key, device_id, device.verify_key, now)

    async with spinner(f"Sending {device_display} to server"):
        async with backend_anonymous_cmds_factory(organization_bootstrap_addr) as cmds:
            await cmds.organization_bootstrap(
                organization_bootstrap_addr.organization_id,
                organization_bootstrap_addr.bootstrap_token,
                root_verify_key,
                certified_user,
                certified_device,
            )

    organization_addr_display = click.style(organization_addr, fg="yellow")
    click.echo(f"Organization url: {organization_addr_display}")
async def claim_device(cmds: BackendCmdsPool, new_device_id: DeviceID,
                       token: str) -> LocalDevice:
    """
    Raises:
        InviteClaimError
        core.backend_connection.BackendConnectionError
        core.trustchain.TrustChainError
    """
    device_signing_key = SigningKey.generate()
    answer_private_key = PrivateKey.generate()

    invitation_creator = await cmds.device_get_invitation_creator(new_device_id
                                                                  )

    encrypted_claim = generate_device_encrypted_claim(
        creator_public_key=invitation_creator.public_key,
        token=token,
        device_id=new_device_id,
        verify_key=device_signing_key.verify_key,
        answer_public_key=answer_private_key.public_key,
    )
    encrypted_answer = await cmds.device_claim(new_device_id, encrypted_claim)

    answer = extract_device_encrypted_answer(answer_private_key,
                                             encrypted_answer)
    return LocalDevice(
        organization_addr=cmds.addr,
        device_id=new_device_id,
        signing_key=device_signing_key,
        private_key=answer["private_key"],
        user_manifest_access=answer["user_manifest_access"],
        local_symkey=generate_secret_key(),
    )
Example #4
0
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
Example #5
0
    async def do_claim_user(
        self,
        requested_device_label: Optional[DeviceLabel],
        requested_human_handle: Optional[HumanHandle],
    ) -> LocalDevice:
        # User&device keys are generated here and kept in memory until the end of
        # the enrollment process. This mean we can lost it if something goes wrong.
        # This has no impact until step 4 (somewhere between data exchange and
        # confirmation exchange steps) where greeter upload our certificates in
        # the server.
        # This is considered acceptable given 1) the error window is small and
        # 2) if this occurs the inviter can revoke the user and retry the
        # enrollment process to fix this
        private_key = PrivateKey.generate()
        signing_key = SigningKey.generate()

        try:
            payload = InviteUserData(
                requested_device_label=requested_device_label,
                requested_human_handle=requested_human_handle,
                public_key=private_key.public_key,
                verify_key=signing_key.verify_key,
            ).dump_and_encrypt(key=self._shared_secret_key)
        except DataError as exc:
            raise InviteError(
                "Cannot generate InviteUserData payload") from exc

        rep = await self._cmds.invite_4_claimer_communicate(payload=payload)
        _check_rep(rep, step_name="step 4 (data exchange)")

        rep = await self._cmds.invite_4_claimer_communicate(payload=b"")
        _check_rep(rep, step_name="step 4 (confirmation exchange)")

        try:
            confirmation = InviteUserConfirmation.decrypt_and_load(
                rep["payload"], key=self._shared_secret_key)
        except DataError as exc:
            raise InviteError(
                "Invalid InviteUserConfirmation payload provided by peer"
            ) from exc

        organization_addr = BackendOrganizationAddr.build(
            backend_addr=self._cmds.addr.get_backend_addr(),
            organization_id=self._cmds.addr.organization_id,
            root_verify_key=confirmation.root_verify_key,
        )

        new_device = generate_new_device(
            organization_addr=organization_addr,
            device_id=confirmation.device_id,
            device_label=confirmation.device_label,
            human_handle=confirmation.human_handle,
            profile=confirmation.profile,
            private_key=private_key,
            signing_key=signing_key,
        )

        return new_device
def test_signing_key():
    from parsec.crypto import (
        _PySigningKey,
        _RsSigningKey,
        SigningKey,
        VerifyKey,
        _RsVerifyKey,
        _PyVerifyKey,
    )

    assert SigningKey is _RsSigningKey
    assert VerifyKey is _RsVerifyKey

    KEY = b"a" * 32

    rs_sk = SigningKey(KEY)
    py_sk = _PySigningKey(KEY)

    rs_vk = rs_sk.verify_key
    py_vk = py_sk.verify_key

    assert SigningKey(KEY) == SigningKey(KEY)
    assert SigningKey(KEY) != SigningKey(b"b" * 32)

    assert isinstance(rs_vk, _RsVerifyKey)
    assert isinstance(py_vk, _PyVerifyKey)

    # Sign a message with both, check if the signed message is the same
    MESSAGE = b"My message"

    rs_signed = rs_sk.sign(MESSAGE)
    py_signed = py_sk.sign(MESSAGE)

    assert rs_signed == py_signed

    # Verify with both
    assert rs_vk.verify(rs_signed) == py_vk.verify(py_signed)
    assert rs_vk.verify(py_signed) == py_vk.verify(rs_signed)

    # Check if unsecure_unwrap is the same
    assert VerifyKey.unsecure_unwrap(
        rs_signed) == _PyVerifyKey.unsecure_unwrap(py_signed)
    assert VerifyKey.unsecure_unwrap(
        py_signed) == _PyVerifyKey.unsecure_unwrap(rs_signed)

    # Check if generate returns the right type
    assert isinstance(SigningKey.generate(), SigningKey)
    assert isinstance(_PySigningKey.generate(), _PySigningKey)

    # Check if they both react in a similar manner with incorrect data
    assert rs_vk.unsecure_unwrap(b"random_data") == py_vk.unsecure_unwrap(
        b"random_data")

    with pytest.raises(nacl.exceptions.CryptoError):
        py_vk.verify(b"random_data")
    with pytest.raises(nacl.exceptions.CryptoError):
        rs_vk.verify(b"random data")
Example #7
0
    async def do_claim_user(
            self, requested_device_label: Optional[str],
            requested_human_handle: Optional[HumanHandle]) -> LocalDevice:
        private_key = PrivateKey.generate()
        signing_key = SigningKey.generate()

        try:
            payload = InviteUserData(
                requested_device_label=requested_device_label,
                requested_human_handle=requested_human_handle,
                public_key=private_key.public_key,
                verify_key=signing_key.verify_key,
            ).dump_and_encrypt(key=self._shared_secret_key)
        except DataError as exc:
            raise InviteError(
                "Cannot generate InviteUserData payload") from exc

        rep = await self._cmds.invite_4_claimer_communicate(payload=payload)
        if rep["status"] == "invalid_state":
            raise InvitePeerResetError()
        elif rep["status"] != "ok":
            raise InviteError(
                f"Backend error during step 4 (data exchange): {rep}")

        rep = await self._cmds.invite_4_claimer_communicate(payload=b"")
        if rep["status"] == "invalid_state":
            raise InvitePeerResetError()
        elif rep["status"] != "ok":
            raise InviteError(
                f"Backend error during step 4 (confirmation exchange): {rep}")

        try:
            confirmation = InviteUserConfirmation.decrypt_and_load(
                rep["payload"], key=self._shared_secret_key)
        except DataError as exc:
            raise InviteError(
                "Invalid InviteUserConfirmation payload provided by peer"
            ) from exc

        organization_addr = BackendOrganizationAddr.build(
            backend_addr=self._cmds.addr,
            organization_id=self._cmds.addr.organization_id,
            root_verify_key=confirmation.root_verify_key,
        )

        new_device = generate_new_device(
            organization_addr=organization_addr,
            device_id=confirmation.device_id,
            device_label=confirmation.device_label,
            human_handle=confirmation.human_handle,
            profile=confirmation.profile,
            private_key=private_key,
            signing_key=signing_key,
        )

        return new_device
Example #8
0
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
Example #9
0
def test_local_device():
    from parsec.core.types.local_device import _RsLocalDevice, LocalDevice, _PyLocalDevice

    assert LocalDevice is _RsLocalDevice

    def _assert_local_device_eq(py, rs):
        assert isinstance(py, _PyLocalDevice)
        assert isinstance(rs, _RsLocalDevice)

        assert py.organization_addr == rs.organization_addr
        assert py.device_id == rs.device_id
        assert py.device_label == rs.device_label
        assert py.human_handle == rs.human_handle
        assert py.signing_key == rs.signing_key
        assert py.private_key == rs.private_key
        assert py.profile == rs.profile
        assert py.user_manifest_id == rs.user_manifest_id
        assert py.user_manifest_key == rs.user_manifest_key
        assert py.local_symkey == rs.local_symkey

        assert py.is_admin == rs.is_admin
        assert py.is_outsider == rs.is_outsider
        assert py.slug == rs.slug
        assert py.slughash == rs.slughash
        assert py.root_verify_key == rs.root_verify_key
        assert py.organization_id == rs.organization_id
        assert py.device_name == rs.device_name
        assert py.user_id == rs.user_id
        assert py.verify_key == rs.verify_key
        assert py.public_key == rs.public_key
        assert py.user_display == rs.user_display
        assert py.short_user_display == rs.short_user_display
        assert py.device_display == rs.device_display

    signing_key = SigningKey.generate()
    kwargs = {
        "organization_addr": BackendOrganizationAddr.build(
            BackendAddr.from_url("parsec://foo"),
            organization_id=OrganizationID("org"),
            root_verify_key=signing_key.verify_key,
        ),
        "device_id": DeviceID.new(),
        "device_label": None,
        "human_handle": None,
        "signing_key": signing_key,
        "private_key": PrivateKey.generate(),
        "profile": UserProfile.ADMIN,
        "user_manifest_id": EntryID.new(),
        "user_manifest_key": SecretKey.generate(),
        "local_symkey": SecretKey.generate(),
    }

    py_ba = _PyLocalDevice(**kwargs)
    rs_ba = LocalDevice(**kwargs)
    _assert_local_device_eq(py_ba, rs_ba)
Example #10
0
def test_available_devices_slughash_uniqueness(organization_factory,
                                               local_device_factory,
                                               config_dir):
    def _to_available(device):
        return AvailableDevice(
            key_file_path=get_default_key_file(config_dir, device),
            organization_id=device.organization_id,
            device_id=device.device_id,
            human_handle=device.human_handle,
            device_label=device.device_label,
            slug=device.slug,
            type=DeviceFileType.PASSWORD,
        )

    def _assert_different_as_available(d1, d2):
        available_device_d1 = _to_available(d1)
        available_device_d2 = _to_available(d2)
        assert available_device_d1.slughash != available_device_d2.slughash
        # Make sure slughash is consistent between LocalDevice and AvailableDevice
        assert available_device_d1.slughash == d1.slughash
        assert available_device_d2.slughash == d2.slughash

    o1 = organization_factory("org1")
    o2 = organization_factory("org2")

    # Different user id
    o1u1d1 = local_device_factory("u1@d1", o1)
    o1u2d1 = local_device_factory("u2@d1", o1)
    _assert_different_as_available(o1u1d1, o1u2d1)

    # Different device name
    o1u1d2 = local_device_factory("u1@d2", o1)
    _assert_different_as_available(o1u1d1, o1u1d2)

    # Different organization id
    o2u1d1 = local_device_factory("u1@d1", o2)
    _assert_different_as_available(o1u1d1, o2u1d1)

    # Same organization_id, but different root verify key !
    dummy_key = SigningKey.generate().verify_key
    o1u1d1_bad_rvk = o1u1d1.evolve(
        organization_addr=o1u1d1.organization_addr.build(
            backend_addr=o1u1d1.organization_addr.get_backend_addr(),
            organization_id=o1u1d1.organization_addr.organization_id,
            root_verify_key=dummy_key,
        ))
    _assert_different_as_available(o1u1d1, o1u1d1_bad_rvk)

    # Finally make sure slughash is stable through save/load
    save_device_with_password_in_config(config_dir, o1u1d1, "S3Cr37")
    key_file = get_key_file(config_dir, o1u1d1)
    o1u1d1_reloaded = load_device_with_password(key_file, "S3Cr37")
    available_device = _to_available(o1u1d1)
    available_device_reloaded = _to_available(o1u1d1_reloaded)
    assert available_device.slughash == available_device_reloaded.slughash
Example #11
0
def generate_new_device(
        device_id: DeviceID,
        organization_addr: BackendOrganizationAddr) -> LocalDevice:
    return LocalDevice(
        organization_addr=organization_addr,
        device_id=device_id,
        signing_key=SigningKey.generate(),
        private_key=PrivateKey.generate(),
        user_manifest_access=ManifestAccess(),
        local_symkey=generate_secret_key(),
    )
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}")
Example #13
0
async def test_proxy_with_websocket(monkeypatch, connection_type, proxy_type):
    signing_key = SigningKey.generate()
    device_id = DeviceID("zack@pc1")
    proxy_events = []

    def _event_hook(event):
        proxy_events.append(event)

    async with trio.open_nursery() as nursery:
        target_port = await start_port_watchdog(nursery, _event_hook)
        proxy_port = await start_proxy_for_websocket(nursery, target_port,
                                                     _event_hook)

        if proxy_type == "http_proxy":
            proxy_url = f"http://127.0.0.1:{proxy_port}"
            monkeypatch.setitem(os.environ, "http_proxy", proxy_url)
        else:
            assert proxy_type == "http_proxy_pac"
            pac_server_port = await start_pac_server(
                nursery=nursery,
                pac_rule=f"PROXY 127.0.0.1:{proxy_port}",
                event_hook=_event_hook)
            pac_server_url = f"http://127.0.0.1:{pac_server_port}"
            monkeypatch.setitem(os.environ, "http_proxy_pac", pac_server_url)
            # HTTP_PROXY_PAC has priority over HTTP_PROXY
            monkeypatch.setitem(os.environ, "http_proxy",
                                f"http://127.0.0.1:{target_port}")

        async with real_clock_timeout():
            with pytest.raises(BackendNotAvailable):
                if connection_type == "authenticated":
                    await connect_as_authenticated(
                        addr=BackendOrganizationAddr.from_url(
                            f"parsec://127.0.0.1:{target_port}/CoolOrg?no_ssl=true&rvk=7NFDS4VQLP3XPCMTSEN34ZOXKGGIMTY2W2JI2SPIHB2P3M6K4YWAssss"
                        ),
                        device_id=device_id,
                        signing_key=signing_key,
                    )

                else:
                    assert connection_type == "invited"
                    await connect_as_invited(addr=BackendInvitationAddr.from_url(
                        f"parsec://127.0.0.1:{target_port}/CoolOrg?no_ssl=true&action=claim_user&token=3a50b191122b480ebb113b10216ef343"
                    ))

        assert proxy_events == [
            *(["PAC file retreived from server"]
              if proxy_type == "http_proxy_pac" else []),
            "Connected to proxy",
            "Reaching target through proxy",
        ]

        nursery.cancel_scope.cancel()
Example #14
0
def generate_new_device(device_id: DeviceID,
                        organization_addr: BackendOrganizationAddr,
                        is_admin: bool = False) -> LocalDevice:
    return LocalDevice(
        organization_addr=organization_addr,
        device_id=device_id,
        signing_key=SigningKey.generate(),
        private_key=PrivateKey.generate(),
        is_admin=is_admin,
        user_manifest_id=EntryID(uuid4().hex),
        user_manifest_key=SecretKey.generate(),
        local_symkey=SecretKey.generate(),
    )
Example #15
0
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
Example #16
0
def _check_equal_backend_organization_bootstrap_addrs(rs, py):
    assert rs.to_url() == py.to_url()
    assert rs.hostname == py.hostname
    assert rs.port == py.port
    assert rs.netloc == py.netloc
    assert rs.use_ssl == py.use_ssl
    assert str(rs.organization_id) == str(py.organization_id)
    assert rs.token == py.token
    assert str(rs) == str(py)
    assert hash(rs) == hash(py)
    assert repr(rs) == repr(py)
    vk = SigningKey.generate().verify_key
    assert (rs.generate_organization_addr(
        root_verify_key=vk).to_url() == py.generate_organization_addr(
            root_verify_key=vk).to_url())
def test_build_addrs():
    backend_addr = BackendAddr.from_url(BackendAddrTestbed.url)
    assert backend_addr.hostname == "parsec.cloud.com"
    assert backend_addr.port == 443
    assert backend_addr.use_ssl is True

    organization_id = OrganizationID("MyOrg")
    root_verify_key = SigningKey.generate().verify_key

    organization_addr = BackendOrganizationAddr.build(
        backend_addr=backend_addr, organization_id=organization_id, root_verify_key=root_verify_key
    )
    assert organization_addr.organization_id == organization_id
    assert organization_addr.root_verify_key == root_verify_key

    organization_bootstrap_addr = BackendOrganizationBootstrapAddr.build(
        backend_addr=backend_addr,
        organization_id=organization_id,
        token="a0000000000000000000000000000001",
    )
    assert organization_bootstrap_addr.token == "a0000000000000000000000000000001"
    assert organization_bootstrap_addr.organization_id == organization_id

    organization_bootstrap_addr2 = BackendOrganizationBootstrapAddr.build(
        backend_addr=backend_addr, organization_id=organization_id, token=None
    )
    assert organization_bootstrap_addr2.organization_id == organization_id
    assert organization_bootstrap_addr2.token == ""

    organization_file_link_addr = BackendOrganizationFileLinkAddr.build(
        organization_addr=organization_addr,
        workspace_id=EntryID.from_hex("2d4ded12-7406-4608-833b-7f57f01156e2"),
        encrypted_path=b"<encrypted_payload>",
    )
    assert organization_file_link_addr.workspace_id == EntryID.from_hex(
        "2d4ded12-7406-4608-833b-7f57f01156e2"
    )
    assert organization_file_link_addr.encrypted_path == b"<encrypted_payload>"

    invitation_addr = BackendInvitationAddr.build(
        backend_addr=backend_addr,
        organization_id=organization_id,
        invitation_type=InvitationType.USER,
        token=InvitationToken.from_hex("a0000000000000000000000000000001"),
    )
    assert invitation_addr.organization_id == organization_id
    assert invitation_addr.token == InvitationToken.from_hex("a0000000000000000000000000000001")
    assert invitation_addr.invitation_type == InvitationType.USER
Example #18
0
def _check_equal_backend_pki_enrollment_addrs(rs, py):
    assert rs.to_url() == py.to_url()
    assert rs.hostname == py.hostname
    assert rs.port == py.port
    assert rs.netloc == py.netloc
    assert rs.use_ssl == py.use_ssl
    assert rs.to_http_domain_url() == py.to_http_domain_url() + "/"
    assert rs.to_http_domain_url("/path") == py.to_http_domain_url("/path")
    assert str(rs.organization_id) == str(py.organization_id)
    assert str(rs) == str(py)
    assert hash(rs) == hash(py)
    assert repr(rs) == repr(py)
    vk = SigningKey.generate().verify_key
    assert (rs.generate_organization_addr(
        root_verify_key=vk).to_url() == py.generate_organization_addr(
            root_verify_key=vk).to_url())
Example #19
0
    def _organization_factory(orgname=None):
        nonlocal count

        if not orgname:
            count += 1
            orgname = f"Org{count}"

        organization_id = OrganizationID(orgname)
        assert organization_id not in organizations
        organizations.add(organization_id)
        bootstrap_token = f"<{orgname}-bootstrap-token>"
        bootstrap_addr = BackendOrganizationBootstrapAddr.build(
            backend_addr, organization_id=organization_id, token=bootstrap_token
        )
        root_signing_key = SigningKey.generate()
        addr = bootstrap_addr.generate_organization_addr(root_signing_key.verify_key)
        return OrganizationFullData(bootstrap_addr, addr, root_signing_key)
Example #20
0
    async def do_claim_device(
            self, requested_device_label: Optional[str]) -> LocalDevice:
        signing_key = SigningKey.generate()

        try:
            payload = InviteDeviceData(
                requested_device_label=requested_device_label,
                verify_key=signing_key.verify_key).dump_and_encrypt(
                    key=self._shared_secret_key)
        except DataError as exc:
            raise InviteError(
                "Cannot generate InviteDeviceData payload") from exc

        rep = await self._cmds.invite_4_claimer_communicate(payload=payload)
        _check_rep(rep, step_name="step 4 (data exchange)")

        rep = await self._cmds.invite_4_claimer_communicate(payload=b"")
        _check_rep(rep, step_name="step 4 (confirmation exchange)")

        try:
            confirmation = InviteDeviceConfirmation.decrypt_and_load(
                rep["payload"], key=self._shared_secret_key)
        except DataError as exc:
            raise InviteError(
                "Invalid InviteDeviceConfirmation payload provided by peer"
            ) from exc

        organization_addr = BackendOrganizationAddr.build(
            backend_addr=self._cmds.addr,
            organization_id=self._cmds.addr.organization_id,
            root_verify_key=confirmation.root_verify_key,
        )

        return LocalDevice(
            organization_addr=organization_addr,
            device_id=confirmation.device_id,
            device_label=confirmation.device_label,
            human_handle=confirmation.human_handle,
            profile=confirmation.profile,
            private_key=confirmation.private_key,
            signing_key=signing_key,
            user_manifest_id=confirmation.user_manifest_id,
            user_manifest_key=confirmation.user_manifest_key,
            local_symkey=SecretKey.generate(),
        )
Example #21
0
def test_backend_organization_addr_init():
    from parsec.core.types.backend_address import (
        _PyBackendOrganizationAddr,
        _RsBackendOrganizationAddr,
        BackendOrganizationAddr,
    )

    assert BackendOrganizationAddr is _RsBackendOrganizationAddr

    vk = SigningKey.generate().verify_key

    py_ba = _PyBackendOrganizationAddr(OrganizationID("MyOrg"),
                                       vk,
                                       hostname="parsec.cloud")
    rs_ba = BackendOrganizationAddr(OrganizationID("MyOrg"),
                                    vk,
                                    hostname="parsec.cloud")

    _check_equal_backend_organization_addrs(rs_ba, py_ba)
Example #22
0
def test_backend_organization_addr_build():
    from parsec.core.types.backend_address import (
        _PyBackendOrganizationAddr,
        _RsBackendOrganizationAddr,
        BackendOrganizationAddr,
        BackendAddr,
    )

    assert BackendOrganizationAddr is _RsBackendOrganizationAddr

    BACKEND_ADDR = BackendAddr.from_url("parsec://parsec.cloud:1337")

    vk = SigningKey.generate().verify_key

    py_ba = _PyBackendOrganizationAddr.build(BACKEND_ADDR,
                                             OrganizationID("MyOrg"), vk)
    rs_ba = BackendOrganizationAddr.build(BACKEND_ADDR,
                                          OrganizationID("MyOrg"), vk)
    _check_equal_backend_organization_addrs(rs_ba, py_ba)
Example #23
0
def test_revoked_user_certificate():
    from parsec.api.data.certif import (
        _RsRevokedUserCertificateContent,
        RevokedUserCertificateContent,
        _PyRevokedUserCertificateContent,
    )

    assert RevokedUserCertificateContent is _RsRevokedUserCertificateContent

    def _assert_revoked_user_certificate_eq(py, rs):
        assert py.author == rs.author
        assert py.timestamp == rs.timestamp
        assert py.user_id == rs.user_id

    kwargs = {"author": DeviceID.new(), "timestamp": pendulum.now(), "user_id": UserID("bob")}

    py_ruc = _PyRevokedUserCertificateContent(**kwargs)
    rs_ruc = RevokedUserCertificateContent(**kwargs)
    _assert_revoked_user_certificate_eq(py_ruc, rs_ruc)

    kwargs = {"author": DeviceID.new(), "timestamp": pendulum.now(), "user_id": UserID("alice")}

    py_ruc = py_ruc.evolve(**kwargs)
    rs_ruc = rs_ruc.evolve(**kwargs)
    _assert_revoked_user_certificate_eq(py_ruc, rs_ruc)

    sign_key = SigningKey.generate()
    py_data = py_ruc.dump_and_sign(sign_key)
    rs_data = rs_ruc.dump_and_sign(sign_key)

    py_ruc = _PyRevokedUserCertificateContent.verify_and_load(
        rs_data, sign_key.verify_key, expected_author=py_ruc.author, expected_user=py_ruc.user_id
    )
    rs_ruc = RevokedUserCertificateContent.verify_and_load(
        py_data, sign_key.verify_key, expected_author=rs_ruc.author, expected_user=rs_ruc.user_id
    )
    _assert_revoked_user_certificate_eq(py_ruc, rs_ruc)

    py_ruc = _PyRevokedUserCertificateContent.unsecure_load(rs_data)
    rs_ruc = RevokedUserCertificateContent.unsecure_load(py_data)
    _assert_revoked_user_certificate_eq(py_ruc, rs_ruc)
Example #24
0
def generate_new_device(
    organization_addr: BackendOrganizationAddr,
    device_id: Optional[DeviceID] = None,
    profile: UserProfile = UserProfile.STANDARD,
    human_handle: Optional[HumanHandle] = None,
    device_label: Optional[DeviceLabel] = None,
    signing_key: Optional[SigningKey] = None,
    private_key: Optional[PrivateKey] = None,
) -> LocalDevice:
    return LocalDevice(
        organization_addr=organization_addr,
        device_id=device_id or DeviceID.new(),
        device_label=device_label,
        human_handle=human_handle,
        signing_key=signing_key or SigningKey.generate(),
        private_key=private_key or PrivateKey.generate(),
        profile=profile,
        user_manifest_id=EntryID.new(),
        user_manifest_key=SecretKey.generate(),
        local_symkey=SecretKey.generate(),
    )
Example #25
0
async def _register_new_device(cmds: BackendAuthenticatedCmds,
                               author: LocalDevice,
                               device_label: Optional[str]):
    new_device = LocalDevice(
        organization_addr=author.organization_addr,
        device_id=DeviceID(f"{author.user_id}@{DeviceName.new()}"),
        device_label=device_label,
        human_handle=author.human_handle,
        profile=author.profile,
        private_key=author.private_key,
        signing_key=SigningKey.generate(),
        user_manifest_id=author.user_manifest_id,
        user_manifest_key=author.user_manifest_key,
        local_symkey=author.local_symkey,
    )
    now = pendulum_now()

    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)

    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.device_create(
        device_certificate=device_certificate,
        redacted_device_certificate=redacted_device_certificate,
    )

    if rep["status"] != "ok":
        raise RuntimeError(f"Cannot create device: {rep}")

    return new_device
Example #26
0
    async def new(
        cls, addr: BackendPkiEnrollmentAddr
    ) -> "PkiEnrollmentSubmitterSubmittedStatusCtx":
        """
        Raises:
            PkiEnrollmentCertificateError
            PkiEnrollmentCertificateCryptoError
            PkiEnrollmentCertificateNotFoundError
        """
        enrollment_id = uuid4()
        signing_key = SigningKey.generate()
        private_key = PrivateKey.generate()

        x509_certificate = await pki_enrollment_select_certificate()

        return cls(
            addr=addr,
            enrollment_id=enrollment_id,
            signing_key=signing_key,
            private_key=private_key,
            x509_certificate=x509_certificate,
        )
Example #27
0
def test_invite_device_data():
    from parsec.api.data.invite import _RsInviteDeviceData, InviteDeviceData, _PyInviteDeviceData

    assert InviteDeviceData is _RsInviteDeviceData

    dl = DeviceLabel("label")
    sk = SigningKey.generate()
    vk = sk.verify_key
    sek = SecretKey.generate()

    py_idd = _PyInviteDeviceData(requested_device_label=dl, verify_key=vk)
    rs_idd = InviteDeviceData(requested_device_label=dl, verify_key=vk)

    assert rs_idd.requested_device_label.str == py_idd.requested_device_label.str

    rs_encrypted = rs_idd.dump_and_encrypt(key=sek)
    py_encrypted = py_idd.dump_and_encrypt(key=sek)

    # Decrypt Rust-encrypted with Rust
    rs_idd2 = InviteDeviceData.decrypt_and_load(rs_encrypted, sek)
    assert rs_idd.requested_device_label.str == rs_idd2.requested_device_label.str

    # Decrypt Python-encrypted with Python
    rs_idd3 = InviteDeviceData.decrypt_and_load(py_encrypted, sek)
    assert rs_idd.requested_device_label.str == rs_idd3.requested_device_label.str

    # Decrypt Rust-encrypted with Python
    py_idd2 = _PyInviteDeviceData.decrypt_and_load(rs_encrypted, sek)
    assert rs_idd.requested_device_label.str == py_idd2.requested_device_label.str

    # With requested_human_handle and requested_device_label as None
    py_idd = _PyInviteDeviceData(requested_device_label=None, verify_key=vk)
    rs_idd = InviteDeviceData(requested_device_label=None, verify_key=vk)

    assert py_idd.requested_device_label is None
    assert rs_idd.requested_device_label is None
Example #28
0
async def claim_device(
    organization_addr: BackendOrganizationAddr,
    new_device_id: DeviceID,
    token: str,
    keepalive: Optional[int] = None,
) -> LocalDevice:
    """
    Raises:
        InviteClaimError
        InviteClaimBackendOfflineError
        InviteClaimValidationError
        InviteClaimPackingError
        InviteClaimCryptoError
    """
    device_signing_key = SigningKey.generate()
    answer_private_key = PrivateKey.generate()

    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_device_invitation_creator(
                    cmds, organization_addr.root_verify_key, new_device_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 = DeviceClaimContent(
                    token=token,
                    device_id=new_device_id,
                    verify_key=device_signing_key.verify_key,
                    answer_public_key=answer_private_key.public_key,
                ).dump_and_encrypt_for(
                    recipient_pubkey=invitation_creator_user.public_key)

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

            # 3) Send claim
            rep = await cmds.device_claim(new_device_id, encrypted_claim)
            if rep["status"] != "ok":
                raise InviteClaimError(f"Claim request error: {rep}")

            # 4) Verify device certificate
            try:
                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,
                )

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

            try:
                answer = DeviceClaimAnswerContent.decrypt_and_load_for(
                    rep["encrypted_answer"],
                    recipient_privkey=answer_private_key)

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

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

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

    return LocalDevice(
        organization_addr=organization_addr,
        device_id=new_device_id,
        signing_key=device_signing_key,
        private_key=answer.private_key,
        is_admin=invitation_creator_user.is_admin,
        user_manifest_id=answer.user_manifest_id,
        user_manifest_key=answer.user_manifest_key,
        local_symkey=SecretKey.generate(),
    )
Example #29
0
def test_invite_user_data():
    from parsec.api.data.invite import _RsInviteUserData, InviteUserData, _PyInviteUserData

    assert InviteUserData is _RsInviteUserData

    dl = DeviceLabel("label")
    hh = HumanHandle("*****@*****.**",
                     "Hubert Farnsworth")
    pk = PrivateKey.generate()
    sik = SigningKey.generate()
    sek = SecretKey.generate()

    py_iud = _PyInviteUserData(
        requested_device_label=dl,
        requested_human_handle=hh,
        public_key=pk.public_key,
        verify_key=sik.verify_key,
    )
    rs_iud = InviteUserData(
        requested_device_label=dl,
        requested_human_handle=hh,
        public_key=pk.public_key,
        verify_key=sik.verify_key,
    )

    assert rs_iud.requested_device_label.str == py_iud.requested_device_label.str
    assert str(rs_iud.requested_human_handle) == str(
        py_iud.requested_human_handle)
    rs_encrypted = rs_iud.dump_and_encrypt(key=sek)
    py_encrypted = py_iud.dump_and_encrypt(key=sek)

    # Decrypt Rust-encrypted with Rust
    rs_iud2 = InviteUserData.decrypt_and_load(rs_encrypted, sek)
    assert rs_iud.requested_device_label.str == rs_iud2.requested_device_label.str
    assert str(rs_iud.requested_human_handle) == str(
        rs_iud2.requested_human_handle)

    # Decrypt Python-encrypted with Python
    rs_iud3 = InviteUserData.decrypt_and_load(py_encrypted, sek)
    assert rs_iud.requested_device_label.str == rs_iud3.requested_device_label.str
    assert str(rs_iud.requested_human_handle) == str(
        rs_iud3.requested_human_handle)

    # Decrypt Rust-encrypted with Python
    py_iud2 = _PyInviteUserData.decrypt_and_load(rs_encrypted, sek)
    assert rs_iud.requested_device_label.str == py_iud2.requested_device_label.str
    assert str(rs_iud.requested_human_handle) == str(
        py_iud2.requested_human_handle)

    # With requested_human_handle and requested_device_label as None
    py_iud = _PyInviteUserData(
        requested_device_label=None,
        requested_human_handle=None,
        public_key=pk.public_key,
        verify_key=sik.verify_key,
    )
    rs_iud = InviteUserData(
        requested_device_label=None,
        requested_human_handle=None,
        public_key=pk.public_key,
        verify_key=sik.verify_key,
    )

    assert py_iud.requested_device_label is None
    assert rs_iud.requested_device_label is None
    assert py_iud.requested_human_handle is None
    assert rs_iud.requested_human_handle is None
Example #30
0
def test_invite_device_confirmation():
    from parsec.api.data.invite import (
        _RsInviteDeviceConfirmation,
        InviteDeviceConfirmation,
        _PyInviteDeviceConfirmation,
    )

    assert InviteDeviceConfirmation is _RsInviteDeviceConfirmation

    di = DeviceID("a@b")
    dl = DeviceLabel("label")
    hh = HumanHandle("*****@*****.**",
                     "Hubert Farnsworth")
    profile = UserProfile.STANDARD
    pk = PrivateKey.generate()
    umi = EntryID.new()
    umk = SecretKey.generate()
    sk = SigningKey.generate()
    vk = sk.verify_key
    sek = SecretKey.generate()

    py_idc = _PyInviteDeviceConfirmation(
        device_id=di,
        device_label=dl,
        human_handle=hh,
        profile=profile,
        private_key=pk,
        user_manifest_id=umi,
        user_manifest_key=umk,
        root_verify_key=vk,
    )
    rs_idc = InviteDeviceConfirmation(
        device_id=di,
        device_label=dl,
        human_handle=hh,
        profile=profile,
        private_key=pk,
        user_manifest_id=umi,
        user_manifest_key=umk,
        root_verify_key=vk,
    )

    assert rs_idc.device_label.str == py_idc.device_label.str
    assert str(rs_idc.human_handle) == str(py_idc.human_handle)
    assert rs_idc.device_id.str == py_idc.device_id.str
    assert rs_idc.profile == py_idc.profile
    assert rs_idc.user_manifest_id.hex == py_idc.user_manifest_id.hex

    rs_encrypted = rs_idc.dump_and_encrypt(key=sek)
    py_encrypted = py_idc.dump_and_encrypt(key=sek)

    # Decrypt Rust-encrypted with Rust
    rs_idc2 = InviteDeviceConfirmation.decrypt_and_load(rs_encrypted, sek)
    assert rs_idc.device_label.str == rs_idc2.device_label.str
    assert str(rs_idc.human_handle) == str(rs_idc2.human_handle)
    assert rs_idc.device_id.str == rs_idc2.device_id.str
    assert rs_idc.profile == rs_idc2.profile
    assert rs_idc.user_manifest_id.hex == rs_idc2.user_manifest_id.hex

    # Decrypt Python-encrypted with Python
    rs_idc3 = InviteDeviceConfirmation.decrypt_and_load(py_encrypted, sek)
    assert rs_idc.device_label.str == rs_idc3.device_label.str
    assert str(rs_idc.human_handle) == str(rs_idc3.human_handle)
    assert rs_idc.device_id.str == rs_idc3.device_id.str
    assert rs_idc.profile == rs_idc3.profile
    assert rs_idc.user_manifest_id.hex == rs_idc3.user_manifest_id.hex

    # Decrypt Rust-encrypted with Python
    py_idc2 = _PyInviteDeviceConfirmation.decrypt_and_load(rs_encrypted, sek)
    assert rs_idc.device_label.str == py_idc2.device_label.str
    assert str(rs_idc.human_handle) == str(py_idc2.human_handle)
    assert rs_idc.device_id.str == py_idc2.device_id.str
    assert rs_idc.profile == py_idc2.profile
    assert rs_idc.user_manifest_id.hex == rs_idc2.user_manifest_id.hex

    # With human_handle and device_label as None
    py_idc = _PyInviteDeviceConfirmation(
        device_id=di,
        device_label=None,
        human_handle=None,
        profile=profile,
        private_key=pk,
        user_manifest_id=umi,
        user_manifest_key=umk,
        root_verify_key=vk,
    )
    rs_idc = InviteDeviceConfirmation(
        device_id=di,
        device_label=None,
        human_handle=None,
        profile=profile,
        private_key=pk,
        user_manifest_id=umi,
        user_manifest_key=umk,
        root_verify_key=vk,
    )

    assert py_idc.device_label is None
    assert rs_idc.device_label is None
    assert py_idc.human_handle is None
    assert rs_idc.human_handle is None