echspec 0.0.1 → 0.0.3
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 +4 -4
- data/.rubocop.yml +2 -1
- data/.ruby-version +1 -1
- data/Gemfile +2 -1
- data/README.md +11 -3
- data/Rakefile +41 -0
- data/docs/echspec-demo.png +0 -0
- data/echspec.gemspec +8 -4
- data/lib/echspec/log.rb +4 -0
- data/lib/echspec/spec/5.1-10.rb +6 -6
- data/lib/echspec/spec/5.1-9.rb +5 -5
- data/lib/echspec/spec/7-5.rb +7 -3
- data/lib/echspec/spec/7.1-11.rb +1 -1
- data/lib/echspec/spec/7.1-14.2.1.rb +5 -10
- data/lib/echspec/spec/7.1.1-2.rb +2 -2
- data/lib/echspec/spec/7.1.1-5.rb +3 -3
- data/lib/echspec/spec/9.rb +9 -9
- data/lib/echspec/spec.rb +50 -28
- data/lib/echspec/tls13_client.rb +2 -2
- data/lib/echspec/utils.rb +4 -0
- data/lib/echspec/version.rb +1 -1
- data/lib/echspec.rb +11 -10
- metadata +21 -20
- data/.github/workflows/ci.yml +0 -30
- data/.gitignore +0 -17
- data/spec/9_spec.rb +0 -13
- data/spec/log_spec.rb +0 -58
- data/spec/spec_helper.rb +0 -12
- data/spec/with_socket_spec.rb +0 -77
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ff77e42c8a7e2d787372fe6f96f3c00619fb6bcb3feea8df89c5400a838aef6
|
|
4
|
+
data.tar.gz: d317448071982052658909b19a99f535ce99e1c9f063de65be9cecae1ab4179f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c4eea37fd0a7002bf6fd2a68b0a5c299bf84b43a71933053fb6cf2df59aedc17836e3299e809d1e5889130ccfcbd6ec19a276ffda556da8cf58b9e28042e211
|
|
7
|
+
data.tar.gz: c56a875c0547d3456958a5cbd9b8d3ec7d4e921033d176c2b2835b80657b59d9d207e9dc0342564506f89d9cee313168427e906e9d6249f6298a0e90d6a9a5cf
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
4.0.1
|
data/Gemfile
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
source 'https://rubygems.org'
|
|
2
2
|
|
|
3
3
|
gem 'base64'
|
|
4
|
+
gem 'ech_config', github: 'thekuwayama/ech_config'
|
|
4
5
|
gem 'resolv', '> 0.4.0'
|
|
5
6
|
gem 'tttls1.3', github: 'thekuwayama/tttls1.3'
|
|
6
7
|
|
|
7
8
|
group :development do
|
|
8
9
|
gem 'rake', '13.2.1'
|
|
9
10
|
gem 'rspec'
|
|
10
|
-
gem 'rubocop', '1.
|
|
11
|
+
gem 'rubocop', '1.82.1'
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
gemspec
|
data/README.md
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
- https://datatracker.ietf.org/doc/html/
|
|
11
|
+
- https://datatracker.ietf.org/doc/html/rfc9849
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
##
|
|
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
|
-
|
|
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/rfc9849#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:
|
data/Rakefile
CHANGED
|
@@ -1,8 +1,49 @@
|
|
|
1
1
|
require 'bundler/gem_tasks'
|
|
2
|
+
require 'ech_config'
|
|
3
|
+
require 'hpke'
|
|
4
|
+
require 'openssl'
|
|
2
5
|
require 'rspec/core/rake_task'
|
|
3
6
|
require 'rubocop/rake_task'
|
|
4
7
|
|
|
5
8
|
RuboCop::RakeTask.new
|
|
6
9
|
RSpec::Core::RakeTask.new(:spec)
|
|
7
10
|
|
|
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
|
+
|
|
8
49
|
task default: %i[rubocop spec]
|
data/docs/echspec-demo.png
CHANGED
|
Binary file
|
data/echspec.gemspec
CHANGED
|
@@ -11,16 +11,20 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.description = spec.summary
|
|
12
12
|
spec.homepage = 'https://github.com/thekuwayama/echspec'
|
|
13
13
|
spec.license = 'MIT'
|
|
14
|
-
spec.required_ruby_version = '>=
|
|
14
|
+
spec.required_ruby_version = '>=4.0'
|
|
15
15
|
|
|
16
|
-
spec.files
|
|
17
|
-
|
|
16
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
17
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
18
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
19
|
+
end
|
|
20
|
+
end
|
|
18
21
|
spec.require_paths = ['lib']
|
|
19
22
|
spec.bindir = 'exe'
|
|
20
23
|
spec.executables = ['echspec']
|
|
21
24
|
|
|
22
25
|
spec.add_development_dependency 'bundler'
|
|
23
26
|
spec.add_dependency 'base64'
|
|
27
|
+
spec.add_dependency 'ech_config', '~> 0.0.4'
|
|
24
28
|
spec.add_dependency 'resolv', '> 0.4.0'
|
|
25
|
-
spec.add_dependency 'tttls1.3', '~> 0.3.
|
|
29
|
+
spec.add_dependency 'tttls1.3', '~> 0.3.6'
|
|
26
30
|
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
|
data/lib/echspec/spec/5.1-10.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module EchSpec
|
|
2
2
|
module Spec
|
|
3
3
|
class Spec5_1_10 < WithSocket
|
|
4
|
-
# Next it makes a copy of the client_hello field and copies the
|
|
4
|
+
# Next, it makes a copy of the client_hello field and copies the
|
|
5
5
|
# legacy_session_id field from ClientHelloOuter. It then looks for an
|
|
6
6
|
# "ech_outer_extensions" extension. If found, it replaces the extension
|
|
7
7
|
# with the corresponding sequence of extensions in the
|
|
@@ -14,7 +14,7 @@ module EchSpec
|
|
|
14
14
|
# * The extensions in ClientHelloOuter corresponding to those in
|
|
15
15
|
# OuterExtensions do not occur in the same order.
|
|
16
16
|
#
|
|
17
|
-
# https://datatracker.ietf.org/doc/html/
|
|
17
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-5.1-10
|
|
18
18
|
|
|
19
19
|
# @return [EchSpec::SpecGroup]
|
|
20
20
|
def self.spec_group
|
|
@@ -130,7 +130,7 @@ module EchSpec
|
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
class MissingReferencedExtensions < TTTLS13::Message::Extensions
|
|
133
|
-
# @param _ [Array
|
|
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
|
|
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
|
|
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
|
|
197
|
+
# @param _ [Array<TTTLS13::Message::ExtensionType>]
|
|
198
198
|
#
|
|
199
199
|
# @return [TTTLS13::Message::Extensions] for EncodedClientHelloInner
|
|
200
200
|
def remove_and_replace!(_)
|
data/lib/echspec/spec/5.1-9.rb
CHANGED
|
@@ -2,12 +2,12 @@ module EchSpec
|
|
|
2
2
|
module Spec
|
|
3
3
|
class Spec5_1_9 < WithSocket
|
|
4
4
|
# The client-facing server computes ClientHelloInner by reversing this
|
|
5
|
-
# process. First it parses EncodedClientHelloInner, interpreting all
|
|
6
|
-
# bytes after client_hello as padding. If any padding byte is non-
|
|
7
|
-
#
|
|
8
|
-
#
|
|
5
|
+
# process. First, it parses EncodedClientHelloInner, interpreting all
|
|
6
|
+
# bytes after client_hello as padding. If any padding byte is non-zero,
|
|
7
|
+
# the server MUST abort the connection with an "illegal_parameter"
|
|
8
|
+
# alert.
|
|
9
9
|
#
|
|
10
|
-
# https://datatracker.ietf.org/doc/html/
|
|
10
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-5.1-9
|
|
11
11
|
|
|
12
12
|
# @return [EchSpec::SpecGroup]
|
|
13
13
|
def self.spec_group
|
data/lib/echspec/spec/7-5.rb
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
module EchSpec
|
|
2
2
|
module Spec
|
|
3
3
|
class Spec7_5 < WithSocket
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# In shared mode, a server plays both roles, first decrypting the
|
|
5
|
+
# ClientHelloOuter and then using the contents of the ClientHelloInner.
|
|
6
|
+
# A shared mode server which receives a ClientHello with
|
|
7
|
+
# ECHClientHello.type of inner MUST abort with an "illegal_parameter"
|
|
8
|
+
# alert, because such a ClientHello should never be received directly
|
|
9
|
+
# from the network.
|
|
6
10
|
#
|
|
7
|
-
# https://datatracker.ietf.org/doc/html/
|
|
11
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-7-5
|
|
8
12
|
|
|
9
13
|
# @return [EchSpec::SpecGroup]
|
|
10
14
|
def self.spec_group
|
data/lib/echspec/spec/7.1-11.rb
CHANGED
|
@@ -7,7 +7,7 @@ module EchSpec
|
|
|
7
7
|
# offer TLS 1.2 or below. If either of these checks fails, the client-
|
|
8
8
|
# facing server MUST abort with an "illegal_parameter" alert.
|
|
9
9
|
#
|
|
10
|
-
# https://datatracker.ietf.org/doc/html/
|
|
10
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-7.1-11
|
|
11
11
|
|
|
12
12
|
# @return [SpecGroup]
|
|
13
13
|
def self.spec_group
|
|
@@ -3,8 +3,8 @@ module EchSpec
|
|
|
3
3
|
class Spec7_1_14_2_1 < WithSocket
|
|
4
4
|
# Otherwise, if all candidate ECHConfig values fail to decrypt the
|
|
5
5
|
# extension, the client-facing server MUST ignore the extension and
|
|
6
|
-
# proceed with the connection using ClientHelloOuter
|
|
7
|
-
#
|
|
6
|
+
# proceed with the connection using ClientHelloOuter with the following
|
|
7
|
+
# modifications:
|
|
8
8
|
#
|
|
9
9
|
# * If the server is configured with any ECHConfigs, it MUST include
|
|
10
10
|
# the "encrypted_client_hello" extension in its EncryptedExtensions
|
|
@@ -13,7 +13,7 @@ module EchSpec
|
|
|
13
13
|
# ECHConfig values of different versions. This allows a server to
|
|
14
14
|
# support multiple versions at once.
|
|
15
15
|
#
|
|
16
|
-
# https://datatracker.ietf.org/doc/html/
|
|
16
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-7.1-14.2.1
|
|
17
17
|
|
|
18
18
|
# @return [EchSpec::SpecGroup]
|
|
19
19
|
def self.spec_group
|
|
@@ -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,
|
|
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
|
)
|
data/lib/echspec/spec/7.1.1-2.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module EchSpec
|
|
2
2
|
module Spec
|
|
3
3
|
class Spec7_1_1_2 < WithSocket
|
|
4
|
-
# If the client-facing server accepted ECH, it checks the second
|
|
4
|
+
# If the client-facing server accepted ECH, it checks that the second
|
|
5
5
|
# ClientHelloOuter also contains the "encrypted_client_hello"
|
|
6
6
|
# extension. If not, it MUST abort the handshake with a
|
|
7
7
|
# "missing_extension" alert. Otherwise, it checks that
|
|
@@ -9,7 +9,7 @@ module EchSpec
|
|
|
9
9
|
# unchanged, and that ECHClientHello.enc is empty. If not, it MUST
|
|
10
10
|
# abort the handshake with an "illegal_parameter" alert.
|
|
11
11
|
#
|
|
12
|
-
# https://datatracker.ietf.org/doc/html/
|
|
12
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-7.1.1-2
|
|
13
13
|
|
|
14
14
|
# @return [EchSpec::SpecGroup]
|
|
15
15
|
def self.spec_group
|
data/lib/echspec/spec/7.1.1-5.rb
CHANGED
|
@@ -5,10 +5,10 @@ module EchSpec
|
|
|
5
5
|
# using the second ClientHelloOuter. If decryption fails, the client-
|
|
6
6
|
# facing server MUST abort the handshake with a "decrypt_error" alert.
|
|
7
7
|
# Otherwise, it reconstructs the second ClientHelloInner from the new
|
|
8
|
-
# EncodedClientHelloInner as described in Section 5.1, using the
|
|
9
|
-
#
|
|
8
|
+
# EncodedClientHelloInner as described in Section 5.1, using the second
|
|
9
|
+
# ClientHelloOuter for any referenced extensions.
|
|
10
10
|
#
|
|
11
|
-
# https://datatracker.ietf.org/doc/html/
|
|
11
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-7.1.1-5
|
|
12
12
|
|
|
13
13
|
# @return [EchSpec::SpecGroup]
|
|
14
14
|
def self.spec_group
|
data/lib/echspec/spec/9.rb
CHANGED
|
@@ -9,7 +9,7 @@ module EchSpec
|
|
|
9
9
|
# * KDF: HKDF-SHA256 (see Section 7.2 of [HPKE])
|
|
10
10
|
# * AEAD: AES-128-GCM (see Section 7.3 of [HPKE])
|
|
11
11
|
#
|
|
12
|
-
# https://datatracker.ietf.org/doc/html/
|
|
12
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-9
|
|
13
13
|
@section = '9'
|
|
14
14
|
@description = 'MUST implement the following HPKE cipher suite: KEM: DHKEM(X25519, HKDF-SHA256), KDF: HKDF-SHA256 and AEAD: AES-128-GCM.'
|
|
15
15
|
class << self
|
|
@@ -41,15 +41,15 @@ module EchSpec
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
# @param ech_configs [Array
|
|
44
|
+
# @param ech_configs [Array<ECHConfig>]
|
|
45
45
|
#
|
|
46
|
-
# @return [EchSpec::Ok
|
|
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
|
|
50
|
-
valid_kem_id = kconfig.kem_id.uint16 ==
|
|
50
|
+
valid_kem_id = kconfig.kem_id.uint16 == HPKE::DHKEM_X25519_HKDF_SHA256
|
|
51
51
|
valid_cipher_suite = kconfig.cipher_suites.any? do |cs|
|
|
52
|
-
cs.kdf_id.uint16 ==
|
|
52
|
+
cs.kdf_id.uint16 == HPKE::HKDF_SHA256 && cs.aead_id.uint16 == HPKE::AES_128_GCM
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
valid_kem_id && valid_cipher_suite
|
|
@@ -61,7 +61,7 @@ module EchSpec
|
|
|
61
61
|
|
|
62
62
|
# @param hostname [String]
|
|
63
63
|
#
|
|
64
|
-
# @return [EchSpec::Ok<Array
|
|
64
|
+
# @return [EchSpec::Ok<Array<ECHConfig>> | Err]
|
|
65
65
|
def resolve_ech_configs(hostname)
|
|
66
66
|
begin
|
|
67
67
|
rr = Resolv::DNS.new.getresource(
|
|
@@ -72,7 +72,7 @@ module EchSpec
|
|
|
72
72
|
return Err.new(e.message, nil)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
# https://datatracker.ietf.org/doc/html/
|
|
75
|
+
# https://datatracker.ietf.org/doc/html/rfc9934#section-3
|
|
76
76
|
ech = 5
|
|
77
77
|
return Err.new("HTTPS resource record for #{hostname} does NOT have ech SvcParams.", nil) if rr.params[ech].nil?
|
|
78
78
|
|
|
@@ -85,7 +85,7 @@ module EchSpec
|
|
|
85
85
|
|
|
86
86
|
# @param pem [String]
|
|
87
87
|
#
|
|
88
|
-
# @return [EchSpec::Ok<Array
|
|
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
|
|
@@ -95,7 +95,7 @@ module EchSpec
|
|
|
95
95
|
ech_configs = ECHConfig.decode_vectors(b.slice(2..))
|
|
96
96
|
Ok.new(ech_configs)
|
|
97
97
|
rescue StandardError
|
|
98
|
-
# https://datatracker.ietf.org/doc/html/
|
|
98
|
+
# https://datatracker.ietf.org/doc/html/rfc9934#section-3
|
|
99
99
|
example = <<~PEM
|
|
100
100
|
-----BEGIN PRIVATE KEY-----
|
|
101
101
|
MC4CAQAwBQYDK2VuBCIEICjd4yGRdsoP9gU7YT7My8DHx1Tjme8GYDXrOMCi8v1V
|
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
|
-
|
|
15
|
+
ResultDescURL = Struct.new(:result, :desc, :url)
|
|
16
16
|
|
|
17
|
-
# @param
|
|
17
|
+
# @param rdus [Array<ResultDescURL>] result: EchSpec::Ok | Err, desc: String, url: URI
|
|
18
18
|
# @param verbose [Boolean]
|
|
19
|
-
def print_results(
|
|
20
|
-
|
|
21
|
-
failures =
|
|
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 { |
|
|
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
|
-
"
|
|
39
|
+
"#{check} #{desc}".green.indent
|
|
40
40
|
in Err
|
|
41
|
-
"
|
|
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 "
|
|
52
|
-
puts
|
|
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
|
|
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
|
|
124
|
+
# @param targets [Array<EchSpec::SpecGroup>]
|
|
123
125
|
# @param verbose [Boolean]
|
|
124
126
|
def do_run(port, hostname, ech_config, targets, verbose)
|
|
125
|
-
|
|
127
|
+
rdus = targets.flat_map do |g|
|
|
126
128
|
g.spec_cases.map do |sc|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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(
|
|
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/rfc9849#section-#{section}"
|
|
134
152
|
end
|
|
135
153
|
|
|
136
154
|
# @param fpath [String | NilClass]
|
|
@@ -139,24 +157,28 @@ module EchSpec
|
|
|
139
157
|
#
|
|
140
158
|
# @return [ECHConfig]
|
|
141
159
|
def try_get_ech_config(fpath, hostname, force_compliant)
|
|
142
|
-
# https://datatracker.ietf.org/doc/html/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
160
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-9
|
|
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
|
-
|
|
172
|
+
print_results([ResultDescURL.new(result:, desc:, url:)], true)
|
|
151
173
|
exit 1
|
|
152
174
|
end
|
|
153
175
|
end
|
|
154
176
|
|
|
155
177
|
def spec_groups
|
|
156
|
-
# https://datatracker.ietf.org/doc/html/
|
|
178
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-5
|
|
157
179
|
groups = [Spec5_1_9, Spec5_1_10]
|
|
158
180
|
|
|
159
|
-
# https://datatracker.ietf.org/doc/html/
|
|
181
|
+
# https://datatracker.ietf.org/doc/html/rfc9849#section-7
|
|
160
182
|
groups += [Spec7_5, Spec7_1_11, Spec7_1_14_2_1, Spec7_1_1_2, Spec7_1_1_5]
|
|
161
183
|
|
|
162
184
|
groups.map(&:spec_group)
|
data/lib/echspec/tls13_client.rb
CHANGED
|
@@ -66,12 +66,12 @@ module EchSpec
|
|
|
66
66
|
exs << TTTLS13::Message::Extension::SupportedGroups.new(groups)
|
|
67
67
|
|
|
68
68
|
# key_share
|
|
69
|
-
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,
|
|
74
|
+
[exs, shared_secret]
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# @param ch1 [TTTLS13::Message::ClientHello]
|
data/lib/echspec/utils.rb
CHANGED
data/lib/echspec/version.rb
CHANGED
data/lib/echspec.rb
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
require 'base64'
|
|
2
2
|
require 'optparse'
|
|
3
|
+
require 'pp'
|
|
3
4
|
require 'resolv'
|
|
4
5
|
require 'timeout'
|
|
5
6
|
require 'tttls1.3'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
require_relative 'echspec/version'
|
|
9
|
+
require_relative 'echspec/utils'
|
|
10
|
+
require_relative 'echspec/log'
|
|
11
|
+
require_relative 'echspec/error'
|
|
12
|
+
require_relative 'echspec/result'
|
|
13
|
+
require_relative 'echspec/tls13_client'
|
|
14
|
+
require_relative 'echspec/spec_case'
|
|
15
|
+
require_relative 'echspec/spec_group'
|
|
16
|
+
require_relative 'echspec/spec'
|
|
17
|
+
require_relative 'echspec/cli'
|
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.
|
|
4
|
+
version: 0.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- thekuwayama
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: bundler
|
|
@@ -38,6 +37,20 @@ dependencies:
|
|
|
38
37
|
- - ">="
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: ech_config
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 0.0.4
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 0.0.4
|
|
41
54
|
- !ruby/object:Gem::Dependency
|
|
42
55
|
name: resolv
|
|
43
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,14 +71,14 @@ dependencies:
|
|
|
58
71
|
requirements:
|
|
59
72
|
- - "~>"
|
|
60
73
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: 0.3.
|
|
74
|
+
version: 0.3.6
|
|
62
75
|
type: :runtime
|
|
63
76
|
prerelease: false
|
|
64
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
78
|
requirements:
|
|
66
79
|
- - "~>"
|
|
67
80
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 0.3.
|
|
81
|
+
version: 0.3.6
|
|
69
82
|
description: A conformance testing tool for ECH implementation
|
|
70
83
|
email:
|
|
71
84
|
- thekuwayama@gmail.com
|
|
@@ -74,8 +87,6 @@ executables:
|
|
|
74
87
|
extensions: []
|
|
75
88
|
extra_rdoc_files: []
|
|
76
89
|
files:
|
|
77
|
-
- ".github/workflows/ci.yml"
|
|
78
|
-
- ".gitignore"
|
|
79
90
|
- ".rubocop.yml"
|
|
80
91
|
- ".ruby-version"
|
|
81
92
|
- Gemfile
|
|
@@ -107,15 +118,10 @@ files:
|
|
|
107
118
|
- lib/echspec/tls13_client.rb
|
|
108
119
|
- lib/echspec/utils.rb
|
|
109
120
|
- lib/echspec/version.rb
|
|
110
|
-
- spec/9_spec.rb
|
|
111
|
-
- spec/log_spec.rb
|
|
112
|
-
- spec/spec_helper.rb
|
|
113
|
-
- spec/with_socket_spec.rb
|
|
114
121
|
homepage: https://github.com/thekuwayama/echspec
|
|
115
122
|
licenses:
|
|
116
123
|
- MIT
|
|
117
124
|
metadata: {}
|
|
118
|
-
post_install_message:
|
|
119
125
|
rdoc_options: []
|
|
120
126
|
require_paths:
|
|
121
127
|
- lib
|
|
@@ -123,19 +129,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
123
129
|
requirements:
|
|
124
130
|
- - ">="
|
|
125
131
|
- !ruby/object:Gem::Version
|
|
126
|
-
version: '
|
|
132
|
+
version: '4.0'
|
|
127
133
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
134
|
requirements:
|
|
129
135
|
- - ">="
|
|
130
136
|
- !ruby/object:Gem::Version
|
|
131
137
|
version: '0'
|
|
132
138
|
requirements: []
|
|
133
|
-
rubygems_version:
|
|
134
|
-
signing_key:
|
|
139
|
+
rubygems_version: 4.0.7
|
|
135
140
|
specification_version: 4
|
|
136
141
|
summary: A conformance testing tool for ECH implementation
|
|
137
|
-
test_files:
|
|
138
|
-
- spec/9_spec.rb
|
|
139
|
-
- spec/log_spec.rb
|
|
140
|
-
- spec/spec_helper.rb
|
|
141
|
-
- spec/with_socket_spec.rb
|
|
142
|
+
test_files: []
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
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
DELETED
data/spec/9_spec.rb
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
require_relative 'spec_helper'
|
|
2
|
-
|
|
3
|
-
RSpec.describe EchSpec::Spec::Spec9 do
|
|
4
|
-
context 'parse_pem' do
|
|
5
|
-
let(:pem) do
|
|
6
|
-
File.open("#{__dir__}/../fixtures/echconfigs.pem").read
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
it 'could parse' do
|
|
10
|
-
expect(EchSpec::Spec::Spec9.send(:parse_pem, pem)).to be_a EchSpec::Ok
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
data/spec/log_spec.rb
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
require_relative 'spec_helper'
|
|
2
|
-
|
|
3
|
-
RSpec.describe EchSpec::Log::MessageStack do
|
|
4
|
-
context 'obj2json' do
|
|
5
|
-
let(:crt) do
|
|
6
|
-
File.open("#{__dir__}/../fixtures/server.crt").read
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
it 'should convert' do
|
|
10
|
-
expect(EchSpec::Log::MessageStack.obj2json(OpenSSL::X509::Certificate.new(crt)))
|
|
11
|
-
.to eq "#{crt.split("\n").join('\n')}\\n"
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
it 'should convert' do
|
|
15
|
-
expect(EchSpec::Log::MessageStack.obj2json(1)).to eq '1'
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it 'should convert' do
|
|
19
|
-
expect(EchSpec::Log::MessageStack.obj2json(0.1)).to eq '0.1'
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it 'should convert' do
|
|
23
|
-
expect(EchSpec::Log::MessageStack.obj2json(true)).to eq 'true'
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
it 'should convert' do
|
|
27
|
-
expect(EchSpec::Log::MessageStack.obj2json(false)).to eq 'false'
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it 'should convert' do
|
|
31
|
-
expect(EchSpec::Log::MessageStack.obj2json('')).to eq '""'
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
it 'should convert' do
|
|
35
|
-
expect(EchSpec::Log::MessageStack.obj2json('string')).to eq '"0x737472696e67"'
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
it 'should convert' do
|
|
39
|
-
expect(EchSpec::Log::MessageStack.obj2json(nil)).to eq 'null'
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
it 'should convert' do
|
|
43
|
-
expect(EchSpec::Log::MessageStack.obj2json([1, true, '', 'string', nil])).to eq '[1,true,"","0x737472696e67",null]'
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
it 'should convert' do
|
|
47
|
-
expect(EchSpec::Log::MessageStack.obj2json(1 => true, '' => 'string', nil => [])).to eq '{1:true,"":"0x737472696e67",null:[]}'
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
it 'should convert' do
|
|
51
|
-
expect(EchSpec::Log::MessageStack.obj2json(C.new('string'))).to eq '{"name":"0x737472696e67"}'
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it 'should convert' do
|
|
55
|
-
expect(EchSpec::Log::MessageStack.obj2json(D.new)).to eq '"$D"'
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
data/spec/spec_helper.rb
DELETED
data/spec/with_socket_spec.rb
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
require_relative 'spec_helper'
|
|
2
|
-
|
|
3
|
-
module EchSpec
|
|
4
|
-
module Spec
|
|
5
|
-
class SpecX < WithSocket
|
|
6
|
-
def validate(hostname, port)
|
|
7
|
-
with_socket(hostname, port) do |_socket|
|
|
8
|
-
# not return
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
class SpecY < WithSocket
|
|
14
|
-
def validate(hostname, port)
|
|
15
|
-
with_socket(hostname, port) do |_socket|
|
|
16
|
-
return EchSpec::Ok.new(1)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
class SpecZ < WithSocket
|
|
22
|
-
def validate(hostname, port)
|
|
23
|
-
with_socket(hostname, port) do |_socket|
|
|
24
|
-
msg = TTTLS13::Message::Alert.new(
|
|
25
|
-
level: TTTLS13::Message::AlertLevel::FATAL,
|
|
26
|
-
description: "\x0a"
|
|
27
|
-
)
|
|
28
|
-
return EchSpec::Err.new('details', [msg])
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
class SpecW < WithSocket
|
|
34
|
-
def validate(hostname, port)
|
|
35
|
-
with_socket(hostname, port) do |_socket|
|
|
36
|
-
raise EchSpec::Error::BeforeTargetSituationError, 'not received ClientHello'
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
RSpec.describe EchSpec::Spec::WithSocket do
|
|
44
|
-
context 'with_socket' do
|
|
45
|
-
before do
|
|
46
|
-
socket = StringIO.new
|
|
47
|
-
allow(TCPSocket).to receive(:new).and_return(socket)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
it 'should return nil' do
|
|
51
|
-
result = EchSpec::Spec::SpecX.new.validate('localhost', 4433)
|
|
52
|
-
expect(result).to eq nil
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it 'should return Ok(1)' do
|
|
56
|
-
result = EchSpec::Spec::SpecY.new.validate('localhost', 4433)
|
|
57
|
-
expect(result).to be_a EchSpec::Ok
|
|
58
|
-
expect(result.obj).to eq 1
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
it 'should return Err(details, message_stack)' do
|
|
62
|
-
result = EchSpec::Spec::SpecZ.new.validate('localhost', 4433)
|
|
63
|
-
expect(result).to be_a EchSpec::Err
|
|
64
|
-
expect(result.details).to eq 'details'
|
|
65
|
-
expect(result.message_stack.length).to be 1
|
|
66
|
-
expect(result.message_stack.first.level).to be TTTLS13::Message::AlertLevel::FATAL
|
|
67
|
-
expect(result.message_stack.first.description).to eq "\x0a"
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
it 'should return Err(details, message_stack), raised BeforeTargetSituationError' do
|
|
71
|
-
result = EchSpec::Spec::SpecW.new.validate('localhost', 4433)
|
|
72
|
-
expect(result).to be_a EchSpec::Err
|
|
73
|
-
expect(result.details).to eq 'not received ClientHello'
|
|
74
|
-
expect(result.message_stack).to eq '{}'
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|