use crate::error::Error;
use crate::vault::Vault;
use crate::{audit, gui, security_config, totp};
pub fn security_config_get() -> Result<security_config::SecurityConfig, Error> {
let vault = Vault::open_default()?;
security_config_get_in(&vault)
}
pub fn security_config_get_in(vault: &Vault) -> Result<security_config::SecurityConfig, Error> {
security_config::load_config(vault.root(), vault.master_key_bytes())
}
pub fn security_config_save(config: &security_config::SecurityConfig) -> Result<(), Error> {
let vault = Vault::open_default()?;
security_config_save_in(&vault, config)
}
pub fn security_config_save_in(
vault: &Vault,
config: &security_config::SecurityConfig,
) -> Result<(), Error> {
security_config::save_config(vault.root(), config, vault.master_key_bytes())
}
pub fn security_apply_preset(tier: security_config::SecurityTier) -> Result<(), Error> {
let vault = Vault::open_default()?;
let mut config = security_config::load_config(vault.root(), vault.master_key_bytes())?;
let required = config.validate_tier_change(tier);
if required != security_config::SecurityTier::Standard {
return Err(Error::CryptoFailure(format!(
"tier change requires re-authentication at the {required:?} tier"
)));
}
let old_tier = format!("{:?}", config.tier);
config.apply_preset(tier);
security_config::save_config(vault.root(), &config, vault.master_key_bytes())?;
audit::log_required(&audit::AuditEvent::TierChanged {
from: old_tier,
to: format!("{:?}", config.tier),
})?;
Ok(())
}
pub fn security_apply_preset_by_name(preset: &str) -> Result<(), Error> {
let tier = match preset {
"standard" => security_config::SecurityTier::Standard,
"hardened" => security_config::SecurityTier::Hardened,
"lockdown" => security_config::SecurityTier::Lockdown,
other => {
return Err(Error::CryptoFailure(format!(
"unknown security preset: {other} (expected standard|hardened|lockdown)"
)))
}
};
security_apply_preset(tier)
}
pub fn security_set_field(field: &str, value: &str) -> Result<(), Error> {
let parse_bool = |s: &str| -> Result<bool, Error> {
match s {
"true" | "1" | "yes" | "on" => Ok(true),
"false" | "0" | "no" | "off" => Ok(false),
_ => Err(Error::CryptoFailure(format!(
"invalid boolean: {s} (use true/false)"
))),
}
};
let mut config = security_config_get()?;
match field {
"challenge_required" => config.challenge_required = parse_bool(value)?,
"approval_delay_secs" => {
config.approval_delay_secs = value
.parse::<u32>()
.map_err(|_| Error::CryptoFailure(format!("invalid u32: {value}")))?;
}
"audit_logging" => config.audit_logging = parse_bool(value)?,
"relay_required" => config.relay_required = parse_bool(value)?,
"x11_auto_upgrade" => config.x11_auto_upgrade = parse_bool(value)?,
"block_hostile" | "block_degraded" => {
return Err(Error::CryptoFailure(format!(
"field '{field}' was removed in v0.2.0. The policy \
taxonomy now decides hostile/degraded handling per tier. \
Use `envseal security override-tier` to author overrides."
)));
}
other => {
return Err(Error::CryptoFailure(format!(
"unknown security field '{other}'. valid: challenge_required, \
approval_delay_secs, audit_logging, relay_required, x11_auto_upgrade"
)));
}
}
security_config_save(&config)
}
pub fn security_override_signal(signal_id: &str, action: &str) -> Result<(), Error> {
let mut config = security_config_get()?;
config.set_signal_override(signal_id, action)?;
security_config_save(&config)
}
pub fn security_clear_signal_override(signal_id: &str) -> Result<bool, Error> {
let mut config = security_config_get()?;
let removed = config.clear_signal_override(signal_id);
if removed {
security_config_save(&config)?;
}
Ok(removed)
}
pub fn security_override_tier(severity: &str, tier: &str, action: &str) -> Result<(), Error> {
let mut config = security_config_get()?;
config.set_tier_override(severity, tier, action)?;
security_config_save(&config)
}
pub fn security_clear_tier_override(severity: &str, tier: &str) -> Result<bool, Error> {
let mut config = security_config_get()?;
let removed = config.clear_tier_override(severity, tier)?;
if removed {
security_config_save(&config)?;
}
Ok(removed)
}
pub type OverrideSnapshot = (
std::collections::BTreeMap<String, String>,
std::collections::BTreeMap<String, String>,
);
pub fn security_list_overrides() -> Result<OverrideSnapshot, Error> {
let config = security_config_get()?;
Ok((config.signal_overrides, config.tier_overrides))
}
pub fn totp_setup() -> Result<String, Error> {
let mut config = security_config_get()?;
if config.totp_required && config.totp_secret_encrypted.is_some() {
return Err(Error::CryptoFailure(
"TOTP is already configured. Disable it first.".to_string(),
));
}
let secret_b32 = totp::generate_secret();
let uri = totp::otpauth_uri(&secret_b32, "vault");
let user_code = gui::request_totp_code(1)?;
if !totp::verify_code(&secret_b32, &user_code)? {
return Err(Error::CryptoFailure(
"invalid TOTP verification code — setup cancelled".to_string(),
));
}
let vault = Vault::open_default()?;
let encrypted = totp::encrypt_secret(&secret_b32, vault.master_key_bytes())?;
config.totp_secret_encrypted = Some(encrypted);
config.totp_required = true;
security_config::save_config(vault.root(), &config, vault.master_key_bytes())?;
audit::log_required(&audit::AuditEvent::TierChanged {
from: "totp_disabled".to_string(),
to: "totp_enabled".to_string(),
})?;
Ok(uri)
}
pub fn totp_disable() -> Result<(), Error> {
let vault = Vault::open_default()?;
let mut config = security_config::load_config(vault.root(), vault.master_key_bytes())?;
if !config.totp_required {
return Err(Error::CryptoFailure("TOTP is not enabled.".to_string()));
}
if let Some(ref encrypted) = config.totp_secret_encrypted {
let secret_b32 = totp::decrypt_secret(encrypted, vault.master_key_bytes())?;
let user_code = gui::request_totp_code(1)?;
if !totp::verify_code(&secret_b32, &user_code)? {
return Err(Error::CryptoFailure(
"invalid TOTP code — disable cancelled".to_string(),
));
}
}
config.totp_required = false;
config.totp_secret_encrypted = None;
security_config::save_config(vault.root(), &config, vault.master_key_bytes())?;
audit::log_required(&audit::AuditEvent::TierChanged {
from: "totp_enabled".to_string(),
to: "totp_disabled".to_string(),
})?;
Ok(())
}