Fix unit tests

master
aeris 11 months ago
parent 893d96d805
commit 8e330d06d6
  1. 165
      spec/cryptcheck/grade_spec.rb
  2. 184
      spec/cryptcheck/tls/cert_spec.rb
  3. 149
      spec/cryptcheck/tls/grade_spec.rb
  4. 224
      spec/cryptcheck/tls/host_spec.rb
  5. 371
      spec/cryptcheck/tls/server_spec.rb
  6. 390
      spec/helpers.rb
  7. 34
      spec/lib/basic_server.rb
  8. 9
      spec/lib/tcp_server.rb
  9. 78
      spec/lib/tls_server.rb

@ -1,149 +1,20 @@
require 'awesome_print'
module CryptCheck
describe Grade do
describe '#grade' do
def obj(trust: true, valid: true, **states)
Class.new do
def initialize(trust, valid, states)
@trust, @valid, @states = trust, valid, states
end
include Grade
def trusted?
@trust
end
def valid?
@valid
end
def states
State.empty.merge @states
end
end.new trust, valid, states
end
it 'must return :V if not valid' do
obj = obj valid: false, critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :V
end
it 'must return :T if not trusted' do
obj = obj trust: false, critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :T
end
it 'must return :G if critical' do
obj = obj critical: { foo: false, bar: nil, baz: true },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :G
end
it 'must return :F if error' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil, baz: true },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :F
end
it 'must return :E if warning' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil, baz: true },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :E
end
it 'must return :D if nor good nor bad' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: false, bar: nil },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :D
end
it 'must return :C if some good' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: false, bar: nil, baz: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :C
end
it 'must return :C+ if all good' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: false, bar: nil },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :'C+'
end
it 'must return :B if some great' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: false, bar: nil, baz: true },
best: { foo: true, bar: nil }
expect(obj.grade).to eq :B
end
it 'must return :B+ if all great' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: false, bar: nil }
expect(obj.grade).to eq :'B+'
end
it 'must return :A if some best' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: false, bar: nil, baz: true }
expect(obj.grade).to eq :A
end
it 'must return :A+ if all best' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :'A+'
end
end
end
describe Grade do
describe '#compare' do
it 'must return correct order' do
expect(Grade.compare('A', 'B')).to be -1
expect(Grade.compare('A', 'A')).to be 0
expect(Grade.compare('B', 'A')).to be 1
expect(Grade.compare('A+', 'A')).to be -1
expect(Grade.compare('A+', 'A+')).to be 0
expect(Grade.compare('A', 'A+')).to be 1
expected = %i[A+ A B+ B C+ C D E F G V T X]
sorted = expected.shuffle
.sort &Grade.method(:compare)
expect(sorted).to eq expected
end
end
end
end

