-
Notifications
You must be signed in to change notification settings - Fork 1
Closed
Labels
priority:mediumMedium priority issueMedium priority issuestatus:readyReady to be worked onReady to be worked ontype:enhancementNew feature or requestNew feature or request
Description
Summary
Implement bssh-keygen tool for generating SSH key pairs in OpenSSH format, supporting Ed25519 and RSA algorithms.
Parent Epic
- Implement bssh-server with SFTP/SCP support #123 - bssh-server 추가 구현
Implementation Details
1. Key Generation Module
// src/keygen/mod.rs
pub mod ed25519;
pub mod rsa;
use std::path::Path;
/// Key generation result
pub struct GeneratedKey {
pub private_key_pem: String,
pub public_key_openssh: String,
pub fingerprint: String,
}
/// Generate an Ed25519 key pair
pub fn generate_ed25519(output_path: &Path, comment: Option<&str>) -> Result<GeneratedKey> {
ed25519::generate(output_path, comment)
}
/// Generate an RSA key pair
pub fn generate_rsa(output_path: &Path, bits: u32, comment: Option<&str>) -> Result<GeneratedKey> {
rsa::generate(output_path, bits, comment)
}2. Ed25519 Key Generation
// src/keygen/ed25519.rs
use rand::rngs::OsRng;
use russh_keys::key::{KeyPair, PublicKey};
pub fn generate(output_path: &Path, comment: Option<&str>) -> Result<GeneratedKey> {
// Generate key pair
let keypair = KeyPair::generate_ed25519()
.context("Failed to generate Ed25519 key")?;
// Get public key
let public_key = keypair.public_key();
let fingerprint = public_key.fingerprint();
// Format private key (OpenSSH format)
let private_key_pem = keypair.to_openssh_string(comment)?;
// Format public key
let comment_str = comment.unwrap_or("bssh-keygen");
let public_key_openssh = format!(
"{} {}",
public_key.to_openssh_string()?,
comment_str
);
// Write private key
{
use std::os::unix::fs::OpenOptionsExt;
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600) // -rw-------
.open(output_path)?;
use std::io::Write;
file.write_all(private_key_pem.as_bytes())?;
}
// Write public key
let pub_path = format!("{}.pub", output_path.display());
std::fs::write(&pub_path, &public_key_openssh)?;
tracing::info!("Generated Ed25519 key: {}", output_path.display());
tracing::info!("Fingerprint: {}", fingerprint);
Ok(GeneratedKey {
private_key_pem,
public_key_openssh,
fingerprint,
})
}3. RSA Key Generation
// src/keygen/rsa.rs
pub fn generate(output_path: &Path, bits: u32, comment: Option<&str>) -> Result<GeneratedKey> {
// Validate key size
if bits < 2048 {
anyhow::bail!("RSA key size must be at least 2048 bits");
}
if bits > 16384 {
anyhow::bail!("RSA key size must not exceed 16384 bits");
}
// Generate key pair
let keypair = KeyPair::generate_rsa(bits as usize)
.context("Failed to generate RSA key")?;
// Same output format as Ed25519...
let public_key = keypair.public_key();
let fingerprint = public_key.fingerprint();
let private_key_pem = keypair.to_openssh_string(comment)?;
let comment_str = comment.unwrap_or("bssh-keygen");
let public_key_openssh = format!(
"{} {}",
public_key.to_openssh_string()?,
comment_str
);
// Write files with proper permissions
{
use std::os::unix::fs::OpenOptionsExt;
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
.open(output_path)?;
use std::io::Write;
file.write_all(private_key_pem.as_bytes())?;
}
let pub_path = format!("{}.pub", output_path.display());
std::fs::write(&pub_path, &public_key_openssh)?;
tracing::info!("Generated RSA-{} key: {}", bits, output_path.display());
tracing::info!("Fingerprint: {}", fingerprint);
Ok(GeneratedKey {
private_key_pem,
public_key_openssh,
fingerprint,
})
}4. CLI Binary
// src/bin/bssh_keygen.rs
use clap::Parser;
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "bssh-keygen")]
#[command(about = "Generate SSH key pairs in OpenSSH format")]
#[command(version)]
pub struct Cli {
/// Key type: ed25519 (recommended) or rsa
#[arg(short = 't', long, default_value = "ed25519")]
key_type: String,
/// Output file path
#[arg(short = 'f', long)]
output: Option<PathBuf>,
/// RSA key bits (only for RSA, minimum 2048)
#[arg(short = 'b', long, default_value = "4096")]
bits: u32,
/// Comment for the key
#[arg(short = 'C', long)]
comment: Option<String>,
/// Overwrite existing files without prompting
#[arg(short = 'y', long)]
yes: bool,
/// Quiet mode (no output except errors)
#[arg(short = 'q', long)]
quiet: bool,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
// Determine output path
let output = cli.output.unwrap_or_else(|| {
let home = dirs::home_dir().expect("Cannot determine home directory");
match cli.key_type.as_str() {
"ed25519" => home.join(".ssh/id_ed25519"),
"rsa" => home.join(".ssh/id_rsa"),
_ => home.join(".ssh/id_key"),
}
});
// Check if file exists
if output.exists() && !cli.yes {
print!("{} already exists. Overwrite? (y/n) ", output.display());
std::io::Write::flush(&mut std::io::stdout())?;
let mut response = String::new();
std::io::stdin().read_line(&mut response)?;
if !response.trim().eq_ignore_ascii_case("y") {
println!("Aborted.");
return Ok(());
}
}
// Generate key
let result = match cli.key_type.to_lowercase().as_str() {
"ed25519" => bssh::keygen::generate_ed25519(&output, cli.comment.as_deref())?,
"rsa" => bssh::keygen::generate_rsa(&output, cli.bits, cli.comment.as_deref())?,
other => anyhow::bail!("Unknown key type: {}. Use 'ed25519' or 'rsa'", other),
};
if !cli.quiet {
println!("Your identification has been saved in {}", output.display());
println!("Your public key has been saved in {}.pub", output.display());
println!("The key fingerprint is:");
println!("{}", result.fingerprint);
}
Ok(())
}5. CLI Help Output
bssh-keygen 1.0.0
Generate SSH key pairs in OpenSSH format
USAGE:
bssh-keygen [OPTIONS]
OPTIONS:
-t, --key-type <TYPE> Key type: ed25519 (recommended) or rsa [default: ed25519]
-f, --output <FILE> Output file path [default: ~/.ssh/id_<type>]
-b, --bits <BITS> RSA key bits (minimum 2048) [default: 4096]
-C, --comment <COMMENT> Comment for the key
-y, --yes Overwrite existing files without prompting
-q, --quiet Quiet mode
-h, --help Print help
-V, --version Print version
Files to Create/Modify
| File | Action |
|---|---|
src/keygen/mod.rs |
Create - Module exports |
src/keygen/ed25519.rs |
Create - Ed25519 generation |
src/keygen/rsa.rs |
Create - RSA generation |
src/bin/bssh_keygen.rs |
Create - CLI binary |
src/lib.rs |
Modify - Add keygen module |
Cargo.toml |
Modify - Add binary target |
Testing Requirements
- Unit test: Ed25519 key generation
- Unit test: RSA key generation (various sizes)
- Unit test: Key file permissions (0600)
- Unit test: Public key format
- Integration test: Generated keys work with ssh-keygen -y
- Integration test: Generated keys work with OpenSSH
# Test Ed25519
bssh-keygen -t ed25519 -f /tmp/test_ed25519 -y
ssh-keygen -y -f /tmp/test_ed25519 # Should output same public key
# Test RSA
bssh-keygen -t rsa -b 4096 -f /tmp/test_rsa -y
ssh-keygen -y -f /tmp/test_rsa # Should output same public key
# Verify key works
ssh-keygen -lf /tmp/test_ed25519.pubAcceptance Criteria
- Ed25519 key generation
- RSA key generation (2048-16384 bits)
- OpenSSH private key format
- OpenSSH public key format (.pub file)
- Proper file permissions (0600 for private key)
- Comment support
- Fingerprint display
- Overwrite confirmation
- Quiet mode
- Keys compatible with OpenSSH
- Tests passing
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
priority:mediumMedium priority issueMedium priority issuestatus:readyReady to be worked onReady to be worked ontype:enhancementNew feature or requestNew feature or request