2017-01-17 21:37:47 +01:00
|
|
|
"""Tests for the stix2 library"""
|
|
|
|
|
2017-01-17 22:58:19 +01:00
|
|
|
import datetime
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
import pytz
|
|
|
|
|
2017-01-17 21:37:47 +01:00
|
|
|
import stix2
|
|
|
|
|
2017-01-17 22:58:19 +01:00
|
|
|
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
|
|
|
|
|
2017-01-17 21:37:47 +01:00
|
|
|
|
2017-01-18 20:21:46 +01:00
|
|
|
INDICATOR_ID = "indicator--01234567-89ab-cdef-0123-456789abcdef"
|
|
|
|
MALWARE_ID = "malware--fedcba98-7654-3210-fedc-ba9876543210"
|
|
|
|
RELATIONSHIP_ID = "relationship--00000000-1111-2222-3333-444444444444"
|
|
|
|
|
2017-01-18 18:31:33 +01:00
|
|
|
# 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",
|
|
|
|
)
|
|
|
|
|
2017-01-18 19:59:28 +01:00
|
|
|
# Minimum required args for a Relationship instance
|
|
|
|
RELATIONSHIP_KWARGS = dict(
|
|
|
|
relationship_type="indicates",
|
2017-01-18 20:21:46 +01:00
|
|
|
source_ref=INDICATOR_ID,
|
|
|
|
target_ref=MALWARE_ID,
|
2017-01-18 19:59:28 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2017-01-18 18:31:33 +01:00
|
|
|
@pytest.fixture
|
|
|
|
def indicator():
|
2017-01-18 20:21:46 +01:00
|
|
|
return stix2.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
2017-01-18 18:31:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def malware():
|
2017-01-18 20:21:46 +01:00
|
|
|
return stix2.Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
2017-01-18 19:59:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def relationship():
|
|
|
|
return stix2.Relationship(**RELATIONSHIP_KWARGS)
|
2017-01-18 18:31:33 +01:00
|
|
|
|
|
|
|
|
2017-01-18 01:53:27 +01:00
|
|
|
EXPECTED_INDICATOR = """{
|
2017-01-17 22:58:19 +01:00
|
|
|
"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",
|
2017-01-18 20:21:46 +01:00
|
|
|
id=INDICATOR_ID,
|
2017-01-17 22:58:19 +01:00
|
|
|
labels=['malicious-activity'],
|
|
|
|
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
2017-01-17 23:09:20 +01:00
|
|
|
created=now,
|
|
|
|
modified=now,
|
2017-01-17 22:58:19 +01:00
|
|
|
valid_from=epoch,
|
|
|
|
)
|
|
|
|
|
2017-01-18 01:53:27 +01:00
|
|
|
assert str(indicator) == EXPECTED_INDICATOR
|
2017-01-17 23:09:20 +01:00
|
|
|
|
|
|
|
|
2017-01-18 18:31:33 +01:00
|
|
|
def test_indicator_autogenerated_fields(indicator):
|
2017-01-17 23:09:20 +01:00
|
|
|
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
|
2017-01-17 23:46:00 +01:00
|
|
|
|
2017-01-18 00:03:56 +01:00
|
|
|
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
|
|
|
|
|
2017-01-17 23:46:00 +01:00
|
|
|
|
|
|
|
def test_indicator_type_must_be_indicator():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
indicator = stix2.Indicator(type='xxx')
|
|
|
|
|
2017-01-18 02:25:40 +01:00
|
|
|
assert "Indicator must have type='indicator'." in str(excinfo)
|
2017-01-17 23:46:00 +01:00
|
|
|
|
|
|
|
|
|
|
|
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)
|
2017-01-17 23:52:56 +01:00
|
|
|
|
|
|
|
|
|
|
|
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)
|
2017-01-18 00:52:03 +01:00
|
|
|
|
|
|
|
|
2017-01-18 19:59:28 +01:00
|
|
|
def test_cannot_assign_to_indicator_attributes(indicator):
|
2017-01-18 00:52:03 +01:00
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
indicator.valid_from = datetime.datetime.now()
|
|
|
|
|
|
|
|
assert "Cannot modify properties after creation." in str(excinfo)
|
2017-01-18 01:53:27 +01:00
|
|
|
|
|
|
|
|
2017-01-18 02:25:40 +01:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2017-01-18 01:53:27 +01:00
|
|
|
EXPECTED_MALWARE = """{
|
|
|
|
"created": "2016-05-12T08:17:27Z",
|
2017-01-18 20:21:46 +01:00
|
|
|
"id": "malware--fedcba98-7654-3210-fedc-ba9876543210",
|
2017-01-18 01:53:27 +01:00
|
|
|
"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",
|
2017-01-18 20:21:46 +01:00
|
|
|
id=MALWARE_ID,
|
2017-01-18 01:53:27 +01:00
|
|
|
created=now,
|
|
|
|
modified=now,
|
|
|
|
labels=["ransomware"],
|
|
|
|
name="Cryptolocker",
|
|
|
|
)
|
|
|
|
|
|
|
|
assert str(malware) == EXPECTED_MALWARE
|
|
|
|
|
|
|
|
|
2017-01-18 18:31:33 +01:00
|
|
|
def test_malware_autogenerated_fields(malware):
|
2017-01-18 01:53:27 +01:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2017-01-18 19:59:28 +01:00
|
|
|
def test_cannot_assign_to_malware_attributes(malware):
|
2017-01-18 01:53:27 +01:00
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
malware.name = "Cryptolocker II"
|
|
|
|
|
|
|
|
assert "Cannot modify properties after creation." in str(excinfo)
|
2017-01-18 19:59:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_invalid_kwarg_to_malware():
|
|
|
|
with pytest.raises(TypeError) as excinfo:
|
|
|
|
malware = stix2.Malware(my_custom_property="foo", **MALWARE_KWARGS)
|
|
|
|
assert "unexpected keyword arguments: ['my_custom_property']" in str(excinfo)
|
|
|
|
|
|
|
|
|
|
|
|
EXPECTED_RELATIONSHIP = """{
|
|
|
|
"created": "2016-04-06T20:06:37Z",
|
2017-01-18 20:21:46 +01:00
|
|
|
"id": "relationship--00000000-1111-2222-3333-444444444444",
|
2017-01-18 19:59:28 +01:00
|
|
|
"modified": "2016-04-06T20:06:37Z",
|
|
|
|
"relationship_type": "indicates",
|
|
|
|
"source_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
|
|
|
"target_ref": "malware--fedcba98-7654-3210-fedc-ba9876543210",
|
|
|
|
"type": "relationship"
|
|
|
|
}"""
|
|
|
|
|
|
|
|
|
2017-01-18 20:21:46 +01:00
|
|
|
def test_relationship_all_required_fields():
|
2017-01-18 19:59:28 +01:00
|
|
|
now = datetime.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
|
|
|
|
|
|
|
relationship = stix2.Relationship(
|
|
|
|
type='relationship',
|
2017-01-18 20:21:46 +01:00
|
|
|
id=RELATIONSHIP_ID,
|
2017-01-18 19:59:28 +01:00
|
|
|
created=now,
|
|
|
|
modified=now,
|
|
|
|
relationship_type='indicates',
|
2017-01-18 20:21:46 +01:00
|
|
|
source_ref=INDICATOR_ID,
|
|
|
|
target_ref=MALWARE_ID,
|
2017-01-18 19:59:28 +01:00
|
|
|
)
|
|
|
|
assert str(relationship) == EXPECTED_RELATIONSHIP
|
|
|
|
|
|
|
|
|
|
|
|
def test_relationship_autogenerated_fields(relationship):
|
|
|
|
assert relationship.type == 'relationship'
|
|
|
|
assert relationship.id.startswith('relationship--')
|
|
|
|
assert relationship.created is not None
|
|
|
|
assert relationship.modified is not None
|
|
|
|
assert relationship.relationship_type == 'indicates'
|
2017-01-18 20:21:46 +01:00
|
|
|
assert relationship.source_ref == INDICATOR_ID
|
|
|
|
assert relationship.target_ref == MALWARE_ID
|
2017-01-18 19:59:28 +01:00
|
|
|
|
|
|
|
assert relationship['type'] == 'relationship'
|
|
|
|
assert relationship['id'].startswith('relationship--')
|
|
|
|
assert relationship['created'] is not None
|
|
|
|
assert relationship['modified'] is not None
|
|
|
|
assert relationship['relationship_type'] == 'indicates'
|
2017-01-18 20:21:46 +01:00
|
|
|
assert relationship['source_ref'] == INDICATOR_ID
|
|
|
|
assert relationship['target_ref'] == MALWARE_ID
|
2017-01-18 19:59:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_relationship_type_must_be_relationship():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
relationship = stix2.Relationship(type='xxx')
|
|
|
|
|
|
|
|
assert "Relationship must have type='relationship'." in str(excinfo)
|
|
|
|
|
|
|
|
|
|
|
|
def test_relationship_id_must_start_with_relationship():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
relationship = stix2.Relationship(id='my-prefix--')
|
|
|
|
|
|
|
|
assert "Relationship id values must begin with 'relationship--'." in str(excinfo)
|
|
|
|
|
|
|
|
|
|
|
|
def test_relationship_required_field_relationship_type():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
relationship = stix2.Relationship()
|
|
|
|
assert "Missing required field for Relationship: 'relationship_type'." in str(excinfo)
|
|
|
|
|
|
|
|
|
|
|
|
def test_relationship_required_field_source_ref():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
# relationship_type is checked first, so make sure that is provided
|
|
|
|
relationship = stix2.Relationship(relationship_type='indicates')
|
|
|
|
assert "Missing required field for Relationship: 'source_ref'." in str(excinfo)
|
|
|
|
|
|
|
|
|
|
|
|
def test_relationship_required_field_target_ref():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
# relationship_type and source_ref are checked first, so make sure those are provided
|
|
|
|
relationship = stix2.Relationship(
|
|
|
|
relationship_type='indicates',
|
2017-01-18 20:21:46 +01:00
|
|
|
source_ref=INDICATOR_ID
|
2017-01-18 19:59:28 +01:00
|
|
|
)
|
|
|
|
assert "Missing required field for Relationship: 'target_ref'." in str(excinfo)
|
|
|
|
|
|
|
|
|
|
|
|
def test_cannot_assign_to_relationship_attributes(relationship):
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
relationship.relationship_type = "derived-from"
|
|
|
|
|
|
|
|
assert "Cannot modify properties after creation." in str(excinfo)
|
|
|
|
|
|
|
|
|
|
|
|
def test_invalid_kwarg_to_relationship():
|
|
|
|
with pytest.raises(TypeError) as excinfo:
|
|
|
|
relationship = stix2.Relationship(my_custom_property="foo", **RELATIONSHIP_KWARGS)
|
|
|
|
assert "unexpected keyword arguments: ['my_custom_property']" in str(excinfo)
|
2017-01-19 00:14:22 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_create_relationship_from_objects_rather_than_ids(indicator, malware):
|
|
|
|
relationship = stix2.Relationship(
|
|
|
|
relationship_type="indicates",
|
|
|
|
source_ref=indicator,
|
|
|
|
target_ref=malware,
|
|
|
|
)
|
|
|
|
|
|
|
|
assert relationship.relationship_type == 'indicates'
|
|
|
|
assert relationship.source_ref == INDICATOR_ID
|
|
|
|
assert relationship.target_ref == MALWARE_ID
|
2017-01-19 00:14:56 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_create_relationship_with_positional_args(indicator, malware):
|
|
|
|
relationship = stix2.Relationship(indicator, 'indicates', malware)
|
|
|
|
|
|
|
|
assert relationship.relationship_type == 'indicates'
|
|
|
|
assert relationship.source_ref == INDICATOR_ID
|
|
|
|
assert relationship.target_ref == MALWARE_ID
|