echspec 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 942fd582bb741ca465bb5255b458ef3cbf3ff8cd09f20b45957df8c64d32f55a
4
+ data.tar.gz: e5748a921bc1ab45c053884690fe0e7f5718b8847c6376f87100f22173f8dd9c
5
+ SHA512:
6
+ metadata.gz: a9597e9295d2b6a8d2b836a59a9ec1013edb42b2fc605e2dacd9ba274510ccb9982f1971d6aa4d092f1896da19a4d2317386d72f9d690639049e288e96fe1fe5
7
+ data.tar.gz: d104c73ca8124af91cbaeb172757d8353e3a328668c89a9d1b66f01281ac6d8e5bacd298ac96b05fa27d1b70dadceb9a216c8b873db9607286a7e0767f304c58
@@ -0,0 +1,30 @@
1
+ name: lint & test
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - '*'
10
+
11
+ jobs:
12
+ ci:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ ruby-version: ['3.2', '3.3', '3.4']
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ${{ matrix.ruby-version }}
23
+ - name: Install dependencies
24
+ run: |
25
+ gem --version
26
+ gem install bundler
27
+ bundle --version
28
+ bundle install
29
+ - name: Run rubocop & rspec
30
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .config
4
+ .rvmrc
5
+ /.bundle/
6
+ /vendor/
7
+ /lib/bundler/man/
8
+ /pkg/
9
+ /.yardoc/
10
+ /_yardoc/
11
+ /doc/
12
+ /rdoc/
13
+ /coverage/
14
+ /spec/reports/
15
+ /tmp/
16
+ .DS_Store
17
+ Gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,36 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.2
3
+
4
+ Layout/LineLength:
5
+ Max: 200
6
+
7
+ Metrics/AbcSize:
8
+ Max: 30
9
+
10
+ Metrics/BlockLength:
11
+ Exclude:
12
+ - 'spec/*.rb'
13
+
14
+ Metrics/ClassLength:
15
+ Max: 200
16
+
17
+ Metrics/MethodLength:
18
+ Max: 30
19
+
20
+ Naming/MethodParameterName:
21
+ MinNameLength: 1
22
+
23
+ Naming/FileName:
24
+ Enabled: false
25
+
26
+ Naming/ClassAndModuleCamelCase:
27
+ Enabled: false
28
+
29
+ Semicolon:
30
+ AllowAsExpressionSeparator: true
31
+
32
+ Style/Documentation:
33
+ Enabled: false
34
+
35
+ Style/FrozenStringLiteralComment:
36
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.6
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'base64'
4
+ gem 'resolv', '> 0.4.0'
5
+ gem 'tttls1.3', github: 'thekuwayama/tttls1.3'
6
+
7
+ group :development do
8
+ gem 'rake', '13.2.1'
9
+ gem 'rspec'
10
+ gem 'rubocop', '1.62.0'
11
+ end
12
+
13
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Tomoya Kuwayama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # echspec
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/echspec.svg)](https://badge.fury.io/rb/echspec)
4
+ [![Actions Status](https://github.com/thekuwayama/echspec/actions/workflows/ci.yml/badge.svg)](https://github.com/thekuwayama/echspec/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.txt)
6
+
7
+ `echspec` is a conformance testing tool for ECH implementation.
8
+
9
+ ![echspec demo](docs/echspec-demo.png)
10
+
11
+ - https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22
12
+
13
+
14
+ ## Initial Setup
15
+
16
+ The gem is available at [rubygems.org](https://rubygems.org/gems/echspec). You can install it the following:
17
+
18
+ ```sh-session
19
+ $ gem install echspec
20
+ ```
21
+
22
+
23
+ ## Usage
24
+
25
+ ```sh-session
26
+ $ echspec --help
27
+ Usage: echspec [OPTIONS] <HOSTNAME>
28
+ -f, --file FILE path to ECHConfigs PEM file (default resolve ECHConfigs via DNS)
29
+ -p, --port VALUE server port number (default 443)
30
+ -n, --not-force-compliant-hpke not force compliant ECHConfig HPKE cipher suite
31
+ -v, --verbose verbose mode; prints message stack if raised an error
32
+ -s, --sections SECTIONS sections to test; by the default, test all sections
33
+ ```
34
+
35
+ You can run it the following:
36
+
37
+ ```sh-session
38
+ $ echspec research.cloudflare.com
39
+ TLS Encrypted Client Hello Server
40
+ ✔ MUST implement the following HPKE cipher suite: KEM: DHKEM(X25519, HKDF-SHA256), KDF: HKDF-SHA256 and AEAD: AES-128-GCM. [9]
41
+ ✔ MUST abort with an "illegal_parameter" alert, if EncodedClientHelloInner is padded with non-zero values. [5.1-9]
42
+ ✔ MUST abort with an "illegal_parameter" alert, if any referenced extension is missing in ClientHelloOuter. [5.1-10]
43
+ ✔ MUST abort with an "illegal_parameter" alert, if any extension is referenced in OuterExtensions more than once. [5.1-10]
44
+ ✔ MUST abort with an "illegal_parameter" alert, if "encrypted_client_hello" is referenced in OuterExtensions. [5.1-10]
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
+ ✔ 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]
48
+ ✔ MUST abort with an "illegal_parameter" alert, if ClientHelloInner offers TLS 1.2 or below. [7.1-11]
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
+ ✔ MUST abort with a "missing_extension" alert, if 2nd ClientHelloOuter does not contains the "encrypted_client_hello" extension. [7.1.1-2]
51
+ ✔ MUST abort with an "illegal_parameter" alert, if 2nd ClientHelloOuter "encrypted_client_hello" enc is empty. [7.1.1-2]
52
+ ✔ MUST abort with a "decrypt_error" alert, if fails to decrypt 2nd ClientHelloOuter. [7.1.1-5]
53
+ ```
54
+
55
+ 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:
56
+
57
+ ```sh-session
58
+ $ echspec -f fixtures/echconfigs.pem -p 4433 localhost
59
+ ```
60
+
61
+ By default, `echspec` uses the following HPKE cipher suite
62
+
63
+ - KEM
64
+ - DHKEM(X25519, HKDF-SHA256)
65
+ - KDF
66
+ - HKDF-SHA256
67
+ - AEAD
68
+ - AES-128-GCM
69
+
70
+ Using the `-n` or `--not-force-compliant-hpke`, you can not enforce the HPKE cipher suite.
71
+
72
+ ```sh-session
73
+ $ echspec -f fixtures/echconfigs.pem -p 4433 -n localhost
74
+ ```
75
+
76
+ If you specify the SECTIONS, you can run only SECTIONS the following:
77
+
78
+ ```sh-session
79
+ $ echspec -f fixtures/echconfigs.pem -p 4433 -n -s 7.1.1-2,7.1.1-5 localhost
80
+ TLS Encrypted Client Hello Server
81
+ ✔ MUST abort with a "missing_extension" alert, if 2nd ClientHelloOuter does not contains the "encrypted_client_hello" extension. [7.1.1-2]
82
+ ✔ MUST abort with an "illegal_parameter" alert, if 2nd ClientHelloOuter "encrypted_client_hello" enc is empty. [7.1.1-2]
83
+ ✔ MUST abort with a "decrypt_error" alert, if fails to decrypt 2nd ClientHelloOuter. [7.1.1-5]
84
+ ```
85
+
86
+ Using the `-v` or `--verbose` option provides a message stack if an error occurs. The message stack is formatted as JSON.
87
+
88
+ ```sh-session
89
+ $ echspec -s 7-5 -v research.cloudflare.com 2>&1 > /dev/null | jq .
90
+ ````
91
+
92
+ <details>
93
+
94
+ ```json
95
+ {
96
+ "Alert": {
97
+ "level": "0x02",
98
+ "description": "0x32"
99
+ },
100
+ "ClientHello": {
101
+ "msg_type": "0x01",
102
+ "legacy_version": "0x0303",
103
+ "random": "0x29142f95eb55066cdb496267d3154628685ad1dbbe5b877e66eda4af20df2c69",
104
+ "legacy_session_id": "0x9c557bc381f62d73ba3b99629f8fe6e347787be66c56fa99db4f6bc6fd06fd5f",
105
+ "cipher_suites": [
106
+ "0x1302",
107
+ "0x1303",
108
+ "0x1301"
109
+ ],
110
+ "legacy_compression_methods": [
111
+ "0x00"
112
+ ],
113
+ "extensions": {
114
+ "0x0000": {
115
+ "extension_type": "0x0000",
116
+ "server_name": "0x636c6f7564666c6172652d6563682e636f6d"
117
+ },
118
+ "0x002b": {
119
+ "extension_type": "0x002b",
120
+ "msg_type": "0x01",
121
+ "versions": [
122
+ "0x0304"
123
+ ]
124
+ },
125
+ "0x000d": {
126
+ "extension_type": "0x000d",
127
+ "supported_signature_algorithms": [
128
+ "0x0403",
129
+ "0x0503",
130
+ "0x0603",
131
+ "0x0804",
132
+ "0x0805",
133
+ "0x0806",
134
+ "0x0401",
135
+ "0x0501",
136
+ "0x0601"
137
+ ]
138
+ },
139
+ "0x000a": {
140
+ "extension_type": "0x000a",
141
+ "named_group_list": [
142
+ "0x0017",
143
+ "0x0018",
144
+ "0x0019"
145
+ ]
146
+ },
147
+ "0x0033": {
148
+ "extension_type": "0x0033",
149
+ "msg_type": "0x01",
150
+ "key_share_entry": [
151
+ {
152
+ "group": "0x0017",
153
+ "key_exchange": "0x0421747aa4234dbefc61906c165b8f1050b3346bb67f2c4ad8af9f58135888354a631b9b5c68f8ec1b6d6e67485a971bd3ff0ba6ab46da08f1524d7a4a3578c110"
154
+ },
155
+ {
156
+ "group": "0x0018",
157
+ "key_exchange": "0x046d374a61f2b75717b28b47b3fa227b51e09bb7a4ce0ea24b3cd3c946e9d4da2d54186b76812a74eb53adaa8a4451573201613b2f6301c05efb79bb6a782e88150dc3ac14aad702b8268aa8f3435d1a404166133216467aeb933247f994035fa0"
158
+ },
159
+ {
160
+ "group": "0x0019",
161
+ "key_exchange": "0x0401c90f95f64dc1e8d7d3f29f8a9103308835a2b56f5581ffc2837a1f9fcf6b23db824f01d6e4efccec78d6858aaad2fde8f02c2acc66a463c14b78e5e323b23d1b1301725b4f757811b7a31b4b5ee8016891ecb55b3fb997dd5738f6e610388f9bc85a4515efd04ab1d456e56d4e6e4a3a1e58a58ed6cf86f5dc9d7ace20afcd0af6b23b"
162
+ }
163
+ ]
164
+ },
165
+ "0xfe0d": {
166
+ "extension_type": "0xfe0d",
167
+ "type": "0x02",
168
+ "cipher_suite": {
169
+ "kdf_id": {
170
+ "uint16": 1
171
+ },
172
+ "aead_id": {
173
+ "uint16": 1
174
+ }
175
+ },
176
+ "config_id": 9,
177
+ "enc": "0xe951022abd85cc53f16c92378ba736ea9d28b3a4106a6f2865323ea04fdf4075",
178
+ "payload": "0xbeef20fb3063fd807f6327213a5c5e37a0d355fc67c4c3a10362f947c7b72f06514db0e6bf470efb87d0db30669331caa3723441fb2850190851a9179b8b42e1e7f78bd0d281daf631872d9f7008624e24baef48ac8e18951fed9ad7d80def0b1bec492d5c5c2c532c4c8ec32b6dd3a34522c70c64e21ca639c7f54d2c3ad72c65ffda0dc1f82df5abf0857586eb17f0df08a15770a91d5c3640cff59b49b0fb3d19cf77137cd27416470e19db21519751c3d0ae417a62641903731408b4b81e008fff641d22a3300f92c8b9330d260055677c545a8d561076ecc3a4de5639120f6c67736df87a464ca221373c3bbe8e74dfe795eb43593f1d03d8668d49c4a9a73ed6d3264d7126bcfc93a975c8848170c57322b7c1bef210235bc79fd58bb4ff202d9e75bec3d2251a2429e11e5c7876edcca0685d52e1b99f51b46d0e723975a7c9d894e5674ef8debf380e799d75bff93c13e4917296242e2cb7b99bfcb0fc7cd8f98f414ceb0ef3a63fa29efc722194b91beb354efff5215b1804b9c555d4aad36a85e6eb3536b0b66fea50c9b055fa8441a36fc61a2228e0d4cb3c5ecb1662c641adf30a70c3b1104fb9b0f9b2c3130dc52939e9695a470774bee6dabc2691d06f870d01fe249199d831258583"
179
+ }
180
+ }
181
+ },
182
+ "ClientHelloInner": {
183
+ "msg_type": "0x01",
184
+ "legacy_version": "0x0303",
185
+ "random": "0x99f21aebeff5838b88011e581ade4de4eb334e5528efaf223dfa33c55d7bc20b",
186
+ "legacy_session_id": "0x9c557bc381f62d73ba3b99629f8fe6e347787be66c56fa99db4f6bc6fd06fd5f",
187
+ "cipher_suites": [
188
+ "0x1302",
189
+ "0x1303",
190
+ "0x1301"
191
+ ],
192
+ "legacy_compression_methods": [
193
+ "0x00"
194
+ ],
195
+ "extensions": {
196
+ "0x0000": {
197
+ "extension_type": "0x0000",
198
+ "server_name": "0x72657365617263682e636c6f7564666c6172652e636f6d"
199
+ },
200
+ "0x002b": {
201
+ "extension_type": "0x002b",
202
+ "msg_type": "0x01",
203
+ "versions": [
204
+ "0x0304"
205
+ ]
206
+ },
207
+ "0x000d": {
208
+ "extension_type": "0x000d",
209
+ "supported_signature_algorithms": [
210
+ "0x0403",
211
+ "0x0503",
212
+ "0x0603",
213
+ "0x0804",
214
+ "0x0805",
215
+ "0x0806",
216
+ "0x0401",
217
+ "0x0501",
218
+ "0x0601"
219
+ ]
220
+ },
221
+ "0x000a": {
222
+ "extension_type": "0x000a",
223
+ "named_group_list": [
224
+ "0x0017",
225
+ "0x0018",
226
+ "0x0019"
227
+ ]
228
+ },
229
+ "0x0033": {
230
+ "extension_type": "0x0033",
231
+ "msg_type": "0x01",
232
+ "key_share_entry": [
233
+ {
234
+ "group": "0x0017",
235
+ "key_exchange": "0x0421747aa4234dbefc61906c165b8f1050b3346bb67f2c4ad8af9f58135888354a631b9b5c68f8ec1b6d6e67485a971bd3ff0ba6ab46da08f1524d7a4a3578c110"
236
+ },
237
+ {
238
+ "group": "0x0018",
239
+ "key_exchange": "0x046d374a61f2b75717b28b47b3fa227b51e09bb7a4ce0ea24b3cd3c946e9d4da2d54186b76812a74eb53adaa8a4451573201613b2f6301c05efb79bb6a782e88150dc3ac14aad702b8268aa8f3435d1a404166133216467aeb933247f994035fa0"
240
+ },
241
+ {
242
+ "group": "0x0019",
243
+ "key_exchange": "0x0401c90f95f64dc1e8d7d3f29f8a9103308835a2b56f5581ffc2837a1f9fcf6b23db824f01d6e4efccec78d6858aaad2fde8f02c2acc66a463c14b78e5e323b23d1b1301725b4f757811b7a31b4b5ee8016891ecb55b3fb997dd5738f6e610388f9bc85a4515efd04ab1d456e56d4e6e4a3a1e58a58ed6cf86f5dc9d7ace20afcd0af6b23b"
244
+ }
245
+ ]
246
+ },
247
+ "0xfe0d": {
248
+ "extension_type": "0xfe0d",
249
+ "type": "0x01",
250
+ "cipher_suite": null,
251
+ "config_id": null,
252
+ "enc": null,
253
+ "payload": null
254
+ }
255
+ }
256
+ }
257
+ }
258
+ ```
259
+
260
+ </details>
261
+
262
+
263
+ ## Note
264
+
265
+ `echspec` is inspired by:
266
+
267
+ - https://github.com/summerwind/h2spec
268
+
269
+
270
+ ## License
271
+
272
+ `echspec` is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RuboCop::RakeTask.new
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: %i[rubocop spec]
Binary file
data/echspec.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'echspec/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'echspec'
7
+ spec.version = EchSpec::VERSION
8
+ spec.authors = ['thekuwayama']
9
+ spec.email = ['thekuwayama@gmail.com']
10
+ spec.summary = 'A conformance testing tool for ECH implementation'
11
+ spec.description = spec.summary
12
+ spec.homepage = 'https://github.com/thekuwayama/echspec'
13
+ spec.license = 'MIT'
14
+ spec.required_ruby_version = '>=3.2'
15
+
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+ spec.bindir = 'exe'
20
+ spec.executables = ['echspec']
21
+
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_dependency 'base64'
24
+ spec.add_dependency 'resolv', '> 0.4.0'
25
+ spec.add_dependency 'tttls1.3', '~> 0.3.4'
26
+ end
data/exe/echspec ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << "#{__dir__}/../lib"
4
+
5
+ require 'echspec'
6
+
7
+ EchSpec::CLI.new.run
@@ -0,0 +1,7 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MC4CAQAwBQYDK2VuBCIEICjd4yGRdsoP9gU7YT7My8DHx1Tjme8GYDXrOMCi8v1V
3
+ -----END PRIVATE KEY-----
4
+ -----BEGIN ECHCONFIG-----
5
+ AD7+DQA65wAgACA8wVN2BtscOl3vQheUzHeIkVmKIiydUhDCliA4iyQRCwAEAAEA
6
+ AQALZXhhbXBsZS5jb20AAA==
7
+ -----END ECHCONFIG-----
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDOzCCAiOgAwIBAgIJAOOHBGEtaVlUMA0GCSqGSIb3DQEBCwUAMBwxGjAYBgNV
3
+ BAMMEXRlc3QtaW50ZXJtZWRpYXRlMB4XDTI0MDUwMzIyNTgzOVoXDTI1MDUwMzIy
4
+ NTgzOVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOC
5
+ AQ8AMIIBCgKCAQEAyJ0/RY8esNtOCfdi3iR1VhPKpteACMybSZL+yvCr7HVWSIAF
6
+ TK+sCN5ODdG+rnICvmmaLLF1aVwz9Jn0uW+MX3tejsDftw6CH7z/xblqPmEl2gTW
7
+ vOdDSfJttaqSf1F+XQMadVov4YFweRAAI8uY4BTV4TLEfIK06xNNFqcMv5EmUyrv
8
+ XEVW3ki7+7nOqMUH7nfNymR8ENU1YX8y4WpyfEyKw1Gk3PQH3iIQ5J7gqoDqlBYG
9
+ 083eLJau5qVDM/Q9Ro002tfmdpnJg+g59q9KstOQbTHKBYTP8YIotJhDOCl/fgoP
10
+ g+r3gP6EAnMaE4XjsQTp7YDRiVnvF2lMqYC0QwIDAQABo4GHMIGEMAkGA1UdEwQC
11
+ MAAwCwYDVR0PBAQDAgWgMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDBUBggrBgEFBQcB
12
+ AQRIMEYwIQYIKwYBBQUHMAKGFWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MDAhBggrBgEF
13
+ BQcwAYYVaHR0cDovL2xvY2FsaG9zdDo4MDgwMA0GCSqGSIb3DQEBCwUAA4IBAQA2
14
+ ZuJuzDr3JARfqpi5C2yktW0Hiv0Dzx3g1vLeOGl6ZPPIxChxHKQ79BACM2FsVYcu
15
+ ZGK7qGXpCMuzOuMjoCcfydXwjmoMlLtjAwXSKv5/ZT+SXWkO4weMKVQsovfBYDvr
16
+ ktiHjJJV1ToPa1R4FsXIyNQDTByyT8D+8Fj3+et/I+JyxNrm65Rv6l+qapP6J5Fn
17
+ oAVxoE7JFLcqvvRLH7/ggqMhUgftN4olzhUT2d6sThevsA9D6PWkcRuGvPWFMXkY
18
+ R1ABD5sQynFvkcjjCcl+m7tOyqcmvwFFlS9X+ql4LLT8MHOOXhAGfxcD6qquVmvL
19
+ Is1FlMw33rZEkugXwT0w
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpAIBAAKCAQEAyJ0/RY8esNtOCfdi3iR1VhPKpteACMybSZL+yvCr7HVWSIAF
3
+ TK+sCN5ODdG+rnICvmmaLLF1aVwz9Jn0uW+MX3tejsDftw6CH7z/xblqPmEl2gTW
4
+ vOdDSfJttaqSf1F+XQMadVov4YFweRAAI8uY4BTV4TLEfIK06xNNFqcMv5EmUyrv
5
+ XEVW3ki7+7nOqMUH7nfNymR8ENU1YX8y4WpyfEyKw1Gk3PQH3iIQ5J7gqoDqlBYG
6
+ 083eLJau5qVDM/Q9Ro002tfmdpnJg+g59q9KstOQbTHKBYTP8YIotJhDOCl/fgoP
7
+ g+r3gP6EAnMaE4XjsQTp7YDRiVnvF2lMqYC0QwIDAQABAoIBAAzYZ2e9zUTyazcf
8
+ fFjQVCRfKg0FeXDsIPlUtzR+tT4ebpg/0kG3HI3eJiNOYyY/rfChSbpDi/VjblQ4
9
+ KL+tkSW//C2p0z6hEts+D9E07m+batVglUYNN3VxrMKtbv9F/uWtFYb0GmLBwKuP
10
+ xw0uXoCNSlCcHpFWZofdeYR8zR0q7L64P8EsqjrYGOTeyQWKshHIxAAks6Xb19zW
11
+ h4f3aBTZ3tWHCMAZj8LsGVG4O2Nx9esuKwybe0vBMkv/esyEVXEPm+RDjQtlKGqQ
12
+ i4V03ykI6LbIwi8bk3H7jItGKX8fNK9CNMi0ZEyYgvVe5HQciJBtcaWUiBUEHhum
13
+ N0Np7uECgYEA6jsG72P5GleyRIBAUNrN/a1yIc9b9RXZS63lCb5UMMW6rYuP8wRe
14
+ 1ARzCNhkzYoKxay3ZQHPW8deDasTXCPiIkJqRDfDRr464OQ29qBEWx4aWtVbAsuH
15
+ 8/SJK45YpvdCSxT3wBzTzzff4bAIF7yFYBJjVBEs948TrtGePlvpyBMCgYEA20Jl
16
+ xpQUorhEXn4JOjC65FHj3hJgPus8JgPSLpjpuDs9WZBN3mRt7GIZ0Tb6gA/Yv2wn
17
+ Ps0u2iCe7uGKC5XEQ84KrtcM+rk7mRGsdWxQqnJp6tbZbHqGqF0Y9uBqOgRiJJTm
18
+ i9L1QiHnkrtlwMdlQRXQvPvwG4Z5OU/W4D7/SRECgYEAr/wZgdPDXZ92OTGDITzE
19
+ eEzQ68Y4eTQpR0soQuHVr69gSvQI+7XU6cdOBt9PHX8SCON0B1gMzBBHAk3/BcOQ
20
+ K91qqkabWZOj+UR+Z16S/ULo2kZjUv5I72pThX417XzpOjBO1PDT02VPuOnhqrPi
21
+ IgSuzIL7HiVJzJeCJag5RjECgYBhAiuNhI7sv6JgPFtQx6aoxiKPaonyzJk8KIyh
22
+ 2T3vKSanrdUGBGEuKOlLS4vhhSFc8Dkc7CNClxQ6lMdDAOxpI4xOdw9jDvlzbAJl
23
+ oZq/DwgVwyFHgZ56d1ZIRFo7eR0DGm42hwvESsPug8MtXAtMlJ5aPw2o4AJafRyQ
24
+ 8s54QQKBgQCnaQcbPdoNrnn1giz6s1XNKMayIfod8tPS+xnKJpGzjyDVQzEDWA05
25
+ pEp1GFMaNXjUa8ICj/DeZ630ZfODDboTp3JVKLe+QW3U89H60AJz/0UnZ8k/fNwA
26
+ 39CfIk3ukQS4oAWpMua2s2e9Vf7zXHM/sm07R8BKo0wtoUEg3RtCYQ==
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,97 @@
1
+ module EchSpec
2
+ class CLI
3
+ # rubocop: disable Metrics/AbcSize
4
+ # rubocop: disable Metrics/MethodLength
5
+ def parse_options(argv = ARGV)
6
+ op = OptionParser.new
7
+
8
+ # default value
9
+ fpath = nil
10
+ port = 443
11
+ force_compliant = true
12
+ verbose = false
13
+ sections = nil
14
+
15
+ op.on(
16
+ '-f',
17
+ '--file FILE',
18
+ 'path to ECHConfigs PEM file (default resolve ECHConfigs via DNS)'
19
+ ) do |v|
20
+ fpath = v
21
+ end
22
+
23
+ op.on(
24
+ '-p',
25
+ '--port VALUE',
26
+ "server port number (default #{port})"
27
+ ) do |v|
28
+ port = v
29
+ end
30
+
31
+ op.on(
32
+ '-n',
33
+ '--not-force-compliant-hpke',
34
+ 'not force compliant ECHConfig HPKE cipher suite'
35
+ ) do
36
+ force_compliant = false
37
+ end
38
+
39
+ op.on(
40
+ '-v',
41
+ '--verbose',
42
+ 'verbose mode; prints message stack if raised an error'
43
+ ) do
44
+ verbose = true
45
+ end
46
+
47
+ op.on(
48
+ '-s',
49
+ '--sections SECTIONS',
50
+ 'sections to test; by the default, test all sections'
51
+ ) do |v|
52
+ sections = v.split(',')
53
+ end
54
+
55
+ op.banner = 'Usage: echspec [OPTIONS] <HOSTNAME>'
56
+ begin
57
+ args = op.parse(argv)
58
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
59
+ warn op
60
+ warn "** #{e.message}"
61
+ exit 1
62
+ end
63
+
64
+ if !fpath.nil? && !File.exist?(fpath)
65
+ warn '** <FILE> is not found'
66
+ exit 1
67
+ end
68
+
69
+ unknowns = sections.nil? ? [] : sections - Spec.sections
70
+ unless unknowns.empty?
71
+ warn "** #{unknowns} are unknown sections"
72
+ exit 1
73
+ end
74
+
75
+ if args.length != 1
76
+ warn op
77
+ warn '** <HOSTNAME> argument is not specified'
78
+ exit 1
79
+ end
80
+ hostname = args[0]
81
+
82
+ [fpath, port, force_compliant, verbose, hostname, sections]
83
+ end
84
+ # rubocop: enable Metrics/AbcSize
85
+ # rubocop: enable Metrics/MethodLength
86
+
87
+ def run
88
+ fpath, port, force_compliant, verbose, hostname, sections = parse_options
89
+
90
+ if sections.nil?
91
+ Spec.run(fpath, port, hostname, force_compliant, verbose)
92
+ else
93
+ Spec.run_only(fpath, port, hostname, sections, verbose)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,9 @@
1
+ module EchSpec
2
+ module Error
3
+ # Generic error, common for all classes under EchSpec::Error module.
4
+ class Error < StandardError; end
5
+
6
+ # Raised if the server behaves unintended before the target situation.
7
+ class BeforeTargetSituationError < Error; end
8
+ end
9
+ end