ruby-sslyze 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +19 -0
  6. data/.yardopts +1 -0
  7. data/ChangeLog.md +8 -0
  8. data/Gemfile +18 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +70 -0
  11. data/Rakefile +23 -0
  12. data/lib/sslyze.rb +3 -0
  13. data/lib/sslyze/cert_info.rb +55 -0
  14. data/lib/sslyze/certificate.rb +139 -0
  15. data/lib/sslyze/certificate/domain_name.rb +77 -0
  16. data/lib/sslyze/certificate/extensions.rb +127 -0
  17. data/lib/sslyze/certificate/extensions/authority_information_access.rb +38 -0
  18. data/lib/sslyze/certificate/extensions/extension.rb +26 -0
  19. data/lib/sslyze/certificate/extensions/x509v3_basic_constraints.rb +60 -0
  20. data/lib/sslyze/certificate/extensions/x509v3_certificate_policies.rb +50 -0
  21. data/lib/sslyze/certificate/extensions/x509v3_crl_distribution_points.rb +32 -0
  22. data/lib/sslyze/certificate/extensions/x509v3_extended_key_usage.rb +32 -0
  23. data/lib/sslyze/certificate/extensions/x509v3_key_usage.rb +50 -0
  24. data/lib/sslyze/certificate/extensions/x509v3_subject_alternative_name.rb +71 -0
  25. data/lib/sslyze/certificate/issuer.rb +56 -0
  26. data/lib/sslyze/certificate/public_key.rb +9 -0
  27. data/lib/sslyze/certificate/subject.rb +117 -0
  28. data/lib/sslyze/certificate/subject_public_key_info.rb +53 -0
  29. data/lib/sslyze/certificate/validity.rb +9 -0
  30. data/lib/sslyze/certificate_chain.rb +89 -0
  31. data/lib/sslyze/certificate_validation.rb +44 -0
  32. data/lib/sslyze/cipher_suite.rb +237 -0
  33. data/lib/sslyze/key_exchange.rb +106 -0
  34. data/lib/sslyze/ocsp_response.rb +87 -0
  35. data/lib/sslyze/program.rb +66 -0
  36. data/lib/sslyze/protocol.rb +133 -0
  37. data/lib/sslyze/target.rb +295 -0
  38. data/lib/sslyze/task.rb +65 -0
  39. data/lib/sslyze/types.rb +17 -0
  40. data/lib/sslyze/version.rb +4 -0
  41. data/lib/sslyze/xml.rb +139 -0
  42. data/ruby-sslyze.gemspec +24 -0
  43. data/spec/cert_info_spec.rb +29 -0
  44. data/spec/certificate/subject_name_spec.rb +72 -0
  45. data/spec/certificate_chain_spec.rb +61 -0
  46. data/spec/certificate_spec.rb +330 -0
  47. data/spec/certificate_validation_spec.rb +27 -0
  48. data/spec/cipher_suite_spec.rb +50 -0
  49. data/spec/issuer_spec.rb +33 -0
  50. data/spec/key_exchange_spec.rb +97 -0
  51. data/spec/ocsp_response_spec.rb +59 -0
  52. data/spec/protocol_spec.rb +99 -0
  53. data/spec/spec_helper.rb +9 -0
  54. data/spec/sslyze.xml +2798 -0
  55. data/spec/sslyze_spec.rb +8 -0
  56. data/spec/subject_public_key_info_spec.rb +35 -0
  57. data/spec/subject_spec.rb +67 -0
  58. data/spec/target_spec.rb +176 -0
  59. data/spec/xml_examples.rb +9 -0
  60. data/spec/xml_spec.rb +72 -0
  61. metadata +162 -0