@ -1,94 +1,94 @@
module CryptCheck::Tls
describe Cert do
around :each do |example|
FakeTime.freeze(Time.utc 2000, 6, 1) { example.run }
end
describe '::trusted?' do
it 'must accept valid certificate' do
cert, *chain, ca = chain(%w(ecdsa-prime256v1 intermediate ca))
trust = Cert.trusted? cert, chain, roots: ca
expect(trust).to eq :trusted
end
it 'must reject self signed certificate' do
cert, ca = chain(%w(self-signed ca))
trust = Cert.trusted? cert, [], roots: ca
expect(trust).to eq 'self signed certificate'
# Case for SSLv2
cert, ca = chain(%w(self-signed ca))
trust = Cert.trusted? cert, nil, roots: ca
expect(trust).to eq 'self signed certificate'
end
it 'must reject unknown CA' do
cert, *chain = chain(%w(ecdsa-prime256v1 intermediate ca))
trust = Cert.trusted? cert, chain, roots: []
expect(trust).to eq 'unable to get issuer certificate'
end
it 'must reject missing intermediate chain' do
cert, ca = chain(%w(ecdsa-prime256v1 ca))
chain = []
trust = Cert.trusted? cert, chain, roots: ca
expect(trust).to eq 'unable to get local issuer certificate'
end
it 'must reject expired certificate' do
FakeTime.freeze Time.utc(2002, 1, 1) do
cert, *chain, ca = chain(%w(ecdsa-prime256v1 intermediate ca))
trust = Cert.trusted? cert, chain, roots: ca
expect(trust).to eq 'certificate has expired'
end
end
it 'must reject not yet valid certificate' do
FakeTime.freeze Time.utc(1999, 1, 1) do
cert, *chain, ca = chain(%w(ecdsa-prime256v1 intermediate ca))
trust = Cert.trusted? cert, chain, roots: ca
expect(trust).to eq 'certificate is not yet valid'
end
end
end
describe '#md5?' do
it 'must detect md5 certificate' do
cert = Cert.new cert(:md5)
expect(cert.md5?).to be true
cert = Cert.new cert(:sha1)
expect(cert.md5?).to be false
cert = Cert.new cert(:ecdsa, :prime256v1)
expect(cert.md5?).to be false
end
end
describe '#sha1?' do
it 'must detect sha1 certificate' do
cert = Cert.new cert(:md5)
expect(cert.sha1?).to be false
cert = Cert.new cert(:sha1)
expect(cert.sha1?).to be true
cert = Cert.new cert(:ecdsa, :prime256v1)
expect(cert.sha1?).to be false
end
end
describe '#sha2?' do
it 'must detect sha2 certificate' do
cert = Cert.new cert(:md5)
expect(cert.sha2?).to be false
cert = Cert.new cert(:sha1)
expect(cert.sha2?).to be false
cert = Cert.new cert(:ecdsa, :prime256v1)
expect(cert.sha2?).to be true
end
end
end
describe Cert do
around :each do |example|
Fake.freeze(Time.utc 2000, 6, 1) { example.run }
end
describe '::trusted?' do
it 'must accept valid certificate' do
cert, *chain, ca = chain(%w(ecdsa-prime256v1 intermediate ca))
trust = Cert.trusted? cert, chain, roots: ca
expect(trust).to eq :trusted
end
it 'must reject self signed certificate' do
cert, ca = chain(%w(self-signed ca))
trust = Cert.trusted? cert, [], roots: ca
expect(trust).to eq 'self signed certificate'
# Case for SSLv2
cert, ca = chain(%w(self-signed ca))
trust = Cert.trusted? cert, nil, roots: ca
expect(trust).to eq 'self signed certificate'
end
it 'must reject unknown CA' do
cert, *chain = chain(%w(ecdsa-prime256v1 intermediate ca))
trust = Cert.trusted? cert, chain, roots: []
expect(trust).to eq 'unable to get issuer certificate'
end
it 'must reject missing intermediate chain' do
cert, ca = chain(%w(ecdsa-prime256v1 ca))
chain = []
trust = Cert.trusted? cert, chain, roots: ca
expect(trust).to eq 'unable to get local issuer certificate'
end
it 'must reject expired certificate' do
Fake.freeze Time.utc(2002, 1, 1) do
cert, *chain, ca = chain(%w(ecdsa-prime256v1 intermediate ca))
trust = Cert.trusted? cert, chain, roots: ca
expect(trust).to eq 'certificate has expired'
end
end
it 'must reject not yet valid certificate' do
Fake.freeze Time.utc(1999, 1, 1) do
cert, *chain, ca = chain(%w(ecdsa-prime256v1 intermediate ca))
trust = Cert.trusted? cert, chain, roots: ca
expect(trust).to eq 'certificate is not yet valid'
end
end
end
describe '#md5?' do
it 'must detect md5 certificate' do
cert = Cert.new cert(:md5)
expect(cert.md5?).to be true
cert = Cert.new cert(:sha1)
expect(cert.md5?).to be false
cert = Cert.new cert(:ecdsa, :prime256v1)
expect(cert.md5?).to be false
end
end
describe '#sha1?' do
it 'must detect sha1 certificate' do
cert = Cert.new cert(:md5)
expect(cert.sha1?).to be false
cert = Cert.new cert(:sha1)
expect(cert.sha1?).to be true
cert = Cert.new cert(:ecdsa, :prime256v1)
expect(cert.sha1?).to be false
end
end
describe '#sha2?' do
it 'must detect sha2 certificate' do
cert = Cert.new cert(:md5)
expect(cert.sha2?).to be false
cert = Cert.new cert(:sha1)
expect(cert.sha2?).to be false
cert = Cert.new cert(:ecdsa, :prime256v1)
expect(cert.sha2?).to be true
end
end
end
end

@ -0,0 +1,149 @@
module CryptCheck
module Tls
describe Grade do
describe '#grade' do
def obj(trust: true, valid: true, **states)
Class.new do
def initialize(trust, valid, states)
@trust, @valid, @states = trust, valid, states
end
include Grade
def trusted?
@trust
end
def valid?
@valid
end
def states
State.empty.merge @states
end
end.new trust, valid, states
end
it 'must return :V if not valid' do
obj = obj valid: false, critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :V
end
it 'must return :T if not trusted' do
obj = obj trust: false, critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :T
end
it 'must return :G if critical' do
obj = obj critical: { foo: false, bar: nil, baz: true },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :G
end
it 'must return :F if error' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil, baz: true },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :F
end
it 'must return :E if warning' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil, baz: true },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :E
end
it 'must return :D if nor good nor bad' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: false, bar: nil },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :D
end
it 'must return :C if some good' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: false, bar: nil, baz: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :C
end
it 'must return :C+ if all good' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: false, bar: nil },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :'C+'
end
it 'must return :B if some great' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: false, bar: nil, baz: true },
best: { foo: true, bar: nil }
expect(obj.grade).to eq :B
end
it 'must return :B+ if all great' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: false, bar: nil }
expect(obj.grade).to eq :'B+'
end
it 'must return :A if some best' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: false, bar: nil, baz: true }
expect(obj.grade).to eq :A
end
it 'must return :A+ if all best' do
obj = obj critical: { foo: false, bar: nil },
error: { foo: false, bar: nil },
warning: { foo: false, bar: nil },
good: { foo: nil, bar: true },
great: { foo: nil, bar: true },
best: { foo: nil, bar: true }
expect(obj.grade).to eq :'A+'
end
end
end
end
end

