Added checks for zero shared-secret

To mirror the behavior from the kernel module,
as per private correspondence with Jason.
This commit is contained in:
Mathias Hall-Andersen
2020-03-29 22:56:57 +02:00
parent 12a7b371d4
commit ded348d586
4 changed files with 42 additions and 13 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "wireguard-rs" name = "wireguard-rs"
version = "0.1.2" version = "0.1.3"
authors = ["Mathias Hall-Andersen <mathias@hall-andersen.dk>"] authors = ["Mathias Hall-Andersen <mathias@hall-andersen.dk>"]
edition = "2018" edition = "2018"

View File

@@ -1,8 +1,7 @@
use std::time::Instant; use std::time::Instant;
// DH // DH
use x25519_dalek::PublicKey; use x25519_dalek::{PublicKey, StaticSecret, SharedSecret};
use x25519_dalek::StaticSecret;
// HASH & MAC // HASH & MAC
use blake2::Blake2s; use blake2::Blake2s;
@@ -215,6 +214,21 @@ mod tests {
} }
} }
// Computes an X25519 shared secret.
//
// This function wraps dalek to add a zero-check.
// This is not recommended by the Noise specification,
// but implemented in the kernel with which we strive for absolute equivalent behavior.
#[inline(always)]
fn shared_secret(sk: &StaticSecret, pk: &PublicKey) -> Result<SharedSecret, HandshakeError> {
let ss = sk.diffie_hellman(pk);
if ss.as_bytes().ct_eq(&[0u8; 32]).into() {
Err(HandshakeError::InvalidSharedSecret)
} else {
Ok(ss)
}
}
pub(super) fn create_initiation<R: RngCore + CryptoRng, O>( pub(super) fn create_initiation<R: RngCore + CryptoRng, O>(
rng: &mut R, rng: &mut R,
keyst: &KeyState, keyst: &KeyState,
@@ -224,6 +238,12 @@ pub(super) fn create_initiation<R: RngCore + CryptoRng, O>(
msg: &mut NoiseInitiation, msg: &mut NoiseInitiation,
) -> Result<(), HandshakeError> { ) -> Result<(), HandshakeError> {
log::debug!("create initiation"); log::debug!("create initiation");
// check for zero shared-secret (see "shared_secret" note).
if peer.ss.ct_eq(&[0u8; 32]).into() {
return Err(HandshakeError::InvalidSharedSecret);
}
clear_stack_on_return(CLEAR_PAGES, || { clear_stack_on_return(CLEAR_PAGES, || {
// initialize state // initialize state
@@ -253,7 +273,7 @@ pub(super) fn create_initiation<R: RngCore + CryptoRng, O>(
// (C, k) := Kdf2(C, DH(E_priv, S_pub)) // (C, k) := Kdf2(C, DH(E_priv, S_pub))
let (ck, key) = KDF2!(&ck, eph_sk.diffie_hellman(&pk).as_bytes()); let (ck, key) = KDF2!(&ck, shared_secret(&eph_sk, &pk)?.as_bytes());
// msg.static := Aead(k, 0, S_pub, H) // msg.static := Aead(k, 0, S_pub, H)
@@ -270,6 +290,7 @@ pub(super) fn create_initiation<R: RngCore + CryptoRng, O>(
// (C, k) := Kdf2(C, DH(S_priv, S_pub)) // (C, k) := Kdf2(C, DH(S_priv, S_pub))
let (ck, key) = KDF2!(&ck, &peer.ss); let (ck, key) = KDF2!(&ck, &peer.ss);
// msg.timestamp := Aead(k, 0, Timestamp(), H) // msg.timestamp := Aead(k, 0, Timestamp(), H)
@@ -304,6 +325,7 @@ pub(super) fn consume_initiation<'a, O>(
msg: &NoiseInitiation, msg: &NoiseInitiation,
) -> Result<(&'a Peer<O>, PublicKey, TemporaryState), HandshakeError> { ) -> Result<(&'a Peer<O>, PublicKey, TemporaryState), HandshakeError> {
log::debug!("consume initiation"); log::debug!("consume initiation");
clear_stack_on_return(CLEAR_PAGES, || { clear_stack_on_return(CLEAR_PAGES, || {
// initialize new state // initialize new state
@@ -322,7 +344,7 @@ pub(super) fn consume_initiation<'a, O>(
// (C, k) := Kdf2(C, DH(E_priv, S_pub)) // (C, k) := Kdf2(C, DH(E_priv, S_pub))
let eph_r_pk = PublicKey::from(msg.f_ephemeral); let eph_r_pk = PublicKey::from(msg.f_ephemeral);
let (ck, key) = KDF2!(&ck, keyst.sk.diffie_hellman(&eph_r_pk).as_bytes()); let (ck, key) = KDF2!(&ck, shared_secret(&keyst.sk, &eph_r_pk)?.as_bytes());
// msg.static := Aead(k, 0, S_pub, H) // msg.static := Aead(k, 0, S_pub, H)
@@ -337,6 +359,12 @@ pub(super) fn consume_initiation<'a, O>(
let peer = device.lookup_pk(&PublicKey::from(pk))?; let peer = device.lookup_pk(&PublicKey::from(pk))?;
// check for zero shared-secret (see "shared_secret" note).
if peer.ss.ct_eq(&[0u8; 32]).into() {
return Err(HandshakeError::InvalidSharedSecret);
}
// reset initiation state // reset initiation state
*peer.state.lock() = State::Reset; *peer.state.lock() = State::Reset;
@@ -415,11 +443,11 @@ pub(super) fn create_response<R: RngCore + CryptoRng, O>(
// C := Kdf1(C, DH(E_priv, E_pub)) // C := Kdf1(C, DH(E_priv, E_pub))
let ck = KDF1!(&ck, eph_sk.diffie_hellman(&eph_r_pk).as_bytes()); let ck = KDF1!(&ck, shared_secret(&eph_sk, &eph_r_pk)?.as_bytes());
// C := Kdf1(C, DH(E_priv, S_pub)) // C := Kdf1(C, DH(E_priv, S_pub))
let ck = KDF1!(&ck, eph_sk.diffie_hellman(&pk).as_bytes()); let ck = KDF1!(&ck, shared_secret(&eph_sk, &pk)?.as_bytes());
// (C, tau, k) := Kdf3(C, Q) // (C, tau, k) := Kdf3(C, Q)
@@ -497,11 +525,11 @@ pub(super) fn consume_response<'a, O>(
// C := Kdf1(C, DH(E_priv, E_pub)) // C := Kdf1(C, DH(E_priv, E_pub))
let eph_r_pk = PublicKey::from(msg.f_ephemeral); let eph_r_pk = PublicKey::from(msg.f_ephemeral);
let ck = KDF1!(&ck, eph_sk.diffie_hellman(&eph_r_pk).as_bytes()); let ck = KDF1!(&ck, shared_secret(&eph_sk, &eph_r_pk)?.as_bytes());
// C := Kdf1(C, DH(E_priv, S_pub)) // C := Kdf1(C, DH(E_priv, S_pub))
let ck = KDF1!(&ck, keyst.sk.diffie_hellman(&eph_r_pk).as_bytes()); let ck = KDF1!(&ck, shared_secret(&keyst.sk, &eph_r_pk)?.as_bytes());
// (C, tau, k) := Kdf3(C, Q) // (C, tau, k) := Kdf3(C, Q)

View File

@@ -18,10 +18,9 @@ use super::types::*;
const TIME_BETWEEN_INITIATIONS: Duration = Duration::from_millis(20); const TIME_BETWEEN_INITIATIONS: Duration = Duration::from_millis(20);
/* Represents the recomputation and state of a peer. // Represents the state of a peer.
* //
* This type is only for internal use and not exposed. // This type is only for internal use and not exposed.
*/
pub(super) struct Peer<O> { pub(super) struct Peer<O> {
// opaque type which identifies a peer // opaque type which identifies a peer
pub opaque: O, pub opaque: O,

View File

@@ -40,6 +40,7 @@ pub enum HandshakeError {
UnknownPublicKey, UnknownPublicKey,
UnknownReceiverId, UnknownReceiverId,
InvalidMessageFormat, InvalidMessageFormat,
InvalidSharedSecret,
OldTimestamp, OldTimestamp,
InvalidState, InvalidState,
InvalidMac1, InvalidMac1,
@@ -50,6 +51,7 @@ pub enum HandshakeError {
impl fmt::Display for HandshakeError { impl fmt::Display for HandshakeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
HandshakeError::InvalidSharedSecret => write!(f, "Zero shared secret"),
HandshakeError::DecryptionFailure => write!(f, "Failed to AEAD:OPEN"), HandshakeError::DecryptionFailure => write!(f, "Failed to AEAD:OPEN"),
HandshakeError::UnknownPublicKey => write!(f, "Unknown public key"), HandshakeError::UnknownPublicKey => write!(f, "Unknown public key"),
HandshakeError::UnknownReceiverId => { HandshakeError::UnknownReceiverId => {