mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-16 17:35:04 +01:00
initial privilege separation
This commit is contained in:
parent
46f77b7f58
commit
543b1b6e76
15 changed files with 887 additions and 108 deletions
89
Cargo.lock
generated
89
Cargo.lock
generated
|
|
@ -110,6 +110,15 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-polyfill"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
@ -396,6 +405,7 @@ dependencies = [
|
||||||
"p256",
|
"p256",
|
||||||
"pem",
|
"pem",
|
||||||
"rand",
|
"rand",
|
||||||
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
@ -406,6 +416,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cluelessh-connection",
|
"cluelessh-connection",
|
||||||
"cluelessh-format",
|
"cluelessh-format",
|
||||||
|
"cluelessh-keys",
|
||||||
"cluelessh-transport",
|
"cluelessh-transport",
|
||||||
"rand",
|
"rand",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -416,6 +427,7 @@ name = "cluelessh-tokio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cluelessh-connection",
|
"cluelessh-connection",
|
||||||
|
"cluelessh-keys",
|
||||||
"cluelessh-protocol",
|
"cluelessh-protocol",
|
||||||
"cluelessh-transport",
|
"cluelessh-transport",
|
||||||
"eyre",
|
"eyre",
|
||||||
|
|
@ -451,6 +463,7 @@ dependencies = [
|
||||||
name = "cluelesshd"
|
name = "cluelesshd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"cluelessh-format",
|
"cluelessh-format",
|
||||||
"cluelessh-keys",
|
"cluelessh-keys",
|
||||||
"cluelessh-protocol",
|
"cluelessh-protocol",
|
||||||
|
|
@ -458,6 +471,7 @@ dependencies = [
|
||||||
"cluelessh-transport",
|
"cluelessh-transport",
|
||||||
"eyre",
|
"eyre",
|
||||||
"futures",
|
"futures",
|
||||||
|
"postcard",
|
||||||
"rustix",
|
"rustix",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
@ -468,6 +482,12 @@ dependencies = [
|
||||||
"users",
|
"users",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cobs"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
@ -489,6 +509,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "critical-section"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-bigint"
|
name = "crypto-bigint"
|
||||||
version = "0.5.5"
|
version = "0.5.5"
|
||||||
|
|
@ -631,6 +657,18 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
@ -811,12 +849,35 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hash32"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heapless"
|
||||||
|
version = "0.7.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-polyfill",
|
||||||
|
"hash32",
|
||||||
|
"rustc_version",
|
||||||
|
"serde",
|
||||||
|
"spin",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
@ -1111,6 +1172,19 @@ dependencies = [
|
||||||
"universal-hash",
|
"universal-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postcard"
|
||||||
|
version = "1.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e"
|
||||||
|
dependencies = [
|
||||||
|
"cobs",
|
||||||
|
"embedded-io 0.4.0",
|
||||||
|
"embedded-io 0.6.1",
|
||||||
|
"heapless",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
|
|
@ -1434,6 +1508,15 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.9.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spki"
|
name = "spki"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
|
|
@ -1444,6 +1527,12 @@ dependencies = [
|
||||||
"der",
|
"der",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ mod readline;
|
||||||
use std::{net::SocketAddr, sync::Arc};
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
use cluelessh_keys::private::EncryptedPrivateKeys;
|
use cluelessh_keys::private::EncryptedPrivateKeys;
|
||||||
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
|
use cluelessh_tokio::{server::ServerAuth, Channel};
|
||||||
use eyre::{Context, Result};
|
use eyre::{Context, OptionExt, Result};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
|
|
@ -40,7 +40,25 @@ async fn main() -> eyre::Result<()> {
|
||||||
|
|
||||||
let listener = TcpListener::bind(addr).await.wrap_err("binding listener")?;
|
let listener = TcpListener::bind(addr).await.wrap_err("binding listener")?;
|
||||||
|
|
||||||
let auth_verify = ServerAuthVerify {
|
let host_keys = vec![
|
||||||
|
EncryptedPrivateKeys::parse(ED25519_PRIVKEY.as_bytes())
|
||||||
|
.unwrap()
|
||||||
|
.decrypt(None)
|
||||||
|
.unwrap()
|
||||||
|
.remove(0),
|
||||||
|
EncryptedPrivateKeys::parse(ECDSA_PRIVKEY.as_bytes())
|
||||||
|
.unwrap()
|
||||||
|
.decrypt(None)
|
||||||
|
.unwrap()
|
||||||
|
.remove(0),
|
||||||
|
];
|
||||||
|
|
||||||
|
let pub_host_keys = host_keys
|
||||||
|
.iter()
|
||||||
|
.map(|key| key.private_key.public_key())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let auth_verify = ServerAuth {
|
||||||
verify_password: Some(Arc::new(|auth| {
|
verify_password: Some(Arc::new(|auth| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
info!(password = %auth.password, "Got password");
|
info!(password = %auth.password, "Got password");
|
||||||
|
|
@ -59,21 +77,21 @@ async fn main() -> eyre::Result<()> {
|
||||||
!! DO NOT ENTER PASSWORDS YOU DON'T WANT STOLEN !!\r\n"
|
!! DO NOT ENTER PASSWORDS YOU DON'T WANT STOLEN !!\r\n"
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
),
|
),
|
||||||
|
sign_with_hostkey: Arc::new(move |msg| {
|
||||||
|
let host_keys = host_keys.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
let private = host_keys
|
||||||
|
.iter()
|
||||||
|
.find(|privkey| privkey.private_key.public_key() == msg.public_key)
|
||||||
|
.ok_or_eyre("missing private key")?;
|
||||||
|
|
||||||
|
Ok(private.private_key.sign(&msg.hash))
|
||||||
|
})
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let transport_config = cluelessh_protocol::transport::server::ServerConfig {
|
let transport_config = cluelessh_protocol::transport::server::ServerConfig {
|
||||||
host_keys: vec![
|
host_keys: pub_host_keys,
|
||||||
EncryptedPrivateKeys::parse(ED25519_PRIVKEY.as_bytes())
|
|
||||||
.unwrap()
|
|
||||||
.decrypt(None)
|
|
||||||
.unwrap()
|
|
||||||
.remove(0),
|
|
||||||
EncryptedPrivateKeys::parse(ECDSA_PRIVKEY.as_bytes())
|
|
||||||
.unwrap()
|
|
||||||
.decrypt(None)
|
|
||||||
.unwrap()
|
|
||||||
.remove(0),
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut listener =
|
let mut listener =
|
||||||
|
|
@ -187,9 +205,11 @@ async fn handle_session_channel(
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = execute_command(&command);
|
let result = execute_command(&command);
|
||||||
channel
|
if !result.stdout.is_empty() {
|
||||||
.send(ChannelOperationKind::Data(result.stdout))
|
channel
|
||||||
.await?;
|
.send(ChannelOperationKind::Data(result.stdout))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
channel
|
channel
|
||||||
.send(ChannelOperationKind::Request(ChannelRequest::ExitStatus {
|
.send(ChannelOperationKind::Request(ChannelRequest::ExitStatus {
|
||||||
status: result.status,
|
status: result.status,
|
||||||
|
|
@ -221,6 +241,7 @@ async fn handle_session_channel(
|
||||||
readline.recv_bytes(&data);
|
readline.recv_bytes(&data);
|
||||||
let to_write = readline.bytes_to_write();
|
let to_write = readline.bytes_to_write();
|
||||||
if !to_write.is_empty() {
|
if !to_write.is_empty() {
|
||||||
|
// TODO: introduce helper to Channel that allows writing 0 data
|
||||||
channel.send(ChannelOperationKind::Data(to_write)).await?;
|
channel.send(ChannelOperationKind::Data(to_write)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ thiserror = "1.0.63"
|
||||||
cluelessh-keys = { version = "0.1.0", path = "../../lib/cluelessh-keys" }
|
cluelessh-keys = { version = "0.1.0", path = "../../lib/cluelessh-keys" }
|
||||||
serde = { version = "1.0.209", features = ["derive"] }
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
|
clap = { version = "4.5.16", features = ["derive"] }
|
||||||
|
postcard = { version = "1.0.10", features = ["alloc"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use eyre::{Context, Result};
|
use eyre::{Context, Result};
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
net::{IpAddr, Ipv4Addr},
|
net::{IpAddr, Ipv4Addr},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
use crate::Args;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(default = "default_info")]
|
#[serde(default = "default_info")]
|
||||||
|
|
@ -14,7 +16,7 @@ pub struct Config {
|
||||||
pub auth: AuthConfig,
|
pub auth: AuthConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct NetConfig {
|
pub struct NetConfig {
|
||||||
#[serde(default = "addr_default")]
|
#[serde(default = "addr_default")]
|
||||||
|
|
@ -23,7 +25,7 @@ pub struct NetConfig {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct AuthConfig {
|
pub struct AuthConfig {
|
||||||
pub host_keys: Vec<PathBuf>,
|
pub host_keys: Vec<PathBuf>,
|
||||||
|
|
@ -33,15 +35,18 @@ pub struct AuthConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn find() -> Result<Self> {
|
pub fn find(args: &Args) -> Result<Self> {
|
||||||
let path =
|
let path = std::env::var("CLUELESSHD_CONFIG")
|
||||||
std::env::var("CLUELESSHD_CONFIG").unwrap_or_else(|_| "cluelesshd.toml".to_owned());
|
.map(PathBuf::from)
|
||||||
|
.or(args.config.clone().ok_or(std::env::VarError::NotPresent))
|
||||||
|
.unwrap_or_else(|_| PathBuf::from("cluelesshd.toml"));
|
||||||
|
|
||||||
let content = std::fs::read_to_string(&path).wrap_err_with(|| {
|
let content = std::fs::read_to_string(&path).wrap_err_with(|| {
|
||||||
format!("failed to open config file '{path}', refusing to start. you can change the config file path with the CLUELESSHD_CONFIG environment variable")
|
format!("failed to open config file '{}', refusing to start. you can change the config file path with the --config arg or the CLUELESSHD_CONFIG environment variable", path.display())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
toml::from_str(&content).wrap_err_with(|| format!("invalid config file '{path}'"))
|
toml::from_str(&content)
|
||||||
|
.wrap_err_with(|| format!("invalid config file '{}'", path.display()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,30 @@
|
||||||
mod auth;
|
mod auth;
|
||||||
mod config;
|
mod config;
|
||||||
mod pty;
|
mod pty;
|
||||||
|
mod rpc;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
io::{self, Read, Seek, SeekFrom},
|
||||||
|
marker::PhantomData,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
|
os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
process::{ExitStatus, Stdio},
|
process::{ExitStatus, Stdio},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use cluelessh_keys::{host_keys::HostKeySet, private::EncryptedPrivateKeys};
|
use clap::Parser;
|
||||||
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
|
use cluelessh_keys::{host_keys::HostKeySet, private::EncryptedPrivateKeys, public::PublicKey};
|
||||||
use cluelessh_transport::server::ServerConfig;
|
use cluelessh_tokio::{
|
||||||
|
server::{ServerAuth, ServerConnection, SignWithHostKey},
|
||||||
|
Channel,
|
||||||
|
};
|
||||||
|
use config::Config;
|
||||||
use eyre::{bail, Context, OptionExt, Result};
|
use eyre::{bail, Context, OptionExt, Result};
|
||||||
use pty::Pty;
|
use pty::Pty;
|
||||||
use rustix::termios::Winsize;
|
use rustix::{fs::MemfdFlags, termios::Winsize};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
||||||
|
|
@ -27,66 +35,324 @@ use tokio::{
|
||||||
use tracing::{debug, error, info, info_span, warn, Instrument};
|
use tracing::{debug, error, info, info_span, warn, Instrument};
|
||||||
|
|
||||||
use cluelessh_protocol::{
|
use cluelessh_protocol::{
|
||||||
|
auth::{CheckPubkey, VerifySignature},
|
||||||
connection::{ChannelKind, ChannelOperationKind, ChannelRequest},
|
connection::{ChannelKind, ChannelOperationKind, ChannelRequest},
|
||||||
ChannelUpdateKind, SshStatus,
|
ChannelUpdateKind, SshStatus,
|
||||||
};
|
};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use users::os::unix::UserExt;
|
use users::os::unix::UserExt;
|
||||||
|
|
||||||
|
#[derive(clap::Parser)]
|
||||||
|
struct Args {
|
||||||
|
/// The path to the config file
|
||||||
|
#[arg(long)]
|
||||||
|
config: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MemFd<T> {
|
||||||
|
fd: std::fs::File,
|
||||||
|
_data: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: serde::Serialize + serde::de::DeserializeOwned> MemFd<T> {
|
||||||
|
fn new(data: &T) -> Result<Self> {
|
||||||
|
let fd = rustix::fs::memfd_create("cluelesshd.toml", MemfdFlags::empty())
|
||||||
|
.wrap_err("failed to memfd memfd")?;
|
||||||
|
let mut fd: std::fs::File = std::fs::File::from(fd);
|
||||||
|
std::io::Write::write_all(&mut fd, &postcard::to_allocvec(data)?)
|
||||||
|
.wrap_err("failed to write config")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
fd,
|
||||||
|
_data: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn from_raw_fd(fd: RawFd) -> Result<Self> {
|
||||||
|
let fd = unsafe { std::fs::File::from_raw_fd(fd) };
|
||||||
|
Ok(Self {
|
||||||
|
fd,
|
||||||
|
_data: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&mut self) -> Result<T> {
|
||||||
|
self.fd.seek(SeekFrom::Start(0))?;
|
||||||
|
let mut data = Vec::new();
|
||||||
|
self.fd.read_to_end(&mut data).wrap_err("reading data")?;
|
||||||
|
postcard::from_bytes(&data).wrap_err("failed to deserialize")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
let config = config::Config::find()?;
|
match std::env::var("CLUELESSH_PRIVSEP_PROCESS") {
|
||||||
|
Ok(privsep_process) => match privsep_process.as_str() {
|
||||||
|
"connection" => connnection().await,
|
||||||
|
_ => bail!("unknown CLUELESSH_PRIVSEP_PROCESS: {privsep_process}"),
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
// Initial setup
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let config = config::Config::find(&args)?;
|
||||||
|
|
||||||
|
let env_filter = EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| EnvFilter::new(&config.log_level));
|
||||||
|
|
||||||
|
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||||
|
let addr: SocketAddr = SocketAddr::new(config.net.ip, config.net.port);
|
||||||
|
info!(%addr, "Starting server");
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(addr)
|
||||||
|
.await
|
||||||
|
.wrap_err_with(|| format!("trying to listen on {addr}"))?;
|
||||||
|
|
||||||
|
main_process(config, listener).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRIVSEP_CONNECTION_STATE_FD: RawFd = 3;
|
||||||
|
|
||||||
|
/// The connection state passed to the child in the STATE_FD
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct SerializedConnectionState {
|
||||||
|
stream_fd: RawFd,
|
||||||
|
peer_addr: SocketAddr,
|
||||||
|
pub_host_keys: Vec<PublicKey>,
|
||||||
|
config: Config,
|
||||||
|
rpc_client_fd: RawFd,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connnection() -> Result<()> {
|
||||||
|
rustix::fs::fcntl_getfd(unsafe { BorrowedFd::borrow_raw(PRIVSEP_CONNECTION_STATE_FD) })
|
||||||
|
.unwrap();
|
||||||
|
let mut memfd =
|
||||||
|
unsafe { MemFd::<SerializedConnectionState>::from_raw_fd(PRIVSEP_CONNECTION_STATE_FD) }
|
||||||
|
.wrap_err("failed to open memfd")?;
|
||||||
|
let state = memfd.read().wrap_err("failed to read state")?;
|
||||||
|
|
||||||
|
let config = state.config;
|
||||||
|
|
||||||
let env_filter =
|
let env_filter =
|
||||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&config.log_level));
|
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&config.log_level));
|
||||||
|
|
||||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||||
|
|
||||||
let addr = SocketAddr::new(config.net.ip, config.net.port);
|
let span = info_span!("connection", addr = %state.peer_addr);
|
||||||
info!(%addr, "Starting server");
|
|
||||||
|
|
||||||
let listener = TcpListener::bind(addr)
|
let stream = unsafe { std::net::TcpStream::from_raw_fd(state.stream_fd) };
|
||||||
.await
|
let stream = TcpStream::from_std(stream)?;
|
||||||
.wrap_err_with(|| format!("trying to listen on {addr}"))?;
|
|
||||||
|
|
||||||
let auth_verify = ServerAuthVerify {
|
let host_keys = state.pub_host_keys;
|
||||||
|
let transport_config = cluelessh_transport::server::ServerConfig { host_keys };
|
||||||
|
|
||||||
|
let rpc_client = unsafe { OwnedFd::from_raw_fd(state.rpc_client_fd) };
|
||||||
|
let rpc_client1 = Arc::new(rpc::Client::from_fd(rpc_client)?);
|
||||||
|
let rpc_client2 = rpc_client1.clone();
|
||||||
|
let rpc_client3 = rpc_client1.clone();
|
||||||
|
|
||||||
|
let auth_verify = ServerAuth {
|
||||||
verify_password: config.auth.password_login.then(|| todo!("password login")),
|
verify_password: config.auth.password_login.then(|| todo!("password login")),
|
||||||
verify_signature: Some(Arc::new(|auth| Box::pin(auth::verify_signature(auth)))),
|
verify_signature: Some(Arc::new(move |msg: VerifySignature| {
|
||||||
check_pubkey: Some(Arc::new(|auth| Box::pin(auth::check_pubkey(auth)))),
|
let rpc_client = rpc_client1.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
rpc_client
|
||||||
|
.verify_signature(
|
||||||
|
msg.user,
|
||||||
|
msg.session_identifier,
|
||||||
|
msg.pubkey_alg_name,
|
||||||
|
msg.pubkey,
|
||||||
|
msg.signature,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
check_pubkey: Some(Arc::new(move |msg: CheckPubkey| {
|
||||||
|
let rpc_client = rpc_client2.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
rpc_client
|
||||||
|
.check_pubkey(
|
||||||
|
msg.user,
|
||||||
|
msg.session_identifier,
|
||||||
|
msg.pubkey_alg_name,
|
||||||
|
msg.pubkey,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
})),
|
||||||
auth_banner: config.auth.banner,
|
auth_banner: config.auth.banner,
|
||||||
|
sign_with_hostkey: Arc::new(move |msg: SignWithHostKey| {
|
||||||
|
let rpc_client = rpc_client3.clone();
|
||||||
|
Box::pin(async move { rpc_client.sign(msg.hash, msg.public_key).await })
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let server_conn = ServerConnection::new(stream, state.peer_addr, auth_verify, transport_config);
|
||||||
|
|
||||||
|
connection_inner(server_conn).instrument(span).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connection_inner(server_conn: ServerConnection<TcpStream>) {
|
||||||
|
if let Err(err) = handle_connection(server_conn).await {
|
||||||
|
if let Some(err) = err.downcast_ref::<std::io::Error>() {
|
||||||
|
if err.kind() == std::io::ErrorKind::ConnectionReset {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error!(?err, "error handling connection");
|
||||||
|
}
|
||||||
|
info!("Finished connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn main_process(config: Config, listener: TcpListener) -> Result<()> {
|
||||||
let host_keys = load_host_keys(&config.auth.host_keys).await?.into_keys();
|
let host_keys = load_host_keys(&config.auth.host_keys).await?.into_keys();
|
||||||
|
|
||||||
if host_keys.is_empty() {
|
if host_keys.is_empty() {
|
||||||
bail!("no host keys found");
|
bail!("no host keys found");
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = ServerConfig { host_keys };
|
let pub_host_keys = host_keys
|
||||||
|
.iter()
|
||||||
|
.map(|key| key.private_key.public_key())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut listener = cluelessh_tokio::server::ServerListener::new(listener, auth_verify, config);
|
let auth_operations = ServerAuth {
|
||||||
|
verify_password: config
|
||||||
|
.auth
|
||||||
|
.clone()
|
||||||
|
.password_login
|
||||||
|
.then(|| todo!("password login")),
|
||||||
|
verify_signature: Some(Arc::new(|auth| Box::pin(auth::verify_signature(auth)))),
|
||||||
|
check_pubkey: Some(Arc::new(|auth| Box::pin(auth::check_pubkey(auth)))),
|
||||||
|
auth_banner: config.auth.clone().banner,
|
||||||
|
sign_with_hostkey: Arc::new(move |msg: SignWithHostKey| {
|
||||||
|
let host_keys = host_keys.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
let private = host_keys
|
||||||
|
.iter()
|
||||||
|
.find(|privkey| privkey.private_key.public_key() == msg.public_key)
|
||||||
|
.ok_or_eyre("missing private key")?;
|
||||||
|
|
||||||
|
Ok(private.private_key.sign(&msg.hash))
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// let server_config = ServerConfig {
|
||||||
|
// host_keys: pub_host_keys,
|
||||||
|
// };
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let next = listener.accept().await?;
|
let (next_stream, peer_addr) = listener.accept().await?;
|
||||||
let span = info_span!("connection", addr = %next.peer_addr());
|
|
||||||
tokio::spawn(
|
|
||||||
async move {
|
|
||||||
if let Err(err) = handle_connection(next).await {
|
|
||||||
if let Some(err) = err.downcast_ref::<std::io::Error>() {
|
|
||||||
if err.kind() == std::io::ErrorKind::ConnectionReset {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error!(?err, "error handling connection");
|
// let server_conn = cluelessh_tokio::server::ServerConnection::new(
|
||||||
}
|
// next_stream,
|
||||||
info!("Finished connection");
|
// peer_addr,
|
||||||
|
// auth_verify.clone(),
|
||||||
|
// server_config.clone(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
let config = config.clone();
|
||||||
|
let pub_host_keys = pub_host_keys.clone();
|
||||||
|
let auth_operations = auth_operations.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let err = spawn_connection_child(
|
||||||
|
next_stream,
|
||||||
|
peer_addr,
|
||||||
|
pub_host_keys,
|
||||||
|
config,
|
||||||
|
auth_operations,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
if let Err(err) = err {
|
||||||
|
error!(?err, "child failed");
|
||||||
}
|
}
|
||||||
.instrument(span),
|
});
|
||||||
);
|
|
||||||
|
//tokio::spawn(
|
||||||
|
// async move {
|
||||||
|
// if let Err(err) = handle_connection(server_conn).await {
|
||||||
|
// if let Some(err) = err.downcast_ref::<std::io::Error>() {
|
||||||
|
// if err.kind() == std::io::ErrorKind::ConnectionReset {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// error!(?err, "error handling connection");
|
||||||
|
// }
|
||||||
|
// info!("Finished connection");
|
||||||
|
// }
|
||||||
|
// .instrument(span),
|
||||||
|
//);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn spawn_connection_child(
|
||||||
|
stream: TcpStream,
|
||||||
|
peer_addr: SocketAddr,
|
||||||
|
pub_host_keys: Vec<PublicKey>,
|
||||||
|
config: Config,
|
||||||
|
auth_operations: ServerAuth,
|
||||||
|
) -> Result<()> {
|
||||||
|
let stream_fd = stream.as_fd();
|
||||||
|
|
||||||
|
let rpc_server = rpc::Server::new(auth_operations).wrap_err("creating RPC server")?;
|
||||||
|
|
||||||
|
// dup to avoid cloexec
|
||||||
|
// TODO: we should probably do this in the child? not that it matters that much.
|
||||||
|
let stream_fd = rustix::io::dup(stream_fd).wrap_err("duping tcp stream")?;
|
||||||
|
let rpc_client_fd = rustix::io::dup(rpc_server.client_fd()).wrap_err("duping tcp stream")?;
|
||||||
|
|
||||||
|
let config_fd = MemFd::new(&SerializedConnectionState {
|
||||||
|
stream_fd: stream_fd.as_raw_fd(),
|
||||||
|
peer_addr,
|
||||||
|
pub_host_keys,
|
||||||
|
config,
|
||||||
|
rpc_client_fd: rpc_client_fd.as_raw_fd(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let exe = std::env::current_exe().wrap_err("failed to get current executable path")?;
|
||||||
|
let mut cmd = tokio::process::Command::new(exe);
|
||||||
|
cmd.env("CLUELESSH_PRIVSEP_PROCESS", "connection")
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let fd = config_fd.fd.as_raw_fd();
|
||||||
|
cmd.pre_exec(move || {
|
||||||
|
let mut state_fd = OwnedFd::from_raw_fd(PRIVSEP_CONNECTION_STATE_FD);
|
||||||
|
rustix::io::dup2(BorrowedFd::borrow_raw(fd), &mut state_fd)?;
|
||||||
|
// Ensure that it stays open in the child.
|
||||||
|
std::mem::forget(state_fd);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut listen_child = cmd.spawn().wrap_err("failed to spawn listener process")?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
server_err = rpc_server.process() => {
|
||||||
|
error!(err = ?server_err, "RPC server error");
|
||||||
|
}
|
||||||
|
status = listen_child.wait() => {
|
||||||
|
let status = status?;
|
||||||
|
if !status.success() {
|
||||||
|
bail!("connection child process failed: {}", status);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn load_host_keys(keys: &[PathBuf]) -> Result<HostKeySet> {
|
async fn load_host_keys(keys: &[PathBuf]) -> Result<HostKeySet> {
|
||||||
let mut host_keys = HostKeySet::new();
|
let mut host_keys = HostKeySet::new();
|
||||||
|
|
||||||
|
|
|
||||||
239
bin/cluelesshd/src/rpc.rs
Normal file
239
bin/cluelesshd/src/rpc.rs
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
//! [`postcard`]-based RPC between the different processes.
|
||||||
|
|
||||||
|
use std::os::fd::AsFd;
|
||||||
|
use std::os::fd::BorrowedFd;
|
||||||
|
use std::os::fd::OwnedFd;
|
||||||
|
|
||||||
|
use cluelessh_keys::public::PublicKey;
|
||||||
|
use cluelessh_keys::signature::Signature;
|
||||||
|
use cluelessh_protocol::auth::CheckPubkey;
|
||||||
|
use cluelessh_protocol::auth::VerifySignature;
|
||||||
|
use cluelessh_tokio::server::ServerAuth;
|
||||||
|
use cluelessh_tokio::server::SignWithHostKey;
|
||||||
|
use eyre::eyre;
|
||||||
|
use eyre::Context;
|
||||||
|
use eyre::Result;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::net::UnixDatagram;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
enum Request {
|
||||||
|
Sign {
|
||||||
|
hash: [u8; 32],
|
||||||
|
public_key: PublicKey,
|
||||||
|
},
|
||||||
|
VerifySignature {
|
||||||
|
user: String,
|
||||||
|
session_identifier: [u8; 32],
|
||||||
|
pubkey_alg_name: String,
|
||||||
|
pubkey: Vec<u8>,
|
||||||
|
signature: Vec<u8>,
|
||||||
|
},
|
||||||
|
CheckPubkey {
|
||||||
|
user: String,
|
||||||
|
session_identifier: [u8; 32],
|
||||||
|
pubkey_alg_name: String,
|
||||||
|
pubkey: Vec<u8>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct SignResponse {
|
||||||
|
signature: Result<Signature, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct VerifySignatureResponse {
|
||||||
|
is_ok: Result<bool, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct CheckPubkeyResponse {
|
||||||
|
is_ok: Result<bool, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Client {
|
||||||
|
socket: UnixDatagram,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Server {
|
||||||
|
server: UnixDatagram,
|
||||||
|
client: UnixDatagram,
|
||||||
|
auth_operations: ServerAuth,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub fn new(auth_operations: ServerAuth) -> Result<Self> {
|
||||||
|
let (server, client) = UnixDatagram::pair().wrap_err("creating socketpair")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
client,
|
||||||
|
auth_operations,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_fd(&self) -> BorrowedFd<'_> {
|
||||||
|
self.client.as_fd()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process(&self) -> Result<()> {
|
||||||
|
let mut req = [0; 1024];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let read = self
|
||||||
|
.server
|
||||||
|
.recv(&mut req)
|
||||||
|
.await
|
||||||
|
.wrap_err("receiving response")?;
|
||||||
|
|
||||||
|
let req = postcard::from_bytes::<Request>(&req[..read]).wrap_err("invalid request")?;
|
||||||
|
|
||||||
|
match req {
|
||||||
|
Request::Sign { hash, public_key } => {
|
||||||
|
let signature = (self.auth_operations.sign_with_hostkey)(SignWithHostKey {
|
||||||
|
hash,
|
||||||
|
public_key,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string());
|
||||||
|
|
||||||
|
self.respond(SignResponse { signature }).await?;
|
||||||
|
}
|
||||||
|
Request::VerifySignature {
|
||||||
|
user,
|
||||||
|
session_identifier,
|
||||||
|
pubkey_alg_name,
|
||||||
|
pubkey,
|
||||||
|
signature,
|
||||||
|
} => {
|
||||||
|
let Some(verify_signature) = &self.auth_operations.verify_signature else {
|
||||||
|
self.respond(VerifySignatureResponse {
|
||||||
|
is_ok: Err("public key login not supported".into()),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let is_ok = verify_signature(VerifySignature {
|
||||||
|
user,
|
||||||
|
session_identifier,
|
||||||
|
pubkey_alg_name,
|
||||||
|
pubkey,
|
||||||
|
signature,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string());
|
||||||
|
|
||||||
|
self.respond(VerifySignatureResponse { is_ok }).await?;
|
||||||
|
}
|
||||||
|
Request::CheckPubkey {
|
||||||
|
user,
|
||||||
|
session_identifier,
|
||||||
|
pubkey_alg_name,
|
||||||
|
pubkey,
|
||||||
|
} => {
|
||||||
|
let Some(check_pubkey) = &self.auth_operations.check_pubkey else {
|
||||||
|
self.respond(VerifySignatureResponse {
|
||||||
|
is_ok: Err("public key login not supported".into()),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let is_ok = check_pubkey(CheckPubkey {
|
||||||
|
user,
|
||||||
|
session_identifier,
|
||||||
|
pubkey_alg_name,
|
||||||
|
pubkey,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string());
|
||||||
|
|
||||||
|
self.respond(CheckPubkeyResponse { is_ok }).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn respond(&self, resp: impl Serialize) -> Result<()> {
|
||||||
|
self.server
|
||||||
|
.send(&postcard::to_allocvec(&resp)?)
|
||||||
|
.await
|
||||||
|
.wrap_err("sending response")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub fn from_fd(fd: OwnedFd) -> Result<Self> {
|
||||||
|
let socket = UnixDatagram::from_std(std::os::unix::net::UnixDatagram::from(fd))?;
|
||||||
|
Ok(Self { socket })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sign(&self, hash: [u8; 32], public_key: PublicKey) -> Result<Signature> {
|
||||||
|
let resp = self
|
||||||
|
.request_response::<SignResponse>(&Request::Sign { hash, public_key })
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
resp.signature.map_err(|err| eyre!(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_pubkey(
|
||||||
|
&self,
|
||||||
|
user: String,
|
||||||
|
session_identifier: [u8; 32],
|
||||||
|
pubkey_alg_name: String,
|
||||||
|
pubkey: Vec<u8>,
|
||||||
|
) -> Result<bool> {
|
||||||
|
let resp = self
|
||||||
|
.request_response::<CheckPubkeyResponse>(&Request::CheckPubkey {
|
||||||
|
user,
|
||||||
|
session_identifier,
|
||||||
|
pubkey_alg_name,
|
||||||
|
pubkey,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
resp.is_ok.map_err(|err| eyre!(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn verify_signature(
|
||||||
|
&self,
|
||||||
|
user: String,
|
||||||
|
session_identifier: [u8; 32],
|
||||||
|
pubkey_alg_name: String,
|
||||||
|
pubkey: Vec<u8>,
|
||||||
|
signature: Vec<u8>,
|
||||||
|
) -> Result<bool> {
|
||||||
|
let resp = self
|
||||||
|
.request_response::<VerifySignatureResponse>(&Request::VerifySignature {
|
||||||
|
user,
|
||||||
|
session_identifier,
|
||||||
|
pubkey_alg_name,
|
||||||
|
pubkey,
|
||||||
|
signature,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
resp.is_ok.map_err(|err| eyre!(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn request_response<Resp: DeserializeOwned>(&self, req: &Request) -> Result<Resp> {
|
||||||
|
self.socket
|
||||||
|
.send(&postcard::to_allocvec(&req)?)
|
||||||
|
.await
|
||||||
|
.wrap_err("sending request")?;
|
||||||
|
|
||||||
|
let mut resp = [0; 1024];
|
||||||
|
let read = self
|
||||||
|
.socket
|
||||||
|
.recv(&mut resp)
|
||||||
|
.await
|
||||||
|
.wrap_err("receiving response")?;
|
||||||
|
|
||||||
|
let resp =
|
||||||
|
postcard::from_bytes::<Resp>(&resp[..read]).wrap_err("invalid signature response")?;
|
||||||
|
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ base64 = "0.22.1"
|
||||||
cluelessh-format = { version = "0.1.0", path = "../cluelessh-format" }
|
cluelessh-format = { version = "0.1.0", path = "../cluelessh-format" }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
p256 = "0.13.2"
|
p256 = "0.13.2"
|
||||||
|
serde = "1.0.209"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,45 @@ fn b64encode(bytes: &[u8]) -> String {
|
||||||
base64::prelude::BASE64_STANDARD.encode(bytes)
|
base64::prelude::BASE64_STANDARD.encode(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for PublicKey {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_bytes(&self.to_wire_encoding())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for PublicKey {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
use serde::de;
|
||||||
|
|
||||||
|
struct Visitor;
|
||||||
|
impl<'de> de::Visitor<'de> for Visitor {
|
||||||
|
type Value = PublicKey;
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(formatter, "bytes encoded as an SSH public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_bytes<E>(self, bytes: &[u8]) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
PublicKey::from_wire_encoding(bytes).map_err(|err| {
|
||||||
|
serde::de::Error::custom(format_args!(
|
||||||
|
"invalid value: {}: {err}",
|
||||||
|
de::Unexpected::Bytes(bytes),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deserializer.deserialize_bytes(Visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,45 @@ impl Signature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for Signature {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_bytes(&self.to_wire_encoding())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for Signature {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
use serde::de;
|
||||||
|
|
||||||
|
struct Visitor;
|
||||||
|
impl<'de> de::Visitor<'de> for Visitor {
|
||||||
|
type Value = Signature;
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(formatter, "bytes encoded as an SSH signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_bytes<E>(self, bytes: &[u8]) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
Signature::from_wire_encoding(bytes).map_err(|err| {
|
||||||
|
serde::de::Error::custom(format_args!(
|
||||||
|
"invalid value: {}: {err}",
|
||||||
|
de::Unexpected::Bytes(bytes),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deserializer.deserialize_bytes(Visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PrivateKey {
|
impl PrivateKey {
|
||||||
pub fn sign(&self, data: &[u8]) -> Signature {
|
pub fn sign(&self, data: &[u8]) -> Signature {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
cluelessh-connection = { path = "../cluelessh-connection" }
|
cluelessh-connection = { path = "../cluelessh-connection" }
|
||||||
cluelessh-transport = { path = "../cluelessh-transport" }
|
cluelessh-transport = { path = "../cluelessh-transport" }
|
||||||
|
cluelessh-keys = { path = "../cluelessh-keys" }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
cluelessh-format = { version = "0.1.0", path = "../cluelessh-format" }
|
cluelessh-format = { version = "0.1.0", path = "../cluelessh-format" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ use std::mem;
|
||||||
|
|
||||||
use auth::AuthOption;
|
use auth::AuthOption;
|
||||||
use cluelessh_connection::ChannelOperation;
|
use cluelessh_connection::ChannelOperation;
|
||||||
|
use cluelessh_keys::public::PublicKey;
|
||||||
|
use cluelessh_keys::signature::Signature;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
// Re-exports
|
// Re-exports
|
||||||
|
|
@ -76,6 +78,14 @@ impl ServerConnection {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_waiting_on_signature(&self) -> Option<(&PublicKey, [u8; 32])> {
|
||||||
|
self.transport.is_waiting_on_signature()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_signature(&mut self, signature: Signature) {
|
||||||
|
self.transport.do_signature(signature);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn next_msg_to_send(&mut self) -> Option<cluelessh_transport::Msg> {
|
pub fn next_msg_to_send(&mut self) -> Option<cluelessh_transport::Msg> {
|
||||||
self.transport.next_msg_to_send()
|
self.transport.next_msg_to_send()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ eyre.workspace = true
|
||||||
cluelessh-transport = { path = "../cluelessh-transport" }
|
cluelessh-transport = { path = "../cluelessh-transport" }
|
||||||
cluelessh-connection = { path = "../cluelessh-connection" }
|
cluelessh-connection = { path = "../cluelessh-connection" }
|
||||||
cluelessh-protocol = { path = "../cluelessh-protocol" }
|
cluelessh-protocol = { path = "../cluelessh-protocol" }
|
||||||
|
cluelessh-keys = { path = "../cluelessh-keys" }
|
||||||
tokio = { version = "1.39.3", features = ["net"] }
|
tokio = { version = "1.39.3", features = ["net"] }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation};
|
use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation};
|
||||||
|
use cluelessh_keys::{public::PublicKey, signature::Signature};
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
|
|
@ -23,7 +24,7 @@ use crate::{Channel, ChannelState, PendingChannel};
|
||||||
|
|
||||||
pub struct ServerListener {
|
pub struct ServerListener {
|
||||||
listener: TcpListener,
|
listener: TcpListener,
|
||||||
auth_verify: ServerAuthVerify,
|
auth_verify: ServerAuth,
|
||||||
transport_config: cluelessh_transport::server::ServerConfig, // TODO ratelimits etc
|
transport_config: cluelessh_transport::server::ServerConfig, // TODO ratelimits etc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,27 +46,35 @@ pub struct ServerConnection<S> {
|
||||||
/// New channels opened by the peer.
|
/// New channels opened by the peer.
|
||||||
new_channels: VecDeque<Channel>,
|
new_channels: VecDeque<Channel>,
|
||||||
|
|
||||||
auth_verify: ServerAuthVerify,
|
signature_in_progress: bool,
|
||||||
|
auth_verify: ServerAuth,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Operation {
|
enum Operation {
|
||||||
VerifyPassword(String, Result<bool>),
|
VerifyPassword(String, Result<bool>),
|
||||||
CheckPubkey(Result<bool>, String, Vec<u8>),
|
CheckPubkey(Result<bool>, String, Vec<u8>),
|
||||||
VerifySignature(String, Result<bool>),
|
VerifySignature(String, Result<bool>),
|
||||||
|
SignatureReceived(Result<Signature>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type AuthFn<A, R> = Arc<dyn Fn(A) -> BoxFuture<'static, R> + Send + Sync>;
|
pub type AuthFn<A, R> = Arc<dyn Fn(A) -> BoxFuture<'static, R> + Send + Sync>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServerAuthVerify {
|
pub struct ServerAuth {
|
||||||
pub verify_password: Option<AuthFn<VerifyPassword, Result<bool>>>,
|
pub verify_password: Option<AuthFn<VerifyPassword, Result<bool>>>,
|
||||||
pub verify_signature: Option<AuthFn<VerifySignature, Result<bool>>>,
|
pub verify_signature: Option<AuthFn<VerifySignature, Result<bool>>>,
|
||||||
pub check_pubkey: Option<AuthFn<CheckPubkey, Result<bool>>>,
|
pub check_pubkey: Option<AuthFn<CheckPubkey, Result<bool>>>,
|
||||||
|
pub sign_with_hostkey: AuthFn<SignWithHostKey, Result<Signature>>,
|
||||||
pub auth_banner: Option<String>,
|
pub auth_banner: Option<String>,
|
||||||
}
|
}
|
||||||
fn _assert_send_sync() {
|
fn _assert_send_sync() {
|
||||||
fn send<T: Send + Sync>() {}
|
fn send<T: Send + Sync>() {}
|
||||||
send::<ServerAuthVerify>();
|
send::<ServerAuth>();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SignWithHostKey {
|
||||||
|
pub hash: [u8; 32],
|
||||||
|
pub public_key: PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
|
@ -81,7 +90,7 @@ impl From<eyre::Report> for Error {
|
||||||
impl ServerListener {
|
impl ServerListener {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
listener: TcpListener,
|
listener: TcpListener,
|
||||||
auth_verify: ServerAuthVerify,
|
auth_verify: ServerAuth,
|
||||||
transport_config: cluelessh_transport::server::ServerConfig,
|
transport_config: cluelessh_transport::server::ServerConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -107,7 +116,7 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
stream: S,
|
stream: S,
|
||||||
peer_addr: SocketAddr,
|
peer_addr: SocketAddr,
|
||||||
auth_verify: ServerAuthVerify,
|
auth_verify: ServerAuth,
|
||||||
transport_config: cluelessh_transport::server::ServerConfig,
|
transport_config: cluelessh_transport::server::ServerConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (operations_send, operations_recv) = tokio::sync::mpsc::channel(15);
|
let (operations_send, operations_recv) = tokio::sync::mpsc::channel(15);
|
||||||
|
|
@ -149,6 +158,7 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
),
|
),
|
||||||
new_channels: VecDeque::new(),
|
new_channels: VecDeque::new(),
|
||||||
auth_verify,
|
auth_verify,
|
||||||
|
signature_in_progress: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,6 +169,20 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
/// Executes one loop iteration of the main loop.
|
/// Executes one loop iteration of the main loop.
|
||||||
// IMPORTANT: no operations on this struct should ever block the main loop, except this one.
|
// IMPORTANT: no operations on this struct should ever block the main loop, except this one.
|
||||||
pub async fn progress(&mut self) -> Result<(), Error> {
|
pub async fn progress(&mut self) -> Result<(), Error> {
|
||||||
|
if let Some((public_key, hash)) = self.proto.is_waiting_on_signature() {
|
||||||
|
if !self.signature_in_progress {
|
||||||
|
self.signature_in_progress = true;
|
||||||
|
|
||||||
|
let send = self.operations_send.clone();
|
||||||
|
let public_key = public_key.clone();
|
||||||
|
let sign_with_hostkey = self.auth_verify.sign_with_hostkey.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let result = sign_with_hostkey(SignWithHostKey { public_key, hash }).await;
|
||||||
|
let _ = send.send(Operation::SignatureReceived(result)).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(auth) = self.proto.auth() {
|
if let Some(auth) = self.proto.auth() {
|
||||||
for req in auth.server_requests() {
|
for req in auth.server_requests() {
|
||||||
match req {
|
match req {
|
||||||
|
|
@ -329,6 +353,10 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
Some(Operation::VerifyPassword(user, result)) => if let Some(auth) = self.proto.auth() {
|
Some(Operation::VerifyPassword(user, result)) => if let Some(auth) = self.proto.auth() {
|
||||||
auth.verification_result(result?, user);
|
auth.verification_result(result?, user);
|
||||||
},
|
},
|
||||||
|
Some(Operation::SignatureReceived(signature)) => {
|
||||||
|
let signature = signature?;
|
||||||
|
self.proto.do_signature(signature);
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
self.send_off_data().await?;
|
self.send_off_data().await?;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
pub mod encrypt;
|
pub mod encrypt;
|
||||||
|
|
||||||
use cluelessh_keys::{
|
use cluelessh_keys::{public::PublicKey, signature::Signature};
|
||||||
private::{PlaintextPrivateKey, PrivateKey},
|
|
||||||
public::PublicKey,
|
|
||||||
signature::Signature,
|
|
||||||
};
|
|
||||||
use p256::ecdsa::signature::Verifier;
|
use p256::ecdsa::signature::Verifier;
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
|
|
||||||
|
|
@ -110,26 +106,21 @@ impl AlgorithmName for EncryptionAlgorithm {
|
||||||
pub struct EncodedSshSignature(pub Vec<u8>);
|
pub struct EncodedSshSignature(pub Vec<u8>);
|
||||||
|
|
||||||
pub struct HostKeySigningAlgorithm {
|
pub struct HostKeySigningAlgorithm {
|
||||||
private_key: Box<PrivateKey>,
|
public_key: PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlgorithmName for HostKeySigningAlgorithm {
|
impl AlgorithmName for HostKeySigningAlgorithm {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
self.private_key.algorithm_name()
|
self.public_key.algorithm_name()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostKeySigningAlgorithm {
|
impl HostKeySigningAlgorithm {
|
||||||
pub fn new(private_key: PrivateKey) -> Self {
|
pub fn new(public_key: PublicKey) -> Self {
|
||||||
Self {
|
Self { public_key }
|
||||||
private_key: Box::new(private_key),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn sign(&self, data: &[u8]) -> Signature {
|
|
||||||
self.private_key.sign(data)
|
|
||||||
}
|
}
|
||||||
pub fn public_key(&self) -> PublicKey {
|
pub fn public_key(&self) -> PublicKey {
|
||||||
self.private_key.public_key()
|
self.public_key.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,10 +244,10 @@ pub struct SupportedAlgorithms {
|
||||||
|
|
||||||
impl SupportedAlgorithms {
|
impl SupportedAlgorithms {
|
||||||
/// A secure default using elliptic curves and AEAD.
|
/// A secure default using elliptic curves and AEAD.
|
||||||
pub fn secure(host_keys: &[PlaintextPrivateKey]) -> Self {
|
pub fn secure(host_keys: &[PublicKey]) -> Self {
|
||||||
let supported_host_keys = host_keys
|
let supported_host_keys = host_keys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|key| HostKeySigningAlgorithm::new(key.private_key.clone()))
|
.map(|key| HostKeySigningAlgorithm::new(key.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ use crate::Result;
|
||||||
use crate::{peer_error, Msg, SshRng, SshStatus};
|
use crate::{peer_error, Msg, SshRng, SshStatus};
|
||||||
use cluelessh_format::numbers;
|
use cluelessh_format::numbers;
|
||||||
use cluelessh_format::{NameList, Reader, Writer};
|
use cluelessh_format::{NameList, Reader, Writer};
|
||||||
|
use cluelessh_keys::public::PublicKey;
|
||||||
|
use cluelessh_keys::signature::Signature;
|
||||||
use tracing::{debug, info, trace};
|
use tracing::{debug, info, trace};
|
||||||
|
|
||||||
// This is definitely who we are.
|
// This is definitely who we are.
|
||||||
|
|
@ -28,7 +30,7 @@ pub struct ServerConnection {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
pub host_keys: Vec<cluelessh_keys::private::PlaintextPrivateKey>,
|
pub host_keys: Vec<cluelessh_keys::public::PublicKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServerState {
|
enum ServerState {
|
||||||
|
|
@ -47,9 +49,21 @@ enum ServerState {
|
||||||
encryption_client_to_server: EncryptionAlgorithm,
|
encryption_client_to_server: EncryptionAlgorithm,
|
||||||
encryption_server_to_client: EncryptionAlgorithm,
|
encryption_server_to_client: EncryptionAlgorithm,
|
||||||
},
|
},
|
||||||
|
WaitingForSignature {
|
||||||
|
/// h
|
||||||
|
hash: [u8; 32],
|
||||||
|
pub_hostkey: PublicKey,
|
||||||
|
/// k
|
||||||
|
shared_secret: Vec<u8>,
|
||||||
|
server_ephemeral_public_key: Vec<u8>,
|
||||||
|
encryption_client_to_server: EncryptionAlgorithm,
|
||||||
|
encryption_server_to_client: EncryptionAlgorithm,
|
||||||
|
},
|
||||||
NewKeys {
|
NewKeys {
|
||||||
h: [u8; 32],
|
/// h
|
||||||
k: Vec<u8>,
|
hash: [u8; 32],
|
||||||
|
/// k
|
||||||
|
shared_secret: Vec<u8>,
|
||||||
encryption_client_to_server: EncryptionAlgorithm,
|
encryption_client_to_server: EncryptionAlgorithm,
|
||||||
encryption_server_to_client: EncryptionAlgorithm,
|
encryption_server_to_client: EncryptionAlgorithm,
|
||||||
},
|
},
|
||||||
|
|
@ -242,11 +256,11 @@ impl ServerConnection {
|
||||||
} => {
|
} => {
|
||||||
let dh = KeyExchangeEcDhInitPacket::parse(&packet.payload)?;
|
let dh = KeyExchangeEcDhInitPacket::parse(&packet.payload)?;
|
||||||
|
|
||||||
let client_public_key = dh.qc;
|
let client_ephemeral_public_key = dh.qc;
|
||||||
|
|
||||||
let server_secret = (kex_algorithm.generate_secret)(&mut *self.rng);
|
let server_secret = (kex_algorithm.generate_secret)(&mut *self.rng);
|
||||||
let server_public_key = server_secret.pubkey;
|
let server_ephemeral_public_key = server_secret.pubkey;
|
||||||
let shared_secret = (server_secret.exchange)(client_public_key)?;
|
let shared_secret = (server_secret.exchange)(client_ephemeral_public_key)?;
|
||||||
let pub_hostkey = server_host_key_algorithm.public_key();
|
let pub_hostkey = server_host_key_algorithm.public_key();
|
||||||
|
|
||||||
let hash = crypto::key_exchange_hash(
|
let hash = crypto::key_exchange_hash(
|
||||||
|
|
@ -255,35 +269,31 @@ impl ServerConnection {
|
||||||
client_kexinit,
|
client_kexinit,
|
||||||
server_kexinit,
|
server_kexinit,
|
||||||
&pub_hostkey.to_wire_encoding(),
|
&pub_hostkey.to_wire_encoding(),
|
||||||
client_public_key,
|
client_ephemeral_public_key,
|
||||||
&server_public_key,
|
&server_ephemeral_public_key,
|
||||||
&shared_secret,
|
&shared_secret,
|
||||||
);
|
);
|
||||||
|
|
||||||
let signature = server_host_key_algorithm.sign(&hash);
|
// eprintln!("client_ephemeral_public_key: {:x?}", client_ephemeral_public_key);
|
||||||
|
// eprintln!("server_ephemeral_public_key: {:x?}", server_ephemeral_public_key);
|
||||||
|
// eprintln!("shared_secret: {:x?}", shared_secret);
|
||||||
|
// eprintln!("hash: {:x?}", hash);
|
||||||
|
|
||||||
// eprintln!("client_public_key: {:x?}", client_public_key);
|
self.state = ServerState::WaitingForSignature {
|
||||||
// eprintln!("server_public_key: {:x?}", server_public_key);
|
hash,
|
||||||
// eprintln!("shared_secret: {:x?}", shared_secret);
|
pub_hostkey,
|
||||||
// eprintln!("hash: {:x?}", hash);
|
shared_secret,
|
||||||
|
server_ephemeral_public_key,
|
||||||
let packet = Packet::new_msg_kex_ecdh_reply(
|
|
||||||
&pub_hostkey.to_wire_encoding(),
|
|
||||||
&server_public_key,
|
|
||||||
&signature.to_wire_encoding(),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.packet_transport.queue_packet(packet);
|
|
||||||
self.state = ServerState::NewKeys {
|
|
||||||
h: hash,
|
|
||||||
k: shared_secret,
|
|
||||||
encryption_client_to_server: *encryption_client_to_server,
|
encryption_client_to_server: *encryption_client_to_server,
|
||||||
encryption_server_to_client: *encryption_server_to_client,
|
encryption_server_to_client: *encryption_server_to_client,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
ServerState::WaitingForSignature { .. } => {
|
||||||
|
return Err(peer_error!("unexpected packet"));
|
||||||
|
}
|
||||||
ServerState::NewKeys {
|
ServerState::NewKeys {
|
||||||
h,
|
hash: h,
|
||||||
k,
|
shared_secret: k,
|
||||||
encryption_client_to_server,
|
encryption_client_to_server,
|
||||||
encryption_server_to_client,
|
encryption_server_to_client,
|
||||||
} => {
|
} => {
|
||||||
|
|
@ -344,6 +354,43 @@ impl ServerConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_waiting_on_signature(&self) -> Option<(&PublicKey, [u8; 32])> {
|
||||||
|
match &self.state {
|
||||||
|
ServerState::WaitingForSignature {
|
||||||
|
pub_hostkey, hash, ..
|
||||||
|
} => Some((pub_hostkey, *hash)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_signature(&mut self, signature: Signature) {
|
||||||
|
match &self.state {
|
||||||
|
ServerState::WaitingForSignature {
|
||||||
|
hash,
|
||||||
|
pub_hostkey,
|
||||||
|
shared_secret,
|
||||||
|
server_ephemeral_public_key,
|
||||||
|
encryption_client_to_server,
|
||||||
|
encryption_server_to_client,
|
||||||
|
} => {
|
||||||
|
let packet = Packet::new_msg_kex_ecdh_reply(
|
||||||
|
&pub_hostkey.to_wire_encoding(),
|
||||||
|
&server_ephemeral_public_key,
|
||||||
|
&signature.to_wire_encoding(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.packet_transport.queue_packet(packet);
|
||||||
|
self.state = ServerState::NewKeys {
|
||||||
|
hash: *hash,
|
||||||
|
shared_secret: shared_secret.clone(),
|
||||||
|
encryption_client_to_server: *encryption_client_to_server,
|
||||||
|
encryption_server_to_client: *encryption_server_to_client,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => unreachable!("doing signature while not waiting for it"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn next_msg_to_send(&mut self) -> Option<Msg> {
|
pub fn next_msg_to_send(&mut self) -> Option<Msg> {
|
||||||
self.packet_transport.next_msg_to_send()
|
self.packet_transport.next_msg_to_send()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue