Improve Windows client parity
ci / test (push) Canceled after 0s
ci / fuzz-smoke (push) Canceled after 0s
ci / windows-client (push) Canceled after 0s
ci / package-release (linux-x86_64, ubuntu-latest) (push) Canceled after 0s
ci / package-release (macos-aarch64, macos-14) (push) Canceled after 0s
ci / package-release (macos-x86_64, macos-13) (push) Canceled after 0s
ci / package-release (windows-x86_64, windows-latest) (push) Canceled after 0s
ci / remote-bench (push) Canceled after 0s
ci / publish-gitea-release (push) Canceled after 0s

This commit is contained in:
DuProcess
2026-07-16 19:41:23 -04:00
parent be98fb8d91
commit c03a2843ee
4 changed files with 96 additions and 42 deletions
Generated
+1
View File
@@ -463,6 +463,7 @@ dependencies = [
"tokio",
"toml",
"vt100",
"windows-sys 0.59.0",
"x25519-dalek",
]
+3
View File
@@ -31,6 +31,9 @@ toml = "0.8"
vt100 = "0.15"
x25519-dalek = { version = "2.0.1", features = ["static_secrets", "getrandom"] }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Console"] }
[dev-dependencies]
tempfile = "3.14"
+22 -2
View File
@@ -9929,7 +9929,22 @@ fn flush_local_terminal_input() {
}
}
#[cfg(not(unix))]
#[cfg(windows)]
fn flush_local_terminal_input() {
unsafe {
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::System::Console::{
FlushConsoleInputBuffer, GetStdHandle, STD_INPUT_HANDLE,
};
let handle = GetStdHandle(STD_INPUT_HANDLE);
if !handle.is_null() && handle != INVALID_HANDLE_VALUE {
let _ = FlushConsoleInputBuffer(handle);
}
}
}
#[cfg(all(not(unix), not(windows)))]
fn flush_local_terminal_input() {}
#[cfg(unix)]
@@ -9954,7 +9969,12 @@ fn drain_local_terminal_input() {
}
}
#[cfg(not(unix))]
#[cfg(windows)]
fn drain_local_terminal_input() {
flush_local_terminal_input();
}
#[cfg(all(not(unix), not(windows)))]
fn drain_local_terminal_input() {}
impl Drop for RawMode {
+70 -40
View File
@@ -1,31 +1,28 @@
use crate::native::{ForwardingRequest, NativeClientHello, NativeServerHello, NativeUserAuth};
#[cfg(unix)]
use crate::native::{
is_supported_user_key_algorithm, parse_ssh_ed25519_public_blob, user_auth_transcript,
};
#[cfg(unix)]
use anyhow::{Context, bail};
use anyhow::{Result, anyhow};
#[cfg(unix)]
#[cfg(windows)]
use std::fs::OpenOptions;
use std::io::{Read, Write};
#[cfg(unix)]
use std::os::unix::net::UnixStream;
use std::path::Path;
#[cfg(unix)]
const SSH_AGENT_FAILURE: u8 = 5;
#[cfg(unix)]
const SSH2_AGENTC_REQUEST_IDENTITIES: u8 = 11;
#[cfg(unix)]
const SSH2_AGENT_IDENTITIES_ANSWER: u8 = 12;
#[cfg(unix)]
const SSH2_AGENTC_SIGN_REQUEST: u8 = 13;
#[cfg(unix)]
const SSH2_AGENT_SIGN_RESPONSE: u8 = 14;
#[cfg(unix)]
const SSH_AGENT_RSA_SHA2_512: u32 = 4;
#[cfg(unix)]
const MAX_AGENT_PACKET: usize = 256 * 1024;
#[cfg(windows)]
const WINDOWS_OPENSSH_AGENT_PIPE: &str = r"\\.\pipe\openssh-ssh-agent";
trait AgentStream: Read + Write {}
impl<T: Read + Write> AgentStream for T {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentIdentity {
@@ -47,7 +44,21 @@ pub fn sign_user_auth_with_agent(
sign_user_auth_with_agent_at(sock, client, server, requested_forwardings)
}
#[cfg(not(unix))]
#[cfg(windows)]
pub fn sign_user_auth_with_agent(
client: &NativeClientHello,
server: &NativeServerHello,
requested_forwardings: Vec<ForwardingRequest>,
) -> Result<NativeUserAuth> {
sign_user_auth_with_agent_at(
WINDOWS_OPENSSH_AGENT_PIPE,
client,
server,
requested_forwardings,
)
}
#[cfg(not(any(unix, windows)))]
pub fn sign_user_auth_with_agent(
_client: &NativeClientHello,
_server: &NativeServerHello,
@@ -67,23 +78,30 @@ pub fn sign_user_auth_with_agent_at(
) -> Result<NativeUserAuth> {
let mut agent = UnixStream::connect(socket_path.as_ref())
.with_context(|| format!("connect ssh-agent {}", socket_path.as_ref().display()))?;
let identities = request_supported_identities(&mut agent)?;
let identity = identities
.first()
.ok_or_else(|| anyhow!("ssh-agent has no supported identities"))?;
let mut auth = NativeUserAuth {
public_key_algorithm: identity.sign_algorithm.clone(),
public_key: identity.public_key.clone(),
signature: Vec::new(),
requested_forwardings,
};
let transcript = user_auth_transcript(client, server, &auth)?;
auth.signature = sign_with_agent(&mut agent, identity, &transcript)
.with_context(|| format!("ssh-agent sign with {}", identity.comment))?;
Ok(auth)
sign_user_auth_with_agent_stream(&mut agent, client, server, requested_forwardings)
}
#[cfg(not(unix))]
#[cfg(windows)]
pub fn sign_user_auth_with_agent_at(
pipe_path: impl AsRef<Path>,
client: &NativeClientHello,
server: &NativeServerHello,
requested_forwardings: Vec<ForwardingRequest>,
) -> Result<NativeUserAuth> {
let mut agent = OpenOptions::new()
.read(true)
.write(true)
.open(pipe_path.as_ref())
.with_context(|| {
format!(
"connect Windows OpenSSH agent pipe {}",
pipe_path.as_ref().display()
)
})?;
sign_user_auth_with_agent_stream(&mut agent, client, server, requested_forwardings)
}
#[cfg(not(any(unix, windows)))]
pub fn sign_user_auth_with_agent_at(
_socket_path: impl AsRef<Path>,
_client: &NativeClientHello,
@@ -95,8 +113,29 @@ pub fn sign_user_auth_with_agent_at(
))
}
#[cfg(unix)]
fn request_supported_identities(agent: &mut UnixStream) -> Result<Vec<AgentIdentity>> {
fn sign_user_auth_with_agent_stream(
agent: &mut impl AgentStream,
client: &NativeClientHello,
server: &NativeServerHello,
requested_forwardings: Vec<ForwardingRequest>,
) -> Result<NativeUserAuth> {
let identities = request_supported_identities(agent)?;
let identity = identities
.first()
.ok_or_else(|| anyhow!("ssh-agent has no supported identities"))?;
let mut auth = NativeUserAuth {
public_key_algorithm: identity.sign_algorithm.clone(),
public_key: identity.public_key.clone(),
signature: Vec::new(),
requested_forwardings,
};
let transcript = user_auth_transcript(client, server, &auth)?;
auth.signature = sign_with_agent(agent, identity, &transcript)
.with_context(|| format!("ssh-agent sign with {}", identity.comment))?;
Ok(auth)
}
fn request_supported_identities(agent: &mut impl AgentStream) -> Result<Vec<AgentIdentity>> {
write_agent_packet(agent, &[SSH2_AGENTC_REQUEST_IDENTITIES])?;
let payload = read_agent_packet(agent)?;
let mut cursor = payload.as_slice();
@@ -122,7 +161,6 @@ fn request_supported_identities(agent: &mut UnixStream) -> Result<Vec<AgentIdent
Ok(identities)
}
#[cfg(unix)]
fn supported_identity(key_blob: Vec<u8>, comment: String) -> Result<Option<AgentIdentity>> {
let algorithm = key_blob_algorithm(&key_blob)?;
if !is_supported_user_key_algorithm(&algorithm) {
@@ -158,9 +196,8 @@ fn supported_identity(key_blob: Vec<u8>, comment: String) -> Result<Option<Agent
Ok(Some(identity))
}
#[cfg(unix)]
fn sign_with_agent(
agent: &mut UnixStream,
agent: &mut impl AgentStream,
identity: &AgentIdentity,
transcript: &[u8],
) -> Result<Vec<u8>> {
@@ -203,8 +240,7 @@ fn sign_with_agent(
Ok(signature.to_vec())
}
#[cfg(unix)]
fn read_agent_packet(stream: &mut UnixStream) -> Result<Vec<u8>> {
fn read_agent_packet(stream: &mut impl AgentStream) -> Result<Vec<u8>> {
let mut len = [0u8; 4];
stream
.read_exact(&mut len)
@@ -218,8 +254,7 @@ fn read_agent_packet(stream: &mut UnixStream) -> Result<Vec<u8>> {
Ok(payload)
}
#[cfg(unix)]
fn write_agent_packet(stream: &mut UnixStream, payload: &[u8]) -> Result<()> {
fn write_agent_packet(stream: &mut impl AgentStream, payload: &[u8]) -> Result<()> {
anyhow::ensure!(
payload.len() <= MAX_AGENT_PACKET,
"ssh-agent request too large"
@@ -229,7 +264,6 @@ fn write_agent_packet(stream: &mut UnixStream, payload: &[u8]) -> Result<()> {
Ok(())
}
#[cfg(unix)]
fn read_u8(cursor: &mut &[u8]) -> Result<u8> {
anyhow::ensure!(!cursor.is_empty(), "truncated u8");
let value = cursor[0];
@@ -237,7 +271,6 @@ fn read_u8(cursor: &mut &[u8]) -> Result<u8> {
Ok(value)
}
#[cfg(unix)]
fn read_u32(cursor: &mut &[u8]) -> Result<u32> {
anyhow::ensure!(cursor.len() >= 4, "truncated u32");
let value = u32::from_be_bytes(cursor[..4].try_into().unwrap());
@@ -245,7 +278,6 @@ fn read_u32(cursor: &mut &[u8]) -> Result<u32> {
Ok(value)
}
#[cfg(unix)]
fn read_ssh_string<'a>(cursor: &mut &'a [u8]) -> Result<&'a [u8]> {
let len = read_u32(cursor)? as usize;
anyhow::ensure!(cursor.len() >= len, "truncated SSH string");
@@ -254,13 +286,11 @@ fn read_ssh_string<'a>(cursor: &mut &'a [u8]) -> Result<&'a [u8]> {
Ok(value)
}
#[cfg(unix)]
fn write_ssh_string(out: &mut Vec<u8>, value: &[u8]) {
out.extend_from_slice(&(value.len() as u32).to_be_bytes());
out.extend_from_slice(value);
}
#[cfg(unix)]
fn key_blob_algorithm(blob: &[u8]) -> Result<String> {
let mut cursor = blob;
let algorithm = read_ssh_string(&mut cursor)?;