瀏覽代碼

Refactor test checks

master
aeris 2 年之前
父節點
當前提交
e604c11e13

+ 6
- 10
lib/cryptcheck/tls.rb 查看文件

@@ -9,23 +9,19 @@ module CryptCheck
9 9
 
10 10
 		def self.colorize(cipher)
11 11
 			colors = case
12
-						 when /^SSL/ =~ cipher then { color: :white, background: :red }
13
-						 when :TLSv1_2 == cipher then { color: :green }
12
+						 when /^SSL/ =~ cipher then :critical
13
+						 when :TLSv1_2 == cipher then :good
14 14
 					 end
15 15
 			cipher.to_s.colorize colors
16 16
 		end
17 17
 
18 18
 		def self.key_to_s(key)
19 19
 			type_color = case key.type
20
-							 when :ecc then { color: :green }
21
-							 when :dsa then { color: :red }
20
+							 when :ecc then :good
21
+							 when :dh then :warning
22
+							 when :dsa then :critical
22 23
 						 end
23
-			size_color = case key.status
24
-							when :error then { color: :white, background: :red }
25
-							when :warning then { color: :yellow }
26
-							when :success then { color: :green }
27
-						end
28
-			"#{key.type.to_s.upcase.colorize type_color} #{key.size.to_s.colorize size_color} bits"
24
+			"#{key.type.to_s.upcase.colorize type_color} #{key.size.to_s.colorize key.status} bits"
29 25
 		end
30 26
 	end
31 27
 end

+ 72
- 28
lib/cryptcheck/tls/cipher.rb 查看文件

@@ -31,7 +31,7 @@ module CryptCheck
31 31
 					idea:      %w(IDEA),
32 32
 					chacha20:  %w(CHACHA20),
33 33
 
34
-					cbc:       %w(CBC),
34
+					#cbc:       %w(CBC),
35 35
 					gcm:       %w(GCM),
36 36
 					ccm:       %w(CCM)
37 37
 			}
@@ -54,6 +54,19 @@ module CryptCheck
54 54
 				RUBY_EVAL
55 55
 			end
56 56
 
57
+			def self.cbc?(cipher)
58
+				!aead? cipher
59
+			end
60
+			def cbc?
61
+				!aead?
62
+			end
63
+			def aead?(cipher)
64
+				gcm?(cipher) or ccm?(cipher)
65
+			end
66
+			def aead?
67
+				gcm? or ccm?
68
+			end
69
+
57 70
 			def ssl?
58 71
 				sslv2? or sslv3?
59 72
 			end
@@ -66,40 +79,71 @@ module CryptCheck
66 79
 				dhe? or ecdhe?
67 80
 			end
68 81
 
82
+			def sweet32?
83
+				enc = params[:enc]
84
+				return false unless enc # No encryption
85
+				block = enc[2]
86
+				return false unless block # No block encryption
87
+				block <= 64
88
+			end
89
+
90
+			def sweet32?
91
+				enc = params[:enc]
92
+				return false unless enc # No encryption
93
+				block = enc[2]
94
+				return false unless block # No block encryption
95
+				block <= 64
96
+			end
97
+
69 98
 			def colorize
