use anyhow::{Context, Result}; use rand::RngExt; use sqlx::PgPool; use crate::{crypto, db}; const MIN_MASTER_PASSWORD_LEN: usize = 8; pub async fn run(pool: &PgPool) -> Result<()> { println!("Initializing secrets master key..."); println!(); // Read password (no echo) let password = rpassword::prompt_password(format!( "Enter master password (at least {} characters): ", MIN_MASTER_PASSWORD_LEN )) .context("failed to read password")?; if password.chars().count() < MIN_MASTER_PASSWORD_LEN { anyhow::bail!( "Master password must be at least {} characters.", MIN_MASTER_PASSWORD_LEN ); } let confirm = rpassword::prompt_password("Confirm master password: ") .context("failed to read password confirmation")?; if password != confirm { anyhow::bail!("Passwords do not match."); } // Get or create Argon2id salt let salt = match db::load_argon2_salt(pool).await? { Some(existing) => { println!("Found existing salt in database (not the first device)."); existing } None => { println!("Generating new Argon2id salt and storing in database..."); let mut salt = vec![0u8; 16]; rand::rng().fill(&mut salt[..]); db::store_argon2_salt(pool, &salt).await?; salt } }; // Derive master key print!("Deriving master key (Argon2id, this takes a moment)... "); let master_key = crypto::derive_master_key(&password, &salt)?; println!("done."); // Store in OS Keychain crypto::store_master_key(&master_key)?; // Self-test: encrypt and decrypt a canary value let canary = b"secrets-cli-canary"; let enc = crypto::encrypt(&master_key, canary)?; let dec = crypto::decrypt(&master_key, &enc)?; if dec != canary { anyhow::bail!("Self-test failed: encryption roundtrip mismatch"); } println!(); println!("Master key stored in OS Keychain."); println!("You can now use `secrets add` / `secrets search` commands."); println!(); println!("IMPORTANT: Remember your master password — it is not stored anywhere."); println!(" On a new device, run `secrets init` with the same password."); Ok(()) }