13 changed files with 355 additions and 26 deletions
@ -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 |
|||
|
@ -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 |
@ -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 |
Loading…
Reference in new issue