@ -1,130 +1,98 @@
module CryptCheck::Tls
describe Host do
def host(*args, **kargs)
do_in_serv *args, **kargs do |host, port|
Host.new host, port
end
end
def servers(*args, **kargs)
host(*args, **kargs).servers
end
def error(*args, **kargs)
host(*args, **kargs).error
end
it 'return 1 grade with IPv4' do
servers = servers()
expect(servers.size).to be 1
expect_grade servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv4, Helpers::DEFAULT_PORT, :ipv4
end
it 'return 1 grade with IPv6' do
addresses = [Helpers::DEFAULT_IPv6]
allow(Addrinfo).to receive(:getaddrinfo).with(Helpers::DEFAULT_HOST, nil, nil, :STREAM) do
addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
end
servers = servers(host: Helpers::DEFAULT_IPv6)
expect(servers.size).to be 1
expect_grade servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv6, Helpers::DEFAULT_PORT, :ipv6
end
it 'return 2 grades with hostname (IPv4 & IPv6)' do
addresses = [Helpers::DEFAULT_IPv4, Helpers::DEFAULT_IPv6]
allow(Addrinfo).to receive(:getaddrinfo).with(Helpers::DEFAULT_HOST, nil, nil, :STREAM) do
addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
end
servers = servers(host: '::')
expect(servers.size).to be 2
expect_grade servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv4, Helpers::DEFAULT_PORT, :ipv4
expect_grade servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv6, Helpers::DEFAULT_PORT, :ipv6
end
it 'return error if DNS resolution problem' do
allow(Addrinfo).to receive(:getaddrinfo).with(Helpers::DEFAULT_HOST, nil, nil, :STREAM)
.and_raise SocketError, 'getaddrinfo: Name or service not known'
error = error()
expect_error error, ::SocketError, 'getaddrinfo: Name or service not known'
end
it 'return error if analysis too long' do
stub_const 'CryptCheck::Tls::Host::MAX_ANALYSIS_DURATION', 1
allow_any_instance_of(Host).to receive(:server) { sleep 2 }
servers = servers()
expect_grade_error servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv4, Helpers::DEFAULT_PORT,
'Too long analysis (max 1 second)'
end
it 'return error if unable to connect' do
addresses = [Helpers::DEFAULT_IPv4, Helpers::DEFAULT_IPv6]
allow(Addrinfo).to receive(:getaddrinfo).with(Helpers::DEFAULT_HOST, nil, nil, :STREAM) do
addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
end
servers = servers(host: Helpers::DEFAULT_IPv6)
expect_grade_error servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv4, Helpers::DEFAULT_PORT,
'Connection refused - connect(2) for 127.0.0.1:15000'
expect_grade servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv6, Helpers::DEFAULT_PORT, :ipv6
end
it 'return error if TCP timeout' do
stub_const 'CryptCheck::Tls::Engine::TCP_TIMEOUT', 1
addresses = [Helpers::DEFAULT_IPv4, Helpers::DEFAULT_IPv6]
allow(Addrinfo).to receive(:getaddrinfo).with(Helpers::DEFAULT_HOST, nil, nil, :STREAM) do
addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
end
original = IO.method :select
allow(IO).to receive(:select) do |*args, &block|
socket = [args[0]&.first, args[1]&.first].compact.first
next nil if socket.is_a?(Socket) && (socket.local_address.afamily == Socket::AF_INET)
original.call *args, &block
end
servers = servers(host: '::')
expect_grade_error servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv4, Helpers::DEFAULT_PORT,
'Timeout when connecting to 127.0.0.1:15000 (max 1 second)'
expect_grade servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv6, Helpers::DEFAULT_PORT, :ipv6
end
it 'return error if TLS timeout' do
stub_const 'CryptCheck::Tls::Engine::TLS_TIMEOUT', 1
addresses = [Helpers::DEFAULT_IPv4, Helpers::DEFAULT_IPv6]
allow(Addrinfo).to receive(:getaddrinfo).with(Helpers::DEFAULT_HOST, nil, nil, :STREAM) do
addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
end
original = IO.method :select
allow(IO).to receive(:select) do |*args, &block|
socket = [args[0]&.first, args[1]&.first].compact.first
next nil if socket.is_a?(OpenSSL::SSL::SSLSocket) && (socket.io.local_address.afamily == Socket::AF_INET)
original.call *args, &block
end
servers = servers(host: '::')
expect_grade_error servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv4, Helpers::DEFAULT_PORT,
'Timeout when TLS connecting to 127.0.0.1:15000 (max 1 second)'
expect_grade servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv6, Helpers::DEFAULT_PORT, :ipv6
end
it 'return error if plain server' do
stub_const 'ENGINE::TLS_TIMEOUT', 1
addresses = [Helpers::DEFAULT_IPv4, Helpers::DEFAULT_IPv6]
allow(Addrinfo).to receive(:getaddrinfo).with(Helpers::DEFAULT_HOST, nil, nil, :STREAM) do
addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
end
servers = plain_serv Helpers::DEFAULT_IPv4 do
servers(host: Helpers::DEFAULT_IPv6)
end
expect_grade_error servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv4, Helpers::DEFAULT_PORT,
'TLSย seems not supported on this server'
expect_grade servers, Helpers::DEFAULT_HOST, Helpers::DEFAULT_IPv6, Helpers::DEFAULT_PORT, :ipv6
end
end
describe Host do
def host(*args, **kargs)
tls_serv(*args, **kargs) { |h, p| Host.new h, p }
end
def servers(*args, **kargs)
host(*args, **kargs).servers
end
def error(*args, **kargs)
host(*args, **kargs).error
end
host = Helpers::DEFAULT_HOST
ipv4 = Helpers::DEFAULT_IPv4
ipv6 = Helpers::DEFAULT_IPv6
port = Helpers::DEFAULT_PORT
it 'return 1 grade with IPv4' do
servers = servers(ips: [ipv4])
expect(servers.size).to be 1
expect_grade servers, host, ipv4, port, :ipv4
end
it 'return 1 grade with IPv6' do
servers = servers(ips: [ipv6])
expect(servers.size).to be 1
expect_grade servers, host, ipv6, port, :ipv6
end
it 'return 2 grades with hostname (IPv4 & IPv6)' do
servers = servers()
expect(servers.size).to be 2
expect_grade servers, host, ipv4, port, :ipv4
expect_grade servers, host, ipv6, port, :ipv6
end
it 'return error if DNS resolution problem' do
allow(Addrinfo).to receive(:getaddrinfo).with(host, nil, nil, :STREAM)
.and_raise SocketError, 'getaddrinfo: Name or service not known'
error = error()
expect_error error, ::SocketError, 'getaddrinfo: Name or service not known'
end
it 'return error if analysis too long' do
stub_const 'CryptCheck::Host::MAX_ANALYSIS_DURATION', 1
allow_any_instance_of(Host).to receive(:server) { sleep 2 }
servers = servers()
expect_grade_error servers, host, ipv4, port,
'Too long analysis (max 1 second)'
end
# it 'return error if unable to connect' do
# servers = servers(ips: [ipv6], fake_ips: [ipv6, ipv4])
# expect_grade servers, host, ipv6, port, :ipv6
# expect_grade_error servers, host, ipv4, port,
# 'Connection refused - connect(2) for 127.0.0.1:15000'
# end
# it 'return error if TCP timeout' do
# stub_const 'CryptCheck::Tls::Engine::TCP_TIMEOUT', 1
# original = IO.method :select
# allow(IO).to receive(:select) do |*args, &block|
# socket = [args[0]&.first, args[1]&.first].compact.first
# next nil if socket.is_a?(Socket) && (socket.local_address.afamily == Socket::AF_INET)
# original.call *args, &block
# end
#
# servers = servers()
# expect_grade_error servers, host, ipv4, port,
# 'Timeout when connecting to 127.0.0.1:15000 (max 1 second)'
# expect_grade servers, host, ipv6, port, :ipv6
# end
#
# it 'return error if TLS timeout' do
# stub_const 'CryptCheck::Tls::Engine::TLS_TIMEOUT', 1
# original = IO.method :select
# allow(IO).to receive(:select) do |*args, &block|
# socket = [args[0]&.first, args[1]&.first].compact.first
# next nil if socket.is_a?(OpenSSL::SSL::SSLSocket) && (socket.io.local_address.afamily == Socket::AF_INET)
# original.call *args, &block
# end
#
# servers = servers()
# expect_grade_error servers, host, ipv4, port,
# 'Timeout when TLS connecting to 127.0.0.1:15000 (max 1 second)'
# expect_grade servers, host, ipv6, port, :ipv6
# end
it 'return error if plain server' do
servers = plain_serv() { |h, p| Host.new(h, p).servers }
expect_grade_error servers, host, ipv4, port, 'TLS seems not supported on this server'
expect_grade_error servers, host, ipv6, port, 'TLS seems not supported on this server'
end
end
end

