Fix race condition on response processing
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user