Add native Dosh host trust foundation
This commit is contained in:
@@ -8,6 +8,9 @@ use dosh::auth::{
|
||||
};
|
||||
use dosh::config::{expand_tilde, load_client_config, load_hosts_config, load_server_config};
|
||||
use dosh::crypto;
|
||||
use dosh::native::{
|
||||
TrustResult, host_fingerprint, parse_host_public_key_line, remove_trusted_host, trust_host,
|
||||
};
|
||||
use dosh::protocol::{
|
||||
self, AttachOk, AttachReject, BootstrapAttachRequest, CLIENT_TO_SERVER, Frame, Input,
|
||||
PacketKind, Resize, ResumeRequest, SERVER_TO_CLIENT, TicketAttachBody, TicketAttachEnvelope,
|
||||
@@ -42,6 +45,10 @@ struct Args {
|
||||
#[arg(long)]
|
||||
no_cache: bool,
|
||||
#[arg(long)]
|
||||
remove: bool,
|
||||
#[arg(long)]
|
||||
replace: bool,
|
||||
#[arg(long)]
|
||||
attach_only: bool,
|
||||
#[arg(long)]
|
||||
predict: bool,
|
||||
@@ -88,6 +95,9 @@ async fn main() -> Result<()> {
|
||||
if args.server.as_deref() == Some("import-ssh") {
|
||||
return run_import_ssh(&config, &args.command);
|
||||
}
|
||||
if args.server.as_deref() == Some("trust") {
|
||||
return run_trust_command(&config, &args);
|
||||
}
|
||||
let requested_server = args.server.unwrap_or_else(|| config.server.clone());
|
||||
let hosts = load_hosts_config(None).unwrap_or_default();
|
||||
let host = hosts
|
||||
@@ -278,6 +288,96 @@ async fn main() -> Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
fn run_trust_command(config: &dosh::config::ClientConfig, args: &Args) -> Result<()> {
|
||||
let host = args
|
||||
.command
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("usage: dosh trust [--remove] [--replace] <host>"))?;
|
||||
let path = expand_tilde(&config.known_hosts);
|
||||
if args.remove {
|
||||
if remove_trusted_host(&path, host)? {
|
||||
eprintln!("dosh trust: removed {host} from {}", path.display());
|
||||
} else {
|
||||
eprintln!("dosh trust: {host} was not trusted in {}", path.display());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let hosts = load_hosts_config(None).unwrap_or_default();
|
||||
let host_config = hosts.hosts.get(host).cloned().unwrap_or_default();
|
||||
let server = host_config.ssh.clone().unwrap_or_else(|| host.to_string());
|
||||
let ssh_port = args.ssh_port.or(host_config.ssh_port).or(config.ssh_port);
|
||||
let ssh_auth_command = args
|
||||
.ssh_auth_command
|
||||
.clone()
|
||||
.or_else(|| config.ssh_auth_command.clone())
|
||||
.unwrap_or_else(|| "~/.local/bin/dosh-auth".to_string());
|
||||
let public_key = fetch_host_key_over_ssh(
|
||||
&server,
|
||||
ssh_port,
|
||||
&ssh_auth_command,
|
||||
args.ssh_key.as_deref(),
|
||||
args.ssh_known_hosts.as_deref(),
|
||||
args.ssh_control_path.as_deref(),
|
||||
)?;
|
||||
match trust_host(&path, host, &public_key, "ssh", args.replace)? {
|
||||
TrustResult::AlreadyTrusted => {
|
||||
eprintln!(
|
||||
"dosh trust: {host} already trusted ({})",
|
||||
host_fingerprint(&public_key)
|
||||
);
|
||||
}
|
||||
TrustResult::Trusted => {
|
||||
eprintln!(
|
||||
"dosh trust: trusted {host} {} in {}",
|
||||
host_fingerprint(&public_key),
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fetch_host_key_over_ssh(
|
||||
server: &str,
|
||||
ssh_port: Option<u16>,
|
||||
ssh_auth_command: &str,
|
||||
ssh_key: Option<&Path>,
|
||||
ssh_known_hosts: Option<&Path>,
|
||||
ssh_control_path: Option<&Path>,
|
||||
) -> Result<dosh::native::HostPublicKey> {
|
||||
let mut command = Command::new("ssh");
|
||||
if let Some(ssh_port) = ssh_port {
|
||||
command.arg("-p").arg(ssh_port.to_string());
|
||||
}
|
||||
command.arg("-T");
|
||||
if let Some(key) = ssh_key {
|
||||
command.arg("-i").arg(key);
|
||||
}
|
||||
if let Some(known_hosts) = ssh_known_hosts {
|
||||
command
|
||||
.arg("-o")
|
||||
.arg(format!("UserKnownHostsFile={}", known_hosts.display()));
|
||||
}
|
||||
if let Some(control_path) = ssh_control_path {
|
||||
command.arg("-S").arg(control_path);
|
||||
}
|
||||
let output = command
|
||||
.arg(server)
|
||||
.arg(ssh_auth_command)
|
||||
.arg("--host-key")
|
||||
.output()
|
||||
.context("fetch Dosh host key over SSH")?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Dosh host-key fetch failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
let raw = String::from_utf8(output.stdout)?;
|
||||
parse_host_public_key_line(&raw)
|
||||
}
|
||||
|
||||
fn startup_command(args: &[String]) -> Option<Vec<u8>> {
|
||||
if args.is_empty() {
|
||||
return None;
|
||||
|
||||
Reference in New Issue
Block a user