sslsmart 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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