tttls1.3 0.2.19 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|