tttls1.3 0.2.18 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +8 -5
  3. data/Gemfile +2 -0
  4. data/README.md +6 -3
  5. data/example/helper.rb +5 -2
  6. data/example/https_client_using_0rtt.rb +1 -1
  7. data/example/https_client_using_ech.rb +32 -0
  8. data/example/https_client_using_grease_ech.rb +26 -0
  9. data/example/https_client_using_grease_psk.rb +66 -0
  10. data/example/https_client_using_hrr_and_ech.rb +32 -0
  11. data/example/https_client_using_hrr_and_ticket.rb +1 -1
  12. data/example/https_client_using_ticket.rb +1 -1
  13. data/interop/client_spec.rb +3 -2
  14. data/interop/server_spec.rb +1 -3
  15. data/interop/{helper.rb → spec_helper.rb} +12 -5
  16. data/lib/tttls1.3/client.rb +553 -32
  17. data/lib/tttls1.3/connection.rb +9 -8
  18. data/lib/tttls1.3/cryptograph/aead.rb +1 -1
  19. data/lib/tttls1.3/error.rb +1 -1
  20. data/lib/tttls1.3/hpke.rb +91 -0
  21. data/lib/tttls1.3/key_schedule.rb +111 -8
  22. data/lib/tttls1.3/message/alert.rb +2 -1
  23. data/lib/tttls1.3/message/client_hello.rb +2 -1
  24. data/lib/tttls1.3/message/encrypted_extensions.rb +2 -1
  25. data/lib/tttls1.3/message/extension/alpn.rb +4 -5
  26. data/lib/tttls1.3/message/extension/compress_certificate.rb +1 -1
  27. data/lib/tttls1.3/message/extension/ech.rb +241 -0
  28. data/lib/tttls1.3/message/extension/key_share.rb +2 -4
  29. data/lib/tttls1.3/message/extension/server_name.rb +1 -1
  30. data/lib/tttls1.3/message/extensions.rb +20 -7
  31. data/lib/tttls1.3/message/record.rb +1 -1
  32. data/lib/tttls1.3/message/server_hello.rb +3 -5
  33. data/lib/tttls1.3/message.rb +3 -1
  34. data/lib/tttls1.3/named_group.rb +1 -1
  35. data/lib/tttls1.3/server.rb +2 -2
  36. data/lib/tttls1.3/utils.rb +8 -0
  37. data/lib/tttls1.3/version.rb +1 -1
  38. data/lib/tttls1.3.rb +4 -0
  39. data/spec/client_spec.rb +40 -0
  40. data/spec/connection_spec.rb +22 -7
  41. data/spec/ech_spec.rb +81 -0
  42. data/spec/extensions_spec.rb +1 -2
  43. data/spec/key_schedule_spec.rb +2 -2
  44. data/spec/server_spec.rb +22 -7
  45. data/spec/spec_helper.rb +41 -5
  46. data/tttls1.3.gemspec +2 -0
  47. metadata +39 -3
@@ -5,6 +5,9 @@ module TTTLS13
5
5
  INITIAL = 0
6
6
  EOF = -1
7
7
 
8
+ SUPPORTED_ECHCONFIG_VERSIONS = ["\xfe\x0d"].freeze
9
+ private_constant :SUPPORTED_ECHCONFIG_VERSIONS
10
+
8
11
  # rubocop: disable Metrics/ClassLength
9
12
  class Connection
10
13
  include Logging
@@ -25,7 +28,7 @@ module TTTLS13
25
28
  @send_record_size = Message::DEFAULT_RECORD_SIZE_LIMIT
26
29
  @recv_record_size = Message::DEFAULT_RECORD_SIZE_LIMIT
27
30
  @alpn = nil # String
28
- @exporter_master_secret = nil # String
31
+ @exporter_secret = nil # String
29
32
  end
30
33
 
31
34
  # @raise [TTTLS13::Error::ConfigError]
@@ -109,15 +112,15 @@ module TTTLS13
109
112
  #
110
113
  # @return [String, nil]
111
114
  def exporter(label, context, key_length)
112
- return nil if @exporter_master_secret.nil? || @cipher_suite.nil?
115
+ return nil if @exporter_secret.nil? || @cipher_suite.nil?
113
116
 
114
117
  digest = CipherSuite.digest(@cipher_suite)
