echspec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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