Browse Source

Move up host resolving for speed ehanchement

master
Aeris 3 years ago
parent
commit
c1499fc6e6

+ 4
- 8
bin/check_https.rb View File

@@ -6,14 +6,10 @@ require 'cryptcheck'

name = ARGV[0] || 'index'
file = ::File.join 'output', "#{name}.yml"

if ::File.exist? file
::CryptCheck::Logger.level = :none
::CryptCheck::Tls::Https.analyze_from_file "output/#{name}.yml", "output/#{name}.html"
::CryptCheck::Logger.level = ENV['LOG'] || :none
::CryptCheck::Tls::Https.analyze_file file, "output/#{name}.html"
else
::CryptCheck::Logger.level = (ARGV[1] || :info).to_sym
server = ::CryptCheck::Tls::Https::Server.new ARGV[0]
grade = ::CryptCheck::Tls::Https::Grade.new server
::CryptCheck::Logger.info { '' }
grade.display
::CryptCheck::Logger.level = ENV['LOG'] || :info
::CryptCheck::Tls::Https.analyze ARGV[0], (ARGV[1] || 443)
end

+ 0
- 1
bin/check_https_alexa View File

@@ -1 +0,0 @@
runner

+ 0
- 21
bin/check_https_alexa.rb View File

@@ -1,21 +0,0 @@
#!/usr/bin/env ruby
$:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib'
require 'rubygems'
require 'bundler/setup'
require 'cryptcheck'

GROUP_NAME = 'Top 100 Alexa'

::CryptCheck::Logger.level = :none

hosts = []
::File.open('top-1m.csv', 'r') do |file|
i = 0
while line = file.gets
hosts << [GROUP_NAME, line.strip.split(',')[1]]
i += 1
break if i == 100
end
end

::CryptCheck::Tls::Https.analyze hosts, 'output/alexa.html'

+ 7
- 9
bin/check_smtp.rb View File

@@ -4,16 +4,14 @@ require 'rubygems'
require 'bundler/setup'
require 'cryptcheck'

name = ARGV[0]
if name
::CryptCheck::Logger.level = (ARGV[1] || :info).to_sym
server = ::CryptCheck::Tls::Smtp::Server.new ARGV[0]
grade = ::CryptCheck::Tls::Smtp::Grade.new server
::CryptCheck::Logger.info { '' }
grade.display
name = ARGV[0] || 'smtp'
file = ::File.join 'output', "#{name}.yml"
if ::File.exist? file
::CryptCheck::Logger.level = ENV['LOG'] || :none
::CryptCheck::Tls::Smtp.analyze_file file, "output/#{name}.html"
else
::CryptCheck::Logger.level = :none
::CryptCheck::Tls::Smtp.analyze_from_file 'output/smtp.yml', 'output/smtp.html'
::CryptCheck::Logger.level = ENV['LOG'] || :info
::CryptCheck::Tls::Smtp.analyze ARGV[0]
end



+ 2
- 11
bin/check_ssh.rb View File

@@ -4,14 +4,5 @@ require 'rubygems'
require 'bundler/setup'
require 'cryptcheck'

name, port, level = case ARGV.length
when 1 then [ARGV[0], 22, :info]
when 2 then [ARGV[0], ARGV[1], :info]
when 3 then [ARGV[0], ARGV[1], ARGV[2]]
end

::CryptCheck::Logger.level = level
server = ::CryptCheck::Ssh::Server.new name, port
#grade = ::CryptCheck::Ssh::Grade.new server
#::CryptCheck::Logger.info { '' }
#grade.display
::CryptCheck::Logger.level = ENV['LOG'] || :info
::CryptCheck::Ssh.analyze ARGV[0], (ARGV[1] || 22)

+ 2
- 5
bin/check_tls.rb View File

@@ -4,8 +4,5 @@ require 'rubygems'
require 'bundler/setup'
require 'cryptcheck'

::CryptCheck::Logger.level = (ARGV[2] || :info).to_sym
server = ::CryptCheck::Tls::TcpServer.new ARGV[0], ARGV[1]
grade = ::CryptCheck::Tls::Grade.new server
::CryptCheck::Logger.info { '' }
grade.display
::CryptCheck::Logger.level = ENV['LOG'] || :info
::CryptCheck::Tls::analyze ARGV[0], ARGV[1]

+ 7
- 15
bin/check_xmpp.rb View File

@@ -4,20 +4,12 @@ require 'rubygems'
require 'bundler/setup'
require 'cryptcheck'

name, type, level = case ARGV.length
when 1 then [ARGV[0], :s2s, :info]
when 2 then [ARGV[0], ARGV[1].to_sym, :info]
when 3 then [ARGV[0], ARGV[1].to_sym, ARGV[2].to_sym]
end

if name
::CryptCheck::Logger.level = level
server = ::CryptCheck::Tls::Xmpp::Server.new name, type
grade = ::CryptCheck::Tls::Xmpp::Grade.new server
::CryptCheck::Logger.info { '' }
grade.display
name = ARGV[0] || 'xmpp'
file = ::File.join 'output', "#{name}.yml"
if ::File.exist? file
::CryptCheck::Logger.level = ENV['LOG'] || :none
::CryptCheck::Tls::Xmpp.analyze_file file, "output/#{name}.html"
else
::CryptCheck::Logger.level = :none
::CryptCheck::Tls::Xmpp.analyze_from_file 'output/xmpp.yml', 'output/xmpp.html'
::CryptCheck::Logger.level = ENV['LOG'] || :info
::CryptCheck::Tls::Xmpp.analyze ARGV[0], type: (ARGV[1] || :s2s).to_sym
end


+ 93
- 0
lib/cryptcheck.rb View File

@@ -1,7 +1,13 @@
require 'colorize'
require 'ipaddr'
require 'timeout'
require 'yaml'
require 'cryptcheck/tls/fixture'

module CryptCheck
MAX_ANALYSIS_DURATION = 600
PARALLEL_ANALYSIS = 10

autoload :Logger, 'cryptcheck/logger'
autoload :Tls, 'cryptcheck/tls'
module Tls
@@ -32,9 +38,96 @@ module CryptCheck
end
end

autoload :Ssh, 'cryptcheck/ssh'
module Ssh
autoload :Packet, 'cryptcheck/ssh/packet'
autoload :Server, 'cryptcheck/ssh/server'
autoload :SshNotSupportedServer, 'cryptcheck/ssh/server'
autoload :Grade, 'cryptcheck/ssh/grade'
end

private
def self.addresses(host)
begin
ip = IPAddr.new host
[[ip.family, ip.to_s, nil]]
rescue IPAddr::InvalidAddressError
begin
::Addrinfo.getaddrinfo(host, nil, nil, :STREAM)
.collect { |a| [a.afamily, a.ip_address, host] }
rescue ::SocketError => e
Logger.error { "Unable to resolv #{host} : #{e}" }
[]
end
end
end

def self.analyze_addresses(host, addresses, port, on_error = TLS_NOT_AVAILABLE, &block)
begin
::Timeout::timeout MAX_ANALYSIS_DURATION do
addresses.each { |family, ip, host| return block.call family, ip, host }
end
rescue ::Exception => e
Logger.error e
end
on_error.call host, port
end

def self.analyze(host, port, on_error = Tls::TLS_NOT_AVAILABLE, &block)
analyze_addresses host, addresses(host), port, on_error, &block
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
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 = b.score <=> a.score
if cmp == 0
cmp = a.server.hostname <=> b.server.hostname
end
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 A- B C D E F T M X)

def self.score(a)
SCORES.index a.grade
end
end

+ 1
- 1
lib/cryptcheck/logger.rb View File

@@ -4,7 +4,7 @@ module CryptCheck
@@level = :info

def self.level=(level)
@@level = level
@@level = level.to_sym
end

def self.log(level, string=nil, output: $stdout, &block)

+ 9
- 0
lib/cryptcheck/ssh.rb View File

@@ -0,0 +1,9 @@
module CryptCheck
module Ssh
def self.analyze(host, port=22)
::CryptCheck.analyze(host, port, Proc.new { SshNotSupportedServer.new host, port }) do |_, ip, host|
Server.new ip, port, hostname: host
end
end
end
end

+ 0
- 134
lib/cryptcheck/ssh/grade.rb View File

@@ -1,134 +0,0 @@
module CryptCheck
module Ssh
class SshNotSupportedGrade
attr_reader :server, :score, :grade

def initialize(server)
@server, @score, @grade = server, -1, 'X'
end
end

class Grade
attr_reader :server, :protocol_score, :key_exchange_score, :cipher_strengths_score, :score, :grade, :error, :warning, :success

def initialize(server)
@server = server
end

def display
color = case self.grade
when 'A+' then :blue
when 'A' then :green
when 'B', 'C' then :yellow
when 'E', 'F' then :red
when 'M', 'T' then { color: :white, background: :red }
end

Logger.info { "Grade : #{self.grade.colorize color }" }
Logger.info { '' }
Logger.info { "Protocole : #{self.protocol_score} / 100" }
Logger.info { "Key exchange : #{self.key_exchange_score} / 100" }
Logger.info { "Ciphers strength : #{self.cipher_strengths_score} / 100" }
Logger.info { "Overall score : #{self.score} / 100" }
Logger.info { '' }
Logger.info { "Errors : #{self.error.join(' ').colorize :red }" } unless self.error.empty?
Logger.info { "Warnings : #{self.warning.join(' ').colorize :yellow }" } unless self.warning.empty?
Logger.info { "Best practices : #{self.success.join(' ').colorize :green }" } unless self.success.empty?
end

