mod kdf;
mod nonce;
mod public_key;
pub use self::{kdf::Kdf, nonce::Nonce, public_key::PublicKey};
use crate::{amino_types::AuthSigMessage, error::Error};
use byteorder::{ByteOrder, LE};
use bytes::BufMut;
use prost::{encoding::encode_varint, Message};
use rand_os::OsRng;
use ring::aead;
use signatory::{ed25519, Signature, Signer, Verifier};
use signatory_dalek::Ed25519Verifier;
use std::{
cmp,
io::{self, Read, Write},
marker::{Send, Sync},
};
use x25519_dalek::{EphemeralSecret, PublicKey as EphemeralPublic};
pub const TAG_SIZE: usize = 16;
const DATA_LEN_SIZE: usize = 4;
const DATA_MAX_SIZE: usize = 1024;
const TOTAL_FRAME_SIZE: usize = DATA_MAX_SIZE + DATA_LEN_SIZE;
pub struct SecretConnection<IoHandler: Read + Write + Send + Sync> {
io_handler: IoHandler,
recv_nonce: Nonce,
send_nonce: Nonce,
recv_secret: aead::OpeningKey,
send_secret: aead::SealingKey,
remote_pubkey: PublicKey,
recv_buffer: Vec<u8>,
}
impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {
pub fn remote_pubkey(&self) -> PublicKey {
self.remote_pubkey
}
#[allow(clippy::new_ret_no_self)]
pub fn new(
mut handler: IoHandler,
local_pubkey: &PublicKey,
local_privkey: &dyn Signer<ed25519::Signature>,
) -> Result<SecretConnection<IoHandler>, Error> {
let (local_eph_pubkey, local_eph_privkey) = gen_eph_keys();
let remote_eph_pubkey = share_eph_pubkey(&mut handler, &local_eph_pubkey)?;
let shared_secret = EphemeralSecret::diffie_hellman(local_eph_privkey, &remote_eph_pubkey);
let local_eph_pubkey_bytes = *local_eph_pubkey.as_bytes();
let (low_eph_pubkey_bytes, _) =
sort32(local_eph_pubkey_bytes, *remote_eph_pubkey.as_bytes());
let loc_is_least = local_eph_pubkey_bytes == low_eph_pubkey_bytes;
let kdf = Kdf::derive_secrets_and_challenge(shared_secret.as_bytes(), loc_is_least);
let mut sc = SecretConnection {
io_handler: handler,
recv_buffer: vec![],
recv_nonce: Nonce::default(),
send_nonce: Nonce::default(),
recv_secret: aead::OpeningKey::new(&aead::CHACHA20_POLY1305, &kdf.recv_secret)
.map_err(|_| Error::Crypto)?,
send_secret: aead::SealingKey::new(&aead::CHACHA20_POLY1305, &kdf.send_secret)
.map_err(|_| Error::Crypto)?,
remote_pubkey: PublicKey::from(
ed25519::PublicKey::from_bytes(remote_eph_pubkey.as_bytes())
.ok_or_else(|| Error::Crypto)?,
),
};
let local_signature = sign_challenge(&kdf.challenge, local_privkey)?;
let auth_sig_msg = match local_pubkey {
PublicKey::Ed25519(ref pk) => {
share_auth_signature(&mut sc, pk.as_bytes(), local_signature)?
}
};
let remote_pubkey =
ed25519::PublicKey::from_bytes(&auth_sig_msg.key).ok_or_else(|| Error::Crypto)?;
let remote_signature: &[u8] = &auth_sig_msg.sig;
let remote_sig = ed25519::Signature::from_bytes(remote_signature)?;
let remote_verifier = Ed25519Verifier::from(&remote_pubkey);
remote_verifier.verify(&kdf.challenge, &remote_sig)?;
sc.remote_pubkey = PublicKey::from(remote_pubkey);
Ok(sc)
}
fn open(&self, ciphertext: &[u8], out: &mut [u8]) -> Result<usize, Error> {
let nonce = aead::Nonce::from(&self.recv_nonce);
let associated_data = aead::Aad::empty();
let len = if out.len() >= ciphertext.len() {
let in_out = &mut out[..ciphertext.len()];
in_out.copy_from_slice(ciphertext);
aead::open_in_place(&self.recv_secret, nonce, associated_data, 0, in_out)
.map_err(|_| Error::Crypto)?
.len()
} else {
let mut in_out = ciphertext.to_vec();
let out0 =
aead::open_in_place(&self.recv_secret, nonce, aead::Aad::empty(), 0, &mut in_out)
.map_err(|_| Error::Crypto)?;
out[..out0.len()].copy_from_slice(out0);
out0.len()
};
Ok(len)
}
fn seal(
&self,
chunk: &[u8],
sealed_frame: &mut [u8; TAG_SIZE + TOTAL_FRAME_SIZE],
) -> Result<(), Error> {
let chunk_length = chunk.len();
let mut frame = [0u8; TOTAL_FRAME_SIZE];
LE::write_u32(&mut frame[..DATA_LEN_SIZE], chunk_length as u32);
frame[DATA_LEN_SIZE..DATA_LEN_SIZE + chunk_length].copy_from_slice(chunk);
sealed_frame[..frame.len()].copy_from_slice(&frame);
aead::seal_in_place(
&self.send_secret,
aead::Nonce::from(&self.send_nonce),
aead::Aad::empty(),
sealed_frame,
TAG_SIZE,
)
.map_err(|_| Error::Crypto)?;
Ok(())
}
}
impl<IoHandler> Read for SecretConnection<IoHandler>
where
IoHandler: Read + Write + Send + Sync,
{
fn read(&mut self, data: &mut [u8]) -> Result<usize, io::Error> {
if !self.recv_buffer.is_empty() {
let n = cmp::min(data.len(), self.recv_buffer.len());
data.copy_from_slice(&self.recv_buffer[..n]);
let mut leftover_portion = vec![0; self.recv_buffer.len().checked_sub(n).unwrap()];
leftover_portion.clone_from_slice(&self.recv_buffer[n..]);
self.recv_buffer = leftover_portion;
return Ok(n);
}
let mut sealed_frame = [0u8; TAG_SIZE + TOTAL_FRAME_SIZE];
self.io_handler.read_exact(&mut sealed_frame)?;
let mut frame = [0u8; TOTAL_FRAME_SIZE];
let res = self.open(&sealed_frame, &mut frame);
if res.is_err() {
return Err(io::Error::new(
io::ErrorKind::Other,
res.err().unwrap().to_string(),
));
}
self.recv_nonce.increment();
let mut chunk_length_specifier = vec![0; 4];
chunk_length_specifier.clone_from_slice(&frame[..4]);
let chunk_length = LE::read_u32(&chunk_length_specifier);
if chunk_length as usize > DATA_MAX_SIZE {
return Err(io::Error::new(
io::ErrorKind::Other,
"chunk_length is greater than dataMaxSize",
));
}
let mut chunk = vec![0; chunk_length as usize];
chunk.clone_from_slice(
&frame[DATA_LEN_SIZE..(DATA_LEN_SIZE.checked_add(chunk_length as usize).unwrap())],
);
let n = cmp::min(data.len(), chunk.len());
data[..n].copy_from_slice(&chunk[..n]);
self.recv_buffer.copy_from_slice(&chunk[n..]);
Ok(n)
}
}
impl<IoHandler> Write for SecretConnection<IoHandler>
where
IoHandler: Read + Write + Send + Sync,
{
fn write(&mut self, data: &[u8]) -> Result<usize, io::Error> {
let mut n = 0usize;
let mut data_copy = &data[..];
while !data_copy.is_empty() {
let chunk: &[u8];
if DATA_MAX_SIZE < data.len() {
chunk = &data[..DATA_MAX_SIZE];
data_copy = &data_copy[DATA_MAX_SIZE..];
} else {
chunk = data_copy;
data_copy = &[0u8; 0];
}
let sealed_frame = &mut [0u8; TAG_SIZE + TOTAL_FRAME_SIZE];
let res = self.seal(chunk, sealed_frame);
if res.is_err() {
return Err(io::Error::new(
io::ErrorKind::Other,
res.err().unwrap().to_string(),
));
}
self.send_nonce.increment();
self.io_handler.write_all(&sealed_frame[..])?;
n = n.checked_add(chunk.len()).unwrap();
}
Ok(n)
}
fn flush(&mut self) -> Result<(), io::Error> {
self.io_handler.flush()
}
}
fn gen_eph_keys() -> (EphemeralPublic, EphemeralSecret) {
let mut local_csprng = OsRng::new().unwrap();
let local_privkey = EphemeralSecret::new(&mut local_csprng);
let local_pubkey = EphemeralPublic::from(&local_privkey);
(local_pubkey, local_privkey)
}
fn share_eph_pubkey<IoHandler: Read + Write + Send + Sync>(
handler: &mut IoHandler,
local_eph_pubkey: &EphemeralPublic,
) -> Result<EphemeralPublic, Error> {
let mut buf = vec![0; 0];
let local_eph_pubkey_vec = local_eph_pubkey.as_bytes();
encode_varint((local_eph_pubkey_vec.len() + 1) as u64, &mut buf); encode_varint(local_eph_pubkey_vec.len() as u64, &mut buf); buf.put_slice(local_eph_pubkey_vec);
handler.write_all(&buf)?;
let mut buf = vec![0; 34];
handler.read_exact(&mut buf)?;
let mut remote_eph_pubkey_fixed: [u8; 32] = Default::default();
if buf[0] != 33 || buf[1] != 32 {
return Err(Error::Protocol);
}
remote_eph_pubkey_fixed.copy_from_slice(&buf[2..34]);
Ok(EphemeralPublic::from(remote_eph_pubkey_fixed))
}
fn sort32(first: [u8; 32], second: [u8; 32]) -> ([u8; 32], [u8; 32]) {
if second > first {
(first, second)
} else {
(second, first)
}
}
fn sign_challenge(
challenge: &[u8; 32],
local_privkey: &dyn Signer<ed25519::Signature>,
) -> Result<ed25519::Signature, Error> {
local_privkey.try_sign(challenge).map_err(|_| Error::Crypto)
}
fn share_auth_signature<IoHandler: Read + Write + Send + Sync>(
sc: &mut SecretConnection<IoHandler>,
pubkey: &[u8; 32],
signature: ed25519::Signature,
) -> Result<AuthSigMessage, Error> {
let amsg = AuthSigMessage {
key: pubkey.to_vec(),
sig: signature.into_bytes().to_vec(),
};
let mut buf: Vec<u8> = vec![];
amsg.encode_length_delimited(&mut buf)?;
sc.write_all(&buf)?;
let mut rbuf = vec![0; 106]; sc.read_exact(&mut rbuf)?;
Ok(AuthSigMessage::decode_length_delimited(&rbuf)?)
}
#[cfg(tests)]
mod tests {
use super::*;
#[test]
fn test_sort() {
let t1 = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
];
let t2 = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1,
];
let (ref t3, ref t4) = sort32(t1, t2);
assert_eq!(t1, *t3);
assert_eq!(t2, *t4);
}
#[test]
fn test_dh_compatibility() {
let local_priv = &[
15, 54, 189, 54, 63, 255, 158, 244, 56, 168, 155, 63, 246, 79, 208, 192, 35, 194, 39,
232, 170, 187, 179, 36, 65, 36, 237, 12, 225, 176, 201, 54,
];
let remote_pub = &[
193, 34, 183, 46, 148, 99, 179, 185, 242, 148, 38, 40, 37, 150, 76, 251, 25, 51, 46,
143, 189, 201, 169, 218, 37, 136, 51, 144, 88, 196, 10, 20,
];
let expected_dh = &[
92, 56, 205, 118, 191, 208, 49, 3, 226, 150, 30, 205, 230, 157, 163, 7, 36, 28, 223,
84, 165, 43, 78, 38, 126, 200, 40, 217, 29, 36, 43, 37,
];
let got_dh = diffie_hellman(local_priv, remote_pub);
assert_eq!(expected_dh, &got_dh);
}
}