diff --git a/Makefile b/Makefile index b767823..f1687bc 100644 --- a/Makefile +++ b/Makefile @@ -1,38 +1,57 @@ PWD = $(shell pwd) export CPATH = $(PWD)/openssl/include export LIBRARY_PATH = $(PWD)/openssl -OPENSSL_VERSION = OpenSSL_1_0_1j -RUBY_VERSION = 2.1.5 -RUBY_OPENSSL_EXT_DIR = ruby-$(RUBY_VERSION)/ext/openssl +OPENSSL_VERSION = 1.0.1m +OPENSSL_DIR = openssl-$(OPENSSL_VERSION) +RUBY_MAJOR_VERSION = 2.2 +RUBY_VERSION = $(RUBY_MAJOR_VERSION).2 +RUBY_DIR = ruby-$(RUBY_VERSION) +RUBY_OPENSSL_EXT_DIR = $(RUBY_DIR)/ext/openssl +export LIBRARY_PATH = $(PWD)/lib +export C_INCLUDE_PATH = $(PWD)/$(OPENSSL_DIR)/include -all: lib/libssl.so.1.0.0 lib/libcrypto.so.1.0.0 lib/openssl.so +.SECONDARY: + +all: libs ext clean: - rm -rf ruby-$(RUBY_VERSION) openssl + rm -rf $(RUBY_DIR) $(OPENSSL_DIR) + +mr-proper: clean + rm -rf lib/libcrypto.so* lib/libssl.so* lib/openssl.so + +$(OPENSSL_DIR)/: + wget https://www.openssl.org/source/$(OPENSSL_DIR).tar.gz + tar xf $(OPENSSL_DIR).tar.gz + rm -rf $(OPENSSL_DIR).tar.gz -openssl: - git clone https://github.com/openssl/openssl -b $(OPENSSL_VERSION) +$(OPENSSL_DIR)/Makefile: $(OPENSSL_DIR)/ + cd $(OPENSSL_DIR); ./config shared -openssl/Makefile: openssl - cd openssl; ./config shared +$(OPENSSL_DIR)/libssl.so.1.0.0 $(OPENSSL_DIR)/libcrypto.so.1.0.0: $(OPENSSL_DIR)/Makefile + $(MAKE) -C $(OPENSSL_DIR) depend build_libs -openssl/libssl.so: openssl/Makefile - cd openssl; $(MAKE) depend all +lib/%.so.1.0.0: $(OPENSSL_DIR)/%.so.1.0.0 + cp $< $@ + +lib/%.so: lib/%.so.1.0.0 + ln -s $(notdir $<) $@ -lib/%.so.1.0.0: openssl/%.so - cp $^ $@ +libs: lib/libssl.so lib/libcrypto.so -ruby-$(RUBY_VERSION): - wget http://cache.ruby-lang.org/pub/ruby/2.1/ruby-$(RUBY_VERSION).tar.gz - tar xf ruby-$(RUBY_VERSION).tar.gz - rm -f ruby-$(RUBY_VERSION).tar.gz +$(RUBY_DIR): + wget http://cache.ruby-lang.org/pub/ruby/$(RUBY_MAJOR_VERSION)/$(RUBY_DIR).tar.gz + tar xf $(RUBY_DIR).tar.gz + rm -f $(RUBY_DIR).tar.gz -$(RUBY_OPENSSL_EXT_DIR)/Makefile: ruby-$(RUBY_VERSION) +$(RUBY_OPENSSL_EXT_DIR)/Makefile: libs $(RUBY_DIR) cd $(RUBY_OPENSSL_EXT_DIR); ruby extconf.rb patch $@ patch -$(RUBY_OPENSSL_EXT_DIR)/openssl.so: $(RUBY_OPENSSL_EXT_DIR)/Makefile - cd $(RUBY_OPENSSL_EXT_DIR); $(MAKE); $(MAKE) install +$(RUBY_OPENSSL_EXT_DIR)/openssl.so: libs $(RUBY_OPENSSL_EXT_DIR)/Makefile + $(MAKE) -C $(RUBY_OPENSSL_EXT_DIR) lib/openssl.so: $(RUBY_OPENSSL_EXT_DIR)/openssl.so cp $< $@ + +ext: lib/openssl.so \ No newline at end of file diff --git a/bin/check_https_alexa b/bin/check_https_alexa index 94b5f90..c22bee4 100755 --- a/bin/check_https_alexa +++ b/bin/check_https_alexa @@ -3,6 +3,8 @@ $:.unshift 'lib' require 'logging' require 'cryptcheck' +GROUP_NAME = 'Top 100 Alexa' + ::Logging.logger.root.appenders = ::Logging.appenders.stdout ::Logging.logger.root.level = :error @@ -10,7 +12,7 @@ hosts = [] ::File.open('top-1m.csv', 'r') do |file| i = 0 while line = file.gets - hosts << ['Top 100 Alexa', line.strip.split(',')[1]] + hosts << [GROUP_NAME, line.strip.split(',')[1]] i += 1 break if i == 100 end diff --git a/bin/check_smtp b/bin/check_smtp new file mode 100755 index 0000000..4e94b99 --- /dev/null +++ b/bin/check_smtp @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby +$:.unshift 'lib' +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 + + server = ::CryptCheck::Tls::Smtp::Server.new(ARGV[0], ARGV[1] || 25) + p grade = ::CryptCheck::Tls::Smtp::Grade.new(server) +end + + diff --git a/lib/cryptcheck.rb b/lib/cryptcheck.rb index 5e6db93..cb9cb9c 100644 --- a/lib/cryptcheck.rb +++ b/lib/cryptcheck.rb @@ -1,4 +1,5 @@ module CryptCheck + autoload :Tls, 'cryptcheck/tls' module Tls autoload :Server, 'cryptcheck/tls/server' autoload :TcpServer, 'cryptcheck/tls/server' @@ -18,5 +19,11 @@ module CryptCheck autoload :Server, 'cryptcheck/tls/xmpp/server' autoload :Grade, 'cryptcheck/tls/xmpp/grade' end + + autoload :Smtp, 'cryptcheck/tls/smtp' + module Smtp + autoload :Server, 'cryptcheck/tls/smtp/server' + autoload :Grade, 'cryptcheck/tls/smtp/grade' + end end end diff --git a/lib/cryptcheck/tls.rb b/lib/cryptcheck/tls.rb new file mode 100644 index 0000000..c4f89ca --- /dev/null +++ b/lib/cryptcheck/tls.rb @@ -0,0 +1,71 @@ +require 'erb' +require 'logging' +require 'parallel' + +module CryptCheck + module Tls + MAX_ANALYSIS_DURATION = 600 + PARALLEL_ANALYSIS = 10 + @@log = ::Logging.logger[Tls] + + 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}" } + 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 + + 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 diff --git a/lib/cryptcheck/tls/https.rb b/lib/cryptcheck/tls/https.rb index 3f75967..baa73eb 100644 --- a/lib/cryptcheck/tls/https.rb +++ b/lib/cryptcheck/tls/https.rb @@ -1,72 +1,12 @@ -require 'erb' -require 'logging' -require 'parallel' - module CryptCheck module Tls module Https - MAX_ANALYSIS_DURATION = 600 - PARALLEL_ANALYSIS = 10 - @@log = ::Logging.logger[Https] - - def self.grade(hostname, port=443) - timeout MAX_ANALYSIS_DURATION do - Grade.new Server.new hostname, port - end - rescue ::Exception => e - @@log.error { "Error during #{hostname}:#{port} analysis : #{e}" } - TlsNotSupportedGrade.new TlsNotSupportedServer.new hostname, port - end - - 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 - - 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('output/https.erb')).result(binding) + def self.analyze(hosts, output) + Tls.analyze hosts, 'output/https.erb', output, nil, port: 443, server_class: Server, grade_class: Grade end def self.analyze_from_file(file, output) - 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, output, groups - end - - private - SCORES = %w(A+ A A- B C D E F T M X) - - def self.score(a) - SCORES.index a.grade + Tls.analyze_from_file file, 'output/https.erb', output, port: 443, server_class: Server, grade_class: Grade end end end diff --git a/lib/cryptcheck/tls/https/server.rb b/lib/cryptcheck/tls/https/server.rb index 84b97bf..7b36147 100644 --- a/lib/cryptcheck/tls/https/server.rb +++ b/lib/cryptcheck/tls/https/server.rb @@ -1,5 +1,3 @@ -require 'socket' -require 'openssl' require 'httparty' module CryptCheck @@ -17,7 +15,7 @@ module CryptCheck port = @port == 443 ? '' : ":#{@port}" response = nil - @methods.each do |method| + EXISTING_METHODS.each do |method| begin next unless SUPPORTED_METHODS.include? method @log.debug { "Check HSTS with #{method}" } diff --git a/lib/cryptcheck/tls/server.rb b/lib/cryptcheck/tls/server.rb index 390221f..504d220 100644 --- a/lib/cryptcheck/tls/server.rb +++ b/lib/cryptcheck/tls/server.rb @@ -38,6 +38,7 @@ module CryptCheck @port = port @log.error { "Begin analysis" } extract_cert + #@prefered_ciphers = @supported_ciphers = Hash[SUPPORTED_METHODS.collect { |m| [m, []]}] fetch_prefered_ciphers check_supported_cipher @log.error { "End analysis" } @@ -191,8 +192,8 @@ module CryptCheck @log.debug { "Waiting for SSL write to #{@hostname}:#{@port}" } raise TLSTimeout unless IO.select nil, [socket], nil, SSL_TIMEOUT retry - rescue ::OpenSSL::SSL::SSLError => e - raise TLSException, e + rescue => e + raise TLSException, e ensure ssl_socket.close end @@ -283,14 +284,14 @@ module CryptCheck def verify_trust(chain, cert) store = ::OpenSSL::X509::Store.new store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT - %w(mozilla cacert).each do |directory| + store.set_default_paths + + %w(cacert).each do |directory| ::Dir.glob(::File.join '/usr/share/ca-certificates', directory, '*').each do |file| - ::File.open file, 'r' do |file| - cert = ::OpenSSL::X509::Certificate.new file.read - begin - store.add_cert cert - rescue ::OpenSSL::X509::StoreError - end + cert = ::OpenSSL::X509::Certificate.new ::File.read file + begin + store.add_cert cert + rescue ::OpenSSL::X509::StoreError end end end @@ -300,7 +301,9 @@ module CryptCheck rescue ::OpenSSL::X509::StoreError end end - store.verify cert + trusted = store.verify cert + p store.error_string unless trusted + trusted end end diff --git a/lib/cryptcheck/tls/smtp.rb b/lib/cryptcheck/tls/smtp.rb new file mode 100644 index 0000000..1c5806d --- /dev/null +++ b/lib/cryptcheck/tls/smtp.rb @@ -0,0 +1,9 @@ +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 + end + end + end +end diff --git a/lib/cryptcheck/tls/smtp/grade.rb b/lib/cryptcheck/tls/smtp/grade.rb new file mode 100644 index 0000000..ce0f466 --- /dev/null +++ b/lib/cryptcheck/tls/smtp/grade.rb @@ -0,0 +1,8 @@ +module CryptCheck + module Tls + module Smtp + class Grade < Tls::Grade + end + end + end +end diff --git a/lib/cryptcheck/tls/smtp/server.rb b/lib/cryptcheck/tls/smtp/server.rb new file mode 100644 index 0000000..e99cf90 --- /dev/null +++ b/lib/cryptcheck/tls/smtp/server.rb @@ -0,0 +1,35 @@ +require 'resolv' + +module CryptCheck + module Tls + module Smtp + class Server < Tls::TcpServer + RESOLVER = Resolv::DNS.new + + attr_reader :domain + + def initialize(domain, port=25) + @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 + 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" + starttls = features.find { |f| /250[- ]STARTTLS/ =~ f } + raise TLSNotAvailableException unless starttls + socket.write "STARTTLS\r\n" + socket.recv 1024 + super + end + end + end + end +end diff --git a/lib/cryptcheck/tls/xmpp.rb b/lib/cryptcheck/tls/xmpp.rb index 451f94e..b1361d9 100644 --- a/lib/cryptcheck/tls/xmpp.rb +++ b/lib/cryptcheck/tls/xmpp.rb @@ -7,7 +7,7 @@ module CryptCheck module Xmpp MAX_ANALYSIS_DURATION = 600 PARALLEL_ANALYSIS = 10 - @@log = ::Logging.logger[Https] + @@log = ::Logging.logger[Xmpp] def self.grade(hostname, type=:s2s) timeout MAX_ANALYSIS_DURATION do diff --git a/lib/cryptcheck/tls/xmpp/server.rb b/lib/cryptcheck/tls/xmpp/server.rb index d39876b..a549900 100644 --- a/lib/cryptcheck/tls/xmpp/server.rb +++ b/lib/cryptcheck/tls/xmpp/server.rb @@ -1,5 +1,3 @@ -require 'socket' -require 'openssl' require 'nokogiri' require 'resolv' diff --git a/output/ca.yml b/output/ca.yml new file mode 100644 index 0000000..10b35f8 --- /dev/null +++ b/output/ca.yml @@ -0,0 +1,69 @@ +- description: Autorités de certification + hostnames: + - www.cacert.org + - acedicom.edicomgroup.com + - grca.nat.gov.tw + - pki.atos.net + - www.bundesdruckerei.de + - www.cybertrust.ne.jp + - www.logius.nl + - www.procert.net.ve + - www.s-trust.de + - webappsecurity.trendmicro.com + - www1.cnnic.cn + - www.actalis.it + - www.aoc.cat + - www.a-trust.at + - www.buypass.no + - www.camerfirma.com + - www.certicamara.com + - www.certigna.fr + - www.certinomis.com + - www.certsign.ro + - www.certum.pl + - www.cfca.com.cn + - www.cht.com.tw + - www.comodo.com + - www.comsign.co.il + - www.digicert.com + - www.disig.eu + - www.emc.com + - www.entrust.net + - www.e-szigno.hu + - www.etugra.com.tr + - www.firmaprofesional.com + - www.geotrust.com + - www.globalsign.com + - www.godaddy.com + - www.gpki.go.jp + - www.harica.gr + - www.hongkongpost.gov.hk + - www.identrust.com + - www.izenpe.com + - www.kamusm.gov.tr + - www.netlock.hu + - www.networksolutions.com + - www.opentrust.com + - www.pki.gva.es + - www.quovadisglobal.com + - www.secomtrust.net + - www.sgtrustservices.com + - www.sk.ee + - www.ssi.gouv.fr + - www.startssl.com + - www.swissdigicert.ch + - www.swisssign.com + - www.symantec.com + - www.teliasonera.com + - www.thawte.com + - www.trustcenter.de + - www.trustis.com + - www.trustwave.com + - www.t-systems.com + - www.turktrust.com.tr + - www.twca.com.tw + - www.verizon.com + - www.visa.com + - www.wellsfargo.com + - www.wisekey.com + - www.wosign.com diff --git a/output/https.erb b/output/https.erb index 4493d21..f49eb93 100644 --- a/output/https.erb +++ b/output/https.erb @@ -4,7 +4,7 @@ -
+ | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
<%= r[0] %> | +||||||||||||||||||||||||||||||
Site | +Grade | +Certificate | +Protocols | +Ciphers | +Best practices | +|||||||||||||||||||||||||
Key size (bits) | +SHA1 sig | + +SSL v2 | +SSL v3 | +TLS 1.2 | +TLS | + +Strength (bits) | +MD5 | +SHA1 | +DES/RC4 | +3DES | + +PFS | +|||||||||||||||||||
+ <%= s.hostname %> + | ++ No SSL/TLS + | + <% + 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 + %> +<%= 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 = s.cipher_size[:worst] %> ++ <%= 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? ? '☺' : '☹' %>) + | + <% end %> +|||||||||||||||
Site | +Grade | + +Key size (bits) | +SHA1 sig | + +SSL v2 | +SSL v3 | +TLS 1.2 | +TLS | + +Strength (bits) | +MD5 | +SHA1 | +DES/RC4 | +3DES | + +PFS | +|||||||||||||||||
Certificate | +Protocols | +Ciphers | +Best practices | +