rsh: implement signature verification

This commit is contained in:
Mark Poliakov 2024-11-02 19:17:32 +02:00
parent 80e6658f55
commit f0fdeb1004
14 changed files with 906 additions and 213 deletions

2
userspace/Cargo.lock generated
View File

@ -1167,10 +1167,12 @@ dependencies = [
"bytemuck",
"clap",
"cross",
"ed25519-dalek",
"env_logger",
"libterm",
"log",
"rand 0.8.5",
"sha2",
"thiserror",
"x25519-dalek",
]

View File

@ -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" }

View File

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIO6qKZCX08BZ6LQV1DJYP0ONRXWaj6qSXwSc2mtcrl9I
-----END PRIVATE KEY-----

View File

@ -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"

View File

@ -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,31 +129,76 @@ 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)) => {
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
None => {
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;
let ciphersuite = match (self.config.select_ciphersuite)(info.symmetric_ciphersuites) {
// Picked a ciphersuite
Some(ciphersuite) => ciphersuite,
// Server didn't offer anything acceptable
None => {
return Err(Error::UnacceptableCiphersuites);
}
};
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)
}
}

View File

@ -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(())
}
}
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,
})
pub fn needs_signature(&self) -> bool {
match self {
Self::Hello(_)
| Self::Reject(_)
| Self::Kick(_)
| Self::Ping
| Self::SigPublicKey(_, _) => false,
_ => true,
}
}
}
@ -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(())
// }
// }

View File

@ -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 {
removed.push(*remote);
}
*missed += 1;
}
_ => (),
if peer.missed_pings >= limit {
removed.push(*remote);
}
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!()
}
}
}
Ok((message, remote))
}
fn recv_from<'a>(&mut self, buffer: &'a mut [u8]) -> Result<MultiplexedSocketEvent<'a>, Error> {
let (message, remote) = self.transport.recv_from(&mut self.buffer)?;
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)?;
let mut buf = [0; 256];
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(
&remote,
&mut buf,
&ServerNegotiationMessage::StartKex,
)?;
peer.state = PeerState::Negotiation(secret, ciphersuite);
self.transport
.send_to(
&remote,
&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);
Ok(MultiplexedSocketEvent::None(remote))
return Ok(MultiplexedSocketEvent::None(remote));
}
}
}
(ClientNegotiationMessage::Agreed, ServerPeerTransport::Connected(_, _)) => {
log::debug!("{remote}: negotiated");
};
self.transport
.send_to(&remote, &mut buf, &ServerNegotiationMessage::Agreed)?;
.send_to(
&remote,
&ServerNegotiationMessage::DHPublicKey(true, public.as_bytes()),
peer.signer.as_mut(),
)
.ok();
peer.state = PeerState::Connected(symmetric);
Ok(MultiplexedSocketEvent::None(remote))
}
(
ClientNegotiationMessage::CipherText(data),
ServerPeerTransport::Connected(cipher, _),
) => {
// Connected -> Connected
(ClientNegotiationMessage::Agreed, PeerState::Connected(_)) => {
log::debug!("{remote}: negotiated");
self.transport
.send_to(
&remote,
&ServerNegotiationMessage::Agreed,
peer.signer.as_mut(),
)
.ok();
Ok(MultiplexedSocketEvent::None(remote))
}
// 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)
}
}

View 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
}

View File

@ -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,
}

View File

@ -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}");
}

View File

@ -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 {

View File

@ -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 {

View File

@ -44,6 +44,7 @@ pub enum MultiplexedSocketEvent<'a> {
ClientData(SocketAddr, &'a [u8]),
ClientDisconnected(SocketAddr),
None(SocketAddr),
Error(Error)
}
pub trait MultiplexedSocket: AsRawFd {

View File

@ -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" }