sslsmart 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,177 @@
1
+ # Gursev Singh Kalra @ Foundstone(McAfee)
2
+ # Please see LICENSE.txt for licensing information
3
+
4
+ require 'singleton'
5
+ require 'sslsmartlib'
6
+ require 'sslsmartlog'
7
+
8
+ $log = SSLSmartLog.instance
9
+
10
+ class CipherSuite
11
+ def initialize(version, name, bits)
12
+ @test = true
13
+ @version = version
14
+ @name = name
15
+ @bits = bits
16
+ end
17
+
18
+ def test=(val)
19
+ case val
20
+ when true, false
21
+ @test = val
22
+ end
23
+ end
24
+
25
+ def test?
26
+ @test
27
+ end
28
+
29
+ def to_s
30
+ "test = #{@test}\tversion = #{@version}\tbits = #{@bits}\tname = #{@name}"
31
+ end
32
+
33
+
34
+ # Sort with version, bits and then name
35
+ def <=>(obj)
36
+ if((version <=>obj.version) == 0)
37
+ if((bitcomp = (obj.bits.to_i <=> bits.to_i)) == 0)
38
+ return name <=> obj.name
39
+ else
40
+ return bitcomp
41
+ end
42
+ else
43
+ version <=> obj.version
44
+ end
45
+ end
46
+
47
+ def +(cs)
48
+ @bits = "--"
49
+ @name = @name + ":" + cs.name
50
+ self
51
+ end
52
+
53
+ attr_reader :version, :name, :bits, :test
54
+ end
55
+
56
+ class SSLSmartConfig
57
+ SCAN_TYPES = ["Connect", "Content"]
58
+ SCAN_MODES = ["Test Individual Ciphers", "SSL Version Check (Faster)"]
59
+ include Singleton
60
+
61
+ def initialize
62
+ @urls = []
63
+ @cipher_suites = []
64
+ @sslv2 = true
65
+ @sslv3 = true
66
+ @tlsv1 = true
67
+ @scan_type = SCAN_TYPES[1] # Content or Connect
68
+ @scan_mode = SCAN_MODES[0] # Cipher or version check
69
+ @proxy_add = nil
70
+ @proxy_port = nil
71
+ @rootcert_path = File.join(File.expand_path("."), "rootcerts.pem")
72
+ @filter = nil
73
+ end
74
+
75
+ attr_reader :urls, :cipher_suites, :sslv2, :sslv3, :tlsv1, :scan_type, :proxy_add, :proxy_port, :rootcert_path, :filter, :scan_mode
76
+ attr_writer :cipher_suites
77
+
78
+ def update_test_status(cs)
79
+ case cs.version
80
+ when "SSLv2"
81
+ cs.test = @sslv2
82
+ when "SSLv3"
83
+ cs.test = @sslv3
84
+ when "TLSv1"
85
+ cs.test = @tlsv1
86
+ end
87
+ end
88
+
89
+ def update_version_test_status()
90
+ @cipher_suites.each do |cs|
91
+ update_test_status(cs)
92
+ end
93
+ end
94
+
95
+ def index_for_version(version)
96
+ @cipher_suites.each_with_index do |x, index|
97
+ return index if(x.version == version)
98
+ end
99
+ nil
100
+ end
101
+
102
+ def update_config(config_hash)
103
+ return unless(config_hash.class == Hash)
104
+ $log.info("Updating configuration with #{config_hash.inspect}")
105
+ config_hash.each do |key, value|
106
+ case key
107
+ when :urls
108
+ @urls = value
109
+ when :cipher_suites
110
+ @cipher_suites = value
111
+ when :sslv2
112
+ @sslv2 = value
113
+ update_version_test_status()
114
+ when :sslv3
115
+ @sslv3 = value
116
+ update_version_test_status()
117
+ when :tlsv1
118
+ @tlsv1 = value
119
+ update_version_test_status()
120
+ when :scan_type
121
+ @scan_type = SCAN_TYPES[value] if(SCAN_TYPES[value])
122
+ when :proxy_add
123
+ @proxy_add = value
124
+ when :proxy_port
125
+ @proxy_port = value
126
+ when :rootcert_path
127
+ @rootcert_path = value
128
+ when :filter
129
+ begin
130
+ cipher_suites = OpenSSL::SSL.get_mod_cipher_suites(value)
131
+ rescue => ex
132
+ raise ex
133
+ end
134
+ @filter = value
135
+ @cipher_suites.clear
136
+
137
+ case @scan_mode
138
+ when SCAN_MODES[0]
139
+ cipher_suites.each do |x|
140
+ cs = CipherSuite.new(x[1], x[0], x[2].to_s)
141
+ update_test_status(cs)
142
+ @cipher_suites << cs
143
+ end
144
+ when SCAN_MODES[1]
145
+ cipher_suites.each do |xmode|
146
+ newsuite = CipherSuite.new(xmode[1], xmode[0], xmode[2].to_s)
147
+ idx = index_for_version(newsuite.version)
148
+ if(idx)
149
+ @cipher_suites[idx] = @cipher_suites[idx] + newsuite
150
+ else
151
+ update_test_status(newsuite)
152
+ @cipher_suites << newsuite
153
+ end
154
+
155
+ end
156
+ end
157
+
158
+ @cipher_suites.sort!
159
+ when :scan_mode
160
+ @scan_mode = SCAN_MODES[value] if(SCAN_MODES[value])
161
+ end
162
+ end
163
+ end
164
+
165
+
166
+ def count_to_test()
167
+ count = 0
168
+ cipher_suites.each do |x|
169
+ count += 1 if(x.test?)
170
+ end
171
+ count
172
+ end
173
+
174
+ end
175
+
176
+ CONF = SSLSmartConfig.instance
177
+ CONF.update_config({:filter => "DEFAULT"})
@@ -0,0 +1,351 @@
1
+ # Gursev Singh Kalra @ Foundstone(McAfee)
2
+ # Please see LICENSE.txt for licensing information
3
+
4
+ require 'rubygems'
5
+ require 'sslsmartlib'
6
+ require 'sslsmartconfig'
7
+ require 'sslsmartdb'
8
+ require 'uri'
9
+ require 'cgi'
10
+ require 'builder'
11
+ require 'socket'
12
+ require 'sslsmartlog'
13
+
14
+ $log = SSLSmartLog.instance
15
+ $conf = SSLSmartConfig.instance
16
+
17
+ MAX_THREADS = 3
18
+ MAX_THREAD_TIME = 10
19
+ class SSLSmartController < SSLSmartDB
20
+ def initialize()
21
+ super()
22
+ end
23
+
24
+ def start_test()
25
+ $log.debug("Starting SSLSmart Test")
26
+ begin
27
+ thr = []
28
+ $conf.urls.each_with_index do |url, url_index|
29
+ $log.info("Starting Test for #{url}")
30
+ purl = URI.parse(url)
31
+ path_query = (purl.query == nil || purl.query == "") ? "#{purl.path}" : "#{purl.path}?#{purl.query}"
32
+ add_url(url)
33
+
34
+ begin
35
+ Socket.gethostbyname(purl.host) # An exception is raised if this fails and is logged
36
+ rq = Net::HTTP.new(purl.host, purl.port, $conf.proxy_add, $conf.proxy_port)
37
+ rq.use_ssl = true
38
+ rq.disable_validations
39
+ rq.get("#{path_query}")
40
+ rescue SocketError => ex
41
+ $log.error("#{ex.class}\t#{purl.host}\t#{ex.message}")
42
+ next
43
+ rescue => ex
44
+ $log.error("#{ex.class}\t#{purl.host}\t#{ex.message}")
45
+ next
46
+ end
47
+
48
+ rq = Net::HTTP.new(purl.host, purl.port, $conf.proxy_add, $conf.proxy_port)
49
+ cert_details = rq.get_cert_details
50
+ $log.debug("Certificate Retrieved for #{purl.host}:#{purl.port}")
51
+ rq = Net::HTTP.new(purl.host, purl.port, $conf.proxy_add, $conf.proxy_port)
52
+ cert_validity = rq.verify_cert($conf.rootcert_path)
53
+ $log.debug("Certificate Vefified for #{purl.host}:#{purl.port}")
54
+ add_cert(url, cert_details, cert_validity)
55
+ yield url, url_index, nil if(block_given?)
56
+
57
+ #rq = Net::HTTP.new(purl.host, purl.port, $conf.proxy_add, $conf.proxy_port)
58
+
59
+ $conf.cipher_suites.each_with_index do |cipher_suite, suite_index|
60
+ $log.debug("Starting Threads")
61
+ thr << Thread.new do
62
+ next unless(cipher_suite.test?)
63
+ $log.debug("Testing #{url} with #{cipher_suite}")
64
+ rq = Net::HTTP.new(purl.host, purl.port, $conf.proxy_add, $conf.proxy_port)
65
+ response = rq.verify_ssl_config(cipher_suite.version, cipher_suite.name, SSLSmartConfig::SCAN_TYPES.index($conf.scan_type), "#{path_query}")
66
+ add_cipher_response(url, suite_index, response)
67
+ yield url, url_index, suite_index if(block_given?) #"#{cipher_suite.version}\t#{cipher_suite.bits}\t#{cipher_suite.name}\t\t#{response.status}"
68
+ end #End of thread
69
+
70
+ t1 = Time.now
71
+ if(thr.length >= MAX_THREADS)
72
+ thr.each do |x|
73
+ if(Time.now - t1 > MAX_THREAD_TIME)
74
+ x.terminate
75
+ $log.warn "Thread killed"
76
+ end
77
+ x.join
78
+ end
79
+ thr.clear
80
+ end
81
+
82
+ end
83
+ $log.debug("Ending Test for #{url}")
84
+ end
85
+
86
+ rescue => ex
87
+ $log.fatal(ex.message)
88
+ $log.fatal(ex.backtrace)
89
+ raise
90
+ end
91
+ end
92
+
93
+ # def show_results()
94
+ # $conf.urls.each do |url|
95
+ # puts "#{url}"
96
+ # puts "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
97
+ # puts get_cert(url.to_sym).data.to_text
98
+ # puts cert_valid?(url.to_sym).status
99
+ # $conf.cipher_suites.each_with_index do |cipher_suite, suite_index|
100
+ # next unless(cipher_suite.test?)
101
+ # print "#{cipher_suite}\t#{suite_index}\t"
102
+ # puts "#{get_response(url, suite_index).status}" if(get_response(url, suite_index))
103
+ # end
104
+ # end
105
+ #
106
+ # end
107
+
108
+
109
+ def create_report(type)
110
+ case type
111
+ when :text, :textv
112
+ return create_text_report(type)
113
+ when :xml, :xmlv
114
+ return create_xml_report(type)
115
+ when :html, :htmlv
116
+ return create_html_report(type)
117
+ end
118
+
119
+ end
120
+
121
+ #WORKING GET_TEXT_REPORT FUNCTION
122
+ def create_text_report(type)
123
+ $log.debug("Creating text report")
124
+ return nil unless(type == :text || type == :textv)
125
+ tr = ""
126
+ tr << %q{
127
+ ___ ___ _ ___ _ ___ _ _
128
+ / __/ __| | / __|_ __ __ _ _ _| |_ | _ \___ ____ _| | |_ ___
129
+ \__ \__ \ |__\__ \ ' \/ _` | '_| _| | / -_|_-< || | | _(_-<
130
+ |___/___/____|___/_|_|_\__,_|_| \__| |_|_\___/__/\_,_|_|\__/__/
131
+
132
+ }
133
+ $conf.urls.each do |url|
134
+ next if(self.get_progress(url) == 0)
135
+ tr << "\n#{url}\n"
136
+ tr << "-"*80
137
+ if(results = get_all_cipher_results(url))
138
+ results.each_with_index do |result, suite_index|
139
+ next unless(result)
140
+ case result.status
141
+ when true
142
+ tr << "\n[+] Accepted%7s %-25s%5s bits %-s" % [$conf.cipher_suites[suite_index].version, $conf.cipher_suites[suite_index].name, $conf.cipher_suites[suite_index].bits, get_response_code(url, suite_index)]
143
+ if(type == :textv)
144
+ resp = get_text_response(url, suite_index)
145
+ tr << "\n%s\n" % [resp] if(resp)
146
+ end
147
+ when false
148
+ tr << "\n[-] Rejected%7s %-25s%5s bits %-s" % [$conf.cipher_suites[suite_index].version, $conf.cipher_suites[suite_index].name, $conf.cipher_suites[suite_index].bits, get_response_code(url, suite_index)]
149
+ end
150
+ end
151
+ end
152
+
153
+ if(cert_validity = cert_valid?(url))
154
+ case cert_validity.status
155
+ when true
156
+ tr << "\n\n[+] Valid Digital Certificate\n"
157
+ when false
158
+ tr << "\n\n[-] Invalid Digital Certificate. "
159
+ tr << "#{cert_validity.data.message}\n" if(cert_validity.data)
160
+ end
161
+ end
162
+ cert = get_cert(url)
163
+ if(type == :textv)
164
+ tr << cert.data.to_text if(cert && cert.data)
165
+ else
166
+ if(cert && cert.data)
167
+ tr << "\t%-20s\t:%s" % ["Certificate Subject", cert.data.subject]
168
+ tr << "\n\t%-20s\t:%s" % ["Certificate Issuer", cert.data.issuer]
169
+ tr << "\n\t%-20s\t:%s" % ["Valid Not Before", cert.data.not_before]
170
+ tr << "\n\t%-20s\t:%s" % ["Valid Not After", cert.data.not_after]
171
+ end
172
+ end
173
+ tr << "\n\n"
174
+ end
175
+ tr << "\n\n"
176
+ tr
177
+ end
178
+
179
+ #WORKING GET_TEXT_REPORT FUNCTION
180
+ def create_html_report(type)
181
+ $log.debug("Creating html report")
182
+ return nil unless(type == :html || type == :htmlv)
183
+ tr = ""
184
+ tr << "<html><head><title>SSLSmart Results</title></head>\n<body>"
185
+ tr << "\n<h1 align='center'>SSLSmart Results</h1>"
186
+ $conf.urls.each do |url|
187
+ next if(self.get_progress(url) == 0)
188
+ tr << "\n"
189
+ tr << '<table width="80%" border="1" cellpadding="0" cellspacing="0">'
190
+ tr << "\n<tr align='center'><td colspan='5' bgcolor=#A4A4A4><h3>#{url}</h3></td></tr>"
191
+ if(results = get_all_cipher_results(url))
192
+ tr << "<tr bgcolor=#BDBDBD>
193
+ <td><b>Supported?</b></td>
194
+ <td><b>Version</b></td>
195
+ <td><b>Cipher Suite</></td>
196
+ <td><b>Bits</b></td>
197
+ <td><b>Response Code</b></td>
198
+ </tr>
199
+ " if(results.length > 0)
200
+ results.each_with_index do |result, suite_index|
201
+ next unless(result)
202
+ case result.status
203
+ when true
204
+ tr << "<tr >
205
+ <td>Yes</td>
206
+ <td>#{$conf.cipher_suites[suite_index].version}</td>
207
+ <td>#{$conf.cipher_suites[suite_index].name}</td>
208
+ <td>#{$conf.cipher_suites[suite_index].bits}</td>
209
+ <td>#{get_response_code(url, suite_index)}</td>
210
+ </tr>"
211
+ if(type == :htmlv)
212
+ resp = get_text_response(url, suite_index)
213
+ if(resp)
214
+ resp = CGI.escapeHTML(resp)
215
+ resp = resp.gsub(/[\n\r]/,'<br/>')
216
+ tr << "\n<tr><td colspan='5'><font size='2'>#{resp}</font></td></tr>"
217
+ end
218
+ end
219
+ when false
220
+ tr << "<tr>
221
+ <td>No</td>
222
+ <td>#{$conf.cipher_suites[suite_index].version}</td>
223
+ <td>#{$conf.cipher_suites[suite_index].name}</td>
224
+ <td>#{$conf.cipher_suites[suite_index].bits}</td>
225
+ <td>#{get_response_code(url, suite_index)}</td>
226
+ </tr>"
227
+ end
228
+ end
229
+ end
230
+
231
+ cert = get_cert(url)
232
+ validity = ""
233
+ if(cert && cert.data)
234
+ if(cert_validity = cert_valid?(url))
235
+ case cert_validity.status
236
+ when true
237
+ validity << "Valid Digital Certificate"
238
+ when false
239
+ validity << "Invalid Digital Certificate.&nbsp;"
240
+ validity << "#{cert_validity.data.message}\n" if(cert_validity.data)
241
+ end
242
+ end
243
+
244
+ tr << "<tr><td colspan='5' align='center' bgcolor=#BDBDBD><b>Certificate Details</b></td></tr>\n"
245
+ tr << "<tr><td colspan='2'><b>Validity</b></td><td colspan='3'>#{validity} </td></tr>\n"
246
+ tr << "<tr><td colspan='2'><b>Subject </b></td><td colspan='3'>#{cert.data.subject} </td></tr>\n"
247
+ tr << "<tr><td colspan='2'><b>Issuer </b></td><td colspan='3'>#{cert.data.issuer} </td></tr>\n"
248
+ tr << "<tr><td colspan='2'><b>Valid Not before </b></td><td colspan='3'>#{cert.data.not_before} </td></tr>\n"
249
+ tr << "<tr><td colspan='2'><b>Valid Not After </b></td><td colspan='3'>#{cert.data.not_after} </td></tr>\n"
250
+ if(type == :htmlv)
251
+ tr << "<tr><td colspan='2'><b>Version </b></td><td colspan='3'>#{cert.data.version}</td></tr>\n"
252
+ tr << "<tr><td colspan='2'><b>Serial Number </b></td><td colspan='3'>#{cert.data.serial} </td></tr>\n"
253
+ tr << "<tr><td colspan='2'><b>Signature Algorithm</b></td><td colspan='3'>#{cert.data.signature_algorithm} </td></tr>\n"
254
+ cert.data.extensions.each do |extn|
255
+ extns = extn.to_s.split("=")
256
+ tr << "<tr><td colspan='2'><b>#{extns[0]}</b></td><td colspan='3'>#{extns[1]} </td></tr>\n"
257
+ end
258
+ tr << "<tr><td colspan='2'><b>Public Key</b></td><td colspan='3'>#{cert.data.public_key.to_text.gsub(/\n/,'<br/>')} </td></tr>\n"
259
+ end
260
+ end
261
+
262
+ tr << "</table><br/><br/>\n"
263
+
264
+ end
265
+ tr << "</body>\n</html>"
266
+ tr
267
+ end
268
+
269
+
270
+ def create_xml_report(type)
271
+ $log.debug("Creating XML report")
272
+ return nil unless(type == :xml || type == :xmlv)
273
+ tr = ""
274
+ xm = Builder::XmlMarkup.new(:target => tr, :indent => 2)
275
+ xm.instruct!
276
+
277
+ xm.SSLSmart {
278
+ $conf.urls.each do |url|
279
+ next if(self.get_progress(url) == 0)
280
+
281
+ xm.url("value" => url) {
282
+ if(results = get_all_cipher_results(url))
283
+ results.each_with_index do |result, suite_index|
284
+ next unless(result)
285
+
286
+ if(result.status == true || result.status == false)
287
+ xm.cipher_suite {
288
+ xm.version($conf.cipher_suites[suite_index].version)
289
+ xm.name($conf.cipher_suites[suite_index].name)
290
+ xm.bits($conf.cipher_suites[suite_index].bits)
291
+ xm.supported(result.status)
292
+ xm.response_code(get_response_code(url, suite_index))
293
+ if(type == :xmlv)
294
+ xm.full_response{
295
+ xm.cdata!(CGI.escapeHTML(get_text_response(url, suite_index))) if(get_text_response(url, suite_index))
296
+ }
297
+ end
298
+ }
299
+ end
300
+ end
301
+ end # END OF if(results = get_all_cipher_results(url))
302
+
303
+ cert = get_cert(url)
304
+ validity_msg = ""
305
+ validity = ""
306
+ if(cert && cert.data)
307
+ if(cert_validity = cert_valid?(url))
308
+ validity = cert_validity.status
309
+ case cert_validity.status
310
+ when true
311
+ validity_msg << "Valid Digital Certificate"
312
+ when false
313
+ validity_msg << "Invalid Digital Certificate.&nbsp;"
314
+ validity_msg << "#{cert_validity.data.message}\n" if(cert_validity.data)
315
+ end
316
+ end
317
+
318
+ xm.certificate {
319
+ xm.validity(validity)
320
+ xm.validity_msg(validity_msg)
321
+ xm.subject(cert.data.subject)
322
+ xm.issuer(cert.data.issuer)
323
+ xm.not_before(cert.data.not_before)
324
+ xm.not_after(cert.data.not_after)
325
+ if(type == :xmlv)
326
+ xm.version(cert.data.version)
327
+ xm.serial(cert.data.serial)
328
+ xm.signature_algorithm(cert.data.signature_algorithm)
329
+ if(cert.data.extensions)
330
+ xm.x509extensions {
331
+ cert.data.extensions.each do |extn|
332
+ extns = extn.to_s.split("=", 2)
333
+ xm.extension {
334
+ xm.name(extns[0])
335
+ xm.value(extns[1])
336
+ }
337
+ end
338
+ }
339
+ end
340
+ xm.public_key{
341
+ xm.cdata!(cert.data.public_key.to_text)
342
+ }
343
+ end
344
+ }
345
+ end
346
+ }
347
+ end # END OF $conf.urls.each do |url|
348
+ }
349
+ tr
350
+ end
351
+ end