115
- do_exporter(@exporter_master_secret, digest, label, context, key_length)
118
+ do_exporter(@exporter_secret, digest, label, context, key_length)
116
119
  end
117
120
 
118
121
  private
119
122
 
120
- # @param secret [String] (early_)exporter_master_secret
123
+ # @param secret [String] (early_)exporter_secret
121
124
  # @param digest [String] name of digest algorithm
122
125
  # @param label [String]
123
126
  # @param context [String]
@@ -517,10 +520,8 @@ module TTTLS13
517
520
  #
518
521
  # @return [Array of TTTLS13::Message::Extension::SignatureAlgorithms]
519
522
  def do_select_signature_algorithms(signature_algorithms, crt)
520
- spki = OpenSSL::Netscape::SPKI.new
521
- spki.public_key = crt.public_key
522
- pka = OpenSSL::ASN1.decode(spki.to_der)
523
- .value.first.value.first.value.first.value.first.value
523
+ pka = OpenSSL::ASN1.decode(crt.public_key.to_der)
524
+ .value.first.value.first.value
524
525
  signature_algorithms.select do |sa|
525
526
  case sa
526
527
  when SignatureScheme::ECDSA_SECP256R1_SHA256,
@@ -45,7 +45,7 @@ module TTTLS13
45
45
  # @return [String]
46
46
  def encrypt(content, type)
47
47
  cipher = reset_cipher
48
- plaintext = content + type + "\x00" * @length_of_padding
48
+ plaintext = content + type + @length_of_padding.zeros
49
49
  cipher.auth_data = additional_data(plaintext.length)
50
50
  encrypted_data = cipher.update(plaintext) + cipher.final
51
51
  @sequence_number.succ
@@ -10,7 +10,7 @@ module TTTLS13
10
10
  class ConfigError < Error; end
11
11
 
12
12
  # Raised on received Error Alerts message or invalid message.
13
- # https://tools.ietf.org/html/rfc8446#section-6.2
13
+ # https://datatracker.ietf.org/doc/html/rfc8446#section-6.2
14
14
  # Terminated the connection, so you *cannot* recover from this exception.
15
15
  class ErrorAlerts < Error
16
16
  # @return [TTTLS13::Message::Alert]
@@ -0,0 +1,91 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ # NOTE: Hpke module is the adapter for ech_config using hpke-rb.
6
+ module Hpke
7
+ module KemId
8
+ # https://www.iana.org/assignments/hpke/hpke.xhtml#hpke-kem-ids
9
+ P_256_SHA256 = 0x0010
10
+ P_384_SHA384 = 0x0011
11
+ P_521_SHA512 = 0x0012
12
+ X25519_SHA256 = 0x0020
13
+ X448_SHA512 = 0x0021
14
+ end
15
+
16
+ def self.kem_id2dhkem(kem_id)
17
+ case kem_id
18
+ when KemId::P_256_SHA256
19
+ %i[p_256 sha256]
20
+ when KemId::P_384_SHA384
21
+ %i[p_384 sha384]
22
+ when KemId::P_521_SHA512
23
+ %i[p_521 sha512]
24
+ when KemId::X25519_SHA256
25
+ %i[x25519 sha256]
26
+ when KemId::X448_SHA512
27
+ %i[x448 sha512]
28
+ end
29
+ end
30
+
31
+ def self.kem_curve_name2dhkem(kem_curve_name)
32
+ case kem_curve_name
33
+ when :p_256
34
+ HPKE::DHKEM::EC::P_256
35
+ when :p_384
36
+ HPKE::DHKEM::EC::P_384
37
+ when :p_521
38
+ HPKE::DHKEM::EC::P_521
39
+ when :x25519
40
+ HPKE::DHKEM::X25519
41
+ when :x448
42
+ HPKE::DHKEM::X448
43
+ end
44
+ end
45
+
46
+ module KdfId
47
+ # https://www.iana.org/assignments/hpke/hpke.xhtml#hpke-kdf-ids
48
+ HKDF_SHA256 = 0x0001
49
+ HKDF_SHA384 = 0x0002
50
+ HKDF_SHA512 = 0x0003
51
+ end
52
+
53
+ def self.kdf_id2kdf_hash(kdf_id)
54
+ case kdf_id
55
+ when KdfId::HKDF_SHA256
56
+ :sha256
57
+ when KdfId::HKDF_SHA384
58
+ :sha384
59
+ when KdfId::HKDF_SHA512
60
+ :sha512
61
+ end
62
+ end
63
+
64
+ module AeadId
65
+ # https://www.iana.org/assignments/hpke/hpke.xhtml#hpke-aead-ids
66
+ AES_128_GCM = 0x0001
67
+ AES_256_GCM = 0x0002
68
+ CHACHA20_POLY1305 = 0x0003
69
+ end
70
+
71
+ def self.aead_id2overhead_len(aead_id)
72
+ case aead_id
73
+ when AeadId::AES_128_GCM, AeadId::CHACHA20_POLY1305
74
+ 16
75
+ when AeadId::AES_256_GCM
76
+ 32
77
+ end
78
+ end
79
+
80
+ def self.aead_id2aead_cipher(aead_id)
81
+ case aead_id
82
+ when AeadId::AES_128_GCM
83
+ :aes_128_gcm
84
+ when AeadId::AES_256_GCM
85
+ :aes_256_gcm
86
+ when AeadId::CHACHA20_POLY1305
87
+ :chacha20_poly1305
88
+ end
89
+ end
90
+ end
91
+ end
@@ -14,14 +14,14 @@ module TTTLS13
14
14
  @hash_len = CipherSuite.hash_len(cipher_suite)
