Merge pull request #1913 from zerotier/tetanus-sha512

More Noise Compliant ZSSP
This commit is contained in:
Monica Moniot 2023-03-21 08:42:35 -04:00 committed by GitHub
commit 1b789779d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 98 deletions

View file

@ -269,19 +269,19 @@ 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<const C: usize>(key: &[u8], msg: &[u8]) -> Secret<C> {
debug_assert!(C <= HMAC_SHA512_SIZE);
pub fn hmac_sha512_secret(key: &[u8], msg: &[u8]) -> Secret<HMAC_SHA512_SIZE> {
let mut hm = HMACSHA512::new(key);
hm.update(msg);
let buff = hm.finish();
unsafe { Secret::from_bytes(&buff[..C]) }
Secret::move_bytes(hm.finish())
}
#[inline(always)]
@ -290,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,8 +8,9 @@
use std::mem::size_of;
use hex_literal::hex;
use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES};
use zerotier_crypto::hash::SHA384_HASH_SIZE;
use zerotier_crypto::hash::SHA512_HASH_SIZE;
use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE;
use crate::error::Error;
@ -25,11 +26,13 @@ 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; SHA384_HASH_SIZE] = [
0x35, 0x27, 0x16, 0x62, 0x58, 0x04, 0x0c, 0x7a, 0x99, 0xa8, 0x0b, 0x49, 0xb2, 0x6b, 0x25, 0xfb, 0xf5, 0x26, 0x2a, 0x26, 0xe7, 0xb3, 0x70, 0xcb,
0x2c, 0x3c, 0xcb, 0x7f, 0xca, 0x20, 0x06, 0x91, 0x20, 0x55, 0x52, 0x8e, 0xd4, 0x3c, 0x97, 0xc3, 0xd5, 0x6c, 0xb4, 0x13, 0x02, 0x54, 0x83, 0x12,
];
/// 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;
@ -65,7 +68,7 @@ pub(crate) const MAX_NOISE_HANDSHAKE_FRAGMENTS: usize = 16; // enough room for p
pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_NOISE_HANDSHAKE_FRAGMENTS * MIN_TRANSPORT_MTU;
/// Size of keys used during derivation, mixing, etc. process.
pub(crate) const BASE_KEY_SIZE: usize = 64;
pub(crate) const NOISE_HASHLEN: usize = SHA512_HASH_SIZE;
pub(crate) const AES_256_KEY_SIZE: usize = 32;
pub(crate) const AES_HEADER_PROTECTION_KEY_SIZE: usize = 16;
@ -160,14 +163,14 @@ pub(crate) struct RekeyAck {
pub session_protocol_version: u8,
// -- start AES-GCM encrypted portion (using current key)
pub bob_e: [u8; P384_PUBLIC_KEY_SIZE],
pub next_key_fingerprint: [u8; SHA384_HASH_SIZE], // SHA384(next secret)
pub next_key_fingerprint: [u8; SHA512_HASH_SIZE], // SHA384(next secret)
// -- end AES-GCM encrypted portion
pub gcm_tag: [u8; AES_GCM_TAG_SIZE],
}
impl RekeyAck {
pub const ENC_START: usize = HEADER_SIZE + 1;
pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE + SHA384_HASH_SIZE;
pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE + SHA512_HASH_SIZE;
pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
}

View file

