ruby-sslyze 0.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.
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