echspec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ module EchSpec
2
+ module Log
3
+ class MessageStack
4
+ def initialize
5
+ @stack = []
6
+ end
7
+
8
+ # @param msg [TTTLS13::Message::$Object]
9
+ def <<(msg)
10
+ @stack << msg
11
+ end
12
+
13
+ def marshal
14
+ arr = []
15
+ arr = @stack.reduce(arr) { |sum, msg| sum << "\"#{MessageStack.msg2name(msg)}\":#{MessageStack.obj2json(msg)}" }
16
+ "{#{arr.reverse.join(',')}}"
17
+ end
18
+
19
+ # rubocop: disable Metrics/CyclomaticComplexity
20
+ # rubocop: disable Metrics/PerceivedComplexity
21
+ def self.msg2name(msg)
22
+ case msg
23
+ in TTTLS13::Message::ClientHello if msg.ch_inner?
24
+ 'ClientHelloInner'
25
+ in TTTLS13::Message::ClientHello
26
+ 'ClientHello'
27
+ in TTTLS13::Message::ServerHello if msg.hrr?
28
+ 'HelloRetryRequest'
29
+ in TTTLS13::Message::ServerHello
30
+ 'ServerHello'
31
+ in TTTLS13::Message::ChangeCipherSpec
32
+ 'ChangeCipherSpec'
33
+ in TTTLS13::Message::EncryptedExtensions
34
+ 'EncryptedExtensions'
35
+ in TTTLS13::Message::Certificate
36
+ 'Certificate'
37
+ in TTTLS13::Message::CertificateVerify
38
+ 'CertificateVerify'
39
+ in TTTLS13::Message::Finished
40
+ 'Finished'
41
+ in TTTLS13::Message::EndOfEarlyData
42
+ 'EndOfEarlyData'
43
+ in TTTLS13::Message::Alert
44
+ 'Alert'
45
+ end
46
+ end
47
+ # rubocop: enable Metrics/CyclomaticComplexity
48
+ # rubocop: enable Metrics/PerceivedComplexity
49
+
50
+ # rubocop: disable Metrics/CyclomaticComplexity
51
+ # rubocop: disable Metrics/PerceivedComplexity
52
+ def self.obj2json(obj)
53
+ case obj
54
+ in OpenSSL::X509::Certificate
55
+ obj.to_pem.gsub("\n", '\n')
56
+ in Numeric | TrueClass | FalseClass
57
+ obj.pretty_print_inspect
58
+ in ''
59
+ '""'
60
+ in String
61
+ "\"0x#{obj.unpack1('H*')}\""
62
+ in NilClass
63
+ 'null'
64
+ in Array
65
+ s = obj.map { |i| obj2json(i) }.join(',')
66
+ "[#{s}]"
67
+ in Hash
68
+ s = obj.map { |k, v| "#{obj2json(k)}:#{obj2json(v)}" }.join(',')
69
+ "{#{s}}"
70
+ in Object if !obj.instance_variables.empty?
71
+ arr = obj.instance_variables.map do |i|
72
+ k = i[1..]
73
+ v = obj2json(obj.instance_variable_get(i))
74
+ "\"#{k}\":#{v}"
75
+ end
76
+ "{#{arr.join(',')}}"
77
+ else
78
+ "\"$#{obj.class.name}\""
79
+ end
80
+ end
81
+ # rubocop: enable Metrics/CyclomaticComplexity
82
+ # rubocop: enable Metrics/PerceivedComplexity
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,26 @@
1
+ module EchSpec
2
+ class Ok
3
+ attr_reader :obj
4
+
5
+ def initialize(obj)
6
+ @obj = obj
7
+ end
8
+
9
+ def deconstruct
10
+ [@obj]
11
+ end
12
+ end
13
+
14
+ class Err
15
+ attr_reader :details, :message_stack
16
+
17
+ def initialize(details, message_stack)
18
+ @details = details
19
+ @message_stack = message_stack
20
+ end
21
+
22
+ def deconstruct
23
+ [@details, @message_stack]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,222 @@
1
+ module EchSpec
2
+ module Spec
3
+ class Spec5_1_10 < WithSocket
4
+ # Next it makes a copy of the client_hello field and copies the
5
+ # legacy_session_id field from ClientHelloOuter. It then looks for an
6
+ # "ech_outer_extensions" extension. If found, it replaces the extension
7
+ # with the corresponding sequence of extensions in the
8
+ # ClientHelloOuter. The server MUST abort the connection with an
9
+ # "illegal_parameter" alert if any of the following are true:
10
+ #
11
+ # * Any referenced extension is missing in ClientHelloOuter.
12
+ # * Any extension is referenced in OuterExtensions more than once.
13
+ # * "encrypted_client_hello" is referenced in OuterExtensions.
14
+ # * The extensions in ClientHelloOuter corresponding to those in
15
+ # OuterExtensions do not occur in the same order.
16
+ #
17
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22#section-5.1-10
18
+
19
+ # @return [EchSpec::SpecGroup]
20
+ def self.spec_group
21
+ SpecGroup.new(
22
+ '5.1-10',
23
+ [
24
+ SpecCase.new(
25
+ 'MUST abort with an "illegal_parameter" alert, if any referenced extension is missing in ClientHelloOuter.',
26
+ method(:validate_missing_referenced_extensions)
27
+ ),
28
+ SpecCase.new(
29
+ 'MUST abort with an "illegal_parameter" alert, if any extension is referenced in OuterExtensions more than once.',
30
+ method(:validate_duplicated_outer_extensions)
31
+ ),
32
+ SpecCase.new(
33
+ 'MUST abort with an "illegal_parameter" alert, if "encrypted_client_hello" is referenced in OuterExtensions.',
34
+ method(:validate_referenced_encrypted_client_hello)
35
+ ),
36
+ SpecCase.new(
37
+ 'MUST abort with an "illegal_parameter" alert, if the extensions in ClientHelloOuter corresponding to those in OuterExtensions do not occur in the same order.',
38
+ method(:validate_not_same_order_extensions)
39
+ )
40
+ ]
41
+ )
42
+ end
43
+
44
+ # @param hostname [String]
45
+ # @param port [Integer]
46
+ # @param ech_config [ECHConfig]
47
+ #
48
+ # @return [EchSpec::Ok | Err]
49
+ def self.validate_missing_referenced_extensions(hostname, port, ech_config)
50
+ Spec5_1_10.new.validate_invalid_ech_outer_extensions(hostname, port, ech_config, MissingReferencedExtensions)
51
+ end
52
+
53
+ # @param hostname [String]
54
+ # @param port [Integer]
55
+ # @param ech_config [ECHConfig]
56
+ #
57
+ # @return [EchSpec::Ok | Err]
58
+ def self.validate_duplicated_outer_extensions(hostname, port, ech_config)
59
+ Spec5_1_10.new.validate_invalid_ech_outer_extensions(hostname, port, ech_config, DuplicatedOuterExtensions)
60
+ end
61
+
62
+ # @param hostname [String]
63
+ # @param port [Integer]
64
+ # @param ech_config [ECHConfig]
65
+ #
66
+ # @return [EchSpec::Ok | Err]
67
+ def self.validate_referenced_encrypted_client_hello(hostname, port, ech_config)
68
+ Spec5_1_10.new.validate_invalid_ech_outer_extensions(hostname, port, ech_config, ReferencedEncryptedClientHello)
69
+ end
70
+
71
+ # @param hostname [String]
72
+ # @param port [Integer]
73
+ # @param ech_config [ECHConfig]
74
+ #
75
+ # @return [EchSpec::Ok | Err]
76
+ def self.validate_not_same_order_extensions(hostname, port, ech_config)
77
+ Spec5_1_10.new.validate_invalid_ech_outer_extensions(hostname, port, ech_config, NotSameOrderExtensions)
78
+ end
79
+
80
+ # @param hostname [String]
81
+ # @param port [Integer]
82
+ # @param ech_config [ECHConfig]
83
+ # @param super_extensions [TTTLS13::Message::Extension::$Object]
84
+ #
85
+ # @return [EchSpec::Ok | Err]
86
+ def validate_invalid_ech_outer_extensions(hostname, port, ech_config, super_extensions)
87
+ with_socket(hostname, port) do |socket|
88
+ recv = send_invalid_ech_outer_extensions(socket, hostname, ech_config, super_extensions)
89
+ return Err.new('did not send expected alert: illegal_parameter', message_stack) \
90
+ unless Spec.expect_alert(recv, :illegal_parameter)
91
+
92
+ Ok.new(nil)
93
+ end
94
+ end
95
+
96
+ def send_invalid_ech_outer_extensions(socket, hostname, ech_config, super_extensions)
97
+ conn = TLS13Client::Connection.new(socket, :client)
98
+ inner_ech = TTTLS13::Message::Extension::ECHClientHello.new_inner
99
+ exs, = TLS13Client.gen_ch_extensions(hostname)
100
+ exs = super_extensions.new(exs.values)
101
+ inner = TTTLS13::Message::ClientHello.new(
102
+ cipher_suites: TTTLS13::CipherSuites.new(
103
+ [
104
+ TTTLS13::CipherSuite::TLS_AES_256_GCM_SHA384,
105
+ TTTLS13::CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
106
+ TTTLS13::CipherSuite::TLS_AES_128_GCM_SHA256
107
+ ]
108
+ ),
109
+ extensions: exs.merge(
110
+ TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => inner_ech
111
+ )
112
+ )
113
+
114
+ selector = proc { |x| TLS13Client.select_ech_hpke_cipher_suite(x) }
115
+ ch, inner, = TTTLS13::Ech.offer_ech(inner, ech_config, selector)
116
+ conn.send_record(
117
+ TTTLS13::Message::Record.new(
118
+ type: TTTLS13::Message::ContentType::HANDSHAKE,
119
+ messages: [ch],
120
+ cipher: TTTLS13::Cryptograph::Passer.new
121
+ )
122
+ )
123
+ @stack << inner
124
+ @stack << ch
125
+
126
+ recv, = conn.recv_message(TTTLS13::Cryptograph::Passer.new)
127
+ @stack << recv
128
+
129
+ recv
130
+ end
131
+
132
+ class MissingReferencedExtensions < TTTLS13::Message::Extensions
133
+ # @param _ [Array of TTTLS13::Message::ExtensionType]
134
+ #
135
+ # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
136
+ def remove_and_replace!(_)
137
+ outer_extensions = [TTTLS13::Message::ExtensionType::KEY_SHARE]
138
+ tmp1 = filter { |k, _| !outer_extensions.include?(k) }
139
+
140
+ clear
141
+ replaced = TTTLS13::Message::Extensions.new
142
+
143
+ tmp1.each_value { |v| self << v; replaced << v }
144
+ # key_share is referenced, but it is missing in ClientHelloOuter.
145
+ replaced << TTTLS13::Message::Extension::ECHOuterExtensions.new(
146
+ [TTTLS13::Message::ExtensionType::KEY_SHARE]
147
+ )
148
+ replaced
149
+ end
150
+ end
151
+
152
+ class DuplicatedOuterExtensions < TTTLS13::Message::Extensions
153
+ # @param _ [Array of TTTLS13::Message::ExtensionType]
154
+ #
155
+ # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
156
+ def remove_and_replace!(_)
157
+ outer_extensions = [TTTLS13::Message::ExtensionType::KEY_SHARE]
158
+ tmp1 = filter { |k, _| !outer_extensions.include?(k) }
159
+ tmp2 = filter { |k, _| outer_extensions.include?(k) }
160
+
161
+ clear
162
+ replaced = TTTLS13::Message::Extensions.new
163
+
164
+ tmp1.each_value { |v| self << v; replaced << v }
165
+ tmp2.each_value { |v| self << v }
166
+ # key_share appears twice in OuterExtensions.
167
+ replaced << TTTLS13::Message::Extension::ECHOuterExtensions.new(
168
+ [TTTLS13::Message::ExtensionType::KEY_SHARE] * 2
169
+ )
170
+ replaced
171
+ end
172
+ end
173
+
174
+ class ReferencedEncryptedClientHello < TTTLS13::Message::Extensions
175
+ # @param _ [Array of TTTLS13::Message::ExtensionType]
176
+ #
177
+ # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
178
+ def remove_and_replace!(_)
179
+ outer_extensions = [TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO]
180
+ tmp1 = filter { |k, _| !outer_extensions.include?(k) }
181
+ tmp2 = filter { |k, _| outer_extensions.include?(k) }
182
+
183
+ clear
184
+ replaced = TTTLS13::Message::Extensions.new
185
+
186
+ tmp1.each_value { |v| self << v; replaced << v }
187
+ tmp2.each_value { |v| self << v }
188
+ # encrypted_client_hello appears in OuterExtensions.
189
+ replaced << TTTLS13::Message::Extension::ECHOuterExtensions.new(
190
+ [TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO]
191
+ )
192
+ replaced
193
+ end
194
+ end
195
+
196
+ class NotSameOrderExtensions < TTTLS13::Message::Extensions
197
+ # @param _ [Array of TTTLS13::Message::ExtensionType]
198
+ #
199
+ # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
200
+ def remove_and_replace!(_)
201
+ outer_extensions = [
202
+ TTTLS13::Message::ExtensionType::KEY_SHARE,
203
+ TTTLS13::Message::ExtensionType::SUPPORTED_VERSIONS
204
+ ]
205
+ tmp1 = filter { |k, _| !outer_extensions.include?(k) }
206
+ tmp2 = filter { |k, _| outer_extensions.include?(k) }
207
+
208
+ clear
209
+ replaced = TTTLS13::Message::Extensions.new
210
+
211
+ tmp1.each_value { |v| self << v; replaced << v }
212
+ tmp2.each_value { |v| self << v }
213
+ # extensions in ClientHelloOuter and OuterExtensions are not in the same order.
214
+ replaced << TTTLS13::Message::Extension::ECHOuterExtensions.new(
215
+ tmp2.keys.reverse
216
+ )
217
+ replaced
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,108 @@
1
+ module EchSpec
2
+ module Spec
3
+ class Spec5_1_9 < WithSocket
4
+ # The client-facing server computes ClientHelloInner by reversing this
5
+ # process. First it parses EncodedClientHelloInner, interpreting all
6
+ # bytes after client_hello as padding. If any padding byte is non-
7
+ # zero, the server MUST abort the connection with an
8
+ # "illegal_parameter" alert.
9
+ #
10
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22#section-5.1-9
11
+
12
+ # @return [EchSpec::SpecGroup]
13
+ def self.spec_group
14
+ SpecGroup.new(
15
+ '5.1-9',
16
+ [
17
+ SpecCase.new(
18
+ 'MUST abort with an "illegal_parameter" alert, if EncodedClientHelloInner is padded with non-zero values.',
19
+ method(:validate_nonzero_padding_encoded_ch_inner)
20
+ )
21
+ ]
22
+ )
23
+ end
24
+
25
+ # @param hostname [String]
26
+ # @param port [Integer]
27
+ # @param ech_config [ECHConfig]
28
+ #
29
+ # @return [EchSpec::Ok | Err]
30
+ def self.validate_nonzero_padding_encoded_ch_inner(hostname, port, ech_config)
31
+ Spec5_1_9.new.do_validate_nonzero_padding_encoded_ch_inner(hostname, port, ech_config)
32
+ end
33
+
34
+ # @param hostname [String]
35
+ # @param port [Integer]
36
+ # @param ech_config [ECHConfig]
37
+ #
38
+ # @return [EchSpec::Ok | Err]
39
+ def do_validate_nonzero_padding_encoded_ch_inner(hostname, port, ech_config)
40
+ with_socket(hostname, port) do |socket|
41
+ recv = send_nonzero_padding_encoded_ch_inner(socket, hostname, ech_config)
42
+ return Err.new('did not send expected alert: illegal_parameter', message_stack) \
43
+ unless Spec.expect_alert(recv, :illegal_parameter)
44
+
45
+ Ok.new(nil)
46
+ end
47
+ end
48
+
49
+ def send_nonzero_padding_encoded_ch_inner(socket, hostname, ech_config)
50
+ conn = TLS13Client::Connection.new(socket, :client)
51
+ inner_ech = TTTLS13::Message::Extension::ECHClientHello.new_inner
52
+ exs, = TLS13Client.gen_ch_extensions(hostname)
53
+ inner = TTTLS13::Message::ClientHello.new(
54
+ cipher_suites: TTTLS13::CipherSuites.new(
55
+ [
56
+ TTTLS13::CipherSuite::TLS_AES_256_GCM_SHA384,
57
+ TTTLS13::CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
58
+ TTTLS13::CipherSuite::TLS_AES_128_GCM_SHA256
59
+ ]
60
+ ),
61
+ extensions: exs.merge(
62
+ TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => inner_ech
63
+ )
64
+ )
65
+
66
+ selector = proc { |x| TLS13Client.select_ech_hpke_cipher_suite(x) }
67
+ ch, inner, = NonzeroPaddingEch.offer_ech(inner, ech_config, selector)
68
+ conn.send_record(
69
+ TTTLS13::Message::Record.new(
70
+ type: TTTLS13::Message::ContentType::HANDSHAKE,
71
+ messages: [ch],
72
+ cipher: TTTLS13::Cryptograph::Passer.new
73
+ )
74
+ )
75
+ @stack << inner
76
+ @stack << ch
77
+
78
+ recv, = conn.recv_message(TTTLS13::Cryptograph::Passer.new)
79
+ @stack << recv
80
+
81
+ recv
82
+ end
83
+
84
+ class NonzeroPaddingEch < TTTLS13::Ech
85
+ NON_ZERO = "\x11".freeze
86
+
87
+ # @param s [String]
88
+ # @param server_name_length [Integer]
89
+ # @param maximum_name_length [Integer]
90
+ #
91
+ # @return [String]
92
+ def self.padding_encoded_ch_inner(s,
93
+ server_name_length,
94
+ maximum_name_length)
95
+ padding_len =
96
+ if server_name_length.positive?
97
+ [maximum_name_length - server_name_length, 0].max
98
+ else
99
+ 9 + maximum_name_length
100
+ end
101
+
102
+ padding_len = 31 - ((s.length + padding_len - 1) % 32)
103
+ s + NON_ZERO * padding_len # padding with non-zero value
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,242 @@
1
+ module EchSpec
2
+ module Spec
3
+ class Spec7_5 < WithSocket
4
+ # If ECHClientHello.type is not a valid ECHClientHelloType, then the
5
+ # server MUST abort with an "illegal_parameter" alert.
6
+ #
7
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22#section-7-5
8
+
9
+ # @return [EchSpec::SpecGroup]
10
+ def self.spec_group
11
+ SpecGroup.new(
12
+ '7-5',
13
+ [
14
+ SpecCase.new(
15
+ 'MUST abort with an "illegal_parameter" alert, if ECHClientHello.type is not a valid ECHClientHelloType in ClientHelloInner.',
16
+ method(:validate_illegal_inner_ech_type)
17
+ ),
18
+ SpecCase.new(
19
+ 'MUST abort with an "illegal_parameter" alert, if ECHClientHello.type is not a valid ECHClientHelloType in ClientHelloOuter.',
20
+ method(:validate_illegal_outer_ech_type)
21
+ )
22
+ ]
23
+ )
24
+ end
25
+
26
+ # @param hostname [String]
27
+ # @param port [Integer]
28
+ # @param ech_config [ECHConfig]
29
+ #
30
+ # @return [EchSpec::Ok | Err]
31
+ def self.validate_illegal_inner_ech_type(hostname, port, ech_config)
32
+ Spec7_5.new.do_validate_illegal_ech_type(
33
+ hostname,
34
+ port,
35
+ ech_config,
36
+ :send_ch_illegal_inner_ech_type
37
+ )
38
+ end
39
+
40
+ # @param hostname [String]
41
+ # @param port [Integer]
42
+ # @param ech_config [ECHConfig]
43
+ #
44
+ # @return [EchSpec::Ok | Err]
45
+ def self.validate_illegal_outer_ech_type(hostname, port, ech_config)
46
+ Spec7_5.new.do_validate_illegal_ech_type(
47
+ hostname,
48
+ port,
49
+ ech_config,
50
+ :send_ch_illegal_outer_ech_type
51
+ )
52
+ end
53
+
54
+ # @param hostname [String]
55
+ # @param port [Integer]
56
+ # @param ech_config [ECHConfig]
57
+ # @param method [Method]
58
+ #
59
+ # @return [EchSpec::Ok | Err]
60
+ def do_validate_illegal_ech_type(hostname, port, ech_config, method)
61
+ with_socket(hostname, port) do |socket|
62
+ recv = send(method, socket, hostname, ech_config)
63
+ return Err.new('did not send expected alert: illegal_parameter', message_stack) \
64
+ unless Spec.expect_alert(recv, :illegal_parameter)
65
+
66
+ Ok.new(nil)
67
+ end
68
+ end
69
+
70
+ # @param socket [TCPSocket]
71
+ # @param hostname [String]
72
+ # @param ech_config [ECHConfig]
73
+ #
74
+ # @return [TTTLS13::Message::Record]
75
+ def send_ch_illegal_inner_ech_type(socket, hostname, ech_config)
76
+ conn = TLS13Client::Connection.new(socket, :client)
77
+ inner_ech = IllegalEchClientHello.new_inner
78
+ exs, = TLS13Client.gen_ch_extensions(hostname)
79
+ inner = TTTLS13::Message::ClientHello.new(
80
+ cipher_suites: TTTLS13::CipherSuites.new(
81
+ [
82
+ TTTLS13::CipherSuite::TLS_AES_256_GCM_SHA384,
83
+ TTTLS13::CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
84
+ TTTLS13::CipherSuite::TLS_AES_128_GCM_SHA256
85
+ ]
86
+ ),
87
+ extensions: exs.merge(
88
+ TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => inner_ech
89
+ )
90
+ )
91
+
92
+ selector = proc { |x| TLS13Client.select_ech_hpke_cipher_suite(x) }
93
+ ch, inner, = TTTLS13::Ech.offer_ech(inner, ech_config, selector)
94
+ conn.send_record(
95
+ TTTLS13::Message::Record.new(
96
+ type: TTTLS13::Message::ContentType::HANDSHAKE,
97
+ messages: [ch],
98
+ cipher: TTTLS13::Cryptograph::Passer.new
99
+ )
100
+ )
101
+ @stack << inner
102
+ @stack << ch
103
+
104
+ recv, = conn.recv_message(TTTLS13::Cryptograph::Passer.new)
105
+ @stack << recv
106
+
107
+ recv
108
+ end
109
+
110
+ # @param socket [TCPSocket]
111
+ # @param hostname [String]
112
+ # @param ech_config [ECHConfig]
113
+ #
114
+ # @return [TTTLS13::Message::Record]
115
+ # rubocop: disable Metrics/AbcSize
116
+ # rubocop: disable Metrics/MethodLength
117
+ def send_ch_illegal_outer_ech_type(socket, hostname, ech_config)
118
+ conn = TLS13Client::Connection.new(socket, :client)
119
+ inner_ech = TTTLS13::Message::Extension::ECHClientHello.new_inner
120
+ exs, = TLS13Client.gen_ch_extensions(hostname)
121
+ inner = TTTLS13::Message::ClientHello.new(
122
+ cipher_suites: TTTLS13::CipherSuites.new(
123
+ [
124
+ TTTLS13::CipherSuite::TLS_AES_256_GCM_SHA384,
125
+ TTTLS13::CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
126
+ TTTLS13::CipherSuite::TLS_AES_128_GCM_SHA256
127
+ ]
128
+ ),
129
+ extensions: exs.merge(
130
+ TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => inner_ech
131
+ )
132
+ )
133
+ @stack << inner
134
+
135
+ # offer_ech
136
+ selector = proc { |x| TLS13Client.select_ech_hpke_cipher_suite(x) }
137
+
138
+ # for ech_outer_extensions
139
+ replaced = \
140
+ inner.extensions.remove_and_replace!([])
141
+
142
+ # Encrypted ClientHello Configuration
143
+ ech_state, enc = TTTLS13::Ech.encrypted_ech_config(
144
+ ech_config,
145
+ selector
146
+ )
147
+ encoded = TTTLS13::Ech.encode_ch_inner(inner, ech_state.maximum_name_length, replaced)
148
+ overhead_len = TTTLS13::Ech.aead_id2overhead_len(
149
+ ech_state.cipher_suite.aead_id.uint16
150
+ )
151
+
152
+ # Encoding the ClientHelloInner
153
+ aad_ech = IllegalEchClientHello.new_outer(
154
+ cipher_suite: ech_state.cipher_suite,
155
+ config_id: ech_state.config_id,
156
+ enc:,
157
+ payload: '0' * (encoded.length + overhead_len)
158
+ )
159
+ aad = TTTLS13::Message::ClientHello.new(
160
+ legacy_version: inner.legacy_version,
161
+ legacy_session_id: inner.legacy_session_id,
162
+ cipher_suites: inner.cipher_suites,
163
+ legacy_compression_methods: inner.legacy_compression_methods,
164
+ extensions: inner.extensions.merge(
165
+ TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => aad_ech,
166
+ TTTLS13::Message::ExtensionType::SERVER_NAME => \
167
+ TTTLS13::Message::Extension::ServerName.new(ech_state.public_name)
168
+ )
169
+ )
170
+
171
+ # Authenticating the ClientHelloOuter
172
+ # which does not include the Handshake structure's four byte header.
173
+ outer_ech = IllegalEchClientHello.new_outer(
174
+ cipher_suite: ech_state.cipher_suite,
175
+ config_id: ech_state.config_id,
176
+ enc:,
177
+ payload: ech_state.ctx.seal(aad.serialize[4..], encoded)
178
+ )
179
+ outer = TTTLS13::Message::ClientHello.new(
180
+ legacy_version: aad.legacy_version,
181
+ random: aad.random,
182
+ legacy_session_id: aad.legacy_session_id,
183
+ cipher_suites: aad.cipher_suites,
184
+ legacy_compression_methods: aad.legacy_compression_methods,
185
+ extensions: aad.extensions.merge(
186
+ TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => outer_ech
187
+ )
188
+ )
189
+ conn.send_record(
190
+ TTTLS13::Message::Record.new(
191
+ type: TTTLS13::Message::ContentType::HANDSHAKE,
192
+ messages: [outer],
193
+ cipher: TTTLS13::Cryptograph::Passer.new
194
+ )
195
+ )
196
+ @stack << outer
197
+
198
+ recv, = conn.recv_message(TTTLS13::Cryptograph::Passer.new)
199
+ @stack << recv
200
+
201
+ recv
202
+ end
203
+ # rubocop: enable Metrics/AbcSize
204
+ # rubocop: enable Metrics/MethodLength
205
+
206
+ class IllegalEchClientHello < TTTLS13::Message::Extension::ECHClientHello
207
+ using TTTLS13::Refinements
208
+
209
+ ILLEGAL_OUTER = "\x02".freeze
210
+ ILLEGAL_INNER = "\x03".freeze
211
+
212
+ def self.new_inner
213
+ IllegalEchClientHello.new(type: ILLEGAL_INNER)
214
+ end
215
+
216
+ def self.new_outer(cipher_suite:, config_id:, enc:, payload:)
217
+ IllegalEchClientHello.new(
218
+ type: ILLEGAL_OUTER,
219
+ cipher_suite:,
220
+ config_id:,
221
+ enc:,
222
+ payload:
223
+ )
224
+ end
225
+
226
+ def serialize
227
+ case @type
228
+ when ILLEGAL_OUTER
229
+ binary = @type + @cipher_suite.encode + @config_id.to_uint8 \
230
+ + @enc.prefix_uint16_length + @payload.prefix_uint16_length
231
+ when ILLEGAL_INNER
232
+ binary = @type
233
+ else
234
+ return super
235
+ end
236
+
237
+ @extension_type + binary.prefix_uint16_length
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end