From f1c14eef39f9b0b0bdab287239b4f00a9196b6a3 Mon Sep 17 00:00:00 2001 From: Aeris Date: Fri, 6 May 2016 21:27:02 +0200 Subject: [PATCH] More unit tests (XMPP) --- bin/check_xmpp.rb | 2 +- lib/cryptcheck/tls/server.rb | 2 +- lib/cryptcheck/tls/xmpp.rb | 8 +- lib/cryptcheck/tls/xmpp/server.rb | 8 +- spec/cryptcheck/https_spec.rb | 19 ++-- spec/cryptcheck/support/analysis.rb | 20 ++--- spec/cryptcheck/tls_spec.rb | 7 +- spec/cryptcheck/xmpp_spec.rb | 72 +++++++++++++++ spec/helpers.rb | 135 ++++++++++++++++------------ 9 files changed, 179 insertions(+), 94 deletions(-) create mode 100644 spec/cryptcheck/xmpp_spec.rb diff --git a/bin/check_xmpp.rb b/bin/check_xmpp.rb index b6a43ff..13e9be7 100755 --- a/bin/check_xmpp.rb +++ b/bin/check_xmpp.rb @@ -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 diff --git a/lib/cryptcheck/tls/server.rb b/lib/cryptcheck/tls/server.rb index 4978c16..a7eaaea 100644 --- a/lib/cryptcheck/tls/server.rb +++ b/lib/cryptcheck/tls/server.rb @@ -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 diff --git a/lib/cryptcheck/tls/xmpp.rb b/lib/cryptcheck/tls/xmpp.rb index 5593adb..8f0af74 100644 --- a/lib/cryptcheck/tls/xmpp.rb +++ b/lib/cryptcheck/tls/xmpp.rb @@ -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) diff --git a/lib/cryptcheck/tls/xmpp/server.rb b/lib/cryptcheck/tls/xmpp/server.rb index 7e59a48..678342c 100644 --- a/lib/cryptcheck/tls/xmpp/server.rb +++ b/lib/cryptcheck/tls/xmpp/server.rb @@ -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 "" + socket.puts "" 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 "\r\n" response = ::Nokogiri::XML socket.recv 4096 raise TLSNotAvailableException unless response.xpath '//tls:proceed', tls: TLS_NAMESPACE diff --git a/spec/cryptcheck/https_spec.rb b/spec/cryptcheck/https_spec.rb index ead0de6..216a7c8 100644 --- a/spec/cryptcheck/https_spec.rb +++ b/spec/cryptcheck/https_spec.rb @@ -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 diff --git a/spec/cryptcheck/support/analysis.rb b/spec/cryptcheck/support/analysis.rb index 5b7ef69..1f01037 100644 --- a/spec/cryptcheck/support/analysis.rb +++ b/spec/cryptcheck/support/analysis.rb @@ -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 diff --git a/spec/cryptcheck/tls_spec.rb b/spec/cryptcheck/tls_spec.rb index 477848e..713d320 100644 --- a/spec/cryptcheck/tls_spec.rb +++ b/spec/cryptcheck/tls_spec.rb @@ -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) diff --git a/spec/cryptcheck/xmpp_spec.rb b/spec/cryptcheck/xmpp_spec.rb new file mode 100644 index 0000000..361d860 --- /dev/null +++ b/spec/cryptcheck/xmpp_spec.rb @@ -0,0 +1,72 @@ +describe CryptCheck::Tls::Xmpp do + def server(*args, **kargs, &block) + kargs[:plain_process] = proc do |socket| + socket.gets + socket.puts "" + socket.gets + socket.puts "" + 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 "" + socket.gets + socket.puts "" + 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 "" + 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, + '' + 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 "" + socket.gets + socket.puts "" + 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 diff --git a/spec/helpers.rb b/spec/helpers.rb index e0b9c36..7613730 100644 --- a/spec/helpers.rb +++ b/spec/helpers.rb @@ -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