70
-				colors = case self.score
71
-							 when :error then
72
-								 { color: :white, background: :red }
73
-							 when :danger then
74
-								 { color: :red }
75
-							 when :warning then
76
-								 { color: :yellow }
77
-							 when :success then
78
-								 { color: :green }
79
-						 end
80
-				@name.colorize colors
81
-			end
82
-
83
-			def state
84
-				ok = Proc.new { |n| self.send "#{n}?" }
85
-				{
86
-						success: %i(pfs).select { |n| ok.call n },
87
-						warning: %i().select { |n| ok.call n },
88
-						danger:  %i().select { |n| ok.call n },
89
-						error:   %i(dss md5 psk srp anonymous null export des des3 rc2 rc4 idea).select { |n| ok.call n }
90
-				}
99
+				@name.colorize self.score
100
+			end
101
+
102
+			CHECKS = [
103
+					[:dss, Proc.new { |s| s.dss? }, :critical],
104
+					[:anonymous, Proc.new { |s| s.anonymous? }, :critical],
105
+					[:null, Proc.new { |s| s.null? }, :critical],
106
+					[:export, Proc.new { |s| s.export? }, :critical],
107
+					[:des, Proc.new { |s| s.des? }, :critical],
108
+					[:md5, Proc.new { |s| s.md5? }, :critical],
109
+
110
+					[:rc4, Proc.new { |s| s.rc4? }, :error],
111
+					[:sweet32, Proc.new { |s| s.sweet32? }, :error],
112
+
113
+					#[:cbc, Proc.new { |s| s.cbc? }, :warning],
114
+					#[:dhe, Proc.new { |s| s.dhe? }, :warning],
115
+					[:weak_dh, Proc.new do |s|
116
+						dh = s.dh
117
+						next nil unless dh
118
+						status = dh.status
119
+						next status if %i(critical error warning).include? status
120
+						nil
121
+					end ],
122
+					[:no_pfs, Proc.new { |s| not s.pfs? }, :warning],
123
+
124
+					[:pfs, Proc.new { |s| s.pfs? }, :good],
125
+					[:ecdhe, Proc.new { |s| s.ecdhe? }, :good],
126
+					[:aead, Proc.new { |s| s.aead? }, :good],
127
+			]
128
+
129
+			def states
130
+				return @states if @states
131
+				@states = { critical: [], error: [], warning: [], good: [], perfect: [], best: [] }
132
+				CHECKS.each do |name, check, status|
133
+					result = check.call self
134
+					states[status ? status : result] << name if result
135
+				end
136
+				@states
91 137
 			end
92 138
 
93 139
 			def score
94
-				state = self.state
95
-				return :error unless state[:error].empty?
96
-				return :danger unless state[:danger].empty?
97
-				return :warning unless state[:warning].empty?
98
-				return :success unless state[:success].empty?
140
+				%i(critical error warning good perfect best).each do |s|
141
+					return s unless self.states[s].empty?
142
+				end
99 143
 				:none
100 144
 			end
101 145
 
102
-			PRIORITY = { success: 1, none: 2, warning: 3, danger: 4, error: 5 }
146
+			PRIORITY = { good: 1, none: 2, warning: 3, error: 4, critical: 5 }
103 147
 
104 148
 			def self.sort(ciphers)
105 149
 				ciphers.sort do |a, b|

+ 29
- 10
lib/cryptcheck/tls/fixture.rb 查看文件

@@ -1,5 +1,23 @@
1 1
 require 'openssl'
2 2
 
3
+class String
4
+	alias :colorize_old :colorize
5
+
6
+	COLORS = {
7
+			critical: { color: :white, background: :red },
8
+			error: :red,
9
+			warning: :light_red,
10
+			good: :green,
11
+			perfect: :blue,
12
+			best: :magenta,
13
+			unknown: { background: :black }
14
+	}
15
+
16
+	def colorize(state)
17
+		self.colorize_old COLORS[state]
18
+	end
19
+end
20
+
3 21
 class Integer
4 22
 	def humanize
5 23
 		secs = self
@@ -28,9 +46,10 @@ class ::OpenSSL::PKey::EC
28 46
 
29 47
 	def status
30 48
 		case self.size
31
-			when 0...160 then :error
32
-			when 160...256 then :warning
33
-			when 384...::Float::INFINITY then :success
49
+			when 0...160 then :critical
50
+			when 160...192 then :error
51
+			when 192...256 then :warning
52
+			when 384...::Float::INFINITY then :good
34 53
 		end
35 54
 	end
36 55
 end
@@ -50,9 +69,9 @@ class ::OpenSSL::PKey::RSA
50 69
 
51 70
 	def status