private
def calculate_grade
@grade = case @score
when 0...20 then 'F'
when 20...35 then 'E'
when 35...50 then 'D'
when 50...65 then 'C'
when 65...80 then 'B'
else 'A'
end

@grade = [@grade, 'B'].max if !@server.tlsv1_2? or @server.key_size < 2048
@grade = [@grade, 'C'].max if @server.des3?
@grade = [@grade, 'F'].max unless @error.empty?

@grade = 'M' unless @server.cert_valid
@grade = 'T' unless @server.cert_trusted

@grade = 'A+' if @grade == 'A' and @error.empty? and @warning.empty? and (all_success & @success) == all_success
end

def calculate_error
@error = []

@error << :md5_sig if @server.md5_sig?
@error << :sslv2 if @server.sslv2?
@error << :sslv3 if @server.sslv3?

@error << :md5 if @server.md5?
@error << :anonymous if @server.anonymous?
@error << :dss if @server.dss?

@error << :null if @server.null?
@error << :export if @server.export?
@error << :des if @server.des?
@error << :rc4 if @server.rc4?
end

def calculate_warning
@warning = []

@warning << :sha1_sig if @server.sha1_sig?

#@warning << :sha1 if @server.sha1?

@warning << :des3 if @server.des3?
end

def calculate_success
@success = []
@success << :pfs if @server.pfs_only?
end

ALL_ERROR = %i(md5_sig md5 anonymous dss null export des rc4)
def all_error
ALL_ERROR
end

ALL_WARNING = %i(sha1_sig des3)
def all_warning
ALL_WARNING
end

ALL_SUCCESS = %i(pfs)
def all_success
ALL_SUCCESS
end

METHODS_SCORES = { SSLv2: 0, SSLv3: 20, TLSv1: 60, TLSv1_1: 80, TLSv1_2: 100 }
def calculate_protocol_score
@protocol_score = @server.supported_protocols.collect { |p| METHODS_SCORES[p] }.min
end

def calculate_key_exchange_score
@key_exchange_score = case @server.key_size
when 0 then 0
when 0...512 then 10
when 512...1024 then 20
when 1024...2048 then 50
when 2048...4096 then 90
else 100
end
end

def calculate_cipher_strengths_score
@cipher_strengths_score = case @server.cipher_size
when 0 then 0
when 0...112 then 10
when 112...128 then 50
when 128...256 then 90
else 100
end
end
end
end
end

+ 15
- 8
lib/cryptcheck/ssh/server.rb View File

@@ -3,10 +3,10 @@ require 'socket'
module CryptCheck
module Ssh
class SshNotSupportedServer
attr_reader :hostname, :port
attr_reader :host, :port

def initialize(hostname, port)
@hostname, @port = hostname, port
def initialize(host, port)
@host, @port = host, port
end
end

@@ -15,7 +15,7 @@ module CryptCheck
class SshNotAvailableException < Exception
end

attr_reader :hostname, :port, :kex, :encryption, :hmac, :compression, :key
attr_reader :ip, :port, :hostname, :kex, :encryption, :hmac, :compression, :key

KEX = {
'curve25519-sha256@libssh.org' => :green,
@@ -89,11 +89,11 @@ module CryptCheck
'ssh-dss-cert-v00@openssh.com' => :red, # DSA
}

def initialize(hostname, port=22)
@hostname, @port = hostname, port
def initialize(ip, port=22, hostname:)
@ip, @port, @hostname = ip, port, hostname

Logger.info { "#{hostname}:#{port}".colorize :blue }
kex = ::Socket.tcp hostname, port, connect_timeout: TCP_TIMEOUT do |socket|
Logger.info { name.colorize :blue }
kex = ::Socket.tcp ip, port, connect_timeout: TCP_TIMEOUT do |socket|
socket.readline
socket.write "SSH-2.0-CryptCheck\r\n"
Packet.read_kex_init socket
@@ -115,6 +115,13 @@ module CryptCheck
Logger.debug { "SSH not supported : #{e}" }
raise SshNotAvailableException, e
end

private
def name
name = "#{@hostname || @ip}:#@port"
name += " [#@ip]" if @hostname
name
end
end
end
end

+ 11
- 60
lib/cryptcheck/tls.rb View File

@@ -3,60 +3,18 @@ require 'parallel'

module CryptCheck
module Tls
MAX_ANALYSIS_DURATION = 600
PARALLEL_ANALYSIS = 10

