1use alloc::boxed::Box;
2use alloc::vec;
3use alloc::vec::Vec;
4use core::iter;
5
6use pki_types::{DnsName, EchConfigListBytes, ServerName};
7use subtle::ConstantTimeEq;
8
9use crate::CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV;
10use crate::client::tls13;
11use crate::crypto::SecureRandom;
12use crate::crypto::hash::Hash;
13use crate::crypto::hpke::{EncapsulatedSecret, Hpke, HpkePublicKey, HpkeSealer, HpkeSuite};
14use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer};
15use crate::log::{debug, trace, warn};
16use crate::msgs::base::{Payload, PayloadU16};
17use crate::msgs::codec::{Codec, Reader};
18use crate::msgs::enums::{ExtensionType, HpkeKem};
19use crate::msgs::handshake::{
20 ClientExtensions, ClientHelloPayload, EchConfigContents, EchConfigPayload, Encoding,
21 EncryptedClientHello, EncryptedClientHelloOuter, HandshakeMessagePayload, HandshakePayload,
22 HelloRetryRequest, HpkeKeyConfig, HpkeSymmetricCipherSuite, PresharedKeyBinder,
23 PresharedKeyOffer, Random, ServerHelloPayload, ServerNamePayload,
24};
25use crate::msgs::message::{Message, MessagePayload};
26use crate::msgs::persist;
27use crate::msgs::persist::Retrieved;
28use crate::tls13::key_schedule::{
29 KeyScheduleEarly, KeyScheduleHandshakeStart, server_ech_hrr_confirmation_secret,
30};
31use crate::{
32 AlertDescription, ClientConfig, CommonState, EncryptedClientHelloError, Error,
33 PeerIncompatible, PeerMisbehaved, ProtocolVersion, Tls13CipherSuite,
34};
35
36#[derive(Clone, Debug)]
38pub enum EchMode {
39 Enable(EchConfig),
42
43 Grease(EchGreaseConfig),
48}
49
50impl EchMode {
51 pub fn fips(&self) -> bool {
53 match self {
54 Self::Enable(ech_config) => ech_config.suite.fips(),
55 Self::Grease(grease_config) => grease_config.suite.fips(),
56 }
57 }
58}
59
60impl From<EchConfig> for EchMode {
61 fn from(config: EchConfig) -> Self {
62 Self::Enable(config)
63 }
64}
65
66impl From<EchGreaseConfig> for EchMode {
67 fn from(config: EchGreaseConfig) -> Self {
68 Self::Grease(config)
69 }
70}
71
72#[derive(Clone, Debug)]
76pub struct EchConfig {
77 pub(crate) config: EchConfigPayload,
79
80 pub(crate) suite: &'static dyn Hpke,
83}
84
85impl EchConfig {
86 pub fn new(
101 ech_config_list: EchConfigListBytes<'_>,
102 hpke_suites: &[&'static dyn Hpke],
103 ) -> Result<Self, Error> {
104 let ech_configs = Vec::<EchConfigPayload>::read(&mut Reader::init(&ech_config_list))
105 .map_err(|_| {
106 Error::InvalidEncryptedClientHello(EncryptedClientHelloError::InvalidConfigList)
107 })?;
108
109 #[cfg_attr(not(feature = "logging"), allow(clippy::unused_enumerate_index))]
112 for (_i, config) in ech_configs.iter().enumerate() {
113 let contents = match config {
114 EchConfigPayload::V18(contents) => contents,
115 EchConfigPayload::Unknown {
116 version: _version, ..
117 } => {
118 warn!(
119 "ECH config {} has unsupported version {:?}",
120 _i + 1,
121 _version
122 );
123 continue; }
125 };
126
127 if contents.has_unknown_mandatory_extension() || contents.has_duplicate_extension() {
128 warn!("ECH config has duplicate, or unknown mandatory extensions: {contents:?}",);
129 continue; }
131
132 let key_config = &contents.key_config;
133 for cipher_suite in &key_config.symmetric_cipher_suites {
134 if cipher_suite.aead_id.tag_len().is_none() {
135 continue; }
137
138 let suite = HpkeSuite {
139 kem: key_config.kem_id,
140 sym: *cipher_suite,
141 };
142 if let Some(hpke) = hpke_suites
143 .iter()
144 .find(|hpke| hpke.suite() == suite)
145 {
146 debug!(
147 "selected ECH config ID {:?} suite {:?} public_name {:?}",
148 key_config.config_id, suite, contents.public_name
149 );
150 return Ok(Self {
151 config: config.clone(),
152 suite: *hpke,
153 });
154 }
155 }
156 }
157
158 Err(EncryptedClientHelloError::NoCompatibleConfig.into())
159 }
160
161 pub(super) fn state(
162 &self,
163 server_name: ServerName<'static>,
164 config: &ClientConfig,
165 ) -> Result<EchState, Error> {
166 EchState::new(
167 self,
168 server_name.clone(),
169 config
170 .client_auth_cert_resolver
171 .has_certs(),
172 config.provider.secure_random,
173 config.enable_sni,
174 )
175 }
176
177 pub(crate) fn hpke_info(&self) -> Vec<u8> {
181 let mut info = Vec::with_capacity(128);
182 info.extend_from_slice(b"tls ech\0");
184 self.config.encode(&mut info);
185 info
186 }
187}
188
189#[derive(Clone, Debug)]
191pub struct EchGreaseConfig {
192 pub(crate) suite: &'static dyn Hpke,
193 pub(crate) placeholder_key: HpkePublicKey,
194}
195
196impl EchGreaseConfig {
197 pub fn new(suite: &'static dyn Hpke, placeholder_key: HpkePublicKey) -> Self {
207 Self {
208 suite,
209 placeholder_key,
210 }
211 }
212
213 pub(crate) fn grease_ext(
218 &self,
219 secure_random: &'static dyn SecureRandom,
220 inner_name: ServerName<'static>,
221 outer_hello: &ClientHelloPayload,
222 ) -> Result<EncryptedClientHello, Error> {
223 trace!("Preparing GREASE ECH extension");
224
225 let mut config_id: [u8; 1] = [0; 1];
227 secure_random.fill(&mut config_id[..])?;
228
229 let suite = self.suite.suite();
230
231 let mut grease_state = EchState::new(
234 &EchConfig {
235 config: EchConfigPayload::V18(EchConfigContents {
236 key_config: HpkeKeyConfig {
237 config_id: config_id[0],
238 kem_id: HpkeKem::DHKEM_P256_HKDF_SHA256,
239 public_key: PayloadU16::new(self.placeholder_key.0.clone()),
240 symmetric_cipher_suites: vec![suite.sym],
241 },
242 maximum_name_length: 0,
243 public_name: DnsName::try_from("filler").unwrap(),
244 extensions: Vec::default(),
245 }),
246 suite: self.suite,
247 },
248 inner_name,
249 false,
250 secure_random,
251 false, )?;
253
254 let encoded_inner_hello = grease_state.encode_inner_hello(outer_hello, None, &None);
257
258 let payload_len = encoded_inner_hello.len()
260 + suite
261 .sym
262 .aead_id
263 .tag_len()
264 .unwrap();
267 let mut payload = vec![0; payload_len];
268 secure_random.fill(&mut payload)?;
269
270 Ok(EncryptedClientHello::Outer(EncryptedClientHelloOuter {
272 cipher_suite: suite.sym,
273 config_id: config_id[0],
274 enc: PayloadU16::new(grease_state.enc.0),
275 payload: PayloadU16::new(payload),
276 }))
277 }
278}
279
280#[derive(Debug, Clone, Copy, Eq, PartialEq)]
282pub enum EchStatus {
283 NotOffered,
285 Grease,
287 Offered,
289 Accepted,
291 Rejected,
293}
294
295pub(crate) struct EchState {
297 pub(crate) outer_name: DnsName<'static>,
300 pub(crate) early_data_key_schedule: Option<KeyScheduleEarly>,
303 pub(crate) inner_hello_random: Random,
305 pub(crate) inner_hello_transcript: HandshakeHashBuffer,
308 secure_random: &'static dyn SecureRandom,
310 sender: Box<dyn HpkeSealer>,
312 config_id: u8,
314 inner_name: ServerName<'static>,
316 maximum_name_length: u8,
319 cipher_suite: HpkeSymmetricCipherSuite,
322 enc: EncapsulatedSecret,
325 enable_sni: bool,
327 sent_extensions: Vec<ExtensionType>,
329}
330
331impl EchState {
332 pub(crate) fn new(
333 config: &EchConfig,
334 inner_name: ServerName<'static>,
335 client_auth_enabled: bool,
336 secure_random: &'static dyn SecureRandom,
337 enable_sni: bool,
338 ) -> Result<Self, Error> {
339 let EchConfigPayload::V18(config_contents) = &config.config else {
340 unreachable!("ECH config version mismatch");
343 };
344 let key_config = &config_contents.key_config;
345
346 let (enc, sender) = config.suite.setup_sealer(
349 &config.hpke_info(),
350 &HpkePublicKey(key_config.public_key.0.clone()),
351 )?;
352
353 let mut inner_hello_transcript = HandshakeHashBuffer::new();
355 if client_auth_enabled {
356 inner_hello_transcript.set_client_auth_enabled();
357 }
358
359 Ok(Self {
360 secure_random,
361 sender,
362 config_id: key_config.config_id,
363 inner_name,
364 outer_name: config_contents.public_name.clone(),
365 maximum_name_length: config_contents.maximum_name_length,
366 cipher_suite: config.suite.suite().sym,
367 enc,
368 inner_hello_random: Random::new(secure_random)?,
369 inner_hello_transcript,
370 early_data_key_schedule: None,
371 enable_sni,
372 sent_extensions: Vec::new(),
373 })
374 }
375
376 pub(crate) fn ech_hello(
385 &mut self,
386 mut outer_hello: ClientHelloPayload,
387 retry_req: Option<&HelloRetryRequest>,
388 resuming: &Option<Retrieved<&persist::Tls13ClientSessionValue>>,
389 ) -> Result<ClientHelloPayload, Error> {
390 trace!(
391 "Preparing ECH offer {}",
392 if retry_req.is_some() { "for retry" } else { "" }
393 );
394
395 let encoded_inner_hello = self.encode_inner_hello(&outer_hello, retry_req, resuming);
397
398 let payload_len = encoded_inner_hello.len()
402 + self
403 .cipher_suite
404 .aead_id
405 .tag_len()
406 .unwrap();
409
410 let enc = match retry_req.is_some() {
412 true => Vec::default(),
413 false => self.enc.0.clone(),
414 };
415
416 fn outer_hello_ext(ctx: &EchState, enc: Vec<u8>, payload: Vec<u8>) -> EncryptedClientHello {
417 EncryptedClientHello::Outer(EncryptedClientHelloOuter {
418 cipher_suite: ctx.cipher_suite,
419 config_id: ctx.config_id,
420 enc: PayloadU16::new(enc),
421 payload: PayloadU16::new(payload),
422 })
423 }
424
425 if let Some(psk_offer) = outer_hello.preshared_key_offer.as_mut() {
430 self.grease_psk(psk_offer)?;
431 }
432
433 outer_hello.encrypted_client_hello =
435 Some(outer_hello_ext(self, enc.clone(), vec![0; payload_len]));
436
437 let payload = self
439 .sender
440 .seal(&outer_hello.get_encoding(), &encoded_inner_hello)?;
441
442 outer_hello.encrypted_client_hello = Some(outer_hello_ext(self, enc, payload));
444
445 Ok(outer_hello)
446 }
447
448 pub(crate) fn confirm_acceptance(
450 self,
451 ks: &mut KeyScheduleHandshakeStart,
452 server_hello: &ServerHelloPayload,
453 server_hello_encoded: &Payload<'_>,
454 hash: &'static dyn Hash,
455 ) -> Result<Option<EchAccepted>, Error> {
456 let inner_transcript = self
458 .inner_hello_transcript
459 .start_hash(hash);
460
461 let mut confirmation_transcript = inner_transcript.clone();
464
465 confirmation_transcript
468 .add_message(&Self::server_hello_conf(server_hello, server_hello_encoded));
469
470 let derived = ks.server_ech_confirmation_secret(
472 self.inner_hello_random.0.as_ref(),
473 confirmation_transcript.current_hash(),
474 );
475
476 match ConstantTimeEq::ct_eq(derived.as_ref(), server_hello.random.0[24..].as_ref()).into() {
481 true => {
482 trace!("ECH accepted by server");
483 Ok(Some(EchAccepted {
484 transcript: inner_transcript,
485 random: self.inner_hello_random,
486 sent_extensions: self.sent_extensions,
487 }))
488 }
489 false => {
490 trace!("ECH rejected by server");
491 Ok(None)
492 }
493 }
494 }
495
496 pub(crate) fn confirm_hrr_acceptance(
497 &self,
498 hrr: &HelloRetryRequest,
499 cs: &Tls13CipherSuite,
500 common: &mut CommonState,
501 ) -> Result<bool, Error> {
502 let ech_conf = match &hrr.encrypted_client_hello {
504 None => return Ok(false),
506 Some(ech_conf) if ech_conf.bytes().len() != 8 => {
509 return Err({
510 common.send_fatal_alert(
511 AlertDescription::DecodeError,
512 PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch,
513 )
514 });
515 }
516 Some(ech_conf) => ech_conf,
517 };
518
519 let confirmation_transcript = self.inner_hello_transcript.clone();
522 let mut confirmation_transcript =
523 confirmation_transcript.start_hash(cs.common.hash_provider);
524 confirmation_transcript.rollup_for_hrr();
525 confirmation_transcript.add_message(&Self::hello_retry_request_conf(hrr));
526
527 let derived = server_ech_hrr_confirmation_secret(
528 cs.hkdf_provider,
529 &self.inner_hello_random.0,
530 confirmation_transcript.current_hash(),
531 );
532
533 match ConstantTimeEq::ct_eq(derived.as_ref(), ech_conf.bytes()).into() {
534 true => {
535 trace!("ECH accepted by server in hello retry request");
536 Ok(true)
537 }
538 false => {
539 trace!("ECH rejected by server in hello retry request");
540 Ok(false)
541 }
542 }
543 }
544
545 pub(crate) fn transcript_hrr_update(&mut self, hash: &'static dyn Hash, m: &Message<'_>) {
550 trace!("Updating ECH inner transcript for HRR");
551
552 let inner_transcript = self
553 .inner_hello_transcript
554 .clone()
555 .start_hash(hash);
556
557 let mut inner_transcript_buffer = inner_transcript.into_hrr_buffer();
558 inner_transcript_buffer.add_message(m);
559 self.inner_hello_transcript = inner_transcript_buffer;
560 }
561
562 fn encode_inner_hello(
564 &mut self,
565 outer_hello: &ClientHelloPayload,
566 retryreq: Option<&HelloRetryRequest>,
567 resuming: &Option<Retrieved<&persist::Tls13ClientSessionValue>>,
568 ) -> Vec<u8> {
569 let mut inner_hello = ClientHelloPayload {
571 client_version: outer_hello.client_version,
573 session_id: outer_hello.session_id,
574 compression_methods: outer_hello.compression_methods.clone(),
575
576 extensions: Box::new(ClientExtensions::default()),
578
579 random: self.inner_hello_random,
583
584 cipher_suites: outer_hello
588 .cipher_suites
589 .iter()
590 .filter(|cs| **cs != TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
591 .cloned()
592 .collect(),
593 };
594
595 inner_hello.order_seed = outer_hello.order_seed;
596
597 inner_hello.encrypted_client_hello = Some(EncryptedClientHello::Inner);
600
601 let inner_sni = match &self.inner_name {
602 ServerName::DnsName(dns_name) if self.enable_sni => Some(dns_name),
605 _ => None,
606 };
607
608 let outer_extensions = outer_hello.used_extensions_in_encoding_order();
614 let mut compressed_exts = Vec::with_capacity(outer_extensions.len());
615 for ext in outer_extensions {
616 if matches!(
620 ext,
621 ExtensionType::ExtendedMasterSecret
622 | ExtensionType::SessionTicket
623 | ExtensionType::ECPointFormats
624 ) {
625 continue;
626 }
627
628 if ext == ExtensionType::ServerName {
629 if let Some(sni_value) = inner_sni {
631 inner_hello.server_name = Some(ServerNamePayload::from(sni_value));
632 }
633 continue;
635 }
636
637 if ext.ech_compress() {
640 compressed_exts.push(ext);
641 }
642
643 inner_hello.clone_one(outer_hello, ext);
644 }
645
646 inner_hello.contiguous_extensions = compressed_exts.clone();
649
650 self.sent_extensions = inner_hello.collect_used();
654
655 if let Some(resuming) = resuming.as_ref() {
657 let mut chp = HandshakeMessagePayload(HandshakePayload::ClientHello(inner_hello));
658
659 self.early_data_key_schedule = Some(tls13::fill_in_psk_binder(
661 resuming,
662 &self.inner_hello_transcript,
663 &mut chp,
664 ));
665
666 inner_hello = match chp.0 {
669 HandshakePayload::ClientHello(chp) => chp,
670 _ => unreachable!(),
672 };
673 }
674
675 trace!("ECH Inner Hello: {inner_hello:#?}");
676
677 let mut encoded_hello = inner_hello.ech_inner_encoding(compressed_exts);
681
682 let max_name_len = usize::from(self.maximum_name_length);
685 let max_name_len = if max_name_len > 0 { max_name_len } else { 255 };
686
687 let name_padding_len = match &inner_hello.server_name {
688 Some(ServerNamePayload::SingleDnsName(name)) => {
689 Ord::max(0, max_name_len.saturating_sub(name.as_ref().len()))
692 }
693 _ => max_name_len + 9,
696 };
697 encoded_hello.extend(iter::repeat(0).take(name_padding_len));
698
699 let padding_len = 31 - ((encoded_hello.len() - 1) % 32);
702 encoded_hello.extend(iter::repeat(0).take(padding_len));
703
704 let inner_hello_msg = Message {
706 version: match retryreq {
707 Some(_) => ProtocolVersion::TLSv1_2,
711 None => ProtocolVersion::TLSv1_0,
717 },
718 payload: MessagePayload::handshake(HandshakeMessagePayload(
719 HandshakePayload::ClientHello(inner_hello),
720 )),
721 };
722
723 self.inner_hello_transcript
725 .add_message(&inner_hello_msg);
726
727 encoded_hello
728 }
729
730 fn grease_psk(&self, psk_offer: &mut PresharedKeyOffer) -> Result<(), Error> {
732 for ident in psk_offer.identities.iter_mut() {
733 self.secure_random
736 .fill(&mut ident.identity.0)?;
737 let mut ticket_age = [0_u8; 4];
740 self.secure_random
741 .fill(&mut ticket_age)?;
742 ident.obfuscated_ticket_age = u32::from_be_bytes(ticket_age);
743 }
744
745 psk_offer.binders = psk_offer
748 .binders
749 .iter()
750 .map(|old_binder| {
751 let mut new_binder = vec![0; old_binder.as_ref().len()];
754 self.secure_random
755 .fill(&mut new_binder)?;
756 Ok::<PresharedKeyBinder, Error>(PresharedKeyBinder::from(new_binder))
757 })
758 .collect::<Result<_, _>>()?;
759 Ok(())
760 }
761
762 fn server_hello_conf(
763 server_hello: &ServerHelloPayload,
764 server_hello_encoded: &Payload<'_>,
765 ) -> Message<'static> {
766 let mut encoded = server_hello_encoded.clone().into_vec();
774 encoded[SERVER_HELLO_ECH_CONFIRMATION_SPAN].fill(0x00);
775
776 Message {
777 version: ProtocolVersion::TLSv1_3,
778 payload: MessagePayload::Handshake {
779 encoded: Payload::Owned(encoded),
780 parsed: HandshakeMessagePayload(HandshakePayload::ServerHello(
781 server_hello.clone(),
782 )),
783 },
784 }
785 }
786
787 fn hello_retry_request_conf(retry_req: &HelloRetryRequest) -> Message<'_> {
788 Self::ech_conf_message(HandshakeMessagePayload(
789 HandshakePayload::HelloRetryRequest(retry_req.clone()),
790 ))
791 }
792
793 fn ech_conf_message(hmp: HandshakeMessagePayload<'_>) -> Message<'_> {
794 let mut hmp_encoded = Vec::new();
795 hmp.payload_encode(&mut hmp_encoded, Encoding::EchConfirmation);
796 Message {
797 version: ProtocolVersion::TLSv1_3,
798 payload: MessagePayload::Handshake {
799 encoded: Payload::new(hmp_encoded),
800 parsed: hmp,
801 },
802 }
803 }
804}
805
806const SERVER_HELLO_ECH_CONFIRMATION_SPAN: core::ops::Range<usize> =
814 (1 + 3 + 2 + 24)..(1 + 3 + 2 + 32);
815
816pub(crate) struct EchAccepted {
820 pub(crate) transcript: HandshakeHash,
821 pub(crate) random: Random,
822 pub(crate) sent_extensions: Vec<ExtensionType>,
823}
824
825pub(crate) fn fatal_alert_required(
826 retry_configs: Option<Vec<EchConfigPayload>>,
827 common: &mut CommonState,
828) -> Error {
829 common.send_fatal_alert(
830 AlertDescription::EncryptedClientHelloRequired,
831 PeerIncompatible::ServerRejectedEncryptedClientHello(retry_configs),
832 )
833}
834
835#[cfg(test)]
836mod tests {
837 use std::string::String;
838
839 use super::*;
840 use crate::enums::CipherSuite;
841 use crate::msgs::enums::{Compression, HpkeAead, HpkeKdf};
842 use crate::msgs::handshake::{Random, ServerExtensions, SessionId};
843
844 #[test]
845 fn server_hello_conf_alters_server_hello_random() {
846 let server_hello = ServerHelloPayload {
847 legacy_version: ProtocolVersion::TLSv1_2,
848 random: Random([0xffu8; 32]),
849 session_id: SessionId::empty(),
850 cipher_suite: CipherSuite::TLS13_AES_256_GCM_SHA384,
851 compression_method: Compression::Null,
852 extensions: Box::new(ServerExtensions::default()),
853 };
854 let message = Message {
855 version: ProtocolVersion::TLSv1_3,
856 payload: MessagePayload::handshake(HandshakeMessagePayload(
857 HandshakePayload::ServerHello(server_hello.clone()),
858 )),
859 };
860 let Message {
861 payload:
862 MessagePayload::Handshake {
863 encoded: server_hello_encoded_before,
864 ..
865 },
866 ..
867 } = &message
868 else {
869 unreachable!("ServerHello is a handshake message");
870 };
871
872 let message = EchState::server_hello_conf(&server_hello, server_hello_encoded_before);
873
874 let Message {
875 payload:
876 MessagePayload::Handshake {
877 encoded: server_hello_encoded_after,
878 ..
879 },
880 ..
881 } = &message
882 else {
883 unreachable!("ServerHello is a handshake message");
884 };
885
886 assert_eq!(
887 std::format!("{server_hello_encoded_before:x?}"),
888 "020000280303ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001302000000",
889 "beforehand eight bytes at end of Random should be 0xff here ^^^^^^^^^^^^^^^^ "
890 );
891 assert_eq!(
892 std::format!("{server_hello_encoded_after:x?}"),
893 "020000280303ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001302000000",
894 " afterwards those bytes are zeroed ^^^^^^^^^^^^^^^^ "
895 );
896 }
897
898 #[test]
899 fn inner_client_hello_length_conceals_inner_name_length() {
900 let base_inner_len = inner_hello_encoding_for_name(dns_name_of_len(1), true).len();
901 assert!(
902 base_inner_len % 32 == 0,
903 "inner hello length must be 32-byte padded"
904 );
905 assert!(
906 base_inner_len >= 256,
907 "inner hello must include inner name and its padding"
908 );
909
910 for inner_name_len in 1..251 {
911 assert_eq!(
912 inner_hello_encoding_for_name(dns_name_of_len(inner_name_len), true).len(),
913 base_inner_len,
914 "all inner hello lengths must be invariant wrt inner name length"
915 );
916 }
917 }
918
919 #[test]
920 fn inner_client_hello_length_does_not_leak_length_of_omitted_inner_name() {
921 let base_inner_len = inner_hello_encoding_for_name(dns_name_of_len(1), false).len();
922 assert!(
923 base_inner_len % 32 == 0,
924 "inner hello length must be 32-byte padded"
925 );
926 assert!(
927 base_inner_len >= 256,
928 "inner hello must include maximum_name_length bytes of padding"
929 );
930
931 for inner_name_len in 1..251 {
932 assert_eq!(
933 inner_hello_encoding_for_name(dns_name_of_len(inner_name_len), false).len(),
934 base_inner_len,
935 "all inner hello lengths must be invariant wrt inner name length"
936 );
937 }
938 }
939
940 fn inner_hello_encoding_for_name(name: DnsName<'static>, enable_sni: bool) -> Vec<u8> {
941 let config = EchConfig {
942 config: EchConfigPayload::V18(EchConfigContents {
943 key_config: HpkeKeyConfig {
944 config_id: 0,
945 kem_id: MockHpke::SUITE.kem,
946 public_key: PayloadU16::new(vec![0; 32]),
947 symmetric_cipher_suites: vec![],
948 },
949 maximum_name_length: 255,
950 public_name: DnsName::try_from("public").unwrap(),
951 extensions: vec![],
952 }),
953 suite: &MockHpke,
954 };
955
956 EchState::new(
957 &config,
958 ServerName::from(name.clone()),
959 false,
960 &FixedRandom,
961 enable_sni,
962 )
963 .unwrap()
964 .encode_inner_hello(
965 &ClientHelloPayload {
966 client_version: ProtocolVersion::TLSv1_3,
967 random: Random([0u8; 32]),
968 session_id: SessionId::empty(),
969 cipher_suites: vec![],
970 compression_methods: vec![Compression::Null],
971 extensions: Box::new(ClientExtensions {
972 server_name: Some(ServerNamePayload::from(&name)),
973 ..Default::default()
974 }),
975 },
976 None,
977 &None,
978 )
979 }
980
981 fn dns_name_of_len(mut len: usize) -> DnsName<'static> {
982 let mut s = String::new();
983 let labels = len.div_ceil(63);
984 for _ in 0..labels {
985 let chars = Ord::min(len, 63);
986 len -= chars;
987 for _ in 0..chars {
988 s.push('a');
989 }
990 if len != 0 {
991 s.push('.');
992 }
993 }
994 DnsName::try_from(s).unwrap()
995 }
996
997 #[derive(Debug)]
998 struct MockHpke;
999
1000 impl MockHpke {
1001 const SUITE: HpkeSuite = HpkeSuite {
1002 kem: HpkeKem::DHKEM_P256_HKDF_SHA256,
1003 sym: HpkeSymmetricCipherSuite {
1004 kdf_id: HpkeKdf::HKDF_SHA256,
1005 aead_id: HpkeAead::AES_128_GCM,
1006 },
1007 };
1008 }
1009
1010 impl Hpke for MockHpke {
1011 #[cfg_attr(coverage_nightly, coverage(off))]
1012 fn seal(
1013 &self,
1014 _info: &[u8],
1015 _aad: &[u8],
1016 _plaintext: &[u8],
1017 _pub_key: &HpkePublicKey,
1018 ) -> Result<(EncapsulatedSecret, Vec<u8>), Error> {
1019 todo!()
1020 }
1021
1022 fn setup_sealer(
1023 &self,
1024 _info: &[u8],
1025 _pub_key: &HpkePublicKey,
1026 ) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error> {
1027 Ok((EncapsulatedSecret(vec![]), Box::new(MockHpkeSealer)))
1028 }
1029
1030 #[cfg_attr(coverage_nightly, coverage(off))]
1031 fn open(
1032 &self,
1033 _enc: &EncapsulatedSecret,
1034 _info: &[u8],
1035 _aad: &[u8],
1036 _ciphertext: &[u8],
1037 _secret_key: &crate::crypto::hpke::HpkePrivateKey,
1038 ) -> Result<Vec<u8>, Error> {
1039 todo!()
1040 }
1041
1042 #[cfg_attr(coverage_nightly, coverage(off))]
1043 fn setup_opener(
1044 &self,
1045 _enc: &EncapsulatedSecret,
1046 _info: &[u8],
1047 _secret_key: &crate::crypto::hpke::HpkePrivateKey,
1048 ) -> Result<Box<dyn crate::crypto::hpke::HpkeOpener + 'static>, Error> {
1049 todo!()
1050 }
1051
1052 #[cfg_attr(coverage_nightly, coverage(off))]
1053 fn generate_key_pair(
1054 &self,
1055 ) -> Result<(HpkePublicKey, crate::crypto::hpke::HpkePrivateKey), Error> {
1056 todo!()
1057 }
1058
1059 fn suite(&self) -> HpkeSuite {
1060 Self::SUITE
1061 }
1062 }
1063
1064 #[derive(Debug)]
1065 struct MockHpkeSealer;
1066
1067 impl HpkeSealer for MockHpkeSealer {
1068 #[cfg_attr(coverage_nightly, coverage(off))]
1069 fn seal(&mut self, _aad: &[u8], _plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1070 todo!()
1071 }
1072 }
1073
1074 #[derive(Debug)]
1075 struct FixedRandom;
1076
1077 impl SecureRandom for FixedRandom {
1078 fn fill(&self, buf: &mut [u8]) -> Result<(), crate::rand::GetRandomFailed> {
1079 buf.fill(0x55);
1080 Ok(())
1081 }
1082 }
1083}