Some checks failed
Secrets CLI - Build & Release / 版本 & Release (push) Successful in 3s
Secrets CLI - Build & Release / 质量检查 (fmt / clippy / test) (push) Successful in 1m47s
Secrets CLI - Build & Release / Build (macOS aarch64 + x86_64) (push) Successful in 48s
Secrets CLI - Build & Release / Build (x86_64-unknown-linux-musl) (push) Successful in 1m2s
Secrets CLI - Build & Release / 发布草稿 Release (push) Has been cancelled
Secrets CLI - Build & Release / Build (x86_64-pc-windows-msvc) (push) Has been cancelled
Made-with: Cursor
71 lines
2.3 KiB
Rust
71 lines
2.3 KiB
Rust
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(())
|
|
}
|