15
15
  @key_len = CipherSuite.key_len(cipher_suite)
16
16
  @iv_len = CipherSuite.iv_len(cipher_suite)
17
- @psk = psk || "\x00" * @hash_len
17
+ @psk = psk || @hash_len.zeros
18
18
  @shared_secret = shared_secret
19
19
  @transcript = transcript
20
20
  end
21
21
 
22
22
  # @return [String]
23
23
  def early_salt
24
- "\x00" * @hash_len
24
+ @hash_len.zeros
25
25
  end
26
26
 
27
27
  # @return [String]
@@ -61,8 +61,15 @@ module TTTLS13
61
61
  self.class.hkdf_expand_label(secret, 'iv', '', @iv_len, @digest)
62
62
  end
63
63
 
64
+ # @deprecated Please use `early_exporter_secret` instead
65
+ #
64
66
  # @return [String]
65
67
  def early_exporter_master_secret
68
+ early_exporter_secret
69
+ end
70
+
71
+ # @return [String]
72
+ def early_exporter_secret
66
73
  hash = OpenSSL::Digest.digest(@digest, '')
67
74
  derive_secret(early_secret, 'e exp master', hash)
68
75
  end
@@ -126,22 +133,36 @@ module TTTLS13
126
133
  self.class.hkdf_expand_label(secret, 'iv', '', @iv_len, @digest)
127
134
  end
128
135
 
136
+ # @deprecated Please use `main_salt` instead
137
+ #
129
138
  # @return [String]
130
139
  def master_salt
140
+ main_salt
141
+ end
142
+
143
+ # @return [String]
144
+ def main_salt
131
145
  hash = OpenSSL::Digest.digest(@digest, '')
132
146
  derive_secret(handshake_secret, 'derived', hash)
133
147
  end
134
148
 
149
+ # @deprecated Please use `main_secret` instead
150
+ #
135
151
  # @return [String]
136
152
  def master_secret
137
- ikm = "\x00" * @hash_len
138
- hkdf_extract(ikm, master_salt)
153
+ main_secret
154
+ end
155
+
156
+ # @return [String]
157
+ def main_secret
158
+ ikm = @hash_len.zeros
159
+ hkdf_extract(ikm, main_salt)
139
160
  end
140
161
 
141
162
  # @return [String]
142
163
  def client_application_traffic_secret
143
164
  hash = @transcript.hash(@digest, SF)
144
- derive_secret(master_secret, 'c ap traffic', hash)
165
+ derive_secret(main_secret, 'c ap traffic', hash)
145
166
  end
146
167
 
147
168
  # @return [String]
@@ -159,7 +180,7 @@ module TTTLS13
159
180
  # @return [String]
160
181
  def server_application_traffic_secret
161
182
  hash = @transcript.hash(@digest, SF)
162
- derive_secret(master_secret, 's ap traffic', hash)
183
+ derive_secret(main_secret, 's ap traffic', hash)
163
184
  end
164
185
 
165
186
  # @return [String]
@@ -174,16 +195,30 @@ module TTTLS13
174
195
  self.class.hkdf_expand_label(secret, 'iv', '', @iv_len, @digest)