52 71
 		case self.size
53
-			when 0...1024 then :error
54
-			when 1024...2048 then :warning
55
-			when 4096...::Float::INFINITY then :success
72
+			when 0...1024 then :critical
73
+			when 1024...2048 then :error
74
+			when 4096...::Float::INFINITY then :good
56 75
 		end
57 76
 	end
58 77
 end
@@ -71,7 +90,7 @@ class ::OpenSSL::PKey::DSA
71 90
 	end
72 91
 
73 92
 	def status
74
-		return :error
93
+		return :critical
75 94
 	end
76 95
 end
77 96
 
@@ -90,9 +109,9 @@ class ::OpenSSL::PKey::DH
90 109
 
91 110
 	def status
92 111
 		case self.size
93
-			when 0...1024 then :error
94
-			when 1024...2048 then :warning
95
-			when 4096...::Float::INFINITY then :success
112
+			when 0...1024 then :critical
113
+			when 1024...2048 then :error
114
+			when 4096...::Float::INFINITY then :good
96 115
 		end
97 116
 	end
98 117
 end

+ 110
- 53
lib/cryptcheck/tls/grade.rb 查看文件

@@ -1,80 +1,137 @@
1 1
 module CryptCheck
2 2
 	module Tls
3 3
 		class Grade
4
-			attr_reader :server, :score, :grade, :error, :danger, :warning, :success
4
+			attr_reader :server, :grade, :states
5 5
 
6 6
 			def initialize(server)
7 7
 				@server = server
8
-				calculate_states
9
-				calculate_grade
8
+				@checks = checks
9
+				@states = calculate_states
10
+				@grade = calculate_grade
10 11
 			end
11 12
 
12 13
 			def display
13
-				color = case self.grade
14
-							when 'A+' then :blue
15
-							when 'A' then :green
16
-							when 'B', 'C' then :yellow
17
-							when 'E', 'F' then :red
18
-							when 'M', 'T' then { color: :white, background: :red }
14
+				color = case @grade
15
+							when 'A', 'A+'
16
+								:best
17
+							when 'B', 'B+'
18
+								:perfect
19
+							when 'C', 'C+'
20
+								nil
21
+							when 'E'
22
+								:warning
23
+							when 'F'
24
+								:error
25
+							when 'G'
26
+								:critical
27
+							when 'M', 'T'
28
+								:unknown
19 29
 						end
20 30
 
21 31
 				Logger.info { "Grade : #{self.grade.colorize color }" }
22 32
 				Logger.info { '' }
23
-				Logger.info { "Errors : #{self.error.join(' ').colorize :red }" } unless self.error.empty?
24
-				Logger.info { "Warnings : #{self.warning.join(' ').colorize :yellow }" } unless self.warning.empty?
25
-				Logger.info { "Best practices : #{self.success.join(' ').colorize :green }" } unless self.success.empty?
33
+				[
34
+						['Critical', :critical],
35
+						['Error', :error],
36
+						['Warning', :warning],
37
+						['Good', :good],
38
+						['Perfect', :perfect],
39
+						['Best', :best],
40
+				].each do |text, color|
41
+					states = @states[color]
42
+					Logger.info { "#{text} : #{states.collect { |s| s.to_s.colorize color }.join ' '}" } unless states.empty?
43
+				end
26 44
 			end
27 45
 
28 46
 			private
29 47
 			def calculate_grade
