let dgram = client.process_output(now()).dgram(); // Initial let dgram2 = client.process_output(now()).dgram(); // Initial
assert!(dgram.is_some() && dgram2.is_some());
_ = server.process(dgram, now()).dgram().unwrap(); // Retry let dgram = server.process(dgram2, now()).dgram().unwrap(); // Retry
assertions::assert_retry(&dgram);
let dgram = client.process(Some(dgram), now()).dgram(); // Initial w/token let dgram2 = client.process_output(now()).dgram(); // Initial
assert!(dgram.is_some() && dgram2.is_some());
_ = server.process(dgram, now()).dgram().unwrap(); let dgram = server.process(dgram2, now()).dgram(); let dgram = client.process(dgram, now()).dgram(); let dgram = server.process(dgram, now()).dgram(); // Initial, HS
assert!(dgram.is_some());
drop(client.process(dgram, now()).dgram()); // Ingest, drop any ACK.
client.authenticated(AuthenticationStatus::Ok, now()); let dgram = client.process_output(now()).dgram(); // Send Finished
assert!(dgram.is_some());
assert_eq!(*client.state(), State::Connected); let dgram = server.process(dgram, now()).dgram(); // (done)
assert!(dgram.is_some()); // Note that this packet will be dropped...
connected_server(&server);
assert_dscp(&client.stats());
}
/// Verify that ECH fallback works, even when there is a retry. /// /// This is necessary to demonstrate that the transport parameters /// in the outer `ClientHello` are sufficient to establish a connection. #[test] fn retry_ech_fallback() { const CONFIG_ID: u8 = 12; const PUBLIC_NAME: &str = "public.name.example";
letmut server = default_server();
server.set_validation(ValidateAddress::Always); letmut client = default_client();
let dgram = client.process(dgram, now).dgram(); // Initial w/token
assert!(dgram.is_some());
now += Duration::from_secs(60); // Too long for Retry. let dgram = server.process(dgram, now).dgram(); // Initial, HS
assert!(dgram.is_none());
assert_dscp(&client.stats());
}
// Attempt a retry with 0-RTT, and have 0-RTT packets sent with the second ClientHello. #[test] fn retry_0rtt() { letmut server = default_server(); let token = generate_ticket(&mut server);
server.set_validation(ValidateAddress::Always);
// After retry, there should be a token and still coalesced 0-RTT. let dgram = client.process(dgram, now()).dgram(); // Initial let dgram2 = client.process_output(now()).dgram(); // Initial w/0-RTT
assert!(dgram.is_some() && dgram2.is_some());
assertions::assert_coalesced_0rtt(dgram2.as_ref().unwrap());
_ = server.process(dgram, now()).dgram(); // ACK let dgram = server.process(dgram2, now()).dgram(); // Initial, HS
assert!(dgram.is_some()); let dgram = client.process(dgram, now()).dgram(); let dgram = server.process(dgram, now()).dgram(); let dgram = client.process(dgram, now()).dgram(); // Note: the client doesn't need to authenticate the server here // as there is no certificate; authentication is based on the ticket.
assert!(dgram.is_some());
assert_eq!(*client.state(), State::Connected); let dgram = server.process(dgram, now()).dgram(); // (done)
assert!(dgram.is_some());
connected_server(&server);
assert!(client.tls_info().unwrap().resumed());
assert_dscp(&client.stats());
}
let dgram = client.process(dgram, now()).dgram(); // Initial w/token
assert!(dgram.is_some());
// Change the source IP on the address from the client. let dgram = dgram.unwrap(); let other_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)); let other_addr = SocketAddr::new(other_v4, 443); let from_other = Datagram::new(other_addr, dgram.destination(), dgram.tos(), &dgram[..]); let dgram = server.process(Some(from_other), now()).dgram();
assert!(dgram.is_none());
assert_dscp(&client.stats());
}
#[test] fn new_token_different_ip() { letmut server = default_server(); let token = generate_ticket(&mut server);
server.set_validation(ValidateAddress::NoToken);
let dgram = client.process_output(now()).dgram(); // Initial
assert!(dgram.is_some());
assertions::assert_initial(dgram.as_ref().unwrap(), true);
// Now move into the future. // We can't go too far or we'll overflow our field. Not when checking, // but when trying to generate another Retry. A month is fine. let the_future = now() + Duration::from_secs(60 * 60 * 24 * 30); let d = dgram.unwrap(); let src = SocketAddr::new(d.source().ip(), d.source().port() + 1); let dgram = Some(Datagram::new(src, d.destination(), d.tos(), &d[..])); let dgram = server.process(dgram, the_future).dgram(); // Retry
assert!(dgram.is_some());
assertions::assert_retry(dgram.as_ref().unwrap());
assert_dscp(&client.stats());
}
let cinit = client.process_output(now()).dgram(); // Initial let cinit2 = client.process_output(now()).dgram(); // Initial
assert!(cinit.is_some() && cinit2.is_some());
_ = server.process(cinit.clone(), now()).dgram(); // Initial let server_initial = server.process(cinit2, now()).dgram().unwrap(); let server_handshake = server.process_output(now()).dgram().unwrap();
// We need to have the client just process the Initial. let dgram = client.process(Some(server_initial), now()).dgram();
assert!(dgram.is_some());
assert_ne!(*client.state(), State::Connected);
let retry = retry_server.process(cinit, now()).dgram(); // Retry!
assert!(retry.is_some());
assertions::assert_retry(retry.as_ref().unwrap());
// The client should ignore the retry. let junk = client.process(retry, now()).dgram();
assert!(junk.is_none());
// Either way, the client should still be able to process the server handshake and connect. let dgram = client.process(Some(server_handshake), now()).dgram();
assert!(dgram.is_some()); // Drop this one.
assert!(test_fixture::maybe_authenticate(&mut client)); let dgram = server.process(dgram, now()).dgram(); let dgram = client.process(dgram, now()).dgram();
assert!(dgram.is_some());
let dgram = client.process_output(now()).dgram(); // Initial let dgram2 = client.process_output(now()).dgram(); // Initial
assert!(dgram.is_some() && dgram2.is_some());
_ = server.process(dgram, now()).dgram(); // Retry let dgram = server.process(dgram2, now()).dgram(); // Retry
assert!(dgram.is_some());
let retry = &dgram.as_ref().unwrap();
assertions::assert_retry(retry);
letmut tweaked = retry.to_vec();
tweaked[retry.len() - 1] ^= 0x45; // damage the auth tag let tweaked_packet = Datagram::new(retry.source(), retry.destination(), retry.tos(), tweaked);
// The client should ignore this packet. let dgram = client.process(Some(tweaked_packet), now()).dgram();
assert!(dgram.is_none());
assert_dscp(&client.stats());
}
// Send a retry to one server, then replay it to the other. let client_initial1 = client.process_output(now()).dgram();
assert!(client_initial1.is_some()); let retry = retry_server.process(client_initial1, now()).dgram();
assert!(retry.is_some()); let client_initial2 = client.process(retry, now()).dgram();
assert!(client_initial2.is_some());
let dgram = server.process(client_initial2, now()).dgram();
assert!(dgram.is_none());
assert_dscp(&client.stats());
}
// This is really a client test, but we need a server with Retry to test it. // In this test, the client sends Initial on PTO. The Retry should cause // all loss recovery timers to be reset, but we had a bug where the PTO timer // was not properly reset. This tests that the client generates a new Initial // in response to receiving a Retry, even after it sends the Initial on PTO. #[test] fn retry_after_pto() { letmut client = default_client(); letmut server = default_server();
server.set_validation(ValidateAddress::Always); letmut now = now();
let ci = client.process_output(now).dgram(); let ci2 = client.process_output(now).dgram();
assert!(ci.is_some() && ci2.is_some()); // sit on this for a bit
// Let PTO fire on the client and then let it exhaust its PTO packets.
now += Duration::from_secs(1); let pto = client.process_output(now).dgram();
assert!(pto.unwrap().len() >= MIN_INITIAL_PACKET_SIZE);
_ = client.process_output(now).dgram(); let cb = client.process_output(now).callback();
assert_ne!(cb, Duration::new(0, 0));
_ = server.process(ci, now).dgram(); let retry = server.process(ci2, now).dgram();
assertions::assert_retry(retry.as_ref().unwrap());
let ci2 = client.process(retry, now).dgram();
assert!(ci2.unwrap().len() >= MIN_INITIAL_PACKET_SIZE);
assert_dscp(&client.stats());
}
// This tests a simulated on-path attacker that intercepts the first // client Initial packet and spoofs a retry. // The tricky part is in rewriting the second client Initial so that // the server doesn't reject the Initial for having a bad token. // The client is the only one that can detect this, and that is because // the original connection ID is not in transport parameters. // // Note that this depends on having the server produce a CID that is // at least 8 bytes long. Otherwise, the second Initial won't have a // long enough connection ID. #[test] fn mitm_retry() { // This test decrypts packets and hence does not work with MLKEM and packet number randomization // enabled. letmut client = test_fixture::new_client::<CountingConnectionIdGenerator>(
ConnectionParameters::default().mlkem(false),
); letmut retry_server = default_server();
retry_server.set_validation(ValidateAddress::Always); letmut server = default_server();
// Trigger initial and a second client Initial. let client_initial1 = client.process_output(now()).dgram();
assert!(client_initial1.is_some()); let retry = retry_server.process(client_initial1, now()).dgram();
assert!(retry.is_some()); let client_initial2 = client.process(retry, now()).dgram();
assert!(client_initial2.is_some());
// Now to start the epic process of decrypting the packet, // rewriting the header to remove the token, and then re-encrypting. let client_initial2 = client_initial2.unwrap(); let (protected_header, d_cid, s_cid, payload) =
decode_initial_header(&client_initial2, Role::Client).unwrap();
// Now we have enough information to make keys. let (aead_enc, aead_dec, hp) = initial_aead_and_hp(d_cid, Role::Client); let (header, pn) = header_protection::remove(&hp, protected_header, payload); let pn_len = header.len() - protected_header.len();
let new_datagram = Datagram::new(
client_initial2.source(),
client_initial2.destination(),
client_initial2.tos(),
notoken_packet,
);
qdebug!("passing modified Initial to the main server"); let dgram = server.process(Some(new_datagram), now()).dgram();
assert!(dgram.is_some());
let dgram = client.process(dgram, now()).dgram(); // Generate an ACK.
assert!(dgram.is_some()); let dgram = server.process(dgram, now()).dgram();
assert!(dgram.is_none());
assert!(test_fixture::maybe_authenticate(&mut client)); let dgram = client.process(dgram, now()).dgram();
assert!(dgram.is_some()); // Client sending CLOSE_CONNECTIONs
assert!(matches!(
*client.state(),
State::Closing {
error: CloseReason::Transport(Error::ProtocolViolation),
..
}
));
assert_dscp(&client.stats());
}
/// Server should reject Initial with DCID < 8 bytes. #[test] fn retry_short_dcid() { letmut client = test_fixture::new_client::<CountingConnectionIdGenerator>(
ConnectionParameters::default().mlkem(false),
); letmut server = default_server();
server.set_validation(ValidateAddress::Always);
let client_initial1 = client.process_output(now()).dgram().unwrap(); let (protected_header, d_cid, s_cid, payload) =
decode_initial_header(&client_initial1, Role::Client).unwrap();
let short_dcid = &[0x01, 0x02, 0x03, 0x04];
// Decrypt with the original DCID. let (_, aead_orig, hp_orig) = initial_aead_and_hp(d_cid, Role::Client); let (header, pn) = header_protection::remove(&hp_orig, protected_header, payload); let pn_len = header.len() - protected_header.len();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.