small fixes

This commit is contained in:
nora 2024-08-30 01:38:58 +02:00
parent 185d77e94f
commit 026965bda5
19 changed files with 124 additions and 106 deletions

2
Cargo.lock generated
View file

@ -449,11 +449,13 @@ dependencies = [
"crypto-bigint", "crypto-bigint",
"ctr", "ctr",
"ed25519-dalek", "ed25519-dalek",
"hex",
"hex-literal", "hex-literal",
"p256", "p256",
"poly1305", "poly1305",
"rand_core", "rand_core",
"secrecy", "secrecy",
"serde",
"sha2", "sha2",
"subtle", "subtle",
"tracing", "tracing",

View file

@ -102,6 +102,8 @@ async fn main() -> eyre::Result<()> {
let transport_config = cluelessh_protocol::transport::server::ServerConfig { let transport_config = cluelessh_protocol::transport::server::ServerConfig {
host_keys: pub_host_keys, host_keys: pub_host_keys,
// This is definitely who we are.
server_identification: b"SSH-2.0-OpenSSH_9.7\r\n".to_vec(),
}; };
let mut listener = let mut listener =

View file

@ -69,7 +69,7 @@ async fn main() -> eyre::Result<()> {
result.wrap_err("failed to prompt password") result.wrap_err("failed to prompt password")
}) })
}), }),
sign_pubkey: Arc::new(move |session_identifier| { sign_pubkey: Arc::new(move |session_id| {
let mut attempted_public_keys = HashSet::new(); let mut attempted_public_keys = HashSet::new();
let username = username.clone(); let username = username.clone();
Box::pin(async move { Box::pin(async move {
@ -93,11 +93,8 @@ async fn main() -> eyre::Result<()> {
} }
let pubkey = PublicKey::from_wire_encoding(&identity.key_blob)?; let pubkey = PublicKey::from_wire_encoding(&identity.key_blob)?;
let sign_data = cluelessh_keys::signature::signature_data( let sign_data =
session_identifier, cluelessh_keys::signature::signature_data(session_id.0, &username, &pubkey);
&username,
&pubkey,
);
let signature = agent let signature = agent
.sign(&identity.key_blob, &sign_data, 0) .sign(&identity.key_blob, &sign_data, 0)
.await .await

View file

@ -74,7 +74,7 @@ pub async fn verify_signature(auth: VerifySignature) -> eyre::Result<Option<User
// Verify signature... // Verify signature...
let sign_data = cluelessh_keys::signature::signature_data( let sign_data = cluelessh_keys::signature::signature_data(
auth.session_identifier, auth.session_id.0,
&auth.user, &auth.user,
&auth.public_key, &auth.public_key,
); );

View file

@ -80,7 +80,6 @@ fn default_false() -> bool {
false false
} }
fn addr_default() -> IpAddr { fn addr_default() -> IpAddr {
IpAddr::V4(Ipv4Addr::UNSPECIFIED) IpAddr::V4(Ipv4Addr::UNSPECIFIED)
} }

View file

@ -52,7 +52,10 @@ async fn connection_inner(state: SerializedConnectionState) -> Result<()> {
let stream = TcpStream::from_std(stream)?; let stream = TcpStream::from_std(stream)?;
let host_keys = state.pub_host_keys; let host_keys = state.pub_host_keys;
let transport_config = cluelessh_transport::server::ServerConfig { host_keys }; let transport_config = cluelessh_transport::server::ServerConfig {
host_keys,
server_identification: b"SSH-2.0-ClueleSSH_0.1\r\n".to_vec(),
};
let rpc_client = unsafe { OwnedFd::from_raw_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD) }; let rpc_client = unsafe { OwnedFd::from_raw_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD) };
let rpc_client1 = Arc::new(rpc::Client::from_fd(rpc_client)?); let rpc_client1 = Arc::new(rpc::Client::from_fd(rpc_client)?);
@ -66,25 +69,13 @@ async fn connection_inner(state: SerializedConnectionState) -> Result<()> {
let rpc_client = rpc_client1.clone(); let rpc_client = rpc_client1.clone();
Box::pin(async move { Box::pin(async move {
rpc_client rpc_client
.verify_signature( .verify_signature(msg.user, msg.session_id, msg.public_key, msg.signature)
msg.user,
msg.session_identifier,
msg.public_key,
msg.signature,
)
.await .await
}) })
})), })),
check_pubkey: Some(Arc::new(move |msg| { check_pubkey: Some(Arc::new(move |msg| {
let rpc_client = rpc_client2.clone(); let rpc_client = rpc_client2.clone();
Box::pin(async move { Box::pin(async move { rpc_client.check_public_key(msg.user, msg.public_key).await })
rpc_client
.check_public_key(
msg.user,
msg.public_key,
)
.await
})
})), })),
auth_banner: config.auth.banner, auth_banner: config.auth.banner,
do_key_exchange: Arc::new(move |msg| { do_key_exchange: Arc::new(move |msg| {

View file

@ -14,6 +14,7 @@ use cluelessh_keys::public::PublicKey;
use cluelessh_keys::signature::Signature; use cluelessh_keys::signature::Signature;
use cluelessh_protocol::auth::VerifySignature; use cluelessh_protocol::auth::VerifySignature;
use cluelessh_transport::crypto::AlgorithmName; use cluelessh_transport::crypto::AlgorithmName;
use cluelessh_transport::SessionId;
use eyre::bail; use eyre::bail;
use eyre::ensure; use eyre::ensure;
use eyre::eyre; use eyre::eyre;
@ -56,7 +57,7 @@ enum Request {
/// 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_id: SessionId,
public_key: PublicKey, public_key: PublicKey,
signature: Signature, signature: Signature,
}, },
@ -115,7 +116,7 @@ impl secrecy::DebugSecret for SerializableSharedSecret {}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct KeyExchangeResponse { pub struct KeyExchangeResponse {
pub hash: [u8; 32], pub hash: SessionId,
pub server_ephemeral_public_key: Vec<u8>, pub server_ephemeral_public_key: Vec<u8>,
pub shared_secret: secrecy::Secret<SerializableSharedSecret>, pub shared_secret: secrecy::Secret<SerializableSharedSecret>,
pub signature: Signature, pub signature: Signature,
@ -259,7 +260,7 @@ impl Server {
} }
Request::VerifySignature { Request::VerifySignature {
user, user,
session_identifier, session_id,
public_key, public_key,
signature, signature,
} => { } => {
@ -269,7 +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_id,
public_key, public_key,
signature, signature,
}) })
@ -487,13 +488,13 @@ impl Client {
pub async fn verify_signature( pub async fn verify_signature(
&self, &self,
user: String, user: String,
session_identifier: [u8; 32], session_id: SessionId,
public_key: PublicKey, public_key: PublicKey,
signature: Signature, signature: Signature,
) -> Result<bool> { ) -> Result<bool> {
self.request_response::<VerifySignatureResponse>(&Request::VerifySignature { self.request_response::<VerifySignatureResponse>(&Request::VerifySignature {
user, user,
session_identifier, session_id,
public_key, public_key,
signature, signature,
}) })

View file

@ -14,7 +14,9 @@ use rustix::{
use seccompiler::{BpfProgram, SeccompAction, SeccompFilter, SeccompRule, TargetArch}; use seccompiler::{BpfProgram, SeccompAction, SeccompFilter, SeccompRule, TargetArch};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
use crate::{SerializedConnectionState, PRIVSEP_CONNECTION_RPC_CLIENT_FD, PRIVSEP_CONNECTION_STREAM_FD}; use crate::{
SerializedConnectionState, PRIVSEP_CONNECTION_RPC_CLIENT_FD, PRIVSEP_CONNECTION_STREAM_FD,
};
#[tracing::instrument(skip(state), ret)] #[tracing::instrument(skip(state), ret)]
pub fn drop_privileges(state: &SerializedConnectionState) -> Result<()> { pub fn drop_privileges(state: &SerializedConnectionState) -> Result<()> {
@ -228,12 +230,24 @@ fn seccomp() -> Result<()> {
(libc::SYS_eventfd2, vec![]), (libc::SYS_eventfd2, vec![]),
(libc::SYS_epoll_wait, vec![]), (libc::SYS_epoll_wait, vec![]),
(libc::SYS_epoll_ctl, vec![]), (libc::SYS_epoll_ctl, vec![]),
(libc::SYS_fcntl, vec![]), // todo: restrict (72) (libc::SYS_fcntl, vec![]), // todo: restrict this
(libc::SYS_socketpair, vec![]), (libc::SYS_socketpair, vec![]),
(libc::SYS_sendmsg, vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)],), (
(libc::SYS_recvmsg, vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)]), libc::SYS_sendmsg,
(libc::SYS_sendto, vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)]), vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)],
(libc::SYS_recvfrom, vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)]), ),
(
libc::SYS_recvmsg,
vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)],
),
(
libc::SYS_sendto,
vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)],
),
(
libc::SYS_recvfrom,
vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)],
),
(libc::SYS_getrandom, vec![]), (libc::SYS_getrandom, vec![]),
(libc::SYS_rt_sigaction, vec![]), (libc::SYS_rt_sigaction, vec![]),
(libc::SYS_rt_sigprocmask, vec![]), (libc::SYS_rt_sigprocmask, vec![]),

