diff --git a/Gemfile b/Gemfile index 9e64055..6d82d50 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,6 @@ source 'https://rubygems.org' +gem 'rake' gem 'httparty' gem 'nokogiri' gem 'net-ssh', '>= 2.9.2.beta' @@ -8,9 +9,9 @@ gem 'tcp_timeout' gem 'parallel' gem 'ruby-progressbar' gem 'logging' -gem 'activerecord' -gem 'sqlite3' -gem 'rake' +#gem 'activerecord' +#gem 'sqlite3' +gem 'colorize' group :test do gem 'rspec' diff --git a/Makefile b/Makefile index dffeb84..428927d 100644 --- a/Makefile +++ b/Makefile @@ -34,13 +34,15 @@ $(OPENSSL_DIR)/: $(OPENSSL_DIR)/Makefile: | $(OPENSSL_DIR)/ cd $(OPENSSL_DIR); ./config shared -$(OPENSSL_DIR)/libssl.so.1.0.0 $(OPENSSL_DIR)/libcrypto.so.1.0.0: $(OPENSSL_DIR)/Makefile +$(OPENSSL_DIR)/libssl.so $(OPENSSL_DIR)/libcrypto.so: $(OPENSSL_DIR)/Makefile $(MAKE) -C $(OPENSSL_DIR) depend build_libs +lib/%.so: $(OPENSSL_DIR)/%.so + cp $< $@ lib/%.so.1.0.0: $(OPENSSL_DIR)/%.so.1.0.0 cp $< $@ -libs: lib/libssl.so.1.0.0 lib/libcrypto.so.1.0.0 +libs: lib/libssl.so lib/libcrypto.so lib/libssl.so.1.0.0 lib/libcrypto.so.1.0.0 $(RUBY_DIR)/: wget http://cache.ruby-lang.org/pub/ruby/$(RUBY_MAJOR_VERSION)/$(RUBY_DIR).tar.gz diff --git a/bin/check_https.rb b/bin/check_https.rb index d375821..6df5a07 100755 --- a/bin/check_https.rb +++ b/bin/check_https.rb @@ -2,18 +2,18 @@ $:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib' require 'rubygems' require 'bundler/setup' -require 'logging' 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" else - ::Logging.logger.root.appenders = ::Logging.appenders.stdout - ::Logging.logger.root.level = :warn - + ::CryptCheck::Logger.level = :info server = ::CryptCheck::Tls::Https::Server.new(ARGV[0], ARGV[1] || 443) - p grade = ::CryptCheck::Tls::Https::Grade.new(server) + grade = ::CryptCheck::Tls::Https::Grade.new server + ::CryptCheck::Logger.info { '' } + grade.display end diff --git a/bin/check_https_alexa.rb b/bin/check_https_alexa.rb index c803690..ee78231 100755 --- a/bin/check_https_alexa.rb +++ b/bin/check_https_alexa.rb @@ -7,8 +7,7 @@ require 'cryptcheck' GROUP_NAME = 'Top 100 Alexa' -::Logging.logger.root.appenders = ::Logging.appenders.stdout -::Logging.logger.root.level = :error +::CryptCheck::Logger.level = :none hosts = [] ::File.open('top-1m.csv', 'r') do |file| diff --git a/bin/check_smtp.rb b/bin/check_smtp.rb index 69e82d8..349423d 100755 --- a/bin/check_smtp.rb +++ b/bin/check_smtp.rb @@ -6,14 +6,15 @@ require 'logging' require 'cryptcheck' name = ARGV[0] -unless name - ::CryptCheck::Tls::Smtp.analyze_from_file 'output/smtp.yml', 'output/smtp.html' -else - ::Logging.logger.root.appenders = ::Logging.appenders.stdout - ::Logging.logger.root.level = :warn - +if name + ::CryptCheck::Logger.level = :info server = ::CryptCheck::Tls::Smtp::Server.new(ARGV[0], ARGV[1] || 25) - p grade = ::CryptCheck::Tls::Smtp::Grade.new(server) + grade = ::CryptCheck::Tls::Smtp::Grade.new server + ::CryptCheck::Logger.info { '' } + grade.display +else + ::CryptCheck::Logger.level = :none + ::CryptCheck::Tls::Smtp.analyze_from_file 'output/smtp.yml', 'output/smtp.html' end diff --git a/bin/check_xmpp.rb b/bin/check_xmpp.rb index 132e7f7..c9a8698 100755 --- a/bin/check_xmpp.rb +++ b/bin/check_xmpp.rb @@ -7,12 +7,13 @@ require 'cryptcheck' name = ARGV[0] if name - ::Logging.logger.root.appenders = ::Logging.appenders.stdout - ::Logging.logger.root.level = :warn - + ::CryptCheck::Logger.level = :info server = ::CryptCheck::Tls::Xmpp::Server.new(name, ARGV[1] || :s2s) - p grade = ::CryptCheck::Tls::Xmpp::Grade.new(server) + grade = ::CryptCheck::Tls::Xmpp::Grade.new(server) + ::CryptCheck::Logger.info { '' } + grade.display else + ::CryptCheck::Logger.level = :none ::CryptCheck::Tls::Xmpp.analyze_from_file 'output/xmpp.yml', 'output/xmpp.html' end diff --git a/lib/cryptcheck.rb b/lib/cryptcheck.rb index cb9cb9c..f350c31 100644 --- a/lib/cryptcheck.rb +++ b/lib/cryptcheck.rb @@ -1,4 +1,7 @@ +require 'colorize' + module CryptCheck + autoload :Logger, 'cryptcheck/logger' autoload :Tls, 'cryptcheck/tls' module Tls autoload :Server, 'cryptcheck/tls/server' diff --git a/lib/cryptcheck/logger.rb b/lib/cryptcheck/logger.rb new file mode 100644 index 0000000..bcd60c5 --- /dev/null +++ b/lib/cryptcheck/logger.rb @@ -0,0 +1,28 @@ +module CryptCheck + class Logger + LEVELS = %i(trace debug info warning error fatal none) + @@level = :info + + def self.level=(level) + @@level = level + end + + def self.log(level, string=nil, output: $stdout, &block) + return unless enabled? level + output.puts(string ? string : block.call) + end + + LEVELS.each do |level| + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def self.#{level}(string=nil, output: $stdout, &block) + self.log :#{level}, string, output: output, &block + end + RUBY_EVAL + end + + private + def self.enabled?(level) + LEVELS.index(level) >= LEVELS.index(@@level) + end + end +end diff --git a/lib/cryptcheck/tls.rb b/lib/cryptcheck/tls.rb index c4f89ca..239344a 100644 --- a/lib/cryptcheck/tls.rb +++ b/lib/cryptcheck/tls.rb @@ -5,20 +5,46 @@ require 'parallel' module CryptCheck module Tls MAX_ANALYSIS_DURATION = 600 - PARALLEL_ANALYSIS = 10 - @@log = ::Logging.logger[Tls] + PARALLEL_ANALYSIS = 10 + + TYPES = { + md5: %w(MD5), + sha1: %w(SHA), + + psk: %w(PSK), + srp: %w(SRP), + anonymous: %w(ADH AECDH), + + dss: %w(DSS), + + null: %w(NULL), + export: %w(EXP), + des: %w(DES-CBC), + rc4: %w(RC4), + des3: %w(3DES DES-CBC3), + + pfs: %w(DHE EDH ECDHE ECDH) + } + + TYPES.each do |name, ciphers| + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def self.#{name}?(cipher) + #{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ cipher } + end + RUBY_EVAL + end def self.grade(hostname, port, server_class:, grade_class:) timeout MAX_ANALYSIS_DURATION do grade_class.new server_class.new hostname, port end rescue ::Exception => e - @@log.error { "Error during #{hostname}:#{port} analysis : #{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 = {} + 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 @@ -51,7 +77,7 @@ module CryptCheck def self.analyze_from_file(file, template, output, port:, server_class:, grade_class:) config = ::YAML.load_file file - hosts = [] + hosts = [] groups = [] config.each do |c| d, hs = c['description'], c['hostnames'] @@ -61,6 +87,26 @@ module CryptCheck self.analyze hosts, template, output, groups, port: port, server_class: server_class, grade_class: grade_class end + def self.colorize(cipher) + colors = case + when /^SSL/ =~ cipher, + dss?(cipher), + anonymous?(cipher), + null?(cipher), + export?(cipher), + md5?(cipher), + des?(cipher), + rc4?(cipher) + { color: :white, background: :red } + when des3?(cipher) + { color: :yellow } + when :TLSv1_2 == cipher, + pfs?(cipher) + { color: :green } + end + cipher.to_s.colorize colors + end + private SCORES = %w(A+ A A- B C D E F T M X) diff --git a/lib/cryptcheck/tls/grade.rb b/lib/cryptcheck/tls/grade.rb index 484f924..0d83ad3 100644 --- a/lib/cryptcheck/tls/grade.rb +++ b/lib/cryptcheck/tls/grade.rb @@ -2,109 +2,173 @@ module CryptCheck module Tls class TlsNotSupportedGrade attr_reader :server, :score, :grade + def initialize(server) @server, @score, @grade = server, -1, 'X' end end class Grade - attr_reader :server, :score, :grade, :warning, :success + attr_reader :server, :protocol_score, :key_exchange_score, :cipher_strengths_score, :score, :grade, :error, :warning, :success def initialize(server) @server = server - protocol_score - key_exchange_score - cipher_strengths_score - @score = @protocol_score*0.3 + @key_exchange_score*0.3 + @cipher_strengths_score*0.4 + calculate_protocol_score + calculate_key_exchange_score + calculate_cipher_strengths_score + @score = @protocol_score*0.3 + @key_exchange_score*0.3 + @cipher_strengths_score*0.4 + calculate_error + calculate_warning + calculate_success calculate_grade - warning - success - perfect + calculate_perfect + end + + def display + color = case self.grade + when 'A+' + :blue + when 'A' + :green + when 'B', 'C' + :yellow + when 'E', 'F' + :red + when 'M', 'T' + { 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 + 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, 'E'].max if @server.rc4? or @server.des? - @grade = [@grade, 'F'].max if @server.ssl? or @server.key_size < 1024 + @grade = [@grade, 'F'].max unless @error.empty? @grade = 'M' unless @server.cert_valid @grade = 'T' unless @server.cert_trusted end - def warning + def calculate_error + @error = [] + + @error << :md5_sig if @server.md5_sig? + + @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 << :md5_sig if @server.md5_sig? @warning << :sha1_sig if @server.sha1_sig? - @warning << :md5 if @server.md5? #@warning << :sha1 if @server.sha1? - @warning << :rc4 if @server.rc4? - @warning << :des if @server.des? @warning << :des3 if @server.des3? end - def success + def calculate_success @success = [] @success << :pfs if @server.pfs_only? end - ALL_WARNING = %i(md5_sig md5 rc4 des) + ALL_ERROR = %i(md5_sig md5 anonymous dss null export des rc4) + ALL_WARNING = %i(sha1_sig des3) + ALL_SUCCESS = %i(pfs) + + def all_error + ALL_ERROR + end + def all_warning ALL_WARNING end - ALL_SUCCESS = %i(pfs) + def all_success ALL_SUCCESS end - def perfect - @grade = 'A+' if @grade == 'A' and (all_warning & @warning).empty? and (all_success & @success) == all_success + def calculate_perfect + @grade = 'A+' if @grade == 'A' and @error.empty? and @warning.empty? and (ALL_SUCCESS & @success) == ALL_SUCCESS end METHODS_SCORES = { SSLv2: 0, SSLv3: 80, TLSv1: 90, TLSv1_1: 95, TLSv1_2: 100 } - def protocol_score - methods = @server.supported_methods - worst, best = methods[:worst], methods[:best] + + def calculate_protocol_score + methods = @server.supported_methods + worst, best = methods[:worst], methods[:best] @protocol_score = (METHODS_SCORES[worst] + METHODS_SCORES[best]) / 2 end - def key_exchange_score + def calculate_key_exchange_score @key_exchange_score = case @server.key_size - when 0 then 0 - when 0...512 then 20 - when 512...1024 then 40 - when 1024...2048 then 80 - when 2048...4096 then 90 - else 100 - end + when 0 then + 0 + when 0...512 then + 20 + when 512...1024 then + 40 + when 1024...2048 then + 80 + when 2048...4096 then + 90 + else + 100 + end end - def cipher_strength_score(cipher_strength) + def calculate_cipher_strength_score(cipher_strength) case cipher_strength - when 0 then 0 - when 0...128 then 20 - when 128...256 then 80 - else 100 + when 0 then + 0 + when 0...128 then + 20 + when 128...256 then + 80 + else + 100 end end - def cipher_strengths_score - strength = @server.cipher_size - worst, best = strength[:min], strength[:max] - @cipher_strengths_score = (cipher_strength_score(worst) + cipher_strength_score(best)) / 2 + def calculate_cipher_strengths_score + strength = @server.cipher_size + worst, best = strength[:min], strength[:max] + @cipher_strengths_score = (calculate_cipher_strength_score(worst) + calculate_cipher_strength_score(best)) / 2 end end end diff --git a/lib/cryptcheck/tls/https/grade.rb b/lib/cryptcheck/tls/https/grade.rb index 465db1a..82452c1 100644 --- a/lib/cryptcheck/tls/https/grade.rb +++ b/lib/cryptcheck/tls/https/grade.rb @@ -3,7 +3,7 @@ module CryptCheck module Https class Grade < Tls::Grade private - def success + def calculate_success super @success << :hsts if @server.hsts? @success << :hsts_long if @server.hsts_long? diff --git a/lib/cryptcheck/tls/https/server.rb b/lib/cryptcheck/tls/https/server.rb index 7b36147..68f5ce4 100644 --- a/lib/cryptcheck/tls/https/server.rb +++ b/lib/cryptcheck/tls/https/server.rb @@ -14,28 +14,17 @@ module CryptCheck def fetch_hsts port = @port == 443 ? '' : ":#{@port}" - response = nil - EXISTING_METHODS.each do |method| - begin - next unless SUPPORTED_METHODS.include? method - @log.debug { "Check HSTS with #{method}" } - response = ::HTTParty.head "https://#{@hostname}#{port}/", { follow_redirects: false, verify: false, ssl_version: method, timeout: SSL_TIMEOUT } - break - rescue Exception => e - @log.debug { "#{method} not supported : #{e}" } - end - end - - if response and header = response.headers['strict-transport-security'] + response = ::HTTParty.head "https://#{@hostname}#{port}/", { follow_redirects: false, verify: false, timeout: SSL_TIMEOUT } + if header = response.headers['strict-transport-security'] name, value = header.split '=' if name == 'max-age' @hsts = value.to_i - @log.info { "HSTS : #{@hsts}" } + Logger.info { "HSTS : #{@hsts.to_s.colorize hsts_long? ? :green : nil}" } return end end - @log.info { 'No HSTS' } + Logger.info { 'No HSTS'.colorize :yellow } @hsts = nil end diff --git a/lib/cryptcheck/tls/server.rb b/lib/cryptcheck/tls/server.rb index 504d220..982680c 100644 --- a/lib/cryptcheck/tls/server.rb +++ b/lib/cryptcheck/tls/server.rb @@ -13,9 +13,9 @@ module CryptCheck end class Server - TCP_TIMEOUT = 10 - SSL_TIMEOUT = 2*TCP_TIMEOUT - EXISTING_METHODS = %i(TLSv1_2 TLSv1_1 TLSv1 SSLv3 SSLv2) + TCP_TIMEOUT = 10 + SSL_TIMEOUT = 2*TCP_TIMEOUT + EXISTING_METHODS = %i(TLSv1_2 TLSv1_1 TLSv1 SSLv3 SSLv2) SUPPORTED_METHODS = ::OpenSSL::SSL::SSLContext::METHODS class TLSException < ::Exception end @@ -33,20 +33,18 @@ module CryptCheck attr_reader :hostname, :port, :prefered_ciphers, :cert, :cert_valid, :cert_trusted def initialize(hostname, port) - @log = Logging.logger[hostname] @hostname = hostname - @port = port - @log.error { "Begin analysis" } + @port = port + Logger.info { "#{hostname}:#{port}".colorize :blue } extract_cert #@prefered_ciphers = @supported_ciphers = Hash[SUPPORTED_METHODS.collect { |m| [m, []]}] fetch_prefered_ciphers check_supported_cipher - @log.error { "End analysis" } end def supported_methods worst = EXISTING_METHODS.find { |method| !@prefered_ciphers[method].nil? } - best = EXISTING_METHODS.reverse.find { |method| !@prefered_ciphers[method].nil? } + best = EXISTING_METHODS.reverse.find { |method| !@prefered_ciphers[method].nil? } { worst: worst, best: best } end @@ -66,24 +64,24 @@ module CryptCheck type, size = self.key if type == :ecc size = case size - when 160 then - 1024 - when 224 then - 2048 - when 256 then - 3072 - when 384 then - 7680 - when 521 then - 15360 - end + when 160 then + 1024 + when 224 then + 2048 + when 256 then + 3072 + when 384 then + 7680 + when 521 then + 15360 + end end size end def cipher_size cipher_strengths = supported_ciphers.collect { |c| c[2] }.uniq.sort - worst, best = cipher_strengths.first, cipher_strengths.last + worst, best = cipher_strengths.first, cipher_strengths.last { worst: worst, best: best } end @@ -96,29 +94,22 @@ module CryptCheck end { - md2: %w(md2WithRSAEncryption), - md5: %w(md5WithRSAEncryption md5WithRSA), + md2: %w(md2WithRSAEncryption), + md5: %w(md5WithRSAEncryption md5WithRSA), sha1: %w(sha1WithRSAEncryption sha1WithRSA dsaWithSHA1 dsaWithSHA1_2 ecdsa_with_SHA1) }.each do |name, signature| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{name}_sig? - #{signature}.include? @cert.signature_algorithm - end + def #{name}_sig? + #{signature}.include? @cert.signature_algorithm + end RUBY_EVAL end - { - md5: %w(MD5), - sha1: %w(SHA), - - rc4: %w(RC4), - des3: %w(3DES DES-CBC3), - des: %w(DES-CBC) - }.each do |name, ciphers| + Tls::TYPES.each do |type, _| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{name}? - supported_ciphers.any? { |supported| #{ciphers}.any? { |available| /(^|-)#\{available\}(-|$)/ =~ supported[0] } } - end + def #{type}? + supported_ciphers.any? { |s| Tls.#{type}? s.first } + end RUBY_EVAL end @@ -134,14 +125,12 @@ module CryptCheck tls? and !ssl? end - PFS_CIPHERS = [/^DHE-RSA-/, /^DHE-DSS-/, /^ECDHE-RSA-/, /^ECDHE-ECDSA-/] - def pfs? - supported_ciphers.any? { |cipher| PFS_CIPHERS.any? { |pc| pc =~ cipher[0] } } + supported_ciphers.any? { |c| Tls.pfs? c.first } end def pfs_only? - supported_ciphers.all? { |cipher| PFS_CIPHERS.any? { |pc| pc =~ cipher[0] } } + supported_ciphers.all? { |c| Tls.pfs? c.first } end def supported_ciphers @@ -154,21 +143,21 @@ module CryptCheck private def connect(family, host, port, &block) - socket = ::Socket.new family, sock_type + socket = ::Socket.new family, sock_type sockaddr = ::Socket.sockaddr_in port, host - @log.debug { "Connecting to #{host}:#{port}" } + Logger.trace { "Connecting to #{host}:#{port}" } begin status = socket.connect_nonblock sockaddr - @log.debug { "Connecting to #{host}:#{port} status : #{status}" } + Logger.trace { "Connecting to #{host}:#{port} status : #{status}" } raise ConnectionError, status unless status == 0 - @log.debug { "Connected to #{host}:#{port}" } + Logger.trace { "Connected to #{host}:#{port}" } block_given? ? block.call(socket) : nil rescue ::IO::WaitReadable - @log.debug { "Waiting for read to #{host}:#{port}" } + Logger.trace { "Waiting for read to #{host}:#{port}" } raise Timeout unless IO.select [socket], nil, nil, TCP_TIMEOUT retry rescue ::IO::WaitWritable - @log.debug { "Waiting for write to #{host}:#{port}" } + Logger.trace { "Waiting for write to #{host}:#{port}" } raise Timeout unless IO.select nil, [socket], nil, TCP_TIMEOUT retry ensure @@ -177,39 +166,39 @@ module CryptCheck end def ssl_connect(socket, context, method, &block) - ssl_socket = ::OpenSSL::SSL::SSLSocket.new socket, context + ssl_socket = ::OpenSSL::SSL::SSLSocket.new socket, context ssl_socket.hostname = @hostname unless method == :SSLv2 - @log.debug { "SSL connecting to #{@hostname}:#{@port}" } + Logger.trace { "SSL connecting to #{@hostname}:#{@port}" } begin ssl_socket.connect_nonblock - @log.debug { "SSL connected to #{@hostname}:#{@port}" } + Logger.trace { "SSL connected to #{@hostname}:#{@port}" } return block_given? ? block.call(ssl_socket) : nil rescue ::IO::WaitReadable - @log.debug { "Waiting for SSL read to #{@hostname}:#{@port}" } + Logger.trace { "Waiting for SSL read to #{@hostname}:#{@port}" } raise TLSTimeout unless IO.select [socket], nil, nil, SSL_TIMEOUT retry rescue ::IO::WaitWritable - @log.debug { "Waiting for SSL write to #{@hostname}:#{@port}" } + Logger.trace { "Waiting for SSL write to #{@hostname}:#{@port}" } raise TLSTimeout unless IO.select nil, [socket], nil, SSL_TIMEOUT retry - rescue => e - raise TLSException, e + rescue => e + raise TLSNotAvailableException, e ensure ssl_socket.close end end def ssl_client(method, ciphers = nil, &block) - ssl_context = ::OpenSSL::SSL::SSLContext.new method + ssl_context = ::OpenSSL::SSL::SSLContext.new method ssl_context.ciphers = ciphers if ciphers - @log.debug { "Try #{method} connection with #{ciphers}" } + Logger.trace { "Try #{method} connection with #{ciphers}" } [::Socket::AF_INET, ::Socket::AF_INET6].each do |family| - @log.debug { "Try connection for family #{family}" } + Logger.trace { "Try connection for family #{family}" } addrs = begin ::Socket.getaddrinfo @hostname, nil, family, :STREAM rescue ::SocketError => e - @log.debug { "Unable to resolv #{@hostname} : #{e}" } + Logger.error { "Unable to resolv #{@hostname} : #{e}" } next end @@ -222,7 +211,7 @@ module CryptCheck end end - @log.debug { "No SSL available on #{@hostname}" } + Logger.debug { "No SSL available on #{@hostname}" } raise CipherNotAvailable end @@ -231,27 +220,28 @@ module CryptCheck next unless SUPPORTED_METHODS.include? method begin @cert, @chain = ssl_client(method) { |s| [s.peer_cert, s.peer_cert_chain] } - @log.warn { "Certificate #{@cert.subject}" } + Logger.debug { "Certificate #{@cert.subject}" } break rescue TLSException => e - @log.info { "Method #{method} not supported : #{e}" } + Logger.trace { "Method #{Tls.colorize method} not supported : #{e}" } end end raise TLSNotAvailableException unless @cert - @cert_valid = ::OpenSSL::SSL.verify_certificate_identity @cert, @hostname + @cert_valid = ::OpenSSL::SSL.verify_certificate_identity @cert, @hostname @cert_trusted = verify_trust @chain, @cert end def prefered_cipher(method) - cipher = ssl_client(method, %w(ALL:COMPLEMENTOFALL)) { |s| s.cipher } - @log.warn { "Prefered cipher for #{method} : #{cipher[0]}" } + cipher = ssl_client(method, 'ALL:COMPLEMENTOFALL') { |s| s.cipher } + Logger.info { "Prefered cipher for #{Tls.colorize method} : #{Tls.colorize cipher.first}" } cipher - rescue Exception => e - @log.info { "Method #{method} not supported : #{e}" } + rescue Exception + Logger.debug { "Method #{Tls.colorize method} not supported" } nil end def fetch_prefered_ciphers + Logger.info { '' } @prefered_ciphers = {} EXISTING_METHODS.each do |method| next unless SUPPORTED_METHODS.include? method @@ -261,28 +251,33 @@ module CryptCheck end def available_ciphers(method) - ::OpenSSL::SSL::SSLContext.new(method).ciphers + context = ::OpenSSL::SSL::SSLContext.new method + context.ciphers = 'ALL:COMPLEMENTOFALL' + context.ciphers end def supported_cipher?(method, cipher) ssl_client method, [cipher] - @log.warn { "Verify #{method} / #{cipher[0]} : OK" } + Logger.info { "#{Tls.colorize method} / #{Tls.colorize cipher[0]} : Supported" } true rescue TLSException => e - @log.info { "Verify #{method} / #{cipher[0]} : NOK (#{e})" } + Logger.debug { "#{Tls.colorize method} / #{Tls.colorize cipher[0]} : Not supported" } false end def check_supported_cipher + Logger.info { '' } @supported_ciphers = {} EXISTING_METHODS.each do |method| next unless SUPPORTED_METHODS.include? method and @prefered_ciphers[method] - @supported_ciphers[method] = available_ciphers(method).select { |cipher| supported_cipher? method, cipher } + ciphers = available_ciphers(method).select { |cipher| supported_cipher? method, cipher } + @supported_ciphers[method] = ciphers + Logger.info { '' } unless ciphers.empty? end end def verify_trust(chain, cert) - store = ::OpenSSL::X509::Store.new + store = ::OpenSSL::X509::Store.new store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT store.set_default_paths diff --git a/lib/cryptcheck/tls/xmpp.rb b/lib/cryptcheck/tls/xmpp.rb index b1361d9..832a455 100644 --- a/lib/cryptcheck/tls/xmpp.rb +++ b/lib/cryptcheck/tls/xmpp.rb @@ -7,14 +7,14 @@ module CryptCheck module Xmpp MAX_ANALYSIS_DURATION = 600 PARALLEL_ANALYSIS = 10 - @@log = ::Logging.logger[Xmpp] + @Logger = ::Logging.logger[Xmpp] def self.grade(hostname, type=:s2s) timeout MAX_ANALYSIS_DURATION do Grade.new Server.new hostname, type end rescue ::Exception => e - @@log.error { "Error during #{hostname}:#{type} analysis : #{e}" } + @Logger.error { "Error during #{hostname}:#{type} analysis : #{e}" } TlsNotSupportedGrade.new TlsNotSupportedServer.new hostname, type end diff --git a/lib/cryptcheck/tls/xmpp/grade.rb b/lib/cryptcheck/tls/xmpp/grade.rb index 391390c..7c03906 100644 --- a/lib/cryptcheck/tls/xmpp/grade.rb +++ b/lib/cryptcheck/tls/xmpp/grade.rb @@ -2,7 +2,7 @@ module CryptCheck module Tls module Xmpp class Grade < Tls::Grade - def success + def calculate_success super @success << :required if @server.required? end diff --git a/lib/cryptcheck/tls/xmpp/server.rb b/lib/cryptcheck/tls/xmpp/server.rb index a549900..603c1f1 100644 --- a/lib/cryptcheck/tls/xmpp/server.rb +++ b/lib/cryptcheck/tls/xmpp/server.rb @@ -25,6 +25,8 @@ module CryptCheck end end super hostname, port + Logger.info { '' } + Logger.info { self.required? ? 'Required'.colorize(:green) : 'Not required'.colorize(:yellow) } end def ssl_connect(socket, context, method, &block)