Move checks from grade to server/cert/key/dh

new-scoring
aeris 6 years ago
parent 13fa9541d6
commit 416737d33c
  1. 5
      lib/cryptcheck.rb
  2. 52
      lib/cryptcheck/fixture.rb
  3. 10
      lib/cryptcheck/tls/cert.rb
  4. 144
      lib/cryptcheck/tls/fixture.rb
  5. 67
      lib/cryptcheck/tls/grade.rb
  6. 11
      lib/cryptcheck/tls/https/server.rb
  7. 75
      lib/cryptcheck/tls/server.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'

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

Loading…
Cancel
Save