175
196
  end
176
197
 
198
+ # @deprecated Please use `exporter_secret` instead
199
+ #
177
200
  # @return [String]
178
201
  def exporter_master_secret
202
+ exporter_secret
203
+ end
204
+
205
+ # @return [String]
206
+ def exporter_secret
179
207
  hash = @transcript.hash(@digest, SF)
180
- derive_secret(master_secret, 'exp master', hash)
208
+ derive_secret(main_secret, 'exp master', hash)
181
209
  end
182
210
 
211
+ # @deprecated Please use `resumption_secret` instead
212
+ #
183
213
  # @return [String]
184
214
  def resumption_master_secret
215
+ resumption_secret
216
+ end
217
+
218
+ # @return [String]
219
+ def resumption_secret
185
220
  hash = @transcript.hash(@digest, CF)
186
- derive_secret(master_secret, 'res master', hash)
221
+ derive_secret(main_secret, 'res master', hash)
187
222
  end
188
223
 
189
224
  # @param ikm [String]
@@ -238,6 +273,74 @@ module TTTLS13
238
273
  def derive_secret(secret, label, context)
239
274
  self.class.hkdf_expand_label(secret, label, context, @hash_len, @digest)
240
275
  end
276
+
277
+ # @return [String]
278
+ def accept_confirmation
279
+ ch_inner_random = @transcript[CH].first.random
280
+ sh = @transcript[SH].first
281
+ sh = Message::ServerHello.new(
282
+ random: sh.random[...-8] + 8.zeros,
283
+ legacy_session_id_echo: sh.legacy_session_id_echo,
284
+ cipher_suite: sh.cipher_suite,
285
+ extensions: sh.extensions
286
+ )
287
+ transcript = @transcript.clone
288
+ transcript[SH] = [sh, sh.serialize]
289
+ transcript_ech_conf = transcript.hash(@digest, SH)
290
+
291
+ self.class.hkdf_expand_label(
292
+ hkdf_extract(ch_inner_random, ''),
293
+ 'ech accept confirmation',
294
+ transcript_ech_conf,
295
+ 8,
296
+ @digest
297
+ )
298
+ end
299
+
300
+ # @return [Boolean]
301
+ def accept_ech?
302
+ accept_confirmation == @transcript[SH].first.random[-8..]
303
+ end
304
+
305
+ # @return [String]
306
+ def hrr_accept_confirmation
307
+ ch1_inner_random = @transcript[CH1].first.random
308
+ hrr = @transcript[HRR].first
309
+ ech = Message::Extension::ECHHelloRetryRequest.new("\x00" * 8)
310
+ hrr = Message::ServerHello.new(
311
+ random: hrr.random,
312
+ legacy_session_id_echo: hrr.legacy_session_id_echo,
313
+ cipher_suite: hrr.cipher_suite,
314
+ extensions: hrr.extensions.merge(
315
+ Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => ech
316
+ )
317
+ )
318
+
319
+ # It then computes the transcript hash for the first ClientHelloInner,
320
+ # denoted ClientHelloInner1, up to and including the modified
321
+ # HelloRetryRequest.
322
+ #
323
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-7.2.1-2
324
+ transcript = @transcript.clone
325
+ transcript[HRR] = [hrr, hrr.serialize]
326
+ transcript_hrr_ech_conf = transcript.hash(@digest, HRR)
327
+
328
+ self.class.hkdf_expand_label(
329
+ hkdf_extract(ch1_inner_random, ''),
330
+ 'hrr ech accept confirmation',
331
+ transcript_hrr_ech_conf,
332
+ 8,
333
+ @digest
334
+ )
335
+ end
336
+
337
+ # @return [Boolean]
338
+ def hrr_accept_ech?
339
+ hrr_ech = @transcript[HRR]
340
+ .first
341
+ .extensions[Message::ExtensionType::ENCRYPTED_CLIENT_HELLO]
342
+ hrr_accept_confirmation == hrr_ech.confirmation
343
+ end
241
344
  end
242
345
  # rubocop: enable Metrics/ClassLength
243
346
  end
@@ -36,7 +36,8 @@ module TTTLS13
36
36
  bad_certificate_status_response: "\x71",
37
37
  unknown_psk_identity: "\x73",
38
38
  certificate_required: "\x74",