def self.grade(hostname, port, server_class:, grade_class:)
timeout MAX_ANALYSIS_DURATION do
grade_class.new server_class.new hostname, port
TLS_NOT_AVAILABLE = Proc.new { |host, port|
TlsNotSupportedGrade.new TlsNotSupportedServer.new host, port
}

def self.analyze(host, port)
::CryptCheck.analyze host, port do |family, ip, host|
s = TcpServer.new family, ip, port, hostname: host
g = Grade.new s
Logger.info { '' }
g.display
g
end
rescue ::Exception => e
Logger.error { "Error during #{hostname}:#{port} analysis : #{e}" }
TlsNotSupportedGrade.new TlsNotSupportedServer.new hostname, port
end

def self.analyze(hosts, template, output, groups = nil, port:, server_class:, grade_class:)
results = {}
semaphore = ::Mutex.new
::Parallel.each hosts, progress: 'Analysing', in_threads: PARALLEL_ANALYSIS, finish: lambda { |item, _, _| puts item[1] } do |description, host|
result = grade host.strip, port, server_class: server_class, grade_class: grade_class
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 = b.score <=> a.score
if cmp == 0
cmp = a.server.hostname <=> b.server.hostname
end
end
cmp
end
end

::File.write output, ::ERB.new(::File.read(template)).result(binding)
end

def self.analyze_from_file(file, template, output, port:, server_class:, grade_class:)
config = ::YAML.load_file file
hosts = []
groups = []
config.each do |c|
d, hs = c['description'], c['hostnames']
groups << d
hs.each { |host| hosts << [d, host] }
end
self.analyze hosts, template, output, groups, port: port, server_class: server_class, grade_class: grade_class
end

def self.colorize(cipher)
@@ -80,12 +38,5 @@ module CryptCheck
end
"#{key.type.to_s.upcase.colorize type_color} #{key.size.to_s.colorize size_color} bits"
end

private
SCORES = %w(A+ A A- B C D E F T M X)

def self.score(a)
SCORES.index a.grade
end
end
end

+ 10
- 4
lib/cryptcheck/tls/https.rb View File

@@ -1,12 +1,18 @@
module CryptCheck
module Tls
module Https
def self.analyze(hosts, output)
Tls.analyze hosts, 'output/https.erb', output, nil, port: 443, server_class: Server, grade_class: Grade
def self.analyze(host, port=443)
::CryptCheck.analyze host, port do |family, ip, host|
s = Server.new family, ip, port, hostname: host
g = Grade.new s
Logger.info { '' }
g.display
g
end
end

def self.analyze_from_file(file, output)
Tls.analyze_from_file file, 'output/https.erb', output, port: 443, server_class: Server, grade_class: Grade
def self.analyze_file(input, output)
::CryptCheck.analyze_file(input, 'output/https.erb', output) { |host| self.analyze host }
end
end
end

+ 1
- 1
lib/cryptcheck/tls/https/server.rb View File

@@ -6,7 +6,7 @@ module CryptCheck
class Server < Tls::TcpServer
attr_reader :hsts

def initialize(hostname, port=443)
def initialize(family, ip, port = 443, hostname: nil)
super
fetch_hsts
end

+ 30
- 37
lib/cryptcheck/tls/server.rb View File

@@ -7,8 +7,8 @@ module CryptCheck
class TlsNotSupportedServer
attr_reader :hostname, :port

def initialize(hostname, port)
@hostname, @port = hostname, port
def initialize(host, port)
@hostname, @port = host, port
end
end

@@ -32,12 +32,12 @@ module CryptCheck
class ConnectionError < TLSException
end

attr_reader :hostname, :port, :prefered_ciphers, :cert, :cert_valid, :cert_trusted, :dh
attr_reader :family, :ip, :port, :hostname, :prefered_ciphers, :cert, :cert_valid, :cert_trusted, :dh

def initialize(hostname, port)
@hostname, @port = hostname, port
def initialize(family, ip, port, hostname: nil)
@family, @ip, @port, @hostname = family, ip, port, hostname
@dh = []
Logger.info { "#{hostname}:#{port}".colorize :blue }
Logger.info { name.colorize :blue }
extract_cert
Logger.info { '' }
Logger.info { "Key : #{Tls.key_to_s self.key}" }
@@ -119,22 +119,28 @@ module CryptCheck
end

private
def connect(family, host, port, &block)
socket = ::Socket.new family, sock_type
sockaddr = ::Socket.sockaddr_in port, host
Logger.trace { "Connecting to #{host}:#{port}" }
def name
name = "#{@hostname || @ip}:#@port"
name += " [#@ip]" if @hostname
name
end

