Move to nested handshake message structure

Having the nested structure:

    Handshake Message:
        Noise part (zerocopy message)
        MAC footer part (zerocopy message)

Greatly simplifies processing the MAC fields,
since the MAC footer covers the noise part, which can
be accessed as bytes using AsBytes.
This commit is contained in:
Mathias Hall-Andersen
2019-07-30 15:28:11 +02:00
parent f46f36ad29
commit 1cfd5aea1a
14 changed files with 266 additions and 1391 deletions

8
Cargo.lock generated
View File

@@ -70,7 +70,7 @@ dependencies = [
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -298,7 +298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "subtle"
version = "2.0.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -367,12 +367,14 @@ version = "0.1.0"
dependencies = [
"blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"x25519-dalek 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"zerocopy 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -445,7 +447,7 @@ dependencies = [
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55"
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
"checksum subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "702662512f3ddeb74a64ce2fbbf3707ee1b6bb663d28bb054e0779bbc720d926"
"checksum subtle 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01dca13cf6c3b179864ab3292bd794e757618d35a7766b7c46050c614ba00829"
"checksum syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)" = "eadc09306ca51a40555dd6fc2b415538e9e18bc9f870e47b1a524a79fe2dcf5e"
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"

View File

@@ -15,6 +15,11 @@ rust-crypto = "^0.2"
generic-array = "0.12.3"
zerocopy = "0.2.7"
byteorder = "1.3.1"
digest = "0.8.0"
[dependencies.x25519-dalek]
version = "^0.5"
[dependencies.subtle]
version = "2.1"
features = ["nightly"]

View File

@@ -7,7 +7,8 @@ use rand::rngs::OsRng;
use x25519_dalek::PublicKey;
use x25519_dalek::StaticSecret;
use super::messages;
use super::messages::{CookieReply, Initiation, Response};
use super::messages::{TYPE_COOKIEREPLY, TYPE_INITIATION, TYPE_RESPONSE};
use super::noise;
use super::peer::Peer;
use super::types::*;
@@ -170,20 +171,40 @@ where
/// * `msg` - Byte slice containing the message (untrusted input)
pub fn process(&self, msg: &[u8]) -> Result<Output<T>, HandshakeError> {
match msg.get(0) {
Some(&messages::TYPE_INITIATION) => {
Some(&TYPE_INITIATION) => {
let msg = Initiation::parse(msg)?;
// check mac footer and ratelimiter
// consume the initiation
let (peer, st) = noise::consume_initiation(self, msg)?;
let (peer, st) = noise::consume_initiation(self, &msg.noise)?;
// allocate new index for response
let sender = self.allocate(peer);
// create response (release id on error)
noise::create_response(peer, sender, st).map_err(|e| {
// create response (release id on error), TODO: take slice
let mut resp = Response::default();
noise::create_response(peer, sender, st, &mut resp.noise).map_err(|e| {
self.release(sender);
e
})
}
Some(&messages::TYPE_RESPONSE) => noise::consume_response(self, msg),
Some(&TYPE_RESPONSE) => {
let msg = Response::parse(msg)?;
// check mac footer and ratelimiter
noise::consume_response(self, &msg.noise)
}
Some(&TYPE_COOKIEREPLY) => {
let msg = CookieReply::parse(msg)?;
// validate cookie reply
// update cookie generator for peer
unimplemented!()
}
_ => Err(HandshakeError::InvalidMessageFormat),
}
}
@@ -235,9 +256,9 @@ where
#[cfg(test)]
mod tests {
use super::super::messages::*;
use super::*;
use hex;
use messages::*;
#[test]
fn handshake() {

View File

@@ -4,7 +4,7 @@ use blake2::Blake2s;
use subtle::ConstantTimeEq;
use x25519_dalek::PublicKey;
use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified};
use super::messages::{CookieReply, MacsFooter};
const LABEL_MAC1: &[u8] = b"mac1----";
const LABEL_COOKIE: &[u8] = b"cookie--";
@@ -38,22 +38,6 @@ macro_rules! MAC {
}};
}
#[repr(C)]
#[derive(Copy, Clone, FromBytes, AsBytes)]
pub struct MacsFooter {
pub f_mac1: [u8; SIZE_MAC],
pub f_mac2: [u8; SIZE_MAC],
}
impl Default for MacsFooter {
fn default() -> Self {
Self {
f_mac1: [0u8; SIZE_MAC],
f_mac2: [0u8; SIZE_MAC],
}
}
}
struct Generator {
mac1_key: [u8; 32],
cookie_value: [u8; 16],

View File

@@ -8,10 +8,10 @@ use byteorder::LittleEndian;
use zerocopy::byteorder::U32;
use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified};
use super::macs::MacsFooter;
use super::timestamp;
use super::types::*;
const SIZE_MAC: usize = 16;
const SIZE_TAG: usize = 16; // poly1305 tag
const SIZE_NONCE: usize = 16; // xchacha20 nonce
const SIZE_COOKIE: usize = 16; //
@@ -21,28 +21,20 @@ pub const TYPE_INITIATION: u8 = 1;
pub const TYPE_RESPONSE: u8 = 2;
pub const TYPE_COOKIEREPLY: u8 = 3;
#[repr(C)]
#[derive(Copy, Clone, FromBytes, AsBytes)]
pub struct Initiation {
f_type: U32<LittleEndian>,
pub f_sender: U32<LittleEndian>,
pub f_ephemeral: [u8; SIZE_X25519_POINT],
pub f_static: [u8; SIZE_X25519_POINT],
pub f_static_tag: [u8; SIZE_TAG],
pub f_timestamp: timestamp::TAI64N,
pub f_timestamp_tag: [u8; SIZE_TAG],
pub f_macs: MacsFooter,
}
/* Handshake messsages */
#[repr(C)]
#[derive(Copy, Clone, FromBytes, AsBytes)]
pub struct Response {
f_type: U32<LittleEndian>,
pub f_sender: U32<LittleEndian>,
pub f_receiver: U32<LittleEndian>,
pub f_ephemeral: [u8; SIZE_X25519_POINT],
pub f_empty_tag: [u8; SIZE_TAG],
pub f_macs: MacsFooter,
pub noise: NoiseResponse, // inner message covered by macs
pub macs: MacsFooter,
}
#[repr(C)]
#[derive(Copy, Clone, FromBytes, AsBytes)]
pub struct Initiation {
pub noise: NoiseInitiation, // inner message covered by macs
pub macs: MacsFooter,
}
#[repr(C)]
@@ -55,7 +47,120 @@ pub struct CookieReply {
pub f_cookie_tag: [u8; SIZE_TAG],
}
/* Inner sub-messages */
#[repr(C)]
#[derive(Copy, Clone, FromBytes, AsBytes)]
pub struct MacsFooter {
pub f_mac1: [u8; SIZE_MAC],
pub f_mac2: [u8; SIZE_MAC],
}
#[repr(C)]
#[derive(Copy, Clone, FromBytes, AsBytes)]
pub struct NoiseInitiation {
f_type: U32<LittleEndian>,
pub f_sender: U32<LittleEndian>,
pub f_ephemeral: [u8; SIZE_X25519_POINT],
pub f_static: [u8; SIZE_X25519_POINT],
pub f_static_tag: [u8; SIZE_TAG],
pub f_timestamp: timestamp::TAI64N,
pub f_timestamp_tag: [u8; SIZE_TAG]
}
#[repr(C)]
#[derive(Copy, Clone, FromBytes, AsBytes)]
pub struct NoiseResponse {
f_type: U32<LittleEndian>,
pub f_sender: U32<LittleEndian>,
pub f_receiver: U32<LittleEndian>,
pub f_ephemeral: [u8; SIZE_X25519_POINT],
pub f_empty_tag: [u8; SIZE_TAG]
}
/* Zero copy parsing of handshake messages */
impl Initiation {
pub fn parse<B: ByteSlice>(bytes: B) -> Result<LayoutVerified<B, Self>, HandshakeError> {
let msg: LayoutVerified<B, Self> =
LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?;
if msg.noise.f_type.get() != (TYPE_INITIATION as u32) {
return Err(HandshakeError::InvalidMessageFormat);
}
Ok(msg)
}
}
impl Response {
pub fn parse<B: ByteSlice>(bytes: B) -> Result<LayoutVerified<B, Self>, HandshakeError> {
let msg: LayoutVerified<B, Self> =
LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?;
if msg.noise.f_type.get() != (TYPE_RESPONSE as u32) {
return Err(HandshakeError::InvalidMessageFormat);
}
Ok(msg)
}
}
impl CookieReply {
pub fn parse<B: ByteSlice>(bytes: B) -> Result<LayoutVerified<B, Self>, HandshakeError> {
let msg: LayoutVerified<B, Self> =
LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?;
if msg.f_type.get() != (TYPE_COOKIEREPLY as u32) {
return Err(HandshakeError::InvalidMessageFormat);
}
Ok(msg)
}
}
/* Default values */
impl Default for Response {
fn default() -> Self {
Self {
noise: Default::default(),
macs: Default::default(),
}
}
}
impl Default for Initiation {
fn default() -> Self {
Self {
noise: Default::default(),
macs: Default::default(),
}
}
}
impl Default for CookieReply {
fn default() -> Self {
Self {
f_type: <U32<LittleEndian>>::new(TYPE_COOKIEREPLY as u32),
f_receiver: <U32<LittleEndian>>::ZERO,
f_nonce: [0u8; SIZE_NONCE],
f_cookie: [0u8; SIZE_COOKIE],
f_cookie_tag: [0u8; SIZE_TAG],
}
}
}
impl Default for MacsFooter {
fn default() -> Self {
Self {
f_mac1: [0u8; SIZE_MAC],
f_mac2: [0u8; SIZE_MAC],
}
}
}
impl Default for NoiseInitiation {
fn default() -> Self {
Self {
f_type: <U32<LittleEndian>>::new(TYPE_INITIATION as u32),
@@ -65,30 +170,58 @@ impl Default for Initiation {
f_static: [0u8; SIZE_X25519_POINT],
f_static_tag: [0u8; SIZE_TAG],
f_timestamp: timestamp::ZERO,
f_timestamp_tag: [0u8; SIZE_TAG],
f_macs: Default::default(),
f_timestamp_tag: [0u8; SIZE_TAG]
}
}
}
impl Initiation {
pub fn parse<B: ByteSlice>(bytes: B) -> Result<LayoutVerified<B, Self>, HandshakeError> {
let msg: LayoutVerified<B, Self> =
LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?;
if msg.f_type.get() != (TYPE_INITIATION as u32) {
return Err(HandshakeError::InvalidMessageFormat);
impl Default for NoiseResponse {
fn default() -> Self {
Self {
f_type: <U32<LittleEndian>>::new(TYPE_RESPONSE as u32),
f_sender: <U32<LittleEndian>>::ZERO,
f_receiver: <U32<LittleEndian>>::ZERO,
f_ephemeral: [0u8; SIZE_X25519_POINT],
f_empty_tag: [0u8; SIZE_TAG],
}
}
}
Ok(msg)
}
}
/* Debug formatting (for testing purposes) */
#[cfg(test)]
impl fmt::Debug for Initiation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Initiation {{ {:?} || {:?} }}", self.noise, self.macs)
}
}
#[cfg(test)]
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Response {{ {:?} || {:?} }}", self.noise, self.macs)
}
}
#[cfg(test)]
impl fmt::Debug for CookieReply {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,
"MessageInitiation {{ type = {}, sender = {}, ephemeral = {}, static = {}|{}, timestamp = {}|{} }}",
"CookieReply {{ type = {}, receiver = {}, nonce = {}, cookie = {}|{} }}",
self.f_type,
self.f_receiver,
hex::encode(self.f_nonce),
hex::encode(self.f_cookie),
hex::encode(self.f_cookie_tag)
)
}
}
#[cfg(test)]
impl fmt::Debug for NoiseInitiation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,
"NoiseInitiation {{ type = {}, sender = {}, ephemeral = {}, static = {}|{}, timestamp = {}|{} }}",
self.f_type.get(),
self.f_sender.get(),
hex::encode(self.f_ephemeral),
@@ -101,52 +234,10 @@ impl fmt::Debug for Initiation {
}
#[cfg(test)]
impl PartialEq for Initiation {
fn eq(&self, other: &Self) -> bool {
self.f_type.get() == other.f_type.get()
&& self.f_sender.get() == other.f_sender.get()
&& self.f_ephemeral[..] == other.f_ephemeral[..]
&& self.f_static[..] == other.f_static[..]
&& self.f_static_tag[..] == other.f_static_tag[..]
&& self.f_timestamp[..] == other.f_timestamp
&& self.f_timestamp_tag[..] == other.f_timestamp_tag
}
}
#[cfg(test)]
impl Eq for Initiation {}
impl Response {
pub fn parse<B: ByteSlice>(bytes: B) -> Result<LayoutVerified<B, Self>, HandshakeError> {
let msg: LayoutVerified<B, Self> =
LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?;
if msg.f_type.get() != (TYPE_RESPONSE as u32) {
return Err(HandshakeError::InvalidMessageFormat);
}
Ok(msg)
}
}
impl Default for Response {
fn default() -> Self {
Self {
f_type: <U32<LittleEndian>>::new(TYPE_RESPONSE as u32),
f_sender: <U32<LittleEndian>>::ZERO,
f_receiver: <U32<LittleEndian>>::ZERO,
f_ephemeral: [0u8; SIZE_X25519_POINT],
f_empty_tag: [0u8; SIZE_TAG],
f_macs: Default::default(),
}
}
}
#[cfg(test)]
impl fmt::Debug for Response {
impl fmt::Debug for NoiseResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,
"MessageResponse {{ type = {}, sender = {}, receiver = {}, ephemeral = {}, empty = |{} }}",
"NoiseResponse {{ type = {}, sender = {}, receiver = {}, ephemeral = {}, empty = |{} }}",
self.f_type,
self.f_sender,
self.f_receiver,
@@ -157,15 +248,46 @@ impl fmt::Debug for Response {
}
#[cfg(test)]
impl PartialEq for Response {
impl fmt::Debug for MacsFooter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,
"Macs {{ mac1 = {}, mac2 = {} }}",
hex::encode(self.f_mac1),
hex::encode(self.f_mac2)
)
}
}
/* Equality (for testing purposes) */
macro_rules! eq_as_bytes {
($type:path) => {
impl PartialEq for $type {
fn eq(&self, other: &Self) -> bool {
self.f_type == other.f_type
&& self.f_sender == other.f_sender
&& self.f_receiver == other.f_receiver
&& self.f_ephemeral == other.f_ephemeral
&& self.f_empty_tag == other.f_empty_tag
self.as_bytes() == other.as_bytes()
}
}
impl Eq for $type {}
}
}
#[cfg(test)]
eq_as_bytes!(Initiation);
#[cfg(test)]
eq_as_bytes!(Response);
#[cfg(test)]
eq_as_bytes!(CookieReply);
#[cfg(test)]
eq_as_bytes!(MacsFooter);
#[cfg(test)]
eq_as_bytes!(NoiseInitiation);
#[cfg(test)]
eq_as_bytes!(NoiseResponse);
#[cfg(test)]
mod tests {
@@ -175,22 +297,22 @@ mod tests {
fn message_response_identity() {
let mut msg: Response = Default::default();
msg.f_sender.set(146252);
msg.f_receiver.set(554442);
msg.f_ephemeral = [
msg.noise.f_sender.set(146252);
msg.noise.f_receiver.set(554442);
msg.noise.f_ephemeral = [
0xc1, 0x66, 0x0a, 0x0c, 0xdc, 0x0f, 0x6c, 0x51, 0x0f, 0xc2, 0xcc, 0x51, 0x52, 0x0c,
0xde, 0x1e, 0xf7, 0xf1, 0xca, 0x90, 0x86, 0x72, 0xad, 0x67, 0xea, 0x89, 0x45, 0x44,
0x13, 0x56, 0x52, 0x1f,
];
msg.f_empty_tag = [
msg.noise.f_empty_tag = [
0x60, 0x0e, 0x1e, 0x95, 0x41, 0x6b, 0x52, 0x05, 0xa2, 0x09, 0xe1, 0xbf, 0x40, 0x05,
0x2f, 0xde,
];
msg.f_macs.f_mac1 = [
msg.macs.f_mac1 = [
0xf2, 0xad, 0x40, 0xb5, 0xf7, 0xde, 0x77, 0x35, 0x89, 0x19, 0xb7, 0x5c, 0xf9, 0x54,
0x69, 0x29,
];
msg.f_macs.f_mac2 = [
msg.macs.f_mac2 = [
0x4f, 0xd2, 0x1b, 0xfe, 0x77, 0xe6, 0x2e, 0xc9, 0x07, 0xe2, 0x87, 0x17, 0xbb, 0xe5,
0xdf, 0xbb,
];
@@ -204,33 +326,33 @@ mod tests {
fn message_initiate_identity() {
let mut msg: Initiation = Default::default();
msg.f_sender.set(575757);
msg.f_ephemeral = [
msg.noise.f_sender.set(575757);
msg.noise.f_ephemeral = [
0xc1, 0x66, 0x0a, 0x0c, 0xdc, 0x0f, 0x6c, 0x51, 0x0f, 0xc2, 0xcc, 0x51, 0x52, 0x0c,
0xde, 0x1e, 0xf7, 0xf1, 0xca, 0x90, 0x86, 0x72, 0xad, 0x67, 0xea, 0x89, 0x45, 0x44,
0x13, 0x56, 0x52, 0x1f,
];
msg.f_static = [
msg.noise.f_static = [
0xdc, 0x33, 0x90, 0x15, 0x8f, 0x82, 0x3e, 0x06, 0x44, 0xa0, 0xde, 0x4c, 0x15, 0x6c,
0x5d, 0xa4, 0x65, 0x99, 0xf6, 0x6c, 0xa1, 0x14, 0x77, 0xf9, 0xeb, 0x6a, 0xec, 0xc3,
0x3c, 0xda, 0x47, 0xe1,
];
msg.f_static_tag = [
msg.noise.f_static_tag = [
0x45, 0xac, 0x8d, 0x43, 0xea, 0x1b, 0x2f, 0x02, 0x45, 0x5d, 0x86, 0x37, 0xee, 0x83,
0x6b, 0x42,
];
msg.f_timestamp = [
msg.noise.f_timestamp = [
0x4f, 0x1c, 0x60, 0xec, 0x0e, 0xf6, 0x36, 0xf0, 0x78, 0x28, 0x57, 0x42,
];
msg.f_timestamp_tag = [
msg.noise.f_timestamp_tag = [
0x60, 0x0e, 0x1e, 0x95, 0x41, 0x6b, 0x52, 0x05, 0xa2, 0x09, 0xe1, 0xbf, 0x40, 0x05,
0x2f, 0xde,
];
msg.f_macs.f_mac1 = [
msg.macs.f_mac1 = [
0xf2, 0xad, 0x40, 0xb5, 0xf7, 0xde, 0x77, 0x35, 0x89, 0x19, 0xb7, 0x5c, 0xf9, 0x54,
0x69, 0x29,
];
msg.f_macs.f_mac2 = [
msg.macs.f_mac2 = [
0x4f, 0xd2, 0x1b, 0xfe, 0x77, 0xe6, 0x2e, 0xc9, 0x07, 0xe2, 0x87, 0x17, 0xbb, 0xe5,
0xdf, 0xbb,
];

View File

@@ -18,7 +18,7 @@ use generic_array::GenericArray;
use zerocopy::AsBytes;
use super::device::Device;
use super::messages::{Initiation, Response};
use super::messages::{NoiseInitiation, NoiseResponse};
use super::peer::{Peer, State};
use super::timestamp;
use super::types::*;
@@ -142,7 +142,7 @@ pub fn create_initiation<T: Copy>(
sender: u32,
) -> Result<Vec<u8>, HandshakeError> {
let mut rng = OsRng::new().unwrap();
let mut msg: Initiation = Default::default();
let mut msg: NoiseInitiation = Default::default();
// initialize state
@@ -221,12 +221,8 @@ pub fn create_initiation<T: Copy>(
pub fn consume_initiation<'a, T: Copy>(
device: &'a Device<T>,
msg: &[u8],
msg: &NoiseInitiation,
) -> Result<(&'a Peer<T>, TemporaryState), HandshakeError> {
// parse message
let msg = Initiation::parse(msg)?;
// initialize state
let ck = INITIAL_CK;
@@ -297,10 +293,9 @@ pub fn create_response<T: Copy>(
peer: &Peer<T>,
sender: u32, // sending identifier
state: TemporaryState, // state from "consume_initiation"
msg: &mut NoiseResponse, // resulting response
) -> Result<Output<T>, HandshakeError> {
let mut rng = OsRng::new().unwrap();
let mut msg: Response = Default::default();
let (receiver, eph_r_pk, hs, ck) = state;
msg.f_sender.set(sender);
@@ -380,12 +375,8 @@ pub fn create_response<T: Copy>(
pub fn consume_response<T: Copy>(
device: &Device<T>,
msg: &[u8],
msg: &NoiseResponse,
) -> Result<Output<T>, HandshakeError> {
// parse message
let msg = Response::parse(msg)?;
// retrieve peer and associated state
let peer = device.lookup_id(msg.f_receiver.get())?;

View File

@@ -1,7 +1,7 @@
mod noise;
mod handshake;
mod types;
use noise::Device;
use handshake::Device;
use types::KeyPair;
fn main() {}

View File

@@ -1,315 +0,0 @@
use spin::RwLock;
use std::collections::HashMap;
use rand::prelude::*;
use rand::rngs::OsRng;
use x25519_dalek::PublicKey;
use x25519_dalek::StaticSecret;
use super::messages;
use super::noise;
use super::peer::Peer;
use super::types::*;
pub struct Device<T> {
pub sk: StaticSecret, // static secret key
pub pk: PublicKey, // static public key
pk_map: HashMap<[u8; 32], Peer<T>>, // public key -> peer state
id_map: RwLock<HashMap<u32, [u8; 32]>>, // receiver ids -> public key
}
/* A mutable reference to the device needs to be held during configuration.
* Wrapping the device in a RwLock enables peer config after "configuration time"
*/
impl<T> Device<T>
where
T: Copy,
{
/// Initialize a new handshake state machine
///
/// # Arguments
///
/// * `sk` - x25519 scalar representing the local private key
pub fn new(sk: StaticSecret) -> Device<T> {
Device {
pk: PublicKey::from(&sk),
sk: sk,
pk_map: HashMap::new(),
id_map: RwLock::new(HashMap::new()),
}
}
/// Add a new public key to the state machine
/// To remove public keys, you must create a new machine instance
///
/// # Arguments
///
/// * `pk` - The public key to add
/// * `identifier` - Associated identifier which can be used to distinguish the peers
pub fn add(&mut self, pk: PublicKey, identifier: T) -> Result<(), ConfigError> {
// check that the pk is not added twice
if let Some(_) = self.pk_map.get(pk.as_bytes()) {
return Err(ConfigError::new("Duplicate public key"));
};
// check that the pk is not that of the device
if *self.pk.as_bytes() == *pk.as_bytes() {
return Err(ConfigError::new(
"Public key corresponds to secret key of interface",
));
}
// map : pk -> new index
self.pk_map.insert(
*pk.as_bytes(),
Peer::new(identifier, pk, self.sk.diffie_hellman(&pk)),
);
Ok(())
}
/// Remove a peer by public key
/// To remove public keys, you must create a new machine instance
///
/// # Arguments
///
/// * `pk` - The public key of the peer to remove
///
/// # Returns
///
/// The call might fail if the public key is not found
pub fn remove(&mut self, pk: PublicKey) -> Result<(), ConfigError> {
// take write-lock on receive id table
let mut id_map = self.id_map.write();
// remove the peer
self.pk_map
.remove(pk.as_bytes())
.ok_or(ConfigError::new("Public key not in device"))?;
// pruge the id map (linear scan)
id_map.retain(|_, v| v != pk.as_bytes());
Ok(())
}
/// Add a psk to the peer
///
/// # Arguments
///
/// * `pk` - The public key of the peer
/// * `psk` - The psk to set / unset
///
/// # Returns
///
/// The call might fail if the public key is not found
pub fn set_psk(&mut self, pk: PublicKey, psk: Option<Psk>) -> Result<(), ConfigError> {
match self.pk_map.get_mut(pk.as_bytes()) {
Some(mut peer) => {
peer.psk = match psk {
Some(v) => v,
None => [0u8; 32],
};
Ok(())
}
_ => Err(ConfigError::new("No such public key")),
}
}
/// Return the psk for the peer
///
/// # Arguments
///
/// * `pk` - The public key of the peer
///
/// # Returns
///
/// A 32 byte array holding the PSK
///
/// The call might fail if the public key is not found
pub fn get_psk(&self, pk: PublicKey) -> Result<Psk, ConfigError> {
match self.pk_map.get(pk.as_bytes()) {
Some(peer) => Ok(peer.psk),
_ => Err(ConfigError::new("No such public key")),
}
}
/// Release an id back to the pool
///
/// # Arguments
///
/// * `id` - The (sender) id to release
pub fn release(&self, id: u32) {
let mut m = self.id_map.write();
debug_assert!(m.contains_key(&id), "Releasing id not allocated");
m.remove(&id);
}
/// Begin a new handshake
///
/// # Arguments
///
/// * `pk` - Public key of peer to initiate handshake for
pub fn begin(&self, pk: &PublicKey) -> Result<Vec<u8>, HandshakeError> {
match self.pk_map.get(pk.as_bytes()) {
None => Err(HandshakeError::UnknownPublicKey),
Some(peer) => {
let sender = self.allocate(peer);
noise::create_initiation(self, peer, sender)
}
}
}
/// Process a handshake message.
///
/// # Arguments
///
/// * `msg` - Byte slice containing the message (untrusted input)
pub fn process(&self, msg: &[u8]) -> Result<Output<T>, HandshakeError> {
match msg.get(0) {
Some(&messages::TYPE_INITIATION) => {
// consume the initiation
let (peer, st) = noise::consume_initiation(self, msg)?;
// allocate new index for response
let sender = self.allocate(peer);
// create response (release id on error)
noise::create_response(peer, sender, st).map_err(|e| {
self.release(sender);
e
})
}
Some(&messages::TYPE_RESPONSE) => noise::consume_response(self, msg),
_ => Err(HandshakeError::InvalidMessageFormat),
}
}
// Internal function
//
// Return the peer associated with the public key
pub(crate) fn lookup_pk(&self, pk: &PublicKey) -> Result<&Peer<T>, HandshakeError> {
self.pk_map
.get(pk.as_bytes())
.ok_or(HandshakeError::UnknownPublicKey)
}
// Internal function
//
// Return the peer currently associated with the receiver identifier
pub(crate) fn lookup_id(&self, id: u32) -> Result<&Peer<T>, HandshakeError> {
let im = self.id_map.read();
let pk = im.get(&id).ok_or(HandshakeError::UnknownReceiverId)?;
match self.pk_map.get(pk) {
Some(peer) => Ok(peer),
_ => unreachable!(), // if the id-lookup succeeded, the peer should exist
}
}
// Internal function
//
// Allocated a new receiver identifier for the peer
fn allocate(&self, peer: &Peer<T>) -> u32 {
let mut rng = OsRng::new().unwrap();
loop {
let id = rng.gen();
// check membership with read lock
if self.id_map.read().contains_key(&id) {
continue;
}
// take write lock and add index
let mut m = self.id_map.write();
if !m.contains_key(&id) {
m.insert(id, *peer.pk.as_bytes());
return id;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use hex;
use messages::*;
#[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);
// pick random psk
let mut psk = [0u8; 32];
rng.fill_bytes(&mut psk[..]);
// intialize devices on both ends
let mut dev1 = Device::new(sk1);
let mut dev2 = Device::new(sk2);
dev1.add(pk2, 1337).unwrap();
dev2.add(pk1, 2600).unwrap();
dev1.set_psk(pk2, Some(psk)).unwrap();
dev2.set_psk(pk1, Some(psk)).unwrap();
// do a few handshakes
for i in 0..10 {
println!("handshake : {}", i);
// create initiation
let msg1 = dev1.begin(&pk2).unwrap();
println!("msg1 = {}", hex::encode(&msg1[..]));
println!("msg1 = {:?}", Initiation::parse(&msg1[..]).unwrap());
// process initiation and create response
let (_, msg2, ks_r) = dev2.process(&msg1).unwrap();
let ks_r = ks_r.unwrap();
let msg2 = msg2.unwrap();
println!("msg2 = {}", hex::encode(&msg2[..]));
println!("msg2 = {:?}", Response::parse(&msg2[..]).unwrap());
assert!(!ks_r.confirmed, "Responders key-pair is confirmed");
// process response and obtain confirmed key-pair
let (_, msg3, ks_i) = dev1.process(&msg2).unwrap();
let ks_i = ks_i.unwrap();
assert!(msg3.is_none(), "Returned message after response");
assert!(ks_i.confirmed, "Initiators key-pair is not confirmed");
assert_eq!(ks_i.send, ks_r.recv, "KeyI.send != KeyR.recv");
assert_eq!(ks_i.recv, ks_r.send, "KeyI.recv != KeyR.send");
dev1.release(ks_i.send.id);
dev2.release(ks_r.send.id);
}
assert_eq!(dev1.get_psk(pk2).unwrap(), psk);
assert_eq!(dev2.get_psk(pk1).unwrap(), psk);
dev1.remove(pk2).unwrap();
dev2.remove(pk1).unwrap();
}
}

View File

@@ -1,208 +0,0 @@
#[cfg(test)]
use hex;
#[cfg(test)]
use std::fmt;
use byteorder::LittleEndian;
use zerocopy::byteorder::U32;
use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified};
use super::types::*;
const SIZE_TAG: usize = 16;
const SIZE_X25519_POINT: usize = 32;
const SIZE_TIMESTAMP: usize = 12;
pub const TYPE_INITIATION: u8 = 1;
pub const TYPE_RESPONSE: u8 = 2;
#[repr(C)]
#[derive(Copy, Clone, FromBytes, AsBytes)]
pub struct Initiation {
f_type: U32<LittleEndian>,
pub f_sender: U32<LittleEndian>,
pub f_ephemeral: [u8; SIZE_X25519_POINT],
pub f_static: [u8; SIZE_X25519_POINT],
pub f_static_tag: [u8; SIZE_TAG],
pub f_timestamp: [u8; SIZE_TIMESTAMP],
pub f_timestamp_tag: [u8; SIZE_TAG],
}
impl Default for Initiation {
fn default() -> Self {
Self {
f_type: <U32<LittleEndian>>::new(TYPE_INITIATION as u32),
f_sender: <U32<LittleEndian>>::ZERO,
f_ephemeral: [0u8; SIZE_X25519_POINT],
f_static: [0u8; SIZE_X25519_POINT],
f_static_tag: [0u8; SIZE_TAG],
f_timestamp: [0u8; SIZE_TIMESTAMP],
f_timestamp_tag: [0u8; SIZE_TAG],
}
}
}
impl Initiation {
pub fn parse<B: ByteSlice>(bytes: B) -> Result<LayoutVerified<B, Self>, HandshakeError> {
let msg: LayoutVerified<B, Self> =
LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?;
if msg.f_type.get() != (TYPE_INITIATION as u32) {
return Err(HandshakeError::InvalidMessageFormat);
}
Ok(msg)
}
}
#[cfg(test)]
impl fmt::Debug for Initiation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,
"MessageInitiation {{ type = {}, sender = {}, ephemeral = {}, static = {}|{}, timestamp = {}|{} }}",
self.f_type.get(),
self.f_sender.get(),
hex::encode(self.f_ephemeral),
hex::encode(self.f_static),
hex::encode(self.f_static_tag),
hex::encode(self.f_timestamp),
hex::encode(self.f_timestamp_tag)
)
}
}
#[cfg(test)]
impl PartialEq for Initiation {
fn eq(&self, other: &Self) -> bool {
self.f_type.get() == other.f_type.get()
&& self.f_sender.get() == other.f_sender.get()
&& self.f_ephemeral[..] == other.f_ephemeral[..]
&& self.f_static[..] == other.f_static[..]
&& self.f_static_tag[..] == other.f_static_tag[..]
&& self.f_timestamp[..] == other.f_timestamp
&& self.f_timestamp_tag[..] == other.f_timestamp_tag
}
}
#[cfg(test)]
impl Eq for Initiation {}
#[repr(C)]
#[derive(Copy, Clone, FromBytes, AsBytes)]
pub struct Response {
f_type: U32<LittleEndian>,
pub f_sender: U32<LittleEndian>,
pub f_receiver: U32<LittleEndian>,
pub f_ephemeral: [u8; SIZE_X25519_POINT],
pub f_empty_tag: [u8; SIZE_TAG],
}
impl Response {
pub fn parse<B: ByteSlice>(bytes: B) -> Result<LayoutVerified<B, Self>, HandshakeError> {
let msg: LayoutVerified<B, Self> =
LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?;
if msg.f_type.get() != (TYPE_RESPONSE as u32) {
return Err(HandshakeError::InvalidMessageFormat);
}
Ok(msg)
}
}
impl Default for Response {
fn default() -> Self {
Self {
f_type: <U32<LittleEndian>>::new(TYPE_RESPONSE as u32),
f_sender: <U32<LittleEndian>>::ZERO,
f_receiver: <U32<LittleEndian>>::ZERO,
f_ephemeral: [0u8; SIZE_X25519_POINT],
f_empty_tag: [0u8; SIZE_TAG],
}
}
}
#[cfg(test)]
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,
"MessageResponse {{ type = {}, sender = {}, receiver = {}, ephemeral = {}, empty = |{} }}",
self.f_type,
self.f_sender,
self.f_receiver,
hex::encode(self.f_ephemeral),
hex::encode(self.f_empty_tag)
)
}
}
#[cfg(test)]
impl PartialEq for Response {
fn eq(&self, other: &Self) -> bool {
self.f_type == other.f_type
&& self.f_sender == other.f_sender
&& self.f_receiver == other.f_receiver
&& self.f_ephemeral == other.f_ephemeral
&& self.f_empty_tag == other.f_empty_tag
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn message_response_identity() {
let mut msg: Response = Default::default();
msg.f_sender.set(146252);
msg.f_receiver.set(554442);
msg.f_ephemeral = [
0xc1, 0x66, 0x0a, 0x0c, 0xdc, 0x0f, 0x6c, 0x51, 0x0f, 0xc2, 0xcc, 0x51, 0x52, 0x0c,
0xde, 0x1e, 0xf7, 0xf1, 0xca, 0x90, 0x86, 0x72, 0xad, 0x67, 0xea, 0x89, 0x45, 0x44,
0x13, 0x56, 0x52, 0x1f,
];
msg.f_empty_tag = [
0x60, 0x0e, 0x1e, 0x95, 0x41, 0x6b, 0x52, 0x05, 0xa2, 0x09, 0xe1, 0xbf, 0x40, 0x05,
0x2f, 0xde,
];
let buf: Vec<u8> = msg.as_bytes().to_vec();
let msg_p = Response::parse(&buf[..]).unwrap();
assert_eq!(msg, *msg_p.into_ref());
}
#[test]
fn message_initiate_identity() {
let mut msg: Initiation = Default::default();
msg.f_sender.set(575757);
msg.f_ephemeral = [
0xc1, 0x66, 0x0a, 0x0c, 0xdc, 0x0f, 0x6c, 0x51, 0x0f, 0xc2, 0xcc, 0x51, 0x52, 0x0c,
0xde, 0x1e, 0xf7, 0xf1, 0xca, 0x90, 0x86, 0x72, 0xad, 0x67, 0xea, 0x89, 0x45, 0x44,
0x13, 0x56, 0x52, 0x1f,
];
msg.f_static = [
0xdc, 0x33, 0x90, 0x15, 0x8f, 0x82, 0x3e, 0x06, 0x44, 0xa0, 0xde, 0x4c, 0x15, 0x6c,
0x5d, 0xa4, 0x65, 0x99, 0xf6, 0x6c, 0xa1, 0x14, 0x77, 0xf9, 0xeb, 0x6a, 0xec, 0xc3,
0x3c, 0xda, 0x47, 0xe1,
];
msg.f_static_tag = [
0x45, 0xac, 0x8d, 0x43, 0xea, 0x1b, 0x2f, 0x02, 0x45, 0x5d, 0x86, 0x37, 0xee, 0x83,
0x6b, 0x42,
];
msg.f_timestamp = [
0x4f, 0x1c, 0x60, 0xec, 0x0e, 0xf6, 0x36, 0xf0, 0x78, 0x28, 0x57, 0x42,
];
msg.f_timestamp_tag = [
0x60, 0x0e, 0x1e, 0x95, 0x41, 0x6b, 0x52, 0x05, 0xa2, 0x09, 0xe1, 0xbf, 0x40, 0x05,
0x2f, 0xde,
];
let buf: Vec<u8> = msg.as_bytes().to_vec();
let msg_p = Initiation::parse(&buf[..]).unwrap();
assert_eq!(msg, *msg_p.into_ref());
}
}

View File

@@ -1,18 +0,0 @@
/* Implementation of the:
*
* Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s
*
* Protocol pattern, see: http://www.noiseprotocol.org/noise.html.
* For documentation.
*/
mod device;
mod messages;
mod noise;
mod peer;
mod timestamp;
mod types;
// publicly exposed interface
pub use device::Device;

View File

@@ -1,458 +0,0 @@
// DH
use x25519_dalek::PublicKey;
use x25519_dalek::StaticSecret;
// HASH & MAC
use blake2::Blake2s;
use hmac::Hmac;
// AEAD
use crypto::aead::{AeadDecryptor, AeadEncryptor};
use crypto::chacha20poly1305::ChaCha20Poly1305;
use rand::rngs::OsRng;
use generic_array::typenum::*;
use generic_array::GenericArray;
use zerocopy::AsBytes;
use super::device::Device;
use super::messages::{Initiation, Response};
use super::peer::{Peer, State};
use super::timestamp;
use super::types::*;
use crate::types::{Key, KeyPair};
// HMAC hasher (generic construction)
type HMACBlake2s = Hmac<Blake2s>;
// convenient alias to pass state temporarily into device.rs and back
type TemporaryState = (u32, PublicKey, GenericArray<u8, U32>, GenericArray<u8, U32>);
const SIZE_CK: usize = 32;
const SIZE_HS: usize = 32;
const SIZE_NONCE: usize = 8;
// C := Hash(Construction)
const INITIAL_CK: [u8; SIZE_CK] = [
0x60, 0xe2, 0x6d, 0xae, 0xf3, 0x27, 0xef, 0xc0, 0x2e, 0xc3, 0x35, 0xe2, 0xa0, 0x25, 0xd2, 0xd0,
0x16, 0xeb, 0x42, 0x06, 0xf8, 0x72, 0x77, 0xf5, 0x2d, 0x38, 0xd1, 0x98, 0x8b, 0x78, 0xcd, 0x36,
];
// H := Hash(C || Identifier)
const INITIAL_HS: [u8; SIZE_HS] = [
0x22, 0x11, 0xb3, 0x61, 0x08, 0x1a, 0xc5, 0x66, 0x69, 0x12, 0x43, 0xdb, 0x45, 0x8a, 0xd5, 0x32,
0x2d, 0x9c, 0x6c, 0x66, 0x22, 0x93, 0xe8, 0xb7, 0x0e, 0xe1, 0x9c, 0x65, 0xba, 0x07, 0x9e, 0xf3,
];
const ZERO_NONCE: [u8; SIZE_NONCE] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
macro_rules! HASH {
( $($input:expr),* ) => {{
use blake2::Digest;
let mut hsh = Blake2s::new();
$(
hsh.input($input);
)*
hsh.result()
}};
}
macro_rules! HMAC {
($key:expr, $($input:expr),*) => {{
use hmac::Mac;
let mut mac = HMACBlake2s::new_varkey($key).unwrap();
$(
mac.input($input);
)*
mac.result().code()
}};
}
macro_rules! KDF1 {
($ck:expr, $input:expr) => {{
let t0 = HMAC!($ck, $input);
let t1 = HMAC!(&t0, &[0x1]);
t1
}};
}
macro_rules! KDF2 {
($ck:expr, $input:expr) => {{
let t0 = HMAC!($ck, $input);
let t1 = HMAC!(&t0, &[0x1]);
let t2 = HMAC!(&t0, &t1, &[0x2]);
(t1, t2)
}};
}
macro_rules! KDF3 {
($ck:expr, $input:expr) => {{
let t0 = HMAC!($ck, $input);
let t1 = HMAC!(&t0, &[0x1]);
let t2 = HMAC!(&t0, &t1, &[0x2]);
let t3 = HMAC!(&t0, &t2, &[0x3]);
(t1, t2, t3)
}};
}
macro_rules! SEAL {
($key:expr, $aead:expr, $pt:expr, $ct:expr, $tag:expr) => {{
let mut aead = ChaCha20Poly1305::new($key, &ZERO_NONCE, $aead);
aead.encrypt($pt, $ct, $tag);
}};
}
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::*;
const IDENTIFIER: &[u8] = b"WireGuard v1 zx2c4 Jason@zx2c4.com";
const CONSTRUCTION: &[u8] = b"Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s";
#[test]
fn precomputed_chain_key() {
assert_eq!(INITIAL_CK[..], HASH!(CONSTRUCTION)[..]);
}
#[test]
fn precomputed_hash() {
assert_eq!(INITIAL_HS[..], HASH!(INITIAL_CK, IDENTIFIER)[..]);
}
}
pub fn create_initiation<T: Copy>(
device: &Device<T>,
peer: &Peer<T>,
sender: u32,
) -> Result<Vec<u8>, HandshakeError> {
let mut rng = OsRng::new().unwrap();
let mut msg: Initiation = Default::default();
// initialize state
let ck = INITIAL_CK;
let hs = INITIAL_HS;
let hs = HASH!(&hs, peer.pk.as_bytes());
msg.f_sender.set(sender);
// (E_priv, E_pub) := DH-Generate()
let eph_sk = StaticSecret::new(&mut rng);
let eph_pk = PublicKey::from(&eph_sk);
// C := Kdf(C, E_pub)
let ck = KDF1!(&ck, eph_pk.as_bytes());
// msg.ephemeral := E_pub
msg.f_ephemeral = *eph_pk.as_bytes();
// H := HASH(H, msg.ephemeral)
let hs = HASH!(&hs, msg.f_ephemeral);
// (C, k) := Kdf2(C, DH(E_priv, S_pub))
let (ck, key) = KDF2!(&ck, eph_sk.diffie_hellman(&peer.pk).as_bytes());
// msg.static := Aead(k, 0, S_pub, H)
SEAL!(
&key,
&hs, // ad
device.pk.as_bytes(), // pt
&mut msg.f_static, // ct
&mut msg.f_static_tag // tag
);
// 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)
SEAL!(
&key,
&hs, // ad
&timestamp::now(), // pt
&mut msg.f_timestamp, // ct
&mut msg.f_timestamp_tag // tag
);
// H := Hash(H || msg.timestamp)
let hs = HASH!(&hs, &msg.f_timestamp, &msg.f_timestamp_tag);
// update state of peer
peer.set_state(State::InitiationSent {
hs,
ck,
eph_sk,
sender,
});
// return message as vector
Ok(msg.as_bytes().to_vec())
}
pub fn consume_initiation<'a, T: Copy>(
device: &'a Device<T>,
msg: &[u8],
) -> Result<(&'a Peer<T>, TemporaryState), HandshakeError> {
// parse message
let msg = Initiation::parse(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_r_pk = PublicKey::from(msg.f_ephemeral);
let (ck, key) = KDF2!(&ck, device.sk.diffie_hellman(&eph_r_pk).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_pk(&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
)?;
// check and update timestamp
peer.check_timestamp(device, &ts)?;
// H := Hash(H || msg.timestamp)
let hs = HASH!(&hs, &msg.f_timestamp, &msg.f_timestamp_tag);
// return state (to create response)
Ok((peer, (msg.f_sender.get(), eph_r_pk, hs, ck)))
}
pub fn create_response<T: Copy>(
peer: &Peer<T>,
sender: u32, // sending identifier
state: TemporaryState, // state from "consume_initiation"
) -> Result<Output<T>, HandshakeError> {
let mut rng = OsRng::new().unwrap();
let mut msg: Response = Default::default();
let (receiver, eph_r_pk, hs, ck) = state;
msg.f_sender.set(sender);
msg.f_receiver.set(receiver);
// (E_priv, E_pub) := DH-Generate()
let eph_sk = StaticSecret::new(&mut rng);
let eph_pk = PublicKey::from(&eph_sk);
// C := Kdf1(C, E_pub)
let ck = KDF1!(&ck, eph_pk.as_bytes());
// msg.ephemeral := E_pub
msg.f_ephemeral = *eph_pk.as_bytes();
// H := Hash(H || msg.ephemeral)
let hs = HASH!(&hs, &msg.f_ephemeral);
// C := Kdf1(C, DH(E_priv, E_pub))
let ck = KDF1!(&ck, eph_sk.diffie_hellman(&eph_r_pk).as_bytes());
// C := Kdf1(C, DH(E_priv, S_pub))
let ck = KDF1!(&ck, eph_sk.diffie_hellman(&peer.pk).as_bytes());
// (C, tau, k) := Kdf3(C, Q)
let (ck, tau, key) = KDF3!(&ck, &peer.psk);
// H := Hash(H || tau)
let hs = HASH!(&hs, tau);
// msg.empty := Aead(k, 0, [], H)
SEAL!(
&key,
&hs, // ad
&[], // pt
&mut [], // ct
&mut msg.f_empty_tag // tag
);
/* not strictly needed
* // H := Hash(H || msg.empty)
* let hs = HASH!(&hs, &msg.f_empty_tag);
*/
// derive key-pair
// (verbose code, due to GenericArray -> [u8; 32] conversion)
let (key_recv, key_send) = KDF2!(&ck, &[]);
// return response and unconfirmed key-pair
Ok((
peer.identifier,
Some(msg.as_bytes().to_vec()),
Some(KeyPair {
confirmed: false,
send: Key {
id: sender,
key: key_send.into(),
},
recv: Key {
id: receiver,
key: key_recv.into(),
},
}),
))
}
pub fn consume_response<T: Copy>(
device: &Device<T>,
msg: &[u8],
) -> Result<Output<T>, HandshakeError> {
// parse message
let msg = Response::parse(msg)?;
// retrieve peer and associated state
let peer = device.lookup_id(msg.f_receiver.get())?;
let (hs, ck, sender, eph_sk) = match peer.get_state() {
State::Reset => Err(HandshakeError::InvalidState),
State::InitiationSent {
hs,
ck,
sender,
eph_sk,
} => Ok((hs, ck, sender, eph_sk)),
}?;
// C := Kdf1(C, E_pub)
let ck = KDF1!(&ck, &msg.f_ephemeral);
// H := Hash(H || msg.ephemeral)
let hs = HASH!(&hs, &msg.f_ephemeral);
// C := Kdf1(C, DH(E_priv, E_pub))
let eph_r_pk = PublicKey::from(msg.f_ephemeral);
let ck = KDF1!(&ck, eph_sk.diffie_hellman(&eph_r_pk).as_bytes());
// C := Kdf1(C, DH(E_priv, S_pub))
let ck = KDF1!(&ck, device.sk.diffie_hellman(&eph_r_pk).as_bytes());
// (C, tau, k) := Kdf3(C, Q)
let (ck, tau, key) = KDF3!(&ck, &peer.psk);
// H := Hash(H || tau)
let hs = HASH!(&hs, tau);
// msg.empty := Aead(k, 0, [], H)
OPEN!(
&key,
&hs, // ad
&mut [], // pt
&[], // ct
&msg.f_empty_tag // tag
)?;
// derive key-pair
let (key_send, key_recv) = KDF2!(&ck, &[]);
// return confirmed key-pair
Ok((
peer.identifier,
None,
Some(KeyPair {
confirmed: true,
send: Key {
id: sender,
key: key_send.into(),
},
recv: Key {
id: msg.f_sender.get(),
key: key_recv.into(),
},
}),
))
}

View File

@@ -1,136 +0,0 @@
use spin::Mutex;
use generic_array::typenum::U32;
use generic_array::GenericArray;
use x25519_dalek::PublicKey;
use x25519_dalek::SharedSecret;
use x25519_dalek::StaticSecret;
use super::device::Device;
use super::timestamp;
use super::types::*;
/* Represents the recomputation and state of a peer.
*
* This type is only for internal use and not exposed.
*/
pub struct Peer<T> {
// external identifier
pub(crate) identifier: T,
// mutable state
state: Mutex<State>,
timestamp: Mutex<Option<timestamp::TAI64N>>,
// constant state
pub(crate) pk: PublicKey, // public key of peer
pub(crate) ss: SharedSecret, // precomputed DH(static, static)
pub(crate) psk: Psk, // psk of peer
}
pub enum State {
Reset,
InitiationSent {
sender: u32, // assigned sender id
eph_sk: StaticSecret,
hs: GenericArray<u8, U32>,
ck: GenericArray<u8, U32>,
},
}
impl Clone for State {
fn clone(&self) -> State {
match self {
State::Reset => State::Reset,
State::InitiationSent {
sender,
eph_sk,
hs,
ck,
} => State::InitiationSent {
sender: *sender,
eph_sk: StaticSecret::from(eph_sk.to_bytes()),
hs: *hs,
ck: *ck,
},
}
}
}
impl<T> Peer<T>
where
T: Copy,
{
pub fn new(
identifier: T, // external identifier
pk: PublicKey, // public key of peer
ss: SharedSecret, // precomputed DH(static, static)
) -> Self {
Self {
identifier: identifier,
state: Mutex::new(State::Reset),
timestamp: Mutex::new(None),
pk: pk,
ss: ss,
psk: [0u8; 32],
}
}
/// Return the state of the peer
///
/// # Arguments
pub fn get_state(&self) -> State {
self.state.lock().clone()
}
/// Set the state of the peer unconditionally
///
/// # Arguments
///
pub fn set_state(&self, state_new: State) {
*self.state.lock() = state_new;
}
/// Set the mutable state of the peer conditioned on the timestamp being newer
///
/// # Arguments
///
/// * st_new - The updated state of the peer
/// * ts_new - The associated timestamp
pub fn check_timestamp(
&self,
device: &Device<T>,
timestamp_new: &timestamp::TAI64N,
) -> Result<(), HandshakeError> {
let mut state = self.state.lock();
let mut timestamp = self.timestamp.lock();
let update = match *timestamp {
None => true,
Some(timestamp_old) => {
if timestamp::compare(&timestamp_old, &timestamp_new) {
true
} else {
false
}
}
};
if update {
// release existing identifier
match *state {
State::InitiationSent { sender, .. } => device.release(sender),
_ => (),
}
// reset state and update timestamp
*state = State::Reset;
*timestamp = Some(*timestamp_new);
Ok(())
} else {
Err(HandshakeError::OldTimestamp)
}
}
}

View File

@@ -1,34 +0,0 @@
use std::time::{SystemTime, UNIX_EPOCH};
const TAI64_EPOCH: u64 = 0x4000000000000000;
pub type TAI64N = [u8; 12];
pub fn zero() -> TAI64N {
[0u8; 12]
}
pub fn now() -> TAI64N {
// get system time as duration
let sysnow = SystemTime::now();
let delta = sysnow.duration_since(UNIX_EPOCH).unwrap();
// convert to tai64n
let tai64_secs = delta.as_secs() + TAI64_EPOCH;
let tai64_nano = delta.subsec_nanos();
// serialize
let mut res = [0u8; 12];
res[..8].copy_from_slice(&tai64_secs.to_be_bytes()[..]);
res[8..].copy_from_slice(&tai64_nano.to_be_bytes()[..]);
res
}
pub fn compare(old: &TAI64N, new: &TAI64N) -> bool {
for i in 0..12 {
if new[i] > old[i] {
return true;
}
}
return false;
}

View File

@@ -1,81 +0,0 @@
use std::error::Error;
use std::fmt;
use crate::types::KeyPair;
/* Internal types for the noise IKpsk2 implementation */
// config error
#[derive(Debug)]
pub struct ConfigError(String);
impl ConfigError {
pub fn new(s: &str) -> Self {
ConfigError(s.to_string())
}
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ConfigError({})", self.0)
}
}
impl Error for ConfigError {
fn description(&self) -> &str {
&self.0
}
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
// handshake error
#[derive(Debug)]
pub enum HandshakeError {
DecryptionFailure,
UnknownPublicKey,
UnknownReceiverId,
InvalidMessageFormat,
OldTimestamp,
InvalidState,
}
impl fmt::Display for HandshakeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HandshakeError::DecryptionFailure => write!(f, "Failed to AEAD:OPEN"),
HandshakeError::UnknownPublicKey => write!(f, "Unknown public key"),
HandshakeError::UnknownReceiverId => {
write!(f, "Receiver id not allocated to any handshake")
}
HandshakeError::InvalidMessageFormat => write!(f, "Invalid handshake message format"),
HandshakeError::OldTimestamp => write!(f, "Timestamp is less/equal to the newest"),
HandshakeError::InvalidState => write!(f, "Message does not apply to handshake state"),
}
}
}
impl Error for HandshakeError {
fn description(&self) -> &str {
"Generic Handshake Error"
}
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
pub type Output<T> = (
T, // external identifier associated with peer
// (e.g. a reference or vector index)
Option<Vec<u8>>, // message to send
Option<KeyPair>, // resulting key-pair of successful handshake
);
// preshared key
pub type Psk = [u8; 32];