From 6a5f1747f2fa2dcaf096933a4327518ec884506b Mon Sep 17 00:00:00 2001 From: Nicolas Vinot Date: Thu, 12 Feb 2015 01:48:30 +0100 Subject: [PATCH] XMPP TLS check --- bin/check_xmpp | 16 +++ lib/cryptcheck.rb | 7 +- lib/cryptcheck/tls/grade.rb | 2 +- lib/cryptcheck/tls/https.rb | 22 ++-- lib/cryptcheck/tls/https/server.rb | 2 +- lib/cryptcheck/tls/server.rb | 10 +- lib/cryptcheck/tls/xmpp.rb | 55 +++++++++ lib/cryptcheck/tls/xmpp/grade.rb | 16 +++ lib/cryptcheck/tls/xmpp/server.rb | 50 ++++++++ output/https.erb | 5 +- output/index.yml | 1 + output/xmpp.erb | 178 +++++++++++++++++++++++++++++ output/xmpp.yml | 17 +++ 13 files changed, 355 insertions(+), 26 deletions(-) create mode 100755 bin/check_xmpp create mode 100644 lib/cryptcheck/tls/xmpp.rb create mode 100644 lib/cryptcheck/tls/xmpp/grade.rb create mode 100644 lib/cryptcheck/tls/xmpp/server.rb create mode 100644 output/xmpp.erb create mode 100644 output/xmpp.yml diff --git a/bin/check_xmpp b/bin/check_xmpp new file mode 100755 index 0000000..1457a4a --- /dev/null +++ b/bin/check_xmpp @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +$:.unshift 'lib' +require 'logging' +require 'cryptcheck' + +name = ARGV[0] +if name + ::Logging.logger.root.appenders = ::Logging.appenders.stdout + ::Logging.logger.root.level = :warn + + server = ::CryptCheck::Tls::Xmpp::Server.new(name, ARGV[1] || :s2s) + p grade = ::CryptCheck::Tls::Xmpp::Grade.new(server) +else + ::CryptCheck::Tls::Xmpp.analyze_from_file 'output/xmpp.yml', 'output/xmpp.html' +end + diff --git a/lib/cryptcheck.rb b/lib/cryptcheck.rb index 8789784..e1867f9 100644 --- a/lib/cryptcheck.rb +++ b/lib/cryptcheck.rb @@ -3,10 +3,11 @@ module CryptCheck autoload :Server, 'cryptcheck/tls/server' autoload :Grade, 'cryptcheck/tls/grade' autoload :Https, 'cryptcheck/tls/https' + autoload :Xmpp, 'cryptcheck/tls/xmpp' - module Https - autoload :Server, 'cryptcheck/tls/https/server' - autoload :Grade, 'cryptcheck/tls/https/grade' + module Xmpp + autoload :Server, 'cryptcheck/tls/xmpp/server' + autoload :Grade, 'cryptcheck/tls/xmpp/grade' end end end diff --git a/lib/cryptcheck/tls/grade.rb b/lib/cryptcheck/tls/grade.rb index fdedf25..484f924 100644 --- a/lib/cryptcheck/tls/grade.rb +++ b/lib/cryptcheck/tls/grade.rb @@ -65,7 +65,7 @@ module CryptCheck def all_warning ALL_WARNING end - ALL_SUCCESS = %i(pfs hsts hsts_long) + ALL_SUCCESS = %i(pfs) def all_success ALL_SUCCESS end diff --git a/lib/cryptcheck/tls/https.rb b/lib/cryptcheck/tls/https.rb index dd42cf2..3f75967 100644 --- a/lib/cryptcheck/tls/https.rb +++ b/lib/cryptcheck/tls/https.rb @@ -21,17 +21,16 @@ module CryptCheck def self.analyze(hosts, output, groups = nil) 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 - semaphore.synchronize do - if results.include? description - results[description] << result - else - results[description] = [result] - end - end - end + ::Parallel.each hosts, progress: 'Analysing', in_threads: PARALLEL_ANALYSIS, finish: lambda { |item, _, _| puts item[1] } do |description, host| + result = grade 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 @@ -65,6 +64,7 @@ module CryptCheck private SCORES = %w(A+ A A- B C D E F T M X) + def self.score(a) SCORES.index a.grade end diff --git a/lib/cryptcheck/tls/https/server.rb b/lib/cryptcheck/tls/https/server.rb index d1aaff7..1200cbf 100644 --- a/lib/cryptcheck/tls/https/server.rb +++ b/lib/cryptcheck/tls/https/server.rb @@ -8,7 +8,7 @@ module CryptCheck class Server < Tls::Server attr_reader :hsts - def initialize(hostname, port=443, methods: EXISTING_METHODS) + def initialize(hostname, port=443) super fetch_hsts end diff --git a/lib/cryptcheck/tls/server.rb b/lib/cryptcheck/tls/server.rb index e53e05c..9971161 100644 --- a/lib/cryptcheck/tls/server.rb +++ b/lib/cryptcheck/tls/server.rb @@ -32,16 +32,14 @@ module CryptCheck attr_reader :hostname, :port, :prefered_ciphers, :cert, :cert_valid, :cert_trusted - def initialize(hostname, port, methods: EXISTING_METHODS) + def initialize(hostname, port) @log = Logging.logger[hostname] @hostname = hostname @port = port - @methods = methods @log.error { "Begin analysis" } extract_cert fetch_prefered_ciphers check_supported_cipher - fetch_hsts @log.error { "End analysis" } end @@ -228,7 +226,7 @@ module CryptCheck end def extract_cert - @methods.each do |method| + EXISTING_METHODS.each do |method| next unless SUPPORTED_METHODS.include? method begin @cert, @chain = ssl_client(method) { |s| [s.peer_cert, s.peer_cert_chain] } @@ -254,7 +252,7 @@ module CryptCheck def fetch_prefered_ciphers @prefered_ciphers = {} - @methods.each do |method| + EXISTING_METHODS.each do |method| next unless SUPPORTED_METHODS.include? method @prefered_ciphers[method] = prefered_cipher method end @@ -276,7 +274,7 @@ module CryptCheck def check_supported_cipher @supported_ciphers = {} - @methods.each do |method| + 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 } end diff --git a/lib/cryptcheck/tls/xmpp.rb b/lib/cryptcheck/tls/xmpp.rb new file mode 100644 index 0000000..451f94e --- /dev/null +++ b/lib/cryptcheck/tls/xmpp.rb @@ -0,0 +1,55 @@ +require 'erb' +require 'logging' +require 'parallel' + +module CryptCheck + module Tls + module Xmpp + MAX_ANALYSIS_DURATION = 600 + PARALLEL_ANALYSIS = 10 + @@log = ::Logging.logger[Https] + + 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}" } + 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 + end + + ::File.write output, ::ERB.new(::File.read('output/xmpp.erb')).result(binding) + 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 + end + end + end +end diff --git a/lib/cryptcheck/tls/xmpp/grade.rb b/lib/cryptcheck/tls/xmpp/grade.rb new file mode 100644 index 0000000..391390c --- /dev/null +++ b/lib/cryptcheck/tls/xmpp/grade.rb @@ -0,0 +1,16 @@ +module CryptCheck + module Tls + module Xmpp + class Grade < Tls::Grade + def success + super + @success << :required if @server.required? + end + + def all_success + super + %i(required) + end + end + end + end +end diff --git a/lib/cryptcheck/tls/xmpp/server.rb b/lib/cryptcheck/tls/xmpp/server.rb new file mode 100644 index 0000000..33171f1 --- /dev/null +++ b/lib/cryptcheck/tls/xmpp/server.rb @@ -0,0 +1,50 @@ +require 'socket' +require 'openssl' +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::Server + attr_reader :domain + + def initialize(domain, type=:s2s, hostname: nil) + service, port = case type + when :s2s then ['_xmpp-server', 5269] + when :c2s then ['_xmpp-client', 5222] + end + @domain = domain + 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 + end + + def ssl_connect(socket, context, method, &block) + socket.write "" + response = ::Nokogiri::XML socket.recv 4096 + starttls = response.xpath '//tls:starttls', tls: TLS_NAMESPACE + raise TLSNotAvailableException unless starttls + @required = !starttls.xpath('//tls:required', tls: TLS_NAMESPACE).nil? + socket.write "\r\n" + response = ::Nokogiri::XML socket.recv 4096 + raise TLSNotAvailableException unless response.xpath '//tls:proceed', tls: TLS_NAMESPACE + super + end + + def required? + @required + end + end + end + end +end diff --git a/output/https.erb b/output/https.erb index 27b7957..4493d21 100644 --- a/output/https.erb +++ b/output/https.erb @@ -1,6 +1,3 @@ -<% - -%> @@ -84,7 +81,7 @@ <%= s.hostname %> - <% if s.is_a? SSLCheck::TlsNotSupportedServer %> + <% if s.is_a? Tls::TlsNotSupportedServer %> No SSL/TLS diff --git a/output/index.yml b/output/index.yml index 35ec135..f19fb92 100644 --- a/output/index.yml +++ b/output/index.yml @@ -15,6 +15,7 @@ - pfag.me - komic.eu - apericraft.ovh + - nicolas.legland.fr - description: Associations hostnames: - april.org diff --git a/output/xmpp.erb b/output/xmpp.erb new file mode 100644 index 0000000..4073069 --- /dev/null +++ b/output/xmpp.erb @@ -0,0 +1,178 @@ + + + + + + + XMPP + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% servers.each do |n| + s = n.server + %> + + <% if s.is_a? Tls::TlsNotSupportedServer %> + + + <% 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 %> + + + + + + + + + + + + <% cipher_size = s.cipher_size[:worst] %> + + + + + + + + + <% end %> + + <% end %> + +
HostGradeCertificateProtocolsCiphersBest practices
Key size (bits)SHA1 sigSSL v2SSL v3TLS 1.2TLSStrength (bits)MD5SHA1DES/RC43DESPFSRequired
HostGradeCertificateProtocolsCiphersBest practices
Key size (bits)SHA1 sigSSL v2SSL v3TLS 1.2TLSStrength (bits)MD5SHA1DES/RC43DESPFSRequired
<%= s.hostname %> + No SSL/TLS + <%= s.domain %> + <%= n.grade %> + + <% type, size = s.key %> + <%= "#{size} (#{type.to_s.upcase})" %> + (<%= s.key_size < 2048 ? '☹' : '☺' %>) + + <%= s.sha1_sig? ? '✓' : '✗' %> + (<%= s.sha1_sig? ? '☹' : '☺' %>) + + <%= s.sslv2? ? '✓' : '✗' %> + (<%= s.sslv2? ? '☹' : '☺' %>) + + <%= s.sslv3? ? '✓' : '✗' %> + (<%= s.sslv3? ? '☹' : '☺' %>) + + <%= s.tlsv1_2? ? '✓' : '✗' %> + (<%= s.tlsv1_2? ? '☺' : '☹' %>) + + <%= s.tls? ? '✓' : '✗' %> + (<%= s.tls? ? '☺' : '☹' %>) + + <%= cipher_size %> + (<%= cipher_size < 128 ? '☹' : '☺' %>) + + <%= s.md5? ? '✓' : '✗' %> + (<%= s.md5? ? '☹' : '☺' %>) + + <%= s.sha1? ? '✓' : '✗' %> + (<%= s.sha1? ? '☹' : '☺' %>) + + <%= (s.rc4? or s.des?) ? '✓' : '✗' %> + (<%= (s.rc4? or s.des?) ? '☹' : '☺' %>) + + <%= s.des3? ? '✓' : '✗' %> + (<%= s.des3? ? '☹' : '☺' %>) + + <%= s.pfs? ? '✓' : '✗' %> + (<%= s.pfs? ? '☺' : '☹' %>) + + <%= s.required? ? '✓' : '✗' %> + (<%= s.required? ? '☺' : '☹' %>) +
+
+
+
+ + diff --git a/output/xmpp.yml b/output/xmpp.yml new file mode 100644 index 0000000..821f280 --- /dev/null +++ b/output/xmpp.yml @@ -0,0 +1,17 @@ +- imirhil.fr +- magicbox.okhin.fr +- cyphercat.eu +- jabber.ccc.de +- jbfavre.im +- axelsimon.net +- google.com +- ecuri.es +- dattaz.fr +- jabber.lqdn.fr +- mailfr.com +- arysthaar.pw +- startcom.org +- riseup.net +- citronna.de +- matlink.fr +- verry.org