From 7fecf2454519f6fc921b5e7f39a5512fc8eaa720 Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Sun, 14 Apr 2024 01:18:34 +0900 Subject: [PATCH 01/15] add ECHOuterExtensions --- lib/tttls1.3/message.rb | 1 + .../message/extension/ech_outer_extensions.rb | 52 +++++++++++++++++++ spec/ech_outer_extensions_spec.rb | 42 +++++++++++++++ spec/spec_helper.rb | 4 ++ 4 files changed, 99 insertions(+) create mode 100644 lib/tttls1.3/message/extension/ech_outer_extensions.rb create mode 100644 spec/ech_outer_extensions_spec.rb diff --git a/lib/tttls1.3/message.rb b/lib/tttls1.3/message.rb index 3e4674f..f9af187 100644 --- a/lib/tttls1.3/message.rb +++ b/lib/tttls1.3/message.rb @@ -74,6 +74,7 @@ module ExtensionType KEY_SHARE = "\x00\x33" # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-11.1 ENCRYPTED_CLIENT_HELLO = "\xfe\x0d" + ECH_OUTER_EXTENSIONS = "\xfd\x00" end DEFINED_EXTENSIONS = ExtensionType.constants.map do |c| diff --git a/lib/tttls1.3/message/extension/ech_outer_extensions.rb b/lib/tttls1.3/message/extension/ech_outer_extensions.rb new file mode 100644 index 0000000..93ede7d --- /dev/null +++ b/lib/tttls1.3/message/extension/ech_outer_extensions.rb @@ -0,0 +1,52 @@ +# encoding: ascii-8bit +# frozen_string_literal: true + +module TTTLS13 + using Refinements + module Message + module Extension + # NOTE: + # ExtensionType OuterExtensions<2..254>; + class ECHOuterExtensions + attr_reader :extension_type + attr_reader :outer_extensions + + # @param outer_extensions [Array of TTTLS13::Message::ExtensionType] + def initialize(outer_extensions) + @extension_type = ExtensionType::ECH_OUTER_EXTENSIONS + @outer_extensions = outer_extensions + end + + # @raise [TTTLS13::Error::ErrorAlerts] + # + # @return [String] + def serialize + binary = @outer_extensions.join.prefix_uint8_length + @extension_type + binary.prefix_uint16_length + end + + # @param binary [String] + # + # @raise [TTTLS13::Error::ErrorAlerts] + # + # @return [TTTLS13::Message::Extensions::ECHOuterExtensions] + def self.deserialize(binary) + raise Error::ErrorAlerts, :internal_error if binary.nil? + + return nil if binary.length < 2 + + exlist_len = Convert.bin2i(binary.slice(0, 1)) + i = 1 + outer_extensions = [] + while i < exlist_len + 1 + outer_extensions << binary.slice(i, 2) + i += 2 + end + return nil unless outer_extensions.length * 2 == exlist_len + + ECHOuterExtensions.new(outer_extensions) + end + end + end + end +end diff --git a/spec/ech_outer_extensions_spec.rb b/spec/ech_outer_extensions_spec.rb new file mode 100644 index 0000000..ba47f88 --- /dev/null +++ b/spec/ech_outer_extensions_spec.rb @@ -0,0 +1,42 @@ +# encoding: ascii-8bit +# frozen_string_literal: true + +require_relative 'spec_helper' +using Refinements + +RSpec.describe ECHOuterExtensions do + context 'valid ech_outer_extensions, [key_share]' do + let(:extension) do + ECHOuterExtensions.new([ExtensionType::KEY_SHARE]) + end + + it 'should be generated' do + expect(extension.extension_type).to eq ExtensionType::ECH_OUTER_EXTENSIONS + expect(extension.outer_extensions).to eq [ExtensionType::KEY_SHARE] + end + + it 'should be serialized' do + expect(extension.serialize).to eq ExtensionType::ECH_OUTER_EXTENSIONS \ + + 3.to_uint16 \ + + 2.to_uint8 \ + + ExtensionType::KEY_SHARE + end + end + + context 'valid ech_outer_extensions binary' do + let(:extension) do + ECHOuterExtensions.deserialize(TESTBINARY_ECH_OUTER_EXTENSIONS) + end + + it 'should generate valid object' do + expect(extension.extension_type).to be ExtensionType::ECH_OUTER_EXTENSIONS + expect(extension.outer_extensions).to eq [ExtensionType::KEY_SHARE] + end + + it 'should generate serializable object' do + expect(extension.serialize) + .to eq ExtensionType::ECH_OUTER_EXTENSIONS \ + + TESTBINARY_ECH_OUTER_EXTENSIONS.prefix_uint16_length + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6d51b75..dd46c80 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -245,6 +245,10 @@ def read(len = @buffer.length) 00 00 00 00 00 00 00 00 BIN +TESTBINARY_ECH_OUTER_EXTENSIONS = < Date: Sun, 14 Apr 2024 01:28:54 +0900 Subject: [PATCH 02/15] support deserialize_extension ExtensionType::ECH_OUTER_EXTENSIONS --- lib/tttls1.3/message/extensions.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/tttls1.3/message/extensions.rb b/lib/tttls1.3/message/extensions.rb index b78a071..d00007a 100644 --- a/lib/tttls1.3/message/extensions.rb +++ b/lib/tttls1.3/message/extensions.rb @@ -173,6 +173,8 @@ def deserialize_extension(binary, extension_type, msg_type) else Extension::UnknownExtension.deserialize(binary, extension_type) end + when ExtensionType::ECH_OUTER_EXTENSIONS + Extension::ECHOuterExtensions.deserialize(binary) else Extension::UnknownExtension.deserialize(binary, extension_type) end From f5546304a4474ce2643d1020d00db2b2f0259f91 Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Sun, 14 Apr 2024 11:33:59 +0900 Subject: [PATCH 03/15] add remove_and_replace_exs & modify offer(_new)_ech --- lib/tttls1.3/client.rb | 5 +++- lib/tttls1.3/ech.rb | 58 +++++++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/lib/tttls1.3/client.rb b/lib/tttls1.3/client.rb index 77b99ed..bac506e 100644 --- a/lib/tttls1.3/client.rb +++ b/lib/tttls1.3/client.rb @@ -194,6 +194,7 @@ def connect binder_key = (use_psk? ? key_schedule.binder_key_res : nil) ch, inner, ech_state = send_client_hello(extensions, binder_key) ch_outer = ch + @outer_exs = ch_outer.extensions # FIXME # use ClientHelloInner messages for the transcript hash ch = inner.nil? ? ch : inner @transcript[CH] = [ch, ch.serialize] @@ -293,6 +294,7 @@ def connect ) # use ClientHelloInner messages for the transcript hash ch_outer = ch + @outer_exs = ch_outer.extensions # FIXME ch = inner.nil? ? ch : inner @transcript[CH] = [ch, ch.serialize] @@ -306,7 +308,8 @@ def connect else psk = nil end - ch_ks = ch.extensions[Message::ExtensionType::KEY_SHARE] + + ch_ks = @outer_exs[Message::ExtensionType::KEY_SHARE] .key_share_entry.map(&:group) sh_ks = sh.extensions[Message::ExtensionType::KEY_SHARE] .key_share_entry.first.group diff --git a/lib/tttls1.3/ech.rb b/lib/tttls1.3/ech.rb index 959ccfb..1c74972 100644 --- a/lib/tttls1.3/ech.rb +++ b/lib/tttls1.3/ech.rb @@ -30,12 +30,16 @@ def self.offer_ech(inner, ech_config, hpke_cipher_suite_selector) return [new_greased_ch(inner, new_grease_ech), nil, nil] \ if ech_state.nil? || enc.nil? - encoded = encode_ch_inner(inner, ech_state.maximum_name_length) + # for ech_outer_extensions + replaced_exs = remove_and_replace_exs(inner.extensions) + + # Encoding the ClientHelloInner + encoded = encode_ch_inner(inner, ech_state.maximum_name_length, replaced_exs) overhead_len = aead_id2overhead_len( ech_state.cipher_suite.aead_id.uint16 ) - # Encoding the ClientHelloInner + # Authenticating the ClientHelloOuter aad = new_ch_outer_aad( inner, ech_state.cipher_suite, @@ -44,13 +48,13 @@ def self.offer_ech(inner, ech_config, hpke_cipher_suite_selector) encoded.length + overhead_len, ech_state.public_name ) - # Authenticating the ClientHelloOuter - # which does not include the Handshake structure's four byte header. + outer = new_ch_outer( aad, ech_state.cipher_suite, ech_state.config_id, enc, + # which does not include the Handshake structure's four byte header. ech_state.ctx.seal(aad.serialize[4..], encoded) ) @@ -104,7 +108,12 @@ def self.encrypted_ech_config(ech_config, hpke_cipher_suite_selector) # @return [TTTLS13::Message::ClientHello] # @return [TTTLS13::Message::ClientHello] def self.offer_new_ech(inner, ech_state) - encoded = encode_ch_inner(inner, ech_state.maximum_name_length) + # for ech_outer_extensions + outer_exs = inner.extensions.clone + encoded_exs = remove_and_replace_exs(inner.extensions) + + # Encoding the ClientHelloInner + encoded = encode_ch_inner(inner, ech_state.maximum_name_length, encoded_exs) overhead_len \ = aead_id2overhead_len(ech_state.cipher_suite.aead_id.uint16) @@ -122,13 +131,14 @@ def self.offer_new_ech(inner, ech_state) encoded.length + overhead_len, ech_state.public_name ) + # Authenticating the ClientHelloOuter - # which does not include the Handshake structure's four byte header. outer = new_ch_outer( aad, ech_state.cipher_suite, ech_state.config_id, '', + # which does not include the Handshake structure's four byte header. ech_state.ctx.seal(aad.serialize[4..], encoded) ) @@ -137,23 +147,23 @@ def self.offer_new_ech(inner, ech_state) # @param inner [TTTLS13::Message::ClientHello] # @param maximum_name_length [Integer] + # @param replaced_exs [TTTLS13::Message::Extensions] # # @return [String] EncodedClientHelloInner - def self.encode_ch_inner(inner, maximum_name_length) - # TODO: ech_outer_extensions + def self.encode_ch_inner(inner, maximum_name_length, replaced_exs) encoded = Message::ClientHello.new( legacy_version: inner.legacy_version, random: inner.random, legacy_session_id: '', cipher_suites: inner.cipher_suites, legacy_compression_methods: inner.legacy_compression_methods, - extensions: inner.extensions + extensions: replaced_exs ) server_name_length = \ - inner.extensions[Message::ExtensionType::SERVER_NAME].server_name.length + replaced_exs[Message::ExtensionType::SERVER_NAME].server_name.length - # which does not include the Handshake structure's four byte header. padding_encoded_ch_inner( + # which does not include the Handshake structure's four byte header. encoded.serialize[4..], server_name_length, maximum_name_length @@ -284,6 +294,8 @@ def self.placeholder_encoded_ch_inner_len # @param inner [TTTLS13::Message::ClientHello] # @param ech [Message::Extension::ECHClientHello] + # + # @return [TTTLS13::Message::ClientHello] def self.new_greased_ch(inner, ech) Message::ClientHello.new( legacy_version: inner.legacy_version, @@ -297,6 +309,28 @@ def self.new_greased_ch(inner, ech) ) end + # @param exs [TTTLS13::Message::Extensions] from ClientHelloInner + # + # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner + def self.remove_and_replace_exs(exs) + # TODO: specify ex_types + ex_types = [Message::ExtensionType::KEY_SHARE] + + # removing and replacing extensions from EncodedClientHelloInner + # with a single "ech_outer_extensions" + exs.reduce(Message::Extensions.new) do |acc, (k, v)| + if ex_types.include?(k) && + !acc.include?(Message::ExtensionType::ECH_OUTER_EXTENSIONS) + acc[Message::ExtensionType::ECH_OUTER_EXTENSIONS] = \ + Message::Extension::ECHOuterExtensions.new(ex_types) + elsif !ex_types.include?(k) + acc[k] = v + end + + acc + end + end + module KemId # https://www.iana.org/assignments/hpke/hpke.xhtml#hpke-kem-ids P_256_SHA256 = 0x0010 @@ -393,7 +427,7 @@ class EchState # @param config_id [Integer] # @param cipher_suite [HpkeSymmetricCipherSuite] # @param public_name [String] - # @param ctx [[HPKE::ContextS] + # @param ctx [HPKE::ContextS] def initialize(maximum_name_length, config_id, cipher_suite, From 78b1db91f5cca008a8a6a41535797ec6f3b455de Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Tue, 16 Apr 2024 06:28:55 +0900 Subject: [PATCH 04/15] refactor: using DEFAULT_ECH_OUTER_EXTENSIONS improvement: add testcases; removing and replacing extensions from EncodedClientHelloInner clean: for rubocop --- .rubocop.yml | 3 +++ lib/tttls1.3/client.rb | 5 +---- lib/tttls1.3/ech.rb | 34 +++++++++++++++++---------------- spec/ech_spec.rb | 43 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6e5ce3f..4d9112d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -10,6 +10,9 @@ Style/ConditionalAssignment: Style/Documentation: Enabled: false +Style/EachWithObject: + Enabled: false + Style/NumericLiterals: Enabled: false diff --git a/lib/tttls1.3/client.rb b/lib/tttls1.3/client.rb index bac506e..77b99ed 100644 --- a/lib/tttls1.3/client.rb +++ b/lib/tttls1.3/client.rb @@ -194,7 +194,6 @@ def connect binder_key = (use_psk? ? key_schedule.binder_key_res : nil) ch, inner, ech_state = send_client_hello(extensions, binder_key) ch_outer = ch - @outer_exs = ch_outer.extensions # FIXME # use ClientHelloInner messages for the transcript hash ch = inner.nil? ? ch : inner @transcript[CH] = [ch, ch.serialize] @@ -294,7 +293,6 @@ def connect ) # use ClientHelloInner messages for the transcript hash ch_outer = ch - @outer_exs = ch_outer.extensions # FIXME ch = inner.nil? ? ch : inner @transcript[CH] = [ch, ch.serialize] @@ -308,8 +306,7 @@ def connect else psk = nil end - - ch_ks = @outer_exs[Message::ExtensionType::KEY_SHARE] + ch_ks = ch.extensions[Message::ExtensionType::KEY_SHARE] .key_share_entry.map(&:group) sh_ks = sh.extensions[Message::ExtensionType::KEY_SHARE] .key_share_entry.first.group diff --git a/lib/tttls1.3/ech.rb b/lib/tttls1.3/ech.rb index 1c74972..0e63999 100644 --- a/lib/tttls1.3/ech.rb +++ b/lib/tttls1.3/ech.rb @@ -7,6 +7,11 @@ module TTTLS13 SUPPORTED_ECHCONFIG_VERSIONS = ["\xfe\x0d"].freeze private_constant :SUPPORTED_ECHCONFIG_VERSIONS + DEFAULT_ECH_OUTER_EXTENSIONS = [ + Message::ExtensionType::KEY_SHARE + ].freeze + private_constant :DEFAULT_ECH_OUTER_EXTENSIONS + # rubocop: disable Metrics/ClassLength class Ech # @param inner [TTTLS13::Message::ClientHello] @@ -31,13 +36,12 @@ def self.offer_ech(inner, ech_config, hpke_cipher_suite_selector) if ech_state.nil? || enc.nil? # for ech_outer_extensions - replaced_exs = remove_and_replace_exs(inner.extensions) + replaced = \ + remove_and_replace_exs(inner.extensions, DEFAULT_ECH_OUTER_EXTENSIONS) # Encoding the ClientHelloInner - encoded = encode_ch_inner(inner, ech_state.maximum_name_length, replaced_exs) - overhead_len = aead_id2overhead_len( - ech_state.cipher_suite.aead_id.uint16 - ) + encoded = encode_ch_inner(inner, ech_state.maximum_name_length, replaced) + overhead_len = aead_id2overhead_len(ech_state.cipher_suite.aead_id.uint16) # Authenticating the ClientHelloOuter aad = new_ch_outer_aad( @@ -109,11 +113,11 @@ def self.encrypted_ech_config(ech_config, hpke_cipher_suite_selector) # @return [TTTLS13::Message::ClientHello] def self.offer_new_ech(inner, ech_state) # for ech_outer_extensions - outer_exs = inner.extensions.clone - encoded_exs = remove_and_replace_exs(inner.extensions) + replaced = \ + remove_and_replace_exs(inner.extensions, DEFAULT_ECH_OUTER_EXTENSIONS) # Encoding the ClientHelloInner - encoded = encode_ch_inner(inner, ech_state.maximum_name_length, encoded_exs) + encoded = encode_ch_inner(inner, ech_state.maximum_name_length, replaced) overhead_len \ = aead_id2overhead_len(ech_state.cipher_suite.aead_id.uint16) @@ -147,20 +151,20 @@ def self.offer_new_ech(inner, ech_state) # @param inner [TTTLS13::Message::ClientHello] # @param maximum_name_length [Integer] - # @param replaced_exs [TTTLS13::Message::Extensions] + # @param replaced [TTTLS13::Message::Extensions] # # @return [String] EncodedClientHelloInner - def self.encode_ch_inner(inner, maximum_name_length, replaced_exs) + def self.encode_ch_inner(inner, maximum_name_length, replaced) encoded = Message::ClientHello.new( legacy_version: inner.legacy_version, random: inner.random, legacy_session_id: '', cipher_suites: inner.cipher_suites, legacy_compression_methods: inner.legacy_compression_methods, - extensions: replaced_exs + extensions: replaced ) server_name_length = \ - replaced_exs[Message::ExtensionType::SERVER_NAME].server_name.length + replaced[Message::ExtensionType::SERVER_NAME].server_name.length padding_encoded_ch_inner( # which does not include the Handshake structure's four byte header. @@ -310,12 +314,10 @@ def self.new_greased_ch(inner, ech) end # @param exs [TTTLS13::Message::Extensions] from ClientHelloInner + # @param ex_types [Array of TTTLS13::Message::ExtensionType] # # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner - def self.remove_and_replace_exs(exs) - # TODO: specify ex_types - ex_types = [Message::ExtensionType::KEY_SHARE] - + def self.remove_and_replace_exs(exs, ex_types) # removing and replacing extensions from EncodedClientHelloInner # with a single "ech_outer_extensions" exs.reduce(Message::Extensions.new) do |acc, (k, v)| diff --git a/spec/ech_spec.rb b/spec/ech_spec.rb index dd2bc08..85b22a5 100644 --- a/spec/ech_spec.rb +++ b/spec/ech_spec.rb @@ -78,7 +78,9 @@ expect(extension.confirmation).to eq "\x00" * 8 end end +end +RSpec.describe Ech do context 'EncodedClientHelloInner length' do let(:server_name) do 'localhost' @@ -117,4 +119,45 @@ .to eq padding_encoded_ch_inner.length end end + + context 'removing and replacing extensions from EncodedClientHelloInner' do + let(:extensions) do + extensions, = Client.new(nil, 'localhost').send(:gen_ch_extensions) + extensions + end + + let(:no_key_share_exs) do + extensions.filter { |k, _| k != ExtensionType::KEY_SHARE } + end + + it 'should be equal remove_and_replace_exs with extensions & []' do + expect(Ech.remove_and_replace_exs(extensions, [])) + .to eq extensions + end + + it 'should be equal remove_and_replace_exs with extensions & [key_share]' do + got = Ech.remove_and_replace_exs(extensions, [ExtensionType::KEY_SHARE]) + expected = extensions.map do |k, v| + if k == ExtensionType::KEY_SHARE + [ + ExtensionType::ECH_OUTER_EXTENSIONS, + Extension::ECHOuterExtensions.new([ExtensionType::KEY_SHARE]) + ] + else + [k, v] + end + end + expected = expected.to_h + expect(got.keys).to eq expected.keys + expect(got[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions) + .to eq expected[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions + end + + it 'should be equal remove_and_replace_exs with no key_share extensions' \ + ' & [key_share]' do + expect(Ech.remove_and_replace_exs(no_key_share_exs, + [ExtensionType::KEY_SHARE])) + .to eq no_key_share_exs + end + end end From 6915cb98c251ed965fed801b15d447d6b23d1efc Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Wed, 17 Apr 2024 02:54:18 +0900 Subject: [PATCH 05/15] add example/https_client_using_ticket_and_ech.rb --- example/https_client_using_ticket_and_ech.rb | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 example/https_client_using_ticket_and_ech.rb diff --git a/example/https_client_using_ticket_and_ech.rb b/example/https_client_using_ticket_and_ech.rb new file mode 100644 index 0000000..c5aa347 --- /dev/null +++ b/example/https_client_using_ticket_and_ech.rb @@ -0,0 +1,59 @@ +# encoding: ascii-8bit +# frozen_string_literal: true + +require_relative 'helper' + +uri = URI.parse(ARGV[0] || 'https://localhost:4433') +ca_file = __dir__ + '/../tmp/ca.crt' +req = simple_http_request(uri.host, uri.path) +ech_config = if ARGV.length > 1 + parse_echconfigs_pem(File.open(ARGV[1]).read).first + else + rr = Resolv::DNS.new.getresources( + uri.host, + Resolv::DNS::Resource::IN::HTTPS + ) + rr.first.svc_params['ech'].echconfiglist.first + end + +settings_2nd = { + ca_file: File.exist?(ca_file) ? ca_file : nil, + alpn: ['http/1.1'], + ech_config: ech_config, + ech_hpke_cipher_suites: + TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES +} +process_new_session_ticket = lambda do |nst, rms, cs| + return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime + + settings_2nd[:ticket] = nst.ticket + settings_2nd[:resumption_main_secret] = rms + settings_2nd[:psk_cipher_suite] = cs + settings_2nd[:ticket_nonce] = nst.ticket_nonce + settings_2nd[:ticket_age_add] = nst.ticket_age_add + settings_2nd[:ticket_timestamp] = nst.timestamp +end +settings_1st = { + ca_file: File.exist?(ca_file) ? ca_file : nil, + alpn: ['http/1.1'], + ech_config: ech_config, + ech_hpke_cipher_suites: + TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES, + process_new_session_ticket: process_new_session_ticket +} + +[ + # Initial Handshake: + settings_1st, + # Subsequent Handshake: + settings_2nd +].each do |settings| + socket = TCPSocket.new(uri.host, uri.port) + client = TTTLS13::Client.new(socket, uri.host, **settings) + client.connect + client.write(req) + + print recv_http_response(client) + client.close unless client.eof? + socket.close +end From e5ec5358413b8264919fd20b91133a37a230496e Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Wed, 17 Apr 2024 02:58:39 +0900 Subject: [PATCH 06/15] improvement: add sslkeylogfile to example/https_clien_*.rb --- example/https_client_using_0rtt.rb | 6 ++++-- example/https_client_using_hrr.rb | 3 ++- example/https_client_using_hrr_and_ticket.rb | 6 ++++-- example/https_client_using_status_request.rb | 3 ++- example/https_client_using_ticket.rb | 6 ++++-- example/https_client_using_ticket_and_ech.rb | 6 ++++-- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/example/https_client_using_0rtt.rb b/example/https_client_using_0rtt.rb index b4ec9e7..d207623 100644 --- a/example/https_client_using_0rtt.rb +++ b/example/https_client_using_0rtt.rb @@ -9,7 +9,8 @@ settings_2nd = { ca_file: File.exist?(ca_file) ? ca_file : nil, - alpn: ['http/1.1'] + alpn: ['http/1.1'], + sslkeylogfile: '/tmp/sslkeylogfile.log' } process_new_session_ticket = lambda do |nst, rms, cs| return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime @@ -24,7 +25,8 @@ settings_1st = { ca_file: File.exist?(ca_file) ? ca_file : nil, alpn: ['http/1.1'], - process_new_session_ticket: process_new_session_ticket + process_new_session_ticket: process_new_session_ticket, + sslkeylogfile: '/tmp/sslkeylogfile.log' } succeed_early_data = false diff --git a/example/https_client_using_hrr.rb b/example/https_client_using_hrr.rb index b259656..2b86951 100644 --- a/example/https_client_using_hrr.rb +++ b/example/https_client_using_hrr.rb @@ -11,7 +11,8 @@ settings = { ca_file: File.exist?(ca_file) ? ca_file : nil, key_share_groups: [], # empty KeyShareClientHello.client_shares - alpn: ['http/1.1'] + alpn: ['http/1.1'], + sslkeylogfile: '/tmp/sslkeylogfile.log' } client = TTTLS13::Client.new(socket, uri.host, **settings) client.connect diff --git a/example/https_client_using_hrr_and_ticket.rb b/example/https_client_using_hrr_and_ticket.rb index 93801da..6d8fe57 100644 --- a/example/https_client_using_hrr_and_ticket.rb +++ b/example/https_client_using_hrr_and_ticket.rb @@ -9,7 +9,8 @@ settings_2nd = { ca_file: File.exist?(ca_file) ? ca_file : nil, - alpn: ['http/1.1'] + alpn: ['http/1.1'], + sslkeylogfile: '/tmp/sslkeylogfile.log' } process_new_session_ticket = lambda do |nst, rms, cs| return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime @@ -25,7 +26,8 @@ settings_1st = { ca_file: File.exist?(ca_file) ? ca_file : nil, alpn: ['http/1.1'], - process_new_session_ticket: process_new_session_ticket + process_new_session_ticket: process_new_session_ticket, + sslkeylogfile: '/tmp/sslkeylogfile.log' } [ diff --git a/example/https_client_using_status_request.rb b/example/https_client_using_status_request.rb index a54d4f0..f19b9d7 100644 --- a/example/https_client_using_status_request.rb +++ b/example/https_client_using_status_request.rb @@ -19,7 +19,8 @@ ca_file: File.exist?(ca_file) ? ca_file : nil, alpn: ['http/1.1'], check_certificate_status: true, - process_certificate_status: process_certificate_status + process_certificate_status: process_certificate_status, + sslkeylogfile: '/tmp/sslkeylogfile.log' } client = TTTLS13::Client.new(socket, uri.host, **settings) client.connect diff --git a/example/https_client_using_ticket.rb b/example/https_client_using_ticket.rb index 96a387b..1509a3b 100644 --- a/example/https_client_using_ticket.rb +++ b/example/https_client_using_ticket.rb @@ -9,7 +9,8 @@ settings_2nd = { ca_file: File.exist?(ca_file) ? ca_file : nil, - alpn: ['http/1.1'] + alpn: ['http/1.1'], + sslkeylogfile: '/tmp/sslkeylogfile.log' } process_new_session_ticket = lambda do |nst, rms, cs| return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime @@ -24,7 +25,8 @@ settings_1st = { ca_file: File.exist?(ca_file) ? ca_file : nil, alpn: ['http/1.1'], - process_new_session_ticket: process_new_session_ticket + process_new_session_ticket: process_new_session_ticket, + sslkeylogfile: '/tmp/sslkeylogfile.log' } [ diff --git a/example/https_client_using_ticket_and_ech.rb b/example/https_client_using_ticket_and_ech.rb index c5aa347..8a0de73 100644 --- a/example/https_client_using_ticket_and_ech.rb +++ b/example/https_client_using_ticket_and_ech.rb @@ -21,7 +21,8 @@ alpn: ['http/1.1'], ech_config: ech_config, ech_hpke_cipher_suites: - TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES + TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES, + sslkeylogfile: '/tmp/sslkeylogfile.log' } process_new_session_ticket = lambda do |nst, rms, cs| return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime @@ -39,7 +40,8 @@ ech_config: ech_config, ech_hpke_cipher_suites: TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES, - process_new_session_ticket: process_new_session_ticket + process_new_session_ticket: process_new_session_ticket, + sslkeylogfile: '/tmp/sslkeylogfile.log' } [ From 5cce2726c1a00e00f1557d01f82e5c83cbf70413 Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Wed, 17 Apr 2024 03:35:08 +0900 Subject: [PATCH 07/15] supprt multiple OuterExtensions --- lib/tttls1.3/ech.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/tttls1.3/ech.rb b/lib/tttls1.3/ech.rb index 0e63999..c010336 100644 --- a/lib/tttls1.3/ech.rb +++ b/lib/tttls1.3/ech.rb @@ -318,13 +318,21 @@ def self.new_greased_ch(inner, ech) # # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner def self.remove_and_replace_exs(exs, ex_types) + # NOTE: sort external_extensions in descending order. + tmp1 = exs.filter { |k, _| !ex_types.include?(k) } + tmp2 = exs.filter { |k, _| ex_types.include?(k) }.sort + exs.clear + tmp1.each { |k, v| exs[k] = v } + tmp2.each { |k, v| exs[k] = v } + # removing and replacing extensions from EncodedClientHelloInner # with a single "ech_outer_extensions" exs.reduce(Message::Extensions.new) do |acc, (k, v)| if ex_types.include?(k) && !acc.include?(Message::ExtensionType::ECH_OUTER_EXTENSIONS) + outer_extensions = (ex_types - (ex_types - exs.keys)).sort acc[Message::ExtensionType::ECH_OUTER_EXTENSIONS] = \ - Message::Extension::ECHOuterExtensions.new(ex_types) + Message::Extension::ECHOuterExtensions.new(outer_extensions) elsif !ex_types.include?(k) acc[k] = v end From a13b265f59ed4f61e92a7ed7bd33f3f0f781d514 Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Wed, 17 Apr 2024 05:01:02 +0900 Subject: [PATCH 08/15] refactor: using Refinements; remove_and_replace_exs -> remove_and_replace! --- lib/tttls1.3/ech.rb | 67 +++++++++++++++++++++++---------------------- spec/ech_spec.rb | 45 ++++++++++++++---------------- 2 files changed, 55 insertions(+), 57 deletions(-) diff --git a/lib/tttls1.3/ech.rb b/lib/tttls1.3/ech.rb index c010336..f760dac 100644 --- a/lib/tttls1.3/ech.rb +++ b/lib/tttls1.3/ech.rb @@ -2,6 +2,37 @@ # frozen_string_literal: true module TTTLS13 + module Refinements + refine TTTLS13::Message::Extensions do + # @param ex_types [Array of TTTLS13::Message::ExtensionType] + # + # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner + define_method(:remove_and_replace!) do |ex_types| + # NOTE: sort external_extensions in descending order. + tmp1 = filter { |k, _| !ex_types.include?(k) } + tmp2 = filter { |k, _| ex_types.include?(k) }.sort + clear + tmp1.each { |k, v| self[k] = v } + tmp2.each { |k, v| self[k] = v } + + # removing and replacing extensions from EncodedClientHelloInner + # with a single "ech_outer_extensions" + reduce(Message::Extensions.new) do |acc, (k, v)| + if ex_types.include?(k) && + !acc.include?(Message::ExtensionType::ECH_OUTER_EXTENSIONS) + outer_extensions = (ex_types - (ex_types - keys)).sort + acc[Message::ExtensionType::ECH_OUTER_EXTENSIONS] = \ + Message::Extension::ECHOuterExtensions.new(outer_extensions) + elsif !ex_types.include?(k) + acc[k] = v + end + + acc + end + end + end + end + using Refinements SUPPORTED_ECHCONFIG_VERSIONS = ["\xfe\x0d"].freeze @@ -37,7 +68,7 @@ def self.offer_ech(inner, ech_config, hpke_cipher_suite_selector) # for ech_outer_extensions replaced = \ - remove_and_replace_exs(inner.extensions, DEFAULT_ECH_OUTER_EXTENSIONS) + inner.extensions.remove_and_replace!(DEFAULT_ECH_OUTER_EXTENSIONS) # Encoding the ClientHelloInner encoded = encode_ch_inner(inner, ech_state.maximum_name_length, replaced) @@ -114,12 +145,12 @@ def self.encrypted_ech_config(ech_config, hpke_cipher_suite_selector) def self.offer_new_ech(inner, ech_state) # for ech_outer_extensions replaced = \ - remove_and_replace_exs(inner.extensions, DEFAULT_ECH_OUTER_EXTENSIONS) + inner.extensions.remove_and_replace!(DEFAULT_ECH_OUTER_EXTENSIONS) # Encoding the ClientHelloInner encoded = encode_ch_inner(inner, ech_state.maximum_name_length, replaced) - overhead_len \ - = aead_id2overhead_len(ech_state.cipher_suite.aead_id.uint16) + overhead_len = \ + aead_id2overhead_len(ech_state.cipher_suite.aead_id.uint16) # It encrypts EncodedClientHelloInner as described in Section 6.1.1, using # the second partial ClientHelloOuterAAD, to obtain a second @@ -313,34 +344,6 @@ def self.new_greased_ch(inner, ech) ) end - # @param exs [TTTLS13::Message::Extensions] from ClientHelloInner - # @param ex_types [Array of TTTLS13::Message::ExtensionType] - # - # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner - def self.remove_and_replace_exs(exs, ex_types) - # NOTE: sort external_extensions in descending order. - tmp1 = exs.filter { |k, _| !ex_types.include?(k) } - tmp2 = exs.filter { |k, _| ex_types.include?(k) }.sort - exs.clear - tmp1.each { |k, v| exs[k] = v } - tmp2.each { |k, v| exs[k] = v } - - # removing and replacing extensions from EncodedClientHelloInner - # with a single "ech_outer_extensions" - exs.reduce(Message::Extensions.new) do |acc, (k, v)| - if ex_types.include?(k) && - !acc.include?(Message::ExtensionType::ECH_OUTER_EXTENSIONS) - outer_extensions = (ex_types - (ex_types - exs.keys)).sort - acc[Message::ExtensionType::ECH_OUTER_EXTENSIONS] = \ - Message::Extension::ECHOuterExtensions.new(outer_extensions) - elsif !ex_types.include?(k) - acc[k] = v - end - - acc - end - end - module KemId # https://www.iana.org/assignments/hpke/hpke.xhtml#hpke-kem-ids P_256_SHA256 = 0x0010 diff --git a/spec/ech_spec.rb b/spec/ech_spec.rb index 85b22a5..23b159f 100644 --- a/spec/ech_spec.rb +++ b/spec/ech_spec.rb @@ -127,37 +127,32 @@ end let(:no_key_share_exs) do - extensions.filter { |k, _| k != ExtensionType::KEY_SHARE } - end - - it 'should be equal remove_and_replace_exs with extensions & []' do - expect(Ech.remove_and_replace_exs(extensions, [])) - .to eq extensions - end - - it 'should be equal remove_and_replace_exs with extensions & [key_share]' do - got = Ech.remove_and_replace_exs(extensions, [ExtensionType::KEY_SHARE]) - expected = extensions.map do |k, v| - if k == ExtensionType::KEY_SHARE - [ - ExtensionType::ECH_OUTER_EXTENSIONS, - Extension::ECHOuterExtensions.new([ExtensionType::KEY_SHARE]) - ] - else - [k, v] - end - end - expected = expected.to_h + Extensions.new( + extensions.filter { |k, _| k != ExtensionType::KEY_SHARE }.values + ) + end + + it 'should be equal remove_and_replace! with []' do + cloned = extensions.clone + expect(extensions.remove_and_replace!([])) + .to eq cloned + end + + it 'should be equal remove_and_replace! with [key_share]' do + expected = extensions.filter { |k, _| k != ExtensionType::KEY_SHARE } + expected[ExtensionType::ECH_OUTER_EXTENSIONS] = \ + Extension::ECHOuterExtensions.new([ExtensionType::KEY_SHARE]) + got = extensions.remove_and_replace!([ExtensionType::KEY_SHARE]) expect(got.keys).to eq expected.keys expect(got[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions) .to eq expected[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions end - it 'should be equal remove_and_replace_exs with no key_share extensions' \ + it 'should be equal remove_and_replace! with no key_share extensions' \ ' & [key_share]' do - expect(Ech.remove_and_replace_exs(no_key_share_exs, - [ExtensionType::KEY_SHARE])) - .to eq no_key_share_exs + cloned = no_key_share_exs.clone + expect(no_key_share_exs.remove_and_replace!([ExtensionType::KEY_SHARE])) + .to eq cloned end end end From 99bc3955fcafa1327cea554ea21fad7af1597447 Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Thu, 18 Apr 2024 03:30:02 +0900 Subject: [PATCH 09/15] mv remove_and_replace! --- lib/tttls1.3/ech.rb | 31 ------------------------------ lib/tttls1.3/message/extensions.rb | 27 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/lib/tttls1.3/ech.rb b/lib/tttls1.3/ech.rb index f760dac..f4ef297 100644 --- a/lib/tttls1.3/ech.rb +++ b/lib/tttls1.3/ech.rb @@ -2,37 +2,6 @@ # frozen_string_literal: true module TTTLS13 - module Refinements - refine TTTLS13::Message::Extensions do - # @param ex_types [Array of TTTLS13::Message::ExtensionType] - # - # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner - define_method(:remove_and_replace!) do |ex_types| - # NOTE: sort external_extensions in descending order. - tmp1 = filter { |k, _| !ex_types.include?(k) } - tmp2 = filter { |k, _| ex_types.include?(k) }.sort - clear - tmp1.each { |k, v| self[k] = v } - tmp2.each { |k, v| self[k] = v } - - # removing and replacing extensions from EncodedClientHelloInner - # with a single "ech_outer_extensions" - reduce(Message::Extensions.new) do |acc, (k, v)| - if ex_types.include?(k) && - !acc.include?(Message::ExtensionType::ECH_OUTER_EXTENSIONS) - outer_extensions = (ex_types - (ex_types - keys)).sort - acc[Message::ExtensionType::ECH_OUTER_EXTENSIONS] = \ - Message::Extension::ECHOuterExtensions.new(outer_extensions) - elsif !ex_types.include?(k) - acc[k] = v - end - - acc - end - end - end - end - using Refinements SUPPORTED_ECHCONFIG_VERSIONS = ["\xfe\x0d"].freeze diff --git a/lib/tttls1.3/message/extensions.rb b/lib/tttls1.3/message/extensions.rb index d00007a..6ecf2bb 100644 --- a/lib/tttls1.3/message/extensions.rb +++ b/lib/tttls1.3/message/extensions.rb @@ -105,6 +105,33 @@ def <<(ex) store(ex.extension_type, ex) end + # @param ex_types [Array of TTTLS13::Message::ExtensionType] + # + # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner + def remove_and_replace!(ex_types) + # NOTE: sort external_extensions in descending order. + tmp1 = filter { |k, _| !ex_types.include?(k) } + tmp2 = filter { |k, _| ex_types.include?(k) }.sort + clear + tmp1.each { |k, v| self[k] = v } + tmp2.each { |k, v| self[k] = v } + + # removing and replacing extensions from EncodedClientHelloInner + # with a single "ech_outer_extensions" + reduce(Message::Extensions.new) do |acc, (k, v)| + if ex_types.include?(k) && + !acc.include?(Message::ExtensionType::ECH_OUTER_EXTENSIONS) + outer_extensions = (ex_types - (ex_types - keys)).sort + acc[Message::ExtensionType::ECH_OUTER_EXTENSIONS] = \ + Message::Extension::ECHOuterExtensions.new(outer_extensions) + elsif !ex_types.include?(k) + acc[k] = v + end + + acc + end + end + class << self private From 0d5b37e83857d9c341832ce92f51695ab3308515 Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Thu, 18 Apr 2024 03:34:08 +0900 Subject: [PATCH 10/15] refactor remove_and_replace! without sort --- lib/tttls1.3/message/extensions.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/tttls1.3/message/extensions.rb b/lib/tttls1.3/message/extensions.rb index 6ecf2bb..3ceb126 100644 --- a/lib/tttls1.3/message/extensions.rb +++ b/lib/tttls1.3/message/extensions.rb @@ -109,9 +109,8 @@ def <<(ex) # # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner def remove_and_replace!(ex_types) - # NOTE: sort external_extensions in descending order. tmp1 = filter { |k, _| !ex_types.include?(k) } - tmp2 = filter { |k, _| ex_types.include?(k) }.sort + tmp2 = filter { |k, _| ex_types.include?(k) } clear tmp1.each { |k, v| self[k] = v } tmp2.each { |k, v| self[k] = v } @@ -121,9 +120,8 @@ def remove_and_replace!(ex_types) reduce(Message::Extensions.new) do |acc, (k, v)| if ex_types.include?(k) && !acc.include?(Message::ExtensionType::ECH_OUTER_EXTENSIONS) - outer_extensions = (ex_types - (ex_types - keys)).sort acc[Message::ExtensionType::ECH_OUTER_EXTENSIONS] = \ - Message::Extension::ECHOuterExtensions.new(outer_extensions) + Message::Extension::ECHOuterExtensions.new(tmp2.keys) elsif !ex_types.include?(k) acc[k] = v end From 23d3ce87a94a8ea617059c14ff14cbd38261305c Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Thu, 18 Apr 2024 03:41:10 +0900 Subject: [PATCH 11/15] rename ex_types -> outer_extensions --- lib/tttls1.3/message/extensions.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/tttls1.3/message/extensions.rb b/lib/tttls1.3/message/extensions.rb index 3ceb126..e07d950 100644 --- a/lib/tttls1.3/message/extensions.rb +++ b/lib/tttls1.3/message/extensions.rb @@ -105,12 +105,12 @@ def <<(ex) store(ex.extension_type, ex) end - # @param ex_types [Array of TTTLS13::Message::ExtensionType] + # @param outer_extensions [Array of TTTLS13::Message::ExtensionType] # # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner - def remove_and_replace!(ex_types) - tmp1 = filter { |k, _| !ex_types.include?(k) } - tmp2 = filter { |k, _| ex_types.include?(k) } + def remove_and_replace!(outer_extensions) + tmp1 = filter { |k, _| !outer_extensions.include?(k) } + tmp2 = filter { |k, _| outer_extensions.include?(k) } clear tmp1.each { |k, v| self[k] = v } tmp2.each { |k, v| self[k] = v } @@ -118,11 +118,11 @@ def remove_and_replace!(ex_types) # removing and replacing extensions from EncodedClientHelloInner # with a single "ech_outer_extensions" reduce(Message::Extensions.new) do |acc, (k, v)| - if ex_types.include?(k) && + if outer_extensions.include?(k) && !acc.include?(Message::ExtensionType::ECH_OUTER_EXTENSIONS) acc[Message::ExtensionType::ECH_OUTER_EXTENSIONS] = \ Message::Extension::ECHOuterExtensions.new(tmp2.keys) - elsif !ex_types.include?(k) + elsif !outer_extensions.include?(k) acc[k] = v end From 62e3e161f9e38f281ec19020b586b38ec126a3cd Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Thu, 18 Apr 2024 04:07:02 +0900 Subject: [PATCH 12/15] refactor: without reduce --- .rubocop.yml | 6 +++--- lib/tttls1.3/message/extensions.rb | 19 +++++++------------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 4d9112d..822b30a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,15 +4,15 @@ AllCops: Gemspec/RequiredRubyVersion: Enabled: false +Semicolon: + AllowAsExpressionSeparator: true + Style/ConditionalAssignment: Enabled: false Style/Documentation: Enabled: false -Style/EachWithObject: - Enabled: false - Style/NumericLiterals: Enabled: false diff --git a/lib/tttls1.3/message/extensions.rb b/lib/tttls1.3/message/extensions.rb index e07d950..4c46b6c 100644 --- a/lib/tttls1.3/message/extensions.rb +++ b/lib/tttls1.3/message/extensions.rb @@ -111,23 +111,18 @@ def <<(ex) def remove_and_replace!(outer_extensions) tmp1 = filter { |k, _| !outer_extensions.include?(k) } tmp2 = filter { |k, _| outer_extensions.include?(k) } + clear - tmp1.each { |k, v| self[k] = v } - tmp2.each { |k, v| self[k] = v } + replaced = Message::Extensions.new + tmp1.each { |_, v| self << v; replaced << v } + tmp2.each { |_, v| self << v } # removing and replacing extensions from EncodedClientHelloInner # with a single "ech_outer_extensions" - reduce(Message::Extensions.new) do |acc, (k, v)| - if outer_extensions.include?(k) && - !acc.include?(Message::ExtensionType::ECH_OUTER_EXTENSIONS) - acc[Message::ExtensionType::ECH_OUTER_EXTENSIONS] = \ - Message::Extension::ECHOuterExtensions.new(tmp2.keys) - elsif !outer_extensions.include?(k) - acc[k] = v - end + replaced << Message::Extension::ECHOuterExtensions.new(tmp2.keys) \ + unless tmp2.keys.empty? - acc - end + replaced end class << self From 37e65aad961fb9d054963ae040d2402c64cc5ee4 Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Thu, 18 Apr 2024 04:14:17 +0900 Subject: [PATCH 13/15] docs: comments --- lib/tttls1.3/message/extensions.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/tttls1.3/message/extensions.rb b/lib/tttls1.3/message/extensions.rb index 4c46b6c..53f6ddb 100644 --- a/lib/tttls1.3/message/extensions.rb +++ b/lib/tttls1.3/message/extensions.rb @@ -105,6 +105,16 @@ def <<(ex) store(ex.extension_type, ex) end + # removing and replacing extensions from EncodedClientHelloInner + # with a single "ech_outer_extensions" + # + # for example + # - before + # - self.keys: [A B C D E] + # - param : [D B] + # - after remove_and_replace! + # - self.keys: [A C E B D] + # - return : [A C E ech_outer_extensions[B D]] # @param outer_extensions [Array of TTTLS13::Message::ExtensionType] # # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner @@ -117,11 +127,8 @@ def remove_and_replace!(outer_extensions) tmp1.each { |_, v| self << v; replaced << v } tmp2.each { |_, v| self << v } - # removing and replacing extensions from EncodedClientHelloInner - # with a single "ech_outer_extensions" replaced << Message::Extension::ECHOuterExtensions.new(tmp2.keys) \ unless tmp2.keys.empty? - replaced end From 5fb4478e47853da68c432adf3861187bdd432d1f Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Thu, 18 Apr 2024 04:32:58 +0900 Subject: [PATCH 14/15] add resolve_echconfig to example/helper.rb --- example/helper.rb | 11 +++++++++++ example/https_client_using_ech.rb | 6 +----- example/https_client_using_hrr_and_ech.rb | 6 +----- example/https_client_using_ticket_and_ech.rb | 6 +----- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/example/helper.rb b/example/helper.rb index 6f9e4be..eec9c07 100644 --- a/example/helper.rb +++ b/example/helper.rb @@ -94,3 +94,14 @@ def parse_echconfigs_pem(pem) ECHConfig.decode_vectors(b.slice(2..)) end + +def resolve_echconfig(hostname) + rr = Resolv::DNS.new.getresources( + hostname, + Resolv::DNS::Resource::IN::HTTPS + ) + raise "failed to resolve echconfig via #{hostname} HTTPS RR" \ + if rr.first.nil? || !rr.first.svc_params.keys.include?('ech') + + rr.first.svc_params['ech'].echconfiglist.first +end diff --git a/example/https_client_using_ech.rb b/example/https_client_using_ech.rb index 23e3421..c03e66c 100644 --- a/example/https_client_using_ech.rb +++ b/example/https_client_using_ech.rb @@ -9,11 +9,7 @@ ech_config = if ARGV.length > 1 parse_echconfigs_pem(File.open(ARGV[1]).read).first else - rr = Resolv::DNS.new.getresources( - uri.host, - Resolv::DNS::Resource::IN::HTTPS - ) - rr.first.svc_params['ech'].echconfiglist.first + resolve_echconfig(uri.host) end socket = TCPSocket.new(uri.host, uri.port) diff --git a/example/https_client_using_hrr_and_ech.rb b/example/https_client_using_hrr_and_ech.rb index 97404ce..e33f225 100644 --- a/example/https_client_using_hrr_and_ech.rb +++ b/example/https_client_using_hrr_and_ech.rb @@ -9,11 +9,7 @@ ech_config = if ARGV.length > 1 parse_echconfigs_pem(File.open(ARGV[1]).read).first else - rr = Resolv::DNS.new.getresources( - uri.host, - Resolv::DNS::Resource::IN::HTTPS - ) - rr.first.svc_params['ech'].echconfiglist.first + resolve_echconfig(uri.host) end socket = TCPSocket.new(uri.host, uri.port) diff --git a/example/https_client_using_ticket_and_ech.rb b/example/https_client_using_ticket_and_ech.rb index 8a0de73..09d35b0 100644 --- a/example/https_client_using_ticket_and_ech.rb +++ b/example/https_client_using_ticket_and_ech.rb @@ -9,11 +9,7 @@ ech_config = if ARGV.length > 1 parse_echconfigs_pem(File.open(ARGV[1]).read).first else - rr = Resolv::DNS.new.getresources( - uri.host, - Resolv::DNS::Resource::IN::HTTPS - ) - rr.first.svc_params['ech'].echconfiglist.first + resolve_echconfig(uri.host) end settings_2nd = { From 44b567cc809122dd94785a93322fd527e800778b Mon Sep 17 00:00:00 2001 From: thekuwayama Date: Thu, 18 Apr 2024 04:38:01 +0900 Subject: [PATCH 15/15] refactor: mv Extensions remove_and_replace! testcase refactor: using each_value --- lib/tttls1.3/message/extensions.rb | 5 +++-- spec/ech_spec.rb | 36 ------------------------------ spec/extensions_spec.rb | 36 ++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/lib/tttls1.3/message/extensions.rb b/lib/tttls1.3/message/extensions.rb index 53f6ddb..3d45734 100644 --- a/lib/tttls1.3/message/extensions.rb +++ b/lib/tttls1.3/message/extensions.rb @@ -124,11 +124,12 @@ def remove_and_replace!(outer_extensions) clear replaced = Message::Extensions.new - tmp1.each { |_, v| self << v; replaced << v } - tmp2.each { |_, v| self << v } + tmp1.each_value { |v| self << v; replaced << v } + tmp2.each_value { |v| self << v } replaced << Message::Extension::ECHOuterExtensions.new(tmp2.keys) \ unless tmp2.keys.empty? + replaced end diff --git a/spec/ech_spec.rb b/spec/ech_spec.rb index 23b159f..69a89d1 100644 --- a/spec/ech_spec.rb +++ b/spec/ech_spec.rb @@ -119,40 +119,4 @@ .to eq padding_encoded_ch_inner.length end end - - context 'removing and replacing extensions from EncodedClientHelloInner' do - let(:extensions) do - extensions, = Client.new(nil, 'localhost').send(:gen_ch_extensions) - extensions - end - - let(:no_key_share_exs) do - Extensions.new( - extensions.filter { |k, _| k != ExtensionType::KEY_SHARE }.values - ) - end - - it 'should be equal remove_and_replace! with []' do - cloned = extensions.clone - expect(extensions.remove_and_replace!([])) - .to eq cloned - end - - it 'should be equal remove_and_replace! with [key_share]' do - expected = extensions.filter { |k, _| k != ExtensionType::KEY_SHARE } - expected[ExtensionType::ECH_OUTER_EXTENSIONS] = \ - Extension::ECHOuterExtensions.new([ExtensionType::KEY_SHARE]) - got = extensions.remove_and_replace!([ExtensionType::KEY_SHARE]) - expect(got.keys).to eq expected.keys - expect(got[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions) - .to eq expected[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions - end - - it 'should be equal remove_and_replace! with no key_share extensions' \ - ' & [key_share]' do - cloned = no_key_share_exs.clone - expect(no_key_share_exs.remove_and_replace!([ExtensionType::KEY_SHARE])) - .to eq cloned - end - end end diff --git a/spec/extensions_spec.rb b/spec/extensions_spec.rb index b0b9ded..96fa546 100644 --- a/spec/extensions_spec.rb +++ b/spec/extensions_spec.rb @@ -182,4 +182,40 @@ .to raise_error(ErrorAlerts) end end + + context 'removing and replacing extensions from EncodedClientHelloInner' do + let(:extensions) do + extensions, = Client.new(nil, 'localhost').send(:gen_ch_extensions) + extensions + end + + let(:no_key_share_exs) do + Extensions.new( + extensions.filter { |k, _| k != ExtensionType::KEY_SHARE }.values + ) + end + + it 'should be equal remove_and_replace! with []' do + cloned = extensions.clone + expect(extensions.remove_and_replace!([])) + .to eq cloned + end + + it 'should be equal remove_and_replace! with [key_share]' do + expected = extensions.filter { |k, _| k != ExtensionType::KEY_SHARE } + expected[ExtensionType::ECH_OUTER_EXTENSIONS] = \ + Extension::ECHOuterExtensions.new([ExtensionType::KEY_SHARE]) + got = extensions.remove_and_replace!([ExtensionType::KEY_SHARE]) + expect(got.keys).to eq expected.keys + expect(got[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions) + .to eq expected[ExtensionType::ECH_OUTER_EXTENSIONS].outer_extensions + end + + it 'should be equal remove_and_replace! with no key_share extensions' \ + ' & [key_share]' do + cloned = no_key_share_exs.clone + expect(no_key_share_exs.remove_and_replace!([ExtensionType::KEY_SHARE])) + .to eq cloned + end + end end