[noise] work on handshake init
This commit is contained in:
parent
4a87313da5
commit
f10a931a30
|
@ -1,4 +1,5 @@
|
|||
//! A simple, almost pure-rust, cross-platform `WireGuard` implementation.
|
||||
//! Designed to function similarly to boringtun, this crate has modules for cross-platform device drivers, the Noise_IKpsk2 handshake, and cryptography constructs required for the above.
|
||||
|
||||
#![warn(clippy::pedantic)]
|
||||
#![warn(clippy::nursery)]
|
||||
|
@ -7,7 +8,8 @@
|
|||
// This is an annoyance
|
||||
#![allow(clippy::must_use_candidate)]
|
||||
|
||||
pub mod drivers; // Baremetal network drivers for various platforms
|
||||
pub mod drivers;
|
||||
pub mod qcrypto;
|
||||
pub mod noise;
|
||||
|
||||
pub use cidr;
|
|
@ -0,0 +1,40 @@
|
|||
//! `Noise_IKpsk2` handshake errors
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// Represents a error while doing Noise handshake operations
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NoiseError {
|
||||
/// Represents an error while parsing a Noise packet
|
||||
PacketParseError(NoisePacketParseError),
|
||||
/// Represents an opaque error from ChaCha
|
||||
ChaCha20Error(chacha20poly1305::Error)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents and error while parsing a Noise packet
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NoisePacketParseError {
|
||||
/// Represents an invalid packet length while parsing a packet. The first was the expected length, the second was the actual length
|
||||
InvalidLength(usize, usize),
|
||||
/// Represents that the packet being parsed is of the incorrect type. The first value is the expected packet type, the second was the actual packet type
|
||||
WrongPacketType(usize, usize),
|
||||
}
|
||||
impl Display for NoisePacketParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match &self {
|
||||
Self::InvalidLength(expected, got) => write!(f, "Invalid packet length: expected {} got {}", expected, got),
|
||||
Self::WrongPacketType(expected, got) => write!(f, "Incorrect packet type: expected {} got {}", expected, got)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
//! `Noise_IKpsk2` handshake packets
|
||||
|
||||
use tai64::Tai64N;
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret};
|
||||
use crate::noise::error::NoiseError;
|
||||
use crate::qcrypto::aead::qcrypto_aead;
|
||||
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
|
||||
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 i_i: u32,
|
||||
pub i_r: u32,
|
||||
|
||||
pub cookies: Vec<Cookie>
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let mut msg = HandshakeInitiatorRaw {
|
||||
sender: session.i_i.to_le_bytes(),
|
||||
ephemeral: [0u8; 32],
|
||||
static_pub: [0u8; 32 + 16],
|
||||
timestamp: [0u8; 12 + 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];
|
||||
|
||||
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]
|
||||
}
|
||||
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];
|
||||
|
||||
let past_cookie_timeout = match session.cookies[session.cookies.len()-1].time.duration_since(×tamp()) {
|
||||
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]);
|
||||
}
|
||||
|
||||
output[132..148].copy_from_slice(&mac2);
|
||||
|
||||
output
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
//! Contains structs and functions for serializing and deserializing different packets in the Noise_IKpsk2 handshake and data frames
|
||||
|
||||
pub mod handshake;
|
||||
pub mod error;
|
|
@ -13,6 +13,14 @@ pub fn qcrypto_hash(input: &[u8]) -> [u8; 32] {
|
|||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
/// Given two varied length inputs, produce a 32-byte Blake2s hash digest
|
||||
pub fn qcrypto_hash_twice(input: &[u8], input2: &[u8]) -> [u8; 32] {
|
||||
let mut hasher = Blake2s256::new();
|
||||
Update::update(&mut hasher, input);
|
||||
Update::update(&mut hasher, input2);
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
/// Given a varied length MAC key and a varied length input, produce a 16-byte MAC digest using Blake2s
|
||||
/// # Panics
|
||||
/// This function will panic if the key is an incorrect size.
|
||||
|
|
|
@ -1,18 +1,33 @@
|
|||
//! Various public-key cryptography functions
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
use x25519_dalek::{PublicKey, SharedSecret, StaticSecret};
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret, StaticSecret};
|
||||
|
||||
type Keypair = (StaticSecret, PublicKey);
|
||||
type LongtermKeypair = (StaticSecret, PublicKey);
|
||||
|
||||
/// Generate a X25519 keypair
|
||||
pub fn qcrypto_dh_generate() -> Keypair {
|
||||
pub fn qcrypto_dh_generate_longterm() -> LongtermKeypair {
|
||||
let secret = StaticSecret::new(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
(secret, public)
|
||||
}
|
||||
|
||||
/// Perform Elliptic-Curve Diffie-Hellman between a secret and public key to get a shared secret value
|
||||
pub fn qcrypto_dh(secret: &StaticSecret, public: &PublicKey) -> SharedSecret {
|
||||
pub fn qcrypto_dh_longterm(secret: &StaticSecret, public: &PublicKey) -> SharedSecret {
|
||||
secret.diffie_hellman(public)
|
||||
}
|
||||
|
||||
|
||||
type EphemeralKeypair = (EphemeralSecret, PublicKey);
|
||||
|
||||
/// Generate a X25519 keypair
|
||||
pub fn qcrypto_dh_generate_ephemeral() -> EphemeralKeypair {
|
||||
let secret = EphemeralSecret::new(OsRng);
|
||||
let public = PublicKey::from(&secret);
|
||||
(secret, public)
|
||||
}
|
||||
|
||||
/// Perform Elliptic-Curve Diffie-Hellman between a secret and public key to get a shared secret value
|
||||
pub fn qcrypto_dh_ephemeral(secret: EphemeralSecret, public: &PublicKey) -> SharedSecret {
|
||||
secret.diffie_hellman(public)
|
||||
}
|
|
@ -1,17 +1,26 @@
|
|||
use hex_lit::hex;
|
||||
use x25519_dalek::PublicKey;
|
||||
use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt, qcrypto_xaead, qcrypto_xaead_decrypt};
|
||||
use crate::qcrypto::hashes::{qcrypto_hash, qcrypto_hmac, qcrypto_mac};
|
||||
use crate::qcrypto::{CONSTURCTION, IDENTIFIER};
|
||||
use crate::qcrypto::hashes::{qcrypto_hash, qcrypto_hash_twice, qcrypto_hmac, qcrypto_mac};
|
||||
use crate::qcrypto::hkdf::qcrypto_hkdf;
|
||||
use crate::qcrypto::pki::{qcrypto_dh, qcrypto_dh_generate};
|
||||
use crate::qcrypto::pki::{qcrypto_dh_longterm, qcrypto_dh_generate_longterm};
|
||||
|
||||
#[test]
|
||||
fn qcrypto_hash_test() {
|
||||
assert_eq!(qcrypto_hash(&[0u8; 32]), hex!("320b5ea99e653bc2b593db4130d10a4efd3a0b4cc2e1a6672b678d71dfbd33ad"))
|
||||
assert_eq!(qcrypto_hash(&[0u8; 32]), hex!("320b5ea99e653bc2b593db4130d10a4efd3a0b4cc2e1a6672b678d71dfbd33ad"));
|
||||
assert_eq!(qcrypto_hash(CONSTURCTION.as_bytes()), [
|
||||
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,
|
||||
]);
|
||||
assert_eq!(qcrypto_hash_twice(&qcrypto_hash(CONSTURCTION.as_bytes()), IDENTIFIER.as_bytes()), [
|
||||
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,
|
||||
]);
|
||||
}
|
||||
#[test]
|
||||
fn qcrypto_mac_test() {
|
||||
assert_eq!(qcrypto_mac(&[0u8; 32], &[0u8; 32]), hex!("086de86cfb256a2bc40740062bcf4dcc"))
|
||||
assert_eq!(qcrypto_mac(&[0u8; 32], &[0u8; 32]), hex!("086de86cfb256a2bc40740062bcf4dcc"));
|
||||
}
|
||||
#[test]
|
||||
fn qcrypto_hmac_test() {
|
||||
|
@ -20,16 +29,16 @@ fn qcrypto_hmac_test() {
|
|||
|
||||
#[test]
|
||||
fn qcrypto_pki_generate_test() {
|
||||
let keypair = qcrypto_dh_generate();
|
||||
let keypair = qcrypto_dh_generate_longterm();
|
||||
assert_eq!(keypair.1, PublicKey::from(&keypair.0))
|
||||
}
|
||||
#[test]
|
||||
fn qcrypto_dh_test() {
|
||||
let alice = qcrypto_dh_generate();
|
||||
let bob = qcrypto_dh_generate();
|
||||
let alice = qcrypto_dh_generate_longterm();
|
||||
let bob = qcrypto_dh_generate_longterm();
|
||||
|
||||
let secret = qcrypto_dh(&alice.0, &bob.1);
|
||||
let secret2 = qcrypto_dh(&bob.0, &alice.1);
|
||||
let secret = qcrypto_dh_longterm(&alice.0, &bob.1);
|
||||
let secret2 = qcrypto_dh_longterm(&bob.0, &alice.1);
|
||||
|
||||
assert_eq!(secret.as_bytes(), secret2.as_bytes())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue