"""Tests for the stix2 library""" import datetime import pytest import pytz import stix2 amsterdam = pytz.timezone('Europe/Amsterdam') eastern = pytz.timezone('US/Eastern') @pytest.mark.parametrize('dt,timestamp', [ (datetime.datetime(2017, 1, 1, tzinfo=pytz.utc), '2017-01-01T00:00:00Z'), (amsterdam.localize(datetime.datetime(2017, 1, 1)), '2016-12-31T23:00:00Z'), (eastern.localize(datetime.datetime(2017, 1, 1, 12, 34, 56)), '2017-01-01T17:34:56Z'), (eastern.localize(datetime.datetime(2017, 7, 1)), '2017-07-01T04:00:00Z'), ]) def test_timestamp_formatting(dt, timestamp): assert stix2.format_datetime(dt) == timestamp # Minimum required args for an Indicator instance INDICATOR_KWARGS = dict( labels=['malicious-activity'], pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", ) # Minimum required args for a Malware instance MALWARE_KWARGS = dict( labels=['ransomware'], name="Cryptolocker", ) @pytest.fixture def indicator(): return stix2.Indicator(**INDICATOR_KWARGS) @pytest.fixture def malware(): return stix2.Malware(**MALWARE_KWARGS) EXPECTED_INDICATOR = """{ "created": "2017-01-01T00:00:00Z", "id": "indicator--01234567-89ab-cdef-0123-456789abcdef", "labels": [ "malicious-activity" ], "modified": "2017-01-01T00:00:00Z", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "type": "indicator", "valid_from": "1970-01-01T00:00:00Z" }""" def test_indicator_with_all_required_fields(): now = datetime.datetime(2017, 1, 1, 0, 0, 0, tzinfo=pytz.utc) epoch = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=pytz.utc) indicator = stix2.Indicator( type="indicator", id="indicator--01234567-89ab-cdef-0123-456789abcdef", labels=['malicious-activity'], pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", created=now, modified=now, valid_from=epoch, ) assert str(indicator) == EXPECTED_INDICATOR def test_indicator_autogenerated_fields(indicator): assert indicator.type == 'indicator' assert indicator.id.startswith('indicator--') assert indicator.created is not None assert indicator.modified is not None assert indicator.labels == ['malicious-activity'] assert indicator.pattern == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']" assert indicator.valid_from is not None assert indicator['type'] == 'indicator' assert indicator['id'].startswith('indicator--') assert indicator['created'] is not None assert indicator['modified'] is not None assert indicator['labels'] == ['malicious-activity'] assert indicator['pattern'] == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']" assert indicator['valid_from'] is not None def test_indicator_type_must_be_indicator(): with pytest.raises(ValueError) as excinfo: indicator = stix2.Indicator(type='xxx') assert "Indicator must have type='indicator'." in str(excinfo) def test_indicator_id_must_start_with_indicator(): with pytest.raises(ValueError) as excinfo: indicator = stix2.Indicator(id='my-prefix--') assert "Indicator id values must begin with 'indicator--'." in str(excinfo) def test_indicator_required_field_labels(): with pytest.raises(ValueError) as excinfo: indicator = stix2.Indicator() assert "Missing required field for Indicator: 'labels'." in str(excinfo) def test_indicator_required_field_pattern(): with pytest.raises(ValueError) as excinfo: # Label is checked first, so make sure that is provided indicator = stix2.Indicator(labels=['malicious-activity']) assert "Missing required field for Indicator: 'pattern'." in str(excinfo) def test_cannot_assign_to_attributes(indicator): with pytest.raises(ValueError) as excinfo: indicator.valid_from = datetime.datetime.now() assert "Cannot modify properties after creation." in str(excinfo) def test_invalid_kwarg_to_indicator(): with pytest.raises(TypeError) as excinfo: indicator = stix2.Indicator(my_custom_property="foo", **INDICATOR_KWARGS) assert "unexpected keyword arguments: ['my_custom_property']" in str(excinfo) EXPECTED_MALWARE = """{ "created": "2016-05-12T08:17:27Z", "id": "malware--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", "labels": [ "ransomware" ], "modified": "2016-05-12T08:17:27Z", "name": "Cryptolocker", "type": "malware" }""" def test_malware_with_all_required_fields(): now = datetime.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc) malware = stix2.Malware( type="malware", id="malware--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", created=now, modified=now, labels=["ransomware"], name="Cryptolocker", ) assert str(malware) == EXPECTED_MALWARE def test_malware_autogenerated_fields(malware): assert malware.type == 'malware' assert malware.id.startswith('malware--') assert malware.created is not None assert malware.modified is not None assert malware.labels == ['ransomware'] assert malware.name == "Cryptolocker" assert malware['type'] == 'malware' assert malware['id'].startswith('malware--') assert malware['created'] is not None assert malware['modified'] is not None assert malware['labels'] == ['ransomware'] assert malware['name'] == "Cryptolocker" def test_malware_type_must_be_malware(): with pytest.raises(ValueError) as excinfo: malware = stix2.Malware(type='xxx') assert "Malware must have type='malware'." in str(excinfo) def test_malware_id_must_start_with_malware(): with pytest.raises(ValueError) as excinfo: malware = stix2.Malware(id='my-prefix--') assert "Malware id values must begin with 'malware--'." in str(excinfo) def test_malware_required_field_labels(): with pytest.raises(ValueError) as excinfo: malware = stix2.Malware() assert "Missing required field for Malware: 'labels'." in str(excinfo) def test_malware_required_field_name(): with pytest.raises(ValueError) as excinfo: # Label is checked first, so make sure that is provided malware = stix2.Malware(labels=['ransomware']) assert "Missing required field for Malware: 'name'." in str(excinfo) def test_cannot_assign_to_attributes(malware): with pytest.raises(ValueError) as excinfo: malware.name = "Cryptolocker II" assert "Cannot modify properties after creation." in str(excinfo)