30
-				@grade = case @score
31
-							 when 0...20 then 'F'
32
-							 when 20...35 then 'E'
33
-							 when 35...50 then 'D'
34
-							 when 50...65 then 'C'
35
-							 when 65...80 then 'B'
36
-							 else 'A'
37
-						 end
38
-
39
-				@grade = [@grade, 'B'].max if !@server.tlsv1_2? or %i(error warning).include? @server.key.status
40
-				@grade = [@grade, 'F'].max unless @error.empty?
41
-				@grade = [@grade, 'F'].max unless @error.empty?
42
-
43
-				@grade = 'M' unless @server.cert_valid
44
-				@grade = 'T' unless @server.cert_trusted
45
-
46
-				@grade = 'A+' if @grade == 'A' and @error.empty? and @warning.empty? and (all_success & @success) == all_success
47
-			end
48
+				case
49
+					when !@states[:critical].empty?
50
+						return 'G'
51
+					when !@states[:error].empty?
52
+						return 'F'
53
+					when !@states[:warning].empty?
54
+						return 'E'
55
+				end
48 56
 
49
-			def calculate_states
50
-				ok = Proc.new { |n| @server.send "#{n}?" }
51
-				state = {
52
-						success: all_success.select { |n| ok.call n },
53
-						warning: all_warning.select { |n| ok.call n },
54
-						danger:  all_danger.select { |n| ok.call n },
55
-						error:   all_error.select { |n| ok.call n }
56
-				}
57
-				@success, @warning, @danger, @error = state[:success], state[:warning], state[:danger], state[:error]
58
-			end
57
+				goods = @checks.select { |c| c.last == :good }.collect &:first
58
+				unless goods.empty?
59
+					return 'D' if @states[:good].empty?
60
+					return 'C' if @states[:good] != goods
61
+				end
59 62
 
60
-			ALL_ERROR = %i(md5_sig md5 anonymous dss null export des des3 rc4)
61
-			def all_error
62
-				ALL_ERROR
63
-			end
63
+				perfects = @checks.select { |c| c.last == :perfect }.collect &:first
64
+				unless perfects.empty?
65
+					return 'C+' if @states[:perfect].empty?
66
+					return 'B' if @states[:perfect] != perfects
67
+				end
64 68
 
65
-			ALL_DANGER = %i()
66
-			def all_danger
67
-				ALL_DANGER
69
+				bests = @checks.select { |c| c.last == :best }.collect &:first
70
+				unless bests.empty?
71
+					return 'B+' if @states[:best].empty?
72
+					return 'A' if @states[:best] != bests
73
+				end
74
+
75
+				'A+'
68 76
 			end
69 77
 
70
-			ALL_WARNING = %i(sha1_sig)
71
-			def all_warning
72
-				ALL_WARNING
78
+			CHECKS = [
79
+					# Keys
80
+					[:dss_sign, Proc.new { |s| s.dss_sig? }, :critical],
81
+					[:weak_key, Proc.new { |s| %i(critical error warning).include? s.key.status } ],
82
+
83
+					# DH
84
+					[:weak_dh, Proc.new { |s| (%i(critical error warning) & s.dh.collect(&:status).uniq).first } ],
85
+
86
+					# Certificates
87
+					[:md2_sign, Proc.new { |s| s.md2_sig? }, :critical],
88
+					[:mdc2_sign, Proc.new { |s| s.mdc2_sig? }, :critical],
89
+					[:md4_sign, Proc.new { |s| s.md4_sig? }, :critical],
90
+					[:md5_sign, Proc.new { |s| s.md5_sig? }, :critical],
91
+					[:sha_sign, Proc.new { |s| s.sha_sig? }, :critical],
92
+
93
+					[:sha1_sign, Proc.new { |s| s.sha1_sig? }, :warning],
94
+
95
+					# Protocols
96
+					[:ssl, Proc.new { |s| s.ssl? }, :critical],
97
+					[:tls12, Proc.new { |s| s.tlsv1_2? }, :good],
98
+					[:tls12_only, Proc.new { |s| s.tlsv1_2_only? }, :perfect],
99
+
100
+					# Ciphers
101
+					[:dss, Proc.new { |s| s.dss? }, :critical],
102
+					[:anonymous, Proc.new { |s| s.anonymous? }, :critical],
103
+					[:null, Proc.new { |s| s.null? }, :critical],
104
+					[:export, Proc.new { |s| s.export? }, :critical],
105
+					[:des, Proc.new { |s| s.des? }, :critical],
106
+					[:md5, Proc.new { |s| s.md5? }, :critical],
107
+
108
+					[:rc4, Proc.new { |s| s.rc4? }, :error],
109
+					[:sweet32, Proc.new { |s| s.sweet32? }, :error],
110
+
111
+					[:no_pfs, Proc.new { |s| not s.pfs_only? }, :warning],
112
+					[:pfs, Proc.new { |s| s.pfs? }, :good],
113
+					[:pfs_only, Proc.new { |s| s.pfs_only? }, :perfect],
114
+					[:ecdhe, Proc.new { |s| s.ecdhe? }, :good],
115
+					[:ecdhe_only, Proc.new { |s| s.ecdhe_only? }, :perfect],
116
+
117
+					[:aead, Proc.new { |s| s.aead_only? }, :good],
118
+					#[:aead_only, Proc.new { |s| s.aead_only? }, :best],
119
+			]
120
+
121
+			def checks
122
+				CHECKS
73 123
 			end
74 124
 
75
-			ALL_SUCCESS = %i(pfs_only)
76
-			def all_success
77
-				ALL_SUCCESS
125
+			def calculate_states
126
+				states = { critical: [], error: [], warning: [], good: [], perfect: [], best: [] }
127
+				@checks.each do |name, check, status|
128
+					result = check.call @server
129
+					if result
130
+						state = states[status ? status : result]
131
+						state << name if state
132
+					end
133
+				end
134
+				states
78 135
 			end
79 136
 		end
80 137
 	end

+ 5
- 2
lib/cryptcheck/tls/https/grade.rb 查看文件

@@ -2,8 +2,11 @@ module CryptCheck
2 2
 	module Tls
3 3
 		module Https
4 4
 			class Grade < Tls::Grade
5
-				def all_success
6
-					super + %i(hsts hsts_long)
5
+				def checks
6
+					super + [
7
+						[:hsts, Proc.new { |s| s.hsts? }, :good],
8
+						[:hsts_long, Proc.new { |s| s.hsts_long? }, :perfect],
9
+					]
7 10
 				end
8 11
 			end
9 12
 		end

+ 2
- 2
lib/cryptcheck/tls/https/server.rb 查看文件

@@ -27,14 +27,14 @@ module CryptCheck
27 27
 							name, value = header.split '='
28 28
 							if name == 'max-age'
29 29
 								@hsts = value.to_i
30
-								Logger.info { "HSTS : #{@hsts.to_s.colorize hsts_long? ? :green : nil}" }
30
+								Logger.info { "HSTS : #{@hsts.to_s.colorize hsts_long? ? :good : nil}" }
31 31
 								return
32 32
 							end
33 33
 						end
34 34
 					rescue
35 35
 					end
36 36
 
37
-					Logger.info { 'No HSTS'.colorize :yellow }
37
+					Logger.info { 'No HSTS'.colorize :warning }
38 38
 					@hsts = nil
39 39
 				end
40 40
 

+ 101
- 27
lib/cryptcheck/tls/server.rb 查看文件

@@ -21,6 +21,8 @@ module CryptCheck
21 21
 			end
22 22
 			class CipherNotAvailable < TLSException
23 23
 			end
24
+			class InappropriateFallback < TLSException
25
+			end
24 26
 			class Timeout < ::StandardError
25 27
 			end
26 28
 			class TLSTimeout < Timeout
@@ -33,7 +35,7 @@ module CryptCheck
33 35
 			def initialize(hostname, family, ip, port)
34 36
 				@hostname, @family, @ip, @port = hostname, family, ip, port
35 37
 				@dh                            = []
36
-				Logger.info { name.colorize :blue }
38
+				Logger.info { name.colorize :perfect }
37 39
 				extract_cert
38 40
 				Logger.info { '' }
39 41
 				Logger.info { "Key : #{Tls.key_to_s self.key}" }
