rsh: implement signature verification
This commit is contained in:
parent
80e6658f55
commit
f0fdeb1004
2
userspace/Cargo.lock
generated
2
userspace/Cargo.lock
generated
@ -1167,10 +1167,12 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"clap",
|
||||
"cross",
|
||||
"ed25519-dalek",
|
||||
"env_logger",
|
||||
"libterm",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
@ -29,6 +29,7 @@ serde = { version = "1.0.214", features = ["derive"] }
|
||||
bytemuck = "1.19.0"
|
||||
thiserror = "1.0.64"
|
||||
flexbuffers = "2.0.0"
|
||||
sha2 = { version = "0.10.8" }
|
||||
|
||||
# Vendored/patched dependencies
|
||||
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil" }
|
||||
|
3
userspace/etc/rsh/id_ed25519
Normal file
3
userspace/etc/rsh/id_ed25519
Normal file
@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIO6qKZCX08BZ6LQV1DJYP0ONRXWaj6qSXwSc2mtcrl9I
|
||||
-----END PRIVATE KEY-----
|
@ -15,6 +15,9 @@ clap.workspace = true
|
||||
thiserror.workspace = true
|
||||
bytemuck.workspace = true
|
||||
x25519-dalek.workspace = true
|
||||
ed25519-dalek = { workspace = true, features = ["rand_core", "pem"] }
|
||||
sha2.workspace = true
|
||||
|
||||
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil-rng_core-0.6.4" }
|
||||
aes = { version = "0.8.4" }
|
||||
log = "0.4.22"
|
||||
|
@ -8,39 +8,53 @@ use cross::io::Poll;
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret};
|
||||
|
||||
use crate::{
|
||||
crypt::{ClientNegotiationMessage, ServerNegotiationMessage, V1_CIPHER_AES_256_CBC, V1_KEX_X25519_DALEK},
|
||||
socket::{MessageSocket, PacketSocket, SocketWrapper},
|
||||
crypt::{
|
||||
ciphersuite_name, sig_algo_name, signature::VerificationMethod, ClientNegotiationMessage, ServerNegotiationMessage, V1_CIPHER_AES_256_CBC, V1_KEX_X25519_DALEK
|
||||
},
|
||||
proto::{Decode, Decoder, Encode, Encoder},
|
||||
socket::PacketSocket,
|
||||
Error,
|
||||
};
|
||||
|
||||
use super::{
|
||||
symmetric::SymmetricCipher, ClientMessageProxy, ServerMessageProxy, V1_CIPHER_AES_256_ECB,
|
||||
signature::{fingerprint_sha256, SignatureKeyStore, SignatureMethod},
|
||||
symmetric::SymmetricCipher,
|
||||
DummyKeystore, V1_CIPHER_AES_256_ECB,
|
||||
};
|
||||
|
||||
pub struct ClientConfig {
|
||||
pub signature_keystore: Box<dyn SignatureKeyStore>,
|
||||
pub select_ciphersuite: fn(&[u8]) -> Option<u8>,
|
||||
}
|
||||
|
||||
enum ClientState {
|
||||
PreNegotioation,
|
||||
FingerprintSent(u8),
|
||||
StartKexSent(u8, u8),
|
||||
ClientKeySent(u8, EphemeralSecret),
|
||||
ServerKeyReceived(u8, SharedSecret),
|
||||
Connected(SymmetricCipher),
|
||||
}
|
||||
|
||||
struct TransportWrapper<S: PacketSocket> {
|
||||
inner: S,
|
||||
send_buf: [u8; 256],
|
||||
}
|
||||
|
||||
pub struct ClientEncryptedSocket<S: PacketSocket> {
|
||||
transport: SocketWrapper<S, ServerMessageProxy, ClientMessageProxy>,
|
||||
transport: TransportWrapper<S>,
|
||||
state: Option<ClientState>,
|
||||
last_ping: Instant,
|
||||
config: ClientConfig,
|
||||
signer: Option<SignatureMethod>,
|
||||
verifier: Option<VerificationMethod>,
|
||||
}
|
||||
|
||||
fn select_ciphersuite_default(offered: &[u8]) -> Option<u8> {
|
||||
// List of default accepted ciphers, ordered descending by their priority
|
||||
const ACCEPTED: &[u8] = &[
|
||||
V1_CIPHER_AES_256_CBC,
|
||||
V1_CIPHER_AES_256_ECB,
|
||||
V1_CIPHER_AES_256_ECB
|
||||
];
|
||||
|
||||
for cipher in ACCEPTED {
|
||||
@ -54,7 +68,8 @@ fn select_ciphersuite_default(offered: &[u8]) -> Option<u8> {
|
||||
impl Default for ClientConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
select_ciphersuite: select_ciphersuite_default
|
||||
signature_keystore: Box::new(DummyKeystore),
|
||||
select_ciphersuite: select_ciphersuite_default,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,10 +81,15 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
||||
|
||||
pub fn new_with_config(transport: S, config: ClientConfig) -> Self {
|
||||
Self {
|
||||
transport: SocketWrapper::new(transport),
|
||||
transport: TransportWrapper {
|
||||
inner: transport,
|
||||
send_buf: [0; 256],
|
||||
},
|
||||
state: None,
|
||||
last_ping: Instant::now(),
|
||||
config
|
||||
config,
|
||||
signer: None,
|
||||
verifier: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,21 +111,16 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
||||
}
|
||||
|
||||
fn do_handshake_sequence(&mut self, poll: &mut Poll, timeout: Duration) -> Result<(), Error> {
|
||||
let mut recv_buf = [0; 256];
|
||||
|
||||
self.transport.send(
|
||||
&mut recv_buf,
|
||||
&ClientNegotiationMessage::Hello { protocol: 1 },
|
||||
)?;
|
||||
self.transport
|
||||
.send(&ClientNegotiationMessage::Hello { protocol: 1 }, None)?;
|
||||
|
||||
loop {
|
||||
let Some(fd) = poll.wait(Some(timeout))? else {
|
||||
return Err(Error::Timeout);
|
||||
};
|
||||
assert_eq!(fd, self.transport.as_raw_fd());
|
||||
assert_eq!(fd, self.transport.inner.as_raw_fd());
|
||||
|
||||
let (message, _) = self.transport.recv_from(&mut recv_buf)?;
|
||||
if self.update_handshake_sequence(&message)? {
|
||||
if self.update_handshake_sequence()? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -114,17 +129,23 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_handshake_sequence(
|
||||
&mut self,
|
||||
message: &ServerNegotiationMessage,
|
||||
) -> Result<bool, Error> {
|
||||
let mut send_buf = [0; 256];
|
||||
fn update_handshake_sequence(&mut self) -> Result<bool, Error> {
|
||||
let mut recv_buf = [0; 256];
|
||||
let message = self.transport.recv(&mut recv_buf, self.verifier.as_mut())?;
|
||||
|
||||
match (message, self.state.take()) {
|
||||
// PreNegotioation -> StartKexSent
|
||||
// PreNegotioation -> FingerprintSent
|
||||
(ServerNegotiationMessage::Hello(info), Some(ClientState::PreNegotioation)) => {
|
||||
// TODO kex algorithm selection
|
||||
let kex_algo = V1_KEX_X25519_DALEK;
|
||||
let ciphersuite = match (self.config.select_ciphersuite)(info.symmetric_ciphersuites) {
|
||||
log::debug!("Server offered ciphersuites:");
|
||||
for cipher in info.symmetric_ciphersuites {
|
||||
log::debug!("* {:?}", ciphersuite_name(*cipher));
|
||||
}
|
||||
log::debug!("Server offered signature algorithms:");
|
||||
for algo in info.sig_algos {
|
||||
log::debug!("* {:?}", sig_algo_name(*algo));
|
||||
}
|
||||
let ciphersuite =
|
||||
match (self.config.select_ciphersuite)(info.symmetric_ciphersuites) {
|
||||
// Picked a ciphersuite
|
||||
Some(ciphersuite) => ciphersuite,
|
||||
// Server didn't offer anything acceptable
|
||||
@ -132,13 +153,52 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
||||
return Err(Error::UnacceptableCiphersuites);
|
||||
}
|
||||
};
|
||||
log::debug!("Selected ciphersuite: {:?}", ciphersuite_name(ciphersuite));
|
||||
let signer = self
|
||||
.config
|
||||
.signature_keystore
|
||||
.signer(info.sig_algos)
|
||||
.ok_or(Error::UnacceptableSignatureMethods)?;
|
||||
|
||||
let fingerprint = signer.fingerprint();
|
||||
log::debug!("Send fingerprint: {}", fingerprint);
|
||||
self.transport.send(
|
||||
&ClientNegotiationMessage::SigPublicKey(
|
||||
signer.algorithm(),
|
||||
&signer.verifying_key_bytes(),
|
||||
),
|
||||
None,
|
||||
)?;
|
||||
self.signer = Some(signer);
|
||||
self.state = Some(ClientState::FingerprintSent(ciphersuite));
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
// FingerprintSent -> StartKexSent
|
||||
(
|
||||
ServerNegotiationMessage::SigPublicKey(their_algo, key_data),
|
||||
Some(ClientState::FingerprintSent(ciphersuite)),
|
||||
) => {
|
||||
let algo_name = sig_algo_name(their_algo).unwrap_or("???");
|
||||
let their_fingerprint = fingerprint_sha256(algo_name, key_data);
|
||||
log::debug!("Server fingerprint: {}", their_fingerprint);
|
||||
let verifier = self
|
||||
.config
|
||||
.signature_keystore
|
||||
.accept_public_key(their_algo, key_data)
|
||||
.ok_or(Error::PublicKeyRejected)?;
|
||||
|
||||
// TODO kex algorithm selection
|
||||
let kex_algo = V1_KEX_X25519_DALEK;
|
||||
|
||||
self.transport.send(
|
||||
&mut send_buf,
|
||||
&ClientNegotiationMessage::StartKex {
|
||||
kex_algo,
|
||||
ciphersuite,
|
||||
},
|
||||
self.signer.as_mut(),
|
||||
)?;
|
||||
self.verifier = Some(verifier);
|
||||
self.state = Some(ClientState::StartKexSent(kex_algo, ciphersuite));
|
||||
|
||||
Ok(false)
|
||||
@ -154,8 +214,8 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
||||
|
||||
assert!(public.as_bytes().len() < 128);
|
||||
self.transport.send(
|
||||
&mut send_buf,
|
||||
&ClientNegotiationMessage::PublicKey(true, public.as_bytes()),
|
||||
&ClientNegotiationMessage::DHPublicKey(true, public.as_bytes()),
|
||||
self.signer.as_mut(),
|
||||
)?;
|
||||
|
||||
self.state = Some(ClientState::ClientKeySent(ciphersuite, secret));
|
||||
@ -163,7 +223,7 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
||||
}
|
||||
// ClientKeySent -> ServerKeyReceived
|
||||
(
|
||||
ServerNegotiationMessage::PublicKey(true, key_data),
|
||||
ServerNegotiationMessage::DHPublicKey(true, key_data),
|
||||
Some(ClientState::ClientKeySent(ciphersuite, secret)),
|
||||
) if key_data.len() == 32 => {
|
||||
let mut public = [0; 32];
|
||||
@ -172,7 +232,7 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
||||
let shared = secret.diffie_hellman(&public);
|
||||
|
||||
self.transport
|
||||
.send(&mut send_buf, &ClientNegotiationMessage::Agreed)?;
|
||||
.send(&ClientNegotiationMessage::Agreed, self.signer.as_mut())?;
|
||||
self.state = Some(ClientState::ServerKeyReceived(ciphersuite, shared));
|
||||
Ok(false)
|
||||
}
|
||||
@ -187,26 +247,42 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
||||
}
|
||||
Err(error) => {
|
||||
self.transport
|
||||
.send(&mut send_buf, &ClientNegotiationMessage::Disconnect(0))
|
||||
.send(
|
||||
&ClientNegotiationMessage::Disconnect(0),
|
||||
self.signer.as_mut(),
|
||||
)
|
||||
.ok();
|
||||
self.state = None;
|
||||
Err(error)
|
||||
}
|
||||
},
|
||||
// *** -> ***
|
||||
(ServerNegotiationMessage::Ping, _) => {
|
||||
self.transport.send(&ClientNegotiationMessage::Pong, None).ok();
|
||||
self.last_ping = Instant::now();
|
||||
Ok(false)
|
||||
}
|
||||
// *** -> None
|
||||
(ServerNegotiationMessage::Kick(_reason), _) => {
|
||||
// No need to send any disconnects, server already forgot us
|
||||
self.state = None;
|
||||
self.reset();
|
||||
Err(Error::Disconnected)
|
||||
}
|
||||
(_, _) => {
|
||||
self.transport
|
||||
.send(&mut send_buf, &ClientNegotiationMessage::Disconnect(0))
|
||||
.send(&ClientNegotiationMessage::Disconnect(0), None)
|
||||
.ok();
|
||||
self.state = None;
|
||||
self.reset();
|
||||
Err(Error::Disconnected)
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.signer = None;
|
||||
self.verifier = None;
|
||||
self.state = None;
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,7 +295,9 @@ impl<S: PacketSocket> PacketSocket for ClientEncryptedSocket<S> {
|
||||
};
|
||||
|
||||
let mut buffer = [0; 256];
|
||||
let (message, peer) = self.transport.recv_from(&mut buffer)?;
|
||||
let (message, peer) = self
|
||||
.transport
|
||||
.recv_from(&mut buffer, self.verifier.as_mut())?;
|
||||
match message {
|
||||
ServerNegotiationMessage::CipherText(ciphertext) => {
|
||||
let len = cipher.decrypt(ciphertext, output)?;
|
||||
@ -227,7 +305,7 @@ impl<S: PacketSocket> PacketSocket for ClientEncryptedSocket<S> {
|
||||
}
|
||||
ServerNegotiationMessage::Ping => {
|
||||
self.transport
|
||||
.send(&mut buffer, &ClientNegotiationMessage::Pong)?;
|
||||
.send(&ClientNegotiationMessage::Pong, self.signer.as_mut())?;
|
||||
self.last_ping = Instant::now();
|
||||
// TODO this is a workaround
|
||||
Err(Error::Ping)
|
||||
@ -246,11 +324,10 @@ impl<S: PacketSocket> PacketSocket for ClientEncryptedSocket<S> {
|
||||
};
|
||||
|
||||
let mut buffer = [0; 256];
|
||||
let mut send_buffer = [0; 256];
|
||||
let len = cipher.encrypt(data, &mut buffer)?;
|
||||
self.transport.send(
|
||||
&mut send_buffer,
|
||||
&ClientNegotiationMessage::CipherText(&buffer[..len]),
|
||||
self.signer.as_mut(),
|
||||
)?;
|
||||
Ok(data.len())
|
||||
}
|
||||
@ -259,9 +336,8 @@ impl<S: PacketSocket> PacketSocket for ClientEncryptedSocket<S> {
|
||||
impl<S: PacketSocket> Drop for ClientEncryptedSocket<S> {
|
||||
fn drop(&mut self) {
|
||||
if self.state.is_some() {
|
||||
let mut buffer = [0; 32];
|
||||
self.transport
|
||||
.send(&mut buffer, &ClientNegotiationMessage::Disconnect(0))
|
||||
.send(&ClientNegotiationMessage::Disconnect(0), None)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
@ -269,6 +345,64 @@ impl<S: PacketSocket> Drop for ClientEncryptedSocket<S> {
|
||||
|
||||
impl<S: PacketSocket> AsRawFd for ClientEncryptedSocket<S> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.transport.as_raw_fd()
|
||||
self.transport.inner.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: PacketSocket> TransportWrapper<S> {
|
||||
fn send(
|
||||
&mut self,
|
||||
message: &ClientNegotiationMessage,
|
||||
signer: Option<&mut SignatureMethod>,
|
||||
) -> Result<(), Error> {
|
||||
let mut encoder = Encoder::new(&mut self.send_buf);
|
||||
|
||||
message.encode(&mut encoder)?;
|
||||
|
||||
let len = match (message.needs_signature(), signer) {
|
||||
(false, _) => encoder.get().len(),
|
||||
(true, Some(signer)) => {
|
||||
let (message, signature) = encoder.split_mut();
|
||||
let signature_len = signer.sign(message, signature)?;
|
||||
message.len() + signature_len
|
||||
}
|
||||
(true, None) => unreachable!(),
|
||||
};
|
||||
|
||||
let payload = &self.send_buf[..len];
|
||||
|
||||
self.inner.send(payload).map_err(Into::into)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recv_from<'de>(
|
||||
&mut self,
|
||||
recv_buf: &'de mut [u8],
|
||||
verifier: Option<&mut VerificationMethod>,
|
||||
) -> Result<(ServerNegotiationMessage<'de>, SocketAddr), Error> {
|
||||
let (len, peer) = self.inner.recv_from(recv_buf).map_err(Into::into)?;
|
||||
|
||||
let mut decoder = Decoder::new(&recv_buf[..len]);
|
||||
let message = ServerNegotiationMessage::decode(&mut decoder)?;
|
||||
|
||||
match (message.needs_signature(), verifier) {
|
||||
(false, _) => (),
|
||||
(true, Some(verifier)) => {
|
||||
let (payload, signature) = decoder.split();
|
||||
verifier.verify(payload, signature)?;
|
||||
}
|
||||
(true, None) => unimplemented!(),
|
||||
};
|
||||
|
||||
Ok((message, peer))
|
||||
}
|
||||
|
||||
fn recv<'de>(
|
||||
&mut self,
|
||||
recv_buf: &'de mut [u8],
|
||||
verifier: Option<&mut VerificationMethod>,
|
||||
) -> Result<ServerNegotiationMessage<'de>, Error> {
|
||||
let (message, _) = self.recv_from(recv_buf, verifier)?;
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,18 @@
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
use crate::proto::{Decode, DecodeError, Decoder, Encode, EncodeError, Encoder, MessageProxy};
|
||||
|
||||
pub mod client;
|
||||
pub mod server;
|
||||
pub mod signature;
|
||||
pub mod symmetric;
|
||||
pub mod util;
|
||||
|
||||
pub use client::ClientEncryptedSocket;
|
||||
pub use server::ServerEncryptedSocket;
|
||||
use signature::{
|
||||
fingerprint_sha256, SignEd25519, SignatureKeyStore, SignatureMethod, VerificationMethod,
|
||||
};
|
||||
|
||||
pub const V1_CIPHER_AES_256_ECB: u8 = 0x10;
|
||||
pub const V1_CIPHER_AES_256_CBC: u8 = 0x11;
|
||||
@ -14,6 +20,9 @@ pub const V1_CIPHER_AES_256_CBC: u8 = 0x11;
|
||||
// v1 supports only one DH algo
|
||||
pub const V1_KEX_X25519_DALEK: u8 = 0x10;
|
||||
|
||||
pub const V1_SIG_ED25519: u8 = 0x10;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerReject {
|
||||
NoReason,
|
||||
UnsupportedKexAlgo,
|
||||
@ -22,28 +31,35 @@ pub enum ServerReject {
|
||||
}
|
||||
|
||||
// v1 protocol
|
||||
#[derive(Debug)]
|
||||
pub struct ServerHello<'a> {
|
||||
// TODO server will send its public fingerprint and advertise
|
||||
// supported asymmetric key formats
|
||||
pub symmetric_ciphersuites: &'a [u8],
|
||||
pub kex_algos: &'a [u8],
|
||||
pub sig_algos: &'a [u8],
|
||||
pub key_hash_algos: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientNegotiationMessage<'a> {
|
||||
Hello { protocol: u8 },
|
||||
SigPublicKey(u8, &'a [u8]),
|
||||
StartKex { kex_algo: u8, ciphersuite: u8 },
|
||||
PublicKey(bool, &'a [u8]),
|
||||
DHPublicKey(bool, &'a [u8]),
|
||||
Agreed,
|
||||
Pong,
|
||||
CipherText(&'a [u8]),
|
||||
Disconnect(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerNegotiationMessage<'a> {
|
||||
Hello(ServerHello<'a>),
|
||||
SigPublicKey(u8, &'a [u8]),
|
||||
Reject(ServerReject),
|
||||
StartKex,
|
||||
PublicKey(bool, &'a [u8]),
|
||||
DHPublicKey(bool, &'a [u8]),
|
||||
Agreed,
|
||||
Kick(u8),
|
||||
Ping,
|
||||
@ -53,6 +69,89 @@ pub enum ServerNegotiationMessage<'a> {
|
||||
pub struct ClientMessageProxy;
|
||||
pub struct ServerMessageProxy;
|
||||
|
||||
struct DummyKeystore;
|
||||
|
||||
pub struct SimpleServerKeyStore {
|
||||
pub path: PathBuf,
|
||||
pub accepted_keys: HashSet<String>,
|
||||
}
|
||||
|
||||
pub struct SimpleClientKeyStore {
|
||||
algo: [u8; 1],
|
||||
key: Option<SignatureMethod>,
|
||||
}
|
||||
|
||||
impl SignatureKeyStore for DummyKeystore {
|
||||
fn offer_signature_algorithms(&self) -> &[u8] {
|
||||
&[V1_SIG_ED25519]
|
||||
}
|
||||
|
||||
fn signer_for_algorithm(&mut self, algorithm: u8) -> Option<SignatureMethod> {
|
||||
// Just generate a one-time key
|
||||
SignatureMethod::generate(algorithm).ok()
|
||||
}
|
||||
|
||||
fn accept_public_key(&mut self, algorithm: u8, data: &[u8]) -> Option<VerificationMethod> {
|
||||
// Accept any key
|
||||
VerificationMethod::from_parts(algorithm, data).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl SignatureKeyStore for SimpleServerKeyStore {
|
||||
fn offer_signature_algorithms(&self) -> &[u8] {
|
||||
&[V1_SIG_ED25519]
|
||||
}
|
||||
|
||||
fn signer_for_algorithm(&mut self, algorithm: u8) -> Option<SignatureMethod> {
|
||||
match algorithm {
|
||||
V1_SIG_ED25519 => SignEd25519::load_signing_key(self.path.join("id_ed25519"))
|
||||
.map(SignatureMethod::Ed25519)
|
||||
.ok(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_public_key(&mut self, algorithm: u8, data: &[u8]) -> Option<VerificationMethod> {
|
||||
let algorithm_name = sig_algo_name(algorithm)?;
|
||||
let fingerprint = fingerprint_sha256(algorithm_name, data);
|
||||
if self.accepted_keys.contains(&fingerprint) {
|
||||
VerificationMethod::from_parts(algorithm, data).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleClientKeyStore {
|
||||
pub fn new(key: SignatureMethod) -> Self {
|
||||
Self {
|
||||
algo: match key {
|
||||
SignatureMethod::Ed25519(_) => [V1_SIG_ED25519],
|
||||
},
|
||||
key: Some(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SignatureKeyStore for SimpleClientKeyStore {
|
||||
fn signer_for_algorithm(&mut self, algorithm: u8) -> Option<SignatureMethod> {
|
||||
if algorithm == self.algo[0] {
|
||||
self.key.take()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_public_key(&mut self, algorithm: u8, data: &[u8]) -> Option<VerificationMethod> {
|
||||
// Accept any key
|
||||
VerificationMethod::from_parts(algorithm, data).ok()
|
||||
}
|
||||
|
||||
fn offer_signature_algorithms(&self) -> &[u8] {
|
||||
&self.algo
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageProxy for ClientMessageProxy {
|
||||
type Type<'de> = ClientNegotiationMessage<'de>;
|
||||
}
|
||||
@ -60,27 +159,66 @@ impl MessageProxy for ServerMessageProxy {
|
||||
type Type<'de> = ServerNegotiationMessage<'de>;
|
||||
}
|
||||
|
||||
impl Encode for ServerHello<'_> {
|
||||
fn encode(&self, buffer: &mut Encoder) -> Result<(), EncodeError> {
|
||||
buffer.write_variable_bytes(self.kex_algos)?;
|
||||
buffer.write_variable_bytes(self.symmetric_ciphersuites)?;
|
||||
buffer.write_variable_bytes(self.sig_algos)?;
|
||||
buffer.write_variable_bytes(self.key_hash_algos)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Decode<'de> for ServerHello<'de> {
|
||||
fn decode(buffer: &mut Decoder<'de>) -> Result<Self, DecodeError> {
|
||||
let kex_algos = buffer.read_variable_bytes()?;
|
||||
let symmetric_ciphersuites = buffer.read_variable_bytes()?;
|
||||
let sig_algos = buffer.read_variable_bytes()?;
|
||||
let key_hash_algos = buffer.read_variable_bytes()?;
|
||||
Ok(Self {
|
||||
kex_algos,
|
||||
symmetric_ciphersuites,
|
||||
sig_algos,
|
||||
key_hash_algos,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientNegotiationMessage<'_> {
|
||||
const TAG_HELLO: u8 = 0x90;
|
||||
const TAG_START_KEX: u8 = 0x91;
|
||||
const TAG_PUBLIC_KEY: u8 = 0x92;
|
||||
const TAG_AGREED: u8 = 0x93;
|
||||
const TAG_SIG_PUBLIC_KEY: u8 = 0x91;
|
||||
const TAG_START_KEX: u8 = 0x92;
|
||||
const TAG_DH_PUBLIC_KEY: u8 = 0x93;
|
||||
const TAG_AGREED: u8 = 0x94;
|
||||
|
||||
const TAG_CIPHERTEXT: u8 = 0xA0;
|
||||
const TAG_DISCONNECT: u8 = 0xA1;
|
||||
const TAG_PONG: u8 = 0xA2;
|
||||
|
||||
pub fn needs_signature(&self) -> bool {
|
||||
match self {
|
||||
Self::Hello { .. } | Self::SigPublicKey(_, _) | Self::Pong | Self::Disconnect(_) => {
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for ClientNegotiationMessage<'_> {
|
||||
fn encode(&self, buffer: &mut Encoder) -> Result<(), EncodeError> {
|
||||
match self {
|
||||
&Self::Hello { protocol } => buffer.write(&[Self::TAG_HELLO, protocol]),
|
||||
&Self::SigPublicKey(algo, data) => {
|
||||
buffer.write(&[Self::TAG_SIG_PUBLIC_KEY, algo])?;
|
||||
buffer.write_variable_bytes(data)
|
||||
}
|
||||
&Self::StartKex {
|
||||
kex_algo,
|
||||
ciphersuite,
|
||||
} => buffer.write(&[Self::TAG_START_KEX, kex_algo, ciphersuite]),
|
||||
Self::PublicKey(end, data) => {
|
||||
buffer.write(&[Self::TAG_PUBLIC_KEY, *end as u8])?;
|
||||
Self::DHPublicKey(end, data) => {
|
||||
buffer.write(&[Self::TAG_DH_PUBLIC_KEY, *end as u8])?;
|
||||
buffer.write_variable_bytes(data)
|
||||
}
|
||||
Self::Agreed => buffer.write(&[Self::TAG_AGREED]),
|
||||
@ -102,6 +240,11 @@ impl<'de> Decode<'de> for ClientNegotiationMessage<'de> {
|
||||
let protocol = buffer.read_u8()?;
|
||||
Ok(Self::Hello { protocol })
|
||||
}
|
||||
Self::TAG_SIG_PUBLIC_KEY => {
|
||||
let algo = buffer.read_u8()?;
|
||||
let data = buffer.read_variable_bytes()?;
|
||||
Ok(Self::SigPublicKey(algo, data))
|
||||
}
|
||||
Self::TAG_START_KEX => {
|
||||
let kex_algo = buffer.read_u8()?;
|
||||
let ciphersuite = buffer.read_u8()?;
|
||||
@ -110,10 +253,10 @@ impl<'de> Decode<'de> for ClientNegotiationMessage<'de> {
|
||||
ciphersuite,
|
||||
})
|
||||
}
|
||||
Self::TAG_PUBLIC_KEY => {
|
||||
Self::TAG_DH_PUBLIC_KEY => {
|
||||
let end = buffer.read_u8()? != 0;
|
||||
let data = buffer.read_variable_bytes()?;
|
||||
Ok(Self::PublicKey(end, data))
|
||||
Ok(Self::DHPublicKey(end, data))
|
||||
}
|
||||
Self::TAG_CIPHERTEXT => buffer.read_variable_bytes().map(Self::CipherText),
|
||||
Self::TAG_AGREED => Ok(Self::Agreed),
|
||||
@ -126,32 +269,25 @@ impl<'de> Decode<'de> for ClientNegotiationMessage<'de> {
|
||||
|
||||
impl ServerNegotiationMessage<'_> {
|
||||
const TAG_HELLO: u8 = 0xB0;
|
||||
const TAG_REJECT: u8 = 0xB1;
|
||||
const TAG_START_KEX: u8 = 0xB2;
|
||||
const TAG_PUBLIC_KEY: u8 = 0xB3;
|
||||
const TAG_AGREED: u8 = 0xB4;
|
||||
const TAG_SIG_PUBLIC_KEY: u8 = 0xB1;
|
||||
const TAG_REJECT: u8 = 0xB2;
|
||||
const TAG_START_KEX: u8 = 0xB3;
|
||||
const TAG_DH_PUBLIC_KEY: u8 = 0xB4;
|
||||
const TAG_AGREED: u8 = 0xB5;
|
||||
|
||||
const TAG_CIPHERTEXT: u8 = 0xC0;
|
||||
const TAG_PING: u8 = 0xC1;
|
||||
const TAG_KICK: u8 = 0xC2;
|
||||
}
|
||||
|
||||
impl Encode for ServerHello<'_> {
|
||||
fn encode(&self, buffer: &mut Encoder) -> Result<(), EncodeError> {
|
||||
buffer.write_variable_bytes(self.kex_algos)?;
|
||||
buffer.write_variable_bytes(self.symmetric_ciphersuites)?;
|
||||
Ok(())
|
||||
pub fn needs_signature(&self) -> bool {
|
||||
match self {
|
||||
Self::Hello(_)
|
||||
| Self::Reject(_)
|
||||
| Self::Kick(_)
|
||||
| Self::Ping
|
||||
| Self::SigPublicKey(_, _) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Decode<'de> for ServerHello<'de> {
|
||||
fn decode(buffer: &mut Decoder<'de>) -> Result<Self, DecodeError> {
|
||||
let kex_algos = buffer.read_variable_bytes()?;
|
||||
let symmetric_ciphersuites = buffer.read_variable_bytes()?;
|
||||
Ok(Self {
|
||||
kex_algos,
|
||||
symmetric_ciphersuites,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,10 +298,14 @@ impl Encode for ServerNegotiationMessage<'_> {
|
||||
buffer.write(&[Self::TAG_HELLO])?;
|
||||
hello.encode(buffer)
|
||||
}
|
||||
&Self::SigPublicKey(algo, key) => {
|
||||
buffer.write(&[Self::TAG_SIG_PUBLIC_KEY, algo])?;
|
||||
buffer.write_variable_bytes(key)
|
||||
}
|
||||
Self::Reject(_reason) => todo!(),
|
||||
Self::StartKex => buffer.write(&[Self::TAG_START_KEX]),
|
||||
Self::PublicKey(end, data) => {
|
||||
buffer.write(&[Self::TAG_PUBLIC_KEY, *end as u8])?;
|
||||
Self::DHPublicKey(end, data) => {
|
||||
buffer.write(&[Self::TAG_DH_PUBLIC_KEY, *end as u8])?;
|
||||
buffer.write_variable_bytes(data)
|
||||
}
|
||||
Self::Agreed => buffer.write(&[Self::TAG_AGREED]),
|
||||
@ -184,12 +324,17 @@ impl<'de> Decode<'de> for ServerNegotiationMessage<'de> {
|
||||
let tag = buffer.read_u8()?;
|
||||
match tag {
|
||||
Self::TAG_HELLO => ServerHello::decode(buffer).map(Self::Hello),
|
||||
Self::TAG_SIG_PUBLIC_KEY => {
|
||||
let algo = buffer.read_u8()?;
|
||||
let data = buffer.read_variable_bytes()?;
|
||||
Ok(Self::SigPublicKey(algo, data))
|
||||
}
|
||||
Self::TAG_REJECT => todo!(),
|
||||
Self::TAG_START_KEX => Ok(Self::StartKex),
|
||||
Self::TAG_PUBLIC_KEY => {
|
||||
Self::TAG_DH_PUBLIC_KEY => {
|
||||
let end = buffer.read_u8()? != 0;
|
||||
let data = buffer.read_variable_bytes()?;
|
||||
Ok(Self::PublicKey(end, data))
|
||||
Ok(Self::DHPublicKey(end, data))
|
||||
}
|
||||
Self::TAG_AGREED => Ok(Self::Agreed),
|
||||
Self::TAG_CIPHERTEXT => buffer.read_variable_bytes().map(Self::CipherText),
|
||||
@ -205,6 +350,41 @@ pub fn ciphersuite_name(cipher: u8) -> Option<&'static str> {
|
||||
match cipher {
|
||||
V1_CIPHER_AES_256_ECB => Some("aes-256-ecb"),
|
||||
V1_CIPHER_AES_256_CBC => Some("aes-256-cbc"),
|
||||
_ => None
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn hash_algo_name(hash: u8) -> Option<&'static str> {
|
||||
// match hash {
|
||||
// V1_HASH_SHA256 => Some("sha256"),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn sig_algo_name(sig: u8) -> Option<&'static str> {
|
||||
match sig {
|
||||
V1_SIG_ED25519 => Some("ed25519"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// impl fmt::Display for PublicKeyFingerprint<'_> {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// let hash = match hash_algo_name(self.hash) {
|
||||
// Some(name) => name,
|
||||
// None => "unknown",
|
||||
// };
|
||||
// let sig = match sig_algo_name(self.sig) {
|
||||
// Some(name) => name,
|
||||
// None => "unknown",
|
||||
// };
|
||||
// write!(f, "{} {} {} ", sig, self.key_bits, hash)?;
|
||||
// for (i, byte) in self.hash_data.iter().enumerate() {
|
||||
// if i != 0 {
|
||||
// write!(f, ":")?;
|
||||
// }
|
||||
// write!(f, "{:02x}", *byte)?;
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
@ -9,33 +9,53 @@ use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||
|
||||
use crate::{
|
||||
crypt::{
|
||||
ciphersuite_name, ServerHello, ServerNegotiationMessage, V1_CIPHER_AES_256_CBC,
|
||||
V1_CIPHER_AES_256_ECB,
|
||||
},
|
||||
socket::{
|
||||
MessageSocket, MultiplexedSocket, MultiplexedSocketEvent, PacketSocket, SocketWrapper,
|
||||
ciphersuite_name, sig_algo_name, ServerHello, ServerNegotiationMessage,
|
||||
V1_CIPHER_AES_256_CBC, V1_CIPHER_AES_256_ECB,
|
||||
},
|
||||
proto::{Decode, Decoder, Encode, Encoder},
|
||||
socket::{MultiplexedSocket, MultiplexedSocketEvent, PacketSocket},
|
||||
Error,
|
||||
};
|
||||
|
||||
use super::{
|
||||
symmetric::SymmetricCipher, ClientMessageProxy, ClientNegotiationMessage, ServerMessageProxy,
|
||||
signature::{SignatureKeyStore, SignatureMethod, VerificationMethod},
|
||||
symmetric::SymmetricCipher,
|
||||
ClientNegotiationMessage, DummyKeystore,
|
||||
};
|
||||
|
||||
pub struct ServerConfig {
|
||||
// Returns Some(...) if a corresponding accepted key was found for the fingerprint
|
||||
// None otherwise or if signature/hash algorithms are not accepted by the server
|
||||
pub signature_keystore: Box<dyn SignatureKeyStore>,
|
||||
|
||||
pub accept_ciphersuite: fn(u8) -> bool,
|
||||
pub offer_ciphersuites: fn() -> &'static [u8],
|
||||
}
|
||||
|
||||
pub enum ServerPeerTransport {
|
||||
enum PeerState {
|
||||
None,
|
||||
PreNegotiation,
|
||||
Authorized,
|
||||
Negotiation(EphemeralSecret, u8),
|
||||
Connected(SymmetricCipher, usize),
|
||||
Connected(SymmetricCipher),
|
||||
}
|
||||
|
||||
struct ServerPeer {
|
||||
state: PeerState,
|
||||
signer: Option<SignatureMethod>,
|
||||
verifier: Option<VerificationMethod>,
|
||||
missed_pings: usize,
|
||||
}
|
||||
|
||||
struct TransportWrapper<S: PacketSocket> {
|
||||
inner: S,
|
||||
send_buf: [u8; 256],
|
||||
}
|
||||
|
||||
pub struct ServerEncryptedSocket<S: PacketSocket> {
|
||||
transport: SocketWrapper<S, ClientMessageProxy, ServerMessageProxy>,
|
||||
peers: HashMap<SocketAddr, ServerPeerTransport>,
|
||||
transport: TransportWrapper<S>,
|
||||
// transport: SocketWrapper<S, ClientMessageProxy, ServerMessageProxy>,
|
||||
peers: HashMap<SocketAddr, ServerPeer>,
|
||||
buffer: [u8; 256],
|
||||
config: ServerConfig,
|
||||
}
|
||||
@ -53,6 +73,7 @@ fn offer_ciphersuites_default() -> &'static [u8] {
|
||||
impl Default for ServerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
signature_keystore: Box::new(DummyKeystore),
|
||||
accept_ciphersuite: accept_ciphersuite_default,
|
||||
offer_ciphersuites: offer_ciphersuites_default,
|
||||
}
|
||||
@ -66,7 +87,10 @@ impl<S: PacketSocket> ServerEncryptedSocket<S> {
|
||||
|
||||
pub fn new_with_config(transport: S, config: ServerConfig) -> Self {
|
||||
Self {
|
||||
transport: SocketWrapper::new(transport),
|
||||
transport: TransportWrapper {
|
||||
inner: transport,
|
||||
send_buf: [0; 256],
|
||||
},
|
||||
peers: HashMap::new(),
|
||||
buffer: [0; 256],
|
||||
config,
|
||||
@ -74,32 +98,22 @@ impl<S: PacketSocket> ServerEncryptedSocket<S> {
|
||||
}
|
||||
|
||||
pub fn remove_client(&mut self, remote: &SocketAddr) {
|
||||
let mut buf = [0; 32];
|
||||
if self.peers.remove(remote).is_some() {
|
||||
self.transport
|
||||
.send_to(remote, &mut buf, &ServerNegotiationMessage::Kick(0))
|
||||
.send_to(remote, &ServerNegotiationMessage::Kick(0), None)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ping_clients(&mut self, limit: usize) -> Vec<SocketAddr> {
|
||||
let mut send_buf = [0; 32];
|
||||
let mut removed = vec![];
|
||||
for (remote, state) in self.peers.iter_mut() {
|
||||
match state {
|
||||
ServerPeerTransport::Connected(_, missed) => {
|
||||
self.transport
|
||||
.send_to(remote, &mut send_buf, &ServerNegotiationMessage::Ping)
|
||||
.ok();
|
||||
for (remote, peer) in self.peers.iter_mut() {
|
||||
self.transport.send_to(remote, &ServerNegotiationMessage::Ping, None).ok();
|
||||
|
||||
if *missed >= limit {
|
||||
if peer.missed_pings >= limit {
|
||||
removed.push(*remote);
|
||||
}
|
||||
|
||||
*missed += 1;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
peer.missed_pings += 1;
|
||||
}
|
||||
|
||||
for entry in removed.iter() {
|
||||
@ -108,35 +122,90 @@ impl<S: PacketSocket> ServerEncryptedSocket<S> {
|
||||
|
||||
removed
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: PacketSocket> MultiplexedSocket for ServerEncryptedSocket<S> {
|
||||
fn send_to(&mut self, remote: &SocketAddr, data: &[u8]) -> Result<(), Error> {
|
||||
let mut buf = [0; 256];
|
||||
if let Some(ServerPeerTransport::Connected(cipher, _)) = self.peers.get_mut(remote) {
|
||||
let len = cipher.encrypt(data, &mut self.buffer)?;
|
||||
self.transport.send_to(
|
||||
remote,
|
||||
&mut buf,
|
||||
&ServerNegotiationMessage::CipherText(&self.buffer[..len]),
|
||||
)
|
||||
} else {
|
||||
Err(Error::NotConnected)
|
||||
fn recv_inner<'de>(
|
||||
&mut self,
|
||||
recv_buf: &'de mut [u8],
|
||||
) -> Result<(ClientNegotiationMessage<'de>, SocketAddr), Error> {
|
||||
let (len, remote) = self
|
||||
.transport
|
||||
.inner
|
||||
.recv_from(recv_buf)
|
||||
.map_err(Into::into)?;
|
||||
let mut decoder = Decoder::new(&recv_buf[..len]);
|
||||
let message = ClientNegotiationMessage::decode(&mut decoder)?;
|
||||
|
||||
if message.needs_signature() {
|
||||
let Some(peer) = self.peers.get_mut(&remote) else {
|
||||
return Err(Error::NotConnected);
|
||||
};
|
||||
|
||||
match peer.verifier.as_mut() {
|
||||
Some(verifier) => {
|
||||
let (payload, signature) = decoder.split();
|
||||
if signature.is_empty() {
|
||||
return Err(Error::MissingSignature);
|
||||
}
|
||||
verifier.verify(payload, signature)?;
|
||||
}
|
||||
None => {
|
||||
// TODO kick the client, because it sent a signed message without a signature
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn recv_from<'a>(&mut self, buffer: &'a mut [u8]) -> Result<MultiplexedSocketEvent<'a>, Error> {
|
||||
let (message, remote) = self.transport.recv_from(&mut self.buffer)?;
|
||||
Ok((message, remote))
|
||||
}
|
||||
|
||||
let mut buf = [0; 256];
|
||||
fn recv_from_inner<'a>(&mut self, buffer: &'a mut [u8]) -> Result<MultiplexedSocketEvent<'a>, Error> {
|
||||
let mut recv_buf = [0; 256];
|
||||
let (message, remote) = self.recv_inner(&mut recv_buf)?;
|
||||
|
||||
if let Some(mut state) = self.peers.get_mut(&remote) {
|
||||
match (message, &mut state) {
|
||||
if let Some(peer) = self.peers.get_mut(&remote) {
|
||||
match (message, &mut peer.state) {
|
||||
// TODO check kex params
|
||||
(
|
||||
ClientNegotiationMessage::StartKex { ciphersuite, .. },
|
||||
ServerPeerTransport::PreNegotiation,
|
||||
) => {
|
||||
// PreNegotiation -> Authorized (or fail)
|
||||
(ClientNegotiationMessage::SigPublicKey(sig, data), PeerState::PreNegotiation) => {
|
||||
// TODO server might as well choose its own signature algorithm
|
||||
log::debug!("{remote} sent public key");
|
||||
let verifier = match self.config.signature_keystore.accept_public_key(sig, data)
|
||||
{
|
||||
Some(verifier) => verifier,
|
||||
None => {
|
||||
log::warn!("{remote}: rejected public key");
|
||||
self.remove_client(&remote);
|
||||
return Ok(MultiplexedSocketEvent::None(remote));
|
||||
}
|
||||
};
|
||||
let signer = match self.config.signature_keystore.signer_for_algorithm(sig) {
|
||||
Some(signer) => signer,
|
||||
None => {
|
||||
let name = sig_algo_name(sig);
|
||||
log::warn!("{remote}: no key for signature algorithm {name:?}");
|
||||
self.remove_client(&remote);
|
||||
return Ok(MultiplexedSocketEvent::None(remote));
|
||||
}
|
||||
};
|
||||
|
||||
// self.transport.send_to(&remote, &ServerNegotiationMessage::SigPublicKey(sig, ))?;
|
||||
self.transport.send_to(
|
||||
&remote,
|
||||
&ServerNegotiationMessage::SigPublicKey(
|
||||
sig,
|
||||
&*signer.verifying_key_bytes(),
|
||||
),
|
||||
None,
|
||||
)?;
|
||||
|
||||
peer.signer = Some(signer);
|
||||
peer.verifier = Some(verifier);
|
||||
peer.state = PeerState::Authorized;
|
||||
|
||||
Ok(MultiplexedSocketEvent::None(remote))
|
||||
}
|
||||
// Authorized -> Negotiation
|
||||
(ClientNegotiationMessage::StartKex { ciphersuite, .. }, PeerState::Authorized) => {
|
||||
let name = ciphersuite_name(ciphersuite);
|
||||
if !(self.config.accept_ciphersuite)(ciphersuite) {
|
||||
log::warn!("Kicking {remote}: cannot accept offered ciphersuite: {name:?}",);
|
||||
@ -148,59 +217,72 @@ impl<S: PacketSocket> MultiplexedSocket for ServerEncryptedSocket<S> {
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let secret = EphemeralSecret::random_from_rng(&mut rng);
|
||||
*state = ServerPeerTransport::Negotiation(secret, ciphersuite);
|
||||
self.transport.send_to(
|
||||
|
||||
peer.state = PeerState::Negotiation(secret, ciphersuite);
|
||||
|
||||
self.transport
|
||||
.send_to(
|
||||
&remote,
|
||||
&mut buf,
|
||||
&ServerNegotiationMessage::StartKex,
|
||||
)?;
|
||||
peer.signer.as_mut(),
|
||||
)
|
||||
.ok();
|
||||
Ok(MultiplexedSocketEvent::None(remote))
|
||||
}
|
||||
// Negotiation -> Connected
|
||||
(
|
||||
ClientNegotiationMessage::PublicKey(true, data),
|
||||
ServerPeerTransport::Negotiation(secret, _),
|
||||
ClientNegotiationMessage::DHPublicKey(true, data),
|
||||
PeerState::Negotiation(_, _),
|
||||
) if data.len() == 32 => {
|
||||
let public = PublicKey::from(&*secret);
|
||||
let PeerState::Negotiation(secret, cipher) = peer.state.take() else {
|
||||
unreachable!();
|
||||
};
|
||||
let public = PublicKey::from(&secret);
|
||||
let mut remote_key = [0; 32];
|
||||
remote_key.copy_from_slice(data);
|
||||
let remote_key = PublicKey::from(remote_key);
|
||||
match state.negotiate(&remote_key) {
|
||||
Ok(()) => {
|
||||
// Send public key to the client
|
||||
self.transport.send_to(
|
||||
&remote,
|
||||
&mut buf,
|
||||
&ServerNegotiationMessage::PublicKey(true, public.as_bytes()),
|
||||
)?;
|
||||
|
||||
Ok(MultiplexedSocketEvent::None(remote))
|
||||
}
|
||||
let shared = secret.diffie_hellman(&remote_key);
|
||||
let symmetric = match SymmetricCipher::new(cipher, shared.as_bytes()) {
|
||||
Ok(symmetric) => symmetric,
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
"Kicking {remote}: couldn't setup requested ciphersuite: {error}"
|
||||
);
|
||||
log::warn!("{remote}: couldn't setup symmetric cipher: {error}");
|
||||
self.remove_client(&remote);
|
||||
return Ok(MultiplexedSocketEvent::None(remote));
|
||||
}
|
||||
};
|
||||
self.transport
|
||||
.send_to(
|
||||
&remote,
|
||||
&ServerNegotiationMessage::DHPublicKey(true, public.as_bytes()),
|
||||
peer.signer.as_mut(),
|
||||
)
|
||||
.ok();
|
||||
peer.state = PeerState::Connected(symmetric);
|
||||
Ok(MultiplexedSocketEvent::None(remote))
|
||||
}
|
||||
}
|
||||
}
|
||||
(ClientNegotiationMessage::Agreed, ServerPeerTransport::Connected(_, _)) => {
|
||||
// Connected -> Connected
|
||||
(ClientNegotiationMessage::Agreed, PeerState::Connected(_)) => {
|
||||
log::debug!("{remote}: negotiated");
|
||||
self.transport
|
||||
.send_to(&remote, &mut buf, &ServerNegotiationMessage::Agreed)?;
|
||||
.send_to(
|
||||
&remote,
|
||||
&ServerNegotiationMessage::Agreed,
|
||||
peer.signer.as_mut(),
|
||||
)
|
||||
.ok();
|
||||
Ok(MultiplexedSocketEvent::None(remote))
|
||||
}
|
||||
(
|
||||
ClientNegotiationMessage::CipherText(data),
|
||||
ServerPeerTransport::Connected(cipher, _),
|
||||
) => {
|
||||
// Connected -> Connected
|
||||
(ClientNegotiationMessage::CipherText(data), PeerState::Connected(cipher)) => {
|
||||
let len = cipher.decrypt(data, buffer)?;
|
||||
Ok(MultiplexedSocketEvent::ClientData(remote, &buffer[..len]))
|
||||
}
|
||||
(ClientNegotiationMessage::Pong, ServerPeerTransport::Connected(_, missed)) => {
|
||||
*missed = 0;
|
||||
// Connected -> Connected
|
||||
(ClientNegotiationMessage::Pong, _) => {
|
||||
peer.missed_pings = 0;
|
||||
Ok(MultiplexedSocketEvent::None(remote))
|
||||
}
|
||||
// *** -> remove
|
||||
(ClientNegotiationMessage::Disconnect(_reason), _) => {
|
||||
log::debug!("{remote}: disconnected");
|
||||
self.peers.remove(&remote);
|
||||
@ -217,44 +299,105 @@ impl<S: PacketSocket> MultiplexedSocket for ServerEncryptedSocket<S> {
|
||||
let ClientNegotiationMessage::Hello { protocol: 1 } = message else {
|
||||
return Ok(MultiplexedSocketEvent::None(remote));
|
||||
};
|
||||
self.peers
|
||||
.insert(remote, ServerPeerTransport::PreNegotiation);
|
||||
let peer = ServerPeer {
|
||||
signer: None,
|
||||
verifier: None,
|
||||
state: PeerState::PreNegotiation,
|
||||
missed_pings: 0,
|
||||
};
|
||||
self.peers.insert(remote, peer);
|
||||
|
||||
// Reply with hello
|
||||
let symmetric_ciphersuites = (self.config.offer_ciphersuites)();
|
||||
let sig_algos = self.config.signature_keystore.offer_signature_algorithms();
|
||||
log::debug!("{remote}: offering ciphersuites:");
|
||||
for suite in symmetric_ciphersuites {
|
||||
log::debug!("* {:?}", ciphersuite_name(*suite));
|
||||
}
|
||||
log::debug!("{remote}: offering signature algorithms:");
|
||||
for sig in sig_algos {
|
||||
log::debug!("* {:?}", sig_algo_name(*sig));
|
||||
}
|
||||
let hello = ServerHello {
|
||||
kex_algos: &[],
|
||||
symmetric_ciphersuites,
|
||||
sig_algos,
|
||||
key_hash_algos: &[],
|
||||
};
|
||||
self.transport
|
||||
.send_to(&remote, &mut buf, &ServerNegotiationMessage::Hello(hello))?;
|
||||
.send_to(&remote, &ServerNegotiationMessage::Hello(hello), None)
|
||||
.ok();
|
||||
|
||||
Ok(MultiplexedSocketEvent::None(remote))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: PacketSocket> AsRawFd for ServerEncryptedSocket<S> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.transport.as_raw_fd()
|
||||
impl<S: PacketSocket> MultiplexedSocket for ServerEncryptedSocket<S> {
|
||||
fn send_to(&mut self, remote: &SocketAddr, data: &[u8]) -> Result<(), Error> {
|
||||
let peer = self.peers.get_mut(remote).ok_or(Error::NotConnected)?;
|
||||
let PeerState::Connected(cipher) = &mut peer.state else {
|
||||
return Err(Error::NotConnected)?;
|
||||
};
|
||||
let len = cipher.encrypt(data, &mut self.buffer)?;
|
||||
self.transport.send_to(
|
||||
remote,
|
||||
&ServerNegotiationMessage::CipherText(&self.buffer[..len]),
|
||||
peer.signer.as_mut(),
|
||||
)
|
||||
}
|
||||
|
||||
fn recv_from<'a>(&mut self, buffer: &'a mut [u8]) -> Result<MultiplexedSocketEvent<'a>, Error> {
|
||||
match self.recv_from_inner(buffer) {
|
||||
Ok(event) => Ok(event),
|
||||
Err(Error::Io(error)) => {
|
||||
log::error!("Fatal: {error}");
|
||||
Err(Error::Io(error))
|
||||
}
|
||||
Err(error) => {
|
||||
log::warn!("{error}");
|
||||
Ok(MultiplexedSocketEvent::Error(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerPeerTransport {
|
||||
fn negotiate(&mut self, public: &PublicKey) -> Result<(), Error> {
|
||||
let Self::Negotiation(secret, symmetric) =
|
||||
mem::replace(self, ServerPeerTransport::PreNegotiation)
|
||||
else {
|
||||
panic!();
|
||||
impl<S: PacketSocket> AsRawFd for ServerEncryptedSocket<S> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.transport.inner.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: PacketSocket> TransportWrapper<S> {
|
||||
fn send_to(
|
||||
&mut self,
|
||||
remote: &SocketAddr,
|
||||
message: &ServerNegotiationMessage,
|
||||
signer: Option<&mut SignatureMethod>,
|
||||
) -> Result<(), Error> {
|
||||
let mut encoder = Encoder::new(&mut self.send_buf);
|
||||
|
||||
message.encode(&mut encoder)?;
|
||||
|
||||
let len = match (message.needs_signature(), signer) {
|
||||
(false, _) => encoder.get().len(),
|
||||
(true, Some(signer)) => {
|
||||
let (message, signature) = encoder.split_mut();
|
||||
let signature_len = signer.sign(message, signature)?;
|
||||
message.len() + signature_len
|
||||
}
|
||||
(true, None) => unreachable!("{message:?} requires signature, but client has no signer"),
|
||||
};
|
||||
let shared = secret.diffie_hellman(public);
|
||||
let cipher = SymmetricCipher::new(symmetric, shared.as_bytes())?;
|
||||
// let aes = Aes256::new_from_slice(shared.as_bytes()).unwrap();
|
||||
*self = ServerPeerTransport::Connected(cipher, 0);
|
||||
|
||||
let payload = &self.send_buf[..len];
|
||||
|
||||
self.inner.send_to(payload, remote).map_err(Into::into)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerState {
|
||||
fn take(&mut self) -> Self {
|
||||
mem::replace(self, Self::None)
|
||||
}
|
||||
}
|
||||
|
177
userspace/rsh/src/crypt/signature.rs
Normal file
177
userspace/rsh/src/crypt/signature.rs
Normal file
@ -0,0 +1,177 @@
|
||||
use std::{borrow::Cow, fmt, io::Write, path::Path};
|
||||
|
||||
use ed25519_dalek::{ed25519::signature::SignerMut, pkcs8::DecodePrivateKey};
|
||||
use sha2::Digest;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
use super::V1_SIG_ED25519;
|
||||
|
||||
pub struct SignEd25519 {
|
||||
signing_key: ed25519_dalek::SigningKey,
|
||||
verifying_key: ed25519_dalek::VerifyingKey,
|
||||
}
|
||||
|
||||
pub struct VerifyEd25519 {
|
||||
verifying_key: ed25519_dalek::VerifyingKey,
|
||||
}
|
||||
|
||||
pub enum SignatureMethod {
|
||||
Ed25519(SignEd25519),
|
||||
}
|
||||
|
||||
pub enum VerificationMethod {
|
||||
Ed25519(VerifyEd25519),
|
||||
}
|
||||
|
||||
pub trait SignatureKeyStore {
|
||||
fn accept_public_key(&mut self, algorithm: u8, data: &[u8]) -> Option<VerificationMethod>;
|
||||
fn signer_for_algorithm(&mut self, algorithm: u8) -> Option<SignatureMethod>;
|
||||
|
||||
fn offer_signature_algorithms(&self) -> &[u8];
|
||||
|
||||
fn signer(&mut self, algorithms: &[u8]) -> Option<SignatureMethod> {
|
||||
for &algo in algorithms {
|
||||
if let Some(signer) = self.signer_for_algorithm(algo) {
|
||||
return Some(signer);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl VerifyEd25519 {
|
||||
pub fn from_slice(key: &[u8]) -> Result<Self, Error> {
|
||||
if key.len() != ed25519_dalek::PUBLIC_KEY_LENGTH {
|
||||
return Err(Error::InvalidKey);
|
||||
}
|
||||
let mut bytes = [0; 32];
|
||||
bytes.copy_from_slice(key);
|
||||
let verifying_key =
|
||||
ed25519_dalek::VerifyingKey::from_bytes(&bytes).map_err(|_| Error::InvalidKey)?;
|
||||
Ok(Self { verifying_key })
|
||||
}
|
||||
|
||||
pub fn verify(&mut self, message: &[u8], signature: &[u8]) -> Result<(), Error> {
|
||||
if signature.len() != ed25519_dalek::SIGNATURE_LENGTH {
|
||||
return Err(Error::InvalidSignatureSize(signature.len()));
|
||||
}
|
||||
let signature = ed25519_dalek::Signature::from_slice(signature).unwrap();
|
||||
self.verifying_key
|
||||
.verify_strict(message, &signature)
|
||||
.map_err(|_| Error::InvalidSignature)
|
||||
}
|
||||
|
||||
pub fn fingerprint(&self) -> String {
|
||||
fingerprint_sha256("ed25519", self.verifying_key.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl SignEd25519 {
|
||||
pub fn generate() -> Result<Self, Error> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let signing_key = ed25519_dalek::SigningKey::generate(&mut rng);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
Ok(Self {
|
||||
signing_key,
|
||||
verifying_key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_signing_key<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||
let signing_key = ed25519_dalek::SigningKey::read_pkcs8_pem_file(path).unwrap();
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
Ok(Self {
|
||||
signing_key,
|
||||
verifying_key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sign(&mut self, message: &[u8], buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
if buffer.len() < ed25519_dalek::SIGNATURE_LENGTH {
|
||||
return Err(Error::CannotFitSignature(
|
||||
buffer.len(),
|
||||
ed25519_dalek::SIGNATURE_LENGTH,
|
||||
));
|
||||
}
|
||||
let signature = self.signing_key.sign(message);
|
||||
buffer[..ed25519_dalek::SIGNATURE_LENGTH].copy_from_slice(&signature.to_bytes());
|
||||
Ok(ed25519_dalek::SIGNATURE_LENGTH)
|
||||
}
|
||||
|
||||
pub fn verifying_key_bytes(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(self.verifying_key.as_bytes())
|
||||
}
|
||||
|
||||
pub fn fingerprint(&self) -> String {
|
||||
fingerprint_sha256("ed25519", self.verifying_key.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl VerificationMethod {
|
||||
pub fn from_parts(algorithm: u8, data: &[u8]) -> Result<Self, Error> {
|
||||
match algorithm {
|
||||
V1_SIG_ED25519 => VerifyEd25519::from_slice(data).map(Self::Ed25519),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&mut self, message: &[u8], signature: &[u8]) -> Result<(), Error> {
|
||||
match self {
|
||||
Self::Ed25519(ed25519) => ed25519.verify(message, signature),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fingerprint(&self) -> String {
|
||||
match self {
|
||||
Self::Ed25519(ed25519) => ed25519.fingerprint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SignatureMethod {
|
||||
pub fn generate(algorithm: u8) -> Result<Self, Error> {
|
||||
match algorithm {
|
||||
V1_SIG_ED25519 => SignEd25519::generate().map(Self::Ed25519),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(&mut self, message: &[u8], signature: &mut [u8]) -> Result<usize, Error> {
|
||||
match self {
|
||||
Self::Ed25519(ed25519) => ed25519.sign(message, signature),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verifying_key_bytes(&self) -> Cow<[u8]> {
|
||||
match self {
|
||||
Self::Ed25519(ed25519) => ed25519.verifying_key_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn algorithm(&self) -> u8 {
|
||||
match self {
|
||||
Self::Ed25519(_) => V1_SIG_ED25519,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fingerprint(&self) -> String {
|
||||
match self {
|
||||
Self::Ed25519(ed25519) => ed25519.fingerprint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fingerprint_sha256(key_algo: impl fmt::Display, key_bytes: &[u8]) -> String {
|
||||
let mut digest = sha2::Sha256::new();
|
||||
digest.write_all(key_bytes).unwrap();
|
||||
let hash = digest.finalize();
|
||||
let mut result = format!("{} sha256 ", key_algo);
|
||||
for (i, &byte) in hash.iter().enumerate() {
|
||||
if i != 0 {
|
||||
result.push(':');
|
||||
}
|
||||
result.push_str(&format!("{byte:02x}"));
|
||||
}
|
||||
result
|
||||
}
|
@ -32,12 +32,26 @@ pub enum Error {
|
||||
Disconnected,
|
||||
#[error("Message too large: buffer size {0}, message size {1}")]
|
||||
MessageTooLarge(usize, usize),
|
||||
#[error("Cannot sign message: buffer size {0}, signature size {1}")]
|
||||
CannotFitSignature(usize, usize),
|
||||
#[error("Malformed ciphertext")]
|
||||
InvalidCiphertext,
|
||||
#[error("Message signature mismatch")]
|
||||
InvalidSignature,
|
||||
#[error("Invalid signature size ({0})")]
|
||||
InvalidSignatureSize(usize),
|
||||
#[error("Malformed encryption key")]
|
||||
InvalidKey,
|
||||
#[error("Communication timed out")]
|
||||
Timeout,
|
||||
#[error("Cannot accept any of the offered ciphersuites")]
|
||||
UnacceptableCiphersuites,
|
||||
#[error("Cannot accept any of the offered signature methods")]
|
||||
UnacceptableSignatureMethods,
|
||||
#[error("No verifying key for remote signature method")]
|
||||
NoVerifyingKey,
|
||||
#[error("Message is not signed")]
|
||||
MissingSignature,
|
||||
#[error("Rejected remote public key")]
|
||||
PublicKeyRejected,
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))]
|
||||
|
||||
use std::{
|
||||
io::{self, stdin, stdout, Read, Stdin, Stdout, Write as IoWrite},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket},
|
||||
os::fd::AsRawFd,
|
||||
process::ExitCode,
|
||||
time::{Duration, Instant},
|
||||
io::{self, stdin, stdout, Read, Stdin, Stdout, Write as IoWrite}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket}, os::fd::AsRawFd, path::PathBuf, process::ExitCode, time::{Duration, Instant}
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use cross::io::Poll;
|
||||
use libterm::{RawMode, RawTerminal};
|
||||
use rsh::{
|
||||
crypt::ClientEncryptedSocket, proto::{ClientMessage, ServerMessage, TerminalInfo}, socket::{MessageSocket, PacketSocket}, ClientSocket
|
||||
crypt::{client::ClientConfig, signature::{SignEd25519, SignatureMethod}, ClientEncryptedSocket, SimpleClientKeyStore}, proto::{ClientMessage, ServerMessage, TerminalInfo}, socket::{MessageSocket, PacketSocket}, ClientSocket
|
||||
};
|
||||
|
||||
pub const PING_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
@ -33,6 +29,8 @@ pub enum Error {
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
#[clap(short, long)]
|
||||
key: PathBuf,
|
||||
remote: IpAddr,
|
||||
}
|
||||
|
||||
@ -52,7 +50,7 @@ pub enum Event<'b> {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn connect(remote: SocketAddr) -> Result<Self, Error> {
|
||||
pub fn connect(remote: SocketAddr, crypto_config: ClientConfig) -> Result<Self, Error> {
|
||||
let mut poll = Poll::new()?;
|
||||
let stdin = stdin();
|
||||
let stdout = stdout();
|
||||
@ -66,7 +64,7 @@ impl Client {
|
||||
let socket = UdpSocket::bind(local)?;
|
||||
socket.connect(remote)?;
|
||||
|
||||
let mut socket = ClientEncryptedSocket::new(socket);
|
||||
let mut socket = ClientEncryptedSocket::new_with_config(socket, crypto_config);
|
||||
|
||||
poll.add(&socket)?;
|
||||
|
||||
@ -225,8 +223,14 @@ fn terminal_info(stdout: &Stdout) -> Result<TerminalInfo, Error> {
|
||||
}
|
||||
|
||||
fn run(args: Args) -> Result<(), Error> {
|
||||
let remote = SocketAddr::new(args.remote, 7777);
|
||||
let reason = Client::connect(remote)?.run()?;
|
||||
let remote = SocketAddr::new(args.remote, 77);
|
||||
let ed25519 = SignEd25519::load_signing_key(args.key).unwrap();
|
||||
let key = SignatureMethod::Ed25519(ed25519);
|
||||
let config = ClientConfig {
|
||||
signature_keystore: Box::new(SimpleClientKeyStore::new(key)),
|
||||
..Default::default()
|
||||
};
|
||||
let reason = Client::connect(remote, config)?.run()?;
|
||||
if !reason.is_empty() {
|
||||
eprintln!("\nDisconnected: {reason}");
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ impl<'a> Encoder<'a> {
|
||||
}
|
||||
|
||||
pub fn write_variable_bytes(&mut self, bytes: &[u8]) -> Result<(), EncodeError> {
|
||||
let len: u32 = bytes
|
||||
let len: u16 = bytes
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(|_| EncodeError::ValueTooLong)?;
|
||||
@ -95,6 +95,10 @@ impl<'a> Encoder<'a> {
|
||||
pub fn get(&self) -> &[u8] {
|
||||
&self.buffer[..self.pos]
|
||||
}
|
||||
|
||||
pub fn split_mut(&mut self) -> (&mut [u8], &mut [u8]) {
|
||||
self.buffer.split_at_mut(self.pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decoder<'a> {
|
||||
@ -111,6 +115,11 @@ impl<'a> Decoder<'a> {
|
||||
Ok(byte)
|
||||
}
|
||||
|
||||
pub fn read_le_u16(&mut self) -> Result<u16, DecodeError> {
|
||||
let bytes = self.read_bytes(size_of::<u16>())?;
|
||||
Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
|
||||
}
|
||||
|
||||
pub fn read_le_u32(&mut self) -> Result<u32, DecodeError> {
|
||||
let bytes = self.read_bytes(size_of::<u32>())?;
|
||||
Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
|
||||
@ -126,7 +135,7 @@ impl<'a> Decoder<'a> {
|
||||
}
|
||||
|
||||
pub fn read_variable_bytes(&mut self) -> Result<&'a [u8], DecodeError> {
|
||||
let len = self.read_le_u32()?;
|
||||
let len = self.read_le_u16()?;
|
||||
self.read_bytes(len as usize)
|
||||
}
|
||||
|
||||
@ -134,6 +143,10 @@ impl<'a> Decoder<'a> {
|
||||
let slice = self.read_variable_bytes()?;
|
||||
core::str::from_utf8(slice).map_err(DecodeError::InvalidString)
|
||||
}
|
||||
|
||||
pub fn split(&self) -> (&'a [u8], &'a [u8]) {
|
||||
self.buffer.split_at(self.pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageProxy for ClientMessageProxy {
|
||||
|
@ -1,19 +1,13 @@
|
||||
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
|
||||
#![feature(if_let_guard)]
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
net::{SocketAddr, UdpSocket},
|
||||
os::fd::{AsRawFd, FromRawFd, RawFd},
|
||||
process::{Child, Command, ExitCode, Stdio},
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
collections::{HashMap, HashSet}, fs::File, io::{Read, Write}, net::{SocketAddr, UdpSocket}, os::fd::{AsRawFd, FromRawFd, RawFd}, path::PathBuf, process::{Child, Command, ExitCode, Stdio}, str::FromStr, time::Duration
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use cross::io::{Poll, TimerFd};
|
||||
use rsh::{
|
||||
crypt::ServerEncryptedSocket,
|
||||
crypt::{server::ServerConfig, ServerEncryptedSocket, SimpleServerKeyStore},
|
||||
proto::{ClientMessage, Decode, Decoder, ServerMessage, TerminalInfo},
|
||||
socket::{MultiplexedSocket, MultiplexedSocketEvent},
|
||||
Error,
|
||||
@ -21,6 +15,14 @@ use rsh::{
|
||||
|
||||
pub const PING_INTERVAL: Duration = Duration::from_millis(500);
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
struct Args {
|
||||
#[clap(short = 'P', long, help = "rsh listen port", default_value_t = 77)]
|
||||
port: u16,
|
||||
#[clap(short = 'S', long, help = "where rsh will load private keys from", default_value = "/etc/rsh")]
|
||||
keystore: PathBuf
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
pty_master: File,
|
||||
remote: SocketAddr,
|
||||
@ -95,10 +97,11 @@ impl Session {
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(listen_addr: SocketAddr) -> Result<Self, Error> {
|
||||
pub fn new(listen_addr: SocketAddr, crypto_config: ServerConfig) -> Result<Self, Error> {
|
||||
let mut poll = Poll::new()?;
|
||||
let timer = TimerFd::new()?;
|
||||
let socket = UdpSocket::bind(listen_addr).map(ServerEncryptedSocket::new)?;
|
||||
let socket = UdpSocket::bind(listen_addr)?;
|
||||
let socket = ServerEncryptedSocket::new_with_config(socket, crypto_config);
|
||||
poll.add(&socket)?;
|
||||
poll.add(&timer)?;
|
||||
Ok(Self {
|
||||
@ -132,6 +135,7 @@ impl Server {
|
||||
let message = ClientMessage::decode(&mut decoder);
|
||||
(message, peer)
|
||||
}
|
||||
MultiplexedSocketEvent::Error(_) => return Ok(None),
|
||||
};
|
||||
|
||||
let message = match message {
|
||||
@ -187,7 +191,6 @@ impl Server {
|
||||
let session = self.pty_to_session.get_mut(&fd).unwrap();
|
||||
if let Err(error) = session.pty_master.write(&data) {
|
||||
eprintln!("PTY write error: {error}");
|
||||
self.remove_session_by_fd(fd)?;
|
||||
self.socket
|
||||
.send_message_to(
|
||||
&remote,
|
||||
@ -195,6 +198,7 @@ impl Server {
|
||||
&ServerMessage::Bye("PTY error"),
|
||||
)
|
||||
.ok();
|
||||
self.remove_session_by_fd(fd)?;
|
||||
}
|
||||
}
|
||||
Event::ClientBye(remote, reason) => {
|
||||
@ -219,6 +223,7 @@ impl Server {
|
||||
&ServerMessage::Bye("PTY open error"),
|
||||
)
|
||||
.ok();
|
||||
self.socket.remove_client(&remote);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -230,7 +235,6 @@ impl Server {
|
||||
}
|
||||
PtyEvent::Err(error) => {
|
||||
eprintln!("PTY read error: {error}");
|
||||
self.remove_session_by_fd(fd)?;
|
||||
self.socket
|
||||
.send_message_to(
|
||||
&remote,
|
||||
@ -238,13 +242,14 @@ impl Server {
|
||||
&ServerMessage::Bye("PTY error"),
|
||||
)
|
||||
.ok();
|
||||
self.remove_session_by_fd(fd)?;
|
||||
}
|
||||
PtyEvent::Closed => {
|
||||
println!("End of PTY for {remote}");
|
||||
self.remove_session_by_fd(fd)?;
|
||||
self.socket
|
||||
.send_message_to(&remote, &mut send_buf, &ServerMessage::Bye(""))
|
||||
.ok();
|
||||
self.remove_session_by_fd(fd)?;
|
||||
}
|
||||
},
|
||||
Event::Tick => {
|
||||
@ -259,6 +264,7 @@ impl Server {
|
||||
fn update_client_timeouts(&mut self) -> Result<(), Error> {
|
||||
let removed = self.socket.ping_clients(8);
|
||||
for entry in removed {
|
||||
log::debug!("Client timed out: {entry}");
|
||||
self.remove_session_by_remote(entry).ok();
|
||||
}
|
||||
Ok(())
|
||||
@ -294,8 +300,19 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), Error> {
|
||||
let server = Server::new(SocketAddr::from_str("0.0.0.0:7777").unwrap())?;
|
||||
fn run(args: Args) -> Result<(), Error> {
|
||||
let keystore = Box::new(SimpleServerKeyStore {
|
||||
path: args.keystore,
|
||||
accepted_keys: HashSet::from_iter([
|
||||
"ed25519 sha256 63:f4:df:41:c3:ec:a7:7c:ea:f1:65:22:91:8b:14:60:8f:ec:cd:19:90:2e:12:33:66:e3:33:24:96:3d:63:c3".into()
|
||||
]),
|
||||
});
|
||||
let server_config = ServerConfig {
|
||||
signature_keystore: keystore,
|
||||
..Default::default()
|
||||
};
|
||||
let listen_addr = SocketAddr::from_str(&format!("0.0.0.0:{}", args.port)).unwrap();
|
||||
let server = Server::new(listen_addr, server_config)?;
|
||||
server.run()
|
||||
}
|
||||
|
||||
@ -304,7 +321,8 @@ fn main() -> ExitCode {
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.format_timestamp(None)
|
||||
.init();
|
||||
if let Err(error) = run() {
|
||||
let args = Args::parse();
|
||||
if let Err(error) = run(args) {
|
||||
eprintln!("Finished with error: {error}");
|
||||
ExitCode::FAILURE
|
||||
} else {
|
||||
|
@ -44,6 +44,7 @@ pub enum MultiplexedSocketEvent<'a> {
|
||||
ClientData(SocketAddr, &'a [u8]),
|
||||
ClientDisconnected(SocketAddr),
|
||||
None(SocketAddr),
|
||||
Error(Error)
|
||||
}
|
||||
|
||||
pub trait MultiplexedSocket: AsRawFd {
|
||||
|
@ -15,10 +15,10 @@ thiserror.workspace = true
|
||||
clap.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
# TODO own impl
|
||||
humansize = { version = "2.1.3", features = ["impl_style"] }
|
||||
sha2 = { version = "0.10.8" }
|
||||
|
||||
init = { path = "../init" }
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user