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