@@ -70,14 +72,48 @@ module CryptCheck
70 72
 				RUBY_EVAL
71 73
 			end
72 74
 
73
-			{
74
-					md2:  %w(md2WithRSAEncryption),
75
-					md5:  %w(md5WithRSAEncryption md5WithRSA),
76
-					sha1: %w(sha1WithRSAEncryption sha1WithRSA dsaWithSHA1 dsaWithSHA1_2 ecdsa_with_SHA1)
77
-			}.each do |name, signature|
75
+			SIGNATURE_ALGORITHMS = {
76
+					'dsaWithSHA'                             => %i(sha1 dss),
77
+					'dsaWithSHA1'                            => %i(sha1 dss),
78
+					'dsaWithSHA1_2'                          => %i(sha1 dss),
79
+					'dsa_with_SHA224'                        => %i(sha2 dss),
80
+					'dsa_with_SHA256'                        => %i(sha2 dss),
81
+
82
+					'mdc2WithRSA'                            => %i(mdc2 rsa),
83
+
84
+					'md2WithRSAEncryption'                   => %i(md2 rsa),
85
+
86
+					'md4WithRSAEncryption'                   => %i(md4, rsa),
87
+
88
+					'md5WithRSA'                             => %i(md5 rsa),
89
+					'md5WithRSAEncryption'                   => %i(md5 rsa),
90
+
91
+					'shaWithRSAEncryption'                   => %i(sha rsa),
92
+					'sha1WithRSA'                            => %i(sha1 rsa),
93
+					'sha1WithRSAEncryption'                  => %i(sha1 rsa),
94
+					'sha224WithRSAEncryption'                => %i(sha2 rsa),
95
+					'sha256WithRSAEncryption'                => %i(sha2 rsa),
96
+					'sha384WithRSAEncryption'                => %i(sha2 rsa),
97
+					'sha512WithRSAEncryption'                => %i(sha2 rsa),
98
+
99
+					'ripemd160WithRSA'                       => %i(ripemd160 rsa),
100
+
101
+					'ecdsa-with-SHA1'                        => %i(sha1 ecc),
102
+					'ecdsa-with-SHA224'                      => %i(sha2 ecc),
103
+					'ecdsa-with-SHA256'                      => %i(sha2 ecc),
104
+					'ecdsa-with-SHA384'                      => %i(sha2 ecc),
105
+					'ecdsa-with-SHA512'                      => %i(sha2 ecc),
106
+
107
+					'id_GostR3411_94_with_GostR3410_2001'    => %i(ghost),
108
+					'id_GostR3411_94_with_GostR3410_94'      => %i(ghost),
109
+					'id_GostR3411_94_with_GostR3410_94_cc'   => %i(ghost),
110
+					'id_GostR3411_94_with_GostR3410_2001_cc' => %i(ghost)
111
+			}
112
+
113
+			%i(md2 mdc2 md4 md5 ripemd160 sha sha1 sha2 rsa dss ecc ghost).each do |name|
78 114
 				class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
79 115
 					def #{name}_sig?
80
-						#{signature}.include? @cert.signature_algorithm
116
+						SIGNATURE_ALGORITHMS[@cert.signature_algorithm].include? :#{name}
81 117
 					end
82 118
 				RUBY_EVAL
83 119
 			end
@@ -106,6 +142,10 @@ module CryptCheck
106 142
 				tls? and !ssl?
107 143
 			end
108 144
 
145
+			def tlsv1_2_only?
146
+				tlsv1_2? and not ssl? and not tlsv1? and not tlsv1_1?
147
+			end
148
+
109 149
 			def pfs?
110 150
 				supported_ciphers.any? { |c| c.pfs? }
111 151
 			end
@@ -114,6 +154,26 @@ module CryptCheck
114 154
 				supported_ciphers.all? { |c| c.pfs? }
115 155
 			end
116 156
 
