Skip to content

Commit 879b3fa

Browse files
authored
feat: Implement password authentication for server (#127) (#152)
* feat: Implement password authentication for server (#127) - Add Argon2id password hashing with secure memory cleanup (zeroize) - Implement timing attack mitigation with constant-time verification - Support users from YAML file and inline configuration - Add bcrypt compatibility for backward compatibility - Integrate password auth with SSH handler and rate limiting - Add CompositeAuthProvider for combined pubkey/password auth - Update hash-password CLI command to use Argon2id - Add comprehensive tests for password authentication * fix: use Zeroizing wrapper for password memory cleanup in auth handler * docs: Update documentation for password authentication feature - Add PasswordVerifier and CompositeAuthProvider to architecture docs - Document Argon2id hashing parameters and security features - Update auth module docstrings with usage examples - Update CLI hash-password description to mention Argon2id - Update server-configuration.md with Argon2id hash format
1 parent 74e6f2e commit 879b3fa

10 files changed

Lines changed: 1452 additions & 29 deletions

File tree

ARCHITECTURE.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ The `bssh-server` binary provides a command-line interface for managing and oper
197197
**Subcommands**:
198198
- **run** - Start the SSH server (default when no subcommand specified)
199199
- **gen-config** - Generate a configuration file template with secure defaults
200-
- **hash-password** - Hash passwords for configuration using bcrypt
200+
- **hash-password** - Hash passwords for configuration using Argon2id (recommended)
201201
- **check-config** - Validate configuration files and display settings
202202
- **gen-host-key** - Generate SSH host keys (Ed25519 or RSA)
203203
- **version** - Show version and build information
@@ -321,6 +321,8 @@ The authentication subsystem (`src/server/auth/`) provides extensible authentica
321321
- `mod.rs` - Module exports and re-exports
322322
- `provider.rs` - `AuthProvider` trait definition
323323
- `publickey.rs` - `PublicKeyVerifier` implementation
324+
- `password.rs` - `PasswordVerifier` implementation with Argon2id hashing
325+
- `composite.rs` - `CompositeAuthProvider` combining multiple auth methods
324326

325327
**AuthProvider Trait**:
326328

@@ -356,14 +358,49 @@ Implements public key authentication by parsing OpenSSH authorized_keys files:
356358
- `no-pty`, `no-port-forwarding`, `no-agent-forwarding`, `no-X11-forwarding`
357359
- `environment="..."` - Set environment variables
358360

361+
**PasswordVerifier**:
362+
363+
Implements password authentication with secure password hashing:
364+
365+
- **Argon2id hashing**: Uses the OWASP-recommended password hashing algorithm
366+
- Memory cost: 19 MiB
367+
- Time cost: 2 iterations
368+
- Parallelism: 1
369+
370+
- **User configuration**:
371+
- External YAML file with user definitions
372+
- Inline users in server configuration
373+
- User attributes: name, password_hash, shell, home, env
374+
375+
- **Security features**:
376+
- Timing attack mitigation with constant-time verification
377+
- Minimum verification time (100ms) regardless of user existence
378+
- Dummy hash verification for non-existent users
379+
- Secure memory cleanup using `zeroize` crate
380+
- User enumeration protection
381+
382+
- **Hash compatibility**:
383+
- Argon2id (recommended, generated by `hash-password` command)
384+
- bcrypt (supported for backward compatibility)
385+
386+
**CompositeAuthProvider**:
387+
388+
Combines multiple authentication methods into a single provider:
389+
390+
- Delegates to `PublicKeyVerifier` for public key auth
391+
- Delegates to `PasswordVerifier` for password auth
392+
- Prioritizes password verifier for user info (more detailed)
393+
- Supports hot-reloading of password users via `reload_password_users()`
394+
359395
**Security Features**:
360396

361397
- **Username validation**: Prevents path traversal attacks (e.g., `../etc/passwd`)
362398
- **File permission checks** (Unix): Rejects world/group-writable files and symlinks
363399
- **Symlink protection**: Uses `symlink_metadata()` to detect and reject symlinks
364400
- **Parent directory validation**: Checks parent directory permissions
365401
- **Rate limiting**: Token bucket rate limiter for authentication attempts
366-
- **Timing attack mitigation**: Constant-time behavior in `user_exists()` check
402+
- **Timing attack mitigation**: Constant-time behavior in password verification and `user_exists()` check
403+
- **Secure memory handling**: Password strings cleared from memory after use via `zeroize`
367404
- **Comprehensive logging**: All authentication attempts are logged
368405

369406
## Data Flow

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ shell-words = "1.1.1"
5555
libc = "0.2"
5656
ipnetwork = "0.20"
5757
bcrypt = "0.16"
58+
argon2 = "0.5"
5859
rand = "0.8"
5960
ssh-key = { version = "0.6", features = ["std"] }
6061

docs/architecture/server-configuration.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ auth:
118118
# Inline user definitions
119119
users:
120120
- name: testuser
121-
password_hash: "$6$rounds=656000$..." # openssl passwd -6
121+
password_hash: "$argon2id$v=19$m=19456,t=2,p=1$..." # bssh-server hash-password
122122
shell: /bin/bash
123123
home: /home/testuser
124124
env:
@@ -364,11 +364,19 @@ Generated keys have secure permissions (0600) and are in OpenSSH format.
364364
### Hash Passwords
365365

366366
```bash
367-
# Interactive password hashing with bcrypt
367+
# Interactive password hashing with Argon2id (recommended)
368368
bssh-server hash-password
369369
```
370370

371-
This prompts for a password, confirms it, and outputs a bcrypt hash suitable for use in the configuration file.
371+
This prompts for a password, confirms it, and outputs an Argon2id hash suitable for use in the configuration file. Argon2id is the OWASP-recommended password hashing algorithm with memory-hard properties that resist GPU and ASIC attacks.
372+
373+
The generated hash includes:
374+
- Algorithm: Argon2id (variant resistant to both side-channel and GPU attacks)
375+
- Memory cost: 19 MiB
376+
- Time cost: 2 iterations
377+
- Parallelism: 1
378+
379+
Note: bcrypt hashes are also supported for backward compatibility with existing configurations.
372380

373381
### Validate Configuration
374382

src/bin/bssh_server.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ fn gen_config(output: Option<PathBuf>) -> Result<()> {
257257

258258
/// Hash a password for configuration
259259
async fn hash_password() -> Result<()> {
260+
use bssh::server::auth::hash_password as generate_hash;
260261
use rpassword::read_password;
261262

262263
print!("Enter password: ");
@@ -269,7 +270,7 @@ async fn hash_password() -> Result<()> {
269270

270271
// Warn about weak passwords (but still allow them)
271272
if password.len() < 8 {
272-
println!("\n Warning: Password is shorter than 8 characters.");
273+
println!("\n Warning: Password is shorter than 8 characters.");
273274
println!(" This is considered weak and may be easily compromised.");
274275
println!(" Consider using a longer password for better security.\n");
275276
}
@@ -282,8 +283,8 @@ async fn hash_password() -> Result<()> {
282283
anyhow::bail!("Passwords do not match");
283284
}
284285

285-
// Use bcrypt for password hashing (cost factor 12)
286-
let hash = bcrypt::hash(&password, 12).context("Failed to hash password")?;
286+
// Use Argon2id for password hashing (recommended algorithm)
287+
let hash = generate_hash(&password).context("Failed to hash password")?;
287288

288289
println!("\nPassword hash (use in configuration):");
289290
println!("{}", hash);
@@ -295,6 +296,8 @@ async fn hash_password() -> Result<()> {
295296
println!(" users:");
296297
println!(" - name: username");
297298
println!(" password_hash: \"{}\"", hash);
299+
println!("\nNote: This hash uses Argon2id algorithm (recommended).");
300+
println!(" bcrypt hashes are also supported for compatibility.");
298301

299302
Ok(())
300303
}

0 commit comments

Comments
 (0)