|
|
|
module CryptCheck
|
|
|
|
module Tls
|
|
|
|
class Cipher
|
|
|
|
TYPES = {
|
|
|
|
md5: %w(MD5),
|
|
|
|
sha1: %w(SHA),
|
|
|
|
sha256: %w(SHA256),
|
|
|
|
sha384: %w(SHA384),
|
|
|
|
poly1305: %w(POLY1305),
|
|
|
|
|
|
|
|
psk: %w(PSK),
|
|
|
|
srp: %w(SRP),
|
|
|
|
anonymous: %w(ADH AECDH),
|
|
|
|
dss: %w(DSS),
|
|
|
|
rsa: %w(RSA),
|
|
|
|
ecdsa: %w(ECDSA),
|
|
|
|
dh: %w(DH ADH),
|
|
|
|
ecdh: %w(ECDH AECDH),
|
|
|
|
dhe: %w(DHE EDH ADH),
|
|
|
|
ecdhe: %w(ECDHE AECDH),
|
|
|
|
|
|
|
|
null: %w(NULL),
|
|
|
|
export: %w(EXP),
|
|
|
|
rc2: %w(RC2),
|
|
|
|
rc4: %w(RC4),
|
|
|
|
des: %w(DES-CBC),
|
|
|
|
des3: %w(3DES DES-CBC3),
|
|
|
|
aes: %w(AES(128|256) AES-(128|256)),
|
|
|
|
aes128: %w(AES128 AES-128),
|
|
|
|
aes256: %w(AES256 AES-256),
|
|
|
|
camellia: %w(CAMELLIA(128|256)),
|
|
|
|
seed: %w(SEED),
|
|
|
|
idea: %w(IDEA),
|
|
|
|
chacha20: %w(CHACHA20),
|
|
|
|
|
|
|
|
# cbc: %w(CBC),
|
|
|
|
gcm: %w(GCM),
|
|
|
|
ccm: %w(CCM)
|
|
|
|
}.freeze
|
|
|
|
|
|
|
|
attr_reader :method, :name
|
|
|
|
|
|
|
|
def initialize(method, name)
|
|
|
|
@method, @name = method, name
|
|
|
|
end
|
|
|
|
|
|
|
|
extend Enumerable
|
|
|
|
|
|
|
|
def self.each(&block)
|
|
|
|
SUPPORTED.each &block
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.[](method)
|
|
|
|
method = Method[method] if method.is_a? Symbol
|
|
|
|
SUPPORTED[method]
|
|
|
|
end
|
|
|
|
|
|
|
|
TYPES.each do |name, ciphers|
|
|
|
|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
|
|
|
def self.#{name}?(cipher)
|
|
|
|
#{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ cipher }
|
|
|
|
end
|
|
|
|
def #{name}?
|
|
|
|
#{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ @name }
|
|
|
|
end
|
|
|
|
RUBY_EVAL
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.aes?(cipher)
|
|
|
|
aes?(cipher) or aes?(cipher)
|
|
|
|
end
|
|
|
|
|
|
|
|
def aes?
|
|
|
|
aes128? or aes256?
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def self.cbc?(cipher)
|
|
|
|
!aead? cipher
|
|
|
|
end
|
|
|
|
|
|
|
|
def cbc?
|
|
|
|
!aead?
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.aead?(cipher)
|
|
|
|
gcm?(cipher) or ccm?(cipher)
|
|
|
|
end
|
|
|
|
|
|
|
|
def aead?
|
|
|
|
gcm? or ccm? or chacha20?
|
|
|
|
end
|
|
|
|
|
|
|
|
def ssl?
|
|
|
|
sslv2? or sslv3?
|
|
|
|
end
|
|
|
|
|
|
|
|
def tls?
|
|
|
|
tlsv1? or tlsv1_1? or tlsv1_2?
|
|
|
|
end
|
|
|
|
|
|
|
|
def pfs?
|
|
|
|
dhe? or ecdhe?
|
|
|
|
end
|
|
|
|
|
|
|
|
def ecc?
|
|
|
|
ecdsa? or ecdhe? or ecdh?
|
|
|
|
end
|
|
|
|
|
|
|
|
def sweet32?
|
|
|
|
size = self.encryption[1]
|
|
|
|
return false unless size # Not block encryption
|
|
|
|
size <= 64
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_s(type = :long)
|
|
|
|
case type
|
|
|
|
when :long
|
|
|
|
states = self.states.collect { |k, vs| vs.select { |_, c| c == true }.collect { |v| v.first.to_s.colorize k } }.flatten.join ' '
|
|
|
|
"#{@method} #{@name.colorize self.status} [#{states}]"
|
|
|
|
when :short
|
|
|
|
@name.colorize self.status
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_h
|
|
|
|
hmac = self.hmac
|
|
|
|
{
|
|
|
|
protocol: @method, name: self.name, key_exchange: self.kex, authentication: self.auth,
|
|
|
|
encryption: self.encryption,
|
|
|
|
hmac: { name: hmac.first, size: hmac.last }, states: self.states
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
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
|
|
|
|
return compare unless compare == 0
|
|
|
|
|
|
|
|
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
|
|
|
|
return compare unless compare == 0
|
|
|
|
|
|
|
|
a.name <=> b.name
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.list(cipher_suite = 'ALL:COMPLEMENTOFALL', method: :TLSv1_2)
|
|
|
|
context = OpenSSL::SSL::SSLContext.new method
|
|
|
|
context.ciphers = cipher_suite
|
|
|
|
ciphers = context.ciphers.collect { |c| self.new method, c }
|
|
|
|
self.sort ciphers
|
|
|
|
end
|
|
|
|
|
|
|
|
def kex
|
|
|
|
case
|
|
|
|
when ecdhe? || ecdh?
|
|
|
|
:ecdh
|
|
|
|
when dhe? || dh?
|
|
|
|
:dh
|
|
|
|
when dss?
|
|
|
|
:dss
|
|
|
|
else
|
|
|
|
:rsa
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def auth
|
|
|
|
case
|
|
|
|
when ecdsa?
|
|
|
|
:ecdsa
|
|
|
|
when rsa?
|
|
|
|
:rsa
|
|
|
|
when dss?
|
|
|
|
:dss
|
|
|
|
when anonymous?
|
|
|
|
nil
|
|
|
|
else
|
|
|
|
:rsa
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def encryption
|
|
|
|
case
|
|
|
|
when chacha20?
|
|
|
|
[:chacha20, nil, 128, self.mode]
|
|
|
|
when aes128?
|
|
|
|
[:aes, 128, 128, self.mode]
|
|
|
|
when aes256?
|
|
|
|
[:aes, 128, 128, self.mode]
|
|
|
|
when camellia?
|
|
|
|
[:camellia, 128, 128, self.mode]
|
|
|
|
when seed?
|
|
|
|
[:seed, 128, 128, self.mode]
|
|
|
|
when idea?
|
|
|
|
[:idea, 64, 128, self.mode]
|
|
|
|
when des3?
|
|
|
|
[:'3des', 64, 112, self.mode]
|
|
|
|
when des?
|
|
|
|
[:des, 64, 56, self.mode]
|
|
|
|
when rc4?
|
|
|
|
[:rc4, nil, nil, self.mode]
|
|
|
|
when rc2?
|
|
|
|
[:rc2, 64, 64, self.mode]
|
|
|
|
when null?
|
|
|
|
[nil, nil, nil, nil]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def mode
|
|
|
|
case
|
|
|
|
when gcm?
|
|
|
|
:gcm
|
|
|
|
when ccm?
|
|
|
|
:ccm
|
|
|
|
when chacha20?
|
|
|
|
:aead
|
|
|
|
when rc4?
|
|
|
|
nil
|
|
|
|
else
|
|
|
|
:cbc
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def hmac
|
|
|
|
case
|
|
|
|
when poly1305?
|
|
|
|
[:poly1305, 128]
|
|
|
|
when sha384?
|
|
|
|
[:sha384, 384]
|
|
|
|
when sha256?
|
|
|
|
[:sha256, 256]
|
|
|
|
when sha1?
|
|
|
|
[:sha1, 160]
|
|
|
|
when md5?
|
|
|
|
[:md5, 128]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
include State
|
|
|
|
|
|
|
|
CHECKS = [
|
|
|
|
[:dss, :critical, -> (c) { c.dss? }],
|
|
|
|
[:anonymous, :critical, -> (c) { c.anonymous? }],
|
|
|
|
[:null, :critical, -> (c) { c.null? }],
|
|
|
|
[:export, :critical, -> (c) { c.export? }],
|
|
|
|
[:des, :critical, -> (c) { c.des? }],
|
|
|
|
[:md5, :critical, -> (c) { c.md5? }],
|
|
|
|
[:rc4, :critical, -> (c) { c.rc4? }],
|
|
|
|
[:sweet32, :critical, -> (c) { c.sweet32? }],
|
|
|
|
|
|
|
|
[:pfs, :error, -> (c) { not c.pfs? }],
|
|
|
|
[:dhe, :warning, -> (c) { c.dhe? }],
|
|
|
|
|
|
|
|
[:aead, :good, -> (c) { c.aead? }]
|
|
|
|
].freeze
|
|
|
|
|
|
|
|
def available_checks
|
|
|
|
CHECKS
|
|
|
|
end
|
|
|
|
|
|
|
|
def <=>(other)
|
|
|
|
status = State.compare self, other
|
|
|
|
return status if status != 0
|
|
|
|
@name <=> other.name
|
|
|
|
end
|
|
|
|
|
|
|
|
ALL = 'ALL:COMPLEMENTOFALL'.freeze
|
|
|
|
SUPPORTED = Method.collect do |m|
|
|
|
|
context = ::OpenSSL::SSL::SSLContext.new m.to_sym
|
|
|
|
context.ciphers = ALL
|
|
|
|
ciphers = context.ciphers.collect { |c| Cipher.new m, c.first }
|
|
|
|
[m, ciphers.sort]
|
|
|
|
end.to_h.freeze
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|