Browse Source

More unit tests (HTTPS)

master
Aeris 3 years ago
parent
commit
5aa9a975fe

+ 1
- 1
lib/cryptcheck.rb View File

@@ -87,7 +87,7 @@ module CryptCheck
87 87
 					g.display
88 88
 					[key, g]
89 89
 				end
90
-			rescue Exception => e
90
+			rescue => e
91 91
 				e = "Too long analysis (max #{MAX_ANALYSIS_DURATION.humanize})" if e.message == 'execution expired'
92 92
 				Logger.error e
93 93
 				[key, AnalysisFailure.new(e)]

+ 2
- 1
lib/cryptcheck/tls/https/server.rb View File

@@ -41,8 +41,9 @@ module CryptCheck
41 41
 					!@hsts.nil?
42 42
 				end
43 43
 
44
+				LONG_HSTS = 6*30*24*60*60
44 45
 				def hsts_long?
45
-					hsts? and @hsts >= 6*30*24*60*60
46
+					hsts? and @hsts >= LONG_HSTS
46 47
 				end
47 48
 			end
48 49
 		end

+ 8
- 5
lib/cryptcheck/tls/server.rb View File

@@ -9,15 +9,18 @@ module CryptCheck
9 9
 			SSL_TIMEOUT       = 2*TCP_TIMEOUT
10 10
 			EXISTING_METHODS  = %i(TLSv1_2 TLSv1_1 TLSv1 SSLv3 SSLv2)
11 11
 			SUPPORTED_METHODS = ::OpenSSL::SSL::SSLContext::METHODS
12
-			class TLSException < ::Exception
12
+			class TLSException < ::StandardError
13 13
 			end
14 14
 			class TLSNotAvailableException < TLSException
15
+				def to_s
16
+					'TLS seems not supported on this server'
17
+				end
15 18
 			end
16 19
 			class MethodNotAvailable < TLSException
17 20
 			end
18 21
 			class CipherNotAvailable < TLSException
19 22
 			end
20
-			class Timeout < Exception
23
+			class Timeout < ::StandardError
21 24
 			end
22 25
 			class TLSTimeout < Timeout
23 26
 			end
@@ -112,8 +115,8 @@ module CryptCheck
112 115
 
113 116
 			private
114 117
 			def name
115
-				name = "#{@hostname || @ip}:#@port"
116
-				name += " [#@ip]" if @hostname
118
+				name = "#@ip:#@port"
119
+				name += " [#@hostname]" if @hostname
117 120
 				name
118 121
 			end
119 122
 
@@ -165,7 +168,7 @@ module CryptCheck
165 168
 								/state=SSLv3 read server hello A: sslv3 alert handshake failure$/
166 169
 							raise CipherNotAvailable, e
167 170
 					end
168
-				rescue => e
171
+				rescue SystemCallError => e
169 172
 					case e
170 173
 						when /^Connection reset by peer$/
171 174
 							raise MethodNotAvailable, e

+ 97
- 0
spec/cryptcheck/https_spec.rb View File

