Auth cleanup

This commit is contained in:
nora 2024-08-30 01:21:50 +02:00
parent 5102c3ff64
commit 185d77e94f
7 changed files with 76 additions and 115 deletions

View file

@ -5,8 +5,9 @@ use std::io;
use cluelessh_keys::{ use cluelessh_keys::{
authorized_keys::{self, AuthorizedKeys}, authorized_keys::{self, AuthorizedKeys},
public::{PublicKey, PublicKeyWithComment}, public::{PublicKey, PublicKeyWithComment},
signature::Signature,
}; };
use cluelessh_protocol::auth::{CheckPubkey, VerifySignature}; use cluelessh_protocol::auth::VerifySignature;
use eyre::eyre; use eyre::eyre;
use tracing::debug; use tracing::debug;
use users::{os::unix::UserExt, User}; use users::{os::unix::UserExt, User};
@ -58,20 +59,13 @@ impl UserPublicKey {
} }
} }
pub fn verify_signature(&self, data: &[u8], signature: &[u8]) -> bool { pub fn verify_signature(&self, data: &[u8], signature: &Signature) -> bool {
self.key.key.verify_signature(data, signature) self.key.key.verify_signature(data, signature)
} }
} }
pub async fn verify_signature(auth: VerifySignature) -> eyre::Result<Option<User>> { pub async fn verify_signature(auth: VerifySignature) -> eyre::Result<Option<User>> {
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else { let result = UserPublicKey::for_user_and_key(auth.user.clone(), &auth.public_key).await;
return Ok(None);
};
if auth.pubkey_alg_name != public_key.algorithm_name() {
return Ok(None);
}
let result = UserPublicKey::for_user_and_key(auth.user.clone(), &public_key).await;
debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey signature"); debug!(user = %auth.user, err = ?result.as_ref().err(), "Attempting publickey signature");
@ -82,7 +76,7 @@ pub async fn verify_signature(auth: VerifySignature) -> eyre::Result<Option<User
let sign_data = cluelessh_keys::signature::signature_data( let sign_data = cluelessh_keys::signature::signature_data(
auth.session_identifier, auth.session_identifier,
&auth.user, &auth.user,
&public_key, &auth.public_key,
); );
if user_key.verify_signature(&sign_data, &auth.signature) { if user_key.verify_signature(&sign_data, &auth.signature) {
@ -100,16 +94,10 @@ pub async fn verify_signature(auth: VerifySignature) -> eyre::Result<Option<User
} }
} }
pub async fn check_pubkey(auth: CheckPubkey) -> eyre::Result<bool> { pub async fn check_pubkey(user: String, public_key: PublicKey) -> eyre::Result<bool> {
let Ok(public_key) = PublicKey::from_wire_encoding(&auth.pubkey) else { let result = UserPublicKey::for_user_and_key(user.clone(), &public_key).await;
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"); debug!(%user, err = ?result.as_ref().err(), "Attempting publickey check");
match result { match result {
Ok(_) => Ok(true), Ok(_) => Ok(true),

View file

@ -69,8 +69,7 @@ async fn connection_inner(state: SerializedConnectionState) -> Result<()> {
.verify_signature( .verify_signature(
msg.user, msg.user,
msg.session_identifier, msg.session_identifier,
msg.pubkey_alg_name, msg.public_key,
msg.pubkey,
msg.signature, msg.signature,
) )
.await .await
@ -82,9 +81,7 @@ async fn connection_inner(state: SerializedConnectionState) -> Result<()> {
rpc_client rpc_client
.check_public_key( .check_public_key(
msg.user, msg.user,
msg.session_identifier, msg.public_key,
msg.pubkey_alg_name,
msg.pubkey,
) )
.await .await
}) })

View file

@ -12,7 +12,6 @@ use std::process::Stdio;
use cluelessh_keys::private::PlaintextPrivateKey; use cluelessh_keys::private::PlaintextPrivateKey;
use cluelessh_keys::public::PublicKey; use cluelessh_keys::public::PublicKey;
use cluelessh_keys::signature::Signature; use cluelessh_keys::signature::Signature;
use cluelessh_protocol::auth::CheckPubkey;
use cluelessh_protocol::auth::VerifySignature; use cluelessh_protocol::auth::VerifySignature;
use cluelessh_transport::crypto::AlgorithmName; use cluelessh_transport::crypto::AlgorithmName;
use eyre::bail; use eyre::bail;
@ -51,18 +50,15 @@ enum Request {
KeyExchange(KeyExchangeRequest), KeyExchange(KeyExchangeRequest),
CheckPublicKey { CheckPublicKey {
user: String, user: String,
session_identifier: [u8; 32], pubkey: PublicKey,
pubkey_alg_name: String,
pubkey: Vec<u8>,
}, },
/// Verify that the public key signature for the user is okay. /// Verify that the public key signature for the user is okay.
/// If it is okay, store the user so we can later spawn a process as them. /// If it is okay, store the user so we can later spawn a process as them.
VerifySignature { VerifySignature {
user: String, user: String,
session_identifier: [u8; 32], session_identifier: [u8; 32],
pubkey_alg_name: String, public_key: PublicKey,
pubkey: Vec<u8>, signature: Signature,
signature: Vec<u8>,
}, },
/// Request a PTY. We create a new PTY and give the client an FD to the controller. /// Request a PTY. We create a new PTY and give the client an FD to the controller.
PtyReq(PtyRequest), PtyReq(PtyRequest),
@ -253,16 +249,9 @@ impl Server {
} }
Request::CheckPublicKey { Request::CheckPublicKey {
user, user,
session_identifier, pubkey: public_key,
pubkey_alg_name,
pubkey,
} => { } => {
let is_ok = crate::auth::check_pubkey(CheckPubkey { let is_ok = crate::auth::check_pubkey(user, public_key)
user,
session_identifier,
pubkey_alg_name,
pubkey,
})
.await .await
.map_err(|err| err.to_string()); .map_err(|err| err.to_string());
@ -271,8 +260,7 @@ impl Server {
Request::VerifySignature { Request::VerifySignature {
user, user,
session_identifier, session_identifier,
pubkey_alg_name, public_key,
pubkey,
signature, signature,
} => { } => {
if self.authenticated_user.is_some() { if self.authenticated_user.is_some() {
@ -282,8 +270,7 @@ impl Server {
let is_ok = crate::auth::verify_signature(VerifySignature { let is_ok = crate::auth::verify_signature(VerifySignature {
user, user,
session_identifier, session_identifier,
pubkey_alg_name, public_key,
pubkey,
signature, signature,
}) })
.await .await
@ -492,19 +479,8 @@ impl Client {
}) })
} }
pub async fn check_public_key( pub async fn check_public_key(&self, user: String, pubkey: PublicKey) -> Result<bool> {
&self, self.request_response::<CheckPublicKeyResponse>(&Request::CheckPublicKey { user, pubkey })
user: String,
session_identifier: [u8; 32],
pubkey_alg_name: String,
pubkey: Vec<u8>,
) -> Result<bool> {
self.request_response::<CheckPublicKeyResponse>(&Request::CheckPublicKey {
user,
session_identifier,
pubkey_alg_name,
pubkey,
})
.await .await
} }
@ -512,15 +488,13 @@ impl Client {
&self, &self,
user: String, user: String,
session_identifier: [u8; 32], session_identifier: [u8; 32],
pubkey_alg_name: String, public_key: PublicKey,
pubkey: Vec<u8>, signature: Signature,
signature: Vec<u8>,
) -> Result<bool> { ) -> Result<bool> {
self.request_response::<VerifySignatureResponse>(&Request::VerifySignature { self.request_response::<VerifySignatureResponse>(&Request::VerifySignature {
user, user,
session_identifier, session_identifier,
pubkey_alg_name, public_key,
pubkey,
signature, signature,
}) })
.await .await

View file

@ -2,14 +2,15 @@
// <https://datatracker.ietf.org/doc/html/rfc4716> exists but is kinda weird // <https://datatracker.ietf.org/doc/html/rfc4716> exists but is kinda weird
use std::fmt::Display; use std::fmt::{Debug, Display};
use base64::Engine; use base64::Engine;
use tracing::debug;
use cluelessh_format::{ParseError, Reader, Writer}; use cluelessh_format::{ParseError, Reader, Writer};
#[derive(Debug, Clone, PartialEq, Eq)] use crate::signature::Signature;
#[derive(Clone, PartialEq, Eq)]
pub enum PublicKey { pub enum PublicKey {
Ed25519 { Ed25519 {
public_key: ed25519_dalek::VerifyingKey, public_key: ed25519_dalek::VerifyingKey,
@ -87,27 +88,14 @@ impl PublicKey {
} }
} }
pub fn verify_signature(&self, data: &[u8], signature: &[u8]) -> bool { pub fn verify_signature(&self, data: &[u8], signature: &Signature) -> bool {
match self { match self {
PublicKey::Ed25519 { public_key } => { PublicKey::Ed25519 { public_key } => match signature {
let mut s = Reader::new(signature); Signature::Ed25519 { signature } => {
let Ok(alg) = s.utf8_string() else {
return false;
};
if alg != "ssh-ed25519" {
return false;
}
let Ok(signature) = s.string() else {
return false;
};
let Ok(signature) = ed25519_dalek::Signature::from_slice(signature) else {
debug!("Invalid signature length");
return false;
};
public_key.verify_strict(data, &signature).is_ok() public_key.verify_strict(data, &signature).is_ok()
} }
_ => false,
},
PublicKey::EcdsaSha2NistP256 { .. } => { PublicKey::EcdsaSha2NistP256 { .. } => {
todo!("ecdsa-sha2-nistp256 signature verification") todo!("ecdsa-sha2-nistp256 signature verification")
} }
@ -115,6 +103,12 @@ impl PublicKey {
} }
} }
impl Debug for PublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self, f)
}
}
impl Display for PublicKey { impl Display for PublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {

View file

@ -18,7 +18,7 @@ pub fn signature_data(session_id: [u8; 32], username: &str, pubkey: &PublicKey)
s.finish() s.finish()
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
pub enum Signature { pub enum Signature {
Ed25519 { signature: ed25519_dalek::Signature }, Ed25519 { signature: ed25519_dalek::Signature },
EcdsaSha2NistP256 { signature: p256::ecdsa::Signature }, EcdsaSha2NistP256 { signature: p256::ecdsa::Signature },

View file

@ -277,6 +277,7 @@ pub mod auth {
use std::collections::{HashSet, VecDeque}; use std::collections::{HashSet, VecDeque};
use cluelessh_format::{numbers, NameList}; use cluelessh_format::{numbers, NameList};
use cluelessh_keys::{public::PublicKey, signature::Signature};
use cluelessh_transport::{packet::Packet, peer_error, Result}; use cluelessh_transport::{packet::Packet, peer_error, Result};
use tracing::debug; use tracing::debug;
@ -293,7 +294,7 @@ pub mod auth {
pub enum ServerRequest { pub enum ServerRequest {
VerifyPassword(VerifyPassword), VerifyPassword(VerifyPassword),
/// Check whether a pubkey is usable. /// Check whether a pubkey is usable.
CheckPubkey(CheckPubkey), CheckPubkey(CheckPublicKey),
/// Verify the signature from a pubkey. /// Verify the signature from a pubkey.
VerifySignature(VerifySignature), VerifySignature(VerifySignature),
} }
@ -305,20 +306,18 @@ pub mod auth {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CheckPubkey { pub struct CheckPublicKey {
pub user: String, pub user: String,
pub session_identifier: [u8; 32], pub public_key: PublicKey,
pub pubkey_alg_name: String,
pub pubkey: Vec<u8>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct VerifySignature { pub struct VerifySignature {
pub user: String, pub user: String,
pub session_identifier: [u8; 32], pub session_identifier: [u8; 32],
pub pubkey_alg_name: String, pub public_key: PublicKey,
pub pubkey: Vec<u8>, /// The signature. Guaranteed to match the algorithm of `public_key`.
pub signature: Vec<u8>, pub signature: Signature,
} }
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@ -405,24 +404,31 @@ pub mod auth {
let pubkey_alg_name = p.utf8_string()?; let pubkey_alg_name = p.utf8_string()?;
let public_key_blob = p.string()?; let public_key_blob = p.string()?;
let public_key = PublicKey::from_wire_encoding(public_key_blob)?;
if pubkey_alg_name != public_key.algorithm_name() {
return Err(peer_error!("algorithm name mismatch"));
}
// Whether the client is just checking whether the public key is allowed. // Whether the client is just checking whether the public key is allowed.
if !has_signature { if !has_signature {
self.server_requests self.server_requests.push_back(ServerRequest::CheckPubkey(
.push_back(ServerRequest::CheckPubkey(CheckPubkey { CheckPublicKey {
user: username.to_owned(), user: username.to_owned(),
session_identifier: self.session_ident, public_key,
pubkey_alg_name: pubkey_alg_name.to_owned(), },
pubkey: public_key_blob.to_vec(), ));
}));
} else { } else {
let signature = p.string()?; let signature = p.string()?;
let signature = Signature::from_wire_encoding(signature)?;
if signature.algorithm_name() != public_key.algorithm_name() {
return Err(peer_error!("signature algorithm name mismatch"));
}
self.server_requests self.server_requests
.push_back(ServerRequest::VerifySignature(VerifySignature { .push_back(ServerRequest::VerifySignature(VerifySignature {
user: username.to_owned(), user: username.to_owned(),
session_identifier: self.session_ident, session_identifier: self.session_ident,
pubkey_alg_name: pubkey_alg_name.to_owned(), public_key,
pubkey: public_key_blob.to_vec(), signature,
signature: signature.to_vec(),
})); }));
} }
} }
@ -443,9 +449,12 @@ pub mod auth {
Ok(()) Ok(())
} }
pub fn pubkey_check_result(&mut self, is_ok: bool, alg: &str, key_blob: &[u8]) { pub fn pubkey_check_result(&mut self, is_ok: bool, key: PublicKey) {
if is_ok { if is_ok {
self.queue_packet(Packet::new_msg_userauth_pk_ok(alg.as_bytes(), key_blob)); self.queue_packet(Packet::new_msg_userauth_pk_ok(
key.algorithm_name().as_bytes(),
&key.to_wire_encoding(),
));
} else { } else {
self.send_failure(); self.send_failure();
// It's ok, don't treat this as a fatal failure. // It's ok, don't treat this as a fatal failure.

View file

@ -14,7 +14,7 @@ use tokio::{
}; };
use cluelessh_protocol::{ use cluelessh_protocol::{
auth::{AuthOption, CheckPubkey, VerifyPassword, VerifySignature}, auth::{AuthOption, CheckPublicKey, VerifyPassword, VerifySignature},
ChannelUpdateKind, SshStatus, ChannelUpdateKind, SshStatus,
}; };
use eyre::{eyre, ContextCompat, OptionExt, Result, WrapErr}; use eyre::{eyre, ContextCompat, OptionExt, Result, WrapErr};
@ -53,7 +53,7 @@ pub struct ServerConnection<S> {
enum Operation { enum Operation {
VerifyPassword(String, Result<bool>), VerifyPassword(String, Result<bool>),
CheckPubkey(Result<bool>, String, Vec<u8>), CheckPubkey(Result<bool>, PublicKey),
VerifySignature(String, Result<bool>), VerifySignature(String, Result<bool>),
KeyExchangeResponseReceived(Result<KeyExchangeResponse>), KeyExchangeResponseReceived(Result<KeyExchangeResponse>),
} }
@ -64,7 +64,7 @@ pub type AuthFn<A, R> = Arc<dyn Fn(A) -> BoxFuture<'static, R> + Send + Sync>;
pub struct ServerAuth { 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<CheckPublicKey, Result<bool>>>,
pub do_key_exchange: AuthFn<KeyExchangeParameters, Result<KeyExchangeResponse>>, pub do_key_exchange: AuthFn<KeyExchangeParameters, Result<KeyExchangeResponse>>,
pub auth_banner: Option<String>, pub auth_banner: Option<String>,
} }
@ -215,8 +215,7 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
let _ = send let _ = send
.send(Operation::CheckPubkey( .send(Operation::CheckPubkey(
result, result,
check_pubkey.pubkey_alg_name, check_pubkey.public_key,
check_pubkey.pubkey,
)) ))
.await; .await;
}); });
@ -350,8 +349,8 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
Some(Operation::VerifySignature(user, result)) => if let Some(auth) = self.proto.auth() { Some(Operation::VerifySignature(user, result)) => if let Some(auth) = self.proto.auth() {
auth.verification_result(result?, user); auth.verification_result(result?, user);
}, },
Some(Operation::CheckPubkey(result, alg, key_blob)) => if let Some(auth) = self.proto.auth() { Some(Operation::CheckPubkey(result, public_key)) => if let Some(auth) = self.proto.auth() {
auth.pubkey_check_result(result?, &alg, &key_blob); auth.pubkey_check_result(result?, public_key);
}, },
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);