diff --git a/lib/cryptcheck.rb b/lib/cryptcheck.rb index a09d0db..cd43944 100644 --- a/lib/cryptcheck.rb +++ b/lib/cryptcheck.rb @@ -2,7 +2,6 @@ require 'colorize' require 'ipaddr' require 'timeout' require 'yaml' -require 'cryptcheck/tls/fixture' module CryptCheck MAX_ANALYSIS_DURATION = 120 @@ -96,6 +95,7 @@ module CryptCheck else server.new *a, **kargs end + ap s.status exit if grade g = grade.new s @@ -181,3 +181,6 @@ module CryptCheck SCORES.index a.grade end end + +require 'cryptcheck/fixture' +require 'cryptcheck/tls/fixture' diff --git a/lib/cryptcheck/fixture.rb b/lib/cryptcheck/fixture.rb new file mode 100644 index 0000000..6c51f4e --- /dev/null +++ b/lib/cryptcheck/fixture.rb @@ -0,0 +1,52 @@ +class String + alias :colorize_old :colorize + + COLORS = { + critical: { color: :white, background: :red }, + error: :red, + warning: :light_red, + good: :green, + perfect: :blue, + best: :magenta, + unknown: { background: :black } + } + + def colorize(state) + color = COLORS[state] || state + self.colorize_old color + end +end + +class Exception + BACKTRACE_REGEXP = /^(.*):(\d+):in `(.*)'$/ + + def colorize + $stderr.puts self.message.colorize(:red) + self.backtrace.each do |line| + line = BACKTRACE_REGEXP.match line + line = '%s:%s:in `%s\'' % [ + line[1].colorize(:yellow), + line[2].colorize(:blue), + line[3].colorize(:magenta) + ] + $stderr.puts line + end + end +end + +class Integer + def humanize + secs = self + [[60, :second], + [60, :minute], + [24, :hour], + [30, :day], + [12, :month]].map do |count, name| + if secs > 0 + secs, n = secs.divmod count + n = n.to_i + n > 0 ? "#{n} #{name}#{n > 1 ? 's' : ''}" : nil + end + end.compact.reverse.join ' ' + end +end diff --git a/lib/cryptcheck/tls/cert.rb b/lib/cryptcheck/tls/cert.rb index 52b8092..dafca2a 100644 --- a/lib/cryptcheck/tls/cert.rb +++ b/lib/cryptcheck/tls/cert.rb @@ -106,6 +106,16 @@ module CryptCheck def issuer @cert.issuer end + + include ::CryptCheck::Statused + + CHECKS = [:weak_sign, -> (s) do + not (SIGNATURE_ALGORITHMS_X509[s.signature_algorithm] & WEAK_SIGN).empty? + end, :critical].freeze + + def children + [self.key] + end end end end diff --git a/lib/cryptcheck/tls/fixture.rb b/lib/cryptcheck/tls/fixture.rb index 52ad287..bf10ad3 100644 --- a/lib/cryptcheck/tls/fixture.rb +++ b/lib/cryptcheck/tls/fixture.rb @@ -1,58 +1,5 @@ require 'openssl' -class String - alias :colorize_old :colorize - - COLORS = { - critical: { color: :white, background: :red }, - error: :red, - warning: :light_red, - good: :green, - perfect: :blue, - best: :magenta, - unknown: { background: :black } - } - - def colorize(state) - color = COLORS[state] || state - self.colorize_old color - end -end - -class Exception - BACKTRACE_REGEXP = /^(.*):(\d+):in `(.*)'$/ - - def colorize - $stderr.puts self.message.colorize(:red) - self.backtrace.each do |line| - line = BACKTRACE_REGEXP.match line - line = '%s:%s:in `%s\'' % [ - line[1].colorize(:yellow), - line[2].colorize(:blue), - line[3].colorize(:magenta) - ] - $stderr.puts line - end - end -end - -class Integer - def humanize - secs = self - [[60, :second], - [60, :minute], - [24, :hour], - [30, :day], - [12, :month]].map do |count, name| - if secs > 0 - secs, n = secs.divmod count - n = n.to_i - n > 0 ? "#{n} #{name}#{n > 1 ? 's' : ''}" : nil - end - end.compact.reverse.join ' ' - end -end - class ::OpenSSL::PKey::EC def type :ecc @@ -70,18 +17,23 @@ class ::OpenSSL::PKey::EC "ECC #{self.size} bits" end - def status - case self.size - when 0...160 - :critical - when 160...192 - :error - when 192...256 - :warning - when 256...364 - else - :good - end + include ::CryptCheck::Statused + + CHECKS = [ + [:weak_key, -> (s) do + case s.size + when 0...160 + :critical + when 160...192 + :error + when 192...256 + :warning + end + end] + ].freeze + + def checks + CHECKS end end @@ -98,16 +50,21 @@ class ::OpenSSL::PKey::RSA "RSA #{self.size} bits" end - def status - case self.size - when 0...1024 - :critical - when 1024...2048 - :error - when 2048...4096 - else - :good - end + include ::CryptCheck::Statused + + CHECKS = [ + [:weak_key, -> (s) do + case s.size + when 0...1024 + :critical + when 1024...2048 + :error + end + end] + ].freeze + + def checks + CHECKS end end @@ -124,8 +81,14 @@ class ::OpenSSL::PKey::DSA "DSA #{self.size} bits" end - def status - return :critical + include ::CryptCheck::Statused + + CHECKS = [ + [:weak_key, -> (_) { :critical }] + ].freeze + + def checks + CHECKS end end @@ -142,16 +105,23 @@ class ::OpenSSL::PKey::DH "DH #{self.size} bits" end - def status - case self.size - when 0...1024 - :critical - when 1024...2048 - :error - when 2048...4096 - else - :good - end + include ::CryptCheck::Statused + + CHECKS = [ + [:weak_dh, -> (s) do + case s.size + when 0...1024 + :critical + when 1024...2048 + :error + else + :warning + end + end] + ].freeze + + def checks + CHECKS end end diff --git a/lib/cryptcheck/tls/grade.rb b/lib/cryptcheck/tls/grade.rb index a953d43..e0e22ce 100644 --- a/lib/cryptcheck/tls/grade.rb +++ b/lib/cryptcheck/tls/grade.rb @@ -67,73 +67,6 @@ module CryptCheck 'A+' end - - CHECKS = ([ - # Certificates - [:weak_sign, Proc.new { |s| - Cert::WEAK_SIGN[:critical] - }, :critical], - - # Keys - [:weak_key, Proc.new { |s| Status.problem s.keys.collect &:status }], - - # DH - [:weak_dh, Proc.new { |s| Status.problem s.dh.collect &:status }], - - # Protocols - [:ssl, Proc.new { |s| s.ssl? }, :critical], - [:tls12, Proc.new { |s| s.tlsv1_2? }, :good], - [:tls12_only, Proc.new { |s| s.tlsv1_2_only? }, :perfect], - - # Ciphers - [: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], - - [:no_pfs, Proc.new { |s| not s.pfs_only? }, :warning], - [:pfs, Proc.new { |s| s.pfs? }, :good], - [:pfs_only, Proc.new { |s| s.pfs_only? }, :perfect], - - [:no_ecdhe, Proc.new { |s| not s.ecdhe? }, :warning], - [:ecdhe, Proc.new { |s| s.ecdhe? }, :good], - [:ecdhe_only, Proc.new { |s| s.ecdhe_only? }, :perfect], - - [:aead, Proc.new { |s| s.aead? }, :good], - #[:aead_only, Proc.new { |s| s.aead_only? }, :best], - ] + Cert::WEAK_SIGN.collect do |level, hashes| - hashes.collect do |hash| - ["#{hash}_sig?".to_sym, Proc.new { |s| s.call "#{hash}_sig?".to_sym }, level ] - end - end.flatten(1)).freeze - - def checks - checks = CHECKS - unless @server.fallback_scsv? == nil - checks += [ - [:no_fallback_scsv, Proc.new { |s| not s.fallback_scsv? }, :error], - [:fallback_scsv, Proc.new { |s| s.fallback_scsv? }, :good] - ] - end - checks - end - - def calculate_states - states = Status.empty - @checks.each do |name, check, status| - result = check.call @server - if result - state = states[status ? status : result] - state << name if state - end - end - states - end end end end diff --git a/lib/cryptcheck/tls/https/server.rb b/lib/cryptcheck/tls/https/server.rb index 234bef8..56e0f32 100644 --- a/lib/cryptcheck/tls/https/server.rb +++ b/lib/cryptcheck/tls/https/server.rb @@ -20,7 +20,7 @@ module CryptCheck follow_redirects: false, verify: false, timeout: SSL_TIMEOUT, - ssl_version: @supported_methods.first.name, + ssl_version: @supported_methods.first.to_sym, ciphers: Cipher::ALL } if header = response.headers['strict-transport-security'] @@ -43,9 +43,18 @@ module CryptCheck end LONG_HSTS = 6*30*24*60*60 + def hsts_long? hsts? and @hsts >= LONG_HSTS end + + def checks + super + [ + [:hsts, -> (s) { s.hsts? }, :good], + [:hsts_long, -> (s) { s.hsts_long? }, :perfect], + #[:must_staple, -> (s) { s.must_staple? }, :best], + ] + end end end end diff --git a/lib/cryptcheck/tls/server.rb b/lib/cryptcheck/tls/server.rb index 75bc775..4726613 100644 --- a/lib/cryptcheck/tls/server.rb +++ b/lib/cryptcheck/tls/server.rb @@ -126,7 +126,7 @@ module CryptCheck def fetch_dh @dh = @supported_ciphers.collect do |_, ciphers| - ciphers.values.collect(&:tmp_key).select { |d| d.is_a? OpenSSL::PKey::DH }.collect &:size + ciphers.values.collect(&:tmp_key).select { |d| d.is_a? OpenSSL::PKey::DH } end.flatten end @@ -285,7 +285,7 @@ module CryptCheck Method.each do |method| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{method.to_sym.downcase}? - @supported_methods.detect { |m| m == method } + @supported_methods.detect { |m| m == :#{method.to_sym} } end RUBY_EVAL end @@ -293,7 +293,7 @@ module CryptCheck Cipher::TYPES.each do |type, _| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{type}? - @supported_ciphers.any? { |c| c.#{type}? } + uniq_supported_ciphers.any? { |c| c.#{type}? } end RUBY_EVAL end @@ -315,31 +315,31 @@ module CryptCheck end def pfs? - supported_ciphers.any? { |c| c.pfs? } + uniq_supported_ciphers.any? { |c| c.pfs? } end def pfs_only? - supported_ciphers.all? { |c| c.pfs? } + uniq_supported_ciphers.all? { |c| c.pfs? } end def ecdhe? - supported_ciphers.any? { |c| c.ecdhe? } + uniq_supported_ciphers.any? { |c| c.ecdhe? } end def ecdhe_only? - supported_ciphers.all? { |c| c.ecdhe? } + uniq_supported_ciphers.all? { |c| c.ecdhe? } end def aead? - supported_ciphers.any? { |c| c.aead? } + uniq_supported_ciphers.any? { |c| c.aead? } end def aead_only? - supported_ciphers.all? { |c| c.aead? } + uniq_supported_ciphers.all? { |c| c.aead? } end def sweet32? - supported_ciphers.any? { |c| c.sweet32? } + uniq_supported_ciphers.any? { |c| c.sweet32? } end def fallback_scsv? @@ -350,6 +350,52 @@ module CryptCheck @cert.extensions.any? { |e| e.oid == '1.3.6.1.5.5.7.1.24' } end + include Statused + + 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], + [: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 + + def checks + checks = CHECKS + unless self.fallback_scsv? == nil + checks += [ + [:no_fallback_scsv, -> (s) { not s.fallback_scsv? }, :error], + [:fallback_scsv, -> (s) { s.fallback_scsv? }, :good] + ] + end + checks + end + + def children + @certs + @dh + end + private def connect(&block) socket = ::Socket.new @family, sock_type @@ -497,12 +543,9 @@ module CryptCheck @dh = dh end - Cert::SIGNATURE_ALGORITHMS.each do |s| - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{s}_sign? - @certs.any? &:#{s}? - end - RUBY_EVAL + private + def uniq_supported_ciphers + @supported_ciphers.values.collect(&:keys).flatten.uniq end end