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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +8 -5
- data/Gemfile +2 -0
- data/README.md +6 -3
- data/example/helper.rb +5 -2
- data/example/https_client_using_0rtt.rb +1 -1
- data/example/https_client_using_ech.rb +32 -0
- data/example/https_client_using_grease_ech.rb +26 -0
- data/example/https_client_using_grease_psk.rb +66 -0
- data/example/https_client_using_hrr_and_ech.rb +32 -0
- data/example/https_client_using_hrr_and_ticket.rb +1 -1
- data/example/https_client_using_ticket.rb +1 -1
- data/interop/client_spec.rb +3 -2
- data/interop/server_spec.rb +1 -3
- data/interop/{helper.rb → spec_helper.rb} +12 -5
- data/lib/tttls1.3/client.rb +553 -32
- data/lib/tttls1.3/connection.rb +9 -8
- data/lib/tttls1.3/cryptograph/aead.rb +1 -1
- data/lib/tttls1.3/error.rb +1 -1
- data/lib/tttls1.3/hpke.rb +91 -0
- data/lib/tttls1.3/key_schedule.rb +111 -8
- data/lib/tttls1.3/message/alert.rb +2 -1
- data/lib/tttls1.3/message/client_hello.rb +2 -1
- data/lib/tttls1.3/message/encrypted_extensions.rb +2 -1
- data/lib/tttls1.3/message/extension/alpn.rb +4 -5
- data/lib/tttls1.3/message/extension/compress_certificate.rb +1 -1
- data/lib/tttls1.3/message/extension/ech.rb +241 -0
- data/lib/tttls1.3/message/extension/key_share.rb +2 -4
- data/lib/tttls1.3/message/extension/server_name.rb +1 -1
- data/lib/tttls1.3/message/extensions.rb +20 -7
- data/lib/tttls1.3/message/record.rb +1 -1
- data/lib/tttls1.3/message/server_hello.rb +3 -5
- data/lib/tttls1.3/message.rb +3 -1
- data/lib/tttls1.3/named_group.rb +1 -1
- data/lib/tttls1.3/server.rb +2 -2
- data/lib/tttls1.3/utils.rb +8 -0
- data/lib/tttls1.3/version.rb +1 -1
- data/lib/tttls1.3.rb +4 -0
- data/spec/client_spec.rb +40 -0
- data/spec/connection_spec.rb +22 -7
- data/spec/ech_spec.rb +81 -0
- data/spec/extensions_spec.rb +1 -2
- data/spec/key_schedule_spec.rb +2 -2
- data/spec/server_spec.rb +22 -7
- data/spec/spec_helper.rb +41 -5
- data/tttls1.3.gemspec +2 -0
- metadata +39 -3
data/lib/tttls1.3/connection.rb
CHANGED
@@ -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
|
-
@
|
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 @
|
115
|
+
return nil if @exporter_secret.nil? || @cipher_suite.nil?
|
113
116
|
|
114
117
|
digest = CipherSuite.digest(@cipher_suite)
|
115
|
-
do_exporter(@
|
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_)
|
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
|
-
|
521
|
-
|
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 +
|
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
|
data/lib/tttls1.3/error.rb
CHANGED
@@ -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://
|
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 ||
|
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
|
-
|
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
|
-
|
138
|
-
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
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
|
-
|
32
|
-
|
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
|
@@ -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.
|
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.
|
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,
|