diff --git a/lib/cryptcheck/status.rb b/lib/cryptcheck/status.rb index 31d69e8..b5f89eb 100644 --- a/lib/cryptcheck/status.rb +++ b/lib/cryptcheck/status.rb @@ -1,9 +1,10 @@ module CryptCheck - module Status - LEVELS = %i(critical error warning good perfect best).freeze - PROBLEMS = %i(critical error warning).freeze + class Status + 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 @@ -22,17 +23,25 @@ module CryptCheck self.min PROBLEMS, statuses end + def self.sort(statuses) + statuses.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(statuses) - statuses = [ statuses ] unless statuses.respond_to? :first - first = statuses.first + statuses = [statuses] unless statuses.respond_to? :first + first = statuses.first statuses = statuses.collect &:status if first.respond_to? :status statuses end def self.min(levels, statuses) return nil if statuses.empty? - (levels & statuses).first + (levels & statuses).last end end end diff --git a/lib/cryptcheck/tls/cipher.rb b/lib/cryptcheck/tls/cipher.rb index 58506a4..bcdf3c2 100644 --- a/lib/cryptcheck/tls/cipher.rb +++ b/lib/cryptcheck/tls/cipher.rb @@ -93,7 +93,7 @@ module CryptCheck end def ecc? - ecdsa? or ecdhe? + ecdsa? or ecdhe? or ecdh? end def sweet32? @@ -116,14 +116,7 @@ module CryptCheck [:sweet32, Proc.new { |s| s.sweet32? }, :error], #[:cbc, Proc.new { |s| s.cbc? }, :warning], - #[:dhe, Proc.new { |s| s.dhe? }, :warning], - # [:weak_dh, Proc.new do |s| - # dh = s.dh - # next nil unless dh - # status = dh.status - # next status if %i(critical error warning).include? status - # nil - # end], + [:dhe, Proc.new { |s| s.dhe? }, :warning], [:no_pfs, Proc.new { |s| not s.pfs? }, :warning], [:pfs, Proc.new { |s| s.pfs? }, :good], @@ -135,16 +128,16 @@ module CryptCheck @states = Status.collect { |s| [s, []] }.to_h CHECKS.each do |name, check, status| result = check.call self - states[status ? status : result] << name if result + @states[status ? status : result] << name if result end - - @status = Status[@states.reject { |_, v| v.empty? }.keys] + 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 ' ' + states = @states.collect { |k, vs| vs.collect { |v| v.to_s.colorize k } }.flatten.join ' ' "#{@method} #{@name.colorize @status} [#{states}]" when :short @name.colorize @status @@ -152,6 +145,7 @@ module CryptCheck 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] @@ -271,12 +265,18 @@ module CryptCheck end end - ALL = 'ALL:COMPLEMENTOFALL' + def <=>(other) + status = Status.compare self, other + return status if status != 0 + @name <=> other.name + end + + ALL = 'ALL:COMPLEMENTOFALL' SUPPORTED = Method.collect do |m| context = ::OpenSSL::SSL::SSLContext.new m.name context.ciphers = ALL - [m, context.ciphers.collect { |c| Cipher.new m, c.first }] - end.to_h + [m, context.ciphers.collect { |c| Cipher.new m, c.first }.sort ] + end.to_h.freeze end end end diff --git a/lib/cryptcheck/tls/curve.rb b/lib/cryptcheck/tls/curve.rb index 8b3f098..77bd8b4 100644 --- a/lib/cryptcheck/tls/curve.rb +++ b/lib/cryptcheck/tls/curve.rb @@ -17,7 +17,7 @@ module CryptCheck SUPPORTED = %w(secp256k1 sect283k1 sect283r1 secp384r1 sect409k1 sect409r1 secp521r1 sect571k1 sect571r1 prime192v1 prime256v1 - brainpoolP256r1 brainpoolP384r1 brainpoolP512r1).collect { |c| self.new c } + brainpoolP256r1 brainpoolP384r1 brainpoolP512r1).collect { |c| self.new c }.freeze extend Enumerable diff --git a/lib/cryptcheck/tls/method.rb b/lib/cryptcheck/tls/method.rb index aa0d5da..d0345e2 100644 --- a/lib/cryptcheck/tls/method.rb +++ b/lib/cryptcheck/tls/method.rb @@ -13,8 +13,8 @@ module CryptCheck @name = name end - EXISTING = %i(TLSv1_2 TLSv1_1 TLSv1 SSLv3 SSLv2) - SUPPORTED = (EXISTING & ::OpenSSL::SSL::SSLContext::METHODS).collect { |m| self.new m } + EXISTING = %i(TLSv1_2 TLSv1_1 TLSv1 SSLv3 SSLv2).freeze + SUPPORTED = (EXISTING & ::OpenSSL::SSL::SSLContext::METHODS).collect { |m| self.new m }.freeze def to_s colors = case @name @@ -25,6 +25,10 @@ module CryptCheck end @name.to_s.colorize colors end + + def <=>(other) + EXISTING.find_index(@name) <=> EXISTING.find_index(other.name) + end end end end diff --git a/lib/cryptcheck/tls/server.rb b/lib/cryptcheck/tls/server.rb index c83a742..707d345 100644 --- a/lib/cryptcheck/tls/server.rb +++ b/lib/cryptcheck/tls/server.rb @@ -40,14 +40,16 @@ module CryptCheck fetch_supported_methods fetch_supported_ciphers + fetch_ciphers_preferences + fetch_ecdsa_certs fetch_supported_curves - - fetch_ciphers_preferences + fetch_curves_preference # verify_certs check_fallback_scsv + exit end def supported_method?(method) @@ -87,6 +89,37 @@ module CryptCheck end.to_h end + def fetch_ciphers_preferences + Logger.info { '' } + Logger.info { 'Cipher suite preferences' } + + @preferences = @supported_ciphers.collect do |method, ciphers| + ciphers = ciphers.keys + preferences = if ciphers.size < 2 + Logger.info { method.to_s + ' : ' + 'not applicable'.colorize(:unknown) } + nil + else + a, b, _ = ciphers + ab = ssl_client(method, [a, b]).cipher.first + ba = ssl_client(method, [b, a]).cipher.first + if ab != ba + Logger.info { method.to_s + ' : ' + 'client preference'.colorize(:warning) } + :client + else + sort = -> (a, b) do + connection = ssl_client method, [a, b] + cipher = connection.cipher.first + cipher == a.name ? -1 : 1 + end + preferences = ciphers.sort &sort + Logger.info { method.to_s + ' : ' + preferences.collect { |c| c.to_s :short }.join(', ') } + preferences + end + end + [method, preferences] + end.to_h + end + def fetch_ecdsa_certs @ecdsa_certs = {} @@ -152,6 +185,7 @@ module CryptCheck begin ssl_client method, ecdh, curves: curve Logger.info { "ECC curve #{curve} : supported" } + true rescue TLSException Logger.debug { "ECC curve #{curve} : not supported" } false @@ -162,34 +196,33 @@ module CryptCheck end end - def fetch_ciphers_preferences - Logger.info { '' } - Logger.info { 'Server preferences' } - - @preferences = @supported_ciphers.collect do |method, ciphers| - ciphers = ciphers.keys - if ciphers.size < 2 - Logger.info { "Preference not applicable for #{method}" } - else - a, b, _ = ciphers - ab = ssl_client(method, [a, b]).cipher.first - ba = ssl_client(method, [b, a]).cipher.first - if ab != ba - Logger.info { 'Server use client preference for '.colorize(:warning) + method.to_s } - :client - else - sort = -> (a, b) do - connection = ssl_client method, [a, b] - cipher = connection.cipher.first - cipher == a.name ? -1 : 1 - end - preferences = ciphers.sort &sort - Logger.info { "Cipher preference for #{method} is #{preferences.collect { |c| c.to_s :short }.join ':'}" } - preferences - end - end - [method, preferences] - end.to_h + def fetch_curves_preference + @curves_preference = if @supported_curves.size < 2 + Logger.info { 'Curves preference : ' + 'not applicable'.colorize(:unknown) } + nil + else + method, cipher = @supported_ciphers.collect do |method, ciphers| + cipher = ciphers.keys.detect { |c| c.ecdh? or c.ecdhe? } + [method, cipher] + end.detect { |n| !n.nil? } + + a, b, _ = @supported_curves + ab = ssl_client(method, cipher, curves: [a, b]).tmp_key.curve + ba = ssl_client(method, cipher, curves: [b, a]).tmp_key.curve + if ab != ba + Logger.info { 'Curves preference : ' + 'client preference'.colorize(:warning) } + :client + else + sort = -> (a, b) do + connection = ssl_client method, cipher, curves: [a, b] + curve = connection.tmp_key.curve + curve == a.name ? -1 : 1 + end + preferences = @supported_curves.sort &sort + Logger.info { 'Curves preference : ' + preferences.collect { |c| c.to_s }.join(', ') } + preferences + end + end end def check_fallback_scsv @@ -224,7 +257,7 @@ module CryptCheck method = method.name class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{method.to_s.downcase}? - @supported_methods.detect { |m| m.name == method } + @supported_methods.detect { |m| m.name == method } end RUBY_EVAL end