echspec 0.0.6 → 0.0.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dff1c822b0e5bef3e79cfbf5d1eb200f8312fb8fd0e0582e34d152dafdb8d810
4
- data.tar.gz: 8082a87b962ee5ea9b05589558e4704919c63d69fc69bf4f3e35379164429d80
3
+ metadata.gz: 3902d0c840b18babccf1c9e7c764c570fd6b29e6f9688e0f9f0c5696bae4e871
4
+ data.tar.gz: c01a13d625bc07918e8f80db55266019ff3031c597e30e9be0dbd4515755bcb5
5
5
  SHA512:
6
- metadata.gz: 37c0cfc5bc91f0be87099720490b2870921998f377d88b862152275586492670d4eb26c5ae6023a32345367b608830b4f9005b30a6df7b1fe7aea968769e0177
7
- data.tar.gz: c6cf6cbbe9a0607117a173f5d921201a38d18986e37217665d51c2ff1f004ab1784bf6f9b01ac9263f686bf2192e9099b8a9df947ebf6622f3201585c035b255
6
+ metadata.gz: 22d2c6085da85d18cbbf3d5e52f7f4f8511f2d878da9642ce1ad53817e4a33d8429c132456305592dd37e547c53998aa2db0bc091df276b4a216f4b2283b9492
7
+ data.tar.gz: 9fa6ac8c64727d9e64418b7bcd3e38a98dc693b59ff52f6c7a42b4c7dc7635420d5dc60ef7f11ecf9837cd8d85a0fe7b5fd59987d81f57539ded979f330c4938
data/README.md CHANGED
@@ -26,8 +26,11 @@ $ gem install echspec
26
26
  $ echspec --help
27
27
  Usage: echspec {SUBCOMMAND}
28
28
 
29
- Available subcommands: run, gen_configs, version, help.
29
+ Available subcommands: run, gen_configs, resolve, grease, version, help.
30
30
  ```
31
+
32
+ ### run
33
+
31
34
  ```sh-session
32
35
  $ echspec run --help
33
36
  Usage: echspec run [OPTIONS...] {HOSTNAME}
@@ -75,7 +78,7 @@ Failures:
75
78
  1 failure
76
79
  ```
77
80
 
78
- By default, `echspec` retrieves ECHConfigs via DNS HTTPS records. You can specify a local PEM file using the `-f, --file FILE` option. To test a server on localhost:
81
+ By default, `echspec` retrieves ECHConfigs via DNS HTTPS resource records. You can specify a local PEM file using the `-f, --file FILE` option. To test a server on localhost:
79
82
 
80
83
  ```sh-session
81
84
  $ echspec run -f fixtures/echconfigs.pem -p 4433 localhost
@@ -282,6 +285,8 @@ $ echspec run -s 7-5 -v research.cloudflare.com 2>&1 > /dev/null | jq .
282
285
 
283
286
  </details>
284
287
 
288
+ ### gen_configs
289
+
285
290
  You can generate an ECHConfig PEM file the following:
286
291
 
287
292
  ```sh-session
@@ -297,6 +302,82 @@ AQALZXhhbXBsZS5jb20AAA==
297
302
  -----END ECHCONFIG-----
298
303
  ```
299
304
 
