checkpoint

This commit is contained in:
John Smith 2023-05-26 00:53:07 +01:00
parent cefbeed09a
commit 5b0bfcef48
8 changed files with 178 additions and 35 deletions

View File

@ -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;

View File

@ -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())),
}; };

View File

@ -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);
} }

View File

@ -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,

View File

@ -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

View File

@ -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'];
} }
//////////// ////////////

View File

@ -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")

View File

@ -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);