157
+			def ecdhe?
158
+				supported_ciphers.any? { |c| c.ecdhe? }
159
+			end
160
+
161
+			def ecdhe_only?
162
+				supported_ciphers.all? { |c| c.ecdhe? }
163
+			end
164
+
165
+			def aead?
166
+				supported_ciphers.any? { |c| c.aead? }
167
+			end
168
+
169
+			def aead_only?
170
+				supported_ciphers.all? { |c| c.aead? }
171
+			end
172
+
173
+			def sweet32?
174
+				supported_ciphers.any? { |c| c.sweet32? }
175
+			end
176
+
117 177
 			private
118 178
 			def name
119 179
 				name = "#@ip:#@port"
@@ -152,39 +212,47 @@ module CryptCheck
152 212
 					ssl_socket.connect_nonblock
153 213
 					Logger.trace { "SSL connected to #{name}" }
154 214
 					return block_given? ? block.call(ssl_socket) : nil
155
-				rescue ::IO::WaitReadable
215
+				rescue ::OpenSSL::SSL::SSLErrorWaitReadable
156 216
 					Logger.trace { "Waiting for SSL read to #{name}" }
157 217
 					raise TLSTimeout, "Timeout when TLS connect to #{@ip}:#{@port} (max #{SSL_TIMEOUT.humanize})" unless IO.select [ssl_socket], nil, nil, SSL_TIMEOUT
158 218
 					retry
159
-				rescue ::IO::WaitWritable
219
+				rescue ::OpenSSL::SSL::SSLErrorWaitWritable
160 220
 					Logger.trace { "Waiting for SSL write to #{name}" }
161 221
 					raise TLSTimeout, "Timeout when TLS connect to #{@ip}:#{@port} (max #{SSL_TIMEOUT.humanize})" unless IO.select nil, [ssl_socket], nil, SSL_TIMEOUT
162 222
 					retry
163 223
 				rescue ::OpenSSL::SSL::SSLError => e
164
-					case e
165
-						when /state=SSLv2 read server hello A$/,
166
-								/state=SSLv3 read server hello A: wrong version number$/
224
+					case e.message
225
+						when /state=SSLv.* read server hello A$/
226
+							raise TLSNotAvailableException, e
227
+						when /state=SSLv.* read server hello A: wrong version number$/
167 228
 							raise MethodNotAvailable, e
168 229
 						when /state=error: no ciphers available$/,
169
-								/state=SSLv3 read server hello A: sslv3 alert handshake failure$/
230
+								/state=SSLv.* read server hello A: sslv.* alert handshake failure$/
170 231
 							raise CipherNotAvailable, e
171 232
 					end
172
-				rescue SystemCallError => e
173
-					case e
174
-						when /^Connection reset by peer$/
175
-							raise MethodNotAvailable, e
233
+					raise
234
+				rescue ::SystemCallError => e
235
+					case e.message
236
+						when /^Connection reset by peer - SSL_connect$/
237
+							raise TLSNotAvailableException, e
176 238
 					end
239
+					raise
177 240
 				ensure
178 241
 					ssl_socket.close
179 242
 				end
180 243
 			end
181 244
 
182 245
 			# secp192r1 secp256r1
183
-			SUPPORTED_CURVES = %w(secp160k1 secp160r1 secp160r2 sect163k1 sect163r1 sect163r2 secp192k1 sect193r1 sect193r2 secp224k1 secp224r1 sect233k1 sect233r1 sect239k1 secp256k1 sect283k1 sect283r1 secp384r1 sect409k1 sect409r1 secp521r1 sect571k1 sect571r1)
246
+			SUPPORTED_CURVES = %w(secp160k1 secp160r1 secp160r2 sect163k1
247
+				sect163r1 sect163r2 secp192k1 sect193r1 sect193r2 secp224k1
248
+				secp224r1 sect233k1 sect233r1 sect239k1 secp256k1 sect283k1
249
+				sect283r1 secp384r1 sect409k1 sect409r1 secp521r1 sect571k1
250
+				sect571r1)
184 251
 
