Merge branch 'master' of github.com:oasis-open/cti-python-stix2

master
chrisr3d 2019-12-12 22:59:39 +01:00
commit 31d944b159
19 changed files with 190 additions and 86 deletions

View File

@ -52,6 +52,7 @@ To parse a STIX JSON string into a Python STIX object, use ``parse()``:
"indicator_types": [ "indicator_types": [
"malicious-activity" "malicious-activity"
], ],
"pattern_type": "stix",
"pattern": "[file:hashes.md5 ='d41d8cd98f00b204e9800998ecf8427e']", "pattern": "[file:hashes.md5 ='d41d8cd98f00b204e9800998ecf8427e']",
"valid_from": "2017-09-26T23:33:39.829952Z" "valid_from": "2017-09-26T23:33:39.829952Z"
}""") }""")

View File

@ -8,7 +8,7 @@ import re
import stix2 import stix2
from .base import _STIXBase from .base import _STIXBase
from .exceptions import CustomContentError, ParseError from .exceptions import ParseError
from .markings import _MarkingsMixin from .markings import _MarkingsMixin
from .utils import _get_dict from .utils import _get_dict
@ -126,7 +126,7 @@ def dict_to_stix2(stix_dict, allow_custom=False, interoperability=False, version
# '2.0' representation. # '2.0' representation.
v = 'v20' v = 'v20'
OBJ_MAP = STIX2_OBJ_MAPS[v]['objects'] OBJ_MAP = dict(STIX2_OBJ_MAPS[v]['objects'], **STIX2_OBJ_MAPS[v]['observables'])
try: try:
obj_class = OBJ_MAP[stix_dict['type']] 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 # flag allows for unknown custom objects too, but will not
# be parsed into STIX observable object, just returned as is # be parsed into STIX observable object, just returned as is
return obj return obj
raise CustomContentError("Can't parse unknown observable type '%s'! For custom observables, " raise ParseError("Can't parse unknown observable type '%s'! For custom observables, "
"use the CustomObservable decorator." % obj['type']) "use the CustomObservable decorator." % obj['type'])
return obj_class(allow_custom=allow_custom, **obj) return obj_class(allow_custom=allow_custom, **obj)

View File

@ -67,19 +67,34 @@ def _custom_observable_builder(cls, type, properties, version):
if not properties or not isinstance(properties, list): if not properties or not isinstance(properties, list):
raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]") raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]")
# Check properties ending in "_ref/s" are ObjectReferenceProperties if version == "2.0":
for prop_name, prop in properties: # If using STIX2.0, check properties ending in "_ref/s" are ObjectReferenceProperties
if prop_name.endswith('_ref') and ('ObjectReferenceProperty' not in get_class_hierarchy_names(prop)): for prop_name, prop in properties:
raise ValueError( if prop_name.endswith('_ref') and ('ObjectReferenceProperty' not in get_class_hierarchy_names(prop)):
"'%s' is named like an object reference property but " raise ValueError(
"is not an ObjectReferenceProperty." % prop_name, "'%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))): elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or
raise ValueError( 'ObjectReferenceProperty' not in get_class_hierarchy_names(prop.contained))):
"'%s' is named like an object reference list property but " raise ValueError(
"is not a ListProperty containing ObjectReferenceProperty." % prop_name, "'%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 _type = type
_properties = OrderedDict(properties) _properties = OrderedDict(properties)

View File

@ -135,6 +135,7 @@ _HASH_REGEX = {
"SHA3512": ("^[a-fA-F0-9]{128}$", "SHA3-512"), "SHA3512": ("^[a-fA-F0-9]{128}$", "SHA3-512"),
"SSDEEP": ("^[a-zA-Z0-9/+:.]{1,128}$", "ssdeep"), "SSDEEP": ("^[a-zA-Z0-9/+:.]{1,128}$", "ssdeep"),
"WHIRLPOOL": ("^[a-fA-F0-9]{128}$", "WHIRLPOOL"), "WHIRLPOOL": ("^[a-fA-F0-9]{128}$", "WHIRLPOOL"),
"TLSH": ("^[a-fA-F0-9]{70}$", "TLSH"),
} }

View File

@ -373,6 +373,10 @@ class DictionaryProperty(Property):
"underscore (_)" "underscore (_)"
) )
raise DictionaryKeyError(k, msg) raise DictionaryKeyError(k, msg)
if len(dictified) < 1:
raise ValueError("must not be empty.")
return dictified return dictified
@ -391,6 +395,7 @@ HASHES_REGEX = {
"SHA3512": (r"^[a-fA-F0-9]{128}$", "SHA3-512"), "SHA3512": (r"^[a-fA-F0-9]{128}$", "SHA3-512"),
"SSDEEP": (r"^[a-zA-Z0-9/+:.]{1,128}$", "ssdeep"), "SSDEEP": (r"^[a-zA-Z0-9/+:.]{1,128}$", "ssdeep"),
"WHIRLPOOL": (r"^[a-fA-F0-9]{128}$", "WHIRLPOOL"), "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 = value.id
value = str(value) value = str(value)
possible_prefix = value[:value.index('--') + 2] possible_prefix = value[:value.index('--')]
if self.valid_types: if self.valid_types:
if self.valid_types == ["only_SDO"]: ref_valid_types = enumerate_types(self.valid_types, 'v' + self.spec_version.replace(".", ""))
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']
if possible_prefix[:-2] in self.valid_types: if possible_prefix in ref_valid_types:
required_prefix = possible_prefix required_prefix = possible_prefix
else: else:
raise ValueError("The type-specifying prefix '%s' for this property is not valid" % (possible_prefix)) raise ValueError("The type-specifying prefix '%s' for this property is not valid" % (possible_prefix))
elif self.invalid_types: 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 required_prefix = possible_prefix
else: else:
raise ValueError("An invalid type-specifying prefix '%s' was specified for this property" % (possible_prefix, value)) 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 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}))*$") SELECTOR_REGEX = re.compile(r"^[a-z0-9_-]{3,250}(\.(\[\d+\]|[a-z0-9_-]{1,250}))*$")

View File

@ -583,7 +583,7 @@ def test_parse_unregistered_custom_observable_object():
"property1": "something" "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') stix2.parse_observable(nt_string, version='2.0')
assert "Can't parse unknown observable type" in str(excinfo.value) assert "Can't parse unknown observable type" in str(excinfo.value)

View File

@ -29,7 +29,7 @@ def test_encode_json_object():
def test_deterministic_id_unicode(): def test_deterministic_id_unicode():
mutex = {'name': u'D*Fl#Ed*\u00a3\u00a8', 'type': 'mutex'} 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("--") dd_idx = obs.id.index("--")
id_uuid = uuid.UUID(obs.id[dd_idx+2:]) id_uuid = uuid.UUID(obs.id[dd_idx+2:])

View File

@ -22,6 +22,7 @@ EXPECTED_BUNDLE = """{
], ],
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
"pattern_type": "stix", "pattern_type": "stix",
"pattern_version": "2.1",
"valid_from": "2017-01-01T12:34:56Z" "valid_from": "2017-01-01T12:34:56Z"
}, },
{ {
@ -61,6 +62,7 @@ EXPECTED_BUNDLE_DICT = {
"modified": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z",
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
"pattern_type": "stix", "pattern_type": "stix",
"pattern_version": "2.1",
"valid_from": "2017-01-01T12:34:56Z", "valid_from": "2017-01-01T12:34:56Z",
"indicator_types": [ "indicator_types": [
"malicious-activity", "malicious-activity",

View File

@ -508,7 +508,7 @@ def test_custom_observable_object_invalid_ref_property():
) )
class NewObs(): class NewObs():
pass 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(): def test_custom_observable_object_invalid_refs_property():
@ -520,7 +520,7 @@ def test_custom_observable_object_invalid_refs_property():
) )
class NewObs(): class NewObs():
pass 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(): def test_custom_observable_object_invalid_refs_list_property():
@ -532,26 +532,7 @@ def test_custom_observable_object_invalid_refs_list_property():
) )
class NewObs(): class NewObs():
pass 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_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)
def test_custom_no_properties_raises_exception(): def test_custom_no_properties_raises_exception():
@ -575,8 +556,7 @@ def test_parse_custom_observable_object():
"type": "x-new-observable", "type": "x-new-observable",
"property1": "something" "property1": "something"
}""" }"""
nt = stix2.parse(nt_string, [], version='2.1')
nt = stix2.parse_observable(nt_string, [], version='2.1')
assert isinstance(nt, stix2.base._STIXBase) assert isinstance(nt, stix2.base._STIXBase)
assert nt.property1 == 'something' assert nt.property1 == 'something'
@ -587,11 +567,10 @@ def test_parse_unregistered_custom_observable_object():
"property1": "something" "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.1') stix2.parse(nt_string, version='2.1')
assert "Can't parse unknown observable type" in str(excinfo.value) assert "Can't parse unknown object type" in str(excinfo.value)
parsed_custom = stix2.parse(nt_string, allow_custom=True, version='2.1')
parsed_custom = stix2.parse_observable(nt_string, allow_custom=True, version='2.1')
assert parsed_custom['property1'] == 'something' assert parsed_custom['property1'] == 'something'
with pytest.raises(AttributeError) as excinfo: with pytest.raises(AttributeError) as excinfo:
assert parsed_custom.property1 == 'something' 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: with pytest.raises(stix2.exceptions.ParseError) as excinfo:
stix2.parse_observable(nt_string, allow_custom=True, version='2.1') stix2.parse(nt_string, allow_custom=True, version='2.1')
assert "Can't parse observable with no 'type' property" in str(excinfo.value) assert "Can't parse object with no 'type' property" in str(excinfo.value)
def test_parse_observed_data_with_custom_observable(): 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: with pytest.raises(stix2.exceptions.ParseError) as excinfo:
stix2.parse_observable(nt_string, version='2.1') stix2.parse(nt_string, version='2.1')
assert "Can't parse observable with no 'type' property" in str(excinfo.value) assert "Can't parse object with no 'type' property" in str(excinfo.value)
def test_observable_custom_property(): def test_observable_custom_property():
@ -885,8 +864,7 @@ def test_parse_observable_with_custom_extension():
} }
} }
}""" }"""
parsed = stix2.parse(input_str, version='2.1')
parsed = stix2.parse_observable(input_str, version='2.1')
assert parsed.extensions['x-new-ext'].property2 == 12 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): def test_parse_observable_with_unregistered_custom_extension(data):
with pytest.raises(InvalidValueError) as excinfo: 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) assert "Can't parse unknown extension type" in str(excinfo.value)
parsed_ob = stix2.parse(data, allow_custom=True, version='2.1')
parsed_ob = stix2.parse_observable(data, allow_custom=True, version='2.1')
assert parsed_ob['extensions']['x-foobar-ext']['property1'] == 'foo' assert parsed_ob['extensions']['x-foobar-ext']['property1'] == 'foo'
assert not isinstance(parsed_ob['extensions']['x-foobar-ext'], stix2.base._STIXBase) assert not isinstance(parsed_ob['extensions']['x-foobar-ext'], stix2.base._STIXBase)

View File

@ -19,6 +19,7 @@ EXPECTED_INDICATOR = """{
], ],
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
"pattern_type": "stix", "pattern_type": "stix",
"pattern_version": "2.1",
"valid_from": "1970-01-01T00:00:01Z" "valid_from": "1970-01-01T00:00:01Z"
}""" }"""
@ -31,6 +32,7 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
indicator_types=['malicious-activity'], indicator_types=['malicious-activity'],
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
pattern_type='stix', pattern_type='stix',
pattern_version='2.1',
valid_from='1970-01-01T00:00:01Z' valid_from='1970-01-01T00:00:01Z'
""".split()) + ")" """.split()) + ")"

