import io import json import pytest import stix2 from ...exceptions import InvalidValueError from .constants import IDENTITY_ID EXPECTED_BUNDLE = """{ "type": "bundle", "id": "bundle--00000000-0000-4000-8000-000000000007", "spec_version": "2.0", "objects": [ { "type": "indicator", "id": "indicator--00000000-0000-4000-8000-000000000001", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "valid_from": "2017-01-01T12:34:56Z", "labels": [ "malicious-activity" ] }, { "type": "malware", "id": "malware--00000000-0000-4000-8000-000000000003", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "name": "Cryptolocker", "labels": [ "ransomware" ] }, { "type": "relationship", "id": "relationship--00000000-0000-4000-8000-000000000005", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "relationship_type": "indicates", "source_ref": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7", "target_ref": "malware--9c4638ec-f1de-4ddb-abf4-1b760417654e" } ] }""" EXPECTED_BUNDLE_DICT = { "type": "bundle", "id": "bundle--00000000-0000-4000-8000-000000000007", "spec_version": "2.0", "objects": [ { "type": "indicator", "id": "indicator--00000000-0000-4000-8000-000000000001", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "valid_from": "2017-01-01T12:34:56Z", "labels": [ "malicious-activity", ], }, { "type": "malware", "id": "malware--00000000-0000-4000-8000-000000000003", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "name": "Cryptolocker", "labels": [ "ransomware", ], }, { "type": "relationship", "id": "relationship--00000000-0000-4000-8000-000000000005", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "relationship_type": "indicates", "source_ref": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7", "target_ref": "malware--9c4638ec-f1de-4ddb-abf4-1b760417654e", }, ], } def test_empty_bundle(): bundle = stix2.v20.Bundle() assert bundle.type == "bundle" assert bundle.id.startswith("bundle--") with pytest.raises(AttributeError): assert bundle.objects def test_bundle_with_wrong_type(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v20.Bundle(type="not-a-bundle") assert excinfo.value.cls == stix2.v20.Bundle assert excinfo.value.prop_name == "type" assert excinfo.value.reason == "must equal 'bundle'." assert str(excinfo.value) == "Invalid value for Bundle 'type': must equal 'bundle'." def test_bundle_id_must_start_with_bundle(): with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v20.Bundle(id='my-prefix--') assert excinfo.value.cls == stix2.v20.Bundle assert excinfo.value.prop_name == "id" assert excinfo.value.reason == "must start with 'bundle--'." assert str(excinfo.value) == "Invalid value for Bundle 'id': must start with 'bundle--'." def test_create_bundle_fp_serialize_pretty(indicator, malware, relationship): bundle = stix2.v20.Bundle(objects=[indicator, malware, relationship]) buffer = io.StringIO() bundle.fp_serialize(buffer, pretty=True) assert bundle.serialize(pretty=True) == EXPECTED_BUNDLE assert buffer.getvalue() == EXPECTED_BUNDLE def test_create_bundle_fp_serialize_nonpretty(indicator, malware, relationship): bundle = stix2.v20.Bundle(objects=[indicator, malware, relationship]) buffer = io.StringIO() bundle.fp_serialize(buffer, sort_keys=True) assert bundle.serialize(sort_keys=True) == json.dumps(json.loads(EXPECTED_BUNDLE), sort_keys=True) assert buffer.getvalue() == json.dumps(json.loads(EXPECTED_BUNDLE), sort_keys=True) def test_create_bundle1(indicator, malware, relationship): bundle = stix2.v20.Bundle(objects=[indicator, malware, relationship]) assert bundle.serialize(pretty=True) == EXPECTED_BUNDLE assert bundle.serialize(pretty=True) == EXPECTED_BUNDLE def test_create_bundle2(indicator, malware, relationship): bundle = stix2.v20.Bundle(objects=[indicator, malware, relationship]) assert json.loads(bundle.serialize()) == EXPECTED_BUNDLE_DICT def test_create_bundle_with_positional_args(indicator, malware, relationship): bundle = stix2.v20.Bundle(indicator, malware, relationship) assert bundle.serialize(pretty=True) == EXPECTED_BUNDLE def test_create_bundle_with_positional_listarg(indicator, malware, relationship): bundle = stix2.v20.Bundle([indicator, malware, relationship]) assert bundle.serialize(pretty=True) == EXPECTED_BUNDLE def test_create_bundle_with_listarg_and_positional_arg(indicator, malware, relationship): bundle = stix2.v20.Bundle([indicator, malware], relationship) assert bundle.serialize(pretty=True) == EXPECTED_BUNDLE def test_create_bundle_with_listarg_and_kwarg(indicator, malware, relationship): bundle = stix2.v20.Bundle([indicator, malware], objects=[relationship]) assert bundle.serialize(pretty=True) == EXPECTED_BUNDLE def test_create_bundle_with_arg_listarg_and_kwarg(indicator, malware, relationship): bundle = stix2.v20.Bundle([indicator], malware, objects=[relationship]) assert bundle.serialize(pretty=True) == EXPECTED_BUNDLE def test_create_bundle_invalid(indicator, malware, relationship): with pytest.raises(InvalidValueError) as excinfo: stix2.v20.Bundle(objects=[1]) assert excinfo.value.reason == "This property may only contain a dictionary or object" with pytest.raises(InvalidValueError) as excinfo: stix2.v20.Bundle(objects=[{}]) assert excinfo.value.reason == "This property may only contain a non-empty dictionary or object" with pytest.raises(InvalidValueError) as excinfo: stix2.v20.Bundle(objects=[{'type': 'bundle'}]) assert excinfo.value.reason == 'This property may not contain a Bundle object' @pytest.mark.parametrize("version", ["2.0"]) def test_parse_bundle(version): bundle = stix2.parse(EXPECTED_BUNDLE, version=version) assert bundle.type == "bundle" assert bundle.id.startswith("bundle--") assert isinstance(bundle.objects[0], stix2.v20.Indicator) assert bundle.objects[0].type == 'indicator' assert bundle.objects[1].type == 'malware' assert bundle.objects[2].type == 'relationship' def test_parse_unknown_type(): unknown = { "type": "other", "id": "other--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", "created": "2016-04-06T20:03:00Z", "modified": "2016-04-06T20:03:00Z", "created_by_ref": IDENTITY_ID, "description": "Campaign by Green Group against a series of targets in the financial services sector.", "name": "Green Group Attacks Against Finance", } with pytest.raises(stix2.exceptions.ParseError) as excinfo: stix2.parse(unknown, version="2.0") assert str(excinfo.value) == "Can't parse unknown object type 'other'! For custom types, use the CustomObject decorator." def test_stix_object_property(): prop = stix2.properties.STIXObjectProperty(spec_version='2.0') identity = stix2.v20.Identity(name="test", identity_class="individual") assert prop.clean(identity, False) == (identity, False) def test_bundle_with_different_spec_objects(): # This is a 2.0 case only... data = [ { "spec_version": "2.1", "type": "indicator", "id": "indicator--00000000-0000-4000-8000-000000000001", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "valid_from": "2017-01-01T12:34:56Z", "labels": [ "malicious-activity", ], }, { "type": "malware", "id": "malware--00000000-0000-4000-8000-000000000003", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "name": "Cryptolocker", "labels": [ "ransomware", ], }, ] with pytest.raises(InvalidValueError) as excinfo: stix2.v20.Bundle(objects=data) assert "Spec version 2.0 bundles don't yet support containing objects of a different spec version." in str(excinfo.value) def test_bundle_obj_id_found(): bundle = stix2.parse(EXPECTED_BUNDLE) mal_list = bundle.get_obj("malware--00000000-0000-4000-8000-000000000003") assert bundle.objects[1] == mal_list[0] assert len(mal_list) == 1 @pytest.mark.parametrize( "bundle_data", [{ "type": "bundle", "id": "bundle--00000000-0000-4000-8000-000000000007", "spec_version": "2.0", "objects": [ { "type": "indicator", "id": "indicator--00000000-0000-4000-8000-000000000001", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", "valid_from": "2017-01-01T12:34:56Z", "labels": [ "malicious-activity", ], }, { "type": "malware", "id": "malware--00000000-0000-4000-8000-000000000003", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "name": "Cryptolocker1", "labels": [ "ransomware", ], }, { "type": "malware", "id": "malware--00000000-0000-4000-8000-000000000003", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-12-21T12:34:56.000Z", "name": "CryptolockerOne", "labels": [ "ransomware", ], }, { "type": "relationship", "id": "relationship--00000000-0000-4000-8000-000000000005", "created": "2017-01-01T12:34:56.000Z", "modified": "2017-01-01T12:34:56.000Z", "relationship_type": "indicates", "source_ref": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7", "target_ref": "malware--9c4638ec-f1de-4ddb-abf4-1b760417654e", }, ], }], ) def test_bundle_objs_ids_found(bundle_data): bundle = stix2.parse(bundle_data) mal_list = bundle.get_obj("malware--00000000-0000-4000-8000-000000000003") assert bundle.objects[1] == mal_list[0] assert bundle.objects[2] == mal_list[1] assert len(mal_list) == 2 def test_bundle_getitem_overload_property_found(): bundle = stix2.parse(EXPECTED_BUNDLE) assert bundle.type == "bundle" assert bundle['type'] == "bundle" def test_bundle_getitem_overload_obj_id_found(): bundle = stix2.parse(EXPECTED_BUNDLE) mal_list = bundle["malware--00000000-0000-4000-8000-000000000003"] assert bundle.objects[1] == mal_list[0] assert len(mal_list) == 1 def test_bundle_obj_id_not_found(): bundle = stix2.parse(EXPECTED_BUNDLE) with pytest.raises(KeyError) as excinfo: bundle.get_obj('non existent') assert "does not match the id property of any of the bundle" in str(excinfo.value) def test_bundle_getitem_overload_obj_id_not_found(): bundle = stix2.parse(EXPECTED_BUNDLE) with pytest.raises(KeyError) as excinfo: bundle['non existent'] assert "neither a property on the bundle nor does it match the id property" in str(excinfo.value)