185
-			def ssl_client(method, ciphers = nil, curves = nil, &block)
186
-				ssl_context         = ::OpenSSL::SSL::SSLContext.new method
187
-				ssl_context.ciphers = ciphers.join ':' if ciphers
252
+			def ssl_client(method, ciphers = nil, curves = nil, fallback: false, &block)
253
+				ssl_context = ::OpenSSL::SSL::SSLContext.new method
254
+				ssl_context.enable_fallback_scsv if fallback
255
+				ssl_context.ciphers     = ciphers.join ':' if ciphers
188 256
 
189 257
 				ssl_context.ecdh_curves = curves.join ':' if curves
190 258
 				#ssl_context.ecdh_auto = false
@@ -197,9 +265,6 @@ module CryptCheck
197 265
 						return block_given? ? block.call(ssl_socket) : nil
198 266
 					end
199 267
 				end
200
-
201
-				Logger.debug { "No SSL available on #{name}" }
202
-				raise CipherNotAvailable
203 268
 			end
204 269
 
205 270
 			def extract_cert
@@ -209,7 +274,8 @@ module CryptCheck
209 274
 						@cert, @chain = ssl_client(method) { |s| [s.peer_cert, s.peer_cert_chain] }
210 275
 						Logger.debug { "Certificate #{@cert.subject}" }
211 276
 						break
212
-					rescue TLSException
277
+					rescue TLSTimeout, ::SystemCallError
278
+						raise
213 279
 					end
214 280
 				end
215 281
 				raise TLSNotAvailableException unless @cert
@@ -245,8 +311,16 @@ module CryptCheck
245 311
 				dh = ssl_client(method, [cipher], curves) { |s| s.tmp_key }
246 312
 				@dh << dh if dh
247 313
 				cipher = Cipher.new method, cipher, dh
248
-				dh     = dh ? " (#{'DH'.colorize :green} : #{Tls.key_to_s dh})" : ''
249
-				Logger.info { "#{Tls.colorize method} / #{cipher.colorize} : Supported#{dh}" }
314
+				dh     = dh ? " (#{'PFS'.colorize :good} : #{Tls.key_to_s dh})" : ''
315
+
316
+				states = cipher.states
317
+				text   = %i(critical error warning good perfect best).collect do |s|
318
+					states[s].collect { |t| t.to_s.colorize s }.join ' '
319
+				end.reject &:empty?
320
+				text   = text.join ' '
321
+
322
+				Logger.info { "#{Tls.colorize method} / #{cipher.colorize}#{dh} [#{text}]" }
323
+
250 324
 				cipher
251 325
 			rescue => e
252 326
 				cipher = Cipher.new method, cipher

+ 4
- 2
lib/cryptcheck/tls/xmpp/grade.rb 查看文件

@@ -2,8 +2,10 @@ module CryptCheck
2 2
 	module Tls
3 3
 		module Xmpp
4 4
 			class Grade < Tls::Grade
5
-				def all_success
6
-					super + %i(required)
5
+				def checks
6
+					super + [
7
+							[:required, Proc.new { |s| s.required? }, :good],
8
+					]
7 9
 				end
8 10
 			end
9 11
 		end

+ 1
- 1
lib/cryptcheck/tls/xmpp/server.rb 查看文件

@@ -19,7 +19,7 @@ module CryptCheck
19 19
 									 end unless port
20 20
 					super hostname, family, ip, port
21 21
 					Logger.info { '' }
22
-					Logger.info { self.required? ? 'Required'.colorize(:green) : 'Not required'.colorize(:yellow) }
22
+					Logger.info { self.required? ? 'Required'.colorize(:good) : 'Not required'.colorize(:warning) }
23 23
 				end
24 24
 
25 25
 				def ssl_connect(socket, context, method, &block)

Loading…
取消
儲存