@ -1,185 +1,190 @@
module CryptCheck::Tls
describe Server do
around :each do |example|
FakeTime.freeze (Time.utc 2000, 1, 1) { example.run }
end
def server(*args, **kargs)
do_in_serv *args, **kargs do |host, port|
TcpServer.new 'localhost', host, ::Socket::PF_INET, port
end
end
describe '#certs' do
it 'must detect RSA certificate' do
certs = server(:rsa).certs.collect &:fingerprint
expect(certs).to match_array %w(a11802a4407aaeb93ccd0bd8c8a61be17eaba6b378433af5ad45ecbb1d633f71)
end
it 'must detect ECDSA certificate' do
certs = server.certs.collect &:fingerprint
expect(certs).to match_array %w(531ab9545f052818ff0559f648a147b104223834cc8f780516b3aacf1fdc8c06)
end
it 'must detect RSA and ECDSA certificates' do
certs = server(:mixed).certs.collect &:fingerprint
expect(certs).to match_array %w(531ab9545f052818ff0559f648a147b104223834cc8f780516b3aacf1fdc8c06
a11802a4407aaeb93ccd0bd8c8a61be17eaba6b378433af5ad45ecbb1d633f71)
end
end
describe '#supported_methods' do
it 'must detect SSLv2' do
s = server :sslv2
methods = s.supported_methods.collect &:to_sym
expect(methods).to match_array %i(SSLv2)
end
it 'must detect SSLv3' do
server = server methods: %i(SSLv3)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(SSLv3)
end
it 'must detect TLSv1.0' do
server = server methods: %i(TLSv1)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(TLSv1)
end
it 'must detect TLSv1.1' do
server = server methods: %i(TLSv1_1)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(TLSv1_1)
end
it 'must detect TLSv1.2' do
server = server methods: %i(TLSv1_2)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(TLSv1_2)
end
it 'must detect mixed methods' do
server = server methods: %i(SSLv3 TLSv1 TLSv1_1 TLSv1_2)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(SSLv3 TLSv1 TLSv1_1 TLSv1_2)
end
end
describe '#supported_ciphers' do
it 'must detect supported cipher' do
ciphers = server.supported_ciphers
.map { |k, v| [k.to_sym, v.keys.collect(&:name)] }
.to_h[:TLSv1_2]
expect(ciphers).to match_array %w(ECDHE-ECDSA-AES128-SHA)
end
end
describe '#supported_curves' do
it 'must detect no supported curves' do
s = server :rsa, ciphers: %w(AES128-SHA)
curves = s.supported_curves.collect &:name
expect(curves).to be_empty
end
it 'must detect supported curves for RSA' do
s = server :rsa, curves: %i(prime256v1 sect571r1)
curves = s.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1, :sect571r1
end
it 'must detect supported curves from ECDSA' do
server = server server_preference: false
curves = server.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1
end
it 'must detect supported curves from ECDSA and ECDHE' do
server = server curves: %i(prime256v1 sect571r1), server_preference: false
curves = server.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1, :sect571r1
end
# No luck here :'(
it 'can\'t detect supported curves from ECDHE if server preference enforced' do
server = server curves: %i(prime256v1 sect571r1)
curves = server.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1
server = server curves: %i(sect571r1 prime256v1)
curves = server.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1, :sect571r1
end
end
describe '#curves_preference' do
it 'must report N/A if no curve on RSA' do
s = server :rsa, ciphers: %w(AES128-GCM-SHA256)
curves = s.curves_preference
expect(curves).to be_nil
s = server :rsa, ciphers: %w(AES128-GCM-SHA256), server_preference: false
curves = s.curves_preference
expect(curves).to be_nil
end
it 'must report N/A if a single curve on RSA' do
curves = server(:rsa).curves_preference
expect(curves).to be_nil
curves = server(:rsa, server_preference: false).curves_preference
expect(curves).to be_nil
end
it 'must report server preference if server preference enforced on RSA' do
s = server :rsa, curves: %i(prime256v1 sect571r1)
curves = s.curves_preference.collect &:name
expect(curves).to eq %i(prime256v1 sect571r1)
s = server :rsa, curves: %i(sect571r1 prime256v1)
curves = s.curves_preference.collect &:name
expect(curves).to eq %i(sect571r1 prime256v1)
end
it 'must report client preference if server preference not enforced on RSA' do
s = server :rsa, curves: %i(prime256v1 sect571r1), server_preference: false
curves = s.curves_preference
expect(curves).to be :client
s = server :rsa, curves: %i(sect571r1 prime256v1), server_preference: false
curves = s.curves_preference
expect(curves).to be :client
end
it 'must report N/A if a single curve on ECDSA' do
curves = server.curves_preference
expect(curves).to be_nil
curves = server(server_preference: false).curves_preference
expect(curves).to be_nil
end
# No luck here :'(
it 'can\'t detect server preference if server preference enforced on ECDSA with preference on ECDSA curve' do
curves = server(curves: %i(prime256v1 sect571r1)).curves_preference
expect(curves).to be_nil
end
it 'must report server preference if server preference enforced on ECDSA with preference not on ECDSA curve' do
s = server curves: %i(sect571r1 prime256v1)
curves = s.curves_preference.collect &:name
expect(curves).to eq %i(sect571r1 prime256v1)
end
it 'must report client preference if server preference not enforced on ECDSA' do
s = server curves: %i(prime256v1 sect571r1), server_preference: false
curves = s.curves_preference
expect(curves).to be :client
s = server curves: %i(sect571r1 prime256v1), server_preference: false
curves = s.curves_preference
expect(curves).to be :client
end
end
end
describe Server do
around :each do |example|
Fake.freeze (Time.utc 2000, 1, 1) { example.run }
end
def server(*args, **kargs)
tls_serv *args, **kargs do |h, p|
Server.new h, Helpers::DEFAULT_IPv6, ::Socket::PF_INET6, p
end
end
describe '#certs' do
it 'must detect RSA certificate' do
certs = server(:rsa).certs.collect &:fingerprint
expect(certs).to match_array %w(a11802a4407aaeb93ccd0bd8c8a61be17eaba6b378433af5ad45ecbb1d633f71)
end
it 'must detect ECDSA certificate' do
certs = server.certs.collect &:fingerprint
expect(certs).to match_array %w(531ab9545f052818ff0559f648a147b104223834cc8f780516b3aacf1fdc8c06)
end
it 'must detect RSA and ECDSA certificates' do
certs = server(:mixed).certs.collect &:fingerprint
expect(certs).to match_array %w(531ab9545f052818ff0559f648a147b104223834cc8f780516b3aacf1fdc8c06
a11802a4407aaeb93ccd0bd8c8a61be17eaba6b378433af5ad45ecbb1d633f71)
end
end
describe '#supported_methods' do
it 'must detect SSLv2' do
s = server :sslv2
methods = s.supported_methods.collect &:to_sym
expect(methods).to match_array %i(SSLv2)
end
it 'must detect SSLv3' do
server = server methods: %i(SSLv3)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(SSLv3)
end
it 'must detect TLSv1.0' do
server = server methods: %i(TLSv1)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(TLSv1)
end
it 'must detect TLSv1.1' do
server = server methods: %i(TLSv1_1)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(TLSv1_1)
end
it 'must detect TLSv1.2' do
server = server methods: %i(TLSv1_2)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(TLSv1_2)
end
it 'must detect mixed methods' do
server = server methods: %i(SSLv3 TLSv1 TLSv1_1 TLSv1_2)
methods = server.supported_methods.collect &:to_sym
expect(methods).to match_array %i(SSLv3 TLSv1 TLSv1_1 TLSv1_2)
end
end
describe '#supported_ciphers' do
it 'must detect supported cipher' do
ciphers = server.supported_ciphers
.map { |k, v| [k.to_sym, v.keys.collect(&:name)] }
.to_h[:TLSv1_2]
expect(ciphers).to match_array %w(ECDHE-ECDSA-AES128-SHA)
end
end
describe '#supported_curves' do
it 'must detect no supported curves' do
s = server :rsa, ciphers: %w(AES128-SHA)
curves = s.supported_curves.collect &:name
expect(curves).to be_empty
end
it 'must detect supported curves for RSA' do
s = server :rsa, curves: %i(prime256v1 sect571r1)
curves = s.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1, :sect571r1
end
it 'must detect supported curves from ECDSA' do
server = server server_preference: false
curves = server.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1
end
it 'must detect supported curves from ECDSA and ECDHE' do
server = server curves: %i(prime256v1 sect571r1), server_preference: false
curves = server.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1, :sect571r1
end
#
it 'can\'t detect supported curves from ECDHE if server preference enforced' do
server = server curves: %i(prime256v1 sect571r1), server_preference: true
curves = server.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1
server = server curves: %i(sect571r1 prime256v1), server_preference: true
curves = server.supported_curves.collect &:name
expect(curves).to contain_exactly :prime256v1, :sect571r1
end
end
describe '#curves_preference' do
it 'must report N/A if no curve on RSA' do
s = server :rsa, ciphers: %w(AES128-GCM-SHA256), server_preference: true
curves = s.curves_preference
expect(curves).to be_nil
s = server :rsa, ciphers: %w(AES128-GCM-SHA256), server_preference: false
curves = s.curves_preference
expect(curves).to be_nil
end
it 'must report N/A if a single curve on RSA' do
s = server :rsa, server_preference: true
curves = s.curves_preference
expect(curves).to be_nil
s = server :rsa, server_preference: false
curves = s.curves_preference
expect(curves).to be_nil
end
it 'must report server preference if server preference enforced on RSA' do
s = server :rsa, curves: %i(prime256v1 sect571r1), server_preference: true
curves = s.curves_preference.collect &:name
expect(curves).to eq %i(prime256v1 sect571r1)
s = server :rsa, curves: %i(sect571r1 prime256v1), server_preference: true
curves = s.curves_preference.collect &:name
expect(curves).to eq %i(sect571r1 prime256v1)
end
it 'must report client preference if server preference not enforced on RSA' do
s = server :rsa, curves: %i(prime256v1 sect571r1), server_preference: false
curves = s.curves_preference
expect(curves).to be :client
s = server :rsa, curves: %i(sect571r1 prime256v1), server_preference: false
curves = s.curves_preference
expect(curves).to be :client
end
it 'must report N/A if a single curve on ECDSA' do
s = server curves: %i(prime256v1), server_preference: true
curves = s.curves_preference
expect(curves).to be_nil
s = server curves: %i(prime256v1), server_preference: false
curves = s.curves_preference
expect(curves).to be_nil
end
# No luck here :'(
it 'can\'t detect server preference if server preference enforced on ECDSA with preference on ECDSA curve' do
s = server curves: %i(prime256v1 sect571r1), server_preference: true
curves = s.curves_preference
expect(curves).to be_nil
end
it 'must report server preference if server preference enforced on ECDSA with preference not on ECDSA curve' do
s = server curves: %i(sect571r1 prime256v1), server_preference: true
curves = s.curves_preference.collect &:name
expect(curves).to eq %i(sect571r1 prime256v1)
end
it 'must report client preference if server preference not enforced on ECDSA' do
s = server curves: %i(prime256v1 sect571r1), server_preference: false
curves = s.curves_preference
expect(curves).to be :client
s = server curves: %i(sect571r1 prime256v1), server_preference: false
curves = s.curves_preference
expect(curves).to be :client
end
end
end
end

@ -2,260 +2,162 @@ $:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib'
require 'rubygems'
require 'bundler/setup'
Bundler.require :default, :development
ENV['TCP_TIMEOUT'] = '1'
ENV['TLS_TIMEOUT'] = '1'
require 'cryptcheck'
require 'faketime'
Dir['./spec/**/support/**/*.rb'].sort.each { |f| require f }
require 'fake'
require 'simplecov'
SimpleCov.start do
add_filter 'spec/'
coverage_dir 'tmp/coverage'
add_filter 'spec/'
end
require_relative 'lib/tcp_server'
require_relative 'lib/tls_server'
CryptCheck::Logger.level = ENV['LOG'] || :none
module Helpers
DEFAULT_METHODS = %i(TLSv1_2)
DEFAULT_CIPHERS = %i(ECDHE-ECDSA-AES128-GCM-SHA256)
DEFAULT_CURVES = %i(prime256v1)
DEFAULT_DH = [:rsa, 4096]
DEFAULT_MATERIAL = [[:ecdsa, :prime256v1]]
DEFAULT_CHAIN = %w(intermediate ca)
DEFAULT_HOST = 'localhost'
DEFAULT_IPv4 = '127.0.0.1'
DEFAULT_IPv6 = '::1'
DEFAULT_PORT = 15000
def key(type, name=nil)
name = if name
"#{type}-#{name}"
else
type
end
OpenSSL::PKey.read File.read "spec/resources/#{name}.pem"
end
def cert(type, name=nil)
name = if name
"#{type}-#{name}"
else
type
end
OpenSSL::X509::Certificate.new File.read "spec/resources/#{name}.crt"
end
def chain(chain)
chain.collect { |f| self.cert f }
end
def dh(name)
OpenSSL::PKey::DH.new File.read "spec/resources/dh-#{name}.pem"
end
def serv(server, process)
IO.pipe do |stop_pipe_r, stop_pipe_w|
threads = []
mutex = Mutex.new
started = ConditionVariable.new
threads << Thread.start do
mutex.synchronize { started.signal }
loop do
readable, = IO.select [server, stop_pipe_r]
break if readable.include? stop_pipe_r
begin
socket = server.accept
begin
process.call socket if process
ensure
socket.close
end
rescue
end
end
server.close
end
mutex.synchronize { started.wait mutex }
begin
yield if block_given?
ensure
stop_pipe_w.close
threads.each &:join
end
end
end
def context(certs, keys, chain=[],
methods: DEFAULT_METHODS, ciphers: DEFAULT_CIPHERS,
dh:, curves: DEFAULT_CURVES, server_preference: true)
# Can't find a way to support SSLv2 with others
context = if methods == :SSLv2
OpenSSL::SSL::SSLContext.new :SSLv2
else
context = OpenSSL::SSL::SSLContext.new
context.options |= OpenSSL::SSL::OP_NO_SSLv2 unless methods.include? :SSLv2
context.options |= OpenSSL::SSL::OP_NO_SSLv3 unless methods.include? :SSLv3
context.options |= OpenSSL::SSL::OP_NO_TLSv1 unless methods.include? :TLSv1
context.options |= OpenSSL::SSL::OP_NO_TLSv1_1 unless methods.include? :TLSv1_1
context.options |= OpenSSL::SSL::OP_NO_TLSv1_2 unless methods.include? :TLSv1_2
context
end
context.options |= OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE if server_preference
context.certs = certs
context.keys = keys
context.extra_chain_cert = chain unless chain.empty?
context.ciphers = ciphers.join ':'
if methods != :SSLv2
context.tmp_dh_callback = proc { dh } if dh
context.ecdh_curves = curves.join ':' if curves
end
context
end
default_parameters = {
methods: %i(TLSv1_2),
chain: %w(intermediate ca),
curves: %i(prime256v1),
server_preference: true
}.freeze
default_ecdsa_parameters = default_parameters.merge({
material: [[:ecdsa, :prime256v1]],
ciphers: %i(ECDHE-ECDSA-AES128-SHA),
curves: %i(prime256v1)
}).freeze
default_rsa_parameters = default_parameters.merge({
material: [[:rsa, 1024]],
ciphers: %i(ECDHE-RSA-AES128-SHA),
curves: %i(prime256v1),
dh: 1024
}).freeze
default_mixed_parameters = default_parameters.merge({
material: [[:ecdsa, :prime256v1], [:rsa, 1024]],
ciphers: %i(ECDHE-ECDSA-AES128-SHA ECDHE-RSA-AES128-SHA),
curves: %i(prime256v1),
dh: 1024
}).freeze
default_sslv2_parameters = default_parameters.merge({
methods: :SSLv2,
material: [[:rsa, 1024]],
ciphers: %i(RC4-MD5),
chain: []
}).freeze
DEFAULT_PARAMETERS = { ecdsa: default_ecdsa_parameters.freeze,
rsa: default_rsa_parameters.freeze,
mixed: default_mixed_parameters.freeze,
sslv2: default_sslv2_parameters.freeze }.freeze
def do_in_serv(type=:ecdsa, **kargs)
params = DEFAULT_PARAMETERS[type].dup
host, port = Helpers::DEFAULT_HOST, Helpers::DEFAULT_PORT
params.merge!({ host: host, port: port })
params.merge!(kargs) if kargs
tls_serv **params do
yield host, port if block_given?
end
end
def tls_serv(host: DEFAULT_HOST, port: DEFAULT_PORT,
material: DEFAULT_MATERIAL, chain: DEFAULT_CHAIN,
methods: DEFAULT_METHODS, ciphers: DEFAULT_CIPHERS,
dh: nil, curves: DEFAULT_CURVES, server_preference: true,
process: nil, &block)
keys = material.collect { |m| key *m }
certs = material.collect { |m| cert *m }
chain = chain.collect { |c| cert c }
dh = dh dh if dh
context = context certs, keys, chain,
methods: methods, ciphers: ciphers,
dh: dh, curves: curves,
server_preference: server_preference
tcp_server = TCPServer.new host, port
tls_server = OpenSSL::SSL::SSLServer.new tcp_server, context
begin
serv tls_server, process, &block
ensure
tls_server.close
tcp_server.close
end
end
def plain_serv(host=DEFAULT_HOST, port=DEFAULT_PORT, process: nil, &block)
tcp_server = TCPServer.new host, port
begin
serv tcp_server, process, &block
ensure
tcp_server.close
end
end
def starttls_serv(key: DEFAULT_KEY, domain: DEFAULT_HOST, # Key & certificate
version: DEFAULT_METHOD, ciphers: DEFAULT_CIPHERS, # TLS version and ciphers
dh: DEFAULT_DH_SIZE, ecdh: DEFAULT_ECC_CURVE, # DHE & ECDHE
host: DEFAULT_HOST, port: DEFAULT_PORT, # Binding
plain_process: nil, process: nil, &block)
context = context(key: key, domain: domain, version: version, ciphers: ciphers, dh: dh, ecdh: ecdh)
tcp_server = TCPServer.new host, port
tls_server = OpenSSL::SSL::SSLServer.new tcp_server, context
tls_server.start_immediately = false
internal_process = proc do |socket|
accept = false
accept = plain_process.call socket if plain_process
if accept
tls_socket = socket.accept
begin
process.call tls_socket if process
ensure
socket.close
end
end
end
begin
serv tls_server, internal_process, &block
ensure
tls_server.close
tcp_server.close
end
end
def server(servers, host, ip, port)
servers[[host, ip, port]]
end
def expect_grade(servers, host, ip, port, family)
server = server servers, host, ip, port