diff --git a/stix2/patterns.py b/stix2/patterns.py index 9656ff1..2e149be 100644 --- a/stix2/patterns.py +++ b/stix2/patterns.py @@ -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"), } diff --git a/stix2/properties.py b/stix2/properties.py index c956a08..f6211c8 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -363,6 +363,10 @@ class DictionaryProperty(Property): "underscore (_)" ) raise DictionaryKeyError(k, msg) + + if len(dictified) < 1: + raise ValueError("must not be empty.") + return dictified @@ -381,6 +385,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"), } diff --git a/stix2/test/v21/test_bundle.py b/stix2/test/v21/test_bundle.py index 54ef318..4e30c84 100644 --- a/stix2/test/v21/test_bundle.py +++ b/stix2/test/v21/test_bundle.py @@ -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", diff --git a/stix2/test/v21/test_indicator.py b/stix2/test/v21/test_indicator.py index ea46d6d..0562dfd 100644 --- a/stix2/test/v21/test_indicator.py +++ b/stix2/test/v21/test_indicator.py @@ -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()) + ")" diff --git a/stix2/test/v21/test_observed_data.py b/stix2/test/v21/test_observed_data.py index 0074bf7..2910ba1 100644 --- a/stix2/test/v21/test_observed_data.py +++ b/stix2/test/v21/test_observed_data.py @@ -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", diff --git a/stix2/test/v21/test_properties.py b/stix2/test/v21/test_properties.py index 1fb3cc4..50bce17 100644 --- a/stix2/test/v21/test_properties.py +++ b/stix2/test/v21/test_properties.py @@ -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): diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py index 0d27bb4..fdb61f4 100644 --- a/stix2/v21/observables.py +++ b/stix2/v21/observables.py @@ -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. diff --git a/stix2/v21/sdo.py b/stix2/v21/sdo.py index 7cd7891..6f7b52d 100644 --- a/stix2/v21/sdo.py +++ b/stix2/v21/sdo.py @@ -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()