Skip to content

Commit 12e8ae5

Browse files
committed
Stateful nonce desync fix
There was a logic bug where unauthenticated payloads could still cause a nonce increment in snow. This could in turn allow a denial-of-service type attack where an attacker could prevent message delivery by sending garbage data. This only affects those who are using TransportState, not those using StatelessTransportState.
1 parent 02c26b7 commit 12e8ae5

File tree

3 files changed

+54
-14
lines changed

3 files changed

+54
-14
lines changed

src/cipherstate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ impl CipherState {
5959
}
6060

6161
validate_nonce(self.n)?;
62-
let len = self.cipher.decrypt(self.n, authtext, ciphertext, out);
62+
let len = self.cipher.decrypt(self.n, authtext, ciphertext, out)?;
6363

6464
// We have validated this will not wrap around.
6565
self.n += 1;
6666

67-
len
67+
Ok(len)
6868
}
6969

7070
pub fn encrypt(&mut self, plaintext: &[u8], out: &mut [u8]) -> Result<usize, Error> {

src/resolvers/default.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
use blake2::{Blake2b, Blake2b512, Blake2s, Blake2s256};
22
#[cfg(feature = "xchachapoly")]
33
use chacha20poly1305::XChaCha20Poly1305;
4-
use chacha20poly1305::{
5-
aead::AeadInPlace,
6-
KeyInit, ChaCha20Poly1305,
7-
};
4+
use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305, KeyInit};
85
use curve25519_dalek::montgomery::MontgomeryPoint;
96
#[cfg(feature = "pqclean_kyber1024")]
107
use pqcrypto_kyber::kyber1024;
@@ -611,7 +608,7 @@ mod tests {
611608
keypair.dh(&public, &mut output).unwrap();
612609
assert_eq!(
613610
hex::encode(output),
614-
"c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552"
611+
"c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552"
615612
);
616613
}
617614

tests/general.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ fn test_rekey() {
417417
h_i.rekey_outgoing();
418418
let len = h_i.write_message(b"hack the planet", &mut buffer_msg).unwrap();
419419
assert!(h_r.read_message(&buffer_msg[..len], &mut buffer_out).is_err());
420+
h_r.set_receiving_nonce(h_i.sending_nonce());
420421

421422
// rekey incoming on responder
422423
h_r.rekey_incoming();
@@ -428,6 +429,7 @@ fn test_rekey() {
428429
h_r.rekey_outgoing();
429430
let len = h_r.write_message(b"hack the planet", &mut buffer_msg).unwrap();
430431
assert!(h_i.read_message(&buffer_msg[..len], &mut buffer_out).is_err());
432+
h_i.set_receiving_nonce(h_r.sending_nonce());
431433

432434
// rekey incoming on initiator
433435
h_i.rekey_incoming();
@@ -444,37 +446,45 @@ fn test_rekey_manually() {
444446

445447
let mut buffer_msg = [0u8; 200];
446448
let mut buffer_out = [0u8; 200];
449+
450+
// Do a handshake, and transition to stateful transport mode.
447451
let len = h_i.write_message(b"abc", &mut buffer_msg).unwrap();
448452
h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap();
449-
450453
let len = h_r.write_message(b"defg", &mut buffer_msg).unwrap();
451454
h_i.read_message(&buffer_msg[..len], &mut buffer_out).unwrap();
452-
453455
let mut h_i = h_i.into_transport_mode().unwrap();
454456
let mut h_r = h_r.into_transport_mode().unwrap();
455457

456-
// test message initiator->responder before rekeying initiator
458+
// test sanity message initiator->responder before rekeying initiator
457459
let len = h_i.write_message(b"hack the planet", &mut buffer_msg).unwrap();
458460
let len = h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap();
459461
assert_eq!(&buffer_out[..len], b"hack the planet");
460462

461-
// rekey initiator (on initiator)
463+
// rekey initiator-side initiator key to K1 without rekeying the responder,
464+
// expecting a decryption failure.
465+
//
466+
// The message *should* have failed to read, so we also force nonce re-sync.
462467
h_i.rekey_manually(Some(&[1u8; 32]), None);
463468
let len = h_i.write_message(b"hack the planet", &mut buffer_msg).unwrap();
464469
assert!(h_r.read_message(&buffer_msg[..len], &mut buffer_out).is_err());
470+
h_r.set_receiving_nonce(h_i.sending_nonce());
465471

466-
// rekey initiator (on responder)
472+
// rekey responder-side responder key to K1, expecting a successful decryption.
467473
h_r.rekey_manually(Some(&[1u8; 32]), None);
468474
let len = h_i.write_message(b"hack the planet", &mut buffer_msg).unwrap();
469475
let len = h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap();
470476
assert_eq!(&buffer_out[..len], b"hack the planet");
471477

472-
// rekey responder (on responder)
478+
// rekey responder-side responder key to K1 without rekeying the initiator,
479+
// expecting a decryption failure.
480+
//
481+
// The message *should* have failed to read, so we also force nonce re-sync.
473482
h_r.rekey_manually(None, Some(&[1u8; 32]));
474483
let len = h_r.write_message(b"hack the planet", &mut buffer_msg).unwrap();
475484
assert!(h_i.read_message(&buffer_msg[..len], &mut buffer_out).is_err());
485+
h_i.set_receiving_nonce(h_r.sending_nonce());
476486

477-
// rekey responder (on initiator)
487+
// rekey intiator-side responder key to K1, expecting a successful decryption.
478488
h_i.rekey_manually(None, Some(&[1u8; 32]));
479489
let len = h_r.write_message(b"hack the planet", &mut buffer_msg).unwrap();
480490
let len = h_i.read_message(&buffer_msg[..len], &mut buffer_out).unwrap();
@@ -870,3 +880,36 @@ fn test_stateless_nonce_maximum_behavior() {
870880
Err(snow::Error::State(snow::error::StateProblem::Exhausted))
871881
));
872882
}
883+
884+
#[test]
885+
fn test_stateful_nonce_increment_behavior() {
886+
let params: NoiseParams = "Noise_NN_25519_ChaChaPoly_SHA256".parse().unwrap();
887+
let mut h_i = Builder::new(params.clone()).build_initiator().unwrap();
888+
let mut h_r = Builder::new(params).build_responder().unwrap();
889+
890+
let mut buffer_msg = [0u8; 200];
891+
let mut buffer_out = [0u8; 200];
892+
let len = h_i.write_message(b"abc", &mut buffer_msg).unwrap();
893+
h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap();
894+
895+
let len = h_r.write_message(b"defg", &mut buffer_msg).unwrap();
896+
h_i.read_message(&buffer_msg[..len], &mut buffer_out).unwrap();
897+
898+
let mut h_i = h_i.into_transport_mode().unwrap();
899+
let mut h_r = h_r.into_transport_mode().unwrap();
900+
901+
let len = h_i.write_message(b"xyz", &mut buffer_msg).unwrap();
902+
903+
// Corrupt the message by incrementing a byte in the payload
904+
let mut corrupted = buffer_msg[..len].to_owned();
905+
corrupted[0] = corrupted[0].wrapping_add(1);
906+
907+
// This should result in an error, but should not change any internal state
908+
assert!(h_r.read_message(&corrupted, &mut buffer_out).is_err());
909+
910+
// This should now succeed as the nonce counter should have remained the same
911+
h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap();
912+
913+
// This should now fail again as the nonce counter should have incremented
914+
assert!(h_r.read_message(&buffer_msg[..len], &mut buffer_out).is_err());
915+
}

0 commit comments

Comments
 (0)