View file

@ -153,8 +153,8 @@ impl ChannelsState {
} }
pub fn recv_packet(&mut self, packet: Packet) -> Result<()> { pub fn recv_packet(&mut self, packet: Packet) -> Result<()> {
// TODO: window // TODO: what if we mostly ignored window and just always increased it again?
// there's an excention to ignore it entirely that we could also support...
let mut p = packet.payload_parser(); let mut p = packet.payload_parser();
let packet_type = p.u8()?; let packet_type = p.u8()?;
match packet_type { match packet_type {

View file

@ -2,7 +2,6 @@ use cluelessh_format::{ParseError, Reader, Writer};
use crate::{private::PrivateKey, public::PublicKey}; use crate::{private::PrivateKey, public::PublicKey};
// TODO SessionId newtype
pub fn signature_data(session_id: [u8; 32], username: &str, pubkey: &PublicKey) -> Vec<u8> { pub fn signature_data(session_id: [u8; 32], username: &str, pubkey: &PublicKey) -> Vec<u8> {
let mut s = Writer::new(); let mut s = Writer::new();

View file

@ -47,11 +47,11 @@ impl ServerConnection {
self.transport.recv_bytes(bytes)?; self.transport.recv_bytes(bytes)?;
if let ServerConnectionState::Setup(options, auth_banner) = &mut self.state { if let ServerConnectionState::Setup(options, auth_banner) = &mut self.state {
if let Some(session_ident) = self.transport.is_open() { if let Some(session_id) = self.transport.is_open() {
self.state = ServerConnectionState::Auth(auth::ServerAuth::new( self.state = ServerConnectionState::Auth(auth::ServerAuth::new(
mem::take(options), mem::take(options),
auth_banner.take(), auth_banner.take(),
session_ident, session_id,
)); ));
} }
} }
@ -177,9 +177,9 @@ impl ClientConnection {
self.transport.recv_bytes(bytes)?; self.transport.recv_bytes(bytes)?;
if let ClientConnectionState::Setup(auth) = &mut self.state { if let ClientConnectionState::Setup(auth) = &mut self.state {
if let Some(session_ident) = self.transport.is_open() { if let Some(session_id) = self.transport.is_open() {
let mut auth = mem::take(auth).unwrap(); let mut auth = mem::take(auth).unwrap();
auth.set_session_identifier(session_ident); auth.set_session_id(session_id);
debug!("Connection has been opened"); debug!("Connection has been opened");
self.state = ClientConnectionState::Auth(auth); self.state = ClientConnectionState::Auth(auth);
@ -278,7 +278,7 @@ pub mod auth {
use cluelessh_format::{numbers, NameList}; use cluelessh_format::{numbers, NameList};
use cluelessh_keys::{public::PublicKey, signature::Signature}; use cluelessh_keys::{public::PublicKey, signature::Signature};
use cluelessh_transport::{packet::Packet, peer_error, Result}; use cluelessh_transport::{packet::Packet, peer_error, Result, SessionId};
use tracing::debug; use tracing::debug;
pub struct ServerAuth { pub struct ServerAuth {
@ -288,7 +288,7 @@ pub mod auth {
options: HashSet<AuthOption>, options: HashSet<AuthOption>,
banner: Option<String>, banner: Option<String>,
server_requests: VecDeque<ServerRequest>, server_requests: VecDeque<ServerRequest>,
session_ident: [u8; 32], session_id: SessionId,
} }
pub enum ServerRequest { pub enum ServerRequest {
@ -314,7 +314,7 @@ pub mod auth {
#[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_id: SessionId,
pub public_key: PublicKey, pub public_key: PublicKey,
/// The signature. Guaranteed to match the algorithm of `public_key`. /// The signature. Guaranteed to match the algorithm of `public_key`.
pub signature: Signature, pub signature: Signature,
@ -330,14 +330,14 @@ pub mod auth {
pub fn new( pub fn new(
options: HashSet<AuthOption>, options: HashSet<AuthOption>,
banner: Option<String>, banner: Option<String>,
session_ident: [u8; 32], session_id: SessionId,
) -> Self { ) -> Self {
Self { Self {
has_failed: false, has_failed: false,
packets_to_send: VecDeque::new(), packets_to_send: VecDeque::new(),
options, options,
is_authenticated: None, is_authenticated: None,
session_ident, session_id,
banner, banner,
server_requests: VecDeque::new(), server_requests: VecDeque::new(),
} }
@ -426,7 +426,7 @@ pub mod auth {
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_id: self.session_id,
public_key, public_key,
signature, signature,
})); }));
@ -512,12 +512,12 @@ pub mod auth {
packets_to_send: VecDeque<Packet>, packets_to_send: VecDeque<Packet>,
user_requests: VecDeque<ClientUserRequest>, user_requests: VecDeque<ClientUserRequest>,
is_authenticated: bool, is_authenticated: bool,
session_identifier: Option<[u8; 32]>, session_id: Option<SessionId>,
} }
pub enum ClientUserRequest { pub enum ClientUserRequest {
Password, Password,
PrivateKeySign { session_identifier: [u8; 32] }, PrivateKeySign { session_id: SessionId },
Banner(Vec<u8>), Banner(Vec<u8>),
} }
@ -533,13 +533,13 @@ pub mod auth {
username, username,
user_requests: VecDeque::new(), user_requests: VecDeque::new(),
is_authenticated: false, is_authenticated: false,
session_identifier: None, session_id: None,
} }
} }
pub fn set_session_identifier(&mut self, ident: [u8; 32]) { pub fn set_session_id(&mut self, session_id: SessionId) {
assert!(self.session_identifier.is_none()); assert!(self.session_id.is_none());
self.session_identifier = Some(ident); self.session_id = Some(session_id);
} }
pub fn is_authenticated(&self) -> bool { pub fn is_authenticated(&self) -> bool {
@ -605,9 +605,9 @@ pub mod auth {
// TODO: Ask the server whether there are any keys we can use instead of just yoloing the signature. // TODO: Ask the server whether there are any keys we can use instead of just yoloing the signature.
self.user_requests self.user_requests
.push_back(ClientUserRequest::PrivateKeySign { .push_back(ClientUserRequest::PrivateKeySign {
session_identifier: self session_id: self
.session_identifier .session_id
.expect("set_session_identifier has not been called"), .expect("set_session_id has not been called"),
}); });
} else { } else {
return Err(peer_error!( return Err(peer_error!(

View file

@ -1,4 +1,5 @@
use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation}; use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation};
use cluelessh_transport::SessionId;
use std::{collections::HashMap, pin::Pin, sync::Arc}; use std::{collections::HashMap, pin::Pin, sync::Arc};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
@ -31,7 +32,7 @@ pub struct ClientAuth {
pub username: String, pub username: String,
pub prompt_password: Arc<dyn Fn() -> BoxFuture<'static, Result<String>> + Send + Sync>, pub prompt_password: Arc<dyn Fn() -> BoxFuture<'static, Result<String>> + Send + Sync>,
pub sign_pubkey: pub sign_pubkey:
Arc<dyn Fn([u8; 32]) -> BoxFuture<'static, Result<SignatureResult>> + Send + Sync>, Arc<dyn Fn(SessionId) -> BoxFuture<'static, Result<SignatureResult>> + Send + Sync>,
} }
enum Operation { enum Operation {
@ -59,9 +60,7 @@ impl<S: AsyncRead + AsyncWrite> ClientConnection<S> {
channel_ops_recv, channel_ops_recv,
channels: HashMap::new(), channels: HashMap::new(),
proto: cluelessh_protocol::ClientConnection::new( proto: cluelessh_protocol::ClientConnection::new(
cluelessh_transport::client::ClientConnection::new( cluelessh_transport::client::ClientConnection::new(cluelessh_protocol::OsRng),
cluelessh_protocol::OsRng,
),
cluelessh_protocol::auth::ClientAuth::new(auth.username.as_bytes().to_vec()), cluelessh_protocol::auth::ClientAuth::new(auth.username.as_bytes().to_vec()),
), ),
auth, auth,
@ -88,13 +87,11 @@ impl<S: AsyncRead + AsyncWrite> ClientConnection<S> {
let _ = send.send(Operation::PasswordEntered(password)).await; let _ = send.send(Operation::PasswordEntered(password)).await;
}); });
} }
cluelessh_protocol::auth::ClientUserRequest::PrivateKeySign { cluelessh_protocol::auth::ClientUserRequest::PrivateKeySign { session_id } => {
session_identifier,
} => {
let send = self.operations_send.clone(); let send = self.operations_send.clone();
let sign_pubkey = self.auth.sign_pubkey.clone(); let sign_pubkey = self.auth.sign_pubkey.clone();
tokio::spawn(async move { tokio::spawn(async move {
let signature_result = sign_pubkey(session_identifier).await; let signature_result = sign_pubkey(session_id).await;
let _ = send.send(Operation::Signature(signature_result)).await; let _ = send.send(Operation::Signature(signature_result)).await;
}); });
} }

View file

@ -213,10 +213,7 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
tokio::spawn(async move { tokio::spawn(async move {
let result = check(check_pubkey.clone()).await; let result = check(check_pubkey.clone()).await;
let _ = send let _ = send
.send(Operation::CheckPubkey( .send(Operation::CheckPubkey(result, check_pubkey.public_key))
result,
check_pubkey.public_key,
))
.await; .await;
}); });
} }

View file

@ -22,6 +22,8 @@ x25519-dalek = "2.0.1"
tracing.workspace = true tracing.workspace = true
base64 = "0.22.1" base64 = "0.22.1"
secrecy = "0.8.0" secrecy = "0.8.0"
hex = "0.4.3"
serde = { version = "1.0.209", features = ["derive"] }
[dev-dependencies] [dev-dependencies]
hex-literal = "0.4.1" hex-literal = "0.4.1"

View file

@ -4,10 +4,11 @@ use tracing::{debug, info, trace};
use crate::{ use crate::{
crypto::{ crypto::{
self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeyVerifyAlgorithm, KeyExchangeSecret, SharedSecret, SupportedAlgorithms self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeyVerifyAlgorithm,
KeyExchangeSecret, SharedSecret, SupportedAlgorithms,
}, },
packet::{Packet, PacketTransport, ProtocolIdentParser}, packet::{Packet, PacketTransport, ProtocolIdentParser},
peer_error, Msg, Result, SshRng, SshStatus, peer_error, Msg, Result, SessionId, SshRng, SshStatus,
}; };
use cluelessh_format::{numbers, NameList, Reader, Writer}; use cluelessh_format::{numbers, NameList, Reader, Writer};
@ -50,10 +51,10 @@ enum ClientState {
encryption_server_to_client: EncryptionAlgorithm, encryption_server_to_client: EncryptionAlgorithm,
}, },
ServiceRequest { ServiceRequest {
session_identifier: [u8; 32], session_id: SessionId,
}, },
Open { Open {
session_identifier: [u8; 32], session_id: SessionId,
}, },
} }
@ -310,10 +311,10 @@ impl ClientConnection {
.queue_packet(Packet::new_msg_service_request(b"ssh-userauth")); .queue_packet(Packet::new_msg_service_request(b"ssh-userauth"));
self.state = ClientState::ServiceRequest { self.state = ClientState::ServiceRequest {
session_identifier: *h, session_id: SessionId(*h),
}; };
} }
ClientState::ServiceRequest { session_identifier } => { ClientState::ServiceRequest { session_id } => {
let mut accept = packet.payload_parser(); let mut accept = packet.payload_parser();
let packet_type = accept.u8()?; let packet_type = accept.u8()?;
if packet_type != numbers::SSH_MSG_SERVICE_ACCEPT { if packet_type != numbers::SSH_MSG_SERVICE_ACCEPT {
@ -326,7 +327,7 @@ impl ClientConnection {
debug!("Connection has been opened successfully"); debug!("Connection has been opened successfully");
self.state = ClientState::Open { self.state = ClientState::Open {
session_identifier: *session_identifier, session_id: *session_id,
}; };
} }
ClientState::Open { .. } => { ClientState::Open { .. } => {
@ -349,9 +350,9 @@ impl ClientConnection {
self.packet_transport.queue_packet(packet); self.packet_transport.queue_packet(packet);
} }
pub fn is_open(&self) -> Option<[u8; 32]> { pub fn is_open(&self) -> Option<SessionId> {
match self.state { match self.state {
ClientState::Open { session_identifier } => Some(session_identifier), ClientState::Open { session_id } => Some(session_id),
_ => None, _ => None,
} }
} }

View file

@ -7,7 +7,7 @@ use sha2::Digest;
use crate::{ use crate::{
packet::{EncryptedPacket, MsgKind, Packet, RawPacket}, packet::{EncryptedPacket, MsgKind, Packet, RawPacket},
peer_error, Msg, Result, SshRng, peer_error, Msg, Result, SessionId, SshRng,
}; };
pub type SharedSecret = secrecy::Secret<SharedSecretInner>; pub type SharedSecret = secrecy::Secret<SharedSecretInner>;
@ -308,7 +308,7 @@ impl SupportedAlgorithms {
} }
pub(crate) struct Session { pub(crate) struct Session {
session_id: [u8; 32], session_id: SessionId,
from_peer: Tunnel, from_peer: Tunnel,
to_peer: Tunnel, to_peer: Tunnel,
} }
@ -326,6 +326,7 @@ pub(crate) trait Keys: Send + Sync + 'static {
fn encrypt_packet_to_msg(&mut self, packet: Packet, packet_number: u64) -> Msg; fn encrypt_packet_to_msg(&mut self, packet: Packet, packet_number: u64) -> Msg;
fn additional_mac_len(&self) -> usize; fn additional_mac_len(&self) -> usize;
// TODO: actually rekey...
fn rekey( fn rekey(
&mut self, &mut self,
h: [u8; 32], h: [u8; 32],
@ -362,7 +363,7 @@ impl Keys for Plaintext {
impl Session { impl Session {
pub(crate) fn new( pub(crate) fn new(
h: [u8; 32], h: SessionId,
k: &SharedSecret, k: &SharedSecret,
encryption_client_to_server: EncryptionAlgorithm, encryption_client_to_server: EncryptionAlgorithm,
encryption_server_to_client: EncryptionAlgorithm, encryption_server_to_client: EncryptionAlgorithm,
@ -370,7 +371,7 @@ impl Session {
) -> Self { ) -> Self {
Self::from_keys( Self::from_keys(
h, h,
h, h.0,
k, k,
encryption_client_to_server, encryption_client_to_server,
encryption_server_to_client, encryption_server_to_client,
@ -380,7 +381,7 @@ impl Session {
/// <https://datatracker.ietf.org/doc/html/rfc4253#section-7.2> /// <https://datatracker.ietf.org/doc/html/rfc4253#section-7.2>
fn from_keys( fn from_keys(
session_id: [u8; 32], session_id: SessionId,
h: [u8; 32], h: [u8; 32],
k: &SharedSecret, k: &SharedSecret,
alg_c2s: EncryptionAlgorithm, alg_c2s: EncryptionAlgorithm,
@ -462,7 +463,7 @@ fn derive_key(
k: &SharedSecret, k: &SharedSecret,
h: [u8; 32], h: [u8; 32],
letter: &str, letter: &str,
session_id: [u8; 32], session_id: SessionId,
key_size: usize, key_size: usize,
) -> Vec<u8> { ) -> Vec<u8> {
let sha2len = sha2::Sha256::output_size(); let sha2len = sha2::Sha256::output_size();
@ -476,7 +477,7 @@ fn derive_key(
if i == 0 { if i == 0 {
hash.update(letter.as_bytes()); hash.update(letter.as_bytes());
hash.update(session_id); hash.update(session_id.0);
} else { } else {
hash.update(&output[..(i * sha2len)]); hash.update(&output[..(i * sha2len)]);
} }

View file

@ -3,8 +3,13 @@ pub mod crypto;
pub mod packet; pub mod packet;
pub mod server; pub mod server;
use std::fmt::Debug;
use cluelessh_format::ParseError; use cluelessh_format::ParseError;
pub use packet::Msg; pub use packet::Msg;
use serde::{Deserialize, Serialize};
// TODO: extensions
#[derive(Debug)] #[derive(Debug)]
pub enum SshStatus { pub enum SshStatus {
@ -17,6 +22,17 @@ pub enum SshStatus {
PeerError(String), PeerError(String),
} }
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct SessionId(pub [u8; 32]);
impl Debug for SessionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("SessionId")
.field(&hex::encode(self.0))
.finish()
}
}
pub type Result<T, E = SshStatus> = std::result::Result<T, E>; pub type Result<T, E = SshStatus> = std::result::Result<T, E>;
impl From<ParseError> for SshStatus { impl From<ParseError> for SshStatus {

View file

@ -6,8 +6,8 @@ use std::mem;
use tracing::{debug, trace}; use tracing::{debug, trace};
use crate::crypto::{self, EncryptionAlgorithm, Keys, Plaintext, Session, SharedSecret}; use crate::crypto::{self, EncryptionAlgorithm, Keys, Plaintext, Session, SharedSecret};
use crate::peer_error;
use crate::Result; use crate::Result;
use crate::{peer_error, SessionId};
use cluelessh_format::numbers; use cluelessh_format::numbers;
use cluelessh_format::{NameList, Reader, Writer}; use cluelessh_format::{NameList, Reader, Writer};
@ -67,7 +67,8 @@ impl PacketTransport {
} }
fn recv_bytes_step(&mut self, bytes: &[u8]) -> Result<Option<usize>> { fn recv_bytes_step(&mut self, bytes: &[u8]) -> Result<Option<usize>> {
// TODO: This might not work if we buffer two packets where one changes keys in between? // This would not work if we buffer two packets where one changes keys in between,
// but SSH_MSG_NEWKEYS messages guarantee that this cannot happen.
let result = let result =
self.recv_next_packet self.recv_next_packet
@ -125,7 +126,7 @@ impl PacketTransport {
is_server, is_server,
) { ) {
self.keys = Box::new(Session::new( self.keys = Box::new(Session::new(
h, SessionId(h),
k, k,
encryption_client_to_server, encryption_client_to_server,
encryption_server_to_client, encryption_server_to_client,

View file

@ -7,18 +7,14 @@ use crate::crypto::{
use crate::packet::{ use crate::packet::{
KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet, PacketTransport, ProtocolIdentParser, KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet, PacketTransport, ProtocolIdentParser,
}; };
use crate::Result;
use crate::{peer_error, Msg, SshRng, SshStatus}; use crate::{peer_error, Msg, SshRng, SshStatus};
use crate::{Result, SessionId};
use cluelessh_format::numbers; use cluelessh_format::numbers;
use cluelessh_format::{NameList, Reader, Writer}; use cluelessh_format::{NameList, Reader, Writer};
use cluelessh_keys::private::PlaintextPrivateKey; use cluelessh_keys::private::PlaintextPrivateKey;
use cluelessh_keys::signature::Signature; use cluelessh_keys::signature::Signature;
use tracing::{debug, info, trace}; use tracing::{debug, info, trace};
// This is definitely who we are.
// TODO: dont make cluelesshd do this
pub const SERVER_IDENTIFICATION: &[u8] = b"SSH-2.0-OpenSSH_9.7\r\n";
pub struct ServerConnection { pub struct ServerConnection {
state: ServerState, state: ServerState,
packet_transport: PacketTransport, packet_transport: PacketTransport,
@ -31,6 +27,7 @@ pub struct ServerConnection {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ServerConfig { pub struct ServerConfig {
pub server_identification: Vec<u8>,
pub host_keys: Vec<cluelessh_keys::public::PublicKey>, pub host_keys: Vec<cluelessh_keys::public::PublicKey>,
} }
@ -69,10 +66,10 @@ enum ServerState {
encryption_server_to_client: EncryptionAlgorithm, encryption_server_to_client: EncryptionAlgorithm,
}, },
ServiceRequest { ServiceRequest {
session_ident: [u8; 32], session_id: SessionId,
}, },
Open { Open {
session_ident: [u8; 32], session_id: SessionId,
}, },
} }
@ -87,7 +84,7 @@ pub struct KeyExchangeParameters {
} }
pub struct KeyExchangeResponse { pub struct KeyExchangeResponse {
pub hash: [u8; 32], pub hash: SessionId,
pub server_ephemeral_public_key: Vec<u8>, pub server_ephemeral_public_key: Vec<u8>,
pub shared_secret: SharedSecret, pub shared_secret: SharedSecret,
pub signature: Signature, pub signature: Signature,
@ -111,7 +108,7 @@ impl ServerConnection {
ident_parser.recv_bytes(bytes); ident_parser.recv_bytes(bytes);
if let Some(client_identification) = ident_parser.get_peer_ident() { if let Some(client_identification) = ident_parser.get_peer_ident() {
self.packet_transport self.packet_transport
.queue_send_protocol_info(SERVER_IDENTIFICATION.to_vec()); .queue_send_protocol_info(self.config.server_identification.clone());
self.state = ServerState::KeyExchangeInit { self.state = ServerState::KeyExchangeInit {
client_identification, client_identification,
}; };
@ -311,10 +308,11 @@ impl ServerConnection {
*encryption_server_to_client, *encryption_server_to_client,
true, true,
); );
self.state = ServerState::ServiceRequest { session_ident: *h }; self.state = ServerState::ServiceRequest {
session_id: SessionId(*h),
};
} }
ServerState::ServiceRequest { session_ident } => { ServerState::ServiceRequest { session_id } => {
// TODO: this should probably move out of here? unsure.
if packet.payload.first() != Some(&numbers::SSH_MSG_SERVICE_REQUEST) { if packet.payload.first() != Some(&numbers::SSH_MSG_SERVICE_REQUEST) {
return Err(peer_error!("did not send SSH_MSG_SERVICE_REQUEST")); return Err(peer_error!("did not send SSH_MSG_SERVICE_REQUEST"));
} }
@ -335,7 +333,7 @@ impl ServerConnection {
}, },
}); });
self.state = ServerState::Open { self.state = ServerState::Open {
session_ident: *session_ident, session_id: *session_id,
}; };
} }
ServerState::Open { .. } => { ServerState::Open { .. } => {
@ -346,9 +344,9 @@ impl ServerConnection {
Ok(()) Ok(())
} }
pub fn is_open(&self) -> Option<[u8; 32]> { pub fn is_open(&self) -> Option<SessionId> {
match self.state { match self.state {
ServerState::Open { session_ident } => Some(session_ident), ServerState::Open { session_id } => Some(session_id),
_ => None, _ => None,
} }
} }
@ -365,7 +363,7 @@ impl ServerConnection {
.. ..
} => Some(KeyExchangeParameters { } => Some(KeyExchangeParameters {
client_ident: client_identification.clone(), client_ident: client_identification.clone(),
server_ident: SERVER_IDENTIFICATION.to_vec(), server_ident: self.config.server_identification.to_vec(),
client_kexinit: client_kexinit.clone(), client_kexinit: client_kexinit.clone(),
server_kexinit: server_kexinit.clone(), server_kexinit: server_kexinit.clone(),
eph_client_public_key: client_ephemeral_public_key.clone(), eph_client_public_key: client_ephemeral_public_key.clone(),
@ -392,7 +390,7 @@ impl ServerConnection {
self.packet_transport.queue_packet(packet); self.packet_transport.queue_packet(packet);
self.state = ServerState::NewKeys { self.state = ServerState::NewKeys {
hash: response.hash, hash: response.hash.0,
shared_secret: response.shared_secret.clone(), shared_secret: response.shared_secret.clone(),
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,
@ -437,7 +435,7 @@ pub fn do_key_exchange(
); );
Ok(KeyExchangeResponse { Ok(KeyExchangeResponse {
hash, hash: SessionId(hash),
server_ephemeral_public_key, server_ephemeral_public_key,
shared_secret, shared_secret,
signature: private.private_key.sign(&hash), signature: private.private_key.sign(&hash),