@@ -4,7 +4,7 @@ require 'timeout' | |||
require 'yaml' | |||
module CryptCheck | |||
MAX_ANALYSIS_DURATION = 120 | |||
MAX_ANALYSIS_DURATION = 600 | |||
PARALLEL_ANALYSIS = 10 | |||
class AnalysisFailure | |||
@@ -109,9 +109,15 @@ module CryptCheck | |||
include ::CryptCheck::State | |||
CHECKS = [:weak_sign, -> (s) do | |||
not (SIGNATURE_ALGORITHMS_X509[s.signature_algorithm] & WEAK_SIGN).empty? | |||
end, :critical].freeze | |||
CHECKS = WEAK_SIGN.collect do |level, hashes| | |||
hashes.collect do |hash| | |||
["#{hash}_sign".to_sym, -> (s) { s.send "#{hash}?" }, level] | |||
end | |||
end.flatten(1).freeze | |||
def checks | |||
CHECKS | |||
end | |||
def children | |||
[self.key] | |||
@@ -36,11 +36,10 @@ module CryptCheck | |||
ccm: %w(CCM) | |||
} | |||
attr_reader :method, :name, :states, :status | |||
attr_reader :method, :name | |||
def initialize(method, name) | |||
@method, @name = method, name | |||
fetch_states | |||
end | |||
extend Enumerable | |||
@@ -102,70 +101,33 @@ module CryptCheck | |||
size <= 64 | |||
end | |||
CHECKS = [ | |||
[:psk, Proc.new { |s| s.psk? }, :critical], | |||
[:srp, Proc.new { |s| s.srp? }, :critical], | |||
[:dss, Proc.new { |s| s.dss? }, :critical], | |||
[:anonymous, Proc.new { |s| s.anonymous? }, :critical], | |||
[:null, Proc.new { |s| s.null? }, :critical], | |||
[:export, Proc.new { |s| s.export? }, :critical], | |||
[:des, Proc.new { |s| s.des? }, :critical], | |||
[:md5, Proc.new { |s| s.md5? }, :critical], | |||
[:rc4, Proc.new { |s| s.rc4? }, :error], | |||
[:sweet32, Proc.new { |s| s.sweet32? }, :error], | |||
#[:cbc, Proc.new { |s| s.cbc? }, :warning], | |||
[:dhe, Proc.new { |s| s.dhe? }, :warning], | |||
[:no_pfs, Proc.new { |s| not s.pfs? }, :warning], | |||
[:pfs, Proc.new { |s| s.pfs? }, :good], | |||
[:ecdhe, Proc.new { |s| s.ecdhe? }, :good], | |||
[:aead, Proc.new { |s| s.aead? }, :good], | |||
] | |||
def fetch_states | |||
@states = Status.collect { |s| [s, []] }.to_h | |||
CHECKS.each do |name, check, status| | |||
result = check.call self | |||
@states[status ? status : result] << name if result | |||
end | |||
statuses = @states.reject { |_, v| v.empty? }.keys | |||
@status = Status[statuses] | |||
end | |||
def to_s(type = :long) | |||
case type | |||
when :long | |||
states = @states.collect { |k, vs| vs.collect { |v| v.to_s.colorize k } }.flatten.join ' ' | |||
"#{@method} #{@name.colorize @status} [#{states}]" | |||
states = self.states.collect { |k, vs| vs.collect { |v| v.to_s.colorize k } }.flatten.join ' ' | |||
"#{@method} #{@name.colorize self.status} [#{states}]" | |||
when :short | |||
@name.colorize @status | |||
@name.colorize self.status | |||
end | |||
end | |||
PRIORITY = { good: 1, none: 2, warning: 3, error: 4, critical: 5 } | |||
def self.sort(ciphers) | |||
ciphers.sort do |a, b| | |||
error_a, error_b = PRIORITY[a.score], PRIORITY[b.score] | |||
compare = error_a <=> error_b | |||
next compare unless compare == 0 | |||
def <=>(other) | |||
compare = State.compare self, other | |||
return compare unless compare == 0 | |||
size_a, size_b = a.size, b.size | |||
compare = size_b <=> size_a | |||
next compare unless compare == 0 | |||
size_a, size_b = a.size, b.size | |||
compare = size_b <=> size_a | |||
return compare unless compare == 0 | |||
dh_a, dh_b = a.dh, b.dh | |||
next -1 if not dh_a and dh_b | |||
next 1 if dh_a and not dh_b | |||
next a.name <=> b.name if not dh_a and not dh_b | |||
dh_a, dh_b = a.dh, b.dh | |||
return -1 if not dh_a and dh_b | |||
return 1 if dh_a and not dh_b | |||
return a.name <=> b.name if not dh_a and not dh_b | |||
compare = b.dh.size <=> a.dh.size | |||
next compare unless compare == 0 | |||
compare = b.dh.size <=> a.dh.size | |||
return compare unless compare == 0 | |||
a.name <=> b.name | |||
end | |||
a.name <=> b.name | |||
end | |||
def self.list(cipher_suite = 'ALL:COMPLEMENTOFALL', method: :TLSv1_2) | |||
@@ -265,8 +227,33 @@ module CryptCheck | |||
end | |||
end | |||
include State | |||
CHECKS = [ | |||
[:dss, -> (c) { c.dss? }, :critical], | |||
[:anonymous, -> (c) { c.anonymous? }, :critical], | |||
[:null, -> (c) { c.null? }, :critical], | |||
[:export, -> (c) { c.export? }, :critical], | |||
[:des, -> (c) { c.des? }, :critical], | |||
[:md5, -> (c) { c.md5? }, :critical], | |||
[:rc4, -> (c) { c.rc4? }, :error], | |||
[:sweet32, -> (c) { c.sweet32? }, :error], | |||
[:no_pfs, -> (c) { not c.pfs? }, :warning], | |||
[:pfs, -> (c) { c.pfs? }, :good], | |||
[:dhe, -> (c) { c.dhe? }, :warning], | |||
[:ecdhe, -> (c) { c.ecdhe? }, :good], | |||
[:aead, -> (c) { c.aead? }, :good] | |||
].freeze | |||
def checks | |||
CHECKS | |||
end | |||
def <=>(other) | |||
status = Status.compare self, other | |||
status = State.compare self, other | |||
return status if status != 0 | |||
@name <=> other.name | |||
end | |||
@@ -275,8 +262,8 @@ module CryptCheck | |||
SUPPORTED = Method.collect do |m| | |||
context = ::OpenSSL::SSL::SSLContext.new m.to_sym | |||
context.ciphers = ALL | |||
[m, context.ciphers.collect { |c| Cipher.new m, c.first }.sort ] | |||
ciphers = context.ciphers.collect { |c| Cipher.new m, c.first } | |||
[m, ciphers.sort] | |||
end.to_h.freeze | |||
end | |||
end | |||
@@ -114,8 +114,6 @@ class ::OpenSSL::PKey::DH | |||
:critical | |||
when 1024...2048 | |||
:error | |||
else | |||
:warning | |||
end | |||
end] | |||
].freeze | |||
@@ -1,12 +1,12 @@ | |||
module CryptCheck | |||
module Tls | |||
class Grade | |||
attr_reader :server, :grade, :states | |||
attr_reader :server, :grade, :status | |||
def initialize(server) | |||
@server = server | |||
@status = @server.status | |||
@checks = checks | |||
@states = calculate_states | |||
@grade = calculate_grade | |||
end | |||
@@ -30,39 +30,70 @@ module CryptCheck | |||
Logger.info { "Grade : #{self.grade.colorize color }" } | |||
Logger.info { '' } | |||
Status.each do |color| | |||
states = @states[color] | |||
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 | |||
weak_key | |||
weak_dh | |||
sslv2 sslv3 | |||
), | |||
error: %i( | |||
weak_key | |||
weak_dh | |||
), | |||
warning: %i( | |||
weak_key | |||
weak_dh | |||
dhe | |||
), | |||
good: %i( | |||
tls12 | |||
), | |||
perfect: %i( | |||
tls12_only | |||
), | |||
best: %i( | |||
) | |||
}.freeze | |||
def checks | |||
end | |||
def calculate_grade | |||
case | |||
when !@states[:critical].empty? | |||
when !@status[:critical].empty? | |||
return 'G' | |||
when !@states[:error].empty? | |||
when !@status[:error].empty? | |||
return 'F' | |||
when !@states[:warning].empty? | |||
when !@status[:warning].empty? | |||
return 'E' | |||
end | |||
goods = @checks.select { |c| c.last == :good }.collect &:first | |||
unless goods.empty? | |||
return 'D' if @states[:good].empty? | |||
return 'C' if @states[:good] != goods | |||
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 @states[:perfect].empty? | |||
return 'B' if @states[:perfect] != perfects | |||
return 'C+' if @status[:perfect].empty? | |||
return 'B' if @status[:perfect] != perfects | |||
end | |||
bests = @checks.select { |c| c.last == :best }.collect &:first | |||
unless bests.empty? | |||
return 'B+' if @states[:best].empty? | |||
return 'A' if @states[:best] != bests | |||
return 'B+' if @status[:best].empty? | |||
return 'A' if @status[:best] != bests | |||
end | |||
'A+' | |||
@@ -2,14 +2,6 @@ module CryptCheck | |||
module Tls | |||
module Https | |||
class Grade < Tls::Grade | |||
def checks | |||
super + [ | |||
[:hsts, Proc.new { |s| s.hsts? }, :good], | |||
[:hsts_long, Proc.new { |s| s.hsts_long? }, :perfect], | |||
#[:must_staple, Proc.new { |s| s.must_staple? }, :best], | |||
] | |||
end | |||
end | |||
end | |||
end | |||
@@ -33,13 +33,17 @@ module CryptCheck | |||
EXISTING.find_index(self) <=> EXISTING.find_index(other) | |||
end | |||
# def eql?(other) | |||
# self.to_sym.eql? other.to_sym | |||
# end | |||
# | |||
# def equal?(other) | |||
# self.to_sym.equal? other.to_sym | |||
# end | |||
include State | |||
CHECKS = [ | |||
[:sslv2, -> (s) { s == :SSLv2 }, :critical], | |||
[:sslv3, -> (s) { s == :SSLv3 }, :critical], | |||
[:tlsv1_2, -> (s) { s == :TLSv1_2 }, :good] | |||
] | |||
def checks | |||
CHECKS | |||
end | |||
end | |||
end | |||
end |
@@ -33,34 +33,18 @@ module CryptCheck | |||
tlsv1_2? and not ssl? and not tlsv1? and not tlsv1_1? | |||
end | |||
def pfs? | |||
uniq_supported_ciphers.any? { |c| c.pfs? } | |||
end | |||
def pfs_only? | |||
uniq_supported_ciphers.all? { |c| c.pfs? } | |||
end | |||
def ecdhe? | |||
uniq_supported_ciphers.any? { |c| c.ecdhe? } | |||
end | |||
def ecdhe_only? | |||
uniq_supported_ciphers.all? { |c| c.ecdhe? } | |||
end | |||
def aead? | |||
uniq_supported_ciphers.any? { |c| c.aead? } | |||
end | |||
def aead_only? | |||
uniq_supported_ciphers.all? { |c| c.aead? } | |||
end | |||
def sweet32? | |||
uniq_supported_ciphers.any? { |c| c.sweet32? } | |||
end | |||
def fallback_scsv? | |||
@fallback_scsv | |||
end | |||
@@ -72,31 +56,9 @@ module CryptCheck | |||
include State | |||
CHECKS = [ | |||
# Protocols | |||
[:ssl, -> (s) { s.ssl? }, :critical], | |||
[:tls12, -> (s) { s.tlsv1_2? }, :good], | |||
[:tls12_only, -> (s) { s.tlsv1_2_only? }, :perfect], | |||
# Ciphers | |||
[:dss, -> (s) { s.dss? }, :critical], | |||
[:anonymous, -> (s) { s.anonymous? }, :critical], | |||
[:null, -> (s) { s.null? }, :critical], | |||
[:export, -> (s) { s.export? }, :critical], | |||
[:des, -> (s) { s.des? }, :critical], | |||
[:md5, -> (s) { s.md5? }, :critical], | |||
[:rc4, -> (s) { s.rc4? }, :error], | |||
[:sweet32, -> (s) { s.sweet32? }, :error], | |||
[:no_pfs, -> (s) { not s.pfs_only? }, :warning], | |||
[:pfs, -> (s) { s.pfs? }, :good], | |||
[:tlsv1_2_only, -> (s) { s.tlsv1_2_only? }, :perfect], | |||
[:pfs_only, -> (s) { s.pfs_only? }, :perfect], | |||
[:no_ecdhe, -> (s) { not s.ecdhe? }, :warning], | |||
[:ecdhe, -> (s) { s.ecdhe? }, :good], | |||
[:ecdhe_only, -> (s) { s.ecdhe_only? }, :perfect], | |||
[:aead, -> (s) { s.aead? }, :good], | |||
#[:aead_only, -> (s) { s.aead_only? }, :best], | |||
].freeze | |||
@@ -112,7 +74,7 @@ module CryptCheck | |||
end | |||
def children | |||
@certs + @dh | |||
@certs + @dh + @supported_methods + uniq_supported_ciphers | |||
end | |||
include Engine | |||