echspec 0.0.3 → 0.0.4

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: 4ff77e42c8a7e2d787372fe6f96f3c00619fb6bcb3feea8df89c5400a838aef6
4
- data.tar.gz: d317448071982052658909b19a99f535ce99e1c9f063de65be9cecae1ab4179f
3
+ metadata.gz: 9da986ad5207e26d816ebe08d7f8620981836c13d0dd9d695447269276a13453
4
+ data.tar.gz: ad7effc41c7349b011fc386ec4195d1d2dd86d51af68317d4034ad9538a9d31f
5
5
  SHA512:
6
- metadata.gz: 7c4eea37fd0a7002bf6fd2a68b0a5c299bf84b43a71933053fb6cf2df59aedc17836e3299e809d1e5889130ccfcbd6ec19a276ffda556da8cf58b9e28042e211
7
- data.tar.gz: c56a875c0547d3456958a5cbd9b8d3ec7d4e921033d176c2b2835b80657b59d9d207e9dc0342564506f89d9cee313168427e906e9d6249f6298a0e90d6a9a5cf
6
+ metadata.gz: 53a725b8f4e8404f579b4b87b7d434c6146fa054f80a67147a2df2477102ecd3f7ca32db9b8827a5f00bfd1c51c0c70ec2c1575747c8a53716f682111a6d1c23
7
+ data.tar.gz: a0c8940990d04ff038e1ecdc4f1505a8b27f4ca87232daf08daecc9263f0a34c0cb8f14fee896d254a729102cc01d1cb7bb127f754c3ad9119b6bfb39c1487e7
data/README.md CHANGED
@@ -24,7 +24,13 @@ $ gem install echspec
24
24
 
25
25
  ```sh-session
26
26
  $ echspec --help
27
- Usage: echspec [OPTIONS] <HOSTNAME>
27
+ Usage: echspec {SUBCOMMAND}
28
+
29
+ Available subcommands: run, gen_configs, version, help.
30
+ ```
31
+ ```sh-session
32
+ $ echspec run --help
33
+ Usage: echspec run [OPTIONS...] {HOSTNAME}
28
34
  -f, --file FILE path to ECHConfigs PEM file (default resolve ECHConfigs via DNS)
29
35
  -p, --port VALUE server port number (default 443)
30
36
  -n, --not-force-compliant-hpke not force compliant ECHConfig HPKE cipher suite
@@ -35,7 +41,7 @@ Usage: echspec [OPTIONS] <HOSTNAME>
35
41
  You can run it the following:
36
42
 
37
43
  ```sh-session
38
- $ echspec research.cloudflare.com
44
+ $ echspec run research.cloudflare.com
39
45
  TLS Encrypted Client Hello Server
40
46
  ✔ MUST implement the following HPKE cipher suite: KEM: DHKEM(X25519, HKDF-SHA256), KDF: HKDF-SHA256 and AEAD: AES-128-GCM. [9]
41
47
  ✔ MUST abort with an "illegal_parameter" alert, if EncodedClientHelloInner is padded with non-zero values. [5.1-9]
@@ -63,7 +69,7 @@ Failures:
63
69
  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:
64
70
 
65
71
  ```sh-session
66
- $ echspec -f fixtures/echconfigs.pem -p 4433 localhost
72
+ $ echspec run -f fixtures/echconfigs.pem -p 4433 localhost
67
73
  ```
68
74
 
69
75
  By default, `echspec` uses the following HPKE cipher suite
@@ -78,13 +84,13 @@ By default, `echspec` uses the following HPKE cipher suite
78
84
  Using the `-n` or `--not-force-compliant-hpke`, you can not enforce the HPKE cipher suite.
79
85
 
80
86
  ```sh-session
81
- $ echspec -f fixtures/echconfigs.pem -p 4433 -n localhost
87
+ $ echspec run -f fixtures/echconfigs.pem -p 4433 -n localhost
82
88
  ```
83
89
 
84
90
  If you specify the SECTIONS, you can run only SECTIONS the following:
85
91
 
86
92
  ```sh-session
87
- $ echspec -f fixtures/echconfigs.pem -p 4433 -n -s 7.1.1-2,7.1.1-5 localhost
93
+ $ echspec run -f fixtures/echconfigs.pem -p 4433 -n -s 7.1.1-2,7.1.1-5 localhost
88
94
  TLS Encrypted Client Hello Server
89
95
  ✔ MUST abort with a "missing_extension" alert, if 2nd ClientHelloOuter does not contains the "encrypted_client_hello" extension. [7.1.1-2]
90
96
  ✔ MUST abort with an "illegal_parameter" alert, if 2nd ClientHelloOuter "encrypted_client_hello" enc is empty. [7.1.1-2]
@@ -94,7 +100,7 @@ TLS Encrypted Client Hello Server
94
100
  Using the `-v` or `--verbose` option provides a message stack if an error occurs. The message stack is formatted as JSON.
95
101
 
96
102
  ```sh-session
97
- $ echspec -s 7-5 -v research.cloudflare.com 2>&1 > /dev/null | jq .
103
+ $ echspec run -s 7-5 -v research.cloudflare.com 2>&1 > /dev/null | jq .
98
104
  ````
