[noise] smol refactor
This commit is contained in:
parent
e8c4aa7d25
commit
a9ced39c41
|
@ -0,0 +1,186 @@
|
||||||
|
//! `Noise_IKpsk2` handshake initiator packets
|
||||||
|
|
||||||
|
use rand::{Rng, thread_rng};
|
||||||
|
use x25519_dalek::{PublicKey};
|
||||||
|
use crate::noise::error::NoiseError;
|
||||||
|
use crate::noise::handshake::{HANDSHAKE_INITIATOR_CHAIN_KEY, HANDSHAKE_INITIATOR_CHAIN_KEY_HASH, HandshakeState, needs_cookie};
|
||||||
|
use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt};
|
||||||
|
use crate::qcrypto::hashes::{qcrypto_hash_twice, qcrypto_mac};
|
||||||
|
use crate::qcrypto::hkdf::qcrypto_hkdf;
|
||||||
|
use crate::qcrypto::pki::{qcrypto_dh_ephemeral, qcrypto_dh_generate_ephemeral, qcrypto_dh_longterm};
|
||||||
|
use crate::qcrypto::{LABEL_MAC1, timestamp};
|
||||||
|
|
||||||
|
/// Generate a handshake initiator packet and encrypt it using the given session state, starting a new handshake state
|
||||||
|
/// # Errors
|
||||||
|
/// This function will error if encryption was unsuccessful
|
||||||
|
/// # Panics
|
||||||
|
/// While containing unwraps, this function will never panic.
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], NoiseError> {
|
||||||
|
session.s_pub_i = PublicKey::from(&session.s_priv_me);
|
||||||
|
session.s_pub_r = session.s_pub_them;
|
||||||
|
|
||||||
|
session.i_i = thread_rng().gen();
|
||||||
|
|
||||||
|
let mut msg = HandshakeInitiatorRaw {
|
||||||
|
sender: session.i_i.to_le_bytes(),
|
||||||
|
ephemeral: [0u8; 32],
|
||||||
|
static_pub: [0u8; 32 + 16],
|
||||||
|
timestamp: [0u8; 12 + 16],
|
||||||
|
mac1: [0u8; 16],
|
||||||
|
mac2: [0u8; 16]
|
||||||
|
};
|
||||||
|
|
||||||
|
session.ck = HANDSHAKE_INITIATOR_CHAIN_KEY;
|
||||||
|
session.h = HANDSHAKE_INITIATOR_CHAIN_KEY_HASH;
|
||||||
|
session.h = qcrypto_hash_twice(&session.h, session.s_pub_r.as_bytes());
|
||||||
|
|
||||||
|
let eph_keypair = qcrypto_dh_generate_ephemeral();
|
||||||
|
|
||||||
|
session.ck = qcrypto_hkdf::<1>(&session.ck, eph_keypair.1.as_bytes())[0];
|
||||||
|
session.e_pub_i = eph_keypair.1;
|
||||||
|
|
||||||
|
msg.ephemeral = eph_keypair.1.to_bytes();
|
||||||
|
|
||||||
|
session.h = qcrypto_hash_twice(&session.h, &msg.ephemeral);
|
||||||
|
|
||||||
|
let ci_k_pair = qcrypto_hkdf::<2>(&session.ck, qcrypto_dh_ephemeral(eph_keypair.0, &session.s_pub_r).as_bytes());
|
||||||
|
session.ck = ci_k_pair[0];
|
||||||
|
let k = ci_k_pair[1];
|
||||||
|
|
||||||
|
// This unwrap is safe because the output length is a known constant with these inputs
|
||||||
|
msg.static_pub = match qcrypto_aead(&k, 0, session.s_pub_i.as_bytes(), &session.h) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
||||||
|
}.try_into().unwrap();
|
||||||
|
|
||||||
|
session.h = qcrypto_hash_twice(&session.h, &msg.static_pub);
|
||||||
|
|
||||||
|
let ci_k_pair = qcrypto_hkdf::<2>(&session.ck, qcrypto_dh_longterm(&session.s_priv_me, &session.s_pub_r).as_bytes());
|
||||||
|
session.ck = ci_k_pair[0];
|
||||||
|
let k = ci_k_pair[1];
|
||||||
|
|
||||||
|
// This unwrap is safe because the output length is a known constant with these inputs
|
||||||
|
msg.timestamp = match qcrypto_aead(&k, 0, ×tamp().to_bytes(), &session.h) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
||||||
|
}.try_into().unwrap();
|
||||||
|
|
||||||
|
session.h = qcrypto_hash_twice(&session.h, &msg.timestamp);
|
||||||
|
|
||||||
|
Ok(msg.to_bytes(session))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct HandshakeInitiatorRaw {
|
||||||
|
sender: [u8; 4],
|
||||||
|
ephemeral: [u8; 32],
|
||||||
|
static_pub: [u8; 32 + 16],
|
||||||
|
timestamp: [u8; 12 + 16],
|
||||||
|
mac1: [u8; 16],
|
||||||
|
mac2: [u8; 16]
|
||||||
|
}
|
||||||
|
impl HandshakeInitiatorRaw {
|
||||||
|
fn to_bytes(&self, session: &HandshakeState) -> [u8; 148] {
|
||||||
|
let mut output = [0u8; 148];
|
||||||
|
|
||||||
|
output[0] = 1u8;
|
||||||
|
output[4..8].copy_from_slice(&self.sender);
|
||||||
|
output[8..40].copy_from_slice(&self.ephemeral);
|
||||||
|
output[40..88].copy_from_slice(&self.static_pub);
|
||||||
|
output[88..116].copy_from_slice(&self.timestamp);
|
||||||
|
|
||||||
|
let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &output[..116]);
|
||||||
|
|
||||||
|
output[116..132].copy_from_slice(&mac1);
|
||||||
|
|
||||||
|
let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &packet[..132]) } else { [0u8; 16] };
|
||||||
|
|
||||||
|
output[132..148].copy_from_slice(&mac2);
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_bytes(bytes: [u8; 148]) -> Self {
|
||||||
|
Self {
|
||||||
|
sender: bytes[4..8].try_into().unwrap(),
|
||||||
|
ephemeral: bytes[8..40].try_into().unwrap(),
|
||||||
|
static_pub: bytes[40..88].try_into().unwrap(),
|
||||||
|
timestamp: bytes[88..116].try_into().unwrap(),
|
||||||
|
mac1: bytes[116..132].try_into().unwrap(),
|
||||||
|
mac2: bytes[132..148].try_into().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a handshake initiator packet and encrypt it using the given session state, updating the session state with decrypted and authenticated values
|
||||||
|
/// # Errors
|
||||||
|
/// This function will error if decryption was unsuccessful
|
||||||
|
/// # Panics
|
||||||
|
/// While containing unwraps, this function will never panic.
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub fn handshake_init_from(session: &mut HandshakeState, packet: [u8; 148]) -> Result<(), NoiseError> {
|
||||||
|
let s_pub_i = session.s_pub_them;
|
||||||
|
let s_pub_r = PublicKey::from(&session.s_priv_me);
|
||||||
|
|
||||||
|
let msg = HandshakeInitiatorRaw::from_bytes(packet);
|
||||||
|
|
||||||
|
let i_i = u32::from_le_bytes(msg.sender);
|
||||||
|
|
||||||
|
let ck = HANDSHAKE_INITIATOR_CHAIN_KEY;
|
||||||
|
let h = HANDSHAKE_INITIATOR_CHAIN_KEY_HASH;
|
||||||
|
let h = qcrypto_hash_twice(&h, s_pub_r.as_bytes());
|
||||||
|
|
||||||
|
let ephemeral_public = msg.ephemeral;
|
||||||
|
let eph_pub = PublicKey::from(ephemeral_public);
|
||||||
|
let e_pub_i = eph_pub;
|
||||||
|
|
||||||
|
let ck = qcrypto_hkdf::<1>(&ck, eph_pub.as_bytes())[0];
|
||||||
|
|
||||||
|
let h = qcrypto_hash_twice(&h, &msg.ephemeral);
|
||||||
|
|
||||||
|
let ci_k_pair = qcrypto_hkdf::<2>(&ck, qcrypto_dh_longterm(&session.s_priv_me, &eph_pub).as_bytes());
|
||||||
|
let ck = ci_k_pair[0];
|
||||||
|
let k = ci_k_pair[1];
|
||||||
|
|
||||||
|
// This unwrap is safe because the output length is a known constant with these inputs
|
||||||
|
|
||||||
|
|
||||||
|
session.s_pub_i = PublicKey::from(<Vec<u8> as TryInto<[u8; 32]>>::try_into(match qcrypto_aead_decrypt(&k, 0, &msg.static_pub, &h) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
||||||
|
}).unwrap());
|
||||||
|
|
||||||
|
let h = qcrypto_hash_twice(&h, &msg.static_pub);
|
||||||
|
|
||||||
|
let ci_k_pair = qcrypto_hkdf::<2>(&ck, qcrypto_dh_longterm(&session.s_priv_me, &session.s_pub_i).as_bytes());
|
||||||
|
let ck = ci_k_pair[0];
|
||||||
|
let k = ci_k_pair[1];
|
||||||
|
|
||||||
|
// This unwrap is safe because the output length is a known constant with these inputs
|
||||||
|
let _sent_timestamp: [u8; 12] = match qcrypto_aead_decrypt(&k, 0, &msg.timestamp, &h) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
||||||
|
}.try_into().unwrap();
|
||||||
|
|
||||||
|
let h = qcrypto_hash_twice(&h, &msg.timestamp);
|
||||||
|
|
||||||
|
// we need to check mac1 and mac2
|
||||||
|
|
||||||
|
let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &packet[..116]);
|
||||||
|
|
||||||
|
let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &packet[..132]) } else { [0u8; 16] };
|
||||||
|
|
||||||
|
if mac1 != msg.mac1 || mac2 != msg.mac2 {
|
||||||
|
return Err(NoiseError::PacketUnauthenticated)
|
||||||
|
}
|
||||||
|
|
||||||
|
session.h = h;
|
||||||
|
session.ck = ck;
|
||||||
|
session.e_pub_i = e_pub_i;
|
||||||
|
session.s_pub_i = s_pub_i;
|
||||||
|
session.s_pub_r = s_pub_r;
|
||||||
|
session.i_i = i_i;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
//! `Noise_IKpsk2` handshake, specifically the way WireGuard defines it
|
||||||
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use tai64::Tai64N;
|
||||||
|
use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret};
|
||||||
|
use crate::qcrypto::timestamp;
|
||||||
|
|
||||||
|
pub mod initiator;
|
||||||
|
pub mod response;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests;
|
||||||
|
|
||||||
|
/// The Blake2s hash of the construction
|
||||||
|
pub const HANDSHAKE_INITIATOR_CHAIN_KEY: [u8; 32] = [
|
||||||
|
96, 226, 109, 174, 243, 39, 239, 192, 46, 195, 53, 226, 160, 37, 210, 208, 22, 235, 66, 6, 248,
|
||||||
|
114, 119, 245, 45, 56, 209, 152, 139, 120, 205, 54,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The hashed chaining key
|
||||||
|
pub const HANDSHAKE_INITIATOR_CHAIN_KEY_HASH: [u8; 32] = [
|
||||||
|
34, 17, 179, 97, 8, 26, 197, 102, 105, 18, 67, 219, 69, 138, 213, 50, 45, 156, 108, 102, 34,
|
||||||
|
147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Represents a cookie we got from the other peer
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Cookie {
|
||||||
|
time: Tai64N,
|
||||||
|
cookie: [u8; 16]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the internal handshake state. This does not really need to be messed with by outside users
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub struct HandshakeState {
|
||||||
|
pub h: [u8; 32],
|
||||||
|
pub ck: [u8; 32],
|
||||||
|
|
||||||
|
pub e_pub_i: PublicKey,
|
||||||
|
|
||||||
|
pub s_pub_i: PublicKey,
|
||||||
|
pub s_pub_r: PublicKey,
|
||||||
|
|
||||||
|
pub e_priv_me: EphemeralSecret,
|
||||||
|
pub s_priv_me: StaticSecret,
|
||||||
|
pub s_pub_them: PublicKey,
|
||||||
|
|
||||||
|
pub i_i: u32,
|
||||||
|
pub i_r: u32,
|
||||||
|
|
||||||
|
pub cookies: Vec<Cookie>
|
||||||
|
}
|
||||||
|
impl HandshakeState {
|
||||||
|
/// Determines if the state variables of this `HandshakeState` are the same as another
|
||||||
|
#[allow(clippy::suspicious_operation_groupings)]
|
||||||
|
pub fn is_eq(&self, other: &HandshakeState) -> bool {
|
||||||
|
self.h == other.h && self.ck == other.ck && self.e_pub_i == other.e_pub_i && self.s_pub_i == other.s_pub_i && self.s_pub_r == other.s_pub_r && self.i_i == other.i_i && self.i_r == other.i_r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Debug for HandshakeState {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("HandshakeState")
|
||||||
|
.field("h", &self.h)
|
||||||
|
.field("ck", &self.ck)
|
||||||
|
.field("e_pub_i", &self.e_pub_i)
|
||||||
|
.field("s_pub_i", &self.s_pub_i)
|
||||||
|
.field("s_pub_r", &self.s_pub_r)
|
||||||
|
.field("e_priv_me", &"<redacted>")
|
||||||
|
.field("s_priv_me", &"<redacted>")
|
||||||
|
.field("s_pub_them", &self.s_pub_them)
|
||||||
|
.field("i_i", &self.i_i)
|
||||||
|
.field("i_r", &self.i_r)
|
||||||
|
.field("cookies", &self.cookies).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines if a cookie MAC needs to be added to the packet being sent
|
||||||
|
pub fn needs_cookie(session: &HandshakeState) -> bool {
|
||||||
|
if !session.cookies.is_empty() {
|
||||||
|
let past_cookie_timeout = session.cookies[session.cookies.len()-1].time.duration_since(×tamp()).map_or(true, |t| t.as_secs() >= 120);
|
||||||
|
if !past_cookie_timeout {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
//! `Noise_IKpsk2` handshake response packet
|
|
@ -0,0 +1,46 @@
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
use crate::noise::handshake::HandshakeState;
|
||||||
|
use crate::noise::handshake::initiator::{handshake_init_from, handshake_init_to};
|
||||||
|
use crate::qcrypto::pki::qcrypto_dh_generate_longterm;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn noise_halfhandshake_test() {
|
||||||
|
let alice_keypair = qcrypto_dh_generate_longterm();
|
||||||
|
let bob_keypair = qcrypto_dh_generate_longterm();
|
||||||
|
|
||||||
|
let mut alice_session = HandshakeState {
|
||||||
|
h: [0u8; 32],
|
||||||
|
ck: [0u8; 32],
|
||||||
|
e_pub_i: PublicKey::from([0u8; 32]),
|
||||||
|
s_pub_i: PublicKey::from([0u8; 32]),
|
||||||
|
s_pub_r: PublicKey::from([0u8; 32]),
|
||||||
|
e_priv_me: EphemeralSecret::new(OsRng),
|
||||||
|
s_priv_me: alice_keypair.0,
|
||||||
|
s_pub_them: bob_keypair.1,
|
||||||
|
i_i: 0,
|
||||||
|
i_r: 0,
|
||||||
|
cookies: vec![],
|
||||||
|
};
|
||||||
|
let mut bob_session = HandshakeState {
|
||||||
|
h: [0u8; 32],
|
||||||
|
ck: [0u8; 32],
|
||||||
|
e_pub_i: PublicKey::from([0u8; 32]),
|
||||||
|
s_pub_i: PublicKey::from([0u8; 32]),
|
||||||
|
s_pub_r: PublicKey::from([0u8; 32]),
|
||||||
|
e_priv_me: EphemeralSecret::new(OsRng),
|
||||||
|
s_priv_me: bob_keypair.0,
|
||||||
|
s_pub_them: alice_keypair.1,
|
||||||
|
i_i: 0,
|
||||||
|
i_r: 0,
|
||||||
|
cookies: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let handshake_init = handshake_init_to(&mut alice_session).unwrap();
|
||||||
|
handshake_init_from(&mut bob_session, handshake_init).unwrap();
|
||||||
|
|
||||||
|
println!("{:?}", alice_session);
|
||||||
|
println!("{:?}", bob_session);
|
||||||
|
|
||||||
|
assert!(alice_session.is_eq(&bob_session));
|
||||||
|
}
|
|
@ -1,261 +0,0 @@
|
||||||
//! `Noise_IKpsk2` handshake initiator packets
|
|
||||||
|
|
||||||
use std::fmt::{Debug, Formatter};
|
|
||||||
use std::mem;
|
|
||||||
use rand::{Rng, thread_rng};
|
|
||||||
use tai64::Tai64N;
|
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret};
|
|
||||||
use crate::noise::error::NoiseError;
|
|
||||||
use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt};
|
|
||||||
use crate::qcrypto::hashes::{qcrypto_hash_twice, qcrypto_mac};
|
|
||||||
use crate::qcrypto::hkdf::qcrypto_hkdf;
|
|
||||||
use crate::qcrypto::pki::{qcrypto_dh_ephemeral, qcrypto_dh_generate_ephemeral, qcrypto_dh_longterm};
|
|
||||||
use crate::qcrypto::{LABEL_MAC1, timestamp};
|
|
||||||
|
|
||||||
/// The Blake2s hash of the construction
|
|
||||||
pub const HANDSHAKE_INITIATOR_CHAIN_KEY: [u8; 32] = [
|
|
||||||
96, 226, 109, 174, 243, 39, 239, 192, 46, 195, 53, 226, 160, 37, 210, 208, 22, 235, 66, 6, 248,
|
|
||||||
114, 119, 245, 45, 56, 209, 152, 139, 120, 205, 54,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// The hashed chaining key
|
|
||||||
pub const HANDSHAKE_INITIATOR_CHAIN_KEY_HASH: [u8; 32] = [
|
|
||||||
34, 17, 179, 97, 8, 26, 197, 102, 105, 18, 67, 219, 69, 138, 213, 50, 45, 156, 108, 102, 34,
|
|
||||||
147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Represents a cookie we got from the other peer
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Cookie {
|
|
||||||
time: Tai64N,
|
|
||||||
cookie: [u8; 16]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the internal handshake state. This does not really need to be messed with by outside users
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
|
||||||
pub struct HandshakeState {
|
|
||||||
pub h_i: [u8; 32],
|
|
||||||
pub c_i: [u8; 32],
|
|
||||||
|
|
||||||
pub e_pub_i: PublicKey,
|
|
||||||
|
|
||||||
pub s_pub_i: PublicKey,
|
|
||||||
pub s_pub_r: PublicKey,
|
|
||||||
|
|
||||||
pub e_priv_me: EphemeralSecret,
|
|
||||||
pub s_priv_me: StaticSecret,
|
|
||||||
pub s_pub_them: PublicKey,
|
|
||||||
|
|
||||||
pub i_i: u32,
|
|
||||||
pub i_r: u32,
|
|
||||||
|
|
||||||
pub cookies: Vec<Cookie>
|
|
||||||
}
|
|
||||||
impl HandshakeState {
|
|
||||||
/// Determines if the state variables of this `HandshakeState` are the same as another
|
|
||||||
pub fn is_eq(&self, other: &HandshakeState) -> bool {
|
|
||||||
self.h_i == other.h_i && self.c_i == other.c_i && self.e_pub_i == other.e_pub_i && self.s_pub_i == other.s_pub_i && self.s_pub_r == other.s_pub_r && self.i_i == other.i_i && self.i_r == other.i_r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Debug for HandshakeState {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("HandshakeState")
|
|
||||||
.field("h_i", &self.h_i)
|
|
||||||
.field("c_i", &self.c_i)
|
|
||||||
.field("e_pub_i", &self.e_pub_i)
|
|
||||||
.field("s_pub_i", &self.s_pub_i)
|
|
||||||
.field("s_pub_r", &self.s_pub_r)
|
|
||||||
.field("e_priv_me", &"<redacted>")
|
|
||||||
.field("s_priv_me", &"<redacted>")
|
|
||||||
.field("s_pub_them", &self.s_pub_them)
|
|
||||||
.field("i_i", &self.i_i)
|
|
||||||
.field("i_r", &self.i_r)
|
|
||||||
.field("cookies", &self.cookies).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a handshake initiator packet and encrypt it using the given session state, starting a new handshake state
|
|
||||||
/// # Errors
|
|
||||||
/// This function will error if encryption was unsuccessful
|
|
||||||
/// # Panics
|
|
||||||
/// While containing unwraps, this function will never panic.
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
|
||||||
pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], NoiseError> {
|
|
||||||
session.s_pub_i = PublicKey::from(&session.s_priv_me);
|
|
||||||
session.s_pub_r = session.s_pub_them;
|
|
||||||
|
|
||||||
session.i_i = thread_rng().gen();
|
|
||||||
|
|
||||||
let mut msg = HandshakeInitiatorRaw {
|
|
||||||
sender: session.i_i.to_le_bytes(),
|
|
||||||
ephemeral: [0u8; 32],
|
|
||||||
static_pub: [0u8; 32 + 16],
|
|
||||||
timestamp: [0u8; 12 + 16],
|
|
||||||
mac1: [0u8; 16],
|
|
||||||
mac2: [0u8; 16]
|
|
||||||
};
|
|
||||||
|
|
||||||
session.c_i = HANDSHAKE_INITIATOR_CHAIN_KEY;
|
|
||||||
session.h_i = HANDSHAKE_INITIATOR_CHAIN_KEY_HASH;
|
|
||||||
session.h_i = qcrypto_hash_twice(&session.h_i, session.s_pub_r.as_bytes());
|
|
||||||
|
|
||||||
let eph_keypair = qcrypto_dh_generate_ephemeral();
|
|
||||||
|
|
||||||
session.c_i = qcrypto_hkdf::<1>(&session.c_i, eph_keypair.1.as_bytes())[0];
|
|
||||||
session.e_pub_i = eph_keypair.1;
|
|
||||||
|
|
||||||
msg.ephemeral = eph_keypair.1.to_bytes();
|
|
||||||
|
|
||||||
session.h_i = qcrypto_hash_twice(&session.h_i, &msg.ephemeral);
|
|
||||||
|
|
||||||
let ci_k_pair = qcrypto_hkdf::<2>(&session.c_i, qcrypto_dh_ephemeral(eph_keypair.0, &session.s_pub_r).as_bytes());
|
|
||||||
session.c_i = ci_k_pair[0];
|
|
||||||
let k = ci_k_pair[1];
|
|
||||||
|
|
||||||
// This unwrap is safe because the output length is a known constant with these inputs
|
|
||||||
msg.static_pub = match qcrypto_aead(&k, 0, session.s_pub_i.as_bytes(), &session.h_i) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
|
||||||
}.try_into().unwrap();
|
|
||||||
|
|
||||||
session.h_i = qcrypto_hash_twice(&session.h_i, &msg.static_pub);
|
|
||||||
|
|
||||||
let ci_k_pair = qcrypto_hkdf::<2>(&session.c_i, qcrypto_dh_longterm(&session.s_priv_me, &session.s_pub_r).as_bytes());
|
|
||||||
session.c_i = ci_k_pair[0];
|
|
||||||
let k = ci_k_pair[1];
|
|
||||||
|
|
||||||
// This unwrap is safe because the output length is a known constant with these inputs
|
|
||||||
msg.timestamp = match qcrypto_aead(&k, 0, ×tamp().to_bytes(), &session.h_i) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
|
||||||
}.try_into().unwrap();
|
|
||||||
|
|
||||||
session.h_i = qcrypto_hash_twice(&session.h_i, &msg.timestamp);
|
|
||||||
|
|
||||||
Ok(msg.to_bytes(session))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct HandshakeInitiatorRaw {
|
|
||||||
sender: [u8; 4],
|
|
||||||
ephemeral: [u8; 32],
|
|
||||||
static_pub: [u8; 32 + 16],
|
|
||||||
timestamp: [u8; 12 + 16],
|
|
||||||
mac1: [u8; 16],
|
|
||||||
mac2: [u8; 16]
|
|
||||||
}
|
|
||||||
impl HandshakeInitiatorRaw {
|
|
||||||
fn to_bytes(&self, session: &HandshakeState) -> [u8; 148] {
|
|
||||||
let mut output = [0u8; 148];
|
|
||||||
|
|
||||||
output[0] = 1u8;
|
|
||||||
output[4..8].copy_from_slice(&self.sender);
|
|
||||||
output[8..40].copy_from_slice(&self.ephemeral);
|
|
||||||
output[40..88].copy_from_slice(&self.static_pub);
|
|
||||||
output[88..116].copy_from_slice(&self.timestamp);
|
|
||||||
|
|
||||||
let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &output[..116]);
|
|
||||||
|
|
||||||
output[116..132].copy_from_slice(&mac1);
|
|
||||||
|
|
||||||
let mut mac2 = [0u8; 16];
|
|
||||||
|
|
||||||
if !session.cookies.is_empty() {
|
|
||||||
let past_cookie_timeout = session.cookies[session.cookies.len()-1].time.duration_since(×tamp()).map_or(true, |t| t.as_secs() >= 120);
|
|
||||||
if !past_cookie_timeout {
|
|
||||||
mac2 = qcrypto_mac(&session.cookies[session.cookies.len()-1].cookie, &output[..132]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output[132..148].copy_from_slice(&mac2);
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_bytes(bytes: [u8; 148]) -> Self {
|
|
||||||
Self {
|
|
||||||
sender: bytes[4..8].try_into().unwrap(),
|
|
||||||
ephemeral: bytes[8..40].try_into().unwrap(),
|
|
||||||
static_pub: bytes[40..88].try_into().unwrap(),
|
|
||||||
timestamp: bytes[88..116].try_into().unwrap(),
|
|
||||||
mac1: bytes[116..132].try_into().unwrap(),
|
|
||||||
mac2: bytes[132..148].try_into().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a handshake initiator packet and encrypt it using the given session state, updating the session state with decrypted and authenticated values
|
|
||||||
/// # Errors
|
|
||||||
/// This function will error if decryption was unsuccessful
|
|
||||||
/// # Panics
|
|
||||||
/// While containing unwraps, this function will never panic.
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
|
||||||
pub fn handshake_init_from(session_orig: &mut HandshakeState, packet: [u8; 148]) -> Result<(), NoiseError> {
|
|
||||||
let mut session = session_orig.clone();
|
|
||||||
session.s_pub_i = session.s_pub_them;
|
|
||||||
session.s_pub_r = PublicKey::from(&session.s_priv_me);
|
|
||||||
|
|
||||||
let mut msg = HandshakeInitiatorRaw::from_bytes(packet);
|
|
||||||
|
|
||||||
session.i_i = u32::from_le_bytes(msg.sender);
|
|
||||||
|
|
||||||
session.c_i = HANDSHAKE_INITIATOR_CHAIN_KEY;
|
|
||||||
session.h_i = HANDSHAKE_INITIATOR_CHAIN_KEY_HASH;
|
|
||||||
session.h_i = qcrypto_hash_twice(&session.h_i, session.s_pub_r.as_bytes());
|
|
||||||
|
|
||||||
let ephemeral_public = msg.ephemeral;
|
|
||||||
let eph_pub = PublicKey::from(ephemeral_public);
|
|
||||||
session.e_pub_i = eph_pub;
|
|
||||||
|
|
||||||
session.c_i = qcrypto_hkdf::<1>(&session.c_i, eph_pub.as_bytes())[0];
|
|
||||||
|
|
||||||
session.h_i = qcrypto_hash_twice(&session.h_i, &msg.ephemeral);
|
|
||||||
|
|
||||||
let ci_k_pair = qcrypto_hkdf::<2>(&session.c_i, qcrypto_dh_longterm(&session.s_priv_me, &eph_pub).as_bytes());
|
|
||||||
session.c_i = ci_k_pair[0];
|
|
||||||
let k = ci_k_pair[1];
|
|
||||||
|
|
||||||
// This unwrap is safe because the output length is a known constant with these inputs
|
|
||||||
|
|
||||||
|
|
||||||
session.s_pub_i = PublicKey::from(<Vec<u8> as TryInto<[u8; 32]>>::try_into(match qcrypto_aead_decrypt(&k, 0, &msg.static_pub, &session.h_i) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
|
||||||
}).unwrap());
|
|
||||||
|
|
||||||
session.h_i = qcrypto_hash_twice(&session.h_i, &msg.static_pub);
|
|
||||||
|
|
||||||
let ci_k_pair = qcrypto_hkdf::<2>(&session.c_i, qcrypto_dh_longterm(&session.s_priv_me, &session.s_pub_i).as_bytes());
|
|
||||||
session.c_i = ci_k_pair[0];
|
|
||||||
let k = ci_k_pair[1];
|
|
||||||
|
|
||||||
// This unwrap is safe because the output length is a known constant with these inputs
|
|
||||||
let sent_timestamp: [u8; 12] = match qcrypto_aead_decrypt(&k, 0, &msg.timestamp, &session.h_i) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
|
||||||
}.try_into().unwrap();
|
|
||||||
|
|
||||||
session.h_i = qcrypto_hash_twice(&session.h_i, &msg.timestamp);
|
|
||||||
|
|
||||||
// we need to check mac1 and mac2
|
|
||||||
|
|
||||||
let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &packet[..116]);
|
|
||||||
|
|
||||||
let mut mac2 = [0u8; 16];
|
|
||||||
|
|
||||||
if !session.cookies.is_empty() {
|
|
||||||
let past_cookie_timeout = session.cookies[session.cookies.len()-1].time.duration_since(×tamp()).map_or(true, |t| t.as_secs() >= 120);
|
|
||||||
if !past_cookie_timeout {
|
|
||||||
mac2 = qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &packet[..132]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mac1 != msg.mac1 || mac2 != msg.mac2 {
|
|
||||||
return Err(NoiseError::PacketUnauthenticated)
|
|
||||||
}
|
|
||||||
|
|
||||||
mem::swap(session_orig, &mut session);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Contains structs and functions for serializing and deserializing different packets in the Noise_IKpsk2 handshake and data frames
|
//! Contains structs and functions for serializing and deserializing different packets in the Noise_IKpsk2 handshake and data frames
|
||||||
|
|
||||||
pub mod handshake_init;
|
pub mod handshake;
|
||||||
pub mod error;
|
pub mod error;
|
|
@ -1,7 +1,5 @@
|
||||||
use hex_lit::hex;
|
use hex_lit::hex;
|
||||||
use rand::rngs::OsRng;
|
use x25519_dalek::{PublicKey};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret};
|
|
||||||
use crate::noise::handshake_init::{handshake_init_from, handshake_init_to, HandshakeState};
|
|
||||||
use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt, qcrypto_xaead, qcrypto_xaead_decrypt};
|
use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt, qcrypto_xaead, qcrypto_xaead_decrypt};
|
||||||
use crate::qcrypto::{CONSTURCTION, IDENTIFIER};
|
use crate::qcrypto::{CONSTURCTION, IDENTIFIER};
|
||||||
use crate::qcrypto::hashes::{qcrypto_hash, qcrypto_hash_twice, qcrypto_hmac, qcrypto_mac};
|
use crate::qcrypto::hashes::{qcrypto_hash, qcrypto_hash_twice, qcrypto_hmac, qcrypto_mac};
|
||||||
|
@ -59,44 +57,3 @@ fn qcrypto_hkdf_test() {
|
||||||
let derived = qcrypto_hkdf::<1>(&[0u8; 32], &[0u8; 32]);
|
let derived = qcrypto_hkdf::<1>(&[0u8; 32], &[0u8; 32]);
|
||||||
assert_eq!(derived, [hex!("1090894613df8aef670b0b867e222daebc0d3e436cdddbc16c65855ab93cc91a")]);
|
assert_eq!(derived, [hex!("1090894613df8aef670b0b867e222daebc0d3e436cdddbc16c65855ab93cc91a")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn noise_halfhandshake_test() {
|
|
||||||
let alice_keypair = qcrypto_dh_generate_longterm();
|
|
||||||
let bob_keypair = qcrypto_dh_generate_longterm();
|
|
||||||
|
|
||||||
let mut alice_session = HandshakeState {
|
|
||||||
h_i: [0u8; 32],
|
|
||||||
c_i: [0u8; 32],
|
|
||||||
e_pub_i: PublicKey::from([0u8; 32]),
|
|
||||||
s_pub_i: PublicKey::from([0u8; 32]),
|
|
||||||
s_pub_r: PublicKey::from([0u8; 32]),
|
|
||||||
e_priv_me: EphemeralSecret::new(OsRng),
|
|
||||||
s_priv_me: alice_keypair.0,
|
|
||||||
s_pub_them: bob_keypair.1,
|
|
||||||
i_i: 0,
|
|
||||||
i_r: 0,
|
|
||||||
cookies: vec![],
|
|
||||||
};
|
|
||||||
let mut bob_session = HandshakeState {
|
|
||||||
h_i: [0u8; 32],
|
|
||||||
c_i: [0u8; 32],
|
|
||||||
e_pub_i: PublicKey::from([0u8; 32]),
|
|
||||||
s_pub_i: PublicKey::from([0u8; 32]),
|
|
||||||
s_pub_r: PublicKey::from([0u8; 32]),
|
|
||||||
e_priv_me: EphemeralSecret::new(OsRng),
|
|
||||||
s_priv_me: bob_keypair.0,
|
|
||||||
s_pub_them: alice_keypair.1,
|
|
||||||
i_i: 0,
|
|
||||||
i_r: 0,
|
|
||||||
cookies: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let handshake_init = handshake_init_to(&mut alice_session).unwrap();
|
|
||||||
handshake_init_from(&mut bob_session, handshake_init).unwrap();
|
|
||||||
|
|
||||||
println!("{:?}", alice_session);
|
|
||||||
println!("{:?}", bob_session);
|
|
||||||
|
|
||||||
assert!(alice_session.is_eq(&bob_session));
|
|
||||||
}
|
|
Loading…
Reference in New Issue