Skip to content

Commit

Permalink
ping-pong-encrypted example
Browse files Browse the repository at this point in the history
  • Loading branch information
plebhash committed Jun 26, 2024
1 parent cf776f4 commit 56af371
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 0 deletions.
17 changes: 17 additions & 0 deletions examples/ping-pong-encrypted/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "ping-pong-encrypted"
version = "0.1.0"
edition = "2021"
authors = [ "SRI Community" ]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
binary_sv2 = { path = "../../protocols/v2/binary-sv2/binary-sv2" }
codec_sv2 = { path = "../../protocols/v2/codec-sv2", features = [ "noise_sv2" ] }
noise_sv2 = { path = "../../protocols/v2/noise-sv2" }
key-utils = { version = "^1.0.0", path = "../../utils/key-utils" }
network_helpers_sv2 = { version = "2.0.0", path = "../../roles/roles-utils/network-helpers", features =["with_tokio","with_buffer_pool"] }
rand = "0.8"
tokio = { version = "1", features = ["full"] }
async-channel = "1.5.1"
20 changes: 20 additions & 0 deletions examples/ping-pong-encrypted/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
`ping-pong-encrypted` is an example of how to encode and decode SV2 binary frames (without any encryption layer) while leveraging the following crates:
- [`binary_sv2`](http://docs.rs/binary_sv2)
- [`codec_sv2`](http://docs.rs/codec_sv2)
- [`framing_sv2`](http://docs.rs/framing_sv2) (which is actually just re-exported by `codec_sv2`)
- [`noise_sv2`](http://docs.rs/noise_sv2)

We establish a simple `Ping`-`Pong` protocol with a server and a client communicating over a TCP socket.

The server expects to receive a `Ping` message encoded as a SV2 binary frame.
The `Ping` message contains a `nonce`, which is a `u8` generated randomly by the client.

The client expects to get a `Pong` message in response, also encoded as a SV2 binary frame, with the same `nonce`.

The messages are assigned arbitrary values for binary encoding:
```rust
pub const PING_MSG_TYPE: u8 = 0xfe;
pub const PONG_MSG_TYPE: u8 = 0xff;
```

All communication is encrypted with [SV2 Noise Protocol](https://stratumprotocol.org/specification/04-Protocol-Security/).
73 changes: 73 additions & 0 deletions examples/ping-pong-encrypted/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use crate::messages::{Message, Ping, Pong, PING_MSG_TYPE, PONG_MSG_TYPE};
use codec_sv2::{Frame, StandardSv2Frame, HandshakeRole, Initiator};
use key_utils::Secp256k1PublicKey;
use network_helpers_sv2::noise_connection_tokio::Connection;
use tokio::net::TcpStream;

use crate::error::Error;

pub async fn start_client(address: &str, k_pub: String) -> Result<(), Error> {
let stream = TcpStream::connect(address).await?;

println!("CLIENT: Connected to server on {}", address);

// parse server pubkey
let k_pub: Secp256k1PublicKey = k_pub.try_into()?;

// noise handshake initiator
let initiator = Initiator::from_raw_k(k_pub.into_bytes())?;

// channels for encrypted connection
let (receiver, sender, _, _) = Connection::new(stream, HandshakeRole::Initiator(initiator)).await?;

// create Ping message
let ping = Ping::new()?;
let ping_nonce = ping.get_nonce();
let message = Message::Ping(ping);

// create Ping frame
let ping_frame =
StandardSv2Frame::<Message>::from_message(message.clone(), PING_MSG_TYPE, 0, false)
.ok_or(Error::FrameFromMessage)?;

// send Ping frame (sender takes care of encryption)
println!("CLIENT: Sending encrypted Ping to server with nonce: {}", ping_nonce);
match sender.send(ping_frame.into()).await {
Ok(_) => {},
Err(_) => return Err(Error::Sender)
}

// ok, we have successfully sent the ping message
// now it's time to receive and verify the pong response
// receiver already took care of decryption
let mut frame: StandardSv2Frame<Message> = match receiver.recv().await {
Ok(f) => f.try_into()?,
Err(_) => return Err(Error::Receiver)
};

let frame_header = frame.get_header().ok_or(Error::FrameHeader)?;

// check message type on header
if frame_header.msg_type() != PONG_MSG_TYPE {
return Err(Error::FrameHeader);
}

// decode frame payload
let decoded_payload: Pong = match binary_sv2::from_bytes(frame.payload()) {
Ok(pong) => pong,
Err(e) => return Err(Error::BinarySv2(e)),
};

// check if nonce is the same as ping
let pong_nonce = decoded_payload.get_nonce();
if ping_nonce == pong_nonce {
println!(
"CLIENT: Received encrypted Pong with identical nonce as Ping: {}",
pong_nonce
);
} else {
return Err(Error::Nonce);
}

Ok(())
}
55 changes: 55 additions & 0 deletions examples/ping-pong-encrypted/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#[derive(std::fmt::Debug)]
pub enum Error {
Io(std::io::Error),
CodecSv2(codec_sv2::Error),
FramingSv2(codec_sv2::framing_sv2::Error),
BinarySv2(binary_sv2::Error),
NoiseSv2(noise_sv2::Error),
NetworkHelpersSv2(network_helpers_sv2::Error),
KeyUtils(key_utils::Error),
Receiver,
Sender,
FrameHeader,
FrameFromMessage,
Nonce,
WrongMessage,
Tcp(std::io::Error),
}

impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Error {
Error::Io(e)
}
}

impl From<codec_sv2::Error> for Error {
fn from(e: codec_sv2::Error) -> Error {
Error::CodecSv2(e)
}
}

impl From<network_helpers_sv2::Error> for Error {
fn from(e: network_helpers_sv2::Error) -> Error { Error::NetworkHelpersSv2(e) }
}

impl From<binary_sv2::Error> for Error {
fn from(e: binary_sv2::Error) -> Error {
Error::BinarySv2(e)
}
}

impl From<noise_sv2::Error> for Error {
fn from(e: noise_sv2::Error) -> Error {
Error::NoiseSv2(e)
}
}

impl From<key_utils::Error> for Error {
fn from(e: key_utils::Error) -> Error {
Error::KeyUtils(e)
}
}

impl From<codec_sv2::framing_sv2::Error> for Error {
fn from(e: codec_sv2::framing_sv2::Error) -> Error { Error::FramingSv2(e) }
}
30 changes: 30 additions & 0 deletions examples/ping-pong-encrypted/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
mod client;
mod error;
mod messages;
mod server;

const ADDR: &str = "127.0.0.1:3333";
const SERVER_PUBLIC_K: &str = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72";
const SERVER_PRIVATE_K: &str = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n";
const SERVER_CERT_VALIDITY: std::time::Duration = std::time::Duration::from_secs(3600);

#[tokio::main]
async fn main() {
// Start the server in a separate thread
tokio::spawn(async {
server::start_server(
ADDR,
SERVER_PUBLIC_K.to_string(),
SERVER_PRIVATE_K.to_string(),
SERVER_CERT_VALIDITY,
)
.await.expect("Server failed");
});

// Give the server a moment to start up
std::thread::sleep(std::time::Duration::from_secs(1));

// Start the client
// Note: it only knows the server's pubkey!
client::start_client(ADDR, SERVER_PUBLIC_K.to_string()).await.expect("Client failed");
}
79 changes: 79 additions & 0 deletions examples/ping-pong-encrypted/src/messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::error::Error;
use binary_sv2::{binary_codec_sv2, Deserialize, Serialize, decodable::DecodableField, decodable::FieldMarker};

use rand::Rng;

pub const PING_MSG_TYPE: u8 = 0xfe;
pub const PONG_MSG_TYPE: u8 = 0xff;

// we derive binary_sv2::{Serialize, Deserialize}
// to allow for binary encoding
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Ping {
nonce: u8,
}

impl Ping {
pub fn new() -> Result<Self, Error> {
let mut rng = rand::thread_rng();
let random: u8 = rng.gen();
Ok(Self { nonce: random })
}

pub fn get_nonce(&self) -> u8 {
self.nonce
}
}

// we derive binary_sv2::{Serialize, Deserialize}
// to allow for binary encoding
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Pong {
nonce: u8,
}

impl<'decoder> Pong {
pub fn new(nonce: u8) -> Result<Self, Error> {
Ok(Self { nonce })
}

pub fn get_nonce(&self) -> u8 {
self.nonce
}
}

// unifies message types for noise_connection_tokio::Connection
#[derive(Clone)]
pub enum Message {
Ping(Ping),
Pong(Pong)
}

impl binary_sv2::GetSize for Message {
fn get_size(&self) -> usize {
match self {
Self::Ping(ping) => ping.get_size(),
Self::Pong(pong) => pong.get_size(),
}
}
}

impl From<Message> for binary_sv2::encodable::EncodableField<'_> {
fn from(m: Message) -> Self {
match m {
Message::Ping(p) => p.into(),
Message::Pong(p) => p.into(),
}
}
}

impl Deserialize<'_> for Message {
fn get_structure(_v: &[u8]) -> std::result::Result<Vec<FieldMarker>, binary_sv2::Error> {
unimplemented!()
}
fn from_decoded_fields(
_v: Vec<DecodableField>,
) -> std::result::Result<Self, binary_sv2::Error> {
unimplemented!()
}
}
95 changes: 95 additions & 0 deletions examples/ping-pong-encrypted/src/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::{
error::Error,
messages::{Message, Ping, Pong, PING_MSG_TYPE, PONG_MSG_TYPE},
};
use codec_sv2::{Frame, StandardSv2Frame, StandardEitherFrame};

use network_helpers_sv2::noise_connection_tokio::Connection;
use codec_sv2::{HandshakeRole, Responder};
use key_utils::{Secp256k1PublicKey, Secp256k1SecretKey};

use tokio::net::TcpListener;
use async_channel::{Receiver, Sender};

pub async fn start_server(
address: &str,
k_pub: String,
k_priv: String,
cert_validity: std::time::Duration,
) -> Result<(), Error> {
let listener = TcpListener::bind(address).await?;

// parse keys
let k_pub: Secp256k1PublicKey = k_pub.to_string().try_into()?;
let k_priv: Secp256k1SecretKey = k_priv.to_string().try_into()?;

println!("SERVER: Listening on {}", address);

loop {
let (stream, _) = listener.accept().await?;
tokio::spawn(async move {

// noise handshake responder
let responder =
Responder::from_authority_kp(&k_pub.into_bytes(), &k_priv.into_bytes(), cert_validity)?;

// channels for encrypted connection
let (receiver, sender, _, _) = Connection::new(stream, HandshakeRole::Responder(responder)).await?;

// handle encrypted connection
handle_connection(receiver, sender).await?;
Ok::<(), Error>(())
});
}
}

async fn handle_connection(receiver: Receiver<StandardEitherFrame<Message>>, sender: Sender<StandardEitherFrame<Message>>) -> Result<(), Error> {
// first, we need to read the ping frame
// receiver already took care of decryption
let mut frame: StandardSv2Frame<Message> = match receiver.recv().await {
Ok(f) => f.try_into()?,
Err(_) => return Err(Error::Receiver)
};

let frame_header = frame.get_header().ok_or(Error::FrameHeader)?;

// check message type on header
if frame_header.msg_type() != PING_MSG_TYPE {
return Err(Error::WrongMessage);
}

// decode frame payload
let decoded_payload: Ping = match binary_sv2::from_bytes(frame.payload()) {
Ok(ping) => ping,
Err(e) => return Err(Error::BinarySv2(e)),
};

// ok, we have successfully received the ping message
// now it's time to send the pong response

// we need the ping nonce to create our pong response
let ping_nonce = decoded_payload.get_nonce();

println!("SERVER: Received encrypted Ping with nonce: {}", ping_nonce);

// create Pong message
let pong = Pong::new(ping_nonce)?;
let message = Message::Pong(pong.clone());

// create Pong frame
let pong_frame =
StandardSv2Frame::<Message>::from_message(message.clone(), PONG_MSG_TYPE, 0, false)
.ok_or(Error::FrameFromMessage)?;

// respond Pong (sender takes care of encryption)
println!(
"SERVER: Sending encrypted Pong to client with nonce: {}",
pong.get_nonce()
);
match sender.send(pong_frame.into()).await {
Ok(_) => {},
Err(_) => return Err(Error::Sender)
}

Ok(())
}

0 comments on commit 56af371

Please sign in to comment.