ssl_scan 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,241 @@
1
+ require 'ssl_scan/socket'
2
+ require 'ssl_scan/result'
3
+
4
+ module SSLScan
5
+
6
+ class Scanner
7
+
8
+ attr_accessor :context
9
+ attr_accessor :host
10
+ attr_accessor :port
11
+ attr_accessor :timeout
12
+
13
+ attr_reader :supported_versions
14
+ attr_reader :peer_supported_versions
15
+ attr_reader :sslv2
16
+
17
+ # Initializes the scanner object
18
+ # @param host [String] IP address or hostname to scan
19
+ # @param port [Fixnum] Port number to scan, default: 443
20
+ # @param timeout [Fixnum] Timeout for connections, in seconds. default: 5
21
+ # @raise [StandardError] Raised when the configuration is invalid
22
+ def initialize(host,port = 443,context = {},timeout=5)
23
+ @host = host
24
+ @port = port
25
+ @timeout = timeout
26
+ @context = context
27
+ if check_opensslv2 == true
28
+ @supported_versions = [:SSLv2, :SSLv3, :TLSv1]
29
+ @sslv2 = true
30
+ else
31
+ @supported_versions = [:SSLv3, :TLSv1]
32
+ @sslv2 = false
33
+ end
34
+ @peer_supported_versions = []
35
+ raise StandardError, "The scanner configuration is invalid" unless valid?
36
+ end
37
+
38
+ # Checks whether the scanner option has a valid configuration
39
+ # @return [Boolean] True or False, the configuration is valid.
40
+ def valid?
41
+ begin
42
+ @host = SSLScan::Socket.getaddress(@host, true)
43
+ rescue
44
+ return false
45
+ end
46
+ return false unless @port.kind_of? Fixnum
47
+ return false unless @port >= 0 and @port <= 65535
48
+ return false unless @timeout.kind_of? Fixnum
49
+ return true
50
+ end
51
+
52
+ # Initiate the Scan against the target. Will test each cipher one at a time.
53
+ # @return [Result] object containing the details of the scan
54
+ def scan(&block)
55
+ scan_result = SSLScan::Result.new
56
+ scan_result.openssl_sslv2 = sslv2
57
+ # If we can't get any SSL connection, then don't bother testing
58
+ # individual ciphers.
59
+ if test_ssl == :rejected and test_tls == :rejected
60
+ return scan_result
61
+ end
62
+
63
+ @supported_versions.each do |ssl_version|
64
+ sslctx = OpenSSL::SSL::SSLContext.new(ssl_version)
65
+ sslctx.ciphers.each do |cipher_name, ssl_ver, key_length, alg_length|
66
+ status = test_cipher(ssl_version, cipher_name)
67
+ scan_result.add_cipher(ssl_version, cipher_name, key_length, status)
68
+ if status == :accepted and scan_result.cert.nil?
69
+ scan_result.cert = get_cert(ssl_version, cipher_name)
70
+ end
71
+
72
+ if block_given?
73
+ yield(ssl_version, cipher_name, key_length, status, scan_result.cert)
74
+ end
75
+
76
+ end
77
+ end
78
+ @peer_supported_versions = [].tap do |psv|
79
+ psv << :SSLv2 if scan_result.supports_sslv2?
80
+ psv << :SSLv3 if scan_result.supports_sslv3?
81
+ psv << :TLSv1 if scan_result.supports_tlsv1?
82
+ end
83
+ scan_result
84
+ end
85
+
86
+
87
+ def get_preferred_ciphers
88
+ ssl_versions = {}.tap do |v|
89
+ @supported_versions.each { |sv| v[sv] = [] }
90
+ end
91
+
92
+ ssl_versions.keys.each do |ssl_version|
93
+ begin
94
+ scan_client = SSLScan::Socket::Tcp.create(
95
+ 'Context' => @context,
96
+ 'PeerHost' => @host,
97
+ 'PeerPort' => @port,
98
+ 'SSL' => true,
99
+ 'SSLVersion' => ssl_version,
100
+ 'Timeout' => @timeout
101
+ )
102
+ ssl_versions[ssl_version] = scan_client.cipher
103
+ rescue => ex
104
+ ssl_versions.delete(ssl_version)
105
+ end
106
+ end
107
+ ssl_versions
108
+ end
109
+
110
+ def test_ssl
111
+ begin
112
+ scan_client = SSLScan::Socket::Tcp.create(
113
+ 'Context' => @context,
114
+ 'PeerHost' => @host,
115
+ 'PeerPort' => @port,
116
+ 'SSL' => true,
117
+ 'SSLVersion' => :SSLv23,
118
+ 'Timeout' => @timeout
119
+ )
120
+ rescue ::Exception => e
121
+ return :rejected
122
+ ensure
123
+ if scan_client
124
+ scan_client.close
125
+ end
126
+ end
127
+ return :accepted
128
+ end
129
+
130
+ def test_tls
131
+ begin
132
+ scan_client = SSLScan::Socket::Tcp.create(
133
+ 'Context' => @context,
134
+ 'PeerHost' => @host,
135
+ 'PeerPort' => @port,
136
+ 'SSL' => true,
137
+ 'SSLVersion' => :TLSv1,
138
+ 'Timeout' => @timeout
139
+ )
140
+ rescue ::Exception => e
141
+ return :rejected
142
+ ensure
143
+ if scan_client
144
+ scan_client.close
145
+ end
146
+ end
147
+ return :accepted
148
+ end
149
+
150
+ # Tests the specified SSL Version and Cipher against the configured target
151
+ # @param ssl_version [Symbol] The SSL version to use (:SSLv2, :SSLv3, :TLSv1)
152
+ # @param cipher [String] The SSL Cipher to use
153
+ # @return [Symbol] Either :accepted or :rejected
154
+ def test_cipher(ssl_version, cipher)
155
+ validate_params(ssl_version,cipher)
156
+ begin
157
+ scan_client = SSLScan::Socket::Tcp.create(
158
+ 'Context' => @context,
159
+ 'PeerHost' => @host,
160
+ 'PeerPort' => @port,
161
+ 'SSL' => true,
162
+ 'SSLVersion' => ssl_version,
163
+ 'SSLCipher' => cipher,
164
+ 'Timeout' => @timeout
165
+ )
166
+ rescue ::Exception => e
167
+ return :rejected
168
+ ensure
169
+ if scan_client
170
+ scan_client.close
171
+ end
172
+ end
173
+
174
+ return :accepted
175
+ end
176
+
177
+ # Retrieve the X509 Cert from the target service,
178
+ # @param ssl_version [Symbol] The SSL version to use (:SSLv2, :SSLv3, :TLSv1)
179
+ # @param cipher [String] The SSL Cipher to use
180
+ # @return [OpenSSL::X509::Certificate] if the certificate was retrieved
181
+ # @return [Nil] if the cert couldn't be retrieved
182
+ def get_cert(ssl_version, cipher)
183
+ validate_params(ssl_version, cipher)
184
+ begin
185
+ scan_client = SSLScan::Socket::Tcp.create(
186
+ 'PeerHost' => @host,
187
+ 'PeerPort' => @port,
188
+ 'SSL' => true,
189
+ 'SSLVersion' => ssl_version,
190
+ 'SSLCipher' => cipher,
191
+ 'Timeout' => @timeout
192
+ )
193
+ cert = scan_client.peer_cert
194
+ if cert.kind_of? OpenSSL::X509::Certificate
195
+ return cert
196
+ else
197
+ return nil
198
+ end
199
+ rescue ::Exception => e
200
+ return nil
201
+ ensure
202
+ if scan_client
203
+ scan_client.close
204
+ end
205
+ end
206
+ end
207
+
208
+
209
+ protected
210
+
211
+ # Validates that the SSL Version and Cipher are valid both seperately and
212
+ # together as part of an SSL Context.
213
+ # @param ssl_version [Symbol] The SSL version to use (:SSLv2, :SSLv3, :TLSv1)
214
+ # @param cipher [String] The SSL Cipher to use
215
+ # @raise [StandardError] If an invalid or unsupported SSL Version was supplied
216
+ # @raise [StandardError] If the cipher is not valid for that version of SSL
217
+ def validate_params(ssl_version, cipher)
218
+ raise StandardError, "The scanner configuration is invalid" unless valid?
219
+ unless @supported_versions.include? ssl_version
220
+ raise StandardError, "SSL Version must be one of: #{@supported_versions.to_s}"
221
+ end
222
+ if ssl_version == :SSLv2 and sslv2 == false
223
+ raise StandardError, "Your OS hates freedom! Your OpenSSL libs are compiled without SSLv2 support!"
224
+ else
225
+ unless OpenSSL::SSL::SSLContext.new(ssl_version).ciphers.flatten.include? cipher
226
+ raise StandardError, "Must be a valid SSL Cipher for #{ssl_version}!"
227
+ end
228
+ end
229
+ end
230
+
231
+ def check_opensslv2
232
+ begin
233
+ OpenSSL::SSL::SSLContext.new(:SSLv2)
234
+ rescue
235
+ return false
236
+ end
237
+ return true
238
+ end
239
+
240
+ end
241
+ end