Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Secure Session example code #381

Merged
merged 3 commits into from
Feb 15, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ zeroize = "0.5.2"
base64 = "0.10.0"
byteorder = "1.2.7"
clap = "2.32"
lazy_static = "1.2.0"
log = "0.4.6"
env_logger = "0.6.0"

Expand Down Expand Up @@ -68,6 +69,14 @@ path = "docs/examples/rust/secure_message_client_verify.rs"
name = "secure_message_server"
path = "docs/examples/rust/secure_message_server.rs"

[[example]]
name = "secure_session_echo_server"
path = "docs/examples/rust/secure_session_echo_server.rs"

[[example]]
name = "secure_session_echo_client"
path = "docs/examples/rust/secure_session_echo_client.rs"

[[example]]
name = "keygen_tool"
path = "tools/rust/keygen_tool.rs"
Expand Down
18 changes: 18 additions & 0 deletions docs/examples/rust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Here we have some examples of Themis usage.
chat client which encrypts messages
* [**secure_message_client_verify**](secure_message_client_verify.rs) —
chat client which signs and verifies messages
* <b>secure_session_echo_*</b> —
an example of secure network communication with Secure Session
* [**secure_session_echo_client**](secure_session_echo_client.rs) —
simple echo client using buffer-oriented API
* [**secure_session_echo_server**](secure_session_echo_server.rs) —
simple echo server using callback API

You can run the examples with Cargo like this:

Expand Down Expand Up @@ -148,3 +154,15 @@ Some notable things about this example:
But it still verifies their integrity.

Currently all clients are expected to use the same keys.


## secure_session_echo

The server expects connections from clients
and echoes back any messages sent by individual clients.
Communication between parties is secured using Secure Session.

Usually you don’t need to specify any custom options,
the command-line defaults are expected to work right away.
But you can override the port assignment
if the default port is already in use on your system.
125 changes: 125 additions & 0 deletions docs/examples/rust/secure_session_echo_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2019 (c) rust-themis developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::io::{self, Read, Write};
use std::net::TcpStream;

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use clap::clap_app;
use lazy_static::lazy_static;
use log::{debug, info};
use themis::keys::{EcdsaPrivateKey, EcdsaPublicKey};
use themis::secure_session::{SecureSession, SecureSessionTransport};

const MAX_MESSAGE_SIZE: usize = 10000;
const TERMINATION_MESSAGE: &[u8] = b"please let me out of here\n";

// Peer identification data -- IDs and public keys -- should be obtained from trusted sources.
// The corresponding private key is used to identify this peer. It should be managed separately.
const CLIENT_ID: &[u8] = b"client";
const SERVER_ID: &[u8] = b"server";

lazy_static! {
static ref CLIENT_PRIVATE: EcdsaPrivateKey = EcdsaPrivateKey::try_from_slice(b"\x52\x45\x43\x32\x00\x00\x00\x2d\x00\xb2\x7f\x81\x00\x60\x9d\xe7\x7a\x39\x93\x68\xfc\x25\xd1\x79\x88\x6d\xfb\xf6\x19\x35\x53\x74\x10\xfc\x5b\x44\xe1\xf6\xf4\x4e\x59\x8d\x94\x99\x4f" as &[u8]).expect("client private key");
static ref CLIENT_PUBLIC: EcdsaPublicKey = EcdsaPublicKey::try_from_slice(b"\x55\x45\x43\x32\x00\x00\x00\x2d\x10\xf4\x68\x8c\x02\x1c\xd0\x3b\x20\x84\xf2\x7a\x38\xbc\xf6\x39\x74\xbf\xc3\x13\xae\xb1\x00\x26\x78\x07\xe1\x7f\x63\xce\xe0\xb8\xac\x02\x10\x40\x10" as &[u8]).expect("client public key");
static ref SERVER_PUBLIC: EcdsaPublicKey = EcdsaPublicKey::try_from_slice(b"\x55\x45\x43\x32\x00\x00\x00\x2d\xa5\xb3\x9b\x9d\x03\xcd\x34\xc5\xc1\x95\x6a\xb2\x50\x43\xf1\x4f\xe5\x88\x3a\x0f\xb1\x11\x8c\x35\x81\x82\xe6\x9e\x5c\x5a\x3e\x14\x06\xc5\xb3\x7d\xdd" as &[u8]).expect("server public key");
}

struct ExpectServer;

impl SecureSessionTransport for ExpectServer {
fn get_public_key_for_id(&mut self, id: &[u8]) -> Option<EcdsaPublicKey> {
if id == SERVER_ID {
Some(SERVER_PUBLIC.clone())
} else {
None
}
}
}

fn main() {
env_logger::init();

let matches = clap_app!(secure_session_echo_client =>
(version: env!("CARGO_PKG_VERSION"))
(about: "Echo client for Secure Session server.")
(@arg address: -c --connect [addr] "Echo server address (default: localhost:3432)")
)
.get_matches();

let remote_addr = matches.value_of("address").unwrap_or("localhost:3432");

info!("connecting to {:?}", remote_addr);

let mut socket = TcpStream::connect(&remote_addr).expect("client connection");

let mut session = SecureSession::new(&CLIENT_ID, &CLIENT_PRIVATE, ExpectServer);
let mut buffer = [0; MAX_MESSAGE_SIZE];

let request = session.connect_request().expect("connect request");
write_framed(&mut socket, &request).expect("send request");

loop {
let reply = read_framed(&mut socket, &mut buffer).expect("receive reply");
let response = session.negotiate_reply(&reply).expect("negotiate");
if session.is_established() {
break;
}
write_framed(&mut socket, &response).expect("send response");
}

info!("negotiation complete");

loop {
let mut line = String::new();
io::stdin().read_line(&mut line).expect("read from stdin");
if line.is_empty() {
break;
}

let message = session.wrap(line.as_bytes()).expect("wrap outgoing");
write_framed(&mut socket, &message).expect("write to socket");

let reply = read_framed(&mut socket, &mut buffer).expect("read from socket");
let reply = session.unwrap(&reply).expect("unwrap incoming");

io::stdout().write_all(&reply).expect("write to stdout");
}

let quit = session.wrap(TERMINATION_MESSAGE).expect("wrap termination");
write_framed(&mut socket, &quit).expect("send termination");
}

// Note how client has to implement the framing scheme used by the server. It is necessary to send
// and receive complete Secure Session messages over the network.

fn write_framed(socket: &mut TcpStream, data: &[u8]) -> io::Result<()> {
if data.len() > u32::max_value() as usize {
return Err(io::Error::new(io::ErrorKind::Other, "buffer too big"));
}
socket.write_u32::<LittleEndian>(data.len() as u32)?;
socket.write_all(data)?;
debug!("{:?}: sent {} bytes", socket.peer_addr(), data.len());
Ok(())
}

fn read_framed<'a>(socket: &mut TcpStream, data: &'a mut [u8]) -> io::Result<&'a [u8]> {
let length = socket.read_u32::<LittleEndian>()? as usize;
if data.len() < length {
return Err(io::Error::new(io::ErrorKind::Other, "insufficient buffer"));
}
socket.read_exact(&mut data[0..length])?;
debug!("{:?}: received {} bytes", socket.peer_addr(), length);
Ok(&data[0..length])
}
160 changes: 160 additions & 0 deletions docs/examples/rust/secure_session_echo_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright 2019 (c) rust-themis developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use clap::clap_app;
use lazy_static::lazy_static;
use log::{debug, error, info, warn};
use themis::keys::{EcdsaPrivateKey, EcdsaPublicKey};
use themis::secure_session::{
SecureSession, SecureSessionState, SecureSessionTransport, TransportError,
};

const MAX_MESSAGE_SIZE: usize = 10000;
const TERMINATION_MESSAGE: &[u8] = b"please let me out of here\n";

// Peer identification data -- IDs and public keys -- should be obtained from trusted sources.
// The corresponding private key is used to identify this peer. It should be managed separately.
const CLIENT_ID: &[u8] = b"client";
const SERVER_ID: &[u8] = b"server";