@@ -0,0 +1,97 @@
1
+describe CryptCheck::Tls::Https do
2
+	def process
3
+		proc do |socket|
4
+			socket.print [
5
+								 'HTTP/1.1 200 OK',
6
+								 'Content-Type: text/plain',
7
+								 'Content-Length: 0',
8
+								 'Connection: close'
9
+						 ].join "\r\n"
10
+		end
11
+	end
12
+
13
+	def analyze(*args)
14
+		CryptCheck::Tls::Https.analyze *args
15
+	end
16
+
17
+	include_examples :analysis
18
+
19
+	describe '#hsts?' do
20
+		it 'has no hsts' do
21
+			grades = server host: '127.0.0.1', process: process do
22
+				analyze '127.0.0.1', 5000
23
+			end
24
+
25
+			_, server = expect_grade grades, '127.0.0.1', '127.0.0.1', 5000, :ipv4
26
+			expect(server.hsts?).to be false
27
+		end
28
+
29
+		it 'has hsts' do
30
+			process = proc do |socket|
31
+				socket.print [
32
+									 'HTTP/1.1 200 OK',
33
+									 'Strict-transport-security: max-age=31536000; includeSubdomains; preload',
34
+									 'Content-Type: text/plain',
35
+									 'Content-Length: 0',
36
+									 'Connection: close'
37
+							 ].join "\r\n"
38
+			end
39
+
40
+			grades = server host: '127.0.0.1', process: process do
41
+				analyze '127.0.0.1', 5000
42
+			end
43
+
44
+			_, server = expect_grade grades, '127.0.0.1', '127.0.0.1', 5000, :ipv4
45
+			expect(server.hsts?).to be true
46
+		end
47
+	end
48
+
49
+	describe '#hsts_long?' do
50
+		it 'has no hsts' do
51
+			grades = server host: '127.0.0.1', process: process do
52
+				analyze '127.0.0.1', 5000
53
+			end
54
+
55
+			_, server = expect_grade grades, '127.0.0.1', '127.0.0.1', 5000, :ipv4
56
+			expect(server.hsts_long?).to be false
57
+		end
58
+
59
+		it 'has hsts but not long' do
60
+			process = proc do |socket|
61
+				socket.print [
62
+									 'HTTP/1.1 200 OK',
63
+									 "Strict-transport-security: max-age=#{CryptCheck::Tls::Https::Server::LONG_HSTS-1}; includeSubdomains; preload",
64
+									 'Content-Type: text/plain',
65
+									 'Content-Length: 0',
66
+									 'Connection: close'
67
+							 ].join "\r\n"
68
+			end
69
+
70
+			grades = server host: '127.0.0.1', process: process do
71
+				analyze '127.0.0.1', 5000
72
+			end
73
+
74
+			_, server = expect_grade grades, '127.0.0.1', '127.0.0.1', 5000, :ipv4
75
+			expect(server.hsts_long?).to be false
76
+		end
77
+
78
+		it 'has long hsts' do
79
+			process = proc do |socket|
80
+				socket.print [
81
+									 'HTTP/1.1 200 OK',
82
+									 "Strict-transport-security: max-age=#{CryptCheck::Tls::Https::Server::LONG_HSTS}; includeSubdomains; preload",
83
+									 'Content-Type: text/plain',
84
+									 'Content-Length: 0',
85
+									 'Connection: close'
86
+							 ].join "\r\n"
87
+			end
88
+
89
+			grades = server host: '127.0.0.1', process: process do
90
+				analyze '127.0.0.1', 5000
91
+			end
92
+
93
+			_, server = expect_grade grades, '127.0.0.1', '127.0.0.1', 5000, :ipv4
94
+			expect(server.hsts_long?).to be true
95
+		end
96
+	end
97
+end

+ 136
- 0
spec/cryptcheck/support/analysis.rb View File

@@ -0,0 +1,136 @@
1
+RSpec.shared_examples :analysis do
2
+	describe '#analyze' do
3
+		it 'return 1 grade with IPv4' do
4
+			grades = server host: '127.0.0.1', process: process do
5
+				analyze '127.0.0.1', 5000
6
+			end
7
+
8
+			expect(grades.size).to be 1
9
+			expect_grade grades, '127.0.0.1', '127.0.0.1', 5000, :ipv4
10
+		end
11
+
12
+		it 'return 1 grade with IPv6' do
13
+			grades = server host: '::1', process: process do
14
+				analyze '::1', 5000
15
+			end
16
+
17
+			expect(grades.size).to be 1
18
+			expect_grade grades, '::1', '::1', 5000, :ipv6
19
+		end
20
+
21
+		it 'return 2 grades with hostname (IPv4 & IPv6)' do
22
+			addresses = %w(127.0.0.1 ::1)
23
+			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
24
+				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
25
+			end
26
+
27
+			grades = server host: '::', process: process do
28
+				analyze 'localhost', 5000
29
+			end
30
+
31
+			expect_grade grades, 'localhost', '127.0.0.1', 5000, :ipv4
32
+			expect_grade grades, 'localhost', '::1', 5000, :ipv6
33
+		end
34
+
35
+		it 'return error if DNS resolution problem' do
36
+			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM)
37
+									   .and_raise SocketError, 'getaddrinfo: Name or service not known'
38
+
39
+			grades = server process: process do
40
+				analyze 'localhost', 5000
41
+			end
42
+
43
+			expect(grades).to be_a CryptCheck::AnalysisFailure
44
+			expect(grades.to_s).to eq 'Unable to resolve localhost'
45
+		end
46
+
47
+		it 'return error if analysis too long' do
48
+			stub_const 'CryptCheck::MAX_ANALYSIS_DURATION', 1
49
+			allow(CryptCheck::Tls::Server).to receive(:new) { sleep 2 }
50
+
51
+			grades = server process: process do
52
+				analyze 'localhost', 5000
53
+			end
54
+
55
+			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
56
+							   'Too long analysis (max 1 second)'
57
+		end
58
+
59
+		it 'return error if unable to connect' do
60
+			addresses = %w(127.0.0.1 ::1)
61
+			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
62
+				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
63
+			end
64
+
65
+			grades = server host: '::1', process: process do
66
+				analyze 'localhost', 5000
67
+			end
68
+
69
+			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
70
+							   'Connection refused - connect(2) for 127.0.0.1:5000'
71
+			expect_grade grades, 'localhost', '::1', 5000, :ipv6
72
+		end
73
+
74
+		it 'return error if TCP timeout' do
75
+			stub_const 'CryptCheck::Tls::Server::TCP_TIMEOUT', 1
76
+			addresses = %w(127.0.0.1 ::1)
77
+			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
78
+				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
79
+			end
80
+			original = IO.method :select
81
+			allow(IO).to receive(:select) do |*args, &block|
82
+				socket = [args[0]&.first, args[1]&.first].compact.first
83
+				next nil if socket.is_a?(Socket) && (socket.local_address.afamily == Socket::AF_INET)
84
+				original.call *args, &block
85
+			end
86
+
87
+			grades = server host: '::', process: process do
88
+				analyze 'localhost', 5000
89
+			end
90
+
91
+			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
92
+							   'Timeout when connect to 127.0.0.1:5000 (max 1 second)'
93
+			expect_grade grades, 'localhost', '::1', 5000, :ipv6
94
+		end
95
+
96
+		it 'return error if TLS timeout' do
97
+			stub_const 'CryptCheck::Tls::Server::SSL_TIMEOUT', 1
98
+			addresses = %w(127.0.0.1 ::1)
99
+			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
100
+				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
101
+			end
102
+			original = IO.method :select
103
+			allow(IO).to receive(:select) do |*args, &block|
104
+				socket = [args[0]&.first, args[1]&.first].compact.first
105
+				next nil if socket.is_a?(OpenSSL::SSL::SSLSocket) && (socket.io.local_address.afamily == Socket::AF_INET)
106
+				original.call *args, &block
107
+			end
108
+
109
+			grades = server host: '::', process: process do
110
+				analyze 'localhost', 5000
111
+			end
112
+
113
+			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
114
+							   'Timeout when TLS connect to 127.0.0.1:5000 (max 1 second)'
115
+			expect_grade grades, 'localhost', '::1', 5000, :ipv6
116
+		end
117
+
118
+		it 'return error if plain server' do
119
+			stub_const 'CryptCheck::Tls::Server::SSL_TIMEOUT', 1
120
+			addresses = %w(127.0.0.1 ::1)
121
+			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
122
+				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
123
+			end
124
+
125
+			grades = plain_server host: '127.0.0.1', process: process do
126
+				server host: '::1', process: process do
127
+					analyze 'localhost', 5000
128
+				end
129
+			end
130
+
131
+			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
132
+							   'TLS seems not supported on this server'
133
+			expect_grade grades, 'localhost', '::1', 5000, :ipv6
134
+		end
135
+	end
136
+end

+ 6
- 132
spec/cryptcheck/tls_spec.rb View File

@@ -1,136 +1,10 @@
1 1
 describe CryptCheck::Tls do
2
-	describe '#analyze' do
3
-		it 'return 1 grade with IPv4' do
4
-			grades = server(host: '127.0.0.1') do
5
-				CryptCheck::Tls.analyze '127.0.0.1', 5000
6
-			end
7
-
8
-			expect(grades.size).to be 1
9
-			expect_grade grades, '127.0.0.1', '127.0.0.1', 5000, :ipv4
10
-		end
11
-
12
-		it 'return 1 grade with IPv6' do
13
-			grades = server(host: '::1') do
14
-				CryptCheck::Tls.analyze '::1', 5000
15
-			end
16
-
17
-			expect(grades.size).to be 1
18
-			expect_grade grades, '::1', '::1', 5000, :ipv6
19
-		end
20
-
21
-		it 'return 2 grades with hostname (IPv4 & IPv6)' do
22
-			addresses = %w(127.0.0.1 ::1)
23
-			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
24
-				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
25
-			end
26
-
27
-			grades = server(host: '::') do
28
-				CryptCheck::Tls.analyze 'localhost', 5000
29
-			end
30
-
31
-			expect_grade grades, 'localhost', '127.0.0.1', 5000, :ipv4
32
-			expect_grade grades, 'localhost', '::1', 5000, :ipv6
33
-		end
34
-
35
-		it 'return error if DNS resolution problem' do
36
-			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM)
37
-									   .and_raise SocketError, 'getaddrinfo: Name or service not known'
38
-
39
-			grades = server do
40
-				CryptCheck::Tls.analyze 'localhost', 5000
41
-			end
42
-
43
-			expect(grades).to be_a CryptCheck::AnalysisFailure
44
-			expect(grades.to_s).to eq 'Unable to resolve localhost'
45
-		end
46
-
47
-		it 'return error if analysis too long' do
48
-			stub_const 'CryptCheck::MAX_ANALYSIS_DURATION', 1
49
-			allow(CryptCheck::Tls::Server).to receive(:new) { sleep 2 }
50
-
51
-			grades = server do
52
-				CryptCheck::Tls.analyze 'localhost', 5000
53
-			end
54
-
55
-			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
56
-							   'Too long analysis (max 1 second)'
57
-		end
58
-
59
-		it 'return error if unable to connect' do
60
-			addresses = %w(127.0.0.1 ::1)
61
-			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
62
-				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
63
-			end
64
-
65
-			grades = server(host: '::1') do
66
-				CryptCheck::Tls.analyze 'localhost', 5000
67
-			end
68
-
69
-			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
70
-							   'Connection refused - connect(2) for 127.0.0.1:5000'
71
-			expect_grade grades, 'localhost', '::1', 5000, :ipv6
72
-		end
73
-
74
-		it 'return error if TCP timeout' do
75
-			stub_const 'CryptCheck::Tls::Server::TCP_TIMEOUT', 1
76
-			addresses = %w(127.0.0.1 ::1)
77
-			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
78
-				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
79
-			end
80
-			original = IO.method :select
81
-			allow(IO).to receive(:select) do |*args, &block|
82
-				socket = [args[0]&.first, args[1]&.first].compact.first
83
-				next nil if socket.is_a?(Socket) && (socket.local_address.afamily == Socket::AF_INET)
84
-				original.call *args, &block
85
-			end
86
-
87
-			grades = server(host: '::') do
88
-				CryptCheck::Tls.analyze 'localhost', 5000
89
-			end
90
-
91
-			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
92
-							   'Timeout when connect to 127.0.0.1:5000 (max 1 second)'
93
-			expect_grade grades, 'localhost', '::1', 5000, :ipv6
94
-		end
95
-
96
-		it 'return error if TLS timeout' do
97
-			stub_const 'CryptCheck::Tls::Server::SSL_TIMEOUT', 1
98
-			addresses = %w(127.0.0.1 ::1)
99
-			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
100
-				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
101
-			end
102
-			original = IO.method :select
103
-			allow(IO).to receive(:select) do |*args, &block|
104
-				socket = [args[0]&.first, args[1]&.first].compact.first
105
-				next nil if socket.is_a?(OpenSSL::SSL::SSLSocket) && (socket.io.local_address.afamily == Socket::AF_INET)
106
-				original.call *args, &block
107
-			end
108
-
109
-			grades = server(host: '::') do
110
-				CryptCheck::Tls.analyze 'localhost', 5000
111
-			end
112
-
113
-			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
114
-							   'Timeout when TLS connect to 127.0.0.1:5000 (max 1 second)'
115
-			expect_grade grades, 'localhost', '::1', 5000, :ipv6
116
-		end
117
-
118
-		it 'return error if plain server' do
119
-			stub_const 'CryptCheck::Tls::Server::SSL_TIMEOUT', 1
120
-			addresses = %w(127.0.0.1 ::1)
121
-			allow(Addrinfo).to receive(:getaddrinfo).with('localhost', nil, nil, :STREAM) do
122
-				addresses.collect { |a| Addrinfo.new Socket.sockaddr_in(nil, a) }
123
-			end
124
-
125
-			grades = plain_server(host: '127.0.0.1') do
126
-						server(host: '::1') do
127
-							CryptCheck::Tls.analyze 'localhost', 5000
128
-						end
129
-					end
2
+	def process
3
+	end
130 4
 
131
-			expect_grade_error grades, 'localhost', '127.0.0.1', 5000,
132
-							   'Timeout when TLS connect to 127.0.0.1:5000 (max 1 second)'
133
-			expect_grade grades, 'localhost', '::1', 5000, :ipv6
134
-		end
5
+	def analyze(*args)
6
+		CryptCheck::Tls.analyze *args
135 7
 	end
8
+
9
+	include_examples :analysis
136 10
 end

+ 31
- 9
spec/helpers.rb View File

@@ -2,6 +2,7 @@ $:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib'
2 2
 require 'rubygems'
3 3
 require 'bundler/setup'
4 4
 require 'cryptcheck'
5
+Dir['./spec/**/support/**/*.rb'].sort.each { |f| require f }
5 6
 
6 7
 CryptCheck::Logger.level = ENV['LOG'] || :none
7 8
 
@@ -54,7 +55,8 @@ module Helpers
54 55
 	def server(key: 'rsa-1024', domain: 'localhost', # Key & certificate
55 56
 			   host: '127.0.0.1', port: 5000, # Binding
56 57
 			   version: :TLSv1_2, ciphers: 'AES128-SHA', # TLS version and ciphers
57
-			   dh: 1024, ecdh: 'secp256r1') # DHE & ECDHE
58
+			   dh: 1024, ecdh: 'secp256r1', # DHE & ECDHE
59
+			   process: nil)
58 60
 		key  = key key
59 61
 		cert = certificate key, domain
60 62
 
@@ -75,7 +77,7 @@ module Helpers
75 77
 		IO.pipe do |stop_pipe_r, stop_pipe_w|
76 78
 			threads = []
77 79
 
78
-			mutex = Mutex.new
80
+			mutex   = Mutex.new
79 81
 			started = ConditionVariable.new
80 82
 
81 83
 			threads << Thread.start do
@@ -88,7 +90,12 @@ module Helpers
88 90
 					readable, = IO.select [ssl_server, stop_pipe_r]
89 91
 					break if readable.include? stop_pipe_r
90 92
 					begin
91
-						ssl_server.accept
93
+						socket = ssl_server.accept
94
+						begin
95
+							process.call socket if process
96
+						ensure
97
+							socket.close
98
+						end
92 99
 					rescue
93 100
 					end
94 101
 				end
@@ -106,11 +113,11 @@ module Helpers
106 113
 		end
107 114
 	end
108 115
 
109
-	def plain_server(host: '127.0.0.1', port: 5000)
116
+	def plain_server(host: '127.0.0.1', port: 5000, process: nil)
110 117
 		IO.pipe do |stop_pipe_r, stop_pipe_w|
111 118
 			threads = []
112 119
 
113
-			mutex = Mutex.new
120
+			mutex   = Mutex.new
114 121
 			started = ConditionVariable.new
115 122
 
116 123
 			threads << Thread.start do
@@ -120,8 +127,14 @@ module Helpers
120 127
 				loop do
121 128
 					readable, = IO.select [tcp_server, stop_pipe_r]
122 129
 					break if readable.include? stop_pipe_r
130
+
123 131
 					begin
124
-						tcp_server.accept
132
+						socket = tcp_server.accept
133
+						begin
134
+							process.call socket if process
135
+						ensure
136
+							socket.close
137
+						end
125 138
 					rescue
126 139
 					end
127 140
 				end
@@ -138,16 +151,25 @@ module Helpers
138 151
 		end
139 152
 	end
140 153
 
154
+	def grade(grades, host, ip, port)
155
+		grades[[host, ip, port]]
156
+	end
157
+
141 158
 	def expect_grade(grades, host, ip, port, family)
142
-		server = grades[[host, ip, port]].server
159
+		grade = grade grades, host, ip, port
160
+		expect(grade).to_not be nil
161
+		server = grade.server
143 162
 		expect(server).to be_a CryptCheck::Tls::Server
144 163
 		expect(server.hostname).to eq host
145 164
 		expect(server.ip).to eq ip
146 165
 		expect(server.port).to eq port
147 166
 		expect(server.family).to eq case family
148
-										when :ipv4 then Socket::AF_INET
149
-										when :ipv6 then Socket::AF_INET6
167
+										when :ipv4 then
168
+											Socket::AF_INET
169
+										when :ipv6 then
170
+											Socket::AF_INET6
150 171
 									end
172
+		[grade, server]
151 173
 	end
152 174
 
153 175
 	def expect_grade_error(grades, host, ip, port, error)

Loading…
Cancel
Save