示例#1
0
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(),
    )
示例#2
0
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
示例#3
0
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,
    }
示例#4
0
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"],
    )
示例#5
0
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"
            )),
    )
示例#6
0
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
示例#7
0
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
示例#8
0
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"
            )),
    )
示例#9
0
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,
    )
示例#10
0
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
示例#11
0
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
示例#12
0
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
示例#13
0
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)
示例#14
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(),
    )
示例#15
0
    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(),
        )
示例#16
0
 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
     )
示例#17
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)
        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(),
        )
示例#18
0
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
示例#19
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(),
    )
示例#20
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
示例#21
0
 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,
     )
示例#22
0
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
示例#23
0
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)
示例#24
0
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)
示例#25
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(),
    )
示例#26
0
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
示例#27
0
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
示例#28
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
示例#29
0
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
示例#30
0
    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)