Refactoring
parent
c640e26674
commit
3d176613c6
|
@ -96,7 +96,7 @@ module CryptCheck
|
|||
def self.compare(a, b)
|
||||
a = LEVELS.find_index(a.status) || (LEVELS.size - 1) / 2.0
|
||||
b = LEVELS.find_index(b.status) || (LEVELS.size - 1) / 2.0
|
||||
a <=> b
|
||||
b <=> a
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -3,20 +3,21 @@ require 'parallel'
|
|||
|
||||
module CryptCheck
|
||||
module Tls
|
||||
def self.analyze(host, port)
|
||||
::CryptCheck.analyze host, port, TcpServer
|
||||
def self.aggregate(hosts)
|
||||
hosts = [hosts] unless hosts.respond_to? :collect
|
||||
hosts.inject([]) { |l, h| l + h.to_h }
|
||||
end
|
||||
|
||||
def self.key_to_s(key)
|
||||
size, color = case key.type
|
||||
when :ecc
|
||||
["#{key.group.curve_name} #{key.size}", :good]
|
||||
when :rsa
|
||||
[key.size, nil]
|
||||
when :dsa
|
||||
[key.size, :critical]
|
||||
when :dh
|
||||
[key.size, :warning]
|
||||
when :ecc
|
||||
["#{key.group.curve_name} #{key.size}", :good]
|
||||
when :rsa
|
||||
[key.size, nil]
|
||||
when :dsa
|
||||
[key.size, :critical]
|
||||
when :dh
|
||||
[key.size, :warning]
|
||||
end
|
||||
"#{key.type.to_s.upcase.colorize color} #{size.to_s.colorize key.status} bits"
|
||||
end
|
||||
|
|
|
@ -26,15 +26,17 @@ module CryptCheck
|
|||
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),
|
||||
# cbc: %w(CBC),
|
||||
gcm: %w(GCM),
|
||||
ccm: %w(CCM)
|
||||
}
|
||||
}.freeze
|
||||
|
||||
attr_reader :method, :name
|
||||
|
||||
|
@ -49,6 +51,7 @@ module CryptCheck
|
|||
end
|
||||
|
||||
def self.[](method)
|
||||
method = Method[method] if method.is_a? Symbol
|
||||
SUPPORTED[method]
|
||||
end
|
||||
|
||||
|
@ -63,6 +66,15 @@ module CryptCheck
|
|||
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
|
||||
|
@ -76,7 +88,7 @@ module CryptCheck
|
|||
end
|
||||
|
||||
def aead?
|
||||
gcm? or ccm?
|
||||
gcm? or ccm? or chacha20?
|
||||
end
|
||||
|
||||
def ssl?
|
||||
|
@ -96,7 +108,7 @@ module CryptCheck
|
|||
end
|
||||
|
||||
def sweet32?
|
||||
size = self.block_size
|
||||
size = self.encryption[1]
|
||||
return false unless size # Not block encryption
|
||||
size <= 64
|
||||
end
|
||||
|
@ -115,7 +127,7 @@ module CryptCheck
|
|||
hmac = self.hmac
|
||||
{
|
||||
protocol: @method, name: self.name, key_exchange: self.kex, authentication: self.auth,
|
||||
encryption: { name: self.encryption, mode: self.mode, block_size: self.block_size },
|
||||
encryption: self.encryption,
|
||||
hmac: { name: hmac.first, size: hmac.last }, states: self.states
|
||||
}
|
||||
end
|
||||
|
@ -177,23 +189,27 @@ module CryptCheck
|
|||
def encryption
|
||||
case
|
||||
when chacha20?
|
||||
:chacha20
|
||||
when aes?
|
||||
:aes
|
||||
[:chacha20, nil, 128, self.mode]
|
||||
when aes128?
|
||||
[:aes, 128, 128, self.mode]
|
||||
when aes256?
|
||||
[:aes, 128, 128, self.mode]
|
||||
when camellia?
|
||||
:camellia
|
||||
[:camellia, 128, 128, self.mode]
|
||||
when seed?
|
||||
:seed
|
||||
[:seed, 128, 128, self.mode]
|
||||
when idea?
|
||||
:idea
|
||||
[:idea, 64, 128, self.mode]
|
||||
when des3?
|
||||
:'3des'
|
||||
[:'3des', 64, 112, self.mode]
|
||||
when des?
|
||||
:des
|
||||
[:des, 64, 56, self.mode]
|
||||
when rc4?
|
||||
:rc4
|
||||
[:rc4, nil, nil, self.mode]
|
||||
when rc2?
|
||||
:rc2
|
||||
[:rc2, 64, 64, self.mode]
|
||||
when null?
|
||||
[nil, nil, nil, nil]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -203,24 +219,15 @@ module CryptCheck
|
|||
:gcm
|
||||
when ccm?
|
||||
:ccm
|
||||
when rc4? || chacha20?
|
||||
when chacha20?
|
||||
:aead
|
||||
when rc4?
|
||||
nil
|
||||
else
|
||||
:cbc
|
||||
end
|
||||
end
|
||||
|
||||
def block_size
|
||||
case self.encryption
|
||||
when :'3des', :idea, :rc2
|
||||
64
|
||||
when :aes, :camellia, :seed
|
||||
128
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def hmac
|
||||
case
|
||||
when poly1305?
|
||||
|
@ -265,7 +272,7 @@ module CryptCheck
|
|||
@name <=> other.name
|
||||
end
|
||||
|
||||
ALL = 'ALL:COMPLEMENTOFALL'
|
||||
ALL = 'ALL:COMPLEMENTOFALL'.freeze
|
||||
SUPPORTED = Method.collect do |m|
|
||||
context = ::OpenSSL::SSL::SSLContext.new m.to_sym
|
||||
context.ciphers = ALL
|
||||
|
|
|
@ -4,6 +4,7 @@ require 'openssl'
|
|||
module CryptCheck
|
||||
module Tls
|
||||
module Engine
|
||||
SLOW_DOWN = ENV.fetch('SLOW_DOWN', '0').to_i
|
||||
TCP_TIMEOUT = 10
|
||||
TLS_TIMEOUT = 2*TCP_TIMEOUT
|
||||
|
||||
|
@ -176,11 +177,15 @@ module CryptCheck
|
|||
ecdsa = ciphers.keys.detect &:ecdsa?
|
||||
next unless ecdsa
|
||||
@supported_curves = Curve.select do |curve|
|
||||
next true if curve == ecdsa_curve # ECDSA curve is always supported
|
||||
if curve == ecdsa_curve
|
||||
# ECDSA curve is always supported
|
||||
Logger.info { " ECC curve #{curve.name}" }
|
||||
next true
|
||||
end
|
||||
begin
|
||||
connection = ssl_client method, ecdsa, curves: [curve, ecdsa_curve]
|
||||
connection = ssl_client method, ecdsa, curves: [curve, ecdsa_curve]
|
||||
# Not too fast !!!
|
||||
# Handshake will **always** succeed, because ECDSA
|
||||
# Handshake will **always** succeed, because ECDSA
|
||||
# curve is always supported.
|
||||
# So, we need to test for the real curve!
|
||||
# Treaky case : if server preference is enforced,
|
||||
|
@ -248,7 +253,7 @@ module CryptCheck
|
|||
Logger.info { 'Curves preference : ' + 'client preference'.colorize(:warning) }
|
||||
:client
|
||||
else
|
||||
sort = -> (a, b) do
|
||||
sort = lambda do |a, b|
|
||||
curves = [a, b]
|
||||
if cipher.ecdsa?
|
||||
# In case of ECDSA, add the cert key at the end
|
||||
|
@ -273,10 +278,10 @@ module CryptCheck
|
|||
if @supported_methods.size > 1
|
||||
# We will try to connect to the not better supported method
|
||||
method = @supported_methods[1]
|
||||
|
||||
begin
|
||||
ssl_client method, fallback: true
|
||||
rescue InappropriateFallback
|
||||
rescue InappropriateFallback,
|
||||
CipherNotAvailable # Seems some servers reply with "sslv3 alert handshake failure"...
|
||||
@fallback_scsv = true
|
||||
end
|
||||
else
|
||||
|
@ -284,12 +289,12 @@ module CryptCheck
|
|||
end
|
||||
|
||||
text, color = case @fallback_scsv
|
||||
when true
|
||||
['supported', :good]
|
||||
when false
|
||||
['not supported', :error]
|
||||
when nil
|
||||
['not applicable', :unknown]
|
||||
when true
|
||||
['supported', :good]
|
||||
when false
|
||||
['not supported', :error]
|
||||
when nil
|
||||
['not applicable', :unknown]
|
||||
end
|
||||
Logger.info { 'Fallback SCSV : ' + text.colorize(color) }
|
||||
end
|
||||
|
@ -311,6 +316,7 @@ module CryptCheck
|
|||
end
|
||||
|
||||
private
|
||||
|
||||
def connect(&block)
|
||||
socket = ::Socket.new @family, sock_type
|
||||
sockaddr = ::Socket.sockaddr_in @port, @ip
|
||||
|
@ -354,26 +360,26 @@ module CryptCheck
|
|||
retry
|
||||
rescue ::OpenSSL::SSL::SSLError => e
|
||||
case e.message
|
||||
when /state=SSLv2 read server hello A$/,
|
||||
/state=SSLv3 read server hello A$/,
|
||||
/state=SSLv3 read server hello A: wrong version number$/,
|
||||
/state=SSLv3 read server hello A: tlsv1 alert protocol version$/,
|
||||
/state=SSLv3 read server key exchange A: sslv3 alert handshake failure$/
|
||||
raise MethodNotAvailable, e
|
||||
when /state=SSLv2 read server hello A: peer error no cipher$/,
|
||||
/state=error: no ciphers available$/,
|
||||
/state=SSLv3 read server hello A: sslv3 alert handshake failure$/,
|
||||
/state=error: missing export tmp dh key$/,
|
||||
/state=error: wrong curve$/
|
||||
raise CipherNotAvailable, e
|
||||
when /state=SSLv3 read server hello A: tlsv1 alert inappropriate fallback$/
|
||||
raise InappropriateFallback, e
|
||||
when /state=SSLv2 read server hello A$/,
|
||||
/state=SSLv3 read server hello A$/,
|
||||
/state=SSLv3 read server hello A: wrong version number$/,
|
||||
/state=SSLv3 read server hello A: tlsv1 alert protocol version$/,
|
||||
/state=SSLv3 read server key exchange A: sslv3 alert handshake failure$/
|
||||
raise MethodNotAvailable, e
|
||||
when /state=SSLv2 read server hello A: peer error no cipher$/,
|
||||
/state=error: no ciphers available$/,
|
||||
/state=SSLv3 read server hello A: sslv3 alert handshake failure$/,
|
||||
/state=error: missing export tmp dh key$/,
|
||||
/state=error: wrong curve$/
|
||||
raise CipherNotAvailable, e
|
||||
when /state=SSLv3 read server hello A: tlsv1 alert inappropriate fallback$/
|
||||
raise InappropriateFallback, e
|
||||
end
|
||||
raise
|
||||
rescue ::SystemCallError => e
|
||||
case e.message
|
||||
when /^Connection reset by peer - SSL_connect$/
|
||||
raise TLSNotAvailableException, e
|
||||
when /^Connection reset by peer - SSL_connect$/
|
||||
raise TLSNotAvailableException, e
|
||||
end
|
||||
raise
|
||||
ensure
|
||||
|
@ -382,6 +388,7 @@ module CryptCheck
|
|||
end
|
||||
|
||||
def ssl_client(method, ciphers = nil, curves: nil, fallback: false, &block)
|
||||
sleep SLOW_DOWN if SLOW_DOWN > 0
|
||||
ssl_context = ::OpenSSL::SSL::SSLContext.new method.to_sym
|
||||
ssl_context.enable_fallback_scsv if fallback
|
||||
|
||||
|
@ -394,7 +401,7 @@ module CryptCheck
|
|||
ssl_context.ciphers = ciphers
|
||||
|
||||
if curves
|
||||
curves = [curves] unless curves.is_a? Enumerable
|
||||
curves = [curves] unless curves.is_a? Enumerable
|
||||
# OpenSSL fails if the same curve is selected multiple times
|
||||
# So because Array#uniq preserves order, remove the less prefered ones
|
||||
curves = curves.collect(&:name).uniq.join ':'
|
||||
|
@ -421,14 +428,14 @@ module CryptCheck
|
|||
# Let's begin the fun
|
||||
# First, collect "standard" connections
|
||||
# { method => { cipher => connection, ... }, ... }
|
||||
certs = @supported_ciphers.values.collect(&:values).flatten 1
|
||||
certs = @supported_ciphers.values.collect(&:values).flatten 1
|
||||
# Then, collect "ecdsa" connections
|
||||
# { curve => connection, ... }
|
||||
certs += @ecdsa_certs.values
|
||||
certs += @ecdsa_certs.values
|
||||
# For anonymous cipher, there is no certificate at all
|
||||
certs = certs.reject { |c| c.peer_cert.nil? }
|
||||
certs = certs.reject { |c| c.peer_cert.nil? }
|
||||
# Then, fetch cert
|
||||
certs = certs.collect { |c| Cert.new c }
|
||||
certs = certs.collect { |c| Cert.new c }
|
||||
# Then, filter cert to keep uniq fingerprint
|
||||
@certs = certs.uniq { |c| c.fingerprint }
|
||||
|
||||
|
|
|
@ -33,12 +33,14 @@ class ::OpenSSL::PKey::EC
|
|||
CHECKS = [
|
||||
[:ecc, %i(critical error warning), -> (s) do
|
||||
case s.size
|
||||
when 0...160
|
||||
:critical
|
||||
when 160...192
|
||||
:error
|
||||
when 192...256
|
||||
:warning
|
||||
when 0...160
|
||||
:critical
|
||||
when 160...192
|
||||
:error
|
||||
when 192...256
|
||||
:warning
|
||||
else
|
||||
false
|
||||
end
|
||||
end]
|
||||
].freeze
|
||||
|
@ -69,12 +71,14 @@ class ::OpenSSL::PKey::RSA
|
|||
include ::CryptCheck::State
|
||||
|
||||
CHECKS = [
|
||||
[:rsa, %i(critical error), -> (s) do
|
||||
[:rsa, %i(critical error), ->(s) do
|
||||
case s.size
|
||||
when 0...1024
|
||||
:critical
|
||||
when 1024...2048
|
||||
:error
|
||||
when 0...1024
|
||||
:critical
|
||||
when 1024...2048
|
||||
:error
|
||||
else
|
||||
false
|
||||
end
|
||||
end]
|
||||
].freeze
|
||||
|
@ -136,10 +140,12 @@ class ::OpenSSL::PKey::DH
|
|||
CHECKS = [
|
||||
[:dh, %i(critical error), -> (s) do
|
||||
case s.size
|
||||
when 0...1024
|
||||
:critical
|
||||
when 1024...2048
|
||||
:error
|
||||
when 0...1024
|
||||
:critical
|
||||
when 1024...2048
|
||||
:error
|
||||
else
|
||||
false
|
||||
end
|
||||
end]
|
||||
].freeze
|
||||
|
@ -161,17 +167,17 @@ class ::OpenSSL::X509::Store
|
|||
chains = [chains] unless chains.is_a? Enumerable
|
||||
chains.each do |chain|
|
||||
case chain
|
||||
when ::OpenSSL::X509::Certificate
|
||||
self.add_cert chain
|
||||
when ::OpenSSL::X509::Certificate
|
||||
self.add_cert chain
|
||||
else
|
||||
if File.directory?(chain)
|
||||
Dir.entries(chain)
|
||||
.collect { |e| File.join chain, e }
|
||||
.select { |e| File.file? e }
|
||||
.each { |f| self.add_file f }
|
||||
else
|
||||
if File.directory?(chain)
|
||||
Dir.entries(chain)
|
||||
.collect { |e| File.join chain, e }
|
||||
.select { |e| File.file? e }
|
||||
.each { |f| self.add_file f }
|
||||
else
|
||||
self.add_file chain
|
||||
end
|
||||
self.add_file chain
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'awesome_print'
|
||||
AwesomePrint.force_colors = true
|
||||
require 'timeout'
|
||||
|
||||
module CryptCheck
|
||||
|
@ -31,7 +32,7 @@ module CryptCheck
|
|||
|
||||
first = true
|
||||
@servers = resolve.collect do |args|
|
||||
_, ip, _, _ = args
|
||||
_, ip = args
|
||||
first ? (first = false) : Logger.info { '' }
|
||||
result = begin
|
||||
server = ::Timeout.timeout MAX_ANALYSIS_DURATION do
|
||||
|
@ -42,24 +43,31 @@ module CryptCheck
|
|||
Logger.info { server.states.ai }
|
||||
server
|
||||
rescue Engine::TLSException, Engine::ConnectionError, Engine::Timeout => e
|
||||
# Logger.error { e.backtrace }
|
||||
Logger.error { e }
|
||||
AnalysisFailure.new e
|
||||
rescue ::Timeout::Error
|
||||
# Logger.error { e.backtrace }
|
||||
Logger.error { e }
|
||||
TooLongAnalysis.new
|
||||
end
|
||||
[[@hostname, ip, @port], result]
|
||||
end.to_h
|
||||
rescue => e
|
||||
# Logger.error { e.backtrace }
|
||||
Logger.error { e }
|
||||
@error = e
|
||||
end
|
||||
|
||||
def key
|
||||
{ hostname: @hostname, port: @port }
|
||||
end
|
||||
|
||||
def to_h
|
||||
target = {
|
||||
target: { hostname: @hostname, port: @port },
|
||||
}
|
||||
if @error
|
||||
target[:error] = @error
|
||||
target = { error: @error }
|
||||
else
|
||||
target[:hosts] = @servers.collect do |host, server|
|
||||
target = @servers.collect do |host, server|
|
||||
hostname, ip, port = host
|
||||
host = {
|
||||
hostname: hostname,
|
||||
|
@ -72,7 +80,7 @@ module CryptCheck
|
|||
host[:states] = server.states
|
||||
host[:grade] = server.grade
|
||||
else
|
||||
host[:error] = server.message
|
||||
host[:error] = server.to_s
|
||||
end
|
||||
host
|
||||
end
|
||||
|
@ -81,10 +89,11 @@ module CryptCheck
|
|||
end
|
||||
|
||||
private
|
||||
|
||||
def resolve
|
||||
begin
|
||||
ip = IPAddr.new @hostname
|
||||
return [[nil, ip.to_s, ip.family]]
|
||||
return [[nil, ip.to_s, ip.family, @port]]
|
||||
rescue IPAddr::InvalidAddressError
|
||||
end
|
||||
::Addrinfo.getaddrinfo(@hostname, nil, nil, :STREAM)
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
require 'resolv'
|
||||
|
||||
module CryptCheck
|
||||
module Tls
|
||||
module Https
|
||||
def self.analyze(host, port=443)
|
||||
::CryptCheck.analyze host, port, Server
|
||||
end
|
||||
|
||||
def self.analyze_file(input, output)
|
||||
::CryptCheck.analyze_file(input, 'output/https.erb', output) { |host| self.analyze host }
|
||||
def self.analyze(hostname, port = 443)
|
||||
host = Host.new hostname, port
|
||||
Tls.aggregate host
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@ module CryptCheck
|
|||
module Https
|
||||
class Host < Tls::Host
|
||||
private
|
||||
|
||||
def server(*args)
|
||||
Https::Server.new *args
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ module CryptCheck
|
|||
class Server < Tls::TcpServer
|
||||
attr_reader :hsts
|
||||
|
||||
def initialize(hostname, ip, family, port=443)
|
||||
def initialize(hostname, ip, family, port = 443)
|
||||
super
|
||||
fetch_hsts
|
||||
end
|
||||
|
@ -19,7 +19,7 @@ module CryptCheck
|
|||
{
|
||||
follow_redirects: false,
|
||||
verify: false,
|
||||
timeout: TLS_TIMEOUT,
|
||||
timeout: TLS_TIMEOUT,
|
||||
ssl_version: @supported_methods.first.to_sym,
|
||||
ciphers: Cipher::ALL
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ module CryptCheck
|
|||
return
|
||||
end
|
||||
end
|
||||
rescue
|
||||
rescue Exception => e
|
||||
Logger.debug { e }
|
||||
end
|
||||
|
||||
Logger.info { 'No HSTS'.colorize :warning }
|
||||
|
@ -42,7 +43,7 @@ module CryptCheck
|
|||
!@hsts.nil?
|
||||
end
|
||||
|
||||
LONG_HSTS = 6*30*24*60*60
|
||||
LONG_HSTS = 6 * 30 * 24 * 60 * 60
|
||||
|
||||
def hsts_long?
|
||||
hsts? and @hsts >= LONG_HSTS
|
||||
|
@ -53,10 +54,11 @@ module CryptCheck
|
|||
end
|
||||
|
||||
protected
|
||||
|
||||
def available_checks
|
||||
super + [
|
||||
[:hsts, %i(warning good great), -> (s) { s.hsts_long? ? :great : s.hsts? ? :good : :warning }],
|
||||
#[:must_staple, :best, -> (s) { s.must_staple? }],
|
||||
#[:must_staple, :best, -> (s) { s.must_staple? }],
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,7 +31,7 @@ module CryptCheck
|
|||
{ protocol: self.to_sym, states: self.states }
|
||||
end
|
||||
|
||||
alias :to_sym :__getobj__
|
||||
alias to_sym __getobj__
|
||||
|
||||
def <=>(other)
|
||||
EXISTING.find_index(self) <=> EXISTING.find_index(other)
|
||||
|
@ -44,7 +44,7 @@ module CryptCheck
|
|||
[:sslv3, :critical, -> (s) { s == :SSLv3 }],
|
||||
[:tlsv1_0, :error, -> (s) { s == :TLSv1 }],
|
||||
[:tlsv1_1, :warning, -> (s) { s == :TLSv1_1 }]
|
||||
]
|
||||
].freeze
|
||||
|
||||
protected
|
||||
def available_checks
|
||||
|
|
|
@ -62,15 +62,32 @@ module CryptCheck
|
|||
end
|
||||
|
||||
def to_h
|
||||
ciphers_preference = @preferences.collect do |p, cs|
|
||||
case cs
|
||||
when :client
|
||||
{ protocol: p, client: true }
|
||||
when nil
|
||||
{ protocol: p, na: true }
|
||||
else
|
||||
{ protocol: p, cipher_suite: cs.collect(&:to_h) }
|
||||
end
|
||||
end
|
||||
|
||||
curves_preferences = case @curves_preference
|
||||
when :client
|
||||
:client
|
||||
else
|
||||
@curves_preference&.collect(&:name)
|
||||
end
|
||||
{
|
||||
certs: @certs.collect(&:to_h),
|
||||
dh: @dh.collect(&:to_h),
|
||||
protocols: @supported_methods.collect(&:to_h),
|
||||
ciphers: uniq_supported_ciphers.collect(&:to_h),
|
||||
cipher_suites: @preferences.collect { |p, cs| { protocol: p, cipher_suite: cs.collect(&:name) } },
|
||||
curves: @supported_curves.collect(&:to_h),
|
||||
curve_preference: @curves_preference.collect(&:name),
|
||||
fallback_scsv: @fallback_scsv
|
||||
certs: @certs.collect(&:to_h),
|
||||
dh: @dh.collect(&:to_h),
|
||||
protocols: @supported_methods.collect(&:to_h),
|
||||
ciphers: uniq_supported_ciphers.collect(&:to_h),
|
||||
ciphers_preference: ciphers_preference,
|
||||
curves: @supported_curves.collect(&:to_h),
|
||||
curves_preference: curves_preferences,
|
||||
fallback_scsv: @fallback_scsv
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -78,11 +95,11 @@ module CryptCheck
|
|||
include State
|
||||
|
||||
CHECKS = [
|
||||
[:fallback_scsv, :good, -> (s) { s.fallback_scsv? }]
|
||||
[:fallback_scsv, :good, -> (s) { s.fallback_scsv? }],
|
||||
# [:tlsv1_2_only, -> (s) { s.tlsv1_2_only? }, :great],
|
||||
# [:pfs_only, -> (s) { s.pfs_only? }, :great],
|
||||
# [:ecdhe_only, -> (s) { s.ecdhe_only? }, :great],
|
||||
#[:aead_only, -> (s) { s.aead_only? }, :best],
|
||||
# [:aead_only, -> (s) { s.aead_only? }, :best],
|
||||
].freeze
|
||||
|
||||
def available_checks
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
require 'resolv'
|
||||
|
||||
module CryptCheck
|
||||
module Tls
|
||||
module Smtp
|
||||
def self.analyze(host, port=25, domain: nil)
|
||||
::CryptCheck.analyze host, port, Server, Grade, domain: domain
|
||||
end
|
||||
def self.analyze(hostname, port = 25)
|
||||
srv = ::Resolv::DNS.new.getresources(hostname, ::Resolv::DNS::Resource::IN::MX)
|
||||
.sort_by &:preference
|
||||
hosts = if srv.empty?
|
||||
[hostname]
|
||||
else
|
||||
srv.collect { |s| s.exchange.to_s }
|
||||
end
|
||||
|
||||
def self.analyze_domain(domain)
|
||||
srv = Resolv::DNS.new.getresources(domain, Resolv::DNS::Resource::IN::MX).sort_by &:preference
|
||||
hosts = srv.empty? ? [domain] : srv.collect { |s| s.exchange.to_s }
|
||||
results = {}
|
||||
hosts.each { |h| results.merge! self.analyze(h, domain: domain) }
|
||||
results
|
||||
end
|
||||
|
||||
def self.analyze_file(input, output)
|
||||
::CryptCheck.analyze_file(input, 'output/smtp.erb', output) { |host| self.analyze_domain host }
|
||||
Tls.aggregate hosts.collect { |h| Host.new h, port }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
module CryptCheck
|
||||
module Tls
|
||||
module Smtp
|
||||
class Host < Tls::Host
|
||||
private
|
||||
|
||||
def server(*args)
|
||||
Smtp::Server.new *args
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,18 +2,10 @@ module CryptCheck
|
|||
module Tls
|
||||
module Smtp
|
||||
class Server < Tls::TcpServer
|
||||
attr_reader :domain
|
||||
|
||||
def initialize(hostname, family, ip, port, domain:)
|
||||
@domain = domain
|
||||
super hostname, family, ip, port
|
||||
end
|
||||
|
||||
def ssl_connect(socket, context, method, &block)
|
||||
socket.recv 1024
|
||||
socket.write "EHLO #{Socket.gethostbyname(Socket.gethostname).first}\r\n"
|
||||
features = socket.recv(1024).split "\r\n"
|
||||
features
|
||||
starttls = features.find { |f| /250[- ]STARTTLS/ =~ f }
|
||||
raise TLSNotAvailableException unless starttls
|
||||
socket.write "STARTTLS\r\n"
|
||||
|
|
|
@ -1,27 +1,24 @@
|
|||
module CryptCheck
|
||||
module Tls
|
||||
module Xmpp
|
||||
def self.analyze(host, port=nil, domain: nil, type: :s2s)
|
||||
domain ||= host
|
||||
::CryptCheck.analyze host, port, Server, Grade, domain: domain, type: type
|
||||
end
|
||||
|
||||
def self.analyze_domain(domain, type: :s2s)
|
||||
def self.analyze(hostname, type = :s2s)
|
||||
service, port = case type
|
||||
when :s2s
|
||||
['_xmpp-server', 5269]
|
||||
when :c2s
|
||||
['_xmpp-client', 5222]
|
||||
when :s2s
|
||||
['_xmpp-server', 5269]
|
||||
when :c2s
|
||||
['_xmpp-client', 5222]
|
||||
end
|
||||
srv = Resolv::DNS.new.getresources("#{service}._tcp.#{hostname}",
|
||||
Resolv::DNS::Resource::IN::SRV)
|
||||
.sort_by &:priority
|
||||
hosts = if srv.empty?
|
||||
[[hostname, port]]
|
||||
else
|
||||
srv.collect { |s| [s.target.to_s, s.port] }
|
||||
end
|
||||
srv = Resolv::DNS.new.getresources("#{service}._tcp.#{domain}", Resolv::DNS::Resource::IN::SRV).sort_by &:priority
|
||||
hosts = srv.empty? ? [[domain, port]] : srv.collect { |s| [s.target.to_s, s.port] }
|
||||
results = {}
|
||||
hosts.each { |host, port| results.merge! self.analyze(host, port, domain: domain, type: type) }
|
||||
results
|
||||
end
|
||||
|
||||
def self.analyze_file(input, output)
|
||||
::CryptCheck.analyze_file(input, 'output/xmpp.erb', output) { |host| self.analyze_domain host }
|
||||
hosts.collect { |args| Host.new *args, domain: hostname }
|
||||
p hosts
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
module CryptCheck
|
||||
module Tls
|
||||
module Xmpp
|
||||
class Host < Tls::Host
|
||||
attr_reader :domain
|
||||
|
||||
def initialize(*args, domain: nil, type: :s2s)
|
||||
@domain, @type = domain, type
|
||||
super *args
|
||||
Logger.info { '' }
|
||||
Logger.info { self.required? ? 'Required'.colorize(:good) : 'Not required'.colorize(:warning) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def server(*args)
|
||||
Xmpp::Server.new *args, domain: @domain, type: @type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,31 +3,31 @@ require 'nokogiri'
|
|||
module CryptCheck
|
||||
module Tls
|
||||
module Xmpp
|
||||
TLS_NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-tls'
|
||||
|
||||
class Server < Tls::TcpServer
|
||||
attr_reader :domain
|
||||
|
||||
def initialize(hostname, family, ip, port=nil, domain: nil, type: :s2s)
|
||||
def initialize(hostname, ip, family, port = nil, domain: nil, type: :s2s)
|
||||
domain ||= hostname
|
||||
@type, @domain = type, domain
|
||||
@domain, @type = domain, type
|
||||
port = case type
|
||||
when :s2s
|
||||
5269
|
||||
when :c2s
|
||||
5222
|
||||
when :s2s
|
||||
5269
|
||||
when :c2s
|
||||
5222
|
||||
end unless port
|
||||
super hostname, family, ip, port
|
||||
super hostname, ip, family, port
|
||||
Logger.info { '' }
|
||||
Logger.info { self.required? ? 'Required'.colorize(:good) : 'Not required'.colorize(:warning) }
|
||||
end
|
||||
|
||||
TLS_NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-tls'.freeze
|
||||
|
||||
def ssl_connect(socket, context, method, &block)
|
||||
type = case @type
|
||||
when :s2s then
|
||||
'jabber:server'
|
||||
when :c2s then
|
||||
'jabber:client'
|
||||
when :s2s then
|
||||
'jabber:server'
|
||||
when :c2s then
|
||||
'jabber:client'
|
||||
end
|
||||
socket.puts "<?xml version='1.0' ?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='#{type}' to='#{@domain}' version='1.0'>"
|
||||
response = ''
|
||||
|
|
Loading…
Reference in New Issue