@@ -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_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(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_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 = '' |