Manage grades

new-scoring
aeris 6 years ago
parent 0a980f17ec
commit e0808a3937
  1. 12
      lib/cryptcheck/logger.rb
  2. 95
      lib/cryptcheck/state.rb
  3. 7
      lib/cryptcheck/tls/engine.rb
  4. 96
      lib/cryptcheck/tls/grade.rb
  5. 8
      lib/cryptcheck/tls/https/grade.rb
  6. 8
      lib/cryptcheck/tls/server.rb
  7. 6
      lib/cryptcheck/tls/smtp/grade.rb
  8. 3
      lib/cryptcheck/tls/xmpp.rb
  9. 8
      lib/cryptcheck/tls/xmpp/grade.rb

@ -1,6 +1,6 @@
module CryptCheck
class Logger
LEVELS = %i(trace debug info warning error fatal none)
LEVELS = %i(trace debug info warning error fatal none)
@@level = :info
def self.level=(level)
@ -12,6 +12,16 @@ module CryptCheck
output.puts(string ? string : block.call)
end
if Object.respond_to? :ai
def self.ap(name, object)
self.debug { "#{name} : #{object.ai}" }
end
else
def self.ap(name, object)
self.debug { "#{name} : #{object}" }
end
end
LEVELS.each do |level|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def self.#{level}(string=nil, output: $stdout, &block)

@ -0,0 +1,95 @@
module CryptCheck
module State
def states
@status ||= calculate_states
end
def status
State.status self.states.reject { |_, v| v.empty? }.keys
end
LEVELS = %i(best perfect good warning error critical).freeze
PROBLEMS = %i(warning error critical).freeze
extend Enumerable
def self.each(&block)
LEVELS.each &block
end
def self.empty
self.collect { |s| [s, []] }.to_h
end
def self.status(states)
states = self.convert states
self.min LEVELS, states
end
class << self
alias_method :'[]', :status
end
def self.problem(states)
states = self.convert states
self.min PROBLEMS, states
end
def self.sort(states)
states.sort { |a, b| self.compare a, b }
end
def self.compare(a, b)
LEVELS.find_index(a.status) <=> LEVELS.find_index(b.status)
end
private
def self.convert(status)
status = [status] unless status.respond_to? :first
first = status.first
status = status.collect &:status if first.respond_to? :status
status
end
def self.min(levels, states)
return nil if states.empty?
(levels & states).last
end
def self.merge(*states)
State.collect do |s|
state = states.collect { |ss| ss.fetch s, [] }
.inject(&:+).uniq
[s, state]
end.to_h
end
def children
[]
end
def perform_check(check)
name, check, level = check
result = check.call self
return nil unless result
level ||= result
[level, name]
end
def personal_states
states = State.empty
checks.each do |check|
level, name = perform_check check
next unless level
states[level] << name
end
states
end
def calculate_states
children_states = children.collect(&:states)
states = [personal_states] + children_states
State.merge *states
end
end
end

@ -29,8 +29,8 @@ module CryptCheck
attr_reader :certs, :keys, :dh, :supported_methods, :supported_ciphers, :supported_curves, :curves_preference
def initialize(hostname, family, ip, port)
@hostname, @family, @ip, @port = hostname, family, ip, port
def initialize(hostname, ip, family, port)
@hostname, @ip, @family, @port = hostname, ip, family, port
@dh = []
@name = "#@ip:#@port"
@ -412,6 +412,7 @@ module CryptCheck
# Then, filter cert to keep uniq fingerprint
@certs = certs.uniq { |c| c.fingerprint }
@trusted = @valid = true
@certs.each do |cert|
key = cert.key
identity = cert.valid?(@hostname || @ip)
@ -422,11 +423,13 @@ module CryptCheck
Logger.info { ' Identity : ' + 'valid'.colorize(:good) }
else
Logger.info { ' Identity : ' + 'invalid'.colorize(:error) }
@valid = false
end
if trust == :trusted
Logger.info { ' Trust : ' + 'trusted'.colorize(:good) }
else
Logger.info { ' Trust : ' + 'untrusted'.colorize(:error) + ' - ' + trust }
@trusted = false
end
end
@keys = @certs.collect &:key