39
- no_application_protocol: "\x78"
39
+ no_application_protocol: "\x78",
40
+ ech_required: "\x79"
40
41
  }.freeze
41
42
  # rubocop: enable Layout/HashAlignment
42
43
 
@@ -31,7 +31,8 @@ module TTTLS13
31
31
  ExtensionType::CERTIFICATE_AUTHORITIES,
32
32
  ExtensionType::POST_HANDSHAKE_AUTH,
33
33
  ExtensionType::SIGNATURE_ALGORITHMS_CERT,
34
- ExtensionType::KEY_SHARE
34
+ ExtensionType::KEY_SHARE,
35
+ ExtensionType::ENCRYPTED_CLIENT_HELLO
35
36
  ].freeze
36
37
  private_constant :APPEARABLE_CH_EXTENSIONS
37
38
 
@@ -15,7 +15,8 @@ module TTTLS13
15
15
  ExtensionType::CLIENT_CERTIFICATE_TYPE,
16
16
  ExtensionType::SERVER_CERTIFICATE_TYPE,
17
17
  ExtensionType::RECORD_SIZE_LIMIT,
18
- ExtensionType::EARLY_DATA
18
+ ExtensionType::EARLY_DATA,
19
+ ExtensionType::ENCRYPTED_CLIENT_HELLO
19
20
  ].freeze
20
21
  private_constant :APPEARABLE_EE_EXTENSIONS
21
22
 
@@ -19,7 +19,7 @@ module TTTLS13
19
19
  # https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
20
20
  def initialize(protocol_name_list)
21
21
  @extension_type \
22
- = ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION
22
+ = ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION
23
23
  @protocol_name_list = protocol_name_list || []
24
24
  raise Error::ErrorAlerts, :internal_error \
25
25
  if @protocol_name_list.empty?
@@ -27,10 +27,9 @@ module TTTLS13
27
27
 
28
28
  # @return [String]
29
29
  def serialize
30
- binary = @protocol_name_list
31
- .map(&:prefix_uint8_length)
32
- .join
33
- .prefix_uint16_length
30
+ binary = @protocol_name_list.map(&:prefix_uint8_length)
31
+ .join
32
+ .prefix_uint16_length
34
33
 
35
34
  @extension_type + binary.prefix_uint16_length
36
35
  end
@@ -11,7 +11,7 @@ module TTTLS13
11
11
  # ZSTD = "\x00\x03" # UNSUPPORTED
12
12
  end
13
13
 
14
- # https://tools.ietf.org/html/rfc8879
14
+ # https://datatracker.ietf.org/doc/html/rfc8879
15
15
  class CompressCertificate
16
16
  attr_reader :extension_type
17
17
  attr_reader :algorithms
