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",
|
"bytemuck",
|
||||||
"clap",
|
"clap",
|
||||||
"cross",
|
"cross",
|
||||||
|
"ed25519-dalek",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"libterm",
|
"libterm",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"sha2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
]
|
]
|
||||||
|
@ -29,6 +29,7 @@ serde = { version = "1.0.214", features = ["derive"] }
|
|||||||
bytemuck = "1.19.0"
|
bytemuck = "1.19.0"
|
||||||
thiserror = "1.0.64"
|
thiserror = "1.0.64"
|
||||||
flexbuffers = "2.0.0"
|
flexbuffers = "2.0.0"
|
||||||
|
sha2 = { version = "0.10.8" }
|
||||||
|
|
||||||
# Vendored/patched dependencies
|
# Vendored/patched dependencies
|
||||||
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil" }
|
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
|
thiserror.workspace = true
|
||||||
bytemuck.workspace = true
|
bytemuck.workspace = true
|
||||||
x25519-dalek.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" }
|
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil-rng_core-0.6.4" }
|
||||||
aes = { version = "0.8.4" }
|
aes = { version = "0.8.4" }
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
|
@ -8,39 +8,53 @@ use cross::io::Poll;
|
|||||||
use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret};
|
use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
crypt::{ClientNegotiationMessage, ServerNegotiationMessage, V1_CIPHER_AES_256_CBC, V1_KEX_X25519_DALEK},
|
crypt::{
|
||||||
socket::{MessageSocket, PacketSocket, SocketWrapper},
|
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,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
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 struct ClientConfig {
|
||||||
|
pub signature_keystore: Box<dyn SignatureKeyStore>,
|
||||||
pub select_ciphersuite: fn(&[u8]) -> Option<u8>,
|
pub select_ciphersuite: fn(&[u8]) -> Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ClientState {
|
enum ClientState {
|
||||||
PreNegotioation,
|
PreNegotioation,
|
||||||
|
FingerprintSent(u8),
|
||||||
StartKexSent(u8, u8),
|
StartKexSent(u8, u8),
|
||||||
ClientKeySent(u8, EphemeralSecret),
|
ClientKeySent(u8, EphemeralSecret),
|
||||||
ServerKeyReceived(u8, SharedSecret),
|
ServerKeyReceived(u8, SharedSecret),
|
||||||
Connected(SymmetricCipher),
|
Connected(SymmetricCipher),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TransportWrapper<S: PacketSocket> {
|
||||||
|
inner: S,
|
||||||
|
send_buf: [u8; 256],
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ClientEncryptedSocket<S: PacketSocket> {
|
pub struct ClientEncryptedSocket<S: PacketSocket> {
|
||||||
transport: SocketWrapper<S, ServerMessageProxy, ClientMessageProxy>,
|
transport: TransportWrapper<S>,
|
||||||
state: Option<ClientState>,
|
state: Option<ClientState>,
|
||||||
last_ping: Instant,
|
last_ping: Instant,
|
||||||
config: ClientConfig,
|
config: ClientConfig,
|
||||||
|
signer: Option<SignatureMethod>,
|
||||||
|
verifier: Option<VerificationMethod>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_ciphersuite_default(offered: &[u8]) -> Option<u8> {
|
fn select_ciphersuite_default(offered: &[u8]) -> Option<u8> {
|
||||||
// List of default accepted ciphers, ordered descending by their priority
|
// List of default accepted ciphers, ordered descending by their priority
|
||||||
const ACCEPTED: &[u8] = &[
|
const ACCEPTED: &[u8] = &[
|
||||||
V1_CIPHER_AES_256_CBC,
|
V1_CIPHER_AES_256_CBC,
|
||||||
V1_CIPHER_AES_256_ECB,
|
V1_CIPHER_AES_256_ECB
|
||||||
];
|
];
|
||||||
|
|
||||||
for cipher in ACCEPTED {
|
for cipher in ACCEPTED {
|
||||||
@ -54,7 +68,8 @@ fn select_ciphersuite_default(offered: &[u8]) -> Option<u8> {
|
|||||||
impl Default for ClientConfig {
|
impl Default for ClientConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
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 {
|
pub fn new_with_config(transport: S, config: ClientConfig) -> Self {
|
||||||
Self {
|
Self {
|
||||||
transport: SocketWrapper::new(transport),
|
transport: TransportWrapper {
|
||||||
|
inner: transport,
|
||||||
|
send_buf: [0; 256],
|
||||||
|
},
|
||||||
state: None,
|
state: None,
|
||||||
last_ping: Instant::now(),
|
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> {
|
fn do_handshake_sequence(&mut self, poll: &mut Poll, timeout: Duration) -> Result<(), Error> {
|
||||||
let mut recv_buf = [0; 256];
|
self.transport
|
||||||
|
.send(&ClientNegotiationMessage::Hello { protocol: 1 }, None)?;
|
||||||
self.transport.send(
|
|
||||||
&mut recv_buf,
|
|
||||||
&ClientNegotiationMessage::Hello { protocol: 1 },
|
|
||||||
)?;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let Some(fd) = poll.wait(Some(timeout))? else {
|
let Some(fd) = poll.wait(Some(timeout))? else {
|
||||||
return Err(Error::Timeout);
|
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()? {
|
||||||
if self.update_handshake_sequence(&message)? {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,31 +129,76 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_handshake_sequence(
|
fn update_handshake_sequence(&mut self) -> Result<bool, Error> {
|
||||||
&mut self,
|
let mut recv_buf = [0; 256];
|
||||||
message: &ServerNegotiationMessage,
|
let message = self.transport.recv(&mut recv_buf, self.verifier.as_mut())?;
|
||||||
) -> Result<bool, Error> {
|
|
||||||
let mut send_buf = [0; 256];
|
|
||||||
match (message, self.state.take()) {
|
match (message, self.state.take()) {
|
||||||
// PreNegotioation -> StartKexSent
|
// PreNegotioation -> FingerprintSent
|
||||||
(ServerNegotiationMessage::Hello(info), Some(ClientState::PreNegotioation)) => {
|
(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
|
// TODO kex algorithm selection
|
||||||
let kex_algo = V1_KEX_X25519_DALEK;
|
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(
|
self.transport.send(
|
||||||
&mut send_buf,
|
|
||||||
&ClientNegotiationMessage::StartKex {
|
&ClientNegotiationMessage::StartKex {
|
||||||
kex_algo,
|
kex_algo,
|
||||||
ciphersuite,
|
ciphersuite,
|
||||||
},
|
},
|
||||||
|
self.signer.as_mut(),
|
||||||
)?;
|
)?;
|
||||||
|
self.verifier = Some(verifier);
|
||||||
self.state = Some(ClientState::StartKexSent(kex_algo, ciphersuite));
|
self.state = Some(ClientState::StartKexSent(kex_algo, ciphersuite));
|
||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
@ -154,8 +214,8 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
|||||||
|
|
||||||
assert!(public.as_bytes().len() < 128);
|
assert!(public.as_bytes().len() < 128);
|
||||||
self.transport.send(
|
self.transport.send(
|
||||||
&mut send_buf,
|
&ClientNegotiationMessage::DHPublicKey(true, public.as_bytes()),
|
||||||
&ClientNegotiationMessage::PublicKey(true, public.as_bytes()),
|
self.signer.as_mut(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.state = Some(ClientState::ClientKeySent(ciphersuite, secret));
|
self.state = Some(ClientState::ClientKeySent(ciphersuite, secret));
|
||||||
@ -163,7 +223,7 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
|||||||
}
|
}
|
||||||
// ClientKeySent -> ServerKeyReceived
|
// ClientKeySent -> ServerKeyReceived
|
||||||
(
|
(
|
||||||
ServerNegotiationMessage::PublicKey(true, key_data),
|
ServerNegotiationMessage::DHPublicKey(true, key_data),
|
||||||
Some(ClientState::ClientKeySent(ciphersuite, secret)),
|
Some(ClientState::ClientKeySent(ciphersuite, secret)),
|
||||||
) if key_data.len() == 32 => {
|
) if key_data.len() == 32 => {
|
||||||
let mut public = [0; 32];
|
let mut public = [0; 32];
|
||||||
@ -172,7 +232,7 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
|||||||
let shared = secret.diffie_hellman(&public);
|
let shared = secret.diffie_hellman(&public);
|
||||||
|
|
||||||
self.transport
|
self.transport
|
||||||
.send(&mut send_buf, &ClientNegotiationMessage::Agreed)?;
|
.send(&ClientNegotiationMessage::Agreed, self.signer.as_mut())?;
|
||||||
self.state = Some(ClientState::ServerKeyReceived(ciphersuite, shared));
|
self.state = Some(ClientState::ServerKeyReceived(ciphersuite, shared));
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
@ -187,26 +247,42 @@ impl<S: PacketSocket> ClientEncryptedSocket<S> {
|
|||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.transport
|
self.transport
|
||||||
.send(&mut send_buf, &ClientNegotiationMessage::Disconnect(0))
|
.send(
|
||||||
|
&ClientNegotiationMessage::Disconnect(0),
|
||||||
|
self.signer.as_mut(),
|
||||||
|
)
|
||||||
.ok();
|
.ok();
|
||||||
self.state = None;
|
self.state = None;
|
||||||
Err(error)
|
Err(error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// *** -> ***
|
||||||
|
(ServerNegotiationMessage::Ping, _) => {
|
||||||
|
self.transport.send(&ClientNegotiationMessage::Pong, None).ok();
|
||||||
|
self.last_ping = Instant::now();
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
// *** -> None
|
// *** -> None
|
||||||
(ServerNegotiationMessage::Kick(_reason), _) => {
|
(ServerNegotiationMessage::Kick(_reason), _) => {
|
||||||
// No need to send any disconnects, server already forgot us
|
// No need to send any disconnects, server already forgot us
|
||||||
self.state = None;
|
self.reset();
|
||||||
Err(Error::Disconnected)
|
Err(Error::Disconnected)
|
||||||
}
|
}
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
self.transport
|
self.transport
|
||||||
.send(&mut send_buf, &ClientNegotiationMessage::Disconnect(0))
|
.send(&ClientNegotiationMessage::Disconnect(0), None)
|
||||||
.ok();
|
.ok();
|
||||||
self.state = None;
|
self.reset();
|
||||||
Err(Error::Disconnected)
|
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 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 {
|
match message {
|
||||||
ServerNegotiationMessage::CipherText(ciphertext) => {
|
ServerNegotiationMessage::CipherText(ciphertext) => {
|
||||||
let len = cipher.decrypt(ciphertext, output)?;
|
let len = cipher.decrypt(ciphertext, output)?;
|
||||||
@ -227,7 +305,7 @@ impl<S: PacketSocket> PacketSocket for ClientEncryptedSocket<S> {
|
|||||||
}
|
}
|
||||||
ServerNegotiationMessage::Ping => {
|
ServerNegotiationMessage::Ping => {
|
||||||
self.transport
|
self.transport
|
||||||
.send(&mut buffer, &ClientNegotiationMessage::Pong)?;
|
.send(&ClientNegotiationMessage::Pong, self.signer.as_mut())?;
|
||||||
self.last_ping = Instant::now();
|
self.last_ping = Instant::now();
|
||||||
// TODO this is a workaround
|
// TODO this is a workaround
|
||||||
Err(Error::Ping)
|
Err(Error::Ping)
|
||||||
@ -246,11 +324,10 @@ impl<S: PacketSocket> PacketSocket for ClientEncryptedSocket<S> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut buffer = [0; 256];
|
let mut buffer = [0; 256];
|
||||||
let mut send_buffer = [0; 256];
|
|
||||||
let len = cipher.encrypt(data, &mut buffer)?;
|
let len = cipher.encrypt(data, &mut buffer)?;
|
||||||
self.transport.send(
|
self.transport.send(
|
||||||
&mut send_buffer,
|
|
||||||
&ClientNegotiationMessage::CipherText(&buffer[..len]),
|
&ClientNegotiationMessage::CipherText(&buffer[..len]),
|
||||||
|
self.signer.as_mut(),
|
||||||
)?;
|
)?;
|
||||||
Ok(data.len())
|
Ok(data.len())
|
||||||
}
|
}
|
||||||
@ -259,9 +336,8 @@ impl<S: PacketSocket> PacketSocket for ClientEncryptedSocket<S> {
|
|||||||
impl<S: PacketSocket> Drop for ClientEncryptedSocket<S> {
|
impl<S: PacketSocket> Drop for ClientEncryptedSocket<S> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.state.is_some() {
|
if self.state.is_some() {
|
||||||
let mut buffer = [0; 32];
|
|
||||||
self.transport
|
self.transport
|
||||||
.send(&mut buffer, &ClientNegotiationMessage::Disconnect(0))
|
.send(&ClientNegotiationMessage::Disconnect(0), None)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,6 +345,64 @@ impl<S: PacketSocket> Drop for ClientEncryptedSocket<S> {
|
|||||||
|
|
||||||
impl<S: PacketSocket> AsRawFd for ClientEncryptedSocket<S> {
|
impl<S: PacketSocket> AsRawFd for ClientEncryptedSocket<S> {
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
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};
|
use crate::proto::{Decode, DecodeError, Decoder, Encode, EncodeError, Encoder, MessageProxy};
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
pub mod signature;
|
||||||
pub mod symmetric;
|
pub mod symmetric;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
pub use client::ClientEncryptedSocket;
|
pub use client::ClientEncryptedSocket;
|
||||||
pub use server::ServerEncryptedSocket;
|
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_ECB: u8 = 0x10;
|
||||||
pub const V1_CIPHER_AES_256_CBC: u8 = 0x11;
|
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
|
// v1 supports only one DH algo
|
||||||
pub const V1_KEX_X25519_DALEK: u8 = 0x10;
|
pub const V1_KEX_X25519_DALEK: u8 = 0x10;
|
||||||
|
|
||||||
|
pub const V1_SIG_ED25519: u8 = 0x10;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ServerReject {
|
pub enum ServerReject {
|
||||||
NoReason,
|
NoReason,
|
||||||
UnsupportedKexAlgo,
|
UnsupportedKexAlgo,
|
||||||
@ -22,28 +31,35 @@ pub enum ServerReject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// v1 protocol
|
// v1 protocol
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ServerHello<'a> {
|
pub struct ServerHello<'a> {
|
||||||
// TODO server will send its public fingerprint and advertise
|
// TODO server will send its public fingerprint and advertise
|
||||||
// supported asymmetric key formats
|
// supported asymmetric key formats
|
||||||
pub symmetric_ciphersuites: &'a [u8],
|
pub symmetric_ciphersuites: &'a [u8],
|
||||||
pub kex_algos: &'a [u8],
|
pub kex_algos: &'a [u8],
|
||||||
|
pub sig_algos: &'a [u8],
|
||||||
|
pub key_hash_algos: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ClientNegotiationMessage<'a> {
|
pub enum ClientNegotiationMessage<'a> {
|
||||||
Hello { protocol: u8 },
|
Hello { protocol: u8 },
|
||||||
|
SigPublicKey(u8, &'a [u8]),
|
||||||
StartKex { kex_algo: u8, ciphersuite: u8 },
|
StartKex { kex_algo: u8, ciphersuite: u8 },
|
||||||
PublicKey(bool, &'a [u8]),
|
DHPublicKey(bool, &'a [u8]),
|
||||||
Agreed,
|
Agreed,
|
||||||
Pong,
|
Pong,
|
||||||
CipherText(&'a [u8]),
|
CipherText(&'a [u8]),
|
||||||
Disconnect(u8),
|
Disconnect(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ServerNegotiationMessage<'a> {
|
pub enum ServerNegotiationMessage<'a> {
|
||||||
Hello(ServerHello<'a>),
|
Hello(ServerHello<'a>),
|
||||||
|
SigPublicKey(u8, &'a [u8]),
|
||||||
Reject(ServerReject),
|
Reject(ServerReject),
|
||||||
StartKex,
|
StartKex,
|
||||||
PublicKey(bool, &'a [u8]),
|
DHPublicKey(bool, &'a [u8]),
|
||||||
Agreed,
|
Agreed,
|
||||||
Kick(u8),
|
Kick(u8),
|
||||||
Ping,
|
Ping,
|
||||||
@ -53,6 +69,89 @@ pub enum ServerNegotiationMessage<'a> {
|
|||||||
pub struct ClientMessageProxy;
|
pub struct ClientMessageProxy;
|
||||||
pub struct ServerMessageProxy;
|
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 {
|
impl MessageProxy for ClientMessageProxy {
|
||||||
type Type<'de> = ClientNegotiationMessage<'de>;
|
type Type<'de> = ClientNegotiationMessage<'de>;
|
||||||
}
|
}
|
||||||
@ -60,27 +159,66 @@ impl MessageProxy for ServerMessageProxy {
|
|||||||
type Type<'de> = ServerNegotiationMessage<'de>;
|
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<'_> {
|
impl ClientNegotiationMessage<'_> {
|
||||||
const TAG_HELLO: u8 = 0x90;
|
const TAG_HELLO: u8 = 0x90;
|
||||||
const TAG_START_KEX: u8 = 0x91;
|
const TAG_SIG_PUBLIC_KEY: u8 = 0x91;
|
||||||
const TAG_PUBLIC_KEY: u8 = 0x92;
|
const TAG_START_KEX: u8 = 0x92;
|
||||||
const TAG_AGREED: u8 = 0x93;
|
const TAG_DH_PUBLIC_KEY: u8 = 0x93;
|
||||||
|
const TAG_AGREED: u8 = 0x94;
|
||||||
|
|
||||||
const TAG_CIPHERTEXT: u8 = 0xA0;
|
const TAG_CIPHERTEXT: u8 = 0xA0;
|
||||||
const TAG_DISCONNECT: u8 = 0xA1;
|
const TAG_DISCONNECT: u8 = 0xA1;
|
||||||
const TAG_PONG: u8 = 0xA2;
|
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<'_> {
|
impl Encode for ClientNegotiationMessage<'_> {
|
||||||
fn encode(&self, buffer: &mut Encoder) -> Result<(), EncodeError> {
|
fn encode(&self, buffer: &mut Encoder) -> Result<(), EncodeError> {
|
||||||
match self {
|
match self {
|
||||||
&Self::Hello { protocol } => buffer.write(&[Self::TAG_HELLO, protocol]),
|
&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 {
|
&Self::StartKex {
|
||||||
kex_algo,
|
kex_algo,
|
||||||
ciphersuite,
|
ciphersuite,
|
||||||
} => buffer.write(&[Self::TAG_START_KEX, kex_algo, ciphersuite]),
|
} => buffer.write(&[Self::TAG_START_KEX, kex_algo, ciphersuite]),
|
||||||
Self::PublicKey(end, data) => {
|
Self::DHPublicKey(end, data) => {
|
||||||
buffer.write(&[Self::TAG_PUBLIC_KEY, *end as u8])?;
|
buffer.write(&[Self::TAG_DH_PUBLIC_KEY, *end as u8])?;
|
||||||
buffer.write_variable_bytes(data)
|
buffer.write_variable_bytes(data)
|
||||||
}
|
}
|
||||||
Self::Agreed => buffer.write(&[Self::TAG_AGREED]),
|
Self::Agreed => buffer.write(&[Self::TAG_AGREED]),
|
||||||
@ -102,6 +240,11 @@ impl<'de> Decode<'de> for ClientNegotiationMessage<'de> {
|
|||||||
let protocol = buffer.read_u8()?;
|
let protocol = buffer.read_u8()?;
|
||||||
Ok(Self::Hello { protocol })
|
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 => {
|
Self::TAG_START_KEX => {
|
||||||
let kex_algo = buffer.read_u8()?;
|
let kex_algo = buffer.read_u8()?;
|
||||||
let ciphersuite = buffer.read_u8()?;
|
let ciphersuite = buffer.read_u8()?;
|
||||||
@ -110,10 +253,10 @@ impl<'de> Decode<'de> for ClientNegotiationMessage<'de> {
|
|||||||
ciphersuite,
|
ciphersuite,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Self::TAG_PUBLIC_KEY => {
|
Self::TAG_DH_PUBLIC_KEY => {
|
||||||
let end = buffer.read_u8()? != 0;
|
let end = buffer.read_u8()? != 0;
|
||||||
let data = buffer.read_variable_bytes()?;
|
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_CIPHERTEXT => buffer.read_variable_bytes().map(Self::CipherText),
|
||||||
Self::TAG_AGREED => Ok(Self::Agreed),
|
Self::TAG_AGREED => Ok(Self::Agreed),
|
||||||
@ -126,32 +269,25 @@ impl<'de> Decode<'de> for ClientNegotiationMessage<'de> {
|
|||||||
|
|
||||||
impl ServerNegotiationMessage<'_> {
|
impl ServerNegotiationMessage<'_> {
|
||||||
const TAG_HELLO: u8 = 0xB0;
|
const TAG_HELLO: u8 = 0xB0;
|
||||||
const TAG_REJECT: u8 = 0xB1;
|
const TAG_SIG_PUBLIC_KEY: u8 = 0xB1;
|
||||||
const TAG_START_KEX: u8 = 0xB2;
|
const TAG_REJECT: u8 = 0xB2;
|
||||||
const TAG_PUBLIC_KEY: u8 = 0xB3;
|
const TAG_START_KEX: u8 = 0xB3;
|
||||||
const TAG_AGREED: u8 = 0xB4;
|
const TAG_DH_PUBLIC_KEY: u8 = 0xB4;
|
||||||
|
const TAG_AGREED: u8 = 0xB5;
|
||||||
|
|
||||||
const TAG_CIPHERTEXT: u8 = 0xC0;
|
const TAG_CIPHERTEXT: u8 = 0xC0;
|
||||||
const TAG_PING: u8 = 0xC1;
|
const TAG_PING: u8 = 0xC1;
|
||||||
const TAG_KICK: u8 = 0xC2;
|
const TAG_KICK: u8 = 0xC2;
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for ServerHello<'_> {
|
pub fn needs_signature(&self) -> bool {
|
||||||
fn encode(&self, buffer: &mut Encoder) -> Result<(), EncodeError> {
|
match self {
|
||||||
buffer.write_variable_bytes(self.kex_algos)?;
|
Self::Hello(_)
|
||||||
buffer.write_variable_bytes(self.symmetric_ciphersuites)?;
|
| Self::Reject(_)
|
||||||
Ok(())
|
| 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])?;
|
buffer.write(&[Self::TAG_HELLO])?;
|
||||||
hello.encode(buffer)
|
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::Reject(_reason) => todo!(),
|
||||||
Self::StartKex => buffer.write(&[Self::TAG_START_KEX]),
|
Self::StartKex => buffer.write(&[Self::TAG_START_KEX]),
|
||||||
Self::PublicKey(end, data) => {
|
Self::DHPublicKey(end, data) => {
|
||||||
buffer.write(&[Self::TAG_PUBLIC_KEY, *end as u8])?;
|
buffer.write(&[Self::TAG_DH_PUBLIC_KEY, *end as u8])?;
|
||||||
buffer.write_variable_bytes(data)
|
buffer.write_variable_bytes(data)
|
||||||
}
|
}
|
||||||
Self::Agreed => buffer.write(&[Self::TAG_AGREED]),
|
Self::Agreed => buffer.write(&[Self::TAG_AGREED]),
|
||||||
@ -184,12 +324,17 @@ impl<'de> Decode<'de> for ServerNegotiationMessage<'de> {
|
|||||||
let tag = buffer.read_u8()?;
|
let tag = buffer.read_u8()?;
|
||||||
match tag {
|
match tag {
|
||||||
Self::TAG_HELLO => ServerHello::decode(buffer).map(Self::Hello),
|
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_REJECT => todo!(),
|
||||||
Self::TAG_START_KEX => Ok(Self::StartKex),
|
Self::TAG_START_KEX => Ok(Self::StartKex),
|
||||||
Self::TAG_PUBLIC_KEY => {
|
Self::TAG_DH_PUBLIC_KEY => {
|
||||||
let end = buffer.read_u8()? != 0;
|
let end = buffer.read_u8()? != 0;
|
||||||
let data = buffer.read_variable_bytes()?;
|
let data = buffer.read_variable_bytes()?;
|
||||||
Ok(Self::PublicKey(end, data))
|
Ok(Self::DHPublicKey(end, data))
|
||||||
}
|
}
|
||||||
Self::TAG_AGREED => Ok(Self::Agreed),
|
Self::TAG_AGREED => Ok(Self::Agreed),
|
||||||
Self::TAG_CIPHERTEXT => buffer.read_variable_bytes().map(Self::CipherText),
|
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 {
|
match cipher {
|
||||||
V1_CIPHER_AES_256_ECB => Some("aes-256-ecb"),
|
V1_CIPHER_AES_256_ECB => Some("aes-256-ecb"),
|
||||||
V1_CIPHER_AES_256_CBC => Some("aes-256-cbc"),
|
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::{
|
use crate::{
|
||||||
crypt::{
|
crypt::{
|
||||||
ciphersuite_name, ServerHello, ServerNegotiationMessage, V1_CIPHER_AES_256_CBC,
|
ciphersuite_name, sig_algo_name, ServerHello, ServerNegotiationMessage,
|
||||||
V1_CIPHER_AES_256_ECB,
|
V1_CIPHER_AES_256_CBC, V1_CIPHER_AES_256_ECB,
|
||||||
},
|
|
||||||
socket::{
|
|
||||||
MessageSocket, MultiplexedSocket, MultiplexedSocketEvent, PacketSocket, SocketWrapper,
|
|
||||||
},
|
},
|
||||||
|
proto::{Decode, Decoder, Encode, Encoder},
|
||||||
|
socket::{MultiplexedSocket, MultiplexedSocketEvent, PacketSocket},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
symmetric::SymmetricCipher, ClientMessageProxy, ClientNegotiationMessage, ServerMessageProxy,
|
signature::{SignatureKeyStore, SignatureMethod, VerificationMethod},
|
||||||
|
symmetric::SymmetricCipher,
|
||||||
|
ClientNegotiationMessage, DummyKeystore,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ServerConfig {
|
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 accept_ciphersuite: fn(u8) -> bool,
|
||||||
pub offer_ciphersuites: fn() -> &'static [u8],
|
pub offer_ciphersuites: fn() -> &'static [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ServerPeerTransport {
|
enum PeerState {
|
||||||
|
None,
|
||||||
PreNegotiation,
|
PreNegotiation,
|
||||||
|
Authorized,
|
||||||
Negotiation(EphemeralSecret, u8),
|
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> {
|
pub struct ServerEncryptedSocket<S: PacketSocket> {
|
||||||
transport: SocketWrapper<S, ClientMessageProxy, ServerMessageProxy>,
|
transport: TransportWrapper<S>,
|
||||||
peers: HashMap<SocketAddr, ServerPeerTransport>,
|
// transport: SocketWrapper<S, ClientMessageProxy, ServerMessageProxy>,
|
||||||
|
peers: HashMap<SocketAddr, ServerPeer>,
|
||||||
buffer: [u8; 256],
|
buffer: [u8; 256],
|
||||||
config: ServerConfig,
|
config: ServerConfig,
|
||||||
}
|
}
|
||||||
@ -53,6 +73,7 @@ fn offer_ciphersuites_default() -> &'static [u8] {
|
|||||||
impl Default for ServerConfig {
|
impl Default for ServerConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
signature_keystore: Box::new(DummyKeystore),
|
||||||
accept_ciphersuite: accept_ciphersuite_default,
|
accept_ciphersuite: accept_ciphersuite_default,
|
||||||
offer_ciphersuites: offer_ciphersuites_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 {
|
pub fn new_with_config(transport: S, config: ServerConfig) -> Self {
|
||||||
Self {
|
Self {
|
||||||
transport: SocketWrapper::new(transport),
|
transport: TransportWrapper {
|
||||||
|
inner: transport,
|
||||||
|
send_buf: [0; 256],
|
||||||
|
},
|
||||||
peers: HashMap::new(),
|
peers: HashMap::new(),
|
||||||
buffer: [0; 256],
|
buffer: [0; 256],
|
||||||
config,
|
config,
|
||||||
@ -74,32 +98,22 @@ impl<S: PacketSocket> ServerEncryptedSocket<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_client(&mut self, remote: &SocketAddr) {
|
pub fn remove_client(&mut self, remote: &SocketAddr) {
|
||||||
let mut buf = [0; 32];
|
|
||||||
if self.peers.remove(remote).is_some() {
|
if self.peers.remove(remote).is_some() {
|
||||||
self.transport
|
self.transport
|
||||||
.send_to(remote, &mut buf, &ServerNegotiationMessage::Kick(0))
|
.send_to(remote, &ServerNegotiationMessage::Kick(0), None)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ping_clients(&mut self, limit: usize) -> Vec<SocketAddr> {
|
pub fn ping_clients(&mut self, limit: usize) -> Vec<SocketAddr> {
|
||||||
let mut send_buf = [0; 32];
|
|
||||||
let mut removed = vec![];
|
let mut removed = vec![];
|
||||||
for (remote, state) in self.peers.iter_mut() {
|
for (remote, peer) in self.peers.iter_mut() {
|
||||||
match state {
|
self.transport.send_to(remote, &ServerNegotiationMessage::Ping, None).ok();
|
||||||
ServerPeerTransport::Connected(_, missed) => {
|
|
||||||
self.transport
|
|
||||||
.send_to(remote, &mut send_buf, &ServerNegotiationMessage::Ping)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if *missed >= limit {
|
if peer.missed_pings >= limit {
|
||||||
removed.push(*remote);
|
removed.push(*remote);
|
||||||
}
|
|
||||||
|
|
||||||
*missed += 1;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
peer.missed_pings += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry in removed.iter() {
|
for entry in removed.iter() {
|
||||||
@ -108,35 +122,90 @@ impl<S: PacketSocket> ServerEncryptedSocket<S> {
|
|||||||
|
|
||||||
removed
|
removed
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: PacketSocket> MultiplexedSocket for ServerEncryptedSocket<S> {
|
fn recv_inner<'de>(
|
||||||
fn send_to(&mut self, remote: &SocketAddr, data: &[u8]) -> Result<(), Error> {
|
&mut self,
|
||||||
let mut buf = [0; 256];
|
recv_buf: &'de mut [u8],
|
||||||
if let Some(ServerPeerTransport::Connected(cipher, _)) = self.peers.get_mut(remote) {
|
) -> Result<(ClientNegotiationMessage<'de>, SocketAddr), Error> {
|
||||||
let len = cipher.encrypt(data, &mut self.buffer)?;
|
let (len, remote) = self
|
||||||
self.transport.send_to(
|
.transport
|
||||||
remote,
|
.inner
|
||||||
&mut buf,
|
.recv_from(recv_buf)
|
||||||
&ServerNegotiationMessage::CipherText(&self.buffer[..len]),
|
.map_err(Into::into)?;
|
||||||
)
|
let mut decoder = Decoder::new(&recv_buf[..len]);
|
||||||
} else {
|
let message = ClientNegotiationMessage::decode(&mut decoder)?;
|
||||||
Err(Error::NotConnected)
|
|
||||||
|
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> {
|
fn recv_from_inner<'a>(&mut self, buffer: &'a mut [u8]) -> Result<MultiplexedSocketEvent<'a>, Error> {
|
||||||
let (message, remote) = self.transport.recv_from(&mut self.buffer)?;
|
let mut recv_buf = [0; 256];
|
||||||
|
let (message, remote) = self.recv_inner(&mut recv_buf)?;
|
||||||
|
|
||||||
let mut buf = [0; 256];
|
if let Some(peer) = self.peers.get_mut(&remote) {
|
||||||
|
match (message, &mut peer.state) {
|
||||||
if let Some(mut state) = self.peers.get_mut(&remote) {
|
|
||||||
match (message, &mut state) {
|
|
||||||
// TODO check kex params
|
// TODO check kex params
|
||||||
(
|
// PreNegotiation -> Authorized (or fail)
|
||||||
ClientNegotiationMessage::StartKex { ciphersuite, .. },
|
(ClientNegotiationMessage::SigPublicKey(sig, data), PeerState::PreNegotiation) => {
|
||||||
ServerPeerTransport::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);
|
let name = ciphersuite_name(ciphersuite);
|
||||||
if !(self.config.accept_ciphersuite)(ciphersuite) {
|
if !(self.config.accept_ciphersuite)(ciphersuite) {
|
||||||
log::warn!("Kicking {remote}: cannot accept offered ciphersuite: {name:?}",);
|
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 mut rng = rand::thread_rng();
|
||||||
let secret = EphemeralSecret::random_from_rng(&mut rng);
|
let secret = EphemeralSecret::random_from_rng(&mut rng);
|
||||||
*state = ServerPeerTransport::Negotiation(secret, ciphersuite);
|
|
||||||
self.transport.send_to(
|
peer.state = PeerState::Negotiation(secret, ciphersuite);
|
||||||
&remote,
|
|
||||||
&mut buf,
|
self.transport
|
||||||
&ServerNegotiationMessage::StartKex,
|
.send_to(
|
||||||
)?;
|
&remote,
|
||||||
|
&ServerNegotiationMessage::StartKex,
|
||||||
|
peer.signer.as_mut(),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
Ok(MultiplexedSocketEvent::None(remote))
|
Ok(MultiplexedSocketEvent::None(remote))
|
||||||
}
|
}
|
||||||
|
// Negotiation -> Connected
|
||||||
(
|
(
|
||||||
ClientNegotiationMessage::PublicKey(true, data),
|
ClientNegotiationMessage::DHPublicKey(true, data),
|
||||||
ServerPeerTransport::Negotiation(secret, _),
|
PeerState::Negotiation(_, _),
|
||||||
) if data.len() == 32 => {
|
) 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];
|
let mut remote_key = [0; 32];
|
||||||
remote_key.copy_from_slice(data);
|
remote_key.copy_from_slice(data);
|
||||||
let remote_key = PublicKey::from(remote_key);
|
let remote_key = PublicKey::from(remote_key);
|
||||||
match state.negotiate(&remote_key) {
|
let shared = secret.diffie_hellman(&remote_key);
|
||||||
Ok(()) => {
|
let symmetric = match SymmetricCipher::new(cipher, shared.as_bytes()) {
|
||||||
// Send public key to the client
|
Ok(symmetric) => symmetric,
|
||||||
self.transport.send_to(
|
|
||||||
&remote,
|
|
||||||
&mut buf,
|
|
||||||
&ServerNegotiationMessage::PublicKey(true, public.as_bytes()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(MultiplexedSocketEvent::None(remote))
|
|
||||||
}
|
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!(
|
log::warn!("{remote}: couldn't setup symmetric cipher: {error}");
|
||||||
"Kicking {remote}: couldn't setup requested ciphersuite: {error}"
|
|
||||||
);
|
|
||||||
self.remove_client(&remote);
|
self.remove_client(&remote);
|
||||||
Ok(MultiplexedSocketEvent::None(remote))
|
return Ok(MultiplexedSocketEvent::None(remote));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
(ClientNegotiationMessage::Agreed, ServerPeerTransport::Connected(_, _)) => {
|
|
||||||
log::debug!("{remote}: negotiated");
|
|
||||||
self.transport
|
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))
|
Ok(MultiplexedSocketEvent::None(remote))
|
||||||
}
|
}
|
||||||
(
|
// Connected -> Connected
|
||||||
ClientNegotiationMessage::CipherText(data),
|
(ClientNegotiationMessage::Agreed, PeerState::Connected(_)) => {
|
||||||
ServerPeerTransport::Connected(cipher, _),
|
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)?;
|
let len = cipher.decrypt(data, buffer)?;
|
||||||
Ok(MultiplexedSocketEvent::ClientData(remote, &buffer[..len]))
|
Ok(MultiplexedSocketEvent::ClientData(remote, &buffer[..len]))
|
||||||
}
|
}
|
||||||
(ClientNegotiationMessage::Pong, ServerPeerTransport::Connected(_, missed)) => {
|
// Connected -> Connected
|
||||||
*missed = 0;
|
(ClientNegotiationMessage::Pong, _) => {
|
||||||
|
peer.missed_pings = 0;
|
||||||
Ok(MultiplexedSocketEvent::None(remote))
|
Ok(MultiplexedSocketEvent::None(remote))
|
||||||
}
|
}
|
||||||
|
// *** -> remove
|
||||||
(ClientNegotiationMessage::Disconnect(_reason), _) => {
|
(ClientNegotiationMessage::Disconnect(_reason), _) => {
|
||||||
log::debug!("{remote}: disconnected");
|
log::debug!("{remote}: disconnected");
|
||||||
self.peers.remove(&remote);
|
self.peers.remove(&remote);
|
||||||
@ -217,44 +299,105 @@ impl<S: PacketSocket> MultiplexedSocket for ServerEncryptedSocket<S> {
|
|||||||
let ClientNegotiationMessage::Hello { protocol: 1 } = message else {
|
let ClientNegotiationMessage::Hello { protocol: 1 } = message else {
|
||||||
return Ok(MultiplexedSocketEvent::None(remote));
|
return Ok(MultiplexedSocketEvent::None(remote));
|
||||||
};
|
};
|
||||||
self.peers
|
let peer = ServerPeer {
|
||||||
.insert(remote, ServerPeerTransport::PreNegotiation);
|
signer: None,
|
||||||
|
verifier: None,
|
||||||
|
state: PeerState::PreNegotiation,
|
||||||
|
missed_pings: 0,
|
||||||
|
};
|
||||||
|
self.peers.insert(remote, peer);
|
||||||
|
|
||||||
// Reply with hello
|
// Reply with hello
|
||||||
let symmetric_ciphersuites = (self.config.offer_ciphersuites)();
|
let symmetric_ciphersuites = (self.config.offer_ciphersuites)();
|
||||||
|
let sig_algos = self.config.signature_keystore.offer_signature_algorithms();
|
||||||
log::debug!("{remote}: offering ciphersuites:");
|
log::debug!("{remote}: offering ciphersuites:");
|
||||||
for suite in symmetric_ciphersuites {
|
for suite in symmetric_ciphersuites {
|
||||||
log::debug!("* {:?}", ciphersuite_name(*suite));
|
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 {
|
let hello = ServerHello {
|
||||||
kex_algos: &[],
|
kex_algos: &[],
|
||||||
symmetric_ciphersuites,
|
symmetric_ciphersuites,
|
||||||
|
sig_algos,
|
||||||
|
key_hash_algos: &[],
|
||||||
};
|
};
|
||||||
self.transport
|
self.transport
|
||||||
.send_to(&remote, &mut buf, &ServerNegotiationMessage::Hello(hello))?;
|
.send_to(&remote, &ServerNegotiationMessage::Hello(hello), None)
|
||||||
|
.ok();
|
||||||
|
|
||||||
Ok(MultiplexedSocketEvent::None(remote))
|
Ok(MultiplexedSocketEvent::None(remote))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: PacketSocket> AsRawFd for ServerEncryptedSocket<S> {
|
impl<S: PacketSocket> MultiplexedSocket for ServerEncryptedSocket<S> {
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
fn send_to(&mut self, remote: &SocketAddr, data: &[u8]) -> Result<(), Error> {
|
||||||
self.transport.as_raw_fd()
|
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 {
|
impl<S: PacketSocket> AsRawFd for ServerEncryptedSocket<S> {
|
||||||
fn negotiate(&mut self, public: &PublicKey) -> Result<(), Error> {
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
let Self::Negotiation(secret, symmetric) =
|
self.transport.inner.as_raw_fd()
|
||||||
mem::replace(self, ServerPeerTransport::PreNegotiation)
|
}
|
||||||
else {
|
}
|
||||||
panic!();
|
|
||||||
|
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 payload = &self.send_buf[..len];
|
||||||
// let aes = Aes256::new_from_slice(shared.as_bytes()).unwrap();
|
|
||||||
*self = ServerPeerTransport::Connected(cipher, 0);
|
self.inner.send_to(payload, remote).map_err(Into::into)?;
|
||||||
Ok(())
|
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,
|
Disconnected,
|
||||||
#[error("Message too large: buffer size {0}, message size {1}")]
|
#[error("Message too large: buffer size {0}, message size {1}")]
|
||||||
MessageTooLarge(usize, usize),
|
MessageTooLarge(usize, usize),
|
||||||
|
#[error("Cannot sign message: buffer size {0}, signature size {1}")]
|
||||||
|
CannotFitSignature(usize, usize),
|
||||||
#[error("Malformed ciphertext")]
|
#[error("Malformed ciphertext")]
|
||||||
InvalidCiphertext,
|
InvalidCiphertext,
|
||||||
|
#[error("Message signature mismatch")]
|
||||||
|
InvalidSignature,
|
||||||
|
#[error("Invalid signature size ({0})")]
|
||||||
|
InvalidSignatureSize(usize),
|
||||||
#[error("Malformed encryption key")]
|
#[error("Malformed encryption key")]
|
||||||
InvalidKey,
|
InvalidKey,
|
||||||
#[error("Communication timed out")]
|
#[error("Communication timed out")]
|
||||||
Timeout,
|
Timeout,
|
||||||
#[error("Cannot accept any of the offered ciphersuites")]
|
#[error("Cannot accept any of the offered ciphersuites")]
|
||||||
UnacceptableCiphersuites,
|
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))]
|
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, stdin, stdout, Read, Stdin, Stdout, Write as IoWrite},
|
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}
|
||||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket},
|
|
||||||
os::fd::AsRawFd,
|
|
||||||
process::ExitCode,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cross::io::Poll;
|
use cross::io::Poll;
|
||||||
use libterm::{RawMode, RawTerminal};
|
use libterm::{RawMode, RawTerminal};
|
||||||
use rsh::{
|
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);
|
pub const PING_TIMEOUT: Duration = Duration::from_secs(3);
|
||||||
@ -33,6 +29,8 @@ pub enum Error {
|
|||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
#[clap(short, long)]
|
||||||
|
key: PathBuf,
|
||||||
remote: IpAddr,
|
remote: IpAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +50,7 @@ pub enum Event<'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
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 mut poll = Poll::new()?;
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
let stdout = stdout();
|
let stdout = stdout();
|
||||||
@ -66,7 +64,7 @@ impl Client {
|
|||||||
let socket = UdpSocket::bind(local)?;
|
let socket = UdpSocket::bind(local)?;
|
||||||
socket.connect(remote)?;
|
socket.connect(remote)?;
|
||||||
|
|
||||||
let mut socket = ClientEncryptedSocket::new(socket);
|
let mut socket = ClientEncryptedSocket::new_with_config(socket, crypto_config);
|
||||||
|
|
||||||
poll.add(&socket)?;
|
poll.add(&socket)?;
|
||||||
|
|
||||||
@ -225,8 +223,14 @@ fn terminal_info(stdout: &Stdout) -> Result<TerminalInfo, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run(args: Args) -> Result<(), Error> {
|
fn run(args: Args) -> Result<(), Error> {
|
||||||
let remote = SocketAddr::new(args.remote, 7777);
|
let remote = SocketAddr::new(args.remote, 77);
|
||||||
let reason = Client::connect(remote)?.run()?;
|
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() {
|
if !reason.is_empty() {
|
||||||
eprintln!("\nDisconnected: {reason}");
|
eprintln!("\nDisconnected: {reason}");
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ impl<'a> Encoder<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_variable_bytes(&mut self, bytes: &[u8]) -> Result<(), EncodeError> {
|
pub fn write_variable_bytes(&mut self, bytes: &[u8]) -> Result<(), EncodeError> {
|
||||||
let len: u32 = bytes
|
let len: u16 = bytes
|
||||||
.len()
|
.len()
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| EncodeError::ValueTooLong)?;
|
.map_err(|_| EncodeError::ValueTooLong)?;
|
||||||
@ -95,6 +95,10 @@ impl<'a> Encoder<'a> {
|
|||||||
pub fn get(&self) -> &[u8] {
|
pub fn get(&self) -> &[u8] {
|
||||||
&self.buffer[..self.pos]
|
&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> {
|
impl<'a> Decoder<'a> {
|
||||||
@ -111,6 +115,11 @@ impl<'a> Decoder<'a> {
|
|||||||
Ok(byte)
|
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> {
|
pub fn read_le_u32(&mut self) -> Result<u32, DecodeError> {
|
||||||
let bytes = self.read_bytes(size_of::<u32>())?;
|
let bytes = self.read_bytes(size_of::<u32>())?;
|
||||||
Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
|
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> {
|
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)
|
self.read_bytes(len as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +143,10 @@ impl<'a> Decoder<'a> {
|
|||||||
let slice = self.read_variable_bytes()?;
|
let slice = self.read_variable_bytes()?;
|
||||||
core::str::from_utf8(slice).map_err(DecodeError::InvalidString)
|
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 {
|
impl MessageProxy for ClientMessageProxy {
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
|
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
|
||||||
#![feature(if_let_guard)]
|
#![feature(if_let_guard)]
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
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
|
||||||
fs::File,
|
|
||||||
io::{Read, Write},
|
|
||||||
net::{SocketAddr, UdpSocket},
|
|
||||||
os::fd::{AsRawFd, FromRawFd, RawFd},
|
|
||||||
process::{Child, Command, ExitCode, Stdio},
|
|
||||||
str::FromStr,
|
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use cross::io::{Poll, TimerFd};
|
use cross::io::{Poll, TimerFd};
|
||||||
use rsh::{
|
use rsh::{
|
||||||
crypt::ServerEncryptedSocket,
|
crypt::{server::ServerConfig, ServerEncryptedSocket, SimpleServerKeyStore},
|
||||||
proto::{ClientMessage, Decode, Decoder, ServerMessage, TerminalInfo},
|
proto::{ClientMessage, Decode, Decoder, ServerMessage, TerminalInfo},
|
||||||
socket::{MultiplexedSocket, MultiplexedSocketEvent},
|
socket::{MultiplexedSocket, MultiplexedSocketEvent},
|
||||||
Error,
|
Error,
|
||||||
@ -21,6 +15,14 @@ use rsh::{
|
|||||||
|
|
||||||
pub const PING_INTERVAL: Duration = Duration::from_millis(500);
|
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 {
|
pub struct Session {
|
||||||
pty_master: File,
|
pty_master: File,
|
||||||
remote: SocketAddr,
|
remote: SocketAddr,
|
||||||
@ -95,10 +97,11 @@ impl Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
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 mut poll = Poll::new()?;
|
||||||
let timer = TimerFd::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(&socket)?;
|
||||||
poll.add(&timer)?;
|
poll.add(&timer)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@ -132,6 +135,7 @@ impl Server {
|
|||||||
let message = ClientMessage::decode(&mut decoder);
|
let message = ClientMessage::decode(&mut decoder);
|
||||||
(message, peer)
|
(message, peer)
|
||||||
}
|
}
|
||||||
|
MultiplexedSocketEvent::Error(_) => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let message = match message {
|
let message = match message {
|
||||||
@ -187,7 +191,6 @@ impl Server {
|
|||||||
let session = self.pty_to_session.get_mut(&fd).unwrap();
|
let session = self.pty_to_session.get_mut(&fd).unwrap();
|
||||||
if let Err(error) = session.pty_master.write(&data) {
|
if let Err(error) = session.pty_master.write(&data) {
|
||||||
eprintln!("PTY write error: {error}");
|
eprintln!("PTY write error: {error}");
|
||||||
self.remove_session_by_fd(fd)?;
|
|
||||||
self.socket
|
self.socket
|
||||||
.send_message_to(
|
.send_message_to(
|
||||||
&remote,
|
&remote,
|
||||||
@ -195,6 +198,7 @@ impl Server {
|
|||||||
&ServerMessage::Bye("PTY error"),
|
&ServerMessage::Bye("PTY error"),
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
|
self.remove_session_by_fd(fd)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::ClientBye(remote, reason) => {
|
Event::ClientBye(remote, reason) => {
|
||||||
@ -219,6 +223,7 @@ impl Server {
|
|||||||
&ServerMessage::Bye("PTY open error"),
|
&ServerMessage::Bye("PTY open error"),
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
|
self.socket.remove_client(&remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,7 +235,6 @@ impl Server {
|
|||||||
}
|
}
|
||||||
PtyEvent::Err(error) => {
|
PtyEvent::Err(error) => {
|
||||||
eprintln!("PTY read error: {error}");
|
eprintln!("PTY read error: {error}");
|
||||||
self.remove_session_by_fd(fd)?;
|
|
||||||
self.socket
|
self.socket
|
||||||
.send_message_to(
|
.send_message_to(
|
||||||
&remote,
|
&remote,
|
||||||
@ -238,13 +242,14 @@ impl Server {
|
|||||||
&ServerMessage::Bye("PTY error"),
|
&ServerMessage::Bye("PTY error"),
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
|
self.remove_session_by_fd(fd)?;
|
||||||
}
|
}
|
||||||
PtyEvent::Closed => {
|
PtyEvent::Closed => {
|
||||||
println!("End of PTY for {remote}");
|
println!("End of PTY for {remote}");
|
||||||
self.remove_session_by_fd(fd)?;
|
|
||||||
self.socket
|
self.socket
|
||||||
.send_message_to(&remote, &mut send_buf, &ServerMessage::Bye(""))
|
.send_message_to(&remote, &mut send_buf, &ServerMessage::Bye(""))
|
||||||
.ok();
|
.ok();
|
||||||
|
self.remove_session_by_fd(fd)?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Event::Tick => {
|
Event::Tick => {
|
||||||
@ -259,6 +264,7 @@ impl Server {
|
|||||||
fn update_client_timeouts(&mut self) -> Result<(), Error> {
|
fn update_client_timeouts(&mut self) -> Result<(), Error> {
|
||||||
let removed = self.socket.ping_clients(8);
|
let removed = self.socket.ping_clients(8);
|
||||||
for entry in removed {
|
for entry in removed {
|
||||||
|
log::debug!("Client timed out: {entry}");
|
||||||
self.remove_session_by_remote(entry).ok();
|
self.remove_session_by_remote(entry).ok();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -294,8 +300,19 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run() -> Result<(), Error> {
|
fn run(args: Args) -> Result<(), Error> {
|
||||||
let server = Server::new(SocketAddr::from_str("0.0.0.0:7777").unwrap())?;
|
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()
|
server.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +321,8 @@ fn main() -> ExitCode {
|
|||||||
.filter_level(log::LevelFilter::Debug)
|
.filter_level(log::LevelFilter::Debug)
|
||||||
.format_timestamp(None)
|
.format_timestamp(None)
|
||||||
.init();
|
.init();
|
||||||
if let Err(error) = run() {
|
let args = Args::parse();
|
||||||
|
if let Err(error) = run(args) {
|
||||||
eprintln!("Finished with error: {error}");
|
eprintln!("Finished with error: {error}");
|
||||||
ExitCode::FAILURE
|
ExitCode::FAILURE
|
||||||
} else {
|
} else {
|
||||||
|
@ -44,6 +44,7 @@ pub enum MultiplexedSocketEvent<'a> {
|
|||||||
ClientData(SocketAddr, &'a [u8]),
|
ClientData(SocketAddr, &'a [u8]),
|
||||||
ClientDisconnected(SocketAddr),
|
ClientDisconnected(SocketAddr),
|
||||||
None(SocketAddr),
|
None(SocketAddr),
|
||||||
|
Error(Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MultiplexedSocket: AsRawFd {
|
pub trait MultiplexedSocket: AsRawFd {
|
||||||
|
@ -15,10 +15,10 @@ thiserror.workspace = true
|
|||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
sha2.workspace = true
|
||||||
|
|
||||||
# TODO own impl
|
# TODO own impl
|
||||||
humansize = { version = "2.1.3", features = ["impl_style"] }
|
humansize = { version = "2.1.3", features = ["impl_style"] }
|
||||||
sha2 = { version = "0.10.8" }
|
|
||||||
|
|
||||||
init = { path = "../init" }
|
init = { path = "../init" }
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user