verify signature

This commit is contained in:
nora 2024-08-25 22:50:34 +02:00
parent ae425fdefa
commit 3124e6a2ab
14 changed files with 373 additions and 36 deletions

View file

@ -18,6 +18,7 @@ tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] }
tracing.workspace = true
rpassword = "7.3.1"
users = "0.11.0"
cluelessh-keys = { version = "0.1.0", path = "../../lib/cluelessh-keys" }
[lints]
workspace = true

View file

@ -4,7 +4,7 @@ use clap::Parser;
use cluelessh_tokio::client::SignatureResult;
use cluelessh_tokio::PendingChannel;
use cluelessh_transport::{key::PublicKey, numbers, parse::Writer};
use cluelessh_transport::key::PublicKey;
use eyre::{bail, Context, ContextCompat, OptionExt, Result};
use tokio::net::TcpStream;
use tracing::{debug, error};
@ -70,7 +70,6 @@ async fn main() -> eyre::Result<()> {
})
}),
sign_pubkey: Arc::new(move |session_identifier| {
let session_identifier = session_identifier.to_vec();
let mut attempted_public_keys = HashSet::new();
let username = username.clone();
Box::pin(async move {
@ -94,19 +93,13 @@ async fn main() -> eyre::Result<()> {
}
let pubkey = PublicKey::from_wire_encoding(&identity.key_blob)?;
let mut sign_data = Writer::new();
sign_data.string(session_identifier);
sign_data.u8(numbers::SSH_MSG_USERAUTH_REQUEST);
sign_data.string(&username);
sign_data.string("ssh-connection");
sign_data.string("publickey");
sign_data.bool(true);
sign_data.string(pubkey.algorithm_name());
sign_data.string(&identity.key_blob);
let data = sign_data.finish();
let sign_data = cluelessh_keys::signature::signature_data(
session_identifier,
&username,
&pubkey,
);
let signature = agent
.sign(&identity.key_blob, &data, 0)
.sign(&identity.key_blob, &sign_data, 0)
.await
.wrap_err("signing for authentication")?;

View file

@ -14,6 +14,8 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] }
rustix = { version = "0.38.34", features = ["pty", "termios", "procfs", "process", "stdio"] }
users = "0.11.0"
futures = "0.3.30"
thiserror = "1.0.63"
cluelessh-keys = { version = "0.1.0", path = "../../lib/cluelessh-keys" }
[lints]
workspace = true

View file

@ -0,0 +1,54 @@
//! User authentication.
use std::io;
use cluelessh_keys::{
authorized_keys::{self, AuthorizedKeys},
PublicKeyWithComment,
};
use cluelessh_transport::key::PublicKey;
use users::os::unix::UserExt;
/// A known-authorized public key for a user.
pub struct UserPublicKey(PublicKeyWithComment);
#[derive(Debug, thiserror::Error)]
pub enum AuthError {
#[error("unknown user")]
UnknownUser,
#[error("~/.ssh/authorized_keys not found")]
NoAuthorizedKeys(#[source] io::Error),
#[error("invalid ~/.ssh/authorized_keys")]
InvalidAuthorizedKeys(#[from] authorized_keys::Error),
#[error("public key not authorized")]
UnauthorizedPublicKey,
}
impl UserPublicKey {
/// Blocking!
pub async fn for_user_and_key(user: String, provided_key: &PublicKey) -> Result<Self, AuthError> {
let user = tokio::task::spawn_blocking(move || {
users::get_user_by_name(&user).ok_or(AuthError::UnknownUser)
})
.await
.unwrap()?;
let sshd_dir = user.home_dir().join(".ssh").join("authorized_keys");
let file = tokio::fs::read_to_string(sshd_dir)
.await
.map_err(|err| AuthError::NoAuthorizedKeys(err))?;
let authorized_keys = AuthorizedKeys::parse(&file)?;
if let Some(key) = authorized_keys.contains(&provided_key) {
Ok(Self(key.clone()))
} else {
Err(AuthError::UnauthorizedPublicKey)
}
}
pub fn verify_signature(&self, data: &[u8], signature: &[u8]) -> bool {
self.0.key.verify_signature(data, signature)
}
}

View file

@ -1,9 +1,12 @@
mod auth;
mod pty;
use std::{io, net::SocketAddr, process::ExitStatus, sync::Arc};
use auth::AuthError;
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
use eyre::{bail, Context, OptionExt, Result};
use cluelessh_transport::key::PublicKey;
use eyre::{bail, eyre, Context, OptionExt, Result};
use pty::Pty;
use rustix::termios::Winsize;
use tokio::{
@ -42,16 +45,65 @@ async fn main() -> eyre::Result<()> {
verify_password: None,
verify_signature: Some(Arc::new(|auth| {
Box::pin(async move {
debug!(user = %auth.user, "Attempting publickey login");
warn!("Letting in unauthenticated user");
Ok(true)
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<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 {
debug!(user = %auth.user, "Attempting publickey check");
warn!("Letting in unauthenticated user");
Ok(true)
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()),
@ -92,7 +144,7 @@ async fn handle_connection(
step = conn.progress() => match step {
Ok(()) => {}
Err(cluelessh_tokio::server::Error::ServerError(err)) => {
return Err(err);
return Err(err.wrap_err("encountered server error during connection"));
}
Err(cluelessh_tokio::server::Error::SshStatus(status)) => match status {
SshStatus::PeerError(err) => {
@ -109,7 +161,7 @@ async fn handle_connection(
debug!(?result, "error!");
match result {
Ok(_) => channel_tasks.clear(),
Err(err) => return Err(err as eyre::Report),
Err(err) => return Err((err as eyre::Report).wrap_err("channel task failed")),
}
},
}