iodef 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/SECURITY.md +7 -0
- data/data/enums.yml +40 -0
- data/data/mapkeys.yml +5 -0
- data/data/types.yml +282 -0
- data/lib/iodef/cbor.rb +131 -0
- data/lib/iodef/cbor_guard.rb +123 -0
- data/lib/iodef/errors.rb +21 -0
- data/lib/iodef/model.rb +177 -0
- data/lib/iodef/types.rb +168 -0
- data/lib/iodef/validator.rb +237 -0
- data/lib/iodef/version.rb +5 -0
- data/lib/iodef.rb +40 -0
- data/sig/iodef.rbs +278 -0
- metadata +81 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Iodef
|
|
4
|
+
class Validator
|
|
5
|
+
attr_reader :limits
|
|
6
|
+
|
|
7
|
+
def initialize(**limits)
|
|
8
|
+
@limits = Iodef.limits(limits)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def validate(value, type: "Document")
|
|
12
|
+
@violations = []
|
|
13
|
+
@ids = {}
|
|
14
|
+
@references = []
|
|
15
|
+
data = Iodef.copy(value, limits)
|
|
16
|
+
check_object(data, type, "")
|
|
17
|
+
if type == "Document"
|
|
18
|
+
@references.each do |id, path|
|
|
19
|
+
add(path, "reference", "reference has no matching identifier") unless @ids.key?(id)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
@violations.freeze
|
|
23
|
+
rescue LimitError
|
|
24
|
+
raise
|
|
25
|
+
rescue ValidationError => e
|
|
26
|
+
[Violation.new(pointer: e.pointer, rule: "json", message: "value is not a finite UTF-8 JSON tree")].freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def add(path, rule, message)
|
|
32
|
+
return if @violations.size >= limits[:max_violations]
|
|
33
|
+
|
|
34
|
+
@violations << Violation.new(pointer: path.freeze, rule: rule.freeze, message: message.freeze)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def check_object(value, type, path)
|
|
38
|
+
return add(path, type, "expected an object") unless value.is_a?(Hash)
|
|
39
|
+
|
|
40
|
+
definition = SCHEMA.fetch(type)
|
|
41
|
+
definition.fetch("fields").each do |name, descriptor|
|
|
42
|
+
field_path = Iodef.pointer(path, name)
|
|
43
|
+
unless value.key?(name)
|
|
44
|
+
add(field_path, "required", "required property is absent") if descriptor.end_with?("!")
|
|
45
|
+
next
|
|
46
|
+
end
|
|
47
|
+
check_field(value[name], descriptor.delete_suffix("!"), field_path)
|
|
48
|
+
end
|
|
49
|
+
check_groups(value, definition, path)
|
|
50
|
+
check_extensions(value, definition, path)
|
|
51
|
+
check_semantics(value, type, path)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_field(value, type, path)
|
|
55
|
+
if type.end_with?("[]")
|
|
56
|
+
return add(path, type, "expected a nonempty array") unless value.is_a?(Array) && !value.empty?
|
|
57
|
+
|
|
58
|
+
value.each_with_index { |item, index| check_field(item, type.delete_suffix("[]"), Iodef.pointer(path, index)) }
|
|
59
|
+
elsif type == "mlstring"
|
|
60
|
+
unless value.is_a?(String)
|
|
61
|
+
if value.is_a?(Hash)
|
|
62
|
+
check_object(value, "MLString",
|
|
63
|
+
path)
|
|
64
|
+
else
|
|
65
|
+
add(path, type, "expected a string or multilingual string object")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
elsif SCHEMA.key?(type)
|
|
69
|
+
check_object(value, type, path)
|
|
70
|
+
else
|
|
71
|
+
add(path, type, "invalid #{type} value") unless Types.valid?(type, value)
|
|
72
|
+
if type == "binary" && value.is_a?(String) && (value.bytesize / 4 * 3) - value.count("=") > limits[:max_binary_size]
|
|
73
|
+
raise LimitError.new("binary data exceeds max_binary_size", pointer: path)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def check_groups(value, definition, path)
|
|
79
|
+
{ "at_least_one" => (1..), "exactly_one" => (1..1), "at_most_one" => (0..1) }.each do |rule, count|
|
|
80
|
+
fields = definition[rule]
|
|
81
|
+
next unless fields
|
|
82
|
+
|
|
83
|
+
add(path, rule, "invalid combination of #{fields.join(", ")}") unless count.cover?(fields.count { |field| value.key?(field) })
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def check_extensions(value, definition, path)
|
|
88
|
+
definition.fetch("fields").each_key do |name|
|
|
89
|
+
next unless name.start_with?("ext-")
|
|
90
|
+
|
|
91
|
+
original = name.delete_prefix("ext-")
|
|
92
|
+
# SpecID uses "private" for its extension selector, unlike ordinary enumerations.
|
|
93
|
+
marker = original == "SpecID" ? "private" : "ext-value"
|
|
94
|
+
if value[original] == marker
|
|
95
|
+
unless value[name].is_a?(String) && !value[name].empty?
|
|
96
|
+
add(Iodef.pointer(path, name), "extension",
|
|
97
|
+
"extension selector requires its companion property")
|
|
98
|
+
end
|
|
99
|
+
elsif value.key?(name)
|
|
100
|
+
add(Iodef.pointer(path, name), "extension", "extension property requires the extension selector")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def check_semantics(value, type, path)
|
|
106
|
+
register_identifiers(value, type, path)
|
|
107
|
+
case type
|
|
108
|
+
when "Document"
|
|
109
|
+
require_field(value, "private-enum-name", path) if value.key?("private-enum-id")
|
|
110
|
+
when "SoftwareReference"
|
|
111
|
+
require_field(value, "dtype", path) if value["spec-name"] == "custom"
|
|
112
|
+
check_typed_content(value, path)
|
|
113
|
+
when "Extension" then check_typed_content(value, path)
|
|
114
|
+
when "Confidence"
|
|
115
|
+
if value["rating"] == "numeric"
|
|
116
|
+
require_field(value, "value", path)
|
|
117
|
+
elsif value.key?("value")
|
|
118
|
+
add(Iodef.pointer(path, "value"), "confidence", "value is only allowed for a numeric rating")
|
|
119
|
+
end
|
|
120
|
+
when "HistoryItem", "Expectation"
|
|
121
|
+
require_field(value, "DefinedCOA", path) if value["action"] == "defined-coa"
|
|
122
|
+
when "NameServers" then check_name_server_addresses(value, path)
|
|
123
|
+
when "Service" then check_service(value, path)
|
|
124
|
+
when "EmailData" then check_headers(value, "EmailHeaderField", path, string: true)
|
|
125
|
+
when "HashData" then check_hash_data(value, path)
|
|
126
|
+
when "BulkObservable"
|
|
127
|
+
if value["type"] == "file-hash" && !value["BulkObservableFormat"].is_a?(Hash)
|
|
128
|
+
require_field(value, "BulkObservableFormat", path)
|
|
129
|
+
elsif value["type"] == "file-hash" && !value["BulkObservableFormat"].key?("Hash")
|
|
130
|
+
add("#{path}/BulkObservableFormat/Hash", "required", "file hashes require a hash description")
|
|
131
|
+
end
|
|
132
|
+
when "IndicatorExpression"
|
|
133
|
+
operands = %w[IndicatorExpression Observable uid-ref IndicatorReference].sum { |key| value[key].is_a?(Array) ? value[key].size : 0 }
|
|
134
|
+
add(path, "operator", "negation requires exactly one operand") if value["operator"] == "not" && operands != 1
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def register_identifiers(value, type, path)
|
|
139
|
+
fields = SCHEMA.fetch(type).fetch("fields").key?("observable-id") ? ["observable-id"] : []
|
|
140
|
+
fields << "id" if type == "IndicatorID"
|
|
141
|
+
fields.each do |field|
|
|
142
|
+
next unless value[field].is_a?(String)
|
|
143
|
+
|
|
144
|
+
location = Iodef.pointer(path, field)
|
|
145
|
+
add(location, "identifier", "identifier is already defined") if @ids.key?(value[field]) && field == "observable-id"
|
|
146
|
+
@ids[value[field]] = location
|
|
147
|
+
end
|
|
148
|
+
refs = value["uid-ref"] if SCHEMA.fetch(type).fetch("fields").key?("uid-ref")
|
|
149
|
+
(refs.is_a?(Array) ? refs : [refs]).each_with_index do |id, index|
|
|
150
|
+
@references << [id, refs.is_a?(Array) ? Iodef.pointer(Iodef.pointer(path, "uid-ref"), index) : Iodef.pointer(path, "uid-ref")] if id.is_a?(String)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def require_field(value, name, path)
|
|
155
|
+
add(Iodef.pointer(path, name), "required", "required property is absent") unless value.key?(name)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def check_name_server_addresses(value, path)
|
|
159
|
+
return unless value["Address"].is_a?(Array)
|
|
160
|
+
|
|
161
|
+
value["Address"].each_with_index do |address, index|
|
|
162
|
+
next unless address.is_a?(Hash)
|
|
163
|
+
next if %w[ipv4-addr ipv6-addr].include?(address["category"])
|
|
164
|
+
|
|
165
|
+
add("#{path}/Address/#{index}/category", "address", "name servers require an IP address")
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def check_service(value, path)
|
|
170
|
+
require_field(value, "ip-protocol", path) if %w[Port Portlist ProtoCode ProtoType ProtoField].any? { |key| value.key?(key) }
|
|
171
|
+
{ "Port" => 65_535, "ip-protocol" => 255, "ProtoCode" => 255, "ProtoType" => 255 }.each do |field, maximum|
|
|
172
|
+
item = value[field]
|
|
173
|
+
add(Iodef.pointer(path, field), "range", "protocol number is outside its wire range") if item.is_a?(Integer) && !item.between?(0, maximum)
|
|
174
|
+
end
|
|
175
|
+
check_headers(value, "ApplicationHeaderField", path)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def check_headers(value, field, path, string: false)
|
|
179
|
+
return unless value[field].is_a?(Array)
|
|
180
|
+
|
|
181
|
+
value[field].each_with_index do |header, index|
|
|
182
|
+
next unless header.is_a?(Hash)
|
|
183
|
+
|
|
184
|
+
header_path = "#{path}/#{field}/#{index}"
|
|
185
|
+
require_field(header, "name", header_path)
|
|
186
|
+
add("#{header_path}/dtype", "header", "email header data must use the string data type") if string && header["dtype"] != "string"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def check_hash_data(value, path)
|
|
191
|
+
require_field(value, "HashTargetID", path) if %w[file-pe-section file-pe-resource file-pdf-object].include?(value["scope"])
|
|
192
|
+
return unless value["scope"] == "file-pe-iat"
|
|
193
|
+
|
|
194
|
+
hashes = [*value["Hash"], *value["FuzzyHash"]]
|
|
195
|
+
return if hashes.any? { |item| item.is_a?(Hash) && item.key?("Application") }
|
|
196
|
+
|
|
197
|
+
add(path, "hash", "import table hashing requires the tool in Application")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def check_typed_content(value, path)
|
|
201
|
+
content = value["value"]
|
|
202
|
+
return unless content.is_a?(String)
|
|
203
|
+
|
|
204
|
+
valid = case value["dtype"]
|
|
205
|
+
when "byte", "file" then Types.binary?(content)
|
|
206
|
+
when "bytes", "frame", "packet", "ipv4-packet", "ipv6-packet" then content.match?(/\A(?:[0-9A-Fa-f]{2})*\z/)
|
|
207
|
+
when "boolean" then %w[true false 0 1].include?(content)
|
|
208
|
+
when "integer" then content.match?(/\A[+-]?\d+\z/)
|
|
209
|
+
when "real" then content.match?(/\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\z/)
|
|
210
|
+
when "date-time", "ntpstamp", "ntp-stamp" then Types.datetime?(content)
|
|
211
|
+
when "portlist" then Types.portlist?(content)
|
|
212
|
+
when "url" then Types.uri?(content)
|
|
213
|
+
when "character" then content.length == 1
|
|
214
|
+
when "json" then json_content?(content)
|
|
215
|
+
else true
|
|
216
|
+
end
|
|
217
|
+
add(Iodef.pointer(path, "value"), "dtype", "content does not match the declared data type") unless valid
|
|
218
|
+
binary_size = if %w[byte file].include?(value["dtype"])
|
|
219
|
+
(content.bytesize / 4 * 3) - content.count("=")
|
|
220
|
+
elsif %w[bytes frame packet ipv4-packet ipv6-packet].include?(value["dtype"])
|
|
221
|
+
content.bytesize / 2
|
|
222
|
+
end
|
|
223
|
+
raise LimitError.new("binary data exceeds max_binary_size", pointer: "#{path}/value") if binary_size && binary_size > limits[:max_binary_size]
|
|
224
|
+
|
|
225
|
+
return unless value["dtype"] == "byte" && valid && content.unpack1("m0").bytesize != 1
|
|
226
|
+
|
|
227
|
+
add("#{path}/value", "dtype", "byte content must encode exactly one octet")
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def json_content?(content)
|
|
231
|
+
JSON.parse(content, allow_duplicate_key: false, max_nesting: limits[:max_depth])
|
|
232
|
+
true
|
|
233
|
+
rescue JSON::ParserError
|
|
234
|
+
false
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
data/lib/iodef.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "date"
|
|
6
|
+
require "uri"
|
|
7
|
+
require_relative "iodef/version"
|
|
8
|
+
require_relative "iodef/errors"
|
|
9
|
+
require_relative "iodef/types"
|
|
10
|
+
require_relative "iodef/validator"
|
|
11
|
+
require_relative "iodef/model"
|
|
12
|
+
|
|
13
|
+
IODEF = Iodef unless defined?(IODEF)
|
|
14
|
+
|
|
15
|
+
module Iodef
|
|
16
|
+
class << self
|
|
17
|
+
# Reads one JSON IODEF document; unknown properties survive round trips.
|
|
18
|
+
def parse(source, strict: true, validate: true, **limits)
|
|
19
|
+
options = self.limits(limits)
|
|
20
|
+
raise ParseError, "JSON input must be a String" unless source.is_a?(String)
|
|
21
|
+
raise LimitError, "input exceeds max_input_size" if source.bytesize > options[:max_input_size]
|
|
22
|
+
|
|
23
|
+
value = JSON.parse(source, allow_duplicate_key: false, max_nesting: options[:max_depth])
|
|
24
|
+
Document.new(value, strict: strict && validate, **options)
|
|
25
|
+
rescue JSON::NestingError
|
|
26
|
+
raise LimitError, "input exceeds max_depth"
|
|
27
|
+
rescue JSON::ParserError, EncodingError
|
|
28
|
+
raise ParseError, "invalid JSON input"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns all bounded diagnostics, without embedding confidential input values.
|
|
32
|
+
def validate(source, **limits)
|
|
33
|
+
return parse(source, strict: false, **limits).warnings if source.is_a?(String)
|
|
34
|
+
|
|
35
|
+
Validator.new(**limits).validate(source.is_a?(Model) ? source.to_h : source)
|
|
36
|
+
rescue ParseError => e
|
|
37
|
+
[Violation.new(pointer: "", rule: "json", message: e.message)]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/sig/iodef.rbs
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
module Iodef
|
|
2
|
+
VERSION: String
|
|
3
|
+
DEFAULT_LIMITS: Hash[Symbol, Integer]
|
|
4
|
+
SCHEMA: Hash[String, untyped]
|
|
5
|
+
ENUMS: Hash[String, Array[String]]
|
|
6
|
+
MAPKEYS: Hash[String, Integer]
|
|
7
|
+
KEYNAMES: Hash[Integer, String]
|
|
8
|
+
|
|
9
|
+
def self.parse: (String source, ?strict: bool, ?validate: bool, **Integer limits) -> Document
|
|
10
|
+
def self.parse_cbor: (String source, ?strict: bool, ?validate: bool, **Integer limits) -> Document
|
|
11
|
+
def self.validate: (untyped source, **Integer limits) -> Array[Violation]
|
|
12
|
+
def self.generate_json: (untyped value, ?untyped options) -> String
|
|
13
|
+
def self.pointer: (String parent, untyped key) -> String
|
|
14
|
+
|
|
15
|
+
class Violation
|
|
16
|
+
attr_reader pointer: String
|
|
17
|
+
attr_reader rule: String
|
|
18
|
+
attr_reader message: String
|
|
19
|
+
def self.new: (pointer: String, rule: String, message: String) -> instance
|
|
20
|
+
end
|
|
21
|
+
class Error < StandardError
|
|
22
|
+
end
|
|
23
|
+
class ParseError < Error
|
|
24
|
+
end
|
|
25
|
+
class ConversionError < Error
|
|
26
|
+
end
|
|
27
|
+
class ValidationError < Error
|
|
28
|
+
attr_reader pointer: String
|
|
29
|
+
attr_reader violations: Array[Violation]
|
|
30
|
+
def initialize: (?String message, ?pointer: String, ?violations: Array[Violation]) -> void
|
|
31
|
+
end
|
|
32
|
+
class LimitError < ValidationError
|
|
33
|
+
end
|
|
34
|
+
class Validator
|
|
35
|
+
attr_reader limits: Hash[Symbol, Integer]
|
|
36
|
+
def initialize: (**Integer limits) -> void
|
|
37
|
+
def validate: (untyped value, ?type: String) -> Array[Violation]
|
|
38
|
+
end
|
|
39
|
+
class Model
|
|
40
|
+
attr_reader limits: Hash[Symbol, Integer]
|
|
41
|
+
attr_reader warnings: Array[Violation]
|
|
42
|
+
def initialize: (?Hash[untyped, untyped] data, ?strict: bool, **Integer limits) -> void
|
|
43
|
+
def []: (String | Symbol key) -> untyped
|
|
44
|
+
def []=: (String | Symbol key, untyped value) -> untyped
|
|
45
|
+
def delete: (String | Symbol key) -> untyped
|
|
46
|
+
def to_h: () -> Hash[String, untyped]
|
|
47
|
+
def to_json: (?untyped options) -> String
|
|
48
|
+
def validate!: () -> self
|
|
49
|
+
def description: () -> MultilingualText
|
|
50
|
+
def description=: (Array[untyped] value) -> untyped
|
|
51
|
+
end
|
|
52
|
+
class MultilingualText
|
|
53
|
+
include Enumerable[untyped]
|
|
54
|
+
def initialize: (untyped values, ?lang: String?) -> void
|
|
55
|
+
def each: () { (untyped) -> untyped } -> Array[untyped]
|
|
56
|
+
| () -> Enumerator[untyped, Array[untyped]]
|
|
57
|
+
def text: (?String? language) -> String?
|
|
58
|
+
def to_a: () -> Array[untyped]
|
|
59
|
+
end
|
|
60
|
+
class Document < Model
|
|
61
|
+
def self.build: (?lang: String?, **Integer limits) { (Document) -> untyped } -> Document
|
|
62
|
+
def incidents: () -> Array[Incident]
|
|
63
|
+
def add_incident: (purpose: String, **untyped attributes) ?{ (Incident) -> untyped } -> Incident
|
|
64
|
+
def to_cbor: () -> String
|
|
65
|
+
end
|
|
66
|
+
class Incident < Model
|
|
67
|
+
def contacts: () -> Array[Contact]
|
|
68
|
+
def assessments: () -> Array[Assessment]
|
|
69
|
+
def indicators: () -> Array[Indicator]
|
|
70
|
+
def add_contact: (role: String, type: String, **untyped attributes) ?{ (Contact) -> untyped } -> Contact
|
|
71
|
+
end
|
|
72
|
+
class Contact < Model
|
|
73
|
+
end
|
|
74
|
+
class Assessment < Model
|
|
75
|
+
end
|
|
76
|
+
class EventData < Model
|
|
77
|
+
end
|
|
78
|
+
class Indicator < Model
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
module IODEF = Iodef
|
|
82
|
+
|
|
83
|
+
module Iodef
|
|
84
|
+
class Document < Model
|
|
85
|
+
def additional_data: () -> (Array[Hash[String, untyped]])?
|
|
86
|
+
def additional_data=: (untyped value) -> untyped
|
|
87
|
+
def version: () -> (String)?
|
|
88
|
+
def version=: (untyped value) -> untyped
|
|
89
|
+
def lang: () -> (String)?
|
|
90
|
+
def lang=: (untyped value) -> untyped
|
|
91
|
+
def format_id: () -> (String)?
|
|
92
|
+
def format_id=: (untyped value) -> untyped
|
|
93
|
+
def private_enum_name: () -> (String)?
|
|
94
|
+
def private_enum_name=: (untyped value) -> untyped
|
|
95
|
+
def private_enum_id: () -> (String)?
|
|
96
|
+
def private_enum_id=: (untyped value) -> untyped
|
|
97
|
+
def incident: () -> (Array[Incident])?
|
|
98
|
+
def incident=: (untyped value) -> untyped
|
|
99
|
+
end
|
|
100
|
+
class Incident < Model
|
|
101
|
+
def restriction: () -> (String)?
|
|
102
|
+
def restriction=: (untyped value) -> untyped
|
|
103
|
+
def ext_restriction: () -> (String)?
|
|
104
|
+
def ext_restriction=: (untyped value) -> untyped
|
|
105
|
+
def observable_id: () -> (String)?
|
|
106
|
+
def observable_id=: (untyped value) -> untyped
|
|
107
|
+
def additional_data: () -> (Array[Hash[String, untyped]])?
|
|
108
|
+
def additional_data=: (untyped value) -> untyped
|
|
109
|
+
def detect_time: () -> (String)?
|
|
110
|
+
def detect_time=: (untyped value) -> untyped
|
|
111
|
+
def start_time: () -> (String)?
|
|
112
|
+
def start_time=: (untyped value) -> untyped
|
|
113
|
+
def end_time: () -> (String)?
|
|
114
|
+
def end_time=: (untyped value) -> untyped
|
|
115
|
+
def recovery_time: () -> (String)?
|
|
116
|
+
def recovery_time=: (untyped value) -> untyped
|
|
117
|
+
def report_time: () -> (String)?
|
|
118
|
+
def report_time=: (untyped value) -> untyped
|
|
119
|
+
def purpose: () -> (String)?
|
|
120
|
+
def purpose=: (untyped value) -> untyped
|
|
121
|
+
def ext_purpose: () -> (String)?
|
|
122
|
+
def ext_purpose=: (untyped value) -> untyped
|
|
123
|
+
def status: () -> (String)?
|
|
124
|
+
def status=: (untyped value) -> untyped
|
|
125
|
+
def ext_status: () -> (String)?
|
|
126
|
+
def ext_status=: (untyped value) -> untyped
|
|
127
|
+
def lang: () -> (String)?
|
|
128
|
+
def lang=: (untyped value) -> untyped
|
|
129
|
+
def incident_id: () -> (Hash[String, untyped])?
|
|
130
|
+
def incident_id=: (untyped value) -> untyped
|
|
131
|
+
def alternative_id: () -> (Hash[String, untyped])?
|
|
132
|
+
def alternative_id=: (untyped value) -> untyped
|
|
133
|
+
def related_activity: () -> (Array[Hash[String, untyped]])?
|
|
134
|
+
def related_activity=: (untyped value) -> untyped
|
|
135
|
+
def generation_time: () -> (String)?
|
|
136
|
+
def generation_time=: (untyped value) -> untyped
|
|
137
|
+
def discovery: () -> (Array[Hash[String, untyped]])?
|
|
138
|
+
def discovery=: (untyped value) -> untyped
|
|
139
|
+
def assessment: () -> (Array[Assessment])?
|
|
140
|
+
def assessment=: (untyped value) -> untyped
|
|
141
|
+
def method: () -> (Array[Hash[String, untyped]])?
|
|
142
|
+
def method=: (untyped value) -> untyped
|
|
143
|
+
def contact: () -> (Array[Contact])?
|
|
144
|
+
def contact=: (untyped value) -> untyped
|
|
145
|
+
def event_data: () -> (Array[EventData])?
|
|
146
|
+
def event_data=: (untyped value) -> untyped
|
|
147
|
+
def indicator: () -> (Array[Indicator])?
|
|
148
|
+
def indicator=: (untyped value) -> untyped
|
|
149
|
+
def history: () -> (Hash[String, untyped])?
|
|
150
|
+
def history=: (untyped value) -> untyped
|
|
151
|
+
end
|
|
152
|
+
class Contact < Model
|
|
153
|
+
def restriction: () -> (String)?
|
|
154
|
+
def restriction=: (untyped value) -> untyped
|
|
155
|
+
def ext_restriction: () -> (String)?
|
|
156
|
+
def ext_restriction=: (untyped value) -> untyped
|
|
157
|
+
def additional_data: () -> (Array[Hash[String, untyped]])?
|
|
158
|
+
def additional_data=: (untyped value) -> untyped
|
|
159
|
+
def role: () -> (String)?
|
|
160
|
+
def role=: (untyped value) -> untyped
|
|
161
|
+
def ext_role: () -> (String)?
|
|
162
|
+
def ext_role=: (untyped value) -> untyped
|
|
163
|
+
def type: () -> (String)?
|
|
164
|
+
def type=: (untyped value) -> untyped
|
|
165
|
+
def ext_type: () -> (String)?
|
|
166
|
+
def ext_type=: (untyped value) -> untyped
|
|
167
|
+
def contact_name: () -> (Array[String | Hash[String, untyped]])?
|
|
168
|
+
def contact_name=: (untyped value) -> untyped
|
|
169
|
+
def contact_title: () -> (Array[String | Hash[String, untyped]])?
|
|
170
|
+
def contact_title=: (untyped value) -> untyped
|
|
171
|
+
def registry_handle: () -> (Array[Hash[String, untyped]])?
|
|
172
|
+
def registry_handle=: (untyped value) -> untyped
|
|
173
|
+
def postal_address: () -> (Array[Hash[String, untyped]])?
|
|
174
|
+
def postal_address=: (untyped value) -> untyped
|
|
175
|
+
def email: () -> (Array[Hash[String, untyped]])?
|
|
176
|
+
def email=: (untyped value) -> untyped
|
|
177
|
+
def telephone: () -> (Array[Hash[String, untyped]])?
|
|
178
|
+
def telephone=: (untyped value) -> untyped
|
|
179
|
+
def timezone: () -> (String)?
|
|
180
|
+
def timezone=: (untyped value) -> untyped
|
|
181
|
+
def contact: () -> (Array[Contact])?
|
|
182
|
+
def contact=: (untyped value) -> untyped
|
|
183
|
+
end
|
|
184
|
+
class Assessment < Model
|
|
185
|
+
def restriction: () -> (String)?
|
|
186
|
+
def restriction=: (untyped value) -> untyped
|
|
187
|
+
def ext_restriction: () -> (String)?
|
|
188
|
+
def ext_restriction=: (untyped value) -> untyped
|
|
189
|
+
def observable_id: () -> (String)?
|
|
190
|
+
def observable_id=: (untyped value) -> untyped
|
|
191
|
+
def additional_data: () -> (Array[Hash[String, untyped]])?
|
|
192
|
+
def additional_data=: (untyped value) -> untyped
|
|
193
|
+
def occurrence: () -> (String)?
|
|
194
|
+
def occurrence=: (untyped value) -> untyped
|
|
195
|
+
def incident_category: () -> (Array[String | Hash[String, untyped]])?
|
|
196
|
+
def incident_category=: (untyped value) -> untyped
|
|
197
|
+
def impact: () -> (Array[Hash[String, untyped]])?
|
|
198
|
+
def impact=: (untyped value) -> untyped
|
|
199
|
+
def counter: () -> (Array[Hash[String, untyped]])?
|
|
200
|
+
def counter=: (untyped value) -> untyped
|
|
201
|
+
def mitigating_factor: () -> (Array[String | Hash[String, untyped]])?
|
|
202
|
+
def mitigating_factor=: (untyped value) -> untyped
|
|
203
|
+
def cause: () -> (Array[String | Hash[String, untyped]])?
|
|
204
|
+
def cause=: (untyped value) -> untyped
|
|
205
|
+
def confidence: () -> (Hash[String, untyped])?
|
|
206
|
+
def confidence=: (untyped value) -> untyped
|
|
207
|
+
end
|
|
208
|
+
class EventData < Model
|
|
209
|
+
def restriction: () -> (String)?
|
|
210
|
+
def restriction=: (untyped value) -> untyped
|
|
211
|
+
def ext_restriction: () -> (String)?
|
|
212
|
+
def ext_restriction=: (untyped value) -> untyped
|
|
213
|
+
def observable_id: () -> (String)?
|
|
214
|
+
def observable_id=: (untyped value) -> untyped
|
|
215
|
+
def additional_data: () -> (Array[Hash[String, untyped]])?
|
|
216
|
+
def additional_data=: (untyped value) -> untyped
|
|
217
|
+
def detect_time: () -> (String)?
|
|
218
|
+
def detect_time=: (untyped value) -> untyped
|
|
219
|
+
def start_time: () -> (String)?
|
|
220
|
+
def start_time=: (untyped value) -> untyped
|
|
221
|
+
def end_time: () -> (String)?
|
|
222
|
+
def end_time=: (untyped value) -> untyped
|
|
223
|
+
def recovery_time: () -> (String)?
|
|
224
|
+
def recovery_time=: (untyped value) -> untyped
|
|
225
|
+
def report_time: () -> (String)?
|
|
226
|
+
def report_time=: (untyped value) -> untyped
|
|
227
|
+
def contact: () -> (Array[Contact])?
|
|
228
|
+
def contact=: (untyped value) -> untyped
|
|
229
|
+
def discovery: () -> (Array[Hash[String, untyped]])?
|
|
230
|
+
def discovery=: (untyped value) -> untyped
|
|
231
|
+
def assessment: () -> (Assessment)?
|
|
232
|
+
def assessment=: (untyped value) -> untyped
|
|
233
|
+
def method: () -> (Array[Hash[String, untyped]])?
|
|
234
|
+
def method=: (untyped value) -> untyped
|
|
235
|
+
def system: () -> (Array[Hash[String, untyped]])?
|
|
236
|
+
def system=: (untyped value) -> untyped
|
|
237
|
+
def expectation: () -> (Array[Hash[String, untyped]])?
|
|
238
|
+
def expectation=: (untyped value) -> untyped
|
|
239
|
+
def record_data: () -> (Array[Hash[String, untyped]])?
|
|
240
|
+
def record_data=: (untyped value) -> untyped
|
|
241
|
+
def event_data: () -> (Array[EventData])?
|
|
242
|
+
def event_data=: (untyped value) -> untyped
|
|
243
|
+
end
|
|
244
|
+
class Indicator < Model
|
|
245
|
+
def restriction: () -> (String)?
|
|
246
|
+
def restriction=: (untyped value) -> untyped
|
|
247
|
+
def ext_restriction: () -> (String)?
|
|
248
|
+
def ext_restriction=: (untyped value) -> untyped
|
|
249
|
+
def additional_data: () -> (Array[Hash[String, untyped]])?
|
|
250
|
+
def additional_data=: (untyped value) -> untyped
|
|
251
|
+
def indicator_id: () -> (Hash[String, untyped])?
|
|
252
|
+
def indicator_id=: (untyped value) -> untyped
|
|
253
|
+
def alternative_indicator_id: () -> (Array[Hash[String, untyped]])?
|
|
254
|
+
def alternative_indicator_id=: (untyped value) -> untyped
|
|
255
|
+
def start_time: () -> (String)?
|
|
256
|
+
def start_time=: (untyped value) -> untyped
|
|
257
|
+
def end_time: () -> (String)?
|
|
258
|
+
def end_time=: (untyped value) -> untyped
|
|
259
|
+
def confidence: () -> (Hash[String, untyped])?
|
|
260
|
+
def confidence=: (untyped value) -> untyped
|
|
261
|
+
def contact: () -> (Array[Contact])?
|
|
262
|
+
def contact=: (untyped value) -> untyped
|
|
263
|
+
def observable: () -> (Hash[String, untyped])?
|
|
264
|
+
def observable=: (untyped value) -> untyped
|
|
265
|
+
def uid_ref: () -> (String)?
|
|
266
|
+
def uid_ref=: (untyped value) -> untyped
|
|
267
|
+
def indicator_expression: () -> (Hash[String, untyped])?
|
|
268
|
+
def indicator_expression=: (untyped value) -> untyped
|
|
269
|
+
def indicator_reference: () -> (Hash[String, untyped])?
|
|
270
|
+
def indicator_reference=: (untyped value) -> untyped
|
|
271
|
+
def node_role: () -> (Array[Hash[String, untyped]])?
|
|
272
|
+
def node_role=: (untyped value) -> untyped
|
|
273
|
+
def attack_phase: () -> (Array[Hash[String, untyped]])?
|
|
274
|
+
def attack_phase=: (untyped value) -> untyped
|
|
275
|
+
def reference: () -> (Array[Hash[String, untyped]])?
|
|
276
|
+
def reference=: (untyped value) -> untyped
|
|
277
|
+
end
|
|
278
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: iodef
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: json
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 2.19.4
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '4'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: 2.19.4
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '4'
|
|
32
|
+
description: RFC 8727 JSON incident reports, typed IODEF v2 models, validation, and
|
|
33
|
+
optional CBOR conversion.
|
|
34
|
+
email:
|
|
35
|
+
- t.yudai92@gmail.com
|
|
36
|
+
executables: []
|
|
37
|
+
extensions: []
|
|
38
|
+
extra_rdoc_files: []
|
|
39
|
+
files:
|
|
40
|
+
- CHANGELOG.md
|
|
41
|
+
- LICENSE.txt
|
|
42
|
+
- README.md
|
|
43
|
+
- SECURITY.md
|
|
44
|
+
- data/enums.yml
|
|
45
|
+
- data/mapkeys.yml
|
|
46
|
+
- data/types.yml
|
|
47
|
+
- lib/iodef.rb
|
|
48
|
+
- lib/iodef/cbor.rb
|
|
49
|
+
- lib/iodef/cbor_guard.rb
|
|
50
|
+
- lib/iodef/errors.rb
|
|
51
|
+
- lib/iodef/model.rb
|
|
52
|
+
- lib/iodef/types.rb
|
|
53
|
+
- lib/iodef/validator.rb
|
|
54
|
+
- lib/iodef/version.rb
|
|
55
|
+
- sig/iodef.rbs
|
|
56
|
+
homepage: https://github.com/ydah/iodef
|
|
57
|
+
licenses:
|
|
58
|
+
- MIT
|
|
59
|
+
metadata:
|
|
60
|
+
allowed_push_host: https://rubygems.org
|
|
61
|
+
source_code_uri: https://github.com/ydah/iodef
|
|
62
|
+
changelog_uri: https://github.com/ydah/iodef/blob/main/CHANGELOG.md
|
|
63
|
+
rubygems_mfa_required: 'true'
|
|
64
|
+
rdoc_options: []
|
|
65
|
+
require_paths:
|
|
66
|
+
- lib
|
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: 3.3.0
|
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '0'
|
|
77
|
+
requirements: []
|
|
78
|
+
rubygems_version: 4.0.16
|
|
79
|
+
specification_version: 4
|
|
80
|
+
summary: Read, validate, and build IODEF incident reports
|
|
81
|
+
test_files: []
|