[noise] handshake initiator working

This commit is contained in:
c0repwn3r 2022-12-14 14:38:47 -05:00
parent f10a931a30
commit 6332d16f9e
Signed by: core
GPG Key ID: FDBF740DADDCEECF
3 changed files with 170 additions and 12 deletions

View File

@ -10,14 +10,17 @@ pub enum NoiseError {
/// Represents an error while parsing a Noise packet
PacketParseError(NoisePacketParseError),
/// Represents an opaque error from ChaCha
ChaCha20Error(chacha20poly1305::Error)
ChaCha20Error(chacha20poly1305::Error),
/// Represents that the packet had a missing or incorrect cookie MAC.
PacketUnauthenticated
}
impl Error for NoiseError {}
impl Display for NoiseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self {
Self::PacketParseError(err) => write!(f, "{}", err),
Self::ChaCha20Error(error) => write!(f, "Encryption error: {}", error)
Self::ChaCha20Error(error) => write!(f, "Encryption error: {}", error),
Self::PacketUnauthenticated => write!(f, "Unauthenticated packet")
}
}
}

View File

@ -1,9 +1,10 @@
//! `Noise_IKpsk2` handshake packets
use std::fmt::{Debug, Formatter};
use tai64::Tai64N;
use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret};
use crate::noise::error::NoiseError;
use crate::qcrypto::aead::qcrypto_aead;
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};
@ -22,6 +23,7 @@ pub const HANDSHAKE_INITIATOR_CHAIN_KEY_HASH: [u8; 32] = [
];
/// Represents a cookie we got from the other peer
#[derive(Debug)]
pub struct Cookie {
time: Tai64N,
cookie: [u8; 16]
@ -41,12 +43,35 @@ pub struct HandshakeState {
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
@ -55,11 +80,16 @@ pub struct HandshakeState {
/// 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;
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;
@ -69,6 +99,7 @@ pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], Nois
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();
@ -106,7 +137,9 @@ struct HandshakeInitiatorRaw {
sender: [u8; 4],
ephemeral: [u8; 32],
static_pub: [u8; 32 + 16],
timestamp: [u8; 12 + 16]
timestamp: [u8; 12 + 16],
mac1: [u8; 16],
mac2: [u8; 16]
}
impl HandshakeInitiatorRaw {
fn to_bytes(&self, session: &HandshakeState) -> [u8; 148] {
@ -124,17 +157,96 @@ impl HandshakeInitiatorRaw {
let mut mac2 = [0u8; 16];
let past_cookie_timeout = match session.cookies[session.cookies.len()-1].time.duration_since(&timestamp()) {
Ok(t) => t.as_secs() >= 120,
Err(e) => true
};
if !session.cookies.is_empty() && !past_cookie_timeout {
mac2 = qcrypto_mac(&session.cookies[session.cookies.len()-1].cookie, &output[..132]);
if !session.cookies.is_empty() {
let past_cookie_timeout = session.cookies[session.cookies.len()-1].time.duration_since(&timestamp()).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: &mut HandshakeState, packet: [u8; 148]) -> Result<(), NoiseError> {
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.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(&timestamp()).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)
}
Ok(())
}

View File

@ -1,5 +1,7 @@
use hex_lit::hex;
use x25519_dalek::PublicKey;
use rand::rngs::OsRng;
use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret};
use crate::noise::handshake::{handshake_init_from, handshake_init_to, HandshakeState};
use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt, qcrypto_xaead, qcrypto_xaead_decrypt};
use crate::qcrypto::{CONSTURCTION, IDENTIFIER};
use crate::qcrypto::hashes::{qcrypto_hash, qcrypto_hash_twice, qcrypto_hmac, qcrypto_mac};
@ -57,3 +59,44 @@ fn qcrypto_hkdf_test() {
let derived = qcrypto_hkdf::<1>(&[0u8; 32], &[0u8; 32]);
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));
}