mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-22 22:33:58 -07:00
Delete a bunch of commented out old Noise_IK code.
This commit is contained in:
parent
c97d5d28bb
commit
5763988e5b
1 changed files with 6 additions and 732 deletions
738
zssp/src/zssp.rs
738
zssp/src/zssp.rs
|
@ -953,667 +953,29 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
|||
&kbkdf512(noise_es_ee_se_hk_psk.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION).as_bytes()
|
||||
[..HMAC_SHA384_SIZE],
|
||||
&message_nonce,
|
||||
&pkt_assembled[HEADER_SIZE..pkt_assembled_enc_end + HMAC_SHA384_SIZE],
|
||||
&pkt_saved_for_final_hmac[HEADER_SIZE..pkt_assembled_enc_end + HMAC_SHA384_SIZE],
|
||||
),
|
||||
) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
todo!()
|
||||
} else {
|
||||
return Err(Error::NewSessionRejected);
|
||||
}
|
||||
}
|
||||
|
||||
PACKET_TYPE_ALICE_REKEY_INIT => {}
|
||||
PACKET_TYPE_ALICE_REKEY_INIT => todo!(),
|
||||
|
||||
PACKET_TYPE_BOB_REKEY_ACK => {}
|
||||
PACKET_TYPE_BOB_REKEY_ACK => todo!(),
|
||||
|
||||
_ => {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
}
|
||||
todo!()
|
||||
/*
|
||||
// To greatly simplify logic handling key exchange packets, assemble these first.
|
||||
// Handling KEX packets isn't the fast path so the extra copying isn't significant.
|
||||
const KEX_BUF_LEN: usize = 4096;
|
||||
let mut kex_packet = [0_u8; KEX_BUF_LEN];
|
||||
let mut kex_packet_len = 0;
|
||||
for i in 0..fragments.len() {
|
||||
let mut ff = fragments[i].as_ref();
|
||||
if ff.len() < MIN_PACKET_SIZE {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
if i > 0 {
|
||||
ff = &ff[HEADER_SIZE..];
|
||||
}
|
||||
let j = kex_packet_len + ff.len();
|
||||
if j > KEX_BUF_LEN {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
kex_packet[kex_packet_len..j].copy_from_slice(ff);
|
||||
kex_packet_len = j;
|
||||
}
|
||||
let kex_packet_saved_ciphertext = kex_packet.clone(); // save for HMAC check later
|
||||
|
||||
// Key exchange packets begin (after header) with the session protocol version. This could be
|
||||
// changed in the future to support a different cipher suite.
|
||||
if kex_packet[HEADER_SIZE] != SESSION_PROTOCOL_VERSION {
|
||||
return Err(Error::UnknownProtocolVersion);
|
||||
}
|
||||
|
||||
match packet_type {
|
||||
PACKET_TYPE_INITIAL_KEY_OFFER => {
|
||||
// alice (remote) -> bob (local)
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// packet decoding for noise initial key offer
|
||||
// -> e, es, s, ss
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
if kex_packet_len < (HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + HMAC_SIZE + HMAC_SIZE) {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
|
||||
let plaintext_end = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
||||
let payload_end = kex_packet_len - (AES_GCM_TAG_SIZE + HMAC_SIZE + HMAC_SIZE);
|
||||
let aes_gcm_tag_end = kex_packet_len - (HMAC_SIZE + HMAC_SIZE);
|
||||
let hmac1_end = kex_packet_len - HMAC_SIZE;
|
||||
|
||||
// Check the secondary HMAC first, which proves that the sender knows the recipient's full static identity.
|
||||
if !secure_eq(
|
||||
&hmac_sha384_2(
|
||||
app.get_local_s_public_blob_hash(),
|
||||
&message_nonce,
|
||||
&kex_packet[HEADER_SIZE..hmac1_end],
|
||||
),
|
||||
&kex_packet[hmac1_end..kex_packet_len],
|
||||
) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Check rate limits.
|
||||
if let Some(session) = session.as_ref() {
|
||||
if (session.state.read().unwrap().last_remote_offer + Application::REKEY_RATE_LIMIT_MS) > current_time {
|
||||
return Err(Error::RateLimited);
|
||||
}
|
||||
} else {
|
||||
if !app.check_new_session(self, remote_address) {
|
||||
return Err(Error::RateLimited);
|
||||
}
|
||||
}
|
||||
|
||||
// Key agreement: alice (remote) ephemeral NIST P-384 <> local static NIST P-384
|
||||
let alice_e_public =
|
||||
P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..plaintext_end]).ok_or(Error::FailedAuthentication)?;
|
||||
let noise_es = app
|
||||
.get_local_s_keypair()
|
||||
.agree(&alice_e_public)
|
||||
.ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
// Initial key derivation from starting point, mixing in alice's ephemeral public and the es.
|
||||
let noise_ik_incomplete_es = Secret(hmac_sha512(
|
||||
&hmac_sha512(&INITIAL_KEY, alice_e_public.as_bytes()),
|
||||
noise_es.as_bytes(),
|
||||
));
|
||||
|
||||
// Decrypt the encrypted part of the packet payload and authenticate the above key exchange via AES-GCM auth.
|
||||
let mut c = AesGcm::new(
|
||||
kbkdf512(noise_ik_incomplete_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::<AES_KEY_SIZE>(),
|
||||
false,
|
||||
);
|
||||
c.reset_init_gcm(&message_nonce);
|
||||
c.crypt_in_place(&mut kex_packet[plaintext_end..payload_end]);
|
||||
let gcm_tag = &kex_packet[payload_end..aes_gcm_tag_end];
|
||||
if !c.finish_decrypt(gcm_tag) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Parse payload and get alice's session ID, alice's public blob, metadata, and (if present) Alice's Kyber1024 public.
|
||||
let (
|
||||
offer_id,
|
||||
alice_session_id,
|
||||
alice_s_public_blob,
|
||||
alice_metadata,
|
||||
alice_hk_public_raw,
|
||||
alice_ratchet_key_fingerprint,
|
||||
) = parse_dec_key_offer_after_header(&kex_packet[plaintext_end..kex_packet_len], packet_type)?;
|
||||
|
||||
// We either have a session, in which case they should have supplied a ratchet key fingerprint, or
|
||||
// we don't and they should not have supplied one.
|
||||
if session.is_some() != alice_ratchet_key_fingerprint.is_some() {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Extract alice's static NIST P-384 public key from her public blob.
|
||||
let alice_s_public = Application::extract_s_public_from_raw(alice_s_public_blob).ok_or(Error::InvalidPacket)?;
|
||||
|
||||
// Key agreement: both sides' static P-384 keys.
|
||||
let noise_ss = app
|
||||
.get_local_s_keypair()
|
||||
.agree(&alice_s_public)
|
||||
.ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
// Mix result of 'ss' agreement into master key.
|
||||
let noise_ik_incomplete_es_ss = Secret(hmac_sha512(noise_ik_incomplete_es.as_bytes(), noise_ss.as_bytes()));
|
||||
drop(noise_ik_incomplete_es);
|
||||
|
||||
// Authenticate entire packet with HMAC-SHA384, verifying alice's identity via 'ss' secret that was
|
||||
// just mixed into the key.
|
||||
if !secure_eq(
|
||||
&hmac_sha384_2(
|
||||
kbkdf512(noise_ik_incomplete_es_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
||||
&message_nonce,
|
||||
&kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end],
|
||||
),
|
||||
&kex_packet[aes_gcm_tag_end..hmac1_end],
|
||||
) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Alice's offer has been verified and her current key state reconstructed.
|
||||
|
||||
// Perform checks and match ratchet key if there's an existing session, or gate (via host) and
|
||||
// then create new sessions.
|
||||
let (new_session, ratchet_key, last_ratchet_count) = if let Some(session) = session.as_ref() {
|
||||
// Existing session identity must match the one in this offer.
|
||||
if !secure_eq(&session.remote_s_public_blob_hash, &SHA384::hash(&alice_s_public_blob)) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Match ratchet key fingerprint and fail if no match, which likely indicates an old offer packet.
|
||||
let alice_ratchet_key_fingerprint = alice_ratchet_key_fingerprint.unwrap();
|
||||
let mut ratchet_key = None;
|
||||
let mut last_ratchet_count = 0;
|
||||
let state = session.state.read().unwrap();
|
||||
for k in state.session_keys.iter() {
|
||||
if let Some(k) = k.as_ref() {
|
||||
if public_fingerprint_of_secret(k.ratchet_key.as_bytes())[..16].eq(alice_ratchet_key_fingerprint) {
|
||||
ratchet_key = Some(k.ratchet_key.clone());
|
||||
last_ratchet_count = k.ratchet_count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ratchet_key.is_none() {
|
||||
return Ok(ReceiveResult::Ignored); // old packet?
|
||||
}
|
||||
|
||||
(None, ratchet_key, last_ratchet_count)
|
||||
} else {
|
||||
if let Some((new_session_id, psk, associated_object)) =
|
||||
app.accept_new_session(self, remote_address, alice_s_public_blob, alice_metadata)
|
||||
{
|
||||
let header_check_cipher = Aes::new(
|
||||
kbkdf512(noise_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<HEADER_CHECK_AES_KEY_SIZE>(),
|
||||
);
|
||||
(
|
||||
Some(Session::<Application> {
|
||||
id: new_session_id,
|
||||
application_data: associated_object,
|
||||
receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
|
||||
send_counter: AtomicU64::new(1),
|
||||
psk,
|
||||
noise_ss,
|
||||
header_check_cipher,
|
||||
state: RwLock::new(SessionMutableState {
|
||||
remote_session_id: Some(alice_session_id),
|
||||
session_keys: [None, None],
|
||||
cur_session_key_idx: 0,
|
||||
offer: None,
|
||||
last_remote_offer: current_time,
|
||||
}),
|
||||
remote_s_public_blob_hash: SHA384::hash(&alice_s_public_blob),
|
||||
remote_s_public_p384_bytes: alice_s_public.as_bytes().clone(),
|
||||
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
|
||||
}),
|
||||
None,
|
||||
0,
|
||||
)
|
||||
} else {
|
||||
return Err(Error::NewSessionRejected);
|
||||
}
|
||||
};
|
||||
|
||||
// Set 'session' to a reference to either the existing or the new session.
|
||||
let existing_session = session;
|
||||
let session = existing_session.as_ref().map_or_else(|| new_session.as_ref().unwrap(), |s| &*s);
|
||||
|
||||
if !session.update_receive_window(counter) {
|
||||
return Ok(ReceiveResult::Ignored);
|
||||
}
|
||||
|
||||
// Generate our ephemeral NIST P-384 key pair.
|
||||
let bob_e_keypair = P384KeyPair::generate();
|
||||
|
||||
// Key agreement: both sides' ephemeral P-384 public keys.
|
||||
let noise_ee = bob_e_keypair.agree(&alice_e_public).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
// Key agreement: bob (local) static NIST P-384, alice (remote) ephemeral P-384.
|
||||
let noise_se = bob_e_keypair.agree(&alice_s_public).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
// Mix in the psk, the key to this point, our ephemeral public, ee, and se, completing Noise_IK.
|
||||
//
|
||||
// FIPS note: the order of HMAC parameters are flipped here from the usual Noise HMAC(key, X). That's because
|
||||
// NIST/FIPS allows HKDF with HMAC(salt, key) and salt is allowed to be anything. This way if the PSK is not
|
||||
// FIPS compliant the compliance of the entire key derivation is not invalidated. Both inputs are secrets of
|
||||
// fixed size so this shouldn't matter cryptographically.
|
||||
let noise_ik_complete = Secret(hmac_sha512(
|
||||
session.psk.as_bytes(),
|
||||
&hmac_sha512(
|
||||
&hmac_sha512(
|
||||
&hmac_sha512(noise_ik_incomplete_es_ss.as_bytes(), bob_e_keypair.public_key_bytes()),
|
||||
noise_ee.as_bytes(),
|
||||
),
|
||||
noise_se.as_bytes(),
|
||||
),
|
||||
));
|
||||
drop(noise_ik_incomplete_es_ss);
|
||||
drop(noise_ee);
|
||||
drop(noise_se);
|
||||
|
||||
// At this point we've completed Noise_IK key derivation with NIST P-384 ECDH, but now for hybrid and ratcheting...
|
||||
|
||||
// Generate a Kyber encapsulated ciphertext if Kyber is enabled and the other side sent us a public key.
|
||||
let (bob_hk_public, hybrid_kk) = if JEDI && alice_hk_public_raw.len() > 0 {
|
||||
if let Ok((bob_hk_public, hybrid_kk)) =
|
||||
pqc_kyber::encapsulate(alice_hk_public_raw, &mut random::SecureRandom::default())
|
||||
{
|
||||
(Some(bob_hk_public), Some(Secret(hybrid_kk)))
|
||||
} else {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// packet encoding for noise key counter offer
|
||||
// <- e, ee, se
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
let next_ratchet_count = last_ratchet_count + 1;
|
||||
|
||||
let mut reply_buf = [0_u8; KEX_BUF_LEN];
|
||||
let reply_counter = session.send_counter.fetch_add(1, Ordering::SeqCst);
|
||||
let mut idx = HEADER_SIZE;
|
||||
|
||||
idx = safe_write_all(&mut reply_buf, idx, &[SESSION_PROTOCOL_VERSION])?;
|
||||
idx = safe_write_all(&mut reply_buf, idx, bob_e_keypair.public_key_bytes())?;
|
||||
let plaintext_end = idx;
|
||||
|
||||
idx = safe_write_all(&mut reply_buf, idx, offer_id)?;
|
||||
idx = safe_write_all(&mut reply_buf, idx, session.id.as_bytes())?;
|
||||
idx = varint_safe_write(&mut reply_buf, idx, 0)?; // they don't need our static public; they have it
|
||||
idx = varint_safe_write(&mut reply_buf, idx, 0)?; // no meta-data in counter-offers (could be used in the future)
|
||||
if let Some(bob_hk_public) = bob_hk_public.as_ref() {
|
||||
idx = safe_write_all(&mut reply_buf, idx, &[HYBRID_KEY_TYPE_KYBER1024])?;
|
||||
idx = safe_write_all(&mut reply_buf, idx, bob_hk_public)?;
|
||||
} else {
|
||||
idx = safe_write_all(&mut reply_buf, idx, &[HYBRID_KEY_TYPE_NONE])?;
|
||||
}
|
||||
if ratchet_key.is_some() {
|
||||
idx = safe_write_all(&mut reply_buf, idx, &[0x01])?;
|
||||
idx = safe_write_all(&mut reply_buf, idx, alice_ratchet_key_fingerprint.unwrap())?;
|
||||
} else {
|
||||
idx = safe_write_all(&mut reply_buf, idx, &[0x00])?;
|
||||
}
|
||||
let payload_end = idx;
|
||||
|
||||
let reply_message_nonce = create_message_nonce(PACKET_TYPE_KEY_COUNTER_OFFER, reply_counter);
|
||||
|
||||
// Encrypt reply packet using final Noise_IK key BEFORE mixing hybrid or ratcheting, since the other side
|
||||
// must decrypt before doing these things.
|
||||
let mut c = AesGcm::new(
|
||||
kbkdf512(noise_ik_complete.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::<AES_KEY_SIZE>(),
|
||||
true,
|
||||
);
|
||||
c.reset_init_gcm(&reply_message_nonce);
|
||||
c.crypt_in_place(&mut reply_buf[plaintext_end..payload_end]);
|
||||
let gcm_tag = c.finish_encrypt();
|
||||
|
||||
idx = safe_write_all(&mut reply_buf, idx, &gcm_tag)?;
|
||||
let aes_gcm_tag_end = idx;
|
||||
|
||||
// Mix ratchet key from previous session key (if any) and Kyber1024 hybrid shared key (if any).
|
||||
let mut session_key = noise_ik_complete;
|
||||
if let Some(ratchet_key) = ratchet_key {
|
||||
session_key = Secret(hmac_sha512(ratchet_key.as_bytes(), session_key.as_bytes()));
|
||||
}
|
||||
if let Some(hybrid_kk) = hybrid_kk.as_ref() {
|
||||
session_key = Secret(hmac_sha512(hybrid_kk.as_bytes(), session_key.as_bytes()));
|
||||
}
|
||||
|
||||
// Authenticate packet using HMAC-SHA384 with final key. Note that while the final key now has the Kyber secret
|
||||
// mixed in, this doesn't constitute session authentication with Kyber because there's no static Kyber key
|
||||
// associated with the remote identity. An attacker who can break NIST P-384 (and has the psk) could MITM the
|
||||
// Kyber exchange, but you'd need a not-yet-existing quantum computer for that.
|
||||
let hmac = hmac_sha384_2(
|
||||
kbkdf512(session_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
||||
&reply_message_nonce,
|
||||
&reply_buf[HEADER_SIZE..aes_gcm_tag_end],
|
||||
);
|
||||
idx = safe_write_all(&mut reply_buf, idx, &hmac)?;
|
||||
let packet_end = idx;
|
||||
|
||||
let session_key = SessionKey::new(
|
||||
session_key,
|
||||
Role::Bob,
|
||||
current_time,
|
||||
reply_counter,
|
||||
next_ratchet_count,
|
||||
false, // Bob can't know yet if Alice got the counter offer
|
||||
hybrid_kk.is_some(),
|
||||
);
|
||||
|
||||
let next_key_index = (next_ratchet_count as usize) & 1;
|
||||
|
||||
let mut state = session.state.write().unwrap();
|
||||
let _ = state.remote_session_id.replace(alice_session_id);
|
||||
let _ = state.session_keys[next_key_index].replace(session_key);
|
||||
state.last_remote_offer = current_time;
|
||||
drop(state);
|
||||
|
||||
// Bob now has final key state for this exchange. Yay! Now reply to Alice so she can construct it.
|
||||
|
||||
send_with_fragmentation(
|
||||
send,
|
||||
&mut reply_buf[..packet_end],
|
||||
mtu,
|
||||
PACKET_TYPE_KEY_COUNTER_OFFER,
|
||||
u64::from(alice_session_id),
|
||||
next_ratchet_count,
|
||||
reply_counter,
|
||||
&session.header_check_cipher,
|
||||
)?;
|
||||
|
||||
if new_session.is_some() {
|
||||
return Ok(ReceiveResult::OkNewSession(new_session.unwrap()));
|
||||
} else {
|
||||
return Ok(ReceiveResult::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
PACKET_TYPE_KEY_COUNTER_OFFER => {
|
||||
// bob (remote) -> alice (local)
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// packet decoding for noise key counter offer
|
||||
// <- e, ee, se
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
if kex_packet_len < (HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + HMAC_SIZE) {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
let plaintext_end = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE;
|
||||
let payload_end = kex_packet_len - (AES_GCM_TAG_SIZE + HMAC_SIZE);
|
||||
let aes_gcm_tag_end = kex_packet_len - HMAC_SIZE;
|
||||
|
||||
if let Some(session) = session {
|
||||
let state = session.state.read().unwrap();
|
||||
if let Some(offer) = state.offer.as_ref() {
|
||||
let bob_e_public = P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..plaintext_end])
|
||||
.ok_or(Error::FailedAuthentication)?;
|
||||
let noise_ee = offer.alice_e_keypair.agree(&bob_e_public).ok_or(Error::FailedAuthentication)?;
|
||||
let noise_se = app.get_local_s_keypair().agree(&bob_e_public).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
let noise_ik_complete = Secret(hmac_sha512(
|
||||
session.psk.as_bytes(),
|
||||
&hmac_sha512(
|
||||
&hmac_sha512(&hmac_sha512(offer.ss_key.as_bytes(), bob_e_public.as_bytes()), noise_ee.as_bytes()),
|
||||
noise_se.as_bytes(),
|
||||
),
|
||||
));
|
||||
drop(noise_ee);
|
||||
drop(noise_se);
|
||||
|
||||
let mut c = AesGcm::new(
|
||||
kbkdf512(noise_ik_complete.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE)
|
||||
.first_n::<AES_KEY_SIZE>(),
|
||||
false,
|
||||
);
|
||||
c.reset_init_gcm(&message_nonce);
|
||||
c.crypt_in_place(&mut kex_packet[plaintext_end..payload_end]);
|
||||
let gcm_tag = &kex_packet[payload_end..aes_gcm_tag_end];
|
||||
if !c.finish_decrypt(gcm_tag) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Alice has now completed Noise_IK with NIST P-384 and verified with GCM auth, but now for hybrid...
|
||||
|
||||
let (offer_id, bob_session_id, _, _, bob_hk_public_raw, bob_ratchet_key_id) =
|
||||
parse_dec_key_offer_after_header(&kex_packet[plaintext_end..kex_packet_len], packet_type)?;
|
||||
|
||||
// Check that this is a counter offer to the original offer we sent.
|
||||
if !offer.id.eq(offer_id) {
|
||||
return Ok(ReceiveResult::Ignored);
|
||||
}
|
||||
|
||||
// Kyber1024 key agreement if enabled.
|
||||
let hybrid_kk = if JEDI && bob_hk_public_raw.len() > 0 && offer.alice_hk_keypair.is_some() {
|
||||
if let Ok(hybrid_kk) =
|
||||
pqc_kyber::decapsulate(bob_hk_public_raw, &offer.alice_hk_keypair.as_ref().unwrap().secret)
|
||||
{
|
||||
Some(Secret(hybrid_kk))
|
||||
} else {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// The session key starts with the final noise_ik key and may have other things mixed into it below.
|
||||
let mut session_key = noise_ik_complete;
|
||||
|
||||
// Mix ratchet key from previous session key (if any) and Kyber1024 hybrid shared key (if any).
|
||||
let last_ratchet_count = if bob_ratchet_key_id.is_some() && offer.ratchet_key.is_some() {
|
||||
session_key = Secret(hmac_sha512(offer.ratchet_key.as_ref().unwrap().as_bytes(), session_key.as_bytes()));
|
||||
offer.ratchet_count
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if let Some(hybrid_kk) = hybrid_kk.as_ref() {
|
||||
session_key = Secret(hmac_sha512(hybrid_kk.as_bytes(), session_key.as_bytes()));
|
||||
}
|
||||
|
||||
// Check main packet HMAC for full validation of session key.
|
||||
if !secure_eq(
|
||||
&hmac_sha384_2(
|
||||
kbkdf512(session_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
||||
&message_nonce,
|
||||
&kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end],
|
||||
),
|
||||
&kex_packet[aes_gcm_tag_end..kex_packet_len],
|
||||
) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Alice has now completed and validated the full hybrid exchange.
|
||||
|
||||
let reply_counter = session.send_counter.fetch_add(1, Ordering::SeqCst);
|
||||
let next_ratchet_count = last_ratchet_count + 1;
|
||||
|
||||
let session_key = SessionKey::new(
|
||||
session_key,
|
||||
Role::Alice,
|
||||
current_time,
|
||||
reply_counter,
|
||||
next_ratchet_count,
|
||||
true, // Alice knows Bob got the offer
|
||||
hybrid_kk.is_some(),
|
||||
);
|
||||
|
||||
drop(state);
|
||||
let mut state = session.state.write().unwrap();
|
||||
|
||||
let _ = state.remote_session_id.replace(bob_session_id);
|
||||
let next_key_index = (next_ratchet_count as usize) & 1;
|
||||
let _ = state.session_keys[next_key_index].replace(session_key);
|
||||
state.cur_session_key_idx = next_key_index;
|
||||
let _ = state.offer.take();
|
||||
|
||||
return Ok(ReceiveResult::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
// Just ignore counter-offers that are out of place. They probably indicate that this side
|
||||
// restarted and needs to establish a new session.
|
||||
return Ok(ReceiveResult::Ignored);
|
||||
}
|
||||
|
||||
_ => return Err(Error::InvalidPacket),
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// Create an send an ephemeral offer, populating ret_ephemeral_offer on success.
|
||||
fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||
send: &mut SendFunction,
|
||||
counter: u64,
|
||||
alice_session_id: SessionId,
|
||||
bob_session_id: Option<SessionId>,
|
||||
alice_s_public_blob: &[u8],
|
||||
alice_metadata: &[u8],
|
||||
bob_s_public: &P384PublicKey,
|
||||
bob_s_public_blob_hash: &[u8],
|
||||
noise_ss: &Secret<48>,
|
||||
current_key: Option<&SessionKey>,
|
||||
header_check_cipher: Option<&Aes>, // None to use one based on the recipient's public key for initial contact
|
||||
mtu: usize,
|
||||
current_time: i64,
|
||||
ret_ephemeral_offer: &mut Option<EphemeralOffer>, // We want to prevent copying the EphemeralOffer up the stack because it's very big. ret_ephemeral_offer will be overwritten with the returned EphemeralOffer when the call completes.
|
||||
) -> Result<(), Error> {
|
||||
// Generate a NIST P-384 pair.
|
||||
let alice_e_keypair = P384KeyPair::generate();
|
||||
|
||||
// Perform key agreement with the other side's static P-384 public key.
|
||||
let noise_es = alice_e_keypair.agree(bob_s_public).ok_or(Error::InvalidPacket)?;
|
||||
|
||||
// Generate a Kyber1024 (hybrid PQ crypto) pair if enabled.
|
||||
let alice_hk_keypair = if JEDI {
|
||||
Some(pqc_kyber::keypair(&mut random::SecureRandom::get()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Get ratchet key for current key if one exists.
|
||||
let (ratchet_key, ratchet_count) = if let Some(current_key) = current_key {
|
||||
(Some(current_key.ratchet_key.clone()), current_key.ratchet_count)
|
||||
} else {
|
||||
(None, 0)
|
||||
};
|
||||
|
||||
// Random ephemeral offer ID
|
||||
let id: [u8; 16] = random::get_bytes_secure();
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// packet encoding for noise initial key offer and for noise rekeying
|
||||
// -> e, es, s, ss
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Create ephemeral offer packet (not fragmented yet).
|
||||
let mut packet_buf = [0_u8; 4096];
|
||||
let mut idx = HEADER_SIZE;
|
||||
|
||||
idx = safe_write_all(&mut packet_buf, idx, &[SESSION_PROTOCOL_VERSION])?;
|
||||
//TODO: check this, the below line is supposed to be the blob, not just the key, right?
|
||||
idx = safe_write_all(&mut packet_buf, idx, alice_e_keypair.public_key_bytes())?;
|
||||
let plaintext_end = idx;
|
||||
|
||||
idx = safe_write_all(&mut packet_buf, idx, &id)?;
|
||||
idx = safe_write_all(&mut packet_buf, idx, alice_session_id.as_bytes())?;
|
||||
idx = varint_safe_write(&mut packet_buf, idx, alice_s_public_blob.len() as u64)?;
|
||||
idx = safe_write_all(&mut packet_buf, idx, alice_s_public_blob)?;
|
||||
idx = varint_safe_write(&mut packet_buf, idx, alice_metadata.len() as u64)?;
|
||||
idx = safe_write_all(&mut packet_buf, idx, alice_metadata)?;
|
||||
if let Some(hkp) = alice_hk_keypair {
|
||||
idx = safe_write_all(&mut packet_buf, idx, &[HYBRID_KEY_TYPE_KYBER1024])?;
|
||||
idx = safe_write_all(&mut packet_buf, idx, &hkp.public)?;
|
||||
} else {
|
||||
idx = safe_write_all(&mut packet_buf, idx, &[HYBRID_KEY_TYPE_NONE])?;
|
||||
}
|
||||
if let Some(ratchet_key) = ratchet_key.as_ref() {
|
||||
idx = safe_write_all(&mut packet_buf, idx, &[0x01])?;
|
||||
idx = safe_write_all(&mut packet_buf, idx, &public_fingerprint_of_secret(ratchet_key.as_bytes())[..16])?;
|
||||
} else {
|
||||
idx = safe_write_all(&mut packet_buf, idx, &[0x00])?;
|
||||
}
|
||||
let payload_end = idx;
|
||||
|
||||
// Create ephemeral agreement secret.
|
||||
let es_key = Secret(hmac_sha512(
|
||||
&hmac_sha512(&INITIAL_KEY, alice_e_keypair.public_key_bytes()),
|
||||
noise_es.as_bytes(),
|
||||
));
|
||||
|
||||
let bob_session_id = bob_session_id.map_or(0u64, |i| u64::from(i));
|
||||
|
||||
let message_nonce = create_message_nonce(PACKET_TYPE_INITIAL_KEY_OFFER, counter);
|
||||
|
||||
// Encrypt packet and attach AES-GCM tag.
|
||||
let gcm_tag = {
|
||||
let mut c = AesGcm::new(
|
||||
kbkdf512(es_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::<AES_KEY_SIZE>(),
|
||||
true,
|
||||
);
|
||||
c.reset_init_gcm(&message_nonce);
|
||||
c.crypt_in_place(&mut packet_buf[plaintext_end..payload_end]);
|
||||
c.finish_encrypt()
|
||||
};
|
||||
|
||||
idx = safe_write_all(&mut packet_buf, idx, &gcm_tag)?;
|
||||
let aes_gcm_tag_end = idx;
|
||||
|
||||
// Mix in static secret.
|
||||
let ss_key = Secret(hmac_sha512(es_key.as_bytes(), noise_ss.as_bytes()));
|
||||
drop(es_key);
|
||||
|
||||
// HMAC packet using static + ephemeral key.
|
||||
let hmac1 = hmac_sha384_2(
|
||||
kbkdf512(ss_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(),
|
||||
&message_nonce,
|
||||
&packet_buf[HEADER_SIZE..aes_gcm_tag_end],
|
||||
);
|
||||
idx = safe_write_all(&mut packet_buf, idx, &hmac1)?;
|
||||
let hmac1_end = idx;
|
||||
|
||||
// Add secondary HMAC to verify that the caller knows the recipient's full static public identity.
|
||||
let hmac2 = hmac_sha384_2(bob_s_public_blob_hash, &message_nonce, &packet_buf[HEADER_SIZE..hmac1_end]);
|
||||
idx = safe_write_all(&mut packet_buf, idx, &hmac2)?;
|
||||
let packet_end = idx;
|
||||
|
||||
let mut init_header_check_cipher_tmp = None;
|
||||
send_with_fragmentation(
|
||||
send,
|
||||
&mut packet_buf[..packet_end],
|
||||
mtu,
|
||||
PACKET_TYPE_INITIAL_KEY_OFFER,
|
||||
bob_session_id,
|
||||
ratchet_count,
|
||||
counter,
|
||||
header_check_cipher.unwrap_or_else(|| {
|
||||
init_header_check_cipher_tmp = Some(Aes::new(
|
||||
kbkdf512(&bob_s_public_blob_hash, KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<HEADER_CHECK_AES_KEY_SIZE>(),
|
||||
));
|
||||
init_header_check_cipher_tmp.as_ref().unwrap()
|
||||
}),
|
||||
)?;
|
||||
|
||||
*ret_ephemeral_offer = Some(EphemeralOffer {
|
||||
id,
|
||||
creation_time: current_time,
|
||||
ratchet_count,
|
||||
ratchet_key,
|
||||
ss_key,
|
||||
alice_e_keypair,
|
||||
alice_hk_keypair,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
fn set_packet_header(
|
||||
packet: &mut [u8],
|
||||
|
@ -1657,7 +1019,7 @@ fn set_packet_header(
|
|||
#[repr(C, packed)]
|
||||
struct MessageNonce(u64, u32);
|
||||
|
||||
/// Create a 12-bit AES-GCM nonce.
|
||||
/// Create a 96-bit AES-GCM nonce.
|
||||
///
|
||||
/// The primary information that we want to be contained here is the counter and the
|
||||
/// packet type. The former makes this unique and the latter's inclusion authenticates
|
||||
|
@ -1710,54 +1072,6 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
|||
}
|
||||
|
||||
/*
|
||||
/// Parse KEY_OFFER and KEY_COUNTER_OFFER starting after the unencrypted public key part.
|
||||
fn parse_dec_key_offer_after_header(
|
||||
incoming_packet: &[u8],
|
||||
packet_type: u8,
|
||||
) -> Result<(&[u8], SessionId, &[u8], &[u8], &[u8], Option<&[u8]>), Error> {
|
||||
let mut p = &incoming_packet[..];
|
||||
let offer_id = safe_read_exact(&mut p, 16)?;
|
||||
|
||||
let mut session_id_buf = 0_u64.to_ne_bytes();
|
||||
session_id_buf[..SESSION_ID_SIZE].copy_from_slice(safe_read_exact(&mut p, SESSION_ID_SIZE)?);
|
||||
let alice_session_id = SessionId::new_from_u64_le(u64::from_ne_bytes(session_id_buf)).ok_or(Error::InvalidPacket)?;
|
||||
|
||||
let alice_s_public_blob_len = varint_safe_read(&mut p)?;
|
||||
let alice_s_public_blob = safe_read_exact(&mut p, alice_s_public_blob_len as usize)?;
|
||||
|
||||
let alice_metadata_len = varint_safe_read(&mut p)?;
|
||||
let alice_metadata = safe_read_exact(&mut p, alice_metadata_len as usize)?;
|
||||
|
||||
let alice_hk_public_raw = match safe_read_exact(&mut p, 1)?[0] {
|
||||
HYBRID_KEY_TYPE_KYBER1024 => {
|
||||
if packet_type == PACKET_TYPE_INITIAL_KEY_OFFER {
|
||||
safe_read_exact(&mut p, pqc_kyber::KYBER_PUBLICKEYBYTES)?
|
||||
} else {
|
||||
safe_read_exact(&mut p, pqc_kyber::KYBER_CIPHERTEXTBYTES)?
|
||||
}
|
||||
}
|
||||
_ => &[],
|
||||
};
|
||||
|
||||
if p.is_empty() {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
let alice_ratchet_key_fingerprint = if safe_read_exact(&mut p, 1)?[0] == 0x01 {
|
||||
Some(safe_read_exact(&mut p, 16)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok((
|
||||
offer_id, //always 16 bytes
|
||||
alice_session_id,
|
||||
alice_s_public_blob,
|
||||
alice_metadata,
|
||||
alice_hk_public_raw,
|
||||
alice_ratchet_key_fingerprint, //always 16 bytes
|
||||
))
|
||||
}
|
||||
|
||||
impl SessionKey {
|
||||
/// Create a new symmetric shared session key and set its key expiration times, etc.
|
||||
fn new(key: Secret<64>, role: Role, current_time: i64, current_counter: u64, ratchet_count: u64, confirmed: bool, jedi: bool) -> Self {
|
||||
|
@ -1822,46 +1136,6 @@ impl SessionKey {
|
|||
self.receive_cipher_pool.lock().unwrap().push(c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write src into buffer starting at the index idx. If buffer cannot fit src at that location, nothing at all is written and Error::UnexpectedBufferOverrun is returned. No other errors can be returned by this function. An idx incremented by the amount written is returned.
|
||||
fn safe_write_all(buffer: &mut [u8], idx: usize, src: &[u8]) -> Result<usize, Error> {
|
||||
let dest = &mut buffer[idx..];
|
||||
let amt = src.len();
|
||||
if dest.len() >= amt {
|
||||
dest[..amt].copy_from_slice(src);
|
||||
Ok(idx + amt)
|
||||
} else {
|
||||
unlikely_branch();
|
||||
Err(Error::UnexpectedBufferOverrun)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a variable length integer, which can consume up to 10 bytes. Uses safe_write_all to do so.
|
||||
fn varint_safe_write(buffer: &mut [u8], idx: usize, v: u64) -> Result<usize, Error> {
|
||||
let mut b = [0_u8; varint::VARINT_MAX_SIZE_BYTES];
|
||||
let i = varint::encode(&mut b, v);
|
||||
safe_write_all(buffer, idx, &b[0..i])
|
||||
}
|
||||
|
||||
/// Read exactly amt bytes from src and return the slice those bytes reside in. If src is smaller than amt, Error::InvalidPacket is returned. if the read was successful src is incremented to start at the first unread byte.
|
||||
fn safe_read_exact<'a>(src: &mut &'a [u8], amt: usize) -> Result<&'a [u8], Error> {
|
||||
if src.len() >= amt {
|
||||
let (a, b) = src.split_at(amt);
|
||||
*src = b;
|
||||
Ok(a)
|
||||
} else {
|
||||
unlikely_branch();
|
||||
Err(Error::InvalidPacket)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a variable length integer, which can consume up to 10 bytes. Uses varint_safe_read to do so.
|
||||
fn varint_safe_read(src: &mut &[u8]) -> Result<u64, Error> {
|
||||
let (v, amt) = varint::decode(*src).ok_or(Error::InvalidPacket)?;
|
||||
let (_, b) = src.split_at(amt);
|
||||
*src = b;
|
||||
Ok(v)
|
||||
}
|
||||
*/
|
||||
|
||||
/// Shortcut to HMAC data split into two slices.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue