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 +7 -0
- data/.github/workflows/ci.yml +30 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +36 -0
- data/.ruby-version +1 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +272 -0
- data/Rakefile +8 -0
- data/docs/echspec-demo.png +0 -0
- data/echspec.gemspec +26 -0
- data/exe/echspec +7 -0
- data/fixtures/echconfigs.pem +7 -0
- data/fixtures/server.crt +20 -0
- data/fixtures/server.key +27 -0
- data/lib/echspec/cli.rb +97 -0
- data/lib/echspec/error.rb +9 -0
- data/lib/echspec/log.rb +85 -0
- data/lib/echspec/result.rb +26 -0
- data/lib/echspec/spec/5.1-10.rb +222 -0
- data/lib/echspec/spec/5.1-9.rb +108 -0
- data/lib/echspec/spec/7-5.rb +242 -0
- data/lib/echspec/spec/7.1-11.rb +93 -0
- data/lib/echspec/spec/7.1-14.2.1.rb +133 -0
- data/lib/echspec/spec/7.1.1-2.rb +142 -0
- data/lib/echspec/spec/7.1.1-5.rb +83 -0
- data/lib/echspec/spec/9.rb +113 -0
- data/lib/echspec/spec.rb +170 -0
- data/lib/echspec/spec_case.rb +10 -0
- data/lib/echspec/spec_group.rb +10 -0
- data/lib/echspec/tls13_client.rb +167 -0
- data/lib/echspec/utils.rb +21 -0
- data/lib/echspec/version.rb +3 -0
- data/lib/echspec.rb +16 -0
- data/spec/9_spec.rb +13 -0
- data/spec/log_spec.rb +58 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/with_socket_spec.rb +77 -0
- metadata +141 -0
data/lib/echspec/spec.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
module EchSpec
|
2
|
+
module Spec
|
3
|
+
class << self
|
4
|
+
using Refinements
|
5
|
+
|
6
|
+
# @param msg [TTTLS13::Message::Record]
|
7
|
+
# @param desc [Symbol]
|
8
|
+
#
|
9
|
+
# @return [Boolean]
|
10
|
+
def expect_alert(msg, desc)
|
11
|
+
msg.is_a?(TTTLS13::Message::Alert) &&
|
12
|
+
msg.description == TTTLS13::Message::ALERT_DESCRIPTION[desc]
|
13
|
+
end
|
14
|
+
|
15
|
+
ResultDesc = Struct.new(:result, :desc)
|
16
|
+
|
17
|
+
# @param rds [Array of ResultDesc] result: EchSpec::Ok | Err, desc: String
|
18
|
+
# @param verbose [Boolean]
|
19
|
+
def print_results(rds, verbose)
|
20
|
+
rds.each { |rd| print_summary(rd.result, rd.desc) }
|
21
|
+
failures = rds.filter { |rd| rd.result.is_a? Err }
|
22
|
+
return if failures.empty?
|
23
|
+
|
24
|
+
puts
|
25
|
+
puts 'Failures:'
|
26
|
+
puts
|
27
|
+
failures.each
|
28
|
+
.with_index { |rd, idx| print_err_details(rd.result, idx, rd.desc, verbose) }
|
29
|
+
puts "#{failures.length} failure".red
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param result [EchSpec::Ok | Err]
|
33
|
+
# @param desc [String]
|
34
|
+
def print_summary(result, desc)
|
35
|
+
check = "\u2714"
|
36
|
+
cross = "\u0078"
|
37
|
+
summary = case result
|
38
|
+
in Ok
|
39
|
+
"\t#{check} #{desc}".green
|
40
|
+
in Err
|
41
|
+
"\t#{cross} #{desc}".red
|
42
|
+
end
|
43
|
+
puts summary
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param err [EchSpec::Err]
|
47
|
+
# @param idx [Integer]
|
48
|
+
# @param desc [String]
|
49
|
+
# @param verbose [Boolean]
|
50
|
+
def print_err_details(err, idx, desc, verbose)
|
51
|
+
puts "\t#{idx + 1}) #{desc}"
|
52
|
+
puts "\t\t#{err.details}"
|
53
|
+
warn err.message_stack if verbose && !err.message_stack.nil?
|
54
|
+
puts
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class WithSocket
|
59
|
+
def with_socket(hostname, port)
|
60
|
+
socket = TCPSocket.new(hostname, port)
|
61
|
+
yield(socket)
|
62
|
+
rescue Timeout::Error
|
63
|
+
Err.new("#{hostname}:#{port} connection timeout", message_stack)
|
64
|
+
rescue Errno::ECONNREFUSED
|
65
|
+
Err.new("#{hostname}:#{port} connection refused", message_stack)
|
66
|
+
rescue Error::BeforeTargetSituationError => e
|
67
|
+
Err.new(e.message, message_stack)
|
68
|
+
ensure
|
69
|
+
socket&.close
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize
|
73
|
+
@stack = Log::MessageStack.new
|
74
|
+
end
|
75
|
+
|
76
|
+
def message_stack
|
77
|
+
@stack.marshal
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
Dir["#{File.dirname(__FILE__)}/spec/*.rb"].sort.each { |f| require f }
|
84
|
+
|
85
|
+
module EchSpec
|
86
|
+
module Spec
|
87
|
+
class << self
|
88
|
+
using Refinements
|
89
|
+
|
90
|
+
# @param fpath [String | NilClass]
|
91
|
+
# @param port [Integer]
|
92
|
+
# @param hostname [String]
|
93
|
+
# @param force_compliant [Boolean]
|
94
|
+
# @param verbose [Boolean]
|
95
|
+
def run(fpath, port, hostname, force_compliant, verbose)
|
96
|
+
TTTLS13::Logging.logger.level = Logger::WARN
|
97
|
+
puts 'TLS Encrypted Client Hello Server'
|
98
|
+
ech_config = try_get_ech_config(fpath, hostname, force_compliant)
|
99
|
+
|
100
|
+
do_run(port, hostname, ech_config, spec_groups, verbose)
|
101
|
+
end
|
102
|
+
|
103
|
+
# @param fpath [String | NilClass]
|
104
|
+
# @param port [Integer]
|
105
|
+
# @param hostname [String]
|
106
|
+
# @param sections [Array of String]
|
107
|
+
# @param verbose [Boolean]
|
108
|
+
def run_only(fpath, port, hostname, sections, verbose)
|
109
|
+
targets = spec_groups.filter { |g| sections.include?(g.section) }
|
110
|
+
force_compliant = sections.include?(Spec9.section)
|
111
|
+
|
112
|
+
TTTLS13::Logging.logger.level = Logger::WARN
|
113
|
+
puts 'TLS Encrypted Client Hello Server'
|
114
|
+
ech_config = try_get_ech_config(fpath, hostname, force_compliant)
|
115
|
+
|
116
|
+
do_run(port, hostname, ech_config, targets, verbose)
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param port [Integer]
|
120
|
+
# @param hostname [String]
|
121
|
+
# @param ech_config [ECHConfig]
|
122
|
+
# @param targets [Array of EchSpec::SpecGroup]
|
123
|
+
# @param verbose [Boolean]
|
124
|
+
def do_run(port, hostname, ech_config, targets, verbose)
|
125
|
+
rds = targets.flat_map do |g|
|
126
|
+
g.spec_cases.map do |sc|
|
127
|
+
r = sc.method.call(hostname, port, ech_config)
|
128
|
+
d = "#{sc.description} [#{g.section}]"
|
129
|
+
ResultDesc.new(result: r, desc: d)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
print_results(rds, verbose)
|
134
|
+
end
|
135
|
+
|
136
|
+
# @param fpath [String | NilClass]
|
137
|
+
# @param hostname [String]
|
138
|
+
# @param force_compliant [Boolean]
|
139
|
+
#
|
140
|
+
# @return [ECHConfig]
|
141
|
+
def try_get_ech_config(fpath, hostname, force_compliant)
|
142
|
+
# https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22#section-9
|
143
|
+
case result = Spec9.try_get_ech_config(fpath, hostname, force_compliant)
|
144
|
+
in Ok(obj) if force_compliant
|
145
|
+
result.tap { |r| print_summary(r, "#{Spec9.description} [#{Spec9.section}]") }
|
146
|
+
obj
|
147
|
+
in Ok(obj)
|
148
|
+
obj
|
149
|
+
in Err(details, _)
|
150
|
+
puts "\t#{details}".red
|
151
|
+
exit 1
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def spec_groups
|
156
|
+
# https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22#section-5
|
157
|
+
groups = [Spec5_1_9, Spec5_1_10]
|
158
|
+
|
159
|
+
# https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-22#section-7
|
160
|
+
groups += [Spec7_5, Spec7_1_11, Spec7_1_14_2_1, Spec7_1_1_2, Spec7_1_1_5]
|
161
|
+
|
162
|
+
groups.map(&:spec_group)
|
163
|
+
end
|
164
|
+
|
165
|
+
def sections
|
166
|
+
(spec_groups + [Spec9]).map(&:section)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module EchSpec
|
2
|
+
module TLS13Client
|
3
|
+
class Connection < TTTLS13::Connection
|
4
|
+
# @param socket [Socket]
|
5
|
+
# @param side [:client or :server]
|
6
|
+
|
7
|
+
# @param cipher [TTTLS13::Cryptograph::$Object]
|
8
|
+
#
|
9
|
+
# @return [TTTLS13::Message::$Object]
|
10
|
+
# @return [String]
|
11
|
+
def recv_message(cipher)
|
12
|
+
return @message_queue.shift unless @message_queue.empty?
|
13
|
+
|
14
|
+
messages = nil
|
15
|
+
orig_msgs = []
|
16
|
+
loop do
|
17
|
+
record, orig_msgs = recv_record(cipher)
|
18
|
+
messages = record.messages
|
19
|
+
break unless messages.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
@message_queue += messages[1..].zip(orig_msgs[1..])
|
23
|
+
message = messages.first
|
24
|
+
orig_msg = orig_msgs.first
|
25
|
+
|
26
|
+
[message, orig_msg]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# @param hostname [String]
|
32
|
+
#
|
33
|
+
# @return [TTTLS13::Message::Extensions]
|
34
|
+
# @return [Hash of NamedGroup => OpenSSL::PKey::EC.$Object]
|
35
|
+
def gen_ch_extensions(hostname)
|
36
|
+
exs = TTTLS13::Message::Extensions.new
|
37
|
+
# server_name
|
38
|
+
exs << TTTLS13::Message::Extension::ServerName.new(hostname)
|
39
|
+
|
40
|
+
# supported_versions: only TLS 1.3
|
41
|
+
exs << TTTLS13::Message::Extension::SupportedVersions.new(
|
42
|
+
msg_type: TTTLS13::Message::HandshakeType::CLIENT_HELLO
|
43
|
+
)
|
44
|
+
|
45
|
+
# signature_algorithms
|
46
|
+
exs << TTTLS13::Message::Extension::SignatureAlgorithms.new(
|
47
|
+
[
|
48
|
+
TTTLS13::SignatureScheme::ECDSA_SECP256R1_SHA256,
|
49
|
+
TTTLS13::SignatureScheme::ECDSA_SECP384R1_SHA384,
|
50
|
+
TTTLS13::SignatureScheme::ECDSA_SECP521R1_SHA512,
|
51
|
+
TTTLS13::SignatureScheme::RSA_PSS_RSAE_SHA256,
|
52
|
+
TTTLS13::SignatureScheme::RSA_PSS_RSAE_SHA384,
|
53
|
+
TTTLS13::SignatureScheme::RSA_PSS_RSAE_SHA512,
|
54
|
+
TTTLS13::SignatureScheme::RSA_PKCS1_SHA256,
|
55
|
+
TTTLS13::SignatureScheme::RSA_PKCS1_SHA384,
|
56
|
+
TTTLS13::SignatureScheme::RSA_PKCS1_SHA512
|
57
|
+
]
|
58
|
+
)
|
59
|
+
|
60
|
+
# supported_groups
|
61
|
+
groups = [
|
62
|
+
TTTLS13::NamedGroup::SECP256R1,
|
63
|
+
TTTLS13::NamedGroup::SECP384R1,
|
64
|
+
TTTLS13::NamedGroup::SECP521R1
|
65
|
+
]
|
66
|
+
exs << TTTLS13::Message::Extension::SupportedGroups.new(groups)
|
67
|
+
|
68
|
+
# key_share
|
69
|
+
key_share, priv_keys = TTTLS13::Message::Extension::KeyShare.gen_ch_key_share(
|
70
|
+
groups
|
71
|
+
)
|
72
|
+
exs << key_share
|
73
|
+
|
74
|
+
[exs, priv_keys]
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param ch1 [TTTLS13::Message::ClientHello]
|
78
|
+
# @param hrr [TTTLS13::Message::ServerHello]
|
79
|
+
#
|
80
|
+
# @return [TTTLS13::Message::Extensions]
|
81
|
+
def gen_newch_extensions(ch1, hrr)
|
82
|
+
exs = TTTLS13::Message::Extensions.new
|
83
|
+
# key_share
|
84
|
+
if hrr.extensions.include?(TTTLS13::Message::ExtensionType::KEY_SHARE)
|
85
|
+
group = hrr.extensions[TTTLS13::Message::ExtensionType::KEY_SHARE]
|
86
|
+
.key_share_entry.first.group
|
87
|
+
key_share, = TTTLS13::Message::Extension::KeyShare.gen_ch_key_share([group])
|
88
|
+
exs << key_share
|
89
|
+
end
|
90
|
+
|
91
|
+
# cookie
|
92
|
+
exs << hrr.extensions[TTTLS13::Message::ExtensionType::COOKIE] \
|
93
|
+
if hrr.extensions.include?(TTTLS13::Message::ExtensionType::COOKIE)
|
94
|
+
|
95
|
+
ch1.extensions.merge(exs)
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param conf [ECHConfig::ECHConfigContents::HpkeKeyConfig]
|
99
|
+
#
|
100
|
+
# @return [Boolean]
|
101
|
+
def select_ech_hpke_cipher_suite(conf)
|
102
|
+
TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES.find do |cs|
|
103
|
+
conf.cipher_suites.include?(cs)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param socket [TCPSocket]
|
108
|
+
# @param hostname [String]
|
109
|
+
# @param ech_config [ECHConfig]
|
110
|
+
# @param stack [EchSpec::Log::MessageStack]
|
111
|
+
#
|
112
|
+
# @raise [EchSpec::Error::BeforeTargetSituationError]
|
113
|
+
#
|
114
|
+
# @return [EchSpec::TLS13Client::Connection]
|
115
|
+
# @return [TTTLS13::Message::ClientHello] ClientHelloInner
|
116
|
+
# @return [TTTLS13::Message::ClientHello]
|
117
|
+
# @return [TTTLS13::Message::ServerHello] HelloRetryRequest
|
118
|
+
# @return [TTTLS13::EchState]
|
119
|
+
# rubocop: disable Metrics/MethodLength
|
120
|
+
def recv_hrr(socket, hostname, ech_config, stack)
|
121
|
+
# send 1st ClientHello
|
122
|
+
conn = TLS13Client::Connection.new(socket, :client)
|
123
|
+
inner_ech = TTTLS13::Message::Extension::ECHClientHello.new_inner
|
124
|
+
exs, = TLS13Client.gen_ch_extensions(hostname)
|
125
|
+
# for HRR
|
126
|
+
key_share = TTTLS13::Message::Extension::KeyShare.new(
|
127
|
+
msg_type: TTTLS13::Message::HandshakeType::CLIENT_HELLO,
|
128
|
+
key_share_entry: [] # empty client_shares vector
|
129
|
+
)
|
130
|
+
exs[TTTLS13::Message::ExtensionType::KEY_SHARE] = key_share
|
131
|
+
inner = TTTLS13::Message::ClientHello.new(
|
132
|
+
cipher_suites: TTTLS13::CipherSuites.new(
|
133
|
+
[
|
134
|
+
TTTLS13::CipherSuite::TLS_AES_256_GCM_SHA384,
|
135
|
+
TTTLS13::CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
|
136
|
+
TTTLS13::CipherSuite::TLS_AES_128_GCM_SHA256
|
137
|
+
]
|
138
|
+
),
|
139
|
+
extensions: exs.merge(
|
140
|
+
TTTLS13::Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => inner_ech
|
141
|
+
)
|
142
|
+
)
|
143
|
+
stack << inner
|
144
|
+
|
145
|
+
selector = proc { |x| TLS13Client.select_ech_hpke_cipher_suite(x) }
|
146
|
+
ch, _inner, ech_state = TTTLS13::Ech.offer_ech(inner, ech_config, selector)
|
147
|
+
conn.send_record(
|
148
|
+
TTTLS13::Message::Record.new(
|
149
|
+
type: TTTLS13::Message::ContentType::HANDSHAKE,
|
150
|
+
messages: [ch],
|
151
|
+
cipher: TTTLS13::Cryptograph::Passer.new
|
152
|
+
)
|
153
|
+
)
|
154
|
+
stack << ch
|
155
|
+
|
156
|
+
# receive HelloRetryRequest
|
157
|
+
recv, = conn.recv_message(TTTLS13::Cryptograph::Passer.new)
|
158
|
+
stack << recv
|
159
|
+
raise Error::BeforeTargetSituationError, 'did not send expected handshake message: HelloRetryRequest' \
|
160
|
+
unless recv.is_a?(TTTLS13::Message::ServerHello) && recv.hrr?
|
161
|
+
|
162
|
+
[conn, inner, ch, recv, ech_state]
|
163
|
+
end
|
164
|
+
# rubocop: enable Metrics/MethodLength
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module EchSpec
|
2
|
+
module Refinements
|
3
|
+
refine String do
|
4
|
+
def colorize(code)
|
5
|
+
"\e[#{code}m#{self}\e[0m"
|
6
|
+
end
|
7
|
+
|
8
|
+
def red
|
9
|
+
colorize(31)
|
10
|
+
end
|
11
|
+
|
12
|
+
def green
|
13
|
+
colorize(32)
|
14
|
+
end
|
15
|
+
|
16
|
+
def yellow
|
17
|
+
colorize(33)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/echspec.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'optparse'
|
3
|
+
require 'resolv'
|
4
|
+
require 'timeout'
|
5
|
+
require 'tttls1.3'
|
6
|
+
|
7
|
+
require 'echspec/version'
|
8
|
+
require 'echspec/utils'
|
9
|
+
require 'echspec/log'
|
10
|
+
require 'echspec/error'
|
11
|
+
require 'echspec/result'
|
12
|
+
require 'echspec/tls13_client'
|
13
|
+
require 'echspec/spec_case'
|
14
|
+
require 'echspec/spec_group'
|
15
|
+
require 'echspec/spec'
|
16
|
+
require 'echspec/cli'
|
data/spec/9_spec.rb
ADDED
@@ -0,0 +1,13 @@
|
|
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
ADDED
@@ -0,0 +1,58 @@
|
|
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
ADDED
@@ -0,0 +1,77 @@
|
|
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
|