From 9b83d5617ebc01632ed52ed84f66f1d0b86bc561 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Tue, 16 Dec 2025 23:29:55 +0100 Subject: [PATCH] feat(cli): add an option to specify the encrypt password in a file --- Cargo.lock | 2 +- README.md | 3 +++ src/modules/settings/cli.rs | 26 ++++++++++++++++++++++---- src/modules/utils/encrypt.rs | 18 +++++++++++++++--- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4fae5ce..6c7b3b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,7 +424,7 @@ dependencies = [ [[package]] name = "bichon" -version = "0.1.3" +version = "0.1.4" dependencies = [ "ahash", "async-imap", diff --git a/README.md b/README.md index 3344cde..dd6bf1a 100644 --- a/README.md +++ b/README.md @@ -449,9 +449,12 @@ cargo build Or run directly: ```bash +export BICHON_ENCRYPT_PASSWORD=dummy-password-for-testing cargo run -- --bichon-root-dir e:\bichon-data ``` + `--bichon-root-dir` specifies the directory where **all Bichon data** will be stored. +`BICHON_ENCRYPT_PASSWORD` is the password used to encrypt the sensitive data (see `cargo run -- --help` for alternative ways to specify this). ### WebUI Access diff --git a/src/modules/settings/cli.rs b/src/modules/settings/cli.rs index 0f99b18..c62fda8 100644 --- a/src/modules/settings/cli.rs +++ b/src/modules/settings/cli.rs @@ -19,7 +19,7 @@ use clap::{builder::ValueParser, Parser, ValueEnum}; use std::{collections::HashSet, env, fmt, path::PathBuf, sync::LazyLock}; -pub static SETTINGS: LazyLock = LazyLock::new(Settings::parse); +pub static SETTINGS: LazyLock = LazyLock::new(Settings::init); #[derive(Debug, Parser)] #[clap( @@ -132,11 +132,17 @@ pub struct Settings { /// bichon encryption password #[clap( long, - default_value = "change-this-default-password-now", env, - help = "Set the encryption password for bichon. ⚠️ Change this default in production!" + help = "Set the encryption password for bichon. Alternatively, you can use --bichon-encrypt-password-file. If both are set, this parameter takes precedence over the file." )] - pub bichon_encrypt_password: String, + pub bichon_encrypt_password: Option, + + #[clap( + long, + env, + help = "The file containing the encryption password. An alternative to --bichon-encrypt-password." + )] + pub bichon_encrypt_password_file: Option, #[clap( long, @@ -217,6 +223,18 @@ pub struct Settings { pub bichon_sync_concurrency: Option, } +impl Settings { + pub fn init() -> Self { + let s = Self::parse(); + if s.bichon_encrypt_password.is_none() && s.bichon_encrypt_password_file.is_none() { + panic!( + "One of --bichon_encrypt_password or --bichon_encrypt_password_file has to be set" + ); + } + s + } +} + #[derive(Clone, Copy, Debug, PartialEq, ValueEnum)] pub enum CompressionAlgorithm { #[clap(name = "none")] diff --git a/src/modules/utils/encrypt.rs b/src/modules/utils/encrypt.rs index 61e0267..45d99be 100644 --- a/src/modules/utils/encrypt.rs +++ b/src/modules/utils/encrypt.rs @@ -16,18 +16,30 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use base64::{engine::general_purpose, Engine as _}; use ring::aead::{Aad, BoundKey, Nonce, NonceSequence, OpeningKey, SealingKey, AES_256_GCM}; use ring::pbkdf2::{self, derive}; use ring::rand::{SecureRandom, SystemRandom}; +use std::fs; use std::num::NonZeroU32; +use std::sync::LazyLock; use crate::modules::error::code::ErrorCode; use crate::modules::error::BichonResult; use crate::modules::settings::cli::SETTINGS; use crate::raise_error; +static ENCRYPT_PASSWORD: LazyLock = + LazyLock::new(|| match &SETTINGS.bichon_encrypt_password { + Some(p) => p.clone(), + None => { + // unwrap() is safe here, because SETTINGS validates that at least one of the encrypt_password + // fields is set. + fs::read_to_string(SETTINGS.bichon_encrypt_password_file.as_ref().unwrap()) + .expect("failed to read the file with the encrypt password") + } + }); + struct SingleNonceSequence([u8; 12]); impl SingleNonceSequence { @@ -43,12 +55,12 @@ impl NonceSequence for SingleNonceSequence { } pub fn encrypt_string(plaintext: &str) -> BichonResult { - internal_encrypt_string(&SETTINGS.bichon_encrypt_password, plaintext) + internal_encrypt_string(&ENCRYPT_PASSWORD, plaintext) .map_err(|_| raise_error!("Failed to encrypt string.".into(), ErrorCode::InternalError)) } pub fn decrypt_string(data: &str) -> BichonResult { - internal_decrypt_string(&SETTINGS.bichon_encrypt_password, data).map_err(|_| { + internal_decrypt_string(&ENCRYPT_PASSWORD, data).map_err(|_| { raise_error!( "Decryption failed, likely due to incorrect encryption key or corrupted data".into(), ErrorCode::InternalError