lazy_static! {
static ref SERVER_PRIVATE: EcdsaPrivateKey = EcdsaPrivateKey::try_from_slice(b"\x52\x45\x43\x32\x00\x00\x00\x2d\xd0\xfd\x93\xc6\x00\xae\x83\xb3\xef\xef\x06\x2c\x9d\x76\x63\xf2\x50\xd8\xac\x32\x6e\x73\x96\x60\x53\x77\x51\xe4\x34\x26\x7c\xf2\x9f\xb6\x96\xeb\xd8" as &[u8]).expect("client private key");
static ref SERVER_PUBLIC: EcdsaPublicKey = EcdsaPublicKey::try_from_slice(b"\x55\x45\x43\x32\x00\x00\x00\x2d\xa5\xb3\x9b\x9d\x03\xcd\x34\xc5\xc1\x95\x6a\xb2\x50\x43\xf1\x4f\xe5\x88\x3a\x0f\xb1\x11\x8c\x35\x81\x82\xe6\x9e\x5c\x5a\x3e\x14\x06\xc5\xb3\x7d\xdd" as &[u8]).expect("server public key");
static ref CLIENT_PUBLIC: EcdsaPublicKey = EcdsaPublicKey::try_from_slice(b"\x55\x45\x43\x32\x00\x00\x00\x2d\x10\xf4\x68\x8c\x02\x1c\xd0\x3b\x20\x84\xf2\x7a\x38\xbc\xf6\x39\x74\xbf\xc3\x13\xae\xb1\x00\x26\x78\x07\xe1\x7f\x63\xce\xe0\xb8\xac\x02\x10\x40\x10" as &[u8]).expect("client public key");
}

struct SocketTransport {
socket: TcpStream,
}

impl SocketTransport {
fn new(socket: TcpStream) -> Self {
SocketTransport { socket }
}
}

impl SecureSessionTransport for SocketTransport {
fn get_public_key_for_id(&mut self, id: &[u8]) -> Option<EcdsaPublicKey> {
match id {
CLIENT_ID => Some(CLIENT_PUBLIC.clone()),
SERVER_ID => Some(SERVER_PUBLIC.clone()),
_ => None,
}
}

// Note how transport callbacks do proper framing (by prefixing the data with its length) and
// retrying (by using write_all() and read_exact() methods) to ensure that only entire Secure
// Session messages are sent over the network.

fn send_data(&mut self, data: &[u8]) -> Result<usize, TransportError> {
if data.len() > u32::max_value() as usize {
return Err(TransportError::new("buffer too big"));
}
self.socket.write_u32::<LittleEndian>(data.len() as u32)?;
self.socket.write_all(data)?;
debug!("{:?}: sent {} bytes", self.socket.peer_addr(), data.len());
Ok(data.len())
}

fn receive_data(&mut self, data: &mut [u8]) -> Result<usize, TransportError> {
let length = self.socket.read_u32::<LittleEndian>()? as usize;
if data.len() < length {
return Err(TransportError::new("insufficient buffer"));
}
self.socket.read_exact(&mut data[0..length])?;
debug!("{:?}: received {} bytes", self.socket.peer_addr(), length);
Ok(length as usize)
}

fn state_changed(&mut self, state: SecureSessionState) {
info!("{:?}: state changed: {:?}", self.socket.peer_addr(), state);
}
}

fn main() {
env_logger::init();

let matches = clap_app!(secure_session_echo_server =>
(version: env!("CARGO_PKG_VERSION"))
(about: "Echo server for Secure Session clients.")
(@arg port: -p --port [number] "Listening port (default: 3432)")
)
.get_matches();

let port = matches
.value_of("port")
.unwrap_or("3432")
.parse()
.expect("valid port");

let listen_addr = SocketAddr::new([0; 16].into(), port);
let listen_socket = TcpListener::bind(&listen_addr).expect("server listen");

info!("listening on port {}", port);

for client in listen_socket.incoming() {
let client = match client {
Ok(client) => client,
Err(e) => {
warn!("failed to accept client: {}", e);
continue;
}
};
let client_address = client.peer_addr();

std::thread::spawn(move || {
info!("{:?}: connected", client_address);

let transport = SocketTransport::new(client);
let mut session = SecureSession::new(&SERVER_ID, &SERVER_PRIVATE, transport);

while !session.is_established() {
if let Err(e) = session.negotiate() {
error!("{:?}: negotiation failed: {}", client_address, e);
return;
}
}

info!("{:?}: negotiated", client_address);

loop {
let message = match session.receive(MAX_MESSAGE_SIZE) {
Ok(m) => m,
Err(e) => {
error!("{:?}: failed to receive message: {}", client_address, e);
return;
}
};
debug!("{:?}: message: ({} bytes)", client_address, message.len());

// Note that we use a special termination message to initiate orderly shutdown of
// Secure Session. It is not secure to close the session unconditionally when the
// socket appears to be disconnected.
if message == TERMINATION_MESSAGE {
info!("{:?}: disconnected", client_address);
break;
}

if let Err(e) = session.send(&message) {
error!("{:?}: failed to send message: {}", client_address, e);
return;
}
}
});
}
}