|
|
|
@ -4,20 +4,6 @@ require 'timeout' |
|
|
|
|
require 'yaml' |
|
|
|
|
|
|
|
|
|
module CryptCheck |
|
|
|
|
PARALLEL_ANALYSIS = 10 |
|
|
|
|
|
|
|
|
|
class NoTLSAvailableServer |
|
|
|
|
attr_reader :server |
|
|
|
|
|
|
|
|
|
def initialize(server) |
|
|
|
|
@server = OpenStruct.new hostname: server |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def grade |
|
|
|
|
'X' |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
autoload :State, 'cryptcheck/state' |
|
|
|
|
autoload :Grade, 'cryptcheck/grade' |
|
|
|
|
autoload :Logger, 'cryptcheck/logger' |
|
|
|
@ -39,14 +25,16 @@ module CryptCheck |
|
|
|
|
autoload :Host, 'cryptcheck/tls/https/host' |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
autoload :Xmpp, 'cryptcheck/tls/xmpp' |
|
|
|
|
autoload :Xmpp, 'cryptcheck/tls/xmpp.rb' |
|
|
|
|
module Xmpp |
|
|
|
|
autoload :Server, 'cryptcheck/tls/xmpp/server' |
|
|
|
|
autoload :Host, 'cryptcheck/tls/xmpp/host' |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
autoload :Smtp, 'cryptcheck/tls/smtp' |
|
|
|
|
module Smtp |
|
|
|
|
autoload :Server, 'cryptcheck/tls/smtp/server' |
|
|
|
|
autoload :Host, 'cryptcheck/tls/smtp/host' |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
@ -56,115 +44,6 @@ module CryptCheck |
|
|
|
|
autoload :Server, 'cryptcheck/ssh/server' |
|
|
|
|
autoload :SshNotSupportedServer, 'cryptcheck/ssh/server' |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
private |
|
|
|
|
def self.addresses(host) |
|
|
|
|
begin |
|
|
|
|
ip = IPAddr.new host |
|
|
|
|
return [[ip.family, ip.to_s, nil]] |
|
|
|
|
rescue IPAddr::InvalidAddressError |
|
|
|
|
end |
|
|
|
|
::Addrinfo.getaddrinfo(host, nil, nil, :STREAM) |
|
|
|
|
.collect { |a| [a.afamily, a.ip_address, host] } |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def self.analyze_addresses(host, addresses, port, server, grade, *args, **kargs) |
|
|
|
|
first = true |
|
|
|
|
addresses.collect do |family, ip| |
|
|
|
|
first ? (first = false) : Logger.info { '' } |
|
|
|
|
key = [host, ip, port] |
|
|
|
|
a = [host, family, ip, port, *args] |
|
|
|
|
begin |
|
|
|
|
::Timeout::timeout MAX_ANALYSIS_DURATION do |
|
|
|
|
s = if kargs.empty? |
|
|
|
|
server.new *a |
|
|
|
|
else |
|
|
|
|
server.new *a, **kargs |
|
|
|
|
end |
|
|
|
|
ap s.states |
|
|
|
|
if grade |
|
|
|
|
g = grade.new s |
|
|
|
|
Logger.info { '' } |
|
|
|
|
g.display |
|
|
|
|
[key, g] |
|
|
|
|
else |
|
|
|
|
[key, s] |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
rescue => e |
|
|
|
|
e = Tls::Server::TLSException.new "Too long analysis (max #{MAX_ANALYSIS_DURATION.humanize})" \ |
|
|
|
|
if e.message == 'execution expired' |
|
|
|
|
raise unless e.is_a? Tls::Server::TLSException |
|
|
|
|
Logger.error e |
|
|
|
|
[key, AnalysisFailure.new(e)] |
|
|
|
|
end |
|
|
|
|
end.to_h |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def self.analyze(host, port, server, *args, **kargs) |
|
|
|
|
addresses = begin |
|
|
|
|
addresses host |
|
|
|
|
rescue ::SocketError => e |
|
|
|
|
Logger::error e |
|
|
|
|
key = [host, nil, port] |
|
|
|
|
error = AnalysisFailure.new "Unable to resolve #{host}" |
|
|
|
|
return { key => error } |
|
|
|
|
end |
|
|
|
|
analyze_addresses host, addresses, port, server, *args, **kargs |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def self.analyze_hosts(hosts, template, output, groups: nil, &block) |
|
|
|
|
results = {} |
|
|
|
|
semaphore = ::Mutex.new |
|
|
|
|
::Parallel.each hosts, progress: 'Analysing', in_threads: PARALLEL_ANALYSIS, finish: lambda { |item, _, _| puts item[1] } do |description, host| |
|
|
|
|
#hosts.each do |description, host| |
|
|
|
|
result = block.call host.strip |
|
|
|
|
result = result.values.first |
|
|
|
|
result = NoTLSAvailableServer.new(host) if result.is_a? AnalysisFailure |
|
|
|
|
semaphore.synchronize do |
|
|
|
|
if results.include? description |
|
|
|
|
results[description] << result |
|
|
|
|
else |
|
|
|
|
results[description] = [result] |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
results = ::Hash[groups.collect { |g| [g, results[g]] }] if groups |
|
|
|
|
|
|
|
|
|
results.each do |d, _| |
|
|
|
|
results[d].sort! do |a, b| |
|
|
|
|
cmp = score(a) <=> score(b) |
|
|
|
|
if cmp == 0 |
|
|
|
|
cmp = a.server.hostname <=> b.server.hostname |
|
|
|
|
end |
|
|
|
|
cmp |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
::File.write output, ::ERB.new(::File.read template).result(binding) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def self.analyze_file(input, template, output, &block) |
|
|
|
|
config = ::YAML.load_file input |
|
|
|
|
hosts = [] |
|
|
|
|
groups = [] |
|
|
|
|
|
|
|
|
|
config.each do |c| |
|
|
|
|
d, hs = c['description'], c['hostnames'] |
|
|
|
|
groups << d |
|
|
|
|
hs.each { |host| hosts << [d, host] } |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
self.analyze_hosts hosts, template, output, groups: groups, &block |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
private |
|
|
|
|
SCORES = %w(A+ A B+ B C+ C D E F G M T X) |
|
|
|
|
|
|
|
|
|
def self.score(a) |
|
|
|
|
SCORES.index a.grade |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
require 'cryptcheck/fixture' |
|
|
|
|