def connect(&block)
socket = ::Socket.new @family, sock_type
sockaddr = ::Socket.sockaddr_in @port, @ip
Logger.trace { "Connecting to #{name}" }
begin
status = socket.connect_nonblock sockaddr
Logger.trace { "Connecting to #{host}:#{port} status : #{status}" }
Logger.trace { "Connecting to #{name} status : #{status}" }
raise ConnectionError, status unless status == 0
Logger.trace { "Connected to #{host}:#{port}" }
Logger.trace { "Connected to #{name}" }
block_given? ? block.call(socket) : nil
rescue ::IO::WaitReadable
Logger.trace { "Waiting for read to #{host}:#{port}" }
Logger.trace { "Waiting for read to #{name}" }
raise Timeout unless IO.select [socket], nil, nil, TCP_TIMEOUT
retry
rescue ::IO::WaitWritable
Logger.trace { "Waiting for write to #{host}:#{port}" }
Logger.trace { "Waiting for write to #{name}" }
raise Timeout unless IO.select nil, [socket], nil, TCP_TIMEOUT
retry
rescue => e
@@ -150,18 +156,18 @@ module CryptCheck

def ssl_connect(socket, context, method, &block)
ssl_socket = ::OpenSSL::SSL::SSLSocket.new socket, context
ssl_socket.hostname = @hostname unless method == :SSLv2
Logger.trace { "SSL connecting to #{@hostname}:#{@port}" }
ssl_socket.hostname = @hostname if @hostname and method != :SSLv2
Logger.trace { "SSL connecting to #{name}" }
begin
ssl_socket.connect_nonblock
Logger.trace { "SSL connected to #{@hostname}:#{@port}" }
Logger.trace { "SSL connected to #{name}" }
return block_given? ? block.call(ssl_socket) : nil
rescue ::IO::WaitReadable
Logger.trace { "Waiting for SSL read to #{@hostname}:#{@port}" }
Logger.trace { "Waiting for SSL read to #{name}" }
raise TLSTimeout unless IO.select [socket], nil, nil, SSL_TIMEOUT
retry
rescue ::IO::WaitWritable
Logger.trace { "Waiting for SSL write to #{@hostname}:#{@port}" }
Logger.trace { "Waiting for SSL write to #{name}" }
raise TLSTimeout unless IO.select nil, [socket], nil, SSL_TIMEOUT
retry
rescue ::OpenSSL::SSL::SSLError => e
@@ -189,26 +195,13 @@ module CryptCheck
ssl_context = ::OpenSSL::SSL::SSLContext.new method
ssl_context.ciphers = ciphers if ciphers
Logger.trace { "Try #{method} connection with #{ciphers}" }

[::Socket::AF_INET, ::Socket::AF_INET6].each do |family|
Logger.trace { "Try connection for family #{family}" }
addrs = begin
::Socket.getaddrinfo @hostname, nil, family, :STREAM
rescue ::SocketError => e
Logger.error { "Unable to resolv #{@hostname} : #{e}" }
next
end

addrs.each do |addr|
connect family, addr[3], @port do |socket|
ssl_connect socket, ssl_context, method do |ssl_socket|
return block_given? ? block.call(ssl_socket) : nil
end
end
connect do |socket|
ssl_connect socket, ssl_context, method do |ssl_socket|
return block_given? ? block.call(ssl_socket) : nil
end
end

Logger.debug { "No SSL available on #{@hostname}" }
Logger.debug { "No SSL available on #{name}" }
raise CipherNotAvailable
end

@@ -223,7 +216,7 @@ module CryptCheck
end
end
raise TLSNotAvailableException unless @cert
@cert_valid = ::OpenSSL::SSL.verify_certificate_identity @cert, @hostname
@cert_valid = ::OpenSSL::SSL.verify_certificate_identity @cert, (@hostname || @ip)
@cert_trusted = verify_trust @chain, @cert
end


+ 19
- 2
lib/cryptcheck/tls/smtp.rb View File

@@ -1,8 +1,25 @@
module CryptCheck
module Tls
module Smtp
def self.analyze_from_file(file, output)
Tls.analyze_from_file file, 'output/smtp.erb', output, port: 25, server_class: Server, grade_class: Grade
def self.analyze(host, port=25, domain: nil)
domain ||= host
::CryptCheck.analyze host, port do |family, ip, host|
s = Server.new family, ip, port, hostname: host, domain: domain
g = Grade.new s
Logger.info { '' }
g.display
g
end
end

def self.analyze_domain(domain)
srv = Resolv::DNS.new.getresources(domain, Resolv::DNS::Resource::IN::MX).sort_by(&:preference).first
hostname = srv ? srv.exchange.to_s : domain
self.analyze hostname, domain: domain
end

def self.analyze_file(input, output)
::CryptCheck.analyze_file(input, 'output/smtp.erb', output) { |host| self.analyze_domain host }
end
end
end

