checkpoint
This commit is contained in:
		| @@ -88,11 +88,7 @@ impl ServicesContext { | |||||||
|  |  | ||||||
|         // Set up crypto |         // Set up crypto | ||||||
|         trace!("init crypto"); |         trace!("init crypto"); | ||||||
|         let crypto = Crypto::new( |         let crypto = Crypto::new(self.config.clone(), table_store.clone()); | ||||||
|             self.config.clone(), |  | ||||||
|             table_store.clone(), |  | ||||||
|             protected_store.clone(), |  | ||||||
|         ); |  | ||||||
|         if let Err(e) = crypto.init().await { |         if let Err(e) = crypto.init().await { | ||||||
|             error!("failed to init crypto: {}", e); |             error!("failed to init crypto: {}", e); | ||||||
|             self.shutdown().await; |             self.shutdown().await; | ||||||
|   | |||||||
| @@ -82,7 +82,6 @@ struct CryptoInner { | |||||||
| struct CryptoUnlockedInner { | struct CryptoUnlockedInner { | ||||||
|     config: VeilidConfig, |     config: VeilidConfig, | ||||||
|     table_store: TableStore, |     table_store: TableStore, | ||||||
|     protected_store: ProtectedStore, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Crypto factory implementation | /// Crypto factory implementation | ||||||
| @@ -104,16 +103,11 @@ impl Crypto { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn new( |     pub fn new(config: VeilidConfig, table_store: TableStore) -> Self { | ||||||
|         config: VeilidConfig, |  | ||||||
|         table_store: TableStore, |  | ||||||
|         protected_store: ProtectedStore, |  | ||||||
|     ) -> Self { |  | ||||||
|         let out = Self { |         let out = Self { | ||||||
|             unlocked_inner: Arc::new(CryptoUnlockedInner { |             unlocked_inner: Arc::new(CryptoUnlockedInner { | ||||||
|                 config, |                 config, | ||||||
|                 table_store, |                 table_store, | ||||||
|                 protected_store, |  | ||||||
|             }), |             }), | ||||||
|             inner: Arc::new(Mutex::new(Self::new_inner())), |             inner: Arc::new(Mutex::new(Self::new_inner())), | ||||||
|         }; |         }; | ||||||
|   | |||||||
| @@ -163,19 +163,87 @@ impl TableStore { | |||||||
|         self.flush().await; |         self.flush().await; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async fn load_device_encryption_key(&self) -> EyreResult<Option<TypedSharedSecret>> { | ||||||
|  |         let dek_bytes: Option<Vec<u8>> = self | ||||||
|  |             .protected_store | ||||||
|  |             .load_user_secret("device_encryption_key") | ||||||
|  |             .await?; | ||||||
|  |         let Some(dek_bytes) = dek_bytes else { | ||||||
|  |             return Ok(None); | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // Ensure the key is at least as long as necessary if unencrypted | ||||||
|  |         if dek_bytes.len() < (4 + SHARED_SECRET_LENGTH) { | ||||||
|  |             bail!("device encryption key is not valid"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Get cryptosystem | ||||||
|  |         let kind = FourCC::try_from(&dek_bytes[0..4]).unwrap(); | ||||||
|  |         let crypto = self.inner.lock().crypto.as_ref().unwrap().clone(); | ||||||
|  |         let Some(vcrypto) = crypto.get(kind) else { | ||||||
|  |             bail!("unsupported cryptosystem"); | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // Decrypt encryption key if we have it | ||||||
|  |         let device_encryption_key_password = { | ||||||
|  |             let c = self.config.get(); | ||||||
|  |             c.protected_store.device_encryption_key_password.clone() | ||||||
|  |         }; | ||||||
|  |         if !device_encryption_key_password.is_empty() { | ||||||
|  |             if dek_bytes.len() | ||||||
|  |                 != (4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH) | ||||||
|  |             { | ||||||
|  |                 bail!("password protected device encryption key is not valid"); | ||||||
|  |             } | ||||||
|  |             let protected_key = &dek_bytes[4..(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead())]; | ||||||
|  |             let nonce = &dek_bytes[(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead())..]; | ||||||
|  |  | ||||||
|  |             let shared_secret = vcrypto | ||||||
|  |                 .derive_shared_secret(device_encryption_key_password.as_bytes(), &nonce) | ||||||
|  |                 .wrap_err("failed to derive shared secret")?; | ||||||
|  |             let unprotected_key = vcrypto | ||||||
|  |                 .decrypt_aead( | ||||||
|  |                     &protected_key, | ||||||
|  |                     &Nonce::try_from(nonce).wrap_err("invalid nonce")?, | ||||||
|  |                     &shared_secret, | ||||||
|  |                     None, | ||||||
|  |                 ) | ||||||
|  |                 .wrap_err("failed to decrypt device encryption key")?; | ||||||
|  |             return Ok(Some(TypedSharedSecret::new( | ||||||
|  |                 kind, | ||||||
|  |                 SharedSecret::try_from(unprotected_key.as_slice()) | ||||||
|  |                     .wrap_err("invalid shared secret")?, | ||||||
|  |             ))); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Ok(Some(TypedSharedSecret::new( | ||||||
|  |             kind, | ||||||
|  |             SharedSecret::try_from(&dek_bytes[4..])?, | ||||||
|  |         ))) | ||||||
|  |     } | ||||||
|  |     async fn save_device_encryption_key( | ||||||
|  |         &self, | ||||||
|  |         device_encryption_key: Option<TypedSharedSecret>, | ||||||
|  |     ) -> EyreResult<()> { | ||||||
|  |         // Save the new device encryption key | ||||||
|  |         self.protected_store | ||||||
|  |             .save_user_secret_json("device_encryption_key", &device_encryption_key) | ||||||
|  |             .await?; | ||||||
|  |  | ||||||
|  | xxxx | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     pub(crate) async fn init(&self) -> EyreResult<()> { |     pub(crate) async fn init(&self) -> EyreResult<()> { | ||||||
|         let _async_guard = self.async_lock.lock().await; |         let _async_guard = self.async_lock.lock().await; | ||||||
|  |  | ||||||
|         // Get device encryption key from protected store |         // Get device encryption key from protected store | ||||||
|         let mut encryption_key: Option<TypedSharedSecret> = self |         let mut device_encryption_key = self.load_device_encryption_key().await?; | ||||||
|             .protected_store |         let mut device_encryption_key_changed = false; | ||||||
|             .load_user_secret_json("device_encryption_key") |         if let Some(device_encryption_key) = device_encryption_key { | ||||||
|             .await?; |  | ||||||
|  |  | ||||||
|         if let Some(encryption_key) = encryption_key { |  | ||||||
|             // If encryption in current use is not the best encryption, then run table migration |             // If encryption in current use is not the best encryption, then run table migration | ||||||
|             let best_kind = best_crypto_kind(); |             let best_kind = best_crypto_kind(); | ||||||
|             if encryption_key.kind != best_kind { |             if device_encryption_key.kind != best_kind { | ||||||
|                 // XXX: Run migration. See issue #209 |                 // XXX: Run migration. See issue #209 | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
| @@ -183,14 +251,14 @@ impl TableStore { | |||||||
|             let best_kind = best_crypto_kind(); |             let best_kind = best_crypto_kind(); | ||||||
|             let mut shared_secret = SharedSecret::default(); |             let mut shared_secret = SharedSecret::default(); | ||||||
|             random_bytes(&mut shared_secret.bytes); |             random_bytes(&mut shared_secret.bytes); | ||||||
|             let device_encryption_key = TypedSharedSecret::new(best_kind, shared_secret); |  | ||||||
|  |  | ||||||
|             // Save the new device encryption key |             device_encryption_key = Some(TypedSharedSecret::new(best_kind, shared_secret)); | ||||||
|             self.protected_store |             device_encryption_key_changed = true; | ||||||
|                 .save_user_secret_json("device_encryption_key", &device_encryption_key) |         } | ||||||
|  |  | ||||||
|  |         if device_encryption_key_changed { | ||||||
|  |             self.save_device_encryption_key(device_encryption_key) | ||||||
|                 .await?; |                 .await?; | ||||||
|  |  | ||||||
|             encryption_key = Some(device_encryption_key); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Deserialize all table names |         // Deserialize all table names | ||||||
| @@ -220,7 +288,7 @@ impl TableStore { | |||||||
|  |  | ||||||
|         { |         { | ||||||
|             let mut inner = self.inner.lock(); |             let mut inner = self.inner.lock(); | ||||||
|             inner.encryption_key = encryption_key; |             inner.encryption_key = device_encryption_key; | ||||||
|             inner.all_tables_db = Some(all_tables_db); |             inner.all_tables_db = Some(all_tables_db); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -431,6 +431,8 @@ pub struct VeilidConfigProtectedStore { | |||||||
|     pub always_use_insecure_storage: bool, |     pub always_use_insecure_storage: bool, | ||||||
|     pub directory: String, |     pub directory: String, | ||||||
|     pub delete: bool, |     pub delete: bool, | ||||||
|  |     pub device_encryption_key_password: String, | ||||||
|  |     pub new_device_encryption_key_password: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive( | #[derive( | ||||||
| @@ -632,6 +634,8 @@ impl VeilidConfig { | |||||||
|             get_config!(inner.protected_store.always_use_insecure_storage); |             get_config!(inner.protected_store.always_use_insecure_storage); | ||||||
|             get_config!(inner.protected_store.directory); |             get_config!(inner.protected_store.directory); | ||||||
|             get_config!(inner.protected_store.delete); |             get_config!(inner.protected_store.delete); | ||||||
|  |             get_config!(inner.protected_store.device_encryption_key_password); | ||||||
|  |             get_config!(inner.protected_store.new_device_encryption_key_password); | ||||||
|             get_config!(inner.network.connection_initial_timeout_ms); |             get_config!(inner.network.connection_initial_timeout_ms); | ||||||
|             get_config!(inner.network.connection_inactivity_timeout_ms); |             get_config!(inner.network.connection_inactivity_timeout_ms); | ||||||
|             get_config!(inner.network.max_connections_per_ip4); |             get_config!(inner.network.max_connections_per_ip4); | ||||||
| @@ -925,7 +929,7 @@ impl VeilidConfig { | |||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     //xxx#[cfg(not(test))] |     #[cfg(not(test))] | ||||||
|     async fn init_node_id( |     async fn init_node_id( | ||||||
|         &self, |         &self, | ||||||
|         vcrypto: CryptoSystemVersion, |         vcrypto: CryptoSystemVersion, | ||||||
|   | |||||||
| @@ -66,6 +66,8 @@ Future<VeilidConfig> getDefaultVeilidConfig(String programName) async { | |||||||
|       alwaysUseInsecureStorage: false, |       alwaysUseInsecureStorage: false, | ||||||
|       directory: "", |       directory: "", | ||||||
|       delete: false, |       delete: false, | ||||||
|  |       deviceEncryptionKey: "", | ||||||
|  |       newDeviceEncryptionKey: null, | ||||||
|     ), |     ), | ||||||
|     tableStore: VeilidConfigTableStore( |     tableStore: VeilidConfigTableStore( | ||||||
|       directory: kIsWeb |       directory: kIsWeb | ||||||
|   | |||||||
| @@ -828,13 +828,16 @@ class VeilidConfigProtectedStore { | |||||||
|   bool alwaysUseInsecureStorage; |   bool alwaysUseInsecureStorage; | ||||||
|   String directory; |   String directory; | ||||||
|   bool delete; |   bool delete; | ||||||
|  |   String deviceEncryptionKey; | ||||||
|  |   String? newDeviceEncryptionKey; | ||||||
|  |  | ||||||
|   VeilidConfigProtectedStore({ |   VeilidConfigProtectedStore( | ||||||
|     required this.allowInsecureFallback, |       {required this.allowInsecureFallback, | ||||||
|       required this.alwaysUseInsecureStorage, |       required this.alwaysUseInsecureStorage, | ||||||
|       required this.directory, |       required this.directory, | ||||||
|       required this.delete, |       required this.delete, | ||||||
|   }); |       required this.deviceEncryptionKey, | ||||||
|  |       String? newDeviceEncryptionKey}); | ||||||
|  |  | ||||||
|   Map<String, dynamic> toJson() { |   Map<String, dynamic> toJson() { | ||||||
|     return { |     return { | ||||||
| @@ -842,6 +845,8 @@ class VeilidConfigProtectedStore { | |||||||
|       'always_use_insecure_storage': alwaysUseInsecureStorage, |       'always_use_insecure_storage': alwaysUseInsecureStorage, | ||||||
|       'directory': directory, |       'directory': directory, | ||||||
|       'delete': delete, |       'delete': delete, | ||||||
|  |       'device_encryption_key': deviceEncryptionKey, | ||||||
|  |       'new_device_encryption_key': newDeviceEncryptionKey, | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -849,7 +854,9 @@ class VeilidConfigProtectedStore { | |||||||
|       : allowInsecureFallback = json['allow_insecure_fallback'], |       : allowInsecureFallback = json['allow_insecure_fallback'], | ||||||
|         alwaysUseInsecureStorage = json['always_use_insecure_storage'], |         alwaysUseInsecureStorage = json['always_use_insecure_storage'], | ||||||
|         directory = json['directory'], |         directory = json['directory'], | ||||||
|         delete = json['delete']; |         delete = json['delete'], | ||||||
|  |         deviceEncryptionKey = json['device_encryption_key'], | ||||||
|  |         newDeviceEncryptionKey = json['new_device_encryption_key']; | ||||||
| } | } | ||||||
|  |  | ||||||
| //////////// | //////////// | ||||||
|   | |||||||
| @@ -42,6 +42,19 @@ fn do_clap_matches(default_config_path: &OsStr) -> Result<clap::ArgMatches, clap | |||||||
|                 .multiple_occurrences(true) |                 .multiple_occurrences(true) | ||||||
|                 .help("Specify configuration value to set (key in dot format, value in json format), eg: logging.api.enabled=true") |                 .help("Specify configuration value to set (key in dot format, value in json format), eg: logging.api.enabled=true") | ||||||
|         ) |         ) | ||||||
|  |         .arg( | ||||||
|  |             Arg::new("password") | ||||||
|  |                 .short('p') | ||||||
|  |                 .long("password") | ||||||
|  |                 .takes_value(true) | ||||||
|  |                 .help("Specify password to use to protect the device encryption key") | ||||||
|  |         ) | ||||||
|  |         .arg( | ||||||
|  |             Arg::new("new-password") | ||||||
|  |                 .long("new-password") | ||||||
|  |                 .takes_value(true) | ||||||
|  |                 .help("Change password used to protect the device encryption key. Device storage will be migrated.") | ||||||
|  |         )         | ||||||
|         .arg( |         .arg( | ||||||
|             Arg::new("attach") |             Arg::new("attach") | ||||||
|                 .long("attach") |                 .long("attach") | ||||||
|   | |||||||
| @@ -6,13 +6,14 @@ use serde_derive::*; | |||||||
| use std::ffi::OsStr; | use std::ffi::OsStr; | ||||||
| use std::net::SocketAddr; | use std::net::SocketAddr; | ||||||
| use std::path::{Path, PathBuf}; | use std::path::{Path, PathBuf}; | ||||||
|  | use std::sync::Arc; | ||||||
| use sysinfo::{DiskExt, SystemExt}; | use sysinfo::{DiskExt, SystemExt}; | ||||||
| use url::Url; | use url::Url; | ||||||
| use veilid_core::tools::*; | use veilid_core::tools::*; | ||||||
| use veilid_core::*; | use veilid_core::*; | ||||||
|  |  | ||||||
| pub fn load_default_config() -> EyreResult<config::Config> { | pub fn load_default_config() -> EyreResult<config::Config> { | ||||||
|     let default_config = String::from( |     let mut default_config = String::from( | ||||||
|         r#"--- |         r#"--- | ||||||
| daemon: | daemon: | ||||||
|     enabled: false |     enabled: false | ||||||
| @@ -49,6 +50,8 @@ core: | |||||||
|         always_use_insecure_storage: true |         always_use_insecure_storage: true | ||||||
|         directory: '%DIRECTORY%' |         directory: '%DIRECTORY%' | ||||||
|         delete: false |         delete: false | ||||||
|  |         device_encryption_key_password: '%DEVICE_ENCRYPTION_KEY_PASSWORD%' | ||||||
|  |         new_device_encryption_key_password: %NEW_DEVICE_ENCRYPTION_KEY_PASSWORD% | ||||||
|     table_store: |     table_store: | ||||||
|         directory: '%TABLE_STORE_DIRECTORY%' |         directory: '%TABLE_STORE_DIRECTORY%' | ||||||
|         delete: false |         delete: false | ||||||
| @@ -176,6 +179,30 @@ core: | |||||||
|         "%REMOTE_MAX_SUBKEY_CACHE_MEMORY_MB%", |         "%REMOTE_MAX_SUBKEY_CACHE_MEMORY_MB%", | ||||||
|         &Settings::get_default_remote_max_subkey_cache_memory_mb().to_string(), |         &Settings::get_default_remote_max_subkey_cache_memory_mb().to_string(), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     let dek_password = if let Some(dek_password) = std::env::var_os("DEK_PASSWORD") { | ||||||
|  |         dek_password | ||||||
|  |             .to_str() | ||||||
|  |             .ok_or_else(|| eyre!("DEK_PASSWORD is not valid unicode"))? | ||||||
|  |             .to_owned() | ||||||
|  |     } else { | ||||||
|  |         "".to_owned() | ||||||
|  |     }; | ||||||
|  |     default_config = default_config.replace("%DEVICE_ENCRYPTION_KEY_PASSWORD%", &dek_password); | ||||||
|  |  | ||||||
|  |     let new_dek_password = if let Some(new_dek_password) = std::env::var_os("NEW_DEK_PASSWORD") { | ||||||
|  |         format!( | ||||||
|  |             "'{}'", | ||||||
|  |             new_dek_password | ||||||
|  |                 .to_str() | ||||||
|  |                 .ok_or_else(|| eyre!("NEW_DEK_PASSWORD is not valid unicode"))? | ||||||
|  |         ) | ||||||
|  |     } else { | ||||||
|  |         "null".to_owned() | ||||||
|  |     }; | ||||||
|  |     default_config = | ||||||
|  |         default_config.replace("%NEW_DEVICE_ENCRYPTION_KEY_PASSWORD%", &new_dek_password); | ||||||
|  |  | ||||||
|     config::Config::builder() |     config::Config::builder() | ||||||
|         .add_source(config::File::from_str( |         .add_source(config::File::from_str( | ||||||
|             &default_config, |             &default_config, | ||||||
| @@ -588,6 +615,8 @@ pub struct ProtectedStore { | |||||||
|     pub always_use_insecure_storage: bool, |     pub always_use_insecure_storage: bool, | ||||||
|     pub directory: PathBuf, |     pub directory: PathBuf, | ||||||
|     pub delete: bool, |     pub delete: bool, | ||||||
|  |     pub device_encryption_key_password: String, | ||||||
|  |     pub new_device_encryption_key_password: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize, Serialize)] | #[derive(Debug, Deserialize, Serialize)] | ||||||
| @@ -937,6 +966,17 @@ impl Settings { | |||||||
|         ); |         ); | ||||||
|         set_config_value!(inner.core.protected_store.directory, value); |         set_config_value!(inner.core.protected_store.directory, value); | ||||||
|         set_config_value!(inner.core.protected_store.delete, value); |         set_config_value!(inner.core.protected_store.delete, value); | ||||||
|  |         set_config_value!( | ||||||
|  |             inner.core.protected_store.device_encryption_key_password, | ||||||
|  |             value | ||||||
|  |         ); | ||||||
|  |         set_config_value!( | ||||||
|  |             inner | ||||||
|  |                 .core | ||||||
|  |                 .protected_store | ||||||
|  |                 .new_device_encryption_key_password, | ||||||
|  |             value | ||||||
|  |         ); | ||||||
|         set_config_value!(inner.core.table_store.directory, value); |         set_config_value!(inner.core.table_store.directory, value); | ||||||
|         set_config_value!(inner.core.table_store.delete, value); |         set_config_value!(inner.core.table_store.delete, value); | ||||||
|         set_config_value!(inner.core.block_store.directory, value); |         set_config_value!(inner.core.block_store.directory, value); | ||||||
| @@ -1071,6 +1111,20 @@ impl Settings { | |||||||
|                         .to_string(), |                         .to_string(), | ||||||
|                 )), |                 )), | ||||||
|                 "protected_store.delete" => Ok(Box::new(inner.core.protected_store.delete)), |                 "protected_store.delete" => Ok(Box::new(inner.core.protected_store.delete)), | ||||||
|  |                 "protected_store.device_encryption_key_password" => Ok(Box::new( | ||||||
|  |                     inner | ||||||
|  |                         .core | ||||||
|  |                         .protected_store | ||||||
|  |                         .device_encryption_key_password | ||||||
|  |                         .clone(), | ||||||
|  |                 )), | ||||||
|  |                 "protected_store.new_device_encryption_key_password" => Ok(Box::new( | ||||||
|  |                     inner | ||||||
|  |                         .core | ||||||
|  |                         .protected_store | ||||||
|  |                         .new_device_encryption_key_password | ||||||
|  |                         .clone(), | ||||||
|  |                 )), | ||||||
|  |  | ||||||
|                 "table_store.directory" => Ok(Box::new( |                 "table_store.directory" => Ok(Box::new( | ||||||
|                     inner |                     inner | ||||||
| @@ -1505,6 +1559,11 @@ mod tests { | |||||||
|             Settings::get_default_protected_store_directory() |             Settings::get_default_protected_store_directory() | ||||||
|         ); |         ); | ||||||
|         assert_eq!(s.core.protected_store.delete, false); |         assert_eq!(s.core.protected_store.delete, false); | ||||||
|  |         assert_eq!(s.core.protected_store.device_encryption_key_password, ""); | ||||||
|  |         assert_eq!( | ||||||
|  |             s.core.protected_store.new_device_encryption_key_password, | ||||||
|  |             None | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assert_eq!(s.core.network.connection_initial_timeout_ms, 2_000u32); |         assert_eq!(s.core.network.connection_initial_timeout_ms, 2_000u32); | ||||||
|         assert_eq!(s.core.network.connection_inactivity_timeout_ms, 60_000u32); |         assert_eq!(s.core.network.connection_inactivity_timeout_ms, 60_000u32); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user