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(), )
async def bootstrap_organization( cmds: APIV1_BackendAnonymousCmds, human_handle: Optional[HumanHandle], device_label: Optional[str], ) -> LocalDevice: root_signing_key = SigningKey.generate() root_verify_key = root_signing_key.verify_key organization_addr = BackendOrganizationAddr.build( backend_addr=cmds.addr, organization_id=cmds.addr.organization_id, root_verify_key=root_verify_key, ) device = generate_new_device( organization_addr=organization_addr, profile=UserProfile.ADMIN, human_handle=human_handle, device_label=device_label, ) now = pendulum_now() user_certificate = UserCertificateContent( author=None, timestamp=now, user_id=device.user_id, human_handle=device.human_handle, public_key=device.public_key, profile=device.profile, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=None, timestamp=now, device_id=device.device_id, device_label=device.device_label, verify_key=device.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(root_signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( root_signing_key) device_certificate = device_certificate.dump_and_sign(root_signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( root_signing_key) rep = await cmds.organization_bootstrap( organization_id=cmds.addr.organization_id, bootstrap_token=cmds.addr.token, root_verify_key=root_verify_key, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) _check_rep(rep, step_name="organization bootstrap") return device
async def do_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")
async def do_claim_user( self, requested_device_label: Optional[str], requested_human_handle: Optional[HumanHandle]) -> LocalDevice: private_key = PrivateKey.generate() signing_key = SigningKey.generate() try: payload = InviteUserData( requested_device_label=requested_device_label, requested_human_handle=requested_human_handle, public_key=private_key.public_key, verify_key=signing_key.verify_key, ).dump_and_encrypt(key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteUserData payload") from exc rep = await self._cmds.invite_4_claimer_communicate(payload=payload) if rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError( f"Backend error during step 4 (data exchange): {rep}") rep = await self._cmds.invite_4_claimer_communicate(payload=b"") if rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError( f"Backend error during step 4 (confirmation exchange): {rep}") try: confirmation = InviteUserConfirmation.decrypt_and_load( rep["payload"], key=self._shared_secret_key) except DataError as exc: raise InviteError( "Invalid InviteUserConfirmation payload provided by peer" ) from exc organization_addr = BackendOrganizationAddr.build( backend_addr=self._cmds.addr, organization_id=self._cmds.addr.organization_id, root_verify_key=confirmation.root_verify_key, ) new_device = generate_new_device( organization_addr=organization_addr, device_id=confirmation.device_id, device_label=confirmation.device_label, human_handle=confirmation.human_handle, profile=confirmation.profile, private_key=private_key, signing_key=signing_key, ) return new_device
async def 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
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)
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
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}")
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()
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(), )
async def _create_new_device_for_self( original_device: LocalDevice, new_device_label: DeviceLabel) -> LocalDevice: """ Raises: BackendConnectionError """ new_device = LocalDevice( organization_addr=original_device.organization_addr, device_id=DeviceID(f"{original_device.user_id}@{DeviceName.new()}"), device_label=new_device_label, human_handle=original_device.human_handle, profile=original_device.profile, private_key=original_device.private_key, signing_key=SigningKey.generate(), user_manifest_id=original_device.user_manifest_id, user_manifest_key=original_device.user_manifest_key, local_symkey=SecretKey.generate(), ) now = pendulum_now() device_certificate = DeviceCertificateContent( author=original_device.device_id, timestamp=now, device_id=new_device.device_id, device_label=new_device.device_label, verify_key=new_device.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) device_certificate = device_certificate.dump_and_sign( original_device.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( original_device.signing_key) async with backend_authenticated_cmds_factory( addr=original_device.organization_addr, device_id=original_device.device_id, signing_key=original_device.signing_key, ) as cmds: rep = await cmds.device_create( device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, ) if rep["status"] != "ok": raise BackendConnectionError(f"Cannot create recovery device: {rep}") return new_device
def _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
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())
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)
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(), )
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)
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)
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)
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(), )
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
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, )
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
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(), )
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
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