+ 2
- 12
lib/cryptcheck/tls/smtp/server.rb View File

@@ -1,22 +1,12 @@
require 'resolv'

module CryptCheck
module Tls
module Smtp
class Server < Tls::TcpServer
RESOLVER = Resolv::DNS.new

attr_reader :domain

def initialize(domain, port=25)
def initialize(family, ip, port, hostname: nil, domain:)
@domain = domain
srv = RESOLVER.getresources(domain, Resolv::DNS::Resource::IN::MX).sort_by(&:preference).first
if srv
hostname = srv.exchange.to_s
else # DNS is not correctly set, guess config…
hostname = domain
end
super hostname, port
super family, ip, port, hostname: hostname
end

def ssl_connect(socket, context, method, &block)

+ 24
- 37
lib/cryptcheck/tls/xmpp.rb View File

@@ -4,49 +4,36 @@ require 'parallel'
module CryptCheck
module Tls
module Xmpp
MAX_ANALYSIS_DURATION = 600
PARALLEL_ANALYSIS = 10

def self.grade(hostname, type=:s2s)
timeout MAX_ANALYSIS_DURATION do
Grade.new Server.new hostname, type
def self.analyze(host, port=nil, domain: nil, type: :s2s)
domain ||= host
::CryptCheck.analyze host, port do |family, ip, host|
s = Server.new family, ip, port, hostname: host, type: type, domain: domain
g = Grade.new s
Logger.info { '' }
g.display
g
end
rescue ::Exception => e
Logger.error { "Error during #{hostname}:#{type} analysis : #{e}" }
TlsNotSupportedGrade.new TlsNotSupportedServer.new hostname, type
end

def self.analyze(hosts, output)
servers = []
semaphore = ::Mutex.new
::Parallel.each hosts, progress: 'Analysing', in_threads: PARALLEL_ANALYSIS, finish: lambda { |item, _, _| puts item } do |host|
result = grade host.strip
semaphore.synchronize { servers << result }
end
servers.sort! do |a, b|
cmp = score(a) <=> score(b)
if cmp == 0
cmp = b.score <=> a.score
if cmp == 0
cmp = a.server.hostname <=> b.server.hostname
end
end
cmp
def self.analyze_domain(domain, type: :s2s)
service, port = case type
when :s2s
['_xmpp-server', 5269]
when :c2s
['_xmpp-client', 5222]
end
srv = Resolv::DNS.new.getresources("#{service}._tcp.#{domain}", Resolv::DNS::Resource::IN::SRV)
.sort_by(&:priority).first
if srv
hostname, port = srv.target.to_s, srv.port
else # DNS is not correctly set, guess config…
hostname = domain
end

::File.write output, ::ERB.new(::File.read('output/xmpp.erb')).result(binding)
self.analyze hostname, port, domain: domain, type: type
end

def self.analyze_from_file(file, output)
hosts = ::YAML.load_file file
self.analyze hosts, output
end

private
SCORES = %w(A+ A A- B C D E F T M X)

def self.score(a)
SCORES.index a.grade
def self.analyze_file(input, output)
::CryptCheck.analyze_file(input, 'output/xmpp.erb', output) { |host| self.analyze_domain host }
end
end
end

+ 16
- 21
lib/cryptcheck/tls/xmpp/server.rb View File

@@ -1,45 +1,40 @@
require 'nokogiri'
require 'resolv'

module CryptCheck
module Tls
module Xmpp
TLS_NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-tls'
RESOLVER = Resolv::DNS.new

class Server < Tls::TcpServer
attr_reader :domain

def initialize(domain, type=:s2s, hostname: nil)
def initialize(family, ip, port=nil, hostname: nil, domain: nil, type: :s2s)
domain ||= hostname
@type, @domain = type, domain
service, port = case type
when :s2s then ['_xmpp-server', 5269]
when :c2s then ['_xmpp-client', 5222]
end
unless hostname
srv = RESOLVER.getresources("#{service}._tcp.#{domain}", Resolv::DNS::Resource::IN::SRV).sort_by(&:priority).first
if srv
hostname, port = srv.target.to_s, srv.port
else # DNS is not correctly set, guess config…
hostname = domain
end
end
super hostname, port
port = case type
when :s2s
5269
when :c2s
5222
end unless port
super family, ip, port, hostname: hostname
Logger.info { '' }
Logger.info { self.required? ? 'Required'.colorize(:green) : 'Not required'.colorize(:yellow) }
end

