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",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"vt100",
|
"vt100",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ toml = "0.8"
|
|||||||
vt100 = "0.15"
|
vt100 = "0.15"
|
||||||
x25519-dalek = { version = "2.0.1", features = ["static_secrets", "getrandom"] }
|
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]
|
[dev-dependencies]
|
||||||
tempfile = "3.14"
|
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() {}
|
fn flush_local_terminal_input() {}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[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() {}
|
fn drain_local_terminal_input() {}
|
||||||
|
|
||||||
impl Drop for RawMode {
|
impl Drop for RawMode {
|
||||||
|
|||||||
+70
-40
@@ -1,31 +1,28 @@
|
|||||||
use crate::native::{ForwardingRequest, NativeClientHello, NativeServerHello, NativeUserAuth};
|
use crate::native::{ForwardingRequest, NativeClientHello, NativeServerHello, NativeUserAuth};
|
||||||
#[cfg(unix)]
|
|
||||||
use crate::native::{
|
use crate::native::{
|
||||||
is_supported_user_key_algorithm, parse_ssh_ed25519_public_blob, user_auth_transcript,
|
is_supported_user_key_algorithm, parse_ssh_ed25519_public_blob, user_auth_transcript,
|
||||||
};
|
};
|
||||||
#[cfg(unix)]
|
|
||||||
use anyhow::{Context, bail};
|
use anyhow::{Context, bail};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
#[cfg(unix)]
|
#[cfg(windows)]
|
||||||
|
use std::fs::OpenOptions;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
const SSH_AGENT_FAILURE: u8 = 5;
|
const SSH_AGENT_FAILURE: u8 = 5;
|
||||||
#[cfg(unix)]
|
|
||||||
const SSH2_AGENTC_REQUEST_IDENTITIES: u8 = 11;
|
const SSH2_AGENTC_REQUEST_IDENTITIES: u8 = 11;
|
||||||
#[cfg(unix)]
|
|
||||||
const SSH2_AGENT_IDENTITIES_ANSWER: u8 = 12;
|
const SSH2_AGENT_IDENTITIES_ANSWER: u8 = 12;
|
||||||
#[cfg(unix)]
|
|
||||||
const SSH2_AGENTC_SIGN_REQUEST: u8 = 13;
|
const SSH2_AGENTC_SIGN_REQUEST: u8 = 13;
|
||||||
#[cfg(unix)]
|
|
||||||
const SSH2_AGENT_SIGN_RESPONSE: u8 = 14;
|
const SSH2_AGENT_SIGN_RESPONSE: u8 = 14;
|
||||||
#[cfg(unix)]
|
|
||||||
const SSH_AGENT_RSA_SHA2_512: u32 = 4;
|
const SSH_AGENT_RSA_SHA2_512: u32 = 4;
|
||||||
#[cfg(unix)]
|
|
||||||
const MAX_AGENT_PACKET: usize = 256 * 1024;
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AgentIdentity {
|
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)
|
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(
|
pub fn sign_user_auth_with_agent(
|
||||||
_client: &NativeClientHello,
|
_client: &NativeClientHello,
|
||||||
_server: &NativeServerHello,
|
_server: &NativeServerHello,
|
||||||
@@ -67,23 +78,30 @@ pub fn sign_user_auth_with_agent_at(
|
|||||||
) -> Result<NativeUserAuth> {
|
) -> Result<NativeUserAuth> {
|
||||||
let mut agent = UnixStream::connect(socket_path.as_ref())
|
let mut agent = UnixStream::connect(socket_path.as_ref())
|
||||||
.with_context(|| format!("connect ssh-agent {}", socket_path.as_ref().display()))?;
|
.with_context(|| format!("connect ssh-agent {}", socket_path.as_ref().display()))?;
|
||||||
let identities = request_supported_identities(&mut agent)?;
|
sign_user_auth_with_agent_stream(&mut agent, client, server, requested_forwardings)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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(
|
pub fn sign_user_auth_with_agent_at(
|
||||||
_socket_path: impl AsRef<Path>,
|
_socket_path: impl AsRef<Path>,
|
||||||
_client: &NativeClientHello,
|
_client: &NativeClientHello,
|
||||||
@@ -95,8 +113,29 @@ pub fn sign_user_auth_with_agent_at(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
fn sign_user_auth_with_agent_stream(
|
||||||
fn request_supported_identities(agent: &mut UnixStream) -> Result<Vec<AgentIdentity>> {
|
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])?;
|
write_agent_packet(agent, &[SSH2_AGENTC_REQUEST_IDENTITIES])?;
|
||||||
let payload = read_agent_packet(agent)?;
|
let payload = read_agent_packet(agent)?;
|
||||||
let mut cursor = payload.as_slice();
|
let mut cursor = payload.as_slice();
|
||||||
@@ -122,7 +161,6 @@ fn request_supported_identities(agent: &mut UnixStream) -> Result<Vec<AgentIdent
|
|||||||
Ok(identities)
|
Ok(identities)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn supported_identity(key_blob: Vec<u8>, comment: String) -> Result<Option<AgentIdentity>> {
|
fn supported_identity(key_blob: Vec<u8>, comment: String) -> Result<Option<AgentIdentity>> {
|
||||||
let algorithm = key_blob_algorithm(&key_blob)?;
|
let algorithm = key_blob_algorithm(&key_blob)?;
|
||||||
if !is_supported_user_key_algorithm(&algorithm) {
|
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))
|
Ok(Some(identity))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn sign_with_agent(
|
fn sign_with_agent(
|
||||||
agent: &mut UnixStream,
|
agent: &mut impl AgentStream,
|
||||||
identity: &AgentIdentity,
|
identity: &AgentIdentity,
|
||||||
transcript: &[u8],
|
transcript: &[u8],
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
@@ -203,8 +240,7 @@ fn sign_with_agent(
|
|||||||
Ok(signature.to_vec())
|
Ok(signature.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
fn read_agent_packet(stream: &mut impl AgentStream) -> Result<Vec<u8>> {
|
||||||
fn read_agent_packet(stream: &mut UnixStream) -> Result<Vec<u8>> {
|
|
||||||
let mut len = [0u8; 4];
|
let mut len = [0u8; 4];
|
||||||
stream
|
stream
|
||||||
.read_exact(&mut len)
|
.read_exact(&mut len)
|
||||||
@@ -218,8 +254,7 @@ fn read_agent_packet(stream: &mut UnixStream) -> Result<Vec<u8>> {
|
|||||||
Ok(payload)
|
Ok(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
fn write_agent_packet(stream: &mut impl AgentStream, payload: &[u8]) -> Result<()> {
|
||||||
fn write_agent_packet(stream: &mut UnixStream, payload: &[u8]) -> Result<()> {
|
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
payload.len() <= MAX_AGENT_PACKET,
|
payload.len() <= MAX_AGENT_PACKET,
|
||||||
"ssh-agent request too large"
|
"ssh-agent request too large"
|
||||||
@@ -229,7 +264,6 @@ fn write_agent_packet(stream: &mut UnixStream, payload: &[u8]) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn read_u8(cursor: &mut &[u8]) -> Result<u8> {
|
fn read_u8(cursor: &mut &[u8]) -> Result<u8> {
|
||||||
anyhow::ensure!(!cursor.is_empty(), "truncated u8");
|
anyhow::ensure!(!cursor.is_empty(), "truncated u8");
|
||||||
let value = cursor[0];
|
let value = cursor[0];
|
||||||
@@ -237,7 +271,6 @@ fn read_u8(cursor: &mut &[u8]) -> Result<u8> {
|
|||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn read_u32(cursor: &mut &[u8]) -> Result<u32> {
|
fn read_u32(cursor: &mut &[u8]) -> Result<u32> {
|
||||||
anyhow::ensure!(cursor.len() >= 4, "truncated u32");
|
anyhow::ensure!(cursor.len() >= 4, "truncated u32");
|
||||||
let value = u32::from_be_bytes(cursor[..4].try_into().unwrap());
|
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)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn read_ssh_string<'a>(cursor: &mut &'a [u8]) -> Result<&'a [u8]> {
|
fn read_ssh_string<'a>(cursor: &mut &'a [u8]) -> Result<&'a [u8]> {
|
||||||
let len = read_u32(cursor)? as usize;
|
let len = read_u32(cursor)? as usize;
|
||||||
anyhow::ensure!(cursor.len() >= len, "truncated SSH string");
|
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)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn write_ssh_string(out: &mut Vec<u8>, value: &[u8]) {
|
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.len() as u32).to_be_bytes());
|
||||||
out.extend_from_slice(value);
|
out.extend_from_slice(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn key_blob_algorithm(blob: &[u8]) -> Result<String> {
|
fn key_blob_algorithm(blob: &[u8]) -> Result<String> {
|
||||||
let mut cursor = blob;
|
let mut cursor = blob;
|
||||||
let algorithm = read_ssh_string(&mut cursor)?;
|
let algorithm = read_ssh_string(&mut cursor)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user