opentpx 2.2.0.17

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,14 @@
1
+ require 'tpx/2_2/data_model'
2
+ require 'tpx/2_2/observable'
3
+
4
+
5
+ module TPX_2_2
6
+
7
+ # The representation of the threat observable (the hash map keyed
8
+ # by the observable_id within the element observable).
9
+ class ThreatObservable < Observable
10
+ MANDATORY_ATTRIBUTES = Observable::MANDATORY_ATTRIBUTES + [
11
+ :occurred_at_t
12
+ ]
13
+ end # class
14
+ end
@@ -0,0 +1,279 @@
1
+ require 'deep_merge'
2
+ # This uses the TPX JSON Schema to validate an input document.
3
+ module TPX_2_2
4
+ class Validator
5
+
6
+ class << self
7
+ TPX_KEYS = ['observable_dictionary_c_array', 'element_observable_c_array', 'collection_c_array', 'asn_c_array']
8
+ MANIFEST_KEYS = ['dictionary_file_manifest', 'observable_element_file_manifest', 'collection_file_manifest', 'network_file_manifest']
9
+
10
+ # validate_file! opens json file and validates it. Raises an error if validation fails.
11
+ #
12
+ # @param [String] filepath path to file which needs to be validated.
13
+ # @example
14
+ # TPX_2_2::Validator.validate_file!('folder_name/file_name.json')
15
+ def validate_file!(filepath)
16
+ unless File.exists? filepath
17
+ raise ValidationError , "No such file or directory '#{filepath}'"
18
+ end
19
+
20
+ @@current_path = File.dirname(filepath)
21
+
22
+ begin
23
+ h = Oj.load_file(filepath)
24
+ rescue => e
25
+ raise ValidationError, "File '#{filepath}' is not a valid JSON file:\n#{e}"
26
+ end
27
+
28
+ if h.kind_of?(Hash) and tpx_manifest?(h)
29
+ DeepMerge::deep_merge!(load_manifest_files(h), h, {:merge_hash_arrays => true})
30
+ delete_manifest_keys!(h)
31
+ end
32
+
33
+ validate!(h)
34
+ end
35
+
36
+ # validate_json! validates json string. Raises an error if validation fails.
37
+ #
38
+ # @param [String] json_enc_str the json string which need to be validated.
39
+ # @example
40
+ # TPX_2_2::Validator.validate_json!("{\"provider_s\":\"provider\",\"schema_version_s\":\"2.1.6\",\"source_observable_s\":\"source_observable\",\"source_description_s\":\"source_description\",\"distribution_time_t\":1438862400,\"last_updated_t\":1438862400,\"list_name_s\":\"list_name\",\"element_observable_c_array\":[{\"subject_s\":\"192.168.0.1\",\"type_s\":\"ipv4\",\"score_i\":80,\"score_24hr_decay_i\":0,\"threat_observable_c_map\":{\"Conficker\":{\"occurred_at_t\":4355545,\"last_seen_t\":13123}}}],\"observable_dictionary_c_array\":[{\"observable_id_s\":\"Malicious Host\",\"description_s\":\"This network node is a malicious host.\",\"criticality_i\":20,\"classification_c_array\":[{\"classification_id_s\":\"Malicious Host\",\"criticality_i\":20}]}],\"collection_c_array\":[{\"name_id_s\":\"MarketSeg1\",\"occurred_at_t\":1212312323,\"last_updated_t\":1212312323,\"description_s\":\"This collection is related to MarketSeg1\",\"author_s\":\"Allan Thomson\",\"workspace_s\":\"lg-system\"}],\"asn_c_array\":[{\"asn_number_ui\":1,\"occurred_at_t\":1212312323,\"as_owner_s\":\"ABC Corp\"}]}")
41
+ def validate_json!(json_enc_str)
42
+ h = Oj.load(json_enc_str)
43
+ validate!(h)
44
+ end
45
+
46
+ # validate! validates hash. Raises an error if validation fails.
47
+ #
48
+ # @param [Hash] input_hash the hash which needs to be validated.
49
+ # @example
50
+ # TPX_2_2::Validator.validate!({
51
+ # 'provider_s': 'provider',
52
+ # 'schema_version_s': '2.1.6',
53
+ # 'source_observable_s': 'source_observable',
54
+ # 'source_description_s': 'source_description',
55
+ # 'distribution_time_t': 1438862400,
56
+ # 'last_updated_t': 1438862400,
57
+ # 'list_name_s': 'list_name',
58
+ # 'element_observable_c_array': [
59
+ # {
60
+ # 'subject_s': '192.168.0.1',
61
+ # 'type_s': 'ipv4',
62
+ # 'score_i': 80,
63
+ # 'score_24hr_decay_i': 0,
64
+ # 'threat_observable_c_map': {
65
+ # 'Conficker': {
66
+ # 'occurred_at_t': 4355545,
67
+ # 'last_seen_t': 13123
68
+ # }
69
+ # }
70
+ # }
71
+ # ],
72
+ # 'observable_dictionary_c_array': [
73
+ # {
74
+ # 'observable_id_s': 'Malicious Host',
75
+ # 'description_s': 'This network node is a malicious host.',
76
+ # 'criticality_i': 20,
77
+ # 'classification_c_array': [
78
+ # {
79
+ # 'classification_id_s': 'Malicious Host',
80
+ # 'criticality_i': 20
81
+ # }
82
+ # ]
83
+ # }
84
+ # ],
85
+ # 'collection_c_array': [
86
+ # {
87
+ # 'name_id_s': 'MarketSeg1',
88
+ # 'occurred_at_t': 1212312323,
89
+ # 'last_updated_t': 1212312323,
90
+ # 'description_s': 'This collection is related to MarketSeg1',
91
+ # 'author_s': 'Allan Thomson',
92
+ # 'workspace_s': 'lg-system'
93
+ # }
94
+ # ],
95
+ # 'asn_c_array': [
96
+ # {
97
+ # 'asn_number_ui': 1,
98
+ # 'occurred_at_t': 1212312323,
99
+ # 'as_owner_s': 'ABC Corp'
100
+ # }
101
+ # ]
102
+ # })
103
+ def validate!(input_hash)
104
+ @@undefined_observables = []
105
+
106
+ if input_hash.nil? || input_hash.empty?
107
+ raise ValidationError, " TPX has no content"
108
+ end
109
+
110
+ validate_schema!(input_hash)
111
+
112
+ if (TPX_KEYS & input_hash.keys).length > 0 && tpx_manifest?(input_hash)
113
+ raise TPX_2_2::ValidationError, "TPX file must either be in single-file or manifest format."
114
+ end
115
+
116
+ validate_element_observables!(input_hash['element_observable_c_array'])
117
+ validate_observable_dictionary!(input_hash['observable_dictionary_c_array'], input_hash['element_observable_c_array'])
118
+ validate_collections!(input_hash['collection_c_array'])
119
+ validate_networks!(input_hash['asn_c_array'])
120
+ if (input_hash['observable_dictionary_c_array'].nil? || input_hash['observable_dictionary_c_array'].empty?)
121
+ raise TPX_2_2::ValidationWarning, "observable dictionary is not defined"
122
+ end
123
+ unless @@undefined_observables.empty?
124
+ raise TPX_2_2::ValidationWarning, "observables #{@@undefined_observables.inspect} are not defined in the observable dictionary"
125
+ end
126
+ end
127
+
128
+
129
+ private
130
+
131
+ def validate_schema!(input_hash)
132
+ errors = JSON::Validator.fully_validate(TPX_2_2::SCHEMA, input_hash)
133
+ raise ValidationError, "#{errors.join(' ')}" unless errors.empty?
134
+ end
135
+
136
+ def validate_element_observables!(element_observable_list)
137
+ return if element_observable_list.nil? #nothing to validate, just return
138
+
139
+ schema = {
140
+ "type" => "object",
141
+ "required" => ["threat_observable_c_map"],
142
+ "properties" => {
143
+ "threat_observable_c_map" => {"type" => "hash"}
144
+ }
145
+ }
146
+ element_observable_list.each do |element_observable|
147
+ errors = JSON::Validator.fully_validate(schema, element_observable)
148
+ raise ValidationError, "#{errors.join(' ')}" unless errors.empty?
149
+ end
150
+ end
151
+
152
+ def validate_observable_dictionary!(observable_dictionaries, element_observable_list)
153
+ return if observable_dictionaries.nil? #nothing to validate, just return
154
+
155
+ schema = {
156
+ "type" => "object",
157
+ "required" => [
158
+ "observable_id_s",
159
+ "description_s",
160
+ "classification_c_array"
161
+ ],
162
+ "properties" => {
163
+ "observable_id_s" => {"type" => "string"},
164
+ "description_s" => {"type" => "string"},
165
+ "classification_c_array" => {"type" => "hash"}
166
+ }
167
+ }
168
+
169
+ observables = []
170
+
171
+ observable_dictionaries.each do |observable_dictionary|
172
+ errors = JSON::Validator.fully_validate(schema, observable_dictionary)
173
+ raise TPX_2_2::ValidationError, "#{errors.join(' ')}" unless errors.empty?
174
+ observables << observable_dictionary['observable_id_s']
175
+ end
176
+
177
+ return if element_observable_list.nil?
178
+ element_observable_list.each do |element_observable|
179
+ element = element_observable['threat_observable_c_map'] || element_observable[:threat_observable_c_map]
180
+ element.each_key do |observable|
181
+ @@undefined_observables << """#{observable}""" unless observables.include? observable
182
+ end
183
+ end
184
+
185
+ end
186
+
187
+ def validate_collections!(collections)
188
+ return if collections.nil? #nothing to validate, just return
189
+
190
+ schema = {
191
+ "type" => "object",
192
+ "required" => ["name_id_s"],
193
+ "properties" => {
194
+ "name_id_s" => {"type" => "string"}
195
+ }
196
+ }
197
+
198
+ collections.each do |collection|
199
+ errors = JSON::Validator.fully_validate(schema, collection)
200
+ raise ValidationError, "#{errors.join(' ')}" unless errors.empty?
201
+ collection.each do |key, value|
202
+ if (key == 'collection_c_array') && value.is_a?(Array)
203
+ validate_collections!(value)
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ def validate_networks!(networks)
210
+ return if networks.nil? #nothing to validate, just return
211
+
212
+ schema = {
213
+ "type" => "object",
214
+ "required" => ["asn_number_ui"],
215
+ "properties" => {
216
+ "asn_number_ui" => {"type" => "integer"}
217
+ }
218
+ }
219
+
220
+ networks.each do |net|
221
+ errors = JSON::Validator.fully_validate(schema, net)
222
+ raise ValidationError, "#{errors.join(' ')}" unless errors.empty?
223
+ end
224
+ end
225
+
226
+ # tpx_manifest? returns true if provided hash represents TPX manifest and contains TPX manifest entries
227
+ #
228
+ # @param [Hash] Hash with tpx content
229
+ # @example
230
+ # TPX_2_2::Validator.tpx_manifest?({ "schema_version_s"=> "2.2.0", "provider_s"=> "Lookingglass Cyber Solutions", "list_name_s"=> "Virus Tracker", "source_observable_s"=> "Virus Tracker", "source_file_s"=> "https://virustracker.net/", "source_description_s"=> "Virus Tracker provides real-time information about virus infections observed through custom sinkholes.", "distribution_time_t"=> 1443007504, "last_updated_t"=> 1443007504, "score_i"=> 90, "dictionary_file_manifest"=> [ "dictionary.json" ], "observable_element_file_manifest"=> [ "data8.json" ]})
231
+ def tpx_manifest?(input_hash)
232
+ (MANIFEST_KEYS & input_hash.keys).length > 0
233
+ end
234
+
235
+ # current_path returns the current path which can be one of: 1) current working directory 2) file directory path being validated
236
+ def current_path
237
+ @@current_path || Dir.pwd
238
+ end
239
+
240
+ # load_manifest_files loads files referenced in manifests and returns merged content
241
+ #
242
+ # @param [Hash] Hash with tpx content
243
+ def load_manifest_files(input_hash)
244
+ res = {}
245
+ MANIFEST_KEYS.each do |manifest_section|
246
+ files = input_hash[manifest_section]
247
+ unless files.blank?
248
+ files.each do |manifest_file|
249
+ DeepMerge::deep_merge!(load_manifest_file(manifest_file), res, {:merge_hash_arrays => true})
250
+ end
251
+ end
252
+ end
253
+ res
254
+ end
255
+
256
+ # delete_manifest_keys! deletes manifest keys from input_hash in order to be valid TPX
257
+ #
258
+ # @param [Hash] Hash with tpx content
259
+ def delete_manifest_keys!(input_hash)
260
+ MANIFEST_KEYS.each do |key|
261
+ input_hash.delete key
262
+ end
263
+ end
264
+
265
+ # load_manifest_file loads TPX content from specified file, in case if file doesn't exists searches for it in current_path. In case if file not found ValidationError is raised
266
+ #
267
+ # @param [String] filename
268
+ def load_manifest_file(filename)
269
+ return Oj.load_file(filename) if File.exists? filename
270
+
271
+ filepath = File.join(current_path, File.basename(filename))
272
+ return Oj.load_file(filepath) if File.exists? filepath
273
+
274
+ raise ValidationError, "Could not find #{filename}"
275
+ end
276
+
277
+ end # class < self
278
+ end
279
+ end
@@ -0,0 +1,81 @@
1
+ require 'gli'
2
+ require 'tpx'
3
+
4
+ module TPX
5
+ class Tools
6
+ extend ::GLI::App
7
+
8
+ TPX_VALID = "Validation succeeded"
9
+ TPX_INVALID = "The TPX file provided is invalid for the following reasons:"
10
+ TPX_VERSION_UNKNOWN = "Unknown TPX Version: "
11
+
12
+ class << self
13
+ attr_accessor :quiet
14
+
15
+ def msg(message)
16
+ puts message unless quiet
17
+ end
18
+
19
+ def get_tpx_version_const(tpx_version)
20
+ underscored_tpx_version = tpx_version.gsub('.', '_')
21
+ Object.const_get("TPX_#{underscored_tpx_version}")
22
+ rescue NameError => e
23
+ msg( e )
24
+ raise TPX_VERSION_UNKNOWN + "'#{tpx_version}'"
25
+ end
26
+
27
+ def validate(tpx_version_const, filepath)
28
+ begin
29
+ tpx_version_const::Validator.validate_file! filepath
30
+ rescue tpx_version_const::ValidationWarning => w
31
+ msg( "Warning: #{w}" )
32
+ puts TPX_VALID + " against " + tpx_version_const.to_s
33
+ rescue => e
34
+ puts TPX_INVALID
35
+ puts e
36
+ else
37
+ puts TPX_VALID
38
+ end
39
+ end
40
+
41
+
42
+ end
43
+
44
+ program_desc 'OpenTPX Tools'
45
+ version TPX::VERSION
46
+
47
+ switch [:q, :quiet], default_value: false,
48
+ desc: 'quiet non-essential output and warnings.'
49
+
50
+ #--- COMMAND validate
51
+ desc "validates that a file is of a valid TPX file format"
52
+ long_desc "validates that a file is of a valid TPX file format\n\n" \
53
+ "TPX_FILE_PATH - The path to the TPX file. The file may be either a" \
54
+ " stand-alone TPX file or a tpx manifest file. For details on TPX" \
55
+ " manifest, please see https://github.com/Lookingglass/tpx"
56
+
57
+ arg 'TPX_FILE_PATH'
58
+
59
+ command :validate do |c|
60
+ c.flag [:v, :tpx_version], default_value: "2.2",
61
+ arg_name: 'VERSION',
62
+ type: String,
63
+ desc: "The version of tpx to validate the file against. Possible" \
64
+ " values: '2.2'"
65
+
66
+ c.action do |global_options, options, args|
67
+ self.quiet = global_options[:quiet]
68
+ tpx_version = options[:tpx_version]
69
+ tpx_file_path = args[0]
70
+
71
+ raise "Missing option TPX_VERSION" if tpx_version.nil?
72
+ raise "Missing arg TPX_FILE_PATH" if tpx_file_path.nil?
73
+
74
+ tpx_version = tpx_version.strip
75
+ msg( "Validating '#{tpx_file_path}' against TPX '#{tpx_version}' schema" )
76
+ tpx_version_const = get_tpx_version_const(tpx_version)
77
+ validate(tpx_version_const, tpx_file_path)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ module TPX
2
+ VERSION = '2.2.0.17'
3
+ end
@@ -0,0 +1,14 @@
1
+ module TPX_2_2
2
+ CURRENT_SCHEMA_VERSION = '2.2.0'
3
+ SCHEMA = File.join(File.dirname(File.expand_path(__FILE__)), 'tpx', '2_2', 'schema', 'tpx.2.2.schema.json')
4
+
5
+ require 'json-schema'
6
+ require 'active_support'
7
+ require 'active_support/core_ext/object/blank'
8
+ require 'active_support/core_ext/hash'
9
+ require 'oj'
10
+
11
+ require 'tpx/2_2/exceptions'
12
+ require 'tpx/2_2/validator'
13
+ require 'tpx/2_2/exchange'
14
+ end
metadata ADDED
@@ -0,0 +1,218 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opentpx
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.2.0.17
5
+ platform: ruby
6
+ authors:
7
+ - LookingGlass
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: timecop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: oj
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: json-schema
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 2.5.1
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 2.5.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: deep_merge
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: gli
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 2.13.2
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 2.13.2
153
+ description: An open-source format and tools for exchanging threat intelligence data. This
154
+ is a JSON-based format that allows sharing of data between partner organizations.
155
+ email:
156
+ - support@lgscout.com
157
+ executables:
158
+ - opentpx_tools
159
+ extensions: []
160
+ extra_rdoc_files: []
161
+ files:
162
+ - LICENSE.txt
163
+ - README.md
164
+ - bin/opentpx_tools
165
+ - lib/tpx.rb
166
+ - lib/tpx/2_2/attribute_accessors.rb
167
+ - lib/tpx/2_2/classification_element.rb
168
+ - lib/tpx/2_2/classification_element_list.rb
169
+ - lib/tpx/2_2/collection.rb
170
+ - lib/tpx/2_2/collection_element.rb
171
+ - lib/tpx/2_2/data_model.rb
172
+ - lib/tpx/2_2/element_observable.rb
173
+ - lib/tpx/2_2/element_observable_list.rb
174
+ - lib/tpx/2_2/exceptions.rb
175
+ - lib/tpx/2_2/exchange.rb
176
+ - lib/tpx/2_2/heterogeneous_list.rb
177
+ - lib/tpx/2_2/homogeneous_list.rb
178
+ - lib/tpx/2_2/mandatory_attributes.rb
179
+ - lib/tpx/2_2/merging_heterogeneous_list.rb
180
+ - lib/tpx/2_2/merging_homogeneous_list.rb
181
+ - lib/tpx/2_2/network.rb
182
+ - lib/tpx/2_2/network_list.rb
183
+ - lib/tpx/2_2/observable.rb
184
+ - lib/tpx/2_2/observable_attribute_map.rb
185
+ - lib/tpx/2_2/observable_definition.rb
186
+ - lib/tpx/2_2/observable_dictionary.rb
187
+ - lib/tpx/2_2/schema/tpx.2.2.schema.json
188
+ - lib/tpx/2_2/threat_observable.rb
189
+ - lib/tpx/2_2/validator.rb
190
+ - lib/tpx/tools.rb
191
+ - lib/tpx/version.rb
192
+ - lib/tpx_2_2.rb
193
+ homepage: http://www.opentpx.org
194
+ licenses:
195
+ - The Apache License, Version 2.0. See LICENSE.txt
196
+ metadata: {}
197
+ post_install_message:
198
+ rdoc_options: []
199
+ require_paths:
200
+ - lib
201
+ required_ruby_version: !ruby/object:Gem::Requirement
202
+ requirements:
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ required_rubygems_version: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - ">="
209
+ - !ruby/object:Gem::Version
210
+ version: '0'
211
+ requirements: []
212
+ rubyforge_project:
213
+ rubygems_version: 2.4.3
214
+ signing_key:
215
+ specification_version: 4
216
+ summary: Open Threat Partner Exchange (OpenTPX)
217
+ test_files: []
218
+ has_rdoc: