cli: avoid data-dir initialization for version; create db parent dirs; redact generated passwords in CLI output
- Prevent 'nx9-wg version' from creating data directories by avoiding database initialization. - Create parent directories when an explicit --database path is provided. - Redact printed generated administrator passwords; announce file path or redact instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
commit
2ac6c81dfe
140 files changed
+31342
No files matched your search
@@ -0,0 +1,123 @@
|
||||
//! Cryptographic utilities.
|
||||
|
||||
use crate::error::{Nx9Error, Result};
|
||||
use crate::types::wireguard::{WireGuardPresharedKey, WireGuardPrivateKey, WireGuardPublicKey};
|
||||
|
||||
/// Hash a password with Argon2id. Returns the PHC-formatted hash string.
|
||||
pub fn hash_password(password: &str) -> Result<String> {
|
||||
use argon2::Argon2;
|
||||
use argon2::password_hash::rand_core::OsRng;
|
||||
use argon2::password_hash::{PasswordHasher, SaltString};
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::default();
|
||||
argon2
|
||||
.hash_password(password.as_bytes(), &salt)
|
||||
.map(|h| h.to_string())
|
||||
.map_err(|e| Nx9Error::Crypto(format!("password hashing failed: {}", e)))
|
||||
}
|
||||
|
||||
/// Verify a password against an Argon2id PHC hash string.
|
||||
pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
|
||||
use argon2::Argon2;
|
||||
use argon2::password_hash::{PasswordHash, PasswordVerifier};
|
||||
let parsed_hash = PasswordHash::new(hash)
|
||||
.map_err(|e| Nx9Error::Crypto(format!("invalid password hash: {}", e)))?;
|
||||
Ok(Argon2::default()
|
||||
.verify_password(password.as_bytes(), &parsed_hash)
|
||||
.is_ok())
|
||||
}
|
||||
|
||||
/// Generate a WireGuard key pair (x25519).
|
||||
pub fn generate_keypair() -> (WireGuardPrivateKey, WireGuardPublicKey) {
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use rand::rngs::OsRng;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
let secret = StaticSecret::random_from_rng(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
let priv_b64 = STANDARD.encode(secret.to_bytes());
|
||||
let pub_b64 = STANDARD.encode(public.as_bytes());
|
||||
(
|
||||
WireGuardPrivateKey::new(priv_b64),
|
||||
WireGuardPublicKey::new(pub_b64),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate a WireGuard preshared key (32 random bytes, base64).
|
||||
pub fn generate_preshared_key() -> WireGuardPresharedKey {
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use rand::RngCore;
|
||||
let mut key = [0u8; 32];
|
||||
rand::rngs::OsRng.fill_bytes(&mut key);
|
||||
WireGuardPresharedKey::new(STANDARD.encode(key))
|
||||
}
|
||||
|
||||
/// Generate a session ID (UUID v4).
|
||||
pub fn generate_session_id() -> String {
|
||||
uuid::Uuid::new_v4().to_string()
|
||||
}
|
||||
|
||||
/// Generate an API token. Returns (plaintext_token, sha256_hex_hash).
|
||||
pub fn generate_api_token() -> (String, String) {
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||
use rand::RngCore;
|
||||
use sha2::{Digest, Sha256};
|
||||
let mut token_bytes = [0u8; 32];
|
||||
rand::rngs::OsRng.fill_bytes(&mut token_bytes);
|
||||
let plaintext = format!("nx9_{}", URL_SAFE_NO_PAD.encode(token_bytes));
|
||||
let hash_bytes = Sha256::digest(plaintext.as_bytes());
|
||||
let hash = hash_bytes
|
||||
.iter()
|
||||
.map(|b| format!("{:02x}", b))
|
||||
.collect::<String>();
|
||||
(plaintext, hash)
|
||||
}
|
||||
|
||||
/// Generate a cryptographically secure random password.
|
||||
pub fn generate_secure_password(length: usize) -> String {
|
||||
use rand::Rng;
|
||||
const CHARSET: &[u8] =
|
||||
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*-_=+";
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
(0..length)
|
||||
.map(|_| {
|
||||
let idx = rng.gen_range(0..CHARSET.len());
|
||||
CHARSET[idx] as char
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_password_hashing() {
|
||||
let password = "my_secure_password";
|
||||
let hash = hash_password(password).unwrap();
|
||||
assert!(verify_password(password, &hash).unwrap());
|
||||
assert!(!verify_password("wrong_password", &hash).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_keypair() {
|
||||
let (priv_key, pub_key) = generate_keypair();
|
||||
assert!(!priv_key.as_str().is_empty());
|
||||
assert!(!pub_key.as_str().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_api_token() {
|
||||
let (token, hash) = generate_api_token();
|
||||
assert!(token.starts_with("nx9_"));
|
||||
assert_eq!(hash.len(), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_secure_password() {
|
||||
let pw = generate_secure_password(16);
|
||||
assert_eq!(pw.len(), 16);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user