@@ -0,0 +1,241 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ using Refinements
6
+ HpkeSymmetricCipherSuite = \
7
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
8
+ module Message
9
+ module Extension
10
+ module ECHClientHelloType
11
+ OUTER = "\x00"
12
+ INNER = "\x01"
13
+ end
14
+
15
+ # NOTE:
16
+ # struct {
17
+ # ECHClientHelloType type;
18
+ # select (ECHClientHello.type) {
19
+ # case outer:
20
+ # HpkeSymmetricCipherSuite cipher_suite;
21
+ # uint8 config_id;
22
+ # opaque enc<0..2^16-1>;
23
+ # opaque payload<1..2^16-1>;
24
+ # case inner:
25
+ # Empty;
26
+ # };
27
+ # } ECHClientHello;
28
+ class ECHClientHello
29
+ attr_accessor :extension_type
30
+ attr_accessor :type
31
+ attr_accessor :cipher_suite
32
+ attr_accessor :config_id
33
+ attr_accessor :enc
34
+ attr_accessor :payload
35
+
36
+ # @param type [TTTLS13::Message::Extension::ECHClientHelloType]
37
+ # @param cipher_suite [HpkeSymmetricCipherSuite]
38
+ # @param config_id [Integer]
39
+ # @param enc [String]
40
+ # @param payload [String]
41
+ def initialize(type:,
42
+ cipher_suite: nil,
43
+ config_id: nil,
44
+ enc: nil,
45
+ payload: nil)
46
+ @extension_type = ExtensionType::ENCRYPTED_CLIENT_HELLO
47
+ @type = type
48
+ @cipher_suite = cipher_suite
49
+ raise Error::ErrorAlerts, :internal_error \
50
+ if @type == ECHClientHelloType::OUTER && \
51
+ !@cipher_suite.is_a?(HpkeSymmetricCipherSuite)
52
+
53
+ @config_id = config_id
54
+ @enc = enc
55
+ @payload = payload
56
+ end
57
+
58
+ # @raise [TTTLS13::Error::ErrorAlerts]
59
+ #
60
+ # @return [String]
61
+ def serialize
62
+ case @type
63
+ when ECHClientHelloType::OUTER
64
+ binary = @type + @cipher_suite.encode + @config_id.to_uint8 \
65
+ + @enc.prefix_uint16_length + @payload.prefix_uint16_length
66
+ when ECHClientHelloType::INNER
67
+ binary = @type
68
+ else
69
+ raise Error::ErrorAlerts, :internal_error
70
+ end
71
+
72
+ @extension_type + binary.prefix_uint16_length
73
+ end
74
+
75
+ # @param binary [String]
76
+ #
77
+ # @raise [TTTLS13::Error::ErrorAlerts]
78
+ #
79
+ # @return [TTTLS13::Message::Extensions::ECHClientHello]
80
+ def self.deserialize(binary)
81
+ raise Error::ErrorAlerts, :internal_error \
82
+ if binary.nil? || binary.empty?
83
+
84
+ case binary[0]
85
+ when ECHClientHelloType::OUTER
86
+ return deserialize_outer_ech(binary[1..])
87
+ when ECHClientHelloType::INNER
88
+ return deserialize_inner_ech(binary[1..])
89
+ end
90
+
91
+ raise Error::ErrorAlerts, :internal_error
92
+ end
93
+
94
+ class << self
95
+ private
96
+
97
+ # @param binary [String]
98
+ #
99
+ # @raise [TTTLS13::Error::ErrorAlerts]
100
+ #
101
+ # @return [TTTLS13::Message::Extensions::ECHClientHello]
102
+ def deserialize_outer_ech(binary)
103
+ raise Error::ErrorAlerts, :internal_error \
104
+ if binary.nil? || binary.length < 5
105
+
106
+ kdf_id = \
107
+ HpkeSymmetricCipherSuite::HpkeKdfId.decode(binary.slice(0, 2))
108
+ aead_id = \
109
+ HpkeSymmetricCipherSuite::HpkeAeadId.decode(binary.slice(2, 2))
110
+ cs = HpkeSymmetricCipherSuite.new(kdf_id, aead_id)
111
+ cid = Convert.bin2i(binary.slice(4, 1))
112
+ enc_len = Convert.bin2i(binary.slice(5, 2))
113
+ i = 7
114
+ raise Error::ErrorAlerts, :internal_error \
115
+ if i + enc_len > binary.length
116
+
117
+ enc = binary.slice(i, enc_len)
118
+ i += enc_len
119
+ raise Error::ErrorAlerts, :internal_error \
120
+ if i + 2 > binary.length
121
+
122
+ payload_len = Convert.bin2i(binary.slice(i, 2))
123
+ raise Error::ErrorAlerts, :internal_error \
124
+ if i + payload_len > binary.length
125
+
126
+ payload = binary.slice(i, payload_len)
127
+ ECHClientHello.new(
128
+ type: ECHClientHelloType::OUTER,
129
+ cipher_suite: cs,
130
+ config_id: cid,
131
+ enc: enc,
132
+ payload: payload
133
+ )
134
+ end
135
+
136
+ # @param binary [String]
137
+ #
138
+ # @raise [TTTLS13::Error::ErrorAlerts]
139
+ #
140
+ # @return [TTTLS13::Message::Extensions::ECHClientHello]
141
+ def deserialize_inner_ech(binary)
142
+ raise Error::ErrorAlerts, :internal_error unless binary.empty?
143
+
144
+ ECHClientHello.new(type: ECHClientHelloType::INNER)
145
+ end
146
+ end
147
+
148
+ # @return [TTTLS13::Message::Extensions::ECHClientHello]
149
+ def self.new_inner
150
+ ECHClientHello.new(type: ECHClientHelloType::INNER)
151
+ end
152
+
153
+ # @param cipher_suite [HpkeSymmetricCipherSuite]
154
+ # @param config_id [Integer]
155
+ # @param enc [String]
156
+ # @param payload [String]
157
+ #
158
+ # @return [TTTLS13::Message::Extensions::ECHClientHello]
159
+ def self.new_outer(cipher_suite:, config_id:, enc:, payload:)
160
+ ECHClientHello.new(
161
+ type: ECHClientHelloType::OUTER,
162
+ cipher_suite: cipher_suite,
163
+ config_id: config_id,
164
+ enc: enc,
165
+ payload: payload
166
+ )
167
+ end
168
+ end
169
+
170
+ # NOTE:
171
+ # struct {
172
+ # ECHConfigList retry_configs;
173
+ # } ECHEncryptedExtensions;
174
+ class ECHEncryptedExtensions
175
+ attr_accessor :extension_type
176
+ attr_accessor :retry_configs
177
+
178
+ # @param retry_configs [Array of ECHConfig]
179
+ def initialize(retry_configs)
180
+ @extension_type = ExtensionType::ENCRYPTED_CLIENT_HELLO
181
+ @retry_configs = retry_configs
182
+ end
183
+
184
+ # @return [String]
185
+ def serialize
186
+ @extension_type + @retry_configs.map(&:encode)
187
+ .join
188
+ .prefix_uint16_length
189
+ .prefix_uint16_length
190
+ end
191
+
192
+ # @param binary [String]
193
+ #
194
+ # @raise [TTTLS13::Error::ErrorAlerts]
195
+ #
196
+ # @return [TTTLS13::Message::Extensions::ECHEncryptedExtensions]
197
+ def self.deserialize(binary)
198
+ raise Error::ErrorAlerts, :decode_error \
199
+ if binary.nil? ||
200
+ binary.length != binary.slice(0, 2).unpack1('n') + 2
201
+
202
+ ECHEncryptedExtensions.new(
203
+ ECHConfig.decode_vectors(binary.slice(2..))
204
+ )
205
+ end
206
+ end
207
+
208
+ # NOTE:
209
+ # struct {
210
+ # opaque confirmation[8];
211
+ # } ECHHelloRetryRequest;
212
+ class ECHHelloRetryRequest
213
+ attr_accessor :extension_type
214
+ attr_accessor :confirmation
215
+
216
+ # @param confirmation [String]
217
+ def initialize(confirmation)
218
+ @extension_type = ExtensionType::ENCRYPTED_CLIENT_HELLO
219
+ @confirmation = confirmation
220
+ end
221
+
222
+ # @return [String]
223
+ def serialize
224
+ @extension_type + @confirmation.prefix_uint16_length
225
+ end
226
+
227
+ # @param binary [String]
228
+ #
229
+ # @raise [TTTLS13::Error::ErrorAlerts]
230
+ #
231
+ # @return [TTTLS13::Message::Extensions::ECHHelloRetryRequest]
232
+ def self.deserialize(binary)
233
+ raise Error::ErrorAlerts, :decode_error \
234
+ if binary.nil? || binary.length != 8
235
+
236
+ ECHHelloRetryRequest.new(binary)
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
@@ -91,8 +91,7 @@ module TTTLS13
91
91
  priv_keys = {}
