Clavis

Clavis is a Rust library that facilitates secure, encrypted communication over asynchronous streams. It provides a robust framework for establishing encrypted connections and exchanging custom packets between clients and servers. Clavis is built on top of the Tokio runtime and uses the X25519 and ChaCha20Poly1305 crates for encryption and key exchange.
Table of Contents
Installation
Add Clavis to your Cargo.toml:
[dependencies]
clavis = { git = "https://github.com/pyrohost/clavis.git" }
tokio = { version = "1.0", features = ["full"] }
Getting Started
Define Your Packets
Use the define_packets! macro to define your custom packet types:
use clavis::{define_packets, PacketTrait};
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PingData {
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PongData {
pub reply: String,
}
define_packets! {
pub enum Packet {
Ping(PingData),
Pong(PongData)
}
}
Establishing a Secure Connection
Client Example
use clavis::{EncryptedStream, Role, Result};
use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> Result<()> {
let stream = TcpStream::connect("127.0.0.1:7272").await?;
let mut client_stream = EncryptedStream::new(stream, Role::Client, None).await?;
let ping = Packet::Ping(PingData {
message: "Hello, Server!".into(),
});
client_stream.write_packet(&ping).await?;
let response: Packet = client_stream.read_packet().await?;
println!("Received response: {:?}", response);
Ok(())
}
Server Example
use clavis::{EncryptedStream, Role, Result};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> Result<()> {
let listener = TcpListener::bind("127.0.0.1:7272").await?;
println!("Server listening on 127.0.0.1:7272");
while let Ok((stream, addr)) = listener.accept().await {
println!("New connection from {}", addr);
tokio::spawn(async move {
if let Err(e) = handle_client(stream).await {
eprintln!("Error handling {}: {:?}", addr, e);
}
});
}
Ok(())
}
async fn handle_client(stream: TcpStream) -> Result<()> {
let mut server_stream = EncryptedStream::new(stream, Role::Server, None).await?;
loop {
match server_stream.read_packet().await? {
Packet::Ping(ping) => {
println!("Received Ping: {:?}", ping);
let pong = Packet::Pong(PongData {
reply: format!("Pong: {}", ping.message),
});
server_stream.write_packet(&pong).await?;
}
_ => {
continue;
}
}
}
}
Advanced Usage
Using Static Secrets
For scenarios requiring persistent identities, you can provide a static secret:
use x25519_dalek::StaticSecret;
use rand::rngs::OsRng;
let static_secret = StaticSecret::random_from_rng(&mut OsRng);
let stream = EncryptedStream::new(
tcp_stream,
Role::Client,
Some(static_secret)
).await?;
Splitting Streams
You can split an EncryptedStream into separate read and write halves:
let (mut reader, mut writer) = encrypted_stream.split();
tokio::join!(
async move { reader.read_packet().await },
async move { writer.write_packet(&some_packet).await }
);
API Documentation
For detailed API documentation, please run cargo doc --open in your project directory after adding Clavis as a dependency.
License
This project is licensed under the MIT License.