def ssl_connect(socket, context, method, &block)
type = case @type
when :s2s then 'jabber:server'
when :c2s then 'jabber:client'
end
when :s2s then
'jabber:server'
when :c2s then
'jabber:client'
end
socket.write "<?xml version='1.0' ?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='#{type}' to='#{@domain}' version='1.0'>"
response = ''
loop do
response += socket.recv 1024
xml = ::Nokogiri::XML response
error = xml.xpath '//stream:error'
xml = ::Nokogiri::XML response
error = xml.xpath '//stream:error'
raise Exception, error.text unless error.empty?
unless xml.xpath('//stream:features').empty?
response = xml

+ 101
- 0
output/alexa.yml View File

@@ -0,0 +1,101 @@
- description: Top 100 Alexa
- google.com
- facebook.com
- youtube.com
- baidu.com
- yahoo.com
- amazon.com
- wikipedia.org
- qq.com
- google.co.in
- twitter.com
- live.com
- taobao.com
- sina.com.cn
- msn.com
- yahoo.co.jp
- linkedin.com
- google.co.jp
- weibo.com
- bing.com
- yandex.ru
- vk.com
- hao123.com
- ebay.com
- instagram.com
- google.de
- amazon.co.jp
- mail.ru
- 360.cn
- google.ru
- google.co.uk
- t.co
- pinterest.com
- google.com.br
- reddit.com
- netflix.com
- google.fr
- tmall.com
- sohu.com
- paypal.com
- microsoft.com
- wordpress.com
- blogspot.com
- google.it
- tumblr.com
- google.es
- onclickads.net
- imgur.com
- aliexpress.com
- imdb.com
- apple.com
- ok.ru
- xvideos.com
- ask.com
- fc2.com
- stackoverflow.com
- google.com.mx
- gmw.cn
- amazon.de
- google.com.hk
- alibaba.com
- office.com
- google.ca
- google.com.tr
- rakuten.co.jp
- xinhuanet.com
- pornhub.com
- soso.com
- tianya.cn
- google.co.id
- amazon.in
- haosou.com
- blogger.com
- craigslist.org
- github.com
- amazon.co.uk
- nicovideo.jp
- kat.cr
- outbrain.com
- bongacams.com
- go.com
- googleusercontent.com
- 360.com
- naver.com
- google.pl
- adnetworkperformance.com
- cnn.com
- dropbox.com
- pixnet.net
- google.com.au
- flipkart.com
- chinadaily.com.cn
- adobe.com
- xhamster.com
- jd.com
- whatsapp.com
- microsoftonline.com
- chase.com
- coccoc.com
- bbc.co.uk
- indiatimes.com

+ 1
- 0
output/index.yml View File

@@ -23,6 +23,7 @@
- tcit.fr
- aplu.fr
- tdelmas.ovh
- gordon.re
- description: Associations
hostnames:
- april.org

+ 3
- 1
output/smtp.erb View File

@@ -92,7 +92,9 @@
else :danger
end
%>
<th id="<%= s.domain %>"><%= s.domain %></th>
<th id="<%= s.domain %>">
<a href="#<%= s.domain %>"><%= s.domain %></a>
</th>
<td class="<%= rank_color %>">
<%= n.grade %>
</td>

+ 98
- 86
output/xmpp.erb View File

@@ -30,7 +30,22 @@
<div class="row">
<div class="col-md-12">
<table class="table table-bordered table-hover table-condensed">
<thead>
<tbody>
<%
first = true
results.each do |r|
unless first
%>
<tr>
<th colspan="15">&nbsp;</th>
</tr>
<%
end
first = false
%>
<tr>
<th colspan="15" id="<%= r[0] %>"><%= r[0] %></th>
</tr>
<tr>
<th rowspan="2">Host</th>
<td rowspan="2">Grade</td>
@@ -57,8 +72,88 @@
<td class="info">PFS</td>
<td class="success">Required</td>
</tr>
</thead>
<tfoot>
<% r[1].each do |n|
s = n.server
%>
<tr>
<th id="<%= s.domain %>">
<a href="#<%= s.domain %>"><%= s.domain %></a>
</th>
<% if s.is_a? Tls::TlsNotSupportedServer %>
<td class="critical" colspan="16">
No SSL/TLS
</td>
<% else
rank_color = case n.grade
when 'A+' then :info
when 'A', 'A-' then :success
when 'B', 'C' then :warning
when 'T', 'M' then :critical
else :danger
end %>
<td class="<%= rank_color %>">
<%= n.grade %>
</td>

<% key = s.key %>
<td class="<%= key.rsa_equivalent_size < 2048 ? :danger : key.rsa_equivalent_size < 4096 ? :warning : :success %>">
<%= "#{key.size} (#{key.type.to_s.upcase})" %>
<span class="sr-only">(<%= key.size < 2048 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.sha1_sig? ? :warning : :success %>">
<%= s.sha1_sig? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.sha1_sig? ? '☹' : '☺' %>)</span>
</td>

