Security Improvements: - SEC-6: Remove password logging - write to secure file instead - SEC-7: Add CSP headers for XSS prevention - SEC-9: Explicitly configure Argon2id password hashing - SEC-11: Restrict CORS to specific origins (production + localhost) - SEC-12: Implement comprehensive security headers - SEC-13: Explicit JWT expiration enforcement Completed Features: ✓ Password credentials written to .admin-credentials file (600 permissions) ✓ CSP headers prevent XSS attacks ✓ Argon2id explicitly configured (Algorithm::Argon2id) ✓ CORS restricted to connect.azcomputerguru.com + localhost ✓ Security headers: X-Frame-Options, X-Content-Type-Options, etc. ✓ JWT expiration strictly enforced (validate_exp=true, leeway=0) Files Created: - server/src/middleware/security_headers.rs - WEEK1_DAY2-3_SECURITY_COMPLETE.md Files Modified: - server/src/main.rs (password file write, CORS, security headers) - server/src/auth/jwt.rs (explicit expiration validation) - server/src/auth/password.rs (explicit Argon2id) - server/src/middleware/mod.rs (added security_headers) Week 1 Progress: 10/13 items complete (77%) Compilation: SUCCESS (53 warnings, 0 errors) Risk Level: CRITICAL → LOW/MEDIUM Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
77 lines
2.5 KiB
Rust
77 lines
2.5 KiB
Rust
//! Password hashing using Argon2id
|
|
//!
|
|
//! SEC-9: Explicitly uses Argon2id (hybrid variant) for password hashing
|
|
//! Argon2id provides resistance against both side-channel and GPU attacks
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use argon2::{
|
|
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
|
Argon2, Algorithm, Version, Params,
|
|
};
|
|
|
|
/// Hash a password using Argon2id
|
|
///
|
|
/// SEC-9: Explicitly configured to use Argon2id variant
|
|
/// - Algorithm: Argon2id (hybrid of Argon2i and Argon2d)
|
|
/// - Version: 0x13 (latest version)
|
|
/// - Memory: 19456 KiB (default)
|
|
/// - Iterations: 2 (default)
|
|
/// - Parallelism: 1 (default)
|
|
pub fn hash_password(password: &str) -> Result<String> {
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
|
|
// Explicitly use Argon2id (Algorithm::Argon2id)
|
|
let argon2 = Argon2::new(
|
|
Algorithm::Argon2id, // SEC-9: Explicit Argon2id variant
|
|
Version::V0x13, // Latest version
|
|
Params::default(), // Default params (19456 KiB, 2 iterations, 1 parallelism)
|
|
);
|
|
|
|
let hash = argon2
|
|
.hash_password(password.as_bytes(), &salt)
|
|
.map_err(|e| anyhow!("Failed to hash password: {}", e))?;
|
|
Ok(hash.to_string())
|
|
}
|
|
|
|
/// Verify a password against a stored hash
|
|
pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
|
|
let parsed_hash = PasswordHash::new(hash)
|
|
.map_err(|e| anyhow!("Invalid password hash format: {}", e))?;
|
|
|
|
// Argon2::default() uses Argon2id, but we verify against the hash's embedded algorithm
|
|
let argon2 = Argon2::default();
|
|
Ok(argon2.verify_password(password.as_bytes(), &parsed_hash).is_ok())
|
|
}
|
|
|
|
/// Generate a random password (for initial admin)
|
|
pub fn generate_random_password(length: usize) -> String {
|
|
use rand::Rng;
|
|
const CHARSET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%";
|
|
let mut rng = rand::thread_rng();
|
|
(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_hash_and_verify() {
|
|
let password = "test_password_123";
|
|
let hash = hash_password(password).unwrap();
|
|
assert!(verify_password(password, &hash).unwrap());
|
|
assert!(!verify_password("wrong_password", &hash).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_random_password() {
|
|
let password = generate_random_password(16);
|
|
assert_eq!(password.len(), 16);
|
|
}
|
|
}
|