tttls1.3 0.3.2 → 0.3.4
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/.rubocop.yml +3 -0
- data/Gemfile +2 -1
- data/example/helper.rb +32 -1
- data/example/https_client_using_0rtt.rb +4 -2
- data/example/https_client_using_ech.rb +6 -7
- data/example/https_client_using_grease_ech.rb +0 -2
- data/example/https_client_using_hrr.rb +2 -1
- data/example/https_client_using_hrr_and_ech.rb +6 -7
- data/example/https_client_using_hrr_and_ticket.rb +4 -2
- data/example/https_client_using_status_request.rb +2 -1
- data/example/https_client_using_ticket.rb +4 -2
- data/example/https_client_using_ticket_and_ech.rb +57 -0
- data/lib/tttls1.3/client.rb +3 -5
- data/lib/tttls1.3/cryptograph/aead.rb +0 -5
- data/lib/tttls1.3/ech.rb +37 -21
- data/lib/tttls1.3/key_schedule.rb +1 -1
- data/lib/tttls1.3/message/application_data.rb +1 -1
- data/lib/tttls1.3/message/client_hello.rb +8 -0
- data/lib/tttls1.3/message/extension/alpn.rb +1 -1
- data/lib/tttls1.3/message/extension/ech.rb +17 -25
- data/lib/tttls1.3/message/extension/ech_outer_extensions.rb +51 -0
- data/lib/tttls1.3/message/extension/key_share.rb +3 -6
- data/lib/tttls1.3/message/extension/pre_shared_key.rb +0 -3
- data/lib/tttls1.3/message/extension/server_name.rb +0 -1
- data/lib/tttls1.3/message/extension/signature_algorithms_cert.rb +1 -1
- data/lib/tttls1.3/message/extension/unknown_extension.rb +0 -1
- data/lib/tttls1.3/message/extensions.rb +30 -2
- data/lib/tttls1.3/message/record.rb +0 -2
- data/lib/tttls1.3/message.rb +1 -0
- data/lib/tttls1.3/named_group.rb +1 -3
- data/lib/tttls1.3/server.rb +0 -1
- data/lib/tttls1.3/version.rb +1 -1
- data/spec/client_hello_spec.rb +19 -0
- data/spec/ech_outer_extensions_spec.rb +42 -0
- data/spec/ech_spec.rb +2 -0
- data/spec/extensions_spec.rb +65 -0
- data/spec/spec_helper.rb +4 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0913f70f0bdbfd6740f41ce7902f55660ea1bd99533a8f765f9d98a1bd56a58c'
|
4
|
+
data.tar.gz: 611a063ae74498d19636ebf3ee3741b76164856341bb38ec9966a999579bdfb1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88f39003d30f4642c67a61169923fe248bc572b4a1c638eb36803dc4278aa91a499919784cd060fa35c522bcb935745d1b4542abb1963514aba1f86f1ea789fd
|
7
|
+
data.tar.gz: cd2ae8cd383cd9737a732d53c8ca27cbacdcd3c4a5b6bee62f5f43faba4fd8eb8067b0621df18d8b77db679823c3aa26947508fe5827939bf6741e6f417a1d80
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/example/helper.rb
CHANGED
@@ -2,13 +2,15 @@
|
|
2
2
|
|
3
3
|
$LOAD_PATH << __dir__ + '/../lib'
|
4
4
|
|
5
|
+
require 'base64'
|
6
|
+
require 'resolv'
|
5
7
|
require 'socket'
|
6
8
|
require 'time'
|
7
9
|
require 'uri'
|
8
10
|
require 'webrick'
|
9
11
|
|
12
|
+
require 'ech_config'
|
10
13
|
require 'http/parser'
|
11
|
-
require 'svcb_rr_patch'
|
12
14
|
|
13
15
|
require 'tttls1.3'
|
14
16
|
|
@@ -83,3 +85,32 @@ def transcript_htmlize(transcript)
|
|
83
85
|
format(m[k], TTTLS13::Convert.obj2html(v.first))
|
84
86
|
end.join('<br>')
|
85
87
|
end
|
88
|
+
|
89
|
+
def parse_echconfigs_pem(pem)
|
90
|
+
# https://datatracker.ietf.org/doc/html/draft-farrell-tls-pemesni-08#section-3-4
|
91
|
+
s = pem.gsub(/-----(BEGIN|END) (ECH CONFIGS|ECHCONFIG)-----/, '')
|
92
|
+
.gsub("\n", '')
|
93
|
+
b = Base64.decode64(s)
|
94
|
+
raise 'failed to parse ECHConfigs' \
|
95
|
+
unless b.length == b.slice(0, 2).unpack1('n') + 2
|
96
|
+
|
97
|
+
ECHConfig.decode_vectors(b.slice(2..))
|
98
|
+
end
|
99
|
+
|
100
|
+
def resolve_echconfig(hostname)
|
101
|
+
rr = Resolv::DNS.new.getresources(
|
102
|
+
hostname,
|
103
|
+
Resolv::DNS::Resource::IN::HTTPS
|
104
|
+
)
|
105
|
+
|
106
|
+
# https://datatracker.ietf.org/doc/html/draft-ietf-tls-svcb-ech-01#section-6
|
107
|
+
ech = 5
|
108
|
+
raise "failed to resolve echconfig via #{hostname} HTTPS RR" \
|
109
|
+
if rr.first.nil? || rr.first.params[ech].nil?
|
110
|
+
|
111
|
+
octet = rr.first.params[ech].value
|
112
|
+
raise 'failed to parse ECHConfigs' \
|
113
|
+
unless octet.length == octet.slice(0, 2).unpack1('n') + 2
|
114
|
+
|
115
|
+
ECHConfig.decode_vectors(octet.slice(2..)).first
|
116
|
+
end
|
@@ -9,7 +9,8 @@ req = simple_http_request(uri.host, uri.path)
|
|
9
9
|
|
10
10
|
settings_2nd = {
|
11
11
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
12
|
-
alpn: ['http/1.1']
|
12
|
+
alpn: ['http/1.1'],
|
13
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
13
14
|
}
|
14
15
|
process_new_session_ticket = lambda do |nst, rms, cs|
|
15
16
|
return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
|
@@ -24,7 +25,8 @@ end
|
|
24
25
|
settings_1st = {
|
25
26
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
26
27
|
alpn: ['http/1.1'],
|
27
|
-
process_new_session_ticket: process_new_session_ticket
|
28
|
+
process_new_session_ticket: process_new_session_ticket,
|
29
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
28
30
|
}
|
29
31
|
|
30
32
|
succeed_early_data = false
|
@@ -2,22 +2,21 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require_relative 'helper'
|
5
|
-
HpkeSymmetricCipherSuite = \
|
6
|
-
ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
|
7
5
|
|
8
6
|
uri = URI.parse(ARGV[0] || 'https://localhost:4433')
|
9
7
|
ca_file = __dir__ + '/../tmp/ca.crt'
|
10
8
|
req = simple_http_request(uri.host, uri.path)
|
9
|
+
ech_config = if ARGV.length > 1
|
10
|
+
parse_echconfigs_pem(File.open(ARGV[1]).read).first
|
11
|
+
else
|
12
|
+
resolve_echconfig(uri.host)
|
13
|
+
end
|
11
14
|
|
12
|
-
rr = Resolv::DNS.new.getresources(
|
13
|
-
uri.host,
|
14
|
-
Resolv::DNS::Resource::IN::HTTPS
|
15
|
-
)
|
16
15
|
socket = TCPSocket.new(uri.host, uri.port)
|
17
16
|
settings = {
|
18
17
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
19
18
|
alpn: ['http/1.1'],
|
20
|
-
ech_config:
|
19
|
+
ech_config: ech_config,
|
21
20
|
ech_hpke_cipher_suites:
|
22
21
|
TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
|
23
22
|
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
@@ -11,7 +11,8 @@ socket = TCPSocket.new(uri.host, uri.port)
|
|
11
11
|
settings = {
|
12
12
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
13
13
|
key_share_groups: [], # empty KeyShareClientHello.client_shares
|
14
|
-
alpn: ['http/1.1']
|
14
|
+
alpn: ['http/1.1'],
|
15
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
15
16
|
}
|
16
17
|
client = TTTLS13::Client.new(socket, uri.host, **settings)
|
17
18
|
client.connect
|
@@ -2,23 +2,22 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require_relative 'helper'
|
5
|
-
HpkeSymmetricCipherSuite = \
|
6
|
-
ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
|
7
5
|
|
8
6
|
uri = URI.parse(ARGV[0] || 'https://localhost:4433')
|
9
7
|
ca_file = __dir__ + '/../tmp/ca.crt'
|
10
8
|
req = simple_http_request(uri.host, uri.path)
|
9
|
+
ech_config = if ARGV.length > 1
|
10
|
+
parse_echconfigs_pem(File.open(ARGV[1]).read).first
|
11
|
+
else
|
12
|
+
resolve_echconfig(uri.host)
|
13
|
+
end
|
11
14
|
|
12
|
-
rr = Resolv::DNS.new.getresources(
|
13
|
-
uri.host,
|
14
|
-
Resolv::DNS::Resource::IN::HTTPS
|
15
|
-
)
|
16
15
|
socket = TCPSocket.new(uri.host, uri.port)
|
17
16
|
settings = {
|
18
17
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
19
18
|
key_share_groups: [], # empty KeyShareClientHello.client_shares
|
20
19
|
alpn: ['http/1.1'],
|
21
|
-
ech_config:
|
20
|
+
ech_config: ech_config,
|
22
21
|
ech_hpke_cipher_suites:
|
23
22
|
TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
|
24
23
|
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
@@ -9,7 +9,8 @@ req = simple_http_request(uri.host, uri.path)
|
|
9
9
|
|
10
10
|
settings_2nd = {
|
11
11
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
12
|
-
alpn: ['http/1.1']
|
12
|
+
alpn: ['http/1.1'],
|
13
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
13
14
|
}
|
14
15
|
process_new_session_ticket = lambda do |nst, rms, cs|
|
15
16
|
return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
|
@@ -25,7 +26,8 @@ end
|
|
25
26
|
settings_1st = {
|
26
27
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
27
28
|
alpn: ['http/1.1'],
|
28
|
-
process_new_session_ticket: process_new_session_ticket
|
29
|
+
process_new_session_ticket: process_new_session_ticket,
|
30
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
29
31
|
}
|
30
32
|
|
31
33
|
[
|
@@ -19,7 +19,8 @@ settings = {
|
|
19
19
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
20
20
|
alpn: ['http/1.1'],
|
21
21
|
check_certificate_status: true,
|
22
|
-
process_certificate_status: process_certificate_status
|
22
|
+
process_certificate_status: process_certificate_status,
|
23
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
23
24
|
}
|
24
25
|
client = TTTLS13::Client.new(socket, uri.host, **settings)
|
25
26
|
client.connect
|
@@ -9,7 +9,8 @@ req = simple_http_request(uri.host, uri.path)
|
|
9
9
|
|
10
10
|
settings_2nd = {
|
11
11
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
12
|
-
alpn: ['http/1.1']
|
12
|
+
alpn: ['http/1.1'],
|
13
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
13
14
|
}
|
14
15
|
process_new_session_ticket = lambda do |nst, rms, cs|
|
15
16
|
return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
|
@@ -24,7 +25,8 @@ end
|
|
24
25
|
settings_1st = {
|
25
26
|
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
26
27
|
alpn: ['http/1.1'],
|
27
|
-
process_new_session_ticket: process_new_session_ticket
|
28
|
+
process_new_session_ticket: process_new_session_ticket,
|
29
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
28
30
|
}
|
29
31
|
|
30
32
|
[
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'helper'
|
5
|
+
|
6
|
+
uri = URI.parse(ARGV[0] || 'https://localhost:4433')
|
7
|
+
ca_file = __dir__ + '/../tmp/ca.crt'
|
8
|
+
req = simple_http_request(uri.host, uri.path)
|
9
|
+
ech_config = if ARGV.length > 1
|
10
|
+
parse_echconfigs_pem(File.open(ARGV[1]).read).first
|
11
|
+
else
|
12
|
+
resolve_echconfig(uri.host)
|
13
|
+
end
|
14
|
+
|
15
|
+
settings_2nd = {
|
16
|
+
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
17
|
+
alpn: ['http/1.1'],
|
18
|
+
ech_config: ech_config,
|
19
|
+
ech_hpke_cipher_suites:
|
20
|
+
TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
|
21
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
22
|
+
}
|
23
|
+
process_new_session_ticket = lambda do |nst, rms, cs|
|
24
|
+
return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
|
25
|
+
|
26
|
+
settings_2nd[:ticket] = nst.ticket
|
27
|
+
settings_2nd[:resumption_main_secret] = rms
|
28
|
+
settings_2nd[:psk_cipher_suite] = cs
|
29
|
+
settings_2nd[:ticket_nonce] = nst.ticket_nonce
|
30
|
+
settings_2nd[:ticket_age_add] = nst.ticket_age_add
|
31
|
+
settings_2nd[:ticket_timestamp] = nst.timestamp
|
32
|
+
end
|
33
|
+
settings_1st = {
|
34
|
+
ca_file: File.exist?(ca_file) ? ca_file : nil,
|
35
|
+
alpn: ['http/1.1'],
|
36
|
+
ech_config: ech_config,
|
37
|
+
ech_hpke_cipher_suites:
|
38
|
+
TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
|
39
|
+
process_new_session_ticket: process_new_session_ticket,
|
40
|
+
sslkeylogfile: '/tmp/sslkeylogfile.log'
|
41
|
+
}
|
42
|
+
|
43
|
+
[
|
44
|
+
# Initial Handshake:
|
45
|
+
settings_1st,
|
46
|
+
# Subsequent Handshake:
|
47
|
+
settings_2nd
|
48
|
+
].each do |settings|
|
49
|
+
socket = TCPSocket.new(uri.host, uri.port)
|
50
|
+
client = TTTLS13::Client.new(socket, uri.host, **settings)
|
51
|
+
client.connect
|
52
|
+
client.write(req)
|
53
|
+
|
54
|
+
print recv_http_response(client)
|
55
|
+
client.close unless client.eof?
|
56
|
+
socket.close
|
57
|
+
end
|
data/lib/tttls1.3/client.rb
CHANGED
@@ -89,8 +89,8 @@ module TTTLS13
|
|
89
89
|
class Client
|
90
90
|
include Logging
|
91
91
|
|
92
|
-
HpkeSymmetricCipherSuit
|
93
|
-
ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
|
92
|
+
HpkeSymmetricCipherSuit \
|
93
|
+
= ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
|
94
94
|
|
95
95
|
attr_reader :transcript
|
96
96
|
|
@@ -110,7 +110,6 @@ module TTTLS13
|
|
110
110
|
raise Error::ConfigError unless valid_settings?
|
111
111
|
end
|
112
112
|
|
113
|
-
# NOTE:
|
114
113
|
# START <----+
|
115
114
|
# Send ClientHello | | Recv HelloRetryRequest
|
116
115
|
# [K_send = early data] | |
|
@@ -327,7 +326,7 @@ module TTTLS13
|
|
327
326
|
)
|
328
327
|
|
329
328
|
# rejected ECH
|
330
|
-
#
|
329
|
+
# It can compute (hrr_)accept_ech until client selects the
|
331
330
|
# cipher_suite.
|
332
331
|
if !sh.hrr? && use_ech?
|
333
332
|
if !@transcript.include?(HRR) && !key_schedule.accept_ech?
|
@@ -1008,7 +1007,6 @@ module TTTLS13
|
|
1008
1007
|
[new_exs, priv_keys]
|
1009
1008
|
end
|
1010
1009
|
|
1011
|
-
# NOTE:
|
1012
1010
|
# https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2
|
1013
1011
|
#
|
1014
1012
|
# @param ch1 [TTTLS13::Message::ClientHello]
|
@@ -23,8 +23,6 @@ module TTTLS13
|
|
23
23
|
when CipherSuite::TLS_CHACHA20_POLY1305_SHA256
|
24
24
|
@cipher = OpenSSL::Cipher.new('chacha20-poly1305')
|
25
25
|
else
|
26
|
-
# Note:
|
27
|
-
# not supported
|
28
26
|
# CipherSuite::TLS_AES_128_CCM_SHA256
|
29
27
|
# CipherSuite::TLS_AES_128_CCM_8_SHA256
|
30
28
|
raise Error::ErrorAlerts, :internal_error
|
@@ -36,7 +34,6 @@ module TTTLS13
|
|
36
34
|
@auth_tag_len = CipherSuite.auth_tag_len(@cipher_suite)
|
37
35
|
end
|
38
36
|
|
39
|
-
# NOTE:
|
40
37
|
# AEAD-Encrypt(write_key, nonce, additional_data, plaintext)
|
41
38
|
#
|
42
39
|
# @param content [String]
|
@@ -53,7 +50,6 @@ module TTTLS13
|
|
53
50
|
encrypted_data + cipher.auth_tag
|
54
51
|
end
|
55
52
|
|
56
|
-
# NOTE:
|
57
53
|
# AEAD-Decrypt(peer_write_key, nonce,
|
58
54
|
# additional_data, AEADEncrypted)
|
59
55
|
#
|
@@ -78,7 +74,6 @@ module TTTLS13
|
|
78
74
|
[clear[0...-postfix_len], clear[-postfix_len]]
|
79
75
|
end
|
80
76
|
|
81
|
-
# NOTE:
|
82
77
|
# struct {
|
83
78
|
# opaque content[TLSPlaintext.length];
|
84
79
|
# ContentType type;
|
data/lib/tttls1.3/ech.rb
CHANGED
@@ -7,14 +7,19 @@ module TTTLS13
|
|
7
7
|
SUPPORTED_ECHCONFIG_VERSIONS = ["\xfe\x0d"].freeze
|
8
8
|
private_constant :SUPPORTED_ECHCONFIG_VERSIONS
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
DEFAULT_ECH_OUTER_EXTENSIONS = [
|
11
|
+
Message::ExtensionType::KEY_SHARE
|
12
|
+
].freeze
|
13
|
+
private_constant :DEFAULT_ECH_OUTER_EXTENSIONS
|
14
|
+
|
15
|
+
# rubocop: disable Metrics/ClassLength
|
16
|
+
class Ech
|
12
17
|
# @param inner [TTTLS13::Message::ClientHello]
|
13
18
|
# @param ech_config [ECHConfig]
|
14
19
|
# @param hpke_cipher_suite_selector [Method]
|
15
20
|
#
|
16
21
|
# @return [TTTLS13::Message::ClientHello]
|
17
|
-
# @return [TTTLS13::Message::ClientHello]
|
22
|
+
# @return [TTTLS13::Message::ClientHello] ClientHelloInner
|
18
23
|
# @return [TTTLS13::EchState]
|
19
24
|
# rubocop: disable Metrics/AbcSize
|
20
25
|
def self.offer_ech(inner, ech_config, hpke_cipher_suite_selector)
|
@@ -30,12 +35,15 @@ module TTTLS13
|
|
30
35
|
return [new_greased_ch(inner, new_grease_ech), nil, nil] \
|
31
36
|
if ech_state.nil? || enc.nil?
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
)
|
38
|
+
# for ech_outer_extensions
|
39
|
+
replaced = \
|
40
|
+
inner.extensions.remove_and_replace!(DEFAULT_ECH_OUTER_EXTENSIONS)
|
37
41
|
|
38
42
|
# Encoding the ClientHelloInner
|
43
|
+
encoded = encode_ch_inner(inner, ech_state.maximum_name_length, replaced)
|
44
|
+
overhead_len = aead_id2overhead_len(ech_state.cipher_suite.aead_id.uint16)
|
45
|
+
|
46
|
+
# Authenticating the ClientHelloOuter
|
39
47
|
aad = new_ch_outer_aad(
|
40
48
|
inner,
|
41
49
|
ech_state.cipher_suite,
|
@@ -44,13 +52,13 @@ module TTTLS13
|
|
44
52
|
encoded.length + overhead_len,
|
45
53
|
ech_state.public_name
|
46
54
|
)
|
47
|
-
|
48
|
-
# which does not include the Handshake structure's four byte header.
|
55
|
+
|
49
56
|
outer = new_ch_outer(
|
50
57
|
aad,
|
51
58
|
ech_state.cipher_suite,
|
52
59
|
ech_state.config_id,
|
53
60
|
enc,
|
61
|
+
# which does not include the Handshake structure's four byte header.
|
54
62
|
ech_state.ctx.seal(aad.serialize[4..], encoded)
|
55
63
|
)
|
56
64
|
|
@@ -102,11 +110,16 @@ module TTTLS13
|
|
102
110
|
# @param ech_state [TTTLS13::EchState]
|
103
111
|
#
|
104
112
|
# @return [TTTLS13::Message::ClientHello]
|
105
|
-
# @return [TTTLS13::Message::ClientHello]
|
113
|
+
# @return [TTTLS13::Message::ClientHello] ClientHelloInner
|
106
114
|
def self.offer_new_ech(inner, ech_state)
|
107
|
-
|
108
|
-
|
109
|
-
|
115
|
+
# for ech_outer_extensions
|
116
|
+
replaced = \
|
117
|
+
inner.extensions.remove_and_replace!(DEFAULT_ECH_OUTER_EXTENSIONS)
|
118
|
+
|
119
|
+
# Encoding the ClientHelloInner
|
120
|
+
encoded = encode_ch_inner(inner, ech_state.maximum_name_length, replaced)
|
121
|
+
overhead_len = \
|
122
|
+
aead_id2overhead_len(ech_state.cipher_suite.aead_id.uint16)
|
110
123
|
|
111
124
|
# It encrypts EncodedClientHelloInner as described in Section 6.1.1, using
|
112
125
|
# the second partial ClientHelloOuterAAD, to obtain a second
|
@@ -122,13 +135,14 @@ module TTTLS13
|
|
122
135
|
encoded.length + overhead_len,
|
123
136
|
ech_state.public_name
|
124
137
|
)
|
138
|
+
|
125
139
|
# Authenticating the ClientHelloOuter
|
126
|
-
# which does not include the Handshake structure's four byte header.
|
127
140
|
outer = new_ch_outer(
|
128
141
|
aad,
|
129
142
|
ech_state.cipher_suite,
|
130
143
|
ech_state.config_id,
|
131
144
|
'',
|
145
|
+
# which does not include the Handshake structure's four byte header.
|
132
146
|
ech_state.ctx.seal(aad.serialize[4..], encoded)
|
133
147
|
)
|
134
148
|
|
@@ -137,23 +151,23 @@ module TTTLS13
|
|
137
151
|
|
138
152
|
# @param inner [TTTLS13::Message::ClientHello]
|
139
153
|
# @param maximum_name_length [Integer]
|
154
|
+
# @param replaced [TTTLS13::Message::Extensions]
|
140
155
|
#
|
141
156
|
# @return [String] EncodedClientHelloInner
|
142
|
-
def self.encode_ch_inner(inner, maximum_name_length)
|
143
|
-
# TODO: ech_outer_extensions
|
157
|
+
def self.encode_ch_inner(inner, maximum_name_length, replaced)
|
144
158
|
encoded = Message::ClientHello.new(
|
145
159
|
legacy_version: inner.legacy_version,
|
146
160
|
random: inner.random,
|
147
161
|
legacy_session_id: '',
|
148
162
|
cipher_suites: inner.cipher_suites,
|
149
163
|
legacy_compression_methods: inner.legacy_compression_methods,
|
150
|
-
extensions:
|
164
|
+
extensions: replaced
|
151
165
|
)
|
152
166
|
server_name_length = \
|
153
|
-
|
167
|
+
replaced[Message::ExtensionType::SERVER_NAME].server_name.length
|
154
168
|
|
155
|
-
# which does not include the Handshake structure's four byte header.
|
156
169
|
padding_encoded_ch_inner(
|
170
|
+
# which does not include the Handshake structure's four byte header.
|
157
171
|
encoded.serialize[4..],
|
158
172
|
server_name_length,
|
159
173
|
maximum_name_length
|
@@ -284,6 +298,8 @@ module TTTLS13
|
|
284
298
|
|
285
299
|
# @param inner [TTTLS13::Message::ClientHello]
|
286
300
|
# @param ech [Message::Extension::ECHClientHello]
|
301
|
+
#
|
302
|
+
# @return [TTTLS13::Message::ClientHello]
|
287
303
|
def self.new_greased_ch(inner, ech)
|
288
304
|
Message::ClientHello.new(
|
289
305
|
legacy_version: inner.legacy_version,
|
@@ -393,7 +409,7 @@ module TTTLS13
|
|
393
409
|
# @param config_id [Integer]
|
394
410
|
# @param cipher_suite [HpkeSymmetricCipherSuite]
|
395
411
|
# @param public_name [String]
|
396
|
-
# @param ctx [
|
412
|
+
# @param ctx [HPKE::ContextS]
|
397
413
|
def initialize(maximum_name_length,
|
398
414
|
config_id,
|
399
415
|
cipher_suite,
|
@@ -406,5 +422,5 @@ module TTTLS13
|
|
406
422
|
@ctx = ctx
|
407
423
|
end
|
408
424
|
end
|
409
|
-
# rubocop: enable Metrics/
|
425
|
+
# rubocop: enable Metrics/ClassLength
|
410
426
|
end
|
@@ -250,7 +250,7 @@ module TTTLS13
|
|
250
250
|
#
|
251
251
|
# @raise [TTTLS13::Error::ErrorAlerts]
|
252
252
|
#
|
253
|
-
# @
|
253
|
+
# @return [String]
|
254
254
|
def self.hkdf_expand(secret, info, length, digest)
|
255
255
|
hash_len = OpenSSL::Digest.new(digest).digest_length
|
256
256
|
raise Error::ErrorAlerts, :internal_error if length > 255 * hash_len
|
@@ -160,6 +160,14 @@ module TTTLS13
|
|
160
160
|
sg_groups.filter { |g| ks_groups.include?(g) } == ks_groups &&
|
161
161
|
ks_groups.uniq == ks_groups
|
162
162
|
end
|
163
|
+
|
164
|
+
# @return [Boolean]
|
165
|
+
def ch_inner?
|
166
|
+
ech = @extensions[Message::ExtensionType::ENCRYPTED_CLIENT_HELLO]
|
167
|
+
return false if ech.nil?
|
168
|
+
|
169
|
+
ech.type == Message::Extension::ECHClientHelloType::INNER
|
170
|
+
end
|
163
171
|
end
|
164
172
|
end
|
165
173
|
end
|
@@ -12,7 +12,6 @@ module TTTLS13
|
|
12
12
|
INNER = "\x01"
|
13
13
|
end
|
14
14
|
|
15
|
-
# NOTE:
|
16
15
|
# struct {
|
17
16
|
# ECHClientHelloType type;
|
18
17
|
# select (ECHClientHello.type) {
|
@@ -26,12 +25,12 @@ module TTTLS13
|
|
26
25
|
# };
|
27
26
|
# } ECHClientHello;
|
28
27
|
class ECHClientHello
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
attr_reader :extension_type
|
29
|
+
attr_reader :type
|
30
|
+
attr_reader :cipher_suite
|
31
|
+
attr_reader :config_id
|
32
|
+
attr_reader :enc
|
33
|
+
attr_reader :payload
|
35
34
|
|
36
35
|
# @param type [TTTLS13::Message::Extension::ECHClientHelloType]
|
37
36
|
# @param cipher_suite [HpkeSymmetricCipherSuite]
|
@@ -99,10 +98,10 @@ module TTTLS13
|
|
99
98
|
# @raise [TTTLS13::Error::ErrorAlerts]
|
100
99
|
#
|
101
100
|
# @return [TTTLS13::Message::Extensions::ECHClientHello]
|
102
|
-
# rubocop: disable Metrics/AbcSize
|
103
101
|
def deserialize_outer_ech(binary)
|
104
102
|
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
105
|
-
|
103
|
+
|
104
|
+
return nil if binary.length < 5
|
106
105
|
|
107
106
|
kdf_id = \
|
108
107
|
HpkeSymmetricCipherSuite::HpkeKdfId.decode(binary.slice(0, 2))
|
@@ -112,17 +111,14 @@ module TTTLS13
|
|
112
111
|
cid = Convert.bin2i(binary.slice(4, 1))
|
113
112
|
enc_len = Convert.bin2i(binary.slice(5, 2))
|
114
113
|
i = 7
|
115
|
-
|
116
|
-
if i + enc_len > binary.length
|
114
|
+
return nil if i + enc_len > binary.length
|
117
115
|
|
118
116
|
enc = binary.slice(i, enc_len)
|
119
117
|
i += enc_len
|
120
|
-
|
121
|
-
if i + 2 > binary.length
|
118
|
+
return nil if i + 2 > binary.length
|
122
119
|
|
123
120
|
payload_len = Convert.bin2i(binary.slice(i, 2))
|
124
|
-
|
125
|
-
if i + payload_len > binary.length
|
121
|
+
return nil if i + payload_len > binary.length
|
126
122
|
|
127
123
|
payload = binary.slice(i, payload_len)
|
128
124
|
ECHClientHello.new(
|
@@ -133,7 +129,6 @@ module TTTLS13
|
|
133
129
|
payload: payload
|
134
130
|
)
|
135
131
|
end
|
136
|
-
# rubocop: enable Metrics/AbcSize
|
137
132
|
|
138
133
|
# @param binary [String]
|
139
134
|
#
|
@@ -169,13 +164,12 @@ module TTTLS13
|
|
169
164
|
end
|
170
165
|
end
|
171
166
|
|
172
|
-
# NOTE:
|
173
167
|
# struct {
|
174
168
|
# ECHConfigList retry_configs;
|
175
169
|
# } ECHEncryptedExtensions;
|
176
170
|
class ECHEncryptedExtensions
|
177
|
-
|
178
|
-
|
171
|
+
attr_reader :extension_type
|
172
|
+
attr_reader :retry_configs
|
179
173
|
|
180
174
|
# @param retry_configs [Array of ECHConfig]
|
181
175
|
def initialize(retry_configs)
|
@@ -198,8 +192,7 @@ module TTTLS13
|
|
198
192
|
# @return [TTTLS13::Message::Extensions::ECHEncryptedExtensions]
|
199
193
|
def self.deserialize(binary)
|
200
194
|
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
201
|
-
|
202
|
-
if binary.length != binary.slice(0, 2).unpack1('n') + 2
|
195
|
+
return nil if binary.length != binary.slice(0, 2).unpack1('n') + 2
|
203
196
|
|
204
197
|
ECHEncryptedExtensions.new(
|
205
198
|
ECHConfig.decode_vectors(binary.slice(2..))
|
@@ -207,13 +200,12 @@ module TTTLS13
|
|
207
200
|
end
|
208
201
|
end
|
209
202
|
|
210
|
-
# NOTE:
|
211
203
|
# struct {
|
212
204
|
# opaque confirmation[8];
|
213
205
|
# } ECHHelloRetryRequest;
|
214
206
|
class ECHHelloRetryRequest
|
215
|
-
|
216
|
-
|
207
|
+
attr_reader :extension_type
|
208
|
+
attr_reader :confirmation
|
217
209
|
|
218
210
|
# @param confirmation [String]
|
219
211
|
def initialize(confirmation)
|
@@ -233,7 +225,7 @@ module TTTLS13
|
|
233
225
|
# @return [TTTLS13::Message::Extensions::ECHHelloRetryRequest]
|
234
226
|
def self.deserialize(binary)
|
235
227
|
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
236
|
-
|
228
|
+
return nil if binary.length != 8
|
237
229
|
|
238
230
|
ECHHelloRetryRequest.new(binary)
|
239
231
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
using Refinements
|
6
|
+
module Message
|
7
|
+
module Extension
|
8
|
+
# ExtensionType OuterExtensions<2..254>;
|
9
|
+
class ECHOuterExtensions
|
10
|
+
attr_reader :extension_type
|
11
|
+
attr_reader :outer_extensions
|
12
|
+
|
13
|
+
# @param outer_extensions [Array of TTTLS13::Message::ExtensionType]
|
14
|
+
def initialize(outer_extensions)
|
15
|
+
@extension_type = ExtensionType::ECH_OUTER_EXTENSIONS
|
16
|
+
@outer_extensions = outer_extensions
|
17
|
+
end
|
18
|
+
|
19
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
def serialize
|
23
|
+
binary = @outer_extensions.join.prefix_uint8_length
|
24
|
+
@extension_type + binary.prefix_uint16_length
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param binary [String]
|
28
|
+
#
|
29
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
30
|
+
#
|
31
|
+
# @return [TTTLS13::Message::Extensions::ECHOuterExtensions]
|
32
|
+
def self.deserialize(binary)
|
33
|
+
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
34
|
+
|
35
|
+
return nil if binary.length < 2
|
36
|
+
|
37
|
+
exlist_len = Convert.bin2i(binary.slice(0, 1))
|
38
|
+
i = 1
|
39
|
+
outer_extensions = []
|
40
|
+
while i < exlist_len + 1
|
41
|
+
outer_extensions << binary.slice(i, 2)
|
42
|
+
i += 2
|
43
|
+
end
|
44
|
+
return nil unless outer_extensions.length * 2 == exlist_len
|
45
|
+
|
46
|
+
ECHOuterExtensions.new(outer_extensions)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -11,7 +11,7 @@ module TTTLS13
|
|
11
11
|
attr_reader :msg_type
|
12
12
|
attr_reader :key_share_entry
|
13
13
|
|
14
|
-
# @param msg_type [TTTLS13::Message::
|
14
|
+
# @param msg_type [TTTLS13::Message::HandshakeType]
|
15
15
|
# @param key_share_entry [Array of KeyShareEntry]
|
16
16
|
#
|
17
17
|
# @raise [TTTLS13::Error::ErrorAlerts]
|
@@ -108,7 +108,7 @@ module TTTLS13
|
|
108
108
|
[key_share, priv_keys]
|
109
109
|
end
|
110
110
|
|
111
|
-
# @param
|
111
|
+
# @param group [TTTLS13::NamedGroup]
|
112
112
|
#
|
113
113
|
# @return [TTTLS13::Message::Extensions::KeyShare]
|
114
114
|
# @return [OpenSSL::PKey::EC.$Object]
|
@@ -129,7 +129,7 @@ module TTTLS13
|
|
129
129
|
[key_share, ec]
|
130
130
|
end
|
131
131
|
|
132
|
-
# @param
|
132
|
+
# @param group [TTTLS13::NamedGroup]
|
133
133
|
#
|
134
134
|
# @return [TTTLS13::Message::Extensions::KeyShare]
|
135
135
|
def self.gen_hrr_key_share(group)
|
@@ -143,7 +143,6 @@ module TTTLS13
|
|
143
143
|
class << self
|
144
144
|
private
|
145
145
|
|
146
|
-
# NOTE:
|
147
146
|
# struct {
|
148
147
|
# KeyShareEntry client_shares<0..2^16-1>;
|
149
148
|
# } KeyShareClientHello;
|
@@ -178,7 +177,6 @@ module TTTLS13
|
|
178
177
|
key_share_entry
|
179
178
|
end
|
180
179
|
|
181
|
-
# NOTE:
|
182
180
|
# struct {
|
183
181
|
# KeyShareEntry server_share;
|
184
182
|
# } KeyShareServerHello;
|
@@ -201,7 +199,6 @@ module TTTLS13
|
|
201
199
|
[KeyShareEntry.new(group: group, key_exchange: key_exchange)]
|
202
200
|
end
|
203
201
|
|
204
|
-
# NOTE:
|
205
202
|
# struct {
|
206
203
|
# NamedGroup selected_group;
|
207
204
|
# } KeyShareHelloRetryRequest;
|
@@ -5,7 +5,6 @@ module TTTLS13
|
|
5
5
|
using Refinements
|
6
6
|
module Message
|
7
7
|
module Extension
|
8
|
-
# NOTE:
|
9
8
|
# struct {
|
10
9
|
# select (Handshake.msg_type) {
|
11
10
|
# case client_hello: OfferedPsks;
|
@@ -83,7 +82,6 @@ module TTTLS13
|
|
83
82
|
end
|
84
83
|
end
|
85
84
|
|
86
|
-
# NOTE:
|
87
85
|
# opaque PskBinderEntry<32..255>;
|
88
86
|
#
|
89
87
|
# struct {
|
@@ -172,7 +170,6 @@ module TTTLS13
|
|
172
170
|
# rubocop: enable Metrics/PerceivedComplexity
|
173
171
|
end
|
174
172
|
|
175
|
-
# NOTE:
|
176
173
|
# struct {
|
177
174
|
# opaque identity<1..2^16-1>;
|
178
175
|
# uint32 obfuscated_ticket_age;
|
@@ -5,7 +5,7 @@ module TTTLS13
|
|
5
5
|
module Message
|
6
6
|
module Extension
|
7
7
|
class SignatureAlgorithmsCert < SignatureAlgorithms
|
8
|
-
# @param
|
8
|
+
# @param supported_signature_algorithms [Array] Array of SignatureScheme
|
9
9
|
def initialize(supported_signature_algorithms)
|
10
10
|
super(supported_signature_algorithms)
|
11
11
|
@extension_type = ExtensionType::SIGNATURE_ALGORITHMS_CERT
|
@@ -21,7 +21,6 @@ module TTTLS13
|
|
21
21
|
|
22
22
|
alias super_fetch fetch
|
23
23
|
|
24
|
-
# NOTE:
|
25
24
|
# "pre_shared_key" MUST be the last extension in the ClientHello
|
26
25
|
#
|
27
26
|
# @return [String]
|
@@ -105,10 +104,37 @@ module TTTLS13
|
|
105
104
|
store(ex.extension_type, ex)
|
106
105
|
end
|
107
106
|
|
107
|
+
# removing and replacing extensions from EncodedClientHelloInner
|
108
|
+
# with a single "ech_outer_extensions"
|
109
|
+
#
|
110
|
+
# for example
|
111
|
+
# - before
|
112
|
+
# - self.keys: [A B C D E]
|
113
|
+
# - param : [D B]
|
114
|
+
# - after remove_and_replace!
|
115
|
+
# - self.keys: [A C E B D]
|
116
|
+
# - return : [A C E ech_outer_extensions[B D]]
|
117
|
+
# @param outer_extensions [Array of TTTLS13::Message::ExtensionType]
|
118
|
+
#
|
119
|
+
# @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
|
120
|
+
def remove_and_replace!(outer_extensions)
|
121
|
+
tmp1 = filter { |k, _| !outer_extensions.include?(k) }
|
122
|
+
tmp2 = filter { |k, _| outer_extensions.include?(k) }
|
123
|
+
|
124
|
+
clear
|
125
|
+
replaced = Message::Extensions.new
|
126
|
+
|
127
|
+
tmp1.each_value { |v| self << v; replaced << v }
|
128
|
+
tmp2.each_value { |v| self << v }
|
129
|
+
replaced << Message::Extension::ECHOuterExtensions.new(tmp2.keys) \
|
130
|
+
unless tmp2.keys.empty?
|
131
|
+
|
132
|
+
replaced
|
133
|
+
end
|
134
|
+
|
108
135
|
class << self
|
109
136
|
private
|
110
137
|
|
111
|
-
# NOTE:
|
112
138
|
# deserialize_extension ignores unparsable extension.
|
113
139
|
# Received unparsable binary, returns nil, doesn't raise
|
114
140
|
# ErrorAlerts :decode_error.
|
@@ -173,6 +199,8 @@ module TTTLS13
|
|
173
199
|
else
|
174
200
|
Extension::UnknownExtension.deserialize(binary, extension_type)
|
175
201
|
end
|
202
|
+
when ExtensionType::ECH_OUTER_EXTENSIONS
|
203
|
+
Extension::ECHOuterExtensions.deserialize(binary)
|
176
204
|
else
|
177
205
|
Extension::UnknownExtension.deserialize(binary, extension_type)
|
178
206
|
end
|
@@ -27,7 +27,6 @@ module TTTLS13
|
|
27
27
|
@cipher = cipher
|
28
28
|
end
|
29
29
|
|
30
|
-
# NOTE:
|
31
30
|
# serialize joins messages.
|
32
31
|
# If serialize is received Server Parameters(EE, CT, CV),
|
33
32
|
# it returns one binary.
|
@@ -50,7 +49,6 @@ module TTTLS13
|
|
50
49
|
end.join
|
51
50
|
end
|
52
51
|
|
53
|
-
# NOTE:
|
54
52
|
# If previous Record has surplus_binary,
|
55
53
|
# surplus_binary should is given to Record.deserialize as buffered.
|
56
54
|
#
|
data/lib/tttls1.3/message.rb
CHANGED
@@ -74,6 +74,7 @@ module TTTLS13
|
|
74
74
|
KEY_SHARE = "\x00\x33"
|
75
75
|
# https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-11.1
|
76
76
|
ENCRYPTED_CLIENT_HELLO = "\xfe\x0d"
|
77
|
+
ECH_OUTER_EXTENSIONS = "\xfd\x00"
|
77
78
|
end
|
78
79
|
|
79
80
|
DEFINED_EXTENSIONS = ExtensionType.constants.map do |c|
|
data/lib/tttls1.3/named_group.rb
CHANGED
@@ -17,7 +17,6 @@ module TTTLS13
|
|
17
17
|
# ecdhe_private_use "\xfe\x00" ~ "\xfe\xff"
|
18
18
|
|
19
19
|
class << self
|
20
|
-
# NOTE:
|
21
20
|
# For secp256r1, secp384r1, and secp521r1
|
22
21
|
#
|
23
22
|
# struct {
|
@@ -57,7 +56,6 @@ module TTTLS13
|
|
57
56
|
end
|
58
57
|
end
|
59
58
|
|
60
|
-
# NOTE:
|
61
59
|
# SECG | ANSI X9.62 | NIST
|
62
60
|
# ------------+---------------+-------------
|
63
61
|
# secp256r1 | prime256v1 | NIST P-256
|
@@ -66,7 +64,7 @@ module TTTLS13
|
|
66
64
|
#
|
67
65
|
# https://datatracker.ietf.org/doc/html/rfc4492#appendix-A
|
68
66
|
#
|
69
|
-
# @param
|
67
|
+
# @param group [TTTLS13::Message::Extension::NamedGroup]
|
70
68
|
#
|
71
69
|
# @raise [TTTLS13::Error::ErrorAlerts]
|
72
70
|
#
|
data/lib/tttls1.3/server.rb
CHANGED
data/lib/tttls1.3/version.rb
CHANGED
data/spec/client_hello_spec.rb
CHANGED
@@ -37,6 +37,7 @@ RSpec.describe ClientHello do
|
|
37
37
|
expect(message.legacy_compression_methods).to eq ["\x00"]
|
38
38
|
expect(message.extensions).to be_empty
|
39
39
|
expect(message.negotiated_tls_1_3?).to be false
|
40
|
+
expect(message.ch_inner?).to be false
|
40
41
|
end
|
41
42
|
|
42
43
|
it 'should be serialized' do
|
@@ -83,4 +84,22 @@ RSpec.describe ClientHello do
|
|
83
84
|
expect(message.serialize).to eq TESTBINARY_0_RTT_CLIENT_HELLO
|
84
85
|
end
|
85
86
|
end
|
87
|
+
|
88
|
+
context 'valid inner client_hello' do
|
89
|
+
let(:message) do
|
90
|
+
cipher_suites = CipherSuites.new([TLS_AES_256_GCM_SHA384,
|
91
|
+
TLS_CHACHA20_POLY1305_SHA256,
|
92
|
+
TLS_AES_128_GCM_SHA256])
|
93
|
+
ch = ClientHello.new(random: OpenSSL::Random.random_bytes(32),
|
94
|
+
legacy_session_id: Array.new(32, 0).map(&:chr).join,
|
95
|
+
cipher_suites: cipher_suites)
|
96
|
+
ch.extensions[Message::ExtensionType::ENCRYPTED_CLIENT_HELLO] \
|
97
|
+
= Message::Extension::ECHClientHello.new_inner
|
98
|
+
ch
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should generate ClientHelloInner' do
|
102
|
+
expect(message.ch_inner?).to be true
|
103
|
+
end
|
104
|
+
end
|
86
105
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'spec_helper'
|
5
|
+
using Refinements
|
6
|
+
|
7
|
+
RSpec.describe ECHOuterExtensions do
|
8
|
+
context 'valid ech_outer_extensions, [key_share]' do
|
9
|
+
let(:extension) do
|
10
|
+
ECHOuterExtensions.new([ExtensionType::KEY_SHARE])
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should be generated' do
|
14
|
+
expect(extension.extension_type).to eq ExtensionType::ECH_OUTER_EXTENSIONS
|
15
|
+
expect(extension.outer_extensions).to eq [ExtensionType::KEY_SHARE]
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should be serialized' do
|
19
|
+
expect(extension.serialize).to eq ExtensionType::ECH_OUTER_EXTENSIONS \
|
20
|
+
+ 3.to_uint16 \
|
21
|
+
+ 2.to_uint8 \
|
22
|
+
+ ExtensionType::KEY_SHARE
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'valid ech_outer_extensions binary' do
|
27
|
+
let(:extension) do
|
28
|
+
ECHOuterExtensions.deserialize(TESTBINARY_ECH_OUTER_EXTENSIONS)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should generate valid object' do
|
32
|
+
expect(extension.extension_type).to be ExtensionType::ECH_OUTER_EXTENSIONS
|
33
|
+
expect(extension.outer_extensions).to eq [ExtensionType::KEY_SHARE]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should generate serializable object' do
|
37
|
+
expect(extension.serialize)
|
38
|
+
.to eq ExtensionType::ECH_OUTER_EXTENSIONS \
|
39
|
+
+ TESTBINARY_ECH_OUTER_EXTENSIONS.prefix_uint16_length
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/spec/ech_spec.rb
CHANGED
data/spec/extensions_spec.rb
CHANGED
@@ -182,4 +182,69 @@ RSpec.describe Extensions do
|
|
182
182
|
.to raise_error(ErrorAlerts)
|
183
183
|
end
|
184
184
|
end
|
185
|
+
|
186
|
+
context 'removing and replacing extensions from EncodedClientHelloInner' do
|
187
|
+
let(:extensions) do
|
188
|
+
extensions, = Client.new(nil, 'localhost').send(:gen_ch_extensions)
|
189
|
+
extensions
|
190
|
+
end
|
191
|
+
|
192
|
+
let(:no_key_share_exs) do
|
193
|
+
Extensions.new(
|
194
|
+
extensions.filter { |k, _| k != ExtensionType::KEY_SHARE }.values
|
195
|
+
)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should be equal remove_and_replace! with []' do
|
199
|
+
expected = extensions.clone
|
200
|
+
got = extensions.remove_and_replace!([])
|
201
|
+
|
202
|
+
expect(got.keys).to eq expected.keys
|
203
|
+
expect(got[ExtensionType::ECH_OUTER_EXTENSIONS]).to eq nil
|
204
|
+
expect(extensions.keys - got.keys).to eq []
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should be equal remove_and_replace! with [key_share]' do
|
208
|
+
expected = extensions.filter { |k, _| k != ExtensionType::KEY_SHARE }
|
209
|
+
expected[ExtensionType::ECH_OUTER_EXTENSIONS] = \
|
210
|
+
Extension::ECHOuterExtensions.new([ExtensionType::KEY_SHARE])
|
211
|
+
got = extensions.remove_and_replace!([ExtensionType::KEY_SHARE])
|
212
|
+
|
213
|
+
expect(got.keys).to eq expected.keys
|
214
|
+
expect(got[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions)
|
215
|
+
.to eq expected[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions
|
216
|
+
expect(extensions.keys - got.keys)
|
217
|
+
.to eq expected[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'should be equal remove_and_replace! with' \
|
221
|
+
' [key_share,supported_versions]' do
|
222
|
+
outer_extensions = [
|
223
|
+
ExtensionType::KEY_SHARE,
|
224
|
+
ExtensionType::SUPPORTED_VERSIONS
|
225
|
+
]
|
226
|
+
expected = extensions.filter { |k, _| !outer_extensions.include?(k) }
|
227
|
+
expected[ExtensionType::ECH_OUTER_EXTENSIONS] = \
|
228
|
+
Extension::ECHOuterExtensions.new(
|
229
|
+
extensions.filter { |k, _| outer_extensions.include?(k) }.keys
|
230
|
+
)
|
231
|
+
got = extensions.remove_and_replace!(outer_extensions)
|
232
|
+
|
233
|
+
expect(got.keys).to eq expected.keys
|
234
|
+
expect(got[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions)
|
235
|
+
.to eq expected[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions
|
236
|
+
expect(extensions.keys - got.keys)
|
237
|
+
.to eq expected[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'should be equal remove_and_replace! with no key_share extensions' \
|
241
|
+
' & [key_share]' do
|
242
|
+
expected = no_key_share_exs.clone
|
243
|
+
got = no_key_share_exs.remove_and_replace!([ExtensionType::KEY_SHARE])
|
244
|
+
|
245
|
+
expect(got).to eq expected
|
246
|
+
expect(got[ExtensionType::ECH_OUTER_EXTENSIONS]).to eq nil
|
247
|
+
expect(no_key_share_exs.keys - got.keys).to eq []
|
248
|
+
end
|
249
|
+
end
|
185
250
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -245,6 +245,10 @@ TESTBINARY_ECH_HRR = <<BIN.split.map(&:hex).map(&:chr).join
|
|
245
245
|
00 00 00 00 00 00 00 00
|
246
246
|
BIN
|
247
247
|
|
248
|
+
TESTBINARY_ECH_OUTER_EXTENSIONS = <<BIN.split.map(&:hex).map(&:chr).join
|
249
|
+
02 00 33
|
250
|
+
BIN
|
251
|
+
|
248
252
|
# https://datatracker.ietf.org/doc/html/rfc8448#section-3
|
249
253
|
# 3. Simple 1-RTT Handshake
|
250
254
|
TESTBINARY_CLIENT_HELLO = <<BIN.split.map(&:hex).map(&:chr).join
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tttls1.3
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- thekuwayama
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,6 +108,7 @@ files:
|
|
108
108
|
- example/https_client_using_hrr_and_ticket.rb
|
109
109
|
- example/https_client_using_status_request.rb
|
110
110
|
- example/https_client_using_ticket.rb
|
111
|
+
- example/https_client_using_ticket_and_ech.rb
|
111
112
|
- example/https_server.rb
|
112
113
|
- interop/client_spec.rb
|
113
114
|
- interop/server_spec.rb
|
@@ -139,6 +140,7 @@ files:
|
|
139
140
|
- lib/tttls1.3/message/extension/cookie.rb
|
140
141
|
- lib/tttls1.3/message/extension/early_data_indication.rb
|
141
142
|
- lib/tttls1.3/message/extension/ech.rb
|
143
|
+
- lib/tttls1.3/message/extension/ech_outer_extensions.rb
|
142
144
|
- lib/tttls1.3/message/extension/key_share.rb
|
143
145
|
- lib/tttls1.3/message/extension/pre_shared_key.rb
|
144
146
|
- lib/tttls1.3/message/extension/psk_key_exchange_modes.rb
|
@@ -176,6 +178,7 @@ files:
|
|
176
178
|
- spec/compress_certificate_spec.rb
|
177
179
|
- spec/cookie_spec.rb
|
178
180
|
- spec/early_data_indication_spec.rb
|
181
|
+
- spec/ech_outer_extensions_spec.rb
|
179
182
|
- spec/ech_spec.rb
|
180
183
|
- spec/encrypted_extensions_spec.rb
|
181
184
|
- spec/end_of_early_data_spec.rb
|
@@ -254,6 +257,7 @@ test_files:
|
|
254
257
|
- spec/compress_certificate_spec.rb
|
255
258
|
- spec/cookie_spec.rb
|
256
259
|
- spec/early_data_indication_spec.rb
|
260
|
+
- spec/ech_outer_extensions_spec.rb
|
257
261
|
- spec/ech_spec.rb
|
258
262
|
- spec/encrypted_extensions_spec.rb
|
259
263
|
- spec/end_of_early_data_spec.rb
|