@@ -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 | |||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 "<?xml version='1.0' ?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='#{@domain}' version='1.0'>" | |||
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 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />\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 |
@@ -1,6 +1,3 @@ | |||
<% | |||
%> | |||
<!DOCTYPE html> | |||
<html lang="fr"> | |||
<head> | |||
@@ -84,7 +81,7 @@ | |||
<%= s.hostname %> | |||
</a> | |||
</th> | |||
<% if s.is_a? SSLCheck::TlsNotSupportedServer %> | |||
<% if s.is_a? Tls::TlsNotSupportedServer %> | |||
<td class="critical" colspan="16"> | |||
No SSL/TLS | |||
</td> |
@@ -15,6 +15,7 @@ | |||
- pfag.me | |||
- komic.eu | |||
- apericraft.ovh | |||
- nicolas.legland.fr | |||
- description: Associations | |||
hostnames: | |||
- april.org |
@@ -0,0 +1,178 @@ | |||
<!DOCTYPE html> | |||
<html lang="fr"> | |||
<head> | |||
<meta charset="utf-8"> | |||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||
<title>XMPP</title> | |||
<link rel="stylesheet" href="bootstrap.min.css"> | |||
<style> | |||
body { | |||
margin-top: 10px; | |||
} | |||
td { | |||
text-align: center; | |||
} | |||
.critical { | |||
background-color: #000; | |||
color: #fff; | |||
} | |||
tr:hover > td.critical, td:hover.critical { | |||
background-color: #333 !important; | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div class="container-fluid"> | |||
<div class="row"> | |||
<div class="col-md-12"> | |||
<table class="table table-bordered table-hover table-condensed"> | |||
<thead> | |||
<tr> | |||
<th rowspan="2">Host</th> | |||
<td rowspan="2">Grade</td> | |||
<td colspan="2">Certificate</td> | |||
<td colspan="4">Protocols</td> | |||
<td colspan="5">Ciphers</td> | |||
<td colspan="2">Best practices</td> | |||
</tr> | |||
<tr> | |||
<td>Key size (bits)</td> | |||
<td class="warning">SHA1 sig</td> | |||
<td class="critical">SSL v2</td> | |||
<td class="critical">SSL v3</td> | |||
<td class="success">TLS 1.2</td> | |||
<td class="info">TLS</td> | |||
<td>Strength (bits)</td> | |||
<td class="critical">MD5</td> | |||
<td class="warning">SHA1</td> | |||
<td class="critical">DES/RC4</td> | |||
<td class="danger">3DES</td> | |||
<td class="info">PFS</td> | |||
<td class="success">Required</td> | |||
</tr> | |||
</thead> | |||
<tfoot> | |||
<tr> | |||
<th rowspan="2">Host</th> | |||
<td rowspan="2">Grade</td> | |||
<td colspan="2">Certificate</td> | |||
<td colspan="4">Protocols</td> | |||
<td colspan="5">Ciphers</td> | |||
<td colspan="2">Best practices</td> | |||
</tr> | |||
<tr> | |||
<td>Key size (bits)</td> | |||
<td class="warning">SHA1 sig</td> | |||
<td class="critical">SSL v2</td> | |||
<td class="critical">SSL v3</td> | |||
<td class="success">TLS 1.2</td> | |||
<td class="info">TLS</td> | |||
<td>Strength (bits)</td> | |||
<td class="critical">MD5</td> | |||
<td class="warning">SHA1</td> | |||
<td class="critical">DES/RC4</td> | |||
<td class="danger">3DES</td> | |||
<td class="info">PFS</td> | |||
<td class="success">Required</td> | |||
</tr> | |||
</tfoot> | |||
<tbody> | |||
<% servers.each do |n| | |||
s = n.server | |||
%> | |||
<tr> | |||
<% if s.is_a? Tls::TlsNotSupportedServer %> | |||
<th id="<%= s.hostname %>"><%= s.hostname %></th> | |||
<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 %> | |||
<th id="<%= s.domain %>"><%= s.domain %></th> | |||
<td class="<%= rank_color %>"> | |||
<%= n.grade %> | |||
</td> | |||
<td class="<%= s.key_size < 2048 ? :danger : s.key_size < 4096 ? :warning : :success %>"> | |||
<% type, size = s.key %> | |||
<%= "#{size} (#{type.to_s.upcase})" %> | |||
<span class="sr-only">(<%= s.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> | |||
<% cipher_size = s.cipher_size[:worst] %> | |||
<td class="<%= cipher_size < 112 ? :danger : cipher_size < 128 ? :warning : :success %>"> | |||
<%= cipher_size %> | |||
<span class="sr-only">(<%= 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> | |||
</div> | |||
</div> | |||
</div> | |||
</body> | |||
</html> |
@@ -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 |