Fetch curves preference
parent
d92523e4f2
commit
a97668a5f8
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' }
|
||||
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? }
|
||||
|
||||
@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
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue