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(), )
def _save_device_with_password(key_file: Path, device: LocalDevice, password: str, force: bool) -> None: if key_file.exists() and not force: raise LocalDeviceAlreadyExistsError( f"Device key file `{key_file}` already exists") try: key, salt = derivate_secret_key_from_password(password) ciphertext = key.encrypt(device.dump()) except (CryptoError, DataError) as exc: raise LocalDeviceValidationError( f"Cannot dump local device: {exc}") from exc key_file_content = key_file_serializer.dumps({ "salt": salt, "ciphertext": ciphertext, "human_handle": device.human_handle, "device_label": device.device_label, "organization_id": device.organization_id, "device_id": device.device_id, "slug": device.slug, }) try: key_file.parent.mkdir(mode=0o700, exist_ok=True, parents=True) key_file.write_bytes(key_file_content) except OSError as exc: raise LocalDeviceError(f"Cannot save {key_file}: {exc}") from exc
def test_supports_legacy_is_admin_field(alice): # Manually craft a local user in legacy format raw_legacy_local_user = { "organization_addr": alice.organization_addr.to_url(), "device_id": str(alice.device_id), "signing_key": alice.signing_key.encode(), "private_key": alice.private_key.encode(), "is_admin": True, "user_manifest_id": UUID(alice.user_manifest_id.hex), "user_manifest_key": bytes(alice.user_manifest_key.secret), "local_symkey": bytes(alice.local_symkey.secret), } dumped_legacy_local_user = packb(raw_legacy_local_user) # Make sure the legacy format can be loaded legacy_local_user = LocalDevice.load(dumped_legacy_local_user) assert legacy_local_user == alice # Manually decode new format to check it is compatible with legacy dumped_local_user = alice.dump() raw_local_user = unpackb(dumped_local_user) assert raw_local_user == { **raw_legacy_local_user, "profile": alice.profile.value, "human_handle": None, "device_label": None, }
def _load_legacy_device_file(key_file_path: Path) -> Optional[AvailableDevice]: # For the legacy device files, the slug is contained in the device filename slug = key_file_path.stem try: organization_id, device_id = LocalDevice.load_slug(slug) except ValueError: # Not a valid slug, ignore this file return None try: data = legacy_key_file_serializer.loads(key_file_path.read_bytes()) except (FileNotFoundError, LocalDeviceError): # Not a valid device file, ignore this file return None return AvailableDevice( key_file_path=key_file_path, organization_id=organization_id, device_id=device_id, human_handle=data["human_handle"], device_label=data["device_label"], slug=slug, type=data["type"], )
def generate_BOB_local_device(): return LocalDevice( organization_addr=BackendOrganizationAddr.from_url( "parsec://bob_dev1.example.com:9999/CoolOrg?no_ssl=true&rvk=XYUXM4ZM5SGKSTXNZ4FK7VATZUKZGY7A7LOJ42CXFR32DYL5TO6Qssss" ), device_id=DeviceID("bob@dev1"), device_label=DeviceLabel("My dev1 machine"), human_handle=HumanHandle("*****@*****.**", "Boby McBobFace"), signing_key=SigningKey( unhexlify( "85f47472a2c0f30f01b769617db248f3ec8d96a490602a9262f95e9e43432b30" )), private_key=PrivateKey( unhexlify( "16767ec446f2611f971c36f19c2dc11614d853475ac395d6c1d70ba46d07dd49" )), profile=UserProfile.STANDARD, user_manifest_id=EntryID.from_hex("71568d41afcb4e2380b3d164ace4fb85"), user_manifest_key=SecretKey( unhexlify( "65de53d2c6cd965aa53a1ba5cc7e54b331419e6103466121996fa99a97197a48" )), local_symkey=SecretKey( unhexlify( "93f25b18491016f20b10dcf4eb7986716d914653d6ab4e778701c13435e6bdf0" )), )
def list_available_devices(config_dir: Path) -> List[AvailableDevice]: try: candidate_pathes = list(get_devices_dir(config_dir).iterdir()) except FileNotFoundError: return [] # Sanity checks devices = [] for device_folder_path in candidate_pathes: slug = device_folder_path.name key_file_path = device_folder_path / f"{slug}.keys" try: organization_id, device_id = LocalDevice.load_slug(slug) # Not a valid slug, ignore this folder except ValueError: continue try: data = key_file_serializer.loads(key_file_path.read_bytes()) except (FileNotFoundError, LocalDeviceError): # Not a valid device file, ignore this folder continue devices.append( AvailableDevice( key_file_path=key_file_path, organization_id=organization_id, device_id=device_id, human_handle=data["human_handle"], device_label=data["device_label"], )) return devices
def list_available_devices( config_dir: Path) -> List[Tuple[OrganizationID, DeviceID, str, Path]]: try: candidate_pathes = list(get_devices_dir(config_dir).iterdir()) except FileNotFoundError: return [] # Sanity checks devices = [] for device_path in candidate_pathes: slug = device_path.name try: organization_id, device_id = LocalDevice.load_slug(slug) except ValueError: continue key_file = device_path / f"{slug}.keys" try: cipher = get_cipher_info(key_file) devices.append((organization_id, device_id, cipher, key_file)) except (LocalDeviceNotFoundError, LocalDeviceCryptoError): continue return devices
def generate_ALICE_local_device(): return LocalDevice( organization_addr=BackendOrganizationAddr.from_url( "parsec://alice_dev1.example.com:9999/CoolOrg?no_ssl=true&rvk=XYUXM4ZM5SGKSTXNZ4FK7VATZUKZGY7A7LOJ42CXFR32DYL5TO6Qssss" ), device_id=DeviceID("alice@dev1"), device_label=DeviceLabel("My dev1 machine"), human_handle=HumanHandle("*****@*****.**", "Alicey McAliceFace"), signing_key=SigningKey( unhexlify( "d544f66ece9c85d5b80275db9124b5f04bb038081622bed139c1e789c5217400" )), private_key=PrivateKey( unhexlify( "74e860967fd90d063ebd64fb1ba6824c4c010099dd37508b7f2875a5db2ef8c9" )), profile=UserProfile.ADMIN, user_manifest_id=EntryID.from_hex("a4031e8bcdd84df8ae12bd3d05e6e20f"), user_manifest_key=SecretKey( unhexlify( "26bf35a98c1e54e90215e154af92a1af2d1142cdd0dba25b990426b0b30b0f9a" )), local_symkey=SecretKey( unhexlify( "125a78618995e2e0f9a19bc8617083c809c03deb5457d5b82df5bcaec9966cd4" )), )
def _create_new_user_certificates( author: LocalDevice, device_label: Optional[DeviceLabel], human_handle: Optional[HumanHandle], profile: UserProfile, public_key: PublicKey, verify_key: VerifyKey, ) -> Tuple[bytes, bytes, bytes, bytes, InviteUserConfirmation]: """Helper to prepare the creation of a new user.""" device_id = DeviceID.new() try: timestamp = author.timestamp() user_certificate = UserCertificateContent( author=author.device_id, timestamp=timestamp, user_id=device_id.user_id, human_handle=human_handle, public_key=public_key, profile=profile, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=timestamp, device_id=device_id, device_label=device_label, verify_key=verify_key, ) redacted_device_certificate = device_certificate.evolve( device_label=None) user_certificate = user_certificate.dump_and_sign(author.signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( author.signing_key) device_certificate = device_certificate.dump_and_sign( author.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( author.signing_key) except DataError as exc: raise InviteError( f"Cannot generate device certificate: {exc}") from exc invite_user_confirmation = InviteUserConfirmation( device_id=device_id, device_label=device_label, human_handle=human_handle, profile=profile, root_verify_key=author.root_verify_key, ) return ( user_certificate, redacted_user_certificate, device_certificate, redacted_device_certificate, invite_user_confirmation, )
def _save_device( key_file: Path, device: LocalDevice, force: bool, encrypt_dump: Callable[[bytes], Tuple[DeviceFileType, bytes, dict]], ) -> None: assert key_file.suffix == DEVICE_FILE_SUFFIX if key_file.exists() and not force: raise LocalDeviceAlreadyExistsError( f"Device key file `{key_file}` already exists") cleartext = device.dump() type, ciphertext, extra_args = encrypt_dump(cleartext) key_file_content = key_file_serializer.dumps({ "type": type, **extra_args, "ciphertext": ciphertext, "human_handle": device.human_handle, "device_label": device.device_label, "organization_id": device.organization_id, "device_id": device.device_id, "slug": device.slug, }) try: key_file.parent.mkdir(mode=0o700, exist_ok=True, parents=True) key_file.write_bytes(key_file_content) except OSError as exc: raise LocalDeviceError(f"Cannot save {key_file}: {exc}") from exc
def load_device_with_password(key_file: Path, password: str) -> LocalDevice: """ LocalDeviceNotFoundError LocalDeviceCryptoError LocalDeviceValidationError LocalDevicePackingError """ try: ciphertext = key_file.read_bytes() except OSError as exc: raise LocalDeviceNotFoundError( f"Config file `{key_file}` is missing") from exc try: data = key_file_serializer.loads(ciphertext) except LocalDeviceError: data = legacy_key_file_serializer.loads(ciphertext) try: key, _ = derivate_secret_key_from_password(password, data["salt"]) plaintext = key.decrypt(data["ciphertext"]) except CryptoError as exc: raise LocalDeviceCryptoError(str(exc)) from exc try: return LocalDevice.load(plaintext) except DataError as exc: raise LocalDeviceValidationError( f"Cannot load local device: {exc}") from exc
def _save_device(key_file: Path, device: LocalDevice, encryptor: BaseLocalDeviceEncryptor, force: bool = False) -> None: """ Raises: LocalDeviceError LocalDeviceAlreadyExistsError LocalDeviceCryptoError LocalDeviceValidationError LocalDevicePackingError """ if key_file.exists() and not force: raise LocalDeviceAlreadyExistsError( f"Device `{device.organization_id}:{device.device_id}` already exists" ) try: raw = device.dump() except DataError as exc: raise LocalDeviceValidationError( f"Cannot dump local device: {exc}") from exc ciphertext = encryptor.encrypt(raw) try: key_file.parent.mkdir(exist_ok=True, parents=True) key_file.write_bytes(ciphertext) except OSError as exc: raise LocalDeviceError(f"Cannot save {key_file}: {exc}") from exc
def correct_addr(to_correct: Union[BackendAddr, LocalDevice, OrganizationFullData], port: int) -> BackendAddr: """ Helper to fix a backend address so that it will reach the current server. This not needed when using `running_backend` (given in this case the alice/bob/coolorg etc. fixtures are created with the correct port), but must be used when the test has to manually start server (e.g. in the hypothesis tests) """ if isinstance(to_correct, LocalDevice): return LocalDevice( organization_addr=correct_addr(to_correct.organization_addr, port), device_id=to_correct.device_id, device_label=to_correct.device_label, human_handle=to_correct.human_handle, signing_key=to_correct.signing_key, private_key=to_correct.private_key, profile=to_correct.profile, user_manifest_id=to_correct.user_manifest_id, user_manifest_key=to_correct.user_manifest_key, local_symkey=to_correct.local_symkey, ) elif isinstance(to_correct, OrganizationFullData): return OrganizationFullData( bootstrap_addr=correct_addr(to_correct.bootstrap_addr, port), addr=correct_addr(to_correct.addr, port), root_signing_key=to_correct.root_signing_key, ) else: # Consider it's a regular addr *_, to_keep = to_correct.to_url().removeprefix("parsec://").split( "/", 1) url = f"parsec://127.0.0.1:{port}/{to_keep}" return to_correct.__class__.from_url(url)
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 do_claim_device( self, requested_device_label: Optional[DeviceLabel]) -> LocalDevice: # Device key is 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 certificate in # the server. # This is considered acceptable given 1) the error window is small and # 2) if this occurs the inviter can revoke the device and retry the # enrollment process to fix this 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.get_backend_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(), )
async def bind_revocation(self, user_id: UserID, certifier: LocalDevice): timestamp = certifier.timestamp() revoked_user_certificate = RevokedUserCertificateContent( author=certifier.device_id, timestamp=timestamp, user_id=user_id ).dump_and_sign(certifier.signing_key) await self.backend.user.revoke_user( certifier.organization_id, user_id, revoked_user_certificate, certifier.device_id ) self.certificates_store.store_revoked_user( certifier.organization_id, user_id, revoked_user_certificate )
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) 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 = 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 local_device_to_backend_user( device: LocalDevice, certifier: Union[LocalDevice, OrganizationFullData] ) -> Tuple[BackendUser, BackendDevice]: if isinstance(certifier, OrganizationFullData): certifier_id = None certifier_signing_key = certifier.root_signing_key else: certifier_id = certifier.device_id certifier_signing_key = certifier.signing_key timestamp = device.timestamp() user_certificate = UserCertificateContent( author=certifier_id, timestamp=timestamp, user_id=device.user_id, public_key=device.public_key, profile=device.profile, human_handle=device.human_handle, ) device_certificate = DeviceCertificateContent( author=certifier_id, timestamp=timestamp, device_id=device.device_id, device_label=device.device_label, verify_key=device.verify_key, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) redacted_device_certificate = device_certificate.evolve(device_label=None) user = BackendUser( user_id=device.user_id, human_handle=device.human_handle, profile=device.profile, user_certificate=user_certificate.dump_and_sign(certifier_signing_key), redacted_user_certificate=redacted_user_certificate.dump_and_sign( certifier_signing_key), user_certifier=certifier_id, created_on=timestamp, ) first_device = BackendDevice( device_id=device.device_id, device_label=device.device_label, device_certificate=device_certificate.dump_and_sign( certifier_signing_key), redacted_device_certificate=redacted_device_certificate.dump_and_sign( certifier_signing_key), device_certifier=certifier_id, created_on=timestamp, ) return user, first_device
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 _correct_local_device_backend_addr(device): organization_addr = BackendOrganizationAddr.build( backend_addr, organization_id=device.organization_addr.organization_id, root_verify_key=device.organization_addr.root_verify_key, ) return LocalDevice( organization_addr=organization_addr, device_id=device.device_id, device_label=device.device_label, human_handle=device.human_handle, signing_key=device.signing_key, private_key=device.private_key, profile=device.profile, user_manifest_id=device.user_manifest_id, user_manifest_key=device.user_manifest_key, local_symkey=device.local_symkey, )
async def load_recovery_device(key_file: PurePath, passphrase: str) -> LocalDevice: """ Raises: LocalDeviceError LocalDeviceNotFoundError LocalDeviceCryptoError LocalDeviceValidationError LocalDevicePackingError """ key_file = trio.Path(key_file) try: ciphertext = await key_file.read_bytes() except OSError as exc: raise LocalDeviceNotFoundError( f"Recovery file `{key_file}` is missing") from exc try: data = key_file_serializer.loads(ciphertext) except LocalDevicePackingError as exc: raise LocalDeviceValidationError("Not a device recovery file") from exc if data["type"] != DeviceFileType.RECOVERY: raise LocalDeviceValidationError("Not a device recovery file") try: key = derivate_secret_key_from_recovery_passphrase(passphrase) except ValueError as exc: # Not really a crypto operation, but it is more coherent for the caller raise LocalDeviceCryptoError("Invalid passphrase") from exc try: plaintext = key.decrypt(data["ciphertext"]) except CryptoError as exc: raise LocalDeviceCryptoError(str(exc)) from exc try: return LocalDevice.load(plaintext) except DataError as exc: raise LocalDeviceValidationError( f"Cannot load local device: {exc}") from exc
async def user_storage_non_speculative_init(data_base_dir: Path, device: LocalDevice) -> None: data_path = get_user_data_storage_db_path(data_base_dir, device) # Local data storage service async with LocalDatabase.run(data_path) as localdb: # Manifest storage service async with ManifestStorage.run( device, localdb, device.user_manifest_id) as manifest_storage: timestamp = device.timestamp() manifest = LocalUserManifest.new_placeholder( author=device.device_id, id=device.user_manifest_id, timestamp=timestamp, speculative=False, ) await manifest_storage.set_manifest(device.user_manifest_id, manifest)
async def workspace_storage_non_speculative_init( data_base_dir: Path, device: LocalDevice, workspace_id: EntryID) -> None: db_path = get_workspace_data_storage_db_path(data_base_dir, device, workspace_id) # Local data storage service async with LocalDatabase.run(db_path) as data_localdb: # Manifest storage service async with ManifestStorage.run(device, data_localdb, workspace_id) as manifest_storage: timestamp = device.timestamp() manifest = LocalWorkspaceManifest.new_placeholder( author=device.device_id, id=workspace_id, timestamp=timestamp, speculative=False) await manifest_storage.set_manifest(workspace_id, manifest)
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 save_recovery_device(key_file: PurePath, device: LocalDevice, force: bool = False) -> str: """ Return the recovery passphrase """ assert key_file.suffix == RECOVERY_DEVICE_FILE_SUFFIX key_file = trio.Path(key_file) if await key_file.exists() and not force: raise LocalDeviceAlreadyExistsError( f"Device key file `{key_file}` already exists") passphrase, key = generate_recovery_passphrase() try: ciphertext = key.encrypt(device.dump()) except (CryptoError, DataError) as exc: raise LocalDeviceValidationError( f"Cannot dump local device: {exc}") from exc key_file_content = key_file_serializer.dumps({ "type": DeviceFileType.RECOVERY, "ciphertext": ciphertext, "human_handle": device.human_handle, "device_label": device.device_label, "organization_id": device.organization_id, "device_id": device.device_id, "slug": device.slug, }) try: await key_file.parent.mkdir(mode=0o700, exist_ok=True, parents=True) await key_file.write_bytes(key_file_content) except OSError as exc: raise LocalDeviceError(f"Cannot save {key_file}: {exc}") from exc return passphrase
def _load_device(key_file: Path, decryptor: BaseLocalDeviceDecryptor) -> LocalDevice: """ Raises: LocalDeviceNotFoundError LocalDeviceCryptoError LocalDeviceValidationError LocalDevicePackingError """ try: ciphertext = key_file.read_bytes() except OSError as exc: raise LocalDeviceNotFoundError( f"Config file {key_file} is missing") from exc raw = decryptor.decrypt(ciphertext) try: return LocalDevice.load(raw) except DataError as exc: raise LocalDeviceValidationError( f"Cannot load local device: {exc}") from exc
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
def _load_device(key_file: Path, decrypt_ciphertext: Callable[[dict], bytes]) -> LocalDevice: try: ciphertext = key_file.read_bytes() except OSError as exc: raise LocalDeviceNotFoundError( f"Config file `{key_file}` is missing") from exc try: data = key_file_serializer.loads(ciphertext) except LocalDeviceError: data = legacy_key_file_serializer.loads(ciphertext) plaintext = decrypt_ciphertext(data) try: local_device = LocalDevice.load(plaintext) except DataError as exc: raise LocalDeviceValidationError( f"Cannot load local device: {exc}") from exc _KEY_FILE_DATA[local_device.device_id] = data return local_device
async def do_create_new_device( self, author: LocalDevice, device_label: Optional[DeviceLabel]) -> None: device_id = author.user_id.to_device_id(DeviceName.new()) try: timestamp = author.timestamp() device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=timestamp, device_id=device_id, device_label=device_label, verify_key=self._verify_key, ) redacted_device_certificate = device_certificate.evolve( device_label=None) device_certificate = device_certificate.dump_and_sign( author.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( author.signing_key) except DataError as exc: raise InviteError( f"Cannot generate device certificate: {exc}") from exc rep = await self._cmds.device_create( device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, ) _check_rep(rep, step_name="step 4 (device certificates upload)") # From now on the device has been created on the server, but greeter # is not aware of it yet. If something goes wrong, we can end up with # the greeter losing it private keys. # This is considered acceptable given 1) the error window is small and # 2) if this occurs the inviter can revoke the device and retry the # enrollment process to fix this try: payload = InviteDeviceConfirmation( device_id=device_id, device_label=device_label, human_handle=author.human_handle, profile=author.profile, private_key=author.private_key, user_manifest_id=author.user_manifest_id, user_manifest_key=author.user_manifest_key, root_verify_key=author.root_verify_key, ).dump_and_encrypt(key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteUserConfirmation payload") from exc rep = await self._cmds.invite_4_greeter_communicate(token=self.token, payload=payload) _check_rep(rep, step_name="step 4 (confirmation exchange)") # Invitation deletion is not strictly necessary (enrollment has succeeded # anyway) so it's no big deal if something goes wrong before it can be # done (and it can be manually deleted from invitation list). await self._cmds.invite_delete(token=self.token, reason=InvitationDeletedReason.FINISHED)