305
+ ### resolve
306
+
307
+ You can resolve ECHConfigs for a hostname and print fields using the `resolve` subcommand.
308
+ With `-w`, ECHConfigs are resolved from the well-known URI (`https://{HOSTNAME}/.well-known/origin-svcb`) defined in [draft-ietf-tls-wkech](https://datatracker.ietf.org/doc/html/draft-ietf-tls-wkech-12), instead of DNS HTTPS resource records:
309
+
310
+ ```sh-session
311
+ $ echspec resolve --help
312
+ Usage: echspec resolve [OPTIONS...] [{HOSTNAME}]
313
+
314
+ Resolve ECHConfigs for a hostname and print fields.
315
+ {HOSTNAME} is required unless -f is specified.
316
+
317
+ Examples:
318
+
319
+ $ echspec resolve localhost
320
+ $ echspec resolve -w localhost
321
+ $ echspec resolve -f echconfigs.pem
322
+
323
+ Options:
324
+ -f, --file FILE path to ECHConfigs PEM file (default resolve ECHConfigs via DNS)
325
+ -w, --well-known resolve ECHConfigs via well-known URI, instead of DNS
326
+ ```
327
+ ```sh-session
328
+ $ echspec resolve research.cloudflare.com
329
+ ECHConfig: │
330
+ version(uint16): │ fe 0d
331
+ length(uint16): │ 65
332
+ contents(ECHConfigContents): │
333
+ key_config(HpkeKeyConfig): │
334
+ config_id(uint8): │ 104
335
+ kem_id(uint16): │ 00 20
336
+ public_key(opaque): │ e8 49 25 b3 6c e3 fb 9c 72 c7 71 e2 7a b2 8c 65 84 ba c2 2a 90 c2 cb 59 23 b2 27 d8 c1 85 84 17
337
+ cipher_suites(HpkeSymmetricCipherSuite): │
338
+ kdf_id(uint16): │ 00 01
339
+ aead_id(uint16): │ 00 01
340
+ maximum_name_length(uint8): │ 0
341
+ public_name(opaque): │ cloudflare-ech.com
342
+ extensions(opaque): │
343
+ ```
344
+
345
+ ### grease
346
+
347
+ You can send a GREASE ECH to a server to trigger `retry_configs` and print the retry ECHConfigs fields using the `grease` subcommand:
348
+
349
+ ```sh-session
350
+ $ echspec grease --help
351
+ Usage: echspec grease [OPTIONS...] {HOSTNAME}
352
+
353
+ Send GREASE ECH to a server and display retry_configs.
354
+
355
+ Examples:
356
+
357
+ $ echspec grease localhost
358
+ $ echspec grease -p 4433 localhost
359
+
360
+ Options:
361
+ -p, --port VALUE server port number (default 443)
362
+ ```
363
+ ```sh-session
364
+ $ echspec grease research.cloudflare.com
365
+ ECHConfig: │
366
+ version(uint16): │ fe 0d
367
+ length(uint16): │ 65
368
+ contents(ECHConfigContents): │
369
+ key_config(HpkeKeyConfig): │
370
+ config_id(uint8): │ 72
371
+ kem_id(uint16): │ 00 20
372
+ public_key(opaque): │ 0a 89 2a 9f 05 0a d5 21 da cc 42 65 df cf 70 38 22 7e 85 44 93 1b 68 fa a2 b5 37 ad d8 2c 89 39
373
+ cipher_suites(HpkeSymmetricCipherSuite): │
374
+ kdf_id(uint16): │ 00 01
375
+ aead_id(uint16): │ 00 01
376
+ maximum_name_length(uint8): │ 0
377
+ public_name(opaque): │ cloudflare-ech.com
378
+ extensions(opaque): │
379
+ ```
380
+
300
381
 
301
382
  ## Note
302
383
 
@@ -0,0 +1,55 @@
1
+ module EchSpec
2
+ class CLI
3
+ module EchConfigPrinter
4
+ def print_ech_configs(ech_configs)
5
+ all_pairs = ech_configs.map { |c| field_pairs(c) }
6
+ label_width, value_width = evaluate_widths(all_pairs)
7
+
8
+ all_pairs.each_with_index do |pairs, i|
9
+ puts "#{'─' * label_width}┼#{'─' * value_width}" if i.positive?
10
+ pairs.each { |label, value| print_field(label, value, label_width) }
11
+ end
12
+ end
13
+
14
+ def evaluate_widths(all_pairs)
15
+ label_width = all_pairs.flat_map { |pairs| pairs.map { |label, _| label.length } }.max + 1
16
+ value_width = all_pairs.flat_map { |pairs| pairs.map { |_, value| value.to_s.length } }.max + 1
17
+ [label_width, value_width]
18
+ end
19
+
20
+ def print_field(label, value, label_width)
21
+ puts "#{label.ljust(label_width)}│ #{value}"
22
+ end
23
+
24
+ # rubocop: disable Metrics/AbcSize
25
+ def field_pairs(c)
26
+ ec = c.echconfig_contents
27
+ kc = ec.key_config
28
+ ext = ec.extensions.octet.unpack1('H*').scan(/.{2}/).join(' ')
29
+ cipher_suite_pairs = kc.cipher_suites.flat_map do |cs|
30
+ [
31
+ [' kdf_id(uint16):', cs.kdf_id.encode.unpack1('H4').scan(/.{2}/).join(' ')],
32
+ [' aead_id(uint16):', cs.aead_id.encode.unpack1('H4').scan(/.{2}/).join(' ')]
33
+ ]
34
+ end
35
+
36
+ [
37
+ ['ECHConfig:', ''],
38
+ [' version(uint16):', c.version.unpack1('H4').scan(/.{2}/).join(' ')],
39
+ [' length(uint16):', ec.encode.length],
40
+ [' contents(ECHConfigContents):', ''],
41
+ [' key_config(HpkeKeyConfig):', ''],
42
+ [' config_id(uint8):', kc.config_id],
43
+ [' kem_id(uint16):', kc.kem_id.encode.unpack1('H4').scan(/.{2}/).join(' ')],
44
+ [' public_key(opaque):', kc.public_key.opaque.unpack1('H*').scan(/.{2}/).join(' ')],
45
+ [' cipher_suites(HpkeSymmetricCipherSuite):', ''],
46
+ *cipher_suite_pairs,
47
+ [' maximum_name_length(uint8):', ec.maximum_name_length],
48
+ [' public_name(opaque):', ec.public_name],
49
+ [' extensions(opaque):', ext]
50
+ ]
51
+ end
52
+ # rubocop: enable Metrics/AbcSize
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,73 @@
1
+ module EchSpec
2
+ class CLI
3
+ class Grease
4
+ include EchConfigPrinter
5
+
6
+ def execute(argv)
7
+ port, hostname = parse_options(argv)
8
+
9
+ TTTLS13::Logging.logger.level = Logger::WARN
10
+ socket = TCPSocket.new(hostname, port)
11
+ recv = TLS13Client.send_ch_with_greased_ech(socket, hostname)
12
+ ex = recv.extensions[TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO]
13
+
14
+ if ex.nil? || ex.retry_configs.nil? || ex.retry_configs.empty?
15
+ warn "** #{hostname}:#{port} did not send retry_configs"
16
+ exit 1
17
+ end
18
+
19
+ print_ech_configs(ex.retry_configs)
20
+ rescue Timeout::Error
21
+ warn "** #{hostname}:#{port} connection timeout"
22
+ exit 1
23
+ rescue Errno::ECONNREFUSED
24
+ warn "** #{hostname}:#{port} connection refused"
25
+ exit 1
26
+ ensure
27
+ socket&.close
28
+ end
29
+
30
+ def parse_options(argv)
31
+ op = OptionParser.new
32
+ port = 443
33
+
34
+ op.on(
35
+ '-p',
36
+ '--port VALUE',
37
+ "server port number (default #{port})"
38
+ ) do |v|
39
+ port = v
40
+ end
41
+
42
+ op.banner = <<~USAGE
43
+ Usage: echspec grease [OPTIONS...] {HOSTNAME}
44
+
45
+ Send GREASE ECH to a server and display retry_configs.
46
+
47
+ Examples:
48
+
49
+ $ echspec grease localhost
50
+ $ echspec grease -p 4433 localhost
51
+
52
+ Options:
53
+ USAGE
54
+
55
+ begin
56
+ args = op.parse(argv)
57
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
58
+ warn op
59
+ warn "** #{e.message}"
60
+ exit 1
61
+ end
62
+
63
+ if args.length != 1
64
+ warn op
65
+ warn '** {HOSTNAME} argument is not specified'
66
+ exit 1
67
+ end
68
+
69
+ [port, args[0]]
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,86 @@
1
+ module EchSpec
2
+ class CLI
3
+ class Resolve
4
+ include EchConfigPrinter
5
+ def execute(argv)
6
+ fpath, well_known, hostname = parse_options(argv)
7
+
8
+ result = if !fpath.nil?
9
+ Spec::Spec9.parse_pem(File.read(fpath))
10
+ elsif well_known
11
+ Spec::Spec9.well_known_origin_svcb(hostname)
12
+ else
13
+ Spec::Spec9.resolve_ech_configs(hostname)
14
+ end
15
+
16
+ case result
17
+ in Ok(ech_configs)
18
+ print_ech_configs(ech_configs)
19
+ in Err(details, _)
20
+ warn "** #{details}"
21
+ exit 1
22
+ end
23
+ end
24
+
25
+ # rubocop: disable Metrics/MethodLength
26
+ def parse_options(argv)
27
+ op = OptionParser.new
28
+ fpath = nil
29
+ well_known = false
30
+
31
+ op.on(
32
+ '-f',
33
+ '--file FILE',
34
+ 'path to ECHConfigs PEM file (default resolve ECHConfigs via DNS)'
35
+ ) do |v|
36
+ fpath = v
37
+ end
38
+
39
+ op.on(
40
+ '-w',
41
+ '--well-known',
42
+ 'resolve ECHConfigs via well-known URI, instead of DNS'
43
+ ) do
44
+ well_known = true
45
+ end
46
+
47
+ op.banner = <<~USAGE
48
+ Usage: echspec resolve [OPTIONS...] [{HOSTNAME}]
49
+
50
+ Resolve ECHConfigs for a hostname and print fields.
51
+ {HOSTNAME} is required unless -f is specified.
52
+
53
+ Examples:
54
+
55
+ $ echspec resolve localhost
56
+ $ echspec resolve -w localhost
57
+ $ echspec resolve -f echconfigs.pem
58
+
59
+ Options:
60
+ USAGE
61
+
62
+ begin
63
+ args = op.parse(argv)
64
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
65
+ warn op
66
+ warn "** #{e.message}"
67
+ exit 1
68
+ end
69
+
70
+ if !fpath.nil? && !File.exist?(fpath)
71
+ warn '** {FILE} is not found'
72
+ exit 1
73
+ end
74
+
75
+ if fpath.nil? && args.length != 1
76
+ warn op
77
+ warn '** {HOSTNAME} argument is not specified'
78
+ exit 1
79
+ end
80
+
81
+ [fpath, well_known, args[0]]
82
+ end
83
+ # rubocop: enable Metrics/MethodLength
84
+ end
85
+ end
86
+ end
data/lib/echspec/cli.rb CHANGED
@@ -1,4 +1,7 @@
1
+ require_relative 'cli/ech_config_printer'
1
2
  require_relative 'cli/gen_configs'
3
+ require_relative 'cli/grease'
4
+ require_relative 'cli/resolve'
2
5
  require_relative 'cli/run'
3
6
 
4
7
  module EchSpec
@@ -6,7 +9,7 @@ module EchSpec
6
9
  using Refinements
7
10
 
8
11
  def execute(argv = ARGV)
9
- subcommands = %i[run gen_configs]
12
+ subcommands = %i[run gen_configs resolve grease]
10
13
 
11
14
  op = OptionParser.new
12
15
 
@@ -43,7 +43,7 @@ module EchSpec
43
43
  # @return [EchSpec::Ok | Err]
44
44
  def do_validate_ee_retry_configs(hostname, port)
45
45
  with_socket(hostname, port) do |socket|
46
- recv = send_ch_with_greased_ech(socket, hostname)
46
+ recv = TLS13Client.send_ch_with_greased_ech(socket, hostname, @stack)
47
47
  ex = recv.extensions[TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO]
48
48
  return Err.new('did not send expected alert: encrypted_client_hello', message_stack) \
49
49
  unless ex.is_a?(TTTLS13::Message::Extension::ECHEncryptedExtensions)
@@ -53,76 +53,6 @@ module EchSpec
53
53
  Ok.new(nil)
54
54
  end
55
55
  end
56
-
57
- # rubocop: disable Metrics/AbcSize
58
- # rubocop: disable Metrics/MethodLength
59
- def send_ch_with_greased_ech(socket, hostname)
60
- # send ClientHello
61
- conn = TLS13Client::Connection.new(socket, :client)
62
- inner_ech = TTTLS13::Message::Extension::ECHClientHello.new_inner
63
- exs, shared_secret = TLS13Client.gen_ch_extensions(hostname)
64
- inner = TTTLS13::Message::ClientHello.new(
65
- cipher_suites: TTTLS13::CipherSuites.new(
66
- [
67
- TTTLS13::CipherSuite::TLS_AES_256_GCM_SHA384,
68
- TTTLS13::CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
69
- TTTLS13::CipherSuite::TLS_AES_128_GCM_SHA256
70
- ]
71
- ),
72
- extensions: exs.merge(
73
- TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => inner_ech
74
- )
75
- )
76
- @stack << inner
77
-
78
- ch = TTTLS13::Ech.new_greased_ch(inner, TTTLS13::Ech.new_grease_ech)
79
- conn.send_record(
80
- TTTLS13::Message::Record.new(
81
- type: TTTLS13::Message::ContentType::HANDSHAKE,
82
- messages: [ch],
83
- cipher: TTTLS13::Cryptograph::Passer.new
84
- )
85
- )
86
- @stack << ch
87
-
88
- # receive ServerHello
89
- recv, = conn.recv_message(TTTLS13::Cryptograph::Passer.new)
90
- @stack << recv
91
- raise Error::BeforeTargetSituationError, 'not received ServerHello' \
92
- unless recv.is_a?(TTTLS13::Message::ServerHello) && !recv.hrr?
93
-
94
- # receive EncryptedExtensions
95
- transcript = TTTLS13::Transcript.new
96
- transcript[TTTLS13::CH] = [ch, ch.serialize]
97
- sh = recv
98
- transcript[TTTLS13::SH] = [sh, sh.serialize]
99
- kse = sh.extensions[TTTLS13::Message::ExtensionType::KEY_SHARE]
100
- .key_share_entry.first
101
- key_schedule = TTTLS13::KeySchedule.new(
102
- psk: nil,
103
- shared_secret: shared_secret.build(kse.group, kse.key_exchange),
104
- cipher_suite: sh.cipher_suite,
105
- transcript:
106
- )
107
- hs_rcipher = TTTLS13::Endpoint.gen_cipher(
108
- sh.cipher_suite,
109
- key_schedule.server_handshake_write_key,
110
- key_schedule.server_handshake_write_iv
111
- )
112
- recv, = conn.recv_message(hs_rcipher)
113
- @stack << recv
114
- if recv.is_a?(TTTLS13::Message::ChangeCipherSpec)
115
- recv, = conn.recv_message(hs_rcipher)
116
- @stack << recv
117
- end
118
-
119
- raise Error::BeforeTargetSituationError, 'not received EncryptedExtensions' \
120
- unless recv.is_a?(TTTLS13::Message::EncryptedExtensions)
121
-
122
- recv
123
- end
124
- # rubocop: enable Metrics/AbcSize
125
- # rubocop: enable Metrics/MethodLength
126
56
  end
127
57
  end
128
58
  end
@@ -83,6 +83,74 @@ module EchSpec
83
83
  Ok.new(ECHConfig.decode_vectors(octet.slice(2..)))
84
84
  end
85
85
 
86
+ # @param hostname [String]
87
+ #
88
+ # @return [EchSpec::Ok<Array<ECHConfig>> | Err]
89
+ def well_known_origin_svcb(hostname)
90
+ res = fetch_well_known(hostname)
91
+ body = case res
92
+ in Ok(obj)
93
+ obj
94
+ in Err
95
+ return res
96
+ end
97
+
98
+ parse_origin_svcb(body)
99
+ end
100
+
101
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-wkech-12
102
+ WK_ECH_PATH = '/.well-known/origin-svcb'.freeze
103
+ private_constant :WK_ECH_PATH
104
+
105
+ # @param hostname [String]
106
+ #
107
+ # @return [EchSpec::Ok | Err]
108
+ def fetch_well_known(hostname)
109
+ uri = URI("https://#{hostname}#{WK_ECH_PATH}")
110
+ res = Net::HTTP.get_response(uri)
111
+ return Err.new("GET #{uri} returned #{res.code}.", nil) unless res.is_a?(Net::HTTPSuccess)
112
+
113
+ Ok.new(res.body)
114
+ rescue StandardError => e
115
+ Err.new(e.message, nil)
116
+ end
117
+
118
+ # {
119
+ # "regeninterval": 3600,
120
+ # "endpoints": [
121
+ # {
122
+ # "priority": 1,
123
+ # "params": {
124
+ # "ipv4hint": [ "192.0.2.1", "192.0.2.254" ],
125
+ # "ech": "AD7+DQA6NAAgACCn3zNTeX/WOD...AAA==",
126
+ # "ipv6hint": [ "2001:DB::ec4" ],
127
+ # "alpn": [ "h2", "http/1.1" ]
128
+ # }
129
+ # }
130
+ # ]
131
+ # }
132
+ #
133
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-wkech-12#section-5
134
+ #
135
+ # @param body [String]
136
+ #
137
+ # @return [EchSpec::Ok | Err]
138
+ def parse_origin_svcb(body)
139
+ h = JSON.parse(body)
140
+ echs = h['endpoints']&.filter_map { |e| e.dig('params', 'ech') }
141
+ return Err.new('The origin-svcb well-known resource does NOT have ech SvcParams.', nil) if echs.nil? || echs.empty?
142
+
143
+ # if parsing any one of the ech values fails, return Err
144
+ ech_configs = echs.flat_map do |ech|
145
+ octet = Base64.decode64(ech)
146
+ ECHConfig.decode_vectors(octet.slice(2..))
147
+ end
148
+
149
+ Ok.new(ech_configs)
150
+ rescue StandardError => e
151
+ Err.new(e.message, nil)
152
+ end
153
+
86
154
  # @param pem [String]
87
155
  #
88
156
  # @return [EchSpec::Ok<Array<ECHConfig>> | Err]
@@ -1,4 +1,5 @@
1
1
  module EchSpec
2
+ # rubocop: disable Metrics/ModuleLength
2
3
  module TLS13Client
3
4
  class Connection < TTTLS13::Connection
4
5
  # @param socket [Socket]
@@ -95,6 +96,84 @@ module EchSpec
95
96
  ch1.extensions.merge(exs)
96
97
  end
97
98
 
99
+ # @param socket [TCPSocket]
100
+ # @param hostname [String]
101
+ # @param stack [EchSpec::Log::MessageStack | nil]
102
+ #
103
+ # @raise [EchSpec::Error::BeforeTargetSituationError]
104
+ #
105
+ # @return [TTTLS13::Message::EncryptedExtensions]
106
+ # rubocop: disable Metrics/AbcSize
107
+ # rubocop: disable Metrics/CyclomaticComplexity
108
+ # rubocop: disable Metrics/MethodLength
109
+ # rubocop: disable Metrics/PerceivedComplexity
110
+ def send_ch_with_greased_ech(socket, hostname, stack = nil)
111
+ conn = TLS13Client::Connection.new(socket, :client)
112
+ inner_ech = TTTLS13::Message::Extension::ECHClientHello.new_inner
113
+ exs, shared_secret = TLS13Client.gen_ch_extensions(hostname)
114
+ inner = TTTLS13::Message::ClientHello.new(
115
+ cipher_suites: TTTLS13::CipherSuites.new(
116
+ [
117
+ TTTLS13::CipherSuite::TLS_AES_256_GCM_SHA384,
118
+ TTTLS13::CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
119
+ TTTLS13::CipherSuite::TLS_AES_128_GCM_SHA256
120
+ ]
121
+ ),
122
+ extensions: exs.merge(
123
+ TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => inner_ech
124
+ )
125
+ )
126
+ stack << inner if stack
127
+
128
+ ch = TTTLS13::Ech.new_greased_ch(inner, TTTLS13::Ech.new_grease_ech)
129
+ conn.send_record(
130
+ TTTLS13::Message::Record.new(
131
+ type: TTTLS13::Message::ContentType::HANDSHAKE,
132
+ messages: [ch],
133
+ cipher: TTTLS13::Cryptograph::Passer.new
134
+ )
135
+ )
136
+ stack << ch if stack
137
+
138
+ recv, = conn.recv_message(TTTLS13::Cryptograph::Passer.new)
139
+ stack << recv if stack
140
+ raise Error::BeforeTargetSituationError, 'not received ServerHello' \
141
+ unless recv.is_a?(TTTLS13::Message::ServerHello) && !recv.hrr?
142
+
143
+ transcript = TTTLS13::Transcript.new
144
+ transcript[TTTLS13::CH] = [ch, ch.serialize]
145
+ sh = recv
146
+ transcript[TTTLS13::SH] = [sh, sh.serialize]
147
+ kse = sh.extensions[TTTLS13::Message::ExtensionType::KEY_SHARE]
148
+ .key_share_entry.first
149
+ key_schedule = TTTLS13::KeySchedule.new(
150
+ psk: nil,
151
+ shared_secret: shared_secret.build(kse.group, kse.key_exchange),
152
+ cipher_suite: sh.cipher_suite,
153
+ transcript:
154
+ )
155
+ hs_rcipher = TTTLS13::Endpoint.gen_cipher(
156
+ sh.cipher_suite,
157
+ key_schedule.server_handshake_write_key,
158
+ key_schedule.server_handshake_write_iv
159
+ )
160
+ recv, = conn.recv_message(hs_rcipher)
161
+ stack << recv if stack
162
+ if recv.is_a?(TTTLS13::Message::ChangeCipherSpec)
163
+ recv, = conn.recv_message(hs_rcipher)
164
+ stack << recv if stack
165
+ end
166
+
167
+ raise Error::BeforeTargetSituationError, 'not received EncryptedExtensions' \
168
+ unless recv.is_a?(TTTLS13::Message::EncryptedExtensions)
169
+
170
+ recv
171
+ end
172
+ # rubocop: enable Metrics/AbcSize
173
+ # rubocop: enable Metrics/CyclomaticComplexity
174
+ # rubocop: enable Metrics/MethodLength
175
+ # rubocop: enable Metrics/PerceivedComplexity
176
+
98
177
  # @param conf [ECHConfig::ECHConfigContents::HpkeKeyConfig]
99
178
  #
100
179
  # @return [Boolean]
@@ -164,4 +243,5 @@ module EchSpec
164
243
  # rubocop: enable Metrics/MethodLength
165
244
  end
166
245
  end
246
+ # rubocop: enable Metrics/ModuleLength
167
247
  end
@@ -1,3 +1,3 @@
1
1
  module EchSpec
2
- VERSION = '0.0.6'.freeze
2
+ VERSION = '0.0.7'.freeze
3
3
  end
data/lib/echspec.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require 'base64'
2
+ require 'json'
3
+ require 'net/http'
2
4
  require 'optparse'
3
5
  require 'pp'
4
6
  require 'resolv'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: echspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - thekuwayama
@@ -101,7 +101,10 @@ files:
101
101
  - fixtures/server.key
102
102
  - lib/echspec.rb
103
103
  - lib/echspec/cli.rb
104
+ - lib/echspec/cli/ech_config_printer.rb
104
105
  - lib/echspec/cli/gen_configs.rb
106
+ - lib/echspec/cli/grease.rb
107
+ - lib/echspec/cli/resolve.rb
105
108
  - lib/echspec/cli/run.rb
106
109
  - lib/echspec/error.rb
107
110
  - lib/echspec/log.rb