diff options
Diffstat (limited to 'src/noise/noise.rs')
-rw-r--r-- | src/noise/noise.rs | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/src/noise/noise.rs b/src/noise/noise.rs new file mode 100644 index 0000000..980f1db --- /dev/null +++ b/src/noise/noise.rs @@ -0,0 +1,458 @@ +// 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 + ×tamp::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(), + }, + }), + )) +} |