92
92
  kse = groups.map do |group|
93
93
  curve = NamedGroup.curve_name(group)
94
- ec = OpenSSL::PKey::EC.new(curve)
95
- ec.generate_key!
94
+ ec = OpenSSL::PKey::EC.generate(curve)
96
95
  # store private key to do the key-exchange
97
96
  priv_keys.store(group, ec)
98
97
  KeyShareEntry.new(
@@ -115,8 +114,7 @@ module TTTLS13
115
114
  # @return [OpenSSL::PKey::EC.$Object]
116
115
  def self.gen_sh_key_share(group)
117
116
  curve = NamedGroup.curve_name(group)
118
- ec = OpenSSL::PKey::EC.new(curve)
119
- ec.generate_key!
117
+ ec = OpenSSL::PKey::EC.generate(curve)
120
118
 
121
119
  key_share = KeyShare.new(
122
120
  msg_type: HandshakeType::SERVER_HELLO,
@@ -15,7 +15,7 @@ module TTTLS13
15
15
  #
16
16
  # 00 00 00 00
17
17
  #
18
- # https://tools.ietf.org/html/rfc6066#section-3
18
+ # https://datatracker.ietf.org/doc/html/rfc6066#section-3
19
19
  class ServerName
20
20
  attr_reader :extension_type
21
21
  attr_reader :server_name