99
105
 
100
106
  <details>
data/Rakefile CHANGED
@@ -1,49 +1,8 @@
1
1
  require 'bundler/gem_tasks'
2
- require 'ech_config'
3
- require 'hpke'
4
- require 'openssl'
5
2
  require 'rspec/core/rake_task'
6
3
  require 'rubocop/rake_task'
7
4
 
8
5
  RuboCop::RakeTask.new
9
6
  RSpec::Core::RakeTask.new(:spec)
10
7
 
11
- TMP_DIR = "#{__dir__}/tmp".freeze
12
- ECHCONFIGS = "#{TMP_DIR}/echconfigs.pem".freeze
13
-
14
- directory TMP_DIR
15
-
16
- file ECHCONFIGS => TMP_DIR do
17
- puts "generate #{ECHCONFIGS}..."
18
-
19
- key = OpenSSL::PKey.generate_key('X25519')
20
- echconfigs = ECHConfigList.new(
21
- [
22
- ECHConfig.new(
23
- "\xfe\x0d".b,
24
- ECHConfig::ECHConfigContents.new(
25
- ECHConfig::ECHConfigContents::HpkeKeyConfig.new(
26
- 123,
27
- ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeKemId.new(HPKE::DHKEM_X25519_HKDF_SHA256),
28
- ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkePublicKey.new(key.raw_public_key),
29
- [
30
- ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite.new(
31
- ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite::HpkeKdfId.new(HPKE::HKDF_SHA256),
32
- ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite::HpkeAeadId.new(HPKE::AES_128_GCM)
33
- )
34
- ]
35
- ),
36
- 32,
37
- 'localhost'.b,
38
- ECHConfig::ECHConfigContents::Extensions.new('')
39
- )
40
- )
41
- ]
42
- )
43
- File.write(ECHCONFIGS, key.private_to_pem + echconfigs.to_pem)
44
- end
45
-
46
- desc 'generate echconfigs file'
47
- task gen_echconfigs: ECHCONFIGS
48
-
49
8
  task default: %i[rubocop spec]
Binary file
data/exe/echspec CHANGED
@@ -4,4 +4,4 @@ $LOAD_PATH << "#{__dir__}/../lib"
4
4
 
5
5
  require 'echspec'
6
6
 
7
- EchSpec::CLI.new.run
7
+ EchSpec::CLI.new.execute
@@ -0,0 +1,61 @@
1
+ module EchSpec
2
+ class CLI
3
+ class GenConfigs
4
+ def execute(argv)
5
+ fpath = parse_options(argv)
6
+ write(fpath)
7
+ end
8
+
9
+ def parse_options(argv)
10
+ op = OptionParser.new
11
+
12
+ op.banner = 'Usage: echspec gen_configs {FILE}'
13
+
14
+ begin
15
+ args = op.parse(argv)
16
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
17
+ warn op
18
+ warn "** #{e.message}"
19
+ exit 1
20
+ end
21
+
22
+ if args.length != 1
23
+ warn op
24
+ warn '** {FILE} argument is not specified'
25
+ exit 1
26
+ end
27
+ args[0]
28
+ end
29
+
30
+ def write(fpath)
31
+ hostname = 'localhost'
32
+
33
+ key = OpenSSL::PKey.generate_key('X25519')
34
+ echconfigs = ECHConfigList.new(
35
+ [
36
+ ECHConfig.new(
37
+ "\xfe\x0d".b,
38
+ ECHConfig::ECHConfigContents.new(
39
+ ECHConfig::ECHConfigContents::HpkeKeyConfig.new(
40
+ 123,
41
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeKemId.new(HPKE::DHKEM_X25519_HKDF_SHA256),
42
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkePublicKey.new(key.raw_public_key),
43
+ [
44
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite.new(
45
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite::HpkeKdfId.new(HPKE::HKDF_SHA256),
46
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite::HpkeAeadId.new(HPKE::AES_128_GCM)
47
+ )
48
+ ]
49
+ ),
50
+ 32,
51
+ hostname.b,
52
+ ECHConfig::ECHConfigContents::Extensions.new('')
53
+ )
54
+ )
55
+ ]
56
+ )
57
+ File.write(fpath, key.private_to_pem + echconfigs.to_pem)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,100 @@
1
+ module EchSpec
2
+ class CLI
3
+ class Run
4
+ def execute(argv)
5
+ fpath, port, force_compliant, verbose, hostname, sections = parse_options(argv)
6
+
7
+ if sections.nil?
8
+ Spec.run(fpath, port, hostname, force_compliant, verbose)
9
+ else
10
+ Spec.run_only(fpath, port, hostname, sections, verbose)
11
+ end
12
+ end
13
+
14
+ # rubocop: disable Metrics/AbcSize
15
+ # rubocop: disable Metrics/MethodLength
16
+ def parse_options(argv)
17
+ op = OptionParser.new
18
+
19
+ # default value
20
+ fpath = nil
21
+ port = 443
22
+ force_compliant = true
23
+ verbose = false
24
+ sections = nil
25
+
26
+ op.on(
27
+ '-f',
28
+ '--file FILE',
29
+ 'path to ECHConfigs PEM file (default resolve ECHConfigs via DNS)'
30
+ ) do |v|
31
+ fpath = v
32
+ end
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.on(
43
+ '-n',
44
+ '--not-force-compliant-hpke',
45
+ 'not force compliant ECHConfig HPKE cipher suite'
46
+ ) do
47
+ force_compliant = false
48
+ end
49
+
50
+ op.on(
51
+ '-v',
52
+ '--verbose',
53
+ 'verbose mode; prints message stack if raised an error'
54
+ ) do
55
+ verbose = true
56
+ end
57
+
58
+ op.on(
59
+ '-s',
60
+ '--sections SECTIONS',
61
+ 'sections to test; by the default, test all sections'
62
+ ) do |v|
63
+ sections = v.split(',')
64
+ end
65
+
66
+ op.banner = 'Usage: echspec run [OPTIONS...] {HOSTNAME}'
67
+
68
+ begin
69
+ args = op.parse(argv)
70
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
71
+ warn op
72
+ warn "** #{e.message}"
73
+ exit 1
74
+ end
75
+
76
+ if !fpath.nil? && !File.exist?(fpath)
77
+ warn '** {FILE} is not found'
78
+ exit 1
79
+ end
80
+
81
+ unknowns = sections.nil? ? [] : sections - Spec.sections
82
+ unless unknowns.empty?
83
+ warn "** #{unknowns} are unknown sections"
84
+ exit 1
85
+ end
86
+
87
+ if args.length != 1
88
+ warn op
89
+ warn '** {HOSTNAME} argument is not specified'
90
+ exit 1
91
+ end
92
+ hostname = args[0]
93
+
94
+ [fpath, port, force_compliant, verbose, hostname, sections]
95
+ end
96
+ # rubocop: enable Metrics/AbcSize
97
+ # rubocop: enable Metrics/MethodLength
98
+ end
99
+ end
100
+ end
data/lib/echspec/cli.rb CHANGED
@@ -1,96 +1,33 @@
1
+ require_relative 'cli/gen_configs'
2
+ require_relative 'cli/run'
3
+
1
4
  module EchSpec
2
5
  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
6
+ using Refinements
63
7
 
64
- if !fpath.nil? && !File.exist?(fpath)
65
- warn '** <FILE> is not found'
66
- exit 1
67
- end
8
+ def execute(argv = ARGV)
9
+ subcommands = %i[run gen_configs]
68
10
 
69
- unknowns = sections.nil? ? [] : sections - Spec.sections
70
- unless unknowns.empty?
71
- warn "** #{unknowns} are unknown sections"
72
- exit 1
73
- end
11
+ op = OptionParser.new
74
12
 
75
- if args.length != 1
76
- warn op
77
- warn '** <HOSTNAME> argument is not specified'
78
- exit 1
79
- end
80
- hostname = args[0]
13
+ op.banner = <<~USAGE
14
+ Usage: echspec {SUBCOMMAND}
81
15
 
82
- [fpath, port, force_compliant, verbose, hostname, sections]
83
- end
84
- # rubocop: enable Metrics/AbcSize
85
- # rubocop: enable Metrics/MethodLength
16
+ Available subcommands: #{subcommands.join(', ')}, version, help.
17
+ USAGE
86
18
 
87
- def run
88
- fpath, port, force_compliant, verbose, hostname, sections = parse_options
19
+ op.version = EchSpec::VERSION
20
+ op.order!(argv)
89
21
 
90
- if sections.nil?
91
- Spec.run(fpath, port, hostname, force_compliant, verbose)
22
+ subcommand = argv.shift
23
+ case subcommand&.to_sym
24
+ when :version
25
+ puts EchSpec::VERSION
26
+ when *subcommands
27
+ klass = self.class.const_get(subcommand.to_camel)
28
+ klass.new.__send__(:execute, argv)
92
29
  else
93
- Spec.run_only(fpath, port, hostname, sections, verbose)
30
+ puts op
94
31
  end
95
32
  end
96
33
  end
data/lib/echspec/utils.rb CHANGED
@@ -20,6 +20,10 @@ module EchSpec
20
20
  def yellow
21
21
  colorize(33)
22
22
  end
23
+
24
+ def to_camel
25
+ gsub(/(?:^|_)(.)/) { Regexp.last_match(1).upcase }
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -1,3 +1,3 @@
1
1
  module EchSpec
2
- VERSION = '0.0.3'.freeze
2
+ VERSION = '0.0.4'.freeze
3
3
  end
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.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - thekuwayama
@@ -101,6 +101,8 @@ files:
101
101
  - fixtures/server.key
102
102
  - lib/echspec.rb
103
103
  - lib/echspec/cli.rb
104
+ - lib/echspec/cli/gen_configs.rb
105
+ - lib/echspec/cli/run.rb
104
106
  - lib/echspec/error.rb
105
107
  - lib/echspec/log.rb
106
108
  - lib/echspec/result.rb