Fix race condition on response processing

This commit is contained in:
Mathias Hall-Andersen
2019-08-31 15:46:18 +02:00
parent 51179f5433
commit 7e5852ec26

View File

@@ -1,3 +1,5 @@
#![allow(dead_code)]
// DH // DH
use x25519_dalek::PublicKey; use x25519_dalek::PublicKey;
use x25519_dalek::StaticSecret; use x25519_dalek::StaticSecret;
@@ -16,6 +18,7 @@ use generic_array::typenum::*;
use generic_array::*; use generic_array::*;
use clear_on_drop::clear_stack_on_return; use clear_on_drop::clear_stack_on_return;
use subtle::ConstantTimeEq;
use super::device::Device; use super::device::Device;
use super::messages::{NoiseInitiation, NoiseResponse}; use super::messages::{NoiseInitiation, NoiseResponse};
@@ -279,12 +282,12 @@ pub fn create_initiation<T: Copy, R: RngCore + CryptoRng>(
// update state of peer // update state of peer
peer.set_state(State::InitiationSent { *peer.state.lock() = State::InitiationSent {
hs, hs,
ck, ck,
eph_sk, eph_sk,
sender, sender,
}); };
Ok(()) Ok(())
}) })
@@ -295,7 +298,7 @@ pub fn consume_initiation<'a, T: Copy>(
msg: &NoiseInitiation, msg: &NoiseInitiation,
) -> Result<(&'a Peer<T>, TemporaryState), HandshakeError> { ) -> Result<(&'a Peer<T>, TemporaryState), HandshakeError> {
clear_stack_on_return(CLEAR_PAGES, || { clear_stack_on_return(CLEAR_PAGES, || {
// initialize state // initialize new state
let ck = INITIAL_CK; let ck = INITIAL_CK;
let hs = INITIAL_HS; let hs = INITIAL_HS;
@@ -327,6 +330,10 @@ pub fn consume_initiation<'a, T: Copy>(
let peer = device.lookup_pk(&PublicKey::from(pk))?; let peer = device.lookup_pk(&PublicKey::from(pk))?;
// reset initiation state
*peer.state.lock() = State::Reset;
// H := Hash(H || msg.static) // H := Hash(H || msg.static)
let hs = HASH!(&hs, &msg.f_static[..]); let hs = HASH!(&hs, &msg.f_static[..]);
@@ -354,9 +361,6 @@ pub fn consume_initiation<'a, T: Copy>(
let hs = HASH!(&hs, &msg.f_timestamp); let hs = HASH!(&hs, &msg.f_timestamp);
// clear initiation state
*peer.state.lock() = State::Reset;
// return state (to create response) // return state (to create response)
Ok((peer, (msg.f_sender.get(), eph_r_pk, hs, ck))) Ok((peer, (msg.f_sender.get(), eph_r_pk, hs, ck)))
@@ -425,6 +429,7 @@ pub fn create_response<T: Copy, R: RngCore + CryptoRng>(
// let hs = HASH!(&hs, &msg.f_empty_tag); // let hs = HASH!(&hs, &msg.f_empty_tag);
// derive key-pair // derive key-pair
let (key_recv, key_send) = KDF2!(&ck, &[]); let (key_recv, key_send) = KDF2!(&ck, &[]);
// return unconfirmed key-pair // return unconfirmed key-pair
@@ -444,13 +449,18 @@ pub fn create_response<T: Copy, R: RngCore + CryptoRng>(
}) })
} }
/* The state lock is released while processing the message to
* allow concurrent processing of potential responses to the initiation,
* in order to better mitigate DoS from malformed response messages.
*/
pub fn consume_response<T: Copy>( pub fn consume_response<T: Copy>(
device: &Device<T>, device: &Device<T>,
msg: &NoiseResponse, msg: &NoiseResponse,
) -> Result<Output<T>, HandshakeError> { ) -> Result<Output<T>, HandshakeError> {
clear_stack_on_return(CLEAR_PAGES, || { clear_stack_on_return(CLEAR_PAGES, || {
// retrieve peer and copy associated state // retrieve peer and copy initiation state
let peer = device.lookup_id(msg.f_receiver.get())?; let peer = device.lookup_id(msg.f_receiver.get())?;
let (hs, ck, sender, eph_sk) = match *peer.state.lock() { let (hs, ck, sender, eph_sk) = match *peer.state.lock() {
State::InitiationSent { State::InitiationSent {
hs, hs,
@@ -497,29 +507,43 @@ pub fn consume_response<T: Copy>(
// derive key-pair // derive key-pair
let birth = Instant::now();
let (key_send, key_recv) = KDF2!(&ck, &[]); let (key_send, key_recv) = KDF2!(&ck, &[]);
// null the state // check for new initiation sent while lock released
*peer.state.lock() = State::Reset; let mut state = peer.state.lock();
let update = match *state {
State::InitiationSent {
eph_sk: ref old, ..
} => old.to_bytes().ct_eq(&eph_sk.to_bytes()).into(),
_ => false,
};
// return confirmed key-pair if update {
// null the initiation state
// (to avoid replay of this response message)
*state = State::Reset;
Ok(( // return confirmed key-pair
Some(peer.identifier), // proves ownership of the public key (e.g. for updating the endpoint) Ok((
None, // no response message Some(peer.identifier),
Some(KeyPair { None,
birth: Instant::now(), Some(KeyPair {
initiator: true, birth,
send: Key { initiator: true,
id: sender, send: Key {
key: key_send.into(), id: sender,
}, key: key_send.into(),
recv: Key { },
id: msg.f_sender.get(), recv: Key {
key: key_recv.into(), id: msg.f_sender.get(),
}, key: key_recv.into(),
}), },
)) }),
))
} else {
Err(HandshakeError::InvalidState)
}
}) })
} }