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 server_name: &mut ServerName<'static>,
456 ) -> Result<Option<EchAccepted>, Error> {
457 let inner_transcript = self
459 .inner_hello_transcript
460 .start_hash(hash);
461
462 let mut confirmation_transcript = inner_transcript.clone();
465
466 confirmation_transcript
469 .add_message(&Self::server_hello_conf(server_hello, server_hello_encoded));
470
471 let derived = ks.server_ech_confirmation_secret(
473 self.inner_hello_random.0.as_ref(),
474 confirmation_transcript.current_hash(),
475 );
476
477 match ConstantTimeEq::ct_eq(derived.as_ref(), server_hello.random.0[24..].as_ref()).into() {
482 true => {
483 trace!("ECH accepted by server");
484 Ok(Some(EchAccepted {
485 transcript: inner_transcript,
486 random: self.inner_hello_random,
487 sent_extensions: self.sent_extensions,
488 }))
489 }
490 false => {
491 trace!("ECH rejected by server");
492
493 *server_name = ServerName::DnsName(self.outer_name);
497
498 Ok(None)
499 }
500 }
501 }
502
503 pub(crate) fn confirm_hrr_acceptance(
504 &self,
505 hrr: &HelloRetryRequest,
506 cs: &Tls13CipherSuite,
507 common: &mut CommonState,
508 ) -> Result<bool, Error> {
509 let ech_conf = match &hrr.encrypted_client_hello {
511 None => return Ok(false),
513 Some(ech_conf) if ech_conf.bytes().len() != 8 => {
516 return Err({
517 common.send_fatal_alert(
518 AlertDescription::DecodeError,
519 PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch,
520 )
521 });
522 }
523 Some(ech_conf) => ech_conf,
524 };
525
526 let confirmation_transcript = self.inner_hello_transcript.clone();
529 let mut confirmation_transcript =
530 confirmation_transcript.start_hash(cs.common.hash_provider);
531 confirmation_transcript.rollup_for_hrr();
532 confirmation_transcript.add_message(&Self::hello_retry_request_conf(hrr));
533
534 let derived = server_ech_hrr_confirmation_secret(
535 cs.hkdf_provider,
536 &self.inner_hello_random.0,
537 confirmation_transcript.current_hash(),
538 );
539
540 match ConstantTimeEq::ct_eq(derived.as_ref(), ech_conf.bytes()).into() {
541 true => {
542 trace!("ECH accepted by server in hello retry request");
543 Ok(true)
544 }
545 false => {
546 trace!("ECH rejected by server in hello retry request");
547 Ok(false)
548 }
549 }
550 }
551
552 pub(crate) fn transcript_hrr_update(&mut self, hash: &'static dyn Hash, m: &Message<'_>) {
557 trace!("Updating ECH inner transcript for HRR");
558
559 let inner_transcript = self
560 .inner_hello_transcript
561 .clone()
562 .start_hash(hash);
563
564 let mut inner_transcript_buffer = inner_transcript.into_hrr_buffer();
565 inner_transcript_buffer.add_message(m);
566 self.inner_hello_transcript = inner_transcript_buffer;
567 }
568
569 fn encode_inner_hello(
571 &mut self,
572 outer_hello: &ClientHelloPayload,
573 retryreq: Option<&HelloRetryRequest>,
574 resuming: &Option<Retrieved<&persist::Tls13ClientSessionValue>>,
575 ) -> Vec<u8> {
576 let mut inner_hello = ClientHelloPayload {
578 client_version: outer_hello.client_version,
580 session_id: outer_hello.session_id,
581 compression_methods: outer_hello.compression_methods.clone(),
582
583 extensions: Box::new(ClientExtensions::default()),
585
586 random: self.inner_hello_random,
590
591 cipher_suites: outer_hello
595 .cipher_suites
596 .iter()
597 .filter(|cs| **cs != TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
598 .cloned()
599 .collect(),
600 };
601
602 inner_hello.order_seed = outer_hello.order_seed;
603
604 inner_hello.encrypted_client_hello = Some(EncryptedClientHello::Inner);
607
608 let inner_sni = match &self.inner_name {
609 ServerName::DnsName(dns_name) if self.enable_sni => Some(dns_name),
612 _ => None,
613 };
614
615 let outer_extensions = outer_hello.used_extensions_in_encoding_order();
621 let mut compressed_exts = Vec::with_capacity(outer_extensions.len());
622 for ext in outer_extensions {
623 if matches!(
627 ext,
628 ExtensionType::ExtendedMasterSecret
629 | ExtensionType::SessionTicket
630 | ExtensionType::ECPointFormats
631 ) {
632 continue;
633 }
634
635 if ext == ExtensionType::ServerName {
636 if let Some(sni_value) = inner_sni {
638 inner_hello.server_name = Some(ServerNamePayload::from(sni_value));
639 }
640 continue;
642 }
643
644 if ext.ech_compress() {
647 compressed_exts.push(ext);
648 }
649
650 inner_hello.clone_one(outer_hello, ext);
651 }
652
653 inner_hello.contiguous_extensions = compressed_exts.clone();
656
657 self.sent_extensions = inner_hello.collect_used();
661
662 if let Some(resuming) = resuming.as_ref() {
664 let mut chp = HandshakeMessagePayload(HandshakePayload::ClientHello(inner_hello));
665
666 self.early_data_key_schedule = Some(tls13::fill_in_psk_binder(
668 resuming,
669 &self.inner_hello_transcript,
670 &mut chp,
671 ));
672
673 inner_hello = match chp.0 {
676 HandshakePayload::ClientHello(chp) => chp,
677 _ => unreachable!(),
679 };
680 }
681
682 trace!("ECH Inner Hello: {inner_hello:#?}");
683
684 let mut encoded_hello = inner_hello.ech_inner_encoding(compressed_exts);
688
689 let max_name_len = usize::from(self.maximum_name_length);
692 let max_name_len = if max_name_len > 0 { max_name_len } else { 255 };
693
694 let name_padding_len = match &inner_hello.server_name {
695 Some(ServerNamePayload::SingleDnsName(name)) => {
696 Ord::max(0, max_name_len.saturating_sub(name.as_ref().len()))
699 }
700 _ => max_name_len + 9,
703 };
704 encoded_hello.extend(iter::repeat(0).take(name_padding_len));
705
706 let padding_len = 31 - ((encoded_hello.len() - 1) % 32);
709 encoded_hello.extend(iter::repeat(0).take(padding_len));
710
711 let inner_hello_msg = Message {
713 version: match retryreq {
714 Some(_) => ProtocolVersion::TLSv1_2,
718 None => ProtocolVersion::TLSv1_0,
724 },
725 payload: MessagePayload::handshake(HandshakeMessagePayload(
726 HandshakePayload::ClientHello(inner_hello),
727 )),
728 };
729
730 self.inner_hello_transcript
732 .add_message(&inner_hello_msg);
733
734 encoded_hello
735 }
736
737 fn grease_psk(&self, psk_offer: &mut PresharedKeyOffer) -> Result<(), Error> {
739 for ident in psk_offer.identities.iter_mut() {
740 self.secure_random
743 .fill(&mut ident.identity.0)?;
744 let mut ticket_age = [0_u8; 4];
747 self.secure_random
748 .fill(&mut ticket_age)?;
749 ident.obfuscated_ticket_age = u32::from_be_bytes(ticket_age);
750 }
751
752 psk_offer.binders = psk_offer
755 .binders
756 .iter()
757 .map(|old_binder| {
758 let mut new_binder = vec![0; old_binder.as_ref().len()];
761 self.secure_random
762 .fill(&mut new_binder)?;
763 Ok::<PresharedKeyBinder, Error>(PresharedKeyBinder::from(new_binder))
764 })
765 .collect::<Result<_, _>>()?;
766 Ok(())
767 }
768
769 fn server_hello_conf(
770 server_hello: &ServerHelloPayload,
771 server_hello_encoded: &Payload<'_>,
772 ) -> Message<'static> {
773 let mut encoded = server_hello_encoded.clone().into_vec();
781 encoded[SERVER_HELLO_ECH_CONFIRMATION_SPAN].fill(0x00);
782
783 Message {
784 version: ProtocolVersion::TLSv1_3,
785 payload: MessagePayload::Handshake {
786 encoded: Payload::Owned(encoded),
787 parsed: HandshakeMessagePayload(HandshakePayload::ServerHello(
788 server_hello.clone(),
789 )),
790 },
791 }
792 }
793
794 fn hello_retry_request_conf(retry_req: &HelloRetryRequest) -> Message<'_> {
795 Self::ech_conf_message(HandshakeMessagePayload(
796 HandshakePayload::HelloRetryRequest(retry_req.clone()),
797 ))
798 }
799
800 fn ech_conf_message(hmp: HandshakeMessagePayload<'_>) -> Message<'_> {
801 let mut hmp_encoded = Vec::new();
802 hmp.payload_encode(&mut hmp_encoded, Encoding::EchConfirmation);
803 Message {
804 version: ProtocolVersion::TLSv1_3,
805 payload: MessagePayload::Handshake {
806 encoded: Payload::new(hmp_encoded),
807 parsed: hmp,
808 },
809 }
810 }
811}
812
813const SERVER_HELLO_ECH_CONFIRMATION_SPAN: core::ops::Range<usize> =
821 (1 + 3 + 2 + 24)..(1 + 3 + 2 + 32);
822
823pub(crate) struct EchAccepted {
827 pub(crate) transcript: HandshakeHash,
828 pub(crate) random: Random,
829 pub(crate) sent_extensions: Vec<ExtensionType>,
830}
831
832pub(crate) fn fatal_alert_required(
833 retry_configs: Option<Vec<EchConfigPayload>>,
834 common: &mut CommonState,
835) -> Error {
836 common.send_fatal_alert(
837 AlertDescription::EncryptedClientHelloRequired,
838 PeerIncompatible::ServerRejectedEncryptedClientHello(retry_configs),
839 )
840}
841
842#[cfg(test)]
843mod tests {
844 use std::string::String;
845
846 use super::*;
847 use crate::enums::CipherSuite;
848 use crate::msgs::enums::{Compression, HpkeAead, HpkeKdf};
849 use crate::msgs::handshake::{Random, ServerExtensions, SessionId};
850
851 #[test]
852 fn server_hello_conf_alters_server_hello_random() {
853 let server_hello = ServerHelloPayload {
854 legacy_version: ProtocolVersion::TLSv1_2,
855 random: Random([0xffu8; 32]),
856 session_id: SessionId::empty(),
857 cipher_suite: CipherSuite::TLS13_AES_256_GCM_SHA384,
858 compression_method: Compression::Null,
859 extensions: Box::new(ServerExtensions::default()),
860 };
861 let message = Message {
862 version: ProtocolVersion::TLSv1_3,
863 payload: MessagePayload::handshake(HandshakeMessagePayload(
864 HandshakePayload::ServerHello(server_hello.clone()),
865 )),
866 };
867 let Message {
868 payload:
869 MessagePayload::Handshake {
870 encoded: server_hello_encoded_before,
871 ..
872 },
873 ..
874 } = &message
875 else {
876 unreachable!("ServerHello is a handshake message");
877 };
878
879 let message = EchState::server_hello_conf(&server_hello, server_hello_encoded_before);
880
881 let Message {
882 payload:
883 MessagePayload::Handshake {
884 encoded: server_hello_encoded_after,
885 ..
886 },
887 ..
888 } = &message
889 else {
890 unreachable!("ServerHello is a handshake message");
891 };
892
893 assert_eq!(
894 std::format!("{server_hello_encoded_before:x?}"),
895 "020000280303ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001302000000",
896 "beforehand eight bytes at end of Random should be 0xff here ^^^^^^^^^^^^^^^^ "
897 );
898 assert_eq!(
899 std::format!("{server_hello_encoded_after:x?}"),
900 "020000280303ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001302000000",
901 " afterwards those bytes are zeroed ^^^^^^^^^^^^^^^^ "
902 );
903 }
904
905 #[test]
906 fn inner_client_hello_length_conceals_inner_name_length() {
907 let base_inner_len = inner_hello_encoding_for_name(dns_name_of_len(1), true).len();
908 assert!(
909 base_inner_len % 32 == 0,
910 "inner hello length must be 32-byte padded"
911 );
912 assert!(
913 base_inner_len >= 256,
914 "inner hello must include inner name and its padding"
915 );
916
917 for inner_name_len in 1..251 {
918 assert_eq!(
919 inner_hello_encoding_for_name(dns_name_of_len(inner_name_len), true).len(),
920 base_inner_len,
921 "all inner hello lengths must be invariant wrt inner name length"
922 );
923 }
924 }
925
926 #[test]
927 fn inner_client_hello_length_does_not_leak_length_of_omitted_inner_name() {
928 let base_inner_len = inner_hello_encoding_for_name(dns_name_of_len(1), false).len();
929 assert!(
930 base_inner_len % 32 == 0,
931 "inner hello length must be 32-byte padded"
932 );
933 assert!(
934 base_inner_len >= 256,
935 "inner hello must include maximum_name_length bytes of padding"
936 );
937
938 for inner_name_len in 1..251 {
939 assert_eq!(
940 inner_hello_encoding_for_name(dns_name_of_len(inner_name_len), false).len(),
941 base_inner_len,
942 "all inner hello lengths must be invariant wrt inner name length"
943 );
944 }
945 }
946
947 fn inner_hello_encoding_for_name(name: DnsName<'static>, enable_sni: bool) -> Vec<u8> {
948 let config = EchConfig {
949 config: EchConfigPayload::V18(EchConfigContents {
950 key_config: HpkeKeyConfig {
951 config_id: 0,
952 kem_id: MockHpke::SUITE.kem,
953 public_key: PayloadU16::new(vec![0; 32]),
954 symmetric_cipher_suites: vec![],
955 },
956 maximum_name_length: 255,
957 public_name: DnsName::try_from("public").unwrap(),
958 extensions: vec![],
959 }),
960 suite: &MockHpke,
961 };
962
963 EchState::new(
964 &config,
965 ServerName::from(name.clone()),
966 false,
967 &FixedRandom,
968 enable_sni,
969 )
970 .unwrap()
971 .encode_inner_hello(
972 &ClientHelloPayload {
973 client_version: ProtocolVersion::TLSv1_3,
974 random: Random([0u8; 32]),
975 session_id: SessionId::empty(),
976 cipher_suites: vec![],
977 compression_methods: vec![Compression::Null],
978 extensions: Box::new(ClientExtensions {
979 server_name: Some(ServerNamePayload::from(&name)),
980 ..Default::default()
981 }),
982 },
983 None,
984 &None,
985 )
986 }
987
988 fn dns_name_of_len(mut len: usize) -> DnsName<'static> {
989 let mut s = String::new();
990 let labels = len.div_ceil(63);
991 for _ in 0..labels {
992 let chars = Ord::min(len, 63);
993 len -= chars;
994 for _ in 0..chars {
995 s.push('a');
996 }
997 if len != 0 {
998 s.push('.');
999 }
1000 }
1001 DnsName::try_from(s).unwrap()
1002 }
1003
1004 #[derive(Debug)]
1005 struct MockHpke;
1006
1007 impl MockHpke {
1008 const SUITE: HpkeSuite = HpkeSuite {
1009 kem: HpkeKem::DHKEM_P256_HKDF_SHA256,
1010 sym: HpkeSymmetricCipherSuite {
1011 kdf_id: HpkeKdf::HKDF_SHA256,
1012 aead_id: HpkeAead::AES_128_GCM,
1013 },
1014 };
1015 }
1016
1017 impl Hpke for MockHpke {
1018 #[cfg_attr(coverage_nightly, coverage(off))]
1019 fn seal(
1020 &self,
1021 _info: &[u8],
1022 _aad: &[u8],
1023 _plaintext: &[u8],
1024 _pub_key: &HpkePublicKey,
1025 ) -> Result<(EncapsulatedSecret, Vec<u8>), Error> {
1026 todo!()
1027 }
1028
1029 fn setup_sealer(
1030 &self,
1031 _info: &[u8],
1032 _pub_key: &HpkePublicKey,
1033 ) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error> {
1034 Ok((EncapsulatedSecret(vec![]), Box::new(MockHpkeSealer)))
1035 }
1036
1037 #[cfg_attr(coverage_nightly, coverage(off))]
1038 fn open(
1039 &self,
1040 _enc: &EncapsulatedSecret,
1041 _info: &[u8],
1042 _aad: &[u8],
1043 _ciphertext: &[u8],
1044 _secret_key: &crate::crypto::hpke::HpkePrivateKey,
1045 ) -> Result<Vec<u8>, Error> {
1046 todo!()
1047 }
1048
1049 #[cfg_attr(coverage_nightly, coverage(off))]
1050 fn setup_opener(
1051 &self,
1052 _enc: &EncapsulatedSecret,
1053 _info: &[u8],
1054 _secret_key: &crate::crypto::hpke::HpkePrivateKey,
1055 ) -> Result<Box<dyn crate::crypto::hpke::HpkeOpener + 'static>, Error> {
1056 todo!()
1057 }
1058
1059 #[cfg_attr(coverage_nightly, coverage(off))]
1060 fn generate_key_pair(
1061 &self,
1062 ) -> Result<(HpkePublicKey, crate::crypto::hpke::HpkePrivateKey), Error> {
1063 todo!()
1064 }
1065
1066 fn suite(&self) -> HpkeSuite {
1067 Self::SUITE
1068 }
1069 }
1070
1071 #[derive(Debug)]
1072 struct MockHpkeSealer;
1073
1074 impl HpkeSealer for MockHpkeSealer {
1075 #[cfg_attr(coverage_nightly, coverage(off))]
1076 fn seal(&mut self, _aad: &[u8], _plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1077 todo!()
1078 }
1079 }
1080
1081 #[derive(Debug)]
1082 struct FixedRandom;
1083
1084 impl SecureRandom for FixedRandom {
1085 fn fill(&self, buf: &mut [u8]) -> Result<(), crate::rand::GetRandomFailed> {
1086 buf.fill(0x55);
1087 Ok(())
1088 }
1089 }
1090}