XMPP TLS check
parent
777d220b28
commit
6a5f1747f2
|
@ -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
|
Loading…
Reference in New Issue