made rekeying noise compliant

This commit is contained in:
mamoniot 2023-03-14 18:14:01 -04:00
parent 7a7703a268
commit 2c607f72d8
No known key found for this signature in database
GPG key ID: ADCCDBBE0E3D3B3B
4 changed files with 33 additions and 27 deletions

View file

@ -269,13 +269,15 @@ pub fn hmac_sha512(key: &[u8], msg: &[u8]) -> [u8; HMAC_SHA512_SIZE] {
hm.finish()
}
#[inline(always)]
pub fn hmac_sha512_into(key: &[u8], msg: &[u8], md: &mut [u8]) {
pub fn hmac_sha512_secret256(key: &[u8], msg: &[u8]) -> Secret<32> {
let mut hm = HMACSHA512::new(key);
hm.update(msg);
hm.finish_into(md);
let mut md = [0u8; HMAC_SHA512_SIZE];
hm.finish_into(&mut md);
// With such a simple procedure hopefully the compiler implements the following line as a move from md and not a copy
// If not we ought to change this code so we don't leak a secret value on the stack
unsafe { Secret::from_bytes(&md[0..32]) }
}
pub fn hmac_sha512_secret(key: &[u8], msg: &[u8]) -> Secret<HMAC_SHA512_SIZE> {
let mut hm = HMACSHA512::new(key);
hm.update(msg);
@ -288,10 +290,3 @@ pub fn hmac_sha384(key: &[u8], msg: &[u8]) -> [u8; HMAC_SHA384_SIZE] {
hm.update(msg);
hm.finish()
}
#[inline(always)]
pub fn hmac_sha384_into(key: &[u8], msg: &[u8], md: &mut [u8]) {
let mut hm = HMACSHA384::new(key);
hm.update(msg);
hm.finish_into(md);
}

View file

@ -25,3 +25,4 @@ doc = false
zerotier-utils = { path = "../utils" }
zerotier-crypto = { path = "../crypto" }
pqc_kyber = { version = "0.4.0", default-features = false, features = ["kyber1024", "std"] }
hex-literal = "0.3.4"

View file

@ -8,6 +8,7 @@
use std::mem::size_of;
use hex_literal::hex;
use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES};
use zerotier_crypto::hash::SHA512_HASH_SIZE;
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
@ -25,10 +26,11 @@ pub const MIN_TRANSPORT_MTU: usize = 128;
pub const MAX_INIT_PAYLOAD_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE - ALICE_NOISE_XK_ACK_MIN_SIZE;
/// Initial value of 'h'
/// echo -n 'Noise_XKpsk3_P384_AESGCM_SHA384_hybridKyber1024' | shasum -a 384
pub(crate) const INITIAL_H: [u8; SHA512_HASH_SIZE] = [
0xd3, 0x18, 0x1c, 0xee, 0x05, 0x8d, 0x35, 0x06, 0x22, 0xcd, 0xfc, 0x46, 0x82, 0x2a, 0x60, 0x81, 0xf5, 0xe0, 0x47, 0x65, 0x57, 0x66, 0x53, 0x17, 0x95, 0xae, 0x33, 0x3c, 0x90, 0x83, 0x3b, 0xbf, 0x7f, 0x5e, 0xd7, 0x3d, 0x03, 0x39, 0x10, 0x03, 0x29, 0xfd, 0x2d, 0x59, 0xa0, 0x99, 0x56, 0x63, 0x18, 0x2d, 0x63, 0xb7, 0x8d, 0xd1, 0x7a, 0x2c, 0xf7, 0x92, 0x16, 0xdd, 0xfa, 0xf1, 0x05, 0x37,
];
/// echo -n 'Noise_XKpsk3_P384_AESGCM_SHA512_hybridKyber1024' | shasum -a 512
pub(crate) const INITIAL_H: [u8; SHA512_HASH_SIZE] = hex!("12ae70954e8d93bf7f73d0fe48d487155666f541e532f9461af5ef52ab90c8fd9259ef9e48f5adcf9af63f869805a570004ae095655dcaddbc226a50623b2b25");
/// Initial value of 'h'
/// echo -n 'Noise_KKpsk0_P384_AESGCM_SHA512' | shasum -a 512
pub(crate) const INITIAL_H_REKEY: [u8; SHA512_HASH_SIZE] = hex!("daeedd651ac9c5173f2eaaff996beebac6f3f1bfe9a70bb1cc54fa1fb2bf46260d71a3c4fb4d4ee36f654c31773a8a15e5d5be974a0668dc7db70f4e13ed172e");
/// Version 0: Noise_XK with NIST P-384 plus Kyber1024 hybrid exchange on session init.
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;

View file

@ -15,7 +15,7 @@ use std::sync::atomic::{AtomicI64, AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak};
use zerotier_crypto::aes::{Aes, AesGcm};
use zerotier_crypto::hash::{SHA512, hmac_sha512_secret};
use zerotier_crypto::hash::{SHA512, hmac_sha512_secret, hmac_sha512_secret256};
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey};
use zerotier_crypto::secret::Secret;
use zerotier_crypto::{random, secure_eq};
@ -1157,9 +1157,13 @@ impl<Application: ApplicationLayer> Context<Application> {
let noise_es = app.get_local_s_keypair().agree(&alice_e).ok_or(Error::FailedAuthentication)?;
let noise_ee = bob_e_secret.agree(&alice_e).ok_or(Error::FailedAuthentication)?;
let noise_se = bob_e_secret.agree(&session.static_public_key).ok_or(Error::FailedAuthentication)?;
let noise_psk_se_ee_es = hmac_sha512_secret(
let noise_ck_psk_es_ee_se = hmac_sha512_secret(
hmac_sha512_secret(
hmac_sha512_secret(key.ratchet_key.as_bytes(), noise_es.as_bytes()).as_bytes(),
hmac_sha512_secret(
hmac_sha512_secret(&INITIAL_H_REKEY, key.ratchet_key.as_bytes()).as_bytes(),
noise_es.as_bytes(),
)
.as_bytes(),
noise_ee.as_bytes(),
)
.as_bytes(),
@ -1172,7 +1176,7 @@ impl<Application: ApplicationLayer> Context<Application> {
let reply: &mut RekeyAck = byte_array_as_proto_buffer_mut(&mut reply_buf).unwrap();
reply.session_protocol_version = SESSION_PROTOCOL_VERSION;
reply.bob_e = *bob_e_secret.public_key_bytes();
reply.next_key_fingerprint = SHA512::hash(noise_psk_se_ee_es.as_bytes());
reply.next_key_fingerprint = SHA512::hash(noise_ck_psk_es_ee_se.as_bytes());
let counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?.get();
set_packet_header(
@ -1204,7 +1208,7 @@ impl<Application: ApplicationLayer> Context<Application> {
drop(state);
let mut state = session.state.write().unwrap();
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
noise_psk_se_ee_es,
noise_ck_psk_es_ee_se,
next_ratchet_count,
current_time,
counter,
@ -1255,9 +1259,13 @@ impl<Application: ApplicationLayer> Context<Application> {
let noise_es = alice_e_secret.agree(&session.static_public_key).ok_or(Error::FailedAuthentication)?;
let noise_ee = alice_e_secret.agree(&bob_e).ok_or(Error::FailedAuthentication)?;
let noise_se = app.get_local_s_keypair().agree(&bob_e).ok_or(Error::FailedAuthentication)?;
let noise_psk_se_ee_es = hmac_sha512_secret(
let noise_ck_psk_es_ee_se = hmac_sha512_secret(
hmac_sha512_secret(
hmac_sha512_secret(key.ratchet_key.as_bytes(), noise_es.as_bytes()).as_bytes(),
hmac_sha512_secret(
hmac_sha512_secret(&INITIAL_H_REKEY, key.ratchet_key.as_bytes()).as_bytes(),
noise_es.as_bytes(),
)
.as_bytes(),
noise_ee.as_bytes(),
)
.as_bytes(),
@ -1266,7 +1274,7 @@ impl<Application: ApplicationLayer> Context<Application> {
// We need to check that the key Bob is acknowledging matches the latest sent offer.
// Because of OOO, it might not, in which case this rekey must be cancelled and retried.
if secure_eq(&pkt.next_key_fingerprint, &SHA512::hash(noise_psk_se_ee_es.as_bytes())) {
if secure_eq(&pkt.next_key_fingerprint, &SHA512::hash(noise_ck_psk_es_ee_se.as_bytes())) {
if session.update_receive_window(incoming_counter) {
// The new "Alice" knows Bob has the key since this is an ACK, so she can go
// ahead and set current_key to the new key. Then when she sends something
@ -1276,7 +1284,7 @@ impl<Application: ApplicationLayer> Context<Application> {
let next_key_index = key_index ^ 1;
let mut state = session.state.write().unwrap();
let _ = state.keys[next_key_index].replace(SessionKey::new::<Application>(
noise_psk_se_ee_es,
noise_ck_psk_es_ee_se,
next_ratchet_count,
current_time,
session.send_counter.load(Ordering::Relaxed),
@ -1696,7 +1704,7 @@ fn mix_hash(h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; NOISE_HASHLEN] {
/// HMAC-SHA512 key derivation based on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 7)
/// Cryptographically this isn't meaningfully different from HMAC(key, [label]) but this is how NIST rolls.
/// These are the values we have assigned to the 5 variables involved in https://csrc.nist.gov/publications/detail/sp/800-108/final:
/// K_in = key, i = 0x01, Label = 'Z'||'T'||LABEL, Context = 0x00, L = 512 or 256 as a u16
/// K_in = key, i = 1u8, Label = b'Z'||b'T'||LABEL, Context = 0u8, L = 512u16 or 256u16
fn kbkdf512<const LABEL: u8>(key: &Secret<NOISE_HASHLEN>) -> Secret<NOISE_HASHLEN> {
hmac_sha512_secret(
key.as_bytes(),
@ -1713,7 +1721,7 @@ fn kbkdf512<const LABEL: u8>(key: &Secret<NOISE_HASHLEN>) -> Secret<NOISE_HASHLE
)
}
fn kbkdf256<const LABEL: u8>(key: &Secret<NOISE_HASHLEN>) -> Secret<32> {
hmac_sha512_secret(
hmac_sha512_secret256(
key.as_bytes(),
&[
1,
@ -1725,7 +1733,7 @@ fn kbkdf256<const LABEL: u8>(key: &Secret<NOISE_HASHLEN>) -> Secret<32> {
1u8,
0u8,
],
).first_n_clone()
)
}
fn prng32(mut x: u32) -> u32 {