tttls1.3 0.2.19 → 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/Gemfile +2 -0
- data/README.md +4 -1
- data/example/helper.rb +5 -2
- 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/lib/tttls1.3/client.rb +534 -24
- data/lib/tttls1.3/connection.rb +3 -0
- 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 +71 -3
- 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/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 +1 -1
- 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/ech_spec.rb +81 -0
- data/spec/spec_helper.rb +41 -5
- data/tttls1.3.gemspec +2 -0
- metadata +38 -2
data/lib/tttls1.3/connection.rb
CHANGED
@@ -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]
|
@@ -155,7 +155,7 @@ module TTTLS13
|
|
155
155
|
|
156
156
|
# @return [String]
|
157
157
|
def main_secret
|
158
|
-
ikm =
|
158
|
+
ikm = @hash_len.zeros
|
159
159
|
hkdf_extract(ikm, main_salt)
|
160
160
|
end
|
161
161
|
|
@@ -273,6 +273,74 @@ module TTTLS13
|
|
273
273
|
def derive_secret(secret, label, context)
|
274
274
|
self.class.hkdf_expand_label(secret, label, context, @hash_len, @digest)
|
275
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
|
276
344
|
end
|
277
345
|
# rubocop: enable Metrics/ClassLength
|
278
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
|
@@ -7,6 +7,7 @@ Dir[File.dirname(__FILE__) + '/extension/*.rb'].sort.each { |f| require f }
|
|
7
7
|
module TTTLS13
|
8
8
|
using Refinements
|
9
9
|
module Message
|
10
|
+
# rubocop: disable Metrics/ClassLength
|
10
11
|
class Extensions < Hash
|
11
12
|
# @param extensions [Array of TTTLS13::Message::Extension::$Object]
|
12
13
|
#
|
@@ -119,6 +120,7 @@ module TTTLS13
|
|
119
120
|
# @raise [TTTLS13::Error::ErrorAlerts]
|
120
121
|
#
|
121
122
|
# @return [TTTLS13::Message::Extension::$Object, nil]
|
123
|
+
# rubocop: disable Metrics/AbcSize
|
122
124
|
# rubocop: disable Metrics/CyclomaticComplexity
|
123
125
|
# rubocop: disable Metrics/MethodLength
|
124
126
|
# rubocop: disable Metrics/PerceivedComplexity
|
@@ -130,14 +132,12 @@ module TTTLS13
|
|
130
132
|
Extension::ServerName.deserialize(binary)
|
131
133
|
when ExtensionType::STATUS_REQUEST
|
132
134
|
if msg_type == HandshakeType::CLIENT_HELLO
|
133
|
-
|
135
|
+
Extension::OCSPStatusRequest.deserialize(binary)
|
136
|
+
elsif msg_type == HandshakeType::CERTIFICATE
|
137
|
+
Extension::OCSPResponse.deserialize(binary)
|
138
|
+
else
|
139
|
+
Extension::UnknownExtension.deserialize(binary, extension_type)
|
134
140
|
end
|
135
|
-
|
136
|
-
if msg_type == HandshakeType::CERTIFICATE
|
137
|
-
return Extension::OCSPResponse.deserialize(binary)
|
138
|
-
end
|
139
|
-
|
140
|
-
Extension::UnknownExtension.deserialize(binary, extension_type)
|
141
141
|
when ExtensionType::SUPPORTED_GROUPS
|
142
142
|
Extension::SupportedGroups.deserialize(binary)
|
143
143
|
when ExtensionType::SIGNATURE_ALGORITHMS
|
@@ -162,14 +162,27 @@ module TTTLS13
|
|
162
162
|
Extension::SignatureAlgorithmsCert.deserialize(binary)
|
163
163
|
when ExtensionType::KEY_SHARE
|
164
164
|
Extension::KeyShare.deserialize(binary, msg_type)
|
165
|
+
when ExtensionType::ENCRYPTED_CLIENT_HELLO
|
166
|
+
case msg_type
|
167
|
+
when HandshakeType::CLIENT_HELLO
|
168
|
+
Extension::ECHClientHello.deserialize(binary)
|
169
|
+
when HandshakeType::ENCRYPTED_EXTENSIONS
|
170
|
+
Extension::ECHEncryptedExtensions.deserialize(binary)
|
171
|
+
when HandshakeType::HELLO_RETRY_REQUEST
|
172
|
+
Extension::ECHHelloRetryRequest.deserialize(binary)
|
173
|
+
else
|
174
|
+
Extension::UnknownExtension.deserialize(binary, extension_type)
|
175
|
+
end
|
165
176
|
else
|
166
177
|
Extension::UnknownExtension.deserialize(binary, extension_type)
|
167
178
|
end
|
168
179
|
end
|
180
|
+
# rubocop: enable Metrics/AbcSize
|
169
181
|
# rubocop: enable Metrics/CyclomaticComplexity
|
170
182
|
# rubocop: enable Metrics/MethodLength
|
171
183
|
# rubocop: enable Metrics/PerceivedComplexity
|
172
184
|
end
|
173
185
|
end
|
186
|
+
# rubocop: enable Metrics/ClassLength
|
174
187
|
end
|
175
188
|
end
|
@@ -17,7 +17,8 @@ module TTTLS13
|
|
17
17
|
ExtensionType::COOKIE,
|
18
18
|
ExtensionType::PASSWORD_SALT,
|
19
19
|
ExtensionType::SUPPORTED_VERSIONS,
|
20
|
-
ExtensionType::KEY_SHARE
|
20
|
+
ExtensionType::KEY_SHARE,
|
21
|
+
ExtensionType::ENCRYPTED_CLIENT_HELLO
|
21
22
|
].freeze
|
22
23
|
private_constant :APPEARABLE_HRR_EXTENSIONS
|
23
24
|
|
@@ -61,7 +62,6 @@ module TTTLS13
|
|
61
62
|
@cipher_suite = cipher_suite
|
62
63
|
@legacy_compression_method = legacy_compression_method
|
63
64
|
@extensions = extensions
|
64
|
-
@hrr = (random == HRR_RANDOM)
|
65
65
|
end
|
66
66
|
# rubocop: enable Metrics/ParameterLists
|
67
67
|
|
@@ -108,10 +108,8 @@ module TTTLS13
|
|
108
108
|
exs_bin = binary.slice(i, exs_len)
|
109
109
|
if random == HRR_RANDOM
|
110
110
|
msg_type = HandshakeType::HELLO_RETRY_REQUEST
|
111
|
-
@hrr = true
|
112
111
|
else
|
113
112
|
msg_type = HandshakeType::SERVER_HELLO
|
114
|
-
@hrr = false
|
115
113
|
end
|
116
114
|
extensions = Extensions.deserialize(exs_bin, msg_type)
|
117
115
|
i += exs_len
|
@@ -132,7 +130,7 @@ module TTTLS13
|
|
132
130
|
|
133
131
|
# @return [Boolean]
|
134
132
|
def hrr?
|
135
|
-
@
|
133
|
+
@random == HRR_RANDOM
|
136
134
|
end
|
137
135
|
|
138
136
|
# @return [Boolean]
|
data/lib/tttls1.3/message.rb
CHANGED
@@ -27,7 +27,7 @@ module TTTLS13
|
|
27
27
|
HELLO_VERIFY_REQUEST = "\x03" # RESERVED
|
28
28
|
NEW_SESSION_TICKET = "\x04"
|
29
29
|
END_OF_EARLY_DATA = "\x05"
|
30
|
-
HELLO_RETRY_REQUEST = "\x06"
|
30
|
+
HELLO_RETRY_REQUEST = "\x06"
|
31
31
|
ENCRYPTED_EXTENSIONS = "\x08"
|
32
32
|
CERTIFICATE = "\x0b"
|
33
33
|
SERVER_KEY_EXCHANGE = "\x0c" # RESERVED
|
@@ -72,6 +72,8 @@ module TTTLS13
|
|
72
72
|
POST_HANDSHAKE_AUTH = "\x00\x31"
|
73
73
|
SIGNATURE_ALGORITHMS_CERT = "\x00\x32"
|
74
74
|
KEY_SHARE = "\x00\x33"
|
75
|
+
# https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-11.1
|
76
|
+
ENCRYPTED_CLIENT_HELLO = "\xfe\x0d"
|
75
77
|
end
|
76
78
|
|
77
79
|
DEFINED_EXTENSIONS = ExtensionType.constants.map do |c|
|
data/lib/tttls1.3/named_group.rb
CHANGED
@@ -64,7 +64,7 @@ module TTTLS13
|
|
64
64
|
# secp384r1 | | NIST P-384
|
65
65
|
# secp521r1 | | NIST P-521
|
66
66
|
#
|
67
|
-
# https://
|
67
|
+
# https://datatracker.ietf.org/doc/html/rfc4492#appendix-A
|
68
68
|
#
|
69
69
|
# @param groups [Array of TTTLS13::Message::Extension::NamedGroup]
|
70
70
|
#
|
data/lib/tttls1.3/server.rb
CHANGED
@@ -136,7 +136,7 @@ module TTTLS13
|
|
136
136
|
# v
|
137
137
|
# CONNECTED
|
138
138
|
#
|
139
|
-
# https://
|
139
|
+
# https://datatracker.ietf.org/doc/html/rfc8446#appendix-A.2
|
140
140
|
#
|
141
141
|
# rubocop: disable Metrics/AbcSize
|
142
142
|
# rubocop: disable Metrics/BlockLength
|