import datetime as dt import re import pytest import pytz import stix2 from .constants import FAKE_TIME, INDICATOR_ID, INDICATOR_KWARGS EXPECTED_INDICATOR = """{ "type": "indicator", "spec_version": "2.1", "id": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7", "created": "2017-01-01T00:00:01.000Z", "modified": "2017-01-01T00:00:01.000Z", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "pattern_type": "stix", "pattern_version": "2.1", "valid_from": "1970-01-01T00:00:01Z" }""" EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join(""" type='indicator', spec_version='2.1', id='indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7', created='2017-01-01T00:00:01.000Z', modified='2017-01-01T00:00:01.000Z', pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", pattern_type='stix', pattern_version='2.1', valid_from='1970-01-01T00:00:01Z' """.split()) + ")" def test_indicator_with_all_required_properties(): now = dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc) epoch = dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc) ind = stix2.v21.Indicator( type="indicator", id=INDICATOR_ID, created=now, modified=now, pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", pattern_type="stix", valid_from=epoch, ) assert ind.revoked is False assert str(ind) == EXPECTED_INDICATOR rep = re.sub(r"(\[|=| )u('|\"|\\\'|\\\")", r"\g<1>\g<2>", repr(ind)) assert rep == EXPECTED_INDICATOR_REPR def test_indicator_autogenerated_properties(indicator): assert indicator.type == 'indicator' assert indicator.spec_version == '2.1' assert indicator.id == 'indicator--00000000-0000-4000-8000-000000000001' assert indicator.created == FAKE_TIME assert indicator.modified == FAKE_TIME assert indicator.indicator_types == ['malicious-activity'] assert indicator.pattern == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']" assert indicator.valid_from == FAKE_TIME assert indicator['type'] == 'indicator' assert indicator['spec_version'] == '2.1' assert indicator['id'] == 'indicator--00000000-0000-4000-8000-000000000001' assert indicator['created'] == FAKE_TIME assert indicator['modified'] == FAKE_TIME assert indicator['indicator_types'] == ['malicious-activity'] assert indicator['pattern'] == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']" assert indicator['valid_from'] == FAKE_TIME def test_indicator_type_must_be_indicator(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Indicator(type='xxx', **INDICATOR_KWARGS) assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.prop_name == "type" assert excinfo.value.reason == "must equal 'indicator'." assert str(excinfo.value) == "Invalid value for Indicator 'type': must equal 'indicator'." def test_indicator_id_must_start_with_indicator(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Indicator(id='my-prefix--', **INDICATOR_KWARGS) assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.prop_name == "id" assert excinfo.value.reason == "must start with 'indicator--'." assert str(excinfo.value) == "Invalid value for Indicator 'id': must start with 'indicator--'." def test_indicator_required_properties(): with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo: stix2.v21.Indicator() assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.properties == ["pattern", "pattern_type"] assert str(excinfo.value) == "No values for required properties for Indicator: (pattern, pattern_type)." def test_indicator_required_property_pattern(): with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo: stix2.v21.Indicator(indicator_types=['malicious-activity']) assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.properties == ["pattern", "pattern_type"] def test_indicator_created_ref_invalid_format(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Indicator(created_by_ref='myprefix--12345678', **INDICATOR_KWARGS) assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.prop_name == "created_by_ref" def test_indicator_revoked_invalid(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Indicator(revoked='no', **INDICATOR_KWARGS) assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.prop_name == "revoked" assert excinfo.value.reason == "must be a boolean value." def test_cannot_assign_to_indicator_attributes(indicator): with pytest.raises(stix2.exceptions.ImmutableError) as excinfo: indicator.valid_from = dt.datetime.now() assert str(excinfo.value) == "Cannot modify 'valid_from' property in 'Indicator' after creation." def test_invalid_kwarg_to_indicator(): with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo: stix2.v21.Indicator(my_custom_property="foo", **INDICATOR_KWARGS) assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.properties == ['my_custom_property'] assert str(excinfo.value) == "Unexpected properties for Indicator: (my_custom_property)." def test_created_modified_time_are_identical_by_default(): """By default, the created and modified times should be the same.""" ind = stix2.v21.Indicator(**INDICATOR_KWARGS) assert ind.created == ind.modified @pytest.mark.parametrize( "data", [ EXPECTED_INDICATOR, { "type": "indicator", "id": INDICATOR_ID, "created": "2017-01-01T00:00:01Z", "modified": "2017-01-01T00:00:01Z", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "pattern_type": "stix", "valid_from": "1970-01-01T00:00:01Z", }, ], ) def test_parse_indicator(data): idctr = stix2.parse(data, version="2.1") assert idctr.type == 'indicator' assert idctr.spec_version == '2.1' assert idctr.id == INDICATOR_ID assert idctr.created == dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc) assert idctr.modified == dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc) assert idctr.valid_from == dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc) assert idctr.pattern == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']" def test_invalid_indicator_pattern(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Indicator( indicator_types=['malicious-activity'], pattern="file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e'", pattern_type="stix", valid_from="2017-01-01T12:34:56Z", ) assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.prop_name == 'pattern' assert 'input is missing square brackets' in excinfo.value.reason with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Indicator( indicator_types=['malicious-activity'], pattern='[file:hashes.MD5 = "d41d8cd98f00b204e9800998ecf8427e"]', pattern_type="stix", valid_from="2017-01-01T12:34:56Z", ) assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.prop_name == 'pattern' assert 'mismatched input' in excinfo.value.reason def test_indicator_with_custom_embedded_objs(): now = dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc) epoch = dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc) ext_ref = stix2.v21.ExternalReference( source_name="Test", description="Example Custom Ext Ref", random_custom_prop="This is a custom property", allow_custom=True, ) ind = stix2.v21.Indicator( type="indicator", id=INDICATOR_ID, created=now, modified=now, pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", pattern_type="stix", valid_from=epoch, indicator_types=['malicious-activity'], external_references=[ext_ref], ) assert ind.indicator_types == ['malicious-activity'] assert len(ind.external_references) == 1 assert ind.external_references[0] == ext_ref def test_indicator_with_custom_embed_objs_extra_props_error(): ext_ref = stix2.v21.ExternalReference( source_name="Test", description="Example Custom Ext Ref", random_custom_prop="This is a custom property", allow_custom=True, ) with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo: stix2.v21.Indicator(external_references=[ext_ref], bad_custom_prop="shouldn't be here", **INDICATOR_KWARGS) assert excinfo.value.cls == stix2.v21.Indicator assert excinfo.value.properties == ['bad_custom_prop'] assert str(excinfo.value) == "Unexpected properties for Indicator: (bad_custom_prop)." def test_indicator_stix20_invalid_pattern(): now = dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc) epoch = dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc) patrn = "[win-registry-key:key = 'hkey_local_machine\\\\foo\\\\bar'] WITHIN 5 SECONDS WITHIN 6 SECONDS" with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Indicator( type="indicator", id=INDICATOR_ID, created=now, modified=now, pattern=patrn, pattern_type="stix", valid_from=epoch, indicator_types=['malicious-activity'], ) assert excinfo.value.cls == stix2.v21.Indicator assert "FAIL: Duplicate qualifier type encountered: WITHIN" in str(excinfo.value) ind = stix2.v21.Indicator( type="indicator", id=INDICATOR_ID, created=now, modified=now, pattern=patrn, pattern_type="stix", pattern_version="2.0", valid_from=epoch, indicator_types=['malicious-activity'], ) assert ind.id == INDICATOR_ID assert ind.indicator_types == ['malicious-activity'] assert ind.pattern == patrn assert ind.pattern_type == "stix" assert ind.pattern_version == "2.0"