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
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:
Generated
+1
@@ -463,6 +463,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"toml",
|
||||
"vt100",
|
||||
"windows-sys 0.59.0",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)?;
|
||||
|
||||
Reference in New Issue
Block a user