Skip to content

Commit

Permalink
feat: support gpg-agent forwarding
Browse files Browse the repository at this point in the history
  • Loading branch information
nomeaning777 committed Jun 11, 2023
1 parent cf35eff commit 9cabf99
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 17 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

21 changes: 20 additions & 1 deletion client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use std::time::Duration;

fn default_service_port() -> u32 {
6000
6001
}

#[derive(Serialize, Deserialize, Debug)]
Expand All @@ -21,6 +21,9 @@ pub struct Config {

#[serde(default)]
pub ssh_agent: Option<SshAgentConfig>,

#[serde(default)]
pub gpg_agent: Option<GpgAgentConfig>,
}

impl Default for Config {
Expand All @@ -31,6 +34,7 @@ impl Default for Config {
x11: None,
tcp_forward: None,
ssh_agent: None,
gpg_agent: None,
}
}
}
Expand Down Expand Up @@ -99,3 +103,18 @@ pub struct SshAgentConfig {
#[serde(default = "default_ssh_auth_sock")]
pub ssh_auth_sock: String,
}

fn default_gpg_agent_sock() -> String {
dirs::runtime_dir()
.unwrap()
.join("gnupg/S.gpg-agent")
.to_str()
.unwrap()
.to_owned()
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GpgAgentConfig {
#[serde(default = "default_gpg_agent_sock")]
pub gpg_agent_sock: String,
}
40 changes: 40 additions & 0 deletions client/src/gpg_agent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use super::config::GpgAgentConfig;
use super::util::{connect_stream, either};
use super::vmsocket::VmSocket;
use super::CONFIG;

use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use tokio::io::AsyncWriteExt;
use tokio::net::{UnixListener, UnixStream};

async fn handle_stream(mut stream: UnixStream) -> std::io::Result<()> {
let mut server = VmSocket::connect(CONFIG.service_port).await?;
server.write_all(b"gpga").await?;

let (client_r, client_w) = stream.split();
let (server_r, server_w) = server.split();
let a = connect_stream(client_r, server_w);
let b = connect_stream(server_r, client_w);
either(a, b).await
}

pub async fn gpg_agent_forward(config: &'static GpgAgentConfig) -> std::io::Result<()> {
// Remove existing socket
let _ = std::fs::create_dir_all(Path::new(&config.gpg_agent_sock).parent().unwrap());
let _ = std::fs::remove_file(&config.gpg_agent_sock);

let listener = UnixListener::bind(&config.gpg_agent_sock)?;
let _ = std::fs::set_permissions(&config.gpg_agent_sock, Permissions::from_mode(0o600));

loop {
let stream = listener.accept().await?.0;

tokio::task::spawn(async move {
if let Err(err) = handle_stream(stream).await {
eprintln!("Failed to transfer: {}", err);
}
});
}
}
10 changes: 10 additions & 0 deletions client/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod config;
mod gpg_agent;
mod ssh_agent;
mod tcp;
mod time;
Expand Down Expand Up @@ -110,6 +111,15 @@ async fn main() {
}));
}

if let Some(config) = &CONFIG.gpg_agent {
eprintln!("gpg-agent");
tasks.push(tokio::task::spawn(async move {
if let Err(err) = gpg_agent::gpg_agent_forward(config).await {
eprintln!("GPG-Agent forwarder error: {}", err);
}
}));
}