@@ -0,0 +1,106 @@
1
+ module SSLyze
2
+ #
3
+ # Key exchange information.
4
+ #
5
+ class KeyExchange
6
+
7
+ #
8
+ # Initializes the key exchange information.
9
+ #
10
+ # @param [Nokogiri::XML::Node] node
11
+ # The `<keyExchange>` information.
12
+ #
13
+ def initialize(node)
14
+ @node = node
15
+ end
16
+
17
+ #
18
+ # @return [String]
19
+ #
20
+ def a
21
+ @a ||= @node['A']
22
+ end
23
+
24
+ #
25
+ # @return [String]
26
+ #
27
+ def b
28
+ @b ||= @node['B']
29
+ end
30
+
31
+ #
32
+ # @return [Integer]
33
+ #
34
+ def cofactor
35
+ @cofactor ||= @node['Cofactor'].to_i
36
+ end
37
+
38
+ #
39
+ # @return [String]
40
+ #
41
+ def field_type
42
+ @field_type ||= @node['Field_Type']
43
+ end
44
+
45
+ #
46
+ # @return [String]
47
+ #
48
+ def generator
49
+ @generator ||= @node['Generator']
50
+ end
51
+
52
+ #
53
+ # @return [Symbol]
54
+ #
55
+ def generator_type
56
+ @generator ||= @node['GeneratorType'].to_sym
57
+ end
58
+
59
+ #
60
+ # @return [Integer]
61
+ #
62
+ def group_size
63
+ @group_size ||= @node['GroupSize'].to_i
64
+ end
65
+
66
+ #
67
+ # @return [String]
68
+ #
69
+ def prime
70
+ @prime ||= @node['Prime']
71
+ end
72
+
73
+ #
74
+ # @return [String]
75
+ #
76
+ def seed
77
+ @seed ||= @node['Seed']
78
+ end
79
+
80
+ #
81
+ # @return [:DH, :ECDHE]
82
+ #
83
+ def type
84
+ @type ||= @node['Type'].to_sym
85
+ end
86
+
87
+ #
88
+ # Determines if DH key exchange was used.
89
+ #
90
+ # @return [Boolean]
91
+ #
92
+ def dh?
93
+ type == :DH
94
+ end
95
+
96
+ #
97
+ # Determines if ECDHE key exchange was used.
98
+ #
99
+ # @return [Boolean]
100
+ #
101
+ def ecdhe?
102
+ type == :ECDHE
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,87 @@
1
+ require 'sslyze/types'
2
+
3
+ require 'time'
4
+
5
+ module SSLyze
6
+ #
7
+ # Represents the `<ocspResponse>` XML element.
8
+ #
9
+ class OCSPResponse
10
+
11
+ include Types
12
+
13
+ #
14
+ # Initializes the OCSP response.
15
+ #
16
+ # @param [Nokogiri::XML::Node] node
17
+ # The `<ocspResponse>` XML element.
18
+ #
19
+ def initialize(node)
20
+ @node = node
21
+ end
22
+
23
+ #
24
+ # Specifies whether the response was trusted.
25
+ #
26
+ # @return [Boolean]
27
+ #
28
+ def trusted?
29
+ Boolean[@node['isTrustedByMozillaCAStore']]
30
+ end
31
+
32
+ #
33
+ # The response type.
34
+ #
35
+ # @return [String]
36
+ #
37
+ def type
38
+ @type ||= @node.at('responseType').inner_text
39
+ end
40
+
41
+ #
42
+ # The responder ID.
43
+ #
44
+ # @return [String]
45
+ #
46
+ def responder_id
47
+ @id ||= @node.at('responderID').inner_text
48
+ end
49
+
50
+ #
51
+ # The OCSP version.
52
+ #
53
+ # @return [Integer]
54
+ #
55
+ def version
56
+ @version ||= @node.at('version').inner_text.to_i
57
+ end
58
+
59
+ #
60
+ # The response status.
61
+ #
62
+ # @return [Symbol]
63
+ #
64
+ def status
65
+ @status ||= @node.at('responseStatus').inner_text.to_sym
66
+ end
67
+
68
+ #
69
+ # Determines whether the OCSP response was a success.
70
+ #
71
+ # @return [Boolean]
72
+ #
73
+ def successful?
74
+ status == :successful
75
+ end
76
+
77
+ #
78
+ # When the response was produced.
79
+ #
80
+ # @return [Time]
81
+ #
82
+ def produced_at
83
+ @produced_at ||= Time.parse(@node.at('producedAt').inner_text)
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,66 @@
1
+ require 'sslyze/task'
2
+
3
+ require 'rprogram/program'
4
+
5
+ module SSLyze
6
+ #
7
+ # Represents the `sslyze.py` command line utility.
8
+ #
9
+ class Program < RProgram::Program
10
+
11
+ name_program 'sslyze.py'
12
+
13
+ #
14
+ # Finds the `sslyze.py` script and runs it.
15
+ #
16
+ # @param [Hash{Symbol => Object}] options
17
+ # Additional options for `sslyze.py`.
18
+ #
19
+ # @param [Hash{Symbol => Object}] exec_options
20
+ # Additional exec-options.
21
+ #
22
+ # @yield [task]
23
+ # If a block is given, it will be passed a task object
24
+ # used to specify options for `sslyze.py`.
25
+ #
26
+ # @yieldparam [Task] task
27
+ # The sslyze task object.
28
+ #
29
+ # @return [Boolean]
30
+ # Specifies whether the command exited normally.
31
+ #
32
+ # @see http://rubydoc.info/gems/rprogram/0.3.0/RProgram/Program#run-instance_method
33
+ # For additional exec-options.
34
+ #
35
+ def self.analyze(options={},exec_options={},&block)
36
+ find.analyze(options,exec_options,&block)
37
+ end
38
+
39
+ #
40
+ # Runs `sslyze.py`.
41
+ #
42
+ # @param [Hash{Symbol => Object}] options
43
+ # Additional options for `sslyze.py`.
44
+ #
45
+ # @param [Hash{Symbol => Object}] exec_options
46
+ # Additional exec-options.
47
+ #
48
+ # @yield [task]
49
+ # If a block is given, it will be passed a task object
50
+ # used to specify options for `sslyze.py`.
51
+ #
52
+ # @yieldparam [Task] task
53
+ # The sslyze task object.
54
+ #
55
+ # @return [Boolean]
56
+ # Specifies whether the command exited normally.
57
+ #
58
+ # @see http://rubydoc.info/gems/rprogram/0.3.0/RProgram/Program#run-instance_method
59
+ # For additional exec-options.
60
+ #
61
+ def analyze(options={},exec_options={},&block)
62
+ run_task(Task.new(options,&block),exec_options)
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,133 @@
1
+ require 'sslyze/cipher_suite'
2
+
3
+ module SSLyze
4
+ #
5
+ # Represents the `<sslv2>`, `<sslv3>`, `<tls1>`, `<tls1_1>`, `<tlsv1_2>`
6
+ # XML elements.
7
+ #
8
+ class Protocol
9
+
10
+ # SSL protocol name.
11
+ #
12
+ # @return [Symbol]
13
+ attr_reader :name
14
+
15
+ #
16
+ # Initializes the protocol.
17
+ #
18
+ # @param [Nokogiri::XML::Node] node
19
+ # The XML element.
20
+ #
21
+ def initialize(node)
22
+ @node = node
23
+ @name = @node.name.to_sym
24
+ end
25
+
26
+ #
27
+ # Descriptive title.
28
+ #
29
+ # @return [String]
30
+ #
31
+ def title
32
+ @title ||= @node['title']
33
+ end
34
+
35
+ #
36
+ # @raise [NotImplemnetedError]
37
+ #
38
+ # @todo figure out what `<errors />` contains.
39
+ #
40
+ def each_error
41
+ raise(NotImplementedError,"#{__method__} not implemented")
42
+ end
43
+
44
+ #
45
+ # Enumerates over every rejected cipher suite.
46
+ #
47
+ # @yield [cipher_suite]
48
+ #
49
+ # @yieldparam [CipherSuite] cipher_suite
50
+ #
51
+ # @return [Enumerator]
52
+ #
53
+ def each_rejected_cipher_suite
54
+ return enum_for(__method__) unless block_given?
55
+
56
+ @node.search('rejectedCipherSuites/cipherSuite').each do |cipher_suite|
57
+ yield CipherSuite.new(cipher_suite)
58
+ end
59
+ end
60
+
61
+ #
62
+ # The rejected cipher suites.
63
+ #
64
+ # @return [Array<CipherSuite>]
65
+ #
66
+ def rejected_cipher_suites
67
+ each_rejected_cipher_suite.to_a
68
+ end
69
+
70
+ #
71
+ # Enumerates over every accepted cipher suite.
72
+ #
73
+ # @yield [cipher_suite]
74
+ #
75
+ # @yieldparam [CipherSuite] cipher_suite
76
+ #
77
+ # @return [Enumerator]
78
+ #
79
+ def each_accepted_cipher_suite
80
+ return enum_for(__method__) unless block_given?
81
+
82
+ @node.search('acceptedCipherSuites/cipherSuite').each do |cipher_suite|
83
+ yield CipherSuite.new(cipher_suite)
84
+ end
85
+ end
86
+
87
+ #
88
+ # The accepted cipher suites.
89
+ #
90
+ # @return [Array<CipherSuite>]
91
+ #
92
+ def accepted_cipher_suites
93
+ each_accepted_cipher_suite.to_a
94
+ end
95
+
96
+ #
97
+ # Enumerates over every preferred cipher suite.
98
+ #
99
+ # @yield [cipher_suite]
100
+ #
101
+ # @yieldparam [CipherSuite] cipher_suite
102
+ #
103
+ # @return [Enumerator]
104
+ #
105
+ def each_preferred_cipher_suite
106
+ return enum_for(__method__) unless block_given?
107
+
108
+ @node.search('preferredCipherSuites/cipherSuite').each do |cipher_suite|
109
+ yield CipherSuite.new(cipher_suite)
110
+ end
111
+ end
112
+
113
+ #
114
+ # The preferred cipher suites.
115
+ #
116
+ # @return [Array<CipherSuite>]
117
+ #
118
+ def preferred_cipher_suites
119
+ each_preferred_cipher_suite.to_a
120
+ end
121
+
122
+ #
123
+ # Determines whether the protocol is supported.
124
+ #
125
+ # @return [Boolean]
126
+ # Specifies whether any cipher suite was accepted.
127
+ #
128
+ def supported?
129
+ each_accepted_cipher_suite.any?
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,295 @@
1
+ require 'sslyze/types'
2
+ require 'sslyze/cert_info'
3
+ require 'sslyze/protocol'
4
+
5
+ module SSLyze
6
+ #
7
+ # Represents the `<target>` XML element.
8
+ #
9
+ class Target
10
+
11
+ include Types
12
+
13
+ #
14
+ # Initializes the target.
15
+ #
16
+ # @param [Nokogiri::XML::Node] node
17
+ # The `<target>` XML element.
18
+ #
19
+ def initialize(node)
20
+ @node = node
21
+ end
22
+
23
+ #
24
+ # The host name of the target.
25
+ #
26
+ # @return [String]
27
+ #
28
+ def host
29
+ @host ||= @node['host']
30
+ end
31
+
32
+ #
33
+ # The IP address of the target.
34
+ #
35
+ # @return [String]
36
+ #
37
+ def ip
38
+ @ip ||= @node['ip']
39
+ end
40
+
41
+ #
42
+ # The port number that was scanned.
43
+ #
44
+ # @return [Integer]
45
+ #
46
+ def port
47
+ @port ||= @node['port'].to_i
48
+ end
49
+
50
+ #
51
+ # Certificate information.
52
+ #
53
+ # @return [CertInfo, nil]
54
+ #
55
+ def cert_info
56
+ @cert_info ||= if (certinfo = @node.at('certinfo'))
57
+ CertInfo.new(certinfo)
58
+ end
59
+ end
60
+
61
+ #
62
+ # Which compression algorithms are supported.
63
+ #
64
+ # @return [Hash{Symbol => Boolean}]
65
+ # The algorithm name and support status.
66
+ #
67
+ def compression
68
+ unless @compression
69
+ @compression = {}
70
+
71
+ @node.search('compression/compressionMethod').map do |compression|
72
+ type = compression['type'].downcase.to_sym
73
+ supported = Boolean[compression['isSupported']]
74
+
75
+ @compression[type] = supported
76
+ end
77
+ end
78
+
79
+ return @compression
80
+ end
81
+
82
+ #
83
+ # Specifies whether the service was vulnerable to Heartbleed.
84
+ #
85
+ # @return [Boolean, nil]
86
+ #
87
+ def heartbleed?
88
+ if (heartbleed = @node.at('heartbleed/openSslHeartbleed'))
89
+ Boolean[heartbleed['isVulnerable']]
90
+ end
91
+ end
92
+
93
+ #
94
+ # Represents the `<sessionRenegotiation>` XML element.
95
+ #
96
+ class SessionRenegotiation < Struct.new(:client_initiated, :secure)
97
+
98
+ def client_initiated?
99
+ client_initiated == true
100
+ end
101
+
102
+ def secure?
103
+ secure == true
104
+ end
105
+
106
+ end
107
+
108
+ #
109
+ # Specifies whether the service supports Session Renegotiation.
110
+ #
111
+ # @return [SessionRenegotiation, nil]
112
+ #
113
+ def session_renegotiation
114
+ @session_renegotiation ||= (
115
+ if (sessionRenegotiation = @node.at('reneg/sessionRenegotiation'))
116
+
117
+ SessionRenegotiation.new(
118
+ Boolean[sessionRenegotiation['canBeClientInitiated']],
119
+ Boolean[sessionRenegotiation['isSecure']]
120
+ )
121
+ end
122
+ )
123
+ end
124
+
125
+ #
126
+ # SSLv2 protocol information.
127
+ #
128
+ # @return [Protocol, nil]
129
+ #
130
+ def sslv2
131
+ @sslv2 ||= if (node = @node.at('sslv2'))
132
+ Protocol.new(node)
133
+ end
134
+ end
135
+
136
+ #
137
+ # SSLv3 protocol information.
138
+ #
139
+ # @return [Protocol, nil]
140
+ #
141
+ def sslv3
142
+ @sslv3 ||= if (node = @node.at('sslv3'))
143
+ Protocol.new(node)
144
+ end
145
+ end
146
+
147
+ #
148
+ # TLSv1 protocol information.
149
+ #
150
+ # @return [Protocol, nil]
151
+ #
152
+ def tlsv1
153
+ @tlsv1 ||= if (node = @node.at('tlsv1'))
154
+ Protocol.new(node)
155
+ end
156
+ end
157
+
158
+ #
159
+ # TLSv1.1 protocol information.
160
+ #
161
+ # @return [Protocol, nil]
162
+ #
163
+ def tlsv1_1
164
+ @tlsv1_1 ||= if (node = @node.at('tlsv1_1'))
165
+ Protocol.new(node)
166
+ end
167
+ end
168
+
169
+ #
170
+ # TLSv1.2 protocol information.
171
+ #
172
+ # @return [Protocol, nil]
173
+ #
174
+ def tlsv1_2
175
+ @tlsv1_2 ||= if (node = @node.at('tlsv1_2'))
176
+ Protocol.new(node)
177
+ end
178
+ end
179
+
180
+ #
181
+ # Iterates over every SSL protocol.
182
+ #
183
+ # @yield [protocol]
184
+ # The given block will be passed each SSL protocol.
185
+ #
186
+ # @yieldparam [Protocol] protocol
187
+ # A SSL protocol.
188
+ #
189
+ # @return [Enumerator]
190
+ # If a no block was given, an Enumerator will be returned.
191
+ #
192
+ # @see {#sslv2}, {#sslv3}
193
+ #
194
+ def each_ssl_protocol
195
+ return enum_for(__method__) unless block_given?
196
+
197
+ yield sslv2 if sslv2
198
+ yield sslv3 if sslv3
199
+ end
200
+
201
+ #
202
+ # All supported SSL protocols.
203
+ #
204
+ # @return [Array<Protocol>]
205
+ #
206
+ def ssl_protocols
207
+ each_ssl_protocol.to_a
208
+ end
209
+
210
+ #
211
+ # Iterates over every TLS protocol.
212
+ #
213
+ # @yield [protocol]
214
+ # The given block will be passed each TLS protocol.
215
+ #
216
+ # @yieldparam [Protocol] protocol
217
+ # A TLS protocol.
218
+ #
219
+ # @return [Enumerator]
220
+ # If a no block was given, an Enumerator will be returned.
221
+ #
222
+ # @see {#tlsv1}, {#tlsv1_1}, {#tlsv1_2}
223
+ #
224
+ def each_tls_protocol
225
+ return enum_for(__method__) unless block_given?
226
+
227
+ yield tlsv1 if tlsv1
228
+ yield tlsv1_1 if tlsv1_1
229
+ yield tlsv1_2 if tlsv1_2
230
+ end
231
+
232
+ #
233
+ # All supported TLS protocols.
234
+ #
235
+ # @return [Array<Protocol>]
236
+ #
237
+ def tls_protocols
238
+ each_tls_protocol.to_a
239
+ end
240
+
241
+ #
242
+ # Iterates over every SSL/TLS protocol.
243
+ #
244
+ # @yield [protocol]
245
+ # The given block will be passed each SSL/TLS protocol.
246
+ #
247
+ # @yieldparam [Protocol] protocol
248
+ # A SSL/TLS protocol.
249
+ #
250
+ # @return [Enumerator]
251
+ # If a no block was given, an Enumerator will be returned.
252
+ #
253
+ # @see {#sslv2}, {#sslv3}, {#tlsv1}, {#tlsv1_1}, {#tlsv1_2}
254
+ #
255
+ def each_protocol(&block)
256
+ return enum_for(__method__) unless block
257
+
258
+ each_ssl_protocol(&block)
259
+ each_tls_protocol(&block)
260
+ end
261
+
262
+ #
263
+ # All supported SSL/TLS protocols.
264
+ #
265
+ # @return [Array<Protocol>]
266
+ #
267
+ def protocols
268
+ each_protocol.to_a
269
+ end
270
+
271
+ #
272
+ # Convert the target to a String.
273
+ #
274
+ # @return [String]
275
+ # The host and port.
276
+ #
277
+ def to_s
278
+ "#{host}:#{port}"
279
+ end
280
+
281
+ #
282
+ # Compares the other target to this target.
283
+ #
284
+ # @param [Target] other
285
+ # The other target.
286
+ #
287
+ # @return [Boolean]
288
+ # Whether the other target has the same host and port.
289
+ #
290
+ def ==(other)
291
+ other.kind_of?(self.class) && other.host == host && other.port == port
292
+ end
293
+
294
+ end
295
+ end