Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ The `bssh-server` binary provides a command-line interface for managing and oper
**Subcommands**:
- **run** - Start the SSH server (default when no subcommand specified)
- **gen-config** - Generate a configuration file template with secure defaults
- **hash-password** - Hash passwords for configuration using bcrypt
- **hash-password** - Hash passwords for configuration using Argon2id (recommended)
- **check-config** - Validate configuration files and display settings
- **gen-host-key** - Generate SSH host keys (Ed25519 or RSA)
- **version** - Show version and build information
Expand Down Expand Up @@ -321,6 +321,8 @@ The authentication subsystem (`src/server/auth/`) provides extensible authentica
- `mod.rs` - Module exports and re-exports
- `provider.rs` - `AuthProvider` trait definition
- `publickey.rs` - `PublicKeyVerifier` implementation
- `password.rs` - `PasswordVerifier` implementation with Argon2id hashing
- `composite.rs` - `CompositeAuthProvider` combining multiple auth methods

**AuthProvider Trait**:

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

**PasswordVerifier**:

Implements password authentication with secure password hashing:

- **Argon2id hashing**: Uses the OWASP-recommended password hashing algorithm
- Memory cost: 19 MiB
- Time cost: 2 iterations
- Parallelism: 1

- **User configuration**:
- External YAML file with user definitions
- Inline users in server configuration
- User attributes: name, password_hash, shell, home, env

- **Security features**:
- Timing attack mitigation with constant-time verification
- Minimum verification time (100ms) regardless of user existence
- Dummy hash verification for non-existent users
- Secure memory cleanup using `zeroize` crate
- User enumeration protection

- **Hash compatibility**:
- Argon2id (recommended, generated by `hash-password` command)
- bcrypt (supported for backward compatibility)

**CompositeAuthProvider**:

Combines multiple authentication methods into a single provider:

- Delegates to `PublicKeyVerifier` for public key auth
- Delegates to `PasswordVerifier` for password auth
- Prioritizes password verifier for user info (more detailed)
- Supports hot-reloading of password users via `reload_password_users()`

**Security Features**:

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

## Data Flow
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ shell-words = "1.1.1"
libc = "0.2"
ipnetwork = "0.20"
bcrypt = "0.16"
argon2 = "0.5"
rand = "0.8"
ssh-key = { version = "0.6", features = ["std"] }

Expand Down
14 changes: 11 additions & 3 deletions docs/architecture/server-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ auth:
# Inline user definitions
users:
- name: testuser
password_hash: "$6$rounds=656000$..." # openssl passwd -6
password_hash: "$argon2id$v=19$m=19456,t=2,p=1$..." # bssh-server hash-password
shell: /bin/bash
home: /home/testuser
env:
Expand Down Expand Up @@ -364,11 +364,19 @@ Generated keys have secure permissions (0600) and are in OpenSSH format.
### Hash Passwords

```bash
# Interactive password hashing with bcrypt
# Interactive password hashing with Argon2id (recommended)
bssh-server hash-password
```

This prompts for a password, confirms it, and outputs a bcrypt hash suitable for use in the configuration file.
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.

The generated hash includes:
- Algorithm: Argon2id (variant resistant to both side-channel and GPU attacks)
- Memory cost: 19 MiB
- Time cost: 2 iterations
- Parallelism: 1

Note: bcrypt hashes are also supported for backward compatibility with existing configurations.

### Validate Configuration

Expand Down
9 changes: 6 additions & 3 deletions src/bin/bssh_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ fn gen_config(output: Option<PathBuf>) -> Result<()> {

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

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

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

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

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

Ok(())
}
Expand Down
Loading
Loading