Merge branch 'master' of github.com:oasis-open/cti-python-stix2
commit
31d944b159
|
@ -52,6 +52,7 @@ To parse a STIX JSON string into a Python STIX object, use ``parse()``:
|
|||
"indicator_types": [
|
||||
"malicious-activity"
|
||||
],
|
||||
"pattern_type": "stix",
|
||||
"pattern": "[file:hashes.md5 ='d41d8cd98f00b204e9800998ecf8427e']",
|
||||
"valid_from": "2017-09-26T23:33:39.829952Z"
|
||||
}""")
|
||||
|
|
|
@ -8,7 +8,7 @@ import re
|
|||
import stix2
|
||||
|
||||
from .base import _STIXBase
|
||||
from .exceptions import CustomContentError, ParseError
|
||||
from .exceptions import ParseError
|
||||
from .markings import _MarkingsMixin
|
||||
from .utils import _get_dict
|
||||
|
||||
|
@ -126,7 +126,7 @@ def dict_to_stix2(stix_dict, allow_custom=False, interoperability=False, version
|
|||
# '2.0' representation.
|
||||
v = 'v20'
|
||||
|
||||
OBJ_MAP = STIX2_OBJ_MAPS[v]['objects']
|
||||
OBJ_MAP = dict(STIX2_OBJ_MAPS[v]['objects'], **STIX2_OBJ_MAPS[v]['observables'])
|
||||
|
||||
try:
|
||||
obj_class = OBJ_MAP[stix_dict['type']]
|
||||
|
@ -183,8 +183,8 @@ def parse_observable(data, _valid_refs=None, allow_custom=False, version=None):
|
|||
# flag allows for unknown custom objects too, but will not
|
||||
# be parsed into STIX observable object, just returned as is
|
||||
return obj
|
||||
raise CustomContentError("Can't parse unknown observable type '%s'! For custom observables, "
|
||||
"use the CustomObservable decorator." % obj['type'])
|
||||
raise ParseError("Can't parse unknown observable type '%s'! For custom observables, "
|
||||
"use the CustomObservable decorator." % obj['type'])
|
||||
|
||||
return obj_class(allow_custom=allow_custom, **obj)
|
||||
|
||||
|
|
|
@ -67,19 +67,34 @@ def _custom_observable_builder(cls, type, properties, version):
|
|||
if not properties or not isinstance(properties, list):
|
||||
raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]")
|
||||
|
||||
# Check properties ending in "_ref/s" are ObjectReferenceProperties
|
||||
for prop_name, prop in properties:
|
||||
if prop_name.endswith('_ref') and ('ObjectReferenceProperty' not in get_class_hierarchy_names(prop)):
|
||||
raise ValueError(
|
||||
"'%s' is named like an object reference property but "
|
||||
"is not an ObjectReferenceProperty." % prop_name,
|
||||
)
|
||||
elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop)
|
||||
or 'ObjectReferenceProperty' not in get_class_hierarchy_names(prop.contained))):
|
||||
raise ValueError(
|
||||
"'%s' is named like an object reference list property but "
|
||||
"is not a ListProperty containing ObjectReferenceProperty." % prop_name,
|
||||
)
|
||||
if version == "2.0":
|
||||
# If using STIX2.0, check properties ending in "_ref/s" are ObjectReferenceProperties
|
||||
for prop_name, prop in properties:
|
||||
if prop_name.endswith('_ref') and ('ObjectReferenceProperty' not in get_class_hierarchy_names(prop)):
|
||||
raise ValueError(
|
||||
"'%s' is named like an object reference property but "
|
||||
"is not an ObjectReferenceProperty." % prop_name,
|
||||
)
|
||||
elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or
|
||||
'ObjectReferenceProperty' not in get_class_hierarchy_names(prop.contained))):
|
||||
raise ValueError(
|
||||
"'%s' is named like an object reference list property but "
|
||||
"is not a ListProperty containing ObjectReferenceProperty." % prop_name,
|
||||
)
|
||||
else:
|
||||
# If using STIX2.1 (or newer...), check properties ending in "_ref/s" are ReferenceProperties
|
||||
for prop_name, prop in properties:
|
||||
if prop_name.endswith('_ref') and ('ReferenceProperty' not in get_class_hierarchy_names(prop)):
|
||||
raise ValueError(
|
||||
"'%s' is named like a reference property but "
|
||||
"is not a ReferenceProperty." % prop_name,
|
||||
)
|
||||
elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or
|
||||
'ReferenceProperty' not in get_class_hierarchy_names(prop.contained))):
|
||||
raise ValueError(
|
||||
"'%s' is named like a reference list property but "
|
||||
"is not a ListProperty containing ReferenceProperty." % prop_name,
|
||||
)
|
||||
|
||||
_type = type
|
||||
_properties = OrderedDict(properties)
|
||||
|
|
|
@ -135,6 +135,7 @@ _HASH_REGEX = {
|
|||
"SHA3512": ("^[a-fA-F0-9]{128}$", "SHA3-512"),
|
||||
"SSDEEP": ("^[a-zA-Z0-9/+:.]{1,128}$", "ssdeep"),
|
||||
"WHIRLPOOL": ("^[a-fA-F0-9]{128}$", "WHIRLPOOL"),
|
||||
"TLSH": ("^[a-fA-F0-9]{70}$", "TLSH"),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -373,6 +373,10 @@ class DictionaryProperty(Property):
|
|||
"underscore (_)"
|
||||
)
|
||||
raise DictionaryKeyError(k, msg)
|
||||
|
||||
if len(dictified) < 1:
|
||||
raise ValueError("must not be empty.")
|
||||
|
||||
return dictified
|
||||
|
||||
|
||||
|
@ -391,6 +395,7 @@ HASHES_REGEX = {
|
|||
"SHA3512": (r"^[a-fA-F0-9]{128}$", "SHA3-512"),
|
||||
"SSDEEP": (r"^[a-zA-Z0-9/+:.]{1,128}$", "ssdeep"),
|
||||
"WHIRLPOOL": (r"^[a-fA-F0-9]{128}$", "WHIRLPOOL"),
|
||||
"TLSH": (r"^[a-fA-F0-9]{70}$", "TLSH"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -459,22 +464,19 @@ class ReferenceProperty(Property):
|
|||
value = value.id
|
||||
value = str(value)
|
||||
|
||||
possible_prefix = value[:value.index('--') + 2]
|
||||
possible_prefix = value[:value.index('--')]
|
||||
|
||||
if self.valid_types:
|
||||
if self.valid_types == ["only_SDO"]:
|
||||
self.valid_types = STIX2_OBJ_MAPS['v21']['objects'].keys()
|
||||
elif self.valid_types == ["only_SCO"]:
|
||||
self.valid_types = STIX2_OBJ_MAPS['v21']['observables'].keys()
|
||||
elif self.valid_types == ["only_SCO_&_SRO"]:
|
||||
self.valid_types = list(STIX2_OBJ_MAPS['v21']['observables'].keys()) + ['relationship', 'sighting']
|
||||
ref_valid_types = enumerate_types(self.valid_types, 'v' + self.spec_version.replace(".", ""))
|
||||
|
||||
if possible_prefix[:-2] in self.valid_types:
|
||||
if possible_prefix in ref_valid_types:
|
||||
required_prefix = possible_prefix
|
||||
else:
|
||||
raise ValueError("The type-specifying prefix '%s' for this property is not valid" % (possible_prefix))
|
||||
elif self.invalid_types:
|
||||
if possible_prefix[:-2] not in self.invalid_types:
|
||||
ref_invalid_types = enumerate_types(self.invalid_types, 'v' + self.spec_version.replace(".", ""))
|
||||
|
||||
if possible_prefix not in ref_invalid_types:
|
||||
required_prefix = possible_prefix
|
||||
else:
|
||||
raise ValueError("An invalid type-specifying prefix '%s' was specified for this property" % (possible_prefix, value))
|
||||
|
@ -484,6 +486,31 @@ class ReferenceProperty(Property):
|
|||
return value
|
||||
|
||||
|
||||
def enumerate_types(types, spec_version):
|
||||
"""
|
||||
`types` is meant to be a list; it may contain specific object types and/or
|
||||
the any of the words "SCO", "SDO", or "SRO"
|
||||
|
||||
Since "SCO", "SDO", and "SRO" are general types that encompass various specific object types,
|
||||
once each of those words is being processed, that word will be removed from `return_types`,
|
||||
so as not to mistakenly allow objects to be created of types "SCO", "SDO", or "SRO"
|
||||
"""
|
||||
return_types = []
|
||||
return_types += types
|
||||
|
||||
if "SDO" in types:
|
||||
return_types.remove("SDO")
|
||||
return_types += STIX2_OBJ_MAPS[spec_version]['objects'].keys()
|
||||
if "SCO" in types:
|
||||
return_types.remove("SCO")
|
||||
return_types += STIX2_OBJ_MAPS[spec_version]['observables'].keys()
|
||||
if "SRO" in types:
|
||||
return_types.remove("SRO")
|
||||
return_types += ['relationship', 'sighting']
|
||||
|
||||
return return_types
|
||||
|
||||
|
||||
SELECTOR_REGEX = re.compile(r"^[a-z0-9_-]{3,250}(\.(\[\d+\]|[a-z0-9_-]{1,250}))*$")
|
||||
|
||||
|
||||
|
|
|
@ -583,7 +583,7 @@ def test_parse_unregistered_custom_observable_object():
|
|||
"property1": "something"
|
||||
}"""
|
||||
|
||||
with pytest.raises(stix2.exceptions.CustomContentError) as excinfo:
|
||||
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
||||
stix2.parse_observable(nt_string, version='2.0')
|
||||
assert "Can't parse unknown observable type" in str(excinfo.value)
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ def test_encode_json_object():
|
|||
|
||||
def test_deterministic_id_unicode():
|
||||
mutex = {'name': u'D*Fl#Ed*\u00a3\u00a8', 'type': 'mutex'}
|
||||
obs = stix2.parse_observable(mutex, version="2.1")
|
||||
obs = stix2.parse(mutex, version="2.1")
|
||||
|
||||
dd_idx = obs.id.index("--")
|
||||
id_uuid = uuid.UUID(obs.id[dd_idx+2:])
|
||||
|
|
|
@ -22,6 +22,7 @@ EXPECTED_BUNDLE = """{
|
|||
],
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"pattern_version": "2.1",
|
||||
"valid_from": "2017-01-01T12:34:56Z"
|
||||
},
|
||||
{
|
||||
|
@ -61,6 +62,7 @@ EXPECTED_BUNDLE_DICT = {
|
|||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"pattern_version": "2.1",
|
||||
"valid_from": "2017-01-01T12:34:56Z",
|
||||
"indicator_types": [
|
||||
"malicious-activity",
|
||||
|
|
|
@ -508,7 +508,7 @@ def test_custom_observable_object_invalid_ref_property():
|
|||
)
|
||||
class NewObs():
|
||||
pass
|
||||
assert "is named like an object reference property but is not an ObjectReferenceProperty" in str(excinfo.value)
|
||||
assert "is named like a reference property but is not a ReferenceProperty" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_observable_object_invalid_refs_property():
|
||||
|
@ -520,7 +520,7 @@ def test_custom_observable_object_invalid_refs_property():
|
|||
)
|
||||
class NewObs():
|
||||
pass
|
||||
assert "is named like an object reference list property but is not a ListProperty containing ObjectReferenceProperty" in str(excinfo.value)
|
||||
assert "is named like a reference list property but is not a ListProperty containing ReferenceProperty" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_observable_object_invalid_refs_list_property():
|
||||
|
@ -532,26 +532,7 @@ def test_custom_observable_object_invalid_refs_list_property():
|
|||
)
|
||||
class NewObs():
|
||||
pass
|
||||
assert "is named like an object reference list property but is not a ListProperty containing ObjectReferenceProperty" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_observable_object_invalid_valid_refs():
|
||||
@stix2.v21.CustomObservable(
|
||||
'x-new-obs', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
('property_ref', stix2.properties.ObjectReferenceProperty(valid_types='email-addr')),
|
||||
],
|
||||
)
|
||||
class NewObs():
|
||||
pass
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
NewObs(
|
||||
_valid_refs=['1'],
|
||||
property1='something',
|
||||
property_ref='1',
|
||||
)
|
||||
assert "must be created with _valid_refs as a dict, not a list" in str(excinfo.value)
|
||||
assert "is named like a reference list property but is not a ListProperty containing ReferenceProperty" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_no_properties_raises_exception():
|
||||
|
@ -575,8 +556,7 @@ def test_parse_custom_observable_object():
|
|||
"type": "x-new-observable",
|
||||
"property1": "something"
|
||||
}"""
|
||||
|
||||
nt = stix2.parse_observable(nt_string, [], version='2.1')
|
||||
nt = stix2.parse(nt_string, [], version='2.1')
|
||||
assert isinstance(nt, stix2.base._STIXBase)
|
||||
assert nt.property1 == 'something'
|
||||
|
||||
|
@ -587,11 +567,10 @@ def test_parse_unregistered_custom_observable_object():
|
|||
"property1": "something"
|
||||
}"""
|
||||
|
||||
with pytest.raises(stix2.exceptions.CustomContentError) as excinfo:
|
||||
stix2.parse_observable(nt_string, version='2.1')
|
||||
assert "Can't parse unknown observable type" in str(excinfo.value)
|
||||
|
||||
parsed_custom = stix2.parse_observable(nt_string, allow_custom=True, version='2.1')
|
||||
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
||||
stix2.parse(nt_string, version='2.1')
|
||||
assert "Can't parse unknown object type" in str(excinfo.value)
|
||||
parsed_custom = stix2.parse(nt_string, allow_custom=True, version='2.1')
|
||||
assert parsed_custom['property1'] == 'something'
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
assert parsed_custom.property1 == 'something'
|
||||
|
@ -604,8 +583,8 @@ def test_parse_unregistered_custom_observable_object_with_no_type():
|
|||
}"""
|
||||
|
||||
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
||||
stix2.parse_observable(nt_string, allow_custom=True, version='2.1')
|
||||
assert "Can't parse observable with no 'type' property" in str(excinfo.value)
|
||||
stix2.parse(nt_string, allow_custom=True, version='2.1')
|
||||
assert "Can't parse object with no 'type' property" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_parse_observed_data_with_custom_observable():
|
||||
|
@ -634,8 +613,8 @@ def test_parse_invalid_custom_observable_object():
|
|||
}"""
|
||||
|
||||
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
||||
stix2.parse_observable(nt_string, version='2.1')
|
||||
assert "Can't parse observable with no 'type' property" in str(excinfo.value)
|
||||
stix2.parse(nt_string, version='2.1')
|
||||
assert "Can't parse object with no 'type' property" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_observable_custom_property():
|
||||
|
@ -885,8 +864,7 @@ def test_parse_observable_with_custom_extension():
|
|||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
parsed = stix2.parse_observable(input_str, version='2.1')
|
||||
parsed = stix2.parse(input_str, version='2.1')
|
||||
assert parsed.extensions['x-new-ext'].property2 == 12
|
||||
|
||||
|
||||
|
@ -961,10 +939,9 @@ def test_custom_and_spec_extension_mix():
|
|||
)
|
||||
def test_parse_observable_with_unregistered_custom_extension(data):
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.parse_observable(data, version='2.1')
|
||||
stix2.parse(data, version='2.1')
|
||||
assert "Can't parse unknown extension type" in str(excinfo.value)
|
||||
|
||||
parsed_ob = stix2.parse_observable(data, allow_custom=True, version='2.1')
|
||||
parsed_ob = stix2.parse(data, allow_custom=True, version='2.1')
|
||||
assert parsed_ob['extensions']['x-foobar-ext']['property1'] == 'foo'
|
||||
assert not isinstance(parsed_ob['extensions']['x-foobar-ext'], stix2.base._STIXBase)
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ EXPECTED_INDICATOR = """{
|
|||
],
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"pattern_version": "2.1",
|
||||
"valid_from": "1970-01-01T00:00:01Z"
|
||||
}"""
|
||||
|
||||
|
@ -31,6 +32,7 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
|
|||
indicator_types=['malicious-activity'],
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
pattern_type='stix',
|
||||
pattern_version='2.1',
|
||||
valid_from='1970-01-01T00:00:01Z'
|
||||
""".split()) + ")"
|
||||
|
||||
|
|
|
@ -209,7 +209,7 @@ def test_observed_data_example_with_bad_refs():
|
|||
|
||||
assert excinfo.value.cls == stix2.v21.Directory
|
||||
assert excinfo.value.prop_name == "contains_refs"
|
||||
assert "The type-specifying prefix 'monkey--' for this property is not valid" in excinfo.value.reason
|
||||
assert "The type-specifying prefix 'monkey' for this property is not valid" in excinfo.value.reason
|
||||
|
||||
|
||||
def test_observed_data_example_with_non_dictionary():
|
||||
|
@ -369,7 +369,7 @@ def test_parse_autonomous_system_valid(data):
|
|||
],
|
||||
)
|
||||
def test_parse_email_address(data):
|
||||
odata = stix2.parse_observable(data, version='2.1')
|
||||
odata = stix2.parse(data, version='2.1')
|
||||
assert odata.type == "email-addr"
|
||||
|
||||
odata_str = re.compile(
|
||||
|
@ -378,7 +378,7 @@ def test_parse_email_address(data):
|
|||
'"belongs_to_ref": "mutex--9be6365f-b89c-48c0-9340-6953f6595718"', data,
|
||||
)
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError):
|
||||
stix2.parse_observable(odata_str, version='2.1')
|
||||
stix2.parse(odata_str, version='2.1')
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -424,7 +424,7 @@ def test_parse_email_address(data):
|
|||
],
|
||||
)
|
||||
def test_parse_email_message(data):
|
||||
odata = stix2.parse_observable(data, version='2.1')
|
||||
odata = stix2.parse(data, version='2.1')
|
||||
assert odata.type == "email-message"
|
||||
assert odata.body_multipart[0].content_disposition == "inline"
|
||||
|
||||
|
@ -446,7 +446,7 @@ def test_parse_email_message(data):
|
|||
)
|
||||
def test_parse_email_message_not_multipart(data):
|
||||
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
|
||||
stix2.parse_observable(data, version='2.1')
|
||||
stix2.parse(data, version='2.1')
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.EmailMessage
|
||||
assert excinfo.value.dependencies == [("is_multipart", "body")]
|
||||
|
@ -548,7 +548,7 @@ def test_parse_file_archive(data):
|
|||
)
|
||||
def test_parse_email_message_with_at_least_one_error(data):
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.parse_observable(data, version='2.1')
|
||||
stix2.parse(data, version='2.1')
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.EmailMessage
|
||||
assert "At least one of the" in str(excinfo.value)
|
||||
|
@ -570,7 +570,7 @@ def test_parse_email_message_with_at_least_one_error(data):
|
|||
],
|
||||
)
|
||||
def test_parse_basic_tcp_traffic(data):
|
||||
odata = stix2.parse_observable(
|
||||
odata = stix2.parse(
|
||||
data, version='2.1',
|
||||
)
|
||||
|
||||
|
@ -602,7 +602,7 @@ def test_parse_basic_tcp_traffic(data):
|
|||
)
|
||||
def test_parse_basic_tcp_traffic_with_error(data):
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
stix2.parse_observable(data, version='2.1')
|
||||
stix2.parse(data, version='2.1')
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.NetworkTraffic
|
||||
assert excinfo.value.properties == ["dst_ref", "src_ref"]
|
||||
|
@ -1117,6 +1117,28 @@ def test_network_traffic_socket_example():
|
|||
assert nt.extensions['socket-ext'].socket_type == "SOCK_STREAM"
|
||||
|
||||
|
||||
def test_incorrect_socket_options():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.v21.SocketExt(
|
||||
is_listening=True,
|
||||
address_family="AF_INET",
|
||||
protocol_family="PF_INET",
|
||||
socket_type="SOCK_STREAM",
|
||||
options={"RCVTIMEO": 100},
|
||||
)
|
||||
assert "Incorrect options key" == str(excinfo.value)
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.v21.SocketExt(
|
||||
is_listening=True,
|
||||
address_family="AF_INET",
|
||||
protocol_family="PF_INET",
|
||||
socket_type="SOCK_STREAM",
|
||||
options={"SO_RCVTIMEO": '100'},
|
||||
)
|
||||
assert "Options value must be an integer" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_network_traffic_tcp_example():
|
||||
h = stix2.v21.TCPExt(src_flags_hex="00000002")
|
||||
nt = stix2.v21.NetworkTraffic(
|
||||
|
@ -1366,6 +1388,18 @@ def test_x509_certificate_example():
|
|||
assert x509.subject == "C=US, ST=Maryland, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org" # noqa
|
||||
|
||||
|
||||
def test_x509_certificate_error():
|
||||
|
||||
with pytest.raises(stix2.exceptions.PropertyPresenceError) as excinfo:
|
||||
stix2.v21.X509Certificate(
|
||||
defanged=True,
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.X509Certificate
|
||||
assert "At least one of the" in str(excinfo.value)
|
||||
assert "properties for X509Certificate must be populated." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_new_version_with_related_objects():
|
||||
data = stix2.v21.ObservedData(
|
||||
first_observed="2016-03-12T12:00:00Z",
|
||||
|
|
|
@ -72,6 +72,14 @@ def test_list_property():
|
|||
p.clean([])
|
||||
|
||||
|
||||
def test_dictionary_property():
|
||||
p = DictionaryProperty(StringProperty)
|
||||
|
||||
assert p.clean({'spec_version': '2.1'})
|
||||
with pytest.raises(ValueError):
|
||||
p.clean({})
|
||||
|
||||
|
||||
def test_string_property():
|
||||
prop = StringProperty()
|
||||
|
||||
|
@ -411,6 +419,7 @@ def test_property_list_of_dictionary():
|
|||
"value", [
|
||||
{"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"},
|
||||
[('MD5', '2dfb1bcc980200c6706feee399d41b3f'), ('RIPEMD-160', 'b3a8cd8a27c90af79b3c81754f267780f443dfef')],
|
||||
[('TLSH', '6FF02BEF718027B0160B4391212923ED7F1A463D563B1549B86CF62973B197AD2731F8')],
|
||||
],
|
||||
)
|
||||
def test_hashes_property_valid(value):
|
||||
|
@ -422,6 +431,7 @@ def test_hashes_property_valid(value):
|
|||
"value", [
|
||||
{"MD5": "a"},
|
||||
{"SHA-256": "2dfb1bcc980200c6706feee399d41b3f"},
|
||||
{"TLSH": "6FF02BEF718027B0160B4391212923ED7F1A463D563B1549B86CF62973B197AD2731F"},
|
||||
],
|
||||
)
|
||||
def test_hashes_property_invalid(value):
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
"""Utility functions and classes for the STIX2 library."""
|
||||
|
||||
from collections import Mapping
|
||||
try:
|
||||
from collections.abc import Mapping
|
||||
except ImportError:
|
||||
from collections import Mapping
|
||||
import copy
|
||||
import datetime as dt
|
||||
import json
|
||||
|
|
|
@ -233,7 +233,7 @@ class Report(STIXDomainObject):
|
|||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('published', TimestampProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(invalid_types=[""], spec_version='2.0'), required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.0'), required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty, required=True)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
|
|
|
@ -70,7 +70,7 @@ class Sighting(STIXRelationshipObject):
|
|||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
('count', IntegerProperty(min=0, max=999999999)),
|
||||
('sighting_of_ref', ReferenceProperty(valid_types="only_SDO", spec_version='2.0', required=True)),
|
||||
('sighting_of_ref', ReferenceProperty(valid_types="SDO", spec_version='2.0', required=True)),
|
||||
('observed_data_refs', ListProperty(ReferenceProperty(valid_types='observed-data', spec_version='2.0'))),
|
||||
('where_sighted_refs', ListProperty(ReferenceProperty(valid_types='identity', spec_version='2.0'))),
|
||||
('summary', BooleanProperty(default=lambda: False)),
|
||||
|
|
|
@ -76,7 +76,7 @@ class LanguageContent(_STIXBase):
|
|||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('object_ref', ReferenceProperty(invalid_types=[""], spec_version='2.1', required=True)),
|
||||
('object_ref', ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1', required=True)),
|
||||
# TODO: 'object_modified' it MUST be an exact match for the modified time of the STIX Object (SRO or SDO) being referenced.
|
||||
('object_modified', TimestampProperty(precision='millisecond')),
|
||||
# TODO: 'contents' https://docs.google.com/document/d/1ShNq4c3e1CkfANmD9O--mdZ5H0O_GLnjN28a_yrEaco/edit#heading=h.cfz5hcantmvx
|
||||
|
|
|
@ -385,7 +385,7 @@ class File(_Observable):
|
|||
('mtime', TimestampProperty()),
|
||||
('atime', TimestampProperty()),
|
||||
('parent_directory_ref', ReferenceProperty(valid_types='directory', spec_version='2.1')),
|
||||
('contains_refs', ListProperty(ReferenceProperty(invalid_types="", spec_version='2.1'))),
|
||||
('contains_refs', ListProperty(ReferenceProperty(valid_types=["SCO"], spec_version='2.1'))),
|
||||
('content_ref', ReferenceProperty(valid_types='artifact', spec_version='2.1')),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
|
@ -592,6 +592,18 @@ class SocketExt(_Extension):
|
|||
('socket_handle', IntegerProperty()),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(SocketExt, self)._check_object_constraints()
|
||||
|
||||
options = self.get('options')
|
||||
|
||||
if options is not None:
|
||||
for key, val in options.items():
|
||||
if key[:3] != "SO_":
|
||||
raise ValueError("Incorrect options key")
|
||||
if not isinstance(val, int):
|
||||
raise ValueError("Options value must be an integer")
|
||||
|
||||
|
||||
class TCPExt(_Extension):
|
||||
# TODO: Add link
|
||||
|
@ -986,6 +998,18 @@ class X509Certificate(_Observable):
|
|||
])
|
||||
_id_contributing_properties = ["hashes", "serial_number"]
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(X509Certificate, self)._check_object_constraints()
|
||||
|
||||
att_list = [
|
||||
'is_self_signed', 'hashes', 'version', 'serial_number',
|
||||
'signature_algorithm', 'issuer', 'validity_not_before',
|
||||
'validity_not_after', 'subject', 'subject_public_key_algorithm',
|
||||
'subject_public_key_modulus', 'subject_public_key_exponent',
|
||||
'x509_v3_extensions',
|
||||
]
|
||||
self._check_at_least_one_property(att_list)
|
||||
|
||||
|
||||
def CustomObservable(type='x-custom-observable', properties=None):
|
||||
"""Custom STIX Cyber Observable Object type decorator.
|
||||
|
@ -1004,6 +1028,7 @@ def CustomObservable(type='x-custom-observable', properties=None):
|
|||
def wrapper(cls):
|
||||
_properties = list(itertools.chain.from_iterable([
|
||||
[('type', TypeProperty(type))],
|
||||
[('id', IDProperty(type, spec_version='2.1'))],
|
||||
properties,
|
||||
[('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=type))],
|
||||
]))
|
||||
|
|
|
@ -149,7 +149,7 @@ class Grouping(STIXDomainObject):
|
|||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('context', StringProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(invalid_types=[""], spec_version='2.1'), required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -215,6 +215,13 @@ class Indicator(STIXDomainObject):
|
|||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
if kwargs.get('pattern') and kwargs.get('pattern_type') == 'stix' and not kwargs.get('pattern_version'):
|
||||
kwargs['pattern_version'] = '2.1'
|
||||
|
||||
super(STIXDomainObject, self).__init__(*args, **kwargs)
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(Indicator, self)._check_object_constraints()
|
||||
|
||||
|
@ -505,7 +512,7 @@ class MalwareAnalysis(STIXDomainObject):
|
|||
('analysis_started', TimestampProperty()),
|
||||
('analysis_ended', TimestampProperty()),
|
||||
('av_result', StringProperty()),
|
||||
('analysis_sco_refs', ListProperty(ReferenceProperty(valid_types="only_SCO", spec_version='2.1'))),
|
||||
('analysis_sco_refs', ListProperty(ReferenceProperty(valid_types="SCO", spec_version='2.1'))),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
|
@ -531,7 +538,7 @@ class Note(STIXDomainObject):
|
|||
('abstract', StringProperty()),
|
||||
('content', StringProperty(required=True)),
|
||||
('authors', ListProperty(StringProperty)),
|
||||
('object_refs', ListProperty(ReferenceProperty(invalid_types=[""], spec_version='2.1'), required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
|
@ -560,7 +567,7 @@ class ObservedData(STIXDomainObject):
|
|||
('last_observed', TimestampProperty(required=True)),
|
||||
('number_observed', IntegerProperty(min=1, max=999999999, required=True)),
|
||||
('objects', ObservableProperty(spec_version='2.1')),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types="only_SCO_&_SRO", spec_version="2.1"))),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SRO"], spec_version="2.1"))),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
|
@ -625,7 +632,7 @@ class Opinion(STIXDomainObject):
|
|||
], required=True,
|
||||
),
|
||||
),
|
||||
('object_refs', ListProperty(ReferenceProperty(invalid_types=[""], spec_version='2.1'), required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
|
@ -654,7 +661,7 @@ class Report(STIXDomainObject):
|
|||
('description', StringProperty()),
|
||||
('report_types', ListProperty(StringProperty, required=True)),
|
||||
('published', TimestampProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(invalid_types=[""], spec_version='2.1'), required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
|
|
|
@ -89,7 +89,7 @@ class Sighting(STIXRelationshipObject):
|
|||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
('count', IntegerProperty(min=0, max=999999999)),
|
||||
('sighting_of_ref', ReferenceProperty(valid_types="only_SDO", spec_version='2.1', required=True)),
|
||||
('sighting_of_ref', ReferenceProperty(valid_types="SDO", spec_version='2.1', required=True)),
|
||||
('observed_data_refs', ListProperty(ReferenceProperty(valid_types='observed-data', spec_version='2.1'))),
|
||||
('where_sighted_refs', ListProperty(ReferenceProperty(valid_types='identity', spec_version='2.1'))),
|
||||
('summary', BooleanProperty()),
|
||||
|
|
Loading…
Reference in New Issue