View File

@ -209,7 +209,7 @@ def test_observed_data_example_with_bad_refs():
assert excinfo.value.cls == stix2.v21.Directory assert excinfo.value.cls == stix2.v21.Directory
assert excinfo.value.prop_name == "contains_refs" 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(): 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): 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" assert odata.type == "email-addr"
odata_str = re.compile( odata_str = re.compile(
@ -378,7 +378,7 @@ def test_parse_email_address(data):
'"belongs_to_ref": "mutex--9be6365f-b89c-48c0-9340-6953f6595718"', data, '"belongs_to_ref": "mutex--9be6365f-b89c-48c0-9340-6953f6595718"', data,
) )
with pytest.raises(stix2.exceptions.InvalidValueError): with pytest.raises(stix2.exceptions.InvalidValueError):
stix2.parse_observable(odata_str, version='2.1') stix2.parse(odata_str, version='2.1')
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -424,7 +424,7 @@ def test_parse_email_address(data):
], ],
) )
def test_parse_email_message(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.type == "email-message"
assert odata.body_multipart[0].content_disposition == "inline" 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): def test_parse_email_message_not_multipart(data):
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo: 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.cls == stix2.v21.EmailMessage
assert excinfo.value.dependencies == [("is_multipart", "body")] 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): def test_parse_email_message_with_at_least_one_error(data):
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: 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 excinfo.value.cls == stix2.v21.EmailMessage
assert "At least one of the" in str(excinfo.value) 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): def test_parse_basic_tcp_traffic(data):
odata = stix2.parse_observable( odata = stix2.parse(
data, version='2.1', data, version='2.1',
) )
@ -602,7 +602,7 @@ def test_parse_basic_tcp_traffic(data):
) )
def test_parse_basic_tcp_traffic_with_error(data): def test_parse_basic_tcp_traffic_with_error(data):
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: 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.cls == stix2.v21.NetworkTraffic
assert excinfo.value.properties == ["dst_ref", "src_ref"] 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" 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(): def test_network_traffic_tcp_example():
h = stix2.v21.TCPExt(src_flags_hex="00000002") h = stix2.v21.TCPExt(src_flags_hex="00000002")
nt = stix2.v21.NetworkTraffic( 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 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(): def test_new_version_with_related_objects():
data = stix2.v21.ObservedData( data = stix2.v21.ObservedData(
first_observed="2016-03-12T12:00:00Z", first_observed="2016-03-12T12:00:00Z",

View File

@ -72,6 +72,14 @@ def test_list_property():
p.clean([]) 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(): def test_string_property():
prop = StringProperty() prop = StringProperty()
@ -411,6 +419,7 @@ def test_property_list_of_dictionary():
"value", [ "value", [
{"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"}, {"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"},
[('MD5', '2dfb1bcc980200c6706feee399d41b3f'), ('RIPEMD-160', 'b3a8cd8a27c90af79b3c81754f267780f443dfef')], [('MD5', '2dfb1bcc980200c6706feee399d41b3f'), ('RIPEMD-160', 'b3a8cd8a27c90af79b3c81754f267780f443dfef')],
[('TLSH', '6FF02BEF718027B0160B4391212923ED7F1A463D563B1549B86CF62973B197AD2731F8')],
], ],
) )
def test_hashes_property_valid(value): def test_hashes_property_valid(value):
@ -422,6 +431,7 @@ def test_hashes_property_valid(value):
"value", [ "value", [
{"MD5": "a"}, {"MD5": "a"},
{"SHA-256": "2dfb1bcc980200c6706feee399d41b3f"}, {"SHA-256": "2dfb1bcc980200c6706feee399d41b3f"},
{"TLSH": "6FF02BEF718027B0160B4391212923ED7F1A463D563B1549B86CF62973B197AD2731F"},
], ],
) )
def test_hashes_property_invalid(value): def test_hashes_property_invalid(value):

