mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
parse config file
This commit is contained in:
parent
187478464c
commit
13c49524ba
8 changed files with 314 additions and 107 deletions
84
Cargo.lock
generated
84
Cargo.lock
generated
|
|
@ -459,8 +459,10 @@ dependencies = [
|
||||||
"eyre",
|
"eyre",
|
||||||
"futures",
|
"futures",
|
||||||
"rustix",
|
"rustix",
|
||||||
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"users",
|
"users",
|
||||||
|
|
@ -629,6 +631,12 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
@ -803,6 +811,12 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
@ -851,6 +865,16 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inout"
|
name = "inout"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
|
@ -1301,18 +1325,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.205"
|
version = "1.0.209"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
|
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.205"
|
version = "1.0.209"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
|
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1331,6 +1355,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.8"
|
version = "0.10.8"
|
||||||
|
|
@ -1493,6 +1526,40 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.8.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.22.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.40"
|
version = "0.1.40"
|
||||||
|
|
@ -1784,6 +1851,15 @@ version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.6.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "x25519-dalek"
|
name = "x25519-dalek"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ users = "0.11.0"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
thiserror = "1.0.63"
|
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"] }
|
||||||
|
toml = "0.8.19"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
13
bin/cluelesshd/cluelesshd.toml
Normal file
13
bin/cluelesshd/cluelesshd.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
log_level = "info"
|
||||||
|
|
||||||
|
[net]
|
||||||
|
ip = "0.0.0.0"
|
||||||
|
port = 2223
|
||||||
|
|
||||||
|
[auth]
|
||||||
|
host_keys = [
|
||||||
|
# "/etc/ssh/ssh_host_ed25519_key",
|
||||||
|
"./test_ed25519_key"
|
||||||
|
]
|
||||||
|
password_login = false
|
||||||
|
banner = "welcome to my server!!!\r\ni hope you enjoy your stay.\r\n"
|
||||||
|
|
@ -6,6 +6,9 @@ use cluelessh_keys::{
|
||||||
authorized_keys::{self, AuthorizedKeys},
|
authorized_keys::{self, AuthorizedKeys},
|
||||||
public::{PublicKey, PublicKeyWithComment},
|
public::{PublicKey, PublicKeyWithComment},
|
||||||
};
|
};
|
||||||
|
use cluelessh_protocol::auth::{CheckPubkey, VerifySignature};
|
||||||
|
use eyre::eyre;
|
||||||
|
use tracing::debug;
|
||||||
use users::os::unix::UserExt;
|
use users::os::unix::UserExt;
|
||||||
|
|
||||||
/// A known-authorized public key for a user.
|
/// A known-authorized public key for a user.
|
||||||
|
|
@ -54,3 +57,63 @@ impl UserPublicKey {
|
||||||
self.0.key.verify_signature(data, signature)
|
self.0.key.verify_signature(data, signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn verify_signature(auth: VerifySignature) -> eyre::Result<bool> {
|
||||||
|
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
if auth.pubkey_alg_name != public_key.algorithm_name() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: std::result::Result<UserPublicKey, AuthError> =
|
||||||
|
UserPublicKey::for_user_and_key(auth.user.clone(), &public_key).await;
|
||||||
|
|
||||||
|
debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey signature");
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(user_key) => {
|
||||||
|
// Verify signature...
|
||||||
|
|
||||||
|
let sign_data = cluelessh_keys::signature::signature_data(
|
||||||
|
auth.session_identifier,
|
||||||
|
&auth.user,
|
||||||
|
&public_key,
|
||||||
|
);
|
||||||
|
|
||||||
|
if user_key.verify_signature(&sign_data, &auth.signature) {
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(
|
||||||
|
AuthError::UnknownUser
|
||||||
|
| AuthError::UnauthorizedPublicKey
|
||||||
|
| AuthError::NoAuthorizedKeys(_),
|
||||||
|
) => Ok(false),
|
||||||
|
Err(AuthError::InvalidAuthorizedKeys(err)) => Err(eyre!(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_pubkey(auth: CheckPubkey) -> eyre::Result<bool> {
|
||||||
|
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
if auth.pubkey_alg_name != public_key.algorithm_name() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
let result = UserPublicKey::for_user_and_key(auth.user.clone(), &public_key).await;
|
||||||
|
|
||||||
|
debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey check");
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(
|
||||||
|
AuthError::UnknownUser
|
||||||
|
| AuthError::UnauthorizedPublicKey
|
||||||
|
| AuthError::NoAuthorizedKeys(_),
|
||||||
|
) => Ok(false),
|
||||||
|
Err(AuthError::InvalidAuthorizedKeys(err)) => Err(eyre!(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
62
bin/cluelesshd/src/config.rs
Normal file
62
bin/cluelesshd/src/config.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
use eyre::{Context, Result};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{
|
||||||
|
net::{IpAddr, Ipv4Addr},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Config {
|
||||||
|
#[serde(default = "default_info")]
|
||||||
|
pub log_level: String,
|
||||||
|
pub net: NetConfig,
|
||||||
|
pub auth: AuthConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct NetConfig {
|
||||||
|
#[serde(default = "addr_default")]
|
||||||
|
pub ip: IpAddr,
|
||||||
|
#[serde(default = "port_default")]
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct AuthConfig {
|
||||||
|
pub host_keys: Vec<PathBuf>,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub password_login: bool,
|
||||||
|
pub banner: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn find() -> Result<Self> {
|
||||||
|
let path =
|
||||||
|
std::env::var("CLUELESSHD_CONFIG").unwrap_or_else(|_| "cluelesshd.toml".to_owned());
|
||||||
|
|
||||||
|
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")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
toml::from_str(&content).wrap_err_with(|| format!("invalid config file '{path}'"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_info() -> String {
|
||||||
|
"info".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addr_default() -> IpAddr {
|
||||||
|
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn port_default() -> u16 {
|
||||||
|
22
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod config;
|
||||||
mod pty;
|
mod pty;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
io,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
|
path::PathBuf,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
process::{ExitStatus, Stdio},
|
process::{ExitStatus, Stdio},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use auth::AuthError;
|
use cluelessh_keys::{host_keys::HostKeySet, private::EncryptedPrivateKeys};
|
||||||
use cluelessh_keys::{private::EncryptedPrivateKeys, public::PublicKey};
|
|
||||||
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
|
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
|
||||||
use cluelessh_transport::server::ServerConfig;
|
use cluelessh_transport::server::ServerConfig;
|
||||||
use eyre::{bail, eyre, Context, OptionExt, Result};
|
use eyre::{bail, Context, OptionExt, Result};
|
||||||
use pty::Pty;
|
use pty::Pty;
|
||||||
use rustix::termios::Winsize;
|
use rustix::termios::Winsize;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
|
|
@ -34,115 +35,28 @@ use users::os::unix::UserExt;
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
let config = config::Config::find()?;
|
||||||
|
|
||||||
|
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();
|
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||||
|
|
||||||
let addr = "0.0.0.0:2223".to_owned();
|
let addr = SocketAddr::new(config.net.ip, config.net.port);
|
||||||
|
|
||||||
let addr = addr
|
|
||||||
.parse::<SocketAddr>()
|
|
||||||
.wrap_err_with(|| format!("failed to parse listen addr '{addr}'"))?;
|
|
||||||
|
|
||||||
info!(%addr, "Starting server");
|
info!(%addr, "Starting server");
|
||||||
|
|
||||||
let listener = TcpListener::bind(addr).await.wrap_err("binding listener")?;
|
let listener = TcpListener::bind(addr)
|
||||||
|
.await
|
||||||
|
.wrap_err_with(|| format!("trying to listen on {addr}"))?;
|
||||||
|
|
||||||
let auth_verify = ServerAuthVerify {
|
let auth_verify = ServerAuthVerify {
|
||||||
verify_password: None,
|
verify_password: config.auth.password_login.then(|| todo!("password login")),
|
||||||
verify_signature: Some(Arc::new(|auth| {
|
verify_signature: Some(Arc::new(|auth| Box::pin(auth::verify_signature(auth)))),
|
||||||
Box::pin(async move {
|
check_pubkey: Some(Arc::new(|auth| Box::pin(auth::check_pubkey(auth)))),
|
||||||
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else {
|
auth_banner: config.auth.banner,
|
||||||
return Ok(false);
|
|
||||||
};
|
|
||||||
if auth.pubkey_alg_name != public_key.algorithm_name() {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: std::result::Result<auth::UserPublicKey, AuthError> =
|
|
||||||
auth::UserPublicKey::for_user_and_key(auth.user.clone(), &public_key).await;
|
|
||||||
|
|
||||||
debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey signature");
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(user_key) => {
|
|
||||||
// Verify signature...
|
|
||||||
|
|
||||||
let sign_data = cluelessh_keys::signature::signature_data(
|
|
||||||
auth.session_identifier,
|
|
||||||
&auth.user,
|
|
||||||
&public_key,
|
|
||||||
);
|
|
||||||
|
|
||||||
if user_key.verify_signature(&sign_data, &auth.signature) {
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(
|
|
||||||
AuthError::UnknownUser
|
|
||||||
| AuthError::UnauthorizedPublicKey
|
|
||||||
| AuthError::NoAuthorizedKeys(_),
|
|
||||||
) => Ok(false),
|
|
||||||
Err(AuthError::InvalidAuthorizedKeys(err)) => Err(eyre!(err)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})),
|
|
||||||
check_pubkey: Some(Arc::new(|auth| {
|
|
||||||
Box::pin(async move {
|
|
||||||
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else {
|
|
||||||
return Ok(false);
|
|
||||||
};
|
|
||||||
if auth.pubkey_alg_name != public_key.algorithm_name() {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
let result =
|
|
||||||
auth::UserPublicKey::for_user_and_key(auth.user.clone(), &public_key).await;
|
|
||||||
|
|
||||||
debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey check");
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(_) => Ok(true),
|
|
||||||
Err(
|
|
||||||
AuthError::UnknownUser
|
|
||||||
| AuthError::UnauthorizedPublicKey
|
|
||||||
| AuthError::NoAuthorizedKeys(_),
|
|
||||||
) => Ok(false),
|
|
||||||
Err(AuthError::InvalidAuthorizedKeys(err)) => Err(eyre!(err)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})),
|
|
||||||
auth_banner: Some("welcome to my server!!!\r\ni hope you enjoy your stay.\r\n".to_owned()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut host_keys = Vec::new();
|
let host_keys = load_host_keys(&config.auth.host_keys).await?.into_keys();
|
||||||
|
|
||||||
let host_key_locations = ["/etc/ssh/ssh_host_ed25519_key", "./test_ed25519_key"];
|
|
||||||
|
|
||||||
for host_key_location in host_key_locations {
|
|
||||||
match tokio::fs::read_to_string(host_key_location).await {
|
|
||||||
Ok(key) => {
|
|
||||||
let key = EncryptedPrivateKeys::parse(key.as_bytes())
|
|
||||||
.wrap_err_with(|| format!("invalid {host_key_location}"))?;
|
|
||||||
if key.requires_passphrase() {
|
|
||||||
bail!("{host_key_location} must not require a passphrase");
|
|
||||||
}
|
|
||||||
let mut key = key
|
|
||||||
.decrypt(None)
|
|
||||||
.wrap_err_with(|| format!("invalid {host_key_location}"))?;
|
|
||||||
if key.len() != 1 {
|
|
||||||
bail!("{host_key_location} must contain a single key");
|
|
||||||
}
|
|
||||||
host_keys.push(key.remove(0));
|
|
||||||
|
|
||||||
info!(?host_key_location, "Loaded host key")
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
debug!(?err, ?host_key_location, "Failed to load host key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if host_keys.is_empty() {
|
if host_keys.is_empty() {
|
||||||
bail!("no host keys found");
|
bail!("no host keys found");
|
||||||
|
|
@ -173,6 +87,39 @@ async fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn load_host_keys(keys: &[PathBuf]) -> Result<HostKeySet> {
|
||||||
|
let mut host_keys = HostKeySet::new();
|
||||||
|
|
||||||
|
for key_path in keys {
|
||||||
|
load_host_key(key_path, &mut host_keys)
|
||||||
|
.await
|
||||||
|
.wrap_err_with(|| format!("loading host key at '{}'", key_path.display()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(host_keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_host_key(key_path: &PathBuf, host_keys: &mut HostKeySet) -> Result<()> {
|
||||||
|
let key = tokio::fs::read_to_string(key_path)
|
||||||
|
.await
|
||||||
|
.wrap_err("failed to open")?;
|
||||||
|
let key = EncryptedPrivateKeys::parse(key.as_bytes()).wrap_err("failed to parse")?;
|
||||||
|
|
||||||
|
if key.requires_passphrase() {
|
||||||
|
bail!("host key requires a passphrase, which is not allowed");
|
||||||
|
}
|
||||||
|
let mut key = key.decrypt(None).wrap_err("failed to parse")?;
|
||||||
|
if key.len() != 1 {
|
||||||
|
bail!("host key must contain a single key");
|
||||||
|
}
|
||||||
|
let key = key.remove(0);
|
||||||
|
let algorithm = key.private_key.algorithm_name();
|
||||||
|
host_keys.insert(key)?;
|
||||||
|
|
||||||
|
info!(?key_path, ?algorithm, "Loaded host key");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_connection(
|
async fn handle_connection(
|
||||||
mut conn: cluelessh_tokio::server::ServerConnection<TcpStream>,
|
mut conn: cluelessh_tokio::server::ServerConnection<TcpStream>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|
|
||||||
43
lib/cluelessh-keys/src/host_keys.rs
Normal file
43
lib/cluelessh-keys/src/host_keys.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::private::PlaintextPrivateKey;
|
||||||
|
|
||||||
|
/// A set of host keys, ensuring there are no duplicated algorithms.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct HostKeySet {
|
||||||
|
algs: HashSet<&'static str>,
|
||||||
|
keys: Vec<PlaintextPrivateKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostKeySet {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_keys(self) -> Vec<PlaintextPrivateKey> {
|
||||||
|
self.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, key: PlaintextPrivateKey) -> Result<(), DuplicateHostKeyAlgorithm> {
|
||||||
|
let alg = key.private_key.algorithm_name();
|
||||||
|
|
||||||
|
let newly_inserted = self.algs.insert(alg);
|
||||||
|
if !newly_inserted {
|
||||||
|
return Err(DuplicateHostKeyAlgorithm { alg });
|
||||||
|
}
|
||||||
|
|
||||||
|
self.keys.push(key);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("another host key with algorithm {alg} has already been loaded")]
|
||||||
|
pub struct DuplicateHostKeyAlgorithm {
|
||||||
|
alg: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: write tests
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod authorized_keys;
|
pub mod authorized_keys;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
pub mod host_keys;
|
||||||
pub mod private;
|
pub mod private;
|
||||||
pub mod public;
|
pub mod public;
|
||||||
pub mod signature;
|
pub mod signature;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue