Bladeren bron

Unit tests

master
Aeris 3 jaren geleden
bovenliggende
commit
a2c38b05b0

+ 0
- 1
.gitignore Bestand weergeven

@@ -4,7 +4,6 @@ Gemfile.lock
4 4
 .rakeTasks
5 5
 .idea/
6 6
 /html/
7
-.rspec
8 7
 /rakefile
9 8
 /output/*.html
10 9
 /db/*.sqlite3

+ 2
- 0
.rspec Bestand weergeven

@@ -0,0 +1,2 @@
1
+--color
2
+--require helpers

+ 48
- 20
lib/cryptcheck.rb Bestand weergeven

@@ -5,9 +5,21 @@ require 'yaml'
5 5
 require 'cryptcheck/tls/fixture'
6 6
 
7 7
 module CryptCheck
8
-	MAX_ANALYSIS_DURATION = 600
8
+	MAX_ANALYSIS_DURATION = 120
9 9
 	PARALLEL_ANALYSIS     = 10
10 10
 
11
+	class AnalysisFailure
12
+		attr_reader :error
13
+
14
+		def initialize(error)
15
+			@error = error
16
+		end
17
+
18
+		def to_s
19
+			@error.to_s
20
+		end
21
+	end
22
+
11 23
 	autoload :Logger, 'cryptcheck/logger'
12 24
 	autoload :Tls, 'cryptcheck/tls'
13 25
 	module Tls
@@ -50,38 +62,54 @@ module CryptCheck
50 62
 	def self.addresses(host)
51 63
 		begin
52 64
 			ip = IPAddr.new host
53
-			[[ip.family, ip.to_s, nil]]
65
+			return [[ip.family, ip.to_s, nil]]
54 66
 		rescue IPAddr::InvalidAddressError
55
-			begin
56
-				::Addrinfo.getaddrinfo(host, nil, nil, :STREAM)
57
-						.collect { |a| [a.afamily, a.ip_address, host] }
58
-			rescue ::SocketError => e
59
-				Logger.error { "Unable to resolv #{host} : #{e}" }
60
-				[]
61
-			end
62 67
 		end
68
+		::Addrinfo.getaddrinfo(host, nil, nil, :STREAM)
69
+				.collect { |a| [a.afamily, a.ip_address, host] }
63 70
 	end
64 71
 
65
-	def self.analyze_addresses(host, addresses, port, on_error = TLS_NOT_AVAILABLE, &block)
66
-		begin
67
-			::Timeout::timeout MAX_ANALYSIS_DURATION do
68
-				addresses.each { |family, ip, host| return block.call family, ip, host }
72
+	def self.analyze_addresses(host, addresses, port, server, grade, *args, **kargs)
73
+		first = true
74
+		addresses.collect do |family, ip|
75
+			first ? (first = false) : Logger.info { '' }
76
+			key = [host, ip, port]
77
+			a   = [host, family, ip, port, *args]
78
+			begin
79
+				::Timeout::timeout MAX_ANALYSIS_DURATION do
80
+					s = if kargs.empty?
81
+							server.new *a
82
+						else
83
+							server.new *a, **kargs
84
+						end
85
+					g = grade.new s
86
+					Logger.info { '' }
87
+					g.display
88
+					[key, g]
89
+				end
90
+			rescue Exception => e
91
+				e = "Too long analysis (max #{MAX_ANALYSIS_DURATION.humanize})" if e.message == 'execution expired'
92
+				Logger.error e
93
+				[key, AnalysisFailure.new(e)]
69 94
 			end
70
-		rescue ::Exception => e
71
-			Logger.error e
72
-		end
73
-		on_error.call host, port
95
+		end.to_h
74 96
 	end
75 97
 
76
-	def self.analyze(host, port, on_error = Tls::TLS_NOT_AVAILABLE, &block)
77
-		analyze_addresses host, addresses(host), port, on_error, &block
98
+	def self.analyze(host, port, server, grade, *args, **kargs)
99
+		addresses = begin
100
+			addresses host
101
+		rescue ::SocketError => e
102
+			Logger::error e
103
+			return AnalysisFailure.new "Unable to resolve #{host}"
104
+		end
105
+		analyze_addresses host, addresses, port, server, grade, *args, **kargs
78 106
 	end
79 107
 
80 108
 	def self.analyze_hosts(hosts, template, output, groups: nil, &block)
81 109
 		results   = {}
82 110
 		semaphore = ::Mutex.new
83 111
 		::Parallel.each hosts, progress: 'Analysing', in_threads: PARALLEL_ANALYSIS, finish: lambda { |item, _, _| puts item[1] } do |description, host|
84
-		#hosts.each do |description, host|
112
+			#hosts.each do |description, host|
85 113
 			result = block.call host.strip
86 114
 			semaphore.synchronize do
87 115
 				if results.include? description

+ 0
- 8
lib/cryptcheck/ssh/server.rb Bestand weergeven

@@ -2,14 +2,6 @@ require 'socket'
2 2
 
3 3
 module CryptCheck
4 4
 	module Ssh
5
-		class SshNotSupportedServer
6
-			attr_reader :host, :port
7
-
8
-			def initialize(host, port)
9
-				@host, @port = host, port
10
-			end
11
-		end
12
-
13 5
 		class Server
14 6
 			TCP_TIMEOUT = 10
15 7
 			class SshNotAvailableException < Exception

+ 1
- 11
lib/cryptcheck/tls.rb Bestand weergeven

@@ -3,18 +3,8 @@ require 'parallel'
3 3
 
4 4
 module CryptCheck
5 5
 	module Tls
6
-		TLS_NOT_AVAILABLE = Proc.new { |host, port|
7
-			TlsNotSupportedGrade.new TlsNotSupportedServer.new host, port
8
-		}
9
-
10 6
 		def self.analyze(host, port)
11
-			::CryptCheck.analyze host, port do |family, ip, host|
12
-				s = TcpServer.new family, ip, port, hostname: host
13
-				g = Grade.new s
14
-				Logger.info { '' }
15
-				g.display
16
-				g
17
-			end
7
+			::CryptCheck.analyze host, port, TcpServer, Grade
18 8
 		end
19 9
 
20 10
 		def self.colorize(cipher)

+ 13
- 0
lib/cryptcheck/tls/fixture.rb Bestand weergeven

@@ -1,5 +1,18 @@
1 1
 require 'openssl'
2 2
 
3
+class Integer
4
+	def humanize
5
+		secs = self
6
+		[[60, :second], [60, :minute], [24, :hour], [30, :day], [12, :month]].map { |count, name|
7
+			if secs > 0
8
+				secs, n = self.divmod count
9
+				n = n.to_i
10
+				"#{n} #{name}#{n > 1 ? 's' : ''}"
11
+			end
12
+		}.compact.reverse.join(' ')
13
+	end
14
+end
15
+
3 16
 class ::OpenSSL::PKey::EC
4 17
 	def type
5 18
 		:ecc

+ 0
- 8
lib/cryptcheck/tls/grade.rb Bestand weergeven

@@ -1,13 +1,5 @@
1 1
 module CryptCheck
2 2
 	module Tls
3
-		class TlsNotSupportedGrade
4
-			attr_reader :server, :score, :grade
5
-
6
-			def initialize(server)
7
-				@server, @score, @grade = server, -1, 'X'
8
-			end
9
-		end
10
-
11 3
 		class Grade
12 4
 			attr_reader :server, :protocol_score, :key_exchange_score, :cipher_strengths_score, :score, :grade, :error, :danger, :warning, :success
13 5
 

+ 1
- 7
lib/cryptcheck/tls/https.rb Bestand weergeven

@@ -2,13 +2,7 @@ module CryptCheck
2 2
 	module Tls
3 3
 		module Https
4 4
 			def self.analyze(host, port=443)
5
-				::CryptCheck.analyze host, port do |family, ip, host|
6
-					s = Server.new family, ip, port, hostname: host
7
-					g = Grade.new s
8
-					Logger.info { '' }
9
-					g.display
10
-					g
11
-				end
5
+				::CryptCheck.analyze host, port, Server, Grade
12 6
 			end
13 7
 
14 8
 			def self.analyze_file(input, output)

+ 1
- 1
lib/cryptcheck/tls/https/server.rb Bestand weergeven

@@ -6,7 +6,7 @@ module CryptCheck
6 6
 			class Server < Tls::TcpServer
7 7
 				attr_reader :hsts
8 8
 
9
-				def initialize(family, ip, port = 443, hostname: nil)
9
+				def initialize(hostname, family, ip, port=443)
10 10
 					super
11 11
 					fetch_hsts
12 12
 				end

+ 18
- 34
lib/cryptcheck/tls/server.rb Bestand weergeven

@@ -4,14 +4,6 @@ require 'httparty'
4 4
 
5 5
 module CryptCheck
6 6
 	module Tls
7
-		class TlsNotSupportedServer
8
-			attr_reader :hostname, :port
9
-
10
-			def initialize(host, port)
11
-				@hostname, @port = host, port
12
-			end
13
-		end
14
-
15 7
 		class Server
16 8
 			TCP_TIMEOUT       = 10
17 9
 			SSL_TIMEOUT       = 2*TCP_TIMEOUT
@@ -25,18 +17,18 @@ module CryptCheck
25 17
 			end
26 18
 			class CipherNotAvailable < TLSException
27 19
 			end
28
-			class Timeout < TLSException
20
+			class Timeout < Exception
29 21
 			end
30
-			class TLSTimeout < TLSException
22
+			class TLSTimeout < Timeout
31 23
 			end
32
-			class ConnectionError < TLSException
24
+			class ConnectionError < Exception
33 25
 			end
34 26
 
35 27
 			attr_reader :family, :ip, :port, :hostname, :prefered_ciphers, :cert, :cert_valid, :cert_trusted, :dh
36 28
 
37
-			def initialize(family, ip, port, hostname: nil)
38
-				@family, @ip, @port, @hostname = family, ip, port, hostname
39
-				@dh = []
29
+			def initialize(hostname, family, ip, port)
30
+				@hostname, @family, @ip, @port = hostname, family, ip, port
31
+				@dh                            = []
40 32
 				Logger.info { name.colorize :blue }
41 33
 				extract_cert
42 34
 				Logger.info { '' }
@@ -128,27 +120,21 @@ module CryptCheck
128 120
 			def connect(&block)
129 121
 				socket   = ::Socket.new @family, sock_type
130 122
 				sockaddr = ::Socket.sockaddr_in @port, @ip
131
-				Logger.trace { "Connecting to #{name}" }
123
+				Logger.trace { "Connecting to #{@ip}:#{@port}" }
132 124
 				begin
133 125
 					status = socket.connect_nonblock sockaddr
134
-					Logger.trace { "Connecting to #{name} status : #{status}" }
126
+					Logger.trace { "Connecting to #{@ip}:#{@port} status : #{status}" }
135 127
 					raise ConnectionError, status unless status == 0
136
-					Logger.trace { "Connected to #{name}" }
128
+					Logger.trace { "Connected to #{@ip}:#{@port}" }
137 129
 					block_given? ? block.call(socket) : nil
138 130
 				rescue ::IO::WaitReadable
139
-					Logger.trace { "Waiting for read to #{name}" }
140
-					raise Timeout unless IO.select [socket], nil, nil, TCP_TIMEOUT
131
+					Logger.trace { "Waiting for read to #{@ip}:#{@port}" }
132
+					raise Timeout, "Timeout when connect to #{@ip}:#{@port} (max #{TCP_TIMEOUT.humanize})" unless IO.select [socket], nil, nil, TCP_TIMEOUT
141 133
 					retry
142 134
 				rescue ::IO::WaitWritable
143
-					Logger.trace { "Waiting for write to #{name}" }
144
-					raise Timeout unless IO.select nil, [socket], nil, TCP_TIMEOUT
135
+					Logger.trace { "Waiting for write to #{@ip}:#{@port}" }
136
+					raise Timeout, "Timeout when connect to #{@ip}:#{@port} (max #{TCP_TIMEOUT.humanize})" unless IO.select nil, [socket], nil, TCP_TIMEOUT
145 137
 					retry
146
-				rescue => e
147
-					case e.message
148
-						when /^Connection refused/
149
-							raise TLSNotAvailableException, e
150
-					end
151
-					raise
152 138
 				ensure
153 139
 					socket.close
154 140
 				end
@@ -164,14 +150,14 @@ module CryptCheck
164 150
 					return block_given? ? block.call(ssl_socket) : nil
165 151
 				rescue ::IO::WaitReadable
166 152
 					Logger.trace { "Waiting for SSL read to #{name}" }
167
-					raise TLSTimeout unless IO.select [socket], nil, nil, SSL_TIMEOUT
153
+					raise TLSTimeout, "Timeout when TLS connect to #{@ip}:#{@port} (max #{SSL_TIMEOUT.humanize})" unless IO.select [ssl_socket], nil, nil, SSL_TIMEOUT
168 154
 					retry
169 155
 				rescue ::IO::WaitWritable
170 156
 					Logger.trace { "Waiting for SSL write to #{name}" }
171
-					raise TLSTimeout unless IO.select nil, [socket], nil, SSL_TIMEOUT
157
+					raise TLSTimeout, "Timeout when TLS connect to #{@ip}:#{@port} (max #{SSL_TIMEOUT.humanize})" unless IO.select nil, [ssl_socket], nil, SSL_TIMEOUT
172 158
 					retry
173 159
 				rescue ::OpenSSL::SSL::SSLError => e
174
-					case e.message
160
+					case e
175 161
 						when /state=SSLv2 read server hello A$/,
176 162
 								/state=SSLv3 read server hello A: wrong version number$/
177 163
 							raise MethodNotAvailable, e
@@ -179,13 +165,11 @@ module CryptCheck
179 165
 								/state=SSLv3 read server hello A: sslv3 alert handshake failure$/
180 166
 							raise CipherNotAvailable, e
181 167
 					end
182
-					raise TLSException, e
183 168
 				rescue => e
184
-					case e.message
169
+					case e
185 170
 						when /^Connection reset by peer$/
186 171
 							raise MethodNotAvailable, e
187 172
 					end
188
-					raise TLSException, e
189 173
 				ensure
190 174
 					ssl_socket.close
191 175
 				end
@@ -248,7 +232,7 @@ module CryptCheck
248 232
 				dh = ssl_client method, [cipher] { |s| s.tmp_key }
249 233
 				@dh << dh if dh
250 234
 				cipher = Cipher.new method, cipher, dh
251
-				dh = dh ? " (#{'DH'.colorize :green} : #{Tls.key_to_s dh})" : ''
235
+				dh     = dh ? " (#{'DH'.colorize :green} : #{Tls.key_to_s dh})" : ''
252 236
 				Logger.info { "#{Tls.colorize method} / #{cipher.colorize} : Supported#{dh}" }
253 237
 				cipher
254 238
 			rescue TLSException => e

+ 4
- 11
lib/cryptcheck/tls/smtp.rb Bestand weergeven

@@ -2,20 +2,13 @@ module CryptCheck
2 2
 	module Tls
3 3
 		module Smtp
4 4
 			def self.analyze(host, port=25, domain: nil)
5
-				domain ||= host
6
-				::CryptCheck.analyze host, port do |family, ip, host|
7
-					s = Server.new family, ip, port, hostname: host, domain: domain
8
-					g = Grade.new s
9
-					Logger.info { '' }
10
-					g.display
11
-					g
12
-				end
5
+				::CryptCheck.analyze host, port, Server, Grade, domain: domain
13 6
 			end
14 7
 
15 8
 			def self.analyze_domain(domain)
16
-				srv = Resolv::DNS.new.getresources(domain, Resolv::DNS::Resource::IN::MX).sort_by(&:preference).first
17
-				hostname = srv ? srv.exchange.to_s : domain
18
-				self.analyze hostname, domain: domain
9
+				srv = Resolv::DNS.new.getresources(domain, Resolv::DNS::Resource::IN::MX).sort_by &:preference
10
+				hosts = srv.empty? ? [domain] : srv.collect { |s| s.exchange.to_s }
11
+				hosts.collect { |h| self.analyze h, domain: domain }.flatten(1)
19 12
 			end
20 13
 
21 14
 			def self.analyze_file(input, output)

+ 2
- 2
lib/cryptcheck/tls/smtp/server.rb Bestand weergeven

@@ -4,9 +4,9 @@ module CryptCheck
4 4
 			class Server < Tls::TcpServer
5 5
 				attr_reader :domain
6 6
 
7
-				def initialize(family, ip, port, hostname: nil, domain:)
7
+				def initialize(hostname, family, ip, port, domain:)
8 8
 					@domain = domain
9
-					super family, ip, port, hostname: hostname
9
+					super
10 10
 				end
11 11
 
12 12
 				def ssl_connect(socket, context, method, &block)

+ 1
- 1
lib/cryptcheck/tls/xmpp/server.rb Bestand weergeven

@@ -8,7 +8,7 @@ module CryptCheck
8 8
 			class Server < Tls::TcpServer
9 9
 				attr_reader :domain
10 10
 
11
-				def initialize(family, ip, port=nil, hostname: nil, domain: nil, type: :s2s)
11
+				def initialize(hostname, family, ip, port=nil, domain: nil, type: :s2s)
12 12
 					domain         ||= hostname
13 13
 					@type, @domain = type, domain
14 14
 					port           = case type

+ 136
- 0
spec/cryptcheck/tls_spec.rb Bestand weergeven

@@ -0,0 +1,136 @@
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
130
+
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
135
+	end
136
+end

+ 162
- 0
spec/helpers.rb Bestand weergeven

@@ -0,0 +1,162 @@
1
+$:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib'
2
+require 'rubygems'
3
+require 'bundler/setup'
4
+require 'cryptcheck'
5
+
6
+CryptCheck::Logger.level = ENV['LOG'] || :none
7
+
8
+module Helpers
9
+	OpenSSL::PKey::EC.send :alias_method, :private?, :private_key?
10
+
11
+	def key(name)
12
+		open(File.join(File.dirname(__FILE__), 'resources', "#{name}.pem"), 'r') { |f| OpenSSL::PKey.read f }
13
+	end
14
+
15
+	def dh(name)
16
+		open(File.join(File.dirname(__FILE__), 'resources', "dh-#{name}.pem"), 'r') { |f| OpenSSL::PKey::DH.new f }
17
+	end
18
+
19
+	def certificate(key, domain)
20
+		cert            = OpenSSL::X509::Certificate.new
21
+		cert.version    = 2
22
+		cert.serial     = rand 2**(20*8-1) .. 2**(20*8)
23
+		cert.not_before = Time.now
24
+		cert.not_after  = cert.not_before + 60*60
25
+
26
+		cert.public_key = case key
27
+							  when OpenSSL::PKey::EC
28
+								  curve             = key.group.curve_name
29
+								  public            = OpenSSL::PKey::EC.new curve
30
+								  public.public_key = key.public_key
31
+								  public
32
+							  else
33
+								  key.public_key
34
+						  end
35
+
36
+		name         = OpenSSL::X509::Name.parse "CN=#{domain}"
37
+		cert.subject = name
38
+		cert.issuer  = name
39
+
40
+		extension_factory                     = OpenSSL::X509::ExtensionFactory.new nil, cert
41
+		extension_factory.subject_certificate = cert
42
+		extension_factory.issuer_certificate  = cert
43
+
44
+		cert.add_extension extension_factory.create_extension 'basicConstraints', 'CA:TRUE', true
45
+		cert.add_extension extension_factory.create_extension 'keyUsage', 'keyEncipherment, dataEncipherment, digitalSignature,nonRepudiation,keyCertSign'
46
+		cert.add_extension extension_factory.create_extension 'extendedKeyUsage', 'serverAuth, clientAuth'
47
+		cert.add_extension extension_factory.create_extension 'subjectKeyIdentifier', 'hash'
48
+		cert.add_extension extension_factory.create_extension 'authorityKeyIdentifier', 'keyid:always'
49
+		cert.add_extension extension_factory.create_extension 'subjectAltName', "DNS:#{domain}"
50
+
51
+		cert.sign key, OpenSSL::Digest::SHA512.new
52
+	end
53
+
54
+	def server(key: 'rsa-1024', domain: 'localhost', # Key & certificate
55
+			   host: '127.0.0.1', port: 5000, # Binding
56
+			   version: :TLSv1_2, ciphers: 'AES128-SHA', # TLS version and ciphers
57
+			   dh: 1024, ecdh: 'secp256r1') # DHE & ECDHE
58
+		key  = key key
59
+		cert = certificate key, domain
60
+
61
+		context         = OpenSSL::SSL::SSLContext.new version
62
+		context.cert    = cert
63
+		context.key     = key
64
+		context.ciphers = ciphers
65
+
66
+		if dh
67
+			dh                      = dh dh
68
+			context.tmp_dh_callback = proc { dh }
69
+		end
70
+		if ecdh
71
+			ecdh                      = key ecdh
72
+			context.tmp_ecdh_callback = proc { ecdh }
73
+		end
74
+
75
+		IO.pipe do |stop_pipe_r, stop_pipe_w|
76
+			threads = []
77
+
78
+			mutex = Mutex.new
79
+			started = ConditionVariable.new
80
+
81
+			threads << Thread.start do
82
+				tcp_server = TCPServer.new host, port
83
+				ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context
84
+
85
+				mutex.synchronize { started.signal }
86
+
87
+				loop do
88
+					readable, = IO.select [ssl_server, stop_pipe_r]
89
+					break if readable.include? stop_pipe_r
90
+					begin
91
+						ssl_server.accept
92
+					rescue
93
+					end
94
+				end
95
+				ssl_server.close
96
+				tcp_server.close
97
+			end
98
+
99
+			mutex.synchronize { started.wait mutex }
100
+			begin
101
+				yield
102
+			ensure
103
+				stop_pipe_w.close
104
+				threads.each &:join
105
+			end
106
+		end
107
+	end
108
+
109
+	def plain_server(host: '127.0.0.1', port: 5000)
110
+		IO.pipe do |stop_pipe_r, stop_pipe_w|
111
+			threads = []
112
+
113
+			mutex = Mutex.new
114
+			started = ConditionVariable.new
115
+
116
+			threads << Thread.start do
117
+				tcp_server = TCPServer.new host, port
118
+				mutex.synchronize { started.signal }
119
+
120
+				loop do
121
+					readable, = IO.select [tcp_server, stop_pipe_r]
122
+					break if readable.include? stop_pipe_r
123
+					begin
124
+						tcp_server.accept
125
+					rescue
126
+					end
127
+				end
128
+				tcp_server.close
129
+			end
130
+
131
+			mutex.synchronize { started.wait mutex }
132
+			begin
133
+				yield
134
+			ensure
135
+				stop_pipe_w.close
136
+				threads.each &:join
137
+			end
138
+		end
139
+	end
140
+
141
+	def expect_grade(grades, host, ip, port, family)
142
+		server = grades[[host, ip, port]].server
143
+		expect(server).to be_a CryptCheck::Tls::Server
144
+		expect(server.hostname).to eq host
145
+		expect(server.ip).to eq ip
146
+		expect(server.port).to eq port
147
+		expect(server.family).to eq case family
148
+										when :ipv4 then Socket::AF_INET
149
+										when :ipv6 then Socket::AF_INET6
150
+									end
151
+	end
152
+
153
+	def expect_grade_error(grades, host, ip, port, error)
154
+		server = grades[[host, ip, port]]
155
+		expect(server).to be_a CryptCheck::AnalysisFailure
156
+		expect(server.to_s).to eq error
157
+	end
158
+end
159
+
160
+RSpec.configure do |c|
161
+	c.include Helpers
162
+end

+ 5
- 0
spec/resources/dh-1024.pem Bestand weergeven

@@ -0,0 +1,5 @@
1
+-----BEGIN DH PARAMETERS-----
2
+MIGHAoGBANBrEWPccAuXK3fq8VtE1KeDmY1vk1dJ94ht8UPEqJTdsQEtAS2g7UKm
3
+s49RRb7mG4JOVGWy1FWi32FrZTDUxInOP7k0wz8oqQSNBJWeAZjzETd1bjYutoSx
4
+F1DMUlw650faSS2dSXalOXyRfY+2dqR9sa7FQNlOztYFCrtwXMb7AgEC
5
+-----END DH PARAMETERS-----

+ 15
- 0
spec/resources/rsa-1024.pem Bestand weergeven

@@ -0,0 +1,15 @@
1
+-----BEGIN RSA PRIVATE KEY-----
2
+MIICXQIBAAKBgQC0xBo+MgjnYqvszjUpslonvcQVI1TG7yxlGCWqpvN0a3zdgBpV
3
+lpXv7q/821jUtlLc2BhNohRXuoejc2oiG7IOv7Md204NnoTQbxLo6gehnMyo86il
4
+Q7KNAAW4tam79xNgOfdkkV0d80AfG148j+N6jDZCOoZ3dFwH4a6vcSWRgQIDAQAB
5
+AoGBAJ7j+MVOqbDpdIG0R9qc4M4p6Z9C7RPny7gY35L/KOPeT2VLYtp0gNrjjWHP
6
+VGe002U3tTUYEJWEahFsM5BDk+ASqyzesPD5lWzi6QSO3cIkvNSYLdBezNprcPk4
7
+PEy1pX+IXrRFeDXE/wncovuYP2STF18SSP7YgCMBAAwgeZAhAkEA7xLuNz6Qt7HK
8
+euShzsvmzNUIaoBXa9qiOWoIb7aHa/uK87SwXpy6iV85TdWowD34JPnPiRx6FSPk
9
+4rOXYBq0lQJBAMGQYF/ItKUGYnwj7z2Q7N3/Pz5fTyoqzQI7Nza8aCEabFNzAdMv
10
+nZ2ROyWC/qXZ1osgPuwTBBfu9ty7GH2p4j0CQQCk+jJLCzDAor7waV/Dne+qQAQr
11
+wl8RfXFfH22s8Y+oE5CCtpjS4WLUM1MPBDcMWncnxP/TRUR13CwxyO7YEfW1AkBv
12
+VRqJnUiB7sUwv/54O+Zx3cFDn9BJ4apfES411nJSL/+ElA7FqIqQuZr6fXj4be5f
13
+wWFPqbReC72Dwj1Y8iDFAkAXpo8CtvqtxQYdbIh0Jmdj2xHWppkbBs9dT/qVAOdO
14
+RIA5UKKyyweZc+6ZFbAMeouhHGljcL73zOZt5V4YloT7
15
+-----END RSA PRIVATE KEY-----

+ 8
- 0
spec/resources/secp256r1.pem Bestand weergeven

@@ -0,0 +1,8 @@
1
+-----BEGIN EC PARAMETERS-----
2
+BggqhkjOPQMBBw==
3
+-----END EC PARAMETERS-----
4
+-----BEGIN EC PRIVATE KEY-----
5
+MHcCAQEEIIg6KuMLLVhcR7IIU+joH9npRN5eVYfBQo6pRL56xUCuoAoGCCqGSM49
6
+AwEHoUQDQgAE+h78G/a32+1ICT/euHP0Z5INER9Rh1nJNyn0HUSR0yWCistpoX1K
7
+yCHpVwb0SAqB/6WrwOFKnrKIdI/HX1edGQ==
8
+-----END EC PRIVATE KEY-----

+ 0
- 91
spec/spec_helper.rb Bestand weergeven

@@ -1,91 +0,0 @@
1
-require 'webmock/rspec'
2
-
3
-# This file was generated by the `rspec --init` command. Conventionally, all
4
-# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
-# The generated `.rspec` file contains `--require spec_helper` which will cause this
6
-# file to always be loaded, without a need to explicitly require it in any files.
7
-#
8
-# Given that it is always loaded, you are encouraged to keep this file as
9
-# light-weight as possible. Requiring heavyweight dependencies from this file
10
-# will add to the boot time of your test suite on EVERY test run, even for an
11
-# individual file that may not need all of that loaded. Instead, consider making
12
-# a separate helper file that requires the additional dependencies and performs
13
-# the additional setup, and require it from the spec files that actually need it.
14
-#
15
-# The `.rspec` file also contains a few flags that are not defaults but that
16
-# users commonly want.
17
-#
18
-# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
-RSpec.configure do |config|
20
-  # rspec-expectations config goes here. You can use an alternate
21
-  # assertion/expectation library such as wrong or the stdlib/minitest
22
-  # assertions if you prefer.
23
-  config.expect_with :rspec do |expectations|
24
-    # This option will default to `true` in RSpec 4. It makes the `description`
25
-    # and `failure_message` of custom matchers include text for helper methods
26
-    # defined using `chain`, e.g.:
27
-    # be_bigger_than(2).and_smaller_than(4).description
28
-    #   # => "be bigger than 2 and smaller than 4"
29
-    # ...rather than:
30
-    #   # => "be bigger than 2"
31
-    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
-  end
33
-
34
-  # rspec-mocks config goes here. You can use an alternate test double
35
-  # library (such as bogus or mocha) by changing the `mock_with` option here.
36
-  config.mock_with :rspec do |mocks|
37
-    # Prevents you from mocking or stubbing a method that does not exist on
38
-    # a real object. This is generally recommended, and will default to
39
-    # `true` in RSpec 4.
40
-    mocks.verify_partial_doubles = true
41
-  end
42
-
43
-# The settings below are suggested to provide a good initial experience
44
-# with RSpec, but feel free to customize to your heart's content.
45
-=begin
46
-  # These two settings work together to allow you to limit a spec run
47
-  # to individual examples or groups you care about by tagging them with
48
-  # `:focus` metadata. When nothing is tagged with `:focus`, all examples
49
-  # get run.
50
-  config.filter_run :focus
51
-  config.run_all_when_everything_filtered = true
52
-
53
-  # Limits the available syntax to the non-monkey patched syntax that is recommended.
54
-  # For more details, see:
55
-  #   - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
56
-  #   - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
57
-  #   - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
58
-  config.disable_monkey_patching!
59
-
60
-  # This setting enables warnings. It's recommended, but in some cases may
61
-  # be too noisy due to issues in dependencies.
62
-  config.warnings = true
63
-
64
-  # Many RSpec users commonly either run the entire suite or an individual
65
-  # file, and it's useful to allow more verbose output when running an
66
-  # individual spec file.
67
-  if config.files_to_run.one?
68
-    # Use the documentation formatter for detailed output,
69
-    # unless a formatter has already been configured
70
-    # (e.g. via a command-line flag).
71
-    config.default_formatter = 'doc'
72
-  end
73
-
74
-  # Print the 10 slowest examples and example groups at the
75
-  # end of the spec run, to help surface which specs are running
76
-  # particularly slow.
77
-  config.profile_examples = 10
78
-
79
-  # Run specs in random order to surface order dependencies. If you find an
80
-  # order dependency and want to debug it, you can fix the order by providing
81
-  # the seed, which is printed after each run.
82
-  #     --seed 1234
83
-  config.order = :random
84
-
85
-  # Seed global randomization in this process using the `--seed` CLI option.
86
-  # Setting this allows you to use `--seed` to deterministically reproduce
87
-  # test failures related to randomization by passing the same `--seed` value
88
-  # as the one that triggered the failure.
89
-  Kernel.srand config.seed
90
-=end
91
-end

Laden…
Annuleren
Opslaan