mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
initial privilege separation
This commit is contained in:
parent
46f77b7f58
commit
543b1b6e76
15 changed files with 887 additions and 108 deletions
|
|
@ -19,6 +19,8 @@ thiserror = "1.0.63"
|
|||
cluelessh-keys = { version = "0.1.0", path = "../../lib/cluelessh-keys" }
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
toml = "0.8.19"
|
||||
clap = { version = "4.5.16", features = ["derive"] }
|
||||
postcard = { version = "1.0.10", features = ["alloc"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
use eyre::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
use crate::Args;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_info")]
|
||||
|
|
@ -14,7 +16,7 @@ pub struct Config {
|
|||
pub auth: AuthConfig,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NetConfig {
|
||||
#[serde(default = "addr_default")]
|
||||
|
|
@ -23,7 +25,7 @@ pub struct NetConfig {
|
|||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AuthConfig {
|
||||
pub host_keys: Vec<PathBuf>,
|
||||
|
|
@ -33,15 +35,18 @@ pub struct AuthConfig {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
pub fn find() -> Result<Self> {
|
||||
let path =
|
||||
std::env::var("CLUELESSHD_CONFIG").unwrap_or_else(|_| "cluelesshd.toml".to_owned());
|
||||
pub fn find(args: &Args) -> Result<Self> {
|
||||
let path = std::env::var("CLUELESSHD_CONFIG")
|
||||
.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(|| {
|
||||
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 config;
|
||||
mod pty;
|
||||
mod rpc;
|
||||
|
||||
use std::{
|
||||
io,
|
||||
io::{self, Read, Seek, SeekFrom},
|
||||
marker::PhantomData,
|
||||
net::SocketAddr,
|
||||
os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
|
||||
path::PathBuf,
|
||||
pin::Pin,
|
||||
process::{ExitStatus, Stdio},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use cluelessh_keys::{host_keys::HostKeySet, private::EncryptedPrivateKeys};
|
||||
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
|
||||
use cluelessh_transport::server::ServerConfig;
|
||||
use clap::Parser;
|
||||
use cluelessh_keys::{host_keys::HostKeySet, private::EncryptedPrivateKeys, public::PublicKey};
|
||||
use cluelessh_tokio::{
|
||||
server::{ServerAuth, ServerConnection, SignWithHostKey},
|
||||
Channel,
|
||||
};
|
||||
use config::Config;
|
||||
use eyre::{bail, Context, OptionExt, Result};
|
||||
use pty::Pty;
|
||||
use rustix::termios::Winsize;
|
||||
use rustix::{fs::MemfdFlags, termios::Winsize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{
|
||||
fs::File,
|
||||
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
||||
|
|
@ -27,66 +35,324 @@ use tokio::{
|
|||
use tracing::{debug, error, info, info_span, warn, Instrument};
|
||||
|
||||
use cluelessh_protocol::{
|
||||
auth::{CheckPubkey, VerifySignature},
|
||||
connection::{ChannelKind, ChannelOperationKind, ChannelRequest},
|
||||
ChannelUpdateKind, SshStatus,
|
||||
};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
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")]
|
||||
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 =
|
||||
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::new(config.net.ip, config.net.port);
|
||||
info!(%addr, "Starting server");
|
||||
let span = info_span!("connection", addr = %state.peer_addr);
|
||||
|
||||
let listener = TcpListener::bind(addr)
|
||||
.await
|
||||
.wrap_err_with(|| format!("trying to listen on {addr}"))?;
|
||||
let stream = unsafe { std::net::TcpStream::from_raw_fd(state.stream_fd) };
|
||||
let stream = TcpStream::from_std(stream)?;
|
||||
|
||||
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_signature: Some(Arc::new(|auth| Box::pin(auth::verify_signature(auth)))),
|
||||
check_pubkey: Some(Arc::new(|auth| Box::pin(auth::check_pubkey(auth)))),
|
||||
verify_signature: Some(Arc::new(move |msg: VerifySignature| {
|
||||
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,
|
||||
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();
|
||||
|
||||
if host_keys.is_empty() {
|
||||
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 {
|
||||
let next = 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;
|
||||
}
|
||||
}
|
||||
let (next_stream, peer_addr) = listener.accept().await?;
|
||||
|
||||
error!(?err, "error handling connection");
|
||||
}
|
||||
info!("Finished connection");
|
||||
// let server_conn = cluelessh_tokio::server::ServerConnection::new(
|
||||
// next_stream,
|
||||
// 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> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue