From d8d9e29a942b316011289d4c396f9015c6bd60ab Mon Sep 17 00:00:00 2001 From: Vincent Michel Date: Thu, 28 Nov 2024 18:43:04 +0100 Subject: [PATCH] Implement ShamirRecoveryClaimRecoverDeviceCtx::recover_device --- libparsec/crates/client/src/client/mod.rs | 3 +- .../client/src/client/recovery_device.rs | 4 +- libparsec/crates/client/src/invite/claimer.rs | 68 ++++++++++++++++++- .../crates/client/tests/unit/invite/shamir.rs | 19 +++++- 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/libparsec/crates/client/src/client/mod.rs b/libparsec/crates/client/src/client/mod.rs index 9fe2d8aec4a..899f7ebb50b 100644 --- a/libparsec/crates/client/src/client/mod.rs +++ b/libparsec/crates/client/src/client/mod.rs @@ -53,7 +53,8 @@ use libparsec_platform_async::lock::Mutex as AsyncMutex; use libparsec_types::prelude::*; pub use recovery_device::{ - import_recovery_device, ClientExportRecoveryDeviceError, ImportRecoveryDeviceError, + import_recovery_device, register_new_device, ClientExportRecoveryDeviceError, + ImportRecoveryDeviceError, }; // Re-exposed for public API diff --git a/libparsec/crates/client/src/client/recovery_device.rs b/libparsec/crates/client/src/client/recovery_device.rs index 7b1c6d2441f..7ee110b9aab 100644 --- a/libparsec/crates/client/src/client/recovery_device.rs +++ b/libparsec/crates/client/src/client/recovery_device.rs @@ -175,7 +175,7 @@ impl From for ImportRecoveryDeviceError { } } -async fn register_new_device( +pub async fn register_new_device( cmds: &AuthenticatedCmds, new_device: &LocalDevice, new_device_purpose: DevicePurpose, @@ -242,7 +242,7 @@ pub(crate) fn generate_new_device_certificates( } #[derive(Debug, thiserror::Error)] -enum RegisterNewDeviceError { +pub enum RegisterNewDeviceError { #[error("Component has stopped")] Stopped, #[error("Cannot reach the server")] diff --git a/libparsec/crates/client/src/invite/claimer.rs b/libparsec/crates/client/src/invite/claimer.rs index d9bed73d486..56e1a6f40f8 100644 --- a/libparsec/crates/client/src/invite/claimer.rs +++ b/libparsec/crates/client/src/invite/claimer.rs @@ -5,10 +5,12 @@ use std::num::NonZeroU8; use std::{path::PathBuf, sync::Arc}; use invited_cmds::latest::invite_claimer_step; +use libparsec_client_connection::AuthenticatedCmds; use libparsec_client_connection::{protocol::invited_cmds, ConnectionError, InvitedCmds}; use libparsec_protocol::invited_cmds::v4::invite_info::ShamirRecoveryRecipient; use libparsec_types::prelude::*; +use crate::client::register_new_device; use crate::invite::common::{Throttle, WAIT_PEER_MAX_ATTEMPTS}; use crate::ClientConfig; @@ -413,7 +415,71 @@ impl ShamirRecoveryClaimRecoverDeviceCtx { self, requested_device_label: DeviceLabel, ) -> Result { - panic!("{requested_device_label}") + let ciphered_data = { + use invited_cmds::latest::invite_shamir_recovery_reveal::{Rep, Req}; + + let rep = self + .cmds + .send(Req { + reveal_token: self.secret.reveal_token, + }) + .await?; + + match rep { + Rep::Ok { ciphered_data } => Ok(ciphered_data), + // TODO: specialize error + Rep::NotFound => Err(ClaimInProgressError::NotFound), + Rep::UnknownStatus { .. } => { + Err(anyhow::anyhow!("Unexpected server response: {:?}", rep).into()) + } + }? + }; + + let mut recovery_device = + LocalDevice::decrypt_and_load(&ciphered_data, &self.secret.data_key) + .map_err(|e| ClaimInProgressError::Internal(anyhow::Error::msg(e)))?; + + // When using the tested, the recovery device organization address is set to a placeholder address. + // This is because the organization address is not known yet when the testbed is initialized. + // In this case, we replace the placeholder address with the actual organization address. + if cfg!(test) + && recovery_device + .organization_addr + .to_string() + .starts_with("parsec3://parsec.invalid/PlaceholderOrg") + { + recovery_device.organization_addr = ParsecOrganizationAddr::new( + self.cmds.addr(), + self.cmds.addr().organization_id().clone(), + recovery_device.organization_addr.root_verify_key().clone(), + ); + } + + let recovery_device = Arc::new(recovery_device); + + let new_local_device = + LocalDevice::from_existing_device_for_user(&recovery_device, requested_device_label); + + let recovery_cmds = AuthenticatedCmds::new( + &self.config.config_dir, + recovery_device.clone(), + self.config.proxy.clone(), + )?; + + register_new_device( + &recovery_cmds, + &new_local_device, + DevicePurpose::Standard, + &recovery_device, + ) + .await + // TODO: specialize error + .map_err(|e| ClaimInProgressError::Internal(e.into()))?; + + Ok(ShamirRecoveryClaimFinalizeCtx { + config: self.config, + new_local_device: Arc::new(new_local_device), + }) } } diff --git a/libparsec/crates/client/tests/unit/invite/shamir.rs b/libparsec/crates/client/tests/unit/invite/shamir.rs index 678daabd65f..dddd35b5470 100644 --- a/libparsec/crates/client/tests/unit/invite/shamir.rs +++ b/libparsec/crates/client/tests/unit/invite/shamir.rs @@ -213,6 +213,9 @@ async fn shamir(tmp_path: TmpPath, env: &TestbedEnv) { let available_device = alice_finalize_ctx.save_local_device(&access).await.unwrap(); // Checks + let expected_server_url = ParsecAddr::from(bob.organization_addr.clone()) + .to_http_url(None) + .to_string(); p_assert_eq!(available_device.key_file_path, tmp_path.join("device.keys")); p_assert_eq!( available_device.organization_id, @@ -222,14 +225,24 @@ async fn shamir(tmp_path: TmpPath, env: &TestbedEnv) { p_assert_eq!(available_device.device_label, device_label); p_assert_eq!(available_device.human_handle, alice.human_handle); p_assert_eq!(available_device.ty, DeviceFileType::Password); - p_assert_eq!(available_device.server_url, "https://noserver.example.com/"); + p_assert_eq!( + available_device.organization_id, + bob.organization_id().clone() + ); + p_assert_eq!(available_device.server_url, expected_server_url); p_assert_eq!(available_device.user_id, alice.user_id); // created_on and protected_on date times not checked // Check device can be loaded - let reloaded_new_local_device = + let reloaded_new_alice_device = libparsec_platform_device_loader::load_device(&env.discriminant_dir, &access) .await .unwrap(); - p_assert_eq!(reloaded_new_local_device, new_local_device); + p_assert_eq!(reloaded_new_alice_device, new_local_device); + + let new_alice_client = + client_factory(&env.discriminant_dir, reloaded_new_alice_device.clone()).await; + + // Test server connection by deleting the shamir recovery + new_alice_client.delete_shamir_recovery().await.unwrap(); }