View File

@ -1,6 +1,9 @@
"""Utility functions and classes for the STIX2 library.""" """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 copy
import datetime as dt import datetime as dt
import json import json

View File

@ -233,7 +233,7 @@ class Report(STIXDomainObject):
('name', StringProperty(required=True)), ('name', StringProperty(required=True)),
('description', StringProperty()), ('description', StringProperty()),
('published', TimestampProperty(required=True)), ('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)), ('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty, required=True)), ('labels', ListProperty(StringProperty, required=True)),
('external_references', ListProperty(ExternalReference)), ('external_references', ListProperty(ExternalReference)),

View File

@ -70,7 +70,7 @@ class Sighting(STIXRelationshipObject):
('first_seen', TimestampProperty()), ('first_seen', TimestampProperty()),
('last_seen', TimestampProperty()), ('last_seen', TimestampProperty()),
('count', IntegerProperty(min=0, max=999999999)), ('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'))), ('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'))), ('where_sighted_refs', ListProperty(ReferenceProperty(valid_types='identity', spec_version='2.0'))),
('summary', BooleanProperty(default=lambda: False)), ('summary', BooleanProperty(default=lambda: False)),

View File

@ -76,7 +76,7 @@ class LanguageContent(_STIXBase):
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('modified', 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. # 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')), ('object_modified', TimestampProperty(precision='millisecond')),
# TODO: 'contents' https://docs.google.com/document/d/1ShNq4c3e1CkfANmD9O--mdZ5H0O_GLnjN28a_yrEaco/edit#heading=h.cfz5hcantmvx # TODO: 'contents' https://docs.google.com/document/d/1ShNq4c3e1CkfANmD9O--mdZ5H0O_GLnjN28a_yrEaco/edit#heading=h.cfz5hcantmvx

View File

@ -385,7 +385,7 @@ class File(_Observable):
('mtime', TimestampProperty()), ('mtime', TimestampProperty()),
('atime', TimestampProperty()), ('atime', TimestampProperty()),
('parent_directory_ref', ReferenceProperty(valid_types='directory', spec_version='2.1')), ('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')), ('content_ref', ReferenceProperty(valid_types='artifact', spec_version='2.1')),
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)), ('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
('spec_version', StringProperty(fixed='2.1')), ('spec_version', StringProperty(fixed='2.1')),
@ -592,6 +592,18 @@ class SocketExt(_Extension):
('socket_handle', IntegerProperty()), ('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): class TCPExt(_Extension):
# TODO: Add link # TODO: Add link
@ -986,6 +998,18 @@ class X509Certificate(_Observable):
]) ])
_id_contributing_properties = ["hashes", "serial_number"] _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): def CustomObservable(type='x-custom-observable', properties=None):
"""Custom STIX Cyber Observable Object type decorator. """Custom STIX Cyber Observable Object type decorator.
@ -1004,6 +1028,7 @@ def CustomObservable(type='x-custom-observable', properties=None):
def wrapper(cls): def wrapper(cls):
_properties = list(itertools.chain.from_iterable([ _properties = list(itertools.chain.from_iterable([
[('type', TypeProperty(type))], [('type', TypeProperty(type))],
[('id', IDProperty(type, spec_version='2.1'))],
properties, properties,
[('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=type))], [('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=type))],
])) ]))

View File

@ -149,7 +149,7 @@ class Grouping(STIXDomainObject):
('name', StringProperty()), ('name', StringProperty()),
('description', StringProperty()), ('description', StringProperty()),
('context', StringProperty(required=True)), ('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)), ('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): def _check_object_constraints(self):
super(Indicator, self)._check_object_constraints() super(Indicator, self)._check_object_constraints()
@ -505,7 +512,7 @@ class MalwareAnalysis(STIXDomainObject):
('analysis_started', TimestampProperty()), ('analysis_started', TimestampProperty()),
('analysis_ended', TimestampProperty()), ('analysis_ended', TimestampProperty()),
('av_result', StringProperty()), ('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): def _check_object_constraints(self):
@ -531,7 +538,7 @@ class Note(STIXDomainObject):
('abstract', StringProperty()), ('abstract', StringProperty()),
('content', StringProperty(required=True)), ('content', StringProperty(required=True)),
('authors', ListProperty(StringProperty)), ('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)), ('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)), ('labels', ListProperty(StringProperty)),
('confidence', IntegerProperty()), ('confidence', IntegerProperty()),
@ -560,7 +567,7 @@ class ObservedData(STIXDomainObject):
('last_observed', TimestampProperty(required=True)), ('last_observed', TimestampProperty(required=True)),
('number_observed', IntegerProperty(min=1, max=999999999, required=True)), ('number_observed', IntegerProperty(min=1, max=999999999, required=True)),
('objects', ObservableProperty(spec_version='2.1')), ('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)), ('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)), ('labels', ListProperty(StringProperty)),
('confidence', IntegerProperty()), ('confidence', IntegerProperty()),
@ -625,7 +632,7 @@ class Opinion(STIXDomainObject):
], required=True, ], 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)), ('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)), ('labels', ListProperty(StringProperty)),
('confidence', IntegerProperty()), ('confidence', IntegerProperty()),
@ -654,7 +661,7 @@ class Report(STIXDomainObject):
('description', StringProperty()), ('description', StringProperty()),
('report_types', ListProperty(StringProperty, required=True)), ('report_types', ListProperty(StringProperty, required=True)),
('published', TimestampProperty(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)), ('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)), ('labels', ListProperty(StringProperty)),
('confidence', IntegerProperty()), ('confidence', IntegerProperty()),

View File

@ -89,7 +89,7 @@ class Sighting(STIXRelationshipObject):
('first_seen', TimestampProperty()), ('first_seen', TimestampProperty()),
('last_seen', TimestampProperty()), ('last_seen', TimestampProperty()),
('count', IntegerProperty(min=0, max=999999999)), ('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'))), ('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'))), ('where_sighted_refs', ListProperty(ReferenceProperty(valid_types='identity', spec_version='2.1'))),
('summary', BooleanProperty()), ('summary', BooleanProperty()),