@ -15,8 +15,8 @@ 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::{hmac_sha512_secret, SHA384, SHA384_HASH_SIZE};
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE};
use zerotier_crypto::hash::{hmac_sha512_secret, hmac_sha512_secret256, SHA512};
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey};
use zerotier_crypto::secret::Secret;
use zerotier_crypto::{random, secure_eq};
@ -106,8 +106,8 @@ struct IncomingIncompleteSession<Application: ApplicationLayer> {
timestamp: i64,
alice_session_id: SessionId,
bob_session_id: SessionId,
noise_h: [u8; SHA384_HASH_SIZE],
noise_es_ee: Secret<BASE_KEY_SIZE>,
noise_h: [u8; NOISE_HASHLEN],
noise_ck_es_ee: Secret<NOISE_HASHLEN>,
hk: Secret<KYBER_SSBYTES>,
header_protection_key: Secret<AES_HEADER_PROTECTION_KEY_SIZE>,
bob_noise_e_secret: P384KeyPair,
@ -116,9 +116,9 @@ struct IncomingIncompleteSession<Application: ApplicationLayer> {
struct OutgoingSessionOffer {
last_retry_time: AtomicI64,
psk: Secret<BASE_KEY_SIZE>,
noise_h: [u8; SHA384_HASH_SIZE],
noise_es: Secret<P384_ECDH_SHARED_SECRET_SIZE>,
psk: Secret<NOISE_HASHLEN>,
noise_h: [u8; NOISE_HASHLEN],
noise_ck_es: Secret<NOISE_HASHLEN>,
alice_noise_e_secret: P384KeyPair,
alice_hk_secret: Secret<KYBER_SECRETKEYBYTES>,
metadata: Option<Vec<u8>>,
@ -139,7 +139,7 @@ enum Offer {
}
struct SessionKey {
ratchet_key: Secret<BASE_KEY_SIZE>, // Key used in derivation of the next session key
ratchet_key: Secret<NOISE_HASHLEN>, // Key used in derivation of the next session key
receive_cipher_pool: [Mutex<AesGcm<false>>; GCM_CIPHER_POOL_SIZE], // Pool of reusable sending ciphers
send_cipher_pool: [Mutex<AesGcm<true>>; GCM_CIPHER_POOL_SIZE], // Pool of reusable receiving ciphers
rekey_at_time: i64, // Rekey at or after this time (ticks)
@ -289,7 +289,7 @@ impl<Application: ApplicationLayer> Context<Application> {
mtu: usize,
remote_s_public_blob: &[u8],
remote_s_public_p384: P384PublicKey,
psk: Secret<BASE_KEY_SIZE>,
psk: Secret<NOISE_HASHLEN>,
metadata: Option<Vec<u8>>,
application_data: Application::Data,
current_time: i64,
@ -301,9 +301,14 @@ impl<Application: ApplicationLayer> Context<Application> {
let alice_noise_e_secret = P384KeyPair::generate();
let alice_noise_e = alice_noise_e_secret.public_key_bytes().clone();
let noise_es = alice_noise_e_secret.agree(&remote_s_public_p384).ok_or(Error::InvalidParameter)?;
let noise_h = mix_hash(&mix_hash(&INITIAL_H, remote_s_public_blob), &alice_noise_e);
let noise_ck_es = hmac_sha512_secret(&INITIAL_H, noise_es.as_bytes());
let alice_hk_secret = pqc_kyber::keypair(&mut random::SecureRandom::default());
let header_protection_key: Secret<AES_HEADER_PROTECTION_KEY_SIZE> = Secret(random::get_bytes_secure());
// Init aesgcm before we move noise_ck_es
let mut gcm = AesGcm::new(&kbkdf256::<KBKDF_KEY_USAGE_LABEL_KEX_ES>(&noise_ck_es));
let (local_session_id, session) = {
let mut sessions = self.sessions.write().unwrap();
@ -330,8 +335,8 @@ impl<Application: ApplicationLayer> Context<Application> {
outgoing_offer: Offer::NoiseXKInit(Box::new(OutgoingSessionOffer {
last_retry_time: AtomicI64::new(current_time),
psk,
noise_h: mix_hash(&mix_hash(&INITIAL_H, remote_s_public_blob), &alice_noise_e),
noise_es: noise_es.clone(),
noise_h,
noise_ck_es,
alice_noise_e_secret,
alice_hk_secret: Secret(alice_hk_secret.secret),
metadata,
@ -366,7 +371,6 @@ impl<Application: ApplicationLayer> Context<Application> {
}
// Encrypt and add authentication tag.
let mut gcm = AesGcm::new(&kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ES>(noise_es.as_bytes()));
gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1));
gcm.aad(&offer.noise_h);
gcm.crypt_in_place(&mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
@ -735,9 +739,11 @@ impl<Application: ApplicationLayer> Context<Application> {
let noise_h = mix_hash(&mix_hash(&INITIAL_H, app.get_local_s_public_blob()), alice_noise_e.as_bytes());
let noise_h_next = mix_hash(&noise_h, &pkt_assembled[HEADER_SIZE..]);
let noise_ck_es = hmac_sha512_secret(&INITIAL_H, noise_es.as_bytes());
drop(noise_es);
// Decrypt and authenticate init packet, also proving that caller knows our static identity.
let mut gcm = AesGcm::new(&kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ES>(noise_es.as_bytes()));
let mut gcm = AesGcm::new(&kbkdf256::<KBKDF_KEY_USAGE_LABEL_KEX_ES>(&noise_ck_es));
gcm.reset_init_gcm(&incoming_message_nonce);
gcm.aad(&noise_h);
gcm.crypt_in_place(&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
@ -754,12 +760,12 @@ impl<Application: ApplicationLayer> Context<Application> {
let alice_session_id = SessionId::new_from_array(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?;
let header_protection_key = Secret(pkt.header_protection_key);
// Create Bob's ephemeral keys and derive noise_es_ee by agreeing with Alice's. Also create
// Create Bob's ephemeral keys and derive noise_ck by agreeing with Alice's. Also create
// a Kyber ciphertext to send back to Alice.
let bob_noise_e_secret = P384KeyPair::generate();
let bob_noise_e = bob_noise_e_secret.public_key_bytes().clone();
let noise_es_ee = hmac_sha512_secret(
noise_es.as_bytes(),
let noise_ck_es_ee = hmac_sha512_secret(
noise_ck_es.as_bytes(),
bob_noise_e_secret.agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
);
let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
@ -786,7 +792,7 @@ impl<Application: ApplicationLayer> Context<Application> {
ack.bob_hk_ciphertext = bob_hk_ciphertext;
// Encrypt main section of reply and attach tag.
let mut gcm = AesGcm::new(&kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ES_EE>(noise_es_ee.as_bytes()));
let mut gcm = AesGcm::new(&kbkdf256::<KBKDF_KEY_USAGE_LABEL_KEX_ES_EE>(&noise_ck_es_ee));
gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1));
gcm.aad(&noise_h_next);
gcm.crypt_in_place(&mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]);
@ -819,7 +825,7 @@ impl<Application: ApplicationLayer> Context<Application> {
alice_session_id,
bob_session_id,
noise_h: mix_hash(&mix_hash(&noise_h_next, &bob_noise_e), &ack_packet[HEADER_SIZE..]),
noise_es_ee: noise_es_ee.clone(),
noise_ck_es_ee,
hk,
bob_noise_e_secret,
header_protection_key: Secret(pkt.header_protection_key),
@ -874,8 +880,8 @@ impl<Application: ApplicationLayer> Context<Application> {
// Derive noise_es_ee from Bob's ephemeral public key.
let bob_noise_e = P384PublicKey::from_bytes(&pkt.bob_noise_e).ok_or(Error::FailedAuthentication)?;
let noise_es_ee = hmac_sha512_secret::<BASE_KEY_SIZE>(
outgoing_offer.noise_es.as_bytes(),
let noise_ck_es_ee = hmac_sha512_secret(
outgoing_offer.noise_ck_es.as_bytes(),
outgoing_offer
.alice_noise_e_secret
.agree(&bob_noise_e)
@ -887,7 +893,7 @@ impl<Application: ApplicationLayer> Context<Application> {
let noise_h_next = mix_hash(&mix_hash(&outgoing_offer.noise_h, bob_noise_e.as_bytes()), &pkt_assembled[HEADER_SIZE..]);
// Decrypt and authenticate Bob's reply.
let mut gcm = AesGcm::new(&kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ES_EE>(noise_es_ee.as_bytes()));
let mut gcm = AesGcm::new(&kbkdf256::<KBKDF_KEY_USAGE_LABEL_KEX_ES_EE>(&noise_ck_es_ee));
gcm.reset_init_gcm(&incoming_message_nonce);
gcm.aad(&outgoing_offer.noise_h);
gcm.crypt_in_place(&mut pkt_assembled[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]);
@ -909,9 +915,9 @@ impl<Application: ApplicationLayer> Context<Application> {
// Packet fully authenticated
if session.update_receive_window(incoming_counter) {
let noise_es_ee_se_hk_psk = hmac_sha512_secret::<BASE_KEY_SIZE>(
hmac_sha512_secret::<BASE_KEY_SIZE>(noise_es_ee.as_bytes(), noise_se.as_bytes()).as_bytes(),
hmac_sha512_secret::<BASE_KEY_SIZE>(outgoing_offer.psk.as_bytes(), hk.as_bytes()).as_bytes(),
let noise_ck_es_ee_se_hk_psk = hmac_sha512_secret(
hmac_sha512_secret(noise_ck_es_ee.as_bytes(), noise_se.as_bytes()).as_bytes(),
hmac_sha512_secret(outgoing_offer.psk.as_bytes(), hk.as_bytes()).as_bytes(),
);
let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_ACK, 2);
@ -928,9 +934,10 @@ impl<Application: ApplicationLayer> Context<Application> {
let mut enc_start = ack_len;
ack_len = append_to_slice(&mut ack, ack_len, alice_s_public_blob)?;
let mut gcm = AesGcm::new(&kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ES_EE_HK>(
hmac_sha512_secret::<BASE_KEY_SIZE>(noise_es_ee.as_bytes(), hk.as_bytes()).as_bytes(),
));
let mut gcm = AesGcm::new(&kbkdf256::<KBKDF_KEY_USAGE_LABEL_KEX_ES_EE_HK>(&hmac_sha512_secret(
noise_ck_es_ee.as_bytes(),
hk.as_bytes(),
)));
gcm.reset_init_gcm(&reply_message_nonce);
gcm.aad(&noise_h_next);
gcm.crypt_in_place(&mut ack[enc_start..ack_len]);
@ -946,9 +953,7 @@ impl<Application: ApplicationLayer> Context<Application> {
enc_start = ack_len;
ack_len = append_to_slice(&mut ack, ack_len, metadata)?;
let mut gcm = AesGcm::new(&kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ES_EE_SE_HK_PSK>(
noise_es_ee_se_hk_psk.as_bytes(),
));
let mut gcm = AesGcm::new(&kbkdf256::<KBKDF_KEY_USAGE_LABEL_KEX_ES_EE_SE_HK_PSK>(&noise_ck_es_ee_se_hk_psk));
gcm.reset_init_gcm(&reply_message_nonce);
gcm.aad(&noise_h_next);
gcm.crypt_in_place(&mut ack[enc_start..ack_len]);
@ -961,7 +966,7 @@ impl<Application: ApplicationLayer> Context<Application> {
let mut state = session.state.write().unwrap();
let _ = state.remote_session_id.insert(bob_session_id);
let _ = state.keys[0].insert(SessionKey::new::<Application>(
noise_es_ee_se_hk_psk,
noise_ck_es_ee_se_hk_psk,
1,
current_time,
2,
@ -1034,9 +1039,10 @@ impl<Application: ApplicationLayer> Context<Application> {
let alice_static_public_blob = r.read_decrypt_auth(
alice_static_public_blob_size,
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ES_EE_HK>(
hmac_sha512_secret::<BASE_KEY_SIZE>(incoming.noise_es_ee.as_bytes(), incoming.hk.as_bytes()).as_bytes(),
),
kbkdf256::<KBKDF_KEY_USAGE_LABEL_KEX_ES_EE_HK>(&hmac_sha512_secret(
incoming.noise_ck_es_ee.as_bytes(),
incoming.hk.as_bytes(),
)),
&incoming.noise_h,
&incoming_message_nonce,
)?;
@ -1052,9 +1058,9 @@ impl<Application: ApplicationLayer> Context<Application> {
let noise_h_next = mix_hash(&noise_h_next, psk.as_bytes());
// Complete Noise_XKpsk3 on Bob's side.
let noise_es_ee_se_hk_psk = hmac_sha512_secret::<BASE_KEY_SIZE>(
hmac_sha512_secret::<BASE_KEY_SIZE>(
incoming.noise_es_ee.as_bytes(),
let noise_ck_es_ee_se_hk_psk = hmac_sha512_secret(
hmac_sha512_secret(
incoming.noise_ck_es_ee.as_bytes(),
incoming
.bob_noise_e_secret
.agree(&alice_noise_s)
@ -1062,7 +1068,7 @@ impl<Application: ApplicationLayer> Context<Application> {
.as_bytes(),
)
.as_bytes(),
hmac_sha512_secret::<BASE_KEY_SIZE>(psk.as_bytes(), incoming.hk.as_bytes()).as_bytes(),
hmac_sha512_secret(psk.as_bytes(), incoming.hk.as_bytes()).as_bytes(),
);
// Decrypt meta-data and verify the final key in the process. Copy meta-data
@ -1070,7 +1076,7 @@ impl<Application: ApplicationLayer> Context<Application> {
let alice_meta_data_size = r.read_u16()? as usize;
let alice_meta_data = r.read_decrypt_auth(
alice_meta_data_size,
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ES_EE_SE_HK_PSK>(noise_es_ee_se_hk_psk.as_bytes()),
kbkdf256::<KBKDF_KEY_USAGE_LABEL_KEX_ES_EE_SE_HK_PSK>(&noise_ck_es_ee_se_hk_psk),
&noise_h_next,
&incoming_message_nonce,
)?;
@ -1090,7 +1096,7 @@ impl<Application: ApplicationLayer> Context<Application> {
physical_mtu: self.default_physical_mtu.load(Ordering::Relaxed),
remote_session_id: Some(incoming.alice_session_id),
keys: [
Some(SessionKey::new::<Application>(noise_es_ee_se_hk_psk, 1, current_time, 2, true, true)),
Some(SessionKey::new::<Application>(noise_ck_es_ee_se_hk_psk, 1, current_time, 2, true, true)),
None,
],
current_key: 0,
@ -1150,9 +1156,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::<BASE_KEY_SIZE>(
hmac_sha512_secret::<BASE_KEY_SIZE>(
hmac_sha512_secret::<BASE_KEY_SIZE>(key.ratchet_key.as_bytes(), noise_es.as_bytes()).as_bytes(),
let noise_ck_psk_es_ee_se = hmac_sha512_secret(
hmac_sha512_secret(
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(),
@ -1165,7 +1175,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 = SHA384::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(
@ -1197,7 +1207,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,
@ -1248,9 +1258,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::<BASE_KEY_SIZE>(
hmac_sha512_secret::<BASE_KEY_SIZE>(
hmac_sha512_secret::<BASE_KEY_SIZE>(key.ratchet_key.as_bytes(), noise_es.as_bytes()).as_bytes(),
let noise_ck_psk_es_ee_se = hmac_sha512_secret(
hmac_sha512_secret(
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(),
@ -1259,7 +1273,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, &SHA384::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
@ -1269,7 +1283,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),
@ -1390,10 +1404,10 @@ impl<Application: ApplicationLayer> Session<Application> {
}
/// Get the ratchet count and a hash fingerprint of the current active key.
pub fn key_info(&self) -> Option<(u64, [u8; 48])> {
pub fn key_info(&self) -> Option<(u64, [u8; NOISE_HASHLEN])> {
let state = self.state.read().unwrap();
if let Some(key) = state.keys[state.current_key].as_ref() {
Some((key.ratchet_count, SHA384::hash(key.ratchet_key.as_bytes())))
Some((key.ratchet_count, SHA512::hash(key.ratchet_key.as_bytes())))
} else {
None
}
@ -1582,15 +1596,15 @@ fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBu
impl SessionKey {
fn new<Application: ApplicationLayer>(
key: Secret<BASE_KEY_SIZE>,
key: Secret<NOISE_HASHLEN>,
ratchet_count: u64,
current_time: i64,
current_counter: u64,
bob: bool,
confirmed: bool,
) -> Self {
let a2b = kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB>(key.as_bytes());
let b2a = kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(key.as_bytes());
let a2b = kbkdf256::<KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB>(&key);
let b2a = kbkdf256::<KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(&key);
let (receive_key, send_key) = if bob {
(a2b, b2a)
} else {
@ -1599,7 +1613,7 @@ impl SessionKey {
let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(AesGcm::new(&receive_key)));
let send_cipher_pool = std::array::from_fn(|_| Mutex::new(AesGcm::new(&send_key)));
Self {
ratchet_key: kbkdf::<BASE_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_RATCHET>(key.as_bytes()),
ratchet_key: kbkdf512::<KBKDF_KEY_USAGE_LABEL_RATCHET>(&key),
receive_cipher_pool,
send_cipher_pool,
rekey_at_time: current_time
@ -1679,8 +1693,8 @@ fn append_to_slice(s: &mut [u8], p: usize, d: &[u8]) -> Result<usize, Error> {
}
/// MixHash to update 'h' during negotiation.
fn mix_hash(h: &[u8; SHA384_HASH_SIZE], m: &[u8]) -> [u8; SHA384_HASH_SIZE] {
let mut hasher = SHA384::new();
fn mix_hash(h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; NOISE_HASHLEN] {
let mut hasher = SHA512::new();
hasher.update(h);
hasher.update(m);
hasher.finish()
@ -1688,22 +1702,13 @@ fn mix_hash(h: &[u8; SHA384_HASH_SIZE], m: &[u8]) -> [u8; SHA384_HASH_SIZE] {
/// 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.
fn kbkdf<const OUTPUT_BYTES: usize, const LABEL: u8>(key: &[u8]) -> Secret<OUTPUT_BYTES> {
//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 = (OUTPUT_BYTES * 8)
hmac_sha512_secret(
key,
&[
1,
b'Z',
b'T',
LABEL,
0x00,
0,
(((OUTPUT_BYTES * 8) >> 8) & 0xff) as u8,
((OUTPUT_BYTES * 8) & 0xff) as u8,
],
)
/// These are the values we have assigned to the 5 variables involved in their KDF:
/// 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(), &[1, b'Z', b'T', LABEL, 0x00, 0, 2u8, 0u8])
}
fn kbkdf256<const LABEL: u8>(key: &Secret<NOISE_HASHLEN>) -> Secret<32> {
hmac_sha512_secret256(key.as_bytes(), &[1, b'Z', b'T', LABEL, 0x00, 0, 1u8, 0u8])
}
fn prng32(mut x: u32) -> u32 {