@@ -11,5 +11,5 @@ if ::File.exist? file | |||
::CryptCheck::Tls::Xmpp.analyze_file file, "output/#{name}.html" | |||
else | |||
::CryptCheck::Logger.level = ENV['LOG'] || :info | |||
::CryptCheck::Tls::Xmpp.analyze ARGV[0], type: (ARGV[1] || :s2s).to_sym | |||
::CryptCheck::Tls::Xmpp.analyze_domain ARGV[0], type: (ARGV[1] || :s2s).to_sym | |||
end |
@@ -24,7 +24,7 @@ module CryptCheck | |||
end | |||
class TLSTimeout < Timeout | |||
end | |||
class ConnectionError < Exception | |||
class ConnectionError < ::StandardError | |||
end | |||
attr_reader :family, :ip, :port, :hostname, :prefered_ciphers, :cert, :cert_valid, :cert_trusted, :dh | |||
@@ -6,13 +6,7 @@ module CryptCheck | |||
module Xmpp | |||
def self.analyze(host, port=nil, domain: nil, type: :s2s) | |||
domain ||= host | |||
::CryptCheck.analyze host, port do |family, ip, host| | |||
s = Server.new family, ip, port, hostname: host, type: type, domain: domain | |||
g = Grade.new s | |||
Logger.info { '' } | |||
g.display | |||
g | |||
end | |||
::CryptCheck.analyze host, port, Server, Grade, domain: domain, type: type | |||
end | |||
def self.analyze_domain(domain, type: :s2s) | |||
@@ -17,7 +17,7 @@ module CryptCheck | |||
when :c2s | |||
5222 | |||
end unless port | |||
super family, ip, port, hostname: hostname | |||
super hostname, family, ip, port | |||
Logger.info { '' } | |||
Logger.info { self.required? ? 'Required'.colorize(:green) : 'Not required'.colorize(:yellow) } | |||
end | |||
@@ -29,13 +29,13 @@ module CryptCheck | |||
when :c2s then | |||
'jabber:client' | |||
end | |||
socket.write "<?xml version='1.0' ?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='#{type}' to='#{@domain}' version='1.0'>" | |||
socket.puts "<?xml version='1.0' ?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='#{type}' to='#{@domain}' version='1.0'>" | |||
response = '' | |||
loop do | |||
response += socket.recv 1024 | |||
xml = ::Nokogiri::XML response | |||
error = xml.xpath '//stream:error' | |||
raise Exception, error.text unless error.empty? | |||
raise ConnectionError, error.first.child.to_s unless error.empty? | |||
unless xml.xpath('//stream:features').empty? | |||
response = xml | |||
break | |||
@@ -43,7 +43,7 @@ module CryptCheck | |||
end | |||
starttls = response.xpath '//tls:starttls', tls: TLS_NAMESPACE | |||
raise TLSNotAvailableException unless starttls | |||
@required = !starttls.xpath('//tls:required', tls: TLS_NAMESPACE).nil? | |||
@required = !starttls.xpath('//tls:required', tls: TLS_NAMESPACE).empty? | |||
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 | |||
@@ -1,13 +1,10 @@ | |||
describe CryptCheck::Tls::Https do | |||
def process | |||
proc do |socket| | |||
socket.print [ | |||
'HTTP/1.1 200 OK', | |||
'Content-Type: text/plain', | |||
'Content-Length: 0', | |||
'Connection: close' | |||
].join "\r\n" | |||
end | |||
def server(*args, **kargs, &block) | |||
tls_serv *args, **kargs, &block | |||
end | |||
def plain_server(*args, **kargs, &block) | |||
plain_serv *args, **kargs, &block | |||
end | |||
def analyze(*args) | |||
@@ -18,7 +15,7 @@ describe CryptCheck::Tls::Https do | |||
describe '#hsts?' do | |||
it 'has no hsts' do | |||
grades = server host: '127.0.0.1', process: process do | |||
grades = server host: '127.0.0.1' do | |||
analyze '127.0.0.1', 5000 | |||
end | |||
@@ -48,7 +45,7 @@ describe CryptCheck::Tls::Https do | |||
describe '#hsts_long?' do | |||
it 'has no hsts' do | |||
grades = server host: '127.0.0.1', process: process do | |||
grades = server host: '127.0.0.1' do | |||
analyze '127.0.0.1', 5000 | |||
end | |||
@@ -1,7 +1,7 @@ | |||
RSpec.shared_examples :analysis do | |||
describe '#analyze' do | |||
it 'return 1 grade with IPv4' do | |||
grades = server host: '127.0.0.1', process: process do | |||
grades = server host: '127.0.0.1' do | |||
analyze '127.0.0.1', 5000 | |||
end | |||
@@ -10,7 +10,7 @@ RSpec.shared_examples :analysis do | |||
end | |||
it 'return 1 grade with IPv6' do | |||
grades = server host: '::1', process: process do | |||
grades = server host: '::1' do | |||
analyze '::1', 5000 | |||
end | |||
@@ -24,7 +24,7 @@ RSpec.shared_examples :analysis do | |||
addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) } | |||
end | |||
grades = server host: '::', process: process do | |||
grades = server host: '::' do | |||
analyze 'localhost', 5000 | |||
end | |||
@@ -36,7 +36,7 @@ RSpec.shared_examples :analysis do | |||
allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) | |||
.and_raise SocketError, 'getaddrinfo: Name or service not known' | |||
grades = server process: process do | |||
grades = server do | |||
analyze 'localhost', 5000 | |||
end | |||
@@ -48,7 +48,7 @@ RSpec.shared_examples :analysis do | |||
stub_const 'CryptCheck::MAX_ANALYSIS_DURATION', 1 | |||
allow(CryptCheck::Tls::Server).to receive(:new) { sleep 2 } | |||
grades = server process: process do | |||
grades = server do | |||
analyze 'localhost', 5000 | |||
end | |||
@@ -62,7 +62,7 @@ RSpec.shared_examples :analysis do | |||
addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) } | |||
end | |||
grades = server host: '::1', process: process do | |||
grades = server host: '::1' do | |||
analyze 'localhost', 5000 | |||
end | |||
@@ -84,7 +84,7 @@ RSpec.shared_examples :analysis do | |||
original.call *args, &block | |||
end | |||
grades = server host: '::', process: process do | |||
grades = server host: '::' do | |||
analyze 'localhost', 5000 | |||
end | |||
@@ -106,7 +106,7 @@ RSpec.shared_examples :analysis do | |||
original.call *args, &block | |||
end | |||
grades = server host: '::', process: process do | |||
grades = server host: '::' do | |||
analyze 'localhost', 5000 | |||
end | |||
@@ -122,8 +122,8 @@ RSpec.shared_examples :analysis do | |||
addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) } | |||
end | |||
grades = plain_server host: '127.0.0.1', process: process do | |||
server host: '::1', process: process do | |||
grades = plain_server host: '127.0.0.1' do | |||
server host: '::1' do | |||
analyze 'localhost', 5000 | |||
end | |||
end | |||
@@ -1,5 +1,10 @@ | |||
describe CryptCheck::Tls do | |||
def process | |||
def server(*args, &block) | |||
tls_serv *args, &block | |||
end | |||
def plain_server(*args, &block) | |||
plain_serv *args, &block | |||
end | |||
def analyze(*args) | |||
@@ -0,0 +1,72 @@ | |||
describe CryptCheck::Tls::Xmpp do | |||
def server(*args, **kargs, &block) | |||
kargs[:plain_process] = proc do |socket| | |||
socket.gets | |||
socket.puts "<?xml version='1.0'?><stream:stream xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' from='localhost' id='' xml:lang='en' xmlns='jabber:server'><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls><dialback xmlns='urn:xmpp:features:dialback'/></stream:features>" | |||
socket.gets | |||
socket.puts "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />" | |||
true | |||
end unless kargs.include? :plain_process | |||
starttls_serv *args, **kargs, &block | |||
end | |||
def plain_server(*args, **kargs, &block) | |||
kargs[:plain_process] = proc do |socket| | |||
socket.gets | |||
socket.puts "<?xml version='1.0'?><stream:stream xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' from='localhost' id='' xml:lang='en' xmlns='jabber:server'><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls><dialback xmlns='urn:xmpp:features:dialback'/></stream:features>" | |||
socket.gets | |||
socket.puts "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />" | |||
false | |||
end unless kargs.include? :plain_process | |||
starttls_serv *args, **kargs, &block | |||
end | |||
def analyze(*args) | |||
CryptCheck::Tls::Xmpp.analyze *args, type: :s2s | |||
end | |||
include_examples :analysis do | |||
it 'return error on XMPP error' do | |||
plain_process = proc do |socket| | |||
socket.gets | |||
socket.puts "<?xml version='1.0'?><stream:stream xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' from='localhost' id='' xml:lang='en' xmlns='jabber:server'><stream:error><invalid-namespace xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error>" | |||
false | |||
end | |||
grades = server host: '127.0.0.1', plain_process: plain_process do | |||
analyze '127.0.0.1', 5000 | |||
end | |||
expect_grade_error grades, '127.0.0.1', '127.0.0.1', 5000, | |||
'<invalid-namespace xmlns="urn:ietf:params:xml:ns:xmpp-streams"/>' | |||
end | |||
end | |||
describe '#required?' do | |||
it 'has TLS not required' do | |||
grades = server host: '127.0.0.1' do | |||
analyze '127.0.0.1', 5000 | |||
end | |||
_, server = expect_grade grades, '127.0.0.1', '127.0.0.1', 5000, :ipv4 | |||
expect(server.required?).to be false | |||
end | |||
it 'has TLS required' do | |||
plain_process = proc do |socket| | |||
socket.gets | |||
socket.puts "<?xml version='1.0'?><stream:stream xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' from='localhost' id='' xml:lang='en' xmlns='jabber:server'><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required/></starttls><dialback xmlns='urn:xmpp:features:dialback'/></stream:features>" | |||
socket.gets | |||
socket.puts "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />" | |||
true | |||
end | |||
grades = server host: '127.0.0.1', plain_process: plain_process do | |||
analyze '127.0.0.1', 5000 | |||
end | |||
_, server = expect_grade grades, '127.0.0.1', '127.0.0.1', 5000, :ipv4 | |||
expect(server.required?).to be true | |||
end | |||
end | |||
end |
@@ -52,28 +52,7 @@ module Helpers | |||
cert.sign key, OpenSSL::Digest::SHA512.new | |||
end | |||
def server(key: 'rsa-1024', domain: 'localhost', # Key & certificate | |||
host: '127.0.0.1', port: 5000, # Binding | |||
version: :TLSv1_2, ciphers: 'AES128-SHA', # TLS version and ciphers | |||
dh: 1024, ecdh: 'secp256r1', # DHE & ECDHE | |||
process: nil) | |||
key = key key | |||
cert = certificate key, domain | |||
context = OpenSSL::SSL::SSLContext.new version | |||
context.cert = cert | |||
context.key = key | |||
context.ciphers = ciphers | |||
if dh | |||
dh = dh dh | |||
context.tmp_dh_callback = proc { dh } | |||
end | |||
if ecdh | |||
ecdh = key ecdh | |||
context.tmp_ecdh_callback = proc { ecdh } | |||
end | |||
def serv(server, process, &block) | |||
IO.pipe do |stop_pipe_r, stop_pipe_w| | |||
threads = [] | |||
@@ -81,16 +60,14 @@ module Helpers | |||
started = ConditionVariable.new | |||
threads << Thread.start do | |||
tcp_server = TCPServer.new host, port | |||
ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context | |||
mutex.synchronize { started.signal } | |||
loop do | |||
readable, = IO.select [ssl_server, stop_pipe_r] | |||
readable, = IO.select [server, stop_pipe_r] | |||
break if readable.include? stop_pipe_r | |||
begin | |||
socket = ssl_server.accept | |||
socket = server.accept | |||
begin | |||
process.call socket if process | |||
ensure | |||
@@ -99,13 +76,12 @@ module Helpers | |||
rescue | |||
end | |||
end | |||
ssl_server.close | |||
tcp_server.close | |||
server.close | |||
end | |||
mutex.synchronize { started.wait mutex } | |||
begin | |||
yield | |||
block.call if block | |||
ensure | |||
stop_pipe_w.close | |||
threads.each &:join | |||
@@ -113,41 +89,82 @@ module Helpers | |||
end | |||
end | |||
def plain_server(host: '127.0.0.1', port: 5000, process: nil) | |||
IO.pipe do |stop_pipe_r, stop_pipe_w| | |||
threads = [] | |||
def context(key: 'rsa-1024', domain: 'localhost', # Key & certificate | |||
version: :TLSv1_2, ciphers: 'AES128-SHA', # TLS version and ciphers | |||
dh: 1024, ecdh: 'secp256r1') # DHE & ECDHE | |||
key = key key | |||
cert = certificate key, domain | |||
mutex = Mutex.new | |||
started = ConditionVariable.new | |||
context = OpenSSL::SSL::SSLContext.new version | |||
context.cert = cert | |||
context.key = key | |||
context.ciphers = ciphers | |||
threads << Thread.start do | |||
tcp_server = TCPServer.new host, port | |||
mutex.synchronize { started.signal } | |||
if dh | |||
dh = dh dh | |||
context.tmp_dh_callback = proc { dh } | |||
end | |||
if ecdh | |||
ecdh = key ecdh | |||
context.tmp_ecdh_callback = proc { ecdh } | |||
end | |||
loop do | |||
readable, = IO.select [tcp_server, stop_pipe_r] | |||
break if readable.include? stop_pipe_r | |||
context | |||
end | |||
begin | |||
socket = tcp_server.accept | |||
begin | |||
process.call socket if process | |||
ensure | |||
socket.close | |||
end | |||
rescue | |||
end | |||
def tls_serv(key: 'rsa-1024', domain: 'localhost', # Key & certificate | |||
version: :TLSv1_2, ciphers: 'AES128-SHA', # TLS version and ciphers | |||
dh: 1024, ecdh: 'secp256r1', # DHE & ECDHE | |||
host: '127.0.0.1', port: 5000, # Binding | |||
process: nil, &block) | |||
context = context(key: key, domain: domain, version: version, ciphers: ciphers, dh: dh, ecdh: ecdh) | |||
tcp_server = TCPServer.new host, port | |||
tls_server = OpenSSL::SSL::SSLServer.new tcp_server, context | |||
begin | |||
serv tls_server, process, &block | |||
ensure | |||
tls_server.close | |||
tcp_server.close | |||
end | |||
end | |||
def plain_serv(host: '127.0.0.1', port: 5000, process: nil, &block) | |||
tcp_server = TCPServer.new host, port | |||
begin | |||
serv tcp_server, process, &block | |||
ensure | |||
tcp_server.close | |||
end | |||
end | |||
def starttls_serv(key: 'rsa-1024', domain: 'localhost', # Key & certificate | |||
version: :TLSv1_2, ciphers: 'AES128-SHA', # TLS version and ciphers | |||
dh: 1024, ecdh: 'secp256r1', # DHE & ECDHE | |||
host: '127.0.0.1', port: 5000, # Binding | |||
plain_process: nil, process: nil, &block) | |||
context = context(key: key, domain: domain, version: version, ciphers: ciphers, dh: dh, ecdh: ecdh) | |||
tcp_server = TCPServer.new host, port | |||
tls_server = OpenSSL::SSL::SSLServer.new tcp_server, context | |||
tls_server.start_immediately = false | |||
internal_process = proc do |socket| | |||
accept = false | |||
accept = plain_process.call socket if plain_process | |||
if accept | |||
tls_socket = socket.accept | |||
begin | |||
process.call tls_socket if process | |||
ensure | |||
socket.close | |||
end | |||
tcp_server.close | |||
end | |||
end | |||
mutex.synchronize { started.wait mutex } | |||
begin | |||
yield | |||
ensure | |||
stop_pipe_w.close | |||
threads.each &:join | |||
end | |||
begin | |||
serv tls_server, internal_process, &block | |||
ensure | |||
tls_server.close | |||
tcp_server.close | |||
end | |||
end | |||
@@ -157,7 +174,7 @@ module Helpers | |||
def expect_grade(grades, host, ip, port, family) | |||
grade = grade grades, host, ip, port | |||
expect(grade).to_not be nil | |||
expect(grade).to be_a CryptCheck::Tls::Grade | |||
server = grade.server | |||
expect(server).to be_a CryptCheck::Tls::Server | |||
expect(server.hostname).to eq host | |||