// Return an error code if no task is running.
if tasks.is_empty() {
std::process::exit(1);
Expand Down
3 changes: 2 additions & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ authors = ["Gary Guo <gary@garyguo.net>"]
edition = "2018"

[dependencies]
tokio = { version = "1.7", features = ["net", "rt", "macros", "io-util", "time"] }
anyhow = "1.0"
tokio = { version = "1.7", features = ["net", "rt", "macros", "io-util", "time", "process", "fs"] }
async-io = "1"
once_cell = "1"
winapi = { version = "0.3", features = ["wincon", "libloaderapi", "combaseapi"] }
Expand Down
50 changes: 50 additions & 0 deletions server/src/gpg_agent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::process::Stdio;

use super::util::{connect_stream, either};

use anyhow::{anyhow, Context as _, Result};
use tokio::fs::read;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::process::Command;

pub async fn handle_gpg_agent(mut stream: TcpStream) -> Result<()> {
let gpg_conf = Command::new("gpgconf.exe")
.arg("--list-dir")
.arg("agent-socket")
.stdout(Stdio::piped())
.spawn()?;
let output = gpg_conf.wait_with_output().await?;
if !output.status.success() {
return Err(anyhow!("gpgconf failure"));
}

// Start gpg-agent
let run_gpg_agent = Command::new("gpg-connect-agent.exe")
.arg("/bye")
.spawn()?
.wait()
.await?;
if !run_gpg_agent.success() {
return Err(anyhow!("gpg-connect-agent failure"));
}

let content = read(std::str::from_utf8(&output.stdout)?.trim()).await?;
for i in 0..content.len() {
if content[i] == b'\n' {
let port: u16 = std::str::from_utf8(&content[0..i])
.context("non utf8 port number")?
.parse()
.context("invalid port number for gpg-agent")?;
let (client_r, client_w) = stream.split();
let mut server = TcpStream::connect(("127.0.0.1", port)).await?;
server.set_nodelay(true)?;
let (server_r, mut server_w) = server.split();
server_w.write_all(&content[i + 1..]).await?;
let a = connect_stream(client_r, server_w);
let b = connect_stream(server_r, client_w);
return Ok(either(a, b).await?);
}
}
Err(anyhow!("invalid format of agent-socket"))
}
29 changes: 14 additions & 15 deletions server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Hide console window
#![windows_subsystem = "windows"]
//#![windows_subsystem = "windows"]

mod config;
mod gpg_agent;
mod ssh_agent;
mod tcp;
mod time;
Expand All @@ -10,9 +11,9 @@ mod vmcompute;
mod vmsocket;
mod x11;

use anyhow::{bail, Result};
use clap::Parser;
use once_cell::sync::Lazy;
use std::io::{Error, ErrorKind};
use tokio::io::AsyncReadExt;
use tokio::net::TcpStream;
use uuid::Uuid;
Expand All @@ -22,28 +23,26 @@ use vmsocket::VmSocket;

static CONFIG: Lazy<Config> = Lazy::new(|| Config::parse());

async fn handle_stream(mut stream: TcpStream) -> std::io::Result<()> {
async fn handle_stream(mut stream: TcpStream) -> Result<()> {
// Read the function code at the start of the stream for demultiplexing
let func = {
let mut buf = [0; 4];
stream.read_exact(&mut buf).await?;
buf
};

match &func {
b"x11\0" => x11::handle_x11(stream).await,
b"time" => time::handle_time(stream).await,
b"tcp\0" => tcp::handle_tcp(stream).await,
b"ssha" => ssh_agent::handle_ssh_agent(stream).await,
b"noop" => Ok(()),
_ => Err(Error::new(
ErrorKind::InvalidData,
format!("unknown function {:?}", func),
)),
}
Ok(match &func {
b"x11\0" => x11::handle_x11(stream).await?,
b"time" => time::handle_time(stream).await?,
b"tcp\0" => tcp::handle_tcp(stream).await?,
b"ssha" => ssh_agent::handle_ssh_agent(stream).await?,
b"gpga" => gpg_agent::handle_gpg_agent(stream).await?,
b"noop" => (),
_ => bail!("unknown function {:?}", func),
})
}

async fn task(vmid: Uuid) -> std::io::Result<()> {
async fn task(vmid: Uuid) -> Result<()> {
let listener = VmSocket::bind(vmid, CONFIG.service_port).await?;

loop {
Expand Down

0 comments on commit 9cabf99

Please sign in to comment.