@ -1,16 +1,17 @@
module CryptCheck
module Tls
class Grade
attr_reader :server, :grade, :status
attr_reader :server, :grade
def initialize(server)
@server = server
@status = @server.status
@checks = checks
@grade = calculate_grade
end
@states = @server.states
Logger.info { '' }
Logger.ap :checks, @checks
Logger.ap :states, @states
@grade = calculate_grade
def display
color = case @grade
when 'A', 'A+'
:best
@ -24,76 +25,83 @@ module CryptCheck
:error
when 'G'
:critical
when 'M', 'T'
when 'T', 'V'
:unknown
end
Logger.info { "Grade : #{self.grade.colorize color }" }
Logger.info { '' }
State.each do |color|
states = @status[color]
Logger.info { "#{color.to_s.capitalize} : #{states.collect { |s| s.to_s.colorize color }.join ' '}" } unless states.empty?
end
end
private
CHECKS = {
critical: %i(
mdc2_sign md2_sign md4_sign md5_sign sha_sign sha1_sign
best: %i(
),
perfect: %i(
tlsv1_2_only
pfs_only
ecdhe_only
),
good: %i(
tlsv1_2
pfs
ecdhe
aead
),
warning: %i(
weak_key
weak_dh
sslv2 sslv3
dhe
),
error: %i(
weak_key
weak_dh
),
warning: %i(
critical: %i(
mdc2_sign md2_sign md4_sign md5_sign sha_sign sha1_sign
weak_key
weak_dh
dhe
),
good: %i(
tls12
),
perfect: %i(
tls12_only
sslv2 sslv3
),
best: %i(
)
}.freeze
def checks
CHECKS
end
def calculate_grade
return 'V' unless @server.valid?
return 'T' unless @server.trusted?
case
when !@status[:critical].empty?
when !@states[:critical].empty?
return 'G'
when !@status[:error].empty?
when !@states[:error].empty?
return 'F'
when !@status[:warning].empty?
when !@states[:warning].empty?
return 'E'
end
goods = @checks.select { |c| c.last == :good }.collect &:first
unless goods.empty?
return 'D' if @status[:good].empty?
return 'C' if @status[:good] != goods
end
perfects = @checks.select { |c| c.last == :perfect }.collect &:first
unless perfects.empty?
return 'C+' if @status[:perfect].empty?
return 'B' if @status[:perfect] != perfects
end
[[:good, 'D', 'C'],
[:perfect, 'C', 'B'],
[:best, 'B', 'A']].each do |type, score1, score2|
expected = @checks[type]
unless expected.empty?
available = @states[type]
return score1 if available.empty?
missed = expected - available
unless missed.empty?
Logger.info { "Missing #{type} : #{missed}" }
return score2
end
bests = @checks.select { |c| c.last == :best }.collect &:first
unless bests.empty?
return 'B+' if @status[:best].empty?
return 'A' if @status[:best] != bests
# I'm not error prone. The code yes.
additional = available - expected
unless additional.empty?
Logger.fatal { "Developper missed #{type} : #{additional}".colorize :critical }
exit -1
end
end
end
'A+'

@ -2,6 +2,14 @@ module CryptCheck
module Tls
module Https
class Grade < Tls::Grade
CHECKS = {
good: %i(hsts),
perfect: %i(hsts_long)
}
def checks
State.merge super, CHECKS
end
end
end
end

@ -53,6 +53,14 @@ module CryptCheck
@cert.extensions.any? { |e| e.oid == '1.3.6.1.5.5.7.1.24' }
end
def valid?
@valid
end
def trusted?
@trusted
end
include State
CHECKS = [

@ -2,6 +2,12 @@ module CryptCheck
module Tls
module Smtp
class Grade < Tls::Grade
CHECKS = {
}
def checks
State.merge super, CHECKS
end
end
end
end

@ -1,6 +1,3 @@
require 'erb'
require 'parallel'
module CryptCheck
module Tls
module Xmpp

@ -2,10 +2,12 @@ module CryptCheck
module Tls
module Xmpp
class Grade < Tls::Grade
CHECKS = {
good: %i(required)
}
def checks
super + [
[:required, Proc.new { |s| s.required? }, :good],
]
State.merge super, CHECKS
end
end
end

Loading…
Cancel
Save