parent
9ad705beba
commit
5c7b9e7a37
@ -0,0 +1,61 @@ |
||||
module CryptCheck |
||||
module Grade |
||||
def grade |
||||
@grade ||= calculate_grade |
||||
end |
||||
|
||||
GRADES = %i(A+ A B+ B C+ C D E F G V T X) |
||||
GRADE_STATUS = { |
||||
:'A+' => :best, |
||||
A: :best, |
||||
:'B+' => :great, |
||||
B: :great, |
||||
:'C+' => :good, |
||||
C: :good, |
||||
D: nil, |
||||
E: :warning, |
||||
F: :error, |
||||
G: :critical, |
||||
|
||||
V: :critical, |
||||
T: :critical, |
||||
X: :critical |
||||
} |
||||
STATUS_GRADES = { |
||||
critical: :G, |
||||
error: :F, |
||||
warning: :E, |
||||
default: :D, |
||||
good: :C, |
||||
great: :B, |
||||
best: :A |
||||
} |
||||
|
||||
def grade_status |
||||
GRADE_STATUS.fetch self.grade, :unknown |
||||
end |
||||
|
||||
private |
||||
def calculate_grade |
||||
return :V unless self.valid? |
||||
return :T unless self.trusted? |
||||
|
||||
states = self.states |
||||
states = State.collect { |s| [s, State.state(states, s)] }.to_h |
||||
|
||||
State::BADS.each do |s| |
||||
return STATUS_GRADES[s] if states[s] |
||||
end |
||||
|
||||
grade = STATUS_GRADES[:default] |
||||
State::GOODS.each do |s| |
||||
state = states[s] |
||||
return grade if state == false |
||||
grade = STATUS_GRADES[s] |
||||
return grade if state == :some |
||||
grade = "#{grade}+".to_sym |
||||
end |
||||
grade |
||||
end |
||||
end |
||||
end |
@ -1,52 +0,0 @@ |
||||
module CryptCheck |
||||
module Tls |
||||
module Grade |
||||
def grade |
||||
@grade ||= calculate_grade |
||||
end |
||||
|
||||
GRADES = %i(A+ A B+ B C+ C D E F G V T X) |
||||
GRADE_STATUS = { |
||||
V: :critical, |
||||
T: :critical, |
||||
|
||||
G: :critical, |
||||
F: :error, |
||||
E: :warning, |
||||
D: nil, |
||||
C: :good, |
||||
:'C+' => :good, |
||||
B: :great, |
||||
:'B+' => :great, |
||||
A: :best, |
||||
:'A+' => :best |
||||
} |
||||
def grade_status |
||||
GRADE_STATUS.fetch self.grade, :unknown |
||||
end |
||||
|
||||
private |
||||
def calculate_grade |
||||
return :V unless self.valid? |
||||
return :T unless self.trusted? |
||||
|
||||
states = self.states |
||||
|
||||
{ critical: :G, error: :F, warning: :E }.each do |type, grade| |
||||
return grade if states[type].any? { |s| s == true } |
||||
end |
||||
|
||||
{good: %i(D C), great: %i(C B), best: %i(B A)}.each do |type, scores| |
||||
state = states[type] |
||||
return scores.first if state.all? { |s| s != false } |
||||
if state.any? { |s| s == false } |
||||
Logger.info { "Missing #{type} : #{states[type].select { |s| s == false }.collect &:key}" } |
||||
return scores.last |
||||
end |
||||
end |
||||
|
||||
:'A+' |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,149 @@ |
||||
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 |
||||
end |
@ -1,90 +1,92 @@ |
||||
describe CryptCheck::Tls::Cert do |
||||
describe '::trusted?' do |
||||
it 'must accept valid certificate' do |
||||
FakeTime.freeze Time.utc(2000, 1, 1) do |
||||
cert, *chain, ca = chain(%w(ecdsa-prime256v1 intermediate ca)) |
||||
trust = ::CryptCheck::Tls::Cert.trusted? cert, chain, roots: ca |
||||
expect(trust).to eq :trusted |
||||
module CryptCheck::Tls |
||||
describe Cert do |
||||
describe '::trusted?' do |
||||
it 'must accept valid certificate' do |
||||
FakeTime.freeze Time.utc(2000, 1, 1) do |
||||
cert, *chain, ca = chain(%w(ecdsa-prime256v1 intermediate ca)) |
||||
trust = Cert.trusted? cert, chain, roots: ca |
||||
expect(trust).to eq :trusted |
||||
end |
||||
end |
||||
end |
||||
|
||||
it 'must reject self signed certificate' do |
||||
cert, ca = chain(%w(self-signed ca)) |
||||
trust = ::CryptCheck::Tls::Cert.trusted? cert, [], roots: ca |
||||
expect(trust).to eq 'self signed certificate' |
||||
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 = ::CryptCheck::Tls::Cert.trusted? cert, nil, roots: ca |
||||
expect(trust).to eq 'self signed certificate' |
||||
end |
||||
# 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 = ::CryptCheck::Tls::Cert.trusted? cert, chain, roots: [] |
||||
expect(trust).to eq 'unable to get issuer 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 = ::CryptCheck::Tls::Cert.trusted? cert, chain, roots: ca |
||||
expect(trust).to eq 'unable to get local 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 = ::CryptCheck::Tls::Cert.trusted? cert, chain, roots: ca |
||||
expect(trust).to eq 'certificate has expired' |
||||
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 |
||||
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 = ::CryptCheck::Tls::Cert.trusted? cert, chain, roots: ca |
||||
expect(trust).to eq 'certificate is not yet valid' |
||||
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 |
||||
end |
||||
|
||||
describe '#md5?' do |
||||
it 'must detect md5 certificate' do |
||||
cert = ::CryptCheck::Tls::Cert.new cert(:md5) |
||||
expect(cert.md5?).to be true |
||||
describe '#md5?' do |
||||
it 'must detect md5 certificate' do |
||||
cert = Cert.new cert(:md5) |
||||
expect(cert.md5?).to be true |
||||
|
||||
cert = ::CryptCheck::Tls::Cert.new cert(:sha1) |
||||
expect(cert.md5?).to be false |
||||
cert = Cert.new cert(:sha1) |
||||
expect(cert.md5?).to be false |
||||
|
||||
cert = ::CryptCheck::Tls::Cert.new cert(:ecdsa, :prime256v1) |
||||
expect(cert.md5?).to be false |
||||
cert = Cert.new cert(:ecdsa, :prime256v1) |
||||
expect(cert.md5?).to be false |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe '#sha1?' do |
||||
it 'must detect sha1 certificate' do |
||||
cert = ::CryptCheck::Tls::Cert.new cert(:md5) |
||||
expect(cert.sha1?).to be false |
||||
describe '#sha1?' do |
||||
it 'must detect sha1 certificate' do |
||||
cert = Cert.new cert(:md5) |
||||
expect(cert.sha1?).to be false |
||||
|
||||
cert = ::CryptCheck::Tls::Cert.new cert(:sha1) |
||||
expect(cert.sha1?).to be true |
||||
cert = Cert.new cert(:sha1) |
||||
expect(cert.sha1?).to be true |
||||
|
||||
cert = ::CryptCheck::Tls::Cert.new cert(:ecdsa, :prime256v1) |
||||
expect(cert.sha1?).to be false |
||||
cert = Cert.new cert(:ecdsa, :prime256v1) |
||||
expect(cert.sha1?).to be false |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe '#sha2?' do |
||||
it 'must detect sha2 certificate' do |
||||
cert = ::CryptCheck::Tls::Cert.new cert(:md5) |
||||
expect(cert.sha2?).to be false |
||||
describe '#sha2?' do |
||||
it 'must detect sha2 certificate' do |
||||
cert = Cert.new cert(:md5) |
||||
expect(cert.sha2?).to be false |
||||
|
||||
cert = ::CryptCheck::Tls::Cert.new cert(:sha1) |
||||
expect(cert.sha2?).to be false |
||||
cert = Cert.new cert(:sha1) |
||||
expect(cert.sha2?).to be false |
||||
|
||||
cert = ::CryptCheck::Tls::Cert.new cert(:ecdsa, :prime256v1) |
||||
expect(cert.sha2?).to be true |
||||
cert = Cert.new cert(:ecdsa, :prime256v1) |
||||
expect(cert.sha2?).to be true |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
@ -1,129 +1,130 @@ |
||||
describe CryptCheck::Tls::Host do |
||||
def host(*args, **kargs) |
||||
do_in_serv *args, **kargs do |host, port| |
||||
CryptCheck::Tls::Host.new host, port |
||||
module CryptCheck::Tls |
||||
describe Host do |
||||
def host(*args, **kargs) |
||||
do_in_serv *args, **kargs do |host, port| |
||||
Host.new host, port |
||||
end |
||||
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) } |
||||
def servers(*args, **kargs) |
||||
host(*args, **kargs).servers |
||||
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) } |
||||
def error(*args, **kargs) |
||||
host(*args, **kargs).error |
||||
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' |
||||
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 |
||||
|
||||
error = error() |
||||
expect_error error, ::SocketError, 'getaddrinfo: Name or service not known' |
||||
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 |
||||
|
||||
it 'return error if analysis too long' do |
||||
stub_const 'CryptCheck::Tls::Host::MAX_ANALYSIS_DURATION', 1 |
||||
allow_any_instance_of(CryptCheck::Tls::Host).to receive(:server) { sleep 2 } |
||||
servers = servers(host: Helpers::DEFAULT_IPv6) |
||||
expect(servers.size).to be 1 |
||||
expect_grade servers, Helpers:: |