echspec 0.0.1 → 0.0.2

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: 942fd582bb741ca465bb5255b458ef3cbf3ff8cd09f20b45957df8c64d32f55a
4
- data.tar.gz: e5748a921bc1ab45c053884690fe0e7f5718b8847c6376f87100f22173f8dd9c
3
+ metadata.gz: 664b02dfe5659032ff19242937be686085ab762f223fd950a1332da55fd69e12
4
+ data.tar.gz: 0146c242937094ff8a4cb1c494d27af14a55d34e8967c4bf174b6eb76132b8c6
5
5
  SHA512:
6
- metadata.gz: a9597e9295d2b6a8d2b836a59a9ec1013edb42b2fc605e2dacd9ba274510ccb9982f1971d6aa4d092f1896da19a4d2317386d72f9d690639049e288e96fe1fe5
7
- data.tar.gz: d104c73ca8124af91cbaeb172757d8353e3a328668c89a9d1b66f01281ac6d8e5bacd298ac96b05fa27d1b70dadceb9a216c8b873db9607286a7e0767f304c58
6
+ metadata.gz: ca2add160c37c4997ee2f76172c1b4edc598d85149fecfae5fd6861dce34455916fbee67d02bbf60e8d6f121e583ca284fee4bde73a215c735c05465e30b8704
7
+ data.tar.gz: 832197c0251d27bf060a4ac774126117eca42fc11615e1078edbf4db84109a24125e6ab2417cba393223a87632701deea876a90e0e1cf5d9a8d7c7b324a40dc8
@@ -15,9 +15,9 @@ jobs:
15
15
  matrix:
16
16
  ruby-version: ['3.2', '3.3', '3.4']
17
17
  steps:
18
- - uses: actions/checkout@v4
18
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
19
19
  - name: Set up Ruby
20
- uses: ruby/setup-ruby@v1
20
+ uses: ruby/setup-ruby@eaecf785f6a34567a6d97f686bbb7bccc1ac1e5c # v1.237.0
21
21
  with:
22
22
  ruby-version: ${{ matrix.ruby-version }}
23
23
  - name: Install dependencies
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.6
1
+ 3.4.3
data/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  - https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22
12
12
 
13
13
 
14
- ## Initial Setup
14
+ ## Installation
15
15
 
16
16
  The gem is available at [rubygems.org](https://rubygems.org/gems/echspec). You can install it the following:
17
17
 
@@ -44,12 +44,20 @@ TLS Encrypted Client Hello Server
44
44
  ✔ MUST abort with an "illegal_parameter" alert, if "encrypted_client_hello" is referenced in OuterExtensions. [5.1-10]
45
45
  ✔ MUST abort with an "illegal_parameter" alert, if the extensions in ClientHelloOuter corresponding to those in OuterExtensions do not occur in the same order. [5.1-10]
46
46
  ✔ MUST abort with an "illegal_parameter" alert, if ECHClientHello.type is not a valid ECHClientHelloType in ClientHelloInner. [7-5]
47
- MUST abort with an "illegal_parameter" alert, if ECHClientHello.type is not a valid ECHClientHelloType in ClientHelloOuter. [7-5]
47
+ x MUST abort with an "illegal_parameter" alert, if ECHClientHello.type is not a valid ECHClientHelloType in ClientHelloOuter. [7-5]
48
48
  ✔ MUST abort with an "illegal_parameter" alert, if ClientHelloInner offers TLS 1.2 or below. [7.1-11]
49
49
  ✔ MUST include the "encrypted_client_hello" extension in its EncryptedExtensions with the "retry_configs" field set to one or more ECHConfig. [7.1-14.2.1]
50
50
  ✔ MUST abort with a "missing_extension" alert, if 2nd ClientHelloOuter does not contains the "encrypted_client_hello" extension. [7.1.1-2]
51
51
  ✔ MUST abort with an "illegal_parameter" alert, if 2nd ClientHelloOuter "encrypted_client_hello" enc is empty. [7.1.1-2]
52
52
  ✔ MUST abort with a "decrypt_error" alert, if fails to decrypt 2nd ClientHelloOuter. [7.1.1-5]
53
+
54
+ Failures:
55
+
56
+ 1) MUST abort with an "illegal_parameter" alert, if ECHClientHello.type is not a valid ECHClientHelloType in ClientHelloOuter. [7-5]
57
+ https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22#section-7-5
58
+ did not send expected alert: illegal_parameter
59
+
60
+ 1 failure
53
61
  ```
54
62
 
55
63
  By default, `echspec` retrieves ECHConfigs via HTTPS records. By using the `-f, --file FILE` option, you can specify an ECHConfig pem file. If you need to test the server on localhost, you can run it the following:
Binary file
data/echspec.gemspec CHANGED
@@ -22,5 +22,5 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency 'bundler'
23
23
  spec.add_dependency 'base64'
24
24
  spec.add_dependency 'resolv', '> 0.4.0'
25
- spec.add_dependency 'tttls1.3', '~> 0.3.4'
25
+ spec.add_dependency 'tttls1.3', '~> 0.3.5'
26
26
  end
data/lib/echspec/log.rb CHANGED
@@ -34,12 +34,16 @@ module EchSpec
34
34
  'EncryptedExtensions'
35
35
  in TTTLS13::Message::Certificate
36
36
  'Certificate'
37
+ in TTTLS13::Message::CompressedCertificate
38
+ 'CompressedCertificate'
37
39
  in TTTLS13::Message::CertificateVerify
38
40
  'CertificateVerify'
39
41
  in TTTLS13::Message::Finished
40
42
  'Finished'
41
43
  in TTTLS13::Message::EndOfEarlyData
42
44
  'EndOfEarlyData'
45
+ in TTTLS13::Message::NewSessionTicket
46
+ 'NewSessionTicket'
43
47
  in TTTLS13::Message::Alert
44
48
  'Alert'
45
49
  end
@@ -130,7 +130,7 @@ module EchSpec
130
130
  end
131
131
 
132
132
  class MissingReferencedExtensions < TTTLS13::Message::Extensions
133
- # @param _ [Array of TTTLS13::Message::ExtensionType]
133
+ # @param _ [Array<TTTLS13::Message::ExtensionType>]
134
134
  #
135
135
  # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
136
136
  def remove_and_replace!(_)
@@ -150,7 +150,7 @@ module EchSpec
150
150
  end
151
151
 
152
152
  class DuplicatedOuterExtensions < TTTLS13::Message::Extensions
153
- # @param _ [Array of TTTLS13::Message::ExtensionType]
153
+ # @param _ [Array<TTTLS13::Message::ExtensionType>]
154
154
  #
155
155
  # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
156
156
  def remove_and_replace!(_)
@@ -172,7 +172,7 @@ module EchSpec
172
172
  end
173
173
 
174
174
  class ReferencedEncryptedClientHello < TTTLS13::Message::Extensions
175
- # @param _ [Array of TTTLS13::Message::ExtensionType]
175
+ # @param _ [Array<TTTLS13::Message::ExtensionType>]
176
176
  #
177
177
  # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
178
178
  def remove_and_replace!(_)
@@ -194,7 +194,7 @@ module EchSpec
194
194
  end
195
195
 
196
196
  class NotSameOrderExtensions < TTTLS13::Message::Extensions
197
- # @param _ [Array of TTTLS13::Message::ExtensionType]
197
+ # @param _ [Array<TTTLS13::Message::ExtensionType>]
198
198
  #
199
199
  # @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
200
200
  def remove_and_replace!(_)
@@ -60,7 +60,7 @@ module EchSpec
60
60
  # send ClientHello
61
61
  conn = TLS13Client::Connection.new(socket, :client)
62
62
  inner_ech = TTTLS13::Message::Extension::ECHClientHello.new_inner
63
- exs, priv_keys = TLS13Client.gen_ch_extensions(hostname)
63
+ exs, shared_secret = TLS13Client.gen_ch_extensions(hostname)
64
64
  inner = TTTLS13::Message::ClientHello.new(
65
65
  cipher_suites: TTTLS13::CipherSuites.new(
66
66
  [
@@ -98,14 +98,9 @@ module EchSpec
98
98
  transcript[TTTLS13::SH] = [sh, sh.serialize]
99
99
  kse = sh.extensions[TTTLS13::Message::ExtensionType::KEY_SHARE]
100
100
  .key_share_entry.first
101
- shared_secret = TTTLS13::Endpoint.gen_shared_secret(
102
- kse.key_exchange,
103
- priv_keys[kse.group],
104
- kse.group
105
- )
106
101
  key_schedule = TTTLS13::KeySchedule.new(
107
102
  psk: nil,
108
- shared_secret:,
103
+ shared_secret: shared_secret.build(kse.group, kse.key_exchange),
109
104
  cipher_suite: sh.cipher_suite,
110
105
  transcript:
111
106
  )
@@ -41,9 +41,9 @@ module EchSpec
41
41
  end
42
42
  end
43
43
 
44
- # @param ech_configs [Array of ECHConfig]
44
+ # @param ech_configs [Array<ECHConfig>]
45
45
  #
46
- # @return [EchSpec::Ok<ECHConfig> | Err]
46
+ # @return [EchSpec::Ok | Err]
47
47
  def validate_compliant_ech_configs(ech_configs)
48
48
  ech_config = ech_configs.find do |c|
49
49
  kconfig = c.echconfig_contents.key_config
@@ -61,7 +61,7 @@ module EchSpec
61
61
 
62
62
  # @param hostname [String]
63
63
  #
64
- # @return [EchSpec::Ok<Array of ECHConfig> | Err]
64
+ # @return [EchSpec::Ok<Array<ECHConfig>> | Err]
65
65
  def resolve_ech_configs(hostname)
66
66
  begin
67
67
  rr = Resolv::DNS.new.getresource(
@@ -85,7 +85,7 @@ module EchSpec
85
85
 
86
86
  # @param pem [String]
87
87
  #
88
- # @return [EchSpec::Ok<Array of ECHConfig> | Err]
88
+ # @return [EchSpec::Ok<Array<ECHConfig>> | Err]
89
89
  def parse_pem(pem)
90
90
  s = pem.scan(/-----BEGIN ECHCONFIG-----(.*)-----END ECHCONFIG-----/m)
91
91
  .first
data/lib/echspec/spec.rb CHANGED
@@ -12,20 +12,20 @@ module EchSpec
12
12
  msg.description == TTTLS13::Message::ALERT_DESCRIPTION[desc]
13
13
  end
14
14
 
15
- ResultDesc = Struct.new(:result, :desc)
15
+ ResultDescURL = Struct.new(:result, :desc, :url)
16
16
 
17
- # @param rds [Array of ResultDesc] result: EchSpec::Ok | Err, desc: String
17
+ # @param rds [Array<ResultDescURL>] result: EchSpec::Ok | Err, desc: String
18
18
  # @param verbose [Boolean]
19
- def print_results(rds, verbose)
20
- rds.each { |rd| print_summary(rd.result, rd.desc) }
21
- failures = rds.filter { |rd| rd.result.is_a? Err }
19
+ def print_results(rdus, verbose)
20
+ rdus.each { |rdu| print_summary(rdu.result, rdu.desc) }
21
+ failures = rdus.filter { |rdu| rdu.result.is_a? Err }
22
22
  return if failures.empty?
23
23
 
24
24
  puts
25
25
  puts 'Failures:'
26
26
  puts
27
27
  failures.each
28
- .with_index { |rd, idx| print_err_details(rd.result, idx, rd.desc, verbose) }
28
+ .with_index { |rdu, idx| print_err_details(rdu.result, rdu.url, idx, rdu.desc, verbose) }
29
29
  puts "#{failures.length} failure".red
30
30
  end
31
31
 
@@ -36,20 +36,22 @@ module EchSpec
36
36
  cross = "\u0078"
37
37
  summary = case result
38
38
  in Ok
39
- "\t#{check} #{desc}".green
39
+ "#{check} #{desc}".green.indent
40
40
  in Err
41
- "\t#{cross} #{desc}".red
41
+ "#{cross} #{desc}".red.indent
42
42
  end
43
43
  puts summary
44
44
  end
45
45
 
46
46
  # @param err [EchSpec::Err]
47
+ # @param url [String]
47
48
  # @param idx [Integer]
48
49
  # @param desc [String]
49
50
  # @param verbose [Boolean]
50
- def print_err_details(err, idx, desc, verbose)
51
- puts "\t#{idx + 1}) #{desc}"
52
- puts "\t\t#{err.details}"
51
+ def print_err_details(err, url, idx, desc, verbose)
52
+ puts "#{idx + 1}) #{desc}".indent
53
+ puts url.indent.indent
54
+ puts err.details.indent.indent
53
55
  warn err.message_stack if verbose && !err.message_stack.nil?
54
56
  puts
55
57
  end
@@ -103,7 +105,7 @@ module EchSpec
103
105
  # @param fpath [String | NilClass]
104
106
  # @param port [Integer]
105
107
  # @param hostname [String]
106
- # @param sections [Array of String]
108
+ # @param sections [Array<String>]
107
109
  # @param verbose [Boolean]
108
110
  def run_only(fpath, port, hostname, sections, verbose)
109
111
  targets = spec_groups.filter { |g| sections.include?(g.section) }
@@ -119,18 +121,34 @@ module EchSpec
119
121
  # @param port [Integer]
120
122
  # @param hostname [String]
121
123
  # @param ech_config [ECHConfig]
122
- # @param targets [Array of EchSpec::SpecGroup]
124
+ # @param targets [Array<EchSpec::SpecGroup>]
123
125
  # @param verbose [Boolean]
124
126
  def do_run(port, hostname, ech_config, targets, verbose)
125
- rds = targets.flat_map do |g|
127
+ rdus = targets.flat_map do |g|
126
128
  g.spec_cases.map do |sc|
127
- r = sc.method.call(hostname, port, ech_config)
128
- d = "#{sc.description} [#{g.section}]"
129
- ResultDesc.new(result: r, desc: d)
129
+ result = sc.method.call(hostname, port, ech_config)
130
+ desc = desc(sc.description, g.section)
131
+ url = url(g.section)
132
+ ResultDescURL.new(result:, desc:, url:)
130
133
  end
131
134
  end
132
135
 
133
- print_results(rds, verbose)
136
+ print_results(rdus, verbose)
137
+ end
138
+
139
+ # @param description [String]
140
+ # @param section [String]
141
+ #
142
+ # @return [String]
143
+ def desc(description, section)
144
+ "#{description} [#{section}]"
145
+ end
146
+
147
+ # @param section [String]
148
+ #
149
+ # @return [String]
150
+ def url(section)
151
+ "https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22#section-#{section}"
134
152
  end
135
153
 
136
154
  # @param fpath [String | NilClass]
@@ -140,14 +158,18 @@ module EchSpec
140
158
  # @return [ECHConfig]
141
159
  def try_get_ech_config(fpath, hostname, force_compliant)
142
160
  # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22#section-9
143
- case result = Spec9.try_get_ech_config(fpath, hostname, force_compliant)
144
- in Ok(obj) if force_compliant
145
- result.tap { |r| print_summary(r, "#{Spec9.description} [#{Spec9.section}]") }
146
- obj
147
- in Ok(obj)
148
- obj
161
+ result = Spec9.try_get_ech_config(fpath, hostname, force_compliant)
162
+ desc = desc(Spec9.description, Spec9.section)
163
+ url = url(Spec9.section)
164
+
165
+ case result
166
+ in Ok(ech_config) if force_compliant
167
+ print_summary(result, desc)
168
+ ech_config
169
+ in Ok(ech_config)
170
+ ech_config
149
171
  in Err(details, _)
150
- puts "\t#{details}".red
172
+ print_results([ResultDescURL.new(result:, desc:, url:)], true)
151
173
  exit 1
152
174
  end
153
175
  end
@@ -66,12 +66,12 @@ module EchSpec
66
66
  exs << TTTLS13::Message::Extension::SupportedGroups.new(groups)
67
67
 
68
68
  # key_share
69
- key_share, priv_keys = TTTLS13::Message::Extension::KeyShare.gen_ch_key_share(
69
+ key_share, shared_secret = TTTLS13::Message::Extension::KeyShare.gen_ch_key_share(
70
70
  groups
71
71
  )
72
72
  exs << key_share
73
73
 
74
- [exs, priv_keys]
74
+ [exs, shared_secret]
75
75
  end
76
76
 
77
77
  # @param ch1 [TTTLS13::Message::ClientHello]
data/lib/echspec/utils.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  module EchSpec
2
2
  module Refinements
3
3
  refine String do
4
+ def indent
5
+ "\t#{self}"
6
+ end
7
+
4
8
  def colorize(code)
5
9
  "\e[#{code}m#{self}\e[0m"
6
10
  end
@@ -1,3 +1,3 @@
1
1
  module EchSpec
2
- VERSION = '0.0.1'.freeze
2
+ VERSION = '0.0.2'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: echspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - thekuwayama
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-01-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bundler
@@ -58,14 +57,14 @@ dependencies:
58
57
  requirements:
59
58
  - - "~>"
60
59
  - !ruby/object:Gem::Version
61
- version: 0.3.4
60
+ version: 0.3.5
62
61
  type: :runtime
63
62
  prerelease: false
64
63
  version_requirements: !ruby/object:Gem::Requirement
65
64
  requirements:
66
65
  - - "~>"
67
66
  - !ruby/object:Gem::Version
68
- version: 0.3.4
67
+ version: 0.3.5
69
68
  description: A conformance testing tool for ECH implementation
70
69
  email:
71
70
  - thekuwayama@gmail.com
@@ -115,7 +114,6 @@ homepage: https://github.com/thekuwayama/echspec
115
114
  licenses:
116
115
  - MIT
117
116
  metadata: {}
118
- post_install_message:
119
117
  rdoc_options: []
120
118
  require_paths:
121
119
  - lib
@@ -130,8 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
128
  - !ruby/object:Gem::Version
131
129
  version: '0'
132
130
  requirements: []
133
- rubygems_version: 3.5.22
134
- signing_key:
131
+ rubygems_version: 3.6.7
135
132
  specification_version: 4
136
133
  summary: A conformance testing tool for ECH implementation
137
134
  test_files: