diff --git a/veilid-core/src/core_context.rs b/veilid-core/src/core_context.rs index 97096083..b3d46ab3 100644 --- a/veilid-core/src/core_context.rs +++ b/veilid-core/src/core_context.rs @@ -88,11 +88,7 @@ impl ServicesContext { // Set up crypto trace!("init crypto"); - let crypto = Crypto::new( - self.config.clone(), - table_store.clone(), - protected_store.clone(), - ); + let crypto = Crypto::new(self.config.clone(), table_store.clone()); if let Err(e) = crypto.init().await { error!("failed to init crypto: {}", e); self.shutdown().await; diff --git a/veilid-core/src/crypto/mod.rs b/veilid-core/src/crypto/mod.rs index be5879a3..d70be8f7 100644 --- a/veilid-core/src/crypto/mod.rs +++ b/veilid-core/src/crypto/mod.rs @@ -82,7 +82,6 @@ struct CryptoInner { struct CryptoUnlockedInner { config: VeilidConfig, table_store: TableStore, - protected_store: ProtectedStore, } /// Crypto factory implementation @@ -104,16 +103,11 @@ impl Crypto { } } - pub fn new( - config: VeilidConfig, - table_store: TableStore, - protected_store: ProtectedStore, - ) -> Self { + pub fn new(config: VeilidConfig, table_store: TableStore) -> Self { let out = Self { unlocked_inner: Arc::new(CryptoUnlockedInner { config, table_store, - protected_store, }), inner: Arc::new(Mutex::new(Self::new_inner())), }; diff --git a/veilid-core/src/table_store/table_store.rs b/veilid-core/src/table_store/table_store.rs index c8fd03a1..3f0c3574 100644 --- a/veilid-core/src/table_store/table_store.rs +++ b/veilid-core/src/table_store/table_store.rs @@ -163,19 +163,87 @@ impl TableStore { self.flush().await; } + async fn load_device_encryption_key(&self) -> EyreResult> { + let dek_bytes: Option> = 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, + ) -> 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<()> { let _async_guard = self.async_lock.lock().await; // Get device encryption key from protected store - let mut encryption_key: Option = self - .protected_store - .load_user_secret_json("device_encryption_key") - .await?; - - if let Some(encryption_key) = encryption_key { + let mut device_encryption_key = self.load_device_encryption_key().await?; + let mut device_encryption_key_changed = false; + if let Some(device_encryption_key) = device_encryption_key { // If encryption in current use is not the best encryption, then run table migration 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 } } else { @@ -183,14 +251,14 @@ impl TableStore { let best_kind = best_crypto_kind(); let mut shared_secret = SharedSecret::default(); random_bytes(&mut shared_secret.bytes); - let device_encryption_key = TypedSharedSecret::new(best_kind, shared_secret); - // Save the new device encryption key - self.protected_store - .save_user_secret_json("device_encryption_key", &device_encryption_key) + device_encryption_key = Some(TypedSharedSecret::new(best_kind, shared_secret)); + device_encryption_key_changed = true; + } + + if device_encryption_key_changed { + self.save_device_encryption_key(device_encryption_key) .await?; - - encryption_key = Some(device_encryption_key); } // Deserialize all table names @@ -220,7 +288,7 @@ impl TableStore { { 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); } diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 2b2aab55..8b00a40d 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -431,6 +431,8 @@ pub struct VeilidConfigProtectedStore { pub always_use_insecure_storage: bool, pub directory: String, pub delete: bool, + pub device_encryption_key_password: String, + pub new_device_encryption_key_password: Option, } #[derive( @@ -632,6 +634,8 @@ impl VeilidConfig { get_config!(inner.protected_store.always_use_insecure_storage); get_config!(inner.protected_store.directory); 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_inactivity_timeout_ms); get_config!(inner.network.max_connections_per_ip4); @@ -925,7 +929,7 @@ impl VeilidConfig { Ok(()) } - //xxx#[cfg(not(test))] + #[cfg(not(test))] async fn init_node_id( &self, vcrypto: CryptoSystemVersion, diff --git a/veilid-flutter/lib/default_config.dart b/veilid-flutter/lib/default_config.dart index f246bb07..30f08c68 100644 --- a/veilid-flutter/lib/default_config.dart +++ b/veilid-flutter/lib/default_config.dart @@ -66,6 +66,8 @@ Future getDefaultVeilidConfig(String programName) async { alwaysUseInsecureStorage: false, directory: "", delete: false, + deviceEncryptionKey: "", + newDeviceEncryptionKey: null, ), tableStore: VeilidConfigTableStore( directory: kIsWeb diff --git a/veilid-flutter/lib/veilid_config.dart b/veilid-flutter/lib/veilid_config.dart index a062b8d3..7a2134a8 100644 --- a/veilid-flutter/lib/veilid_config.dart +++ b/veilid-flutter/lib/veilid_config.dart @@ -828,13 +828,16 @@ class VeilidConfigProtectedStore { bool alwaysUseInsecureStorage; String directory; bool delete; + String deviceEncryptionKey; + String? newDeviceEncryptionKey; - VeilidConfigProtectedStore({ - required this.allowInsecureFallback, - required this.alwaysUseInsecureStorage, - required this.directory, - required this.delete, - }); + VeilidConfigProtectedStore( + {required this.allowInsecureFallback, + required this.alwaysUseInsecureStorage, + required this.directory, + required this.delete, + required this.deviceEncryptionKey, + String? newDeviceEncryptionKey}); Map toJson() { return { @@ -842,6 +845,8 @@ class VeilidConfigProtectedStore { 'always_use_insecure_storage': alwaysUseInsecureStorage, 'directory': directory, 'delete': delete, + 'device_encryption_key': deviceEncryptionKey, + 'new_device_encryption_key': newDeviceEncryptionKey, }; } @@ -849,7 +854,9 @@ class VeilidConfigProtectedStore { : allowInsecureFallback = json['allow_insecure_fallback'], alwaysUseInsecureStorage = json['always_use_insecure_storage'], directory = json['directory'], - delete = json['delete']; + delete = json['delete'], + deviceEncryptionKey = json['device_encryption_key'], + newDeviceEncryptionKey = json['new_device_encryption_key']; } //////////// diff --git a/veilid-server/src/cmdline.rs b/veilid-server/src/cmdline.rs index f2e7906a..1a349bf6 100644 --- a/veilid-server/src/cmdline.rs +++ b/veilid-server/src/cmdline.rs @@ -42,6 +42,19 @@ fn do_clap_matches(default_config_path: &OsStr) -> Result EyreResult { - let default_config = String::from( + let mut default_config = String::from( r#"--- daemon: enabled: false @@ -49,6 +50,8 @@ core: always_use_insecure_storage: true directory: '%DIRECTORY%' delete: false + device_encryption_key_password: '%DEVICE_ENCRYPTION_KEY_PASSWORD%' + new_device_encryption_key_password: %NEW_DEVICE_ENCRYPTION_KEY_PASSWORD% table_store: directory: '%TABLE_STORE_DIRECTORY%' delete: false @@ -176,6 +179,30 @@ core: "%REMOTE_MAX_SUBKEY_CACHE_MEMORY_MB%", &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() .add_source(config::File::from_str( &default_config, @@ -588,6 +615,8 @@ pub struct ProtectedStore { pub always_use_insecure_storage: bool, pub directory: PathBuf, pub delete: bool, + pub device_encryption_key_password: String, + pub new_device_encryption_key_password: Option, } #[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.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.delete, value); set_config_value!(inner.core.block_store.directory, value); @@ -1071,6 +1111,20 @@ impl Settings { .to_string(), )), "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( inner @@ -1505,6 +1559,11 @@ mod tests { Settings::get_default_protected_store_directory() ); 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_inactivity_timeout_ms, 60_000u32);