Begin processing of initation
This commit is contained in:
@@ -8,6 +8,7 @@ use x25519_dalek::PublicKey;
|
||||
use x25519_dalek::StaticSecret;
|
||||
|
||||
use crate::noise;
|
||||
use crate::messages;
|
||||
use crate::types::*;
|
||||
use crate::peer::Peer;
|
||||
|
||||
@@ -109,9 +110,9 @@ impl Device {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pk` - Public key of peer to initiate handshake for
|
||||
pub fn begin(&self, pk : PublicKey) -> Result<Vec<u8>, HandshakeError> {
|
||||
pub fn begin(&self, pk : &PublicKey) -> Result<Vec<u8>, HandshakeError> {
|
||||
match self.pkmap.get(pk.as_bytes()) {
|
||||
None => Err(HandshakeError::new()),
|
||||
None => Err(HandshakeError::UnknownPublicKey),
|
||||
Some(&idx) => {
|
||||
let peer = &self.peers[idx];
|
||||
let id = self.allocate(idx);
|
||||
@@ -120,15 +121,27 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup(&self, pk : &PublicKey) -> Result<&Peer, HandshakeError> {
|
||||
match self.pkmap.get(pk.as_bytes()) {
|
||||
Some(&idx) => Ok(&self.peers[idx]),
|
||||
_ => Err(HandshakeError::UnknownPublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a handshake message.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `msg` - Byte slice containing the message (untrusted input)
|
||||
pub fn process(&self, msg : &[u8]) -> Result<Output, HandshakeError> {
|
||||
// inspect type field
|
||||
match msg.get(0) {
|
||||
_ => Err(HandshakeError::new())
|
||||
Some(&messages::TYPE_INITIATION) => {
|
||||
noise::process_initiation(self, msg)
|
||||
},
|
||||
Some(&messages::TYPE_RESPONSE) => {
|
||||
Err(HandshakeError::InvalidMessageFormat)
|
||||
},
|
||||
_ => Err(HandshakeError::InvalidMessageFormat)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,3 +160,38 @@ impl Device {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn handshake() {
|
||||
// generate new keypairs
|
||||
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
|
||||
let sk1 = StaticSecret::new(&mut rng);
|
||||
let pk1 = PublicKey::from(&sk1);
|
||||
|
||||
let sk2 = StaticSecret::new(&mut rng);
|
||||
let pk2 = PublicKey::from(&sk2);
|
||||
|
||||
// intialize devices on both ends
|
||||
|
||||
let mut dev1 = Device::new(sk1);
|
||||
let mut dev2 = Device::new(sk2);
|
||||
|
||||
dev1.add(pk2).unwrap();
|
||||
dev2.add(pk1).unwrap();
|
||||
|
||||
// create initiation
|
||||
|
||||
let msg1 = dev1.begin(&pk2).unwrap();
|
||||
|
||||
// process initiation and create response
|
||||
|
||||
let out1 = dev2.process(&msg1).unwrap();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::types::*;
|
||||
|
||||
const SIZE_TAG : usize = 16;
|
||||
const SIZE_X25519_POINT : usize = 32;
|
||||
const SIZE_TIMESTAMP : usize = 12;
|
||||
|
||||
pub const TYPE_INITIATION : u32 = 1;
|
||||
pub const TYPE_RESPONSE : u32 = 2;
|
||||
pub const TYPE_INITIATION : u8 = 1;
|
||||
pub const TYPE_RESPONSE : u8 = 2;
|
||||
|
||||
|
||||
/* Wireguard handshake (noise) initiation message
|
||||
* initator -> responder
|
||||
@@ -23,22 +27,42 @@ pub struct Initiation {
|
||||
pub f_timestamp_tag : [u8; SIZE_TAG],
|
||||
}
|
||||
|
||||
impl From<&[u8]> for Initiation {
|
||||
fn from(b: &[u8]) -> Self {
|
||||
impl TryFrom<&[u8]> for Initiation {
|
||||
|
||||
type Error = HandshakeError;
|
||||
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
|
||||
// check length of slice matches message
|
||||
|
||||
if value.len() != mem::size_of::<Self>() {
|
||||
return Err(HandshakeError::InvalidMessageFormat);
|
||||
}
|
||||
|
||||
// create owned copy
|
||||
|
||||
let mut owned = [0u8; mem::size_of::<Self>()];
|
||||
let mut msg : Self;
|
||||
owned.copy_from_slice(b);
|
||||
owned.copy_from_slice(value);
|
||||
|
||||
// cast to Initiation
|
||||
|
||||
unsafe {
|
||||
msg = mem::transmute::<[u8; mem::size_of::<Self>()], Self>(owned);
|
||||
};
|
||||
|
||||
// correct endianness
|
||||
|
||||
msg.f_type = msg.f_type.to_le();
|
||||
msg.f_sender = msg.f_sender.to_le();
|
||||
msg
|
||||
|
||||
// check type and reserved fields
|
||||
|
||||
if msg.f_type != (TYPE_INITIATION as u32) {
|
||||
return Err(HandshakeError::InvalidMessageFormat);
|
||||
}
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +95,7 @@ impl fmt::Debug for Initiation {
|
||||
impl Default for Initiation {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
f_type : TYPE_INITIATION,
|
||||
f_type : TYPE_INITIATION as u32,
|
||||
f_sender : 0,
|
||||
f_ephemeral : [0u8; SIZE_X25519_POINT],
|
||||
f_static : [0u8; SIZE_X25519_POINT],
|
||||
@@ -112,23 +136,43 @@ pub struct Response {
|
||||
pub f_empty_tag : [u8; SIZE_TAG],
|
||||
}
|
||||
|
||||
impl From<&[u8]> for Response {
|
||||
fn from(b: &[u8]) -> Self {
|
||||
impl TryFrom<&[u8]> for Response {
|
||||
|
||||
type Error = HandshakeError;
|
||||
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
|
||||
// check length of slice matches message
|
||||
|
||||
if value.len() != mem::size_of::<Self>() {
|
||||
return Err(HandshakeError::InvalidMessageFormat);
|
||||
}
|
||||
|
||||
// create owned copy
|
||||
|
||||
let mut owned = [0u8; mem::size_of::<Self>()];
|
||||
let mut msg : Self;
|
||||
owned.copy_from_slice(b);
|
||||
owned.copy_from_slice(value);
|
||||
|
||||
// cast to MessageResponse
|
||||
|
||||
unsafe {
|
||||
msg = mem::transmute::<[u8; mem::size_of::<Self>()], Self>(owned);
|
||||
};
|
||||
|
||||
// correct endianness
|
||||
|
||||
msg.f_type = msg.f_type.to_le();
|
||||
msg.f_sender = msg.f_sender.to_le();
|
||||
msg.f_receiver = msg.f_receiver.to_le();
|
||||
msg
|
||||
|
||||
// check type and reserved fields
|
||||
|
||||
if msg.f_type != (TYPE_RESPONSE as u32) {
|
||||
return Err(HandshakeError::InvalidMessageFormat);
|
||||
}
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +221,7 @@ mod tests {
|
||||
#[test]
|
||||
fn message_response_identity() {
|
||||
let msg = Response {
|
||||
f_type : TYPE_RESPONSE,
|
||||
f_type : TYPE_RESPONSE as u32,
|
||||
f_sender : 146252,
|
||||
f_receiver : 554442,
|
||||
f_ephemeral : [
|
||||
@@ -195,13 +239,14 @@ mod tests {
|
||||
};
|
||||
|
||||
let buf : Vec<u8> = msg.into();
|
||||
assert_eq!(msg, Response::from(&buf[..]));
|
||||
let msg_p : Response = Response::try_from(&buf[..]).unwrap();
|
||||
assert_eq!(msg, msg_p);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_initiate_identity() {
|
||||
let msg = Initiation {
|
||||
f_type : TYPE_RESPONSE,
|
||||
f_type : TYPE_INITIATION as u32,
|
||||
f_sender : 575757,
|
||||
f_ephemeral : [
|
||||
// ephemeral public key
|
||||
@@ -218,7 +263,7 @@ mod tests {
|
||||
0xeb, 0x6a, 0xec, 0xc3, 0x3c, 0xda, 0x47, 0xe1
|
||||
],
|
||||
f_static_tag : [
|
||||
// tag
|
||||
// poly1305 tag
|
||||
0x45, 0xac, 0x8d, 0x43, 0xea, 0x1b, 0x2f, 0x02,
|
||||
0x45, 0x5d, 0x86, 0x37, 0xee, 0x83, 0x6b, 0x42
|
||||
],
|
||||
@@ -228,13 +273,13 @@ mod tests {
|
||||
0x78, 0x28, 0x57, 0x42
|
||||
],
|
||||
f_timestamp_tag : [
|
||||
// tag
|
||||
// poly1305 tag
|
||||
0x60, 0x0e, 0x1e, 0x95, 0x41, 0x6b, 0x52, 0x05,
|
||||
0xa2, 0x09, 0xe1, 0xbf, 0x40, 0x05, 0x2f, 0xde
|
||||
]
|
||||
};
|
||||
|
||||
let buf : Vec<u8> = msg.into();
|
||||
assert_eq!(msg, Initiation::from(&buf[..]));
|
||||
assert_eq!(msg, Initiation::try_from(&buf[..]).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
106
src/noise.rs
106
src/noise.rs
@@ -1,3 +1,5 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// DH
|
||||
use x25519_dalek::PublicKey;
|
||||
use x25519_dalek::StaticSecret;
|
||||
@@ -15,7 +17,7 @@ use rand::rngs::OsRng;
|
||||
use crate::types::*;
|
||||
use crate::peer::{State, Peer};
|
||||
use crate::device::Device;
|
||||
use crate::messages;
|
||||
use crate::messages::{Initiation, Response};
|
||||
use crate::timestamp;
|
||||
|
||||
type HMACBlake2s = Hmac<Blake2s>;
|
||||
@@ -129,6 +131,19 @@ macro_rules! SEAL {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! OPEN {
|
||||
($key:expr, $aead:expr, $pt:expr, $ct:expr, $tag:expr) => {
|
||||
{
|
||||
let mut aead = ChaCha20Poly1305::new($key, &ZERO_NONCE, $aead);
|
||||
if !aead.decrypt($ct, $pt, $tag) {
|
||||
Err(HandshakeError::DecryptionFailure)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -156,7 +171,7 @@ pub fn create_initiation(
|
||||
id : u32
|
||||
) -> Result<Vec<u8>, HandshakeError> {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let mut msg : messages::Initiation = Default::default();
|
||||
let mut msg : Initiation = Default::default();
|
||||
|
||||
// initialize state
|
||||
|
||||
@@ -185,10 +200,7 @@ pub fn create_initiation(
|
||||
|
||||
// (C, k) := Kdf2(C, DH(E_priv, S_pub))
|
||||
|
||||
let (ck, key) = KDF2!(
|
||||
&ck,
|
||||
sk.diffie_hellman(&peer.pk).as_bytes()
|
||||
);
|
||||
let (ck, key) = KDF2!(&ck, sk.diffie_hellman(&peer.pk).as_bytes());
|
||||
|
||||
// msg.static := Aead(k, 0, S_pub, H)
|
||||
|
||||
@@ -206,10 +218,7 @@ pub fn create_initiation(
|
||||
|
||||
// (C, k) := Kdf2(C, DH(S_priv, S_pub))
|
||||
|
||||
let (ck, key) = KDF2!(
|
||||
&ck,
|
||||
peer.ss.as_bytes() // precomputed static-static
|
||||
);
|
||||
let (ck, key) = KDF2!(&ck, peer.ss.as_bytes());
|
||||
|
||||
// msg.timestamp := Aead(k, 0, Timestamp(), H)
|
||||
|
||||
@@ -236,9 +245,80 @@ pub fn create_initiation(
|
||||
|
||||
// return message as vector
|
||||
|
||||
Ok(messages::Initiation::into(msg))
|
||||
Ok(Initiation::into(msg))
|
||||
}
|
||||
|
||||
pub fn process_initiation(device : &Device, peer : &Peer) -> Result<Output, ()> {
|
||||
Err(())
|
||||
pub fn process_initiation(device : &Device, msg : &[u8]) -> Result<Output, HandshakeError> {
|
||||
|
||||
// parse message
|
||||
|
||||
let msg = Initiation::try_from(msg)?;
|
||||
|
||||
// initialize state
|
||||
|
||||
let ck = INITIAL_CK;
|
||||
let hs = INITIAL_HS;
|
||||
let hs = HASH!(&hs, device.pk.as_bytes());
|
||||
|
||||
// C := Kdf(C, E_pub)
|
||||
|
||||
let ck = KDF1!(&ck, &msg.f_ephemeral);
|
||||
|
||||
// H := HASH(H, msg.ephemeral)
|
||||
|
||||
let hs = HASH!(&hs, &msg.f_ephemeral);
|
||||
|
||||
// (C, k) := Kdf2(C, DH(E_priv, S_pub))
|
||||
|
||||
let eph = PublicKey::from(msg.f_ephemeral);
|
||||
let (ck, key) = KDF2!(
|
||||
&ck,
|
||||
device.sk.diffie_hellman(&eph).as_bytes()
|
||||
);
|
||||
|
||||
// msg.static := Aead(k, 0, S_pub, H)
|
||||
|
||||
let mut pk = [0u8; 32];
|
||||
|
||||
OPEN!(
|
||||
&key,
|
||||
&hs, // ad
|
||||
&mut pk, // pt
|
||||
&msg.f_static, // ct
|
||||
&msg.f_static_tag // tag
|
||||
)?;
|
||||
|
||||
let peer = device.lookup(&PublicKey::from(pk))?;
|
||||
|
||||
// H := Hash(H || msg.static)
|
||||
|
||||
let hs = HASH!(&hs, &msg.f_static, &msg.f_static_tag);
|
||||
|
||||
// (C, k) := Kdf2(C, DH(S_priv, S_pub))
|
||||
|
||||
let (ck, key) = KDF2!(&ck, peer.ss.as_bytes());
|
||||
|
||||
// msg.timestamp := Aead(k, 0, Timestamp(), H)
|
||||
|
||||
let mut ts = timestamp::zero();
|
||||
|
||||
OPEN!(
|
||||
&key,
|
||||
&hs, // ad
|
||||
&mut ts, // pt
|
||||
&msg.f_timestamp, // ct
|
||||
&msg.f_timestamp_tag // tag
|
||||
)?;
|
||||
|
||||
// update state of peer
|
||||
|
||||
peer.set_state_timestamp(
|
||||
State::InitiationSent{
|
||||
hs : hs,
|
||||
ck : ck
|
||||
},
|
||||
&ts
|
||||
)?;
|
||||
|
||||
Ok(Output(None, None))
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ impl Peer {
|
||||
&self,
|
||||
state_new : State,
|
||||
timestamp_new : ×tamp::TAI64N
|
||||
) -> bool {
|
||||
) -> Result<(), HandshakeError> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let mut timestamp = self.timestamp.lock().unwrap();
|
||||
match *timestamp {
|
||||
@@ -84,15 +84,15 @@ impl Peer {
|
||||
// no prior timestamp know
|
||||
*state = state_new;
|
||||
*timestamp = Some(*timestamp_new);
|
||||
true
|
||||
Ok(())
|
||||
},
|
||||
Some(timestamp_old) => if timestamp::compare(×tamp_old, ×tamp_new) {
|
||||
// new timestamp is strictly greater
|
||||
*state = state_new;
|
||||
*timestamp = Some(*timestamp_new);
|
||||
true
|
||||
Ok(())
|
||||
} else {
|
||||
false
|
||||
Err(HandshakeError::OldTimestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
pub type TAI64N = [u8; 12];
|
||||
|
||||
pub fn zero() -> TAI64N {
|
||||
[0u8; 12]
|
||||
}
|
||||
|
||||
pub fn now() -> TAI64N {
|
||||
[0u8; 12] // TODO
|
||||
[0u8; 12] // TODO, return current timestamp
|
||||
}
|
||||
|
||||
pub fn compare(old : &TAI64N, new : &TAI64N) -> bool {
|
||||
|
||||
15
src/types.rs
15
src/types.rs
@@ -36,12 +36,11 @@ impl Error for ConfigError {
|
||||
// handshake error
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HandshakeError {}
|
||||
|
||||
impl HandshakeError {
|
||||
pub fn new() -> Self {
|
||||
HandshakeError{}
|
||||
}
|
||||
pub enum HandshakeError {
|
||||
DecryptionFailure,
|
||||
UnknownPublicKey,
|
||||
InvalidMessageFormat,
|
||||
OldTimestamp
|
||||
}
|
||||
|
||||
impl fmt::Display for HandshakeError {
|
||||
@@ -74,8 +73,8 @@ pub struct KeyPair {
|
||||
}
|
||||
|
||||
pub struct Output (
|
||||
Option<KeyPair>, // resulting key-pair of successful handshake
|
||||
Option<Vec<u8>> // message to send
|
||||
pub Option<KeyPair>, // resulting key-pair of successful handshake
|
||||
pub Option<Vec<u8>> // message to send
|
||||
);
|
||||
|
||||
// per-peer state machine
|
||||
|
||||
Reference in New Issue
Block a user