ssl_scan 0.0.1

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,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