Rekeying is now tested and works.

This commit is contained in:
Adam Ierymenko 2023-03-01 14:01:30 -05:00
parent 652f7360f0
commit 8b6c9051fb
4 changed files with 158 additions and 107 deletions

View file

@ -31,7 +31,7 @@ pub trait ApplicationLayer: Sized {
/// Attempting to encrypt more than this many messages with a key will cause a hard error
/// and the internal erasure of ephemeral key material. You'll only ever hit this if something
/// goes wrong and rekeying fails.
const EXPIRE_AFTER_USES: u64 = 2147483648;
const EXPIRE_AFTER_USES: u64 = 2147483647;
/// Start attempting to rekey after a key has been in use for this many milliseconds.
///

View file

@ -14,7 +14,7 @@ struct TestApplication {
}
impl zssp::ApplicationLayer for TestApplication {
const REKEY_AFTER_USES: u64 = 131072;
const REKEY_AFTER_USES: u64 = 100000;
const EXPIRE_AFTER_USES: u64 = 2147483648;
const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60 * 2;
const REKEY_AFTER_TIME_MS_MAX_JITTER: u32 = 1000 * 60 * 10;
@ -44,6 +44,9 @@ fn alice_main(
let context = zssp::Context::<TestApplication>::new(16);
let mut data_buf = [0u8; 65536];
let mut next_service = ms_monotonic() + 500;
let mut last_ratchet_count = 0;
let test_data = [1u8; 10000];
let mut up = false;
let alice_session = context
.open(
@ -62,51 +65,57 @@ fn alice_main(
println!("[alice] opening session {}", alice_session.id.to_string());
let test_data = [1u8; 10000];
let mut up = false;
while run.load(Ordering::Relaxed) {
let pkt = alice_in.try_recv();
let current_time = ms_monotonic();
if let Ok(pkt) = pkt {
//println!("bob >> alice {}", pkt.len());
match context.receive(
alice_app,
|| true,
|s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())),
|_, b| {
let _ = alice_out.send(b.to_vec());
},
&mut data_buf,
pkt,
TEST_MTU,
current_time,
) {
Ok(zssp::ReceiveResult::Ok) => {
//println!("[alice] ok");
}
Ok(zssp::ReceiveResult::OkData(_, _)) => {
//println!("[alice] received {}", data.len());
}
Ok(zssp::ReceiveResult::OkNewSession(s)) => {
println!("[alice] new session {}", s.id.to_string());
}
Ok(zssp::ReceiveResult::Rejected) => {}
Err(e) => {
println!("[alice] ERROR {}", e.to_string());
loop {
let pkt = alice_in.try_recv();
if let Ok(pkt) = pkt {
//println!("bob >> alice {}", pkt.len());
match context.receive(
alice_app,
|| true,
|s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())),
|_, b| {
let _ = alice_out.send(b.to_vec());
},
&mut data_buf,
pkt,
TEST_MTU,
current_time,
) {
Ok(zssp::ReceiveResult::Ok) => {
//println!("[alice] ok");
}
Ok(zssp::ReceiveResult::OkData(_, data)) => {
//println!("[alice] received {}", data.len());
}
Ok(zssp::ReceiveResult::OkNewSession(s)) => {
println!("[alice] new session {}", s.id.to_string());
}
Ok(zssp::ReceiveResult::Rejected) => {}
Err(e) => {
println!("[alice] ERROR {}", e.to_string());
}
}
} else {
break;
}
}
if up {
let ratchet_count = alice_session.key_info().unwrap().0;
if ratchet_count > last_ratchet_count {
last_ratchet_count = ratchet_count;
println!("[alice] new key! ratchet count {}", ratchet_count);
}
assert!(alice_session
.send(
|b| {
let _ = alice_out.send(b.to_vec());
},
&mut data_buf[..TEST_MTU],
&test_data[..2048 + ((zerotier_crypto::random::xorshift64_random() as usize) % (test_data.len() - 2048))],
&test_data[..1000 + ((zerotier_crypto::random::xorshift64_random() as usize) % (test_data.len() - 1000))],
)
.is_ok());
} else {
@ -137,6 +146,8 @@ fn bob_main(
) {
let context = zssp::Context::<TestApplication>::new(16);
let mut data_buf = [0u8; 65536];
let mut data_buf_2 = [0u8; TEST_MTU];
let mut last_ratchet_count = 0;
let mut last_speed_metric = ms_monotonic();
let mut next_service = last_speed_metric + 500;
let mut transferred = 0u64;
@ -144,7 +155,7 @@ fn bob_main(
let mut bob_session = None;
while run.load(Ordering::Relaxed) {
let pkt = bob_in.recv_timeout(Duration::from_millis(10));
let pkt = bob_in.recv_timeout(Duration::from_millis(100));
let current_time = ms_monotonic();
if let Ok(pkt) = pkt {
@ -164,9 +175,18 @@ fn bob_main(
Ok(zssp::ReceiveResult::Ok) => {
//println!("[bob] ok");
}
Ok(zssp::ReceiveResult::OkData(_, data)) => {
Ok(zssp::ReceiveResult::OkData(s, data)) => {
//println!("[bob] received {}", data.len());
transferred += data.len() as u64;
assert!(s
.send(
|b| {
let _ = bob_out.send(b.to_vec());
},
&mut data_buf_2,
data.as_mut(),
)
.is_ok());
}
Ok(zssp::ReceiveResult::OkNewSession(s)) => {
println!("[bob] new session {}", s.id.to_string());
@ -179,6 +199,14 @@ fn bob_main(
}
}
if let Some(bob_session) = bob_session.as_ref() {
let ratchet_count = bob_session.key_info().unwrap().0;
if ratchet_count > last_ratchet_count {
last_ratchet_count = ratchet_count;
println!("[bob] new key! ratchet count {}", ratchet_count);
}
}
let speed_metric_elapsed = current_time - last_speed_metric;
if speed_metric_elapsed >= 1000 {
last_speed_metric = current_time;
@ -208,8 +236,8 @@ fn main() {
let alice_app = TestApplication { identity_key: P384KeyPair::generate() };
let bob_app = TestApplication { identity_key: P384KeyPair::generate() };
let (alice_out, bob_in) = mpsc::sync_channel::<Vec<u8>>(128);
let (bob_out, alice_in) = mpsc::sync_channel::<Vec<u8>>(128);
let (alice_out, bob_in) = mpsc::sync_channel::<Vec<u8>>(1024);
let (bob_out, alice_in) = mpsc::sync_channel::<Vec<u8>>(1024);
thread::scope(|ts| {
let alice_thread = ts.spawn(|| alice_main(&run, &alice_app, &bob_app, alice_out, alice_in));

View file

@ -33,8 +33,8 @@ pub(crate) const PACKET_TYPE_DATA: u8 = 0;
pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_INIT: u8 = 1;
pub(crate) const PACKET_TYPE_BOB_NOISE_XK_ACK: u8 = 2;
pub(crate) const PACKET_TYPE_ALICE_NOISE_XK_ACK: u8 = 3;
pub(crate) const PACKET_TYPE_ALICE_REKEY_INIT: u8 = 4;
pub(crate) const PACKET_TYPE_BOB_REKEY_ACK: u8 = 5;
pub(crate) const PACKET_TYPE_REKEY_INIT: u8 = 4;
pub(crate) const PACKET_TYPE_REKEY_ACK: u8 = 5;
pub(crate) const HEADER_SIZE: usize = 16;
pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6;
@ -123,24 +123,26 @@ pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = ALICE_NOISE_XK_ACK_ENC_STA
#[allow(unused)]
#[repr(C, packed)]
pub(crate) struct AliceRekeyInit {
pub(crate) struct RekeyInit {
pub header: [u8; HEADER_SIZE],
pub session_protocol_version: u8,
// -- start AES-GCM encrypted portion (using current key)
pub alice_e: [u8; P384_PUBLIC_KEY_SIZE],
// -- end AES-GCM encrypted portion
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
}
impl AliceRekeyInit {
pub const ENC_START: usize = HEADER_SIZE;
impl RekeyInit {
pub const ENC_START: usize = HEADER_SIZE + 1;
pub const AUTH_START: usize = Self::ENC_START + P384_PUBLIC_KEY_SIZE;
pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
}
#[allow(unused)]
#[repr(C, packed)]
pub(crate) struct BobRekeyAck {
pub(crate) struct RekeyAck {
pub header: [u8; HEADER_SIZE],
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],
@ -148,8 +150,8 @@ pub(crate) struct BobRekeyAck {
pub gcm_mac: [u8; AES_GCM_TAG_SIZE],
}
impl BobRekeyAck {
pub const ENC_START: usize = HEADER_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 SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE;
}
@ -161,8 +163,8 @@ pub(crate) trait ProtocolFlatBuffer {}
impl ProtocolFlatBuffer for AliceNoiseXKInit {}
impl ProtocolFlatBuffer for BobNoiseXKAck {}
//impl ProtocolFlatBuffer for NoiseXKAliceStaticAck {}
impl ProtocolFlatBuffer for AliceRekeyInit {}
impl ProtocolFlatBuffer for BobRekeyAck {}
impl ProtocolFlatBuffer for RekeyInit {}
impl ProtocolFlatBuffer for RekeyAck {}
#[derive(Clone, Copy)]
#[repr(C, packed)]

View file

@ -119,7 +119,7 @@ struct OutgoingSessionInit {
enum Offer {
None,
NoiseXKInit(Box<OutgoingSessionInit>),
RekeyInit(P384KeyPair, [u8; AliceRekeyInit::SIZE], AtomicI64),
RekeyInit(P384KeyPair, [u8; RekeyInit::SIZE], AtomicI64),
}
/// An ephemeral session key with expiration info.
@ -181,6 +181,7 @@ impl<Application: ApplicationLayer> Context<Application> {
&& (current_time >= key.rekey_at_time
|| session.send_counter.load(Ordering::Relaxed) >= key.rekey_at_counter)
{
drop(state);
session.initiate_rekey(|b| send(&session, b), current_time);
}
}
@ -1106,8 +1107,8 @@ impl<Application: ApplicationLayer> Context<Application> {
}
}
PACKET_TYPE_ALICE_REKEY_INIT => {
if pkt_assembled.len() != AliceRekeyInit::SIZE {
PACKET_TYPE_REKEY_INIT => {
if pkt_assembled.len() != RekeyInit::SIZE {
return Err(Error::InvalidPacket);
}
if incoming.is_some() {
@ -1116,58 +1117,74 @@ impl<Application: ApplicationLayer> Context<Application> {
if let Some(session) = session {
let state = session.state.read().unwrap();
if let Some(key) = state.keys[key_index].as_ref() {
// Only the current "Alice" accepts rekeys initiated by the current "Bob." These roles
// flip with each rekey event.
if !key.bob {
let mut c = key.get_receive_cipher();
c.reset_init_gcm(&incoming_message_nonce);
c.crypt_in_place(&mut pkt_assembled[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]);
let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[AliceRekeyInit::AUTH_START..]);
key.return_receive_cipher(c);
if let Some(remote_session_id) = state.remote_session_id {
if let Some(key) = state.keys[key_index].as_ref() {
// Only the current "Alice" accepts rekeys initiated by the current "Bob." These roles
// flip with each rekey event.
if !key.bob {
let mut c = key.get_receive_cipher();
c.reset_init_gcm(&incoming_message_nonce);
c.crypt_in_place(&mut pkt_assembled[RekeyInit::ENC_START..RekeyInit::AUTH_START]);
let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[RekeyInit::AUTH_START..]);
key.return_receive_cipher(c);
if aead_authentication_ok {
let pkt: &AliceRekeyInit = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
if let Some(alice_e) = P384PublicKey::from_bytes(&pkt.alice_e) {
let bob_e_secret = P384KeyPair::generate();
let next_session_key = Secret(hmac_sha512(
key.ratchet_key.as_bytes(),
bob_e_secret.agree(&alice_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
));
if aead_authentication_ok {
let pkt: &RekeyInit = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
if let Some(alice_e) = P384PublicKey::from_bytes(&pkt.alice_e) {
let bob_e_secret = P384KeyPair::generate();
let next_session_key = Secret(hmac_sha512(
key.ratchet_key.as_bytes(),
bob_e_secret.agree(&alice_e).ok_or(Error::FailedAuthentication)?.as_bytes(),
));
let mut reply_buf = [0u8; BobRekeyAck::SIZE];
let reply: &mut BobRekeyAck = byte_array_as_proto_buffer_mut(&mut reply_buf).unwrap();
reply.bob_e = *bob_e_secret.public_key_bytes();
reply.next_key_fingerprint = SHA384::hash(next_session_key.as_bytes());
let mut reply_buf = [0u8; RekeyAck::SIZE];
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(next_session_key.as_bytes());
let counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?;
let mut c = key.get_send_cipher(counter.get())?;
c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_BOB_REKEY_ACK, counter.get()));
c.crypt_in_place(&mut reply_buf[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]);
reply_buf[BobRekeyAck::AUTH_START..].copy_from_slice(&c.finish_encrypt());
key.return_send_cipher(c);
let counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?.get();
set_packet_header(
&mut reply_buf,
1,
0,
PACKET_TYPE_REKEY_ACK,
u64::from(remote_session_id),
state.current_key,
counter,
);
send(Some(&session), &mut reply_buf);
let mut c = key.get_send_cipher(counter)?;
c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_REKEY_ACK, counter));
c.crypt_in_place(&mut reply_buf[RekeyAck::ENC_START..RekeyAck::AUTH_START]);
reply_buf[RekeyAck::AUTH_START..].copy_from_slice(&c.finish_encrypt());
key.return_send_cipher(c);
// The new "Bob" doesn't know yet if Alice has received the new key, so the
// new key is recorded as the "alt" (key_index ^ 1) but the current key is
// not advanced yet. This happens automatically the first time we receive a
// valid packet with the new key.
let next_ratchet_count = key.ratchet_count + 1;
drop(state);
let mut state = session.state.write().unwrap();
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
next_session_key,
next_ratchet_count,
current_time,
counter.get(),
false,
));
session.header_protection_cipher.encrypt_block_in_place(
&mut reply_buf[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END],
);
send(Some(&session), &mut reply_buf);
return Ok(ReceiveResult::Ok);
// The new "Bob" doesn't know yet if Alice has received the new key, so the
// new key is recorded as the "alt" (key_index ^ 1) but the current key is
// not advanced yet. This happens automatically the first time we receive a
// valid packet with the new key.
let next_ratchet_count = key.ratchet_count + 1;
drop(state);
let mut state = session.state.write().unwrap();
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
next_session_key,
next_ratchet_count,
current_time,
counter,
false,
));
return Ok(ReceiveResult::Ok);
}
}
return Err(Error::FailedAuthentication);
}
return Err(Error::FailedAuthentication);
}
}
return Err(Error::OutOfSequence);
@ -1176,8 +1193,8 @@ impl<Application: ApplicationLayer> Context<Application> {
}
}
PACKET_TYPE_BOB_REKEY_ACK => {
if pkt_assembled.len() != BobRekeyAck::SIZE {
PACKET_TYPE_REKEY_ACK => {
if pkt_assembled.len() != RekeyAck::SIZE {
return Err(Error::InvalidPacket);
}
if incoming.is_some() {
@ -1192,12 +1209,12 @@ impl<Application: ApplicationLayer> Context<Application> {
if key.bob {
let mut c = key.get_receive_cipher();
c.reset_init_gcm(&incoming_message_nonce);
c.crypt_in_place(&mut pkt_assembled[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]);
let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[BobRekeyAck::AUTH_START..]);
c.crypt_in_place(&mut pkt_assembled[RekeyAck::ENC_START..RekeyAck::AUTH_START]);
let aead_authentication_ok = c.finish_decrypt(&pkt_assembled[RekeyAck::AUTH_START..]);
key.return_receive_cipher(c);
if aead_authentication_ok {
let pkt: &BobRekeyAck = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
let pkt: &RekeyAck = byte_array_as_proto_buffer(&pkt_assembled).unwrap();
if let Some(bob_e) = P384PublicKey::from_bytes(&pkt.bob_e) {
let next_session_key = Secret(hmac_sha512(
key.ratchet_key.as_bytes(),
@ -1333,8 +1350,9 @@ impl<Application: ApplicationLayer> Session<Application> {
fn initiate_rekey<SendFunction: FnMut(&mut [u8])>(&self, mut send: SendFunction, current_time: i64) {
let rekey_e = P384KeyPair::generate();
let mut rekey_buf = [0u8; AliceRekeyInit::SIZE];
let pkt: &mut AliceRekeyInit = byte_array_as_proto_buffer_mut(&mut rekey_buf).unwrap();
let mut rekey_buf = [0u8; RekeyInit::SIZE];
let pkt: &mut RekeyInit = byte_array_as_proto_buffer_mut(&mut rekey_buf).unwrap();
pkt.session_protocol_version = SESSION_PROTOCOL_VERSION;
pkt.alice_e = *rekey_e.public_key_bytes();
let state = self.state.read().unwrap();
@ -1342,9 +1360,9 @@ impl<Application: ApplicationLayer> Session<Application> {
if let Some(key) = state.keys[state.current_key].as_ref() {
if let Some(counter) = self.get_next_outgoing_counter() {
if let Ok(mut gcm) = key.get_send_cipher(counter.get()) {
gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_ALICE_REKEY_INIT, counter.get()));
gcm.crypt_in_place(&mut rekey_buf[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]);
rekey_buf[AliceRekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt());
gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_REKEY_INIT, counter.get()));
gcm.crypt_in_place(&mut rekey_buf[RekeyInit::ENC_START..RekeyInit::AUTH_START]);
rekey_buf[RekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt());
key.return_send_cipher(gcm);
debug_assert!(rekey_buf.len() <= MIN_TRANSPORT_MTU);
@ -1352,15 +1370,18 @@ impl<Application: ApplicationLayer> Session<Application> {
&mut rekey_buf,
1,
0,
PACKET_TYPE_ALICE_REKEY_INIT,
PACKET_TYPE_REKEY_INIT,
u64::from(remote_session_id),
state.current_key,
counter.get(),
);
drop(state);
self.header_protection_cipher
.encrypt_block_in_place(&mut rekey_buf[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
send(&mut rekey_buf);
drop(state);
self.state.write().unwrap().current_offer = Offer::RekeyInit(rekey_e, rekey_buf, AtomicI64::new(current_time));
}
}