<td class="<%= s.sslv2? ? :critical : :success %>">
<%= s.sslv2? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.sslv2? ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.sslv3? ? :critical : :success %>">
<%= s.sslv3? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.sslv3? ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.tlsv1_2? ? :success : :danger %>">
<%= s.tlsv1_2? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.tlsv1_2? ? '☺' : '☹' %>)</span>
</td>
<td class="<%= s.tls? ? (s.tls_only? ? :info : :success) : :danger %>">
<%= s.tls? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.tls? ? '☺' : '☹' %>)</span>
</td>

<td class="<%= s.cipher_size < 112 ? :danger : s.cipher_size < 128 ? :warning : :success %>">
<%= s.cipher_size %>
<span class="sr-only">(<%= s.cipher_size < 128 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.md5? ? :critical : :success %>">
<%= s.md5? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.md5? ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.sha1? ? :warning : :success %>">
<%= s.sha1? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.sha1? ? '☹' : '☺' %>)</span>
</td>
<td class="<%= (s.rc4? or s.des?) ? :critical : :success %>">
<%= (s.rc4? or s.des?) ? '✓' : '✗' %>
<span class="sr-only">(<%= (s.rc4? or s.des?) ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.des3? ? :danger : :success %>">
<%= s.des3? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.des3? ? '☹' : '☺' %>)</span>
</td>

<td class="<%= s.pfs? ? (s.pfs_only? ? :info : :success) : :danger %>">
<%= s.pfs? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.pfs? ? '☺' : '☹' %>)</span>
</td>
<td class="<%= s.required? ? :success : :danger %>">
<%= s.required? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.required? ? '☺' : '☹' %>)</span>
</td>
<% end %>
</tr>
<% end %>
<tr>
<th rowspan="2">Host</th>
<td rowspan="2">Grade</td>
@@ -85,89 +180,6 @@
<td class="info">PFS</td>
<td class="success">Required</td>
</tr>
</tfoot>
<tbody>
<% servers.each do |n|
s = n.server
%>
<tr>
<th id="<%= s.hostname %>">
<a href="#<%= s.hostname %>"><%= s.hostname %></a>
</th>
<% if s.is_a? Tls::TlsNotSupportedServer %>
<td class="critical" colspan="16">
No SSL/TLS
</td>
<% else
rank_color = case n.grade
when 'A+' then :info
when 'A', 'A-' then :success
when 'B', 'C' then :warning
when 'T', 'M' then :critical
else :danger
end %>
<td class="<%= rank_color %>">
<%= n.grade %>
</td>

<% key = s.key %>
<td class="<%= key.rsa_equivalent_size < 2048 ? :danger : key.rsa_equivalent_size < 4096 ? :warning : :success %>">
<%= "#{key.size} (#{key.type.to_s.upcase})" %>
<span class="sr-only">(<%= key.size < 2048 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.sha1_sig? ? :warning : :success %>">
<%= s.sha1_sig? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.sha1_sig? ? '☹' : '☺' %>)</span>
</td>

<td class="<%= s.sslv2? ? :critical : :success %>">
<%= s.sslv2? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.sslv2? ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.sslv3? ? :critical : :success %>">
<%= s.sslv3? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.sslv3? ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.tlsv1_2? ? :success : :danger %>">
<%= s.tlsv1_2? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.tlsv1_2? ? '☺' : '☹' %>)</span>
</td>
<td class="<%= s.tls? ? (s.tls_only? ? :info : :success) : :danger %>">
<%= s.tls? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.tls? ? '☺' : '☹' %>)</span>
</td>

<td class="<%= s.cipher_size < 112 ? :danger : s.cipher_size < 128 ? :warning : :success %>">
<%= s.cipher_size %>
<span class="sr-only">(<%= s.cipher_size < 128 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.md5? ? :critical : :success %>">
<%= s.md5? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.md5? ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.sha1? ? :warning : :success %>">
<%= s.sha1? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.sha1? ? '☹' : '☺' %>)</span>
</td>
<td class="<%= (s.rc4? or s.des?) ? :critical : :success %>">
<%= (s.rc4? or s.des?) ? '✓' : '✗' %>
<span class="sr-only">(<%= (s.rc4? or s.des?) ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.des3? ? :danger : :success %>">
<%= s.des3? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.des3? ? '☹' : '☺' %>)</span>
</td>

<td class="<%= s.pfs? ? (s.pfs_only? ? :info : :success) : :danger %>">
<%= s.pfs? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.pfs? ? '☺' : '☹' %>)</span>
</td>
<td class="<%= s.required? ? :success : :danger %>">
<%= s.required? ? '✓' : '✗' %>
<span class="sr-only">(<%= s.required? ? '☺' : '☹' %>)</span>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>

Loading…
Cancel
Save